Compare commits
	
		
			633 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|   | 15c391e763 | ||
|   | 3b3584714c | ||
|   | 61a0244cfe | ||
|   | 73a61933c5 | ||
|   | 72c7407b0b | ||
|   | 6538282a7b | ||
|   | 87326e3292 | ||
|   | 6eadb116c2 | ||
|   | c9ffd2564e | ||
|   | cd6e5f7905 | ||
|   | b96e988f20 | ||
|   | 30f7fa6afa | ||
|   | 4605b19473 | ||
|   | 87327053dc | ||
|   | 5e7d5c0235 | ||
|   | 1ccc56100c | ||
|   | ef948bca76 | ||
|   | d01a964534 | ||
|   | 05004cb141 | ||
|   | dfcef390d0 | ||
|   | ff3b41686c | ||
|   | 7dd5082bac | ||
|   | ec05e81286 | ||
|   | e7a7d391e4 | ||
|   | e6b1ef30a5 | ||
|   | f40b053e5b | ||
|   | 1a891cb048 | ||
|   | 66007c07e2 | ||
|   | 5058a814aa | ||
|   | 37d8244414 | ||
|   | cda5112f5e | ||
|   | 919f10cc11 | ||
|   | 66b12800e4 | ||
|   | 227856b45b | ||
|   | 44ca529027 | ||
|   | 885e3b60f0 | ||
|   | 07bbf057d0 | ||
|   | b9227caaf8 | ||
|   | 55ed67eaed | ||
|   | ac55385696 | ||
|   | f9b93e8c85 | ||
|   | ac7f33a29c | ||
|   | 4de99f66a8 | ||
|   | c0c1d519bf | ||
| ![dependabot[bot]](/assets/img/avatar_default.png)  | f22980f4ce | ||
|   | 7b297e824c | ||
|   | 28bd0e3cc2 | ||
|   | f90c009782 | ||
|   | a54913f788 | ||
|   | ea3dca79aa | ||
|   | 2b207597b9 | ||
|   | 044bb79ae9 | ||
|   | afcebb46a0 | ||
|   | 10427ddd65 | ||
|   | 0ac2b9df7a | ||
|   | 695aacbe14 | ||
|   | 71107fa42f | ||
|   | d36dfc5490 | ||
|   | a463f237b1 | ||
|   | ec105ede83 | ||
| ![dependabot[bot]](/assets/img/avatar_default.png)  | 92aff95b3a | ||
|   | 1fcc345a7a | ||
|   | 7e9a09c960 | ||
|   | 8212ff117b | ||
|   | b5a1ffe338 | ||
|   | 759382bcfb | ||
|   | 12eeb6503c | ||
|   | 9fa9772c07 | ||
|   | 35334c1650 | ||
|   | 006c27fee5 | ||
|   | 271be81557 | ||
|   | b61d8aaabc | ||
|   | 319afd2d51 | ||
|   | bd2facebee | ||
|   | 3cbaa052c8 | ||
|   | a0ceb62372 | ||
|   | dddd190911 | ||
|   | f70b0049d9 | ||
|   | 6fea06729e | ||
|   | 24e6fe89c8 | ||
|   | e507611cad | ||
|   | 72dcdbad1e | ||
|   | 0a55dec949 | ||
|   | 555a32c273 | ||
|   | fdf2a9d247 | ||
|   | 30bb64d058 | ||
|   | 695d4b4a16 | ||
|   | 01c08a8ef9 | ||
|   | dd9f2edf40 | ||
|   | 105c249d97 | ||
|   | b780df96af | ||
|   | 46495937b1 | ||
|   | 597c9ea4c0 | ||
|   | 4662f4703c | ||
|   | 5f750e6f49 | ||
|   | 152ba81c34 | ||
|   | 4bf76aacb1 | ||
|   | 6c4c73e825 | ||
|   | 917851dcd7 | ||
|   | 2e5bf4202c | ||
|   | d4319fa55c | ||
|   | 2ff7726442 | ||
|   | 38bf54ec3b | ||
|   | a1c76600e2 | ||
|   | fbcb3da422 | ||
|   | 3978fb3e46 | ||
|   | 64f80a95ab | ||
|   | 847c14ddda | ||
|   | 38c0d290e7 | ||
|   | c403eb55b1 | ||
|   | 506d78cb00 | ||
|   | 27918e2dc5 | ||
|   | d44231a0ff | ||
|   | 11a58fde91 | ||
|   | 3a8c3b5816 | ||
|   | 50deda27ca | ||
|   | 4482fa23e6 | ||
|   | 4c83a3bf36 | ||
|   | 514538179e | ||
|   | 4c8245045d | ||
|   | efe185e5dc | ||
|   | 62becf819d | ||
|   | f02f32456e | ||
|   | 57916ed6d7 | ||
|   | efa3d06673 | ||
|   | 14f7435f82 | ||
|   | dbeaaa49f3 | ||
|   | 7546391c17 | ||
|   | d8d2ee0f46 | ||
|   | 3c57fa76bf | ||
|   | 3fa4a240f7 | ||
|   | ae38239b47 | ||
|   | 6a63b2e5c4 | ||
|   | 3ead633343 | ||
|   | e71f765f2a | ||
|   | 2b7f68f3fe | ||
|   | c82e20efd7 | ||
|   | aa37e3885c | ||
|   | 7918023322 | ||
|   | 80304937e1 | ||
|   | 352978b54d | ||
|   | b7cda1d2f1 | ||
|   | 20dbd6c181 | ||
|   | 8808e1b0c3 | ||
|   | 47dda4ac9f | ||
|   | b1d40960c4 | ||
|   | 5222caecf9 | ||
|   | bc8d4cbb0f | ||
|   | 0bb31f6caf | ||
|   | 03c8e4d3cb | ||
|   | 68ad653545 | ||
|   | fd5b34d644 | ||
|   | 000096bc26 | ||
|   | 02023102d3 | ||
|   | 95bc3d9855 | ||
|   | f9fb16198b | ||
|   | 11ae05163c | ||
|   | 0275716ff5 | ||
|   | e9bdca5279 | ||
|   | cd84116a03 | ||
|   | 457effae85 | ||
|   | 9361c358f1 | ||
|   | 39f952e460 | ||
|   | 7c600a357c | ||
|   | 8f036a0c3d | ||
|   | 1184ae2b52 | ||
|   | de7ef19034 | ||
|   | e91b7caf4f | ||
|   | 033b3c10c0 | ||
|   | 97774c88d5 | ||
|   | 95e1c73ced | ||
|   | d2a067db77 | ||
|   | 586dee9e70 | ||
|   | 6a717b2517 | ||
|   | 203af988fa | ||
|   | 911166152a | ||
|   | b3ecddb20f | ||
|   | a41d4e55aa | ||
|   | c7a3ef7d72 | ||
|   | b7495a63e9 | ||
|   | 6b3093aa09 | ||
|   | 8f8369c989 | ||
|   | f79293be69 | ||
|   | 4631da2212 | ||
|   | e2a7c51de0 | ||
|   | 633e26b1de | ||
|   | 08992f83b5 | ||
|   | 921e886e71 | ||
|   | 9fc4cad80e | ||
|   | 4fdb81642a | ||
| ![dependabot[bot]](/assets/img/avatar_default.png)  | 056c771d2e | ||
|   | 84183bdfeb | ||
|   | 4e786fee31 | ||
|   | 9f4e5050dd | ||
|   | bb49a610a2 | ||
|   | 5e3e580f51 | ||
|   | 75fa498a7c | ||
|   | 05e5f06cf1 | ||
|   | 3a17f4f29f | ||
|   | 5e7524e4f8 | ||
|   | b2e17d4c42 | ||
| ![dependabot[bot]](/assets/img/avatar_default.png)  | b88303d36a | ||
|   | 5a5b0e3a1b | ||
|   | e504e55a34 | ||
|   | b6758ff92d | ||
|   | 957499e4d7 | ||
|   | 3773d64aa7 | ||
|   | d89f823bef | ||
|   | a8b7c3d8c5 | ||
|   | 9a45ce3110 | ||
|   | 3ad3b180af | ||
|   | 6919ce7b5c | ||
|   | e7a9c3814b | ||
|   | 488eb319a1 | ||
|   | 4f650cad9d | ||
| ![dependabot[bot]](/assets/img/avatar_default.png)  | ce9c2e6d3c | ||
|   | 90a5e741fd | ||
|   | bfedd02d1d | ||
|   | 0187d2f5c1 | ||
|   | 7d968348f5 | ||
|   | 317017a2b4 | ||
|   | 35ca52620c | ||
|   | 01ed60fcb7 | ||
|   | 80599a3576 | ||
|   | d40b2734a9 | ||
|   | 6abd313bd2 | ||
|   | 88f573e2a6 | ||
|   | eb55e16465 | ||
|   | bb48964cca | ||
|   | cbdf1ad7b7 | ||
|   | df77ac4318 | ||
|   | b3caa343d2 | ||
|   | 49f4f316f8 | ||
|   | 2207711400 | ||
|   | 763f450f43 | ||
|   | 0fa11213bb | ||
|   | c743775a1e | ||
|   | b854aa3bfe | ||
|   | 8dfba82a70 | ||
|   | 824051e89e | ||
|   | ab45cc27d5 | ||
|   | 627e467fdf | ||
|   | ca3b4b02e0 | ||
|   | 34ac685e65 | ||
|   | 7a66e71215 | ||
|   | 1e96a1ae84 | ||
|   | ce0342e235 | ||
|   | a48ea497b0 | ||
|   | 4b3c9419f3 | ||
|   | f51b09dc40 | ||
|   | 800f9e2d38 | ||
|   | b5093e4d93 | ||
|   | 10684f9aac | ||
|   | 7c0327c1b8 | ||
|   | 9f5a03419e | ||
|   | ec843f2396 | ||
|   | 2d6925ac4f | ||
|   | 7cbaa4f63d | ||
|   | ebb9e7a2a0 | ||
|   | 84e44127b3 | ||
|   | 0b66bb5eff | ||
|   | e70ec0e978 | ||
|   | d62f566bb3 | ||
|   | 4e1c3be03a | ||
|   | 792e96e95f | ||
|   | c1acd8f5f0 | ||
|   | d64e1713fb | ||
|   | 3e6fefe1b7 | ||
|   | 48e3ef3e73 | ||
|   | c76c24e102 | ||
|   | 4b25dafb92 | ||
|   | c8237d59be | ||
|   | c2629e3945 | ||
|   | b5864ca3a3 | ||
|   | b84c983727 | ||
|   | aa3e8783ae | ||
|   | f65f0d86aa | ||
|   | 93b03c7212 | ||
|   | c96b999b2c | ||
|   | da155fadf2 | ||
|   | c69a1c8ec2 | ||
|   | 83ac2f1ff7 | ||
|   | f77972fa38 | ||
|   | d7c08295f8 | ||
|   | be6b7157f4 | ||
|   | b979c2574c | ||
|   | f7c76c012f | ||
|   | 13abe8cb66 | ||
|   | 7596099aa1 | ||
|   | 626782102a | ||
|   | 3e068a0020 | ||
|   | 09af1e9cdf | ||
|   | 5ac9298db0 | ||
|   | 0d084861e0 | ||
|   | c052887d58 | ||
|   | f5cd45438b | ||
|   | a0a1cc410f | ||
|   | 74cfc11a37 | ||
|   | 53600900f2 | ||
|   | ce19107c95 | ||
|   | 7c56ed7b16 | ||
|   | 152cc76e3f | ||
|   | 8fd4060562 | ||
|   | c2f5908a9d | ||
|   | 57aa2aba74 | ||
|   | d851aeed45 | ||
|   | 03d338d6c9 | ||
|   | a74cdcc061 | ||
|   | 4fca9aeb48 | ||
|   | 205e32dde8 | ||
|   | d065558865 | ||
|   | 950c6b9d08 | ||
|   | d20d5946f2 | ||
|   | 72bc99bddc | ||
|   | 8c4016792b | ||
|   | 2975cf6d5a | ||
|   | 73d93d6104 | ||
|   | 7452cc19e0 | ||
|   | efd545882f | ||
|   | b19cdd9339 | ||
|   | da54794aca | ||
|   | a8df6d0668 | ||
|   | 1f65d2243e | ||
|   | 36db30471c | ||
|   | 80e22ffc82 | ||
|   | 872581fa4b | ||
|   | aeabed70db | ||
|   | d443103e2c | ||
|   | c1332235a0 | ||
|   | 212c8634ea | ||
|   | b966ab3e11 | ||
|   | 25c2246782 | ||
|   | 2ae1db4730 | ||
|   | 43652b2a54 | ||
|   | 574a504ec3 | ||
|   | 52a8331c78 | ||
|   | afa9eecf35 | ||
|   | 46e13d754f | ||
|   | 0157326d61 | ||
|   | 7d08875f95 | ||
|   | bd2a49e299 | ||
|   | 06f1d03b93 | ||
|   | 4c39955f2f | ||
|   | 56f92310f8 | ||
|   | cb47806fd6 | ||
|   | 16ae193e9f | ||
|   | 8770939328 | ||
|   | d5bff6473e | ||
|   | a1c0408aae | ||
|   | ab523b6879 | ||
|   | 740b4c52d2 | ||
|   | dbb94942df | ||
|   | 1e4ea2f8c3 | ||
|   | 8083837333 | ||
|   | dd89933613 | ||
|   | 5101dace59 | ||
|   | 81c56a740c | ||
|   | 27cc3bcccd | ||
|   | 8ddaa26a57 | ||
|   | 3a848428c3 | ||
|   | 4c18f3aa1d | ||
|   | be6278c0b7 | ||
|   | d5e08d5db2 | ||
|   | 2d321b4a79 | ||
|   | fcd764d521 | ||
|   | 7d4a8782d8 | ||
|   | eea28e8481 | ||
|   | 71d679d426 | ||
|   | 2b23523bec | ||
|   | 149c26728c | ||
|   | 8c5c2440fe | ||
|   | f22caed20c | ||
|   | ed3e7e7c26 | ||
|   | 54bc33a1a2 | ||
|   | 900e141297 | ||
|   | ca1eca476c | ||
|   | c4d2593a42 | ||
|   | 932811c167 | ||
|   | 95ad101a08 | ||
|   | a0c329019f | ||
|   | 8a92913230 | ||
|   | 5a8deddc63 | ||
|   | 2a1e60da44 | ||
|   | 6ca3deeac1 | ||
|   | 1b2edbb79a | ||
|   | 49c9929a53 | ||
|   | 8ce73a38dd | ||
|   | 28ebf7a0cc | ||
|   | 4f168656fc | ||
|   | 7f1f758e60 | ||
|   | f8ab71953d | ||
|   | eb50ee1c03 | ||
|   | a184cb9bc4 | ||
|   | 0b50a10fb8 | ||
|   | 9bafdc1b0b | ||
|   | 63af54353b | ||
|   | b67739af13 | ||
|   | 40633619ca | ||
|   | e672d1d896 | ||
|   | c69d162f5a | ||
|   | 2fd97b1753 | ||
|   | 5f893929da | ||
|   | 1c06b7b608 | ||
|   | 3ac64d65b6 | ||
|   | f4f83283d3 | ||
|   | aed4a55655 | ||
|   | 5f55a56a9e | ||
|   | e95e79b572 | ||
|   | 15c0d0074b | ||
|   | f4716cb62f | ||
|   | 1f2dfce7d1 | ||
|   | c679631440 | ||
|   | c62fd4841a | ||
|   | a073113817 | ||
|   | 09d462b829 | ||
|   | 379b171f0a | ||
|   | c424e22924 | ||
|   | 18e171446e | ||
|   | c0a1555c73 | ||
|   | 3b0c844f33 | ||
|   | 566a4f3568 | ||
|   | 2c8b19bff2 | ||
|   | 292dca6419 | ||
|   | ebe0f98209 | ||
|   | eba1e5495f | ||
|   | 0ebd04f012 | ||
|   | c5e8a3fa04 | ||
|   | 42b76fd82d | ||
|   | bbf610569d | ||
|   | eeb5fa81a1 | ||
|   | f65ca6678f | ||
|   | 680f48dcba | ||
|   | 8947e00302 | ||
|   | b42cf32326 | ||
|   | 5b68332b9a | ||
|   | d7f53862e9 | ||
|   | 7887bf96de | ||
|   | db2aa35b2f | ||
|   | 936b0e59ab | ||
|   | 6e3923d0da | ||
|   | 4377575206 | ||
|   | 0c09b12680 | ||
|   | 8a32d8032f | ||
|   | 9f12a91f4a | ||
|   | dd0ca4bcaf | ||
|   | 14cc97eb44 | ||
|   | 2d31c5b005 | ||
|   | 05e7506f61 | ||
|   | 37806f1113 | ||
|   | 79622f324b | ||
|   | 34e84081e3 | ||
|   | edf07be281 | ||
|   | 1208514f37 | ||
|   | bfd857ec4b | ||
|   | 9435290bb6 | ||
|   | acf64dcf25 | ||
|   | 2dd88364f8 | ||
|   | d900b52a50 | ||
|   | bd8d96be4d | ||
|   | 17dd07fe32 | ||
|   | a06e5f28b3 | ||
|   | d5f400670c | ||
|   | 5222388ba1 | ||
|   | 549ccbe6a0 | ||
|   | 7fd6afb17f | ||
|   | 2ff4da5e46 | ||
|   | 0f6e98be20 | ||
|   | 4399ffa9a4 | ||
|   | 44a1d9d16c | ||
|   | 565fd1c605 | ||
|   | 09ac40846f | ||
|   | a0b820649e | ||
|   | 6cee4bfe7c | ||
|   | ad0c859b54 | ||
|   | d660b7d315 | ||
|   | 2dbc7f28fd | ||
|   | e224bc4b24 | ||
|   | 9c52977825 | ||
|   | cb151dc985 | ||
|   | 24b9f8f705 | ||
|   | 5592063e69 | ||
|   | 7b19aab305 | ||
|   | 5c50695bdc | ||
|   | 1086597e3a | ||
|   | 2944ea508e | ||
|   | 5e887bd00c | ||
|   | 76b3cf4d2b | ||
|   | 4a9895218e | ||
|   | 222593ea69 | ||
|   | 05b7fd1f63 | ||
|   | 256c04f3b8 | ||
|   | 02577ef746 | ||
|   | 059c3a0b80 | ||
|   | 5ce1b1591a | ||
|   | 6595ad6228 | ||
|   | e7608673d7 | ||
|   | 4c8fc1defa | ||
|   | 975663f0ff | ||
|   | 9603389586 | ||
|   | cd79cac0af | ||
|   | d151e98fab | ||
|   | 6115ffe0fc | ||
|   | b646c5385d | ||
|   | 04a1cff37e | ||
|   | a5be9ca226 | ||
|   | 0356bdbf54 | ||
|   | 4c276ff383 | ||
|   | f8432ba521 | ||
|   | 7f75ea06c2 | ||
|   | 11d3e98155 | ||
|   | 01185306f2 | ||
|   | 6ff9c4335f | ||
|   | b2e38c320d | ||
|   | c63897ded4 | ||
|   | d6dcb59ab7 | ||
|   | bd1ced0af7 | ||
|   | 1bbf501783 | ||
|   | b16308b7ea | ||
|   | 42531dae5a | ||
|   | aeb9135cde | ||
|   | f092927ab8 | ||
|   | 3ac467fc53 | ||
|   | b9e1de8446 | ||
|   | 245bbf651b | ||
|   | a8b83cf4ed | ||
|   | 38725097a6 | ||
|   | e935fa128f | ||
|   | ef16934952 | ||
|   | 550872a564 | ||
|   | e746a959af | ||
|   | ab0f4380b2 | ||
|   | 7c236ccc3a | ||
|   | 52030dbea0 | ||
|   | 4e20ec1c9b | ||
|   | 0ddbdfeac9 | ||
|   | a5fd55e90e | ||
|   | 844a6c11b4 | ||
|   | a4cab09b62 | ||
|   | 12d010351a | ||
|   | d00e5b0421 | ||
|   | 4c2fb7095d | ||
|   | cca1406f0f | ||
|   | 1bba2664b6 | ||
|   | cc51eaef3f | ||
|   | 2bd289defa | ||
|   | 47d090d481 | ||
|   | 4a4c2b24dd | ||
|   | 8603d4347b | ||
|   | 188a3e0ac5 | ||
|   | a22efc90f6 | ||
|   | d0d76c023f | ||
|   | 241a4e62ac | ||
|   | 295f2a5cf2 | ||
|   | e3cf522565 | ||
|   | 0f1107314f | ||
|   | 18c5c99705 | ||
|   | 18b4c98daa | ||
|   | 24246da71c | ||
|   | 49ab26200d | ||
|   | 5c524f0d23 | ||
|   | 15d7d74c1b | ||
|   | 29979b23c2 | ||
|   | 3de223144d | ||
|   | 18029d8369 | ||
|   | fb62ac17e5 | ||
|   | dbe70ecc28 | ||
|   | 74b6af64e9 | ||
|   | fa83199d7b | ||
|   | 750a2a445e | ||
|   | 6fb1e690aa | ||
|   | 77c0eb99f0 | ||
|   | db64cf02bd | ||
|   | 57a6417812 | ||
|   | 12beef49b5 | ||
|   | 155363afa6 | ||
|   | 3b3fad7278 | ||
|   | b6c4c4539f | ||
|   | 807302f6cd | ||
|   | 8902111c23 | ||
|   | e45ef455ef | ||
|   | 0eb25d8413 | ||
|   | a0e4c266a1 | ||
|   | e452dfdb1f | ||
|   | e3b542665d | ||
|   | a481a8b322 | ||
|   | 1b5387dc5a | ||
|   | 281cf48aaa | ||
|   | ce2b92ee01 | ||
|   | daba12ee42 | ||
|   | 363a67b0f2 | ||
|   | 1a1a14719d | ||
|   | 5706de5ca7 | ||
|   | 947293f34e | ||
|   | 04a76fd94d | ||
|   | c629fa9575 | ||
|   | d4c1caa26a | ||
|   | 22d9bdacf6 | ||
|   | 64a954df53 | ||
|   | 1949be2490 | ||
|   | 8be00dad7f | ||
|   | bf9a37623a | ||
|   | 3de0ae1b0f | ||
|   | 57a2c2616b | ||
|   | c63967158b | ||
|   | 8902fdc78a | ||
|   | 1c5d01b11c | ||
|   | c1c324af23 | ||
|   | db74d010d7 | ||
|   | c9d36d84f7 | ||
|   | d5f446d7cf | ||
|   | a2fcdabb7b | ||
|   | a434bbbc12 | ||
|   | 2585801f8a | ||
|   | c501d9fefe | ||
|   | 1d99559d4c | ||
|   | 1d5797fe68 | ||
|   | bbe08ed218 | ||
|   | 6e12eba356 | ||
|   | 3457b99df6 | ||
|   | 4ad3b82c84 | ||
|   | 85fc07c900 | ||
|   | d71e2a74b4 | ||
|   | a18105349b | ||
|   | 6472227b6b | ||
|   | c927132aa6 | ||
|   | b06058ec18 | ||
|   | 57329a26c8 | ||
|   | 4a1aa84fa8 | ||
|   | cbe9c83515 | ||
|   | 67479e7060 | ||
|   | b454c959b4 | ||
|   | e2d39b9ed0 | ||
|   | b9fbcb0e73 | 
							
								
								
									
										34
									
								
								.github/workflows/build-test.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								.github/workflows/build-test.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,34 @@ | |||||||
|  | on: | ||||||
|  |   pull_request: | ||||||
|  |     branches: | ||||||
|  |       - dev | ||||||
|  |   push: | ||||||
|  |     branches: | ||||||
|  |       - dev | ||||||
|  |  | ||||||
|  | name: Build Test | ||||||
|  |  | ||||||
|  | jobs: | ||||||
|  |   build-linux-binary: | ||||||
|  |     runs-on: ubuntu-latest | ||||||
|  |     steps: | ||||||
|  |       - name: Checkout Code | ||||||
|  |         uses: actions/checkout@v3 | ||||||
|  |       - name: Setup Node | ||||||
|  |         uses: actions/setup-node@v3 | ||||||
|  |         with: | ||||||
|  |           node-version: '18.14' | ||||||
|  |       - name: Build Web | ||||||
|  |         id: build_frontend | ||||||
|  |         run: | | ||||||
|  |           cd frontend && npm install && npm run build:pro | ||||||
|  |         env: | ||||||
|  |           NODE_OPTIONS: --max-old-space-size=8192 | ||||||
|  |       - name: Setup Go | ||||||
|  |         uses: actions/setup-go@v4 | ||||||
|  |         with: | ||||||
|  |           go-version: '1.20.x' | ||||||
|  |       - name: Build Server | ||||||
|  |         uses: goreleaser/goreleaser-action@v4 | ||||||
|  |         with: | ||||||
|  |           args: release --snapshot --clean | ||||||
							
								
								
									
										66
									
								
								.github/workflows/release-drafter.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										66
									
								
								.github/workflows/release-drafter.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,66 @@ | |||||||
|  | on: | ||||||
|  |   push: | ||||||
|  |     # Sequence of patterns matched against refs/tags | ||||||
|  |     tags: | ||||||
|  |       - 'v*' # Push events to matching v*, i.e. v1.0, v20.15.10 | ||||||
|  |  | ||||||
|  | name: Create Release And Upload assets | ||||||
|  |  | ||||||
|  | jobs: | ||||||
|  |   create-release: | ||||||
|  |     runs-on: ubuntu-latest | ||||||
|  |     steps: | ||||||
|  |       - name: Checkout Code | ||||||
|  |         uses: actions/checkout@v2 | ||||||
|  |       - name: Setup Node | ||||||
|  |         uses: actions/setup-node@v3 | ||||||
|  |         with: | ||||||
|  |           node-version: '18.14' | ||||||
|  |       - name: Build Web | ||||||
|  |         run: | | ||||||
|  |           cd frontend && npm install && npm run build:pro | ||||||
|  |         env: | ||||||
|  |           NODE_OPTIONS: --max-old-space-size=8192 | ||||||
|  |       - name: Setup Go | ||||||
|  |         uses: actions/setup-go@v4 | ||||||
|  |         with: | ||||||
|  |           go-version: '1.20.x' | ||||||
|  |       - name: Build Release | ||||||
|  |         uses: goreleaser/goreleaser-action@v4 | ||||||
|  |         with: | ||||||
|  |           distribution: goreleaser | ||||||
|  |           version: latest | ||||||
|  |           args: release --skip-publish --clean | ||||||
|  |       - name: Upload Assets | ||||||
|  |         uses: softprops/action-gh-release@v1 | ||||||
|  |         if: startsWith(github.ref, 'refs/tags/') | ||||||
|  |         with: | ||||||
|  |           draft: true | ||||||
|  |           body: | | ||||||
|  |             # 一、安装和升级 | ||||||
|  |  | ||||||
|  |             ## 1.1 一键安装 | ||||||
|  |             ```sh | ||||||
|  |             curl -sSL https://resource.fit2cloud.com/1panel/package/quick_start.sh -o quick_start.sh && sudo bash quick_start.sh | ||||||
|  |             ``` | ||||||
|  |  | ||||||
|  |             ## 1.2 在线升级 | ||||||
|  |  | ||||||
|  |             登录 1Panel Web 控制台,在页面右下角点击 **【检查更新】** 进行在线升级。 | ||||||
|  |  | ||||||
|  |             >更多信息请查阅在线文档:https://1panel.cn/docs/ | ||||||
|  |  | ||||||
|  |             # 二、更新日志 | ||||||
|  |  | ||||||
|  |           files: | | ||||||
|  |             dist/*.tar.gz | ||||||
|  |             dist/checksums.txt | ||||||
|  |       - name: Setup OSSUTIL | ||||||
|  |         uses: yizhoumo/setup-ossutil@v1 | ||||||
|  |         with: | ||||||
|  |           endpoint: ${{ secrets.OSS_ENDPOINT }} | ||||||
|  |           access-key-id: ${{ secrets.OSS_ACCESS_KEY_ID }} | ||||||
|  |           access-key-secret: ${{ secrets.OSS_ACCESS_KEY_SECRET }} | ||||||
|  |           ossutil-version: '1.7.14' | ||||||
|  |       - name: Upload Assets to OSS | ||||||
|  |         run: ossutil cp -r dist/ oss://resource-fit2cloud-com/1panel/package/stable/${{  github.ref_name }}/release/ --include "*.tar.gz" --include "checksums.txt" --only-current-dir --force | ||||||
							
								
								
									
										10
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										10
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @@ -23,3 +23,13 @@ cmd/server/web/assets | |||||||
| cmd/server/web/monacoeditorwork | cmd/server/web/monacoeditorwork | ||||||
| cmd/server/web/index.html | cmd/server/web/index.html | ||||||
| frontend/auto-imports.d.ts | frontend/auto-imports.d.ts | ||||||
|  | frontend/components.d.ts | ||||||
|  |  | ||||||
|  | .history/ | ||||||
|  | dist/ | ||||||
|  | 1pctl | ||||||
|  | 1panel.service | ||||||
|  | install.sh | ||||||
|  | quick_start.sh | ||||||
|  | cmd/server/web/.DS_Store | ||||||
|  | cmd/server/.DS_Store | ||||||
|   | |||||||
							
								
								
									
										58
									
								
								.goreleaser.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										58
									
								
								.goreleaser.yaml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,58 @@ | |||||||
|  | # This is an example .goreleaser.yml file with some sensible defaults. | ||||||
|  | # Make sure to check the documentation at https://goreleaser.com | ||||||
|  | before: | ||||||
|  |   hooks: | ||||||
|  |     # - export NODE_OPTIONS="--max-old-space-size=8192" | ||||||
|  |     # - make build_web | ||||||
|  |     - chmod +x ./script.sh | ||||||
|  |     - ./script.sh | ||||||
|  |     - sed -i 's@ORIGINAL_VERSION=.*@ORIGINAL_VERSION=v{{ .Version }}@g' 1pctl | ||||||
|  |     - go mod tidy | ||||||
|  |      | ||||||
|  | builds: | ||||||
|  |   - main: ./cmd/server/main.go | ||||||
|  |     binary: 1panel | ||||||
|  |     flags: | ||||||
|  |       - -trimpath | ||||||
|  |     ldflags: | ||||||
|  |       - -w -s | ||||||
|  |     env: | ||||||
|  |       - CGO_ENABLED=0 | ||||||
|  |     goos: | ||||||
|  |       - linux | ||||||
|  |     goarm: | ||||||
|  |       - 7 | ||||||
|  |     goarch: | ||||||
|  |       - amd64 | ||||||
|  |       - arm64 | ||||||
|  |       - arm | ||||||
|  |       - ppc64le | ||||||
|  |       - s390x | ||||||
|  |  | ||||||
|  | archives: | ||||||
|  |   - format: tar.gz | ||||||
|  |     name_template: "1panel-v{{ .Version }}-{{ .Os }}-{{ .Arch }}{{- if .Arm }}v{{ .Arm }}{{ end }}" | ||||||
|  |     wrap_in_directory: true | ||||||
|  |     files: | ||||||
|  |       - 1pctl | ||||||
|  |       - 1panel.service | ||||||
|  |       - install.sh | ||||||
|  |       - README.md | ||||||
|  |       - LICENSE | ||||||
|  |      | ||||||
|  | checksum: | ||||||
|  |   name_template: 'checksums.txt' | ||||||
|  | snapshot: | ||||||
|  |   name_template: "{{ incpatch .Version }}-next" | ||||||
|  | release: | ||||||
|  |   draft: true | ||||||
|  |   mode: append | ||||||
|  |   extra_files: | ||||||
|  |     - glob: dist/*.tar.gz | ||||||
|  |     - glob: dist/checksums.txt | ||||||
|  |   name_template: "Release {{.Tag}}" | ||||||
|  |  | ||||||
|  | # The lines beneath this are called `modelines`. See `:help modeline` | ||||||
|  | # Feel free to remove those if you don't want/use them. | ||||||
|  | # yaml-language-server: $schema=https://goreleaser.com/static/schema.json | ||||||
|  | # vim: set ts=2 sw=2 tw=0 fo=cnqoj | ||||||
							
								
								
									
										3
									
								
								.vscode/settings.json
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								.vscode/settings.json
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,3 @@ | |||||||
|  | { | ||||||
|  |     "ansible.python.interpreterPath": "/opt/homebrew/bin/python3" | ||||||
|  | } | ||||||
							
								
								
									
										27
									
								
								Makefile
									
									
									
									
									
								
							
							
						
						
									
										27
									
								
								Makefile
									
									
									
									
									
								
							| @@ -10,16 +10,29 @@ WEB_PATH=$(BASE_PAH)/frontend | |||||||
| SERVER_PATH=$(BASE_PAH)/backend | SERVER_PATH=$(BASE_PAH)/backend | ||||||
| MAIN= $(BASE_PAH)/cmd/server/main.go | MAIN= $(BASE_PAH)/cmd/server/main.go | ||||||
| APP_NAME=1panel | APP_NAME=1panel | ||||||
|  | ASSERT_PATH= $(BASE_PAH)/cmd/server/web/assets | ||||||
|  |  | ||||||
| build_web: | clean_assets: | ||||||
| 	cd $(WEB_PATH) && npm install && npm run build:dev | 	rm -rf $(ASSERT_PATH) | ||||||
|  |  | ||||||
| build_bin: | upx_bin: | ||||||
|  | 	upx $(BUILD_PATH)/$(APP_NAME) | ||||||
|  |  | ||||||
|  | build_frontend: | ||||||
|  | 	cd $(WEB_PATH) && npm install && npm run build:pro | ||||||
|  |  | ||||||
|  | build_backend_on_linux: | ||||||
| 	cd $(SERVER_PATH) \ | 	cd $(SERVER_PATH) \ | ||||||
|     && CGO_ENABLED=1 GOOS=$(GOOS) GOARCH=$(GOARCH) $(GOBUILD) -trimpath -ldflags '-s -w --extldflags "-static -fpic"' -tags osusergo -o $(BUILD_PATH)/$(APP_NAME) $(MAIN) |     && GOOS=$(GOOS) GOARCH=$(GOARCH) $(GOBUILD) -trimpath -ldflags '-s -w' -o $(BUILD_PATH)/$(APP_NAME) $(MAIN) | ||||||
|  |  | ||||||
| build_linux_on_mac: | build_backend_on_darwin: | ||||||
| 	cd $(SERVER_PATH) \ | 	cd $(SERVER_PATH) \ | ||||||
|     && CGO_ENABLED=1 GOOS=linux GOARCH=amd64 CC=x86_64-linux-musl-gcc CXX=x86_64-linux-musl-g++ $(GOBUILD) -trimpath -ldflags '-s -w --extldflags "-static -fpic"'  -o $(BUILD_PATH)/$(APP_NAME) $(MAIN) |     && GOOS=linux GOARCH=amd64 $(GOBUILD) -trimpath -ldflags '-s -w'  -o $(BUILD_PATH)/$(APP_NAME) $(MAIN) | ||||||
|  |  | ||||||
| build_all: build_web  build_bin | build_backend_on_archlinux: | ||||||
|  | 	cd $(SERVER_PATH) \ | ||||||
|  |     && GOOS=$(GOOS) GOARCH=$(GOARCH) $(GOBUILD) -trimpath -ldflags '-s -w' -o $(BUILD_PATH)/$(APP_NAME) $(MAIN) | ||||||
|  |  | ||||||
|  | build_all: build_frontend build_backend_on_linux | ||||||
|  |  | ||||||
|  | build_on_local: clean_assets build_frontend build_backend_on_darwin upx_bin | ||||||
|   | |||||||
							
								
								
									
										18
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										18
									
								
								README.md
									
									
									
									
									
								
							| @@ -6,6 +6,7 @@ | |||||||
|   <a href="https://app.codacy.com/gh/1Panel-dev/1Panel?utm_source=github.com&utm_medium=referral&utm_content=1Panel-dev/1Panel&utm_campaign=Badge_Grade_Dashboard"><img src="https://app.codacy.com/project/badge/Grade/da67574fd82b473992781d1386b937ef" alt="Codacy"></a> |   <a href="https://app.codacy.com/gh/1Panel-dev/1Panel?utm_source=github.com&utm_medium=referral&utm_content=1Panel-dev/1Panel&utm_campaign=Badge_Grade_Dashboard"><img src="https://app.codacy.com/project/badge/Grade/da67574fd82b473992781d1386b937ef" alt="Codacy"></a> | ||||||
|   <a href="https://github.com/1Panel-dev/1Panel/releases"><img src="https://img.shields.io/github/v/release/1Panel-dev/1Panel" alt="GitHub release"></a> |   <a href="https://github.com/1Panel-dev/1Panel/releases"><img src="https://img.shields.io/github/v/release/1Panel-dev/1Panel" alt="GitHub release"></a> | ||||||
|   <a href="https://github.com/1Panel-dev/1Panel"><img src="https://img.shields.io/github/stars/1Panel-dev/1Panel?color=%231890FF&style=flat-square" alt="Stars"></a> |   <a href="https://github.com/1Panel-dev/1Panel"><img src="https://img.shields.io/github/stars/1Panel-dev/1Panel?color=%231890FF&style=flat-square" alt="Stars"></a> | ||||||
|  |   <a href="https://app.fossa.com/projects/git%2Bgithub.com%2F1Panel-dev%2F1Panel?ref=badge_shield"><img src="https://app.fossa.com/api/projects/git%2Bgithub.com%2F1Panel-dev%2F1Panel.svg?type=shield" alt="FOSSA Status"></a> | ||||||
| </p> | </p> | ||||||
|  |  | ||||||
| ------------------------------ | ------------------------------ | ||||||
| @@ -13,9 +14,9 @@ | |||||||
| 1Panel 是一个现代化、开源的 Linux 服务器运维管理面板。1Panel 的功能和优势包括: | 1Panel 是一个现代化、开源的 Linux 服务器运维管理面板。1Panel 的功能和优势包括: | ||||||
|  |  | ||||||
| - **快速建站**:深度集成 Wordpress 和 [Halo](https://github.com/halo-dev/halo/),域名绑定、SSL 证书配置等一键搞定; | - **快速建站**:深度集成 Wordpress 和 [Halo](https://github.com/halo-dev/halo/),域名绑定、SSL 证书配置等一键搞定; | ||||||
| - **高效管理**:通过 Web 端轻松管理 Linux 服务器,包括应用管理、主机监控、文件管理、数据库管理、容器管理等; | - **高效管理**:通过 Web 端轻松管理 Linux 服务器,包括主机监控、文件管理、数据库管理、容器管理等; | ||||||
| - **安全可靠**:最小漏洞暴露面,提供防火墙和安全审计等功能; | - **安全可靠**:基于容器来管理和部署应用,最小漏洞暴露面,提供防火墙和日志审计等功能; | ||||||
| - **一键备份**:支持一键备份和恢复,备份数据云端存储,永不丢失。 | - **一键备份**:支持一键备份和恢复,备份数据到各类云端存储,永不丢失。 | ||||||
|  |  | ||||||
| ## UI 展示 | ## UI 展示 | ||||||
|  |  | ||||||
| @@ -41,12 +42,9 @@ curl -sSL https://resource.fit2cloud.com/1panel/package/quick_start.sh -o quick_ | |||||||
|  |  | ||||||
| - [在线文档](https://1panel.cn/docs/) | - [在线文档](https://1panel.cn/docs/) | ||||||
| - [教学视频](https://space.bilibili.com/510493147/channel/collectiondetail?sid=1199760) | - [教学视频](https://space.bilibili.com/510493147/channel/collectiondetail?sid=1199760) | ||||||
|  | - [社区论坛](https://bbs.fit2cloud.com/c/1p/7) | ||||||
|  |  | ||||||
| ## 社区 | **加入微信交流群** | ||||||
|  |  | ||||||
| 如果您在使用过程中有任何疑问或对建议,欢迎提交 GitHub Issue 或加入到我们微信交流群进行交流沟通。 |  | ||||||
|  |  | ||||||
| **微信交流群** |  | ||||||
|  |  | ||||||
| <img src="https://1panel.cn/img/wechat-group.jpg" width="156" height="156"/> | <img src="https://1panel.cn/img/wechat-group.jpg" width="156" height="156"/> | ||||||
|  |  | ||||||
| @@ -61,6 +59,10 @@ curl -sSL https://resource.fit2cloud.com/1panel/package/quick_start.sh -o quick_ | |||||||
|  |  | ||||||
| [](https://star-history.com/#1Panel-dev/1Panel&Date) | [](https://star-history.com/#1Panel-dev/1Panel&Date) | ||||||
|  |  | ||||||
|  | ## FOSSA Status | ||||||
|  |  | ||||||
|  | [](https://app.fossa.com/projects/git%2Bgithub.com%2F1Panel-dev%2F1Panel?ref=badge_large) | ||||||
|  |  | ||||||
| ## License | ## License | ||||||
|  |  | ||||||
| Copyright (c) 2014-2023 [FIT2CLOUD 飞致云](https://fit2cloud.com/), All rights reserved. | Copyright (c) 2014-2023 [FIT2CLOUD 飞致云](https://fit2cloud.com/), All rights reserved. | ||||||
|   | |||||||
| @@ -3,6 +3,7 @@ | |||||||
| 如果您发现安全问题,请直接联系我们: | 如果您发现安全问题,请直接联系我们: | ||||||
|  |  | ||||||
| - wanghe@fit2cloud.com | - wanghe@fit2cloud.com | ||||||
|  | - zhengkun@fit2cloud.com | ||||||
| - support@fit2cloud.com | - support@fit2cloud.com | ||||||
| - 400-052-0755 | - 400-052-0755 | ||||||
|  |  | ||||||
| @@ -13,6 +14,7 @@ | |||||||
| All security bugs should be reported to the contact as below: | All security bugs should be reported to the contact as below: | ||||||
|  |  | ||||||
| - wanghe@fit2cloud.com | - wanghe@fit2cloud.com | ||||||
|  | - zhengkun@fit2cloud.com | ||||||
| - support@fit2cloud.com | - support@fit2cloud.com | ||||||
| - 400-052-0755 | - 400-052-0755 | ||||||
|  |  | ||||||
|   | |||||||
| @@ -5,6 +5,7 @@ import ( | |||||||
| 	"github.com/1Panel-dev/1Panel/backend/app/dto/request" | 	"github.com/1Panel-dev/1Panel/backend/app/dto/request" | ||||||
| 	"github.com/1Panel-dev/1Panel/backend/constant" | 	"github.com/1Panel-dev/1Panel/backend/constant" | ||||||
| 	"github.com/1Panel-dev/1Panel/backend/global" | 	"github.com/1Panel-dev/1Panel/backend/global" | ||||||
|  | 	"github.com/1Panel-dev/1Panel/backend/i18n" | ||||||
| 	"github.com/gin-gonic/gin" | 	"github.com/gin-gonic/gin" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| @@ -38,13 +39,24 @@ func (b *BaseApi) SearchApp(c *gin.Context) { | |||||||
| // @Router /apps/sync [post] | // @Router /apps/sync [post] | ||||||
| // @x-panel-log {"bodyKeys":[],"paramKeys":[],"BeforeFuntions":[],"formatZH":"应用商店同步","formatEN":"App store synchronization"} | // @x-panel-log {"bodyKeys":[],"paramKeys":[],"BeforeFuntions":[],"formatZH":"应用商店同步","formatEN":"App store synchronization"} | ||||||
| func (b *BaseApi) SyncApp(c *gin.Context) { | func (b *BaseApi) SyncApp(c *gin.Context) { | ||||||
| 	global.LOG.Infof("sync app list start ...") | 	go appService.SyncAppListFromLocal() | ||||||
| 	if err := appService.SyncAppList(); err != nil { | 	res, err := appService.GetAppUpdate() | ||||||
| 		global.LOG.Errorf("sync app list error [%s]", err.Error()) | 	if err != nil { | ||||||
| 		helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err) | 		helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err) | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
| 	global.LOG.Infof("sync app list success!") | 	if !res.CanUpdate { | ||||||
|  | 		helper.SuccessWithMsg(c, i18n.GetMsgByKey("AppStoreIsUpToDate")) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	go func() { | ||||||
|  | 		global.LOG.Infof("sync app list start ...") | ||||||
|  | 		if err := appService.SyncAppListFromRemote(); err != nil { | ||||||
|  | 			global.LOG.Errorf("sync app list error [%s]", err.Error()) | ||||||
|  | 		} else { | ||||||
|  | 			global.LOG.Infof("sync app list success!") | ||||||
|  | 		} | ||||||
|  | 	}() | ||||||
| 	helper.SuccessWithData(c, "") | 	helper.SuccessWithData(c, "") | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -71,14 +83,15 @@ func (b *BaseApi) GetApp(c *gin.Context) { | |||||||
| } | } | ||||||
|  |  | ||||||
| // @Tags App | // @Tags App | ||||||
| // @Summary Search app detail by id | // @Summary Search app detail by appid | ||||||
| // @Description 通过 id 获取应用详情 | // @Description 通过 appid 获取应用详情 | ||||||
| // @Accept json | // @Accept json | ||||||
| // @Param appId path integer true "app id" | // @Param appId path integer true "app id" | ||||||
| // @Param version path string true "app 版本" | // @Param version path string true "app 版本" | ||||||
|  | // @Param version path string true "app 类型" | ||||||
| // @Success 200 {object} response.AppDetailDTO | // @Success 200 {object} response.AppDetailDTO | ||||||
| // @Security ApiKeyAuth | // @Security ApiKeyAuth | ||||||
| // @Router /apps/detail/:appId/:version [get] | // @Router /apps/detail/:appId/:version/:type [get] | ||||||
| func (b *BaseApi) GetAppDetail(c *gin.Context) { | func (b *BaseApi) GetAppDetail(c *gin.Context) { | ||||||
| 	appId, err := helper.GetIntParamByKey(c, "appId") | 	appId, err := helper.GetIntParamByKey(c, "appId") | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| @@ -86,7 +99,8 @@ func (b *BaseApi) GetAppDetail(c *gin.Context) { | |||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
| 	version := c.Param("version") | 	version := c.Param("version") | ||||||
| 	appDetailDTO, err := appService.GetAppDetail(appId, version) | 	appType := c.Param("type") | ||||||
|  | 	appDetailDTO, err := appService.GetAppDetail(appId, version, appType) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err) | 		helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err) | ||||||
| 		return | 		return | ||||||
| @@ -94,6 +108,44 @@ func (b *BaseApi) GetAppDetail(c *gin.Context) { | |||||||
| 	helper.SuccessWithData(c, appDetailDTO) | 	helper.SuccessWithData(c, appDetailDTO) | ||||||
| } | } | ||||||
|  |  | ||||||
|  | // @Tags App | ||||||
|  | // @Summary Get app detail by id | ||||||
|  | // @Description 通过 id 获取应用详情 | ||||||
|  | // @Accept json | ||||||
|  | // @Param appId path integer true "id" | ||||||
|  | // @Success 200 {object} response.AppDetailDTO | ||||||
|  | // @Security ApiKeyAuth | ||||||
|  | // @Router /apps/details/:id [get] | ||||||
|  | func (b *BaseApi) GetAppDetailByID(c *gin.Context) { | ||||||
|  | 	appDetailID, err := helper.GetIntParamByKey(c, "id") | ||||||
|  | 	if err != nil { | ||||||
|  | 		helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInternalServer, nil) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	appDetailDTO, err := appService.GetAppDetailByID(appDetailID) | ||||||
|  | 	if err != nil { | ||||||
|  | 		helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	helper.SuccessWithData(c, appDetailDTO) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // @Tags App | ||||||
|  | // @Summary Get Ignore App | ||||||
|  | // @Description 获取忽略的应用版本 | ||||||
|  | // @Accept json | ||||||
|  | // @Success 200 {object} response.IgnoredApp | ||||||
|  | // @Security ApiKeyAuth | ||||||
|  | // @Router /apps/ingored [get] | ||||||
|  | func (b *BaseApi) GetIgnoredApp(c *gin.Context) { | ||||||
|  | 	res, err := appService.GetIgnoredApp() | ||||||
|  | 	if err != nil { | ||||||
|  | 		helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	helper.SuccessWithData(c, res) | ||||||
|  | } | ||||||
|  |  | ||||||
| // @Tags App | // @Tags App | ||||||
| // @Summary Install app | // @Summary Install app | ||||||
| // @Description 安装应用 | // @Description 安装应用 | ||||||
| @@ -102,7 +154,7 @@ func (b *BaseApi) GetAppDetail(c *gin.Context) { | |||||||
| // @Success 200 {object} model.AppInstall | // @Success 200 {object} model.AppInstall | ||||||
| // @Security ApiKeyAuth | // @Security ApiKeyAuth | ||||||
| // @Router /apps/install [post] | // @Router /apps/install [post] | ||||||
| // @x-panel-log {"bodyKeys":["name"],"paramKeys":[],"BeforeFuntions":[{"input_colume":"name","input_value":"name","isList":false,"db":"app_installs","output_colume":"app_id","output_value":"appId"},{"info":"appId","isList":false,"db":"apps","output_colume":"key","output_value":"appKey"}],"formatZH":"安装应用 [appKey]-[name]","formatEN":"Install app [appKey]-[name]"} | // @x-panel-log {"bodyKeys":["name"],"paramKeys":[],"BeforeFuntions":[{"input_column":"name","input_value":"name","isList":false,"db":"app_installs","output_column":"app_id","output_value":"appId"},{"info":"appId","isList":false,"db":"apps","output_column":"key","output_value":"appKey"}],"formatZH":"安装应用 [appKey]-[name]","formatEN":"Install app [appKey]-[name]"} | ||||||
| func (b *BaseApi) InstallApp(c *gin.Context) { | func (b *BaseApi) InstallApp(c *gin.Context) { | ||||||
| 	var req request.AppInstallCreate | 	var req request.AppInstallCreate | ||||||
| 	if err := c.ShouldBindJSON(&req); err != nil { | 	if err := c.ShouldBindJSON(&req); err != nil { | ||||||
|   | |||||||
| @@ -93,24 +93,24 @@ func (b *BaseApi) LoadPort(c *gin.Context) { | |||||||
|  |  | ||||||
| // @Tags App | // @Tags App | ||||||
| // @Summary Search app password by key | // @Summary Search app password by key | ||||||
| // @Description 获取应用密码 | // @Description 获取应用连接信息 | ||||||
| // @Accept json | // @Accept json | ||||||
| // @Param key path string true "request" | // @Param key path string true "request" | ||||||
| // @Success 200 {string} password | // @Success 200 {string} response.DatabaseConn | ||||||
| // @Security ApiKeyAuth | // @Security ApiKeyAuth | ||||||
| // @Router /apps/installed/loadpassword/:key [get] | // @Router /apps/installed/conninfo/:key [get] | ||||||
| func (b *BaseApi) LoadPassword(c *gin.Context) { | func (b *BaseApi) LoadConnInfo(c *gin.Context) { | ||||||
| 	key, ok := c.Params.Get("key") | 	key, ok := c.Params.Get("key") | ||||||
| 	if !ok { | 	if !ok { | ||||||
| 		helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, errors.New("error key in path")) | 		helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, errors.New("error key in path")) | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
| 	password, err := appInstallService.LoadPassword(key) | 	conn, err := appInstallService.LoadConnInfo(key) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err) | 		helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err) | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
| 	helper.SuccessWithData(c, password) | 	helper.SuccessWithData(c, conn) | ||||||
| } | } | ||||||
|  |  | ||||||
| // @Tags App | // @Tags App | ||||||
| @@ -118,7 +118,7 @@ func (b *BaseApi) LoadPassword(c *gin.Context) { | |||||||
| // @Description 删除前检查 | // @Description 删除前检查 | ||||||
| // @Accept json | // @Accept json | ||||||
| // @Param appInstallId path integer true "App install id" | // @Param appInstallId path integer true "App install id" | ||||||
| // @Success 200 {anrry} dto.AppResource | // @Success 200 {array} dto.AppResource | ||||||
| // @Security ApiKeyAuth | // @Security ApiKeyAuth | ||||||
| // @Router /apps/installed/delete/check/:appInstallId [get] | // @Router /apps/installed/delete/check/:appInstallId [get] | ||||||
| func (b *BaseApi) DeleteCheck(c *gin.Context) { | func (b *BaseApi) DeleteCheck(c *gin.Context) { | ||||||
| @@ -159,7 +159,7 @@ func (b *BaseApi) SyncInstalled(c *gin.Context) { | |||||||
| // @Success 200 | // @Success 200 | ||||||
| // @Security ApiKeyAuth | // @Security ApiKeyAuth | ||||||
| // @Router /apps/installed/op [post] | // @Router /apps/installed/op [post] | ||||||
| // @x-panel-log {"bodyKeys":["installId","operate"],"paramKeys":[],"BeforeFuntions":[{"input_colume":"id","input_value":"installId","isList":false,"db":"app_installs","output_colume":"app_id","output_value":"appId"},{"input_colume":"id","input_value":"installId","isList":false,"db":"app_installs","output_colume":"name","output_value":"appName"},{"input_colume":"id","input_value":"appId","isList":false,"db":"apps","output_colume":"key","output_value":"appKey"}],"formatZH":"[appKey] 应用 [appName] [operate]","formatEN":"[appKey] App [appName] [operate]"} | // @x-panel-log {"bodyKeys":["installId","operate"],"paramKeys":[],"BeforeFuntions":[{"input_column":"id","input_value":"installId","isList":false,"db":"app_installs","output_column":"app_id","output_value":"appId"},{"input_column":"id","input_value":"installId","isList":false,"db":"app_installs","output_column":"name","output_value":"appName"},{"input_column":"id","input_value":"appId","isList":false,"db":"apps","output_column":"key","output_value":"appKey"}],"formatZH":"[operate] 应用 [appKey][appName]","formatEN":"[operate] App [appKey][appName]"} | ||||||
| func (b *BaseApi) OperateInstalled(c *gin.Context) { | func (b *BaseApi) OperateInstalled(c *gin.Context) { | ||||||
| 	var req request.AppInstalledOperate | 	var req request.AppInstalledOperate | ||||||
| 	if err := c.ShouldBindJSON(&req); err != nil { | 	if err := c.ShouldBindJSON(&req); err != nil { | ||||||
| @@ -178,7 +178,7 @@ func (b *BaseApi) OperateInstalled(c *gin.Context) { | |||||||
| // @Description 通过 key 获取应用 service | // @Description 通过 key 获取应用 service | ||||||
| // @Accept json | // @Accept json | ||||||
| // @Param key path string true "request" | // @Param key path string true "request" | ||||||
| // @Success 200 {anrry} response.AppService | // @Success 200 {array} response.AppService | ||||||
| // @Security ApiKeyAuth | // @Security ApiKeyAuth | ||||||
| // @Router /apps/services/:key [get] | // @Router /apps/services/:key [get] | ||||||
| func (b *BaseApi) GetServices(c *gin.Context) { | func (b *BaseApi) GetServices(c *gin.Context) { | ||||||
| @@ -196,7 +196,7 @@ func (b *BaseApi) GetServices(c *gin.Context) { | |||||||
| // @Description 通过 install id 获取应用更新版本 | // @Description 通过 install id 获取应用更新版本 | ||||||
| // @Accept json | // @Accept json | ||||||
| // @Param appInstallId path integer true "request" | // @Param appInstallId path integer true "request" | ||||||
| // @Success 200 {anrry} dto.AppVersion | // @Success 200 {array} dto.AppVersion | ||||||
| // @Security ApiKeyAuth | // @Security ApiKeyAuth | ||||||
| // @Router /apps/installed/:appInstallId/versions [get] | // @Router /apps/installed/:appInstallId/versions [get] | ||||||
| func (b *BaseApi) GetUpdateVersions(c *gin.Context) { | func (b *BaseApi) GetUpdateVersions(c *gin.Context) { | ||||||
| @@ -305,3 +305,25 @@ func (b *BaseApi) UpdateInstalled(c *gin.Context) { | |||||||
| 	} | 	} | ||||||
| 	helper.SuccessWithData(c, nil) | 	helper.SuccessWithData(c, nil) | ||||||
| } | } | ||||||
|  |  | ||||||
|  | // @Tags App | ||||||
|  | // @Summary ignore App Update | ||||||
|  | // @Description 忽略应用升级版本 | ||||||
|  | // @Accept json | ||||||
|  | // @Param request body request.AppInstalledIgnoreUpgrade true "request" | ||||||
|  | // @Success 200 | ||||||
|  | // @Security ApiKeyAuth | ||||||
|  | // @Router /apps/installed/ignore [post] | ||||||
|  | // @x-panel-log {"bodyKeys":["installId"],"paramKeys":[],"BeforeFuntions":[],"formatZH":"忽略应用 [installId] 版本升级","formatEN":"Application param update [installId]"} | ||||||
|  | func (b *BaseApi) IgnoreUpgrade(c *gin.Context) { | ||||||
|  | 	var req request.AppInstalledIgnoreUpgrade | ||||||
|  | 	if err := c.ShouldBindJSON(&req); err != nil { | ||||||
|  | 		helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	if err := appInstallService.IgnoreUpgrade(req); err != nil { | ||||||
|  | 		helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	helper.SuccessWithOutData(c) | ||||||
|  | } | ||||||
|   | |||||||
| @@ -1,8 +1,6 @@ | |||||||
| package v1 | package v1 | ||||||
|  |  | ||||||
| import ( | import ( | ||||||
| 	"errors" |  | ||||||
|  |  | ||||||
| 	"github.com/1Panel-dev/1Panel/backend/app/api/v1/helper" | 	"github.com/1Panel-dev/1Panel/backend/app/api/v1/helper" | ||||||
| 	"github.com/1Panel-dev/1Panel/backend/app/dto" | 	"github.com/1Panel-dev/1Panel/backend/app/dto" | ||||||
| 	"github.com/1Panel-dev/1Panel/backend/app/model" | 	"github.com/1Panel-dev/1Panel/backend/app/model" | ||||||
| @@ -28,9 +26,11 @@ func (b *BaseApi) Login(c *gin.Context) { | |||||||
| 		helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err) | 		helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err) | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
| 	if err := captcha.VerifyCode(req.CaptchaID, req.Captcha); err != nil { | 	if req.AuthMethod != "jwt" && !req.IgnoreCaptcha { | ||||||
| 		helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err) | 		if err := captcha.VerifyCode(req.CaptchaID, req.Captcha); err != nil { | ||||||
| 		return | 			helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err) | ||||||
|  | 			return | ||||||
|  | 		} | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	user, err := authService.Login(c, req) | 	user, err := authService.Login(c, req) | ||||||
| @@ -100,71 +100,15 @@ func (b *BaseApi) Captcha(c *gin.Context) { | |||||||
| // @Summary Load safety status | // @Summary Load safety status | ||||||
| // @Description 获取系统安全登录状态 | // @Description 获取系统安全登录状态 | ||||||
| // @Success 200 | // @Success 200 | ||||||
| // @Failure 402 | // @Router /auth/issafety [get] | ||||||
| // @Router /auth/status [get] | func (b *BaseApi) CheckIsSafety(c *gin.Context) { | ||||||
| func (b *BaseApi) GetSafetyStatus(c *gin.Context) { | 	code := c.DefaultQuery("code", "") | ||||||
| 	if err := authService.SafetyStatus(c); err != nil { | 	status, err := authService.CheckIsSafety(code) | ||||||
| 		helper.ErrorWithDetail(c, constant.CodeErrUnSafety, constant.ErrTypeNotSafety, err) |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
| 	helper.SuccessWithData(c, nil) |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (b *BaseApi) SafeEntrance(c *gin.Context) { |  | ||||||
| 	code, exist := c.Params.Get("code") |  | ||||||
| 	if !exist { |  | ||||||
| 		helper.ErrorWithDetail(c, constant.CodeErrUnSafety, constant.ErrTypeNotSafety, errors.New("missing code")) |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
| 	ok, err := authService.VerifyCode(code) |  | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		helper.ErrorWithDetail(c, constant.CodeErrUnSafety, constant.ErrTypeNotSafety, errors.New("missing code")) |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
| 	if !ok { |  | ||||||
| 		helper.ErrorWithDetail(c, constant.CodeErrUnSafety, constant.ErrTypeNotSafety, errors.New("missing code")) |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
| 	if err := authService.SafeEntrance(c, code); err != nil { |  | ||||||
| 		helper.ErrorWithDetail(c, constant.CodeErrUnSafety, constant.ErrTypeNotSafety, errors.New("missing code")) |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	helper.SuccessWithData(c, nil) |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // @Tags Auth |  | ||||||
| // @Summary Check is First login |  | ||||||
| // @Description 判断是否为首次登录 |  | ||||||
| // @Success 200 |  | ||||||
| // @Router /auth/status [get] |  | ||||||
| func (b *BaseApi) CheckIsFirstLogin(c *gin.Context) { |  | ||||||
| 	helper.SuccessWithData(c, authService.CheckIsFirst()) |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // @Tags Auth |  | ||||||
| // @Summary Init user |  | ||||||
| // @Description 初始化用户 |  | ||||||
| // @Accept json |  | ||||||
| // @Param request body dto.InitUser true "request" |  | ||||||
| // @Success 200 |  | ||||||
| // @Router /auth/init [post] |  | ||||||
| func (b *BaseApi) InitUserInfo(c *gin.Context) { |  | ||||||
| 	var req dto.InitUser |  | ||||||
| 	if err := c.ShouldBindJSON(&req); err != nil { |  | ||||||
| 		helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err) |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
| 	if err := global.VALID.Struct(req); err != nil { |  | ||||||
| 		helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err) |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	if err := authService.InitUser(c, req); err != nil { |  | ||||||
| 		helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err) | 		helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err) | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
| 	helper.SuccessWithData(c, nil) | 	helper.SuccessWithData(c, status) | ||||||
| } | } | ||||||
|  |  | ||||||
| // @Tags Auth | // @Tags Auth | ||||||
|   | |||||||
| @@ -2,6 +2,8 @@ package v1 | |||||||
|  |  | ||||||
| import ( | import ( | ||||||
| 	"encoding/base64" | 	"encoding/base64" | ||||||
|  | 	"fmt" | ||||||
|  | 	"path" | ||||||
|  |  | ||||||
| 	"github.com/1Panel-dev/1Panel/backend/app/api/v1/helper" | 	"github.com/1Panel-dev/1Panel/backend/app/api/v1/helper" | ||||||
| 	"github.com/1Panel-dev/1Panel/backend/app/dto" | 	"github.com/1Panel-dev/1Panel/backend/app/dto" | ||||||
| @@ -58,7 +60,7 @@ func (b *BaseApi) CreateBackup(c *gin.Context) { | |||||||
| // @Description 获取 bucket 列表 | // @Description 获取 bucket 列表 | ||||||
| // @Accept json | // @Accept json | ||||||
| // @Param request body dto.ForBuckets true "request" | // @Param request body dto.ForBuckets true "request" | ||||||
| // @Success 200 {anrry} string | // @Success 200 {array} string | ||||||
| // @Security ApiKeyAuth | // @Security ApiKeyAuth | ||||||
| // @Router /settings/backup/search [post] | // @Router /settings/backup/search [post] | ||||||
| func (b *BaseApi) ListBuckets(c *gin.Context) { | func (b *BaseApi) ListBuckets(c *gin.Context) { | ||||||
| @@ -96,6 +98,22 @@ func (b *BaseApi) ListBuckets(c *gin.Context) { | |||||||
| 	helper.SuccessWithData(c, buckets) | 	helper.SuccessWithData(c, buckets) | ||||||
| } | } | ||||||
|  |  | ||||||
|  | // @Tags Backup Account | ||||||
|  | // @Summary Load OneDrive info | ||||||
|  | // @Description 获取 OneDrive 信息 | ||||||
|  | // @Accept json | ||||||
|  | // @Success 200 string clientID | ||||||
|  | // @Security ApiKeyAuth | ||||||
|  | // @Router /settings/backup/onedrive [get] | ||||||
|  | func (b *BaseApi) LoadOneDriveInfo(c *gin.Context) { | ||||||
|  | 	clientID, err := backupService.LoadOneDriveInfo() | ||||||
|  | 	if err != nil { | ||||||
|  | 		helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	helper.SuccessWithData(c, clientID) | ||||||
|  | } | ||||||
|  |  | ||||||
| // @Tags Backup Account | // @Tags Backup Account | ||||||
| // @Summary Delete backup account | // @Summary Delete backup account | ||||||
| // @Description 删除备份账号 | // @Description 删除备份账号 | ||||||
| @@ -104,9 +122,9 @@ func (b *BaseApi) ListBuckets(c *gin.Context) { | |||||||
| // @Success 200 | // @Success 200 | ||||||
| // @Security ApiKeyAuth | // @Security ApiKeyAuth | ||||||
| // @Router /settings/backup/del [post] | // @Router /settings/backup/del [post] | ||||||
| // @x-panel-log {"bodyKeys":["ids"],"paramKeys":[],"BeforeFuntions":[{"input_colume":"id","input_value":"ids","isList":true,"db":"backup_accounts","output_colume":"type","output_value":"types"}],"formatZH":"删除备份账号 [types]","formatEN":"delete backup account [types]"} | // @x-panel-log {"bodyKeys":["id"],"paramKeys":[],"BeforeFuntions":[{"input_column":"id","input_value":"id","isList":true,"db":"backup_accounts","output_column":"type","output_value":"types"}],"formatZH":"删除备份账号 [types]","formatEN":"delete backup account [types]"} | ||||||
| func (b *BaseApi) DeleteBackup(c *gin.Context) { | func (b *BaseApi) DeleteBackup(c *gin.Context) { | ||||||
| 	var req dto.BatchDeleteReq | 	var req dto.OperateByID | ||||||
| 	if err := c.ShouldBindJSON(&req); err != nil { | 	if err := c.ShouldBindJSON(&req); err != nil { | ||||||
| 		helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err) | 		helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err) | ||||||
| 		return | 		return | ||||||
| @@ -116,7 +134,7 @@ func (b *BaseApi) DeleteBackup(c *gin.Context) { | |||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	if err := backupService.BatchDelete(req.Ids); err != nil { | 	if err := backupService.Delete(req.ID); err != nil { | ||||||
| 		helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err) | 		helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err) | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
| @@ -186,7 +204,7 @@ func (b *BaseApi) DownloadRecord(c *gin.Context) { | |||||||
| // @Success 200 | // @Success 200 | ||||||
| // @Security ApiKeyAuth | // @Security ApiKeyAuth | ||||||
| // @Router /settings/backup/record/del [post] | // @Router /settings/backup/record/del [post] | ||||||
| // @x-panel-log {"bodyKeys":["ids"],"paramKeys":[],"BeforeFuntions":[{"input_colume":"id","input_value":"ids","isList":true,"db":"backup_records","output_colume":"file_name","output_value":"files"}],"formatZH":"删除备份记录 [files]","formatEN":"delete backup records [files]"} | // @x-panel-log {"bodyKeys":["ids"],"paramKeys":[],"BeforeFuntions":[{"input_column":"id","input_value":"ids","isList":true,"db":"backup_records","output_column":"file_name","output_value":"files"}],"formatZH":"删除备份记录 [files]","formatEN":"delete backup records [files]"} | ||||||
| func (b *BaseApi) DeleteBackupRecord(c *gin.Context) { | func (b *BaseApi) DeleteBackupRecord(c *gin.Context) { | ||||||
| 	var req dto.BatchDeleteReq | 	var req dto.BatchDeleteReq | ||||||
| 	if err := c.ShouldBindJSON(&req); err != nil { | 	if err := c.ShouldBindJSON(&req); err != nil { | ||||||
| @@ -251,7 +269,7 @@ func (b *BaseApi) UpdateBackup(c *gin.Context) { | |||||||
| // @Tags Backup Account | // @Tags Backup Account | ||||||
| // @Summary List backup accounts | // @Summary List backup accounts | ||||||
| // @Description 获取备份账号列表 | // @Description 获取备份账号列表 | ||||||
| // @Success 200 {anrry} dto.BackupInfo | // @Success 200 {array} dto.BackupInfo | ||||||
| // @Security ApiKeyAuth | // @Security ApiKeyAuth | ||||||
| // @Router /settings/backup/search [get] | // @Router /settings/backup/search [get] | ||||||
| func (b *BaseApi) ListBackup(c *gin.Context) { | func (b *BaseApi) ListBackup(c *gin.Context) { | ||||||
| @@ -269,7 +287,7 @@ func (b *BaseApi) ListBackup(c *gin.Context) { | |||||||
| // @Description 获取备份账号内文件列表 | // @Description 获取备份账号内文件列表 | ||||||
| // @Accept json | // @Accept json | ||||||
| // @Param request body dto.BackupSearchFile true "request" | // @Param request body dto.BackupSearchFile true "request" | ||||||
| // @Success 200 {anrry} string | // @Success 200 {array} string | ||||||
| // @Security ApiKeyAuth | // @Security ApiKeyAuth | ||||||
| // @Router /settings/backup/search/files [post] | // @Router /settings/backup/search/files [post] | ||||||
| func (b *BaseApi) LoadFilesFromBackup(c *gin.Context) { | func (b *BaseApi) LoadFilesFromBackup(c *gin.Context) { | ||||||
| @@ -356,6 +374,14 @@ func (b *BaseApi) Recover(c *gin.Context) { | |||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | 	if req.Source != "LOCAL" { | ||||||
|  | 		downloadPath, err := backupService.DownloadRecord(dto.DownloadRecord{Source: req.Source, FileDir: path.Dir(req.File), FileName: path.Base(req.File)}) | ||||||
|  | 		if err != nil { | ||||||
|  | 			helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, fmt.Errorf("download file failed, err: %v", err)) | ||||||
|  | 			return | ||||||
|  | 		} | ||||||
|  | 		req.File = downloadPath | ||||||
|  | 	} | ||||||
| 	switch req.Type { | 	switch req.Type { | ||||||
| 	case "mysql": | 	case "mysql": | ||||||
| 		if err := backupService.MysqlRecover(req); err != nil { | 		if err := backupService.MysqlRecover(req); err != nil { | ||||||
|   | |||||||
| @@ -85,7 +85,7 @@ func (b *BaseApi) ListCommand(c *gin.Context) { | |||||||
| // @Success 200 | // @Success 200 | ||||||
| // @Security ApiKeyAuth | // @Security ApiKeyAuth | ||||||
| // @Router /hosts/command/del [post] | // @Router /hosts/command/del [post] | ||||||
| // @x-panel-log {"bodyKeys":["ids"],"paramKeys":[],"BeforeFuntions":[{"input_colume":"id","input_value":"ids","isList":true,"db":"commands","output_colume":"name","output_value":"names"}],"formatZH":"删除快捷命令 [names]","formatEN":"delete quick command [names]"} | // @x-panel-log {"bodyKeys":["ids"],"paramKeys":[],"BeforeFuntions":[{"input_column":"id","input_value":"ids","isList":true,"db":"commands","output_column":"name","output_value":"names"}],"formatZH":"删除快捷命令 [names]","formatEN":"delete quick command [names]"} | ||||||
| func (b *BaseApi) DeleteCommand(c *gin.Context) { | func (b *BaseApi) DeleteCommand(c *gin.Context) { | ||||||
| 	var req dto.BatchDeleteReq | 	var req dto.BatchDeleteReq | ||||||
| 	if err := c.ShouldBindJSON(&req); err != nil { | 	if err := c.ShouldBindJSON(&req); err != nil { | ||||||
|   | |||||||
| @@ -66,7 +66,7 @@ func (b *BaseApi) SearchComposeTemplate(c *gin.Context) { | |||||||
| // @Summary List compose templates | // @Summary List compose templates | ||||||
| // @Description 获取容器编排模版列表 | // @Description 获取容器编排模版列表 | ||||||
| // @Produce json | // @Produce json | ||||||
| // @Success 200 {anrry} dto.ComposeTemplateInfo | // @Success 200 {array} dto.ComposeTemplateInfo | ||||||
| // @Security ApiKeyAuth | // @Security ApiKeyAuth | ||||||
| // @Router /containers/template [get] | // @Router /containers/template [get] | ||||||
| func (b *BaseApi) ListComposeTemplate(c *gin.Context) { | func (b *BaseApi) ListComposeTemplate(c *gin.Context) { | ||||||
| @@ -87,7 +87,7 @@ func (b *BaseApi) ListComposeTemplate(c *gin.Context) { | |||||||
| // @Success 200 | // @Success 200 | ||||||
| // @Security ApiKeyAuth | // @Security ApiKeyAuth | ||||||
| // @Router /containers/template/del [post] | // @Router /containers/template/del [post] | ||||||
| // @x-panel-log {"bodyKeys":["ids"],"paramKeys":[],"BeforeFuntions":[{"input_colume":"id","input_value":"ids","isList":true,"db":"compose_templates","output_colume":"name","output_value":"names"}],"formatZH":"删除 compose 模版 [names]","formatEN":"delete compose template [names]"} | // @x-panel-log {"bodyKeys":["ids"],"paramKeys":[],"BeforeFuntions":[{"input_column":"id","input_value":"ids","isList":true,"db":"compose_templates","output_column":"name","output_value":"names"}],"formatZH":"删除 compose 模版 [names]","formatEN":"delete compose template [names]"} | ||||||
| func (b *BaseApi) DeleteComposeTemplate(c *gin.Context) { | func (b *BaseApi) DeleteComposeTemplate(c *gin.Context) { | ||||||
| 	var req dto.BatchDeleteReq | 	var req dto.BatchDeleteReq | ||||||
| 	if err := c.ShouldBindJSON(&req); err != nil { | 	if err := c.ShouldBindJSON(&req); err != nil { | ||||||
| @@ -114,7 +114,7 @@ func (b *BaseApi) DeleteComposeTemplate(c *gin.Context) { | |||||||
| // @Success 200 | // @Success 200 | ||||||
| // @Security ApiKeyAuth | // @Security ApiKeyAuth | ||||||
| // @Router /containers/template/update [post] | // @Router /containers/template/update [post] | ||||||
| // @x-panel-log {"bodyKeys":["id"],"paramKeys":[],"BeforeFuntions":[{"input_colume":"id","input_value":"id","isList":false,"db":"compose_templates","output_colume":"name","output_value":"name"}],"formatZH":"更新 compose 模版 [name]","formatEN":"update compose template information [name]"} | // @x-panel-log {"bodyKeys":["id"],"paramKeys":[],"BeforeFuntions":[{"input_column":"id","input_value":"id","isList":false,"db":"compose_templates","output_column":"name","output_value":"name"}],"formatZH":"更新 compose 模版 [name]","formatEN":"update compose template information [name]"} | ||||||
| func (b *BaseApi) UpdateComposeTemplate(c *gin.Context) { | func (b *BaseApi) UpdateComposeTemplate(c *gin.Context) { | ||||||
| 	var req dto.ComposeTemplateUpdate | 	var req dto.ComposeTemplateUpdate | ||||||
| 	if err := c.ShouldBindJSON(&req); err != nil { | 	if err := c.ShouldBindJSON(&req); err != nil { | ||||||
|   | |||||||
| @@ -40,6 +40,23 @@ func (b *BaseApi) SearchContainer(c *gin.Context) { | |||||||
| 	}) | 	}) | ||||||
| } | } | ||||||
|  |  | ||||||
|  | // @Tags Container | ||||||
|  | // @Summary List containers | ||||||
|  | // @Description 获取容器名称 | ||||||
|  | // @Accept json | ||||||
|  | // @Produce json | ||||||
|  | // @Success 200 | ||||||
|  | // @Security ApiKeyAuth | ||||||
|  | // @Router /containers/list [post] | ||||||
|  | func (b *BaseApi) ListContainer(c *gin.Context) { | ||||||
|  | 	list, err := containerService.List() | ||||||
|  | 	if err != nil { | ||||||
|  | 		helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	helper.SuccessWithData(c, list) | ||||||
|  | } | ||||||
|  |  | ||||||
| // @Tags Container Compose | // @Tags Container Compose | ||||||
| // @Summary Page composes | // @Summary Page composes | ||||||
| // @Description 获取编排列表分页 | // @Description 获取编排列表分页 | ||||||
| @@ -153,17 +170,97 @@ func (b *BaseApi) OperatorCompose(c *gin.Context) { | |||||||
| 	helper.SuccessWithData(c, nil) | 	helper.SuccessWithData(c, nil) | ||||||
| } | } | ||||||
|  |  | ||||||
|  | // @Tags Container | ||||||
|  | // @Summary Update container | ||||||
|  | // @Description 更新容器 | ||||||
|  | // @Accept json | ||||||
|  | // @Param request body dto.ContainerOperate true "request" | ||||||
|  | // @Success 200 | ||||||
|  | // @Security ApiKeyAuth | ||||||
|  | // @Router /containers/update [post] | ||||||
|  | // @x-panel-log {"bodyKeys":["name","image"],"paramKeys":[],"BeforeFuntions":[],"formatZH":"更新容器 [name][image]","formatEN":"update container [name][image]"} | ||||||
|  | func (b *BaseApi) ContainerUpdate(c *gin.Context) { | ||||||
|  | 	var req dto.ContainerOperate | ||||||
|  | 	if err := c.ShouldBindJSON(&req); err != nil { | ||||||
|  | 		helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	if err := global.VALID.Struct(req); err != nil { | ||||||
|  | 		helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	if err := containerService.ContainerUpdate(req); err != nil { | ||||||
|  | 		helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	helper.SuccessWithData(c, nil) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // @Tags Container | ||||||
|  | // @Summary Load container info | ||||||
|  | // @Description 获取容器表单信息 | ||||||
|  | // @Accept json | ||||||
|  | // @Param request body dto.OperationWithName true "request" | ||||||
|  | // @Success 200 {object} dto.ContainerOperate | ||||||
|  | // @Security ApiKeyAuth | ||||||
|  | // @Router /containers/info [post] | ||||||
|  | func (b *BaseApi) ContainerInfo(c *gin.Context) { | ||||||
|  | 	var req dto.OperationWithName | ||||||
|  | 	if err := c.ShouldBindJSON(&req); err != nil { | ||||||
|  | 		helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	if err := global.VALID.Struct(req); err != nil { | ||||||
|  | 		helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	data, err := containerService.ContainerInfo(req) | ||||||
|  | 	if err != nil { | ||||||
|  | 		helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	helper.SuccessWithData(c, data) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // @Summary Load container limis | ||||||
|  | // @Description 获取容器限制 | ||||||
|  | // @Success 200 {object} dto.ResourceLimit | ||||||
|  | // @Security ApiKeyAuth | ||||||
|  | // @Router /containers/limit [get] | ||||||
|  | func (b *BaseApi) LoadResouceLimit(c *gin.Context) { | ||||||
|  | 	data, err := containerService.LoadResouceLimit() | ||||||
|  | 	if err != nil { | ||||||
|  | 		helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	helper.SuccessWithData(c, data) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // @Summary Load container stats | ||||||
|  | // @Description 获取容器列表资源占用 | ||||||
|  | // @Success 200 {array} dto.ContainerListStats | ||||||
|  | // @Security ApiKeyAuth | ||||||
|  | // @Router /containers/list/stats [get] | ||||||
|  | func (b *BaseApi) ContainerListStats(c *gin.Context) { | ||||||
|  | 	datas, err := containerService.ContainerListStats() | ||||||
|  | 	if err != nil { | ||||||
|  | 		helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	helper.SuccessWithData(c, datas) | ||||||
|  | } | ||||||
|  |  | ||||||
| // @Tags Container | // @Tags Container | ||||||
| // @Summary Create container | // @Summary Create container | ||||||
| // @Description 创建容器 | // @Description 创建容器 | ||||||
| // @Accept json | // @Accept json | ||||||
| // @Param request body dto.ContainerCreate true "request" | // @Param request body dto.ContainerOperate true "request" | ||||||
| // @Success 200 | // @Success 200 | ||||||
| // @Security ApiKeyAuth | // @Security ApiKeyAuth | ||||||
| // @Router /containers [post] | // @Router /containers [post] | ||||||
| // @x-panel-log {"bodyKeys":["name","image"],"paramKeys":[],"BeforeFuntions":[],"formatZH":"创建容器 [name][image]","formatEN":"create container [name][image]"} | // @x-panel-log {"bodyKeys":["name","image"],"paramKeys":[],"BeforeFuntions":[],"formatZH":"创建容器 [name][image]","formatEN":"create container [name][image]"} | ||||||
| func (b *BaseApi) ContainerCreate(c *gin.Context) { | func (b *BaseApi) ContainerCreate(c *gin.Context) { | ||||||
| 	var req dto.ContainerCreate | 	var req dto.ContainerOperate | ||||||
| 	if err := c.ShouldBindJSON(&req); err != nil { | 	if err := c.ShouldBindJSON(&req); err != nil { | ||||||
| 		helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err) | 		helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err) | ||||||
| 		return | 		return | ||||||
| @@ -179,6 +276,85 @@ func (b *BaseApi) ContainerCreate(c *gin.Context) { | |||||||
| 	helper.SuccessWithData(c, nil) | 	helper.SuccessWithData(c, nil) | ||||||
| } | } | ||||||
|  |  | ||||||
|  | // @Tags Container | ||||||
|  | // @Summary Upgrade container | ||||||
|  | // @Description 更新容器镜像 | ||||||
|  | // @Accept json | ||||||
|  | // @Param request body dto.ContainerUpgrade true "request" | ||||||
|  | // @Success 200 | ||||||
|  | // @Security ApiKeyAuth | ||||||
|  | // @Router /containers/upgrade [post] | ||||||
|  | // @x-panel-log {"bodyKeys":["name","image"],"paramKeys":[],"BeforeFuntions":[],"formatZH":"更新容器镜像 [name][image]","formatEN":"upgrade container image [name][image]"} | ||||||
|  | func (b *BaseApi) ContainerUpgrade(c *gin.Context) { | ||||||
|  | 	var req dto.ContainerUpgrade | ||||||
|  | 	if err := c.ShouldBindJSON(&req); err != nil { | ||||||
|  | 		helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	if err := global.VALID.Struct(req); err != nil { | ||||||
|  | 		helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	if err := containerService.ContainerUpgrade(req); err != nil { | ||||||
|  | 		helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	helper.SuccessWithData(c, nil) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // @Tags Container | ||||||
|  | // @Summary Clean container | ||||||
|  | // @Description 容器清理 | ||||||
|  | // @Accept json | ||||||
|  | // @Param request body dto.ContainerPrune true "request" | ||||||
|  | // @Success 200 {object} dto.ContainerPruneReport | ||||||
|  | // @Security ApiKeyAuth | ||||||
|  | // @Router /containers/prune [post] | ||||||
|  | // @x-panel-log {"bodyKeys":["pruneType"],"paramKeys":[],"BeforeFuntions":[],"formatZH":"清理容器 [pruneType]","formatEN":"clean container [pruneType]"} | ||||||
|  | func (b *BaseApi) ContainerPrune(c *gin.Context) { | ||||||
|  | 	var req dto.ContainerPrune | ||||||
|  | 	if err := c.ShouldBindJSON(&req); err != nil { | ||||||
|  | 		helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	if err := global.VALID.Struct(req); err != nil { | ||||||
|  | 		helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	report, err := containerService.Prune(req) | ||||||
|  | 	if err != nil { | ||||||
|  | 		helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	helper.SuccessWithData(c, report) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // @Tags Container | ||||||
|  | // @Summary Clean container log | ||||||
|  | // @Description 清理容器日志 | ||||||
|  | // @Accept json | ||||||
|  | // @Param request body dto.OperationWithName true "request" | ||||||
|  | // @Success 200 | ||||||
|  | // @Security ApiKeyAuth | ||||||
|  | // @Router /containers/clean/log [post] | ||||||
|  | // @x-panel-log {"bodyKeys":["name"],"paramKeys":[],"BeforeFuntions":[],"formatZH":"清理容器 [name] 日志","formatEN":"clean container [name] logs"} | ||||||
|  | func (b *BaseApi) CleanContainerLog(c *gin.Context) { | ||||||
|  | 	var req dto.OperationWithName | ||||||
|  | 	if err := c.ShouldBindJSON(&req); err != nil { | ||||||
|  | 		helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	if err := global.VALID.Struct(req); err != nil { | ||||||
|  | 		helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	if err := containerService.ContainerLogClean(req); err != nil { | ||||||
|  | 		helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	helper.SuccessWithData(c, nil) | ||||||
|  | } | ||||||
|  |  | ||||||
| // @Tags Container | // @Tags Container | ||||||
| // @Summary Operate Container | // @Summary Operate Container | ||||||
| // @Description 容器操作 | // @Description 容器操作 | ||||||
| @@ -209,7 +385,7 @@ func (b *BaseApi) ContainerOperation(c *gin.Context) { | |||||||
| // @Summary Container stats | // @Summary Container stats | ||||||
| // @Description 容器监控信息 | // @Description 容器监控信息 | ||||||
| // @Param id path integer true "容器id" | // @Param id path integer true "容器id" | ||||||
| // @Success 200 {object} dto.ContainterStats | // @Success 200 {object} dto.ContainerStats | ||||||
| // @Security ApiKeyAuth | // @Security ApiKeyAuth | ||||||
| // @Router /containers/stats/:id [get] | // @Router /containers/stats/:id [get] | ||||||
| func (b *BaseApi) ContainerStats(c *gin.Context) { | func (b *BaseApi) ContainerStats(c *gin.Context) { | ||||||
| @@ -257,27 +433,29 @@ func (b *BaseApi) Inspect(c *gin.Context) { | |||||||
| // @Tags Container | // @Tags Container | ||||||
| // @Summary Container logs | // @Summary Container logs | ||||||
| // @Description 容器日志 | // @Description 容器日志 | ||||||
| // @Accept json | // @Param container query string false "容器名称" | ||||||
| // @Param request body dto.ContainerLog true "request" | // @Param since query string false "时间筛选" | ||||||
| // @Success 200 {string} logs | // @Param follow query string false "是否追踪" | ||||||
|  | // @Param tail query string false "显示行号" | ||||||
| // @Security ApiKeyAuth | // @Security ApiKeyAuth | ||||||
| // @Router /containers/search/log [post] | // @Router /containers/search/log [post] | ||||||
| func (b *BaseApi) ContainerLogs(c *gin.Context) { | func (b *BaseApi) ContainerLogs(c *gin.Context) { | ||||||
| 	var req dto.ContainerLog | 	wsConn, err := upGrader.Upgrade(c.Writer, c.Request, nil) | ||||||
| 	if err := c.ShouldBindJSON(&req); err != nil { |  | ||||||
| 		helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err) |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
| 	if err := global.VALID.Struct(req); err != nil { |  | ||||||
| 		helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err) |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
| 	logs, err := containerService.ContainerLogs(req) |  | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err) | 		global.LOG.Errorf("gin context http handler failed, err: %v", err) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	defer wsConn.Close() | ||||||
|  |  | ||||||
|  | 	container := c.Query("container") | ||||||
|  | 	since := c.Query("since") | ||||||
|  | 	follow := c.Query("follow") == "true" | ||||||
|  | 	tail := c.Query("tail") | ||||||
|  |  | ||||||
|  | 	if err := containerService.ContainerLogs(wsConn, container, since, tail, follow); err != nil { | ||||||
|  | 		_ = wsConn.WriteMessage(1, []byte(err.Error())) | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
| 	helper.SuccessWithData(c, logs) |  | ||||||
| } | } | ||||||
|  |  | ||||||
| // @Tags Container Network | // @Tags Container Network | ||||||
| @@ -311,6 +489,23 @@ func (b *BaseApi) SearchNetwork(c *gin.Context) { | |||||||
| 	}) | 	}) | ||||||
| } | } | ||||||
|  |  | ||||||
|  | // @Tags Container Network | ||||||
|  | // @Summary List networks | ||||||
|  | // @Description 获取容器网络列表 | ||||||
|  | // @Accept json | ||||||
|  | // @Produce json | ||||||
|  | // @Success 200 {array} dto.Options | ||||||
|  | // @Security ApiKeyAuth | ||||||
|  | // @Router /containers/network [get] | ||||||
|  | func (b *BaseApi) ListNetwork(c *gin.Context) { | ||||||
|  | 	list, err := containerService.ListNetwork() | ||||||
|  | 	if err != nil { | ||||||
|  | 		helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	helper.SuccessWithData(c, list) | ||||||
|  | } | ||||||
|  |  | ||||||
| // @Tags Container Network | // @Tags Container Network | ||||||
| // @Summary Delete network | // @Summary Delete network | ||||||
| // @Description 删除容器网络 | // @Description 删除容器网络 | ||||||
| @@ -342,13 +537,13 @@ func (b *BaseApi) DeleteNetwork(c *gin.Context) { | |||||||
| // @Summary Create network | // @Summary Create network | ||||||
| // @Description 创建容器网络 | // @Description 创建容器网络 | ||||||
| // @Accept json | // @Accept json | ||||||
| // @Param request body dto.NetworkCreat true "request" | // @Param request body dto.NetworkCreate true "request" | ||||||
| // @Success 200 | // @Success 200 | ||||||
| // @Security ApiKeyAuth | // @Security ApiKeyAuth | ||||||
| // @Router /containers/network [post] | // @Router /containers/network [post] | ||||||
| // @x-panel-log {"bodyKeys":["name"],"paramKeys":[],"BeforeFuntions":[],"formatZH":"创建容器网络 name","formatEN":"create container network [name]"} | // @x-panel-log {"bodyKeys":["name"],"paramKeys":[],"BeforeFuntions":[],"formatZH":"创建容器网络 name","formatEN":"create container network [name]"} | ||||||
| func (b *BaseApi) CreateNetwork(c *gin.Context) { | func (b *BaseApi) CreateNetwork(c *gin.Context) { | ||||||
| 	var req dto.NetworkCreat | 	var req dto.NetworkCreate | ||||||
| 	if err := c.ShouldBindJSON(&req); err != nil { | 	if err := c.ShouldBindJSON(&req); err != nil { | ||||||
| 		helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err) | 		helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err) | ||||||
| 		return | 		return | ||||||
| @@ -400,11 +595,10 @@ func (b *BaseApi) SearchVolume(c *gin.Context) { | |||||||
| // @Summary List volumes | // @Summary List volumes | ||||||
| // @Description 获取容器存储卷列表 | // @Description 获取容器存储卷列表 | ||||||
| // @Accept json | // @Accept json | ||||||
| // @Param request body dto.PageInfo true "request" |  | ||||||
| // @Produce json | // @Produce json | ||||||
| // @Success 200 {object} dto.PageResult | // @Success 200 {array} dto.Options | ||||||
| // @Security ApiKeyAuth | // @Security ApiKeyAuth | ||||||
| // @Router /containers/volume/search [get] | // @Router /containers/volume [get] | ||||||
| func (b *BaseApi) ListVolume(c *gin.Context) { | func (b *BaseApi) ListVolume(c *gin.Context) { | ||||||
| 	list, err := containerService.ListVolume() | 	list, err := containerService.ListVolume() | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| @@ -445,13 +639,13 @@ func (b *BaseApi) DeleteVolume(c *gin.Context) { | |||||||
| // @Summary Create volume | // @Summary Create volume | ||||||
| // @Description 创建容器存储卷 | // @Description 创建容器存储卷 | ||||||
| // @Accept json | // @Accept json | ||||||
| // @Param request body dto.VolumeCreat true "request" | // @Param request body dto.VolumeCreate true "request" | ||||||
| // @Success 200 | // @Success 200 | ||||||
| // @Security ApiKeyAuth | // @Security ApiKeyAuth | ||||||
| // @Router /containers/volume [post] | // @Router /containers/volume [post] | ||||||
| // @x-panel-log {"bodyKeys":["name"],"paramKeys":[],"BeforeFuntions":[],"formatZH":"创建容器存储卷 [name]","formatEN":"create container volume [name]"} | // @x-panel-log {"bodyKeys":["name"],"paramKeys":[],"BeforeFuntions":[],"formatZH":"创建容器存储卷 [name]","formatEN":"create container volume [name]"} | ||||||
| func (b *BaseApi) CreateVolume(c *gin.Context) { | func (b *BaseApi) CreateVolume(c *gin.Context) { | ||||||
| 	var req dto.VolumeCreat | 	var req dto.VolumeCreate | ||||||
| 	if err := c.ShouldBindJSON(&req); err != nil { | 	if err := c.ShouldBindJSON(&req); err != nil { | ||||||
| 		helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err) | 		helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err) | ||||||
| 		return | 		return | ||||||
|   | |||||||
| @@ -7,6 +7,7 @@ import ( | |||||||
| 	"github.com/1Panel-dev/1Panel/backend/app/dto" | 	"github.com/1Panel-dev/1Panel/backend/app/dto" | ||||||
| 	"github.com/1Panel-dev/1Panel/backend/constant" | 	"github.com/1Panel-dev/1Panel/backend/constant" | ||||||
| 	"github.com/1Panel-dev/1Panel/backend/global" | 	"github.com/1Panel-dev/1Panel/backend/global" | ||||||
|  | 	"github.com/1Panel-dev/1Panel/backend/utils/common" | ||||||
| 	"github.com/gin-gonic/gin" | 	"github.com/gin-gonic/gin" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| @@ -77,8 +78,9 @@ func (b *BaseApi) SearchJobRecords(c *gin.Context) { | |||||||
| 		helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err) | 		helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err) | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
| 	req.StartTime = req.StartTime.Add(8 * time.Hour) | 	loc, _ := time.LoadLocation(common.LoadTimeZone()) | ||||||
| 	req.EndTime = req.EndTime.Add(8 * time.Hour) | 	req.StartTime = req.StartTime.In(loc) | ||||||
|  | 	req.EndTime = req.EndTime.In(loc) | ||||||
|  |  | ||||||
| 	total, list, err := cronjobService.SearchRecords(req) | 	total, list, err := cronjobService.SearchRecords(req) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| @@ -92,17 +94,41 @@ func (b *BaseApi) SearchJobRecords(c *gin.Context) { | |||||||
| 	}) | 	}) | ||||||
| } | } | ||||||
|  |  | ||||||
|  | // @Tags Cronjob | ||||||
|  | // @Summary Clean job records | ||||||
|  | // @Description 清空计划任务记录 | ||||||
|  | // @Accept json | ||||||
|  | // @Param request body dto.CronjobClean true "request" | ||||||
|  | // @Success 200 | ||||||
|  | // @Security ApiKeyAuth | ||||||
|  | // @Router /cronjobs/records/clean [post] | ||||||
|  | // @x-panel-log {"bodyKeys":["id"],"paramKeys":[],"BeforeFuntions":[{"input_column":"id","input_value":"id","isList":false,"db":"cronjobs","output_column":"name","output_value":"name"}],"formatZH":"清空计划任务记录 [name]","formatEN":"clean cronjob [name] records"} | ||||||
|  | func (b *BaseApi) CleanRecord(c *gin.Context) { | ||||||
|  | 	var req dto.CronjobClean | ||||||
|  | 	if err := c.ShouldBindJSON(&req); err != nil { | ||||||
|  | 		helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if err := cronjobService.CleanRecord(req); err != nil { | ||||||
|  | 		helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	helper.SuccessWithData(c, nil) | ||||||
|  | } | ||||||
|  |  | ||||||
| // @Tags Cronjob | // @Tags Cronjob | ||||||
| // @Summary Delete cronjob | // @Summary Delete cronjob | ||||||
| // @Description 删除计划任务 | // @Description 删除计划任务 | ||||||
| // @Accept json | // @Accept json | ||||||
| // @Param request body dto.BatchDeleteReq true "request" | // @Param request body dto.CronjobBatchDelete true "request" | ||||||
| // @Success 200 | // @Success 200 | ||||||
| // @Security ApiKeyAuth | // @Security ApiKeyAuth | ||||||
| // @Router /cronjobs/del [post] | // @Router /cronjobs/del [post] | ||||||
| // @x-panel-log {"bodyKeys":["ids"],"paramKeys":[],"BeforeFuntions":[{"input_colume":"id","input_value":"ids","isList":true,"db":"cronjobs","output_colume":"name","output_value":"names"}],"formatZH":"删除计划任务 [names]","formatEN":"delete cronjob [names]"} | // @x-panel-log {"bodyKeys":["ids"],"paramKeys":[],"BeforeFuntions":[{"input_column":"id","input_value":"ids","isList":true,"db":"cronjobs","output_column":"name","output_value":"names"}],"formatZH":"删除计划任务 [names]","formatEN":"delete cronjob [names]"} | ||||||
| func (b *BaseApi) DeleteCronjob(c *gin.Context) { | func (b *BaseApi) DeleteCronjob(c *gin.Context) { | ||||||
| 	var req dto.BatchDeleteReq | 	var req dto.CronjobBatchDelete | ||||||
| 	if err := c.ShouldBindJSON(&req); err != nil { | 	if err := c.ShouldBindJSON(&req); err != nil { | ||||||
| 		helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err) | 		helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err) | ||||||
| 		return | 		return | ||||||
| @@ -112,7 +138,7 @@ func (b *BaseApi) DeleteCronjob(c *gin.Context) { | |||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	if err := cronjobService.Delete(req.Ids); err != nil { | 	if err := cronjobService.Delete(req); err != nil { | ||||||
| 		helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err) | 		helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err) | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
| @@ -127,7 +153,7 @@ func (b *BaseApi) DeleteCronjob(c *gin.Context) { | |||||||
| // @Success 200 | // @Success 200 | ||||||
| // @Security ApiKeyAuth | // @Security ApiKeyAuth | ||||||
| // @Router /cronjobs/update [post] | // @Router /cronjobs/update [post] | ||||||
| // @x-panel-log {"bodyKeys":["id"],"paramKeys":[],"BeforeFuntions":[{"input_colume":"id","input_value":"id","isList":false,"db":"cronjobs","output_colume":"name","output_value":"name"}],"formatZH":"更新计划任务 [name]","formatEN":"update cronjob [name]"} | // @x-panel-log {"bodyKeys":["id"],"paramKeys":[],"BeforeFuntions":[{"input_column":"id","input_value":"id","isList":false,"db":"cronjobs","output_column":"name","output_value":"name"}],"formatZH":"更新计划任务 [name]","formatEN":"update cronjob [name]"} | ||||||
| func (b *BaseApi) UpdateCronjob(c *gin.Context) { | func (b *BaseApi) UpdateCronjob(c *gin.Context) { | ||||||
| 	var req dto.CronjobUpdate | 	var req dto.CronjobUpdate | ||||||
| 	if err := c.ShouldBindJSON(&req); err != nil { | 	if err := c.ShouldBindJSON(&req); err != nil { | ||||||
| @@ -154,7 +180,7 @@ func (b *BaseApi) UpdateCronjob(c *gin.Context) { | |||||||
| // @Success 200 | // @Success 200 | ||||||
| // @Security ApiKeyAuth | // @Security ApiKeyAuth | ||||||
| // @Router /cronjobs/status [post] | // @Router /cronjobs/status [post] | ||||||
| // @x-panel-log {"bodyKeys":["id","status"],"paramKeys":[],"BeforeFuntions":[{"input_colume":"id","input_value":"id","isList":false,"db":"cronjobs","output_colume":"name","output_value":"name"}],"formatZH":"修改计划任务 [name] 状态为 [status]","formatEN":"change the status of cronjob [name] to [status]."} | // @x-panel-log {"bodyKeys":["id","status"],"paramKeys":[],"BeforeFuntions":[{"input_column":"id","input_value":"id","isList":false,"db":"cronjobs","output_column":"name","output_value":"name"}],"formatZH":"修改计划任务 [name] 状态为 [status]","formatEN":"change the status of cronjob [name] to [status]."} | ||||||
| func (b *BaseApi) UpdateCronjobStatus(c *gin.Context) { | func (b *BaseApi) UpdateCronjobStatus(c *gin.Context) { | ||||||
| 	var req dto.CronjobUpdateStatus | 	var req dto.CronjobUpdateStatus | ||||||
| 	if err := c.ShouldBindJSON(&req); err != nil { | 	if err := c.ShouldBindJSON(&req); err != nil { | ||||||
| @@ -181,7 +207,7 @@ func (b *BaseApi) UpdateCronjobStatus(c *gin.Context) { | |||||||
| // @Success 200 | // @Success 200 | ||||||
| // @Security ApiKeyAuth | // @Security ApiKeyAuth | ||||||
| // @Router /cronjobs/download [post] | // @Router /cronjobs/download [post] | ||||||
| // @x-panel-log {"bodyKeys":["recordID"],"paramKeys":[],"BeforeFuntions":[{"input_colume":"id","input_value":"recordID","isList":false,"db":"job_records","output_colume":"file","output_value":"file"}],"formatZH":"下载计划任务记录 [file]","formatEN":"download the cronjob record [file]"} | // @x-panel-log {"bodyKeys":["recordID"],"paramKeys":[],"BeforeFuntions":[{"input_column":"id","input_value":"recordID","isList":false,"db":"job_records","output_column":"file","output_value":"file"}],"formatZH":"下载计划任务记录 [file]","formatEN":"download the cronjob record [file]"} | ||||||
| func (b *BaseApi) TargetDownload(c *gin.Context) { | func (b *BaseApi) TargetDownload(c *gin.Context) { | ||||||
| 	var req dto.CronjobDownload | 	var req dto.CronjobDownload | ||||||
| 	if err := c.ShouldBindJSON(&req); err != nil { | 	if err := c.ShouldBindJSON(&req); err != nil { | ||||||
| @@ -209,7 +235,7 @@ func (b *BaseApi) TargetDownload(c *gin.Context) { | |||||||
| // @Success 200 | // @Success 200 | ||||||
| // @Security ApiKeyAuth | // @Security ApiKeyAuth | ||||||
| // @Router /cronjobs/handle [post] | // @Router /cronjobs/handle [post] | ||||||
| // @x-panel-log {"bodyKeys":["id"],"paramKeys":[],"BeforeFuntions":[{"input_colume":"id","input_value":"id","isList":false,"db":"cronjobs","output_colume":"name","output_value":"name"}],"formatZH":"手动执行计划任务 [name]","formatEN":"manually execute the cronjob [name]"} | // @x-panel-log {"bodyKeys":["id"],"paramKeys":[],"BeforeFuntions":[{"input_column":"id","input_value":"id","isList":false,"db":"cronjobs","output_column":"name","output_value":"name"}],"formatZH":"手动执行计划任务 [name]","formatEN":"manually execute the cronjob [name]"} | ||||||
| func (b *BaseApi) HandleOnce(c *gin.Context) { | func (b *BaseApi) HandleOnce(c *gin.Context) { | ||||||
| 	var req dto.OperateByID | 	var req dto.OperateByID | ||||||
| 	if err := c.ShouldBindJSON(&req); err != nil { | 	if err := c.ShouldBindJSON(&req); err != nil { | ||||||
|   | |||||||
| @@ -54,7 +54,7 @@ func (b *BaseApi) CreateMysql(c *gin.Context) { | |||||||
| // @Success 200 | // @Success 200 | ||||||
| // @Security ApiKeyAuth | // @Security ApiKeyAuth | ||||||
| // @Router /databases/description/update [post] | // @Router /databases/description/update [post] | ||||||
| // @x-panel-log {"bodyKeys":["id","description"],"paramKeys":[],"BeforeFuntions":[{"input_colume":"id","input_value":"id","isList":false,"db":"database_mysqls","output_colume":"name","output_value":"name"}],"formatZH":"mysql 数据库 [name] 描述信息修改 [description]","formatEN":"The description of the mysql database [name] is modified => [description]"} | // @x-panel-log {"bodyKeys":["id","description"],"paramKeys":[],"BeforeFuntions":[{"input_column":"id","input_value":"id","isList":false,"db":"database_mysqls","output_column":"name","output_value":"name"}],"formatZH":"mysql 数据库 [name] 描述信息修改 [description]","formatEN":"The description of the mysql database [name] is modified => [description]"} | ||||||
| func (b *BaseApi) UpdateMysqlDescription(c *gin.Context) { | func (b *BaseApi) UpdateMysqlDescription(c *gin.Context) { | ||||||
| 	var req dto.UpdateDescription | 	var req dto.UpdateDescription | ||||||
| 	if err := c.ShouldBindJSON(&req); err != nil { | 	if err := c.ShouldBindJSON(&req); err != nil { | ||||||
| @@ -80,7 +80,7 @@ func (b *BaseApi) UpdateMysqlDescription(c *gin.Context) { | |||||||
| // @Success 200 | // @Success 200 | ||||||
| // @Security ApiKeyAuth | // @Security ApiKeyAuth | ||||||
| // @Router /databases/change/password [post] | // @Router /databases/change/password [post] | ||||||
| // @x-panel-log {"bodyKeys":["id"],"paramKeys":[],"BeforeFuntions":[{"input_colume":"id","input_value":"id","isList":false,"db":"database_mysqls","output_colume":"name","output_value":"name"}],"formatZH":"更新数据库 [name] 密码","formatEN":"Update database [name] password"} | // @x-panel-log {"bodyKeys":["id"],"paramKeys":[],"BeforeFuntions":[{"input_column":"id","input_value":"id","isList":false,"db":"database_mysqls","output_column":"name","output_value":"name"}],"formatZH":"更新数据库 [name] 密码","formatEN":"Update database [name] password"} | ||||||
| func (b *BaseApi) ChangeMysqlPassword(c *gin.Context) { | func (b *BaseApi) ChangeMysqlPassword(c *gin.Context) { | ||||||
| 	var req dto.ChangeDBInfo | 	var req dto.ChangeDBInfo | ||||||
| 	if err := c.ShouldBindJSON(&req); err != nil { | 	if err := c.ShouldBindJSON(&req); err != nil { | ||||||
| @@ -115,7 +115,7 @@ func (b *BaseApi) ChangeMysqlPassword(c *gin.Context) { | |||||||
| // @Success 200 | // @Success 200 | ||||||
| // @Security ApiKeyAuth | // @Security ApiKeyAuth | ||||||
| // @Router /databases/change/access [post] | // @Router /databases/change/access [post] | ||||||
| // @x-panel-log {"bodyKeys":["id"],"paramKeys":[],"BeforeFuntions":[{"input_colume":"id","input_value":"id","isList":false,"db":"database_mysqls","output_colume":"name","output_value":"name"}],"formatZH":"更新数据库 [name] 访问权限","formatEN":"Update database [name] access"} | // @x-panel-log {"bodyKeys":["id"],"paramKeys":[],"BeforeFuntions":[{"input_column":"id","input_value":"id","isList":false,"db":"database_mysqls","output_column":"name","output_value":"name"}],"formatZH":"更新数据库 [name] 访问权限","formatEN":"Update database [name] access"} | ||||||
| func (b *BaseApi) ChangeMysqlAccess(c *gin.Context) { | func (b *BaseApi) ChangeMysqlAccess(c *gin.Context) { | ||||||
| 	var req dto.ChangeDBInfo | 	var req dto.ChangeDBInfo | ||||||
| 	if err := c.ShouldBindJSON(&req); err != nil { | 	if err := c.ShouldBindJSON(&req); err != nil { | ||||||
| @@ -216,7 +216,7 @@ func (b *BaseApi) SearchMysql(c *gin.Context) { | |||||||
| // @Description 获取 mysql 数据库列表 | // @Description 获取 mysql 数据库列表 | ||||||
| // @Accept json | // @Accept json | ||||||
| // @Param request body dto.PageInfo true "request" | // @Param request body dto.PageInfo true "request" | ||||||
| // @Success 200 {anrry} string | // @Success 200 {array} string | ||||||
| // @Security ApiKeyAuth | // @Security ApiKeyAuth | ||||||
| // @Router /databases/options [get] | // @Router /databases/options [get] | ||||||
| func (b *BaseApi) ListDBName(c *gin.Context) { | func (b *BaseApi) ListDBName(c *gin.Context) { | ||||||
| @@ -234,7 +234,7 @@ func (b *BaseApi) ListDBName(c *gin.Context) { | |||||||
| // @Description Mysql 数据库删除前检查 | // @Description Mysql 数据库删除前检查 | ||||||
| // @Accept json | // @Accept json | ||||||
| // @Param request body dto.OperateByID true "request" | // @Param request body dto.OperateByID true "request" | ||||||
| // @Success 200 {anrry} string | // @Success 200 {array} string | ||||||
| // @Security ApiKeyAuth | // @Security ApiKeyAuth | ||||||
| // @Router /databases/del/check [post] | // @Router /databases/del/check [post] | ||||||
| func (b *BaseApi) DeleteCheckMysql(c *gin.Context) { | func (b *BaseApi) DeleteCheckMysql(c *gin.Context) { | ||||||
| @@ -264,7 +264,7 @@ func (b *BaseApi) DeleteCheckMysql(c *gin.Context) { | |||||||
| // @Success 200 | // @Success 200 | ||||||
| // @Security ApiKeyAuth | // @Security ApiKeyAuth | ||||||
| // @Router /databases/del [post] | // @Router /databases/del [post] | ||||||
| // @x-panel-log {"bodyKeys":["id"],"paramKeys":[],"BeforeFuntions":[{"input_colume":"id","input_value":"id","isList":false,"db":"database_mysqls","output_colume":"name","output_value":"name"}],"formatZH":"删除 mysql 数据库 [name]","formatEN":"delete mysql database [name]"} | // @x-panel-log {"bodyKeys":["id"],"paramKeys":[],"BeforeFuntions":[{"input_column":"id","input_value":"id","isList":false,"db":"database_mysqls","output_column":"name","output_value":"name"}],"formatZH":"删除 mysql 数据库 [name]","formatEN":"delete mysql database [name]"} | ||||||
| func (b *BaseApi) DeleteMysql(c *gin.Context) { | func (b *BaseApi) DeleteMysql(c *gin.Context) { | ||||||
| 	var req dto.MysqlDBDelete | 	var req dto.MysqlDBDelete | ||||||
| 	if err := c.ShouldBindJSON(&req); err != nil { | 	if err := c.ShouldBindJSON(&req); err != nil { | ||||||
|   | |||||||
| @@ -1,7 +1,6 @@ | |||||||
| package v1 | package v1 | ||||||
|  |  | ||||||
| import ( | import ( | ||||||
| 	"io/ioutil" |  | ||||||
| 	"os" | 	"os" | ||||||
|  |  | ||||||
| 	"github.com/1Panel-dev/1Panel/backend/app/api/v1/helper" | 	"github.com/1Panel-dev/1Panel/backend/app/api/v1/helper" | ||||||
| @@ -35,7 +34,7 @@ func (b *BaseApi) LoadDaemonJsonFile(c *gin.Context) { | |||||||
| 		helper.SuccessWithData(c, "daemon.json is not find in path") | 		helper.SuccessWithData(c, "daemon.json is not find in path") | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
| 	content, err := ioutil.ReadFile(constant.DaemonJsonPath) | 	content, err := os.ReadFile(constant.DaemonJsonPath) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err) | 		helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err) | ||||||
| 		return | 		return | ||||||
| @@ -59,13 +58,13 @@ func (b *BaseApi) LoadDaemonJson(c *gin.Context) { | |||||||
| // @Summary Update docker daemon.json | // @Summary Update docker daemon.json | ||||||
| // @Description 修改 docker 配置信息 | // @Description 修改 docker 配置信息 | ||||||
| // @Accept json | // @Accept json | ||||||
| // @Param request body dto.DaemonJsonConf true "request" | // @Param request body dto.SettingUpdate true "request" | ||||||
| // @Success 200 | // @Success 200 | ||||||
| // @Security ApiKeyAuth | // @Security ApiKeyAuth | ||||||
| // @Router /containers/daemonjson/update [post] | // @Router /containers/daemonjson/update [post] | ||||||
| // @x-panel-log {"bodyKeys":[],"paramKeys":[],"BeforeFuntions":[],"formatZH":"更新 docker daemon.json 配置","formatEN":"Updated the docker daemon.json configuration"} | // @x-panel-log {"bodyKeys":["key", "value"],"paramKeys":[],"BeforeFuntions":[],"formatZH":"更新 docker daemon.json 配置 [key]=>[value]","formatEN":"Updated the docker daemon.json configuration [key]=>[value]"} | ||||||
| func (b *BaseApi) UpdateDaemonJson(c *gin.Context) { | func (b *BaseApi) UpdateDaemonJson(c *gin.Context) { | ||||||
| 	var req dto.DaemonJsonConf | 	var req dto.SettingUpdate | ||||||
| 	if err := c.ShouldBindJSON(&req); err != nil { | 	if err := c.ShouldBindJSON(&req); err != nil { | ||||||
| 		helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err) | 		helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err) | ||||||
| 		return | 		return | ||||||
| @@ -79,6 +78,30 @@ func (b *BaseApi) UpdateDaemonJson(c *gin.Context) { | |||||||
| 	helper.SuccessWithData(c, nil) | 	helper.SuccessWithData(c, nil) | ||||||
| } | } | ||||||
|  |  | ||||||
|  | // @Tags Container Docker | ||||||
|  | // @Summary Update docker daemon.json log option | ||||||
|  | // @Description 修改 docker 日志配置 | ||||||
|  | // @Accept json | ||||||
|  | // @Param request body dto.LogOption true "request" | ||||||
|  | // @Success 200 | ||||||
|  | // @Security ApiKeyAuth | ||||||
|  | // @Router /containers/daemonjson/update [post] | ||||||
|  | // @x-panel-log {"bodyKeys":[],"paramKeys":[],"BeforeFuntions":[],"formatZH":"更新 docker daemon.json 日志配置","formatEN":"Updated the docker daemon.json log option"} | ||||||
|  | func (b *BaseApi) UpdateLogOption(c *gin.Context) { | ||||||
|  | 	var req dto.LogOption | ||||||
|  | 	if err := c.ShouldBindJSON(&req); err != nil { | ||||||
|  | 		helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if err := dockerService.UpdateLogOption(req); err != nil { | ||||||
|  | 		helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	helper.SuccessWithData(c, nil) | ||||||
|  | } | ||||||
|  |  | ||||||
| // @Tags Container Docker | // @Tags Container Docker | ||||||
| // @Summary Update docker daemon.json by upload file | // @Summary Update docker daemon.json by upload file | ||||||
| // @Description 上传替换 docker 配置文件 | // @Description 上传替换 docker 配置文件 | ||||||
|   | |||||||
| @@ -9,40 +9,45 @@ type ApiGroup struct { | |||||||
| var ApiGroupApp = new(ApiGroup) | var ApiGroupApp = new(ApiGroup) | ||||||
|  |  | ||||||
| var ( | var ( | ||||||
| 	authService      = service.ServiceGroupApp.AuthService | 	authService      = service.NewIAuthService() | ||||||
| 	dashboardService = service.ServiceGroupApp.DashboardService | 	dashboardService = service.NewIDashboardService() | ||||||
|  |  | ||||||
| 	appService        = service.NewIAppService() | 	appService        = service.NewIAppService() | ||||||
| 	appInstallService = service.ServiceGroupApp.AppInstallService | 	appInstallService = service.NewIAppInstalledService() | ||||||
|  |  | ||||||
| 	containerService       = service.ServiceGroupApp.ContainerService | 	containerService       = service.NewIContainerService() | ||||||
| 	composeTemplateService = service.ServiceGroupApp.ComposeTemplateService | 	composeTemplateService = service.NewIComposeTemplateService() | ||||||
| 	imageRepoService       = service.ServiceGroupApp.ImageRepoService | 	imageRepoService       = service.NewIImageRepoService() | ||||||
| 	imageService           = service.ServiceGroupApp.ImageService | 	imageService           = service.NewIImageService() | ||||||
| 	dockerService          = service.ServiceGroupApp.DockerService | 	dockerService          = service.NewIDockerService() | ||||||
|  |  | ||||||
| 	mysqlService = service.ServiceGroupApp.MysqlService | 	mysqlService = service.NewIMysqlService() | ||||||
| 	redisService = service.ServiceGroupApp.RedisService | 	redisService = service.NewIRedisService() | ||||||
|  |  | ||||||
| 	cronjobService = service.ServiceGroupApp.CronjobService | 	cronjobService = service.NewICronjobService() | ||||||
|  |  | ||||||
| 	hostService  = service.ServiceGroupApp.HostService | 	hostService     = service.NewIHostService() | ||||||
| 	groupService = service.ServiceGroupApp.GroupService | 	groupService    = service.NewIGroupService() | ||||||
| 	fileService  = service.ServiceGroupApp.FileService | 	fileService     = service.NewIFileService() | ||||||
|  | 	sshService      = service.NewISSHService() | ||||||
|  | 	firewallService = service.NewIFirewallService() | ||||||
|  |  | ||||||
| 	settingService = service.ServiceGroupApp.SettingService | 	settingService = service.NewISettingService() | ||||||
| 	backupService  = service.ServiceGroupApp.BackupService | 	backupService  = service.NewIBackupService() | ||||||
|  |  | ||||||
| 	commandService = service.ServiceGroupApp.CommandService | 	commandService = service.NewICommandService() | ||||||
|  |  | ||||||
| 	websiteService            = service.ServiceGroupApp.WebsiteService | 	websiteService            = service.NewIWebsiteService() | ||||||
| 	websiteDnsAccountService  = service.ServiceGroupApp.WebsiteDnsAccountService | 	websiteDnsAccountService  = service.NewIWebsiteDnsAccountService() | ||||||
| 	websiteSSLService         = service.ServiceGroupApp.WebsiteSSLService | 	websiteSSLService         = service.NewIWebsiteSSLService() | ||||||
| 	websiteAcmeAccountService = service.ServiceGroupApp.WebsiteAcmeAccountService | 	websiteAcmeAccountService = service.NewIWebsiteAcmeAccountService() | ||||||
|  |  | ||||||
| 	nginxService = service.ServiceGroupApp.NginxService | 	nginxService = service.NewINginxService() | ||||||
|  |  | ||||||
| 	logService      = service.ServiceGroupApp.LogService | 	logService      = service.NewILogService() | ||||||
| 	snapshotService = service.ServiceGroupApp.SnapshotService | 	snapshotService = service.NewISnapshotService() | ||||||
| 	upgradeService  = service.ServiceGroupApp.UpgradeService | 	upgradeService  = service.NewIUpgradeService() | ||||||
|  |  | ||||||
|  | 	runtimeService = service.NewRuntimeService() | ||||||
|  | 	processService = service.NewIProcessService() | ||||||
| ) | ) | ||||||
|   | |||||||
| @@ -3,8 +3,9 @@ package v1 | |||||||
| import ( | import ( | ||||||
| 	"errors" | 	"errors" | ||||||
| 	"fmt" | 	"fmt" | ||||||
| 	"io/ioutil" | 	"io" | ||||||
| 	"net/http" | 	"net/http" | ||||||
|  | 	"net/url" | ||||||
| 	"os" | 	"os" | ||||||
| 	"path" | 	"path" | ||||||
| 	"path/filepath" | 	"path/filepath" | ||||||
| @@ -51,7 +52,7 @@ func (b *BaseApi) ListFiles(c *gin.Context) { | |||||||
| // @Description 分页获取上传文件 | // @Description 分页获取上传文件 | ||||||
| // @Accept json | // @Accept json | ||||||
| // @Param request body request.SearchUploadWithPage true "request" | // @Param request body request.SearchUploadWithPage true "request" | ||||||
| // @Success 200 {anrry} response.FileInfo | // @Success 200 {array} response.FileInfo | ||||||
| // @Security ApiKeyAuth | // @Security ApiKeyAuth | ||||||
| // @Router /files/upload/search [post] | // @Router /files/upload/search [post] | ||||||
| func (b *BaseApi) SearchUploadWithPage(c *gin.Context) { | func (b *BaseApi) SearchUploadWithPage(c *gin.Context) { | ||||||
| @@ -80,7 +81,7 @@ func (b *BaseApi) SearchUploadWithPage(c *gin.Context) { | |||||||
| // @Description 加载文件树 | // @Description 加载文件树 | ||||||
| // @Accept json | // @Accept json | ||||||
| // @Param request body request.FileOption true "request" | // @Param request body request.FileOption true "request" | ||||||
| // @Success 200 {anrry} response.FileTree | // @Success 200 {array} response.FileTree | ||||||
| // @Security ApiKeyAuth | // @Security ApiKeyAuth | ||||||
| // @Router /files/tree [post] | // @Router /files/tree [post] | ||||||
| func (b *BaseApi) GetFileTree(c *gin.Context) { | func (b *BaseApi) GetFileTree(c *gin.Context) { | ||||||
| @@ -186,7 +187,29 @@ func (b *BaseApi) ChangeFileMode(c *gin.Context) { | |||||||
| 		helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err) | 		helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err) | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
| 	helper.SuccessWithData(c, nil) | 	helper.SuccessWithOutData(c) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // @Tags File | ||||||
|  | // @Summary Change file owner | ||||||
|  | // @Description 修改文件用户/组 | ||||||
|  | // @Accept json | ||||||
|  | // @Param request body request.FileRoleUpdate true "request" | ||||||
|  | // @Success 200 | ||||||
|  | // @Security ApiKeyAuth | ||||||
|  | // @Router /files/owner [post] | ||||||
|  | // @x-panel-log {"bodyKeys":["path","user","group"],"paramKeys":[],"BeforeFuntions":[],"formatZH":"修改用户/组 [paths] => [user]/[group]","formatEN":"Change owner [paths] => [user]/[group]"} | ||||||
|  | func (b *BaseApi) ChangeFileOwner(c *gin.Context) { | ||||||
|  | 	var req request.FileRoleUpdate | ||||||
|  | 	if err := c.ShouldBindJSON(&req); err != nil { | ||||||
|  | 		helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	if err := fileService.ChangeOwner(req); err != nil { | ||||||
|  | 		helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	helper.SuccessWithOutData(c) | ||||||
| } | } | ||||||
|  |  | ||||||
| // @Tags File | // @Tags File | ||||||
| @@ -432,17 +455,93 @@ func (b *BaseApi) MoveFile(c *gin.Context) { | |||||||
| // @Router /files/download [post] | // @Router /files/download [post] | ||||||
| // @x-panel-log {"bodyKeys":["name"],"paramKeys":[],"BeforeFuntions":[],"formatZH":"下载文件 [name]","formatEN":"Download file [name]"} | // @x-panel-log {"bodyKeys":["name"],"paramKeys":[],"BeforeFuntions":[],"formatZH":"下载文件 [name]","formatEN":"Download file [name]"} | ||||||
| func (b *BaseApi) Download(c *gin.Context) { | func (b *BaseApi) Download(c *gin.Context) { | ||||||
| 	var req request.FileDownload | 	filePath := c.Query("path") | ||||||
|  | 	file, err := os.Open(filePath) | ||||||
|  | 	if err != nil { | ||||||
|  | 		helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err) | ||||||
|  | 	} | ||||||
|  | 	info, _ := file.Stat() | ||||||
|  | 	c.Header("Content-Length", strconv.FormatInt(info.Size(), 10)) | ||||||
|  | 	c.Header("Content-Disposition", "attachment; filename*=utf-8''"+url.PathEscape(info.Name())) | ||||||
|  | 	http.ServeContent(c.Writer, c.Request, info.Name(), info.ModTime(), file) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // @Tags File | ||||||
|  | // @Summary Chunk Download file | ||||||
|  | // @Description 分片下载下载文件 | ||||||
|  | // @Accept json | ||||||
|  | // @Param request body request.FileDownload true "request" | ||||||
|  | // @Success 200 | ||||||
|  | // @Security ApiKeyAuth | ||||||
|  | // @Router /files/chunkdownload [post] | ||||||
|  | // @x-panel-log {"bodyKeys":["name"],"paramKeys":[],"BeforeFuntions":[],"formatZH":"下载文件 [name]","formatEN":"Download file [name]"} | ||||||
|  | func (b *BaseApi) DownloadChunkFiles(c *gin.Context) { | ||||||
|  | 	var req request.FileChunkDownload | ||||||
| 	if err := c.ShouldBindJSON(&req); err != nil { | 	if err := c.ShouldBindJSON(&req); err != nil { | ||||||
| 		helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err) | 		helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err) | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
| 	filePath, err := fileService.FileDownload(req) | 	fileOp := files.NewFileOp() | ||||||
|  | 	if !fileOp.Stat(req.Path) { | ||||||
|  | 		helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrPathNotFound, nil) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	filePath := req.Path | ||||||
|  | 	fstFile, err := fileOp.OpenFile(filePath) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err) | 		helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err) | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
| 	c.File(filePath) | 	info, err := fstFile.Stat() | ||||||
|  | 	if err != nil { | ||||||
|  | 		helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	if info.IsDir() { | ||||||
|  | 		helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrFileDownloadDir, err) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	c.Writer.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=%s", req.Name)) | ||||||
|  | 	c.Writer.Header().Set("Content-Type", "application/octet-stream") | ||||||
|  | 	c.Writer.Header().Set("Content-Length", strconv.FormatInt(info.Size(), 10)) | ||||||
|  | 	c.Writer.Header().Set("Accept-Ranges", "bytes") | ||||||
|  |  | ||||||
|  | 	if c.Request.Header.Get("Range") != "" { | ||||||
|  | 		rangeHeader := c.Request.Header.Get("Range") | ||||||
|  | 		rangeArr := strings.Split(rangeHeader, "=")[1] | ||||||
|  | 		rangeParts := strings.Split(rangeArr, "-") | ||||||
|  |  | ||||||
|  | 		startPos, _ := strconv.ParseInt(rangeParts[0], 10, 64) | ||||||
|  |  | ||||||
|  | 		var endPos int64 | ||||||
|  | 		if rangeParts[1] == "" { | ||||||
|  | 			endPos = info.Size() - 1 | ||||||
|  | 		} else { | ||||||
|  | 			endPos, _ = strconv.ParseInt(rangeParts[1], 10, 64) | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		c.Writer.Header().Set("Content-Range", fmt.Sprintf("bytes %d-%d/%d", startPos, endPos, info.Size())) | ||||||
|  | 		c.Writer.WriteHeader(http.StatusPartialContent) | ||||||
|  |  | ||||||
|  | 		buffer := make([]byte, 1024*1024) | ||||||
|  | 		file, err := os.Open(filePath) | ||||||
|  | 		if err != nil { | ||||||
|  | 			helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err) | ||||||
|  | 			return | ||||||
|  | 		} | ||||||
|  | 		defer file.Close() | ||||||
|  |  | ||||||
|  | 		_, _ = file.Seek(startPos, 0) | ||||||
|  | 		reader := io.LimitReader(file, endPos-startPos+1) | ||||||
|  | 		_, err = io.CopyBuffer(c.Writer, reader, buffer) | ||||||
|  | 		if err != nil { | ||||||
|  | 			helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err) | ||||||
|  | 			return | ||||||
|  | 		} | ||||||
|  | 	} else { | ||||||
|  | 		c.File(filePath) | ||||||
|  | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| // @Tags File | // @Tags File | ||||||
| @@ -498,7 +597,6 @@ func (b *BaseApi) Size(c *gin.Context) { | |||||||
| // @Success 200 {string} content | // @Success 200 {string} content | ||||||
| // @Security ApiKeyAuth | // @Security ApiKeyAuth | ||||||
| // @Router /files/loadfile [post] | // @Router /files/loadfile [post] | ||||||
| // @x-panel-log {"bodyKeys":["path"],"paramKeys":[],"BeforeFuntions":[],"formatZH":"读取文件 [path]","formatEN":"Read file [path]"} |  | ||||||
| func (b *BaseApi) LoadFromFile(c *gin.Context) { | func (b *BaseApi) LoadFromFile(c *gin.Context) { | ||||||
| 	var req dto.FilePath | 	var req dto.FilePath | ||||||
| 	if err := c.ShouldBindJSON(&req); err != nil { | 	if err := c.ShouldBindJSON(&req); err != nil { | ||||||
| @@ -510,7 +608,7 @@ func (b *BaseApi) LoadFromFile(c *gin.Context) { | |||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	content, err := ioutil.ReadFile(req.Path) | 	content, err := os.ReadFile(req.Path) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err) | 		helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err) | ||||||
| 		return | 		return | ||||||
| @@ -533,7 +631,7 @@ func mergeChunks(fileName string, fileDir string, dstDir string, chunkCount int) | |||||||
|  |  | ||||||
| 	for i := 0; i < chunkCount; i++ { | 	for i := 0; i < chunkCount; i++ { | ||||||
| 		chunkPath := filepath.Join(fileDir, fmt.Sprintf("%s.%d", fileName, i)) | 		chunkPath := filepath.Join(fileDir, fmt.Sprintf("%s.%d", fileName, i)) | ||||||
| 		chunkData, err := ioutil.ReadFile(chunkPath) | 		chunkData, err := os.ReadFile(chunkPath) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			return err | 			return err | ||||||
| 		} | 		} | ||||||
| @@ -554,6 +652,7 @@ func mergeChunks(fileName string, fileDir string, dstDir string, chunkCount int) | |||||||
| // @Security ApiKeyAuth | // @Security ApiKeyAuth | ||||||
| // @Router /files/chunkupload [post] | // @Router /files/chunkupload [post] | ||||||
| func (b *BaseApi) UploadChunkFiles(c *gin.Context) { | func (b *BaseApi) UploadChunkFiles(c *gin.Context) { | ||||||
|  | 	var err error | ||||||
| 	fileForm, err := c.FormFile("chunk") | 	fileForm, err := c.FormFile("chunk") | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err) | 		helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err) | ||||||
| @@ -564,19 +663,16 @@ func (b *BaseApi) UploadChunkFiles(c *gin.Context) { | |||||||
| 		helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err) | 		helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err) | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	chunkIndex, err := strconv.Atoi(c.PostForm("chunkIndex")) | 	chunkIndex, err := strconv.Atoi(c.PostForm("chunkIndex")) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err) | 		helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err) | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	chunkCount, err := strconv.Atoi(c.PostForm("chunkCount")) | 	chunkCount, err := strconv.Atoi(c.PostForm("chunkCount")) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err) | 		helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err) | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	fileOp := files.NewFileOp() | 	fileOp := files.NewFileOp() | ||||||
| 	tmpDir := path.Join(global.CONF.System.TmpDir, "upload") | 	tmpDir := path.Join(global.CONF.System.TmpDir, "upload") | ||||||
| 	if !fileOp.Stat(tmpDir) { | 	if !fileOp.Stat(tmpDir) { | ||||||
| @@ -585,37 +681,50 @@ func (b *BaseApi) UploadChunkFiles(c *gin.Context) { | |||||||
| 			return | 			return | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	filename := c.PostForm("filename") | 	filename := c.PostForm("filename") | ||||||
| 	fileDir := filepath.Join(tmpDir, filename) | 	fileDir := filepath.Join(tmpDir, filename) | ||||||
|  | 	if chunkIndex == 0 { | ||||||
| 	_ = os.MkdirAll(fileDir, 0755) | 		if fileOp.Stat(fileDir) { | ||||||
|  | 			_ = fileOp.DeleteDir(fileDir) | ||||||
|  | 		} | ||||||
|  | 		_ = os.MkdirAll(fileDir, 0755) | ||||||
|  | 	} | ||||||
| 	filePath := filepath.Join(fileDir, filename) | 	filePath := filepath.Join(fileDir, filename) | ||||||
|  |  | ||||||
| 	emptyFile, err := os.Create(filePath) | 	defer func() { | ||||||
|  | 		if err != nil { | ||||||
|  | 			_ = os.Remove(fileDir) | ||||||
|  | 		} | ||||||
|  | 	}() | ||||||
|  | 	var ( | ||||||
|  | 		emptyFile *os.File | ||||||
|  | 		chunkData []byte | ||||||
|  | 	) | ||||||
|  |  | ||||||
|  | 	emptyFile, err = os.Create(filePath) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err) | 		helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err) | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
| 	defer emptyFile.Close() | 	defer emptyFile.Close() | ||||||
|  |  | ||||||
| 	chunkData, err := ioutil.ReadAll(uploadFile) | 	chunkData, err = io.ReadAll(uploadFile) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrFileUpload, err) | 		helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, buserr.WithMap(constant.ErrFileUpload, map[string]interface{}{"name": filename, "detail": err.Error()}, err)) | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	chunkPath := filepath.Join(fileDir, fmt.Sprintf("%s.%d", filename, chunkIndex)) | 	chunkPath := filepath.Join(fileDir, fmt.Sprintf("%s.%d", filename, chunkIndex)) | ||||||
| 	err = ioutil.WriteFile(chunkPath, chunkData, 0644) | 	err = os.WriteFile(chunkPath, chunkData, 0644) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrFileUpload, err) | 		helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, buserr.WithMap(constant.ErrFileUpload, map[string]interface{}{"name": filename, "detail": err.Error()}, err)) | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	if chunkIndex+1 == chunkCount { | 	if chunkIndex+1 == chunkCount { | ||||||
| 		err = mergeChunks(filename, fileDir, c.PostForm("path"), chunkCount) | 		err = mergeChunks(filename, fileDir, c.PostForm("path"), chunkCount) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrFileUpload, err) | 			helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, buserr.WithMap(constant.ErrFileUpload, map[string]interface{}{"name": filename, "detail": err.Error()}, err)) | ||||||
| 			return | 			return | ||||||
| 		} | 		} | ||||||
| 		helper.SuccessWithData(c, true) | 		helper.SuccessWithData(c, true) | ||||||
| @@ -630,19 +739,12 @@ var wsUpgrade = websocket.Upgrader{ | |||||||
| 	}, | 	}, | ||||||
| } | } | ||||||
|  |  | ||||||
| var WsManager = websocket2.Manager{ |  | ||||||
| 	Group:       make(map[string]*websocket2.Client), |  | ||||||
| 	Register:    make(chan *websocket2.Client, 128), |  | ||||||
| 	UnRegister:  make(chan *websocket2.Client, 128), |  | ||||||
| 	ClientCount: 0, |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (b *BaseApi) Ws(c *gin.Context) { | func (b *BaseApi) Ws(c *gin.Context) { | ||||||
| 	ws, err := wsUpgrade.Upgrade(c.Writer, c.Request, nil) | 	ws, err := wsUpgrade.Upgrade(c.Writer, c.Request, nil) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
| 	wsClient := websocket2.NewWsClient("wsClient", ws) | 	wsClient := websocket2.NewWsClient("fileClient", ws) | ||||||
| 	go wsClient.Read() | 	go wsClient.Read() | ||||||
| 	go wsClient.Write() | 	go wsClient.Write() | ||||||
| } | } | ||||||
|   | |||||||
							
								
								
									
										207
									
								
								backend/app/api/v1/firewall.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										207
									
								
								backend/app/api/v1/firewall.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,207 @@ | |||||||
|  | package v1 | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"github.com/1Panel-dev/1Panel/backend/app/api/v1/helper" | ||||||
|  | 	"github.com/1Panel-dev/1Panel/backend/app/dto" | ||||||
|  | 	"github.com/1Panel-dev/1Panel/backend/constant" | ||||||
|  | 	"github.com/1Panel-dev/1Panel/backend/global" | ||||||
|  | 	"github.com/gin-gonic/gin" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | // @Tags Firewall | ||||||
|  | // @Summary Load firewall base info | ||||||
|  | // @Description 获取防火墙基础信息 | ||||||
|  | // @Success 200 {object} dto.FirewallBaseInfo | ||||||
|  | // @Security ApiKeyAuth | ||||||
|  | // @Router /hosts/firewall/base [get] | ||||||
|  | func (b *BaseApi) LoadFirewallBaseInfo(c *gin.Context) { | ||||||
|  | 	data, err := firewallService.LoadBaseInfo() | ||||||
|  | 	if err != nil { | ||||||
|  | 		helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	helper.SuccessWithData(c, data) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // @Tags Firewall | ||||||
|  | // @Summary Page firewall rules | ||||||
|  | // @Description 获取防火墙规则列表分页 | ||||||
|  | // @Accept json | ||||||
|  | // @Param request body dto.SearchWithPage true "request" | ||||||
|  | // @Success 200 {object} dto.PageResult | ||||||
|  | // @Security ApiKeyAuth | ||||||
|  | // @Router /hosts/firewall/search [post] | ||||||
|  | func (b *BaseApi) SearchFirewallRule(c *gin.Context) { | ||||||
|  | 	var req dto.RuleSearch | ||||||
|  | 	if err := c.ShouldBindJSON(&req); err != nil { | ||||||
|  | 		helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	total, list, err := firewallService.SearchWithPage(req) | ||||||
|  | 	if err != nil { | ||||||
|  | 		helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	helper.SuccessWithData(c, dto.PageResult{ | ||||||
|  | 		Items: list, | ||||||
|  | 		Total: total, | ||||||
|  | 	}) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // @Tags Firewall | ||||||
|  | // @Summary Page firewall status | ||||||
|  | // @Description 修改防火墙状态 | ||||||
|  | // @Accept json | ||||||
|  | // @Param request body dto.FirewallOperation true "request" | ||||||
|  | // @Success 200 {object} dto.PageResult | ||||||
|  | // @Security ApiKeyAuth | ||||||
|  | // @Router /hosts/firewall/operate [post] | ||||||
|  | // @x-panel-log {"bodyKeys":["operation"],"paramKeys":[],"BeforeFuntions":[],"formatZH":"[operation] 防火墙","formatEN":"[operation] firewall"} | ||||||
|  | func (b *BaseApi) OperateFirewall(c *gin.Context) { | ||||||
|  | 	var req dto.FirewallOperation | ||||||
|  | 	if err := c.ShouldBindJSON(&req); err != nil { | ||||||
|  | 		helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	if err := global.VALID.Struct(req); err != nil { | ||||||
|  | 		helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if err := firewallService.OperateFirewall(req.Operation); err != nil { | ||||||
|  | 		helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	helper.SuccessWithData(c, nil) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // @Tags Firewall | ||||||
|  | // @Summary Create group | ||||||
|  | // @Description 创建防火墙端口规则 | ||||||
|  | // @Accept json | ||||||
|  | // @Param request body dto.PortRuleOperate true "request" | ||||||
|  | // @Success 200 | ||||||
|  | // @Security ApiKeyAuth | ||||||
|  | // @Router /hosts/firewall/port [post] | ||||||
|  | // @x-panel-log {"bodyKeys":["port","strategy"],"paramKeys":[],"BeforeFuntions":[],"formatZH":"添加端口规则 [strategy] [port]","formatEN":"create port rules [strategy][port]"} | ||||||
|  | func (b *BaseApi) OperatePortRule(c *gin.Context) { | ||||||
|  | 	var req dto.PortRuleOperate | ||||||
|  | 	if err := c.ShouldBindJSON(&req); err != nil { | ||||||
|  | 		helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	if err := global.VALID.Struct(req); err != nil { | ||||||
|  | 		helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	if err := firewallService.OperatePortRule(req, true); err != nil { | ||||||
|  | 		helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	helper.SuccessWithData(c, nil) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // @Tags Firewall | ||||||
|  | // @Summary Create group | ||||||
|  | // @Description 创建防火墙 IP 规则 | ||||||
|  | // @Accept json | ||||||
|  | // @Param request body dto.AddrRuleOperate true "request" | ||||||
|  | // @Success 200 | ||||||
|  | // @Security ApiKeyAuth | ||||||
|  | // @Router /hosts/firewall/ip [post] | ||||||
|  | // @x-panel-log {"bodyKeys":["strategy","address"],"paramKeys":[],"BeforeFuntions":[],"formatZH":"添加 ip 规则 [strategy] [address]","formatEN":"create address rules [strategy][address]"} | ||||||
|  | func (b *BaseApi) OperateIPRule(c *gin.Context) { | ||||||
|  | 	var req dto.AddrRuleOperate | ||||||
|  | 	if err := c.ShouldBindJSON(&req); err != nil { | ||||||
|  | 		helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	if err := global.VALID.Struct(req); err != nil { | ||||||
|  | 		helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	if err := firewallService.OperateAddressRule(req, true); err != nil { | ||||||
|  | 		helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	helper.SuccessWithData(c, nil) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // @Tags Firewall | ||||||
|  | // @Summary Create group | ||||||
|  | // @Description 批量删除防火墙规则 | ||||||
|  | // @Accept json | ||||||
|  | // @Param request body dto.BatchRuleOperate true "request" | ||||||
|  | // @Success 200 | ||||||
|  | // @Security ApiKeyAuth | ||||||
|  | // @Router /hosts/firewall/batch [post] | ||||||
|  | func (b *BaseApi) BatchOperateRule(c *gin.Context) { | ||||||
|  | 	var req dto.BatchRuleOperate | ||||||
|  | 	if err := c.ShouldBindJSON(&req); err != nil { | ||||||
|  | 		helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	if err := global.VALID.Struct(req); err != nil { | ||||||
|  | 		helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	if err := firewallService.BatchOperateRule(req); err != nil { | ||||||
|  | 		helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	helper.SuccessWithData(c, nil) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // @Tags Firewall | ||||||
|  | // @Summary Create group | ||||||
|  | // @Description 更新端口防火墙规则 | ||||||
|  | // @Accept json | ||||||
|  | // @Param request body dto.PortRuleUpdate true "request" | ||||||
|  | // @Success 200 | ||||||
|  | // @Security ApiKeyAuth | ||||||
|  | // @Router /hosts/firewall/update/port [post] | ||||||
|  | func (b *BaseApi) UpdatePortRule(c *gin.Context) { | ||||||
|  | 	var req dto.PortRuleUpdate | ||||||
|  | 	if err := c.ShouldBindJSON(&req); err != nil { | ||||||
|  | 		helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	if err := global.VALID.Struct(req); err != nil { | ||||||
|  | 		helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	if err := firewallService.UpdatePortRule(req); err != nil { | ||||||
|  | 		helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	helper.SuccessWithData(c, nil) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // @Tags Firewall | ||||||
|  | // @Summary Create group | ||||||
|  | // @Description 更新 ip 防火墙规则 | ||||||
|  | // @Accept json | ||||||
|  | // @Param request body dto.AddrRuleUpdate true "request" | ||||||
|  | // @Success 200 | ||||||
|  | // @Security ApiKeyAuth | ||||||
|  | // @Router /hosts/firewall/update/addr [post] | ||||||
|  | func (b *BaseApi) UpdateAddrRule(c *gin.Context) { | ||||||
|  | 	var req dto.AddrRuleUpdate | ||||||
|  | 	if err := c.ShouldBindJSON(&req); err != nil { | ||||||
|  | 		helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	if err := global.VALID.Struct(req); err != nil { | ||||||
|  | 		helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	if err := firewallService.UpdateAddrRule(req); err != nil { | ||||||
|  | 		helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	helper.SuccessWithData(c, nil) | ||||||
|  | } | ||||||
| @@ -15,7 +15,7 @@ import ( | |||||||
| // @Param request body dto.GroupCreate true "request" | // @Param request body dto.GroupCreate true "request" | ||||||
| // @Success 200 | // @Success 200 | ||||||
| // @Security ApiKeyAuth | // @Security ApiKeyAuth | ||||||
| // @Router /hosts/group [post] | // @Router /groups [post] | ||||||
| // @x-panel-log {"bodyKeys":["name","type"],"paramKeys":[],"BeforeFuntions":[],"formatZH":"创建组 [name][type]","formatEN":"create group [name][type]"} | // @x-panel-log {"bodyKeys":["name","type"],"paramKeys":[],"BeforeFuntions":[],"formatZH":"创建组 [name][type]","formatEN":"create group [name][type]"} | ||||||
| func (b *BaseApi) CreateGroup(c *gin.Context) { | func (b *BaseApi) CreateGroup(c *gin.Context) { | ||||||
| 	var req dto.GroupCreate | 	var req dto.GroupCreate | ||||||
| @@ -41,8 +41,8 @@ func (b *BaseApi) CreateGroup(c *gin.Context) { | |||||||
| // @Param request body dto.OperateByID true "request" | // @Param request body dto.OperateByID true "request" | ||||||
| // @Success 200 | // @Success 200 | ||||||
| // @Security ApiKeyAuth | // @Security ApiKeyAuth | ||||||
| // @Router /hosts/group/del [post] | // @Router /groups/del [post] | ||||||
| // @x-panel-log {"bodyKeys":["id"],"paramKeys":[],"BeforeFuntions":[{"input_colume":"id","input_value":"id","isList":false,"db":"groups","output_colume":"name","output_value":"name"},{"input_colume":"id","input_value":"id","isList":false,"db":"groups","output_colume":"type","output_value":"type"}],"formatZH":"删除组 [type][name]","formatEN":"delete group [type][name]"} | // @x-panel-log {"bodyKeys":["id"],"paramKeys":[],"BeforeFuntions":[{"input_column":"id","input_value":"id","isList":false,"db":"groups","output_column":"name","output_value":"name"},{"input_column":"id","input_value":"id","isList":false,"db":"groups","output_column":"type","output_value":"type"}],"formatZH":"删除组 [type][name]","formatEN":"delete group [type][name]"} | ||||||
| func (b *BaseApi) DeleteGroup(c *gin.Context) { | func (b *BaseApi) DeleteGroup(c *gin.Context) { | ||||||
| 	var req dto.OperateByID | 	var req dto.OperateByID | ||||||
| 	if err := c.ShouldBindJSON(&req); err != nil { | 	if err := c.ShouldBindJSON(&req); err != nil { | ||||||
| @@ -68,7 +68,7 @@ func (b *BaseApi) DeleteGroup(c *gin.Context) { | |||||||
| // @Param request body dto.GroupUpdate true "request" | // @Param request body dto.GroupUpdate true "request" | ||||||
| // @Success 200 | // @Success 200 | ||||||
| // @Security ApiKeyAuth | // @Security ApiKeyAuth | ||||||
| // @Router /hosts/group/update [post] | // @Router /groups/update [post] | ||||||
| // @x-panel-log {"bodyKeys":["name","type"],"paramKeys":[],"BeforeFuntions":[],"formatZH":"更新组 [name][type]","formatEN":"update group [name][type]"} | // @x-panel-log {"bodyKeys":["name","type"],"paramKeys":[],"BeforeFuntions":[],"formatZH":"更新组 [name][type]","formatEN":"update group [name][type]"} | ||||||
| func (b *BaseApi) UpdateGroup(c *gin.Context) { | func (b *BaseApi) UpdateGroup(c *gin.Context) { | ||||||
| 	var req dto.GroupUpdate | 	var req dto.GroupUpdate | ||||||
| @@ -92,9 +92,9 @@ func (b *BaseApi) UpdateGroup(c *gin.Context) { | |||||||
| // @Description 查询系统组 | // @Description 查询系统组 | ||||||
| // @Accept json | // @Accept json | ||||||
| // @Param request body dto.GroupSearch true "request" | // @Param request body dto.GroupSearch true "request" | ||||||
| // @Success 200 {anrry} dto.GroupInfo | // @Success 200 {array} dto.GroupInfo | ||||||
| // @Security ApiKeyAuth | // @Security ApiKeyAuth | ||||||
| // @Router /hosts/group/search [post] | // @Router /groups/search [post] | ||||||
| func (b *BaseApi) ListGroup(c *gin.Context) { | func (b *BaseApi) ListGroup(c *gin.Context) { | ||||||
| 	var req dto.GroupSearch | 	var req dto.GroupSearch | ||||||
| 	if err := c.ShouldBindJSON(&req); err != nil { | 	if err := c.ShouldBindJSON(&req); err != nil { | ||||||
|   | |||||||
| @@ -83,6 +83,15 @@ func SuccessWithData(ctx *gin.Context, data interface{}) { | |||||||
| 	ctx.Abort() | 	ctx.Abort() | ||||||
| } | } | ||||||
|  |  | ||||||
|  | func SuccessWithOutData(ctx *gin.Context) { | ||||||
|  | 	res := dto.Response{ | ||||||
|  | 		Code:    constant.CodeSuccess, | ||||||
|  | 		Message: "success", | ||||||
|  | 	} | ||||||
|  | 	ctx.JSON(http.StatusOK, res) | ||||||
|  | 	ctx.Abort() | ||||||
|  | } | ||||||
|  |  | ||||||
| func SuccessWithMsg(ctx *gin.Context, msg string) { | func SuccessWithMsg(ctx *gin.Context, msg string) { | ||||||
| 	res := dto.Response{ | 	res := dto.Response{ | ||||||
| 		Code:    constant.CodeSuccess, | 		Code:    constant.CodeSuccess, | ||||||
|   | |||||||
| @@ -7,8 +7,7 @@ import ( | |||||||
| 	"github.com/1Panel-dev/1Panel/backend/app/dto" | 	"github.com/1Panel-dev/1Panel/backend/app/dto" | ||||||
| 	"github.com/1Panel-dev/1Panel/backend/constant" | 	"github.com/1Panel-dev/1Panel/backend/constant" | ||||||
| 	"github.com/1Panel-dev/1Panel/backend/global" | 	"github.com/1Panel-dev/1Panel/backend/global" | ||||||
| 	"github.com/1Panel-dev/1Panel/backend/utils/copier" | 	"github.com/1Panel-dev/1Panel/backend/utils/encrypt" | ||||||
| 	"github.com/1Panel-dev/1Panel/backend/utils/ssh" |  | ||||||
| 	"github.com/gin-gonic/gin" | 	"github.com/gin-gonic/gin" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| @@ -37,7 +36,14 @@ func (b *BaseApi) CreateHost(c *gin.Context) { | |||||||
| 			helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err) | 			helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err) | ||||||
| 			return | 			return | ||||||
| 		} | 		} | ||||||
| 		req.Password = string(password) | 		passwordItem, err := encrypt.StringEncrypt(string(password)) | ||||||
|  | 		if err != nil { | ||||||
|  | 			helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err) | ||||||
|  | 			return | ||||||
|  | 		} | ||||||
|  | 		req.Password = passwordItem | ||||||
|  | 		req.PrivateKey = "" | ||||||
|  | 		req.PassPhrase = "" | ||||||
| 	} | 	} | ||||||
| 	if req.AuthMode == "key" && len(req.PrivateKey) != 0 { | 	if req.AuthMode == "key" && len(req.PrivateKey) != 0 { | ||||||
| 		privateKey, err := base64.StdEncoding.DecodeString(req.PrivateKey) | 		privateKey, err := base64.StdEncoding.DecodeString(req.PrivateKey) | ||||||
| @@ -45,7 +51,22 @@ func (b *BaseApi) CreateHost(c *gin.Context) { | |||||||
| 			helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err) | 			helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err) | ||||||
| 			return | 			return | ||||||
| 		} | 		} | ||||||
| 		req.PrivateKey = string(privateKey) | 		keyItem, err := encrypt.StringEncrypt(string(privateKey)) | ||||||
|  | 		if err != nil { | ||||||
|  | 			helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err) | ||||||
|  | 			return | ||||||
|  | 		} | ||||||
|  | 		req.Password = keyItem | ||||||
|  |  | ||||||
|  | 		if len(req.PassPhrase) != 0 { | ||||||
|  | 			pass, err := encrypt.StringEncrypt(req.PassPhrase) | ||||||
|  | 			if err != nil { | ||||||
|  | 				helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err) | ||||||
|  | 				return | ||||||
|  | 			} | ||||||
|  | 			req.PassPhrase = pass | ||||||
|  | 		} | ||||||
|  | 		req.Password = "" | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	host, err := hostService.Create(req) | 	host, err := hostService.Create(req) | ||||||
| @@ -74,33 +95,9 @@ func (b *BaseApi) TestByInfo(c *gin.Context) { | |||||||
| 		helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err) | 		helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err) | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
| 	if req.AuthMode == "password" && len(req.Password) != 0 { |  | ||||||
| 		password, err := base64.StdEncoding.DecodeString(req.Password) |  | ||||||
| 		if err != nil { |  | ||||||
| 			helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err) |  | ||||||
| 			return |  | ||||||
| 		} |  | ||||||
| 		req.Password = string(password) |  | ||||||
| 	} |  | ||||||
| 	if req.AuthMode == "key" && len(req.PrivateKey) != 0 { |  | ||||||
| 		privateKey, err := base64.StdEncoding.DecodeString(req.PrivateKey) |  | ||||||
| 		if err != nil { |  | ||||||
| 			helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err) |  | ||||||
| 			return |  | ||||||
| 		} |  | ||||||
| 		req.PrivateKey = string(privateKey) |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	var connInfo ssh.ConnInfo | 	connStatus := hostService.TestByInfo(req) | ||||||
| 	_ = copier.Copy(&connInfo, &req) | 	helper.SuccessWithData(c, connStatus) | ||||||
| 	connInfo.PrivateKey = []byte(req.PrivateKey) |  | ||||||
| 	client, err := connInfo.NewClient() |  | ||||||
| 	if err != nil { |  | ||||||
| 		helper.SuccessWithData(c, false) |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
| 	defer client.Close() |  | ||||||
| 	helper.SuccessWithData(c, true) |  | ||||||
| } | } | ||||||
|  |  | ||||||
| // @Tags Host | // @Tags Host | ||||||
| @@ -127,7 +124,7 @@ func (b *BaseApi) TestByID(c *gin.Context) { | |||||||
| // @Description 加载主机树 | // @Description 加载主机树 | ||||||
| // @Accept json | // @Accept json | ||||||
| // @Param request body dto.SearchForTree true "request" | // @Param request body dto.SearchForTree true "request" | ||||||
| // @Success 200 {anrry} dto.HostTree | // @Success 200 {array} dto.HostTree | ||||||
| // @Security ApiKeyAuth | // @Security ApiKeyAuth | ||||||
| // @Router /hosts/tree [post] | // @Router /hosts/tree [post] | ||||||
| func (b *BaseApi) HostTree(c *gin.Context) { | func (b *BaseApi) HostTree(c *gin.Context) { | ||||||
| @@ -151,7 +148,7 @@ func (b *BaseApi) HostTree(c *gin.Context) { | |||||||
| // @Description 获取主机列表分页 | // @Description 获取主机列表分页 | ||||||
| // @Accept json | // @Accept json | ||||||
| // @Param request body dto.SearchHostWithPage true "request" | // @Param request body dto.SearchHostWithPage true "request" | ||||||
| // @Success 200 {anrry} dto.HostTree | // @Success 200 {array} dto.HostTree | ||||||
| // @Security ApiKeyAuth | // @Security ApiKeyAuth | ||||||
| // @Router /hosts/search [post] | // @Router /hosts/search [post] | ||||||
| func (b *BaseApi) SearchHost(c *gin.Context) { | func (b *BaseApi) SearchHost(c *gin.Context) { | ||||||
| @@ -173,33 +170,6 @@ func (b *BaseApi) SearchHost(c *gin.Context) { | |||||||
| 	}) | 	}) | ||||||
| } | } | ||||||
|  |  | ||||||
| // @Tags Host |  | ||||||
| // @Summary Load host info |  | ||||||
| // @Description 加载主机信息 |  | ||||||
| // @Accept json |  | ||||||
| // @Param id path integer true "request" |  | ||||||
| // @Success 200 {object} dto.HostInfo |  | ||||||
| // @Security ApiKeyAuth |  | ||||||
| // @Router /hosts/:id [get] |  | ||||||
| func (b *BaseApi) GetHostInfo(c *gin.Context) { |  | ||||||
| 	id, err := helper.GetParamID(c) |  | ||||||
| 	if err != nil { |  | ||||||
| 		helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err) |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
| 	host, err := hostService.GetHostInfo(id) |  | ||||||
| 	if err != nil { |  | ||||||
| 		helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err) |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
| 	var hostDto dto.HostInfo |  | ||||||
| 	if err := copier.Copy(&hostDto, host); err != nil { |  | ||||||
| 		helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err) |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
| 	helper.SuccessWithData(c, hostDto) |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // @Tags Host | // @Tags Host | ||||||
| // @Summary Delete host | // @Summary Delete host | ||||||
| // @Description 删除主机 | // @Description 删除主机 | ||||||
| @@ -208,7 +178,7 @@ func (b *BaseApi) GetHostInfo(c *gin.Context) { | |||||||
| // @Success 200 | // @Success 200 | ||||||
| // @Security ApiKeyAuth | // @Security ApiKeyAuth | ||||||
| // @Router /hosts/del [post] | // @Router /hosts/del [post] | ||||||
| // @x-panel-log {"bodyKeys":["ids"],"paramKeys":[],"BeforeFuntions":[{"input_colume":"id","input_value":"ids","isList":true,"db":"hosts","output_colume":"addr","output_value":"addrs"}],"formatZH":"删除主机 [addrs]","formatEN":"delete host [addrs]"} | // @x-panel-log {"bodyKeys":["ids"],"paramKeys":[],"BeforeFuntions":[{"input_column":"id","input_value":"ids","isList":true,"db":"hosts","output_column":"addr","output_value":"addrs"}],"formatZH":"删除主机 [addrs]","formatEN":"delete host [addrs]"} | ||||||
| func (b *BaseApi) DeleteHost(c *gin.Context) { | func (b *BaseApi) DeleteHost(c *gin.Context) { | ||||||
| 	var req dto.BatchDeleteReq | 	var req dto.BatchDeleteReq | ||||||
| 	if err := c.ShouldBindJSON(&req); err != nil { | 	if err := c.ShouldBindJSON(&req); err != nil { | ||||||
| @@ -252,7 +222,12 @@ func (b *BaseApi) UpdateHost(c *gin.Context) { | |||||||
| 			helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err) | 			helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err) | ||||||
| 			return | 			return | ||||||
| 		} | 		} | ||||||
| 		req.Password = string(password) | 		passwordItem, err := encrypt.StringEncrypt(string(password)) | ||||||
|  | 		if err != nil { | ||||||
|  | 			helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err) | ||||||
|  | 			return | ||||||
|  | 		} | ||||||
|  | 		req.Password = passwordItem | ||||||
| 	} | 	} | ||||||
| 	if req.AuthMode == "key" && len(req.PrivateKey) != 0 { | 	if req.AuthMode == "key" && len(req.PrivateKey) != 0 { | ||||||
| 		privateKey, err := base64.StdEncoding.DecodeString(req.PrivateKey) | 		privateKey, err := base64.StdEncoding.DecodeString(req.PrivateKey) | ||||||
| @@ -260,7 +235,21 @@ func (b *BaseApi) UpdateHost(c *gin.Context) { | |||||||
| 			helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err) | 			helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err) | ||||||
| 			return | 			return | ||||||
| 		} | 		} | ||||||
| 		req.PrivateKey = string(privateKey) | 		keyItem, err := encrypt.StringEncrypt(string(privateKey)) | ||||||
|  | 		if err != nil { | ||||||
|  | 			helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err) | ||||||
|  | 			return | ||||||
|  | 		} | ||||||
|  | 		req.PrivateKey = keyItem | ||||||
|  |  | ||||||
|  | 		if len(req.PassPhrase) != 0 { | ||||||
|  | 			pass, err := encrypt.StringEncrypt(req.PassPhrase) | ||||||
|  | 			if err != nil { | ||||||
|  | 				helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err) | ||||||
|  | 				return | ||||||
|  | 			} | ||||||
|  | 			req.PassPhrase = pass | ||||||
|  | 		} | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	upMap := make(map[string]interface{}) | 	upMap := make(map[string]interface{}) | ||||||
| @@ -270,11 +259,15 @@ func (b *BaseApi) UpdateHost(c *gin.Context) { | |||||||
| 	upMap["port"] = req.Port | 	upMap["port"] = req.Port | ||||||
| 	upMap["user"] = req.User | 	upMap["user"] = req.User | ||||||
| 	upMap["auth_mode"] = req.AuthMode | 	upMap["auth_mode"] = req.AuthMode | ||||||
| 	if len(req.Password) != 0 { | 	upMap["remember_password"] = req.RememberPassword | ||||||
|  | 	if req.AuthMode == "password" { | ||||||
| 		upMap["password"] = req.Password | 		upMap["password"] = req.Password | ||||||
| 	} | 		upMap["private_key"] = "" | ||||||
| 	if len(req.PrivateKey) != 0 { | 		upMap["pass_phrase"] = "" | ||||||
|  | 	} else { | ||||||
|  | 		upMap["password"] = "" | ||||||
| 		upMap["private_key"] = req.PrivateKey | 		upMap["private_key"] = req.PrivateKey | ||||||
|  | 		upMap["pass_phrase"] = req.PassPhrase | ||||||
| 	} | 	} | ||||||
| 	upMap["description"] = req.Description | 	upMap["description"] = req.Description | ||||||
| 	if err := hostService.Update(req.ID, upMap); err != nil { | 	if err := hostService.Update(req.ID, upMap); err != nil { | ||||||
| @@ -291,8 +284,8 @@ func (b *BaseApi) UpdateHost(c *gin.Context) { | |||||||
| // @Param request body dto.ChangeHostGroup true "request" | // @Param request body dto.ChangeHostGroup true "request" | ||||||
| // @Success 200 | // @Success 200 | ||||||
| // @Security ApiKeyAuth | // @Security ApiKeyAuth | ||||||
| // @Router /hosts/update [post] | // @Router /hosts/update/group [post] | ||||||
| // @x-panel-log {"bodyKeys":["id","group"],"paramKeys":[],"BeforeFuntions":[{"input_colume":"id","input_value":"id","isList":false,"db":"hosts","output_colume":"addr","output_value":"addr"}],"formatZH":"切换主机[addr]分组 => [group]","formatEN":"change host [addr] group => [group]"} | // @x-panel-log {"bodyKeys":["id","group"],"paramKeys":[],"BeforeFuntions":[{"input_column":"id","input_value":"id","isList":false,"db":"hosts","output_column":"addr","output_value":"addr"}],"formatZH":"切换主机[addr]分组 => [group]","formatEN":"change host [addr] group => [group]"} | ||||||
| func (b *BaseApi) UpdateHostGroup(c *gin.Context) { | func (b *BaseApi) UpdateHostGroup(c *gin.Context) { | ||||||
| 	var req dto.ChangeHostGroup | 	var req dto.ChangeHostGroup | ||||||
| 	if err := c.ShouldBindJSON(&req); err != nil { | 	if err := c.ShouldBindJSON(&req); err != nil { | ||||||
|   | |||||||
| @@ -44,7 +44,7 @@ func (b *BaseApi) SearchImage(c *gin.Context) { | |||||||
| // @Summary List images | // @Summary List images | ||||||
| // @Description 获取镜像列表 | // @Description 获取镜像列表 | ||||||
| // @Produce json | // @Produce json | ||||||
| // @Success 200 {anrry} dto.Options | // @Success 200 {array} dto.Options | ||||||
| // @Security ApiKeyAuth | // @Security ApiKeyAuth | ||||||
| // @Router /containers/image [get] | // @Router /containers/image [get] | ||||||
| func (b *BaseApi) ListImage(c *gin.Context) { | func (b *BaseApi) ListImage(c *gin.Context) { | ||||||
| @@ -93,7 +93,7 @@ func (b *BaseApi) ImageBuild(c *gin.Context) { | |||||||
| // @Success 200 {string} log | // @Success 200 {string} log | ||||||
| // @Security ApiKeyAuth | // @Security ApiKeyAuth | ||||||
| // @Router /containers/image/pull [post] | // @Router /containers/image/pull [post] | ||||||
| // @x-panel-log {"bodyKeys":["repoID","imageName"],"paramKeys":[],"BeforeFuntions":[{"input_colume":"id","input_value":"repoID","isList":false,"db":"image_repos","output_colume":"name","output_value":"reponame"}],"formatZH":"镜像拉取 [reponame][imageName]","formatEN":"image pull [reponame][imageName]"} | // @x-panel-log {"bodyKeys":["repoID","imageName"],"paramKeys":[],"BeforeFuntions":[{"input_column":"id","input_value":"repoID","isList":false,"db":"image_repos","output_column":"name","output_value":"reponame"}],"formatZH":"镜像拉取 [reponame][imageName]","formatEN":"image pull [reponame][imageName]"} | ||||||
| func (b *BaseApi) ImagePull(c *gin.Context) { | func (b *BaseApi) ImagePull(c *gin.Context) { | ||||||
| 	var req dto.ImagePull | 	var req dto.ImagePull | ||||||
| 	if err := c.ShouldBindJSON(&req); err != nil { | 	if err := c.ShouldBindJSON(&req); err != nil { | ||||||
| @@ -122,7 +122,7 @@ func (b *BaseApi) ImagePull(c *gin.Context) { | |||||||
| // @Success 200 {string} log | // @Success 200 {string} log | ||||||
| // @Security ApiKeyAuth | // @Security ApiKeyAuth | ||||||
| // @Router /containers/image/push [post] | // @Router /containers/image/push [post] | ||||||
| // @x-panel-log {"bodyKeys":["repoID","tagName","name"],"paramKeys":[],"BeforeFuntions":[{"input_colume":"id","input_value":"repoID","isList":false,"db":"image_repos","output_colume":"name","output_value":"reponame"}],"formatZH":"[tagName] 推送到 [reponame][name]","formatEN":"push [tagName] to [reponame][name]"} | // @x-panel-log {"bodyKeys":["repoID","tagName","name"],"paramKeys":[],"BeforeFuntions":[{"input_column":"id","input_value":"repoID","isList":false,"db":"image_repos","output_column":"name","output_value":"reponame"}],"formatZH":"[tagName] 推送到 [reponame][name]","formatEN":"push [tagName] to [reponame][name]"} | ||||||
| func (b *BaseApi) ImagePush(c *gin.Context) { | func (b *BaseApi) ImagePush(c *gin.Context) { | ||||||
| 	var req dto.ImagePush | 	var req dto.ImagePush | ||||||
| 	if err := c.ShouldBindJSON(&req); err != nil { | 	if err := c.ShouldBindJSON(&req); err != nil { | ||||||
| @@ -207,7 +207,7 @@ func (b *BaseApi) ImageSave(c *gin.Context) { | |||||||
| // @Success 200 | // @Success 200 | ||||||
| // @Security ApiKeyAuth | // @Security ApiKeyAuth | ||||||
| // @Router /containers/image/tag [post] | // @Router /containers/image/tag [post] | ||||||
| // @x-panel-log {"bodyKeys":["repoID","targetName"],"paramKeys":[],"BeforeFuntions":[{"input_colume":"id","input_value":"repoID","isList":false,"db":"image_repos","output_colume":"name","output_value":"reponame"}],"formatZH":"tag 镜像 [reponame][targetName]","formatEN":"tag image [reponame][targetName]"} | // @x-panel-log {"bodyKeys":["repoID","targetName"],"paramKeys":[],"BeforeFuntions":[{"input_column":"id","input_value":"repoID","isList":false,"db":"image_repos","output_column":"name","output_value":"reponame"}],"formatZH":"tag 镜像 [reponame][targetName]","formatEN":"tag image [reponame][targetName]"} | ||||||
| func (b *BaseApi) ImageTag(c *gin.Context) { | func (b *BaseApi) ImageTag(c *gin.Context) { | ||||||
| 	var req dto.ImageTag | 	var req dto.ImageTag | ||||||
| 	if err := c.ShouldBindJSON(&req); err != nil { | 	if err := c.ShouldBindJSON(&req); err != nil { | ||||||
|   | |||||||
| @@ -44,7 +44,7 @@ func (b *BaseApi) SearchRepo(c *gin.Context) { | |||||||
| // @Summary List image repos | // @Summary List image repos | ||||||
| // @Description 获取镜像仓库列表 | // @Description 获取镜像仓库列表 | ||||||
| // @Produce json | // @Produce json | ||||||
| // @Success 200 {anrry} dto.ImageRepoOption | // @Success 200 {array} dto.ImageRepoOption | ||||||
| // @Security ApiKeyAuth | // @Security ApiKeyAuth | ||||||
| // @Router /containers/repo [get] | // @Router /containers/repo [get] | ||||||
| func (b *BaseApi) ListRepo(c *gin.Context) { | func (b *BaseApi) ListRepo(c *gin.Context) { | ||||||
| @@ -119,7 +119,7 @@ func (b *BaseApi) CreateRepo(c *gin.Context) { | |||||||
| // @Success 200 | // @Success 200 | ||||||
| // @Security ApiKeyAuth | // @Security ApiKeyAuth | ||||||
| // @Router /containers/repo/del [post] | // @Router /containers/repo/del [post] | ||||||
| // @x-panel-log {"bodyKeys":["ids"],"paramKeys":[],"BeforeFuntions":[{"input_colume":"id","input_value":"ids","isList":true,"db":"image_repos","output_colume":"name","output_value":"names"}],"formatZH":"删除镜像仓库 [names]","formatEN":"delete image repo [names]"} | // @x-panel-log {"bodyKeys":["ids"],"paramKeys":[],"BeforeFuntions":[{"input_column":"id","input_value":"ids","isList":true,"db":"image_repos","output_column":"name","output_value":"names"}],"formatZH":"删除镜像仓库 [names]","formatEN":"delete image repo [names]"} | ||||||
| func (b *BaseApi) DeleteRepo(c *gin.Context) { | func (b *BaseApi) DeleteRepo(c *gin.Context) { | ||||||
| 	var req dto.ImageRepoDelete | 	var req dto.ImageRepoDelete | ||||||
| 	if err := c.ShouldBindJSON(&req); err != nil { | 	if err := c.ShouldBindJSON(&req); err != nil { | ||||||
| @@ -147,7 +147,7 @@ func (b *BaseApi) DeleteRepo(c *gin.Context) { | |||||||
| // @Success 200 | // @Success 200 | ||||||
| // @Security ApiKeyAuth | // @Security ApiKeyAuth | ||||||
| // @Router /containers/repo/update [post] | // @Router /containers/repo/update [post] | ||||||
| // @x-panel-log {"bodyKeys":["id"],"paramKeys":[],"BeforeFuntions":[{"input_column":"id","input_value":"id","isList":false,"db":"image_repos","output_colume":"name","output_value":"name"}],"formatZH":"更新镜像仓库 [name]","formatEN":"update image repo information [name]"} | // @x-panel-log {"bodyKeys":["id"],"paramKeys":[],"BeforeFuntions":[{"input_column":"id","input_value":"id","isList":false,"db":"image_repos","output_column":"name","output_value":"name"}],"formatZH":"更新镜像仓库 [name]","formatEN":"update image repo information [name]"} | ||||||
| func (b *BaseApi) UpdateRepo(c *gin.Context) { | func (b *BaseApi) UpdateRepo(c *gin.Context) { | ||||||
| 	var req dto.ImageRepoUpdate | 	var req dto.ImageRepoUpdate | ||||||
| 	if err := c.ShouldBindJSON(&req); err != nil { | 	if err := c.ShouldBindJSON(&req); err != nil { | ||||||
|   | |||||||
| @@ -1,6 +1,7 @@ | |||||||
| package v1 | package v1 | ||||||
|  |  | ||||||
| import ( | import ( | ||||||
|  | 	"sort" | ||||||
| 	"time" | 	"time" | ||||||
|  |  | ||||||
| 	"github.com/1Panel-dev/1Panel/backend/app/api/v1/helper" | 	"github.com/1Panel-dev/1Panel/backend/app/api/v1/helper" | ||||||
| @@ -8,6 +9,7 @@ import ( | |||||||
| 	"github.com/1Panel-dev/1Panel/backend/app/model" | 	"github.com/1Panel-dev/1Panel/backend/app/model" | ||||||
| 	"github.com/1Panel-dev/1Panel/backend/constant" | 	"github.com/1Panel-dev/1Panel/backend/constant" | ||||||
| 	"github.com/1Panel-dev/1Panel/backend/global" | 	"github.com/1Panel-dev/1Panel/backend/global" | ||||||
|  | 	"github.com/1Panel-dev/1Panel/backend/utils/common" | ||||||
| 	"github.com/gin-gonic/gin" | 	"github.com/gin-gonic/gin" | ||||||
| 	"github.com/shirou/gopsutil/v3/disk" | 	"github.com/shirou/gopsutil/v3/disk" | ||||||
| 	"github.com/shirou/gopsutil/v3/net" | 	"github.com/shirou/gopsutil/v3/net" | ||||||
| @@ -23,8 +25,9 @@ func (b *BaseApi) LoadMonitor(c *gin.Context) { | |||||||
| 		helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err) | 		helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err) | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
| 	req.StartTime = req.StartTime.Add(8 * time.Hour) | 	loc, _ := time.LoadLocation(common.LoadTimeZone()) | ||||||
| 	req.EndTime = req.EndTime.Add(8 * time.Hour) | 	req.StartTime = req.StartTime.In(loc) | ||||||
|  | 	req.EndTime = req.EndTime.In(loc) | ||||||
|  |  | ||||||
| 	var backdatas []dto.MonitorData | 	var backdatas []dto.MonitorData | ||||||
| 	if req.Param == "all" || req.Param == "cpu" || req.Param == "memory" || req.Param == "load" { | 	if req.Param == "all" || req.Param == "cpu" || req.Param == "memory" || req.Param == "load" { | ||||||
| @@ -88,6 +91,7 @@ func (b *BaseApi) GetNetworkOptions(c *gin.Context) { | |||||||
| 	for _, net := range netStat { | 	for _, net := range netStat { | ||||||
| 		options = append(options, net.Name) | 		options = append(options, net.Name) | ||||||
| 	} | 	} | ||||||
|  | 	sort.Strings(options) | ||||||
| 	helper.SuccessWithData(c, options) | 	helper.SuccessWithData(c, options) | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -98,5 +102,6 @@ func (b *BaseApi) GetIOOptions(c *gin.Context) { | |||||||
| 	for _, net := range diskStat { | 	for _, net := range diskStat { | ||||||
| 		options = append(options, net.Name) | 		options = append(options, net.Name) | ||||||
| 	} | 	} | ||||||
|  | 	sort.Strings(options) | ||||||
| 	helper.SuccessWithData(c, options) | 	helper.SuccessWithData(c, options) | ||||||
| } | } | ||||||
|   | |||||||
| @@ -27,7 +27,7 @@ func (b *BaseApi) GetNginx(c *gin.Context) { | |||||||
| // @Description 获取部分 OpenResty 配置信息 | // @Description 获取部分 OpenResty 配置信息 | ||||||
| // @Accept json | // @Accept json | ||||||
| // @Param request body request.NginxScopeReq true "request" | // @Param request body request.NginxScopeReq true "request" | ||||||
| // @Success 200 {anrry} response.NginxParam | // @Success 200 {array} response.NginxParam | ||||||
| // @Security ApiKeyAuth | // @Security ApiKeyAuth | ||||||
| // @Router /openResty/scope [post] | // @Router /openResty/scope [post] | ||||||
| func (b *BaseApi) GetNginxConfigByScope(c *gin.Context) { | func (b *BaseApi) GetNginxConfigByScope(c *gin.Context) { | ||||||
| @@ -53,7 +53,7 @@ func (b *BaseApi) GetNginxConfigByScope(c *gin.Context) { | |||||||
| // @Success 200 | // @Success 200 | ||||||
| // @Security ApiKeyAuth | // @Security ApiKeyAuth | ||||||
| // @Router /openResty/update [post] | // @Router /openResty/update [post] | ||||||
| // @x-panel-log {"bodyKeys":["websiteId"],"paramKeys":[],"BeforeFuntions":[{"input_colume":"id","input_value":"websiteId","isList":false,"db":"websites","output_colume":"primary_domain","output_value":"domain"}],"formatZH":"更新 nginx 配置 [domain]","formatEN":"Update nginx conf [domain]"} | // @x-panel-log {"bodyKeys":["websiteId"],"paramKeys":[],"BeforeFuntions":[{"input_column":"id","input_value":"websiteId","isList":false,"db":"websites","output_column":"primary_domain","output_value":"domain"}],"formatZH":"更新 nginx 配置 [domain]","formatEN":"Update nginx conf [domain]"} | ||||||
| func (b *BaseApi) UpdateNginxConfigByScope(c *gin.Context) { | func (b *BaseApi) UpdateNginxConfigByScope(c *gin.Context) { | ||||||
| 	var req request.NginxConfigUpdate | 	var req request.NginxConfigUpdate | ||||||
| 	if err := c.ShouldBindJSON(&req); err != nil { | 	if err := c.ShouldBindJSON(&req); err != nil { | ||||||
|   | |||||||
							
								
								
									
										40
									
								
								backend/app/api/v1/process.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										40
									
								
								backend/app/api/v1/process.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,40 @@ | |||||||
|  | package v1 | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"github.com/1Panel-dev/1Panel/backend/app/api/v1/helper" | ||||||
|  | 	"github.com/1Panel-dev/1Panel/backend/app/dto/request" | ||||||
|  | 	"github.com/1Panel-dev/1Panel/backend/constant" | ||||||
|  | 	websocket2 "github.com/1Panel-dev/1Panel/backend/utils/websocket" | ||||||
|  | 	"github.com/gin-gonic/gin" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | func (b *BaseApi) ProcessWs(c *gin.Context) { | ||||||
|  | 	ws, err := wsUpgrade.Upgrade(c.Writer, c.Request, nil) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	wsClient := websocket2.NewWsClient("processClient", ws) | ||||||
|  | 	go wsClient.Read() | ||||||
|  | 	go wsClient.Write() | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // @Tags Process | ||||||
|  | // @Summary Stop Process | ||||||
|  | // @Description 停止进程 | ||||||
|  | // @Param request body request.ProcessReq true "request" | ||||||
|  | // @Success 200 | ||||||
|  | // @Security ApiKeyAuth | ||||||
|  | // @Router /process/stop [post] | ||||||
|  | // @x-panel-log {"bodyKeys":["PID"],"paramKeys":[],"BeforeFuntions":[],"formatZH":"结束进程 [PID]","formatEN":"结束进程 [PID]"} | ||||||
|  | func (b *BaseApi) StopProcess(c *gin.Context) { | ||||||
|  | 	var req request.ProcessReq | ||||||
|  | 	if err := c.ShouldBindJSON(&req); err != nil { | ||||||
|  | 		helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	if err := processService.StopProcess(req); err != nil { | ||||||
|  | 		helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	helper.SuccessWithOutData(c) | ||||||
|  | } | ||||||
							
								
								
									
										123
									
								
								backend/app/api/v1/runtime.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										123
									
								
								backend/app/api/v1/runtime.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,123 @@ | |||||||
|  | package v1 | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"github.com/1Panel-dev/1Panel/backend/app/api/v1/helper" | ||||||
|  | 	"github.com/1Panel-dev/1Panel/backend/app/dto" | ||||||
|  | 	"github.com/1Panel-dev/1Panel/backend/app/dto/request" | ||||||
|  | 	"github.com/1Panel-dev/1Panel/backend/constant" | ||||||
|  | 	"github.com/gin-gonic/gin" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | // @Tags Runtime | ||||||
|  | // @Summary List runtimes | ||||||
|  | // @Description 获取运行环境列表 | ||||||
|  | // @Accept json | ||||||
|  | // @Param request body request.RuntimeSearch true "request" | ||||||
|  | // @Success 200 | ||||||
|  | // @Security ApiKeyAuth | ||||||
|  | // @Router /runtimes/search [post] | ||||||
|  | func (b *BaseApi) SearchRuntimes(c *gin.Context) { | ||||||
|  | 	var req request.RuntimeSearch | ||||||
|  | 	if err := c.ShouldBindJSON(&req); err != nil { | ||||||
|  | 		helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	total, items, err := runtimeService.Page(req) | ||||||
|  | 	if err != nil { | ||||||
|  | 		helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	helper.SuccessWithData(c, dto.PageResult{ | ||||||
|  | 		Total: total, | ||||||
|  | 		Items: items, | ||||||
|  | 	}) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // @Tags Runtime | ||||||
|  | // @Summary Create runtime | ||||||
|  | // @Description 创建运行环境 | ||||||
|  | // @Accept json | ||||||
|  | // @Param request body request.RuntimeCreate true "request" | ||||||
|  | // @Success 200 | ||||||
|  | // @Security ApiKeyAuth | ||||||
|  | // @Router /runtimes [post] | ||||||
|  | // @x-panel-log {"bodyKeys":["name"],"paramKeys":[],"BeforeFuntions":[],"formatZH":"创建运行环境 [name]","formatEN":"Create runtime [name]"} | ||||||
|  | func (b *BaseApi) CreateRuntime(c *gin.Context) { | ||||||
|  | 	var req request.RuntimeCreate | ||||||
|  | 	if err := c.ShouldBindJSON(&req); err != nil { | ||||||
|  | 		helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	if err := runtimeService.Create(req); err != nil { | ||||||
|  | 		helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	helper.SuccessWithOutData(c) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // @Tags Website | ||||||
|  | // @Summary Delete runtime | ||||||
|  | // @Description 删除运行环境 | ||||||
|  | // @Accept json | ||||||
|  | // @Param request body request.RuntimeDelete true "request" | ||||||
|  | // @Success 200 | ||||||
|  | // @Security ApiKeyAuth | ||||||
|  | // @Router /runtimes/del [post] | ||||||
|  | // @x-panel-log {"bodyKeys":["id"],"paramKeys":[],"BeforeFuntions":[],"formatZH":"删除网站 [name]","formatEN":"Delete website [name]"} | ||||||
|  | func (b *BaseApi) DeleteRuntime(c *gin.Context) { | ||||||
|  | 	var req request.RuntimeDelete | ||||||
|  | 	if err := c.ShouldBindJSON(&req); err != nil { | ||||||
|  | 		helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	err := runtimeService.Delete(req.ID) | ||||||
|  | 	if err != nil { | ||||||
|  | 		helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	helper.SuccessWithOutData(c) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // @Tags Runtime | ||||||
|  | // @Summary Update runtime | ||||||
|  | // @Description 更新运行环境 | ||||||
|  | // @Accept json | ||||||
|  | // @Param request body request.RuntimeUpdate true "request" | ||||||
|  | // @Success 200 | ||||||
|  | // @Security ApiKeyAuth | ||||||
|  | // @Router /runtimes/update [post] | ||||||
|  | // @x-panel-log {"bodyKeys":["name"],"paramKeys":[],"BeforeFuntions":[],"formatZH":"更新运行环境 [name]","formatEN":"Update runtime [name]"} | ||||||
|  | func (b *BaseApi) UpdateRuntime(c *gin.Context) { | ||||||
|  | 	var req request.RuntimeUpdate | ||||||
|  | 	if err := c.ShouldBindJSON(&req); err != nil { | ||||||
|  | 		helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	if err := runtimeService.Update(req); err != nil { | ||||||
|  | 		helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	helper.SuccessWithOutData(c) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // @Tags Runtime | ||||||
|  | // @Summary Get runtime | ||||||
|  | // @Description 获取运行环境 | ||||||
|  | // @Accept json | ||||||
|  | // @Param id path string true "request" | ||||||
|  | // @Success 200 | ||||||
|  | // @Security ApiKeyAuth | ||||||
|  | // @Router /runtimes/:id [get] | ||||||
|  | func (b *BaseApi) GetRuntime(c *gin.Context) { | ||||||
|  | 	id, err := helper.GetIntParamByKey(c, "id") | ||||||
|  | 	if err != nil { | ||||||
|  | 		helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInternalServer, nil) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	res, err := runtimeService.Get(id) | ||||||
|  | 	if err != nil { | ||||||
|  | 		helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	helper.SuccessWithData(c, res) | ||||||
|  | } | ||||||
| @@ -2,14 +2,14 @@ package v1 | |||||||
|  |  | ||||||
| import ( | import ( | ||||||
| 	"errors" | 	"errors" | ||||||
| 	"time" | 	"fmt" | ||||||
|  | 	"strconv" | ||||||
|  |  | ||||||
| 	"github.com/1Panel-dev/1Panel/backend/app/api/v1/helper" | 	"github.com/1Panel-dev/1Panel/backend/app/api/v1/helper" | ||||||
| 	"github.com/1Panel-dev/1Panel/backend/app/dto" | 	"github.com/1Panel-dev/1Panel/backend/app/dto" | ||||||
| 	"github.com/1Panel-dev/1Panel/backend/constant" | 	"github.com/1Panel-dev/1Panel/backend/constant" | ||||||
| 	"github.com/1Panel-dev/1Panel/backend/global" | 	"github.com/1Panel-dev/1Panel/backend/global" | ||||||
| 	"github.com/1Panel-dev/1Panel/backend/utils/mfa" | 	"github.com/1Panel-dev/1Panel/backend/utils/mfa" | ||||||
| 	"github.com/1Panel-dev/1Panel/backend/utils/ntp" |  | ||||||
| 	"github.com/gin-gonic/gin" | 	"github.com/gin-gonic/gin" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| @@ -92,6 +92,48 @@ func (b *BaseApi) UpdatePassword(c *gin.Context) { | |||||||
| 	helper.SuccessWithData(c, nil) | 	helper.SuccessWithData(c, nil) | ||||||
| } | } | ||||||
|  |  | ||||||
|  | // @Tags System Setting | ||||||
|  | // @Summary Update system ssl | ||||||
|  | // @Description 修改系统 ssl 登录 | ||||||
|  | // @Accept json | ||||||
|  | // @Param request body dto.SSLUpdate true "request" | ||||||
|  | // @Success 200 | ||||||
|  | // @Security ApiKeyAuth | ||||||
|  | // @Router /settings/ssl/update [post] | ||||||
|  | // @x-panel-log {"bodyKeys":["ssl"],"paramKeys":[],"BeforeFuntions":[],"formatZH":"修改系统 ssl => [ssl]","formatEN":"update system ssl => [ssl]"} | ||||||
|  | func (b *BaseApi) UpdateSSL(c *gin.Context) { | ||||||
|  | 	var req dto.SSLUpdate | ||||||
|  | 	if err := c.ShouldBindJSON(&req); err != nil { | ||||||
|  | 		helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	if err := global.VALID.Struct(req); err != nil { | ||||||
|  | 		helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if err := settingService.UpdateSSL(c, req); err != nil { | ||||||
|  | 		helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	helper.SuccessWithData(c, nil) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // @Tags System Setting | ||||||
|  | // @Summary Load system cert info | ||||||
|  | // @Description 获取证书信息 | ||||||
|  | // @Success 200 {object} dto.SettingInfo | ||||||
|  | // @Security ApiKeyAuth | ||||||
|  | // @Router /settings/ssl/info [get] | ||||||
|  | func (b *BaseApi) LoadFromCert(c *gin.Context) { | ||||||
|  | 	info, err := settingService.LoadFromCert() | ||||||
|  | 	if err != nil { | ||||||
|  | 		helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	helper.SuccessWithData(c, info) | ||||||
|  | } | ||||||
|  |  | ||||||
| // @Tags System Setting | // @Tags System Setting | ||||||
| // @Summary Update system port | // @Summary Update system port | ||||||
| // @Description 更新系统端口 | // @Description 更新系统端口 | ||||||
| @@ -147,26 +189,40 @@ func (b *BaseApi) HandlePasswordExpired(c *gin.Context) { | |||||||
| } | } | ||||||
|  |  | ||||||
| // @Tags System Setting | // @Tags System Setting | ||||||
| // @Summary Sync system time | // @Summary Load time zone options | ||||||
| // @Description 系统时间同步 | // @Description 加载系统可用时区 | ||||||
| // @Success 200 {string} ntime | // @Success 200 | ||||||
| // @Security ApiKeyAuth | // @Security ApiKeyAuth | ||||||
| // @Router /settings/time/sync [post] | // @Router /settings/time/option [get] | ||||||
| // @x-panel-log {"bodyKeys":[],"paramKeys":[],"BeforeFuntions":[],"formatZH":"系统时间同步","formatEN":"sync system time"} | func (b *BaseApi) LoadTimeZone(c *gin.Context) { | ||||||
| func (b *BaseApi) SyncTime(c *gin.Context) { | 	zones, err := settingService.LoadTimeZone() | ||||||
| 	ntime, err := ntp.Getremotetime() |  | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		helper.SuccessWithData(c, time.Now().Format("2006-01-02 15:04:05 MST -0700")) |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	ts := ntime.Format("2006-01-02 15:04:05") |  | ||||||
| 	if err := ntp.UpdateSystemDate(ts); err != nil { |  | ||||||
| 		helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err) | 		helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err) | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
|  | 	helper.SuccessWithData(c, zones) | ||||||
|  | } | ||||||
|  |  | ||||||
| 	helper.SuccessWithData(c, ntime.Format("2006-01-02 15:04:05 MST -0700")) | // @Tags System Setting | ||||||
|  | // @Summary Sync system time | ||||||
|  | // @Description 系统时间同步 | ||||||
|  | // @Accept json | ||||||
|  | // @Param request body dto.SyncTime true "request" | ||||||
|  | // @Success 200 {string} ntime | ||||||
|  | // @Security ApiKeyAuth | ||||||
|  | // @Router /settings/time/sync [post] | ||||||
|  | // @x-panel-log {"bodyKeys":["ntpSite"],"paramKeys":[],"BeforeFuntions":[],"formatZH":"系统时间同步[ntpSite]","formatEN":"sync system time [ntpSite]"} | ||||||
|  | func (b *BaseApi) SyncTime(c *gin.Context) { | ||||||
|  | 	var req dto.SyncTime | ||||||
|  | 	if err := c.ShouldBindJSON(&req); err != nil { | ||||||
|  | 		helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	if err := settingService.SyncTime(req); err != nil { | ||||||
|  | 		helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	helper.SuccessWithData(c, nil) | ||||||
| } | } | ||||||
|  |  | ||||||
| // @Tags System Setting | // @Tags System Setting | ||||||
| @@ -206,11 +262,23 @@ func (b *BaseApi) CleanMonitor(c *gin.Context) { | |||||||
| // @Tags System Setting | // @Tags System Setting | ||||||
| // @Summary Load mfa info | // @Summary Load mfa info | ||||||
| // @Description 获取 mfa 信息 | // @Description 获取 mfa 信息 | ||||||
|  | // @Param interval path string true "request" | ||||||
| // @Success 200 {object} mfa.Otp | // @Success 200 {object} mfa.Otp | ||||||
| // @Security ApiKeyAuth | // @Security ApiKeyAuth | ||||||
| // @Router /settings/mfa [get] | // @Router /settings/mfa/:interval [get] | ||||||
| func (b *BaseApi) GetMFA(c *gin.Context) { | func (b *BaseApi) GetMFA(c *gin.Context) { | ||||||
| 	otp, err := mfa.GetOtp("admin") | 	intervalStr, ok := c.Params.Get("interval") | ||||||
|  | 	if !ok { | ||||||
|  | 		helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, errors.New("error interval in path")) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	interval, err := strconv.Atoi(intervalStr) | ||||||
|  | 	if err != nil { | ||||||
|  | 		helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, fmt.Errorf("type conversion failed, err: %v", err)) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	otp, err := mfa.GetOtp("admin", interval) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err) | 		helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err) | ||||||
| 		return | 		return | ||||||
| @@ -234,12 +302,17 @@ func (b *BaseApi) MFABind(c *gin.Context) { | |||||||
| 		helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err) | 		helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err) | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
| 	success := mfa.ValidCode(req.Code, req.Secret) | 	success := mfa.ValidCode(req.Code, req.Interval, req.Secret) | ||||||
| 	if !success { | 	if !success { | ||||||
| 		helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, errors.New("code is not valid")) | 		helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, errors.New("code is not valid")) | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | 	if err := settingService.Update("MFAInterval", req.Interval); err != nil { | ||||||
|  | 		helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  |  | ||||||
| 	if err := settingService.Update("MFAStatus", "enable"); err != nil { | 	if err := settingService.Update("MFAStatus", "enable"); err != nil { | ||||||
| 		helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err) | 		helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err) | ||||||
| 		return | 		return | ||||||
|   | |||||||
| @@ -68,7 +68,7 @@ func (b *BaseApi) ImportSnapshot(c *gin.Context) { | |||||||
| // @Success 200 | // @Success 200 | ||||||
| // @Security ApiKeyAuth | // @Security ApiKeyAuth | ||||||
| // @Router /settings/snapshot/description/update [post] | // @Router /settings/snapshot/description/update [post] | ||||||
| // @x-panel-log {"bodyKeys":["id","description"],"paramKeys":[],"BeforeFuntions":[{"input_colume":"id","input_value":"id","isList":false,"db":"snapshots","output_colume":"name","output_value":"name"}],"formatZH":"快照 [name] 描述信息修改 [description]","formatEN":"The description of the snapshot [name] is modified => [description]"} | // @x-panel-log {"bodyKeys":["id","description"],"paramKeys":[],"BeforeFuntions":[{"input_column":"id","input_value":"id","isList":false,"db":"snapshots","output_column":"name","output_value":"name"}],"formatZH":"快照 [name] 描述信息修改 [description]","formatEN":"The description of the snapshot [name] is modified => [description]"} | ||||||
| func (b *BaseApi) UpdateSnapDescription(c *gin.Context) { | func (b *BaseApi) UpdateSnapDescription(c *gin.Context) { | ||||||
| 	var req dto.UpdateDescription | 	var req dto.UpdateDescription | ||||||
| 	if err := c.ShouldBindJSON(&req); err != nil { | 	if err := c.ShouldBindJSON(&req); err != nil { | ||||||
| @@ -119,7 +119,7 @@ func (b *BaseApi) SearchSnapshot(c *gin.Context) { | |||||||
| // @Success 200 | // @Success 200 | ||||||
| // @Security ApiKeyAuth | // @Security ApiKeyAuth | ||||||
| // @Router /settings/snapshot/recover [post] | // @Router /settings/snapshot/recover [post] | ||||||
| // @x-panel-log {"bodyKeys":["id"],"paramKeys":[],"BeforeFuntions":[{"input_colume":"id","input_value":"id","isList":false,"db":"snapshots","output_colume":"name","output_value":"name"}],"formatZH":"从系统快照 [name] 恢复","formatEN":"Recover from system backup [name]"} | // @x-panel-log {"bodyKeys":["id"],"paramKeys":[],"BeforeFuntions":[{"input_column":"id","input_value":"id","isList":false,"db":"snapshots","output_column":"name","output_value":"name"}],"formatZH":"从系统快照 [name] 恢复","formatEN":"Recover from system backup [name]"} | ||||||
| func (b *BaseApi) RecoverSnapshot(c *gin.Context) { | func (b *BaseApi) RecoverSnapshot(c *gin.Context) { | ||||||
| 	var req dto.SnapshotRecover | 	var req dto.SnapshotRecover | ||||||
| 	if err := c.ShouldBindJSON(&req); err != nil { | 	if err := c.ShouldBindJSON(&req); err != nil { | ||||||
| @@ -146,7 +146,7 @@ func (b *BaseApi) RecoverSnapshot(c *gin.Context) { | |||||||
| // @Success 200 | // @Success 200 | ||||||
| // @Security ApiKeyAuth | // @Security ApiKeyAuth | ||||||
| // @Router /settings/snapshot/rollback [post] | // @Router /settings/snapshot/rollback [post] | ||||||
| // @x-panel-log {"bodyKeys":["id"],"paramKeys":[],"BeforeFuntions":[{"input_colume":"id","input_value":"id","isList":false,"db":"snapshots","output_colume":"name","output_value":"name"}],"formatZH":"从系统快照 [name] 回滚","formatEN":"Rollback from system backup [name]"} | // @x-panel-log {"bodyKeys":["id"],"paramKeys":[],"BeforeFuntions":[{"input_column":"id","input_value":"id","isList":false,"db":"snapshots","output_column":"name","output_value":"name"}],"formatZH":"从系统快照 [name] 回滚","formatEN":"Rollback from system backup [name]"} | ||||||
| func (b *BaseApi) RollbackSnapshot(c *gin.Context) { | func (b *BaseApi) RollbackSnapshot(c *gin.Context) { | ||||||
| 	var req dto.SnapshotRecover | 	var req dto.SnapshotRecover | ||||||
| 	if err := c.ShouldBindJSON(&req); err != nil { | 	if err := c.ShouldBindJSON(&req); err != nil { | ||||||
| @@ -173,7 +173,7 @@ func (b *BaseApi) RollbackSnapshot(c *gin.Context) { | |||||||
| // @Success 200 | // @Success 200 | ||||||
| // @Security ApiKeyAuth | // @Security ApiKeyAuth | ||||||
| // @Router /settings/snapshot/del [post] | // @Router /settings/snapshot/del [post] | ||||||
| // @x-panel-log {"bodyKeys":["ids"],"paramKeys":[],"BeforeFuntions":[{"input_colume":"id","input_value":"ids","isList":true,"db":"snapshots","output_colume":"name","output_value":"name"}],"formatZH":"删除系统快照 [name]","formatEN":"Delete system backup [name]"} | // @x-panel-log {"bodyKeys":["ids"],"paramKeys":[],"BeforeFuntions":[{"input_column":"id","input_value":"ids","isList":true,"db":"snapshots","output_column":"name","output_value":"name"}],"formatZH":"删除系统快照 [name]","formatEN":"Delete system backup [name]"} | ||||||
| func (b *BaseApi) DeleteSnapshot(c *gin.Context) { | func (b *BaseApi) DeleteSnapshot(c *gin.Context) { | ||||||
| 	var req dto.BatchDeleteReq | 	var req dto.BatchDeleteReq | ||||||
| 	if err := c.ShouldBindJSON(&req); err != nil { | 	if err := c.ShouldBindJSON(&req); err != nil { | ||||||
|   | |||||||
							
								
								
									
										185
									
								
								backend/app/api/v1/ssh.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										185
									
								
								backend/app/api/v1/ssh.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,185 @@ | |||||||
|  | package v1 | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"github.com/1Panel-dev/1Panel/backend/app/api/v1/helper" | ||||||
|  | 	"github.com/1Panel-dev/1Panel/backend/app/dto" | ||||||
|  | 	"github.com/1Panel-dev/1Panel/backend/constant" | ||||||
|  | 	"github.com/1Panel-dev/1Panel/backend/global" | ||||||
|  | 	"github.com/gin-gonic/gin" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | // @Tags SSH | ||||||
|  | // @Summary Load host ssh setting info | ||||||
|  | // @Description 加载 SSH 配置信息 | ||||||
|  | // @Success 200 {object} dto.SSHInfo | ||||||
|  | // @Security ApiKeyAuth | ||||||
|  | // @Router /host/ssh/search [post] | ||||||
|  | func (b *BaseApi) GetSSHInfo(c *gin.Context) { | ||||||
|  | 	info, err := sshService.GetSSHInfo() | ||||||
|  | 	if err != nil { | ||||||
|  | 		helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	helper.SuccessWithData(c, info) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // @Tags SSH | ||||||
|  | // @Summary Operate ssh | ||||||
|  | // @Description 修改 SSH 服务状态 | ||||||
|  | // @Accept json | ||||||
|  | // @Param request body dto.Operate true "request" | ||||||
|  | // @Security ApiKeyAuth | ||||||
|  | // @Router /host/ssh/operate [post] | ||||||
|  | // @x-panel-log {"bodyKeys":["operation"],"paramKeys":[],"BeforeFuntions":[],"formatZH":"[operation] SSH ","formatEN":"[operation] SSH"} | ||||||
|  | func (b *BaseApi) OperateSSH(c *gin.Context) { | ||||||
|  | 	var req dto.Operate | ||||||
|  | 	if err := c.ShouldBindJSON(&req); err != nil { | ||||||
|  | 		helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	if err := global.VALID.Struct(req); err != nil { | ||||||
|  | 		helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if err := sshService.OperateSSH(req.Operation); err != nil { | ||||||
|  | 		helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	helper.SuccessWithData(c, nil) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // @Tags SSH | ||||||
|  | // @Summary Update host ssh setting | ||||||
|  | // @Description 更新 SSH 配置 | ||||||
|  | // @Accept json | ||||||
|  | // @Param request body dto.SettingUpdate true "request" | ||||||
|  | // @Success 200 | ||||||
|  | // @Security ApiKeyAuth | ||||||
|  | // @Router /host/ssh/update [post] | ||||||
|  | // @x-panel-log {"bodyKeys":["key","value"],"paramKeys":[],"BeforeFuntions":[],"formatZH":"修改 SSH 配置 [key] => [value]","formatEN":"update SSH setting [key] => [value]"} | ||||||
|  | func (b *BaseApi) UpdateSSH(c *gin.Context) { | ||||||
|  | 	var req dto.SettingUpdate | ||||||
|  | 	if err := c.ShouldBindJSON(&req); err != nil { | ||||||
|  | 		helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	if err := global.VALID.Struct(req); err != nil { | ||||||
|  | 		helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if err := sshService.Update(req.Key, req.Value); err != nil { | ||||||
|  | 		helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	helper.SuccessWithData(c, nil) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // @Tags SSH | ||||||
|  | // @Summary Update host ssh setting by file | ||||||
|  | // @Description 上传文件更新 SSH 配置 | ||||||
|  | // @Accept json | ||||||
|  | // @Param request body dto.SSHConf true "request" | ||||||
|  | // @Success 200 | ||||||
|  | // @Security ApiKeyAuth | ||||||
|  | // @Router /host/conffile/update [post] | ||||||
|  | // @x-panel-log {"bodyKeys":[],"paramKeys":[],"BeforeFuntions":[],"formatZH":"修改 SSH 配置文件","formatEN":"update SSH conf"} | ||||||
|  | func (b *BaseApi) UpdateSSHByfile(c *gin.Context) { | ||||||
|  | 	var req dto.SSHConf | ||||||
|  | 	if err := c.ShouldBindJSON(&req); err != nil { | ||||||
|  | 		helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	if err := global.VALID.Struct(req); err != nil { | ||||||
|  | 		helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if err := sshService.UpdateByFile(req.File); err != nil { | ||||||
|  | 		helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	helper.SuccessWithData(c, nil) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // @Tags SSH | ||||||
|  | // @Summary Generate host ssh secret | ||||||
|  | // @Description 生成 ssh 密钥 | ||||||
|  | // @Accept json | ||||||
|  | // @Param request body dto.GenerateSSH true "request" | ||||||
|  | // @Success 200 | ||||||
|  | // @Security ApiKeyAuth | ||||||
|  | // @Router /host/ssh/generate [post] | ||||||
|  | // @x-panel-log {"bodyKeys":[],"paramKeys":[],"BeforeFuntions":[],"formatZH":"生成 SSH 密钥 ","formatEN":"generate SSH secret"} | ||||||
|  | func (b *BaseApi) GenerateSSH(c *gin.Context) { | ||||||
|  | 	var req dto.GenerateSSH | ||||||
|  | 	if err := c.ShouldBindJSON(&req); err != nil { | ||||||
|  | 		helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	if err := global.VALID.Struct(req); err != nil { | ||||||
|  | 		helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if err := sshService.GenerateSSH(req); err != nil { | ||||||
|  | 		helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	helper.SuccessWithData(c, nil) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // @Tags SSH | ||||||
|  | // @Summary Load host ssh secret | ||||||
|  | // @Description 获取 ssh 密钥 | ||||||
|  | // @Accept json | ||||||
|  | // @Param request body dto.GenerateLoad true "request" | ||||||
|  | // @Success 200 | ||||||
|  | // @Security ApiKeyAuth | ||||||
|  | // @Router /host/ssh/secret [post] | ||||||
|  | func (b *BaseApi) LoadSSHSecret(c *gin.Context) { | ||||||
|  | 	var req dto.GenerateLoad | ||||||
|  | 	if err := c.ShouldBindJSON(&req); err != nil { | ||||||
|  | 		helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	if err := global.VALID.Struct(req); err != nil { | ||||||
|  | 		helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	data, err := sshService.LoadSSHSecret(req.EncryptionMode) | ||||||
|  | 	if err != nil { | ||||||
|  | 		helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	helper.SuccessWithData(c, data) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // @Tags SSH | ||||||
|  | // @Summary Load host ssh logs | ||||||
|  | // @Description 获取 ssh 登录日志 | ||||||
|  | // @Accept json | ||||||
|  | // @Param request body dto.SearchSSHLog true "request" | ||||||
|  | // @Success 200 {object} dto.SSHLog | ||||||
|  | // @Security ApiKeyAuth | ||||||
|  | // @Router /host/ssh/logs [post] | ||||||
|  | func (b *BaseApi) LoadSSHLogs(c *gin.Context) { | ||||||
|  | 	var req dto.SearchSSHLog | ||||||
|  | 	if err := c.ShouldBindJSON(&req); err != nil { | ||||||
|  | 		helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	if err := global.VALID.Struct(req); err != nil { | ||||||
|  | 		helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	data, err := sshService.LoadLog(req) | ||||||
|  | 	if err != nil { | ||||||
|  | 		helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	helper.SuccessWithData(c, data) | ||||||
|  | } | ||||||
| @@ -1,13 +1,14 @@ | |||||||
| package v1 | package v1 | ||||||
|  |  | ||||||
| import ( | import ( | ||||||
|  | 	"encoding/base64" | ||||||
|  | 	"encoding/json" | ||||||
| 	"fmt" | 	"fmt" | ||||||
| 	"net/http" | 	"net/http" | ||||||
| 	"strconv" | 	"strconv" | ||||||
|  | 	"strings" | ||||||
| 	"time" | 	"time" | ||||||
|  |  | ||||||
| 	"github.com/1Panel-dev/1Panel/backend/app/api/v1/helper" |  | ||||||
| 	"github.com/1Panel-dev/1Panel/backend/constant" |  | ||||||
| 	"github.com/1Panel-dev/1Panel/backend/global" | 	"github.com/1Panel-dev/1Panel/backend/global" | ||||||
| 	"github.com/1Panel-dev/1Panel/backend/utils/cmd" | 	"github.com/1Panel-dev/1Panel/backend/utils/cmd" | ||||||
| 	"github.com/1Panel-dev/1Panel/backend/utils/copier" | 	"github.com/1Panel-dev/1Panel/backend/utils/copier" | ||||||
| @@ -19,30 +20,6 @@ import ( | |||||||
| ) | ) | ||||||
|  |  | ||||||
| func (b *BaseApi) WsSsh(c *gin.Context) { | func (b *BaseApi) WsSsh(c *gin.Context) { | ||||||
| 	id, err := strconv.Atoi(c.Query("id")) |  | ||||||
| 	if err != nil { |  | ||||||
| 		helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err) |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
| 	cols, err := strconv.Atoi(c.DefaultQuery("cols", "80")) |  | ||||||
| 	if err != nil { |  | ||||||
| 		helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err) |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
| 	rows, err := strconv.Atoi(c.DefaultQuery("rows", "40")) |  | ||||||
| 	if err != nil { |  | ||||||
| 		helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err) |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
| 	host, err := hostService.GetHostInfo(uint(id)) |  | ||||||
| 	if err != nil { |  | ||||||
| 		helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err) |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
| 	var connInfo ssh.ConnInfo |  | ||||||
| 	_ = copier.Copy(&connInfo, &host) |  | ||||||
| 	connInfo.PrivateKey = []byte(host.PrivateKey) |  | ||||||
|  |  | ||||||
| 	wsConn, err := upGrader.Upgrade(c.Writer, c.Request, nil) | 	wsConn, err := upGrader.Upgrade(c.Writer, c.Request, nil) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		global.LOG.Errorf("gin context http handler failed, err: %v", err) | 		global.LOG.Errorf("gin context http handler failed, err: %v", err) | ||||||
| @@ -50,6 +27,29 @@ func (b *BaseApi) WsSsh(c *gin.Context) { | |||||||
| 	} | 	} | ||||||
| 	defer wsConn.Close() | 	defer wsConn.Close() | ||||||
|  |  | ||||||
|  | 	id, err := strconv.Atoi(c.Query("id")) | ||||||
|  | 	if wshandleError(wsConn, errors.WithMessage(err, "invalid param id in request")) { | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	cols, err := strconv.Atoi(c.DefaultQuery("cols", "80")) | ||||||
|  | 	if wshandleError(wsConn, errors.WithMessage(err, "invalid param cols in request")) { | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	rows, err := strconv.Atoi(c.DefaultQuery("rows", "40")) | ||||||
|  | 	if wshandleError(wsConn, errors.WithMessage(err, "invalid param rows in request")) { | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	host, err := hostService.GetHostInfo(uint(id)) | ||||||
|  | 	if wshandleError(wsConn, errors.WithMessage(err, "load host info by id failed")) { | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	var connInfo ssh.ConnInfo | ||||||
|  | 	_ = copier.Copy(&connInfo, &host) | ||||||
|  | 	connInfo.PrivateKey = []byte(host.PrivateKey) | ||||||
|  | 	if len(host.PassPhrase) != 0 { | ||||||
|  | 		connInfo.PassPhrase = []byte(host.PassPhrase) | ||||||
|  | 	} | ||||||
|  |  | ||||||
| 	client, err := connInfo.NewClient() | 	client, err := connInfo.NewClient() | ||||||
| 	if wshandleError(wsConn, errors.WithMessage(err, "failed to set up the connection. Please check the host information")) { | 	if wshandleError(wsConn, errors.WithMessage(err, "failed to set up the connection. Please check the host information")) { | ||||||
| 		return | 		return | ||||||
| @@ -79,37 +79,36 @@ func (b *BaseApi) WsSsh(c *gin.Context) { | |||||||
| } | } | ||||||
|  |  | ||||||
| func (b *BaseApi) RedisWsSsh(c *gin.Context) { | func (b *BaseApi) RedisWsSsh(c *gin.Context) { | ||||||
| 	cols, err := strconv.Atoi(c.DefaultQuery("cols", "80")) |  | ||||||
| 	if err != nil { |  | ||||||
| 		helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err) |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
| 	rows, err := strconv.Atoi(c.DefaultQuery("rows", "40")) |  | ||||||
| 	if err != nil { |  | ||||||
| 		helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err) |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
| 	redisConf, err := redisService.LoadConf() |  | ||||||
| 	if err != nil { |  | ||||||
| 		global.LOG.Errorf("load redis container failed, err: %v", err) |  | ||||||
| 		helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err) |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	wsConn, err := upGrader.Upgrade(c.Writer, c.Request, nil) | 	wsConn, err := upGrader.Upgrade(c.Writer, c.Request, nil) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		global.LOG.Errorf("gin context http handler failed, err: %v", err) | 		global.LOG.Errorf("gin context http handler failed, err: %v", err) | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
| 	defer wsConn.Close() |  | ||||||
| 	commands := fmt.Sprintf("docker exec -it %s redis-cli", redisConf.ContainerName) | 	cols, err := strconv.Atoi(c.DefaultQuery("cols", "80")) | ||||||
| 	if len(redisConf.Requirepass) != 0 { | 	if wshandleError(wsConn, errors.WithMessage(err, "invalid param cols in request")) { | ||||||
| 		commands = fmt.Sprintf("docker exec -it %s redis-cli -a %s --no-auth-warning", redisConf.ContainerName, redisConf.Requirepass) | 		return | ||||||
| 	} | 	} | ||||||
| 	slave, err := terminal.NewCommand(commands) | 	rows, err := strconv.Atoi(c.DefaultQuery("rows", "40")) | ||||||
|  | 	if wshandleError(wsConn, errors.WithMessage(err, "invalid param rows in request")) { | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	redisConf, err := redisService.LoadConf() | ||||||
|  | 	if wshandleError(wsConn, errors.WithMessage(err, "load redis container failed")) { | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	defer wsConn.Close() | ||||||
|  | 	commands := "redis-cli" | ||||||
|  | 	if len(redisConf.Requirepass) != 0 { | ||||||
|  | 		commands = fmt.Sprintf("redis-cli -a %s --no-auth-warning", redisConf.Requirepass) | ||||||
|  | 	} | ||||||
|  | 	pidMap := loadMapFromDockerTop(redisConf.ContainerName) | ||||||
|  | 	slave, err := terminal.NewCommand(fmt.Sprintf("docker exec -it %s %s", redisConf.ContainerName, commands)) | ||||||
| 	if wshandleError(wsConn, err) { | 	if wshandleError(wsConn, err) { | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
|  | 	defer killBash(redisConf.ContainerName, commands, pidMap) | ||||||
| 	defer slave.Close() | 	defer slave.Close() | ||||||
|  |  | ||||||
| 	tty, err := terminal.NewLocalWsSession(cols, rows, wsConn, slave) | 	tty, err := terminal.NewLocalWsSession(cols, rows, wsConn, slave) | ||||||
| @@ -130,24 +129,6 @@ func (b *BaseApi) RedisWsSsh(c *gin.Context) { | |||||||
| } | } | ||||||
|  |  | ||||||
| func (b *BaseApi) ContainerWsSsh(c *gin.Context) { | func (b *BaseApi) ContainerWsSsh(c *gin.Context) { | ||||||
| 	containerID := c.Query("containerid") |  | ||||||
| 	command := c.Query("command") |  | ||||||
| 	user := c.Query("user") |  | ||||||
| 	if len(command) == 0 || len(containerID) == 0 { |  | ||||||
| 		helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, errors.New("error param of command or containerID")) |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
| 	cols, err := strconv.Atoi(c.DefaultQuery("cols", "80")) |  | ||||||
| 	if err != nil { |  | ||||||
| 		helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err) |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
| 	rows, err := strconv.Atoi(c.DefaultQuery("rows", "40")) |  | ||||||
| 	if err != nil { |  | ||||||
| 		helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err) |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	wsConn, err := upGrader.Upgrade(c.Writer, c.Request, nil) | 	wsConn, err := upGrader.Upgrade(c.Writer, c.Request, nil) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		global.LOG.Errorf("gin context http handler failed, err: %v", err) | 		global.LOG.Errorf("gin context http handler failed, err: %v", err) | ||||||
| @@ -155,11 +136,33 @@ func (b *BaseApi) ContainerWsSsh(c *gin.Context) { | |||||||
| 	} | 	} | ||||||
| 	defer wsConn.Close() | 	defer wsConn.Close() | ||||||
|  |  | ||||||
| 	cmds := fmt.Sprintf("docker exec %s %s", containerID, command) | 	containerID := c.Query("containerid") | ||||||
| 	if len(user) != 0 { | 	command := c.Query("command") | ||||||
| 		cmds = fmt.Sprintf("docker exec -u %s %s %s", user, containerID, command) | 	user := c.Query("user") | ||||||
|  | 	if len(command) == 0 || len(containerID) == 0 { | ||||||
|  | 		if wshandleError(wsConn, errors.New("error param of command or containerID")) { | ||||||
|  | 			return | ||||||
|  | 		} | ||||||
| 	} | 	} | ||||||
| 	stdout, err := cmd.Exec(cmds) | 	cols, err := strconv.Atoi(c.DefaultQuery("cols", "80")) | ||||||
|  | 	if wshandleError(wsConn, errors.WithMessage(err, "invalid param cols in request")) { | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	rows, err := strconv.Atoi(c.DefaultQuery("rows", "40")) | ||||||
|  | 	if wshandleError(wsConn, errors.WithMessage(err, "invalid param rows in request")) { | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	cmds := []string{"exec", containerID, command} | ||||||
|  | 	if len(user) != 0 { | ||||||
|  | 		cmds = []string{"exec", "-u", user, containerID, command} | ||||||
|  | 	} | ||||||
|  | 	if cmd.CheckIllegal(user, containerID, command) { | ||||||
|  | 		if wshandleError(wsConn, errors.New("  The command contains illegal characters.")) { | ||||||
|  | 			return | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	stdout, err := cmd.ExecWithCheck("docker", cmds...) | ||||||
| 	if wshandleError(wsConn, errors.WithMessage(err, stdout)) { | 	if wshandleError(wsConn, errors.WithMessage(err, stdout)) { | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
| @@ -168,10 +171,12 @@ func (b *BaseApi) ContainerWsSsh(c *gin.Context) { | |||||||
| 	if len(user) != 0 { | 	if len(user) != 0 { | ||||||
| 		commands = fmt.Sprintf("docker exec -it -u %s %s %s", user, containerID, command) | 		commands = fmt.Sprintf("docker exec -it -u %s %s %s", user, containerID, command) | ||||||
| 	} | 	} | ||||||
|  | 	pidMap := loadMapFromDockerTop(containerID) | ||||||
| 	slave, err := terminal.NewCommand(commands) | 	slave, err := terminal.NewCommand(commands) | ||||||
| 	if wshandleError(wsConn, err) { | 	if wshandleError(wsConn, err) { | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
|  | 	defer killBash(containerID, command, pidMap) | ||||||
| 	defer slave.Close() | 	defer slave.Close() | ||||||
|  |  | ||||||
| 	tty, err := terminal.NewLocalWsSession(cols, rows, wsConn, slave) | 	tty, err := terminal.NewLocalWsSession(cols, rows, wsConn, slave) | ||||||
| @@ -196,13 +201,57 @@ func wshandleError(ws *websocket.Conn, err error) bool { | |||||||
| 		global.LOG.Errorf("handler ws faled:, err: %v", err) | 		global.LOG.Errorf("handler ws faled:, err: %v", err) | ||||||
| 		dt := time.Now().Add(time.Second) | 		dt := time.Now().Add(time.Second) | ||||||
| 		if ctlerr := ws.WriteControl(websocket.CloseMessage, []byte(err.Error()), dt); ctlerr != nil { | 		if ctlerr := ws.WriteControl(websocket.CloseMessage, []byte(err.Error()), dt); ctlerr != nil { | ||||||
| 			_ = ws.WriteMessage(websocket.TextMessage, []byte(err.Error())) | 			wsData, err := json.Marshal(terminal.WsMsg{ | ||||||
|  | 				Type: terminal.WsMsgCmd, | ||||||
|  | 				Data: base64.StdEncoding.EncodeToString([]byte(err.Error())), | ||||||
|  | 			}) | ||||||
|  | 			if err != nil { | ||||||
|  | 				_ = ws.WriteMessage(websocket.TextMessage, []byte("{\"type\":\"cmd\",\"data\":\"failed to encoding to json\"}")) | ||||||
|  | 			} else { | ||||||
|  | 				_ = ws.WriteMessage(websocket.TextMessage, wsData) | ||||||
|  | 			} | ||||||
| 		} | 		} | ||||||
| 		return true | 		return true | ||||||
| 	} | 	} | ||||||
| 	return false | 	return false | ||||||
| } | } | ||||||
|  |  | ||||||
|  | func loadMapFromDockerTop(containerID string) map[string]string { | ||||||
|  | 	pidMap := make(map[string]string) | ||||||
|  | 	sudo := cmd.SudoHandleCmd() | ||||||
|  |  | ||||||
|  | 	stdout, err := cmd.Execf("%s docker top %s -eo pid,command ", sudo, containerID) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return pidMap | ||||||
|  | 	} | ||||||
|  | 	lines := strings.Split(stdout, "\n") | ||||||
|  | 	for _, line := range lines { | ||||||
|  | 		parts := strings.Fields(line) | ||||||
|  | 		if len(parts) != 2 { | ||||||
|  | 			continue | ||||||
|  | 		} | ||||||
|  | 		pidMap[parts[0]] = parts[1] | ||||||
|  | 	} | ||||||
|  | 	return pidMap | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func killBash(containerID, comm string, pidMap map[string]string) { | ||||||
|  | 	sudo := cmd.SudoHandleCmd() | ||||||
|  | 	newPidMap := loadMapFromDockerTop(containerID) | ||||||
|  | 	for pid, command := range newPidMap { | ||||||
|  | 		isOld := false | ||||||
|  | 		for pid2 := range pidMap { | ||||||
|  | 			if pid == pid2 { | ||||||
|  | 				isOld = true | ||||||
|  | 				break | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 		if !isOld && command == comm { | ||||||
|  | 			_, _ = cmd.Execf("%s kill -9 %s", sudo, pid) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
| var upGrader = websocket.Upgrader{ | var upGrader = websocket.Upgrader{ | ||||||
| 	ReadBufferSize:  1024, | 	ReadBufferSize:  1024, | ||||||
| 	WriteBufferSize: 1024 * 1024 * 10, | 	WriteBufferSize: 1024 * 1024 * 10, | ||||||
|   | |||||||
| @@ -36,7 +36,7 @@ func (b *BaseApi) PageWebsite(c *gin.Context) { | |||||||
| // @Tags Website | // @Tags Website | ||||||
| // @Summary List websites | // @Summary List websites | ||||||
| // @Description 获取网站列表 | // @Description 获取网站列表 | ||||||
| // @Success 200 {anrry} response.WebsiteDTO | // @Success 200 {array} response.WebsiteDTO | ||||||
| // @Security ApiKeyAuth | // @Security ApiKeyAuth | ||||||
| // @Router /websites/list [get] | // @Router /websites/list [get] | ||||||
| func (b *BaseApi) GetWebsites(c *gin.Context) { | func (b *BaseApi) GetWebsites(c *gin.Context) { | ||||||
| @@ -51,7 +51,7 @@ func (b *BaseApi) GetWebsites(c *gin.Context) { | |||||||
| // @Tags Website | // @Tags Website | ||||||
| // @Summary List website names | // @Summary List website names | ||||||
| // @Description 获取网站列表 | // @Description 获取网站列表 | ||||||
| // @Success 200 {anrry} string | // @Success 200 {array} string | ||||||
| // @Security ApiKeyAuth | // @Security ApiKeyAuth | ||||||
| // @Router /websites/options [get] | // @Router /websites/options [get] | ||||||
| func (b *BaseApi) GetWebsiteOptions(c *gin.Context) { | func (b *BaseApi) GetWebsiteOptions(c *gin.Context) { | ||||||
| @@ -78,14 +78,12 @@ func (b *BaseApi) CreateWebsite(c *gin.Context) { | |||||||
| 		helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err) | 		helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err) | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
| 	tx, ctx := helper.GetTxAndContext() |  | ||||||
| 	err := websiteService.CreateWebsite(ctx, req) | 	err := websiteService.CreateWebsite(req) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		tx.Rollback() |  | ||||||
| 		helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err) | 		helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err) | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
| 	tx.Commit() |  | ||||||
| 	helper.SuccessWithData(c, nil) | 	helper.SuccessWithData(c, nil) | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -97,7 +95,7 @@ func (b *BaseApi) CreateWebsite(c *gin.Context) { | |||||||
| // @Success 200 | // @Success 200 | ||||||
| // @Security ApiKeyAuth | // @Security ApiKeyAuth | ||||||
| // @Router /websites/operate [post] | // @Router /websites/operate [post] | ||||||
| // @x-panel-log {"bodyKeys":["id", "operate"],"paramKeys":[],"BeforeFuntions":[{"input_colume":"id","input_value":"id","isList":false,"db":"websites","output_colume":"primary_domain","output_value":"domain"}],"formatZH":"[operate] 网站 [domain]","formatEN":"[operate] website [domain]"} | // @x-panel-log {"bodyKeys":["id", "operate"],"paramKeys":[],"BeforeFuntions":[{"input_column":"id","input_value":"id","isList":false,"db":"websites","output_column":"primary_domain","output_value":"domain"}],"formatZH":"[operate] 网站 [domain]","formatEN":"[operate] website [domain]"} | ||||||
| func (b *BaseApi) OpWebsite(c *gin.Context) { | func (b *BaseApi) OpWebsite(c *gin.Context) { | ||||||
| 	var req request.WebsiteOp | 	var req request.WebsiteOp | ||||||
| 	if err := c.ShouldBindJSON(&req); err != nil { | 	if err := c.ShouldBindJSON(&req); err != nil { | ||||||
| @@ -120,21 +118,19 @@ func (b *BaseApi) OpWebsite(c *gin.Context) { | |||||||
| // @Success 200 | // @Success 200 | ||||||
| // @Security ApiKeyAuth | // @Security ApiKeyAuth | ||||||
| // @Router /websites/del [post] | // @Router /websites/del [post] | ||||||
| // @x-panel-log {"bodyKeys":["id"],"paramKeys":[],"BeforeFuntions":[{"input_colume":"id","input_value":"id","isList":false,"db":"websites","output_colume":"primary_domain","output_value":"domain"}],"formatZH":"删除网站 [domain]","formatEN":"Delete website [domain]"} | // @x-panel-log {"bodyKeys":["id"],"paramKeys":[],"BeforeFuntions":[{"input_column":"id","input_value":"id","isList":false,"db":"websites","output_column":"primary_domain","output_value":"domain"}],"formatZH":"删除网站 [domain]","formatEN":"Delete website [domain]"} | ||||||
| func (b *BaseApi) DeleteWebsite(c *gin.Context) { | func (b *BaseApi) DeleteWebsite(c *gin.Context) { | ||||||
| 	var req request.WebsiteDelete | 	var req request.WebsiteDelete | ||||||
| 	if err := c.ShouldBindJSON(&req); err != nil { | 	if err := c.ShouldBindJSON(&req); err != nil { | ||||||
| 		helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err) | 		helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err) | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
| 	tx, ctx := helper.GetTxAndContext() |  | ||||||
| 	err := websiteService.DeleteWebsite(ctx, req) | 	err := websiteService.DeleteWebsite(req) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		tx.Rollback() |  | ||||||
| 		helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err) | 		helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err) | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
| 	tx.Commit() |  | ||||||
| 	helper.SuccessWithData(c, nil) | 	helper.SuccessWithData(c, nil) | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -189,14 +185,16 @@ func (b *BaseApi) GetWebsite(c *gin.Context) { | |||||||
| // @Param id path integer true "request" | // @Param id path integer true "request" | ||||||
| // @Success 200 {object} response.FileInfo | // @Success 200 {object} response.FileInfo | ||||||
| // @Security ApiKeyAuth | // @Security ApiKeyAuth | ||||||
| // @Router /websites/:id/nginx [get] | // @Router /websites/:id/config/:type [get] | ||||||
| func (b *BaseApi) GetWebsiteNginx(c *gin.Context) { | func (b *BaseApi) GetWebsiteNginx(c *gin.Context) { | ||||||
| 	id, err := helper.GetParamID(c) | 	id, err := helper.GetParamID(c) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInternalServer, nil) | 		helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInternalServer, nil) | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
| 	fileInfo, err := websiteService.GetWebsiteNginxConfig(id) | 	configType := c.Param("type") | ||||||
|  |  | ||||||
|  | 	fileInfo, err := websiteService.GetWebsiteNginxConfig(id, configType) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err) | 		helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err) | ||||||
| 		return | 		return | ||||||
| @@ -209,7 +207,7 @@ func (b *BaseApi) GetWebsiteNginx(c *gin.Context) { | |||||||
| // @Description 通过网站 id 查询域名 | // @Description 通过网站 id 查询域名 | ||||||
| // @Accept json | // @Accept json | ||||||
| // @Param websiteId path integer true "request" | // @Param websiteId path integer true "request" | ||||||
| // @Success 200 {anrry} model.WebsiteDomain | // @Success 200 {array} model.WebsiteDomain | ||||||
| // @Security ApiKeyAuth | // @Security ApiKeyAuth | ||||||
| // @Router /websites/domains/:websiteId [get] | // @Router /websites/domains/:websiteId [get] | ||||||
| func (b *BaseApi) GetWebDomains(c *gin.Context) { | func (b *BaseApi) GetWebDomains(c *gin.Context) { | ||||||
| @@ -234,7 +232,7 @@ func (b *BaseApi) GetWebDomains(c *gin.Context) { | |||||||
| // @Success 200 | // @Success 200 | ||||||
| // @Security ApiKeyAuth | // @Security ApiKeyAuth | ||||||
| // @Router /websites/domains/del [post] | // @Router /websites/domains/del [post] | ||||||
| // @x-panel-log {"bodyKeys":["id"],"paramKeys":[],"BeforeFuntions":[{"input_colume":"id","input_value":"id","isList":false,"db":"website_domains","output_colume":"domain","output_value":"domain"}],"formatZH":"删除域名 [domain]","formatEN":"Delete domain [domain]"} | // @x-panel-log {"bodyKeys":["id"],"paramKeys":[],"BeforeFuntions":[{"input_column":"id","input_value":"id","isList":false,"db":"website_domains","output_column":"domain","output_value":"domain"}],"formatZH":"删除域名 [domain]","formatEN":"Delete domain [domain]"} | ||||||
| func (b *BaseApi) DeleteWebDomain(c *gin.Context) { | func (b *BaseApi) DeleteWebDomain(c *gin.Context) { | ||||||
| 	var req request.WebsiteDomainDelete | 	var req request.WebsiteDomainDelete | ||||||
| 	if err := c.ShouldBindJSON(&req); err != nil { | 	if err := c.ShouldBindJSON(&req); err != nil { | ||||||
| @@ -302,7 +300,7 @@ func (b *BaseApi) GetNginxConfig(c *gin.Context) { | |||||||
| // @Success 200 | // @Success 200 | ||||||
| // @Security ApiKeyAuth | // @Security ApiKeyAuth | ||||||
| // @Router /websites/config/update [post] | // @Router /websites/config/update [post] | ||||||
| // @x-panel-log {"bodyKeys":["websiteId"],"paramKeys":[],"BeforeFuntions":[{"input_colume":"id","input_value":"websiteId","isList":false,"db":"websites","output_colume":"primary_domain","output_value":"domain"}],"formatZH":"nginx 配置修改 [domain]","formatEN":"Nginx conf update [domain]"} | // @x-panel-log {"bodyKeys":["websiteId"],"paramKeys":[],"BeforeFuntions":[{"input_column":"id","input_value":"websiteId","isList":false,"db":"websites","output_column":"primary_domain","output_value":"domain"}],"formatZH":"nginx 配置修改 [domain]","formatEN":"Nginx conf update [domain]"} | ||||||
| func (b *BaseApi) UpdateNginxConfig(c *gin.Context) { | func (b *BaseApi) UpdateNginxConfig(c *gin.Context) { | ||||||
| 	var req request.NginxConfigUpdate | 	var req request.NginxConfigUpdate | ||||||
| 	if err := c.ShouldBindJSON(&req); err != nil { | 	if err := c.ShouldBindJSON(&req); err != nil { | ||||||
| @@ -346,7 +344,7 @@ func (b *BaseApi) GetHTTPSConfig(c *gin.Context) { | |||||||
| // @Success 200 {object} response.WebsiteHTTPS | // @Success 200 {object} response.WebsiteHTTPS | ||||||
| // @Security ApiKeyAuth | // @Security ApiKeyAuth | ||||||
| // @Router /websites/:id/https [post] | // @Router /websites/:id/https [post] | ||||||
| // @x-panel-log {"bodyKeys":["websiteId"],"paramKeys":[],"BeforeFuntions":[{"input_colume":"id","input_value":"websiteId","isList":false,"db":"websites","output_colume":"primary_domain","output_value":"domain"}],"formatZH":"更新网站 [domain] https 配置","formatEN":"Update website https [domain] conf"} | // @x-panel-log {"bodyKeys":["websiteId"],"paramKeys":[],"BeforeFuntions":[{"input_column":"id","input_value":"websiteId","isList":false,"db":"websites","output_column":"primary_domain","output_value":"domain"}],"formatZH":"更新网站 [domain] https 配置","formatEN":"Update website https [domain] conf"} | ||||||
| func (b *BaseApi) UpdateHTTPSConfig(c *gin.Context) { | func (b *BaseApi) UpdateHTTPSConfig(c *gin.Context) { | ||||||
| 	var req request.WebsiteHTTPSOp | 	var req request.WebsiteHTTPSOp | ||||||
| 	if err := c.ShouldBindJSON(&req); err != nil { | 	if err := c.ShouldBindJSON(&req); err != nil { | ||||||
| @@ -369,7 +367,7 @@ func (b *BaseApi) UpdateHTTPSConfig(c *gin.Context) { | |||||||
| // @Description 网站创建前检查 | // @Description 网站创建前检查 | ||||||
| // @Accept json | // @Accept json | ||||||
| // @Param request body request.WebsiteInstallCheckReq true "request" | // @Param request body request.WebsiteInstallCheckReq true "request" | ||||||
| // @Success 200 {anrry} request.WebsitePreInstallCheck | // @Success 200 {array} response.WebsitePreInstallCheck | ||||||
| // @Security ApiKeyAuth | // @Security ApiKeyAuth | ||||||
| // @Router /websites/check [post] | // @Router /websites/check [post] | ||||||
| func (b *BaseApi) CreateWebsiteCheck(c *gin.Context) { | func (b *BaseApi) CreateWebsiteCheck(c *gin.Context) { | ||||||
| @@ -416,7 +414,7 @@ func (b *BaseApi) GetWebsiteWafConfig(c *gin.Context) { | |||||||
| // @Success 200 | // @Success 200 | ||||||
| // @Security ApiKeyAuth | // @Security ApiKeyAuth | ||||||
| // @Router /websites/waf/update [post] | // @Router /websites/waf/update [post] | ||||||
| // @x-panel-log {"bodyKeys":["websiteId"],"paramKeys":[],"BeforeFuntions":[{"input_colume":"id","input_value":"websiteId","isList":false,"db":"websites","output_colume":"primary_domain","output_value":"domain"}],"formatZH":"WAF 配置修改 [domain]","formatEN":"WAF conf update [domain]"} | // @x-panel-log {"bodyKeys":["websiteId"],"paramKeys":[],"BeforeFuntions":[{"input_column":"id","input_value":"websiteId","isList":false,"db":"websites","output_column":"primary_domain","output_value":"domain"}],"formatZH":"WAF 配置修改 [domain]","formatEN":"WAF conf update [domain]"} | ||||||
| func (b *BaseApi) UpdateWebsiteWafConfig(c *gin.Context) { | func (b *BaseApi) UpdateWebsiteWafConfig(c *gin.Context) { | ||||||
| 	var req request.WebsiteWafUpdate | 	var req request.WebsiteWafUpdate | ||||||
| 	if err := c.ShouldBindJSON(&req); err != nil { | 	if err := c.ShouldBindJSON(&req); err != nil { | ||||||
| @@ -438,7 +436,7 @@ func (b *BaseApi) UpdateWebsiteWafConfig(c *gin.Context) { | |||||||
| // @Success 200 | // @Success 200 | ||||||
| // @Security ApiKeyAuth | // @Security ApiKeyAuth | ||||||
| // @Router /websites/nginx/update [post] | // @Router /websites/nginx/update [post] | ||||||
| // @x-panel-log {"bodyKeys":["id"],"paramKeys":[],"BeforeFuntions":[{"input_colume":"id","input_value":"id","isList":false,"db":"websites","output_colume":"primary_domain","output_value":"domain"}],"formatZH":"[domain] Nginx 配置修改","formatEN":"[domain] Nginx conf update"} | // @x-panel-log {"bodyKeys":["id"],"paramKeys":[],"BeforeFuntions":[{"input_column":"id","input_value":"id","isList":false,"db":"websites","output_column":"primary_domain","output_value":"domain"}],"formatZH":"[domain] Nginx 配置修改","formatEN":"[domain] Nginx conf update"} | ||||||
| func (b *BaseApi) UpdateWebsiteNginxConfig(c *gin.Context) { | func (b *BaseApi) UpdateWebsiteNginxConfig(c *gin.Context) { | ||||||
| 	var req request.WebsiteNginxUpdate | 	var req request.WebsiteNginxUpdate | ||||||
| 	if err := c.ShouldBindJSON(&req); err != nil { | 	if err := c.ShouldBindJSON(&req); err != nil { | ||||||
| @@ -460,7 +458,7 @@ func (b *BaseApi) UpdateWebsiteNginxConfig(c *gin.Context) { | |||||||
| // @Success 200 {object} response.WebsiteLog | // @Success 200 {object} response.WebsiteLog | ||||||
| // @Security ApiKeyAuth | // @Security ApiKeyAuth | ||||||
| // @Router /websites/log [post] | // @Router /websites/log [post] | ||||||
| // @x-panel-log {"bodyKeys":["id", "operate"],"paramKeys":[],"BeforeFuntions":[{"input_colume":"id","input_value":"id","isList":false,"db":"websites","output_colume":"primary_domain","output_value":"domain"}],"formatZH":"[domain][operate] 日志","formatEN":"[domain][operate] logs"} | // @x-panel-log {"bodyKeys":["id", "operate"],"paramKeys":[],"BeforeFuntions":[{"input_column":"id","input_value":"id","isList":false,"db":"websites","output_column":"primary_domain","output_value":"domain"}],"formatZH":"[domain][operate] 日志","formatEN":"[domain][operate] logs"} | ||||||
| func (b *BaseApi) OpWebsiteLog(c *gin.Context) { | func (b *BaseApi) OpWebsiteLog(c *gin.Context) { | ||||||
| 	var req request.WebsiteLogReq | 	var req request.WebsiteLogReq | ||||||
| 	if err := c.ShouldBindJSON(&req); err != nil { | 	if err := c.ShouldBindJSON(&req); err != nil { | ||||||
| @@ -483,7 +481,7 @@ func (b *BaseApi) OpWebsiteLog(c *gin.Context) { | |||||||
| // @Success 200 | // @Success 200 | ||||||
| // @Security ApiKeyAuth | // @Security ApiKeyAuth | ||||||
| // @Router /websites/default/server [post] | // @Router /websites/default/server [post] | ||||||
| // @x-panel-log {"bodyKeys":["id", "operate"],"paramKeys":[],"BeforeFuntions":[{"input_colume":"id","input_value":"id","isList":false,"db":"websites","output_colume":"primary_domain","output_value":"domain"}],"formatZH":"修改默认 server => [domain]","formatEN":"Change default server => [domain]"} | // @x-panel-log {"bodyKeys":["id", "operate"],"paramKeys":[],"BeforeFuntions":[{"input_column":"id","input_value":"id","isList":false,"db":"websites","output_column":"primary_domain","output_value":"domain"}],"formatZH":"修改默认 server => [domain]","formatEN":"Change default server => [domain]"} | ||||||
| func (b *BaseApi) ChangeDefaultServer(c *gin.Context) { | func (b *BaseApi) ChangeDefaultServer(c *gin.Context) { | ||||||
| 	var req request.WebsiteDefaultUpdate | 	var req request.WebsiteDefaultUpdate | ||||||
| 	if err := c.ShouldBindJSON(&req); err != nil { | 	if err := c.ShouldBindJSON(&req); err != nil { | ||||||
| @@ -496,3 +494,310 @@ func (b *BaseApi) ChangeDefaultServer(c *gin.Context) { | |||||||
| 	} | 	} | ||||||
| 	helper.SuccessWithData(c, nil) | 	helper.SuccessWithData(c, nil) | ||||||
| } | } | ||||||
|  |  | ||||||
|  | // @Tags Website | ||||||
|  | // @Summary Load websit php conf | ||||||
|  | // @Description 获取网站 php 配置 | ||||||
|  | // @Accept json | ||||||
|  | // @Param id path integer true "request" | ||||||
|  | // @Success 200 {object} response.PHPConfig | ||||||
|  | // @Security ApiKeyAuth | ||||||
|  | // @Router /websites/php/config/:id [get] | ||||||
|  | func (b *BaseApi) GetWebsitePHPConfig(c *gin.Context) { | ||||||
|  | 	id, err := helper.GetParamID(c) | ||||||
|  | 	if err != nil { | ||||||
|  | 		helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInternalServer, nil) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	data, err := websiteService.GetPHPConfig(id) | ||||||
|  | 	if err != nil { | ||||||
|  | 		helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	helper.SuccessWithData(c, data) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // @Tags Website PHP | ||||||
|  | // @Summary Update website php conf | ||||||
|  | // @Description 更新 网站 PHP 配置 | ||||||
|  | // @Accept json | ||||||
|  | // @Param request body request.WebsitePHPConfigUpdate true "request" | ||||||
|  | // @Success 200 | ||||||
|  | // @Security ApiKeyAuth | ||||||
|  | // @Router /websites/php/config [post] | ||||||
|  | // @x-panel-log {"bodyKeys":["id"],"paramKeys":[],"BeforeFuntions":[{"input_column":"id","input_value":"id","isList":false,"db":"websites","output_column":"primary_domain","output_value":"domain"}],"formatZH":"[domain] PHP 配置修改","formatEN":"[domain] PHP conf update"} | ||||||
|  | func (b *BaseApi) UpdateWebsitePHPConfig(c *gin.Context) { | ||||||
|  | 	var req request.WebsitePHPConfigUpdate | ||||||
|  | 	if err := c.ShouldBindJSON(&req); err != nil { | ||||||
|  | 		helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	if err := websiteService.UpdatePHPConfig(req); err != nil { | ||||||
|  | 		helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	helper.SuccessWithData(c, nil) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // @Tags Website PHP | ||||||
|  | // @Summary Update php conf | ||||||
|  | // @Description 更新 php 配置文件 | ||||||
|  | // @Accept json | ||||||
|  | // @Param request body request.WebsitePHPFileUpdate true "request" | ||||||
|  | // @Success 200 | ||||||
|  | // @Security ApiKeyAuth | ||||||
|  | // @Router /websites/php/update [post] | ||||||
|  | // @x-panel-log {"bodyKeys":["websiteId"],"paramKeys":[],"BeforeFuntions":[{"input_column":"id","input_value":"websiteId","isList":false,"db":"websites","output_column":"primary_domain","output_value":"domain"}],"formatZH":"php 配置修改 [domain]","formatEN":"Nginx conf update [domain]"} | ||||||
|  | func (b *BaseApi) UpdatePHPFile(c *gin.Context) { | ||||||
|  | 	var req request.WebsitePHPFileUpdate | ||||||
|  | 	if err := c.ShouldBindJSON(&req); err != nil { | ||||||
|  | 		helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	if err := websiteService.UpdatePHPConfigFile(req); err != nil { | ||||||
|  | 		helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	helper.SuccessWithData(c, nil) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // @Tags Website | ||||||
|  | // @Summary Get rewrite conf | ||||||
|  | // @Description 获取伪静态配置 | ||||||
|  | // @Accept json | ||||||
|  | // @Param request body request.NginxRewriteReq true "request" | ||||||
|  | // @Success 200 | ||||||
|  | // @Security ApiKeyAuth | ||||||
|  | // @Router /websites/rewrite [post] | ||||||
|  | func (b *BaseApi) GetRewriteConfig(c *gin.Context) { | ||||||
|  | 	var req request.NginxRewriteReq | ||||||
|  | 	if err := c.ShouldBindJSON(&req); err != nil { | ||||||
|  | 		helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	res, err := websiteService.GetRewriteConfig(req) | ||||||
|  | 	if err != nil { | ||||||
|  | 		helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	helper.SuccessWithData(c, res) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // @Tags Website | ||||||
|  | // @Summary Update rewrite conf | ||||||
|  | // @Description 更新伪静态配置 | ||||||
|  | // @Accept json | ||||||
|  | // @Param request body request.NginxRewriteUpdate true "request" | ||||||
|  | // @Success 200 | ||||||
|  | // @Security ApiKeyAuth | ||||||
|  | // @Router /websites/rewrite/update [post] | ||||||
|  | // @x-panel-log {"bodyKeys":["websiteID"],"paramKeys":[],"BeforeFuntions":[{"input_column":"id","input_value":"websiteID","isList":false,"db":"websites","output_column":"primary_domain","output_value":"domain"}],"formatZH":"伪静态配置修改 [domain]","formatEN":"Nginx conf rewrite update [domain]"} | ||||||
|  | func (b *BaseApi) UpdateRewriteConfig(c *gin.Context) { | ||||||
|  | 	var req request.NginxRewriteUpdate | ||||||
|  | 	if err := c.ShouldBindJSON(&req); err != nil { | ||||||
|  | 		helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	if err := websiteService.UpdateRewriteConfig(req); err != nil { | ||||||
|  | 		helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	helper.SuccessWithData(c, nil) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // @Tags Website | ||||||
|  | // @Summary Update Site Dir | ||||||
|  | // @Description 更新网站目录 | ||||||
|  | // @Accept json | ||||||
|  | // @Param request body request.WebsiteUpdateDir true "request" | ||||||
|  | // @Success 200 | ||||||
|  | // @Security ApiKeyAuth | ||||||
|  | // @Router /websites/dir/update [post] | ||||||
|  | // @x-panel-log {"bodyKeys":["id"],"paramKeys":[],"BeforeFuntions":[{"input_column":"id","input_value":"id","isList":false,"db":"websites","output_column":"primary_domain","output_value":"domain"}],"formatZH":"更新网站 [domain] 目录","formatEN":"Update  domain [domain] dir"} | ||||||
|  | func (b *BaseApi) UpdateSiteDir(c *gin.Context) { | ||||||
|  | 	var req request.WebsiteUpdateDir | ||||||
|  | 	if err := c.ShouldBindJSON(&req); err != nil { | ||||||
|  | 		helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	if err := websiteService.UpdateSiteDir(req); err != nil { | ||||||
|  | 		helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	helper.SuccessWithOutData(c) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // @Tags Website | ||||||
|  | // @Summary Update Site Dir permission | ||||||
|  | // @Description 更新网站目录权限 | ||||||
|  | // @Accept json | ||||||
|  | // @Param request body request.WebsiteUpdateDirPermission true "request" | ||||||
|  | // @Success 200 | ||||||
|  | // @Security ApiKeyAuth | ||||||
|  | // @Router /websites/dir/permission [post] | ||||||
|  | // @x-panel-log {"bodyKeys":["id"],"paramKeys":[],"BeforeFuntions":[{"input_column":"id","input_value":"id","isList":false,"db":"websites","output_column":"primary_domain","output_value":"domain"}],"formatZH":"更新网站 [domain] 目录权限","formatEN":"Update  domain [domain] dir permission"} | ||||||
|  | func (b *BaseApi) UpdateSiteDirPermission(c *gin.Context) { | ||||||
|  | 	var req request.WebsiteUpdateDirPermission | ||||||
|  | 	if err := c.ShouldBindJSON(&req); err != nil { | ||||||
|  | 		helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	if err := websiteService.UpdateSitePermission(req); err != nil { | ||||||
|  | 		helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	helper.SuccessWithOutData(c) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // @Tags Website | ||||||
|  | // @Summary Get proxy conf | ||||||
|  | // @Description 获取反向代理配置 | ||||||
|  | // @Accept json | ||||||
|  | // @Param request body request.WebsiteProxyReq true "request" | ||||||
|  | // @Success 200 | ||||||
|  | // @Security ApiKeyAuth | ||||||
|  | // @Router /websites/proxies [post] | ||||||
|  | func (b *BaseApi) GetProxyConfig(c *gin.Context) { | ||||||
|  | 	var req request.WebsiteProxyReq | ||||||
|  | 	if err := c.ShouldBindJSON(&req); err != nil { | ||||||
|  | 		helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	res, err := websiteService.GetProxies(req.ID) | ||||||
|  | 	if err != nil { | ||||||
|  | 		helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	helper.SuccessWithData(c, res) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // @Tags Website | ||||||
|  | // @Summary Update proxy conf | ||||||
|  | // @Description 修改反向代理配置 | ||||||
|  | // @Accept json | ||||||
|  | // @Param request body request.WebsiteProxyConfig true "request" | ||||||
|  | // @Success 200 | ||||||
|  | // @Security ApiKeyAuth | ||||||
|  | // @Router /websites/proxies/update [post] | ||||||
|  | // @x-panel-log {"bodyKeys":["id"],"paramKeys":[],"BeforeFuntions":[{"input_column":"id","input_value":"id","isList":false,"db":"websites","output_column":"primary_domain","output_value":"domain"}],"formatZH":"修改网站 [domain] 反向代理配置 ","formatEN":"Update domain [domain] proxy config"} | ||||||
|  | func (b *BaseApi) UpdateProxyConfig(c *gin.Context) { | ||||||
|  | 	var req request.WebsiteProxyConfig | ||||||
|  | 	if err := c.ShouldBindJSON(&req); err != nil { | ||||||
|  | 		helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	err := websiteService.OperateProxy(req) | ||||||
|  | 	if err != nil { | ||||||
|  | 		helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	helper.SuccessWithOutData(c) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // @Tags Website | ||||||
|  | // @Summary Update proxy file | ||||||
|  | // @Description 更新反向代理文件 | ||||||
|  | // @Accept json | ||||||
|  | // @Param request body request.NginxProxyUpdate true "request" | ||||||
|  | // @Success 200 | ||||||
|  | // @Security ApiKeyAuth | ||||||
|  | // @Router /websites/proxy/file [post] | ||||||
|  | // @x-panel-log {"bodyKeys":["websiteID"],"paramKeys":[],"BeforeFuntions":[{"input_column":"id","input_value":"websiteID","isList":false,"db":"websites","output_column":"primary_domain","output_value":"domain"}],"formatZH":"更新反向代理文件 [domain]","formatEN":"Nginx conf proxy file update [domain]"} | ||||||
|  | func (b *BaseApi) UpdateProxyConfigFile(c *gin.Context) { | ||||||
|  | 	var req request.NginxProxyUpdate | ||||||
|  | 	if err := c.ShouldBindJSON(&req); err != nil { | ||||||
|  | 		helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	if err := websiteService.UpdateProxyFile(req); err != nil { | ||||||
|  | 		helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	helper.SuccessWithOutData(c) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // @Tags Website | ||||||
|  | // @Summary Get AuthBasic conf | ||||||
|  | // @Description 获取密码访问配置 | ||||||
|  | // @Accept json | ||||||
|  | // @Param request body request.NginxAuthReq true "request" | ||||||
|  | // @Success 200 | ||||||
|  | // @Security ApiKeyAuth | ||||||
|  | // @Router /websites/auths [post] | ||||||
|  | func (b *BaseApi) GetAuthConfig(c *gin.Context) { | ||||||
|  | 	var req request.NginxAuthReq | ||||||
|  | 	if err := c.ShouldBindJSON(&req); err != nil { | ||||||
|  | 		helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	res, err := websiteService.GetAuthBasics(req) | ||||||
|  | 	if err != nil { | ||||||
|  | 		helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	helper.SuccessWithData(c, res) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // @Tags Website | ||||||
|  | // @Summary Get AuthBasic conf | ||||||
|  | // @Description 更新密码访问配置 | ||||||
|  | // @Accept json | ||||||
|  | // @Param request body request.NginxAuthUpdate true "request" | ||||||
|  | // @Success 200 | ||||||
|  | // @Security ApiKeyAuth | ||||||
|  | // @Router /websites/auths/update [post] | ||||||
|  | func (b *BaseApi) UpdateAuthConfig(c *gin.Context) { | ||||||
|  | 	var req request.NginxAuthUpdate | ||||||
|  | 	if err := c.ShouldBindJSON(&req); err != nil { | ||||||
|  | 		helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	if err := websiteService.UpdateAuthBasic(req); err != nil { | ||||||
|  | 		helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	helper.SuccessWithOutData(c) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // @Tags Website | ||||||
|  | // @Summary Get AntiLeech conf | ||||||
|  | // @Description 获取防盗链配置 | ||||||
|  | // @Accept json | ||||||
|  | // @Param request body request.NginxCommonReq true "request" | ||||||
|  | // @Success 200 | ||||||
|  | // @Security ApiKeyAuth | ||||||
|  | // @Router /websites/leech [post] | ||||||
|  | func (b *BaseApi) GetAntiLeech(c *gin.Context) { | ||||||
|  | 	var req request.NginxCommonReq | ||||||
|  | 	if err := c.ShouldBindJSON(&req); err != nil { | ||||||
|  | 		helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	res, err := websiteService.GetAntiLeech(req.WebsiteID) | ||||||
|  | 	if err != nil { | ||||||
|  | 		helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	helper.SuccessWithData(c, res) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // @Tags Website | ||||||
|  | // @Summary Update AntiLeech | ||||||
|  | // @Description 更新防盗链配置 | ||||||
|  | // @Accept json | ||||||
|  | // @Param request body request.NginxAntiLeechUpdate true "request" | ||||||
|  | // @Success 200 | ||||||
|  | // @Security ApiKeyAuth | ||||||
|  | // @Router /websites/leech/update [post] | ||||||
|  | func (b *BaseApi) UpdateAntiLeech(c *gin.Context) { | ||||||
|  | 	var req request.NginxAntiLeechUpdate | ||||||
|  | 	if err := c.ShouldBindJSON(&req); err != nil { | ||||||
|  | 		helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	if err := websiteService.UpdateAntiLeech(req); err != nil { | ||||||
|  | 		helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	helper.SuccessWithOutData(c) | ||||||
|  | } | ||||||
|   | |||||||
| @@ -64,7 +64,7 @@ func (b *BaseApi) CreateWebsiteAcmeAccount(c *gin.Context) { | |||||||
| // @Success 200 | // @Success 200 | ||||||
| // @Security ApiKeyAuth | // @Security ApiKeyAuth | ||||||
| // @Router /websites/acme/del [post] | // @Router /websites/acme/del [post] | ||||||
| // @x-panel-log {"bodyKeys":["id"],"paramKeys":[],"BeforeFuntions":[{"input_colume":"id","input_value":"id","isList":false,"db":"website_acme_accounts","output_colume":"email","output_value":"email"}],"formatZH":"删除网站 acme [email]","formatEN":"Delete website acme [email]"} | // @x-panel-log {"bodyKeys":["id"],"paramKeys":[],"BeforeFuntions":[{"input_column":"id","input_value":"id","isList":false,"db":"website_acme_accounts","output_column":"email","output_value":"email"}],"formatZH":"删除网站 acme [email]","formatEN":"Delete website acme [email]"} | ||||||
| func (b *BaseApi) DeleteWebsiteAcmeAccount(c *gin.Context) { | func (b *BaseApi) DeleteWebsiteAcmeAccount(c *gin.Context) { | ||||||
| 	var req request.WebsiteResourceReq | 	var req request.WebsiteResourceReq | ||||||
| 	if err := c.ShouldBindJSON(&req); err != nil { | 	if err := c.ShouldBindJSON(&req); err != nil { | ||||||
|   | |||||||
| @@ -85,7 +85,7 @@ func (b *BaseApi) UpdateWebsiteDnsAccount(c *gin.Context) { | |||||||
| // @Success 200 | // @Success 200 | ||||||
| // @Security ApiKeyAuth | // @Security ApiKeyAuth | ||||||
| // @Router /websites/dns/del [post] | // @Router /websites/dns/del [post] | ||||||
| // @x-panel-log {"bodyKeys":["id"],"paramKeys":[],"BeforeFuntions":[{"input_colume":"id","input_value":"id","isList":false,"db":"website_dns_accounts","output_colume":"name","output_value":"name"}],"formatZH":"删除网站 dns [name]","formatEN":"Delete website dns [name]"} | // @x-panel-log {"bodyKeys":["id"],"paramKeys":[],"BeforeFuntions":[{"input_column":"id","input_value":"id","isList":false,"db":"website_dns_accounts","output_column":"name","output_value":"name"}],"formatZH":"删除网站 dns [name]","formatEN":"Delete website dns [name]"} | ||||||
| func (b *BaseApi) DeleteWebsiteDnsAccount(c *gin.Context) { | func (b *BaseApi) DeleteWebsiteDnsAccount(c *gin.Context) { | ||||||
| 	var req request.WebsiteResourceReq | 	var req request.WebsiteResourceReq | ||||||
| 	if err := c.ShouldBindJSON(&req); err != nil { | 	if err := c.ShouldBindJSON(&req); err != nil { | ||||||
|   | |||||||
| @@ -35,7 +35,7 @@ func (b *BaseApi) PageWebsiteSSL(c *gin.Context) { | |||||||
| 			Items: accounts, | 			Items: accounts, | ||||||
| 		}) | 		}) | ||||||
| 	} else { | 	} else { | ||||||
| 		list, err := websiteSSLService.Search() | 		list, err := websiteSSLService.Search(req) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err) | 			helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err) | ||||||
| 			return | 			return | ||||||
| @@ -75,7 +75,7 @@ func (b *BaseApi) CreateWebsiteSSL(c *gin.Context) { | |||||||
| // @Success 200 | // @Success 200 | ||||||
| // @Security ApiKeyAuth | // @Security ApiKeyAuth | ||||||
| // @Router /websites/ssl/renew [post] | // @Router /websites/ssl/renew [post] | ||||||
| // @x-panel-log {"bodyKeys":["SSLId"],"paramKeys":[],"BeforeFuntions":[{"input_colume":"id","input_value":"SSLId","isList":false,"db":"website_ssls","output_colume":"primary_domain","output_value":"domain"}],"formatZH":"重置 ssl [domain]","formatEN":"Renew ssl [domain]"} | // @x-panel-log {"bodyKeys":["SSLId"],"paramKeys":[],"BeforeFuntions":[{"input_column":"id","input_value":"SSLId","isList":false,"db":"website_ssls","output_column":"primary_domain","output_value":"domain"}],"formatZH":"重置 ssl [domain]","formatEN":"Renew ssl [domain]"} | ||||||
| func (b *BaseApi) RenewWebsiteSSL(c *gin.Context) { | func (b *BaseApi) RenewWebsiteSSL(c *gin.Context) { | ||||||
| 	var req request.WebsiteSSLRenew | 	var req request.WebsiteSSLRenew | ||||||
| 	if err := c.ShouldBindJSON(&req); err != nil { | 	if err := c.ShouldBindJSON(&req); err != nil { | ||||||
| @@ -94,7 +94,7 @@ func (b *BaseApi) RenewWebsiteSSL(c *gin.Context) { | |||||||
| // @Description 解析网站 ssl | // @Description 解析网站 ssl | ||||||
| // @Accept json | // @Accept json | ||||||
| // @Param request body request.WebsiteDNSReq true "request" | // @Param request body request.WebsiteDNSReq true "request" | ||||||
| // @Success 200 {anrry} response.WebsiteDNSRes | // @Success 200 {array} response.WebsiteDNSRes | ||||||
| // @Security ApiKeyAuth | // @Security ApiKeyAuth | ||||||
| // @Router /websites/ssl/resolve [post] | // @Router /websites/ssl/resolve [post] | ||||||
| func (b *BaseApi) GetDNSResolve(c *gin.Context) { | func (b *BaseApi) GetDNSResolve(c *gin.Context) { | ||||||
| @@ -119,7 +119,7 @@ func (b *BaseApi) GetDNSResolve(c *gin.Context) { | |||||||
| // @Success 200 | // @Success 200 | ||||||
| // @Security ApiKeyAuth | // @Security ApiKeyAuth | ||||||
| // @Router /websites/ssl/del [post] | // @Router /websites/ssl/del [post] | ||||||
| // @x-panel-log {"bodyKeys":["id"],"paramKeys":[],"BeforeFuntions":[{"input_colume":"id","input_value":"id","isList":false,"db":"website_ssls","output_colume":"primary_domain","output_value":"domain"}],"formatZH":"删除 ssl [domain]","formatEN":"Delete ssl [domain]"} | // @x-panel-log {"bodyKeys":["id"],"paramKeys":[],"BeforeFuntions":[{"input_column":"id","input_value":"id","isList":false,"db":"website_ssls","output_column":"primary_domain","output_value":"domain"}],"formatZH":"删除 ssl [domain]","formatEN":"Delete ssl [domain]"} | ||||||
| func (b *BaseApi) DeleteWebsiteSSL(c *gin.Context) { | func (b *BaseApi) DeleteWebsiteSSL(c *gin.Context) { | ||||||
| 	var req request.WebsiteResourceReq | 	var req request.WebsiteResourceReq | ||||||
| 	if err := c.ShouldBindJSON(&req); err != nil { | 	if err := c.ShouldBindJSON(&req); err != nil { | ||||||
| @@ -185,7 +185,7 @@ func (b *BaseApi) GetWebsiteSSLById(c *gin.Context) { | |||||||
| // @Success 200 | // @Success 200 | ||||||
| // @Security ApiKeyAuth | // @Security ApiKeyAuth | ||||||
| // @Router /websites/ssl/update [post] | // @Router /websites/ssl/update [post] | ||||||
| // @x-panel-log {"bodyKeys":["id"],"paramKeys":[],"BeforeFuntions":[{"input_colume":"id","input_value":"id","isList":false,"db":"website_ssls","output_colume":"primary_domain","output_value":"domain"}],"formatZH":"更新证书设置 [domain]","formatEN":"Update ssl config [domain]"} | // @x-panel-log {"bodyKeys":["id"],"paramKeys":[],"BeforeFuntions":[{"input_column":"id","input_value":"id","isList":false,"db":"website_ssls","output_column":"primary_domain","output_value":"domain"}],"formatZH":"更新证书设置 [domain]","formatEN":"Update ssl config [domain]"} | ||||||
| func (b *BaseApi) UpdateWebsiteSSL(c *gin.Context) { | func (b *BaseApi) UpdateWebsiteSSL(c *gin.Context) { | ||||||
| 	var req request.WebsiteSSLUpdate | 	var req request.WebsiteSSLUpdate | ||||||
| 	if err := c.ShouldBindJSON(&req); err != nil { | 	if err := c.ShouldBindJSON(&req); err != nil { | ||||||
|   | |||||||
| @@ -1,7 +1,7 @@ | |||||||
| package dto | package dto | ||||||
|  |  | ||||||
| import ( | import ( | ||||||
| 	"encoding/json" | 	"github.com/1Panel-dev/1Panel/backend/app/model" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| type AppDatabase struct { | type AppDatabase struct { | ||||||
| @@ -32,19 +32,47 @@ type AppVersion struct { | |||||||
| } | } | ||||||
|  |  | ||||||
| type AppList struct { | type AppList struct { | ||||||
| 	Version string      `json:"version"` | 	Valid        bool     `json:"valid"` | ||||||
| 	Tags    []Tag       `json:"tags"` | 	Violations   []string `json:"violations"` | ||||||
| 	Items   []AppDefine `json:"items"` | 	LastModified int      `json:"lastModified"` | ||||||
|  |  | ||||||
|  | 	Apps  []AppDefine     `json:"apps"` | ||||||
|  | 	Extra ExtraProperties `json:"additionalProperties"` | ||||||
| } | } | ||||||
|  |  | ||||||
| type AppDefine struct { | type AppDefine struct { | ||||||
| 	Key                string   `json:"key"` | 	Icon         string `json:"icon"` | ||||||
|  | 	Name         string `json:"name"` | ||||||
|  | 	ReadMe       string `json:"readMe"` | ||||||
|  | 	LastModified int    `json:"lastModified"` | ||||||
|  |  | ||||||
|  | 	AppProperty AppProperty        `json:"additionalProperties"` | ||||||
|  | 	Versions    []AppConfigVersion `json:"versions"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type LocalAppAppDefine struct { | ||||||
|  | 	AppProperty model.App `json:"additionalProperties" yaml:"additionalProperties"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type LocalAppParam struct { | ||||||
|  | 	AppParams LocalAppInstallDefine `json:"additionalProperties" yaml:"additionalProperties"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type LocalAppInstallDefine struct { | ||||||
|  | 	FormFields interface{} `json:"formFields" yaml:"formFields"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type ExtraProperties struct { | ||||||
|  | 	Tags []Tag `json:"tags"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type AppProperty struct { | ||||||
| 	Name               string   `json:"name"` | 	Name               string   `json:"name"` | ||||||
|  | 	Type               string   `json:"type"` | ||||||
| 	Tags               []string `json:"tags"` | 	Tags               []string `json:"tags"` | ||||||
| 	Versions           []string `json:"versions"` |  | ||||||
| 	ShortDescZh        string   `json:"shortDescZh"` | 	ShortDescZh        string   `json:"shortDescZh"` | ||||||
| 	ShortDescEn        string   `json:"shortDescEn"` | 	ShortDescEn        string   `json:"shortDescEn"` | ||||||
| 	Type               string   `json:"type"` | 	Key                string   `json:"key"` | ||||||
| 	Required           []string `json:"Required"` | 	Required           []string `json:"Required"` | ||||||
| 	CrossVersionUpdate bool     `json:"crossVersionUpdate"` | 	CrossVersionUpdate bool     `json:"crossVersionUpdate"` | ||||||
| 	Limit              int      `json:"limit"` | 	Limit              int      `json:"limit"` | ||||||
| @@ -54,9 +82,12 @@ type AppDefine struct { | |||||||
| 	Document           string   `json:"document"` | 	Document           string   `json:"document"` | ||||||
| } | } | ||||||
|  |  | ||||||
| func (define AppDefine) GetRequired() string { | type AppConfigVersion struct { | ||||||
| 	by, _ := json.Marshal(define.Required) | 	Name                string      `json:"name"` | ||||||
| 	return string(by) | 	LastModified        int         `json:"lastModified"` | ||||||
|  | 	DownloadUrl         string      `json:"downloadUrl"` | ||||||
|  | 	DownloadCallBackUrl string      `json:"downloadCallBackUrl"` | ||||||
|  | 	AppForm             interface{} `json:"additionalProperties"` | ||||||
| } | } | ||||||
|  |  | ||||||
| type Tag struct { | type Tag struct { | ||||||
| @@ -78,6 +109,8 @@ type AppFormFields struct { | |||||||
| 	Disabled bool           `json:"disabled"` | 	Disabled bool           `json:"disabled"` | ||||||
| 	Edit     bool           `json:"edit"` | 	Edit     bool           `json:"edit"` | ||||||
| 	Rule     string         `json:"rule"` | 	Rule     string         `json:"rule"` | ||||||
|  | 	Multiple bool           `json:"multiple"` | ||||||
|  | 	Child    interface{}    `json:"child"` | ||||||
| 	Values   []AppFormValue `json:"values"` | 	Values   []AppFormValue `json:"values"` | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -12,16 +12,18 @@ type UserLoginInfo struct { | |||||||
| } | } | ||||||
|  |  | ||||||
| type MfaCredential struct { | type MfaCredential struct { | ||||||
| 	Secret string `json:"secret"` | 	Secret   string `json:"secret"` | ||||||
| 	Code   string `json:"code"` | 	Code     string `json:"code"` | ||||||
|  | 	Interval string `json:"interval"` | ||||||
| } | } | ||||||
|  |  | ||||||
| type Login struct { | type Login struct { | ||||||
| 	Name       string `json:"name"` | 	Name          string `json:"name"` | ||||||
| 	Password   string `json:"password"` | 	Password      string `json:"password"` | ||||||
| 	Captcha    string `json:"captcha"` | 	IgnoreCaptcha bool   `json:"ignoreCaptcha"` | ||||||
| 	CaptchaID  string `json:"captchaID"` | 	Captcha       string `json:"captcha"` | ||||||
| 	AuthMethod string `json:"authMethod"` | 	CaptchaID     string `json:"captchaID"` | ||||||
|  | 	AuthMethod    string `json:"authMethod"` | ||||||
| } | } | ||||||
|  |  | ||||||
| type MFALogin struct { | type MFALogin struct { | ||||||
| @@ -30,8 +32,3 @@ type MFALogin struct { | |||||||
| 	Code       string `json:"code"` | 	Code       string `json:"code"` | ||||||
| 	AuthMethod string `json:"authMethod"` | 	AuthMethod string `json:"authMethod"` | ||||||
| } | } | ||||||
|  |  | ||||||
| type InitUser struct { |  | ||||||
| 	Name     string `json:"name" validate:"required"` |  | ||||||
| 	Password string `json:"password" validate:"required"` |  | ||||||
| } |  | ||||||
|   | |||||||
| @@ -8,15 +8,17 @@ type BackupOperate struct { | |||||||
| 	Bucket     string `json:"bucket"` | 	Bucket     string `json:"bucket"` | ||||||
| 	AccessKey  string `json:"accessKey"` | 	AccessKey  string `json:"accessKey"` | ||||||
| 	Credential string `json:"credential"` | 	Credential string `json:"credential"` | ||||||
|  | 	BackupPath string `json:"backupPath"` | ||||||
| 	Vars       string `json:"vars" validate:"required"` | 	Vars       string `json:"vars" validate:"required"` | ||||||
| } | } | ||||||
|  |  | ||||||
| type BackupInfo struct { | type BackupInfo struct { | ||||||
| 	ID        uint      `json:"id"` | 	ID         uint      `json:"id"` | ||||||
| 	CreatedAt time.Time `json:"createdAt"` | 	CreatedAt  time.Time `json:"createdAt"` | ||||||
| 	Type      string    `json:"type"` | 	Type       string    `json:"type"` | ||||||
| 	Bucket    string    `json:"bucket"` | 	Bucket     string    `json:"bucket"` | ||||||
| 	Vars      string    `json:"vars"` | 	BackupPath string    `json:"backupPath"` | ||||||
|  | 	Vars       string    `json:"vars"` | ||||||
| } | } | ||||||
|  |  | ||||||
| type BackupSearch struct { | type BackupSearch struct { | ||||||
| @@ -36,6 +38,7 @@ type CommonBackup struct { | |||||||
| 	DetailName string `json:"detailName"` | 	DetailName string `json:"detailName"` | ||||||
| } | } | ||||||
| type CommonRecover struct { | type CommonRecover struct { | ||||||
|  | 	Source     string `json:"source" validate:"required,oneof=OSS S3 SFTP MINIO LOCAL COS KODO OneDrive"` | ||||||
| 	Type       string `json:"type" validate:"required,oneof=app mysql redis website"` | 	Type       string `json:"type" validate:"required,oneof=app mysql redis website"` | ||||||
| 	Name       string `json:"name"` | 	Name       string `json:"name"` | ||||||
| 	DetailName string `json:"detailName"` | 	DetailName string `json:"detailName"` | ||||||
| @@ -59,7 +62,7 @@ type BackupRecords struct { | |||||||
| } | } | ||||||
|  |  | ||||||
| type DownloadRecord struct { | type DownloadRecord struct { | ||||||
| 	Source   string `json:"source" validate:"required,oneof=OSS S3 SFTP MINIO LOCAL"` | 	Source   string `json:"source" validate:"required,oneof=OSS S3 SFTP MINIO LOCAL COS KODO OneDrive"` | ||||||
| 	FileDir  string `json:"fileDir" validate:"required"` | 	FileDir  string `json:"fileDir" validate:"required"` | ||||||
| 	FileName string `json:"fileName" validate:"required"` | 	FileName string `json:"fileName" validate:"required"` | ||||||
| } | } | ||||||
|   | |||||||
| @@ -2,7 +2,9 @@ package dto | |||||||
|  |  | ||||||
| type SearchWithPage struct { | type SearchWithPage struct { | ||||||
| 	PageInfo | 	PageInfo | ||||||
| 	Info string `json:"info"` | 	Info    string `json:"info"` | ||||||
|  | 	OrderBy string `json:"orderBy"` | ||||||
|  | 	Order   string `json:"order"` | ||||||
| } | } | ||||||
|  |  | ||||||
| type PageInfo struct { | type PageInfo struct { | ||||||
| @@ -12,7 +14,7 @@ type PageInfo struct { | |||||||
|  |  | ||||||
| type UpdateDescription struct { | type UpdateDescription struct { | ||||||
| 	ID          uint   `json:"id" validate:"required"` | 	ID          uint   `json:"id" validate:"required"` | ||||||
| 	Description string `json:"description"` | 	Description string `json:"description" validate:"max=256"` | ||||||
| } | } | ||||||
|  |  | ||||||
| type OperationWithName struct { | type OperationWithName struct { | ||||||
| @@ -23,6 +25,10 @@ type OperateByID struct { | |||||||
| 	ID uint `json:"id" validate:"required"` | 	ID uint `json:"id" validate:"required"` | ||||||
| } | } | ||||||
|  |  | ||||||
|  | type Operate struct { | ||||||
|  | 	Operation string `json:"operation" validate:"required"` | ||||||
|  | } | ||||||
|  |  | ||||||
| type BatchDeleteReq struct { | type BatchDeleteReq struct { | ||||||
| 	Ids []uint `json:"ids" validate:"required"` | 	Ids []uint `json:"ids" validate:"required"` | ||||||
| } | } | ||||||
|   | |||||||
| @@ -5,6 +5,8 @@ import "time" | |||||||
| type PageContainer struct { | type PageContainer struct { | ||||||
| 	PageInfo | 	PageInfo | ||||||
| 	Name    string `json:"name"` | 	Name    string `json:"name"` | ||||||
|  | 	OrderBy string `json:"orderBy"` | ||||||
|  | 	Order   string `json:"order"` | ||||||
| 	Filters string `json:"filters"` | 	Filters string `json:"filters"` | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -22,18 +24,29 @@ type ContainerInfo struct { | |||||||
| 	State       string `json:"state"` | 	State       string `json:"state"` | ||||||
| 	RunTime     string `json:"runTime"` | 	RunTime     string `json:"runTime"` | ||||||
|  |  | ||||||
|  | 	Ports []string `json:"ports"` | ||||||
|  |  | ||||||
| 	IsFromApp     bool `json:"isFromApp"` | 	IsFromApp     bool `json:"isFromApp"` | ||||||
| 	IsFromCompose bool `json:"isFromCompose"` | 	IsFromCompose bool `json:"isFromCompose"` | ||||||
| } | } | ||||||
|  |  | ||||||
| type ContainerCreate struct { | type ResourceLimit struct { | ||||||
|  | 	CPU    int `json:"cpu"` | ||||||
|  | 	Memory int `json:"memory"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type ContainerOperate struct { | ||||||
|  | 	ContainerID     string         `json:"containerID"` | ||||||
|  | 	ForcePull       bool           `json:"forcePull"` | ||||||
| 	Name            string         `json:"name"` | 	Name            string         `json:"name"` | ||||||
| 	Image           string         `json:"image"` | 	Image           string         `json:"image"` | ||||||
|  | 	Network         string         `json:"network"` | ||||||
| 	PublishAllPorts bool           `json:"publishAllPorts"` | 	PublishAllPorts bool           `json:"publishAllPorts"` | ||||||
| 	ExposedPorts    []PortHelper   `json:"exposedPorts"` | 	ExposedPorts    []PortHelper   `json:"exposedPorts"` | ||||||
| 	Cmd             []string       `json:"cmd"` | 	Cmd             []string       `json:"cmd"` | ||||||
| 	NanoCPUs        int64          `json:"nanoCPUs"` | 	CPUShares       int64          `json:"cpuShares"` | ||||||
| 	Memory          int64          `json:"memory"` | 	NanoCPUs        float64        `json:"nanoCPUs"` | ||||||
|  | 	Memory          float64        `json:"memory"` | ||||||
| 	AutoRemove      bool           `json:"autoRemove"` | 	AutoRemove      bool           `json:"autoRemove"` | ||||||
| 	Volumes         []VolumeHelper `json:"volumes"` | 	Volumes         []VolumeHelper `json:"volumes"` | ||||||
| 	Labels          []string       `json:"labels"` | 	Labels          []string       `json:"labels"` | ||||||
| @@ -41,7 +54,19 @@ type ContainerCreate struct { | |||||||
| 	RestartPolicy   string         `json:"restartPolicy"` | 	RestartPolicy   string         `json:"restartPolicy"` | ||||||
| } | } | ||||||
|  |  | ||||||
| type ContainterStats struct { | type ContainerUpgrade struct { | ||||||
|  | 	Name      string `json:"name" validate:"required"` | ||||||
|  | 	Image     string `json:"image" validate:"required"` | ||||||
|  | 	ForcePull bool   `json:"forcePull"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type ContainerListStats struct { | ||||||
|  | 	ContainerID   string  `json:"containerID"` | ||||||
|  | 	CPUPercent    float64 `json:"cpuPercent"` | ||||||
|  | 	MemoryPercent float64 `json:"memoryPercent"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type ContainerStats struct { | ||||||
| 	CPUPercent float64 `json:"cpuPercent"` | 	CPUPercent float64 `json:"cpuPercent"` | ||||||
| 	Memory     float64 `json:"memory"` | 	Memory     float64 `json:"memory"` | ||||||
| 	Cache      float64 `json:"cache"` | 	Cache      float64 `json:"cache"` | ||||||
| @@ -59,13 +84,10 @@ type VolumeHelper struct { | |||||||
| 	Mode         string `json:"mode"` | 	Mode         string `json:"mode"` | ||||||
| } | } | ||||||
| type PortHelper struct { | type PortHelper struct { | ||||||
| 	ContainerPort int `json:"containerPort"` | 	HostIP        string `json:"hostIP"` | ||||||
| 	HostPort      int `json:"hostPort"` | 	HostPort      string `json:"hostPort"` | ||||||
| } | 	ContainerPort string `json:"containerPort"` | ||||||
|  | 	Protocol      string `json:"protocol"` | ||||||
| type ContainerLog struct { |  | ||||||
| 	ContainerID string `json:"containerID" validate:"required"` |  | ||||||
| 	Mode        string `json:"mode" validate:"required"` |  | ||||||
| } | } | ||||||
|  |  | ||||||
| type ContainerOperation struct { | type ContainerOperation struct { | ||||||
| @@ -74,6 +96,16 @@ type ContainerOperation struct { | |||||||
| 	NewName   string `json:"newName"` | 	NewName   string `json:"newName"` | ||||||
| } | } | ||||||
|  |  | ||||||
|  | type ContainerPrune struct { | ||||||
|  | 	PruneType  string `json:"pruneType" validate:"required,oneof=container image volume network"` | ||||||
|  | 	WithTagAll bool   `json:"withTagAll"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type ContainerPruneReport struct { | ||||||
|  | 	DeletedNumber  int `json:"deletedNumber"` | ||||||
|  | 	SpaceReclaimed int `json:"spaceReclaimed"` | ||||||
|  | } | ||||||
|  |  | ||||||
| type Network struct { | type Network struct { | ||||||
| 	ID         string    `json:"id"` | 	ID         string    `json:"id"` | ||||||
| 	Name       string    `json:"name"` | 	Name       string    `json:"name"` | ||||||
| @@ -85,7 +117,7 @@ type Network struct { | |||||||
| 	CreatedAt  time.Time `json:"createdAt"` | 	CreatedAt  time.Time `json:"createdAt"` | ||||||
| 	Attachable bool      `json:"attachable"` | 	Attachable bool      `json:"attachable"` | ||||||
| } | } | ||||||
| type NetworkCreat struct { | type NetworkCreate struct { | ||||||
| 	Name    string   `json:"name"` | 	Name    string   `json:"name"` | ||||||
| 	Driver  string   `json:"driver"` | 	Driver  string   `json:"driver"` | ||||||
| 	Options []string `json:"options"` | 	Options []string `json:"options"` | ||||||
| @@ -102,7 +134,7 @@ type Volume struct { | |||||||
| 	Mountpoint string    `json:"mountpoint"` | 	Mountpoint string    `json:"mountpoint"` | ||||||
| 	CreatedAt  time.Time `json:"createdAt"` | 	CreatedAt  time.Time `json:"createdAt"` | ||||||
| } | } | ||||||
| type VolumeCreat struct { | type VolumeCreate struct { | ||||||
| 	Name    string   `json:"name"` | 	Name    string   `json:"name"` | ||||||
| 	Driver  string   `json:"driver"` | 	Driver  string   `json:"driver"` | ||||||
| 	Options []string `json:"options"` | 	Options []string `json:"options"` | ||||||
| @@ -140,6 +172,7 @@ type ComposeOperation struct { | |||||||
| 	Name      string `json:"name" validate:"required"` | 	Name      string `json:"name" validate:"required"` | ||||||
| 	Path      string `json:"path" validate:"required"` | 	Path      string `json:"path" validate:"required"` | ||||||
| 	Operation string `json:"operation" validate:"required,oneof=start stop down"` | 	Operation string `json:"operation" validate:"required,oneof=start stop down"` | ||||||
|  | 	WithFile  bool   `json:"withFile"` | ||||||
| } | } | ||||||
| type ComposeUpdate struct { | type ComposeUpdate struct { | ||||||
| 	Name    string `json:"name" validate:"required"` | 	Name    string `json:"name" validate:"required"` | ||||||
|   | |||||||
| @@ -6,12 +6,14 @@ type CronjobCreate struct { | |||||||
| 	Name     string `json:"name" validate:"required"` | 	Name     string `json:"name" validate:"required"` | ||||||
| 	Type     string `json:"type" validate:"required"` | 	Type     string `json:"type" validate:"required"` | ||||||
| 	SpecType string `json:"specType" validate:"required"` | 	SpecType string `json:"specType" validate:"required"` | ||||||
| 	Week     int    `json:"week" validate:"number,max=7,min=1"` | 	Week     int    `json:"week" validate:"number,max=6,min=0"` | ||||||
| 	Day      int    `json:"day" validate:"number"` | 	Day      int    `json:"day" validate:"number"` | ||||||
| 	Hour     int    `json:"hour" validate:"number"` | 	Hour     int    `json:"hour" validate:"number"` | ||||||
| 	Minute   int    `json:"minute" validate:"number"` | 	Minute   int    `json:"minute" validate:"number"` | ||||||
|  | 	Second   int    `json:"second" validate:"number"` | ||||||
|  |  | ||||||
| 	Script         string `json:"script"` | 	Script         string `json:"script"` | ||||||
|  | 	ContainerName  string `json:"containerName"` | ||||||
| 	Website        string `json:"website"` | 	Website        string `json:"website"` | ||||||
| 	ExclusionRules string `json:"exclusionRules"` | 	ExclusionRules string `json:"exclusionRules"` | ||||||
| 	DBName         string `json:"dbName"` | 	DBName         string `json:"dbName"` | ||||||
| @@ -26,12 +28,14 @@ type CronjobUpdate struct { | |||||||
| 	ID       uint   `json:"id" validate:"required"` | 	ID       uint   `json:"id" validate:"required"` | ||||||
| 	Name     string `json:"name" validate:"required"` | 	Name     string `json:"name" validate:"required"` | ||||||
| 	SpecType string `json:"specType" validate:"required"` | 	SpecType string `json:"specType" validate:"required"` | ||||||
| 	Week     int    `json:"week" validate:"number,max=7,min=1"` | 	Week     int    `json:"week" validate:"number,max=6,min=0"` | ||||||
| 	Day      int    `json:"day" validate:"number"` | 	Day      int    `json:"day" validate:"number"` | ||||||
| 	Hour     int    `json:"hour" validate:"number"` | 	Hour     int    `json:"hour" validate:"number"` | ||||||
| 	Minute   int    `json:"minute" validate:"number"` | 	Minute   int    `json:"minute" validate:"number"` | ||||||
|  | 	Second   int    `json:"second" validate:"number"` | ||||||
|  |  | ||||||
| 	Script         string `json:"script"` | 	Script         string `json:"script"` | ||||||
|  | 	ContainerName  string `json:"containerName"` | ||||||
| 	Website        string `json:"website"` | 	Website        string `json:"website"` | ||||||
| 	ExclusionRules string `json:"exclusionRules"` | 	ExclusionRules string `json:"exclusionRules"` | ||||||
| 	DBName         string `json:"dbName"` | 	DBName         string `json:"dbName"` | ||||||
| @@ -52,6 +56,16 @@ type CronjobDownload struct { | |||||||
| 	BackupAccountID uint `json:"backupAccountID" validate:"required"` | 	BackupAccountID uint `json:"backupAccountID" validate:"required"` | ||||||
| } | } | ||||||
|  |  | ||||||
|  | type CronjobClean struct { | ||||||
|  | 	CleanData bool `json:"cleanData"` | ||||||
|  | 	CronjobID uint `json:"cronjobID" validate:"required"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type CronjobBatchDelete struct { | ||||||
|  | 	CleanData bool   `json:"cleanData"` | ||||||
|  | 	IDs       []uint `json:"ids"` | ||||||
|  | } | ||||||
|  |  | ||||||
| type CronjobInfo struct { | type CronjobInfo struct { | ||||||
| 	ID       uint   `json:"id"` | 	ID       uint   `json:"id"` | ||||||
| 	Name     string `json:"name"` | 	Name     string `json:"name"` | ||||||
| @@ -61,8 +75,10 @@ type CronjobInfo struct { | |||||||
| 	Day      int    `json:"day"` | 	Day      int    `json:"day"` | ||||||
| 	Hour     int    `json:"hour"` | 	Hour     int    `json:"hour"` | ||||||
| 	Minute   int    `json:"minute"` | 	Minute   int    `json:"minute"` | ||||||
|  | 	Second   int    `json:"second"` | ||||||
|  |  | ||||||
| 	Script         string `json:"script"` | 	Script         string `json:"script"` | ||||||
|  | 	ContainerName  string `json:"containerName"` | ||||||
| 	Website        string `json:"website"` | 	Website        string `json:"website"` | ||||||
| 	ExclusionRules string `json:"exclusionRules"` | 	ExclusionRules string `json:"exclusionRules"` | ||||||
| 	DBName         string `json:"dbName"` | 	DBName         string `json:"dbName"` | ||||||
| @@ -73,7 +89,7 @@ type CronjobInfo struct { | |||||||
| 	TargetDirID    int    `json:"targetDirID"` | 	TargetDirID    int    `json:"targetDirID"` | ||||||
| 	RetainCopies   int    `json:"retainCopies"` | 	RetainCopies   int    `json:"retainCopies"` | ||||||
|  |  | ||||||
| 	LastRecrodTime string `json:"lastRecrodTime"` | 	LastRecordTime string `json:"lastRecordTime"` | ||||||
| 	Status         string `json:"status"` | 	Status         string `json:"status"` | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -3,13 +3,6 @@ package dto | |||||||
| import "time" | import "time" | ||||||
|  |  | ||||||
| type DashboardBase struct { | type DashboardBase struct { | ||||||
| 	HaloID         uint `json:"haloID"` |  | ||||||
| 	DateeaseID     uint `json:"dateeaseID"` |  | ||||||
| 	JumpServerID   uint `json:"jumpserverID"` |  | ||||||
| 	MeterSphereID  uint `json:"metersphereID"` |  | ||||||
| 	KubeoperatorID uint `json:"kubeoperatorID"` |  | ||||||
| 	KubepiID       uint `json:"kubepiID"` |  | ||||||
|  |  | ||||||
| 	WebsiteNumber     int `json:"websiteNumber"` | 	WebsiteNumber     int `json:"websiteNumber"` | ||||||
| 	DatabaseNumber    int `json:"databaseNumber"` | 	DatabaseNumber    int `json:"databaseNumber"` | ||||||
| 	CronjobNumber     int `json:"cronjobNumber"` | 	CronjobNumber     int `json:"cronjobNumber"` | ||||||
| @@ -55,8 +48,21 @@ type DashboardCurrent struct { | |||||||
| 	IOReadBytes  uint64 `json:"ioReadBytes"` | 	IOReadBytes  uint64 `json:"ioReadBytes"` | ||||||
| 	IOWriteBytes uint64 `json:"ioWriteBytes"` | 	IOWriteBytes uint64 `json:"ioWriteBytes"` | ||||||
| 	IOCount      uint64 `json:"ioCount"` | 	IOCount      uint64 `json:"ioCount"` | ||||||
| 	IOTime       uint64 `json:"ioTime"` | 	IOReadTime   uint64 `json:"ioReadTime"` | ||||||
|  | 	IOWriteTime  uint64 `json:"ioWriteTime"` | ||||||
|  |  | ||||||
|  | 	DiskData []DiskInfo `json:"diskData"` | ||||||
|  |  | ||||||
|  | 	NetBytesSent uint64 `json:"netBytesSent"` | ||||||
|  | 	NetBytesRecv uint64 `json:"netBytesRecv"` | ||||||
|  |  | ||||||
|  | 	ShotTime time.Time `json:"shotTime"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type DiskInfo struct { | ||||||
|  | 	Path        string  `json:"path"` | ||||||
|  | 	Type        string  `json:"type"` | ||||||
|  | 	Device      string  `json:"device"` | ||||||
| 	Total       uint64  `json:"total"` | 	Total       uint64  `json:"total"` | ||||||
| 	Free        uint64  `json:"free"` | 	Free        uint64  `json:"free"` | ||||||
| 	Used        uint64  `json:"used"` | 	Used        uint64  `json:"used"` | ||||||
| @@ -66,9 +72,4 @@ type DashboardCurrent struct { | |||||||
| 	InodesUsed        uint64  `json:"inodesUsed"` | 	InodesUsed        uint64  `json:"inodesUsed"` | ||||||
| 	InodesFree        uint64  `json:"inodesFree"` | 	InodesFree        uint64  `json:"inodesFree"` | ||||||
| 	InodesUsedPercent float64 `json:"inodesUsedPercent"` | 	InodesUsedPercent float64 `json:"inodesUsedPercent"` | ||||||
|  |  | ||||||
| 	NetBytesSent uint64 `json:"netBytesSent"` |  | ||||||
| 	NetBytesRecv uint64 `json:"netBytesRecv"` |  | ||||||
|  |  | ||||||
| 	ShotTime time.Time `json:"shotTime"` |  | ||||||
| } | } | ||||||
|   | |||||||
| @@ -5,16 +5,24 @@ type DaemonJsonUpdateByFile struct { | |||||||
| } | } | ||||||
|  |  | ||||||
| type DaemonJsonConf struct { | type DaemonJsonConf struct { | ||||||
|  | 	IsSwarm      bool     `json:"isSwarm"` | ||||||
| 	Status       string   `json:"status"` | 	Status       string   `json:"status"` | ||||||
| 	Version      string   `json:"version"` | 	Version      string   `json:"version"` | ||||||
| 	Mirrors      []string `json:"registryMirrors"` | 	Mirrors      []string `json:"registryMirrors"` | ||||||
| 	Registries   []string `json:"insecureRegistries"` | 	Registries   []string `json:"insecureRegistries"` | ||||||
| 	LiveRestore  bool     `json:"liveRestore"` | 	LiveRestore  bool     `json:"liveRestore"` | ||||||
|  | 	IPTables     bool     `json:"iptables"` | ||||||
| 	CgroupDriver string   `json:"cgroupDriver"` | 	CgroupDriver string   `json:"cgroupDriver"` | ||||||
|  |  | ||||||
|  | 	LogMaxSize string `json:"logMaxSize"` | ||||||
|  | 	LogMaxFile string `json:"logMaxFile"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type LogOption struct { | ||||||
|  | 	LogMaxSize string `json:"logMaxSize"` | ||||||
|  | 	LogMaxFile string `json:"logMaxFile"` | ||||||
| } | } | ||||||
|  |  | ||||||
| type DockerOperation struct { | type DockerOperation struct { | ||||||
| 	StopSocket  bool   `json:"stopSocket"` | 	Operation string `json:"operation" validate:"required,oneof=start restart stop"` | ||||||
| 	StopService bool   `json:"stopService"` |  | ||||||
| 	Operation   string `json:"operation" validate:"required,oneof=start restart stop"` |  | ||||||
| } | } | ||||||
|   | |||||||
							
								
								
									
										47
									
								
								backend/app/dto/firewall.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										47
									
								
								backend/app/dto/firewall.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,47 @@ | |||||||
|  | package dto | ||||||
|  |  | ||||||
|  | type FirewallBaseInfo struct { | ||||||
|  | 	Name       string `json:"name"` | ||||||
|  | 	Status     string `json:"status"` | ||||||
|  | 	Version    string `json:"version"` | ||||||
|  | 	PingStatus string `json:"pingStatus"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type RuleSearch struct { | ||||||
|  | 	PageInfo | ||||||
|  | 	Info string `json:"info"` | ||||||
|  | 	Type string `json:"type" validate:"required"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type FirewallOperation struct { | ||||||
|  | 	Operation string `json:"operation" validate:"required,oneof=start stop disablePing enablePing"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type PortRuleOperate struct { | ||||||
|  | 	Operation string `json:"operation" validate:"required,oneof=add remove"` | ||||||
|  | 	Address   string `json:"address"` | ||||||
|  | 	Port      string `json:"port" validate:"required"` | ||||||
|  | 	Protocol  string `json:"protocol" validate:"required,oneof=tcp udp tcp/udp"` | ||||||
|  | 	Strategy  string `json:"strategy" validate:"required,oneof=accept drop"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type AddrRuleOperate struct { | ||||||
|  | 	Operation string `json:"operation" validate:"required,oneof=add remove"` | ||||||
|  | 	Address   string `json:"address"  validate:"required"` | ||||||
|  | 	Strategy  string `json:"strategy" validate:"required,oneof=accept drop"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type PortRuleUpdate struct { | ||||||
|  | 	OldRule PortRuleOperate `json:"oldRule"` | ||||||
|  | 	NewRule PortRuleOperate `json:"newRule"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type AddrRuleUpdate struct { | ||||||
|  | 	OldRule AddrRuleOperate `json:"oldRule"` | ||||||
|  | 	NewRule AddrRuleOperate `json:"newRule"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type BatchRuleOperate struct { | ||||||
|  | 	Type  string            `json:"type" validate:"required"` | ||||||
|  | 	Rules []PortRuleOperate `json:"rules"` | ||||||
|  | } | ||||||
| @@ -5,15 +5,17 @@ import ( | |||||||
| ) | ) | ||||||
|  |  | ||||||
| type HostOperate struct { | type HostOperate struct { | ||||||
| 	ID         uint   `json:"id"` | 	ID               uint   `json:"id"` | ||||||
| 	GroupID    uint   `json:"groupID"` | 	GroupID          uint   `json:"groupID"` | ||||||
| 	Name       string `json:"name"` | 	Name             string `json:"name"` | ||||||
| 	Addr       string `json:"addr" validate:"required"` | 	Addr             string `json:"addr" validate:"required"` | ||||||
| 	Port       uint   `json:"port" validate:"required,number,max=65535,min=1"` | 	Port             uint   `json:"port" validate:"required,number,max=65535,min=1"` | ||||||
| 	User       string `json:"user" validate:"required"` | 	User             string `json:"user" validate:"required"` | ||||||
| 	AuthMode   string `json:"authMode" validate:"oneof=password key"` | 	AuthMode         string `json:"authMode" validate:"oneof=password key"` | ||||||
| 	PrivateKey string `json:"privateKey"` | 	Password         string `json:"password"` | ||||||
| 	Password   string `json:"password"` | 	PrivateKey       string `json:"privateKey"` | ||||||
|  | 	PassPhrase       string `json:"passPhrase"` | ||||||
|  | 	RememberPassword bool   `json:"rememberPassword"` | ||||||
|  |  | ||||||
| 	Description string `json:"description"` | 	Description string `json:"description"` | ||||||
| } | } | ||||||
| @@ -23,8 +25,9 @@ type HostConnTest struct { | |||||||
| 	Port       uint   `json:"port" validate:"required,number,max=65535,min=1"` | 	Port       uint   `json:"port" validate:"required,number,max=65535,min=1"` | ||||||
| 	User       string `json:"user" validate:"required"` | 	User       string `json:"user" validate:"required"` | ||||||
| 	AuthMode   string `json:"authMode" validate:"oneof=password key"` | 	AuthMode   string `json:"authMode" validate:"oneof=password key"` | ||||||
| 	PrivateKey string `json:"privateKey"` |  | ||||||
| 	Password   string `json:"password"` | 	Password   string `json:"password"` | ||||||
|  | 	PrivateKey string `json:"privateKey"` | ||||||
|  | 	PassPhrase string `json:"passPhrase"` | ||||||
| } | } | ||||||
|  |  | ||||||
| type SearchHostWithPage struct { | type SearchHostWithPage struct { | ||||||
| @@ -43,15 +46,19 @@ type ChangeHostGroup struct { | |||||||
| } | } | ||||||
|  |  | ||||||
| type HostInfo struct { | type HostInfo struct { | ||||||
| 	ID          uint      `json:"id"` | 	ID               uint      `json:"id"` | ||||||
| 	CreatedAt   time.Time `json:"createdAt"` | 	CreatedAt        time.Time `json:"createdAt"` | ||||||
| 	GroupID     uint      `json:"groupID"` | 	GroupID          uint      `json:"groupID"` | ||||||
| 	GroupBelong string    `json:"groupBelong"` | 	GroupBelong      string    `json:"groupBelong"` | ||||||
| 	Name        string    `json:"name"` | 	Name             string    `json:"name"` | ||||||
| 	Addr        string    `json:"addr"` | 	Addr             string    `json:"addr"` | ||||||
| 	Port        uint      `json:"port"` | 	Port             uint      `json:"port"` | ||||||
| 	User        string    `json:"user"` | 	User             string    `json:"user"` | ||||||
| 	AuthMode    string    `json:"authMode"` | 	AuthMode         string    `json:"authMode"` | ||||||
|  | 	Password         string    `json:"password"` | ||||||
|  | 	PrivateKey       string    `json:"privateKey"` | ||||||
|  | 	PassPhrase       string    `json:"passPhrase"` | ||||||
|  | 	RememberPassword bool      `json:"rememberPassword"` | ||||||
|  |  | ||||||
| 	Description string `json:"description"` | 	Description string `json:"description"` | ||||||
| } | } | ||||||
|   | |||||||
| @@ -10,35 +10,35 @@ type ImageInfo struct { | |||||||
| } | } | ||||||
|  |  | ||||||
| type ImageLoad struct { | type ImageLoad struct { | ||||||
| 	Path string `josn:"path" validate:"required"` | 	Path string `json:"path" validate:"required"` | ||||||
| } | } | ||||||
|  |  | ||||||
| type ImageBuild struct { | type ImageBuild struct { | ||||||
| 	From       string   `josn:"from" validate:"required"` | 	From       string   `json:"from" validate:"required"` | ||||||
| 	Name       string   `json:"name" validate:"required"` | 	Name       string   `json:"name" validate:"required"` | ||||||
| 	Dockerfile string   `josn:"dockerfile" validate:"required"` | 	Dockerfile string   `json:"dockerfile" validate:"required"` | ||||||
| 	Tags       []string `josn:"tags"` | 	Tags       []string `json:"tags"` | ||||||
| } | } | ||||||
|  |  | ||||||
| type ImagePull struct { | type ImagePull struct { | ||||||
| 	RepoID    uint   `josn:"repoID"` | 	RepoID    uint   `json:"repoID"` | ||||||
| 	ImageName string `josn:"imageName" validate:"required"` | 	ImageName string `json:"imageName" validate:"required"` | ||||||
| } | } | ||||||
|  |  | ||||||
| type ImageTag struct { | type ImageTag struct { | ||||||
| 	RepoID     uint   `josn:"repoID"` | 	RepoID     uint   `json:"repoID"` | ||||||
| 	SourceID   string `json:"sourceID" validate:"required"` | 	SourceID   string `json:"sourceID" validate:"required"` | ||||||
| 	TargetName string `josn:"targetName" validate:"required"` | 	TargetName string `json:"targetName" validate:"required"` | ||||||
| } | } | ||||||
|  |  | ||||||
| type ImagePush struct { | type ImagePush struct { | ||||||
| 	RepoID  uint   `josn:"repoID" validate:"required"` | 	RepoID  uint   `json:"repoID" validate:"required"` | ||||||
| 	TagName string `json:"tagName" validate:"required"` | 	TagName string `json:"tagName" validate:"required"` | ||||||
| 	Name    string `json:"name" validate:"required"` | 	Name    string `json:"name" validate:"required"` | ||||||
| } | } | ||||||
|  |  | ||||||
| type ImageSave struct { | type ImageSave struct { | ||||||
| 	TagName string `json:"tagName" validate:"required"` | 	TagName string `json:"tagName" validate:"required"` | ||||||
| 	Path    string `josn:"path" validate:"required"` | 	Path    string `json:"path" validate:"required"` | ||||||
| 	Name    string `json:"name" validate:"required"` | 	Name    string `json:"name" validate:"required"` | ||||||
| } | } | ||||||
|   | |||||||
| @@ -28,13 +28,20 @@ type NginxParam struct { | |||||||
| 	Params      []string | 	Params      []string | ||||||
| } | } | ||||||
|  |  | ||||||
|  | type NginxAuth struct { | ||||||
|  | 	Username string `json:"username"` | ||||||
|  | 	Remark   string `json:"remark"` | ||||||
|  | } | ||||||
|  |  | ||||||
| type NginxKey string | type NginxKey string | ||||||
|  |  | ||||||
| const ( | const ( | ||||||
| 	Index     NginxKey = "index" | 	Index      NginxKey = "index" | ||||||
| 	LimitConn NginxKey = "limit-conn" | 	LimitConn  NginxKey = "limit-conn" | ||||||
| 	SSL       NginxKey = "ssl" | 	SSL        NginxKey = "ssl" | ||||||
| 	HttpPer   NginxKey = "http-per" | 	CACHE      NginxKey = "cache" | ||||||
|  | 	HttpPer    NginxKey = "http-per" | ||||||
|  | 	ProxyCache NginxKey = "proxy-cache" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| var ScopeKeyMap = map[NginxKey][]string{ | var ScopeKeyMap = map[NginxKey][]string{ | ||||||
| @@ -46,5 +53,7 @@ var ScopeKeyMap = map[NginxKey][]string{ | |||||||
|  |  | ||||||
| var StaticFileKeyMap = map[NginxKey]struct { | var StaticFileKeyMap = map[NginxKey]struct { | ||||||
| }{ | }{ | ||||||
| 	SSL: {}, | 	SSL:        {}, | ||||||
|  | 	CACHE:      {}, | ||||||
|  | 	ProxyCache: {}, | ||||||
| } | } | ||||||
|   | |||||||
| @@ -18,6 +18,18 @@ type AppInstallCreate struct { | |||||||
| 	Params      map[string]interface{} `json:"params"` | 	Params      map[string]interface{} `json:"params"` | ||||||
| 	Name        string                 `json:"name" validate:"required"` | 	Name        string                 `json:"name" validate:"required"` | ||||||
| 	Services    map[string]string      `json:"services"` | 	Services    map[string]string      `json:"services"` | ||||||
|  | 	AppContainerConfig | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type AppContainerConfig struct { | ||||||
|  | 	Advanced      bool    `json:"advanced"` | ||||||
|  | 	CpuQuota      float64 `json:"cpuQuota"` | ||||||
|  | 	MemoryLimit   float64 `json:"memoryLimit"` | ||||||
|  | 	MemoryUnit    string  `json:"memoryUnit"` | ||||||
|  | 	ContainerName string  `json:"containerName"` | ||||||
|  | 	AllowPort     bool    `json:"allowPort"` | ||||||
|  | 	EditCompose   bool    `json:"editCompose"` | ||||||
|  | 	DockerCompose string  `json:"dockerCompose"` | ||||||
| } | } | ||||||
|  |  | ||||||
| type AppInstalledSearch struct { | type AppInstalledSearch struct { | ||||||
| @@ -51,6 +63,12 @@ type AppInstalledOperate struct { | |||||||
| type AppInstalledUpdate struct { | type AppInstalledUpdate struct { | ||||||
| 	InstallId uint                   `json:"installId" validate:"required"` | 	InstallId uint                   `json:"installId" validate:"required"` | ||||||
| 	Params    map[string]interface{} `json:"params" validate:"required"` | 	Params    map[string]interface{} `json:"params" validate:"required"` | ||||||
|  | 	AppContainerConfig | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type AppInstalledIgnoreUpgrade struct { | ||||||
|  | 	DetailID uint   `json:"detailID"  validate:"required"` | ||||||
|  | 	Operate  string `json:"operate"   validate:"required,oneof=cancel ignore"` | ||||||
| } | } | ||||||
|  |  | ||||||
| type PortUpdate struct { | type PortUpdate struct { | ||||||
|   | |||||||
| @@ -22,6 +22,7 @@ type FileCreate struct { | |||||||
| 	IsLink    bool   `json:"isLink"` | 	IsLink    bool   `json:"isLink"` | ||||||
| 	IsSymlink bool   `json:"isSymlink"` | 	IsSymlink bool   `json:"isSymlink"` | ||||||
| 	LinkPath  string `json:"linkPath"` | 	LinkPath  string `json:"linkPath"` | ||||||
|  | 	Sub       bool   `json:"sub"` | ||||||
| } | } | ||||||
|  |  | ||||||
| type FileDelete struct { | type FileDelete struct { | ||||||
| @@ -81,6 +82,11 @@ type FileDownload struct { | |||||||
| 	Compress bool     `json:"compress" validate:"required"` | 	Compress bool     `json:"compress" validate:"required"` | ||||||
| } | } | ||||||
|  |  | ||||||
|  | type FileChunkDownload struct { | ||||||
|  | 	Path string `json:"path" validate:"required"` | ||||||
|  | 	Name string `json:"name" validate:"required"` | ||||||
|  | } | ||||||
|  |  | ||||||
| type DirSizeReq struct { | type DirSizeReq struct { | ||||||
| 	Path string `json:"path" validate:"required"` | 	Path string `json:"path" validate:"required"` | ||||||
| } | } | ||||||
| @@ -88,3 +94,10 @@ type DirSizeReq struct { | |||||||
| type FileProcessReq struct { | type FileProcessReq struct { | ||||||
| 	Key string `json:"key"` | 	Key string `json:"key"` | ||||||
| } | } | ||||||
|  |  | ||||||
|  | type FileRoleUpdate struct { | ||||||
|  | 	Path  string `json:"path" validate:"required"` | ||||||
|  | 	User  string `json:"user" validate:"required"` | ||||||
|  | 	Group string `json:"group" validate:"required"` | ||||||
|  | 	Sub   bool   `json:"sub" validate:"required"` | ||||||
|  | } | ||||||
|   | |||||||
| @@ -19,3 +19,50 @@ type NginxConfigUpdate struct { | |||||||
| 	WebsiteID uint         `json:"websiteId" validate:"required"` | 	WebsiteID uint         `json:"websiteId" validate:"required"` | ||||||
| 	Params    interface{}  `json:"params"` | 	Params    interface{}  `json:"params"` | ||||||
| } | } | ||||||
|  |  | ||||||
|  | type NginxRewriteReq struct { | ||||||
|  | 	WebsiteID uint   `json:"websiteId" validate:"required"` | ||||||
|  | 	Name      string `json:"name" validate:"required"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type NginxRewriteUpdate struct { | ||||||
|  | 	WebsiteID uint   `json:"websiteId" validate:"required"` | ||||||
|  | 	Name      string `json:"name" validate:"required"` | ||||||
|  | 	Content   string `json:"content" validate:"required"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type NginxProxyUpdate struct { | ||||||
|  | 	WebsiteID uint   `json:"websiteID" validate:"required"` | ||||||
|  | 	Content   string `json:"content" validate:"required"` | ||||||
|  | 	Name      string `json:"name" validate:"required"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type NginxAuthUpdate struct { | ||||||
|  | 	WebsiteID uint   `json:"websiteID" validate:"required"` | ||||||
|  | 	Operate   string `json:"operate" validate:"required"` | ||||||
|  | 	Username  string `json:"username"  validate:"required"` | ||||||
|  | 	Password  string `json:"password" validate:"required"` | ||||||
|  | 	Remark    string `json:"remark"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type NginxAuthReq struct { | ||||||
|  | 	WebsiteID uint `json:"websiteID" validate:"required"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type NginxCommonReq struct { | ||||||
|  | 	WebsiteID uint `json:"websiteID" validate:"required"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type NginxAntiLeechUpdate struct { | ||||||
|  | 	WebsiteID   uint     `json:"websiteID" validate:"required"` | ||||||
|  | 	Extends     string   `json:"extends" validate:"required"` | ||||||
|  | 	Return      string   `json:"return" validate:"required"` | ||||||
|  | 	Enable      bool     `json:"enable"  validate:"required"` | ||||||
|  | 	ServerNames []string `json:"serverNames"` | ||||||
|  | 	Cache       bool     `json:"cache"` | ||||||
|  | 	CacheTime   int      `json:"cacheTime"` | ||||||
|  | 	CacheUint   string   `json:"cacheUint"` | ||||||
|  | 	NoneRef     bool     `json:"noneRef"` | ||||||
|  | 	LogEnable   bool     `json:"logEnable"` | ||||||
|  | 	Blocked     bool     `json:"blocked"` | ||||||
|  | } | ||||||
|   | |||||||
							
								
								
									
										5
									
								
								backend/app/dto/request/process.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								backend/app/dto/request/process.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,5 @@ | |||||||
|  | package request | ||||||
|  |  | ||||||
|  | type ProcessReq struct { | ||||||
|  | 	PID int32 `json:"PID"  validate:"required"` | ||||||
|  | } | ||||||
							
								
								
									
										32
									
								
								backend/app/dto/request/runtime.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								backend/app/dto/request/runtime.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,32 @@ | |||||||
|  | package request | ||||||
|  |  | ||||||
|  | import "github.com/1Panel-dev/1Panel/backend/app/dto" | ||||||
|  |  | ||||||
|  | type RuntimeSearch struct { | ||||||
|  | 	dto.PageInfo | ||||||
|  | 	Type   string `json:"type"` | ||||||
|  | 	Name   string `json:"name"` | ||||||
|  | 	Status string `json:"status"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type RuntimeCreate struct { | ||||||
|  | 	AppDetailID uint                   `json:"appDetailId"` | ||||||
|  | 	Name        string                 `json:"name"` | ||||||
|  | 	Params      map[string]interface{} `json:"params"` | ||||||
|  | 	Resource    string                 `json:"resource"` | ||||||
|  | 	Image       string                 `json:"image"` | ||||||
|  | 	Type        string                 `json:"type"` | ||||||
|  | 	Version     string                 `json:"version"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type RuntimeDelete struct { | ||||||
|  | 	ID uint `json:"id"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type RuntimeUpdate struct { | ||||||
|  | 	Name    string                 `json:"name"` | ||||||
|  | 	ID      uint                   `json:"id"` | ||||||
|  | 	Params  map[string]interface{} `json:"params"` | ||||||
|  | 	Image   string                 `json:"image"` | ||||||
|  | 	Version string                 `json:"version"` | ||||||
|  | } | ||||||
| @@ -7,6 +7,8 @@ import ( | |||||||
| type WebsiteSearch struct { | type WebsiteSearch struct { | ||||||
| 	dto.PageInfo | 	dto.PageInfo | ||||||
| 	Name           string `json:"name"` | 	Name           string `json:"name"` | ||||||
|  | 	OrderBy        string `json:"orderBy"` | ||||||
|  | 	Order          string `json:"order"` | ||||||
| 	WebsiteGroupID uint   `json:"websiteGroupId"` | 	WebsiteGroupID uint   `json:"websiteGroupId"` | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -18,17 +20,28 @@ type WebsiteCreate struct { | |||||||
| 	OtherDomains   string `json:"otherDomains"` | 	OtherDomains   string `json:"otherDomains"` | ||||||
| 	Proxy          string `json:"proxy"` | 	Proxy          string `json:"proxy"` | ||||||
| 	WebsiteGroupID uint   `json:"webSiteGroupID" validate:"required"` | 	WebsiteGroupID uint   `json:"webSiteGroupID" validate:"required"` | ||||||
|  | 	IPV6           bool   `json:"IPV6"` | ||||||
|  |  | ||||||
| 	AppType      string        `json:"appType" validate:"oneof=new installed"` | 	AppType      string        `json:"appType" validate:"oneof=new installed"` | ||||||
| 	AppInstall   NewAppInstall `json:"appInstall"` | 	AppInstall   NewAppInstall `json:"appInstall"` | ||||||
| 	AppID        uint          `json:"appID"` | 	AppID        uint          `json:"appID"` | ||||||
| 	AppInstallID uint          `json:"appInstallID"` | 	AppInstallID uint          `json:"appInstallID"` | ||||||
|  |  | ||||||
|  | 	RuntimeID uint `json:"runtimeID"` | ||||||
|  | 	RuntimeConfig | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type RuntimeConfig struct { | ||||||
|  | 	ProxyType string `json:"proxyType"` | ||||||
|  | 	Port      int    `json:"port"` | ||||||
| } | } | ||||||
|  |  | ||||||
| type NewAppInstall struct { | type NewAppInstall struct { | ||||||
| 	Name        string                 `json:"name"` | 	Name        string                 `json:"name"` | ||||||
| 	AppDetailId uint                   `json:"appDetailID"` | 	AppDetailId uint                   `json:"appDetailID"` | ||||||
| 	Params      map[string]interface{} `json:"params"` | 	Params      map[string]interface{} `json:"params"` | ||||||
|  |  | ||||||
|  | 	AppContainerConfig | ||||||
| } | } | ||||||
|  |  | ||||||
| type WebsiteInstallCheckReq struct { | type WebsiteInstallCheckReq struct { | ||||||
| @@ -41,6 +54,7 @@ type WebsiteUpdate struct { | |||||||
| 	Remark         string `json:"remark"` | 	Remark         string `json:"remark"` | ||||||
| 	WebsiteGroupID uint   `json:"webSiteGroupID" validate:"required"` | 	WebsiteGroupID uint   `json:"webSiteGroupID" validate:"required"` | ||||||
| 	ExpireDate     string `json:"expireDate"` | 	ExpireDate     string `json:"expireDate"` | ||||||
|  | 	IPV6           bool   `json:"IPV6"` | ||||||
| } | } | ||||||
|  |  | ||||||
| type WebsiteDelete struct { | type WebsiteDelete struct { | ||||||
| @@ -101,15 +115,18 @@ type WebsiteDomainDelete struct { | |||||||
| } | } | ||||||
|  |  | ||||||
| type WebsiteHTTPSOp struct { | type WebsiteHTTPSOp struct { | ||||||
| 	WebsiteID    uint     `json:"websiteId" validate:"required"` | 	WebsiteID       uint     `json:"websiteId" validate:"required"` | ||||||
| 	Enable       bool     `json:"enable" validate:"required"` | 	Enable          bool     `json:"enable" validate:"required"` | ||||||
| 	WebsiteSSLID uint     `json:"websiteSSLId"` | 	WebsiteSSLID    uint     `json:"websiteSSLId"` | ||||||
| 	Type         string   `json:"type"  validate:"oneof=existed auto manual"` | 	Type            string   `json:"type"  validate:"oneof=existed auto manual"` | ||||||
| 	PrivateKey   string   `json:"privateKey"` | 	PrivateKey      string   `json:"privateKey"` | ||||||
| 	Certificate  string   `json:"certificate"` | 	Certificate     string   `json:"certificate"` | ||||||
| 	HttpConfig   string   `json:"HttpConfig"  validate:"oneof=HTTPSOnly HTTPAlso HTTPToHTTPS"` | 	PrivateKeyPath  string   `json:"privateKeyPath"` | ||||||
| 	SSLProtocol  []string `json:"SSLProtocol"` | 	CertificatePath string   `json:"certificatePath"` | ||||||
| 	Algorithm    string   `json:"algorithm"` | 	ImportType      string   `json:"importType"` | ||||||
|  | 	HttpConfig      string   `json:"httpConfig"  validate:"oneof=HTTPSOnly HTTPAlso HTTPToHTTPS"` | ||||||
|  | 	SSLProtocol     []string `json:"SSLProtocol"` | ||||||
|  | 	Algorithm       string   `json:"algorithm"` | ||||||
| } | } | ||||||
|  |  | ||||||
| type WebsiteNginxUpdate struct { | type WebsiteNginxUpdate struct { | ||||||
| @@ -126,3 +143,49 @@ type WebsiteLogReq struct { | |||||||
| type WebsiteDefaultUpdate struct { | type WebsiteDefaultUpdate struct { | ||||||
| 	ID uint `json:"id" validate:"required"` | 	ID uint `json:"id" validate:"required"` | ||||||
| } | } | ||||||
|  |  | ||||||
|  | type WebsitePHPConfigUpdate struct { | ||||||
|  | 	ID               uint              `json:"id" validate:"required"` | ||||||
|  | 	Params           map[string]string `json:"params"` | ||||||
|  | 	Scope            string            `json:"scope" validate:"required"` | ||||||
|  | 	DisableFunctions []string          `json:"disableFunctions"` | ||||||
|  | 	UploadMaxSize    string            `json:"uploadMaxSize"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type WebsitePHPFileUpdate struct { | ||||||
|  | 	ID      uint   `json:"id" validate:"required"` | ||||||
|  | 	Type    string `json:"type" validate:"required"` | ||||||
|  | 	Content string `json:"content" validate:"required"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type WebsiteUpdateDir struct { | ||||||
|  | 	ID      uint   `json:"id" validate:"required"` | ||||||
|  | 	SiteDir string `json:"siteDir" validate:"required"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type WebsiteUpdateDirPermission struct { | ||||||
|  | 	ID    uint   `json:"id" validate:"required"` | ||||||
|  | 	User  string `json:"user" validate:"required"` | ||||||
|  | 	Group string `json:"group" validate:"required"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type WebsiteProxyConfig struct { | ||||||
|  | 	ID        uint              `json:"id" validate:"required"` | ||||||
|  | 	Operate   string            `json:"operate" validate:"required"` | ||||||
|  | 	Enable    bool              `json:"enable"  validate:"required"` | ||||||
|  | 	Cache     bool              `json:"cache"  validate:"required"` | ||||||
|  | 	CacheTime int               `json:"cacheTime"  validate:"required"` | ||||||
|  | 	CacheUnit string            `json:"cacheUnit" validate:"required"` | ||||||
|  | 	Name      string            `json:"name" validate:"required"` | ||||||
|  | 	Modifier  string            `json:"modifier" validate:"required"` | ||||||
|  | 	Match     string            `json:"match" validate:"required"` | ||||||
|  | 	ProxyPass string            `json:"proxyPass" validate:"required"` | ||||||
|  | 	ProxyHost string            `json:"proxyHost" validate:"required"` | ||||||
|  | 	Content   string            `json:"content"` | ||||||
|  | 	FilePath  string            `json:"filePath"` | ||||||
|  | 	Replaces  map[string]string `json:"replaces"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type WebsiteProxyReq struct { | ||||||
|  | 	ID uint `json:"id" validate:"required"` | ||||||
|  | } | ||||||
|   | |||||||
| @@ -4,6 +4,7 @@ import "github.com/1Panel-dev/1Panel/backend/app/dto" | |||||||
|  |  | ||||||
| type WebsiteSSLSearch struct { | type WebsiteSSLSearch struct { | ||||||
| 	dto.PageInfo | 	dto.PageInfo | ||||||
|  | 	AcmeAccountID string `json:"acmeAccountID"` | ||||||
| } | } | ||||||
|  |  | ||||||
| type WebsiteSSLCreate struct { | type WebsiteSSLCreate struct { | ||||||
|   | |||||||
| @@ -1,8 +1,10 @@ | |||||||
| package response | package response | ||||||
|  |  | ||||||
| import ( | import ( | ||||||
| 	"github.com/1Panel-dev/1Panel/backend/app/model" | 	"github.com/1Panel-dev/1Panel/backend/app/dto/request" | ||||||
| 	"time" | 	"time" | ||||||
|  |  | ||||||
|  | 	"github.com/1Panel-dev/1Panel/backend/app/model" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| type AppRes struct { | type AppRes struct { | ||||||
| @@ -11,15 +13,15 @@ type AppRes struct { | |||||||
| } | } | ||||||
|  |  | ||||||
| type AppUpdateRes struct { | type AppUpdateRes struct { | ||||||
| 	Version      string `json:"version"` | 	CanUpdate            bool `json:"canUpdate"` | ||||||
| 	CanUpdate    bool   `json:"canUpdate"` | 	AppStoreLastModified int  `json:"appStoreLastModified"` | ||||||
| 	DownloadPath string `json:"downloadPath"` |  | ||||||
| } | } | ||||||
|  |  | ||||||
| type AppDTO struct { | type AppDTO struct { | ||||||
| 	model.App | 	model.App | ||||||
| 	Versions []string    `json:"versions"` | 	Installed bool        `json:"installed"` | ||||||
| 	Tags     []model.Tag `json:"tags"` | 	Versions  []string    `json:"versions"` | ||||||
|  | 	Tags      []model.Tag `json:"tags"` | ||||||
| } | } | ||||||
|  |  | ||||||
| type TagDTO struct { | type TagDTO struct { | ||||||
| @@ -43,6 +45,14 @@ type AppDetailDTO struct { | |||||||
| 	model.AppDetail | 	model.AppDetail | ||||||
| 	Enable bool        `json:"enable"` | 	Enable bool        `json:"enable"` | ||||||
| 	Params interface{} `json:"params"` | 	Params interface{} `json:"params"` | ||||||
|  | 	Image  string      `json:"image"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type IgnoredApp struct { | ||||||
|  | 	Icon     string `json:"icon"` | ||||||
|  | 	Name     string `json:"name"` | ||||||
|  | 	Version  string `json:"version"` | ||||||
|  | 	DetailID uint   `json:"detailID"` | ||||||
| } | } | ||||||
|  |  | ||||||
| type AppInstalledDTO struct { | type AppInstalledDTO struct { | ||||||
| @@ -52,6 +62,13 @@ type AppInstalledDTO struct { | |||||||
| 	AppName   string `json:"appName"` | 	AppName   string `json:"appName"` | ||||||
| 	Icon      string `json:"icon"` | 	Icon      string `json:"icon"` | ||||||
| 	CanUpdate bool   `json:"canUpdate"` | 	CanUpdate bool   `json:"canUpdate"` | ||||||
|  | 	Path      string `json:"path"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type DatabaseConn struct { | ||||||
|  | 	Password    string `json:"password"` | ||||||
|  | 	ServiceName string `json:"serviceName"` | ||||||
|  | 	Port        int64  `json:"port"` | ||||||
| } | } | ||||||
|  |  | ||||||
| type AppService struct { | type AppService struct { | ||||||
| @@ -70,4 +87,11 @@ type AppParam struct { | |||||||
| 	Type      string      `json:"type"` | 	Type      string      `json:"type"` | ||||||
| 	Values    interface{} `json:"values"` | 	Values    interface{} `json:"values"` | ||||||
| 	ShowValue string      `json:"showValue"` | 	ShowValue string      `json:"showValue"` | ||||||
|  | 	Required  bool        `json:"required"` | ||||||
|  | 	Multiple  bool        `json:"multiple"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type AppConfig struct { | ||||||
|  | 	Params []AppParam `json:"params"` | ||||||
|  | 	request.AppContainerConfig | ||||||
| } | } | ||||||
|   | |||||||
| @@ -1,5 +1,7 @@ | |||||||
| package response | package response | ||||||
|  |  | ||||||
|  | import "github.com/1Panel-dev/1Panel/backend/app/dto" | ||||||
|  |  | ||||||
| type NginxStatus struct { | type NginxStatus struct { | ||||||
| 	Active   string `json:"active"` | 	Active   string `json:"active"` | ||||||
| 	Accepts  string `json:"accepts"` | 	Accepts  string `json:"accepts"` | ||||||
| @@ -14,3 +16,21 @@ type NginxParam struct { | |||||||
| 	Name   string   `json:"name"` | 	Name   string   `json:"name"` | ||||||
| 	Params []string `json:"params"` | 	Params []string `json:"params"` | ||||||
| } | } | ||||||
|  |  | ||||||
|  | type NginxAuthRes struct { | ||||||
|  | 	Enable bool            `json:"enable"` | ||||||
|  | 	Items  []dto.NginxAuth `json:"items"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type NginxAntiLeechRes struct { | ||||||
|  | 	Enable      bool     `json:"enable"` | ||||||
|  | 	Extends     string   `json:"extends"` | ||||||
|  | 	Return      string   `json:"return"` | ||||||
|  | 	ServerNames []string `json:"serverNames"` | ||||||
|  | 	Cache       bool     `json:"cache"` | ||||||
|  | 	CacheTime   int      `json:"cacheTime"` | ||||||
|  | 	CacheUint   string   `json:"cacheUint"` | ||||||
|  | 	NoneRef     bool     `json:"noneRef"` | ||||||
|  | 	LogEnable   bool     `json:"logEnable"` | ||||||
|  | 	Blocked     bool     `json:"blocked"` | ||||||
|  | } | ||||||
|   | |||||||
							
								
								
									
										9
									
								
								backend/app/dto/response/runtime.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								backend/app/dto/response/runtime.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,9 @@ | |||||||
|  | package response | ||||||
|  |  | ||||||
|  | import "github.com/1Panel-dev/1Panel/backend/app/model" | ||||||
|  |  | ||||||
|  | type RuntimeRes struct { | ||||||
|  | 	model.Runtime | ||||||
|  | 	AppParams []AppParam `json:"appParams"` | ||||||
|  | 	AppID     uint       `json:"appId"` | ||||||
|  | } | ||||||
| @@ -10,6 +10,7 @@ type WebsiteDTO struct { | |||||||
| 	AccessLogPath string `json:"accessLogPath"` | 	AccessLogPath string `json:"accessLogPath"` | ||||||
| 	SitePath      string `json:"sitePath"` | 	SitePath      string `json:"sitePath"` | ||||||
| 	AppName       string `json:"appName"` | 	AppName       string `json:"appName"` | ||||||
|  | 	RuntimeName   string `json:"runtimeName"` | ||||||
| } | } | ||||||
|  |  | ||||||
| type WebsitePreInstallCheck struct { | type WebsitePreInstallCheck struct { | ||||||
| @@ -42,3 +43,13 @@ type WebsiteLog struct { | |||||||
| 	Enable  bool   `json:"enable"` | 	Enable  bool   `json:"enable"` | ||||||
| 	Content string `json:"content"` | 	Content string `json:"content"` | ||||||
| } | } | ||||||
|  |  | ||||||
|  | type PHPConfig struct { | ||||||
|  | 	Params           map[string]string `json:"params"` | ||||||
|  | 	DisableFunctions []string          `json:"disableFunctions"` | ||||||
|  | 	UploadMaxSize    string            `json:"uploadMaxSize"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type NginxRewriteRes struct { | ||||||
|  | 	Content string `json:"content"` | ||||||
|  | } | ||||||
|   | |||||||
| @@ -5,10 +5,13 @@ import "time" | |||||||
| type SettingInfo struct { | type SettingInfo struct { | ||||||
| 	UserName      string `json:"userName"` | 	UserName      string `json:"userName"` | ||||||
| 	Email         string `json:"email"` | 	Email         string `json:"email"` | ||||||
|  | 	SystemIP      string `json:"systemIP"` | ||||||
| 	SystemVersion string `json:"systemVersion"` | 	SystemVersion string `json:"systemVersion"` | ||||||
|  |  | ||||||
| 	SessionTimeout string `json:"sessionTimeout"` | 	SessionTimeout string `json:"sessionTimeout"` | ||||||
| 	LocalTime      string `json:"localTime"` | 	LocalTime      string `json:"localTime"` | ||||||
|  | 	TimeZone       string `json:"timeZone"` | ||||||
|  | 	NtpSite        string `json:"ntpSite"` | ||||||
|  |  | ||||||
| 	Port      string `json:"port"` | 	Port      string `json:"port"` | ||||||
| 	PanelName string `json:"panelName"` | 	PanelName string `json:"panelName"` | ||||||
| @@ -16,14 +19,20 @@ type SettingInfo struct { | |||||||
| 	Language  string `json:"language"` | 	Language  string `json:"language"` | ||||||
|  |  | ||||||
| 	ServerPort             string `json:"serverPort"` | 	ServerPort             string `json:"serverPort"` | ||||||
|  | 	SSL                    string `json:"ssl"` | ||||||
|  | 	SSLType                string `json:"sslType"` | ||||||
|  | 	BindDomain             string `json:"bindDomain"` | ||||||
|  | 	AllowIPs               string `json:"allowIPs"` | ||||||
| 	SecurityEntrance       string `json:"securityEntrance"` | 	SecurityEntrance       string `json:"securityEntrance"` | ||||||
| 	ExpirationDays         string `json:"expirationDays"` | 	ExpirationDays         string `json:"expirationDays"` | ||||||
| 	ExpirationTime         string `json:"expirationTime"` | 	ExpirationTime         string `json:"expirationTime"` | ||||||
| 	ComplexityVerification string `json:"complexityVerification"` | 	ComplexityVerification string `json:"complexityVerification"` | ||||||
| 	MFAStatus              string `json:"mfaStatus"` | 	MFAStatus              string `json:"mfaStatus"` | ||||||
| 	MFASecret              string `json:"mfaSecret"` | 	MFASecret              string `json:"mfaSecret"` | ||||||
|  | 	MFAInterval            string `json:"mfaInterval"` | ||||||
|  |  | ||||||
| 	MonitorStatus    string `json:"monitorStatus"` | 	MonitorStatus    string `json:"monitorStatus"` | ||||||
|  | 	MonitorInterval  string `json:"monitorInterval"` | ||||||
| 	MonitorStoreDays string `json:"monitorStoreDays"` | 	MonitorStoreDays string `json:"monitorStoreDays"` | ||||||
|  |  | ||||||
| 	MessageType string `json:"messageType"` | 	MessageType string `json:"messageType"` | ||||||
| @@ -31,7 +40,8 @@ type SettingInfo struct { | |||||||
| 	WeChatVars  string `json:"weChatVars"` | 	WeChatVars  string `json:"weChatVars"` | ||||||
| 	DingVars    string `json:"dingVars"` | 	DingVars    string `json:"dingVars"` | ||||||
|  |  | ||||||
| 	AppStoreVersion string `json:"appStoreVersion"` | 	AppStoreVersion      string `json:"appStoreVersion"` | ||||||
|  | 	AppStoreLastModified string `json:"appStoreLastModified"` | ||||||
| } | } | ||||||
|  |  | ||||||
| type SettingUpdate struct { | type SettingUpdate struct { | ||||||
| @@ -39,6 +49,23 @@ type SettingUpdate struct { | |||||||
| 	Value string `json:"value"` | 	Value string `json:"value"` | ||||||
| } | } | ||||||
|  |  | ||||||
|  | type SSLUpdate struct { | ||||||
|  | 	SSLType string `json:"sslType"` | ||||||
|  | 	Domain  string `json:"domain"` | ||||||
|  | 	SSL     string `json:"ssl" validate:"required,oneof=enable disable"` | ||||||
|  | 	Cert    string `json:"cert"` | ||||||
|  | 	Key     string `json:"key"` | ||||||
|  | 	SSLID   uint   `json:"sslID"` | ||||||
|  | } | ||||||
|  | type SSLInfo struct { | ||||||
|  | 	Domain   string `json:"domain"` | ||||||
|  | 	Timeout  string `json:"timeout"` | ||||||
|  | 	RootPath string `json:"rootPath"` | ||||||
|  | 	Cert     string `json:"cert"` | ||||||
|  | 	Key      string `json:"key"` | ||||||
|  | 	SSLID    uint   `json:"sslID"` | ||||||
|  | } | ||||||
|  |  | ||||||
| type PasswordUpdate struct { | type PasswordUpdate struct { | ||||||
| 	OldPassword string `json:"oldPassword" validate:"required"` | 	OldPassword string `json:"oldPassword" validate:"required"` | ||||||
| 	NewPassword string `json:"newPassword" validate:"required"` | 	NewPassword string `json:"newPassword" validate:"required"` | ||||||
| @@ -49,8 +76,8 @@ type PortUpdate struct { | |||||||
| } | } | ||||||
|  |  | ||||||
| type SnapshotCreate struct { | type SnapshotCreate struct { | ||||||
| 	From        string `json:"from" validate:"required,oneof=OSS S3 SFTP MINIO"` | 	From        string `json:"from" validate:"required,oneof=OSS S3 SFTP MINIO COS KODO OneDrive"` | ||||||
| 	Description string `json:"description"` | 	Description string `json:"description" validate:"max=256"` | ||||||
| } | } | ||||||
| type SnapshotRecover struct { | type SnapshotRecover struct { | ||||||
| 	IsNew      bool `json:"isNew"` | 	IsNew      bool `json:"isNew"` | ||||||
| @@ -60,12 +87,12 @@ type SnapshotRecover struct { | |||||||
| type SnapshotImport struct { | type SnapshotImport struct { | ||||||
| 	From        string   `json:"from"` | 	From        string   `json:"from"` | ||||||
| 	Names       []string `json:"names"` | 	Names       []string `json:"names"` | ||||||
| 	Description string   `json:"description"` | 	Description string   `json:"description" validate:"max=256"` | ||||||
| } | } | ||||||
| type SnapshotInfo struct { | type SnapshotInfo struct { | ||||||
| 	ID          uint      `json:"id"` | 	ID          uint      `json:"id"` | ||||||
| 	Name        string    `json:"name"` | 	Name        string    `json:"name"` | ||||||
| 	Description string    `json:"description"` | 	Description string    `json:"description" validate:"max=256"` | ||||||
| 	From        string    `json:"from"` | 	From        string    `json:"from"` | ||||||
| 	Status      string    `json:"status"` | 	Status      string    `json:"status"` | ||||||
| 	Message     string    `json:"message"` | 	Message     string    `json:"message"` | ||||||
| @@ -87,6 +114,10 @@ type UpgradeInfo struct { | |||||||
| 	ReleaseNote   string `json:"releaseNote"` | 	ReleaseNote   string `json:"releaseNote"` | ||||||
| } | } | ||||||
|  |  | ||||||
|  | type SyncTime struct { | ||||||
|  | 	NtpSite string `json:"ntpSite"` | ||||||
|  | } | ||||||
|  |  | ||||||
| type Upgrade struct { | type Upgrade struct { | ||||||
| 	Version string `json:"version"` | 	Version string `json:"version"` | ||||||
| } | } | ||||||
|   | |||||||
							
								
								
									
										49
									
								
								backend/app/dto/ssh.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										49
									
								
								backend/app/dto/ssh.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,49 @@ | |||||||
|  | package dto | ||||||
|  |  | ||||||
|  | import "time" | ||||||
|  |  | ||||||
|  | type SSHInfo struct { | ||||||
|  | 	Status                 string `json:"status"` | ||||||
|  | 	Message                string `json:"message"` | ||||||
|  | 	Port                   string `json:"port"` | ||||||
|  | 	ListenAddress          string `json:"listenAddress"` | ||||||
|  | 	PasswordAuthentication string `json:"passwordAuthentication"` | ||||||
|  | 	PubkeyAuthentication   string `json:"pubkeyAuthentication"` | ||||||
|  | 	PermitRootLogin        string `json:"permitRootLogin"` | ||||||
|  | 	UseDNS                 string `json:"useDNS"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type GenerateSSH struct { | ||||||
|  | 	EncryptionMode string `json:"encryptionMode" validate:"required,oneof=rsa ed25519 ecdsa dsa"` | ||||||
|  | 	Password       string `json:"password"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type GenerateLoad struct { | ||||||
|  | 	EncryptionMode string `json:"encryptionMode" validate:"required,oneof=rsa ed25519 ecdsa dsa"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type SSHConf struct { | ||||||
|  | 	File string `json:"file"` | ||||||
|  | } | ||||||
|  | type SearchSSHLog struct { | ||||||
|  | 	PageInfo | ||||||
|  | 	Info   string `json:"info"` | ||||||
|  | 	Status string `json:"Status" validate:"required,oneof=Success Failed All"` | ||||||
|  | } | ||||||
|  | type SSHLog struct { | ||||||
|  | 	Logs            []SSHHistory `json:"logs"` | ||||||
|  | 	TotalCount      int          `json:"totalCount"` | ||||||
|  | 	SuccessfulCount int          `json:"successfulCount"` | ||||||
|  | 	FailedCount     int          `json:"failedCount"` | ||||||
|  | } | ||||||
|  | type SSHHistory struct { | ||||||
|  | 	Date     time.Time `json:"date"` | ||||||
|  | 	DateStr  string    `json:"dateStr"` | ||||||
|  | 	Area     string    `json:"area"` | ||||||
|  | 	User     string    `json:"user"` | ||||||
|  | 	AuthMode string    `json:"authMode"` | ||||||
|  | 	Address  string    `json:"address"` | ||||||
|  | 	Port     string    `json:"port"` | ||||||
|  | 	Status   string    `json:"status"` | ||||||
|  | 	Message  string    `json:"message"` | ||||||
|  | } | ||||||
| @@ -2,21 +2,25 @@ package model | |||||||
|  |  | ||||||
| type App struct { | type App struct { | ||||||
| 	BaseModel | 	BaseModel | ||||||
| 	Name               string      `json:"name" gorm:"type:varchar(64);not null"` | 	Name               string `json:"name" gorm:"type:varchar(64);not null"` | ||||||
| 	Key                string      `json:"key" gorm:"type:varchar(64);not null;uniqueIndex"` | 	Key                string `json:"key" gorm:"type:varchar(64);not null;"` | ||||||
| 	ShortDescZh        string      `json:"shortDescZh" gorm:"type:longtext;"` | 	ShortDescZh        string `json:"shortDescZh" yaml:"shortDescZh" gorm:"type:longtext;"` | ||||||
| 	ShortDescEn        string      `json:"shortDescEn" gorm:"type:longtext;"` | 	ShortDescEn        string `json:"shortDescEn" yaml:"shortDescEn" gorm:"type:longtext;"` | ||||||
| 	Icon               string      `json:"icon" gorm:"type:longtext;"` | 	Icon               string `json:"icon" gorm:"type:longtext;"` | ||||||
| 	Type               string      `json:"type" gorm:"type:varchar(64);not null"` | 	Type               string `json:"type" gorm:"type:varchar(64);not null"` | ||||||
| 	Status             string      `json:"status" gorm:"type:varchar(64);not null"` | 	Status             string `json:"status" gorm:"type:varchar(64);not null"` | ||||||
| 	Required           string      `json:"required" gorm:"type:varchar(64);not null"` | 	Required           string `json:"required" gorm:"type:varchar(64);"` | ||||||
| 	CrossVersionUpdate bool        `json:"crossVersionUpdate"` | 	CrossVersionUpdate bool   `json:"crossVersionUpdate"` | ||||||
| 	Limit              int         `json:"limit" gorm:"type:Integer;not null"` | 	Limit              int    `json:"limit" gorm:"type:Integer;not null"` | ||||||
| 	Website            string      `json:"website" gorm:"type:varchar(64);not null"` | 	Website            string `json:"website" gorm:"type:varchar(64);not null"` | ||||||
| 	Github             string      `json:"github" gorm:"type:varchar(64);not null"` | 	Github             string `json:"github" gorm:"type:varchar(64);not null"` | ||||||
| 	Document           string      `json:"document" gorm:"type:varchar(64);not null"` | 	Document           string `json:"document" gorm:"type:varchar(64);not null"` | ||||||
| 	Recommend          int         `json:"recommend" gorm:"type:Integer;not null"` | 	Recommend          int    `json:"recommend" gorm:"type:Integer;not null"` | ||||||
| 	Details            []AppDetail `json:"-" gorm:"-:migration"` | 	Resource           string `json:"resource" gorm:"type:varchar;not null;default:remote"` | ||||||
| 	TagsKey            []string    `json:"-" gorm:"-"` | 	ReadMe             string `json:"readMe" gorm:"type:varchar;"` | ||||||
| 	AppTags            []AppTag    `json:"-" gorm:"-:migration"` | 	LastModified       int    `json:"lastModified" gorm:"type:Integer;"` | ||||||
|  |  | ||||||
|  | 	Details []AppDetail `json:"-" gorm:"-:migration"` | ||||||
|  | 	TagsKey []string    `json:"tags" yaml:"tags" gorm:"-"` | ||||||
|  | 	AppTags []AppTag    `json:"-" gorm:"-:migration"` | ||||||
| } | } | ||||||
|   | |||||||
| @@ -2,11 +2,15 @@ package model | |||||||
|  |  | ||||||
| type AppDetail struct { | type AppDetail struct { | ||||||
| 	BaseModel | 	BaseModel | ||||||
| 	AppId         uint   `json:"appId" gorm:"type:integer;not null"` | 	AppId               uint   `json:"appId" gorm:"type:integer;not null"` | ||||||
| 	Version       string `json:"version" gorm:"type:varchar(64);not null"` | 	Version             string `json:"version" gorm:"type:varchar(64);not null"` | ||||||
| 	Params        string `json:"-" gorm:"type:longtext;"` | 	Params              string `json:"-" gorm:"type:longtext;"` | ||||||
| 	DockerCompose string `json:"-"  gorm:"type:longtext;not null"` | 	DockerCompose       string `json:"dockerCompose"  gorm:"type:longtext;"` | ||||||
| 	Readme        string `json:"readme"  gorm:"type:longtext;"` | 	Status              string `json:"status" gorm:"type:varchar(64);not null"` | ||||||
| 	Status        string `json:"status" gorm:"type:varchar(64);not null"` | 	LastVersion         string `json:"lastVersion" gorm:"type:varchar(64);"` | ||||||
| 	LastVersion   string `json:"lastVersion" gorm:"type:varchar(64);"` | 	LastModified        int    `json:"lastModified" gorm:"type:integer;"` | ||||||
|  | 	DownloadUrl         string `json:"downloadUrl"  gorm:"type:varchar;"` | ||||||
|  | 	DownloadCallBackUrl string `json:"downloadCallBackUrl" gorm:"type:longtext;"` | ||||||
|  | 	Update              bool   `json:"update"` | ||||||
|  | 	IgnoreUpgrade       bool   `json:"ignoreUpgrade"` | ||||||
| } | } | ||||||
|   | |||||||
| @@ -2,6 +2,7 @@ package model | |||||||
|  |  | ||||||
| import ( | import ( | ||||||
| 	"path" | 	"path" | ||||||
|  | 	"strings" | ||||||
|  |  | ||||||
| 	"github.com/1Panel-dev/1Panel/backend/constant" | 	"github.com/1Panel-dev/1Panel/backend/constant" | ||||||
| ) | ) | ||||||
| @@ -26,9 +27,21 @@ type AppInstall struct { | |||||||
| } | } | ||||||
|  |  | ||||||
| func (i *AppInstall) GetPath() string { | func (i *AppInstall) GetPath() string { | ||||||
| 	return path.Join(constant.AppInstallDir, i.App.Key, i.Name) | 	return path.Join(i.getAppPath(), i.Name) | ||||||
| } | } | ||||||
|  |  | ||||||
| func (i *AppInstall) GetComposePath() string { | func (i *AppInstall) GetComposePath() string { | ||||||
| 	return path.Join(constant.AppInstallDir, i.App.Key, i.Name, "docker-compose.yml") | 	return path.Join(i.getAppPath(), i.Name, "docker-compose.yml") | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (i *AppInstall) GetEnvPath() string { | ||||||
|  | 	return path.Join(i.getAppPath(), i.Name, ".env") | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (i *AppInstall) getAppPath() string { | ||||||
|  | 	if i.App.Resource == constant.AppResourceLocal { | ||||||
|  | 		return path.Join(constant.LocalAppInstallDir, strings.TrimPrefix(i.App.Key, constant.AppResourceLocal)) | ||||||
|  | 	} else { | ||||||
|  | 		return path.Join(constant.AppInstallDir, i.App.Key) | ||||||
|  | 	} | ||||||
| } | } | ||||||
|   | |||||||
| @@ -6,6 +6,7 @@ type BackupAccount struct { | |||||||
| 	Bucket     string `gorm:"type:varchar(256)" json:"bucket"` | 	Bucket     string `gorm:"type:varchar(256)" json:"bucket"` | ||||||
| 	AccessKey  string `gorm:"type:varchar(256)" json:"accessKey"` | 	AccessKey  string `gorm:"type:varchar(256)" json:"accessKey"` | ||||||
| 	Credential string `gorm:"type:varchar(256)" json:"credential"` | 	Credential string `gorm:"type:varchar(256)" json:"credential"` | ||||||
|  | 	BackupPath string `gorm:"type:varchar(256)" json:"backupPath"` | ||||||
| 	Vars       string `gorm:"type:longText" json:"vars"` | 	Vars       string `gorm:"type:longText" json:"vars"` | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -13,7 +13,9 @@ type Cronjob struct { | |||||||
| 	Day      uint64 `gorm:"type:decimal" json:"day"` | 	Day      uint64 `gorm:"type:decimal" json:"day"` | ||||||
| 	Hour     uint64 `gorm:"type:decimal" json:"hour"` | 	Hour     uint64 `gorm:"type:decimal" json:"hour"` | ||||||
| 	Minute   uint64 `gorm:"type:decimal" json:"minute"` | 	Minute   uint64 `gorm:"type:decimal" json:"minute"` | ||||||
|  | 	Second   uint64 `gorm:"type:decimal" json:"second"` | ||||||
|  |  | ||||||
|  | 	ContainerName  string `gorm:"type:varchar(64)" json:"containerName"` | ||||||
| 	Script         string `gorm:"longtext" json:"script"` | 	Script         string `gorm:"longtext" json:"script"` | ||||||
| 	Website        string `gorm:"type:varchar(64)" json:"website"` | 	Website        string `gorm:"type:varchar(64)" json:"website"` | ||||||
| 	DBName         string `gorm:"type:varchar(64)" json:"dbName"` | 	DBName         string `gorm:"type:varchar(64)" json:"dbName"` | ||||||
|   | |||||||
| @@ -2,14 +2,17 @@ package model | |||||||
|  |  | ||||||
| type Host struct { | type Host struct { | ||||||
| 	BaseModel | 	BaseModel | ||||||
| 	GroupID    uint   `gorm:"type:decimal;not null" json:"group_id"` |  | ||||||
| 	Name       string `gorm:"type:varchar(64);not null" json:"name"` | 	GroupID          uint   `gorm:"type:decimal;not null" json:"group_id"` | ||||||
| 	Addr       string `gorm:"type:varchar(16);not null" json:"addr"` | 	Name             string `gorm:"type:varchar(64);not null" json:"name"` | ||||||
| 	Port       int    `gorm:"type:decimal;not null" json:"port"` | 	Addr             string `gorm:"type:varchar(16);not null" json:"addr"` | ||||||
| 	User       string `gorm:"type:varchar(64);not null" json:"user"` | 	Port             int    `gorm:"type:decimal;not null" json:"port"` | ||||||
| 	AuthMode   string `gorm:"type:varchar(16);not null" json:"authMode"` | 	User             string `gorm:"type:varchar(64);not null" json:"user"` | ||||||
| 	Password   string `gorm:"type:varchar(64)" json:"password"` | 	AuthMode         string `gorm:"type:varchar(16);not null" json:"authMode"` | ||||||
| 	PrivateKey string `gorm:"type:varchar(256)" json:"privateKey"` | 	Password         string `gorm:"type:varchar(64)" json:"password"` | ||||||
|  | 	PrivateKey       string `gorm:"type:varchar(256)" json:"privateKey"` | ||||||
|  | 	PassPhrase       string `gorm:"type:varchar(256)" json:"passPhrase"` | ||||||
|  | 	RememberPassword bool   `json:"rememberPassword"` | ||||||
|  |  | ||||||
| 	Description string `gorm:"type:varchar(256)" json:"description"` | 	Description string `gorm:"type:varchar(256)" json:"description"` | ||||||
| } | } | ||||||
|   | |||||||
							
								
								
									
										17
									
								
								backend/app/model/runtime.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								backend/app/model/runtime.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,17 @@ | |||||||
|  | package model | ||||||
|  |  | ||||||
|  | type Runtime struct { | ||||||
|  | 	BaseModel | ||||||
|  | 	Name          string `gorm:"type:varchar;not null" json:"name"` | ||||||
|  | 	AppDetailID   uint   `gorm:"type:integer" json:"appDetailId"` | ||||||
|  | 	Image         string `gorm:"type:varchar" json:"image"` | ||||||
|  | 	WorkDir       string `gorm:"type:varchar" json:"workDir"` | ||||||
|  | 	DockerCompose string `gorm:"type:varchar" json:"dockerCompose"` | ||||||
|  | 	Env           string `gorm:"type:varchar" json:"env"` | ||||||
|  | 	Params        string `gorm:"type:varchar" json:"params"` | ||||||
|  | 	Version       string `gorm:"type:varchar;not null" json:"version"` | ||||||
|  | 	Type          string `gorm:"type:varchar;not null" json:"type"` | ||||||
|  | 	Status        string `gorm:"type:varchar;not null" json:"status"` | ||||||
|  | 	Resource      string `gorm:"type:varchar;not null" json:"resource"` | ||||||
|  | 	Message       string `gorm:"type:longtext;" json:"message"` | ||||||
|  | } | ||||||
| @@ -4,23 +4,34 @@ import "time" | |||||||
|  |  | ||||||
| type Website struct { | type Website struct { | ||||||
| 	BaseModel | 	BaseModel | ||||||
| 	Protocol       string          `gorm:"type:varchar(64);not null" json:"protocol"` | 	Protocol      string    `gorm:"type:varchar;not null" json:"protocol"` | ||||||
| 	PrimaryDomain  string          `gorm:"type:varchar(128);not null" json:"primaryDomain"` | 	PrimaryDomain string    `gorm:"type:varchar;not null" json:"primaryDomain"` | ||||||
| 	Type           string          `gorm:"type:varchar(64);not null" json:"type"` | 	Type          string    `gorm:"type:varchar;not null" json:"type"` | ||||||
| 	Alias          string          `gorm:"type:varchar(128);not null" json:"alias"` | 	Alias         string    `gorm:"type:varchar;not null" json:"alias"` | ||||||
| 	Remark         string          `gorm:"type:longtext;" json:"remark"` | 	Remark        string    `gorm:"type:longtext;" json:"remark"` | ||||||
| 	Status         string          `gorm:"type:varchar(64);not null" json:"status"` | 	Status        string    `gorm:"type:varchar;not null" json:"status"` | ||||||
| 	HttpConfig     string          `gorm:"type:varchar(64);not null" json:"httpConfig"` | 	HttpConfig    string    `gorm:"type:varchar;not null" json:"httpConfig"` | ||||||
| 	ExpireDate     time.Time       `json:"expireDate"` | 	ExpireDate    time.Time `json:"expireDate"` | ||||||
| 	AppInstallID   uint            `gorm:"type:integer" json:"appInstallId"` |  | ||||||
| 	WebsiteGroupID uint            `gorm:"type:integer" json:"webSiteGroupId"` | 	Proxy         string `gorm:"type:varchar;" json:"proxy"` | ||||||
| 	WebsiteSSLID   uint            `gorm:"type:integer" json:"webSiteSSLId"` | 	ProxyType     string `gorm:"type:varchar;" json:"proxyType"` | ||||||
| 	Proxy          string          `gorm:"type:varchar(128);not null" json:"proxy"` | 	SiteDir       string `gorm:"type:varchar;" json:"siteDir"` | ||||||
| 	ErrorLog       bool            `json:"errorLog"` | 	ErrorLog      bool   `json:"errorLog"` | ||||||
| 	AccessLog      bool            `json:"accessLog"` | 	AccessLog     bool   `json:"accessLog"` | ||||||
| 	DefaultServer  bool            `json:"defaultServer"` | 	DefaultServer bool   `json:"defaultServer"` | ||||||
| 	Domains        []WebsiteDomain `json:"domains" gorm:"-:migration"` | 	IPV6          bool   `json:"IPV6"` | ||||||
| 	WebsiteSSL     WebsiteSSL      `json:"webSiteSSL" gorm:"-:migration"` | 	Rewrite       string `gorm:"type:varchar" json:"rewrite"` | ||||||
|  |  | ||||||
|  | 	WebsiteGroupID uint `gorm:"type:integer" json:"webSiteGroupId"` | ||||||
|  | 	WebsiteSSLID   uint `gorm:"type:integer" json:"webSiteSSLId"` | ||||||
|  | 	RuntimeID      uint `gorm:"type:integer" json:"runtimeID"` | ||||||
|  | 	AppInstallID   uint `gorm:"type:integer" json:"appInstallId"` | ||||||
|  |  | ||||||
|  | 	User  string `gorm:"type:varchar;" json:"user"` | ||||||
|  | 	Group string `gorm:"type:varchar;" json:"group"` | ||||||
|  |  | ||||||
|  | 	Domains    []WebsiteDomain `json:"domains" gorm:"-:migration"` | ||||||
|  | 	WebsiteSSL WebsiteSSL      `json:"webSiteSSL" gorm:"-:migration"` | ||||||
| } | } | ||||||
|  |  | ||||||
| func (w Website) TableName() string { | func (w Website) TableName() string { | ||||||
|   | |||||||
| @@ -11,6 +11,26 @@ import ( | |||||||
| type AppRepo struct { | type AppRepo struct { | ||||||
| } | } | ||||||
|  |  | ||||||
|  | type IAppRepo interface { | ||||||
|  | 	WithKey(key string) DBOption | ||||||
|  | 	WithType(typeStr string) DBOption | ||||||
|  | 	OrderByRecommend() DBOption | ||||||
|  | 	GetRecommend() DBOption | ||||||
|  | 	WithResource(resource string) DBOption | ||||||
|  | 	Page(page, size int, opts ...DBOption) (int64, []model.App, error) | ||||||
|  | 	GetFirst(opts ...DBOption) (model.App, error) | ||||||
|  | 	GetBy(opts ...DBOption) ([]model.App, error) | ||||||
|  | 	BatchCreate(ctx context.Context, apps []model.App) error | ||||||
|  | 	GetByKey(ctx context.Context, key string) (model.App, error) | ||||||
|  | 	Create(ctx context.Context, app *model.App) error | ||||||
|  | 	Save(ctx context.Context, app *model.App) error | ||||||
|  | 	BatchDelete(ctx context.Context, apps []model.App) error | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func NewIAppRepo() IAppRepo { | ||||||
|  | 	return &AppRepo{} | ||||||
|  | } | ||||||
|  |  | ||||||
| func (a AppRepo) WithKey(key string) DBOption { | func (a AppRepo) WithKey(key string) DBOption { | ||||||
| 	return func(db *gorm.DB) *gorm.DB { | 	return func(db *gorm.DB) *gorm.DB { | ||||||
| 		return db.Where("key = ?", key) | 		return db.Where("key = ?", key) | ||||||
| @@ -35,12 +55,18 @@ func (a AppRepo) GetRecommend() DBOption { | |||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
|  | func (a AppRepo) WithResource(resource string) DBOption { | ||||||
|  | 	return func(g *gorm.DB) *gorm.DB { | ||||||
|  | 		return g.Where("resource = ?", resource) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
| func (a AppRepo) Page(page, size int, opts ...DBOption) (int64, []model.App, error) { | func (a AppRepo) Page(page, size int, opts ...DBOption) (int64, []model.App, error) { | ||||||
| 	var apps []model.App | 	var apps []model.App | ||||||
| 	db := getDb(opts...).Model(&model.App{}) | 	db := getDb(opts...).Model(&model.App{}) | ||||||
| 	count := int64(0) | 	count := int64(0) | ||||||
| 	db = db.Count(&count) | 	db = db.Count(&count) | ||||||
| 	err := db.Limit(size).Offset(size * (page - 1)).Preload("AppTags").Find(&apps).Error | 	err := db.Debug().Limit(size).Offset(size * (page - 1)).Preload("AppTags").Find(&apps).Error | ||||||
| 	return count, apps, err | 	return count, apps, err | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -81,3 +107,7 @@ func (a AppRepo) Create(ctx context.Context, app *model.App) error { | |||||||
| func (a AppRepo) Save(ctx context.Context, app *model.App) error { | func (a AppRepo) Save(ctx context.Context, app *model.App) error { | ||||||
| 	return getTx(ctx).Omit(clause.Associations).Save(app).Error | 	return getTx(ctx).Omit(clause.Associations).Save(app).Error | ||||||
| } | } | ||||||
|  |  | ||||||
|  | func (a AppRepo) BatchDelete(ctx context.Context, apps []model.App) error { | ||||||
|  | 	return getTx(ctx).Omit(clause.Associations).Delete(&apps).Error | ||||||
|  | } | ||||||
|   | |||||||
| @@ -4,22 +4,47 @@ import ( | |||||||
| 	"context" | 	"context" | ||||||
| 	"github.com/1Panel-dev/1Panel/backend/app/model" | 	"github.com/1Panel-dev/1Panel/backend/app/model" | ||||||
| 	"gorm.io/gorm" | 	"gorm.io/gorm" | ||||||
|  | 	"gorm.io/gorm/clause" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| type AppDetailRepo struct { | type AppDetailRepo struct { | ||||||
| } | } | ||||||
|  |  | ||||||
|  | type IAppDetailRepo interface { | ||||||
|  | 	WithVersion(version string) DBOption | ||||||
|  | 	WithAppId(id uint) DBOption | ||||||
|  | 	WithIgnored() DBOption | ||||||
|  | 	GetFirst(opts ...DBOption) (model.AppDetail, error) | ||||||
|  | 	Update(ctx context.Context, detail model.AppDetail) error | ||||||
|  | 	BatchCreate(ctx context.Context, details []model.AppDetail) error | ||||||
|  | 	DeleteByAppIds(ctx context.Context, appIds []uint) error | ||||||
|  | 	GetBy(opts ...DBOption) ([]model.AppDetail, error) | ||||||
|  | 	BatchUpdateBy(maps map[string]interface{}, opts ...DBOption) error | ||||||
|  | 	BatchDelete(ctx context.Context, appDetails []model.AppDetail) error | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func NewIAppDetailRepo() IAppDetailRepo { | ||||||
|  | 	return &AppDetailRepo{} | ||||||
|  | } | ||||||
|  |  | ||||||
| func (a AppDetailRepo) WithVersion(version string) DBOption { | func (a AppDetailRepo) WithVersion(version string) DBOption { | ||||||
| 	return func(g *gorm.DB) *gorm.DB { | 	return func(g *gorm.DB) *gorm.DB { | ||||||
| 		return g.Where("version = ?", version) | 		return g.Where("version = ?", version) | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| func (a AppDetailRepo) WithAppId(id uint) DBOption { | func (a AppDetailRepo) WithAppId(id uint) DBOption { | ||||||
| 	return func(g *gorm.DB) *gorm.DB { | 	return func(g *gorm.DB) *gorm.DB { | ||||||
| 		return g.Where("app_id = ?", id) | 		return g.Where("app_id = ?", id) | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
|  | func (a AppDetailRepo) WithIgnored() DBOption { | ||||||
|  | 	return func(g *gorm.DB) *gorm.DB { | ||||||
|  | 		return g.Where("ignore_upgrade = 1") | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
| func (a AppDetailRepo) GetFirst(opts ...DBOption) (model.AppDetail, error) { | func (a AppDetailRepo) GetFirst(opts ...DBOption) (model.AppDetail, error) { | ||||||
| 	var detail model.AppDetail | 	var detail model.AppDetail | ||||||
| 	err := getDb(opts...).Model(&model.AppDetail{}).Find(&detail).Error | 	err := getDb(opts...).Model(&model.AppDetail{}).Find(&detail).Error | ||||||
| @@ -51,3 +76,7 @@ func (a AppDetailRepo) BatchUpdateBy(maps map[string]interface{}, opts ...DBOpti | |||||||
| 	} | 	} | ||||||
| 	return db.Updates(&maps).Error | 	return db.Updates(&maps).Error | ||||||
| } | } | ||||||
|  |  | ||||||
|  | func (a AppDetailRepo) BatchDelete(ctx context.Context, appDetails []model.AppDetail) error { | ||||||
|  | 	return getTx(ctx).Omit(clause.Associations).Delete(&appDetails).Error | ||||||
|  | } | ||||||
|   | |||||||
| @@ -3,6 +3,7 @@ package repo | |||||||
| import ( | import ( | ||||||
| 	"context" | 	"context" | ||||||
| 	"encoding/json" | 	"encoding/json" | ||||||
|  | 	"gorm.io/gorm/clause" | ||||||
|  |  | ||||||
| 	"github.com/1Panel-dev/1Panel/backend/app/model" | 	"github.com/1Panel-dev/1Panel/backend/app/model" | ||||||
| 	"github.com/1Panel-dev/1Panel/backend/global" | 	"github.com/1Panel-dev/1Panel/backend/global" | ||||||
| @@ -11,6 +12,33 @@ import ( | |||||||
|  |  | ||||||
| type AppInstallRepo struct{} | type AppInstallRepo struct{} | ||||||
|  |  | ||||||
|  | type IAppInstallRepo interface { | ||||||
|  | 	WithDetailIdsIn(detailIds []uint) DBOption | ||||||
|  | 	WithDetailIdNotIn(detailIds []uint) DBOption | ||||||
|  | 	WithAppId(appId uint) DBOption | ||||||
|  | 	WithAppIdsIn(appIds []uint) DBOption | ||||||
|  | 	WithStatus(status string) DBOption | ||||||
|  | 	WithServiceName(serviceName string) DBOption | ||||||
|  | 	WithContainerName(containerName string) DBOption | ||||||
|  | 	WithPort(port int) DBOption | ||||||
|  | 	WithIdNotInWebsite() DBOption | ||||||
|  | 	WithIDNotIs(id uint) DBOption | ||||||
|  | 	ListBy(opts ...DBOption) ([]model.AppInstall, error) | ||||||
|  | 	GetFirst(opts ...DBOption) (model.AppInstall, error) | ||||||
|  | 	Create(ctx context.Context, install *model.AppInstall) error | ||||||
|  | 	Save(ctx context.Context, install *model.AppInstall) error | ||||||
|  | 	DeleteBy(opts ...DBOption) error | ||||||
|  | 	Delete(ctx context.Context, install model.AppInstall) error | ||||||
|  | 	Page(page, size int, opts ...DBOption) (int64, []model.AppInstall, error) | ||||||
|  | 	BatchUpdateBy(maps map[string]interface{}, opts ...DBOption) error | ||||||
|  | 	LoadBaseInfo(key string, name string) (*RootInfo, error) | ||||||
|  | 	GetFirstByCtx(ctx context.Context, opts ...DBOption) (model.AppInstall, error) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func NewIAppInstallRepo() IAppInstallRepo { | ||||||
|  | 	return &AppInstallRepo{} | ||||||
|  | } | ||||||
|  |  | ||||||
| func (a *AppInstallRepo) WithDetailIdsIn(detailIds []uint) DBOption { | func (a *AppInstallRepo) WithDetailIdsIn(detailIds []uint) DBOption { | ||||||
| 	return func(g *gorm.DB) *gorm.DB { | 	return func(g *gorm.DB) *gorm.DB { | ||||||
| 		return g.Where("app_detail_id in (?)", detailIds) | 		return g.Where("app_detail_id in (?)", detailIds) | ||||||
| @@ -29,6 +57,12 @@ func (a *AppInstallRepo) WithAppId(appId uint) DBOption { | |||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
|  | func (a *AppInstallRepo) WithIDNotIs(id uint) DBOption { | ||||||
|  | 	return func(g *gorm.DB) *gorm.DB { | ||||||
|  | 		return g.Where("id != ?", id) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
| func (a *AppInstallRepo) WithAppIdsIn(appIds []uint) DBOption { | func (a *AppInstallRepo) WithAppIdsIn(appIds []uint) DBOption { | ||||||
| 	return func(g *gorm.DB) *gorm.DB { | 	return func(g *gorm.DB) *gorm.DB { | ||||||
| 		return g.Where("app_id in (?)", appIds) | 		return g.Where("app_id in (?)", appIds) | ||||||
| @@ -47,6 +81,12 @@ func (a *AppInstallRepo) WithServiceName(serviceName string) DBOption { | |||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
|  | func (a *AppInstallRepo) WithContainerName(containerName string) DBOption { | ||||||
|  | 	return func(db *gorm.DB) *gorm.DB { | ||||||
|  | 		return db.Where("container_name = ?", containerName) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
| func (a *AppInstallRepo) WithPort(port int) DBOption { | func (a *AppInstallRepo) WithPort(port int) DBOption { | ||||||
| 	return func(db *gorm.DB) *gorm.DB { | 	return func(db *gorm.DB) *gorm.DB { | ||||||
| 		return db.Where("https_port = ? or  http_port = ?", port, port) | 		return db.Where("https_port = ? or  http_port = ?", port, port) | ||||||
| @@ -73,13 +113,20 @@ func (a *AppInstallRepo) GetFirst(opts ...DBOption) (model.AppInstall, error) { | |||||||
| 	return install, err | 	return install, err | ||||||
| } | } | ||||||
|  |  | ||||||
| func (a *AppInstallRepo) Create(ctx context.Context, install *model.AppInstall) error { | func (a *AppInstallRepo) GetFirstByCtx(ctx context.Context, opts ...DBOption) (model.AppInstall, error) { | ||||||
| 	db := getTx(ctx).Model(&model.AppInstall{}) | 	var install model.AppInstall | ||||||
| 	return db.Create(&install).Error | 	db := getTx(ctx, opts...).Model(&model.AppInstall{}) | ||||||
|  | 	err := db.Preload("App").First(&install).Error | ||||||
|  | 	return install, err | ||||||
| } | } | ||||||
|  |  | ||||||
| func (a *AppInstallRepo) Save(install *model.AppInstall) error { | func (a *AppInstallRepo) Create(ctx context.Context, install *model.AppInstall) error { | ||||||
| 	return getDb().Save(&install).Error | 	db := getTx(ctx).Model(&model.AppInstall{}) | ||||||
|  | 	return db.Omit(clause.Associations).Create(&install).Error | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (a *AppInstallRepo) Save(ctx context.Context, install *model.AppInstall) error { | ||||||
|  | 	return getTx(ctx).Omit(clause.Associations).Save(&install).Error | ||||||
| } | } | ||||||
|  |  | ||||||
| func (a *AppInstallRepo) DeleteBy(opts ...DBOption) error { | func (a *AppInstallRepo) DeleteBy(opts ...DBOption) error { | ||||||
| @@ -112,9 +159,11 @@ type RootInfo struct { | |||||||
| 	ID            uint   `json:"id"` | 	ID            uint   `json:"id"` | ||||||
| 	Name          string `json:"name"` | 	Name          string `json:"name"` | ||||||
| 	Port          int64  `json:"port"` | 	Port          int64  `json:"port"` | ||||||
|  | 	HttpsPort     int64  `json:"httpsPort"` | ||||||
| 	Password      string `json:"password"` | 	Password      string `json:"password"` | ||||||
| 	UserPassword  string `json:"userPassword"` | 	UserPassword  string `json:"userPassword"` | ||||||
| 	ContainerName string `json:"containerName"` | 	ContainerName string `json:"containerName"` | ||||||
|  | 	ServiceName   string `json:"serviceName"` | ||||||
| 	Param         string `json:"param"` | 	Param         string `json:"param"` | ||||||
| 	Env           string `json:"env"` | 	Env           string `json:"env"` | ||||||
| 	Key           string `json:"key"` | 	Key           string `json:"key"` | ||||||
| @@ -152,7 +201,9 @@ func (a *AppInstallRepo) LoadBaseInfo(key string, name string) (*RootInfo, error | |||||||
| 		info.UserPassword = userPassword | 		info.UserPassword = userPassword | ||||||
| 	} | 	} | ||||||
| 	info.Port = int64(appInstall.HttpPort) | 	info.Port = int64(appInstall.HttpPort) | ||||||
|  | 	info.HttpsPort = int64(appInstall.HttpsPort) | ||||||
| 	info.ID = appInstall.ID | 	info.ID = appInstall.ID | ||||||
|  | 	info.ServiceName = appInstall.ServiceName | ||||||
| 	info.ContainerName = appInstall.ContainerName | 	info.ContainerName = appInstall.ContainerName | ||||||
| 	info.Name = appInstall.Name | 	info.Name = appInstall.Name | ||||||
| 	info.Env = appInstall.Env | 	info.Env = appInstall.Env | ||||||
|   | |||||||
| @@ -11,6 +11,21 @@ import ( | |||||||
| type AppInstallResourceRpo struct { | type AppInstallResourceRpo struct { | ||||||
| } | } | ||||||
|  |  | ||||||
|  | type IAppInstallResourceRpo interface { | ||||||
|  | 	WithAppInstallId(appInstallId uint) DBOption | ||||||
|  | 	WithLinkId(linkId uint) DBOption | ||||||
|  | 	WithResourceId(resourceId uint) DBOption | ||||||
|  | 	GetBy(opts ...DBOption) ([]model.AppInstallResource, error) | ||||||
|  | 	GetFirst(opts ...DBOption) (model.AppInstallResource, error) | ||||||
|  | 	Create(ctx context.Context, resource *model.AppInstallResource) error | ||||||
|  | 	DeleteBy(ctx context.Context, opts ...DBOption) error | ||||||
|  | 	BatchUpdateBy(maps map[string]interface{}, opts ...DBOption) error | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func NewIAppInstallResourceRpo() IAppInstallResourceRpo { | ||||||
|  | 	return &AppInstallResourceRpo{} | ||||||
|  | } | ||||||
|  |  | ||||||
| func (a AppInstallResourceRpo) WithAppInstallId(appInstallId uint) DBOption { | func (a AppInstallResourceRpo) WithAppInstallId(appInstallId uint) DBOption { | ||||||
| 	return func(db *gorm.DB) *gorm.DB { | 	return func(db *gorm.DB) *gorm.DB { | ||||||
| 		return db.Where("app_install_id = ?", appInstallId) | 		return db.Where("app_install_id = ?", appInstallId) | ||||||
| @@ -57,3 +72,11 @@ func (a AppInstallResourceRpo) Create(ctx context.Context, resource *model.AppIn | |||||||
| func (a AppInstallResourceRpo) DeleteBy(ctx context.Context, opts ...DBOption) error { | func (a AppInstallResourceRpo) DeleteBy(ctx context.Context, opts ...DBOption) error { | ||||||
| 	return getTx(ctx, opts...).Delete(&model.AppInstallResource{}).Error | 	return getTx(ctx, opts...).Delete(&model.AppInstallResource{}).Error | ||||||
| } | } | ||||||
|  |  | ||||||
|  | func (a *AppInstallResourceRpo) BatchUpdateBy(maps map[string]interface{}, opts ...DBOption) error { | ||||||
|  | 	db := getDb(opts...).Model(&model.AppInstallResource{}) | ||||||
|  | 	if len(opts) == 0 { | ||||||
|  | 		db = db.Where("1=1") | ||||||
|  | 	} | ||||||
|  | 	return db.Updates(&maps).Error | ||||||
|  | } | ||||||
|   | |||||||
| @@ -8,6 +8,18 @@ import ( | |||||||
| type AppTagRepo struct { | type AppTagRepo struct { | ||||||
| } | } | ||||||
|  |  | ||||||
|  | type IAppTagRepo interface { | ||||||
|  | 	BatchCreate(ctx context.Context, tags []*model.AppTag) error | ||||||
|  | 	DeleteByAppIds(ctx context.Context, appIds []uint) error | ||||||
|  | 	DeleteAll(ctx context.Context) error | ||||||
|  | 	GetByAppId(appId uint) ([]model.AppTag, error) | ||||||
|  | 	GetByTagIds(tagIds []uint) ([]model.AppTag, error) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func NewIAppTagRepo() IAppTagRepo { | ||||||
|  | 	return &AppTagRepo{} | ||||||
|  | } | ||||||
|  |  | ||||||
| func (a AppTagRepo) BatchCreate(ctx context.Context, tags []*model.AppTag) error { | func (a AppTagRepo) BatchCreate(ctx context.Context, tags []*model.AppTag) error { | ||||||
| 	return getTx(ctx).Create(&tags).Error | 	return getTx(ctx).Create(&tags).Error | ||||||
| } | } | ||||||
|   | |||||||
| @@ -20,6 +20,8 @@ type IBackupRepo interface { | |||||||
| 	Delete(opts ...DBOption) error | 	Delete(opts ...DBOption) error | ||||||
| 	DeleteRecord(ctx context.Context, opts ...DBOption) error | 	DeleteRecord(ctx context.Context, opts ...DBOption) error | ||||||
| 	WithByDetailName(detailName string) DBOption | 	WithByDetailName(detailName string) DBOption | ||||||
|  | 	WithByFileName(fileName string) DBOption | ||||||
|  | 	WithByType(backupType string) DBOption | ||||||
| } | } | ||||||
|  |  | ||||||
| func NewIBackupRepo() IBackupRepo { | func NewIBackupRepo() IBackupRepo { | ||||||
|   | |||||||
| @@ -15,6 +15,7 @@ type ICommandRepo interface { | |||||||
| 	Create(command *model.Command) error | 	Create(command *model.Command) error | ||||||
| 	Update(id uint, vars map[string]interface{}) error | 	Update(id uint, vars map[string]interface{}) error | ||||||
| 	Delete(opts ...DBOption) error | 	Delete(opts ...DBOption) error | ||||||
|  | 	Get(opts ...DBOption) (model.Command, error) | ||||||
| } | } | ||||||
|  |  | ||||||
| func NewICommandRepo() ICommandRepo { | func NewICommandRepo() ICommandRepo { | ||||||
|   | |||||||
| @@ -2,6 +2,7 @@ package repo | |||||||
|  |  | ||||||
| import ( | import ( | ||||||
| 	"context" | 	"context" | ||||||
|  | 	"fmt" | ||||||
| 	"time" | 	"time" | ||||||
|  |  | ||||||
| 	"github.com/1Panel-dev/1Panel/backend/constant" | 	"github.com/1Panel-dev/1Panel/backend/constant" | ||||||
| @@ -16,15 +17,21 @@ type ICommonRepo interface { | |||||||
| 	WithByName(name string) DBOption | 	WithByName(name string) DBOption | ||||||
| 	WithByType(tp string) DBOption | 	WithByType(tp string) DBOption | ||||||
| 	WithOrderBy(orderStr string) DBOption | 	WithOrderBy(orderStr string) DBOption | ||||||
|  | 	WithOrderRuleBy(orderBy, order string) DBOption | ||||||
| 	WithByGroupID(groupID uint) DBOption | 	WithByGroupID(groupID uint) DBOption | ||||||
| 	WithLikeName(name string) DBOption | 	WithLikeName(name string) DBOption | ||||||
| 	WithIdsIn(ids []uint) DBOption | 	WithIdsIn(ids []uint) DBOption | ||||||
| 	WithByDate(startTime, endTime time.Time) DBOption | 	WithByDate(startTime, endTime time.Time) DBOption | ||||||
| 	WithByStartDate(startTime time.Time) DBOption | 	WithByStartDate(startTime time.Time) DBOption | ||||||
|  | 	WithByStatus(status string) DBOption | ||||||
| } | } | ||||||
|  |  | ||||||
| type CommonRepo struct{} | type CommonRepo struct{} | ||||||
|  |  | ||||||
|  | func NewCommonRepo() ICommonRepo { | ||||||
|  | 	return &CommonRepo{} | ||||||
|  | } | ||||||
|  |  | ||||||
| func (c *CommonRepo) WithByID(id uint) DBOption { | func (c *CommonRepo) WithByID(id uint) DBOption { | ||||||
| 	return func(g *gorm.DB) *gorm.DB { | 	return func(g *gorm.DB) *gorm.DB { | ||||||
| 		return g.Where("id = ?", id) | 		return g.Where("id = ?", id) | ||||||
| @@ -88,6 +95,21 @@ func (c *CommonRepo) WithOrderBy(orderStr string) DBOption { | |||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
|  | func (c *CommonRepo) WithOrderRuleBy(orderBy, order string) DBOption { | ||||||
|  | 	switch order { | ||||||
|  | 	case constant.OrderDesc: | ||||||
|  | 		order = "desc" | ||||||
|  | 	case constant.OrderAsc: | ||||||
|  | 		order = "asc" | ||||||
|  | 	default: | ||||||
|  | 		orderBy = "created_at" | ||||||
|  | 		order = "desc" | ||||||
|  | 	} | ||||||
|  | 	return func(g *gorm.DB) *gorm.DB { | ||||||
|  | 		return g.Order(fmt.Sprintf("%s %s", orderBy, order)) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
| func (c *CommonRepo) WithIdsIn(ids []uint) DBOption { | func (c *CommonRepo) WithIdsIn(ids []uint) DBOption { | ||||||
| 	return func(g *gorm.DB) *gorm.DB { | 	return func(g *gorm.DB) *gorm.DB { | ||||||
| 		return g.Where("id in (?)", ids) | 		return g.Where("id in (?)", ids) | ||||||
|   | |||||||
| @@ -15,8 +15,10 @@ type IComposeTemplateRepo interface { | |||||||
| 	Update(id uint, vars map[string]interface{}) error | 	Update(id uint, vars map[string]interface{}) error | ||||||
| 	Delete(opts ...DBOption) error | 	Delete(opts ...DBOption) error | ||||||
|  |  | ||||||
|  | 	GetRecord(opts ...DBOption) (model.Compose, error) | ||||||
| 	CreateRecord(compose *model.Compose) error | 	CreateRecord(compose *model.Compose) error | ||||||
| 	DeleteRecord(opts ...DBOption) error | 	DeleteRecord(opts ...DBOption) error | ||||||
|  | 	ListRecord() ([]model.Compose, error) | ||||||
| } | } | ||||||
|  |  | ||||||
| func NewIComposeTemplateRepo() IComposeTemplateRepo { | func NewIComposeTemplateRepo() IComposeTemplateRepo { | ||||||
| @@ -71,6 +73,16 @@ func (u *ComposeTemplateRepo) Delete(opts ...DBOption) error { | |||||||
| 	return db.Delete(&model.ComposeTemplate{}).Error | 	return db.Delete(&model.ComposeTemplate{}).Error | ||||||
| } | } | ||||||
|  |  | ||||||
|  | func (u *ComposeTemplateRepo) GetRecord(opts ...DBOption) (model.Compose, error) { | ||||||
|  | 	var compose model.Compose | ||||||
|  | 	db := global.DB | ||||||
|  | 	for _, opt := range opts { | ||||||
|  | 		db = opt(db) | ||||||
|  | 	} | ||||||
|  | 	err := db.First(&compose).Error | ||||||
|  | 	return compose, err | ||||||
|  | } | ||||||
|  |  | ||||||
| func (u *ComposeTemplateRepo) ListRecord() ([]model.Compose, error) { | func (u *ComposeTemplateRepo) ListRecord() ([]model.Compose, error) { | ||||||
| 	var composes []model.Compose | 	var composes []model.Compose | ||||||
| 	if err := global.DB.Find(&composes).Error; err != nil { | 	if err := global.DB.Find(&composes).Error; err != nil { | ||||||
|   | |||||||
| @@ -20,12 +20,15 @@ type ICronjobRepo interface { | |||||||
| 	Page(limit, offset int, opts ...DBOption) (int64, []model.Cronjob, error) | 	Page(limit, offset int, opts ...DBOption) (int64, []model.Cronjob, error) | ||||||
| 	Create(cronjob *model.Cronjob) error | 	Create(cronjob *model.Cronjob) error | ||||||
| 	WithByJobID(id int) DBOption | 	WithByJobID(id int) DBOption | ||||||
|  | 	WithByBackupID(id uint) DBOption | ||||||
|  | 	WithByRecordDropID(id int) DBOption | ||||||
| 	Save(id uint, cronjob model.Cronjob) error | 	Save(id uint, cronjob model.Cronjob) error | ||||||
| 	Update(id uint, vars map[string]interface{}) error | 	Update(id uint, vars map[string]interface{}) error | ||||||
| 	Delete(opts ...DBOption) error | 	Delete(opts ...DBOption) error | ||||||
| 	DeleteRecord(opts ...DBOption) error | 	DeleteRecord(opts ...DBOption) error | ||||||
| 	StartRecords(cronjobID uint, fromLocal bool, targetPath string) model.JobRecords | 	StartRecords(cronjobID uint, fromLocal bool, targetPath string) model.JobRecords | ||||||
| 	EndRecords(record model.JobRecords, status, message, records string) | 	EndRecords(record model.JobRecords, status, message, records string) | ||||||
|  | 	PageRecords(page, size int, opts ...DBOption) (int64, []model.JobRecords, error) | ||||||
| } | } | ||||||
|  |  | ||||||
| func NewICronjobRepo() ICronjobRepo { | func NewICronjobRepo() ICronjobRepo { | ||||||
| @@ -80,7 +83,7 @@ func (u *CronjobRepo) Page(page, size int, opts ...DBOption) (int64, []model.Cro | |||||||
| 	} | 	} | ||||||
| 	count := int64(0) | 	count := int64(0) | ||||||
| 	db = db.Count(&count) | 	db = db.Count(&count) | ||||||
| 	err := db.Order("created_at desc").Limit(size).Offset(size * (page - 1)).Find(&cronjobs).Error | 	err := db.Limit(size).Offset(size * (page - 1)).Find(&cronjobs).Error | ||||||
| 	return count, cronjobs, err | 	return count, cronjobs, err | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -112,6 +115,18 @@ func (c *CronjobRepo) WithByJobID(id int) DBOption { | |||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
|  | func (c *CronjobRepo) WithByBackupID(id uint) DBOption { | ||||||
|  | 	return func(g *gorm.DB) *gorm.DB { | ||||||
|  | 		return g.Where("target_dir_id = ?", id) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (c *CronjobRepo) WithByRecordDropID(id int) DBOption { | ||||||
|  | 	return func(g *gorm.DB) *gorm.DB { | ||||||
|  | 		return g.Where("id < ?", id) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
| func (u *CronjobRepo) StartRecords(cronjobID uint, fromLocal bool, targetPath string) model.JobRecords { | func (u *CronjobRepo) StartRecords(cronjobID uint, fromLocal bool, targetPath string) model.JobRecords { | ||||||
| 	var record model.JobRecords | 	var record model.JobRecords | ||||||
| 	record.StartTime = time.Now() | 	record.StartTime = time.Now() | ||||||
|   | |||||||
| @@ -1,29 +0,0 @@ | |||||||
| package repo |  | ||||||
|  |  | ||||||
| type RepoGroup struct { |  | ||||||
| 	CommonRepo |  | ||||||
| 	AppRepo |  | ||||||
| 	AppTagRepo |  | ||||||
| 	TagRepo |  | ||||||
| 	AppDetailRepo |  | ||||||
| 	AppInstallRepo |  | ||||||
| 	AppInstallResourceRpo |  | ||||||
| 	ImageRepoRepo |  | ||||||
| 	ComposeTemplateRepo |  | ||||||
| 	MysqlRepo |  | ||||||
| 	CronjobRepo |  | ||||||
| 	HostRepo |  | ||||||
| 	CommandRepo |  | ||||||
| 	GroupRepo |  | ||||||
| 	SettingRepo |  | ||||||
| 	BackupRepo |  | ||||||
| 	WebsiteRepo |  | ||||||
| 	WebsiteDomainRepo |  | ||||||
| 	WebsiteDnsAccountRepo |  | ||||||
| 	WebsiteSSLRepo |  | ||||||
| 	WebsiteAcmeAccountRepo |  | ||||||
| 	LogRepo |  | ||||||
| 	SnapshotRepo |  | ||||||
| } |  | ||||||
|  |  | ||||||
| var RepoGroupApp = new(RepoGroup) |  | ||||||
| @@ -25,7 +25,7 @@ func NewIHostRepo() IHostRepo { | |||||||
| 	return &HostRepo{} | 	return &HostRepo{} | ||||||
| } | } | ||||||
|  |  | ||||||
| func (u *HostRepo) Get(opts ...DBOption) (model.Host, error) { | func (h *HostRepo) Get(opts ...DBOption) (model.Host, error) { | ||||||
| 	var host model.Host | 	var host model.Host | ||||||
| 	db := global.DB | 	db := global.DB | ||||||
| 	for _, opt := range opts { | 	for _, opt := range opts { | ||||||
| @@ -35,7 +35,7 @@ func (u *HostRepo) Get(opts ...DBOption) (model.Host, error) { | |||||||
| 	return host, err | 	return host, err | ||||||
| } | } | ||||||
|  |  | ||||||
| func (u *HostRepo) GetList(opts ...DBOption) ([]model.Host, error) { | func (h *HostRepo) GetList(opts ...DBOption) ([]model.Host, error) { | ||||||
| 	var hosts []model.Host | 	var hosts []model.Host | ||||||
| 	db := global.DB.Model(&model.Host{}) | 	db := global.DB.Model(&model.Host{}) | ||||||
| 	for _, opt := range opts { | 	for _, opt := range opts { | ||||||
| @@ -45,7 +45,7 @@ func (u *HostRepo) GetList(opts ...DBOption) ([]model.Host, error) { | |||||||
| 	return hosts, err | 	return hosts, err | ||||||
| } | } | ||||||
|  |  | ||||||
| func (u *HostRepo) Page(page, size int, opts ...DBOption) (int64, []model.Host, error) { | func (h *HostRepo) Page(page, size int, opts ...DBOption) (int64, []model.Host, error) { | ||||||
| 	var users []model.Host | 	var users []model.Host | ||||||
| 	db := global.DB.Model(&model.Host{}) | 	db := global.DB.Model(&model.Host{}) | ||||||
| 	for _, opt := range opts { | 	for _, opt := range opts { | ||||||
| @@ -57,7 +57,7 @@ func (u *HostRepo) Page(page, size int, opts ...DBOption) (int64, []model.Host, | |||||||
| 	return count, users, err | 	return count, users, err | ||||||
| } | } | ||||||
|  |  | ||||||
| func (c *HostRepo) WithByInfo(info string) DBOption { | func (h *HostRepo) WithByInfo(info string) DBOption { | ||||||
| 	return func(g *gorm.DB) *gorm.DB { | 	return func(g *gorm.DB) *gorm.DB { | ||||||
| 		if len(info) == 0 { | 		if len(info) == 0 { | ||||||
| 			return g | 			return g | ||||||
| @@ -67,22 +67,22 @@ func (c *HostRepo) WithByInfo(info string) DBOption { | |||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| func (u *HostRepo) WithByPort(port uint) DBOption { | func (h *HostRepo) WithByPort(port uint) DBOption { | ||||||
| 	return func(g *gorm.DB) *gorm.DB { | 	return func(g *gorm.DB) *gorm.DB { | ||||||
| 		return g.Where("port = ?", port) | 		return g.Where("port = ?", port) | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| func (u *HostRepo) WithByUser(user string) DBOption { | func (h *HostRepo) WithByUser(user string) DBOption { | ||||||
| 	return func(g *gorm.DB) *gorm.DB { | 	return func(g *gorm.DB) *gorm.DB { | ||||||
| 		return g.Where("user = ?", user) | 		return g.Where("user = ?", user) | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| func (u *HostRepo) WithByAddr(addr string) DBOption { | func (h *HostRepo) WithByAddr(addr string) DBOption { | ||||||
| 	return func(g *gorm.DB) *gorm.DB { | 	return func(g *gorm.DB) *gorm.DB { | ||||||
| 		return g.Where("addr = ?", addr) | 		return g.Where("addr = ?", addr) | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| func (u *HostRepo) WithByGroup(group string) DBOption { | func (h *HostRepo) WithByGroup(group string) DBOption { | ||||||
| 	return func(g *gorm.DB) *gorm.DB { | 	return func(g *gorm.DB) *gorm.DB { | ||||||
| 		if len(group) == 0 { | 		if len(group) == 0 { | ||||||
| 			return g | 			return g | ||||||
| @@ -91,15 +91,15 @@ func (u *HostRepo) WithByGroup(group string) DBOption { | |||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| func (u *HostRepo) Create(host *model.Host) error { | func (h *HostRepo) Create(host *model.Host) error { | ||||||
| 	return global.DB.Create(host).Error | 	return global.DB.Create(host).Error | ||||||
| } | } | ||||||
|  |  | ||||||
| func (u *HostRepo) Update(id uint, vars map[string]interface{}) error { | func (h *HostRepo) Update(id uint, vars map[string]interface{}) error { | ||||||
| 	return global.DB.Model(&model.Host{}).Where("id = ?", id).Updates(vars).Error | 	return global.DB.Model(&model.Host{}).Where("id = ?", id).Updates(vars).Error | ||||||
| } | } | ||||||
|  |  | ||||||
| func (u *HostRepo) Delete(opts ...DBOption) error { | func (h *HostRepo) Delete(opts ...DBOption) error { | ||||||
| 	db := global.DB | 	db := global.DB | ||||||
| 	for _, opt := range opts { | 	for _, opt := range opts { | ||||||
| 		db = opt(db) | 		db = opt(db) | ||||||
|   | |||||||
							
								
								
									
										87
									
								
								backend/app/repo/runtime.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										87
									
								
								backend/app/repo/runtime.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,87 @@ | |||||||
|  | package repo | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"context" | ||||||
|  | 	"github.com/1Panel-dev/1Panel/backend/app/model" | ||||||
|  | 	"gorm.io/gorm" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | type RuntimeRepo struct { | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type IRuntimeRepo interface { | ||||||
|  | 	WithName(name string) DBOption | ||||||
|  | 	WithImage(image string) DBOption | ||||||
|  | 	WithNotId(id uint) DBOption | ||||||
|  | 	WithStatus(status string) DBOption | ||||||
|  | 	WithDetailId(id uint) DBOption | ||||||
|  | 	Page(page, size int, opts ...DBOption) (int64, []model.Runtime, error) | ||||||
|  | 	Create(ctx context.Context, runtime *model.Runtime) error | ||||||
|  | 	Save(runtime *model.Runtime) error | ||||||
|  | 	DeleteBy(opts ...DBOption) error | ||||||
|  | 	GetFirst(opts ...DBOption) (*model.Runtime, error) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func NewIRunTimeRepo() IRuntimeRepo { | ||||||
|  | 	return &RuntimeRepo{} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (r *RuntimeRepo) WithName(name string) DBOption { | ||||||
|  | 	return func(g *gorm.DB) *gorm.DB { | ||||||
|  | 		return g.Where("name = ?", name) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (r *RuntimeRepo) WithStatus(status string) DBOption { | ||||||
|  | 	return func(g *gorm.DB) *gorm.DB { | ||||||
|  | 		return g.Where("status = ?", status) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (r *RuntimeRepo) WithImage(image string) DBOption { | ||||||
|  | 	return func(g *gorm.DB) *gorm.DB { | ||||||
|  | 		return g.Where("image = ?", image) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (r *RuntimeRepo) WithDetailId(id uint) DBOption { | ||||||
|  | 	return func(g *gorm.DB) *gorm.DB { | ||||||
|  | 		return g.Where("app_detail_id = ?", id) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (r *RuntimeRepo) WithNotId(id uint) DBOption { | ||||||
|  | 	return func(g *gorm.DB) *gorm.DB { | ||||||
|  | 		return g.Where("id != ?", id) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (r *RuntimeRepo) Page(page, size int, opts ...DBOption) (int64, []model.Runtime, error) { | ||||||
|  | 	var runtimes []model.Runtime | ||||||
|  | 	db := getDb(opts...).Model(&model.Runtime{}) | ||||||
|  | 	count := int64(0) | ||||||
|  | 	db = db.Count(&count) | ||||||
|  | 	err := db.Limit(size).Offset(size * (page - 1)).Find(&runtimes).Error | ||||||
|  | 	return count, runtimes, err | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (r *RuntimeRepo) Create(ctx context.Context, runtime *model.Runtime) error { | ||||||
|  | 	db := getTx(ctx).Model(&model.Runtime{}) | ||||||
|  | 	return db.Create(&runtime).Error | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (r *RuntimeRepo) Save(runtime *model.Runtime) error { | ||||||
|  | 	return getDb().Save(&runtime).Error | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (r *RuntimeRepo) DeleteBy(opts ...DBOption) error { | ||||||
|  | 	return getDb(opts...).Delete(&model.Runtime{}).Error | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (r *RuntimeRepo) GetFirst(opts ...DBOption) (*model.Runtime, error) { | ||||||
|  | 	var runtime model.Runtime | ||||||
|  | 	if err := getDb(opts...).First(&runtime).Error; err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 	return &runtime, nil | ||||||
|  | } | ||||||
| @@ -1,6 +1,8 @@ | |||||||
| package repo | package repo | ||||||
|  |  | ||||||
| import ( | import ( | ||||||
|  | 	"time" | ||||||
|  |  | ||||||
| 	"github.com/1Panel-dev/1Panel/backend/app/model" | 	"github.com/1Panel-dev/1Panel/backend/app/model" | ||||||
| 	"github.com/1Panel-dev/1Panel/backend/global" | 	"github.com/1Panel-dev/1Panel/backend/global" | ||||||
| 	"gorm.io/gorm" | 	"gorm.io/gorm" | ||||||
| @@ -14,6 +16,13 @@ type ISettingRepo interface { | |||||||
| 	Create(key, value string) error | 	Create(key, value string) error | ||||||
| 	Update(key, value string) error | 	Update(key, value string) error | ||||||
| 	WithByKey(key string) DBOption | 	WithByKey(key string) DBOption | ||||||
|  |  | ||||||
|  | 	CreateMonitorBase(model model.MonitorBase) error | ||||||
|  | 	BatchCreateMonitorIO(ioList []model.MonitorIO) error | ||||||
|  | 	BatchCreateMonitorNet(ioList []model.MonitorNetwork) error | ||||||
|  | 	DelMonitorBase(timeForDelete time.Time) error | ||||||
|  | 	DelMonitorIO(timeForDelete time.Time) error | ||||||
|  | 	DelMonitorNet(timeForDelete time.Time) error | ||||||
| } | } | ||||||
|  |  | ||||||
| func NewISettingRepo() ISettingRepo { | func NewISettingRepo() ISettingRepo { | ||||||
| @@ -57,3 +66,22 @@ func (c *SettingRepo) WithByKey(key string) DBOption { | |||||||
| func (u *SettingRepo) Update(key, value string) error { | func (u *SettingRepo) Update(key, value string) error { | ||||||
| 	return global.DB.Model(&model.Setting{}).Where("key = ?", key).Updates(map[string]interface{}{"value": value}).Error | 	return global.DB.Model(&model.Setting{}).Where("key = ?", key).Updates(map[string]interface{}{"value": value}).Error | ||||||
| } | } | ||||||
|  |  | ||||||
|  | func (u *SettingRepo) CreateMonitorBase(model model.MonitorBase) error { | ||||||
|  | 	return global.DB.Create(&model).Error | ||||||
|  | } | ||||||
|  | func (u *SettingRepo) BatchCreateMonitorIO(ioList []model.MonitorIO) error { | ||||||
|  | 	return global.DB.CreateInBatches(ioList, len(ioList)).Error | ||||||
|  | } | ||||||
|  | func (u *SettingRepo) BatchCreateMonitorNet(ioList []model.MonitorNetwork) error { | ||||||
|  | 	return global.DB.CreateInBatches(ioList, len(ioList)).Error | ||||||
|  | } | ||||||
|  | func (u *SettingRepo) DelMonitorBase(timeForDelete time.Time) error { | ||||||
|  | 	return global.DB.Where("created_at < ?", timeForDelete).Delete(&model.MonitorBase{}).Error | ||||||
|  | } | ||||||
|  | func (u *SettingRepo) DelMonitorIO(timeForDelete time.Time) error { | ||||||
|  | 	return global.DB.Where("created_at < ?", timeForDelete).Delete(&model.MonitorIO{}).Error | ||||||
|  | } | ||||||
|  | func (u *SettingRepo) DelMonitorNet(timeForDelete time.Time) error { | ||||||
|  | 	return global.DB.Where("created_at < ?", timeForDelete).Delete(&model.MonitorNetwork{}).Error | ||||||
|  | } | ||||||
|   | |||||||
| @@ -8,6 +8,19 @@ import ( | |||||||
| type TagRepo struct { | type TagRepo struct { | ||||||
| } | } | ||||||
|  |  | ||||||
|  | type ITagRepo interface { | ||||||
|  | 	BatchCreate(ctx context.Context, tags []*model.Tag) error | ||||||
|  | 	DeleteAll(ctx context.Context) error | ||||||
|  | 	All() ([]model.Tag, error) | ||||||
|  | 	GetByIds(ids []uint) ([]model.Tag, error) | ||||||
|  | 	GetByKeys(keys []string) ([]model.Tag, error) | ||||||
|  | 	GetByAppId(appId uint) ([]model.Tag, error) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func NewITagRepo() ITagRepo { | ||||||
|  | 	return &TagRepo{} | ||||||
|  | } | ||||||
|  |  | ||||||
| func (t TagRepo) BatchCreate(ctx context.Context, tags []*model.Tag) error { | func (t TagRepo) BatchCreate(ctx context.Context, tags []*model.Tag) error { | ||||||
| 	return getTx(ctx).Create(&tags).Error | 	return getTx(ctx).Create(&tags).Error | ||||||
| } | } | ||||||
|   | |||||||
| @@ -17,6 +17,7 @@ type IWebsiteRepo interface { | |||||||
| 	WithGroupID(groupId uint) DBOption | 	WithGroupID(groupId uint) DBOption | ||||||
| 	WithDefaultServer() DBOption | 	WithDefaultServer() DBOption | ||||||
| 	WithDomainLike(domain string) DBOption | 	WithDomainLike(domain string) DBOption | ||||||
|  | 	WithRuntimeID(runtimeID uint) DBOption | ||||||
| 	Page(page, size int, opts ...DBOption) (int64, []model.Website, error) | 	Page(page, size int, opts ...DBOption) (int64, []model.Website, error) | ||||||
| 	List(opts ...DBOption) ([]model.Website, error) | 	List(opts ...DBOption) ([]model.Website, error) | ||||||
| 	GetFirst(opts ...DBOption) (model.Website, error) | 	GetFirst(opts ...DBOption) (model.Website, error) | ||||||
| @@ -40,6 +41,12 @@ func (w *WebsiteRepo) WithAppInstallId(appInstallId uint) DBOption { | |||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
|  | func (w *WebsiteRepo) WithRuntimeID(runtimeID uint) DBOption { | ||||||
|  | 	return func(db *gorm.DB) *gorm.DB { | ||||||
|  | 		return db.Where("runtime_id = ?", runtimeID) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
| func (w *WebsiteRepo) WithDomain(domain string) DBOption { | func (w *WebsiteRepo) WithDomain(domain string) DBOption { | ||||||
| 	return func(db *gorm.DB) *gorm.DB { | 	return func(db *gorm.DB) *gorm.DB { | ||||||
| 		return db.Where("primary_domain = ?", domain) | 		return db.Where("primary_domain = ?", domain) | ||||||
|   | |||||||
| @@ -7,6 +7,19 @@ import ( | |||||||
| type WebsiteDnsAccountRepo struct { | type WebsiteDnsAccountRepo struct { | ||||||
| } | } | ||||||
|  |  | ||||||
|  | type IWebsiteDnsAccountRepo interface { | ||||||
|  | 	Page(page, size int, opts ...DBOption) (int64, []model.WebsiteDnsAccount, error) | ||||||
|  | 	GetFirst(opts ...DBOption) (*model.WebsiteDnsAccount, error) | ||||||
|  | 	List(opts ...DBOption) ([]model.WebsiteDnsAccount, error) | ||||||
|  | 	Create(account model.WebsiteDnsAccount) error | ||||||
|  | 	Save(account model.WebsiteDnsAccount) error | ||||||
|  | 	DeleteBy(opts ...DBOption) error | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func NewIWebsiteDnsAccountRepo() IWebsiteDnsAccountRepo { | ||||||
|  | 	return &WebsiteDnsAccountRepo{} | ||||||
|  | } | ||||||
|  |  | ||||||
| func (w WebsiteDnsAccountRepo) Page(page, size int, opts ...DBOption) (int64, []model.WebsiteDnsAccount, error) { | func (w WebsiteDnsAccountRepo) Page(page, size int, opts ...DBOption) (int64, []model.WebsiteDnsAccount, error) { | ||||||
| 	var accounts []model.WebsiteDnsAccount | 	var accounts []model.WebsiteDnsAccount | ||||||
| 	db := getDb(opts...).Model(&model.WebsiteDnsAccount{}) | 	db := getDb(opts...).Model(&model.WebsiteDnsAccount{}) | ||||||
|   | |||||||
| @@ -10,6 +10,23 @@ import ( | |||||||
| type WebsiteDomainRepo struct { | type WebsiteDomainRepo struct { | ||||||
| } | } | ||||||
|  |  | ||||||
|  | type IWebsiteDomainRepo interface { | ||||||
|  | 	WithWebsiteId(websiteId uint) DBOption | ||||||
|  | 	WithPort(port int) DBOption | ||||||
|  | 	WithDomain(domain string) DBOption | ||||||
|  | 	Page(page, size int, opts ...DBOption) (int64, []model.WebsiteDomain, error) | ||||||
|  | 	GetFirst(opts ...DBOption) (model.WebsiteDomain, error) | ||||||
|  | 	GetBy(opts ...DBOption) ([]model.WebsiteDomain, error) | ||||||
|  | 	BatchCreate(ctx context.Context, domains []model.WebsiteDomain) error | ||||||
|  | 	Create(ctx context.Context, app *model.WebsiteDomain) error | ||||||
|  | 	Save(ctx context.Context, app *model.WebsiteDomain) error | ||||||
|  | 	DeleteBy(ctx context.Context, opts ...DBOption) error | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func NewIWebsiteDomainRepo() IWebsiteDomainRepo { | ||||||
|  | 	return &WebsiteDomainRepo{} | ||||||
|  | } | ||||||
|  |  | ||||||
| func (w WebsiteDomainRepo) WithWebsiteId(websiteId uint) DBOption { | func (w WebsiteDomainRepo) WithWebsiteId(websiteId uint) DBOption { | ||||||
| 	return func(db *gorm.DB) *gorm.DB { | 	return func(db *gorm.DB) *gorm.DB { | ||||||
| 		return db.Where("website_id = ?", websiteId) | 		return db.Where("website_id = ?", websiteId) | ||||||
|   | |||||||
| @@ -5,24 +5,26 @@ import ( | |||||||
| 	"encoding/base64" | 	"encoding/base64" | ||||||
| 	"encoding/json" | 	"encoding/json" | ||||||
| 	"fmt" | 	"fmt" | ||||||
| 	"github.com/1Panel-dev/1Panel/backend/buserr" |  | ||||||
| 	"github.com/1Panel-dev/1Panel/backend/utils/docker" |  | ||||||
| 	"io/ioutil" |  | ||||||
| 	"net/http" |  | ||||||
| 	"os" |  | ||||||
| 	"path" |  | ||||||
| 	"strings" |  | ||||||
|  |  | ||||||
| 	"github.com/1Panel-dev/1Panel/backend/app/dto" | 	"github.com/1Panel-dev/1Panel/backend/app/dto" | ||||||
| 	"github.com/1Panel-dev/1Panel/backend/app/dto/request" | 	"github.com/1Panel-dev/1Panel/backend/app/dto/request" | ||||||
| 	"github.com/1Panel-dev/1Panel/backend/app/dto/response" | 	"github.com/1Panel-dev/1Panel/backend/app/dto/response" | ||||||
| 	"github.com/1Panel-dev/1Panel/backend/app/model" | 	"github.com/1Panel-dev/1Panel/backend/app/model" | ||||||
| 	"github.com/1Panel-dev/1Panel/backend/app/repo" | 	"github.com/1Panel-dev/1Panel/backend/app/repo" | ||||||
|  | 	"github.com/1Panel-dev/1Panel/backend/buserr" | ||||||
| 	"github.com/1Panel-dev/1Panel/backend/constant" | 	"github.com/1Panel-dev/1Panel/backend/constant" | ||||||
| 	"github.com/1Panel-dev/1Panel/backend/global" | 	"github.com/1Panel-dev/1Panel/backend/global" | ||||||
|  | 	"github.com/1Panel-dev/1Panel/backend/i18n" | ||||||
| 	"github.com/1Panel-dev/1Panel/backend/utils/common" | 	"github.com/1Panel-dev/1Panel/backend/utils/common" | ||||||
|  | 	"github.com/1Panel-dev/1Panel/backend/utils/docker" | ||||||
| 	"github.com/1Panel-dev/1Panel/backend/utils/files" | 	"github.com/1Panel-dev/1Panel/backend/utils/files" | ||||||
|  | 	http2 "github.com/1Panel-dev/1Panel/backend/utils/http" | ||||||
| 	"gopkg.in/yaml.v3" | 	"gopkg.in/yaml.v3" | ||||||
|  | 	"io" | ||||||
|  | 	"net/http" | ||||||
|  | 	"os" | ||||||
|  | 	"path" | ||||||
|  | 	"strconv" | ||||||
|  | 	"strings" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| type AppService struct { | type AppService struct { | ||||||
| @@ -32,10 +34,13 @@ type IAppService interface { | |||||||
| 	PageApp(req request.AppSearch) (interface{}, error) | 	PageApp(req request.AppSearch) (interface{}, error) | ||||||
| 	GetAppTags() ([]response.TagDTO, error) | 	GetAppTags() ([]response.TagDTO, error) | ||||||
| 	GetApp(key string) (*response.AppDTO, error) | 	GetApp(key string) (*response.AppDTO, error) | ||||||
| 	GetAppDetail(appId uint, version string) (response.AppDetailDTO, error) | 	GetAppDetail(appId uint, version, appType string) (response.AppDetailDTO, error) | ||||||
| 	Install(ctx context.Context, req request.AppInstallCreate) (*model.AppInstall, error) | 	Install(ctx context.Context, req request.AppInstallCreate) (*model.AppInstall, error) | ||||||
| 	SyncAppList() error | 	SyncAppListFromRemote() error | ||||||
| 	GetAppUpdate() (*response.AppUpdateRes, error) | 	GetAppUpdate() (*response.AppUpdateRes, error) | ||||||
|  | 	GetAppDetailByID(id uint) (*response.AppDetailDTO, error) | ||||||
|  | 	SyncAppListFromLocal() | ||||||
|  | 	GetIgnoredApp() ([]response.IgnoredApp, error) | ||||||
| } | } | ||||||
|  |  | ||||||
| func NewIAppService() IAppService { | func NewIAppService() IAppService { | ||||||
| @@ -79,12 +84,16 @@ func (a AppService) PageApp(req request.AppSearch) (interface{}, error) { | |||||||
| 		return nil, err | 		return nil, err | ||||||
| 	} | 	} | ||||||
| 	var appDTOs []*response.AppDTO | 	var appDTOs []*response.AppDTO | ||||||
| 	for _, a := range apps { | 	for _, ap := range apps { | ||||||
|  | 		ap.ReadMe = "" | ||||||
|  | 		ap.Website = "" | ||||||
|  | 		ap.Document = "" | ||||||
|  | 		ap.Github = "" | ||||||
| 		appDTO := &response.AppDTO{ | 		appDTO := &response.AppDTO{ | ||||||
| 			App: a, | 			App: ap, | ||||||
| 		} | 		} | ||||||
| 		appDTOs = append(appDTOs, appDTO) | 		appDTOs = append(appDTOs, appDTO) | ||||||
| 		appTags, err := appTagRepo.GetByAppId(a.ID) | 		appTags, err := appTagRepo.GetByAppId(ap.ID) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			continue | 			continue | ||||||
| 		} | 		} | ||||||
| @@ -97,6 +106,8 @@ func (a AppService) PageApp(req request.AppSearch) (interface{}, error) { | |||||||
| 			continue | 			continue | ||||||
| 		} | 		} | ||||||
| 		appDTO.Tags = tags | 		appDTO.Tags = tags | ||||||
|  | 		installs, _ := appInstallRepo.ListBy(appInstallRepo.WithAppId(ap.ID)) | ||||||
|  | 		appDTO.Installed = len(installs) > 0 | ||||||
| 	} | 	} | ||||||
| 	res.Items = appDTOs | 	res.Items = appDTOs | ||||||
| 	res.Total = total | 	res.Total = total | ||||||
| @@ -138,7 +149,7 @@ func (a AppService) GetApp(key string) (*response.AppDTO, error) { | |||||||
| 	return &appDTO, nil | 	return &appDTO, nil | ||||||
| } | } | ||||||
|  |  | ||||||
| func (a AppService) GetAppDetail(appId uint, version string) (response.AppDetailDTO, error) { | func (a AppService) GetAppDetail(appId uint, version, appType string) (response.AppDetailDTO, error) { | ||||||
| 	var ( | 	var ( | ||||||
| 		appDetailDTO response.AppDetailDTO | 		appDetailDTO response.AppDetailDTO | ||||||
| 		opts         []repo.DBOption | 		opts         []repo.DBOption | ||||||
| @@ -148,14 +159,61 @@ func (a AppService) GetAppDetail(appId uint, version string) (response.AppDetail | |||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return appDetailDTO, err | 		return appDetailDTO, err | ||||||
| 	} | 	} | ||||||
| 	paramMap := make(map[string]interface{}) |  | ||||||
| 	if err := json.Unmarshal([]byte(detail.Params), ¶mMap); err != nil { |  | ||||||
| 		return appDetailDTO, err |  | ||||||
| 	} |  | ||||||
| 	appDetailDTO.AppDetail = detail | 	appDetailDTO.AppDetail = detail | ||||||
| 	appDetailDTO.Params = paramMap |  | ||||||
| 	appDetailDTO.Enable = true | 	appDetailDTO.Enable = true | ||||||
|  |  | ||||||
|  | 	if appType == "runtime" { | ||||||
|  | 		app, err := appRepo.GetFirst(commonRepo.WithByID(appId)) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return appDetailDTO, err | ||||||
|  | 		} | ||||||
|  | 		fileOp := files.NewFileOp() | ||||||
|  | 		versionPath := path.Join(constant.AppResourceDir, app.Resource, app.Key, detail.Version) | ||||||
|  | 		if !fileOp.Stat(versionPath) || detail.Update { | ||||||
|  | 			if err = downloadApp(app, detail, nil); err != nil { | ||||||
|  | 				return appDetailDTO, err | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 		buildPath := path.Join(versionPath, "build") | ||||||
|  | 		paramsPath := path.Join(buildPath, "config.json") | ||||||
|  | 		if !fileOp.Stat(paramsPath) { | ||||||
|  | 			return appDetailDTO, buserr.New(constant.ErrFileNotExist) | ||||||
|  | 		} | ||||||
|  | 		param, err := fileOp.GetContent(paramsPath) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return appDetailDTO, err | ||||||
|  | 		} | ||||||
|  | 		paramMap := make(map[string]interface{}) | ||||||
|  | 		if err := json.Unmarshal(param, ¶mMap); err != nil { | ||||||
|  | 			return appDetailDTO, err | ||||||
|  | 		} | ||||||
|  | 		appDetailDTO.Params = paramMap | ||||||
|  | 		composePath := path.Join(buildPath, "docker-compose.yml") | ||||||
|  | 		if !fileOp.Stat(composePath) { | ||||||
|  | 			return appDetailDTO, buserr.New(constant.ErrFileNotExist) | ||||||
|  | 		} | ||||||
|  | 		compose, err := fileOp.GetContent(composePath) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return appDetailDTO, err | ||||||
|  | 		} | ||||||
|  | 		composeMap := make(map[string]interface{}) | ||||||
|  | 		if err := yaml.Unmarshal(compose, &composeMap); err != nil { | ||||||
|  | 			return appDetailDTO, err | ||||||
|  | 		} | ||||||
|  | 		if service, ok := composeMap["services"]; ok { | ||||||
|  | 			servicesMap := service.(map[string]interface{}) | ||||||
|  | 			for k := range servicesMap { | ||||||
|  | 				appDetailDTO.Image = k | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	} else { | ||||||
|  | 		paramMap := make(map[string]interface{}) | ||||||
|  | 		if err := json.Unmarshal([]byte(detail.Params), ¶mMap); err != nil { | ||||||
|  | 			return appDetailDTO, err | ||||||
|  | 		} | ||||||
|  | 		appDetailDTO.Params = paramMap | ||||||
|  | 	} | ||||||
|  |  | ||||||
| 	app, err := appRepo.GetFirst(commonRepo.WithByID(detail.AppId)) | 	app, err := appRepo.GetFirst(commonRepo.WithByID(detail.AppId)) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return appDetailDTO, err | 		return appDetailDTO, err | ||||||
| @@ -165,101 +223,419 @@ func (a AppService) GetAppDetail(appId uint, version string) (response.AppDetail | |||||||
| 	} | 	} | ||||||
| 	return appDetailDTO, nil | 	return appDetailDTO, nil | ||||||
| } | } | ||||||
|  | func (a AppService) GetAppDetailByID(id uint) (*response.AppDetailDTO, error) { | ||||||
|  | 	res := &response.AppDetailDTO{} | ||||||
|  | 	appDetail, err := appDetailRepo.GetFirst(commonRepo.WithByID(id)) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 	res.AppDetail = appDetail | ||||||
|  | 	paramMap := make(map[string]interface{}) | ||||||
|  | 	if err := json.Unmarshal([]byte(appDetail.Params), ¶mMap); err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 	res.Params = paramMap | ||||||
|  | 	return res, nil | ||||||
|  | } | ||||||
|  |  | ||||||
| func (a AppService) Install(ctx context.Context, req request.AppInstallCreate) (*model.AppInstall, error) { | func (a AppService) GetIgnoredApp() ([]response.IgnoredApp, error) { | ||||||
| 	if err := docker.CreateDefaultDockerNetwork(); err != nil { | 	var res []response.IgnoredApp | ||||||
| 		return nil, buserr.WithDetail(constant.Err1PanelNetworkFailed, err.Error(), nil) | 	details, _ := appDetailRepo.GetBy(appDetailRepo.WithIgnored()) | ||||||
|  | 	if len(details) == 0 { | ||||||
|  | 		return res, nil | ||||||
|  | 	} | ||||||
|  | 	for _, detail := range details { | ||||||
|  | 		app, err := appRepo.GetFirst(commonRepo.WithByID(detail.AppId)) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return nil, err | ||||||
|  | 		} | ||||||
|  | 		res = append(res, response.IgnoredApp{ | ||||||
|  | 			Name:     app.Name, | ||||||
|  | 			Version:  detail.Version, | ||||||
|  | 			DetailID: detail.ID, | ||||||
|  | 			Icon:     app.Icon, | ||||||
|  | 		}) | ||||||
|  | 	} | ||||||
|  | 	return res, nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (a AppService) Install(ctx context.Context, req request.AppInstallCreate) (appInstall *model.AppInstall, err error) { | ||||||
|  | 	if err = docker.CreateDefaultDockerNetwork(); err != nil { | ||||||
|  | 		err = buserr.WithDetail(constant.Err1PanelNetworkFailed, err.Error(), nil) | ||||||
|  | 		return | ||||||
| 	} | 	} | ||||||
| 	if list, _ := appInstallRepo.ListBy(commonRepo.WithByName(req.Name)); len(list) > 0 { | 	if list, _ := appInstallRepo.ListBy(commonRepo.WithByName(req.Name)); len(list) > 0 { | ||||||
| 		return nil, buserr.New(constant.ErrNameIsExist) | 		err = buserr.New(constant.ErrNameIsExist) | ||||||
|  | 		return | ||||||
| 	} | 	} | ||||||
| 	httpPort, err := checkPort("PANEL_APP_PORT_HTTP", req.Params) | 	var ( | ||||||
| 	if err != nil { | 		httpPort  int | ||||||
| 		return nil, err | 		httpsPort int | ||||||
| 	} | 		appDetail model.AppDetail | ||||||
| 	httpsPort, err := checkPort("PANEL_APP_PORT_HTTPS", req.Params) | 		app       model.App | ||||||
| 	if err != nil { | 	) | ||||||
| 		return nil, err | 	for key := range req.Params { | ||||||
| 	} | 		if !strings.Contains(key, "PANEL_APP_PORT") { | ||||||
| 	appDetail, err := appDetailRepo.GetFirst(commonRepo.WithByID(req.AppDetailId)) | 			continue | ||||||
| 	if err != nil { | 		} | ||||||
| 		return nil, err | 		var port int | ||||||
| 	} | 		if port, err = checkPort(key, req.Params); err == nil { | ||||||
| 	app, err := appRepo.GetFirst(commonRepo.WithByID(appDetail.AppId)) | 			if key == "PANEL_APP_PORT_HTTP" { | ||||||
| 	if err != nil { | 				httpPort = port | ||||||
| 		return nil, err | 			} | ||||||
| 	} | 			if key == "PANEL_APP_PORT_HTTPS" { | ||||||
| 	if err := checkRequiredAndLimit(app); err != nil { | 				httpsPort = port | ||||||
| 		return nil, err | 			} | ||||||
|  | 		} else { | ||||||
|  | 			return | ||||||
|  | 		} | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	paramByte, err := json.Marshal(req.Params) | 	appDetail, err = appDetailRepo.GetFirst(commonRepo.WithByID(req.AppDetailId)) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return nil, err | 		return | ||||||
| 	} | 	} | ||||||
| 	appInstall := model.AppInstall{ | 	app, err = appRepo.GetFirst(commonRepo.WithByID(appDetail.AppId)) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	if err = checkRequiredAndLimit(app); err != nil { | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	appInstall = &model.AppInstall{ | ||||||
| 		Name:        req.Name, | 		Name:        req.Name, | ||||||
| 		AppId:       appDetail.AppId, | 		AppId:       appDetail.AppId, | ||||||
| 		AppDetailId: appDetail.ID, | 		AppDetailId: appDetail.ID, | ||||||
| 		Version:     appDetail.Version, | 		Version:     appDetail.Version, | ||||||
| 		Status:      constant.Installing, | 		Status:      constant.Installing, | ||||||
| 		Env:         string(paramByte), |  | ||||||
| 		HttpPort:    httpPort, | 		HttpPort:    httpPort, | ||||||
| 		HttpsPort:   httpsPort, | 		HttpsPort:   httpsPort, | ||||||
| 		App:         app, | 		App:         app, | ||||||
| 	} | 	} | ||||||
| 	composeMap := make(map[string]interface{}) | 	composeMap := make(map[string]interface{}) | ||||||
| 	if err := yaml.Unmarshal([]byte(appDetail.DockerCompose), &composeMap); err != nil { | 	if req.EditCompose { | ||||||
| 		return nil, err | 		if err = yaml.Unmarshal([]byte(req.DockerCompose), &composeMap); err != nil { | ||||||
|  | 			return | ||||||
|  | 		} | ||||||
|  | 	} else { | ||||||
|  | 		if err = yaml.Unmarshal([]byte(appDetail.DockerCompose), &composeMap); err != nil { | ||||||
|  | 			return | ||||||
|  | 		} | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	value, ok := composeMap["services"] | 	value, ok := composeMap["services"] | ||||||
| 	if !ok { | 	if !ok { | ||||||
| 		return nil, buserr.New("") | 		err = buserr.New(constant.ErrFileParse) | ||||||
|  | 		return | ||||||
| 	} | 	} | ||||||
| 	servicesMap := value.(map[string]interface{}) | 	servicesMap := value.(map[string]interface{}) | ||||||
| 	changeKeys := make(map[string]string, len(servicesMap)) | 	containerName := constant.ContainerPrefix + app.Key + "-" + common.RandStr(4) | ||||||
|  | 	if req.Advanced && req.ContainerName != "" { | ||||||
|  | 		containerName = req.ContainerName | ||||||
|  | 		appInstalls, _ := appInstallRepo.ListBy(appInstallRepo.WithContainerName(containerName)) | ||||||
|  | 		if len(appInstalls) > 0 { | ||||||
|  | 			err = buserr.New(constant.ErrContainerName) | ||||||
|  | 			return | ||||||
|  | 		} | ||||||
|  | 		containerExist := false | ||||||
|  | 		containerExist, err = checkContainerNameIsExist(req.ContainerName, appInstall.GetPath()) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return | ||||||
|  | 		} | ||||||
|  | 		if containerExist { | ||||||
|  | 			err = buserr.New(constant.ErrContainerName) | ||||||
|  | 			return | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	req.Params[constant.ContainerName] = containerName | ||||||
|  | 	appInstall.ContainerName = containerName | ||||||
|  |  | ||||||
| 	index := 0 | 	index := 0 | ||||||
| 	for k := range servicesMap { | 	for k := range servicesMap { | ||||||
| 		serviceName := k + "-" + common.RandStr(4) | 		appInstall.ServiceName = k | ||||||
| 		changeKeys[k] = serviceName |  | ||||||
| 		containerName := constant.ContainerPrefix + k + "-" + common.RandStr(4) |  | ||||||
| 		if index > 0 { | 		if index > 0 { | ||||||
| 			continue | 			continue | ||||||
| 		} | 		} | ||||||
| 		req.Params["CONTAINER_NAME"] = containerName |  | ||||||
| 		appInstall.ServiceName = serviceName |  | ||||||
| 		appInstall.ContainerName = containerName |  | ||||||
| 		index++ | 		index++ | ||||||
| 	} | 	} | ||||||
| 	for k, v := range changeKeys { |  | ||||||
| 		servicesMap[v] = servicesMap[k] | 	if err = addDockerComposeCommonParam(composeMap, appInstall.ServiceName, req.AppContainerConfig, req.Params); err != nil { | ||||||
| 		delete(servicesMap, k) | 		return | ||||||
| 	} | 	} | ||||||
| 	composeByte, err := yaml.Marshal(composeMap) | 	var ( | ||||||
|  | 		composeByte []byte | ||||||
|  | 		paramByte   []byte | ||||||
|  | 	) | ||||||
|  |  | ||||||
|  | 	composeByte, err = yaml.Marshal(composeMap) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return nil, err | 		return | ||||||
| 	} | 	} | ||||||
| 	appInstall.DockerCompose = string(composeByte) | 	appInstall.DockerCompose = string(composeByte) | ||||||
|  |  | ||||||
| 	if err := copyAppData(app.Key, appDetail.Version, req.Name, req.Params); err != nil { | 	defer func() { | ||||||
| 		return nil, err | 		if err != nil { | ||||||
|  | 			hErr := handleAppInstallErr(ctx, appInstall) | ||||||
|  | 			if hErr != nil { | ||||||
|  | 				global.LOG.Errorf("delete app dir error %s", hErr.Error()) | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	}() | ||||||
|  | 	paramByte, err = json.Marshal(req.Params) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return | ||||||
| 	} | 	} | ||||||
|  | 	appInstall.Env = string(paramByte) | ||||||
| 	fileOp := files.NewFileOp() | 	if err = appInstallRepo.Create(ctx, appInstall); err != nil { | ||||||
| 	if err := fileOp.WriteFile(appInstall.GetComposePath(), strings.NewReader(string(composeByte)), 0775); err != nil { | 		return | ||||||
| 		return nil, err |  | ||||||
| 	} | 	} | ||||||
|  | 	if err = createLink(ctx, app, appInstall, req.Params); err != nil { | ||||||
| 	if err := appInstallRepo.Create(ctx, &appInstall); err != nil { | 		return | ||||||
| 		return nil, err |  | ||||||
| 	} | 	} | ||||||
| 	if err := createLink(ctx, app, &appInstall, req.Params); err != nil { | 	go func() { | ||||||
| 		return nil, err | 		if err = copyData(app, appDetail, appInstall, req); err != nil { | ||||||
| 	} | 			if appInstall.Status == constant.Installing { | ||||||
| 	go upApp(appInstall.GetComposePath(), appInstall) | 				appInstall.Status = constant.Error | ||||||
|  | 				appInstall.Message = err.Error() | ||||||
|  | 			} | ||||||
|  | 			_ = appInstallRepo.Save(context.Background(), appInstall) | ||||||
|  | 			return | ||||||
|  | 		} | ||||||
|  | 		if err = upAppPre(app, appInstall); err != nil { | ||||||
|  | 			return | ||||||
|  | 		} | ||||||
|  | 		upApp(appInstall) | ||||||
|  | 	}() | ||||||
| 	go updateToolApp(appInstall) | 	go updateToolApp(appInstall) | ||||||
| 	return &appInstall, nil | 	return | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (a AppService) SyncAppListFromLocal() { | ||||||
|  | 	fileOp := files.NewFileOp() | ||||||
|  | 	localAppDir := constant.LocalAppResourceDir | ||||||
|  | 	if !fileOp.Stat(localAppDir) { | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	var ( | ||||||
|  | 		err        error | ||||||
|  | 		dirEntries []os.DirEntry | ||||||
|  | 		localApps  []model.App | ||||||
|  | 	) | ||||||
|  |  | ||||||
|  | 	defer func() { | ||||||
|  | 		if err != nil { | ||||||
|  | 			global.LOG.Errorf("sync app failed %v", err) | ||||||
|  | 		} | ||||||
|  | 	}() | ||||||
|  |  | ||||||
|  | 	global.LOG.Infof("start sync local apps...") | ||||||
|  | 	dirEntries, err = os.ReadDir(localAppDir) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	for _, dirEntry := range dirEntries { | ||||||
|  | 		if dirEntry.IsDir() { | ||||||
|  | 			appDir := path.Join(localAppDir, dirEntry.Name()) | ||||||
|  | 			appDirEntries, err := os.ReadDir(appDir) | ||||||
|  | 			if err != nil { | ||||||
|  | 				global.LOG.Errorf(i18n.GetMsgWithMap("ErrAppDirNull", map[string]interface{}{"name": dirEntry.Name(), "err": err.Error()})) | ||||||
|  | 				continue | ||||||
|  | 			} | ||||||
|  | 			app, err := handleLocalApp(appDir) | ||||||
|  | 			if err != nil { | ||||||
|  | 				global.LOG.Errorf(i18n.GetMsgWithMap("LocalAppErr", map[string]interface{}{"name": dirEntry.Name(), "err": err.Error()})) | ||||||
|  | 				continue | ||||||
|  | 			} | ||||||
|  | 			var appDetails []model.AppDetail | ||||||
|  | 			for _, appDirEntry := range appDirEntries { | ||||||
|  | 				if appDirEntry.IsDir() { | ||||||
|  | 					appDetail := model.AppDetail{ | ||||||
|  | 						Version: appDirEntry.Name(), | ||||||
|  | 						Status:  constant.AppNormal, | ||||||
|  | 					} | ||||||
|  | 					versionDir := path.Join(appDir, appDirEntry.Name()) | ||||||
|  | 					if err = handleLocalAppDetail(versionDir, &appDetail); err != nil { | ||||||
|  | 						global.LOG.Errorf(i18n.GetMsgWithMap("LocalAppVersionErr", map[string]interface{}{"name": app.Name, "version": appDetail.Version, "err": err.Error()})) | ||||||
|  | 						continue | ||||||
|  | 					} | ||||||
|  | 					appDetails = append(appDetails, appDetail) | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 			if len(appDetails) > 0 { | ||||||
|  | 				app.Details = appDetails | ||||||
|  | 				localApps = append(localApps, *app) | ||||||
|  | 			} else { | ||||||
|  | 				global.LOG.Errorf(i18n.GetMsgWithMap("LocalAppVersionNull", map[string]interface{}{"name": app.Name})) | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	var ( | ||||||
|  | 		newApps    []model.App | ||||||
|  | 		deleteApps []model.App | ||||||
|  | 		updateApps []model.App | ||||||
|  | 		oldAppIds  []uint | ||||||
|  |  | ||||||
|  | 		deleteAppIds     []uint | ||||||
|  | 		deleteAppDetails []model.AppDetail | ||||||
|  | 		newAppDetails    []model.AppDetail | ||||||
|  | 		updateDetails    []model.AppDetail | ||||||
|  |  | ||||||
|  | 		appTags []*model.AppTag | ||||||
|  | 	) | ||||||
|  |  | ||||||
|  | 	oldApps, _ := appRepo.GetBy(appRepo.WithResource(constant.AppResourceLocal)) | ||||||
|  | 	apps := make(map[string]model.App, len(oldApps)) | ||||||
|  | 	for _, old := range oldApps { | ||||||
|  | 		old.Status = constant.AppTakeDown | ||||||
|  | 		apps[old.Key] = old | ||||||
|  | 	} | ||||||
|  | 	for _, app := range localApps { | ||||||
|  | 		if oldApp, ok := apps[app.Key]; ok { | ||||||
|  | 			app.ID = oldApp.ID | ||||||
|  | 			appDetails := make(map[string]model.AppDetail, len(oldApp.Details)) | ||||||
|  | 			for _, old := range oldApp.Details { | ||||||
|  | 				old.Status = constant.AppTakeDown | ||||||
|  | 				appDetails[old.Version] = old | ||||||
|  | 			} | ||||||
|  | 			for i, newDetail := range app.Details { | ||||||
|  | 				version := newDetail.Version | ||||||
|  | 				newDetail.Status = constant.AppNormal | ||||||
|  | 				newDetail.AppId = app.ID | ||||||
|  | 				oldDetail, exist := appDetails[version] | ||||||
|  | 				if exist { | ||||||
|  | 					newDetail.ID = oldDetail.ID | ||||||
|  | 					delete(appDetails, version) | ||||||
|  | 				} | ||||||
|  | 				app.Details[i] = newDetail | ||||||
|  | 			} | ||||||
|  | 			for _, v := range appDetails { | ||||||
|  | 				app.Details = append(app.Details, v) | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 		app.TagsKey = append(app.TagsKey, constant.AppResourceLocal) | ||||||
|  | 		apps[app.Key] = app | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	for _, app := range apps { | ||||||
|  | 		if app.ID == 0 { | ||||||
|  | 			newApps = append(newApps, app) | ||||||
|  | 		} else { | ||||||
|  | 			oldAppIds = append(oldAppIds, app.ID) | ||||||
|  | 			if app.Status == constant.AppTakeDown { | ||||||
|  | 				installs, _ := appInstallRepo.ListBy(appInstallRepo.WithAppId(app.ID)) | ||||||
|  | 				if len(installs) > 0 { | ||||||
|  | 					updateApps = append(updateApps, app) | ||||||
|  | 					continue | ||||||
|  | 				} | ||||||
|  | 				deleteAppIds = append(deleteAppIds, app.ID) | ||||||
|  | 				deleteApps = append(deleteApps, app) | ||||||
|  | 				deleteAppDetails = append(deleteAppDetails, app.Details...) | ||||||
|  | 			} else { | ||||||
|  | 				updateApps = append(updateApps, app) | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	tags, _ := tagRepo.All() | ||||||
|  | 	tagMap := make(map[string]uint, len(tags)) | ||||||
|  | 	for _, tag := range tags { | ||||||
|  | 		tagMap[tag.Key] = tag.ID | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	tx, ctx := getTxAndContext() | ||||||
|  | 	defer tx.Rollback() | ||||||
|  | 	if len(newApps) > 0 { | ||||||
|  | 		if err = appRepo.BatchCreate(ctx, newApps); err != nil { | ||||||
|  | 			return | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	for _, update := range updateApps { | ||||||
|  | 		if err = appRepo.Save(ctx, &update); err != nil { | ||||||
|  | 			return | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	if len(deleteApps) > 0 { | ||||||
|  | 		if err = appRepo.BatchDelete(ctx, deleteApps); err != nil { | ||||||
|  | 			return | ||||||
|  | 		} | ||||||
|  | 		if err = appDetailRepo.DeleteByAppIds(ctx, deleteAppIds); err != nil { | ||||||
|  | 			return | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if err = appTagRepo.DeleteByAppIds(ctx, oldAppIds); err != nil { | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	for _, newApp := range newApps { | ||||||
|  | 		if newApp.ID > 0 { | ||||||
|  | 			for _, detail := range newApp.Details { | ||||||
|  | 				detail.AppId = newApp.ID | ||||||
|  | 				newAppDetails = append(newAppDetails, detail) | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	for _, update := range updateApps { | ||||||
|  | 		for _, detail := range update.Details { | ||||||
|  | 			if detail.ID == 0 { | ||||||
|  | 				detail.AppId = update.ID | ||||||
|  | 				newAppDetails = append(newAppDetails, detail) | ||||||
|  | 			} else { | ||||||
|  | 				if detail.Status == constant.AppNormal { | ||||||
|  | 					updateDetails = append(updateDetails, detail) | ||||||
|  | 				} else { | ||||||
|  | 					deleteAppDetails = append(deleteAppDetails, detail) | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	allApps := append(newApps, updateApps...) | ||||||
|  | 	for _, app := range allApps { | ||||||
|  | 		for _, t := range app.TagsKey { | ||||||
|  | 			tagId, ok := tagMap[t] | ||||||
|  | 			if ok { | ||||||
|  | 				appTags = append(appTags, &model.AppTag{ | ||||||
|  | 					AppId: app.ID, | ||||||
|  | 					TagId: tagId, | ||||||
|  | 				}) | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if len(newAppDetails) > 0 { | ||||||
|  | 		if err = appDetailRepo.BatchCreate(ctx, newAppDetails); err != nil { | ||||||
|  | 			return | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	for _, updateAppDetail := range updateDetails { | ||||||
|  | 		if err = appDetailRepo.Update(ctx, updateAppDetail); err != nil { | ||||||
|  | 			return | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if len(deleteAppDetails) > 0 { | ||||||
|  | 		if err = appDetailRepo.BatchDelete(ctx, deleteAppDetails); err != nil { | ||||||
|  | 			return | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if len(oldAppIds) > 0 { | ||||||
|  | 		if err = appTagRepo.DeleteByAppIds(ctx, oldAppIds); err != nil { | ||||||
|  | 			return | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if len(appTags) > 0 { | ||||||
|  | 		if err = appTagRepo.BatchCreate(ctx, appTags); err != nil { | ||||||
|  | 			return | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	tx.Commit() | ||||||
|  | 	global.LOG.Infof("sync local apps success") | ||||||
| } | } | ||||||
|  |  | ||||||
| func (a AppService) GetAppUpdate() (*response.AppUpdateRes, error) { | func (a AppService) GetAppUpdate() (*response.AppUpdateRes, error) { | ||||||
| @@ -270,45 +646,59 @@ func (a AppService) GetAppUpdate() (*response.AppUpdateRes, error) { | |||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return nil, err | 		return nil, err | ||||||
| 	} | 	} | ||||||
| 	versionUrl := fmt.Sprintf("%s/%s/%s/appstore/apps.json", global.CONF.System.RepoUrl, global.CONF.System.Mode, setting.SystemVersion) | 	versionUrl := fmt.Sprintf("%s/%s/1panel.json.version.txt", global.CONF.System.AppRepo, global.CONF.System.Mode) | ||||||
| 	versionRes, err := http.Get(versionUrl) | 	versionRes, err := http2.GetHttpRes(versionUrl) | ||||||
| 	global.LOG.Infof("get current version from [%s]", versionUrl) |  | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return nil, err | 		return nil, err | ||||||
| 	} | 	} | ||||||
| 	defer versionRes.Body.Close() | 	defer versionRes.Body.Close() | ||||||
| 	body, err := ioutil.ReadAll(versionRes.Body) | 	body, err := io.ReadAll(versionRes.Body) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return nil, err | 		return nil, err | ||||||
| 	} | 	} | ||||||
| 	list := &dto.AppList{} | 	lastModifiedStr := string(body) | ||||||
| 	if err = json.Unmarshal(body, list); err != nil { |  | ||||||
|  | 	lastModified, err := strconv.Atoi(lastModifiedStr) | ||||||
|  | 	if err != nil { | ||||||
| 		return nil, err | 		return nil, err | ||||||
| 	} | 	} | ||||||
| 	res.Version = list.Version | 	appStoreLastModified, _ := strconv.Atoi(setting.AppStoreLastModified) | ||||||
| 	if setting.AppStoreVersion == "" || common.CompareVersion(list.Version, setting.AppStoreVersion) { | 	if setting.AppStoreLastModified == "" || lastModified != appStoreLastModified { | ||||||
| 		res.CanUpdate = true | 		res.CanUpdate = true | ||||||
| 		res.DownloadPath = fmt.Sprintf("%s/%s/%s/appstore/apps-%s.tar.gz", global.CONF.System.RepoUrl, global.CONF.System.Mode, setting.SystemVersion, list.Version) |  | ||||||
| 		return res, err | 		return res, err | ||||||
| 	} | 	} | ||||||
| 	return res, nil | 	return res, nil | ||||||
| } | } | ||||||
|  |  | ||||||
| func (a AppService) SyncAppList() error { | func getAppFromRepo(downloadPath string) error { | ||||||
|  | 	downloadUrl := downloadPath | ||||||
|  | 	global.LOG.Infof("download file from %s", downloadUrl) | ||||||
|  | 	fileOp := files.NewFileOp() | ||||||
|  | 	packagePath := path.Join(constant.ResourceDir, path.Base(downloadUrl)) | ||||||
|  | 	if err := fileOp.DownloadFile(downloadUrl, packagePath); err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 	if err := fileOp.Decompress(packagePath, constant.ResourceDir, files.Zip); err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 	defer func() { | ||||||
|  | 		_ = fileOp.DeleteFile(packagePath) | ||||||
|  | 	}() | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (a AppService) SyncAppListFromRemote() error { | ||||||
| 	updateRes, err := a.GetAppUpdate() | 	updateRes, err := a.GetAppUpdate() | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return err | 		return err | ||||||
| 	} | 	} | ||||||
| 	if !updateRes.CanUpdate { | 	if !updateRes.CanUpdate { | ||||||
| 		global.LOG.Infof("The latest version is [%s] The app store is already up to date", updateRes.Version) |  | ||||||
| 		return nil | 		return nil | ||||||
| 	} | 	} | ||||||
| 	if err := getAppFromRepo(updateRes.DownloadPath, updateRes.Version); err != nil { | 	if err = getAppFromRepo(fmt.Sprintf("%s/%s/1panel.json.zip", global.CONF.System.AppRepo, global.CONF.System.Mode)); err != nil { | ||||||
| 		global.LOG.Errorf("get app from oss  error: %s", err.Error()) |  | ||||||
| 		return err | 		return err | ||||||
| 	} | 	} | ||||||
| 	appDir := constant.AppResourceDir | 	listFile := path.Join(constant.ResourceDir, "1panel.json") | ||||||
| 	listFile := path.Join(appDir, "list.json") |  | ||||||
| 	content, err := os.ReadFile(listFile) | 	content, err := os.ReadFile(listFile) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return err | 		return err | ||||||
| @@ -317,158 +707,207 @@ func (a AppService) SyncAppList() error { | |||||||
| 	if err := json.Unmarshal(content, list); err != nil { | 	if err := json.Unmarshal(content, list); err != nil { | ||||||
| 		return err | 		return err | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	var ( | 	var ( | ||||||
| 		tags    []*model.Tag | 		tags      []*model.Tag | ||||||
| 		appTags []*model.AppTag | 		appTags   []*model.AppTag | ||||||
|  | 		oldAppIds []uint | ||||||
| 	) | 	) | ||||||
| 	for _, t := range list.Tags { | 	for _, t := range list.Extra.Tags { | ||||||
| 		tags = append(tags, &model.Tag{ | 		tags = append(tags, &model.Tag{ | ||||||
| 			Key:  t.Key, | 			Key:  t.Key, | ||||||
| 			Name: t.Name, | 			Name: t.Name, | ||||||
| 		}) | 		}) | ||||||
| 	} | 	} | ||||||
| 	oldApps, err := appRepo.GetBy() | 	oldApps, err := appRepo.GetBy(appRepo.WithResource(constant.AppResourceRemote)) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return err | 		return err | ||||||
| 	} | 	} | ||||||
| 	appsMap := getApps(oldApps, list.Items) | 	for _, old := range oldApps { | ||||||
| 	for _, l := range list.Items { | 		oldAppIds = append(oldAppIds, old.ID) | ||||||
| 		app := appsMap[l.Key] | 	} | ||||||
| 		icon, err := os.ReadFile(path.Join(appDir, l.Key, "metadata", "logo.png")) |  | ||||||
|  | 	baseRemoteUrl := fmt.Sprintf("%s/%s/1panel", global.CONF.System.AppRepo, global.CONF.System.Mode) | ||||||
|  | 	appsMap := getApps(oldApps, list.Apps) | ||||||
|  | 	for _, l := range list.Apps { | ||||||
|  | 		app := appsMap[l.AppProperty.Key] | ||||||
|  | 		iconRes, err := http.Get(l.Icon) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			global.LOG.Errorf("get [%s] icon error: %s", l.Name, err.Error()) | 			return err | ||||||
| 			continue |  | ||||||
| 		} | 		} | ||||||
| 		iconStr := base64.StdEncoding.EncodeToString(icon) | 		body, err := io.ReadAll(iconRes.Body) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return err | ||||||
|  | 		} | ||||||
|  | 		iconStr := base64.StdEncoding.EncodeToString(body) | ||||||
| 		app.Icon = iconStr | 		app.Icon = iconStr | ||||||
| 		app.TagsKey = l.Tags | 		app.TagsKey = l.AppProperty.Tags | ||||||
| 		if l.Recommend > 0 { | 		if l.AppProperty.Recommend > 0 { | ||||||
| 			app.Recommend = l.Recommend | 			app.Recommend = l.AppProperty.Recommend | ||||||
| 		} else { | 		} else { | ||||||
| 			app.Recommend = 9999 | 			app.Recommend = 9999 | ||||||
| 		} | 		} | ||||||
|  | 		app.ReadMe = l.ReadMe | ||||||
|  | 		app.LastModified = l.LastModified | ||||||
| 		versions := l.Versions | 		versions := l.Versions | ||||||
| 		detailsMap := getAppDetails(app.Details, versions) | 		detailsMap := getAppDetails(app.Details, versions) | ||||||
|  |  | ||||||
| 		for _, v := range versions { | 		for _, v := range versions { | ||||||
| 			detail := detailsMap[v] | 			version := v.Name | ||||||
| 			detailPath := path.Join(appDir, l.Key, "versions", v) | 			detail := detailsMap[version] | ||||||
| 			if _, err := os.Stat(detailPath); err != nil { |  | ||||||
| 				global.LOG.Errorf("get [%s] folder error: %s", detailPath, err.Error()) | 			dockerComposeUrl := fmt.Sprintf("%s/%s/%s/%s", baseRemoteUrl, app.Key, version, "docker-compose.yml") | ||||||
| 				continue | 			composeRes, err := http.Get(dockerComposeUrl) | ||||||
| 			} |  | ||||||
| 			readmeStr, err := os.ReadFile(path.Join(detailPath, "README.md")) |  | ||||||
| 			if err != nil { | 			if err != nil { | ||||||
| 				global.LOG.Errorf("get [%s] README error: %s", detailPath, err.Error()) | 				return err | ||||||
| 			} | 			} | ||||||
| 			detail.Readme = string(readmeStr) | 			bodyContent, err := io.ReadAll(composeRes.Body) | ||||||
| 			dockerComposeStr, err := os.ReadFile(path.Join(detailPath, "docker-compose.yml")) |  | ||||||
| 			if err != nil { | 			if err != nil { | ||||||
| 				global.LOG.Errorf("get [%s] docker-compose.yml error: %s", detailPath, err.Error()) | 				return err | ||||||
| 				continue |  | ||||||
| 			} | 			} | ||||||
| 			detail.DockerCompose = string(dockerComposeStr) | 			detail.DockerCompose = string(bodyContent) | ||||||
| 			paramStr, err := os.ReadFile(path.Join(detailPath, "config.json")) |  | ||||||
| 			if err != nil { | 			paramByte, _ := json.Marshal(v.AppForm) | ||||||
| 				global.LOG.Errorf("get [%s] form.json error: %s", detailPath, err.Error()) | 			detail.Params = string(paramByte) | ||||||
| 			} | 			detail.DownloadUrl = v.DownloadUrl | ||||||
| 			detail.Params = string(paramStr) | 			detail.DownloadCallBackUrl = v.DownloadCallBackUrl | ||||||
| 			detailsMap[v] = detail | 			detail.Update = true | ||||||
|  | 			detail.LastModified = v.LastModified | ||||||
|  | 			detailsMap[version] = detail | ||||||
| 		} | 		} | ||||||
| 		var newDetails []model.AppDetail | 		var newDetails []model.AppDetail | ||||||
| 		for _, v := range detailsMap { | 		for _, detail := range detailsMap { | ||||||
| 			newDetails = append(newDetails, v) | 			newDetails = append(newDetails, detail) | ||||||
| 		} | 		} | ||||||
| 		app.Details = newDetails | 		app.Details = newDetails | ||||||
| 		appsMap[l.Key] = app | 		appsMap[l.AppProperty.Key] = app | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	var ( | 	var ( | ||||||
| 		addAppArray []model.App | 		addAppArray    []model.App | ||||||
| 		updateArray []model.App | 		updateAppArray []model.App | ||||||
|  | 		deleteAppArray []model.App | ||||||
|  | 		deleteIds      []uint | ||||||
|  | 		tagMap         = make(map[string]uint, len(tags)) | ||||||
| 	) | 	) | ||||||
| 	tagMap := make(map[string]uint, len(tags)) |  | ||||||
| 	for _, v := range appsMap { | 	for _, v := range appsMap { | ||||||
| 		if v.ID == 0 { | 		if v.ID == 0 { | ||||||
| 			addAppArray = append(addAppArray, v) | 			addAppArray = append(addAppArray, v) | ||||||
| 		} else { | 		} else { | ||||||
| 			updateArray = append(updateArray, v) | 			if v.Status == constant.AppTakeDown { | ||||||
|  | 				installs, _ := appInstallRepo.ListBy(appInstallRepo.WithAppId(v.ID)) | ||||||
|  | 				if len(installs) > 0 { | ||||||
|  | 					updateAppArray = append(updateAppArray, v) | ||||||
|  | 					continue | ||||||
|  | 				} | ||||||
|  | 				deleteAppArray = append(deleteAppArray, v) | ||||||
|  | 				deleteIds = append(deleteIds, v.ID) | ||||||
|  | 			} else { | ||||||
|  | 				updateAppArray = append(updateAppArray, v) | ||||||
|  | 			} | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 	tx, ctx := getTxAndContext() | 	tx, ctx := getTxAndContext() | ||||||
|  | 	defer tx.Rollback() | ||||||
| 	if len(addAppArray) > 0 { | 	if len(addAppArray) > 0 { | ||||||
| 		if err := appRepo.BatchCreate(ctx, addAppArray); err != nil { | 		if err := appRepo.BatchCreate(ctx, addAppArray); err != nil { | ||||||
| 			tx.Rollback() | 			return err | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	if len(deleteAppArray) > 0 { | ||||||
|  | 		if err := appRepo.BatchDelete(ctx, deleteAppArray); err != nil { | ||||||
|  | 			return err | ||||||
|  | 		} | ||||||
|  | 		if err := appDetailRepo.DeleteByAppIds(ctx, deleteIds); err != nil { | ||||||
| 			return err | 			return err | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 	if err := tagRepo.DeleteAll(ctx); err != nil { | 	if err := tagRepo.DeleteAll(ctx); err != nil { | ||||||
| 		tx.Rollback() |  | ||||||
| 		return err | 		return err | ||||||
| 	} | 	} | ||||||
| 	if len(tags) > 0 { | 	if len(tags) > 0 { | ||||||
| 		if err := tagRepo.BatchCreate(ctx, tags); err != nil { | 		if err := tagRepo.BatchCreate(ctx, tags); err != nil { | ||||||
| 			tx.Rollback() |  | ||||||
| 			return err | 			return err | ||||||
| 		} | 		} | ||||||
| 		for _, t := range tags { | 		for _, t := range tags { | ||||||
| 			tagMap[t.Key] = t.ID | 			tagMap[t.Key] = t.ID | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 	for _, update := range updateArray { | 	for _, update := range updateAppArray { | ||||||
| 		if err := appRepo.Save(ctx, &update); err != nil { | 		if err := appRepo.Save(ctx, &update); err != nil { | ||||||
| 			tx.Rollback() |  | ||||||
| 			return err | 			return err | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 	apps := append(addAppArray, updateArray...) | 	apps := append(addAppArray, updateAppArray...) | ||||||
|  |  | ||||||
| 	var ( | 	var ( | ||||||
| 		addDetails    []model.AppDetail | 		addDetails    []model.AppDetail | ||||||
| 		updateDetails []model.AppDetail | 		updateDetails []model.AppDetail | ||||||
|  | 		deleteDetails []model.AppDetail | ||||||
| 	) | 	) | ||||||
| 	for _, a := range apps { | 	for _, app := range apps { | ||||||
| 		for _, t := range a.TagsKey { | 		for _, t := range app.TagsKey { | ||||||
| 			tagId, ok := tagMap[t] | 			tagId, ok := tagMap[t] | ||||||
| 			if ok { | 			if ok { | ||||||
| 				appTags = append(appTags, &model.AppTag{ | 				appTags = append(appTags, &model.AppTag{ | ||||||
| 					AppId: a.ID, | 					AppId: app.ID, | ||||||
| 					TagId: tagId, | 					TagId: tagId, | ||||||
| 				}) | 				}) | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
| 		for _, d := range a.Details { | 		for _, d := range app.Details { | ||||||
| 			d.AppId = a.ID | 			d.AppId = app.ID | ||||||
| 			if d.ID == 0 { | 			if d.ID == 0 { | ||||||
| 				addDetails = append(addDetails, d) | 				addDetails = append(addDetails, d) | ||||||
| 			} else { | 			} else { | ||||||
| 				updateDetails = append(updateDetails, d) | 				if d.Status == constant.AppTakeDown { | ||||||
|  | 					runtime, _ := runtimeRepo.GetFirst(runtimeRepo.WithDetailId(d.ID)) | ||||||
|  | 					if runtime != nil { | ||||||
|  | 						updateDetails = append(updateDetails, d) | ||||||
|  | 						continue | ||||||
|  | 					} | ||||||
|  | 					installs, _ := appInstallRepo.ListBy(appInstallRepo.WithDetailIdsIn([]uint{d.ID})) | ||||||
|  | 					if len(installs) > 0 { | ||||||
|  | 						updateDetails = append(updateDetails, d) | ||||||
|  | 						continue | ||||||
|  | 					} | ||||||
|  | 					deleteDetails = append(deleteDetails, d) | ||||||
|  | 				} else { | ||||||
|  | 					updateDetails = append(updateDetails, d) | ||||||
|  | 				} | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 	if len(addDetails) > 0 { | 	if len(addDetails) > 0 { | ||||||
| 		if err := appDetailRepo.BatchCreate(ctx, addDetails); err != nil { | 		if err := appDetailRepo.BatchCreate(ctx, addDetails); err != nil { | ||||||
| 			tx.Rollback() | 			return err | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	if len(deleteDetails) > 0 { | ||||||
|  | 		if err := appDetailRepo.BatchDelete(ctx, deleteDetails); err != nil { | ||||||
| 			return err | 			return err | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 	for _, u := range updateDetails { | 	for _, u := range updateDetails { | ||||||
| 		if err := appDetailRepo.Update(ctx, u); err != nil { | 		if err := appDetailRepo.Update(ctx, u); err != nil { | ||||||
| 			tx.Rollback() |  | ||||||
| 			return err | 			return err | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 	if err := appTagRepo.DeleteAll(ctx); err != nil { |  | ||||||
| 		tx.Rollback() | 	if len(oldAppIds) > 0 { | ||||||
| 		return err | 		if err := appTagRepo.DeleteByAppIds(ctx, oldAppIds); err != nil { | ||||||
|  | 			return err | ||||||
|  | 		} | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	if len(appTags) > 0 { | 	if len(appTags) > 0 { | ||||||
| 		if err := appTagRepo.BatchCreate(ctx, appTags); err != nil { | 		if err := appTagRepo.BatchCreate(ctx, appTags); err != nil { | ||||||
| 			tx.Rollback() |  | ||||||
| 			return err | 			return err | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 	tx.Commit() | 	tx.Commit() | ||||||
|  | 	if err := NewISettingService().Update("AppStoreLastModified", strconv.Itoa(list.LastModified)); err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
|   | |||||||
| @@ -1,9 +1,10 @@ | |||||||
| package service | package service | ||||||
|  |  | ||||||
| import ( | import ( | ||||||
|  | 	"context" | ||||||
| 	"encoding/json" | 	"encoding/json" | ||||||
| 	"fmt" | 	"fmt" | ||||||
| 	"io/ioutil" | 	"github.com/1Panel-dev/1Panel/backend/i18n" | ||||||
| 	"math" | 	"math" | ||||||
| 	"os" | 	"os" | ||||||
| 	"path" | 	"path" | ||||||
| @@ -11,6 +12,9 @@ import ( | |||||||
| 	"strconv" | 	"strconv" | ||||||
| 	"strings" | 	"strings" | ||||||
|  |  | ||||||
|  | 	"github.com/1Panel-dev/1Panel/backend/utils/files" | ||||||
|  | 	"gopkg.in/yaml.v3" | ||||||
|  |  | ||||||
| 	"github.com/1Panel-dev/1Panel/backend/utils/env" | 	"github.com/1Panel-dev/1Panel/backend/utils/env" | ||||||
| 	"github.com/1Panel-dev/1Panel/backend/utils/nginx" | 	"github.com/1Panel-dev/1Panel/backend/utils/nginx" | ||||||
| 	"github.com/joho/godotenv" | 	"github.com/joho/godotenv" | ||||||
| @@ -38,14 +42,15 @@ type IAppInstallService interface { | |||||||
| 	Page(req request.AppInstalledSearch) (int64, []response.AppInstalledDTO, error) | 	Page(req request.AppInstalledSearch) (int64, []response.AppInstalledDTO, error) | ||||||
| 	CheckExist(key string) (*response.AppInstalledCheck, error) | 	CheckExist(key string) (*response.AppInstalledCheck, error) | ||||||
| 	LoadPort(key string) (int64, error) | 	LoadPort(key string) (int64, error) | ||||||
| 	LoadPassword(key string) (string, error) | 	LoadConnInfo(key string) (response.DatabaseConn, error) | ||||||
| 	SearchForWebsite(req request.AppInstalledSearch) ([]response.AppInstalledDTO, error) | 	SearchForWebsite(req request.AppInstalledSearch) ([]response.AppInstalledDTO, error) | ||||||
| 	Operate(req request.AppInstalledOperate) error | 	Operate(req request.AppInstalledOperate) error | ||||||
| 	Update(req request.AppInstalledUpdate) error | 	Update(req request.AppInstalledUpdate) error | ||||||
|  | 	IgnoreUpgrade(req request.AppInstalledIgnoreUpgrade) error | ||||||
| 	SyncAll(systemInit bool) error | 	SyncAll(systemInit bool) error | ||||||
| 	GetServices(key string) ([]response.AppService, error) | 	GetServices(key string) ([]response.AppService, error) | ||||||
| 	GetUpdateVersions(installId uint) ([]dto.AppVersion, error) | 	GetUpdateVersions(installId uint) ([]dto.AppVersion, error) | ||||||
| 	GetParams(id uint) ([]response.AppParam, error) | 	GetParams(id uint) (*response.AppConfig, error) | ||||||
| 	ChangeAppPort(req request.PortUpdate) error | 	ChangeAppPort(req request.PortUpdate) error | ||||||
| 	GetDefaultConfigByKey(key string) (string, error) | 	GetDefaultConfigByKey(key string) (string, error) | ||||||
| 	DeleteCheck(installId uint) ([]dto.AppResource, error) | 	DeleteCheck(installId uint) ([]dto.AppResource, error) | ||||||
| @@ -133,12 +138,16 @@ func (a *AppInstallService) LoadPort(key string) (int64, error) { | |||||||
| 	return app.Port, nil | 	return app.Port, nil | ||||||
| } | } | ||||||
|  |  | ||||||
| func (a *AppInstallService) LoadPassword(key string) (string, error) { | func (a *AppInstallService) LoadConnInfo(key string) (response.DatabaseConn, error) { | ||||||
|  | 	var data response.DatabaseConn | ||||||
| 	app, err := appInstallRepo.LoadBaseInfo(key, "") | 	app, err := appInstallRepo.LoadBaseInfo(key, "") | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return "", nil | 		return data, nil | ||||||
| 	} | 	} | ||||||
| 	return app.Password, nil | 	data.Password = app.Password | ||||||
|  | 	data.ServiceName = app.ServiceName | ||||||
|  | 	data.Port = app.Port | ||||||
|  | 	return data, nil | ||||||
| } | } | ||||||
|  |  | ||||||
| func (a *AppInstallService) SearchForWebsite(req request.AppInstalledSearch) ([]response.AppInstalledDTO, error) { | func (a *AppInstallService) SearchForWebsite(req request.AppInstalledSearch) ([]response.AppInstalledDTO, error) { | ||||||
| @@ -175,10 +184,13 @@ func (a *AppInstallService) SearchForWebsite(req request.AppInstalledSearch) ([] | |||||||
| } | } | ||||||
|  |  | ||||||
| func (a *AppInstallService) Operate(req request.AppInstalledOperate) error { | func (a *AppInstallService) Operate(req request.AppInstalledOperate) error { | ||||||
| 	install, err := appInstallRepo.GetFirst(commonRepo.WithByID(req.InstallId)) | 	install, err := appInstallRepo.GetFirstByCtx(context.Background(), commonRepo.WithByID(req.InstallId)) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return err | 		return err | ||||||
| 	} | 	} | ||||||
|  | 	if !req.ForceDelete && !files.NewFileOp().Stat(install.GetPath()) { | ||||||
|  | 		return buserr.New(constant.ErrInstallDirNotFound) | ||||||
|  | 	} | ||||||
| 	dockerComposePath := install.GetComposePath() | 	dockerComposePath := install.GetComposePath() | ||||||
| 	switch req.Operate { | 	switch req.Operate { | ||||||
| 	case constant.Rebuild: | 	case constant.Rebuild: | ||||||
| @@ -202,17 +214,14 @@ func (a *AppInstallService) Operate(req request.AppInstalledOperate) error { | |||||||
| 		} | 		} | ||||||
| 		return syncById(install.ID) | 		return syncById(install.ID) | ||||||
| 	case constant.Delete: | 	case constant.Delete: | ||||||
| 		tx, ctx := getTxAndContext() | 		if err := deleteAppInstall(install, req.DeleteBackup, req.ForceDelete, req.DeleteDB); err != nil && !req.ForceDelete { | ||||||
| 		if err := deleteAppInstall(ctx, install, req.DeleteBackup, req.ForceDelete, req.DeleteDB); err != nil && !req.ForceDelete { |  | ||||||
| 			tx.Rollback() |  | ||||||
| 			return err | 			return err | ||||||
| 		} | 		} | ||||||
| 		tx.Commit() |  | ||||||
| 		return nil | 		return nil | ||||||
| 	case constant.Sync: | 	case constant.Sync: | ||||||
| 		return syncById(install.ID) | 		return syncById(install.ID) | ||||||
| 	case constant.Upgrade: | 	case constant.Upgrade: | ||||||
| 		return updateInstall(install.ID, req.DetailId) | 		return upgradeInstall(install.ID, req.DetailId) | ||||||
| 	default: | 	default: | ||||||
| 		return errors.New("operate not support") | 		return errors.New("operate not support") | ||||||
| 	} | 	} | ||||||
| @@ -248,11 +257,53 @@ func (a *AppInstallService) Update(req request.AppInstalledUpdate) error { | |||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | 	backupDockerCompose := installed.DockerCompose | ||||||
|  | 	if req.Advanced { | ||||||
|  | 		composeMap := make(map[string]interface{}) | ||||||
|  | 		if req.EditCompose { | ||||||
|  | 			if err = yaml.Unmarshal([]byte(req.DockerCompose), &composeMap); err != nil { | ||||||
|  | 				return err | ||||||
|  | 			} | ||||||
|  | 		} else { | ||||||
|  | 			if err = yaml.Unmarshal([]byte(installed.DockerCompose), &composeMap); err != nil { | ||||||
|  | 				return err | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 		if err = addDockerComposeCommonParam(composeMap, installed.ServiceName, req.AppContainerConfig, req.Params); err != nil { | ||||||
|  | 			return err | ||||||
|  | 		} | ||||||
|  | 		composeByte, err := yaml.Marshal(composeMap) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return err | ||||||
|  | 		} | ||||||
|  | 		installed.DockerCompose = string(composeByte) | ||||||
|  | 		if req.ContainerName == "" { | ||||||
|  | 			req.Params[constant.ContainerName] = installed.ContainerName | ||||||
|  | 		} else { | ||||||
|  | 			req.Params[constant.ContainerName] = req.ContainerName | ||||||
|  | 			if installed.ContainerName != req.ContainerName { | ||||||
|  | 				exist, _ := appInstallRepo.GetFirst(appInstallRepo.WithContainerName(req.ContainerName), appInstallRepo.WithIDNotIs(installed.ID)) | ||||||
|  | 				if exist.ID > 0 { | ||||||
|  | 					return buserr.New(constant.ErrContainerName) | ||||||
|  | 				} | ||||||
|  | 				containerExist, err := checkContainerNameIsExist(req.ContainerName, installed.GetPath()) | ||||||
|  | 				if err != nil { | ||||||
|  | 					return err | ||||||
|  | 				} | ||||||
|  | 				if containerExist { | ||||||
|  | 					return buserr.New(constant.ErrContainerName) | ||||||
|  | 				} | ||||||
|  | 				installed.ContainerName = req.ContainerName | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
| 	envPath := path.Join(installed.GetPath(), ".env") | 	envPath := path.Join(installed.GetPath(), ".env") | ||||||
| 	oldEnvMaps, err := godotenv.Read(envPath) | 	oldEnvMaps, err := godotenv.Read(envPath) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return err | 		return err | ||||||
| 	} | 	} | ||||||
|  | 	backupEnvMaps := oldEnvMaps | ||||||
| 	handleMap(req.Params, oldEnvMaps) | 	handleMap(req.Params, oldEnvMaps) | ||||||
| 	paramByte, err := json.Marshal(oldEnvMaps) | 	paramByte, err := json.Marshal(oldEnvMaps) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| @@ -262,52 +313,74 @@ func (a *AppInstallService) Update(req request.AppInstalledUpdate) error { | |||||||
| 	if err := env.Write(oldEnvMaps, envPath); err != nil { | 	if err := env.Write(oldEnvMaps, envPath); err != nil { | ||||||
| 		return err | 		return err | ||||||
| 	} | 	} | ||||||
| 	_ = appInstallRepo.Save(&installed) | 	fileOp := files.NewFileOp() | ||||||
|  | 	_ = fileOp.WriteFile(installed.GetComposePath(), strings.NewReader(installed.DockerCompose), 0755) | ||||||
| 	if err := rebuildApp(installed); err != nil { | 	if err := rebuildApp(installed); err != nil { | ||||||
|  | 		_ = env.Write(backupEnvMaps, envPath) | ||||||
|  | 		_ = fileOp.WriteFile(installed.GetComposePath(), strings.NewReader(backupDockerCompose), 0755) | ||||||
| 		return err | 		return err | ||||||
| 	} | 	} | ||||||
|  | 	installed.Status = constant.Running | ||||||
|  | 	_ = appInstallRepo.Save(context.Background(), &installed) | ||||||
|  |  | ||||||
| 	website, _ := websiteRepo.GetFirst(websiteRepo.WithAppInstallId(installed.ID)) | 	website, _ := websiteRepo.GetFirst(websiteRepo.WithAppInstallId(installed.ID)) | ||||||
| 	if changePort && website.ID != 0 && website.Status == constant.Running { | 	if changePort && website.ID != 0 && website.Status == constant.Running { | ||||||
| 		nginxInstall, err := getNginxFull(&website) | 		go func() { | ||||||
| 		if err != nil { | 			nginxInstall, err := getNginxFull(&website) | ||||||
| 			return buserr.WithErr(constant.ErrUpdateBuWebsite, err) | 			if err != nil { | ||||||
| 		} | 				global.LOG.Errorf(buserr.WithErr(constant.ErrUpdateBuWebsite, err).Error()) | ||||||
| 		config := nginxInstall.SiteConfig.Config | 				return | ||||||
| 		servers := config.FindServers() | 			} | ||||||
| 		if len(servers) == 0 { | 			config := nginxInstall.SiteConfig.Config | ||||||
| 			return buserr.WithErr(constant.ErrUpdateBuWebsite, errors.New("nginx config is not valid")) | 			servers := config.FindServers() | ||||||
| 		} | 			if len(servers) == 0 { | ||||||
| 		server := servers[0] | 				global.LOG.Errorf(buserr.WithErr(constant.ErrUpdateBuWebsite, errors.New("nginx config is not valid")).Error()) | ||||||
| 		proxy := fmt.Sprintf("http://127.0.0.1:%d", installed.HttpPort) | 				return | ||||||
| 		server.UpdateRootProxy([]string{proxy}) | 			} | ||||||
|  | 			server := servers[0] | ||||||
|  | 			proxy := fmt.Sprintf("http://127.0.0.1:%d", installed.HttpPort) | ||||||
|  | 			server.UpdateRootProxy([]string{proxy}) | ||||||
|  |  | ||||||
| 		if err := nginx.WriteConfig(config, nginx.IndentedStyle); err != nil { | 			if err := nginx.WriteConfig(config, nginx.IndentedStyle); err != nil { | ||||||
| 			return buserr.WithErr(constant.ErrUpdateBuWebsite, err) | 				global.LOG.Errorf(buserr.WithErr(constant.ErrUpdateBuWebsite, err).Error()) | ||||||
| 		} | 				return | ||||||
| 		if err := nginxCheckAndReload(nginxInstall.SiteConfig.OldContent, config.FilePath, nginxInstall.Install.ContainerName); err != nil { | 			} | ||||||
| 			return buserr.WithErr(constant.ErrUpdateBuWebsite, err) | 			if err := nginxCheckAndReload(nginxInstall.SiteConfig.OldContent, config.FilePath, nginxInstall.Install.ContainerName); err != nil { | ||||||
| 		} | 				global.LOG.Errorf(buserr.WithErr(constant.ErrUpdateBuWebsite, err).Error()) | ||||||
|  | 				return | ||||||
|  | 			} | ||||||
|  | 		}() | ||||||
| 	} | 	} | ||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
|  |  | ||||||
|  | func (a *AppInstallService) IgnoreUpgrade(req request.AppInstalledIgnoreUpgrade) error { | ||||||
|  | 	appDetail, err := appDetailRepo.GetFirst(commonRepo.WithByID(req.DetailID)) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 	appDetail.IgnoreUpgrade = req.Operate == "ignore" | ||||||
|  | 	return appDetailRepo.Update(context.Background(), appDetail) | ||||||
|  | } | ||||||
|  |  | ||||||
| func (a *AppInstallService) SyncAll(systemInit bool) error { | func (a *AppInstallService) SyncAll(systemInit bool) error { | ||||||
| 	allList, err := appInstallRepo.ListBy() | 	allList, err := appInstallRepo.ListBy() | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return err | 		return err | ||||||
| 	} | 	} | ||||||
| 	for _, i := range allList { | 	for _, i := range allList { | ||||||
| 		if i.Status == constant.Installing { | 		if i.Status == constant.Installing || i.Status == constant.Upgrading { | ||||||
| 			if systemInit { | 			if systemInit { | ||||||
| 				i.Status = constant.Error | 				i.Status = constant.Error | ||||||
| 				i.Message = "System restart causes application exception" | 				i.Message = "1Panel restart causes the task to terminate" | ||||||
| 				_ = appInstallRepo.Save(&i) | 				_ = appInstallRepo.Save(context.Background(), &i) | ||||||
| 			} | 			} | ||||||
| 			continue | 			continue | ||||||
| 		} | 		} | ||||||
| 		if err := syncById(i.ID); err != nil { | 		if !systemInit { | ||||||
| 			global.LOG.Errorf("sync install app[%s] error,mgs: %s", i.Name, err.Error()) | 			if err := syncById(i.ID); err != nil { | ||||||
|  | 				global.LOG.Errorf("sync install app[%s] error,mgs: %s", i.Name, err.Error()) | ||||||
|  | 			} | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 	return nil | 	return nil | ||||||
| @@ -352,6 +425,12 @@ func (a *AppInstallService) GetUpdateVersions(installId uint) ([]dto.AppVersion, | |||||||
| 		return versions, err | 		return versions, err | ||||||
| 	} | 	} | ||||||
| 	for _, detail := range details { | 	for _, detail := range details { | ||||||
|  | 		if detail.IgnoreUpgrade { | ||||||
|  | 			continue | ||||||
|  | 		} | ||||||
|  | 		if common.IsCrossVersion(install.Version, detail.Version) && !app.CrossVersionUpdate { | ||||||
|  | 			continue | ||||||
|  | 		} | ||||||
| 		if common.CompareVersion(detail.Version, install.Version) { | 		if common.CompareVersion(detail.Version, install.Version) { | ||||||
| 			versions = append(versions, dto.AppVersion{ | 			versions = append(versions, dto.AppVersion{ | ||||||
| 				Version:  detail.Version, | 				Version:  detail.Version, | ||||||
| @@ -400,14 +479,12 @@ func (a *AppInstallService) DeleteCheck(installId uint) ([]dto.AppResource, erro | |||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return nil, err | 		return nil, err | ||||||
| 	} | 	} | ||||||
| 	if app.Type == "website" { | 	websites, _ := websiteRepo.GetBy(websiteRepo.WithAppInstallId(appInstall.ID)) | ||||||
| 		websites, _ := websiteRepo.GetBy(websiteRepo.WithAppInstallId(appInstall.ID)) | 	for _, website := range websites { | ||||||
| 		for _, website := range websites { | 		res = append(res, dto.AppResource{ | ||||||
| 			res = append(res, dto.AppResource{ | 			Type: "website", | ||||||
| 				Type: "website", | 			Name: website.PrimaryDomain, | ||||||
| 				Name: website.PrimaryDomain, | 		}) | ||||||
| 			}) |  | ||||||
| 		} |  | ||||||
| 	} | 	} | ||||||
| 	if app.Key == constant.AppOpenresty { | 	if app.Key == constant.AppOpenresty { | ||||||
| 		websites, _ := websiteRepo.GetBy() | 		websites, _ := websiteRepo.GetBy() | ||||||
| @@ -436,7 +513,16 @@ func (a *AppInstallService) GetDefaultConfigByKey(key string) (string, error) { | |||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return "", err | 		return "", err | ||||||
| 	} | 	} | ||||||
| 	filePath := path.Join(constant.AppResourceDir, appInstall.App.Key, "versions", appInstall.Version, "conf") |  | ||||||
|  | 	fileOp := files.NewFileOp() | ||||||
|  | 	filePath := path.Join(constant.AppResourceDir, "remote", appInstall.App.Key, appInstall.Version, "conf") | ||||||
|  | 	if !fileOp.Stat(filePath) { | ||||||
|  | 		filePath = path.Join(constant.AppResourceDir, appInstall.App.Key, "versions", appInstall.Version, "conf") | ||||||
|  | 	} | ||||||
|  | 	if !fileOp.Stat(filePath) { | ||||||
|  | 		return "", buserr.New(constant.ErrPathNotFound) | ||||||
|  | 	} | ||||||
|  |  | ||||||
| 	if key == constant.AppMysql { | 	if key == constant.AppMysql { | ||||||
| 		filePath = path.Join(filePath, "my.cnf") | 		filePath = path.Join(filePath, "my.cnf") | ||||||
| 	} | 	} | ||||||
| @@ -453,11 +539,12 @@ func (a *AppInstallService) GetDefaultConfigByKey(key string) (string, error) { | |||||||
| 	return string(contentByte), nil | 	return string(contentByte), nil | ||||||
| } | } | ||||||
|  |  | ||||||
| func (a *AppInstallService) GetParams(id uint) ([]response.AppParam, error) { | func (a *AppInstallService) GetParams(id uint) (*response.AppConfig, error) { | ||||||
| 	var ( | 	var ( | ||||||
| 		res     []response.AppParam | 		params  []response.AppParam | ||||||
| 		appForm dto.AppForm | 		appForm dto.AppForm | ||||||
| 		envs    = make(map[string]interface{}) | 		envs    = make(map[string]interface{}) | ||||||
|  | 		res     response.AppConfig | ||||||
| 	) | 	) | ||||||
| 	install, err := appInstallRepo.GetFirst(commonRepo.WithByID(id)) | 	install, err := appInstallRepo.GetFirst(commonRepo.WithByID(id)) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| @@ -499,10 +586,18 @@ func (a *AppInstallService) GetParams(id uint) ([]response.AppParam, error) { | |||||||
| 				} | 				} | ||||||
| 				appParam.Values = form.Values | 				appParam.Values = form.Values | ||||||
| 			} | 			} | ||||||
| 			res = append(res, appParam) | 			params = append(params, appParam) | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 	return res, nil |  | ||||||
|  | 	config := getAppCommonConfig(envs) | ||||||
|  | 	config.DockerCompose = install.DockerCompose | ||||||
|  | 	res.Params = params | ||||||
|  | 	if config.ContainerName == "" { | ||||||
|  | 		config.ContainerName = install.ContainerName | ||||||
|  | 	} | ||||||
|  | 	res.AppContainerConfig = config | ||||||
|  | 	return &res, nil | ||||||
| } | } | ||||||
|  |  | ||||||
| func syncById(installId uint) error { | func syncById(installId uint) error { | ||||||
| @@ -513,12 +608,10 @@ func syncById(installId uint) error { | |||||||
| 	if appInstall.Status == constant.Installing { | 	if appInstall.Status == constant.Installing { | ||||||
| 		return nil | 		return nil | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	containerNames, err := getContainerNames(appInstall) | 	containerNames, err := getContainerNames(appInstall) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return err | 		return err | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	cli, err := docker.NewClient() | 	cli, err := docker.NewClient() | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return err | 		return err | ||||||
| @@ -544,16 +637,16 @@ func syncById(installId uint) error { | |||||||
| 			errorContainers = append(errorContainers, n.Names[0]) | 			errorContainers = append(errorContainers, n.Names[0]) | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 	for _, old := range containerNames { | 	for _, name := range containerNames { | ||||||
| 		exist := false | 		exist := false | ||||||
| 		for _, new := range containers { | 		for _, container := range containers { | ||||||
| 			if common.ExistWithStrArray(old, new.Names) { | 			if common.ExistWithStrArray(name, container.Names) { | ||||||
| 				exist = true | 				exist = true | ||||||
| 				break | 				break | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
| 		if !exist { | 		if !exist { | ||||||
| 			notFoundContainers = append(notFoundContainers, old) | 			notFoundContainers = append(notFoundContainers, name) | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| @@ -566,16 +659,16 @@ func syncById(installId uint) error { | |||||||
|  |  | ||||||
| 	if containerCount == 0 { | 	if containerCount == 0 { | ||||||
| 		appInstall.Status = constant.Error | 		appInstall.Status = constant.Error | ||||||
| 		appInstall.Message = "container is not found" | 		appInstall.Message = i18n.GetMsgWithMap("ErrContainerNotFound", map[string]interface{}{"name": strings.Join(containerNames, ",")}) | ||||||
| 		return appInstallRepo.Save(&appInstall) | 		return appInstallRepo.Save(context.Background(), &appInstall) | ||||||
| 	} | 	} | ||||||
| 	if errCount == 0 && existedCount == 0 { | 	if errCount == 0 && existedCount == 0 && notFoundCount == 0 { | ||||||
| 		appInstall.Status = constant.Running | 		appInstall.Status = constant.Running | ||||||
| 		return appInstallRepo.Save(&appInstall) | 		return appInstallRepo.Save(context.Background(), &appInstall) | ||||||
| 	} | 	} | ||||||
| 	if existedCount == normalCount { | 	if existedCount == normalCount { | ||||||
| 		appInstall.Status = constant.Stopped | 		appInstall.Status = constant.Stopped | ||||||
| 		return appInstallRepo.Save(&appInstall) | 		return appInstallRepo.Save(context.Background(), &appInstall) | ||||||
| 	} | 	} | ||||||
| 	if errCount == normalCount { | 	if errCount == normalCount { | ||||||
| 		appInstall.Status = constant.Error | 		appInstall.Status = constant.Error | ||||||
| @@ -584,23 +677,15 @@ func syncById(installId uint) error { | |||||||
| 		appInstall.Status = constant.UnHealthy | 		appInstall.Status = constant.UnHealthy | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	var errMsg strings.Builder | 	var errMsg string | ||||||
| 	if errCount > 0 { | 	if errCount > 0 { | ||||||
| 		errMsg.Write([]byte(string(rune(errCount)) + " error containers:")) | 		errMsg += i18n.GetMsgWithMap("ErrContainerMsg", map[string]interface{}{"name": strings.Join(errorContainers, ",")}) | ||||||
| 		for _, e := range errorContainers { |  | ||||||
| 			errMsg.Write([]byte(e)) |  | ||||||
| 		} |  | ||||||
| 		errMsg.Write([]byte("\n")) |  | ||||||
| 	} | 	} | ||||||
| 	if notFoundCount > 0 { | 	if notFoundCount > 0 { | ||||||
| 		errMsg.Write([]byte(string(rune(notFoundCount)) + " not found containers:")) | 		errMsg += i18n.GetMsgWithMap("ErrContainerNotFound", map[string]interface{}{"name": strings.Join(notFoundContainers, ",")}) | ||||||
| 		for _, e := range notFoundContainers { |  | ||||||
| 			errMsg.Write([]byte(e)) |  | ||||||
| 		} |  | ||||||
| 		errMsg.Write([]byte("\n")) |  | ||||||
| 	} | 	} | ||||||
| 	appInstall.Message = errMsg.String() | 	appInstall.Message = errMsg | ||||||
| 	return appInstallRepo.Save(&appInstall) | 	return appInstallRepo.Save(context.Background(), &appInstall) | ||||||
| } | } | ||||||
|  |  | ||||||
| func updateInstallInfoInDB(appKey, appName, param string, isRestart bool, value interface{}) error { | func updateInstallInfoInDB(appKey, appName, param string, isRestart bool, value interface{}) error { | ||||||
| @@ -612,7 +697,7 @@ func updateInstallInfoInDB(appKey, appName, param string, isRestart bool, value | |||||||
| 		return nil | 		return nil | ||||||
| 	} | 	} | ||||||
| 	envPath := fmt.Sprintf("%s/%s/%s/.env", constant.AppInstallDir, appKey, appInstall.Name) | 	envPath := fmt.Sprintf("%s/%s/%s/.env", constant.AppInstallDir, appKey, appInstall.Name) | ||||||
| 	lineBytes, err := ioutil.ReadFile(envPath) | 	lineBytes, err := os.ReadFile(envPath) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return err | 		return err | ||||||
| 	} | 	} | ||||||
|   | |||||||
| @@ -2,12 +2,21 @@ package service | |||||||
|  |  | ||||||
| import ( | import ( | ||||||
| 	"context" | 	"context" | ||||||
|  | 	"encoding/base64" | ||||||
| 	"encoding/json" | 	"encoding/json" | ||||||
| 	"fmt" | 	"fmt" | ||||||
|  | 	"github.com/1Panel-dev/1Panel/backend/app/api/v1/helper" | ||||||
|  | 	"github.com/1Panel-dev/1Panel/backend/app/dto/request" | ||||||
|  | 	"github.com/1Panel-dev/1Panel/backend/i18n" | ||||||
|  | 	"github.com/subosito/gotenv" | ||||||
|  | 	"gopkg.in/yaml.v3" | ||||||
| 	"math" | 	"math" | ||||||
|  | 	"net/http" | ||||||
| 	"os" | 	"os" | ||||||
|  | 	"os/exec" | ||||||
| 	"path" | 	"path" | ||||||
| 	"reflect" | 	"reflect" | ||||||
|  | 	"regexp" | ||||||
| 	"strconv" | 	"strconv" | ||||||
| 	"strings" | 	"strings" | ||||||
|  |  | ||||||
| @@ -23,7 +32,9 @@ import ( | |||||||
| 	"github.com/1Panel-dev/1Panel/backend/global" | 	"github.com/1Panel-dev/1Panel/backend/global" | ||||||
| 	"github.com/1Panel-dev/1Panel/backend/utils/common" | 	"github.com/1Panel-dev/1Panel/backend/utils/common" | ||||||
| 	"github.com/1Panel-dev/1Panel/backend/utils/compose" | 	"github.com/1Panel-dev/1Panel/backend/utils/compose" | ||||||
|  | 	composeV2 "github.com/1Panel-dev/1Panel/backend/utils/docker" | ||||||
| 	"github.com/1Panel-dev/1Panel/backend/utils/files" | 	"github.com/1Panel-dev/1Panel/backend/utils/files" | ||||||
|  | 	dockerTypes "github.com/docker/docker/api/types" | ||||||
| 	"github.com/pkg/errors" | 	"github.com/pkg/errors" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| @@ -37,7 +48,19 @@ var ( | |||||||
| func checkPort(key string, params map[string]interface{}) (int, error) { | func checkPort(key string, params map[string]interface{}) (int, error) { | ||||||
| 	port, ok := params[key] | 	port, ok := params[key] | ||||||
| 	if ok { | 	if ok { | ||||||
| 		portN := int(math.Ceil(port.(float64))) | 		portN := 0 | ||||||
|  | 		var err error | ||||||
|  | 		switch p := port.(type) { | ||||||
|  | 		case string: | ||||||
|  | 			portN, err = strconv.Atoi(p) | ||||||
|  | 			if err != nil { | ||||||
|  | 				return portN, nil | ||||||
|  | 			} | ||||||
|  | 		case float64: | ||||||
|  | 			portN = int(math.Ceil(p)) | ||||||
|  | 		case int: | ||||||
|  | 			portN = p | ||||||
|  | 		} | ||||||
|  |  | ||||||
| 		oldInstalled, _ := appInstallRepo.ListBy(appInstallRepo.WithPort(portN)) | 		oldInstalled, _ := appInstallRepo.ListBy(appInstallRepo.WithPort(portN)) | ||||||
| 		if len(oldInstalled) > 0 { | 		if len(oldInstalled) > 0 { | ||||||
| @@ -125,7 +148,23 @@ func createLink(ctx context.Context, app model.App, appInstall *model.AppInstall | |||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
|  |  | ||||||
| func deleteAppInstall(ctx context.Context, install model.AppInstall, deleteBackup bool, forceDelete bool, deleteDB bool) error { | func handleAppInstallErr(ctx context.Context, install *model.AppInstall) error { | ||||||
|  | 	op := files.NewFileOp() | ||||||
|  | 	appDir := install.GetPath() | ||||||
|  | 	dir, _ := os.Stat(appDir) | ||||||
|  | 	if dir != nil { | ||||||
|  | 		_, _ = compose.Down(install.GetComposePath()) | ||||||
|  | 		if err := op.DeleteDir(appDir); err != nil { | ||||||
|  | 			return err | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	if err := deleteLink(ctx, install, true, true); err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func deleteAppInstall(install model.AppInstall, deleteBackup bool, forceDelete bool, deleteDB bool) error { | ||||||
| 	op := files.NewFileOp() | 	op := files.NewFileOp() | ||||||
| 	appDir := install.GetPath() | 	appDir := install.GetPath() | ||||||
| 	dir, _ := os.Stat(appDir) | 	dir, _ := os.Stat(appDir) | ||||||
| @@ -134,36 +173,34 @@ func deleteAppInstall(ctx context.Context, install model.AppInstall, deleteBacku | |||||||
| 		if err != nil && !forceDelete { | 		if err != nil && !forceDelete { | ||||||
| 			return handleErr(install, err, out) | 			return handleErr(install, err, out) | ||||||
| 		} | 		} | ||||||
| 		if err := op.DeleteDir(appDir); err != nil && !forceDelete { |  | ||||||
| 			return err |  | ||||||
| 		} |  | ||||||
| 	} | 	} | ||||||
|  | 	tx, ctx := helper.GetTxAndContext() | ||||||
|  | 	defer tx.Rollback() | ||||||
| 	if err := appInstallRepo.Delete(ctx, install); err != nil { | 	if err := appInstallRepo.Delete(ctx, install); err != nil { | ||||||
| 		return err | 		return err | ||||||
| 	} | 	} | ||||||
| 	if err := deleteLink(ctx, &install, deleteDB, forceDelete); err != nil && !forceDelete { | 	if err := deleteLink(ctx, &install, deleteDB, forceDelete); err != nil && !forceDelete { | ||||||
| 		return err | 		return err | ||||||
| 	} | 	} | ||||||
|  | 	_ = backupRepo.DeleteRecord(ctx, commonRepo.WithByType("app"), commonRepo.WithByName(install.App.Key), backupRepo.WithByDetailName(install.Name)) | ||||||
|  | 	_ = backupRepo.DeleteRecord(ctx, commonRepo.WithByType(install.App.Key)) | ||||||
|  | 	if install.App.Key == constant.AppMysql { | ||||||
|  | 		_ = mysqlRepo.DeleteAll(ctx) | ||||||
|  | 	} | ||||||
| 	uploadDir := fmt.Sprintf("%s/1panel/uploads/app/%s/%s", global.CONF.System.BaseDir, install.App.Key, install.Name) | 	uploadDir := fmt.Sprintf("%s/1panel/uploads/app/%s/%s", global.CONF.System.BaseDir, install.App.Key, install.Name) | ||||||
| 	if _, err := os.Stat(uploadDir); err == nil { | 	if _, err := os.Stat(uploadDir); err == nil { | ||||||
| 		_ = os.RemoveAll(uploadDir) | 		_ = os.RemoveAll(uploadDir) | ||||||
| 	} | 	} | ||||||
| 	if deleteBackup { | 	if deleteBackup { | ||||||
| 		localDir, err := loadLocalDir() | 		localDir, _ := loadLocalDir() | ||||||
| 		if err != nil && !forceDelete { |  | ||||||
| 			return err |  | ||||||
| 		} |  | ||||||
| 		backupDir := fmt.Sprintf("%s/app/%s/%s", localDir, install.App.Key, install.Name) | 		backupDir := fmt.Sprintf("%s/app/%s/%s", localDir, install.App.Key, install.Name) | ||||||
| 		if _, err := os.Stat(backupDir); err == nil { | 		if _, err := os.Stat(backupDir); err == nil { | ||||||
| 			_ = os.RemoveAll(backupDir) | 			_ = os.RemoveAll(backupDir) | ||||||
| 		} | 		} | ||||||
| 		global.LOG.Infof("delete app %s-%s backups successful", install.App.Key, install.Name) | 		global.LOG.Infof("delete app %s-%s backups successful", install.App.Key, install.Name) | ||||||
| 	} | 	} | ||||||
| 	_ = backupRepo.DeleteRecord(ctx, commonRepo.WithByType("app"), commonRepo.WithByName(install.App.Key), backupRepo.WithByDetailName(install.Name)) | 	_ = op.DeleteDir(appDir) | ||||||
| 	_ = backupRepo.DeleteRecord(ctx, commonRepo.WithByType(install.App.Key)) | 	tx.Commit() | ||||||
| 	if install.App.Key == constant.AppMysql { |  | ||||||
| 		_ = mysqlRepo.DeleteAll(ctx) |  | ||||||
| 	} |  | ||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -190,7 +227,7 @@ func deleteLink(ctx context.Context, install *model.AppInstall, deleteDB bool, f | |||||||
| 	return appInstallResourceRepo.DeleteBy(ctx, appInstallResourceRepo.WithAppInstallId(install.ID)) | 	return appInstallResourceRepo.DeleteBy(ctx, appInstallResourceRepo.WithAppInstallId(install.ID)) | ||||||
| } | } | ||||||
|  |  | ||||||
| func updateInstall(installId uint, detailId uint) error { | func upgradeInstall(installId uint, detailId uint) error { | ||||||
| 	install, err := appInstallRepo.GetFirst(commonRepo.WithByID(installId)) | 	install, err := appInstallRepo.GetFirst(commonRepo.WithByID(installId)) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return err | 		return err | ||||||
| @@ -205,43 +242,163 @@ func updateInstall(installId uint, detailId uint) error { | |||||||
| 	if err := NewIBackupService().AppBackup(dto.CommonBackup{Name: install.App.Key, DetailName: install.Name}); err != nil { | 	if err := NewIBackupService().AppBackup(dto.CommonBackup{Name: install.App.Key, DetailName: install.Name}); err != nil { | ||||||
| 		return err | 		return err | ||||||
| 	} | 	} | ||||||
| 	if _, err = compose.Down(install.GetComposePath()); err != nil { |  | ||||||
| 		return err |  | ||||||
| 	} |  | ||||||
| 	install.DockerCompose = detail.DockerCompose |  | ||||||
| 	install.Version = detail.Version |  | ||||||
| 	install.AppDetailId = detailId |  | ||||||
|  |  | ||||||
| 	fileOp := files.NewFileOp() | 	install.Status = constant.Upgrading | ||||||
| 	if err := fileOp.WriteFile(install.GetComposePath(), strings.NewReader(install.DockerCompose), 0775); err != nil { |  | ||||||
| 		return err | 	go func() { | ||||||
| 	} | 		var upErr error | ||||||
| 	if _, err = compose.Up(install.GetComposePath()); err != nil { | 		defer func() { | ||||||
| 		return err | 			if upErr != nil { | ||||||
| 	} | 				install.Status = constant.UpgradeErr | ||||||
| 	return appInstallRepo.Save(&install) | 				install.Message = upErr.Error() | ||||||
|  | 				_ = appInstallRepo.Save(context.Background(), &install) | ||||||
|  | 			} | ||||||
|  | 		}() | ||||||
|  |  | ||||||
|  | 		detailDir := path.Join(constant.ResourceDir, "apps", install.App.Resource, install.App.Key, detail.Version) | ||||||
|  | 		if install.App.Resource == constant.AppResourceRemote { | ||||||
|  | 			if upErr = downloadApp(install.App, detail, &install); upErr != nil { | ||||||
|  | 				return | ||||||
|  | 			} | ||||||
|  | 			go func() { | ||||||
|  | 				_, _ = http.Get(detail.DownloadCallBackUrl) | ||||||
|  | 			}() | ||||||
|  | 		} | ||||||
|  | 		if install.App.Resource == constant.AppResourceLocal { | ||||||
|  | 			detailDir = path.Join(constant.ResourceDir, "apps", "local", strings.TrimPrefix(install.App.Key, "local"), detail.Version) | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		cmd := exec.Command("/bin/bash", "-c", fmt.Sprintf("cp -rf %s/* %s", detailDir, install.GetPath())) | ||||||
|  | 		stdout, err := cmd.CombinedOutput() | ||||||
|  | 		if err != nil { | ||||||
|  | 			if stdout != nil { | ||||||
|  | 				upErr = errors.New(string(stdout)) | ||||||
|  | 				return | ||||||
|  | 			} | ||||||
|  | 			upErr = err | ||||||
|  | 			return | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		composeMap := make(map[string]interface{}) | ||||||
|  | 		if upErr = yaml.Unmarshal([]byte(detail.DockerCompose), &composeMap); upErr != nil { | ||||||
|  | 			return | ||||||
|  | 		} | ||||||
|  | 		value, ok := composeMap["services"] | ||||||
|  | 		if !ok { | ||||||
|  | 			upErr = buserr.New(constant.ErrFileParse) | ||||||
|  | 			return | ||||||
|  | 		} | ||||||
|  | 		servicesMap := value.(map[string]interface{}) | ||||||
|  | 		index := 0 | ||||||
|  | 		oldServiceName := "" | ||||||
|  | 		for k := range servicesMap { | ||||||
|  | 			oldServiceName = k | ||||||
|  | 			index++ | ||||||
|  | 			if index > 0 { | ||||||
|  | 				break | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 		servicesMap[install.ServiceName] = servicesMap[oldServiceName] | ||||||
|  | 		if install.ServiceName != oldServiceName { | ||||||
|  | 			delete(servicesMap, oldServiceName) | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		envs := make(map[string]interface{}) | ||||||
|  | 		if upErr = json.Unmarshal([]byte(install.Env), &envs); upErr != nil { | ||||||
|  | 			return | ||||||
|  | 		} | ||||||
|  | 		config := getAppCommonConfig(envs) | ||||||
|  | 		if config.ContainerName == "" { | ||||||
|  | 			config.ContainerName = install.ContainerName | ||||||
|  | 			envs[constant.ContainerName] = install.ContainerName | ||||||
|  | 		} | ||||||
|  | 		config.Advanced = true | ||||||
|  | 		if upErr = addDockerComposeCommonParam(composeMap, install.ServiceName, config, envs); upErr != nil { | ||||||
|  | 			return | ||||||
|  | 		} | ||||||
|  | 		paramByte, upErr := json.Marshal(envs) | ||||||
|  | 		if upErr != nil { | ||||||
|  | 			return | ||||||
|  | 		} | ||||||
|  | 		install.Env = string(paramByte) | ||||||
|  | 		composeByte, upErr := yaml.Marshal(composeMap) | ||||||
|  | 		if upErr != nil { | ||||||
|  | 			return | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		install.DockerCompose = string(composeByte) | ||||||
|  | 		install.Version = detail.Version | ||||||
|  | 		install.AppDetailId = detailId | ||||||
|  |  | ||||||
|  | 		if out, err := compose.Down(install.GetComposePath()); err != nil { | ||||||
|  | 			if out != "" { | ||||||
|  | 				upErr = errors.New(out) | ||||||
|  | 				return | ||||||
|  | 			} | ||||||
|  | 			return | ||||||
|  | 		} | ||||||
|  | 		fileOp := files.NewFileOp() | ||||||
|  | 		envParams := make(map[string]string, len(envs)) | ||||||
|  | 		handleMap(envs, envParams) | ||||||
|  | 		if err = env.Write(envParams, install.GetEnvPath()); err != nil { | ||||||
|  | 			return | ||||||
|  | 		} | ||||||
|  | 		if upErr = fileOp.WriteFile(install.GetComposePath(), strings.NewReader(install.DockerCompose), 0775); upErr != nil { | ||||||
|  | 			return | ||||||
|  | 		} | ||||||
|  | 		if out, err := compose.Up(install.GetComposePath()); err != nil { | ||||||
|  | 			if out != "" { | ||||||
|  | 				upErr = errors.New(out) | ||||||
|  | 				return | ||||||
|  | 			} | ||||||
|  | 			upErr = err | ||||||
|  | 			return | ||||||
|  | 		} | ||||||
|  | 		install.Status = constant.Running | ||||||
|  | 		_ = appInstallRepo.Save(context.Background(), &install) | ||||||
|  | 	}() | ||||||
|  |  | ||||||
|  | 	return appInstallRepo.Save(context.Background(), &install) | ||||||
| } | } | ||||||
|  |  | ||||||
| func getContainerNames(install model.AppInstall) ([]string, error) { | func getContainerNames(install model.AppInstall) ([]string, error) { | ||||||
| 	composeMap := install.DockerCompose | 	envStr, err := coverEnvJsonToStr(install.Env) | ||||||
| 	envMap := make(map[string]interface{}) |  | ||||||
| 	_ = json.Unmarshal([]byte(install.Env), &envMap) |  | ||||||
| 	newEnvMap := make(map[string]string, len(envMap)) |  | ||||||
| 	handleMap(envMap, newEnvMap) |  | ||||||
| 	project, err := compose.GetComposeProject([]byte(composeMap), newEnvMap) |  | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return nil, err | 		return nil, err | ||||||
| 	} | 	} | ||||||
| 	containerNames := []string{install.ContainerName} | 	project, err := composeV2.GetComposeProject(install.Name, install.GetPath(), []byte(install.DockerCompose), []byte(envStr), true) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 	containerMap := make(map[string]struct{}) | ||||||
| 	for _, service := range project.AllServices() { | 	for _, service := range project.AllServices() { | ||||||
| 		if service.ContainerName == "${CONTAINER_NAME}" || service.ContainerName == "" { | 		if service.ContainerName == "${CONTAINER_NAME}" || service.ContainerName == "" { | ||||||
| 			continue | 			continue | ||||||
| 		} | 		} | ||||||
| 		containerNames = append(containerNames, service.ContainerName) | 		containerMap[service.ContainerName] = struct{}{} | ||||||
|  | 	} | ||||||
|  | 	var containerNames []string | ||||||
|  | 	for k := range containerMap { | ||||||
|  | 		containerNames = append(containerNames, k) | ||||||
|  | 	} | ||||||
|  | 	if len(containerNames) == 0 { | ||||||
|  | 		containerNames = append(containerNames, install.ContainerName) | ||||||
| 	} | 	} | ||||||
| 	return containerNames, nil | 	return containerNames, nil | ||||||
| } | } | ||||||
|  |  | ||||||
|  | func coverEnvJsonToStr(envJson string) (string, error) { | ||||||
|  | 	envMap := make(map[string]interface{}) | ||||||
|  | 	_ = json.Unmarshal([]byte(envJson), &envMap) | ||||||
|  | 	newEnvMap := make(map[string]string, len(envMap)) | ||||||
|  | 	handleMap(envMap, newEnvMap) | ||||||
|  | 	envStr, err := gotenv.Marshal(newEnvMap) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return "", err | ||||||
|  | 	} | ||||||
|  | 	return envStr, nil | ||||||
|  | } | ||||||
|  |  | ||||||
| func checkLimit(app model.App) error { | func checkLimit(app model.App) error { | ||||||
| 	if app.Limit > 0 { | 	if app.Limit > 0 { | ||||||
| 		installs, err := appInstallRepo.ListBy(appInstallRepo.WithAppId(app.ID)) | 		installs, err := appInstallRepo.ListBy(appInstallRepo.WithAppId(app.ID)) | ||||||
| @@ -256,40 +413,9 @@ func checkLimit(app model.App) error { | |||||||
| } | } | ||||||
|  |  | ||||||
| func checkRequiredAndLimit(app model.App) error { | func checkRequiredAndLimit(app model.App) error { | ||||||
|  |  | ||||||
| 	if err := checkLimit(app); err != nil { | 	if err := checkLimit(app); err != nil { | ||||||
| 		return err | 		return err | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	if app.Required != "" { |  | ||||||
| 		var requiredArray []string |  | ||||||
| 		if err := json.Unmarshal([]byte(app.Required), &requiredArray); err != nil { |  | ||||||
| 			return err |  | ||||||
| 		} |  | ||||||
| 		for _, key := range requiredArray { |  | ||||||
| 			if key == "" { |  | ||||||
| 				continue |  | ||||||
| 			} |  | ||||||
| 			requireApp, err := appRepo.GetFirst(appRepo.WithKey(key)) |  | ||||||
| 			if err != nil { |  | ||||||
| 				return err |  | ||||||
| 			} |  | ||||||
| 			details, err := appDetailRepo.GetBy(appDetailRepo.WithAppId(requireApp.ID)) |  | ||||||
| 			if err != nil { |  | ||||||
| 				return err |  | ||||||
| 			} |  | ||||||
| 			var detailIds []uint |  | ||||||
| 			for _, d := range details { |  | ||||||
| 				detailIds = append(detailIds, d.ID) |  | ||||||
| 			} |  | ||||||
|  |  | ||||||
| 			_, err = appInstallRepo.GetFirst(appInstallRepo.WithDetailIdsIn(detailIds)) |  | ||||||
| 			if err != nil { |  | ||||||
| 				return buserr.WithDetail(constant.ErrAppRequired, requireApp.Name, nil) |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -306,17 +432,74 @@ func handleMap(params map[string]interface{}, envParams map[string]string) { | |||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| func copyAppData(key, version, installName string, params map[string]interface{}) (err error) { | func downloadApp(app model.App, appDetail model.AppDetail, appInstall *model.AppInstall) (err error) { | ||||||
|  | 	appResourceDir := path.Join(constant.AppResourceDir, app.Resource) | ||||||
|  | 	appDownloadDir := path.Join(appResourceDir, app.Key) | ||||||
|  | 	appVersionDir := path.Join(appDownloadDir, appDetail.Version) | ||||||
| 	fileOp := files.NewFileOp() | 	fileOp := files.NewFileOp() | ||||||
| 	resourceDir := path.Join(constant.AppResourceDir, key, "versions", version) | 	if !appDetail.Update && fileOp.Stat(appVersionDir) { | ||||||
| 	installAppDir := path.Join(constant.AppInstallDir, key) | 		return | ||||||
|  | 	} | ||||||
|  | 	if !fileOp.Stat(appDownloadDir) { | ||||||
|  | 		_ = fileOp.CreateDir(appDownloadDir, 0755) | ||||||
|  | 	} | ||||||
|  | 	if !fileOp.Stat(appVersionDir) { | ||||||
|  | 		_ = fileOp.CreateDir(appVersionDir, 0755) | ||||||
|  | 	} | ||||||
|  | 	global.LOG.Infof("download app[%s] from %s", app.Name, appDetail.DownloadUrl) | ||||||
|  | 	filePath := path.Join(appVersionDir, appDetail.Version+".tar.gz") | ||||||
|  |  | ||||||
|  | 	defer func() { | ||||||
|  | 		if err != nil { | ||||||
|  | 			if appInstall != nil { | ||||||
|  | 				appInstall.Status = constant.DownloadErr | ||||||
|  | 				appInstall.Message = err.Error() | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	}() | ||||||
|  |  | ||||||
|  | 	if err = fileOp.DownloadFile(appDetail.DownloadUrl, filePath); err != nil { | ||||||
|  | 		global.LOG.Errorf("download app[%s] error %v", app.Name, err) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	if err = fileOp.Decompress(filePath, appVersionDir, files.TarGz); err != nil { | ||||||
|  | 		global.LOG.Errorf("decompress app[%s] error %v", app.Name, err) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	_ = fileOp.DeleteFile(filePath) | ||||||
|  | 	appDetail.Update = false | ||||||
|  | 	_ = appDetailRepo.Update(context.Background(), appDetail) | ||||||
|  | 	return | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func copyData(app model.App, appDetail model.AppDetail, appInstall *model.AppInstall, req request.AppInstallCreate) (err error) { | ||||||
|  | 	fileOp := files.NewFileOp() | ||||||
|  | 	appResourceDir := path.Join(constant.AppResourceDir, app.Resource) | ||||||
|  |  | ||||||
|  | 	if app.Resource == constant.AppResourceRemote { | ||||||
|  | 		err = downloadApp(app, appDetail, appInstall) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return | ||||||
|  | 		} | ||||||
|  | 		go func() { | ||||||
|  | 			_, _ = http.Get(appDetail.DownloadCallBackUrl) | ||||||
|  | 		}() | ||||||
|  | 	} | ||||||
|  | 	appKey := app.Key | ||||||
|  | 	installAppDir := path.Join(constant.AppInstallDir, app.Key) | ||||||
|  | 	if app.Resource == constant.AppResourceLocal { | ||||||
|  | 		appResourceDir = constant.LocalAppResourceDir | ||||||
|  | 		appKey = strings.TrimPrefix(app.Key, "local") | ||||||
|  | 		installAppDir = path.Join(constant.LocalAppInstallDir, appKey) | ||||||
|  | 	} | ||||||
|  | 	resourceDir := path.Join(appResourceDir, appKey, appDetail.Version) | ||||||
|  |  | ||||||
| 	if !fileOp.Stat(installAppDir) { | 	if !fileOp.Stat(installAppDir) { | ||||||
| 		if err = fileOp.CreateDir(installAppDir, 0755); err != nil { | 		if err = fileOp.CreateDir(installAppDir, 0755); err != nil { | ||||||
| 			return | 			return | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 	appDir := path.Join(installAppDir, installName) | 	appDir := path.Join(installAppDir, req.Name) | ||||||
| 	if fileOp.Stat(appDir) { | 	if fileOp.Stat(appDir) { | ||||||
| 		if err = fileOp.DeleteDir(appDir); err != nil { | 		if err = fileOp.DeleteDir(appDir); err != nil { | ||||||
| 			return | 			return | ||||||
| @@ -325,33 +508,101 @@ func copyAppData(key, version, installName string, params map[string]interface{} | |||||||
| 	if err = fileOp.Copy(resourceDir, installAppDir); err != nil { | 	if err = fileOp.Copy(resourceDir, installAppDir); err != nil { | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
| 	versionDir := path.Join(installAppDir, version) | 	versionDir := path.Join(installAppDir, appDetail.Version) | ||||||
| 	if err = fileOp.Rename(versionDir, appDir); err != nil { | 	if err = fileOp.Rename(versionDir, appDir); err != nil { | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
| 	envPath := path.Join(appDir, ".env") | 	envPath := path.Join(appDir, ".env") | ||||||
|  |  | ||||||
| 	envParams := make(map[string]string, len(params)) | 	envParams := make(map[string]string, len(req.Params)) | ||||||
| 	handleMap(params, envParams) | 	handleMap(req.Params, envParams) | ||||||
| 	if err = env.Write(envParams, envPath); err != nil { | 	if err = env.Write(envParams, envPath); err != nil { | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
|  | 	if err := fileOp.WriteFile(appInstall.GetComposePath(), strings.NewReader(appInstall.DockerCompose), 0755); err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
| 	return | 	return | ||||||
| } | } | ||||||
|  |  | ||||||
| func upApp(composeFilePath string, appInstall model.AppInstall) { | // 处理文件夹权限等问题 | ||||||
| 	out, err := compose.Up(composeFilePath) | func upAppPre(app model.App, appInstall *model.AppInstall) error { | ||||||
| 	if err != nil { | 	if app.Key == "nexus" { | ||||||
| 		if out != "" { | 		dataPath := path.Join(appInstall.GetPath(), "data") | ||||||
| 			appInstall.Message = out | 		if err := files.NewFileOp().Chown(dataPath, 200, 0); err != nil { | ||||||
| 		} else { | 			return err | ||||||
| 			appInstall.Message = err.Error() |  | ||||||
| 		} | 		} | ||||||
|  | 	} | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func checkContainerNameIsExist(containerName, appDir string) (bool, error) { | ||||||
|  | 	client, err := composeV2.NewDockerClient() | ||||||
|  | 	if err != nil { | ||||||
|  | 		return false, err | ||||||
|  | 	} | ||||||
|  | 	var options dockerTypes.ContainerListOptions | ||||||
|  | 	list, err := client.ContainerList(context.Background(), options) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return false, err | ||||||
|  | 	} | ||||||
|  | 	for _, container := range list { | ||||||
|  | 		if containerName == container.Names[0][1:] { | ||||||
|  | 			if workDir, ok := container.Labels[composeWorkdirLabel]; ok { | ||||||
|  | 				if workDir != appDir { | ||||||
|  | 					return true, nil | ||||||
|  | 				} | ||||||
|  | 			} else { | ||||||
|  | 				return true, nil | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 	} | ||||||
|  | 	return false, nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func upApp(appInstall *model.AppInstall) { | ||||||
|  | 	upProject := func(appInstall *model.AppInstall) (err error) { | ||||||
|  | 		if err == nil { | ||||||
|  | 			var ( | ||||||
|  | 				out    string | ||||||
|  | 				errMsg string | ||||||
|  | 			) | ||||||
|  | 			if appInstall.App.Type != "php" { | ||||||
|  | 				out, err = compose.Pull(appInstall.GetComposePath()) | ||||||
|  | 				if err != nil { | ||||||
|  | 					if out != "" { | ||||||
|  | 						if strings.Contains(out, "no such host") { | ||||||
|  | 							errMsg = i18n.GetMsgByKey("ErrNoSuchHost") + ":" | ||||||
|  | 						} | ||||||
|  | 						if strings.Contains(out, "timeout") { | ||||||
|  | 							errMsg = i18n.GetMsgByKey("ErrImagePullTimeOut") + ":" | ||||||
|  | 						} | ||||||
|  | 						appInstall.Message = errMsg + out | ||||||
|  | 					} | ||||||
|  | 					return err | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 			out, err = compose.Up(appInstall.GetComposePath()) | ||||||
|  | 			if err != nil { | ||||||
|  | 				if out != "" { | ||||||
|  | 					appInstall.Message = errMsg + out | ||||||
|  | 				} | ||||||
|  | 				return err | ||||||
|  | 			} | ||||||
|  | 			return | ||||||
|  | 		} else { | ||||||
|  | 			return | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	if err := upProject(appInstall); err != nil { | ||||||
| 		appInstall.Status = constant.Error | 		appInstall.Status = constant.Error | ||||||
| 		_ = appInstallRepo.Save(&appInstall) |  | ||||||
| 	} else { | 	} else { | ||||||
| 		appInstall.Status = constant.Running | 		appInstall.Status = constant.Running | ||||||
| 		_ = appInstallRepo.Save(&appInstall) | 	} | ||||||
|  | 	exist, _ := appInstallRepo.GetFirst(commonRepo.WithByID(appInstall.ID)) | ||||||
|  | 	if exist.ID > 0 { | ||||||
|  | 		_ = appInstallRepo.Save(context.Background(), appInstall) | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -368,21 +619,21 @@ func rebuildApp(appInstall model.AppInstall) error { | |||||||
| 	return syncById(appInstall.ID) | 	return syncById(appInstall.ID) | ||||||
| } | } | ||||||
|  |  | ||||||
| func getAppDetails(details []model.AppDetail, versions []string) map[string]model.AppDetail { | func getAppDetails(details []model.AppDetail, versions []dto.AppConfigVersion) map[string]model.AppDetail { | ||||||
| 	appDetails := make(map[string]model.AppDetail, len(details)) | 	appDetails := make(map[string]model.AppDetail, len(details)) | ||||||
| 	for _, old := range details { | 	for _, old := range details { | ||||||
| 		old.Status = constant.AppTakeDown | 		old.Status = constant.AppTakeDown | ||||||
| 		appDetails[old.Version] = old | 		appDetails[old.Version] = old | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	for _, v := range versions { | 	for _, v := range versions { | ||||||
| 		detail, ok := appDetails[v] | 		version := v.Name | ||||||
|  | 		detail, ok := appDetails[version] | ||||||
| 		if ok { | 		if ok { | ||||||
| 			detail.Status = constant.AppNormal | 			detail.Status = constant.AppNormal | ||||||
| 			appDetails[v] = detail | 			appDetails[version] = detail | ||||||
| 		} else { | 		} else { | ||||||
| 			appDetails[v] = model.AppDetail{ | 			appDetails[version] = model.AppDetail{ | ||||||
| 				Version: v, | 				Version: version, | ||||||
| 				Status:  constant.AppNormal, | 				Status:  constant.AppNormal, | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
| @@ -397,27 +648,103 @@ func getApps(oldApps []model.App, items []dto.AppDefine) map[string]model.App { | |||||||
| 		apps[old.Key] = old | 		apps[old.Key] = old | ||||||
| 	} | 	} | ||||||
| 	for _, item := range items { | 	for _, item := range items { | ||||||
| 		app, ok := apps[item.Key] | 		config := item.AppProperty | ||||||
|  | 		key := config.Key | ||||||
|  | 		app, ok := apps[key] | ||||||
| 		if !ok { | 		if !ok { | ||||||
| 			app = model.App{} | 			app = model.App{} | ||||||
| 		} | 		} | ||||||
|  | 		app.Resource = constant.AppResourceRemote | ||||||
| 		app.Name = item.Name | 		app.Name = item.Name | ||||||
| 		app.Limit = item.Limit | 		app.Limit = config.Limit | ||||||
| 		app.Key = item.Key | 		app.Key = key | ||||||
| 		app.ShortDescZh = item.ShortDescZh | 		app.ShortDescZh = config.ShortDescZh | ||||||
| 		app.ShortDescEn = item.ShortDescEn | 		app.ShortDescEn = config.ShortDescEn | ||||||
| 		app.Website = item.Website | 		app.Website = config.Website | ||||||
| 		app.Document = item.Document | 		app.Document = config.Document | ||||||
| 		app.Github = item.Github | 		app.Github = config.Github | ||||||
| 		app.Type = item.Type | 		app.Type = config.Type | ||||||
| 		app.CrossVersionUpdate = item.CrossVersionUpdate | 		app.CrossVersionUpdate = config.CrossVersionUpdate | ||||||
| 		app.Required = item.GetRequired() |  | ||||||
| 		app.Status = constant.AppNormal | 		app.Status = constant.AppNormal | ||||||
| 		apps[item.Key] = app | 		app.LastModified = item.LastModified | ||||||
|  | 		app.ReadMe = item.ReadMe | ||||||
|  | 		apps[key] = app | ||||||
| 	} | 	} | ||||||
| 	return apps | 	return apps | ||||||
| } | } | ||||||
|  |  | ||||||
|  | func handleLocalAppDetail(versionDir string, appDetail *model.AppDetail) error { | ||||||
|  | 	fileOp := files.NewFileOp() | ||||||
|  | 	dockerComposePath := path.Join(versionDir, "docker-compose.yml") | ||||||
|  | 	if !fileOp.Stat(dockerComposePath) { | ||||||
|  | 		return errors.New(i18n.GetMsgWithMap("ErrFileNotFound", map[string]interface{}{"name": "docker-compose.yml"})) | ||||||
|  | 	} | ||||||
|  | 	dockerComposeByte, _ := fileOp.GetContent(dockerComposePath) | ||||||
|  | 	if dockerComposeByte == nil { | ||||||
|  | 		return errors.New(i18n.GetMsgWithMap("ErrFileParseApp", map[string]interface{}{"name": "docker-compose.yml"})) | ||||||
|  | 	} | ||||||
|  | 	appDetail.DockerCompose = string(dockerComposeByte) | ||||||
|  | 	paramPath := path.Join(versionDir, "data.yml") | ||||||
|  | 	if !fileOp.Stat(paramPath) { | ||||||
|  | 		return errors.New(i18n.GetMsgWithMap("ErrFileNotFound", map[string]interface{}{"name": "data.yml"})) | ||||||
|  | 	} | ||||||
|  | 	paramByte, _ := fileOp.GetContent(paramPath) | ||||||
|  | 	if paramByte == nil { | ||||||
|  | 		return errors.New(i18n.GetMsgWithMap("ErrFileParseApp", map[string]interface{}{"name": "data.yml"})) | ||||||
|  | 	} | ||||||
|  | 	appParamConfig := dto.LocalAppParam{} | ||||||
|  | 	if err := yaml.Unmarshal(paramByte, &appParamConfig); err != nil { | ||||||
|  | 		return errors.New(i18n.GetMsgWithMap("ErrFileParseApp", map[string]interface{}{"name": "data.yml"})) | ||||||
|  | 	} | ||||||
|  | 	dataJson, err := json.Marshal(appParamConfig.AppParams) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return errors.New(i18n.GetMsgWithMap("ErrFileParseApp", map[string]interface{}{"name": "data.yml", "err": err.Error()})) | ||||||
|  | 	} | ||||||
|  | 	appDetail.Params = string(dataJson) | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func handleLocalApp(appDir string) (app *model.App, err error) { | ||||||
|  | 	fileOp := files.NewFileOp() | ||||||
|  | 	configYamlPath := path.Join(appDir, "data.yml") | ||||||
|  | 	if !fileOp.Stat(configYamlPath) { | ||||||
|  | 		err = errors.New(i18n.GetMsgWithMap("ErrFileNotFound", map[string]interface{}{"name": "data.yml"})) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	iconPath := path.Join(appDir, "logo.png") | ||||||
|  | 	if !fileOp.Stat(iconPath) { | ||||||
|  | 		err = errors.New(i18n.GetMsgWithMap("ErrFileNotFound", map[string]interface{}{"name": "logo.png"})) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	configYamlByte, err := fileOp.GetContent(configYamlPath) | ||||||
|  | 	if err != nil { | ||||||
|  | 		err = errors.New(i18n.GetMsgWithMap("ErrFileParseApp", map[string]interface{}{"name": "data.yml", "err": err.Error()})) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	localAppDefine := dto.LocalAppAppDefine{} | ||||||
|  | 	if err = yaml.Unmarshal(configYamlByte, &localAppDefine); err != nil { | ||||||
|  | 		err = errors.New(i18n.GetMsgWithMap("ErrFileParseApp", map[string]interface{}{"name": "data.yml", "err": err.Error()})) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	app = &localAppDefine.AppProperty | ||||||
|  | 	app.Resource = constant.AppResourceLocal | ||||||
|  | 	app.Status = constant.AppNormal | ||||||
|  | 	app.Recommend = 9999 | ||||||
|  | 	app.TagsKey = append(app.TagsKey, "Local") | ||||||
|  | 	app.Key = "local" + app.Key | ||||||
|  | 	readMePath := path.Join(appDir, "README.md") | ||||||
|  | 	readMeByte, err := fileOp.GetContent(readMePath) | ||||||
|  | 	if err == nil { | ||||||
|  | 		app.ReadMe = string(readMeByte) | ||||||
|  | 	} | ||||||
|  | 	iconByte, _ := fileOp.GetContent(iconPath) | ||||||
|  | 	if iconByte != nil { | ||||||
|  | 		iconStr := base64.StdEncoding.EncodeToString(iconByte) | ||||||
|  | 		app.Icon = iconStr | ||||||
|  | 	} | ||||||
|  | 	return | ||||||
|  | } | ||||||
|  |  | ||||||
| func handleErr(install model.AppInstall, err error, out string) error { | func handleErr(install model.AppInstall, err error, out string) error { | ||||||
| 	reErr := err | 	reErr := err | ||||||
| 	install.Message = err.Error() | 	install.Message = err.Error() | ||||||
| @@ -426,38 +753,19 @@ func handleErr(install model.AppInstall, err error, out string) error { | |||||||
| 		reErr = errors.New(out) | 		reErr = errors.New(out) | ||||||
| 		install.Status = constant.Error | 		install.Status = constant.Error | ||||||
| 	} | 	} | ||||||
| 	_ = appInstallRepo.Save(&install) | 	_ = appInstallRepo.Save(context.Background(), &install) | ||||||
| 	return reErr | 	return reErr | ||||||
| } | } | ||||||
|  |  | ||||||
| func getAppFromRepo(downloadPath, version string) error { |  | ||||||
| 	downloadUrl := downloadPath |  | ||||||
| 	appDir := constant.AppResourceDir |  | ||||||
|  |  | ||||||
| 	global.LOG.Infof("download file from %s", downloadUrl) |  | ||||||
| 	fileOp := files.NewFileOp() |  | ||||||
| 	if _, err := fileOp.CopyAndBackup(appDir); err != nil { |  | ||||||
| 		return err |  | ||||||
| 	} |  | ||||||
| 	packagePath := path.Join(constant.ResourceDir, path.Base(downloadUrl)) |  | ||||||
| 	if err := fileOp.DownloadFile(downloadUrl, packagePath); err != nil { |  | ||||||
| 		return err |  | ||||||
| 	} |  | ||||||
| 	if err := fileOp.Decompress(packagePath, constant.ResourceDir, files.TarGz); err != nil { |  | ||||||
| 		return err |  | ||||||
| 	} |  | ||||||
| 	_ = NewISettingService().Update("AppStoreVersion", version) |  | ||||||
| 	defer func() { |  | ||||||
| 		_ = fileOp.DeleteFile(packagePath) |  | ||||||
| 	}() |  | ||||||
| 	return nil |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func handleInstalled(appInstallList []model.AppInstall, updated bool) ([]response.AppInstalledDTO, error) { | func handleInstalled(appInstallList []model.AppInstall, updated bool) ([]response.AppInstalledDTO, error) { | ||||||
| 	var res []response.AppInstalledDTO | 	var res []response.AppInstalledDTO | ||||||
| 	for _, installed := range appInstallList { | 	for _, installed := range appInstallList { | ||||||
|  | 		if updated && (installed.App.Type == "php" || installed.Status == constant.Installing || (installed.App.Key == constant.AppMysql && installed.Version == "5.6.51")) { | ||||||
|  | 			continue | ||||||
|  | 		} | ||||||
| 		installDTO := response.AppInstalledDTO{ | 		installDTO := response.AppInstalledDTO{ | ||||||
| 			AppInstall: installed, | 			AppInstall: installed, | ||||||
|  | 			Path:       installed.GetPath(), | ||||||
| 		} | 		} | ||||||
| 		app, err := appRepo.GetFirst(commonRepo.WithByID(installed.AppId)) | 		app, err := appRepo.GetFirst(commonRepo.WithByID(installed.AppId)) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| @@ -469,9 +777,18 @@ func handleInstalled(appInstallList []model.AppInstall, updated bool) ([]respons | |||||||
| 		} | 		} | ||||||
| 		var versions []string | 		var versions []string | ||||||
| 		for _, detail := range details { | 		for _, detail := range details { | ||||||
|  | 			if detail.IgnoreUpgrade { | ||||||
|  | 				continue | ||||||
|  | 			} | ||||||
|  | 			if common.IsCrossVersion(installed.Version, detail.Version) && !app.CrossVersionUpdate { | ||||||
|  | 				continue | ||||||
|  | 			} | ||||||
| 			versions = append(versions, detail.Version) | 			versions = append(versions, detail.Version) | ||||||
| 		} | 		} | ||||||
| 		versions = common.GetSortedVersions(versions) | 		versions = common.GetSortedVersions(versions) | ||||||
|  | 		if len(versions) == 0 { | ||||||
|  | 			continue | ||||||
|  | 		} | ||||||
| 		lastVersion := versions[0] | 		lastVersion := versions[0] | ||||||
| 		if common.IsCrossVersion(installed.Version, lastVersion) { | 		if common.IsCrossVersion(installed.Version, lastVersion) { | ||||||
| 			installDTO.CanUpdate = app.CrossVersionUpdate | 			installDTO.CanUpdate = app.CrossVersionUpdate | ||||||
| @@ -501,7 +818,7 @@ func getAppInstallByKey(key string) (model.AppInstall, error) { | |||||||
| 	return appInstall, nil | 	return appInstall, nil | ||||||
| } | } | ||||||
|  |  | ||||||
| func updateToolApp(installed model.AppInstall) { | func updateToolApp(installed *model.AppInstall) { | ||||||
| 	tooKey, ok := dto.AppToolMap[installed.App.Key] | 	tooKey, ok := dto.AppToolMap[installed.App.Key] | ||||||
| 	if !ok { | 	if !ok { | ||||||
| 		return | 		return | ||||||
| @@ -537,7 +854,7 @@ func updateToolApp(installed model.AppInstall) { | |||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
| 	toolInstall.Env = string(contentByte) | 	toolInstall.Env = string(contentByte) | ||||||
| 	if err := appInstallRepo.Save(&toolInstall); err != nil { | 	if err := appInstallRepo.Save(context.Background(), &toolInstall); err != nil { | ||||||
| 		global.LOG.Errorf("update tool app [%s] error : %s", toolInstall.Name, err.Error()) | 		global.LOG.Errorf("update tool app [%s] error : %s", toolInstall.Name, err.Error()) | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
| @@ -550,3 +867,108 @@ func updateToolApp(installed model.AppInstall) { | |||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
|  | func addDockerComposeCommonParam(composeMap map[string]interface{}, serviceName string, req request.AppContainerConfig, params map[string]interface{}) error { | ||||||
|  | 	services, serviceValid := composeMap["services"].(map[string]interface{}) | ||||||
|  | 	if !serviceValid { | ||||||
|  | 		return buserr.New(constant.ErrFileParse) | ||||||
|  | 	} | ||||||
|  | 	service, serviceExist := services[serviceName] | ||||||
|  | 	if !serviceExist { | ||||||
|  | 		return buserr.New(constant.ErrFileParse) | ||||||
|  | 	} | ||||||
|  | 	serviceValue := service.(map[string]interface{}) | ||||||
|  | 	deploy := map[string]interface{}{ | ||||||
|  | 		"resources": map[string]interface{}{ | ||||||
|  | 			"limits": map[string]interface{}{ | ||||||
|  | 				"cpus":   "${CPUS}", | ||||||
|  | 				"memory": "${MEMORY_LIMIT}", | ||||||
|  | 			}, | ||||||
|  | 		}, | ||||||
|  | 	} | ||||||
|  | 	serviceValue["deploy"] = deploy | ||||||
|  |  | ||||||
|  | 	ports, ok := serviceValue["ports"].([]interface{}) | ||||||
|  | 	if ok { | ||||||
|  | 		for i, port := range ports { | ||||||
|  | 			portStr, portOK := port.(string) | ||||||
|  | 			if !portOK { | ||||||
|  | 				continue | ||||||
|  | 			} | ||||||
|  | 			portArray := strings.Split(portStr, ":") | ||||||
|  | 			if len(portArray) == 2 { | ||||||
|  | 				portArray = append([]string{"${HOST_IP}"}, portArray...) | ||||||
|  | 			} | ||||||
|  | 			ports[i] = strings.Join(portArray, ":") | ||||||
|  | 		} | ||||||
|  | 		serviceValue["ports"] = ports | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	params[constant.CPUS] = "0" | ||||||
|  | 	params[constant.MemoryLimit] = "0" | ||||||
|  | 	if req.Advanced { | ||||||
|  | 		if req.CpuQuota > 0 { | ||||||
|  | 			params[constant.CPUS] = req.CpuQuota | ||||||
|  | 		} | ||||||
|  | 		if req.MemoryLimit > 0 { | ||||||
|  | 			params[constant.MemoryLimit] = strconv.FormatFloat(req.MemoryLimit, 'f', -1, 32) + req.MemoryUnit | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	_, portExist := serviceValue["ports"].([]interface{}) | ||||||
|  | 	if portExist { | ||||||
|  | 		allowHost := "127.0.0.1" | ||||||
|  | 		if req.Advanced && req.AllowPort { | ||||||
|  | 			allowHost = "0.0.0.0" | ||||||
|  | 		} | ||||||
|  | 		params[constant.HostIP] = allowHost | ||||||
|  | 	} | ||||||
|  | 	services[serviceName] = serviceValue | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func getAppCommonConfig(envs map[string]interface{}) request.AppContainerConfig { | ||||||
|  | 	config := request.AppContainerConfig{} | ||||||
|  |  | ||||||
|  | 	if hostIp, ok := envs[constant.HostIP]; ok { | ||||||
|  | 		config.AllowPort = hostIp.(string) == "0.0.0.0" | ||||||
|  | 	} else { | ||||||
|  | 		config.AllowPort = true | ||||||
|  | 	} | ||||||
|  | 	if cpuCore, ok := envs[constant.CPUS]; ok { | ||||||
|  | 		numStr, ok := cpuCore.(string) | ||||||
|  | 		if ok { | ||||||
|  | 			num, err := strconv.ParseFloat(numStr, 64) | ||||||
|  | 			if err == nil { | ||||||
|  | 				config.CpuQuota = num | ||||||
|  | 			} | ||||||
|  | 		} else { | ||||||
|  | 			num64, flOk := cpuCore.(float64) | ||||||
|  | 			if flOk { | ||||||
|  | 				config.CpuQuota = num64 | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	} else { | ||||||
|  | 		config.CpuQuota = 0 | ||||||
|  | 	} | ||||||
|  | 	if memLimit, ok := envs[constant.MemoryLimit]; ok { | ||||||
|  | 		re := regexp.MustCompile(`(\d+)([A-Za-z]+)`) | ||||||
|  | 		matches := re.FindStringSubmatch(memLimit.(string)) | ||||||
|  | 		if len(matches) == 3 { | ||||||
|  | 			num, err := strconv.ParseFloat(matches[1], 64) | ||||||
|  | 			if err == nil { | ||||||
|  | 				unit := matches[2] | ||||||
|  | 				config.MemoryLimit = num | ||||||
|  | 				config.MemoryUnit = unit | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	} else { | ||||||
|  | 		config.MemoryLimit = 0 | ||||||
|  | 		config.MemoryUnit = "M" | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if containerName, ok := envs[constant.ContainerName]; ok { | ||||||
|  | 		config.ContainerName = containerName.(string) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return config | ||||||
|  | } | ||||||
|   | |||||||
| @@ -1,9 +1,7 @@ | |||||||
| package service | package service | ||||||
|  |  | ||||||
| import ( | import ( | ||||||
| 	"fmt" |  | ||||||
| 	"strconv" | 	"strconv" | ||||||
| 	"time" |  | ||||||
|  |  | ||||||
| 	"github.com/1Panel-dev/1Panel/backend/app/dto" | 	"github.com/1Panel-dev/1Panel/backend/app/dto" | ||||||
| 	"github.com/1Panel-dev/1Panel/backend/constant" | 	"github.com/1Panel-dev/1Panel/backend/constant" | ||||||
| @@ -19,45 +17,27 @@ import ( | |||||||
| type AuthService struct{} | type AuthService struct{} | ||||||
|  |  | ||||||
| type IAuthService interface { | type IAuthService interface { | ||||||
| 	SafetyStatus(c *gin.Context) error | 	CheckIsSafety(code string) (string, error) | ||||||
| 	CheckIsFirst() bool |  | ||||||
| 	InitUser(c *gin.Context, req dto.InitUser) error |  | ||||||
| 	VerifyCode(code string) (bool, error) | 	VerifyCode(code string) (bool, error) | ||||||
| 	SafeEntrance(c *gin.Context, code string) error |  | ||||||
| 	Login(c *gin.Context, info dto.Login) (*dto.UserLoginInfo, error) | 	Login(c *gin.Context, info dto.Login) (*dto.UserLoginInfo, error) | ||||||
| 	LogOut(c *gin.Context) error | 	LogOut(c *gin.Context) error | ||||||
|  | 	MFALogin(c *gin.Context, info dto.MFALogin) (*dto.UserLoginInfo, error) | ||||||
| } | } | ||||||
|  |  | ||||||
| func NewIAuthService() IAuthService { | func NewIAuthService() IAuthService { | ||||||
| 	return &AuthService{} | 	return &AuthService{} | ||||||
| } | } | ||||||
|  |  | ||||||
| func (u *AuthService) SafeEntrance(c *gin.Context, code string) error { |  | ||||||
| 	codeWithMD5 := encrypt.Md5(code) |  | ||||||
| 	cookieValue, _ := encrypt.StringEncrypt(codeWithMD5) |  | ||||||
| 	c.SetCookie(codeWithMD5, cookieValue, 604800, "", "", false, false) |  | ||||||
|  |  | ||||||
| 	expiredSetting, err := settingRepo.Get(settingRepo.WithByKey("ExpirationDays")) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return err |  | ||||||
| 	} |  | ||||||
| 	timeout, _ := strconv.Atoi(expiredSetting.Value) |  | ||||||
| 	if err := settingRepo.Update("ExpirationTime", time.Now().AddDate(0, 0, timeout).Format("2006-01-02 15:04:05")); err != nil { |  | ||||||
| 		return err |  | ||||||
| 	} |  | ||||||
| 	return nil |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (u *AuthService) Login(c *gin.Context, info dto.Login) (*dto.UserLoginInfo, error) { | func (u *AuthService) Login(c *gin.Context, info dto.Login) (*dto.UserLoginInfo, error) { | ||||||
| 	nameSetting, err := settingRepo.Get(settingRepo.WithByKey("UserName")) | 	nameSetting, err := settingRepo.Get(settingRepo.WithByKey("UserName")) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return nil, errors.WithMessage(constant.ErrRecordNotFound, err.Error()) | 		return nil, errors.WithMessage(constant.ErrRecordNotFound, err.Error()) | ||||||
| 	} | 	} | ||||||
| 	passwrodSetting, err := settingRepo.Get(settingRepo.WithByKey("Password")) | 	passwordSetting, err := settingRepo.Get(settingRepo.WithByKey("Password")) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return nil, errors.WithMessage(constant.ErrRecordNotFound, err.Error()) | 		return nil, errors.WithMessage(constant.ErrRecordNotFound, err.Error()) | ||||||
| 	} | 	} | ||||||
| 	pass, err := encrypt.StringDecrypt(passwrodSetting.Value) | 	pass, err := encrypt.StringDecrypt(passwordSetting.Value) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return nil, constant.ErrAuth | 		return nil, constant.ErrAuth | ||||||
| 	} | 	} | ||||||
| @@ -80,11 +60,11 @@ func (u *AuthService) MFALogin(c *gin.Context, info dto.MFALogin) (*dto.UserLogi | |||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return nil, errors.WithMessage(constant.ErrRecordNotFound, err.Error()) | 		return nil, errors.WithMessage(constant.ErrRecordNotFound, err.Error()) | ||||||
| 	} | 	} | ||||||
| 	passwrodSetting, err := settingRepo.Get(settingRepo.WithByKey("Password")) | 	passwordSetting, err := settingRepo.Get(settingRepo.WithByKey("Password")) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return nil, errors.WithMessage(constant.ErrRecordNotFound, err.Error()) | 		return nil, errors.WithMessage(constant.ErrRecordNotFound, err.Error()) | ||||||
| 	} | 	} | ||||||
| 	pass, err := encrypt.StringDecrypt(passwrodSetting.Value) | 	pass, err := encrypt.StringDecrypt(passwordSetting.Value) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return nil, err | 		return nil, err | ||||||
| 	} | 	} | ||||||
| @@ -96,7 +76,11 @@ func (u *AuthService) MFALogin(c *gin.Context, info dto.MFALogin) (*dto.UserLogi | |||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return nil, err | 		return nil, err | ||||||
| 	} | 	} | ||||||
| 	success := mfa.ValidCode(info.Code, mfaSecret.Value) | 	mfaInterval, err := settingRepo.Get(settingRepo.WithByKey("MFAInterval")) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 	success := mfa.ValidCode(info.Code, mfaInterval.Value, mfaSecret.Value) | ||||||
| 	if !success { | 	if !success { | ||||||
| 		return nil, constant.ErrAuth | 		return nil, constant.ErrAuth | ||||||
| 	} | 	} | ||||||
| @@ -118,7 +102,7 @@ func (u *AuthService) generateSession(c *gin.Context, name, authMethod string) ( | |||||||
| 		j := jwt.NewJWT() | 		j := jwt.NewJWT() | ||||||
| 		claims := j.CreateClaims(jwt.BaseClaims{ | 		claims := j.CreateClaims(jwt.BaseClaims{ | ||||||
| 			Name: name, | 			Name: name, | ||||||
| 		}, lifeTime) | 		}) | ||||||
| 		token, err := j.CreateToken(claims) | 		token, err := j.CreateToken(claims) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			return nil, err | 			return nil, err | ||||||
| @@ -129,7 +113,7 @@ func (u *AuthService) generateSession(c *gin.Context, name, authMethod string) ( | |||||||
| 	sessionUser, err := global.SESSION.Get(sID) | 	sessionUser, err := global.SESSION.Get(sID) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		sID = uuid.New().String() | 		sID = uuid.New().String() | ||||||
| 		c.SetCookie(constant.SessionName, sID, 604800, "", "", false, false) | 		c.SetCookie(constant.SessionName, sID, 0, "", "", false, false) | ||||||
| 		err := global.SESSION.Set(sID, sessionUser, lifeTime) | 		err := global.SESSION.Set(sID, sessionUser, lifeTime) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			return nil, err | 			return nil, err | ||||||
| @@ -163,57 +147,16 @@ func (u *AuthService) VerifyCode(code string) (bool, error) { | |||||||
| 	return setting.Value == code, nil | 	return setting.Value == code, nil | ||||||
| } | } | ||||||
|  |  | ||||||
| func (u *AuthService) SafetyStatus(c *gin.Context) error { | func (u *AuthService) CheckIsSafety(code string) (string, error) { | ||||||
| 	setting, err := settingRepo.Get(settingRepo.WithByKey("SecurityEntrance")) | 	status, err := settingRepo.Get(settingRepo.WithByKey("SecurityEntrance")) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return err | 		return "", err | ||||||
| 	} | 	} | ||||||
| 	codeWithEcrypt, err := c.Cookie(encrypt.Md5(setting.Value)) | 	if len(status.Value) == 0 { | ||||||
| 	if err != nil { | 		return "disable", nil | ||||||
| 		return err |  | ||||||
| 	} | 	} | ||||||
| 	code, err := encrypt.StringDecrypt(codeWithEcrypt) | 	if status.Value == code { | ||||||
| 	if err != nil { | 		return "pass", nil | ||||||
| 		return err |  | ||||||
| 	} | 	} | ||||||
| 	if code != encrypt.Md5(setting.Value) { | 	return "unpass", nil | ||||||
| 		return errors.New("code not match") |  | ||||||
| 	} |  | ||||||
| 	return nil |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (u *AuthService) CheckIsFirst() bool { |  | ||||||
| 	user, _ := settingRepo.Get(settingRepo.WithByKey("UserName")) |  | ||||||
| 	pass, _ := settingRepo.Get(settingRepo.WithByKey("Password")) |  | ||||||
| 	return len(user.Value) == 0 || len(pass.Value) == 0 |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (u *AuthService) InitUser(c *gin.Context, req dto.InitUser) error { |  | ||||||
| 	user, _ := settingRepo.Get(settingRepo.WithByKey("UserName")) |  | ||||||
| 	pass, _ := settingRepo.Get(settingRepo.WithByKey("Password")) |  | ||||||
| 	if len(user.Value) == 0 || len(pass.Value) == 0 { |  | ||||||
| 		newPass, err := encrypt.StringEncrypt(req.Password) |  | ||||||
| 		if err != nil { |  | ||||||
| 			return err |  | ||||||
| 		} |  | ||||||
| 		if err := settingRepo.Update("UserName", req.Name); err != nil { |  | ||||||
| 			return err |  | ||||||
| 		} |  | ||||||
| 		if err := settingRepo.Update("Password", newPass); err != nil { |  | ||||||
| 			return err |  | ||||||
| 		} |  | ||||||
| 		expiredSetting, err := settingRepo.Get(settingRepo.WithByKey("ExpirationDays")) |  | ||||||
| 		if err != nil { |  | ||||||
| 			return err |  | ||||||
| 		} |  | ||||||
| 		timeout, _ := strconv.Atoi(expiredSetting.Value) |  | ||||||
| 		if timeout != 0 { |  | ||||||
| 			if err := settingRepo.Update("ExpirationTime", time.Now().AddDate(0, 0, timeout).Format("2006-01-02 15:04:05")); err != nil { |  | ||||||
| 				return err |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 		return nil |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	return fmt.Errorf("can't init user because user %s is in system", user.Value) |  | ||||||
| } | } | ||||||
|   | |||||||
| @@ -2,17 +2,23 @@ package service | |||||||
|  |  | ||||||
| import ( | import ( | ||||||
| 	"context" | 	"context" | ||||||
|  | 	"encoding/base64" | ||||||
| 	"encoding/json" | 	"encoding/json" | ||||||
| 	"fmt" | 	"fmt" | ||||||
|  | 	"io" | ||||||
|  | 	"net/http" | ||||||
|  | 	"net/url" | ||||||
| 	"os" | 	"os" | ||||||
|  | 	"path" | ||||||
| 	"strings" | 	"strings" | ||||||
|  |  | ||||||
| 	"github.com/1Panel-dev/1Panel/backend/app/dto" | 	"github.com/1Panel-dev/1Panel/backend/app/dto" | ||||||
| 	"github.com/1Panel-dev/1Panel/backend/app/model" | 	"github.com/1Panel-dev/1Panel/backend/app/model" | ||||||
|  | 	"github.com/1Panel-dev/1Panel/backend/buserr" | ||||||
| 	"github.com/1Panel-dev/1Panel/backend/constant" | 	"github.com/1Panel-dev/1Panel/backend/constant" | ||||||
| 	"github.com/1Panel-dev/1Panel/backend/global" | 	"github.com/1Panel-dev/1Panel/backend/global" | ||||||
| 	"github.com/1Panel-dev/1Panel/backend/utils/cloud_storage" | 	"github.com/1Panel-dev/1Panel/backend/utils/cloud_storage" | ||||||
| 	"github.com/1Panel-dev/1Panel/backend/utils/cmd" | 	fileUtils "github.com/1Panel-dev/1Panel/backend/utils/files" | ||||||
| 	"github.com/jinzhu/copier" | 	"github.com/jinzhu/copier" | ||||||
| 	"github.com/pkg/errors" | 	"github.com/pkg/errors" | ||||||
| ) | ) | ||||||
| @@ -22,11 +28,12 @@ type BackupService struct{} | |||||||
| type IBackupService interface { | type IBackupService interface { | ||||||
| 	List() ([]dto.BackupInfo, error) | 	List() ([]dto.BackupInfo, error) | ||||||
| 	SearchRecordsWithPage(search dto.RecordSearch) (int64, []dto.BackupRecords, error) | 	SearchRecordsWithPage(search dto.RecordSearch) (int64, []dto.BackupRecords, error) | ||||||
|  | 	LoadOneDriveInfo() (string, error) | ||||||
| 	DownloadRecord(info dto.DownloadRecord) (string, error) | 	DownloadRecord(info dto.DownloadRecord) (string, error) | ||||||
| 	Create(backupDto dto.BackupOperate) error | 	Create(backupDto dto.BackupOperate) error | ||||||
| 	GetBuckets(backupDto dto.ForBuckets) ([]interface{}, error) | 	GetBuckets(backupDto dto.ForBuckets) ([]interface{}, error) | ||||||
| 	Update(ireq dto.BackupOperate) error | 	Update(ireq dto.BackupOperate) error | ||||||
| 	BatchDelete(ids []uint) error | 	Delete(id uint) error | ||||||
| 	BatchDeleteRecord(ids []uint) error | 	BatchDeleteRecord(ids []uint) error | ||||||
| 	NewClient(backup *model.BackupAccount) (cloud_storage.CloudStorageClient, error) | 	NewClient(backup *model.BackupAccount) (cloud_storage.CloudStorageClient, error) | ||||||
|  |  | ||||||
| @@ -53,36 +60,14 @@ func NewIBackupService() IBackupService { | |||||||
| func (u *BackupService) List() ([]dto.BackupInfo, error) { | func (u *BackupService) List() ([]dto.BackupInfo, error) { | ||||||
| 	ops, err := backupRepo.List(commonRepo.WithOrderBy("created_at desc")) | 	ops, err := backupRepo.List(commonRepo.WithOrderBy("created_at desc")) | ||||||
| 	var dtobas []dto.BackupInfo | 	var dtobas []dto.BackupInfo | ||||||
| 	ossExist, s3Exist, sftpExist, minioExist := false, false, false, false | 	dtobas = append(dtobas, u.loadByType("LOCAL", ops)) | ||||||
| 	for _, group := range ops { | 	dtobas = append(dtobas, u.loadByType("OSS", ops)) | ||||||
| 		switch group.Type { | 	dtobas = append(dtobas, u.loadByType("S3", ops)) | ||||||
| 		case "OSS": | 	dtobas = append(dtobas, u.loadByType("SFTP", ops)) | ||||||
| 			ossExist = true | 	dtobas = append(dtobas, u.loadByType("MINIO", ops)) | ||||||
| 		case "S3": | 	dtobas = append(dtobas, u.loadByType("COS", ops)) | ||||||
| 			s3Exist = true | 	dtobas = append(dtobas, u.loadByType("KODO", ops)) | ||||||
| 		case "SFTP": | 	dtobas = append(dtobas, u.loadByType("OneDrive", ops)) | ||||||
| 			sftpExist = true |  | ||||||
| 		case "MINIO": |  | ||||||
| 			minioExist = true |  | ||||||
| 		} |  | ||||||
| 		var item dto.BackupInfo |  | ||||||
| 		if err := copier.Copy(&item, &group); err != nil { |  | ||||||
| 			return nil, errors.WithMessage(constant.ErrStructTransform, err.Error()) |  | ||||||
| 		} |  | ||||||
| 		dtobas = append(dtobas, item) |  | ||||||
| 	} |  | ||||||
| 	if !ossExist { |  | ||||||
| 		dtobas = append(dtobas, dto.BackupInfo{Type: "OSS"}) |  | ||||||
| 	} |  | ||||||
| 	if !s3Exist { |  | ||||||
| 		dtobas = append(dtobas, dto.BackupInfo{Type: "S3"}) |  | ||||||
| 	} |  | ||||||
| 	if !sftpExist { |  | ||||||
| 		dtobas = append(dtobas, dto.BackupInfo{Type: "SFTP"}) |  | ||||||
| 	} |  | ||||||
| 	if !minioExist { |  | ||||||
| 		dtobas = append(dtobas, dto.BackupInfo{Type: "MINIO"}) |  | ||||||
| 	} |  | ||||||
| 	return dtobas, err | 	return dtobas, err | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -105,6 +90,18 @@ func (u *BackupService) SearchRecordsWithPage(search dto.RecordSearch) (int64, [ | |||||||
| 	return total, dtobas, err | 	return total, dtobas, err | ||||||
| } | } | ||||||
|  |  | ||||||
|  | func (u *BackupService) LoadOneDriveInfo() (string, error) { | ||||||
|  | 	OneDriveID, err := settingRepo.Get(settingRepo.WithByKey("OneDriveID")) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return "", err | ||||||
|  | 	} | ||||||
|  | 	idItem, err := base64.StdEncoding.DecodeString(OneDriveID.Value) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return "", err | ||||||
|  | 	} | ||||||
|  | 	return string(idItem), err | ||||||
|  | } | ||||||
|  |  | ||||||
| func (u *BackupService) DownloadRecord(info dto.DownloadRecord) (string, error) { | func (u *BackupService) DownloadRecord(info dto.DownloadRecord) (string, error) { | ||||||
| 	if info.Source == "LOCAL" { | 	if info.Source == "LOCAL" { | ||||||
| 		return info.FileDir + "/" + info.FileName, nil | 		return info.FileDir + "/" + info.FileName, nil | ||||||
| @@ -117,29 +114,35 @@ func (u *BackupService) DownloadRecord(info dto.DownloadRecord) (string, error) | |||||||
| 	if err := json.Unmarshal([]byte(backup.Vars), &varMap); err != nil { | 	if err := json.Unmarshal([]byte(backup.Vars), &varMap); err != nil { | ||||||
| 		return "", err | 		return "", err | ||||||
| 	} | 	} | ||||||
| 	varMap["type"] = backup.Type |  | ||||||
| 	varMap["bucket"] = backup.Bucket | 	varMap["bucket"] = backup.Bucket | ||||||
| 	switch backup.Type { | 	switch backup.Type { | ||||||
| 	case constant.Sftp: | 	case constant.Sftp: | ||||||
| 		varMap["username"] = backup.AccessKey | 		varMap["username"] = backup.AccessKey | ||||||
| 		varMap["password"] = backup.Credential | 		varMap["password"] = backup.Credential | ||||||
| 	case constant.OSS, constant.S3, constant.MinIo: | 	case constant.OSS, constant.S3, constant.MinIo, constant.Cos, constant.Kodo: | ||||||
| 		varMap["accessKey"] = backup.AccessKey | 		varMap["accessKey"] = backup.AccessKey | ||||||
| 		varMap["secretKey"] = backup.Credential | 		varMap["secretKey"] = backup.Credential | ||||||
|  | 	case constant.OneDrive: | ||||||
|  | 		varMap["accessToken"] = backup.Credential | ||||||
| 	} | 	} | ||||||
| 	backClient, err := cloud_storage.NewCloudStorageClient(varMap) | 	backClient, err := cloud_storage.NewCloudStorageClient(backup.Type, varMap) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return "", fmt.Errorf("new cloud storage client failed, err: %v", err) | 		return "", fmt.Errorf("new cloud storage client failed, err: %v", err) | ||||||
| 	} | 	} | ||||||
| 	tempPath := fmt.Sprintf("%sdownload%s", constant.DataDir, info.FileDir) | 	targetPath := fmt.Sprintf("%s/download/%s/%s", constant.DataDir, info.FileDir, info.FileName) | ||||||
| 	if _, err := os.Stat(tempPath); err != nil && os.IsNotExist(err) { | 	if _, err := os.Stat(path.Dir(targetPath)); err != nil && os.IsNotExist(err) { | ||||||
| 		if err = os.MkdirAll(tempPath, os.ModePerm); err != nil { | 		if err = os.MkdirAll(path.Dir(targetPath), os.ModePerm); err != nil { | ||||||
| 			global.LOG.Errorf("mkdir %s failed, err: %v", tempPath, err) | 			global.LOG.Errorf("mkdir %s failed, err: %v", path.Dir(targetPath), err) | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 	targetPath := tempPath + info.FileName | 	srcPath := fmt.Sprintf("%s/%s", info.FileDir, info.FileName) | ||||||
| 	if _, err = os.Stat(targetPath); err != nil && os.IsNotExist(err) { | 	if len(backup.BackupPath) != 0 { | ||||||
| 		isOK, err := backClient.Download(info.FileName, targetPath) | 		itemPath := strings.TrimPrefix(backup.BackupPath, "/") | ||||||
|  | 		itemPath = strings.TrimSuffix(itemPath, "/") + "/" | ||||||
|  | 		srcPath = itemPath + srcPath | ||||||
|  | 	} | ||||||
|  | 	if exist, _ := backClient.Exist(srcPath); exist { | ||||||
|  | 		isOK, err := backClient.Download(srcPath, targetPath) | ||||||
| 		if !isOK { | 		if !isOK { | ||||||
| 			return "", fmt.Errorf("cloud storage download failed, err: %v", err) | 			return "", fmt.Errorf("cloud storage download failed, err: %v", err) | ||||||
| 		} | 		} | ||||||
| @@ -155,6 +158,12 @@ func (u *BackupService) Create(backupDto dto.BackupOperate) error { | |||||||
| 	if err := copier.Copy(&backup, &backupDto); err != nil { | 	if err := copier.Copy(&backup, &backupDto); err != nil { | ||||||
| 		return errors.WithMessage(constant.ErrStructTransform, err.Error()) | 		return errors.WithMessage(constant.ErrStructTransform, err.Error()) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | 	if backupDto.Type == constant.OneDrive { | ||||||
|  | 		if err := u.loadAccessToken(&backup); err != nil { | ||||||
|  | 			return err | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
| 	if err := backupRepo.Create(&backup); err != nil { | 	if err := backupRepo.Create(&backup); err != nil { | ||||||
| 		return err | 		return err | ||||||
| 	} | 	} | ||||||
| @@ -166,24 +175,27 @@ func (u *BackupService) GetBuckets(backupDto dto.ForBuckets) ([]interface{}, err | |||||||
| 	if err := json.Unmarshal([]byte(backupDto.Vars), &varMap); err != nil { | 	if err := json.Unmarshal([]byte(backupDto.Vars), &varMap); err != nil { | ||||||
| 		return nil, err | 		return nil, err | ||||||
| 	} | 	} | ||||||
| 	varMap["type"] = backupDto.Type |  | ||||||
| 	switch backupDto.Type { | 	switch backupDto.Type { | ||||||
| 	case constant.Sftp: | 	case constant.Sftp: | ||||||
| 		varMap["username"] = backupDto.AccessKey | 		varMap["username"] = backupDto.AccessKey | ||||||
| 		varMap["password"] = backupDto.Credential | 		varMap["password"] = backupDto.Credential | ||||||
| 	case constant.OSS, constant.S3, constant.MinIo: | 	case constant.OSS, constant.S3, constant.MinIo, constant.Cos, constant.Kodo: | ||||||
| 		varMap["accessKey"] = backupDto.AccessKey | 		varMap["accessKey"] = backupDto.AccessKey | ||||||
| 		varMap["secretKey"] = backupDto.Credential | 		varMap["secretKey"] = backupDto.Credential | ||||||
| 	} | 	} | ||||||
| 	client, err := cloud_storage.NewCloudStorageClient(varMap) | 	client, err := cloud_storage.NewCloudStorageClient(backupDto.Type, varMap) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return nil, err | 		return nil, err | ||||||
| 	} | 	} | ||||||
| 	return client.ListBuckets() | 	return client.ListBuckets() | ||||||
| } | } | ||||||
|  |  | ||||||
| func (u *BackupService) BatchDelete(ids []uint) error { | func (u *BackupService) Delete(id uint) error { | ||||||
| 	return backupRepo.Delete(commonRepo.WithIdsIn(ids)) | 	cronjobs, _ := cronjobRepo.List(cronjobRepo.WithByBackupID(id)) | ||||||
|  | 	if len(cronjobs) != 0 { | ||||||
|  | 		return buserr.New(constant.ErrBackupInUsed) | ||||||
|  | 	} | ||||||
|  | 	return backupRepo.Delete(commonRepo.WithByID(id)) | ||||||
| } | } | ||||||
|  |  | ||||||
| func (u *BackupService) BatchDeleteRecord(ids []uint) error { | func (u *BackupService) BatchDeleteRecord(ids []uint) error { | ||||||
| @@ -197,7 +209,7 @@ func (u *BackupService) BatchDeleteRecord(ids []uint) error { | |||||||
| 				global.LOG.Errorf("remove file %s failed, err: %v", record.FileDir+record.FileName, err) | 				global.LOG.Errorf("remove file %s failed, err: %v", record.FileDir+record.FileName, err) | ||||||
| 			} | 			} | ||||||
| 		} else { | 		} else { | ||||||
| 			backupAccount, err := backupRepo.Get(commonRepo.WithByName(record.Source)) | 			backupAccount, err := backupRepo.Get(commonRepo.WithByType(record.Source)) | ||||||
| 			if err != nil { | 			if err != nil { | ||||||
| 				return err | 				return err | ||||||
| 			} | 			} | ||||||
| @@ -231,7 +243,17 @@ func (u *BackupService) Update(req dto.BackupOperate) error { | |||||||
| 	upMap := make(map[string]interface{}) | 	upMap := make(map[string]interface{}) | ||||||
| 	upMap["bucket"] = req.Bucket | 	upMap["bucket"] = req.Bucket | ||||||
| 	upMap["credential"] = req.Credential | 	upMap["credential"] = req.Credential | ||||||
|  | 	upMap["backup_path"] = req.BackupPath | ||||||
| 	upMap["vars"] = req.Vars | 	upMap["vars"] = req.Vars | ||||||
|  | 	backup.Vars = req.Vars | ||||||
|  |  | ||||||
|  | 	if req.Type == constant.OneDrive { | ||||||
|  | 		if err := u.loadAccessToken(&backup); err != nil { | ||||||
|  | 			return err | ||||||
|  | 		} | ||||||
|  | 		upMap["credential"] = backup.Credential | ||||||
|  | 		upMap["vars"] = backup.Vars | ||||||
|  | 	} | ||||||
| 	if err := backupRepo.Update(req.ID, upMap); err != nil { | 	if err := backupRepo.Update(req.ID, upMap); err != nil { | ||||||
| 		return err | 		return err | ||||||
| 	} | 	} | ||||||
| @@ -241,7 +263,7 @@ func (u *BackupService) Update(req dto.BackupOperate) error { | |||||||
| 				if strings.HasSuffix(dirStr, "/") { | 				if strings.HasSuffix(dirStr, "/") { | ||||||
| 					dirStr = dirStr[:strings.LastIndex(dirStr, "/")] | 					dirStr = dirStr[:strings.LastIndex(dirStr, "/")] | ||||||
| 				} | 				} | ||||||
| 				if err := updateBackupDir(dirStr, oldDir); err != nil { | 				if err := copyDir(oldDir, dirStr); err != nil { | ||||||
| 					_ = backupRepo.Update(req.ID, (map[string]interface{}{"vars": oldVars})) | 					_ = backupRepo.Update(req.ID, (map[string]interface{}{"vars": oldVars})) | ||||||
| 					return err | 					return err | ||||||
| 				} | 				} | ||||||
| @@ -268,7 +290,6 @@ func (u *BackupService) NewClient(backup *model.BackupAccount) (cloud_storage.Cl | |||||||
| 	if err := json.Unmarshal([]byte(backup.Vars), &varMap); err != nil { | 	if err := json.Unmarshal([]byte(backup.Vars), &varMap); err != nil { | ||||||
| 		return nil, err | 		return nil, err | ||||||
| 	} | 	} | ||||||
| 	varMap["type"] = backup.Type |  | ||||||
| 	if backup.Type == "LOCAL" { | 	if backup.Type == "LOCAL" { | ||||||
| 		return nil, errors.New("not support") | 		return nil, errors.New("not support") | ||||||
| 	} | 	} | ||||||
| @@ -277,12 +298,14 @@ func (u *BackupService) NewClient(backup *model.BackupAccount) (cloud_storage.Cl | |||||||
| 	case constant.Sftp: | 	case constant.Sftp: | ||||||
| 		varMap["username"] = backup.AccessKey | 		varMap["username"] = backup.AccessKey | ||||||
| 		varMap["password"] = backup.Credential | 		varMap["password"] = backup.Credential | ||||||
| 	case constant.OSS, constant.S3, constant.MinIo: | 	case constant.OSS, constant.S3, constant.MinIo, constant.Cos, constant.Kodo: | ||||||
| 		varMap["accessKey"] = backup.AccessKey | 		varMap["accessKey"] = backup.AccessKey | ||||||
| 		varMap["secretKey"] = backup.Credential | 		varMap["secretKey"] = backup.Credential | ||||||
|  | 	case constant.OneDrive: | ||||||
|  | 		varMap["accessToken"] = backup.Credential | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	backClient, err := cloud_storage.NewCloudStorageClient(varMap) | 	backClient, err := cloud_storage.NewCloudStorageClient(backup.Type, varMap) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return nil, err | 		return nil, err | ||||||
| 	} | 	} | ||||||
| @@ -290,6 +313,66 @@ func (u *BackupService) NewClient(backup *model.BackupAccount) (cloud_storage.Cl | |||||||
| 	return backClient, nil | 	return backClient, nil | ||||||
| } | } | ||||||
|  |  | ||||||
|  | func (u *BackupService) loadByType(accountType string, accounts []model.BackupAccount) dto.BackupInfo { | ||||||
|  | 	for _, account := range accounts { | ||||||
|  | 		if account.Type == accountType { | ||||||
|  | 			var item dto.BackupInfo | ||||||
|  | 			if err := copier.Copy(&item, &account); err != nil { | ||||||
|  | 				global.LOG.Errorf("copy backup account to dto backup info failed, err: %v", err) | ||||||
|  | 			} | ||||||
|  | 			return item | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	return dto.BackupInfo{Type: accountType} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (u *BackupService) loadAccessToken(backup *model.BackupAccount) error { | ||||||
|  | 	varMap := make(map[string]interface{}) | ||||||
|  | 	if err := json.Unmarshal([]byte(backup.Vars), &varMap); err != nil { | ||||||
|  | 		return fmt.Errorf("unmarshal backup vars failed, err: %v", err) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	data := url.Values{} | ||||||
|  | 	data.Set("client_id", global.CONF.System.OneDriveID) | ||||||
|  | 	data.Set("client_secret", global.CONF.System.OneDriveSc) | ||||||
|  | 	data.Set("grant_type", "authorization_code") | ||||||
|  | 	data.Set("code", varMap["code"].(string)) | ||||||
|  | 	data.Set("redirect_uri", constant.OneDriveRedirectURI) | ||||||
|  | 	client := &http.Client{} | ||||||
|  | 	req, err := http.NewRequest("POST", "https://login.microsoftonline.com/common/oauth2/v2.0/token", strings.NewReader(data.Encode())) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return fmt.Errorf("new http post client for access token failed, err: %v", err) | ||||||
|  | 	} | ||||||
|  | 	req.Header.Add("Content-Type", "application/x-www-form-urlencoded") | ||||||
|  | 	resp, err := client.Do(req) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return fmt.Errorf("request for access token failed, err: %v", err) | ||||||
|  | 	} | ||||||
|  | 	delete(varMap, "code") | ||||||
|  | 	respBody, err := io.ReadAll(resp.Body) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return fmt.Errorf("read data from response body failed, err: %v", err) | ||||||
|  | 	} | ||||||
|  | 	defer resp.Body.Close() | ||||||
|  |  | ||||||
|  | 	token := map[string]interface{}{} | ||||||
|  | 	if err := json.Unmarshal(respBody, &token); err != nil { | ||||||
|  | 		return fmt.Errorf("unmarshal data from response body failed, err: %v", err) | ||||||
|  | 	} | ||||||
|  | 	accessToken, ok := token["refresh_token"].(string) | ||||||
|  | 	if !ok { | ||||||
|  | 		return errors.New("no such access token in response") | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	itemVars, err := json.Marshal(varMap) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return fmt.Errorf("json marshal var map failed, err: %v", err) | ||||||
|  | 	} | ||||||
|  | 	backup.Credential = accessToken | ||||||
|  | 	backup.Vars = string(itemVars) | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
| func loadLocalDir() (string, error) { | func loadLocalDir() (string, error) { | ||||||
| 	backup, err := backupRepo.Get(commonRepo.WithByType("LOCAL")) | 	backup, err := backupRepo.Get(commonRepo.WithByType("LOCAL")) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| @@ -314,18 +397,33 @@ func loadLocalDir() (string, error) { | |||||||
| 	return "", fmt.Errorf("error type dir: %T", varMap["dir"]) | 	return "", fmt.Errorf("error type dir: %T", varMap["dir"]) | ||||||
| } | } | ||||||
|  |  | ||||||
| func updateBackupDir(dir, oldDir string) error { | func copyDir(src, dst string) error { | ||||||
| 	if _, err := os.Stat(dir); err != nil && os.IsNotExist(err) { | 	srcInfo, err := os.Stat(src) | ||||||
| 		if err = os.MkdirAll(dir, os.ModePerm); err != nil { | 	if err != nil { | ||||||
| 			return err | 		return err | ||||||
|  | 	} | ||||||
|  | 	if err = os.MkdirAll(dst, srcInfo.Mode()); err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 	files, err := os.ReadDir(src) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	fileOP := fileUtils.NewFileOp() | ||||||
|  | 	for _, file := range files { | ||||||
|  | 		srcPath := fmt.Sprintf("%s/%s", src, file.Name()) | ||||||
|  | 		dstPath := fmt.Sprintf("%s/%s", dst, file.Name()) | ||||||
|  | 		if file.IsDir() { | ||||||
|  | 			if err = copyDir(srcPath, dstPath); err != nil { | ||||||
|  | 				global.LOG.Errorf("copy dir %s to %s failed, err: %v", srcPath, dstPath, err) | ||||||
|  | 			} | ||||||
|  | 		} else { | ||||||
|  | 			if err := fileOP.CopyFile(srcPath, dst); err != nil { | ||||||
|  | 				global.LOG.Errorf("copy file %s to %s failed, err: %v", srcPath, dstPath, err) | ||||||
|  | 			} | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 	if strings.HasSuffix(oldDir, "/") { |  | ||||||
| 		oldDir = oldDir[:strings.LastIndex(oldDir, "/")] |  | ||||||
| 	} |  | ||||||
| 	stdout, err := cmd.Execf("cp -r %s/* %s", oldDir, dir) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return errors.New(string(stdout)) |  | ||||||
| 	} |  | ||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
|   | |||||||
| @@ -1,6 +1,7 @@ | |||||||
| package service | package service | ||||||
|  |  | ||||||
| import ( | import ( | ||||||
|  | 	"context" | ||||||
| 	"encoding/json" | 	"encoding/json" | ||||||
| 	"fmt" | 	"fmt" | ||||||
| 	"io/fs" | 	"io/fs" | ||||||
| @@ -32,6 +33,7 @@ func (u *BackupService) AppBackup(req dto.CommonBackup) error { | |||||||
| 		return err | 		return err | ||||||
| 	} | 	} | ||||||
| 	timeNow := time.Now().Format("20060102150405") | 	timeNow := time.Now().Format("20060102150405") | ||||||
|  |  | ||||||
| 	backupDir := fmt.Sprintf("%s/app/%s/%s", localDir, req.Name, req.DetailName) | 	backupDir := fmt.Sprintf("%s/app/%s/%s", localDir, req.Name, req.DetailName) | ||||||
|  |  | ||||||
| 	fileName := fmt.Sprintf("%s_%s.tar.gz", req.DetailName, timeNow) | 	fileName := fmt.Sprintf("%s_%s.tar.gz", req.DetailName, timeNow) | ||||||
| @@ -97,7 +99,7 @@ func handleAppBackup(install *model.AppInstall, backupDir, fileName string) erro | |||||||
| 		return err | 		return err | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	appPath := fmt.Sprintf("%s/%s/%s", constant.AppInstallDir, install.App.Key, install.Name) | 	appPath := install.GetPath() | ||||||
| 	if err := handleTar(appPath, tmpDir, "app.tar.gz", ""); err != nil { | 	if err := handleTar(appPath, tmpDir, "app.tar.gz", ""); err != nil { | ||||||
| 		return err | 		return err | ||||||
| 	} | 	} | ||||||
| @@ -146,7 +148,7 @@ func handleAppRecover(install *model.AppInstall, recoverFile string, isRollback | |||||||
| 	if err := json.Unmarshal(appjson, &oldInstall); err != nil { | 	if err := json.Unmarshal(appjson, &oldInstall); err != nil { | ||||||
| 		return fmt.Errorf("unmarshal app.json failed, err: %v", err) | 		return fmt.Errorf("unmarshal app.json failed, err: %v", err) | ||||||
| 	} | 	} | ||||||
| 	if oldInstall.App.Key != install.App.Key || oldInstall.Name != install.Name || oldInstall.Version != install.Version || oldInstall.ID != install.ID { | 	if oldInstall.App.Key != install.App.Key || oldInstall.Name != install.Name { | ||||||
| 		return errors.New("the current backup file does not match the application") | 		return errors.New("the current backup file does not match the application") | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| @@ -170,6 +172,7 @@ func handleAppRecover(install *model.AppInstall, recoverFile string, isRollback | |||||||
| 		}() | 		}() | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | 	newEnvFile := "" | ||||||
| 	resource, _ := appInstallResourceRepo.GetFirst(appInstallResourceRepo.WithAppInstallId(install.ID)) | 	resource, _ := appInstallResourceRepo.GetFirst(appInstallResourceRepo.WithAppInstallId(install.ID)) | ||||||
| 	if resource.ID != 0 && install.App.Key != "mysql" { | 	if resource.ID != 0 && install.App.Key != "mysql" { | ||||||
| 		mysqlInfo, err := appInstallRepo.LoadBaseInfo(resource.Key, "") | 		mysqlInfo, err := appInstallRepo.LoadBaseInfo(resource.Key, "") | ||||||
| @@ -180,7 +183,22 @@ func handleAppRecover(install *model.AppInstall, recoverFile string, isRollback | |||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			return err | 			return err | ||||||
| 		} | 		} | ||||||
| 		if err := handleMysqlRecover(mysqlInfo, tmpPath, db.Name, fmt.Sprintf("%s.sql.gz", install.Name), true); err != nil { |  | ||||||
|  | 		newDB, envMap, err := reCreateDB(db.ID, oldInstall.Env) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return err | ||||||
|  | 		} | ||||||
|  | 		oldHost := fmt.Sprintf("\"PANEL_DB_HOST\":\"%v\"", envMap["PANEL_DB_HOST"].(string)) | ||||||
|  | 		newHost := fmt.Sprintf("\"PANEL_DB_HOST\":\"%v\"", mysqlInfo.ServiceName) | ||||||
|  | 		oldInstall.Env = strings.ReplaceAll(oldInstall.Env, oldHost, newHost) | ||||||
|  | 		envMap["PANEL_DB_HOST"] = mysqlInfo.ServiceName | ||||||
|  | 		newEnvFile, err = coverEnvJsonToStr(oldInstall.Env) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return err | ||||||
|  | 		} | ||||||
|  | 		_ = appInstallResourceRepo.BatchUpdateBy(map[string]interface{}{"resource_id": newDB.ID}, commonRepo.WithByID(resource.ID)) | ||||||
|  |  | ||||||
|  | 		if err := handleMysqlRecover(mysqlInfo, tmpPath, newDB.Name, fmt.Sprintf("%s.sql.gz", install.Name), true); err != nil { | ||||||
| 			global.LOG.Errorf("handle recover from sql.gz failed, err: %v", err) | 			global.LOG.Errorf("handle recover from sql.gz failed, err: %v", err) | ||||||
| 			return err | 			return err | ||||||
| 		} | 		} | ||||||
| @@ -191,11 +209,50 @@ func handleAppRecover(install *model.AppInstall, recoverFile string, isRollback | |||||||
| 		return err | 		return err | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	oldInstall.Status = constant.Running | 	if len(newEnvFile) != 0 { | ||||||
| 	if err := appInstallRepo.Save(install); err != nil { | 		envPath := fmt.Sprintf("%s/%s/%s/.env", constant.AppInstallDir, install.App.Key, install.Name) | ||||||
|  | 		file, err := os.OpenFile(envPath, os.O_WRONLY|os.O_TRUNC, 0640) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return err | ||||||
|  | 		} | ||||||
|  | 		defer file.Close() | ||||||
|  | 		_, _ = file.WriteString(newEnvFile) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	oldInstall.ID = install.ID | ||||||
|  | 	oldInstall.Status = constant.StatusRunning | ||||||
|  | 	oldInstall.AppId = install.AppId | ||||||
|  | 	oldInstall.AppDetailId = install.AppDetailId | ||||||
|  | 	if err := appInstallRepo.Save(context.Background(), &oldInstall); err != nil { | ||||||
| 		global.LOG.Errorf("save db app install failed, err: %v", err) | 		global.LOG.Errorf("save db app install failed, err: %v", err) | ||||||
| 		return err | 		return err | ||||||
| 	} | 	} | ||||||
| 	isOk = true | 	isOk = true | ||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
|  |  | ||||||
|  | func reCreateDB(dbID uint, oldEnv string) (*model.DatabaseMysql, map[string]interface{}, error) { | ||||||
|  | 	mysqlService := NewIMysqlService() | ||||||
|  | 	ctx := context.Background() | ||||||
|  | 	_ = mysqlService.Delete(ctx, dto.MysqlDBDelete{ID: dbID, DeleteBackup: true, ForceDelete: true}) | ||||||
|  |  | ||||||
|  | 	envMap := make(map[string]interface{}) | ||||||
|  | 	if err := json.Unmarshal([]byte(oldEnv), &envMap); err != nil { | ||||||
|  | 		return nil, envMap, err | ||||||
|  | 	} | ||||||
|  | 	oldName, _ := envMap["PANEL_DB_NAME"].(string) | ||||||
|  | 	oldUser, _ := envMap["PANEL_DB_USER"].(string) | ||||||
|  | 	oldPassword, _ := envMap["PANEL_DB_USER_PASSWORD"].(string) | ||||||
|  | 	createDB, err := mysqlService.Create(context.Background(), dto.MysqlDBCreate{ | ||||||
|  | 		Name:       oldName, | ||||||
|  | 		Format:     "utf8mb4", | ||||||
|  | 		Username:   oldUser, | ||||||
|  | 		Password:   oldPassword, | ||||||
|  | 		Permission: "%", | ||||||
|  | 	}) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, envMap, err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return createDB, envMap, nil | ||||||
|  | } | ||||||
|   | |||||||
| @@ -2,7 +2,6 @@ package service | |||||||
|  |  | ||||||
| import ( | import ( | ||||||
| 	"fmt" | 	"fmt" | ||||||
| 	"io/ioutil" |  | ||||||
| 	"os" | 	"os" | ||||||
| 	"path" | 	"path" | ||||||
| 	"strings" | 	"strings" | ||||||
| @@ -38,7 +37,7 @@ func (u *BackupService) RedisBackup() error { | |||||||
| 	timeNow := time.Now().Format("20060102150405") | 	timeNow := time.Now().Format("20060102150405") | ||||||
| 	fileName := fmt.Sprintf("%s.rdb", timeNow) | 	fileName := fmt.Sprintf("%s.rdb", timeNow) | ||||||
| 	if appendonly == "yes" { | 	if appendonly == "yes" { | ||||||
| 		if redisInfo.Version == "6.0.16" { | 		if strings.HasPrefix(redisInfo.Version, "6.") { | ||||||
| 			fileName = fmt.Sprintf("%s.aof", timeNow) | 			fileName = fmt.Sprintf("%s.aof", timeNow) | ||||||
| 		} else { | 		} else { | ||||||
| 			fileName = fmt.Sprintf("%s.tar.gz", timeNow) | 			fileName = fmt.Sprintf("%s.tar.gz", timeNow) | ||||||
| @@ -121,10 +120,10 @@ func handleRedisRecover(redisInfo *repo.RootInfo, recoverFile string, isRollback | |||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	if appendonly == "yes" { | 	if appendonly == "yes" { | ||||||
| 		if redisInfo.Version == "6.0.16" && !strings.HasSuffix(recoverFile, ".aof") { | 		if strings.HasPrefix(redisInfo.Version, "6.") && !strings.HasSuffix(recoverFile, ".aof") { | ||||||
| 			return buserr.New(constant.ErrTypeOfRedis) | 			return buserr.New(constant.ErrTypeOfRedis) | ||||||
| 		} | 		} | ||||||
| 		if redisInfo.Version == "7.0.5" && !strings.HasSuffix(recoverFile, ".tar.gz") { | 		if strings.HasPrefix(redisInfo.Version, "7.") && !strings.HasSuffix(recoverFile, ".tar.gz") { | ||||||
| 			return buserr.New(constant.ErrTypeOfRedis) | 			return buserr.New(constant.ErrTypeOfRedis) | ||||||
| 		} | 		} | ||||||
| 	} else { | 	} else { | ||||||
| @@ -138,7 +137,7 @@ func handleRedisRecover(redisInfo *repo.RootInfo, recoverFile string, isRollback | |||||||
| 	if !isRollback { | 	if !isRollback { | ||||||
| 		suffix := "rdb" | 		suffix := "rdb" | ||||||
| 		if appendonly == "yes" { | 		if appendonly == "yes" { | ||||||
| 			if redisInfo.Version == "6.0.16" { | 			if strings.HasPrefix(redisInfo.Version, "6.") { | ||||||
| 				suffix = "aof" | 				suffix = "aof" | ||||||
| 			} else { | 			} else { | ||||||
| 				suffix = "tar.gz" | 				suffix = "tar.gz" | ||||||
| @@ -166,21 +165,21 @@ func handleRedisRecover(redisInfo *repo.RootInfo, recoverFile string, isRollback | |||||||
| 	if _, err := compose.Down(composeDir + "/docker-compose.yml"); err != nil { | 	if _, err := compose.Down(composeDir + "/docker-compose.yml"); err != nil { | ||||||
| 		return err | 		return err | ||||||
| 	} | 	} | ||||||
| 	if appendonly == "yes" && redisInfo.Version == "7.0.5" { | 	if appendonly == "yes" && strings.HasPrefix(redisInfo.Version, "7.") { | ||||||
| 		redisDataDir := fmt.Sprintf("%s/%s/%s/data", constant.AppInstallDir, "redis", redisInfo.Name) | 		redisDataDir := fmt.Sprintf("%s/%s/%s/data", constant.AppInstallDir, "redis", redisInfo.Name) | ||||||
| 		if err := handleUnTar(recoverFile, redisDataDir); err != nil { | 		if err := handleUnTar(recoverFile, redisDataDir); err != nil { | ||||||
| 			return err | 			return err | ||||||
| 		} | 		} | ||||||
| 	} else { | 	} else { | ||||||
| 		itemName := "dump.rdb" | 		itemName := "dump.rdb" | ||||||
| 		if appendonly == "yes" && redisInfo.Version == "6.0.16" { | 		if appendonly == "yes" && strings.HasPrefix(redisInfo.Version, "6.") { | ||||||
| 			itemName = "appendonly.aof" | 			itemName = "appendonly.aof" | ||||||
| 		} | 		} | ||||||
| 		input, err := ioutil.ReadFile(recoverFile) | 		input, err := os.ReadFile(recoverFile) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			return err | 			return err | ||||||
| 		} | 		} | ||||||
| 		if err = ioutil.WriteFile(composeDir+"/data/"+itemName, input, 0640); err != nil { | 		if err = os.WriteFile(composeDir+"/data/"+itemName, input, 0640); err != nil { | ||||||
| 			return err | 			return err | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|   | |||||||
| @@ -11,6 +11,7 @@ import ( | |||||||
|  |  | ||||||
| 	"github.com/1Panel-dev/1Panel/backend/app/dto" | 	"github.com/1Panel-dev/1Panel/backend/app/dto" | ||||||
| 	"github.com/1Panel-dev/1Panel/backend/app/model" | 	"github.com/1Panel-dev/1Panel/backend/app/model" | ||||||
|  | 	"github.com/1Panel-dev/1Panel/backend/buserr" | ||||||
| 	"github.com/1Panel-dev/1Panel/backend/constant" | 	"github.com/1Panel-dev/1Panel/backend/constant" | ||||||
| 	"github.com/1Panel-dev/1Panel/backend/global" | 	"github.com/1Panel-dev/1Panel/backend/global" | ||||||
| 	"github.com/1Panel-dev/1Panel/backend/utils/cmd" | 	"github.com/1Panel-dev/1Panel/backend/utils/cmd" | ||||||
| @@ -80,11 +81,11 @@ func handleWebsiteRecover(website *model.Website, recoverFile string, isRollback | |||||||
|  |  | ||||||
| 	temPathWithName := tmpPath + "/" + website.Alias | 	temPathWithName := tmpPath + "/" + website.Alias | ||||||
| 	if !fileOp.Stat(tmpPath+"/website.json") || !fileOp.Stat(temPathWithName+".conf") || !fileOp.Stat(temPathWithName+".web.tar.gz") { | 	if !fileOp.Stat(tmpPath+"/website.json") || !fileOp.Stat(temPathWithName+".conf") || !fileOp.Stat(temPathWithName+".web.tar.gz") { | ||||||
| 		return errors.New("the wrong recovery package does not have .conf or .web.tar.gz files") | 		return buserr.WithDetail(constant.ErrBackupExist, ".conf or .web.tar.gz", nil) | ||||||
| 	} | 	} | ||||||
| 	if website.Type == constant.Deployment { | 	if website.Type == constant.Deployment { | ||||||
| 		if !fileOp.Stat(temPathWithName + ".app.tar.gz") { | 		if !fileOp.Stat(temPathWithName + ".app.tar.gz") { | ||||||
| 			return errors.New("the wrong recovery package does not have .app.tar.gz files") | 			return buserr.WithDetail(constant.ErrBackupExist, ".app.tar.gz", nil) | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 	var oldWebsite model.Website | 	var oldWebsite model.Website | ||||||
| @@ -95,8 +96,9 @@ func handleWebsiteRecover(website *model.Website, recoverFile string, isRollback | |||||||
| 	if err := json.Unmarshal(websiteJson, &oldWebsite); err != nil { | 	if err := json.Unmarshal(websiteJson, &oldWebsite); err != nil { | ||||||
| 		return fmt.Errorf("unmarshal app.json failed, err: %v", err) | 		return fmt.Errorf("unmarshal app.json failed, err: %v", err) | ||||||
| 	} | 	} | ||||||
| 	if oldWebsite.Alias != website.Alias || oldWebsite.Type != website.Type || oldWebsite.ID != website.ID { |  | ||||||
| 		return errors.New("the current backup file does not match the application") | 	if err := checkValidOfWebsite(&oldWebsite, website); err != nil { | ||||||
|  | 		return err | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	isOk := false | 	isOk := false | ||||||
| @@ -155,6 +157,7 @@ func handleWebsiteRecover(website *model.Website, recoverFile string, isRollback | |||||||
| 		return errors.New(string(stdout)) | 		return errors.New(string(stdout)) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | 	oldWebsite.ID = website.ID | ||||||
| 	if err := websiteRepo.SaveWithoutCtx(&oldWebsite); err != nil { | 	if err := websiteRepo.SaveWithoutCtx(&oldWebsite); err != nil { | ||||||
| 		global.LOG.Errorf("handle save website data failed, err: %v", err) | 		global.LOG.Errorf("handle save website data failed, err: %v", err) | ||||||
| 		return err | 		return err | ||||||
| @@ -212,3 +215,29 @@ func handleWebsiteBackup(website *model.Website, backupDir, fileName string) err | |||||||
|  |  | ||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
|  |  | ||||||
|  | func checkValidOfWebsite(oldWebsite, website *model.Website) error { | ||||||
|  | 	if oldWebsite.Alias != website.Alias || oldWebsite.Type != website.Type { | ||||||
|  | 		return buserr.WithDetail(constant.ErrBackupMatch, fmt.Sprintf("oldName: %s, oldType: %v", oldWebsite.Alias, oldWebsite.Type), nil) | ||||||
|  | 	} | ||||||
|  | 	if oldWebsite.AppInstallID != 0 { | ||||||
|  | 		app, err := appInstallRepo.GetFirst(commonRepo.WithByID(website.AppInstallID)) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return buserr.WithDetail(constant.ErrBackupMatch, "app", nil) | ||||||
|  | 		} | ||||||
|  | 		if app.App.Type != "website" { | ||||||
|  | 			return buserr.WithDetail(constant.ErrBackupMatch, fmt.Sprintf("appType: %s", app.App.Type), nil) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	if oldWebsite.RuntimeID != 0 { | ||||||
|  | 		if _, err := runtimeRepo.GetFirst(commonRepo.WithByID(website.RuntimeID)); err != nil { | ||||||
|  | 			return buserr.WithDetail(constant.ErrBackupMatch, "runtime", nil) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	if oldWebsite.WebsiteSSLID != 0 { | ||||||
|  | 		if _, err := websiteSSLRepo.GetFirst(commonRepo.WithByID(website.WebsiteSSLID)); err != nil { | ||||||
|  | 			return buserr.WithDetail(constant.ErrBackupMatch, "ssl", nil) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|   | |||||||
| @@ -3,13 +3,15 @@ package service | |||||||
| import ( | import ( | ||||||
| 	"context" | 	"context" | ||||||
| 	"encoding/json" | 	"encoding/json" | ||||||
| 	"errors" |  | ||||||
| 	"fmt" | 	"fmt" | ||||||
| 	"io/ioutil" | 	"io" | ||||||
|  | 	"os" | ||||||
| 	"os/exec" | 	"os/exec" | ||||||
|  | 	"path/filepath" | ||||||
| 	"sort" | 	"sort" | ||||||
| 	"strconv" | 	"strconv" | ||||||
| 	"strings" | 	"strings" | ||||||
|  | 	"sync" | ||||||
| 	"time" | 	"time" | ||||||
|  |  | ||||||
| 	"github.com/1Panel-dev/1Panel/backend/app/dto" | 	"github.com/1Panel-dev/1Panel/backend/app/dto" | ||||||
| @@ -22,29 +24,44 @@ import ( | |||||||
| 	"github.com/docker/docker/api/types/container" | 	"github.com/docker/docker/api/types/container" | ||||||
| 	"github.com/docker/docker/api/types/filters" | 	"github.com/docker/docker/api/types/filters" | ||||||
| 	"github.com/docker/docker/api/types/network" | 	"github.com/docker/docker/api/types/network" | ||||||
|  | 	"github.com/docker/docker/client" | ||||||
| 	"github.com/docker/go-connections/nat" | 	"github.com/docker/go-connections/nat" | ||||||
|  | 	"github.com/gorilla/websocket" | ||||||
| 	v1 "github.com/opencontainers/image-spec/specs-go/v1" | 	v1 "github.com/opencontainers/image-spec/specs-go/v1" | ||||||
|  | 	"github.com/shirou/gopsutil/v3/cpu" | ||||||
|  | 	"github.com/shirou/gopsutil/v3/mem" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| type ContainerService struct{} | type ContainerService struct{} | ||||||
|  |  | ||||||
| type IContainerService interface { | type IContainerService interface { | ||||||
| 	Page(req dto.PageContainer) (int64, interface{}, error) | 	Page(req dto.PageContainer) (int64, interface{}, error) | ||||||
|  | 	List() ([]string, error) | ||||||
| 	PageNetwork(req dto.SearchWithPage) (int64, interface{}, error) | 	PageNetwork(req dto.SearchWithPage) (int64, interface{}, error) | ||||||
|  | 	ListNetwork() ([]dto.Options, error) | ||||||
| 	PageVolume(req dto.SearchWithPage) (int64, interface{}, error) | 	PageVolume(req dto.SearchWithPage) (int64, interface{}, error) | ||||||
| 	ListVolume() ([]dto.Options, error) | 	ListVolume() ([]dto.Options, error) | ||||||
| 	PageCompose(req dto.SearchWithPage) (int64, interface{}, error) | 	PageCompose(req dto.SearchWithPage) (int64, interface{}, error) | ||||||
| 	CreateCompose(req dto.ComposeCreate) (string, error) | 	CreateCompose(req dto.ComposeCreate) (string, error) | ||||||
| 	ComposeOperation(req dto.ComposeOperation) error | 	ComposeOperation(req dto.ComposeOperation) error | ||||||
| 	ContainerCreate(req dto.ContainerCreate) error | 	ContainerCreate(req dto.ContainerOperate) error | ||||||
|  | 	ContainerUpdate(req dto.ContainerOperate) error | ||||||
|  | 	ContainerUpgrade(req dto.ContainerUpgrade) error | ||||||
|  | 	ContainerInfo(req dto.OperationWithName) (*dto.ContainerOperate, error) | ||||||
|  | 	ContainerListStats() ([]dto.ContainerListStats, error) | ||||||
|  | 	LoadResouceLimit() (*dto.ResourceLimit, error) | ||||||
|  | 	ContainerLogClean(req dto.OperationWithName) error | ||||||
| 	ContainerOperation(req dto.ContainerOperation) error | 	ContainerOperation(req dto.ContainerOperation) error | ||||||
| 	ContainerLogs(param dto.ContainerLog) (string, error) | 	ContainerLogs(wsConn *websocket.Conn, container, since, tail string, follow bool) error | ||||||
| 	ContainerStats(id string) (*dto.ContainterStats, error) | 	ContainerStats(id string) (*dto.ContainerStats, error) | ||||||
| 	Inspect(req dto.InspectReq) (string, error) | 	Inspect(req dto.InspectReq) (string, error) | ||||||
| 	DeleteNetwork(req dto.BatchDelete) error | 	DeleteNetwork(req dto.BatchDelete) error | ||||||
| 	CreateNetwork(req dto.NetworkCreat) error | 	CreateNetwork(req dto.NetworkCreate) error | ||||||
| 	DeleteVolume(req dto.BatchDelete) error | 	DeleteVolume(req dto.BatchDelete) error | ||||||
| 	CreateVolume(req dto.VolumeCreat) error | 	CreateVolume(req dto.VolumeCreate) error | ||||||
|  | 	TestCompose(req dto.ComposeCreate) (bool, error) | ||||||
|  | 	ComposeUpdate(req dto.ComposeUpdate) error | ||||||
|  | 	Prune(req dto.ContainerPrune) (dto.ContainerPruneReport, error) | ||||||
| } | } | ||||||
|  |  | ||||||
| func NewIContainerService() IContainerService { | func NewIContainerService() IContainerService { | ||||||
| @@ -53,9 +70,8 @@ func NewIContainerService() IContainerService { | |||||||
|  |  | ||||||
| func (u *ContainerService) Page(req dto.PageContainer) (int64, interface{}, error) { | func (u *ContainerService) Page(req dto.PageContainer) (int64, interface{}, error) { | ||||||
| 	var ( | 	var ( | ||||||
| 		records   []types.Container | 		records []types.Container | ||||||
| 		list      []types.Container | 		list    []types.Container | ||||||
| 		backDatas []dto.ContainerInfo |  | ||||||
| 	) | 	) | ||||||
| 	client, err := docker.NewDockerClient() | 	client, err := docker.NewDockerClient() | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| @@ -71,19 +87,40 @@ func (u *ContainerService) Page(req dto.PageContainer) (int64, interface{}, erro | |||||||
| 		return 0, nil, err | 		return 0, nil, err | ||||||
| 	} | 	} | ||||||
| 	if len(req.Name) != 0 { | 	if len(req.Name) != 0 { | ||||||
| 		lenth, count := len(list), 0 | 		length, count := len(list), 0 | ||||||
| 		for count < lenth { | 		for count < length { | ||||||
| 			if !strings.Contains(list[count].Names[0][1:], req.Name) { | 			if !strings.Contains(list[count].Names[0][1:], req.Name) { | ||||||
| 				list = append(list[:count], list[(count+1):]...) | 				list = append(list[:count], list[(count+1):]...) | ||||||
| 				lenth-- | 				length-- | ||||||
| 			} else { | 			} else { | ||||||
| 				count++ | 				count++ | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 	sort.Slice(list, func(i, j int) bool { | 	switch req.OrderBy { | ||||||
| 		return list[i].Created > list[j].Created | 	case "name": | ||||||
| 	}) | 		sort.Slice(list, func(i, j int) bool { | ||||||
|  | 			if req.Order == constant.OrderAsc { | ||||||
|  | 				return list[i].Names[0][1:] < list[j].Names[0][1:] | ||||||
|  | 			} | ||||||
|  | 			return list[i].Names[0][1:] > list[j].Names[0][1:] | ||||||
|  | 		}) | ||||||
|  | 	case "state": | ||||||
|  | 		sort.Slice(list, func(i, j int) bool { | ||||||
|  | 			if req.Order == constant.OrderAsc { | ||||||
|  | 				return list[i].State < list[j].State | ||||||
|  | 			} | ||||||
|  | 			return list[i].State > list[j].State | ||||||
|  | 		}) | ||||||
|  | 	default: | ||||||
|  | 		sort.Slice(list, func(i, j int) bool { | ||||||
|  | 			if req.Order == constant.OrderAsc { | ||||||
|  | 				return list[i].Created < list[j].Created | ||||||
|  | 			} | ||||||
|  | 			return list[i].Created > list[j].Created | ||||||
|  | 		}) | ||||||
|  | 	} | ||||||
|  |  | ||||||
| 	total, start, end := len(list), (req.Page-1)*req.PageSize, req.Page*req.PageSize | 	total, start, end := len(list), (req.Page-1)*req.PageSize, req.Page*req.PageSize | ||||||
| 	if start > total { | 	if start > total { | ||||||
| 		records = make([]types.Container, 0) | 		records = make([]types.Container, 0) | ||||||
| @@ -94,31 +131,87 @@ func (u *ContainerService) Page(req dto.PageContainer) (int64, interface{}, erro | |||||||
| 		records = list[start:end] | 		records = list[start:end] | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	for _, container := range records { | 	backDatas := make([]dto.ContainerInfo, len(records)) | ||||||
|  | 	for i := 0; i < len(records); i++ { | ||||||
|  | 		item := records[i] | ||||||
| 		IsFromCompose := false | 		IsFromCompose := false | ||||||
| 		if _, ok := container.Labels[composeProjectLabel]; ok { | 		if _, ok := item.Labels[composeProjectLabel]; ok { | ||||||
| 			IsFromCompose = true | 			IsFromCompose = true | ||||||
| 		} | 		} | ||||||
| 		IsFromApp := false | 		IsFromApp := false | ||||||
| 		if created, ok := container.Labels[composeCreatedBy]; ok && created == "Apps" { | 		if created, ok := item.Labels[composeCreatedBy]; ok && created == "Apps" { | ||||||
| 			IsFromApp = true | 			IsFromApp = true | ||||||
| 		} | 		} | ||||||
| 		backDatas = append(backDatas, dto.ContainerInfo{ |  | ||||||
| 			ContainerID:   container.ID, | 		var ports []string | ||||||
| 			CreateTime:    time.Unix(container.Created, 0).Format("2006-01-02 15:04:05"), | 		for _, port := range item.Ports { | ||||||
| 			Name:          container.Names[0][1:], | 			itemPortStr := fmt.Sprintf("%v/%s", port.PrivatePort, port.Type) | ||||||
| 			ImageId:       strings.Split(container.ImageID, ":")[1], | 			if port.PublicPort != 0 { | ||||||
| 			ImageName:     container.Image, | 				itemPortStr = fmt.Sprintf("%s:%v->%v/%s", port.IP, port.PublicPort, port.PrivatePort, port.Type) | ||||||
| 			State:         container.State, | 			} | ||||||
| 			RunTime:       container.Status, | 			ports = append(ports, itemPortStr) | ||||||
|  | 		} | ||||||
|  | 		backDatas[i] = dto.ContainerInfo{ | ||||||
|  | 			ContainerID:   item.ID, | ||||||
|  | 			CreateTime:    time.Unix(item.Created, 0).Format("2006-01-02 15:04:05"), | ||||||
|  | 			Name:          item.Names[0][1:], | ||||||
|  | 			ImageId:       strings.Split(item.ImageID, ":")[1], | ||||||
|  | 			ImageName:     item.Image, | ||||||
|  | 			State:         item.State, | ||||||
|  | 			RunTime:       item.Status, | ||||||
|  | 			Ports:         ports, | ||||||
| 			IsFromApp:     IsFromApp, | 			IsFromApp:     IsFromApp, | ||||||
| 			IsFromCompose: IsFromCompose, | 			IsFromCompose: IsFromCompose, | ||||||
| 		}) | 		} | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	return int64(total), backDatas, nil | 	return int64(total), backDatas, nil | ||||||
| } | } | ||||||
|  |  | ||||||
|  | func (u *ContainerService) List() ([]string, error) { | ||||||
|  | 	client, err := docker.NewDockerClient() | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 	containers, err := client.ContainerList(context.Background(), types.ContainerListOptions{All: true}) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 	var datas []string | ||||||
|  | 	for _, container := range containers { | ||||||
|  | 		for _, name := range container.Names { | ||||||
|  | 			if len(name) != 0 { | ||||||
|  | 				datas = append(datas, strings.TrimLeft(name, "/")) | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return datas, nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (u *ContainerService) ContainerListStats() ([]dto.ContainerListStats, error) { | ||||||
|  | 	client, err := docker.NewDockerClient() | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 	list, err := client.ContainerList(context.Background(), types.ContainerListOptions{All: true}) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 	var datas []dto.ContainerListStats | ||||||
|  | 	var wg sync.WaitGroup | ||||||
|  | 	wg.Add(len(list)) | ||||||
|  | 	for i := 0; i < len(list); i++ { | ||||||
|  | 		go func(item types.Container) { | ||||||
|  | 			cpu, mem := loadCpuAndMem(client, item.ID) | ||||||
|  | 			datas = append(datas, dto.ContainerListStats{CPUPercent: cpu, MemoryPercent: mem, ContainerID: item.ID}) | ||||||
|  | 			wg.Done() | ||||||
|  | 		}(list[i]) | ||||||
|  | 	} | ||||||
|  | 	wg.Wait() | ||||||
|  | 	return datas, nil | ||||||
|  | } | ||||||
|  |  | ||||||
| func (u *ContainerService) Inspect(req dto.InspectReq) (string, error) { | func (u *ContainerService) Inspect(req dto.InspectReq) (string, error) { | ||||||
| 	client, err := docker.NewDockerClient() | 	client, err := docker.NewDockerClient() | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| @@ -143,67 +236,262 @@ func (u *ContainerService) Inspect(req dto.InspectReq) (string, error) { | |||||||
| 	return string(bytes), nil | 	return string(bytes), nil | ||||||
| } | } | ||||||
|  |  | ||||||
| func (u *ContainerService) ContainerCreate(req dto.ContainerCreate) error { | func (u *ContainerService) Prune(req dto.ContainerPrune) (dto.ContainerPruneReport, error) { | ||||||
| 	if len(req.ExposedPorts) != 0 { | 	report := dto.ContainerPruneReport{} | ||||||
| 		for _, port := range req.ExposedPorts { | 	client, err := docker.NewDockerClient() | ||||||
| 			if common.ScanPort(port.HostPort) { | 	if err != nil { | ||||||
| 				return buserr.WithDetail(constant.ErrPortInUsed, port.HostPort, nil) | 		return report, err | ||||||
| 			} | 	} | ||||||
|  | 	pruneFilters := filters.NewArgs() | ||||||
|  | 	if req.WithTagAll { | ||||||
|  | 		pruneFilters.Add("dangling", "false") | ||||||
|  | 		if req.PruneType != "image" { | ||||||
|  | 			pruneFilters.Add("until", "24h") | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  | 	switch req.PruneType { | ||||||
|  | 	case "container": | ||||||
|  | 		rep, err := client.ContainersPrune(context.Background(), pruneFilters) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return report, err | ||||||
|  | 		} | ||||||
|  | 		report.DeletedNumber = len(rep.ContainersDeleted) | ||||||
|  | 		report.SpaceReclaimed = int(rep.SpaceReclaimed) | ||||||
|  | 	case "image": | ||||||
|  | 		rep, err := client.ImagesPrune(context.Background(), pruneFilters) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return report, err | ||||||
|  | 		} | ||||||
|  | 		report.DeletedNumber = len(rep.ImagesDeleted) | ||||||
|  | 		report.SpaceReclaimed = int(rep.SpaceReclaimed) | ||||||
|  | 	case "network": | ||||||
|  | 		rep, err := client.NetworksPrune(context.Background(), pruneFilters) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return report, err | ||||||
|  | 		} | ||||||
|  | 		report.DeletedNumber = len(rep.NetworksDeleted) | ||||||
|  | 	case "volume": | ||||||
|  | 		rep, err := client.VolumesPrune(context.Background(), pruneFilters) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return report, err | ||||||
|  | 		} | ||||||
|  | 		report.DeletedNumber = len(rep.VolumesDeleted) | ||||||
|  | 		report.SpaceReclaimed = int(rep.SpaceReclaimed) | ||||||
|  | 	} | ||||||
|  | 	return report, nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (u *ContainerService) LoadResouceLimit() (*dto.ResourceLimit, error) { | ||||||
|  | 	cpuCounts, err := cpu.Counts(true) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, fmt.Errorf("load cpu limit failed, err: %v", err) | ||||||
|  | 	} | ||||||
|  | 	memoryInfo, err := mem.VirtualMemory() | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, fmt.Errorf("load memory limit failed, err: %v", err) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	data := dto.ResourceLimit{ | ||||||
|  | 		CPU:    cpuCounts, | ||||||
|  | 		Memory: int(memoryInfo.Total), | ||||||
|  | 	} | ||||||
|  | 	return &data, nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (u *ContainerService) ContainerCreate(req dto.ContainerOperate) error { | ||||||
| 	client, err := docker.NewDockerClient() | 	client, err := docker.NewDockerClient() | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return err | 		return err | ||||||
| 	} | 	} | ||||||
| 	config := &container.Config{ | 	ctx := context.Background() | ||||||
| 		Image:  req.Image, | 	newContainer, _ := client.ContainerInspect(ctx, req.Name) | ||||||
| 		Cmd:    req.Cmd, | 	if newContainer.ContainerJSONBase != nil { | ||||||
| 		Env:    req.Env, | 		return buserr.New(constant.ErrContainerName) | ||||||
| 		Labels: stringsToMap(req.Labels), |  | ||||||
| 	} | 	} | ||||||
| 	hostConf := &container.HostConfig{ |  | ||||||
| 		AutoRemove:      req.AutoRemove, | 	var config container.Config | ||||||
| 		PublishAllPorts: req.PublishAllPorts, | 	var hostConf container.HostConfig | ||||||
| 		RestartPolicy:   container.RestartPolicy{Name: req.RestartPolicy}, | 	var networkConf network.NetworkingConfig | ||||||
| 	} | 	if err := loadConfigInfo(req, &config, &hostConf, &networkConf); err != nil { | ||||||
| 	if req.RestartPolicy == "on-failure" { | 		return err | ||||||
| 		hostConf.RestartPolicy.MaximumRetryCount = 5 |  | ||||||
| 	} |  | ||||||
| 	if req.NanoCPUs != 0 { |  | ||||||
| 		hostConf.NanoCPUs = req.NanoCPUs * 1000000000 |  | ||||||
| 	} |  | ||||||
| 	if req.Memory != 0 { |  | ||||||
| 		hostConf.Memory = req.Memory |  | ||||||
| 	} |  | ||||||
| 	if len(req.ExposedPorts) != 0 { |  | ||||||
| 		hostConf.PortBindings = make(nat.PortMap) |  | ||||||
| 		for _, port := range req.ExposedPorts { |  | ||||||
| 			bindItem := nat.PortBinding{HostPort: strconv.Itoa(port.HostPort)} |  | ||||||
| 			hostConf.PortBindings[nat.Port(fmt.Sprintf("%d/tcp", port.ContainerPort))] = []nat.PortBinding{bindItem} |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 	if len(req.Volumes) != 0 { |  | ||||||
| 		config.Volumes = make(map[string]struct{}) |  | ||||||
| 		for _, volume := range req.Volumes { |  | ||||||
| 			config.Volumes[volume.ContainerDir] = struct{}{} |  | ||||||
| 			hostConf.Binds = append(hostConf.Binds, fmt.Sprintf("%s:%s:%s", volume.SourceDir, volume.ContainerDir, volume.Mode)) |  | ||||||
| 		} |  | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	global.LOG.Infof("new container info %s has been made, now start to create", req.Name) | 	global.LOG.Infof("new container info %s has been made, now start to create", req.Name) | ||||||
| 	container, err := client.ContainerCreate(context.TODO(), config, hostConf, &network.NetworkingConfig{}, &v1.Platform{}, req.Name) |  | ||||||
|  | 	if !checkImageExist(client, req.Image) || req.ForcePull { | ||||||
|  | 		if err := pullImages(ctx, client, req.Image); err != nil { | ||||||
|  | 			if !req.ForcePull { | ||||||
|  | 				return err | ||||||
|  | 			} | ||||||
|  | 			global.LOG.Errorf("force pull image %s failed, err: %v", req.Image, err) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	container, err := client.ContainerCreate(ctx, &config, &hostConf, &networkConf, &v1.Platform{}, req.Name) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		_ = client.ContainerRemove(context.Background(), req.Name, types.ContainerRemoveOptions{RemoveVolumes: true, Force: true}) | 		_ = client.ContainerRemove(ctx, req.Name, types.ContainerRemoveOptions{RemoveVolumes: true, Force: true}) | ||||||
| 		return err | 		return err | ||||||
| 	} | 	} | ||||||
| 	global.LOG.Infof("create container %s successful! now check if the container is started and delete the container information if it is not.", req.Name) | 	global.LOG.Infof("create container %s successful! now check if the container is started and delete the container information if it is not.", req.Name) | ||||||
| 	if err := client.ContainerStart(context.TODO(), container.ID, types.ContainerStartOptions{}); err != nil { | 	if err := client.ContainerStart(ctx, container.ID, types.ContainerStartOptions{}); err != nil { | ||||||
| 		_ = client.ContainerRemove(context.Background(), req.Name, types.ContainerRemoveOptions{RemoveVolumes: true, Force: true}) | 		_ = client.ContainerRemove(ctx, req.Name, types.ContainerRemoveOptions{RemoveVolumes: true, Force: true}) | ||||||
| 		return fmt.Errorf("create successful but start failed, err: %v", err) | 		return fmt.Errorf("create successful but start failed, err: %v", err) | ||||||
| 	} | 	} | ||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
|  |  | ||||||
|  | func (u *ContainerService) ContainerInfo(req dto.OperationWithName) (*dto.ContainerOperate, error) { | ||||||
|  | 	client, err := docker.NewDockerClient() | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 	ctx := context.Background() | ||||||
|  | 	oldContainer, err := client.ContainerInspect(ctx, req.Name) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	var data dto.ContainerOperate | ||||||
|  | 	data.ContainerID = oldContainer.ID | ||||||
|  | 	data.Name = strings.ReplaceAll(oldContainer.Name, "/", "") | ||||||
|  | 	data.Image = oldContainer.Config.Image | ||||||
|  | 	if oldContainer.NetworkSettings != nil { | ||||||
|  | 		for network := range oldContainer.NetworkSettings.Networks { | ||||||
|  | 			data.Network = network | ||||||
|  | 			break | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	data.Cmd = oldContainer.Config.Cmd | ||||||
|  | 	data.Env = oldContainer.Config.Env | ||||||
|  | 	data.CPUShares = oldContainer.HostConfig.CPUShares | ||||||
|  | 	for key, val := range oldContainer.Config.Labels { | ||||||
|  | 		data.Labels = append(data.Labels, fmt.Sprintf("%s=%s", key, val)) | ||||||
|  | 	} | ||||||
|  | 	for key, val := range oldContainer.HostConfig.PortBindings { | ||||||
|  | 		var itemPort dto.PortHelper | ||||||
|  | 		if !strings.Contains(string(key), "/") { | ||||||
|  | 			continue | ||||||
|  | 		} | ||||||
|  | 		itemPort.ContainerPort = strings.Split(string(key), "/")[0] | ||||||
|  | 		itemPort.Protocol = strings.Split(string(key), "/")[1] | ||||||
|  | 		for _, binds := range val { | ||||||
|  | 			itemPort.HostIP = binds.HostIP | ||||||
|  | 			itemPort.HostPort = binds.HostPort | ||||||
|  | 			data.ExposedPorts = append(data.ExposedPorts, itemPort) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	data.AutoRemove = oldContainer.HostConfig.AutoRemove | ||||||
|  | 	data.PublishAllPorts = oldContainer.HostConfig.PublishAllPorts | ||||||
|  | 	data.RestartPolicy = oldContainer.HostConfig.RestartPolicy.Name | ||||||
|  | 	if oldContainer.HostConfig.NanoCPUs != 0 { | ||||||
|  | 		data.NanoCPUs = float64(oldContainer.HostConfig.NanoCPUs) / 1000000000 | ||||||
|  | 	} | ||||||
|  | 	if oldContainer.HostConfig.Memory != 0 { | ||||||
|  | 		data.Memory = float64(oldContainer.HostConfig.Memory) / 1024 / 1024 | ||||||
|  | 	} | ||||||
|  | 	for _, bind := range oldContainer.HostConfig.Binds { | ||||||
|  | 		parts := strings.Split(bind, ":") | ||||||
|  | 		if len(parts) != 3 { | ||||||
|  | 			continue | ||||||
|  | 		} | ||||||
|  | 		data.Volumes = append(data.Volumes, dto.VolumeHelper{SourceDir: parts[0], ContainerDir: parts[1], Mode: parts[2]}) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return &data, nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (u *ContainerService) ContainerUpdate(req dto.ContainerOperate) error { | ||||||
|  | 	client, err := docker.NewDockerClient() | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 	ctx := context.Background() | ||||||
|  | 	newContainer, _ := client.ContainerInspect(ctx, req.Name) | ||||||
|  | 	if newContainer.ContainerJSONBase != nil && newContainer.ID != req.ContainerID { | ||||||
|  | 		return buserr.New(constant.ErrContainerName) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	oldContainer, err := client.ContainerInspect(ctx, req.ContainerID) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 	if !checkImageExist(client, req.Image) || req.ForcePull { | ||||||
|  | 		if err := pullImages(ctx, client, req.Image); err != nil { | ||||||
|  | 			if !req.ForcePull { | ||||||
|  | 				return err | ||||||
|  | 			} | ||||||
|  | 			global.LOG.Errorf("force pull image %s failed, err: %v", req.Image, err) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if err := client.ContainerRemove(ctx, req.ContainerID, types.ContainerRemoveOptions{Force: true}); err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	config := oldContainer.Config | ||||||
|  | 	hostConf := oldContainer.HostConfig | ||||||
|  | 	var networkConf network.NetworkingConfig | ||||||
|  | 	if err := loadConfigInfo(req, config, hostConf, &networkConf); err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	global.LOG.Infof("new container info %s has been update, now start to recreate", req.Name) | ||||||
|  | 	container, err := client.ContainerCreate(ctx, config, hostConf, &networkConf, &v1.Platform{}, req.Name) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return fmt.Errorf("recreate contianer failed, err: %v", err) | ||||||
|  | 	} | ||||||
|  | 	global.LOG.Infof("update container %s successful! now check if the container is started.", req.Name) | ||||||
|  | 	if err := client.ContainerStart(ctx, container.ID, types.ContainerStartOptions{}); err != nil { | ||||||
|  | 		return fmt.Errorf("update successful but start failed, err: %v", err) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (u *ContainerService) ContainerUpgrade(req dto.ContainerUpgrade) error { | ||||||
|  | 	client, err := docker.NewDockerClient() | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 	ctx := context.Background() | ||||||
|  | 	oldContainer, err := client.ContainerInspect(ctx, req.Name) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 	if !checkImageExist(client, req.Image) || req.ForcePull { | ||||||
|  | 		if err := pullImages(ctx, client, req.Image); err != nil { | ||||||
|  | 			if !req.ForcePull { | ||||||
|  | 				return err | ||||||
|  | 			} | ||||||
|  | 			global.LOG.Errorf("force pull image %s failed, err: %v", req.Image, err) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	config := oldContainer.Config | ||||||
|  | 	config.Image = req.Image | ||||||
|  | 	hostConf := oldContainer.HostConfig | ||||||
|  | 	var networkConf network.NetworkingConfig | ||||||
|  | 	if oldContainer.NetworkSettings != nil { | ||||||
|  | 		for networkKey := range oldContainer.NetworkSettings.Networks { | ||||||
|  | 			networkConf.EndpointsConfig = map[string]*network.EndpointSettings{networkKey: {}} | ||||||
|  | 			break | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	if err := client.ContainerRemove(ctx, req.Name, types.ContainerRemoveOptions{Force: true}); err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	global.LOG.Infof("new container info %s has been update, now start to recreate", req.Name) | ||||||
|  | 	container, err := client.ContainerCreate(ctx, config, hostConf, &network.NetworkingConfig{}, &v1.Platform{}, req.Name) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return fmt.Errorf("recreate contianer failed, err: %v", err) | ||||||
|  | 	} | ||||||
|  | 	global.LOG.Infof("update container %s successful! now check if the container is started.", req.Name) | ||||||
|  | 	if err := client.ContainerStart(ctx, container.ID, types.ContainerStartOptions{}); err != nil { | ||||||
|  | 		return fmt.Errorf("update successful but start failed, err: %v", err) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
| func (u *ContainerService) ContainerOperation(req dto.ContainerOperation) error { | func (u *ContainerService) ContainerOperation(req dto.ContainerOperation) error { | ||||||
| 	var err error | 	var err error | ||||||
| 	ctx := context.Background() | 	ctx := context.Background() | ||||||
| @@ -216,9 +504,9 @@ func (u *ContainerService) ContainerOperation(req dto.ContainerOperation) error | |||||||
| 	case constant.ContainerOpStart: | 	case constant.ContainerOpStart: | ||||||
| 		err = client.ContainerStart(ctx, req.Name, types.ContainerStartOptions{}) | 		err = client.ContainerStart(ctx, req.Name, types.ContainerStartOptions{}) | ||||||
| 	case constant.ContainerOpStop: | 	case constant.ContainerOpStop: | ||||||
| 		err = client.ContainerStop(ctx, req.Name, nil) | 		err = client.ContainerStop(ctx, req.Name, container.StopOptions{}) | ||||||
| 	case constant.ContainerOpRestart: | 	case constant.ContainerOpRestart: | ||||||
| 		err = client.ContainerRestart(ctx, req.Name, nil) | 		err = client.ContainerRestart(ctx, req.Name, container.StopOptions{}) | ||||||
| 	case constant.ContainerOpKill: | 	case constant.ContainerOpKill: | ||||||
| 		err = client.ContainerKill(ctx, req.Name, "SIGKILL") | 		err = client.ContainerKill(ctx, req.Name, "SIGKILL") | ||||||
| 	case constant.ContainerOpPause: | 	case constant.ContainerOpPause: | ||||||
| @@ -226,6 +514,10 @@ func (u *ContainerService) ContainerOperation(req dto.ContainerOperation) error | |||||||
| 	case constant.ContainerOpUnpause: | 	case constant.ContainerOpUnpause: | ||||||
| 		err = client.ContainerUnpause(ctx, req.Name) | 		err = client.ContainerUnpause(ctx, req.Name) | ||||||
| 	case constant.ContainerOpRename: | 	case constant.ContainerOpRename: | ||||||
|  | 		newContainer, _ := client.ContainerInspect(ctx, req.NewName) | ||||||
|  | 		if newContainer.ContainerJSONBase != nil { | ||||||
|  | 			return buserr.New(constant.ErrContainerName) | ||||||
|  | 		} | ||||||
| 		err = client.ContainerRename(ctx, req.Name, req.NewName) | 		err = client.ContainerRename(ctx, req.Name, req.NewName) | ||||||
| 	case constant.ContainerOpRemove: | 	case constant.ContainerOpRemove: | ||||||
| 		err = client.ContainerRemove(ctx, req.Name, types.ContainerRemoveOptions{RemoveVolumes: true, Force: true}) | 		err = client.ContainerRemove(ctx, req.Name, types.ContainerRemoveOptions{RemoveVolumes: true, Force: true}) | ||||||
| @@ -233,19 +525,72 @@ func (u *ContainerService) ContainerOperation(req dto.ContainerOperation) error | |||||||
| 	return err | 	return err | ||||||
| } | } | ||||||
|  |  | ||||||
| func (u *ContainerService) ContainerLogs(req dto.ContainerLog) (string, error) { | func (u *ContainerService) ContainerLogClean(req dto.OperationWithName) error { | ||||||
| 	cmd := exec.Command("docker", "logs", req.ContainerID) | 	client, err := docker.NewDockerClient() | ||||||
| 	if req.Mode != "all" { |  | ||||||
| 		cmd = exec.Command("docker", "logs", req.ContainerID, "--since", req.Mode) |  | ||||||
| 	} |  | ||||||
| 	stdout, err := cmd.CombinedOutput() |  | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return "", errors.New(string(stdout)) | 		return err | ||||||
| 	} | 	} | ||||||
| 	return string(stdout), nil | 	container, err := client.ContainerInspect(context.Background(), req.Name) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 	file, err := os.OpenFile(container.LogPath, os.O_RDWR|os.O_CREATE, 0666) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 	defer file.Close() | ||||||
|  | 	if err = file.Truncate(0); err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 	_, _ = file.Seek(0, 0) | ||||||
|  |  | ||||||
|  | 	files, _ := filepath.Glob(fmt.Sprintf("%s.*", container.LogPath)) | ||||||
|  | 	for _, file := range files { | ||||||
|  | 		_ = os.Remove(file) | ||||||
|  | 	} | ||||||
|  | 	return nil | ||||||
| } | } | ||||||
|  |  | ||||||
| func (u *ContainerService) ContainerStats(id string) (*dto.ContainterStats, error) { | func (u *ContainerService) ContainerLogs(wsConn *websocket.Conn, container, since, tail string, follow bool) error { | ||||||
|  | 	command := fmt.Sprintf("docker logs %s", container) | ||||||
|  | 	if tail != "0" { | ||||||
|  | 		command += " -n " + tail | ||||||
|  | 	} | ||||||
|  | 	if since != "all" { | ||||||
|  | 		command += " --since " + since | ||||||
|  | 	} | ||||||
|  | 	if follow { | ||||||
|  | 		command += " -f" | ||||||
|  | 	} | ||||||
|  | 	command += " 2>&1" | ||||||
|  | 	cmd := exec.Command("bash", "-c", command) | ||||||
|  | 	stdout, err := cmd.StdoutPipe() | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 	if err := cmd.Start(); err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	buffer := make([]byte, 1024) | ||||||
|  | 	for { | ||||||
|  | 		n, err := stdout.Read(buffer) | ||||||
|  | 		if err != nil { | ||||||
|  | 			if err == io.EOF { | ||||||
|  | 				break | ||||||
|  | 			} | ||||||
|  | 			global.LOG.Errorf("read bytes from container log failed, err: %v", err) | ||||||
|  | 			continue | ||||||
|  | 		} | ||||||
|  | 		if err = wsConn.WriteMessage(websocket.TextMessage, buffer[:n]); err != nil { | ||||||
|  | 			global.LOG.Errorf("send message with container log to ws failed, err: %v", err) | ||||||
|  | 			break | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (u *ContainerService) ContainerStats(id string) (*dto.ContainerStats, error) { | ||||||
| 	client, err := docker.NewDockerClient() | 	client, err := docker.NewDockerClient() | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return nil, err | 		return nil, err | ||||||
| @@ -256,18 +601,18 @@ func (u *ContainerService) ContainerStats(id string) (*dto.ContainterStats, erro | |||||||
| 	} | 	} | ||||||
| 	defer res.Body.Close() | 	defer res.Body.Close() | ||||||
|  |  | ||||||
| 	body, err := ioutil.ReadAll(res.Body) | 	body, err := io.ReadAll(res.Body) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
|  | 		res.Body.Close() | ||||||
| 		return nil, err | 		return nil, err | ||||||
| 	} | 	} | ||||||
|  | 	res.Body.Close() | ||||||
| 	var stats *types.StatsJSON | 	var stats *types.StatsJSON | ||||||
| 	if err := json.Unmarshal(body, &stats); err != nil { | 	if err := json.Unmarshal(body, &stats); err != nil { | ||||||
| 		return nil, err | 		return nil, err | ||||||
| 	} | 	} | ||||||
| 	var data dto.ContainterStats | 	var data dto.ContainerStats | ||||||
| 	previousCPU := stats.PreCPUStats.CPUUsage.TotalUsage | 	data.CPUPercent = calculateCPUPercentUnix(stats) | ||||||
| 	previousSystem := stats.PreCPUStats.SystemUsage |  | ||||||
| 	data.CPUPercent = calculateCPUPercentUnix(previousCPU, previousSystem, stats) |  | ||||||
| 	data.IORead, data.IOWrite = calculateBlockIO(stats.BlkioStats) | 	data.IORead, data.IOWrite = calculateBlockIO(stats.BlkioStats) | ||||||
| 	data.Memory = float64(stats.MemoryStats.Usage) / 1024 / 1024 | 	data.Memory = float64(stats.MemoryStats.Usage) / 1024 / 1024 | ||||||
| 	if cache, ok := stats.MemoryStats.Stats["cache"]; ok { | 	if cache, ok := stats.MemoryStats.Stats["cache"]; ok { | ||||||
| @@ -282,25 +627,36 @@ func (u *ContainerService) ContainerStats(id string) (*dto.ContainterStats, erro | |||||||
| func stringsToMap(list []string) map[string]string { | func stringsToMap(list []string) map[string]string { | ||||||
| 	var lableMap = make(map[string]string) | 	var lableMap = make(map[string]string) | ||||||
| 	for _, label := range list { | 	for _, label := range list { | ||||||
| 		sps := strings.Split(label, "=") | 		if strings.Contains(label, "=") { | ||||||
| 		if len(sps) > 1 { | 			sps := strings.SplitN(label, "=", 2) | ||||||
| 			lableMap[sps[0]] = sps[1] | 			lableMap[sps[0]] = sps[1] | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 	return lableMap | 	return lableMap | ||||||
| } | } | ||||||
| func calculateCPUPercentUnix(previousCPU, previousSystem uint64, v *types.StatsJSON) float64 { |  | ||||||
| 	var ( | func calculateCPUPercentUnix(stats *types.StatsJSON) float64 { | ||||||
| 		cpuPercent  = 0.0 | 	cpuPercent := 0.0 | ||||||
| 		cpuDelta    = float64(v.CPUStats.CPUUsage.TotalUsage) - float64(previousCPU) | 	cpuDelta := float64(stats.CPUStats.CPUUsage.TotalUsage) - float64(stats.PreCPUStats.CPUUsage.TotalUsage) | ||||||
| 		systemDelta = float64(v.CPUStats.SystemUsage) - float64(previousSystem) | 	systemDelta := float64(stats.CPUStats.SystemUsage) - float64(stats.PreCPUStats.SystemUsage) | ||||||
| 	) |  | ||||||
|  |  | ||||||
| 	if systemDelta > 0.0 && cpuDelta > 0.0 { | 	if systemDelta > 0.0 && cpuDelta > 0.0 { | ||||||
| 		cpuPercent = (cpuDelta / systemDelta) * float64(len(v.CPUStats.CPUUsage.PercpuUsage)) * 100.0 | 		cpuPercent = (cpuDelta / systemDelta) * 100.0 | ||||||
|  | 		if len(stats.CPUStats.CPUUsage.PercpuUsage) != 0 { | ||||||
|  | 			cpuPercent = cpuPercent * float64(len(stats.CPUStats.CPUUsage.PercpuUsage)) | ||||||
|  | 		} | ||||||
| 	} | 	} | ||||||
| 	return cpuPercent | 	return cpuPercent | ||||||
| } | } | ||||||
|  | func calculateMemPercentUnix(memStats types.MemoryStats) float64 { | ||||||
|  | 	memPercent := 0.0 | ||||||
|  | 	memUsage := float64(memStats.Usage - memStats.Stats["cache"]) | ||||||
|  | 	memLimit := float64(memStats.Limit) | ||||||
|  | 	if memUsage > 0.0 && memLimit > 0.0 { | ||||||
|  | 		memPercent = (memUsage / memLimit) * 100.0 | ||||||
|  | 	} | ||||||
|  | 	return memPercent | ||||||
|  | } | ||||||
| func calculateBlockIO(blkio types.BlkioStats) (blkRead float64, blkWrite float64) { | func calculateBlockIO(blkio types.BlkioStats) (blkRead float64, blkWrite float64) { | ||||||
| 	for _, bioEntry := range blkio.IoServiceBytesRecursive { | 	for _, bioEntry := range blkio.IoServiceBytesRecursive { | ||||||
| 		switch strings.ToLower(bioEntry.Op) { | 		switch strings.ToLower(bioEntry.Op) { | ||||||
| @@ -321,3 +677,137 @@ func calculateNetwork(network map[string]types.NetworkStats) (float64, float64) | |||||||
| 	} | 	} | ||||||
| 	return rx, tx | 	return rx, tx | ||||||
| } | } | ||||||
|  |  | ||||||
|  | func checkImageExist(client *client.Client, image string) bool { | ||||||
|  | 	images, err := client.ImageList(context.Background(), types.ImageListOptions{}) | ||||||
|  | 	if err != nil { | ||||||
|  | 		fmt.Println(err) | ||||||
|  | 		return false | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	for _, img := range images { | ||||||
|  | 		for _, tag := range img.RepoTags { | ||||||
|  | 			if tag == image || tag == image+":latest" { | ||||||
|  | 				return true | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	return false | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func pullImages(ctx context.Context, client *client.Client, image string) error { | ||||||
|  | 	out, err := client.ImagePull(ctx, image, types.ImagePullOptions{}) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 	defer out.Close() | ||||||
|  | 	_, err = io.Copy(io.Discard, out) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func loadCpuAndMem(client *client.Client, container string) (float64, float64) { | ||||||
|  | 	res, err := client.ContainerStats(context.Background(), container, false) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return 0, 0 | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	body, err := io.ReadAll(res.Body) | ||||||
|  | 	if err != nil { | ||||||
|  | 		res.Body.Close() | ||||||
|  | 		return 0, 0 | ||||||
|  | 	} | ||||||
|  | 	res.Body.Close() | ||||||
|  | 	var stats *types.StatsJSON | ||||||
|  | 	if err := json.Unmarshal(body, &stats); err != nil { | ||||||
|  | 		return 0, 0 | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	CPUPercent := calculateCPUPercentUnix(stats) | ||||||
|  | 	MemPercent := calculateMemPercentUnix(stats.MemoryStats) | ||||||
|  | 	return CPUPercent, MemPercent | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func checkPortStats(ports []dto.PortHelper) (nat.PortMap, error) { | ||||||
|  | 	portMap := make(nat.PortMap) | ||||||
|  | 	if len(ports) == 0 { | ||||||
|  | 		return portMap, nil | ||||||
|  | 	} | ||||||
|  | 	for _, port := range ports { | ||||||
|  | 		if strings.Contains(port.ContainerPort, "-") { | ||||||
|  | 			if !strings.Contains(port.HostPort, "-") { | ||||||
|  | 				return portMap, buserr.New(constant.ErrPortRules) | ||||||
|  | 			} | ||||||
|  | 			hostStart, _ := strconv.Atoi(strings.Split(port.HostPort, "-")[0]) | ||||||
|  | 			hostEnd, _ := strconv.Atoi(strings.Split(port.HostPort, "-")[1]) | ||||||
|  | 			containerStart, _ := strconv.Atoi(strings.Split(port.ContainerPort, "-")[0]) | ||||||
|  | 			containerEnd, _ := strconv.Atoi(strings.Split(port.ContainerPort, "-")[1]) | ||||||
|  | 			if (hostEnd-hostStart) <= 0 || (containerEnd-containerStart) <= 0 { | ||||||
|  | 				return portMap, buserr.New(constant.ErrPortRules) | ||||||
|  | 			} | ||||||
|  | 			if (containerEnd - containerStart) != (hostEnd - hostStart) { | ||||||
|  | 				return portMap, buserr.New(constant.ErrPortRules) | ||||||
|  | 			} | ||||||
|  | 			for i := 0; i <= hostEnd-hostStart; i++ { | ||||||
|  | 				bindItem := nat.PortBinding{HostPort: strconv.Itoa(hostStart + i), HostIP: port.HostIP} | ||||||
|  | 				portMap[nat.Port(fmt.Sprintf("%d/%s", containerStart+i, port.Protocol))] = []nat.PortBinding{bindItem} | ||||||
|  | 			} | ||||||
|  | 			for i := hostStart; i <= hostEnd; i++ { | ||||||
|  | 				if common.ScanPort(i) { | ||||||
|  | 					return portMap, buserr.WithDetail(constant.ErrPortInUsed, i, nil) | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 		} else { | ||||||
|  | 			portItem := 0 | ||||||
|  | 			if strings.Contains(port.HostPort, "-") { | ||||||
|  | 				portItem, _ = strconv.Atoi(strings.Split(port.HostPort, "-")[0]) | ||||||
|  | 			} else { | ||||||
|  | 				portItem, _ = strconv.Atoi(port.HostPort) | ||||||
|  | 			} | ||||||
|  | 			if common.ScanPort(portItem) { | ||||||
|  | 				return portMap, buserr.WithDetail(constant.ErrPortInUsed, portItem, nil) | ||||||
|  | 			} | ||||||
|  | 			bindItem := nat.PortBinding{HostPort: strconv.Itoa(portItem), HostIP: port.HostIP} | ||||||
|  | 			portMap[nat.Port(fmt.Sprintf("%s/%s", port.ContainerPort, port.Protocol))] = []nat.PortBinding{bindItem} | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	return portMap, nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func loadConfigInfo(req dto.ContainerOperate, config *container.Config, hostConf *container.HostConfig, networkConf *network.NetworkingConfig) error { | ||||||
|  | 	portMap, err := checkPortStats(req.ExposedPorts) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 	exposeds := make(nat.PortSet) | ||||||
|  | 	for port := range portMap { | ||||||
|  | 		exposeds[port] = struct{}{} | ||||||
|  | 	} | ||||||
|  | 	config.Image = req.Image | ||||||
|  | 	config.Cmd = req.Cmd | ||||||
|  | 	config.Env = req.Env | ||||||
|  | 	config.Labels = stringsToMap(req.Labels) | ||||||
|  | 	config.ExposedPorts = exposeds | ||||||
|  |  | ||||||
|  | 	networkConf.EndpointsConfig = map[string]*network.EndpointSettings{req.Network: {}} | ||||||
|  |  | ||||||
|  | 	hostConf.AutoRemove = req.AutoRemove | ||||||
|  | 	hostConf.CPUShares = req.CPUShares | ||||||
|  | 	hostConf.PublishAllPorts = req.PublishAllPorts | ||||||
|  | 	hostConf.RestartPolicy = container.RestartPolicy{Name: req.RestartPolicy} | ||||||
|  | 	if req.RestartPolicy == "on-failure" { | ||||||
|  | 		hostConf.RestartPolicy.MaximumRetryCount = 5 | ||||||
|  | 	} | ||||||
|  | 	hostConf.NanoCPUs = int64(req.NanoCPUs * 1000000000) | ||||||
|  | 	hostConf.Memory = int64(req.Memory * 1024 * 1024) | ||||||
|  | 	hostConf.PortBindings = portMap | ||||||
|  | 	hostConf.Binds = []string{} | ||||||
|  | 	config.Volumes = make(map[string]struct{}) | ||||||
|  | 	for _, volume := range req.Volumes { | ||||||
|  | 		config.Volumes[volume.ContainerDir] = struct{}{} | ||||||
|  | 		hostConf.Binds = append(hostConf.Binds, fmt.Sprintf("%s:%s:%s", volume.SourceDir, volume.ContainerDir, volume.Mode)) | ||||||
|  | 	} | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|   | |||||||
| @@ -4,6 +4,7 @@ import ( | |||||||
| 	"bufio" | 	"bufio" | ||||||
| 	"errors" | 	"errors" | ||||||
| 	"fmt" | 	"fmt" | ||||||
|  | 	"io" | ||||||
| 	"os" | 	"os" | ||||||
| 	"os/exec" | 	"os/exec" | ||||||
| 	"path" | 	"path" | ||||||
| @@ -91,7 +92,7 @@ func (u *ContainerService) PageCompose(req dto.SearchWithPage) (int64, interface | |||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 	for _, item := range composeCreatedByLocal { | 	for _, item := range composeCreatedByLocal { | ||||||
| 		if err := composeRepo.DeleteRecord(commonRepo.WithByName(item.Name)); err != nil { | 		if err := composeRepo.DeleteRecord(commonRepo.WithByID(item.ID)); err != nil { | ||||||
| 			global.LOG.Error(err) | 			global.LOG.Error(err) | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| @@ -100,11 +101,11 @@ func (u *ContainerService) PageCompose(req dto.SearchWithPage) (int64, interface | |||||||
| 		records = append(records, value) | 		records = append(records, value) | ||||||
| 	} | 	} | ||||||
| 	if len(req.Info) != 0 { | 	if len(req.Info) != 0 { | ||||||
| 		lenth, count := len(records), 0 | 		length, count := len(records), 0 | ||||||
| 		for count < lenth { | 		for count < length { | ||||||
| 			if !strings.Contains(records[count].Name, req.Info) { | 			if !strings.Contains(records[count].Name, req.Info) { | ||||||
| 				records = append(records[:count], records[(count+1):]...) | 				records = append(records[:count], records[(count+1):]...) | ||||||
| 				lenth-- | 				length-- | ||||||
| 			} else { | 			} else { | ||||||
| 				count++ | 				count++ | ||||||
| 			} | 			} | ||||||
| @@ -126,6 +127,10 @@ func (u *ContainerService) PageCompose(req dto.SearchWithPage) (int64, interface | |||||||
| } | } | ||||||
|  |  | ||||||
| func (u *ContainerService) TestCompose(req dto.ComposeCreate) (bool, error) { | func (u *ContainerService) TestCompose(req dto.ComposeCreate) (bool, error) { | ||||||
|  | 	composeItem, _ := composeRepo.GetRecord(commonRepo.WithByName(req.Name)) | ||||||
|  | 	if composeItem.ID != 0 { | ||||||
|  | 		return false, constant.ErrRecordExist | ||||||
|  | 	} | ||||||
| 	if err := u.loadPath(&req); err != nil { | 	if err := u.loadPath(&req); err != nil { | ||||||
| 		return false, err | 		return false, err | ||||||
| 	} | 	} | ||||||
| @@ -154,9 +159,10 @@ func (u *ContainerService) CreateCompose(req dto.ComposeCreate) (string, error) | |||||||
| 	go func() { | 	go func() { | ||||||
| 		defer file.Close() | 		defer file.Close() | ||||||
| 		cmd := exec.Command("docker-compose", "-f", req.Path, "up", "-d") | 		cmd := exec.Command("docker-compose", "-f", req.Path, "up", "-d") | ||||||
| 		stdout, err := cmd.CombinedOutput() | 		multiWriter := io.MultiWriter(os.Stdout, file) | ||||||
| 		_, _ = file.Write(stdout) | 		cmd.Stdout = multiWriter | ||||||
| 		if err != nil { | 		cmd.Stderr = multiWriter | ||||||
|  | 		if err := cmd.Run(); err != nil { | ||||||
| 			global.LOG.Errorf("docker-compose up %s failed, err: %v", req.Name, err) | 			global.LOG.Errorf("docker-compose up %s failed, err: %v", req.Name, err) | ||||||
| 			_, _ = compose.Down(req.Path) | 			_, _ = compose.Down(req.Path) | ||||||
| 			_, _ = file.WriteString("docker-compose up failed!") | 			_, _ = file.WriteString("docker-compose up failed!") | ||||||
| @@ -180,7 +186,9 @@ func (u *ContainerService) ComposeOperation(req dto.ComposeOperation) error { | |||||||
| 	global.LOG.Infof("docker-compose %s %s successful", req.Operation, req.Name) | 	global.LOG.Infof("docker-compose %s %s successful", req.Operation, req.Name) | ||||||
| 	if req.Operation == "down" { | 	if req.Operation == "down" { | ||||||
| 		_ = composeRepo.DeleteRecord(commonRepo.WithByName(req.Name)) | 		_ = composeRepo.DeleteRecord(commonRepo.WithByName(req.Name)) | ||||||
| 		_ = os.RemoveAll(strings.ReplaceAll(req.Path, "/docker-compose.yml", "")) | 		if req.WithFile { | ||||||
|  | 			_ = os.RemoveAll(path.Dir(req.Path)) | ||||||
|  | 		} | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	return nil | 	return nil | ||||||
| @@ -211,15 +219,7 @@ func (u *ContainerService) ComposeUpdate(req dto.ComposeUpdate) error { | |||||||
| } | } | ||||||
|  |  | ||||||
| func (u *ContainerService) loadPath(req *dto.ComposeCreate) error { | func (u *ContainerService) loadPath(req *dto.ComposeCreate) error { | ||||||
| 	if req.From == "template" { | 	if req.From == "template" || req.From == "edit" { | ||||||
| 		template, err := composeRepo.Get(commonRepo.WithByID(req.Template)) |  | ||||||
| 		if err != nil { |  | ||||||
| 			return err |  | ||||||
| 		} |  | ||||||
| 		req.From = "edit" |  | ||||||
| 		req.File = template.Content |  | ||||||
| 	} |  | ||||||
| 	if req.From == "edit" { |  | ||||||
| 		dir := fmt.Sprintf("%s/docker/compose/%s", constant.DataDir, req.Name) | 		dir := fmt.Sprintf("%s/docker/compose/%s", constant.DataDir, req.Name) | ||||||
| 		if _, err := os.Stat(dir); err != nil && os.IsNotExist(err) { | 		if _, err := os.Stat(dir); err != nil && os.IsNotExist(err) { | ||||||
| 			if err = os.MkdirAll(dir, os.ModePerm); err != nil { | 			if err = os.MkdirAll(dir, os.ModePerm); err != nil { | ||||||
|   | |||||||
| @@ -24,11 +24,11 @@ func (u *ContainerService) PageNetwork(req dto.SearchWithPage) (int64, interface | |||||||
| 		return 0, nil, err | 		return 0, nil, err | ||||||
| 	} | 	} | ||||||
| 	if len(req.Info) != 0 { | 	if len(req.Info) != 0 { | ||||||
| 		lenth, count := len(list), 0 | 		length, count := len(list), 0 | ||||||
| 		for count < lenth { | 		for count < length { | ||||||
| 			if !strings.Contains(list[count].Name, req.Info) { | 			if !strings.Contains(list[count].Name, req.Info) { | ||||||
| 				list = append(list[:count], list[(count+1):]...) | 				list = append(list[:count], list[(count+1):]...) | ||||||
| 				lenth-- | 				length-- | ||||||
| 			} else { | 			} else { | ||||||
| 				count++ | 				count++ | ||||||
| 			} | 			} | ||||||
| @@ -75,6 +75,26 @@ func (u *ContainerService) PageNetwork(req dto.SearchWithPage) (int64, interface | |||||||
|  |  | ||||||
| 	return int64(total), data, nil | 	return int64(total), data, nil | ||||||
| } | } | ||||||
|  |  | ||||||
|  | func (u *ContainerService) ListNetwork() ([]dto.Options, error) { | ||||||
|  | 	client, err := docker.NewDockerClient() | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 	list, err := client.NetworkList(context.TODO(), types.NetworkListOptions{}) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 	var datas []dto.Options | ||||||
|  | 	for _, item := range list { | ||||||
|  | 		datas = append(datas, dto.Options{Option: item.Name}) | ||||||
|  | 	} | ||||||
|  | 	sort.Slice(datas, func(i, j int) bool { | ||||||
|  | 		return datas[i].Option < datas[j].Option | ||||||
|  | 	}) | ||||||
|  | 	return datas, nil | ||||||
|  | } | ||||||
|  |  | ||||||
| func (u *ContainerService) DeleteNetwork(req dto.BatchDelete) error { | func (u *ContainerService) DeleteNetwork(req dto.BatchDelete) error { | ||||||
| 	client, err := docker.NewDockerClient() | 	client, err := docker.NewDockerClient() | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| @@ -90,7 +110,7 @@ func (u *ContainerService) DeleteNetwork(req dto.BatchDelete) error { | |||||||
| 	} | 	} | ||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
| func (u *ContainerService) CreateNetwork(req dto.NetworkCreat) error { | func (u *ContainerService) CreateNetwork(req dto.NetworkCreate) error { | ||||||
| 	client, err := docker.NewDockerClient() | 	client, err := docker.NewDockerClient() | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return err | 		return err | ||||||
|   | |||||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user