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/index.html | ||||
| 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 | ||||
| MAIN= $(BASE_PAH)/cmd/server/main.go | ||||
| APP_NAME=1panel | ||||
| ASSERT_PATH= $(BASE_PAH)/cmd/server/web/assets | ||||
|  | ||||
| build_web: | ||||
| 	cd $(WEB_PATH) && npm install && npm run build:dev | ||||
| clean_assets: | ||||
| 	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) \ | ||||
|     && 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) \ | ||||
|     && 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://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://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> | ||||
|  | ||||
| ------------------------------ | ||||
| @@ -13,9 +14,9 @@ | ||||
| 1Panel 是一个现代化、开源的 Linux 服务器运维管理面板。1Panel 的功能和优势包括: | ||||
|  | ||||
| - **快速建站**:深度集成 Wordpress 和 [Halo](https://github.com/halo-dev/halo/),域名绑定、SSL 证书配置等一键搞定; | ||||
| - **高效管理**:通过 Web 端轻松管理 Linux 服务器,包括应用管理、主机监控、文件管理、数据库管理、容器管理等; | ||||
| - **安全可靠**:最小漏洞暴露面,提供防火墙和安全审计等功能; | ||||
| - **一键备份**:支持一键备份和恢复,备份数据云端存储,永不丢失。 | ||||
| - **高效管理**:通过 Web 端轻松管理 Linux 服务器,包括主机监控、文件管理、数据库管理、容器管理等; | ||||
| - **安全可靠**:基于容器来管理和部署应用,最小漏洞暴露面,提供防火墙和日志审计等功能; | ||||
| - **一键备份**:支持一键备份和恢复,备份数据到各类云端存储,永不丢失。 | ||||
|  | ||||
| ## UI 展示 | ||||
|  | ||||
| @@ -41,12 +42,9 @@ curl -sSL https://resource.fit2cloud.com/1panel/package/quick_start.sh -o quick_ | ||||
|  | ||||
| - [在线文档](https://1panel.cn/docs/) | ||||
| - [教学视频](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"/> | ||||
|  | ||||
| @@ -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) | ||||
|  | ||||
| ## FOSSA Status | ||||
|  | ||||
| [](https://app.fossa.com/projects/git%2Bgithub.com%2F1Panel-dev%2F1Panel?ref=badge_large) | ||||
|  | ||||
| ## License | ||||
|  | ||||
| Copyright (c) 2014-2023 [FIT2CLOUD 飞致云](https://fit2cloud.com/), All rights reserved. | ||||
|   | ||||
| @@ -3,6 +3,7 @@ | ||||
| 如果您发现安全问题,请直接联系我们: | ||||
|  | ||||
| - wanghe@fit2cloud.com | ||||
| - zhengkun@fit2cloud.com | ||||
| - support@fit2cloud.com | ||||
| - 400-052-0755 | ||||
|  | ||||
| @@ -13,6 +14,7 @@ | ||||
| All security bugs should be reported to the contact as below: | ||||
|  | ||||
| - wanghe@fit2cloud.com | ||||
| - zhengkun@fit2cloud.com | ||||
| - support@fit2cloud.com | ||||
| - 400-052-0755 | ||||
|  | ||||
|   | ||||
| @@ -5,6 +5,7 @@ import ( | ||||
| 	"github.com/1Panel-dev/1Panel/backend/app/dto/request" | ||||
| 	"github.com/1Panel-dev/1Panel/backend/constant" | ||||
| 	"github.com/1Panel-dev/1Panel/backend/global" | ||||
| 	"github.com/1Panel-dev/1Panel/backend/i18n" | ||||
| 	"github.com/gin-gonic/gin" | ||||
| ) | ||||
|  | ||||
| @@ -38,13 +39,24 @@ func (b *BaseApi) SearchApp(c *gin.Context) { | ||||
| // @Router /apps/sync [post] | ||||
| // @x-panel-log {"bodyKeys":[],"paramKeys":[],"BeforeFuntions":[],"formatZH":"应用商店同步","formatEN":"App store synchronization"} | ||||
| func (b *BaseApi) SyncApp(c *gin.Context) { | ||||
| 	global.LOG.Infof("sync app list start ...") | ||||
| 	if err := appService.SyncAppList(); err != nil { | ||||
| 		global.LOG.Errorf("sync app list error [%s]", err.Error()) | ||||
| 	go appService.SyncAppListFromLocal() | ||||
| 	res, err := appService.GetAppUpdate() | ||||
| 	if err != nil { | ||||
| 		helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err) | ||||
| 		return | ||||
| 	} | ||||
| 	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, "") | ||||
| } | ||||
|  | ||||
| @@ -71,14 +83,15 @@ func (b *BaseApi) GetApp(c *gin.Context) { | ||||
| } | ||||
|  | ||||
| // @Tags App | ||||
| // @Summary Search app detail by id | ||||
| // @Description 通过 id 获取应用详情 | ||||
| // @Summary Search app detail by appid | ||||
| // @Description 通过 appid 获取应用详情 | ||||
| // @Accept json | ||||
| // @Param appId path integer true "app id" | ||||
| // @Param version path string true "app 版本" | ||||
| // @Param version path string true "app 类型" | ||||
| // @Success 200 {object} response.AppDetailDTO | ||||
| // @Security ApiKeyAuth | ||||
| // @Router /apps/detail/:appId/:version [get] | ||||
| // @Router /apps/detail/:appId/:version/:type [get] | ||||
| func (b *BaseApi) GetAppDetail(c *gin.Context) { | ||||
| 	appId, err := helper.GetIntParamByKey(c, "appId") | ||||
| 	if err != nil { | ||||
| @@ -86,7 +99,8 @@ func (b *BaseApi) GetAppDetail(c *gin.Context) { | ||||
| 		return | ||||
| 	} | ||||
| 	version := c.Param("version") | ||||
| 	appDetailDTO, err := appService.GetAppDetail(appId, version) | ||||
| 	appType := c.Param("type") | ||||
| 	appDetailDTO, err := appService.GetAppDetail(appId, version, appType) | ||||
| 	if err != nil { | ||||
| 		helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err) | ||||
| 		return | ||||
| @@ -94,6 +108,44 @@ func (b *BaseApi) GetAppDetail(c *gin.Context) { | ||||
| 	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 | ||||
| // @Summary Install app | ||||
| // @Description 安装应用 | ||||
| @@ -102,7 +154,7 @@ func (b *BaseApi) GetAppDetail(c *gin.Context) { | ||||
| // @Success 200 {object} model.AppInstall | ||||
| // @Security ApiKeyAuth | ||||
| // @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) { | ||||
| 	var req request.AppInstallCreate | ||||
| 	if err := c.ShouldBindJSON(&req); err != nil { | ||||
|   | ||||
| @@ -93,24 +93,24 @@ func (b *BaseApi) LoadPort(c *gin.Context) { | ||||
|  | ||||
| // @Tags App | ||||
| // @Summary Search app password by key | ||||
| // @Description 获取应用密码 | ||||
| // @Description 获取应用连接信息 | ||||
| // @Accept json | ||||
| // @Param key path string true "request" | ||||
| // @Success 200 {string} password | ||||
| // @Success 200 {string} response.DatabaseConn | ||||
| // @Security ApiKeyAuth | ||||
| // @Router /apps/installed/loadpassword/:key [get] | ||||
| func (b *BaseApi) LoadPassword(c *gin.Context) { | ||||
| // @Router /apps/installed/conninfo/:key [get] | ||||
| func (b *BaseApi) LoadConnInfo(c *gin.Context) { | ||||
| 	key, ok := c.Params.Get("key") | ||||
| 	if !ok { | ||||
| 		helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, errors.New("error key in path")) | ||||
| 		return | ||||
| 	} | ||||
| 	password, err := appInstallService.LoadPassword(key) | ||||
| 	conn, err := appInstallService.LoadConnInfo(key) | ||||
| 	if err != nil { | ||||
| 		helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err) | ||||
| 		return | ||||
| 	} | ||||
| 	helper.SuccessWithData(c, password) | ||||
| 	helper.SuccessWithData(c, conn) | ||||
| } | ||||
|  | ||||
| // @Tags App | ||||
| @@ -118,7 +118,7 @@ func (b *BaseApi) LoadPassword(c *gin.Context) { | ||||
| // @Description 删除前检查 | ||||
| // @Accept json | ||||
| // @Param appInstallId path integer true "App install id" | ||||
| // @Success 200 {anrry} dto.AppResource | ||||
| // @Success 200 {array} dto.AppResource | ||||
| // @Security ApiKeyAuth | ||||
| // @Router /apps/installed/delete/check/:appInstallId [get] | ||||
| func (b *BaseApi) DeleteCheck(c *gin.Context) { | ||||
| @@ -159,7 +159,7 @@ func (b *BaseApi) SyncInstalled(c *gin.Context) { | ||||
| // @Success 200 | ||||
| // @Security ApiKeyAuth | ||||
| // @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) { | ||||
| 	var req request.AppInstalledOperate | ||||
| 	if err := c.ShouldBindJSON(&req); err != nil { | ||||
| @@ -178,7 +178,7 @@ func (b *BaseApi) OperateInstalled(c *gin.Context) { | ||||
| // @Description 通过 key 获取应用 service | ||||
| // @Accept json | ||||
| // @Param key path string true "request" | ||||
| // @Success 200 {anrry} response.AppService | ||||
| // @Success 200 {array} response.AppService | ||||
| // @Security ApiKeyAuth | ||||
| // @Router /apps/services/:key [get] | ||||
| func (b *BaseApi) GetServices(c *gin.Context) { | ||||
| @@ -196,7 +196,7 @@ func (b *BaseApi) GetServices(c *gin.Context) { | ||||
| // @Description 通过 install id 获取应用更新版本 | ||||
| // @Accept json | ||||
| // @Param appInstallId path integer true "request" | ||||
| // @Success 200 {anrry} dto.AppVersion | ||||
| // @Success 200 {array} dto.AppVersion | ||||
| // @Security ApiKeyAuth | ||||
| // @Router /apps/installed/:appInstallId/versions [get] | ||||
| func (b *BaseApi) GetUpdateVersions(c *gin.Context) { | ||||
| @@ -305,3 +305,25 @@ func (b *BaseApi) UpdateInstalled(c *gin.Context) { | ||||
| 	} | ||||
| 	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 | ||||
|  | ||||
| import ( | ||||
| 	"errors" | ||||
|  | ||||
| 	"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/model" | ||||
| @@ -28,10 +26,12 @@ func (b *BaseApi) Login(c *gin.Context) { | ||||
| 		helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err) | ||||
| 		return | ||||
| 	} | ||||
| 	if req.AuthMethod != "jwt" && !req.IgnoreCaptcha { | ||||
| 		if err := captcha.VerifyCode(req.CaptchaID, req.Captcha); err != nil { | ||||
| 			helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err) | ||||
| 			return | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	user, err := authService.Login(c, req) | ||||
| 	go saveLoginLogs(c, err) | ||||
| @@ -100,71 +100,15 @@ func (b *BaseApi) Captcha(c *gin.Context) { | ||||
| // @Summary Load safety status | ||||
| // @Description 获取系统安全登录状态 | ||||
| // @Success 200 | ||||
| // @Failure 402 | ||||
| // @Router /auth/status [get] | ||||
| func (b *BaseApi) GetSafetyStatus(c *gin.Context) { | ||||
| 	if err := authService.SafetyStatus(c); err != nil { | ||||
| 		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) | ||||
| // @Router /auth/issafety [get] | ||||
| func (b *BaseApi) CheckIsSafety(c *gin.Context) { | ||||
| 	code := c.DefaultQuery("code", "") | ||||
| 	status, err := authService.CheckIsSafety(code) | ||||
| 	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) | ||||
| 		return | ||||
| 	} | ||||
| 	helper.SuccessWithData(c, nil) | ||||
| 	helper.SuccessWithData(c, status) | ||||
| } | ||||
|  | ||||
| // @Tags Auth | ||||
|   | ||||
| @@ -2,6 +2,8 @@ package v1 | ||||
|  | ||||
| import ( | ||||
| 	"encoding/base64" | ||||
| 	"fmt" | ||||
| 	"path" | ||||
|  | ||||
| 	"github.com/1Panel-dev/1Panel/backend/app/api/v1/helper" | ||||
| 	"github.com/1Panel-dev/1Panel/backend/app/dto" | ||||
| @@ -58,7 +60,7 @@ func (b *BaseApi) CreateBackup(c *gin.Context) { | ||||
| // @Description 获取 bucket 列表 | ||||
| // @Accept json | ||||
| // @Param request body dto.ForBuckets true "request" | ||||
| // @Success 200 {anrry} string | ||||
| // @Success 200 {array} string | ||||
| // @Security ApiKeyAuth | ||||
| // @Router /settings/backup/search [post] | ||||
| func (b *BaseApi) ListBuckets(c *gin.Context) { | ||||
| @@ -96,6 +98,22 @@ func (b *BaseApi) ListBuckets(c *gin.Context) { | ||||
| 	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 | ||||
| // @Summary Delete backup account | ||||
| // @Description 删除备份账号 | ||||
| @@ -104,9 +122,9 @@ func (b *BaseApi) ListBuckets(c *gin.Context) { | ||||
| // @Success 200 | ||||
| // @Security ApiKeyAuth | ||||
| // @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) { | ||||
| 	var req dto.BatchDeleteReq | ||||
| 	var req dto.OperateByID | ||||
| 	if err := c.ShouldBindJSON(&req); err != nil { | ||||
| 		helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err) | ||||
| 		return | ||||
| @@ -116,7 +134,7 @@ func (b *BaseApi) DeleteBackup(c *gin.Context) { | ||||
| 		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) | ||||
| 		return | ||||
| 	} | ||||
| @@ -186,7 +204,7 @@ func (b *BaseApi) DownloadRecord(c *gin.Context) { | ||||
| // @Success 200 | ||||
| // @Security ApiKeyAuth | ||||
| // @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) { | ||||
| 	var req dto.BatchDeleteReq | ||||
| 	if err := c.ShouldBindJSON(&req); err != nil { | ||||
| @@ -251,7 +269,7 @@ func (b *BaseApi) UpdateBackup(c *gin.Context) { | ||||
| // @Tags Backup Account | ||||
| // @Summary List backup accounts | ||||
| // @Description 获取备份账号列表 | ||||
| // @Success 200 {anrry} dto.BackupInfo | ||||
| // @Success 200 {array} dto.BackupInfo | ||||
| // @Security ApiKeyAuth | ||||
| // @Router /settings/backup/search [get] | ||||
| func (b *BaseApi) ListBackup(c *gin.Context) { | ||||
| @@ -269,7 +287,7 @@ func (b *BaseApi) ListBackup(c *gin.Context) { | ||||
| // @Description 获取备份账号内文件列表 | ||||
| // @Accept json | ||||
| // @Param request body dto.BackupSearchFile true "request" | ||||
| // @Success 200 {anrry} string | ||||
| // @Success 200 {array} string | ||||
| // @Security ApiKeyAuth | ||||
| // @Router /settings/backup/search/files [post] | ||||
| func (b *BaseApi) LoadFilesFromBackup(c *gin.Context) { | ||||
| @@ -356,6 +374,14 @@ func (b *BaseApi) Recover(c *gin.Context) { | ||||
| 		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 { | ||||
| 	case "mysql": | ||||
| 		if err := backupService.MysqlRecover(req); err != nil { | ||||
|   | ||||
| @@ -85,7 +85,7 @@ func (b *BaseApi) ListCommand(c *gin.Context) { | ||||
| // @Success 200 | ||||
| // @Security ApiKeyAuth | ||||
| // @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) { | ||||
| 	var req dto.BatchDeleteReq | ||||
| 	if err := c.ShouldBindJSON(&req); err != nil { | ||||
|   | ||||
| @@ -66,7 +66,7 @@ func (b *BaseApi) SearchComposeTemplate(c *gin.Context) { | ||||
| // @Summary List compose templates | ||||
| // @Description 获取容器编排模版列表 | ||||
| // @Produce json | ||||
| // @Success 200 {anrry} dto.ComposeTemplateInfo | ||||
| // @Success 200 {array} dto.ComposeTemplateInfo | ||||
| // @Security ApiKeyAuth | ||||
| // @Router /containers/template [get] | ||||
| func (b *BaseApi) ListComposeTemplate(c *gin.Context) { | ||||
| @@ -87,7 +87,7 @@ func (b *BaseApi) ListComposeTemplate(c *gin.Context) { | ||||
| // @Success 200 | ||||
| // @Security ApiKeyAuth | ||||
| // @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) { | ||||
| 	var req dto.BatchDeleteReq | ||||
| 	if err := c.ShouldBindJSON(&req); err != nil { | ||||
| @@ -114,7 +114,7 @@ func (b *BaseApi) DeleteComposeTemplate(c *gin.Context) { | ||||
| // @Success 200 | ||||
| // @Security ApiKeyAuth | ||||
| // @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) { | ||||
| 	var req dto.ComposeTemplateUpdate | ||||
| 	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 | ||||
| // @Summary Page composes | ||||
| // @Description 获取编排列表分页 | ||||
| @@ -153,17 +170,97 @@ func (b *BaseApi) OperatorCompose(c *gin.Context) { | ||||
| 	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 | ||||
| // @Summary Create container | ||||
| // @Description 创建容器 | ||||
| // @Accept json | ||||
| // @Param request body dto.ContainerCreate true "request" | ||||
| // @Param request body dto.ContainerOperate true "request" | ||||
| // @Success 200 | ||||
| // @Security ApiKeyAuth | ||||
| // @Router /containers [post] | ||||
| // @x-panel-log {"bodyKeys":["name","image"],"paramKeys":[],"BeforeFuntions":[],"formatZH":"创建容器 [name][image]","formatEN":"create container [name][image]"} | ||||
| func (b *BaseApi) ContainerCreate(c *gin.Context) { | ||||
| 	var req dto.ContainerCreate | ||||
| 	var req dto.ContainerOperate | ||||
| 	if err := c.ShouldBindJSON(&req); err != nil { | ||||
| 		helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err) | ||||
| 		return | ||||
| @@ -179,6 +276,85 @@ func (b *BaseApi) ContainerCreate(c *gin.Context) { | ||||
| 	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 | ||||
| // @Summary Operate Container | ||||
| // @Description 容器操作 | ||||
| @@ -209,7 +385,7 @@ func (b *BaseApi) ContainerOperation(c *gin.Context) { | ||||
| // @Summary Container stats | ||||
| // @Description 容器监控信息 | ||||
| // @Param id path integer true "容器id" | ||||
| // @Success 200 {object} dto.ContainterStats | ||||
| // @Success 200 {object} dto.ContainerStats | ||||
| // @Security ApiKeyAuth | ||||
| // @Router /containers/stats/:id [get] | ||||
| func (b *BaseApi) ContainerStats(c *gin.Context) { | ||||
| @@ -257,27 +433,29 @@ func (b *BaseApi) Inspect(c *gin.Context) { | ||||
| // @Tags Container | ||||
| // @Summary Container logs | ||||
| // @Description 容器日志 | ||||
| // @Accept json | ||||
| // @Param request body dto.ContainerLog true "request" | ||||
| // @Success 200 {string} logs | ||||
| // @Param container query string false "容器名称" | ||||
| // @Param since query string false "时间筛选" | ||||
| // @Param follow query string false "是否追踪" | ||||
| // @Param tail query string false "显示行号" | ||||
| // @Security ApiKeyAuth | ||||
| // @Router /containers/search/log [post] | ||||
| func (b *BaseApi) ContainerLogs(c *gin.Context) { | ||||
| 	var req dto.ContainerLog | ||||
| 	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) | ||||
| 	wsConn, err := upGrader.Upgrade(c.Writer, c.Request, 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 | ||||
| 	} | ||||
| 	helper.SuccessWithData(c, logs) | ||||
| } | ||||
|  | ||||
| // @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 | ||||
| // @Summary Delete network | ||||
| // @Description 删除容器网络 | ||||
| @@ -342,13 +537,13 @@ func (b *BaseApi) DeleteNetwork(c *gin.Context) { | ||||
| // @Summary Create network | ||||
| // @Description 创建容器网络 | ||||
| // @Accept json | ||||
| // @Param request body dto.NetworkCreat true "request" | ||||
| // @Param request body dto.NetworkCreate true "request" | ||||
| // @Success 200 | ||||
| // @Security ApiKeyAuth | ||||
| // @Router /containers/network [post] | ||||
| // @x-panel-log {"bodyKeys":["name"],"paramKeys":[],"BeforeFuntions":[],"formatZH":"创建容器网络 name","formatEN":"create container network [name]"} | ||||
| func (b *BaseApi) CreateNetwork(c *gin.Context) { | ||||
| 	var req dto.NetworkCreat | ||||
| 	var req dto.NetworkCreate | ||||
| 	if err := c.ShouldBindJSON(&req); err != nil { | ||||
| 		helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err) | ||||
| 		return | ||||
| @@ -400,11 +595,10 @@ func (b *BaseApi) SearchVolume(c *gin.Context) { | ||||
| // @Summary List volumes | ||||
| // @Description 获取容器存储卷列表 | ||||
| // @Accept json | ||||
| // @Param request body dto.PageInfo true "request" | ||||
| // @Produce json | ||||
| // @Success 200 {object} dto.PageResult | ||||
| // @Success 200 {array} dto.Options | ||||
| // @Security ApiKeyAuth | ||||
| // @Router /containers/volume/search [get] | ||||
| // @Router /containers/volume [get] | ||||
| func (b *BaseApi) ListVolume(c *gin.Context) { | ||||
| 	list, err := containerService.ListVolume() | ||||
| 	if err != nil { | ||||
| @@ -445,13 +639,13 @@ func (b *BaseApi) DeleteVolume(c *gin.Context) { | ||||
| // @Summary Create volume | ||||
| // @Description 创建容器存储卷 | ||||
| // @Accept json | ||||
| // @Param request body dto.VolumeCreat true "request" | ||||
| // @Param request body dto.VolumeCreate true "request" | ||||
| // @Success 200 | ||||
| // @Security ApiKeyAuth | ||||
| // @Router /containers/volume [post] | ||||
| // @x-panel-log {"bodyKeys":["name"],"paramKeys":[],"BeforeFuntions":[],"formatZH":"创建容器存储卷 [name]","formatEN":"create container volume [name]"} | ||||
| func (b *BaseApi) CreateVolume(c *gin.Context) { | ||||
| 	var req dto.VolumeCreat | ||||
| 	var req dto.VolumeCreate | ||||
| 	if err := c.ShouldBindJSON(&req); err != nil { | ||||
| 		helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err) | ||||
| 		return | ||||
|   | ||||
| @@ -7,6 +7,7 @@ import ( | ||||
| 	"github.com/1Panel-dev/1Panel/backend/app/dto" | ||||
| 	"github.com/1Panel-dev/1Panel/backend/constant" | ||||
| 	"github.com/1Panel-dev/1Panel/backend/global" | ||||
| 	"github.com/1Panel-dev/1Panel/backend/utils/common" | ||||
| 	"github.com/gin-gonic/gin" | ||||
| ) | ||||
|  | ||||
| @@ -77,8 +78,9 @@ func (b *BaseApi) SearchJobRecords(c *gin.Context) { | ||||
| 		helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err) | ||||
| 		return | ||||
| 	} | ||||
| 	req.StartTime = req.StartTime.Add(8 * time.Hour) | ||||
| 	req.EndTime = req.EndTime.Add(8 * time.Hour) | ||||
| 	loc, _ := time.LoadLocation(common.LoadTimeZone()) | ||||
| 	req.StartTime = req.StartTime.In(loc) | ||||
| 	req.EndTime = req.EndTime.In(loc) | ||||
|  | ||||
| 	total, list, err := cronjobService.SearchRecords(req) | ||||
| 	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 | ||||
| // @Summary Delete cronjob | ||||
| // @Description 删除计划任务 | ||||
| // @Accept json | ||||
| // @Param request body dto.BatchDeleteReq true "request" | ||||
| // @Param request body dto.CronjobBatchDelete true "request" | ||||
| // @Success 200 | ||||
| // @Security ApiKeyAuth | ||||
| // @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) { | ||||
| 	var req dto.BatchDeleteReq | ||||
| 	var req dto.CronjobBatchDelete | ||||
| 	if err := c.ShouldBindJSON(&req); err != nil { | ||||
| 		helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err) | ||||
| 		return | ||||
| @@ -112,7 +138,7 @@ func (b *BaseApi) DeleteCronjob(c *gin.Context) { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	if err := cronjobService.Delete(req.Ids); err != nil { | ||||
| 	if err := cronjobService.Delete(req); err != nil { | ||||
| 		helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err) | ||||
| 		return | ||||
| 	} | ||||
| @@ -127,7 +153,7 @@ func (b *BaseApi) DeleteCronjob(c *gin.Context) { | ||||
| // @Success 200 | ||||
| // @Security ApiKeyAuth | ||||
| // @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) { | ||||
| 	var req dto.CronjobUpdate | ||||
| 	if err := c.ShouldBindJSON(&req); err != nil { | ||||
| @@ -154,7 +180,7 @@ func (b *BaseApi) UpdateCronjob(c *gin.Context) { | ||||
| // @Success 200 | ||||
| // @Security ApiKeyAuth | ||||
| // @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) { | ||||
| 	var req dto.CronjobUpdateStatus | ||||
| 	if err := c.ShouldBindJSON(&req); err != nil { | ||||
| @@ -181,7 +207,7 @@ func (b *BaseApi) UpdateCronjobStatus(c *gin.Context) { | ||||
| // @Success 200 | ||||
| // @Security ApiKeyAuth | ||||
| // @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) { | ||||
| 	var req dto.CronjobDownload | ||||
| 	if err := c.ShouldBindJSON(&req); err != nil { | ||||
| @@ -209,7 +235,7 @@ func (b *BaseApi) TargetDownload(c *gin.Context) { | ||||
| // @Success 200 | ||||
| // @Security ApiKeyAuth | ||||
| // @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) { | ||||
| 	var req dto.OperateByID | ||||
| 	if err := c.ShouldBindJSON(&req); err != nil { | ||||
|   | ||||
| @@ -54,7 +54,7 @@ func (b *BaseApi) CreateMysql(c *gin.Context) { | ||||
| // @Success 200 | ||||
| // @Security ApiKeyAuth | ||||
| // @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) { | ||||
| 	var req dto.UpdateDescription | ||||
| 	if err := c.ShouldBindJSON(&req); err != nil { | ||||
| @@ -80,7 +80,7 @@ func (b *BaseApi) UpdateMysqlDescription(c *gin.Context) { | ||||
| // @Success 200 | ||||
| // @Security ApiKeyAuth | ||||
| // @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) { | ||||
| 	var req dto.ChangeDBInfo | ||||
| 	if err := c.ShouldBindJSON(&req); err != nil { | ||||
| @@ -115,7 +115,7 @@ func (b *BaseApi) ChangeMysqlPassword(c *gin.Context) { | ||||
| // @Success 200 | ||||
| // @Security ApiKeyAuth | ||||
| // @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) { | ||||
| 	var req dto.ChangeDBInfo | ||||
| 	if err := c.ShouldBindJSON(&req); err != nil { | ||||
| @@ -216,7 +216,7 @@ func (b *BaseApi) SearchMysql(c *gin.Context) { | ||||
| // @Description 获取 mysql 数据库列表 | ||||
| // @Accept json | ||||
| // @Param request body dto.PageInfo true "request" | ||||
| // @Success 200 {anrry} string | ||||
| // @Success 200 {array} string | ||||
| // @Security ApiKeyAuth | ||||
| // @Router /databases/options [get] | ||||
| func (b *BaseApi) ListDBName(c *gin.Context) { | ||||
| @@ -234,7 +234,7 @@ func (b *BaseApi) ListDBName(c *gin.Context) { | ||||
| // @Description Mysql 数据库删除前检查 | ||||
| // @Accept json | ||||
| // @Param request body dto.OperateByID true "request" | ||||
| // @Success 200 {anrry} string | ||||
| // @Success 200 {array} string | ||||
| // @Security ApiKeyAuth | ||||
| // @Router /databases/del/check [post] | ||||
| func (b *BaseApi) DeleteCheckMysql(c *gin.Context) { | ||||
| @@ -264,7 +264,7 @@ func (b *BaseApi) DeleteCheckMysql(c *gin.Context) { | ||||
| // @Success 200 | ||||
| // @Security ApiKeyAuth | ||||
| // @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) { | ||||
| 	var req dto.MysqlDBDelete | ||||
| 	if err := c.ShouldBindJSON(&req); err != nil { | ||||
|   | ||||
| @@ -1,7 +1,6 @@ | ||||
| package v1 | ||||
|  | ||||
| import ( | ||||
| 	"io/ioutil" | ||||
| 	"os" | ||||
|  | ||||
| 	"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") | ||||
| 		return | ||||
| 	} | ||||
| 	content, err := ioutil.ReadFile(constant.DaemonJsonPath) | ||||
| 	content, err := os.ReadFile(constant.DaemonJsonPath) | ||||
| 	if err != nil { | ||||
| 		helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err) | ||||
| 		return | ||||
| @@ -59,13 +58,13 @@ func (b *BaseApi) LoadDaemonJson(c *gin.Context) { | ||||
| // @Summary Update docker daemon.json | ||||
| // @Description 修改 docker 配置信息 | ||||
| // @Accept json | ||||
| // @Param request body dto.DaemonJsonConf true "request" | ||||
| // @Param request body dto.SettingUpdate 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 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) { | ||||
| 	var req dto.DaemonJsonConf | ||||
| 	var req dto.SettingUpdate | ||||
| 	if err := c.ShouldBindJSON(&req); err != nil { | ||||
| 		helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err) | ||||
| 		return | ||||
| @@ -79,6 +78,30 @@ func (b *BaseApi) UpdateDaemonJson(c *gin.Context) { | ||||
| 	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 | ||||
| // @Summary Update docker daemon.json by upload file | ||||
| // @Description 上传替换 docker 配置文件 | ||||
|   | ||||
| @@ -9,40 +9,45 @@ type ApiGroup struct { | ||||
| var ApiGroupApp = new(ApiGroup) | ||||
|  | ||||
| var ( | ||||
| 	authService      = service.ServiceGroupApp.AuthService | ||||
| 	dashboardService = service.ServiceGroupApp.DashboardService | ||||
| 	authService      = service.NewIAuthService() | ||||
| 	dashboardService = service.NewIDashboardService() | ||||
|  | ||||
| 	appService        = service.NewIAppService() | ||||
| 	appInstallService = service.ServiceGroupApp.AppInstallService | ||||
| 	appInstallService = service.NewIAppInstalledService() | ||||
|  | ||||
| 	containerService       = service.ServiceGroupApp.ContainerService | ||||
| 	composeTemplateService = service.ServiceGroupApp.ComposeTemplateService | ||||
| 	imageRepoService       = service.ServiceGroupApp.ImageRepoService | ||||
| 	imageService           = service.ServiceGroupApp.ImageService | ||||
| 	dockerService          = service.ServiceGroupApp.DockerService | ||||
| 	containerService       = service.NewIContainerService() | ||||
| 	composeTemplateService = service.NewIComposeTemplateService() | ||||
| 	imageRepoService       = service.NewIImageRepoService() | ||||
| 	imageService           = service.NewIImageService() | ||||
| 	dockerService          = service.NewIDockerService() | ||||
|  | ||||
| 	mysqlService = service.ServiceGroupApp.MysqlService | ||||
| 	redisService = service.ServiceGroupApp.RedisService | ||||
| 	mysqlService = service.NewIMysqlService() | ||||
| 	redisService = service.NewIRedisService() | ||||
|  | ||||
| 	cronjobService = service.ServiceGroupApp.CronjobService | ||||
| 	cronjobService = service.NewICronjobService() | ||||
|  | ||||
| 	hostService  = service.ServiceGroupApp.HostService | ||||
| 	groupService = service.ServiceGroupApp.GroupService | ||||
| 	fileService  = service.ServiceGroupApp.FileService | ||||
| 	hostService     = service.NewIHostService() | ||||
| 	groupService    = service.NewIGroupService() | ||||
| 	fileService     = service.NewIFileService() | ||||
| 	sshService      = service.NewISSHService() | ||||
| 	firewallService = service.NewIFirewallService() | ||||
|  | ||||
| 	settingService = service.ServiceGroupApp.SettingService | ||||
| 	backupService  = service.ServiceGroupApp.BackupService | ||||
| 	settingService = service.NewISettingService() | ||||
| 	backupService  = service.NewIBackupService() | ||||
|  | ||||
| 	commandService = service.ServiceGroupApp.CommandService | ||||
| 	commandService = service.NewICommandService() | ||||
|  | ||||
| 	websiteService            = service.ServiceGroupApp.WebsiteService | ||||
| 	websiteDnsAccountService  = service.ServiceGroupApp.WebsiteDnsAccountService | ||||
| 	websiteSSLService         = service.ServiceGroupApp.WebsiteSSLService | ||||
| 	websiteAcmeAccountService = service.ServiceGroupApp.WebsiteAcmeAccountService | ||||
| 	websiteService            = service.NewIWebsiteService() | ||||
| 	websiteDnsAccountService  = service.NewIWebsiteDnsAccountService() | ||||
| 	websiteSSLService         = service.NewIWebsiteSSLService() | ||||
| 	websiteAcmeAccountService = service.NewIWebsiteAcmeAccountService() | ||||
|  | ||||
| 	nginxService = service.ServiceGroupApp.NginxService | ||||
| 	nginxService = service.NewINginxService() | ||||
|  | ||||
| 	logService      = service.ServiceGroupApp.LogService | ||||
| 	snapshotService = service.ServiceGroupApp.SnapshotService | ||||
| 	upgradeService  = service.ServiceGroupApp.UpgradeService | ||||
| 	logService      = service.NewILogService() | ||||
| 	snapshotService = service.NewISnapshotService() | ||||
| 	upgradeService  = service.NewIUpgradeService() | ||||
|  | ||||
| 	runtimeService = service.NewRuntimeService() | ||||
| 	processService = service.NewIProcessService() | ||||
| ) | ||||
|   | ||||
| @@ -3,8 +3,9 @@ package v1 | ||||
| import ( | ||||
| 	"errors" | ||||
| 	"fmt" | ||||
| 	"io/ioutil" | ||||
| 	"io" | ||||
| 	"net/http" | ||||
| 	"net/url" | ||||
| 	"os" | ||||
| 	"path" | ||||
| 	"path/filepath" | ||||
| @@ -51,7 +52,7 @@ func (b *BaseApi) ListFiles(c *gin.Context) { | ||||
| // @Description 分页获取上传文件 | ||||
| // @Accept json | ||||
| // @Param request body request.SearchUploadWithPage true "request" | ||||
| // @Success 200 {anrry} response.FileInfo | ||||
| // @Success 200 {array} response.FileInfo | ||||
| // @Security ApiKeyAuth | ||||
| // @Router /files/upload/search [post] | ||||
| func (b *BaseApi) SearchUploadWithPage(c *gin.Context) { | ||||
| @@ -80,7 +81,7 @@ func (b *BaseApi) SearchUploadWithPage(c *gin.Context) { | ||||
| // @Description 加载文件树 | ||||
| // @Accept json | ||||
| // @Param request body request.FileOption true "request" | ||||
| // @Success 200 {anrry} response.FileTree | ||||
| // @Success 200 {array} response.FileTree | ||||
| // @Security ApiKeyAuth | ||||
| // @Router /files/tree [post] | ||||
| 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) | ||||
| 		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 | ||||
| @@ -432,18 +455,94 @@ func (b *BaseApi) MoveFile(c *gin.Context) { | ||||
| // @Router /files/download [post] | ||||
| // @x-panel-log {"bodyKeys":["name"],"paramKeys":[],"BeforeFuntions":[],"formatZH":"下载文件 [name]","formatEN":"Download file [name]"} | ||||
| 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 { | ||||
| 		helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err) | ||||
| 		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 { | ||||
| 		helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err) | ||||
| 		return | ||||
| 	} | ||||
| 	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 | ||||
| // @Summary Download file with path | ||||
| @@ -498,7 +597,6 @@ func (b *BaseApi) Size(c *gin.Context) { | ||||
| // @Success 200 {string} content | ||||
| // @Security ApiKeyAuth | ||||
| // @Router /files/loadfile [post] | ||||
| // @x-panel-log {"bodyKeys":["path"],"paramKeys":[],"BeforeFuntions":[],"formatZH":"读取文件 [path]","formatEN":"Read file [path]"} | ||||
| func (b *BaseApi) LoadFromFile(c *gin.Context) { | ||||
| 	var req dto.FilePath | ||||
| 	if err := c.ShouldBindJSON(&req); err != nil { | ||||
| @@ -510,7 +608,7 @@ func (b *BaseApi) LoadFromFile(c *gin.Context) { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	content, err := ioutil.ReadFile(req.Path) | ||||
| 	content, err := os.ReadFile(req.Path) | ||||
| 	if err != nil { | ||||
| 		helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err) | ||||
| 		return | ||||
| @@ -533,7 +631,7 @@ func mergeChunks(fileName string, fileDir string, dstDir string, chunkCount int) | ||||
|  | ||||
| 	for i := 0; i < chunkCount; i++ { | ||||
| 		chunkPath := filepath.Join(fileDir, fmt.Sprintf("%s.%d", fileName, i)) | ||||
| 		chunkData, err := ioutil.ReadFile(chunkPath) | ||||
| 		chunkData, err := os.ReadFile(chunkPath) | ||||
| 		if err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| @@ -554,6 +652,7 @@ func mergeChunks(fileName string, fileDir string, dstDir string, chunkCount int) | ||||
| // @Security ApiKeyAuth | ||||
| // @Router /files/chunkupload [post] | ||||
| func (b *BaseApi) UploadChunkFiles(c *gin.Context) { | ||||
| 	var err error | ||||
| 	fileForm, err := c.FormFile("chunk") | ||||
| 	if err != nil { | ||||
| 		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) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	chunkIndex, err := strconv.Atoi(c.PostForm("chunkIndex")) | ||||
| 	if err != nil { | ||||
| 		helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	chunkCount, err := strconv.Atoi(c.PostForm("chunkCount")) | ||||
| 	if err != nil { | ||||
| 		helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	fileOp := files.NewFileOp() | ||||
| 	tmpDir := path.Join(global.CONF.System.TmpDir, "upload") | ||||
| 	if !fileOp.Stat(tmpDir) { | ||||
| @@ -585,37 +681,50 @@ func (b *BaseApi) UploadChunkFiles(c *gin.Context) { | ||||
| 			return | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	filename := c.PostForm("filename") | ||||
| 	fileDir := filepath.Join(tmpDir, filename) | ||||
|  | ||||
| 	if chunkIndex == 0 { | ||||
| 		if fileOp.Stat(fileDir) { | ||||
| 			_ = fileOp.DeleteDir(fileDir) | ||||
| 		} | ||||
| 		_ = os.MkdirAll(fileDir, 0755) | ||||
| 	} | ||||
| 	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 { | ||||
| 		helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err) | ||||
| 		return | ||||
| 	} | ||||
| 	defer emptyFile.Close() | ||||
|  | ||||
| 	chunkData, err := ioutil.ReadAll(uploadFile) | ||||
| 	chunkData, err = io.ReadAll(uploadFile) | ||||
| 	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 | ||||
| 	} | ||||
|  | ||||
| 	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 { | ||||
| 		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 | ||||
| 	} | ||||
|  | ||||
| 	if chunkIndex+1 == chunkCount { | ||||
| 		err = mergeChunks(filename, fileDir, c.PostForm("path"), chunkCount) | ||||
| 		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 | ||||
| 		} | ||||
| 		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) { | ||||
| 	ws, err := wsUpgrade.Upgrade(c.Writer, c.Request, nil) | ||||
| 	if err != nil { | ||||
| 		return | ||||
| 	} | ||||
| 	wsClient := websocket2.NewWsClient("wsClient", ws) | ||||
| 	wsClient := websocket2.NewWsClient("fileClient", ws) | ||||
| 	go wsClient.Read() | ||||
| 	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" | ||||
| // @Success 200 | ||||
| // @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]"} | ||||
| func (b *BaseApi) CreateGroup(c *gin.Context) { | ||||
| 	var req dto.GroupCreate | ||||
| @@ -41,8 +41,8 @@ func (b *BaseApi) CreateGroup(c *gin.Context) { | ||||
| // @Param request body dto.OperateByID true "request" | ||||
| // @Success 200 | ||||
| // @Security ApiKeyAuth | ||||
| // @Router /hosts/group/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]"} | ||||
| // @Router /groups/del [post] | ||||
| // @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) { | ||||
| 	var req dto.OperateByID | ||||
| 	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" | ||||
| // @Success 200 | ||||
| // @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]"} | ||||
| func (b *BaseApi) UpdateGroup(c *gin.Context) { | ||||
| 	var req dto.GroupUpdate | ||||
| @@ -92,9 +92,9 @@ func (b *BaseApi) UpdateGroup(c *gin.Context) { | ||||
| // @Description 查询系统组 | ||||
| // @Accept json | ||||
| // @Param request body dto.GroupSearch true "request" | ||||
| // @Success 200 {anrry} dto.GroupInfo | ||||
| // @Success 200 {array} dto.GroupInfo | ||||
| // @Security ApiKeyAuth | ||||
| // @Router /hosts/group/search [post] | ||||
| // @Router /groups/search [post] | ||||
| func (b *BaseApi) ListGroup(c *gin.Context) { | ||||
| 	var req dto.GroupSearch | ||||
| 	if err := c.ShouldBindJSON(&req); err != nil { | ||||
|   | ||||
| @@ -83,6 +83,15 @@ func SuccessWithData(ctx *gin.Context, data interface{}) { | ||||
| 	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) { | ||||
| 	res := dto.Response{ | ||||
| 		Code:    constant.CodeSuccess, | ||||
|   | ||||
| @@ -7,8 +7,7 @@ import ( | ||||
| 	"github.com/1Panel-dev/1Panel/backend/app/dto" | ||||
| 	"github.com/1Panel-dev/1Panel/backend/constant" | ||||
| 	"github.com/1Panel-dev/1Panel/backend/global" | ||||
| 	"github.com/1Panel-dev/1Panel/backend/utils/copier" | ||||
| 	"github.com/1Panel-dev/1Panel/backend/utils/ssh" | ||||
| 	"github.com/1Panel-dev/1Panel/backend/utils/encrypt" | ||||
| 	"github.com/gin-gonic/gin" | ||||
| ) | ||||
|  | ||||
| @@ -37,7 +36,14 @@ func (b *BaseApi) CreateHost(c *gin.Context) { | ||||
| 			helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err) | ||||
| 			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 { | ||||
| 		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) | ||||
| 			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) | ||||
| @@ -74,33 +95,9 @@ func (b *BaseApi) TestByInfo(c *gin.Context) { | ||||
| 		helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err) | ||||
| 		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 | ||||
| 	_ = copier.Copy(&connInfo, &req) | ||||
| 	connInfo.PrivateKey = []byte(req.PrivateKey) | ||||
| 	client, err := connInfo.NewClient() | ||||
| 	if err != nil { | ||||
| 		helper.SuccessWithData(c, false) | ||||
| 		return | ||||
| 	} | ||||
| 	defer client.Close() | ||||
| 	helper.SuccessWithData(c, true) | ||||
| 	connStatus := hostService.TestByInfo(req) | ||||
| 	helper.SuccessWithData(c, connStatus) | ||||
| } | ||||
|  | ||||
| // @Tags Host | ||||
| @@ -127,7 +124,7 @@ func (b *BaseApi) TestByID(c *gin.Context) { | ||||
| // @Description 加载主机树 | ||||
| // @Accept json | ||||
| // @Param request body dto.SearchForTree true "request" | ||||
| // @Success 200 {anrry} dto.HostTree | ||||
| // @Success 200 {array} dto.HostTree | ||||
| // @Security ApiKeyAuth | ||||
| // @Router /hosts/tree [post] | ||||
| func (b *BaseApi) HostTree(c *gin.Context) { | ||||
| @@ -151,7 +148,7 @@ func (b *BaseApi) HostTree(c *gin.Context) { | ||||
| // @Description 获取主机列表分页 | ||||
| // @Accept json | ||||
| // @Param request body dto.SearchHostWithPage true "request" | ||||
| // @Success 200 {anrry} dto.HostTree | ||||
| // @Success 200 {array} dto.HostTree | ||||
| // @Security ApiKeyAuth | ||||
| // @Router /hosts/search [post] | ||||
| 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 | ||||
| // @Summary Delete host | ||||
| // @Description 删除主机 | ||||
| @@ -208,7 +178,7 @@ func (b *BaseApi) GetHostInfo(c *gin.Context) { | ||||
| // @Success 200 | ||||
| // @Security ApiKeyAuth | ||||
| // @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) { | ||||
| 	var req dto.BatchDeleteReq | ||||
| 	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) | ||||
| 			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 { | ||||
| 		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) | ||||
| 			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{}) | ||||
| @@ -270,11 +259,15 @@ func (b *BaseApi) UpdateHost(c *gin.Context) { | ||||
| 	upMap["port"] = req.Port | ||||
| 	upMap["user"] = req.User | ||||
| 	upMap["auth_mode"] = req.AuthMode | ||||
| 	if len(req.Password) != 0 { | ||||
| 	upMap["remember_password"] = req.RememberPassword | ||||
| 	if req.AuthMode == "password" { | ||||
| 		upMap["password"] = req.Password | ||||
| 	} | ||||
| 	if len(req.PrivateKey) != 0 { | ||||
| 		upMap["private_key"] = "" | ||||
| 		upMap["pass_phrase"] = "" | ||||
| 	} else { | ||||
| 		upMap["password"] = "" | ||||
| 		upMap["private_key"] = req.PrivateKey | ||||
| 		upMap["pass_phrase"] = req.PassPhrase | ||||
| 	} | ||||
| 	upMap["description"] = req.Description | ||||
| 	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" | ||||
| // @Success 200 | ||||
| // @Security ApiKeyAuth | ||||
| // @Router /hosts/update [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]"} | ||||
| // @Router /hosts/update/group [post] | ||||
| // @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) { | ||||
| 	var req dto.ChangeHostGroup | ||||
| 	if err := c.ShouldBindJSON(&req); err != nil { | ||||
|   | ||||
| @@ -44,7 +44,7 @@ func (b *BaseApi) SearchImage(c *gin.Context) { | ||||
| // @Summary List images | ||||
| // @Description 获取镜像列表 | ||||
| // @Produce json | ||||
| // @Success 200 {anrry} dto.Options | ||||
| // @Success 200 {array} dto.Options | ||||
| // @Security ApiKeyAuth | ||||
| // @Router /containers/image [get] | ||||
| func (b *BaseApi) ListImage(c *gin.Context) { | ||||
| @@ -93,7 +93,7 @@ func (b *BaseApi) ImageBuild(c *gin.Context) { | ||||
| // @Success 200 {string} log | ||||
| // @Security ApiKeyAuth | ||||
| // @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) { | ||||
| 	var req dto.ImagePull | ||||
| 	if err := c.ShouldBindJSON(&req); err != nil { | ||||
| @@ -122,7 +122,7 @@ func (b *BaseApi) ImagePull(c *gin.Context) { | ||||
| // @Success 200 {string} log | ||||
| // @Security ApiKeyAuth | ||||
| // @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) { | ||||
| 	var req dto.ImagePush | ||||
| 	if err := c.ShouldBindJSON(&req); err != nil { | ||||
| @@ -207,7 +207,7 @@ func (b *BaseApi) ImageSave(c *gin.Context) { | ||||
| // @Success 200 | ||||
| // @Security ApiKeyAuth | ||||
| // @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) { | ||||
| 	var req dto.ImageTag | ||||
| 	if err := c.ShouldBindJSON(&req); err != nil { | ||||
|   | ||||
| @@ -44,7 +44,7 @@ func (b *BaseApi) SearchRepo(c *gin.Context) { | ||||
| // @Summary List image repos | ||||
| // @Description 获取镜像仓库列表 | ||||
| // @Produce json | ||||
| // @Success 200 {anrry} dto.ImageRepoOption | ||||
| // @Success 200 {array} dto.ImageRepoOption | ||||
| // @Security ApiKeyAuth | ||||
| // @Router /containers/repo [get] | ||||
| func (b *BaseApi) ListRepo(c *gin.Context) { | ||||
| @@ -119,7 +119,7 @@ func (b *BaseApi) CreateRepo(c *gin.Context) { | ||||
| // @Success 200 | ||||
| // @Security ApiKeyAuth | ||||
| // @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) { | ||||
| 	var req dto.ImageRepoDelete | ||||
| 	if err := c.ShouldBindJSON(&req); err != nil { | ||||
| @@ -147,7 +147,7 @@ func (b *BaseApi) DeleteRepo(c *gin.Context) { | ||||
| // @Success 200 | ||||
| // @Security ApiKeyAuth | ||||
| // @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) { | ||||
| 	var req dto.ImageRepoUpdate | ||||
| 	if err := c.ShouldBindJSON(&req); err != nil { | ||||
|   | ||||
| @@ -1,6 +1,7 @@ | ||||
| package v1 | ||||
|  | ||||
| import ( | ||||
| 	"sort" | ||||
| 	"time" | ||||
|  | ||||
| 	"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/constant" | ||||
| 	"github.com/1Panel-dev/1Panel/backend/global" | ||||
| 	"github.com/1Panel-dev/1Panel/backend/utils/common" | ||||
| 	"github.com/gin-gonic/gin" | ||||
| 	"github.com/shirou/gopsutil/v3/disk" | ||||
| 	"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) | ||||
| 		return | ||||
| 	} | ||||
| 	req.StartTime = req.StartTime.Add(8 * time.Hour) | ||||
| 	req.EndTime = req.EndTime.Add(8 * time.Hour) | ||||
| 	loc, _ := time.LoadLocation(common.LoadTimeZone()) | ||||
| 	req.StartTime = req.StartTime.In(loc) | ||||
| 	req.EndTime = req.EndTime.In(loc) | ||||
|  | ||||
| 	var backdatas []dto.MonitorData | ||||
| 	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 { | ||||
| 		options = append(options, net.Name) | ||||
| 	} | ||||
| 	sort.Strings(options) | ||||
| 	helper.SuccessWithData(c, options) | ||||
| } | ||||
|  | ||||
| @@ -98,5 +102,6 @@ func (b *BaseApi) GetIOOptions(c *gin.Context) { | ||||
| 	for _, net := range diskStat { | ||||
| 		options = append(options, net.Name) | ||||
| 	} | ||||
| 	sort.Strings(options) | ||||
| 	helper.SuccessWithData(c, options) | ||||
| } | ||||
|   | ||||
| @@ -27,7 +27,7 @@ func (b *BaseApi) GetNginx(c *gin.Context) { | ||||
| // @Description 获取部分 OpenResty 配置信息 | ||||
| // @Accept json | ||||
| // @Param request body request.NginxScopeReq true "request" | ||||
| // @Success 200 {anrry} response.NginxParam | ||||
| // @Success 200 {array} response.NginxParam | ||||
| // @Security ApiKeyAuth | ||||
| // @Router /openResty/scope [post] | ||||
| func (b *BaseApi) GetNginxConfigByScope(c *gin.Context) { | ||||
| @@ -53,7 +53,7 @@ func (b *BaseApi) GetNginxConfigByScope(c *gin.Context) { | ||||
| // @Success 200 | ||||
| // @Security ApiKeyAuth | ||||
| // @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) { | ||||
| 	var req request.NginxConfigUpdate | ||||
| 	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 ( | ||||
| 	"errors" | ||||
| 	"time" | ||||
| 	"fmt" | ||||
| 	"strconv" | ||||
|  | ||||
| 	"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/1Panel-dev/1Panel/backend/utils/mfa" | ||||
| 	"github.com/1Panel-dev/1Panel/backend/utils/ntp" | ||||
| 	"github.com/gin-gonic/gin" | ||||
| ) | ||||
|  | ||||
| @@ -92,6 +92,48 @@ func (b *BaseApi) UpdatePassword(c *gin.Context) { | ||||
| 	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 | ||||
| // @Summary Update system port | ||||
| // @Description 更新系统端口 | ||||
| @@ -147,26 +189,40 @@ func (b *BaseApi) HandlePasswordExpired(c *gin.Context) { | ||||
| } | ||||
|  | ||||
| // @Tags System Setting | ||||
| // @Summary Sync system time | ||||
| // @Description 系统时间同步 | ||||
| // @Success 200 {string} ntime | ||||
| // @Summary Load time zone options | ||||
| // @Description 加载系统可用时区 | ||||
| // @Success 200 | ||||
| // @Security ApiKeyAuth | ||||
| // @Router /settings/time/sync [post] | ||||
| // @x-panel-log {"bodyKeys":[],"paramKeys":[],"BeforeFuntions":[],"formatZH":"系统时间同步","formatEN":"sync system time"} | ||||
| func (b *BaseApi) SyncTime(c *gin.Context) { | ||||
| 	ntime, err := ntp.Getremotetime() | ||||
| // @Router /settings/time/option [get] | ||||
| func (b *BaseApi) LoadTimeZone(c *gin.Context) { | ||||
| 	zones, err := settingService.LoadTimeZone() | ||||
| 	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) | ||||
| 		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 | ||||
| @@ -206,11 +262,23 @@ func (b *BaseApi) CleanMonitor(c *gin.Context) { | ||||
| // @Tags System Setting | ||||
| // @Summary Load mfa info | ||||
| // @Description 获取 mfa 信息 | ||||
| // @Param interval path string true "request" | ||||
| // @Success 200 {object} mfa.Otp | ||||
| // @Security ApiKeyAuth | ||||
| // @Router /settings/mfa [get] | ||||
| // @Router /settings/mfa/:interval [get] | ||||
| 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 { | ||||
| 		helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err) | ||||
| 		return | ||||
| @@ -234,12 +302,17 @@ func (b *BaseApi) MFABind(c *gin.Context) { | ||||
| 		helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err) | ||||
| 		return | ||||
| 	} | ||||
| 	success := mfa.ValidCode(req.Code, req.Secret) | ||||
| 	success := mfa.ValidCode(req.Code, req.Interval, req.Secret) | ||||
| 	if !success { | ||||
| 		helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, errors.New("code is not valid")) | ||||
| 		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 { | ||||
| 		helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err) | ||||
| 		return | ||||
|   | ||||
| @@ -68,7 +68,7 @@ func (b *BaseApi) ImportSnapshot(c *gin.Context) { | ||||
| // @Success 200 | ||||
| // @Security ApiKeyAuth | ||||
| // @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) { | ||||
| 	var req dto.UpdateDescription | ||||
| 	if err := c.ShouldBindJSON(&req); err != nil { | ||||
| @@ -119,7 +119,7 @@ func (b *BaseApi) SearchSnapshot(c *gin.Context) { | ||||
| // @Success 200 | ||||
| // @Security ApiKeyAuth | ||||
| // @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) { | ||||
| 	var req dto.SnapshotRecover | ||||
| 	if err := c.ShouldBindJSON(&req); err != nil { | ||||
| @@ -146,7 +146,7 @@ func (b *BaseApi) RecoverSnapshot(c *gin.Context) { | ||||
| // @Success 200 | ||||
| // @Security ApiKeyAuth | ||||
| // @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) { | ||||
| 	var req dto.SnapshotRecover | ||||
| 	if err := c.ShouldBindJSON(&req); err != nil { | ||||
| @@ -173,7 +173,7 @@ func (b *BaseApi) RollbackSnapshot(c *gin.Context) { | ||||
| // @Success 200 | ||||
| // @Security ApiKeyAuth | ||||
| // @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) { | ||||
| 	var req dto.BatchDeleteReq | ||||
| 	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 | ||||
|  | ||||
| import ( | ||||
| 	"encoding/base64" | ||||
| 	"encoding/json" | ||||
| 	"fmt" | ||||
| 	"net/http" | ||||
| 	"strconv" | ||||
| 	"strings" | ||||
| 	"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/utils/cmd" | ||||
| 	"github.com/1Panel-dev/1Panel/backend/utils/copier" | ||||
| @@ -19,30 +20,6 @@ import ( | ||||
| ) | ||||
|  | ||||
| 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) | ||||
| 	if err != nil { | ||||
| 		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() | ||||
|  | ||||
| 	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() | ||||
| 	if wshandleError(wsConn, errors.WithMessage(err, "failed to set up the connection. Please check the host information")) { | ||||
| 		return | ||||
| @@ -79,37 +79,36 @@ func (b *BaseApi) WsSsh(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) | ||||
| 	if err != nil { | ||||
| 		global.LOG.Errorf("gin context http handler failed, err: %v", err) | ||||
| 		return | ||||
| 	} | ||||
| 	defer wsConn.Close() | ||||
| 	commands := fmt.Sprintf("docker exec -it %s redis-cli", redisConf.ContainerName) | ||||
| 	if len(redisConf.Requirepass) != 0 { | ||||
| 		commands = fmt.Sprintf("docker exec -it %s redis-cli -a %s --no-auth-warning", redisConf.ContainerName, redisConf.Requirepass) | ||||
|  | ||||
| 	cols, err := strconv.Atoi(c.DefaultQuery("cols", "80")) | ||||
| 	if wshandleError(wsConn, errors.WithMessage(err, "invalid param cols in request")) { | ||||
| 		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) { | ||||
| 		return | ||||
| 	} | ||||
| 	defer killBash(redisConf.ContainerName, commands, pidMap) | ||||
| 	defer slave.Close() | ||||
|  | ||||
| 	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) { | ||||
| 	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) | ||||
| 	if err != nil { | ||||
| 		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() | ||||
|  | ||||
| 	cmds := fmt.Sprintf("docker exec %s %s", containerID, command) | ||||
| 	if len(user) != 0 { | ||||
| 		cmds = fmt.Sprintf("docker exec -u %s %s %s", user, containerID, command) | ||||
| 	containerID := c.Query("containerid") | ||||
| 	command := c.Query("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)) { | ||||
| 		return | ||||
| 	} | ||||
| @@ -168,10 +171,12 @@ func (b *BaseApi) ContainerWsSsh(c *gin.Context) { | ||||
| 	if len(user) != 0 { | ||||
| 		commands = fmt.Sprintf("docker exec -it -u %s %s %s", user, containerID, command) | ||||
| 	} | ||||
| 	pidMap := loadMapFromDockerTop(containerID) | ||||
| 	slave, err := terminal.NewCommand(commands) | ||||
| 	if wshandleError(wsConn, err) { | ||||
| 		return | ||||
| 	} | ||||
| 	defer killBash(containerID, command, pidMap) | ||||
| 	defer slave.Close() | ||||
|  | ||||
| 	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) | ||||
| 		dt := time.Now().Add(time.Second) | ||||
| 		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 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{ | ||||
| 	ReadBufferSize:  1024, | ||||
| 	WriteBufferSize: 1024 * 1024 * 10, | ||||
|   | ||||
| @@ -36,7 +36,7 @@ func (b *BaseApi) PageWebsite(c *gin.Context) { | ||||
| // @Tags Website | ||||
| // @Summary List websites | ||||
| // @Description 获取网站列表 | ||||
| // @Success 200 {anrry} response.WebsiteDTO | ||||
| // @Success 200 {array} response.WebsiteDTO | ||||
| // @Security ApiKeyAuth | ||||
| // @Router /websites/list [get] | ||||
| func (b *BaseApi) GetWebsites(c *gin.Context) { | ||||
| @@ -51,7 +51,7 @@ func (b *BaseApi) GetWebsites(c *gin.Context) { | ||||
| // @Tags Website | ||||
| // @Summary List website names | ||||
| // @Description 获取网站列表 | ||||
| // @Success 200 {anrry} string | ||||
| // @Success 200 {array} string | ||||
| // @Security ApiKeyAuth | ||||
| // @Router /websites/options [get] | ||||
| 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) | ||||
| 		return | ||||
| 	} | ||||
| 	tx, ctx := helper.GetTxAndContext() | ||||
| 	err := websiteService.CreateWebsite(ctx, req) | ||||
|  | ||||
| 	err := websiteService.CreateWebsite(req) | ||||
| 	if err != nil { | ||||
| 		tx.Rollback() | ||||
| 		helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err) | ||||
| 		return | ||||
| 	} | ||||
| 	tx.Commit() | ||||
| 	helper.SuccessWithData(c, nil) | ||||
| } | ||||
|  | ||||
| @@ -97,7 +95,7 @@ func (b *BaseApi) CreateWebsite(c *gin.Context) { | ||||
| // @Success 200 | ||||
| // @Security ApiKeyAuth | ||||
| // @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) { | ||||
| 	var req request.WebsiteOp | ||||
| 	if err := c.ShouldBindJSON(&req); err != nil { | ||||
| @@ -120,21 +118,19 @@ func (b *BaseApi) OpWebsite(c *gin.Context) { | ||||
| // @Success 200 | ||||
| // @Security ApiKeyAuth | ||||
| // @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) { | ||||
| 	var req request.WebsiteDelete | ||||
| 	if err := c.ShouldBindJSON(&req); err != nil { | ||||
| 		helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err) | ||||
| 		return | ||||
| 	} | ||||
| 	tx, ctx := helper.GetTxAndContext() | ||||
| 	err := websiteService.DeleteWebsite(ctx, req) | ||||
|  | ||||
| 	err := websiteService.DeleteWebsite(req) | ||||
| 	if err != nil { | ||||
| 		tx.Rollback() | ||||
| 		helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err) | ||||
| 		return | ||||
| 	} | ||||
| 	tx.Commit() | ||||
| 	helper.SuccessWithData(c, nil) | ||||
| } | ||||
|  | ||||
| @@ -189,14 +185,16 @@ func (b *BaseApi) GetWebsite(c *gin.Context) { | ||||
| // @Param id path integer true "request" | ||||
| // @Success 200 {object} response.FileInfo | ||||
| // @Security ApiKeyAuth | ||||
| // @Router /websites/:id/nginx [get] | ||||
| // @Router /websites/:id/config/:type [get] | ||||
| func (b *BaseApi) GetWebsiteNginx(c *gin.Context) { | ||||
| 	id, err := helper.GetParamID(c) | ||||
| 	if err != nil { | ||||
| 		helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInternalServer, nil) | ||||
| 		return | ||||
| 	} | ||||
| 	fileInfo, err := websiteService.GetWebsiteNginxConfig(id) | ||||
| 	configType := c.Param("type") | ||||
|  | ||||
| 	fileInfo, err := websiteService.GetWebsiteNginxConfig(id, configType) | ||||
| 	if err != nil { | ||||
| 		helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err) | ||||
| 		return | ||||
| @@ -209,7 +207,7 @@ func (b *BaseApi) GetWebsiteNginx(c *gin.Context) { | ||||
| // @Description 通过网站 id 查询域名 | ||||
| // @Accept json | ||||
| // @Param websiteId path integer true "request" | ||||
| // @Success 200 {anrry} model.WebsiteDomain | ||||
| // @Success 200 {array} model.WebsiteDomain | ||||
| // @Security ApiKeyAuth | ||||
| // @Router /websites/domains/:websiteId [get] | ||||
| func (b *BaseApi) GetWebDomains(c *gin.Context) { | ||||
| @@ -234,7 +232,7 @@ func (b *BaseApi) GetWebDomains(c *gin.Context) { | ||||
| // @Success 200 | ||||
| // @Security ApiKeyAuth | ||||
| // @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) { | ||||
| 	var req request.WebsiteDomainDelete | ||||
| 	if err := c.ShouldBindJSON(&req); err != nil { | ||||
| @@ -302,7 +300,7 @@ func (b *BaseApi) GetNginxConfig(c *gin.Context) { | ||||
| // @Success 200 | ||||
| // @Security ApiKeyAuth | ||||
| // @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) { | ||||
| 	var req request.NginxConfigUpdate | ||||
| 	if err := c.ShouldBindJSON(&req); err != nil { | ||||
| @@ -346,7 +344,7 @@ func (b *BaseApi) GetHTTPSConfig(c *gin.Context) { | ||||
| // @Success 200 {object} response.WebsiteHTTPS | ||||
| // @Security ApiKeyAuth | ||||
| // @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) { | ||||
| 	var req request.WebsiteHTTPSOp | ||||
| 	if err := c.ShouldBindJSON(&req); err != nil { | ||||
| @@ -369,7 +367,7 @@ func (b *BaseApi) UpdateHTTPSConfig(c *gin.Context) { | ||||
| // @Description 网站创建前检查 | ||||
| // @Accept json | ||||
| // @Param request body request.WebsiteInstallCheckReq true "request" | ||||
| // @Success 200 {anrry} request.WebsitePreInstallCheck | ||||
| // @Success 200 {array} response.WebsitePreInstallCheck | ||||
| // @Security ApiKeyAuth | ||||
| // @Router /websites/check [post] | ||||
| func (b *BaseApi) CreateWebsiteCheck(c *gin.Context) { | ||||
| @@ -416,7 +414,7 @@ func (b *BaseApi) GetWebsiteWafConfig(c *gin.Context) { | ||||
| // @Success 200 | ||||
| // @Security ApiKeyAuth | ||||
| // @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) { | ||||
| 	var req request.WebsiteWafUpdate | ||||
| 	if err := c.ShouldBindJSON(&req); err != nil { | ||||
| @@ -438,7 +436,7 @@ func (b *BaseApi) UpdateWebsiteWafConfig(c *gin.Context) { | ||||
| // @Success 200 | ||||
| // @Security ApiKeyAuth | ||||
| // @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) { | ||||
| 	var req request.WebsiteNginxUpdate | ||||
| 	if err := c.ShouldBindJSON(&req); err != nil { | ||||
| @@ -460,7 +458,7 @@ func (b *BaseApi) UpdateWebsiteNginxConfig(c *gin.Context) { | ||||
| // @Success 200 {object} response.WebsiteLog | ||||
| // @Security ApiKeyAuth | ||||
| // @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) { | ||||
| 	var req request.WebsiteLogReq | ||||
| 	if err := c.ShouldBindJSON(&req); err != nil { | ||||
| @@ -483,7 +481,7 @@ func (b *BaseApi) OpWebsiteLog(c *gin.Context) { | ||||
| // @Success 200 | ||||
| // @Security ApiKeyAuth | ||||
| // @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) { | ||||
| 	var req request.WebsiteDefaultUpdate | ||||
| 	if err := c.ShouldBindJSON(&req); err != nil { | ||||
| @@ -496,3 +494,310 @@ func (b *BaseApi) ChangeDefaultServer(c *gin.Context) { | ||||
| 	} | ||||
| 	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 | ||||
| // @Security ApiKeyAuth | ||||
| // @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) { | ||||
| 	var req request.WebsiteResourceReq | ||||
| 	if err := c.ShouldBindJSON(&req); err != nil { | ||||
|   | ||||
| @@ -85,7 +85,7 @@ func (b *BaseApi) UpdateWebsiteDnsAccount(c *gin.Context) { | ||||
| // @Success 200 | ||||
| // @Security ApiKeyAuth | ||||
| // @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) { | ||||
| 	var req request.WebsiteResourceReq | ||||
| 	if err := c.ShouldBindJSON(&req); err != nil { | ||||
|   | ||||
| @@ -35,7 +35,7 @@ func (b *BaseApi) PageWebsiteSSL(c *gin.Context) { | ||||
| 			Items: accounts, | ||||
| 		}) | ||||
| 	} else { | ||||
| 		list, err := websiteSSLService.Search() | ||||
| 		list, err := websiteSSLService.Search(req) | ||||
| 		if err != nil { | ||||
| 			helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err) | ||||
| 			return | ||||
| @@ -75,7 +75,7 @@ func (b *BaseApi) CreateWebsiteSSL(c *gin.Context) { | ||||
| // @Success 200 | ||||
| // @Security ApiKeyAuth | ||||
| // @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) { | ||||
| 	var req request.WebsiteSSLRenew | ||||
| 	if err := c.ShouldBindJSON(&req); err != nil { | ||||
| @@ -94,7 +94,7 @@ func (b *BaseApi) RenewWebsiteSSL(c *gin.Context) { | ||||
| // @Description 解析网站 ssl | ||||
| // @Accept json | ||||
| // @Param request body request.WebsiteDNSReq true "request" | ||||
| // @Success 200 {anrry} response.WebsiteDNSRes | ||||
| // @Success 200 {array} response.WebsiteDNSRes | ||||
| // @Security ApiKeyAuth | ||||
| // @Router /websites/ssl/resolve [post] | ||||
| func (b *BaseApi) GetDNSResolve(c *gin.Context) { | ||||
| @@ -119,7 +119,7 @@ func (b *BaseApi) GetDNSResolve(c *gin.Context) { | ||||
| // @Success 200 | ||||
| // @Security ApiKeyAuth | ||||
| // @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) { | ||||
| 	var req request.WebsiteResourceReq | ||||
| 	if err := c.ShouldBindJSON(&req); err != nil { | ||||
| @@ -185,7 +185,7 @@ func (b *BaseApi) GetWebsiteSSLById(c *gin.Context) { | ||||
| // @Success 200 | ||||
| // @Security ApiKeyAuth | ||||
| // @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) { | ||||
| 	var req request.WebsiteSSLUpdate | ||||
| 	if err := c.ShouldBindJSON(&req); err != nil { | ||||
|   | ||||
| @@ -1,7 +1,7 @@ | ||||
| package dto | ||||
|  | ||||
| import ( | ||||
| 	"encoding/json" | ||||
| 	"github.com/1Panel-dev/1Panel/backend/app/model" | ||||
| ) | ||||
|  | ||||
| type AppDatabase struct { | ||||
| @@ -32,19 +32,47 @@ type AppVersion struct { | ||||
| } | ||||
|  | ||||
| type AppList struct { | ||||
| 	Version string      `json:"version"` | ||||
| 	Tags    []Tag       `json:"tags"` | ||||
| 	Items   []AppDefine `json:"items"` | ||||
| 	Valid        bool     `json:"valid"` | ||||
| 	Violations   []string `json:"violations"` | ||||
| 	LastModified int      `json:"lastModified"` | ||||
|  | ||||
| 	Apps  []AppDefine     `json:"apps"` | ||||
| 	Extra ExtraProperties `json:"additionalProperties"` | ||||
| } | ||||
|  | ||||
| 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"` | ||||
| 	Type               string   `json:"type"` | ||||
| 	Tags               []string `json:"tags"` | ||||
| 	Versions           []string `json:"versions"` | ||||
| 	ShortDescZh        string   `json:"shortDescZh"` | ||||
| 	ShortDescEn        string   `json:"shortDescEn"` | ||||
| 	Type               string   `json:"type"` | ||||
| 	Key                string   `json:"key"` | ||||
| 	Required           []string `json:"Required"` | ||||
| 	CrossVersionUpdate bool     `json:"crossVersionUpdate"` | ||||
| 	Limit              int      `json:"limit"` | ||||
| @@ -54,9 +82,12 @@ type AppDefine struct { | ||||
| 	Document           string   `json:"document"` | ||||
| } | ||||
|  | ||||
| func (define AppDefine) GetRequired() string { | ||||
| 	by, _ := json.Marshal(define.Required) | ||||
| 	return string(by) | ||||
| type AppConfigVersion struct { | ||||
| 	Name                string      `json:"name"` | ||||
| 	LastModified        int         `json:"lastModified"` | ||||
| 	DownloadUrl         string      `json:"downloadUrl"` | ||||
| 	DownloadCallBackUrl string      `json:"downloadCallBackUrl"` | ||||
| 	AppForm             interface{} `json:"additionalProperties"` | ||||
| } | ||||
|  | ||||
| type Tag struct { | ||||
| @@ -78,6 +109,8 @@ type AppFormFields struct { | ||||
| 	Disabled bool           `json:"disabled"` | ||||
| 	Edit     bool           `json:"edit"` | ||||
| 	Rule     string         `json:"rule"` | ||||
| 	Multiple bool           `json:"multiple"` | ||||
| 	Child    interface{}    `json:"child"` | ||||
| 	Values   []AppFormValue `json:"values"` | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -14,11 +14,13 @@ type UserLoginInfo struct { | ||||
| type MfaCredential struct { | ||||
| 	Secret   string `json:"secret"` | ||||
| 	Code     string `json:"code"` | ||||
| 	Interval string `json:"interval"` | ||||
| } | ||||
|  | ||||
| type Login struct { | ||||
| 	Name          string `json:"name"` | ||||
| 	Password      string `json:"password"` | ||||
| 	IgnoreCaptcha bool   `json:"ignoreCaptcha"` | ||||
| 	Captcha       string `json:"captcha"` | ||||
| 	CaptchaID     string `json:"captchaID"` | ||||
| 	AuthMethod    string `json:"authMethod"` | ||||
| @@ -30,8 +32,3 @@ type MFALogin struct { | ||||
| 	Code       string `json:"code"` | ||||
| 	AuthMethod string `json:"authMethod"` | ||||
| } | ||||
|  | ||||
| type InitUser struct { | ||||
| 	Name     string `json:"name" validate:"required"` | ||||
| 	Password string `json:"password" validate:"required"` | ||||
| } | ||||
|   | ||||
| @@ -8,6 +8,7 @@ type BackupOperate struct { | ||||
| 	Bucket     string `json:"bucket"` | ||||
| 	AccessKey  string `json:"accessKey"` | ||||
| 	Credential string `json:"credential"` | ||||
| 	BackupPath string `json:"backupPath"` | ||||
| 	Vars       string `json:"vars" validate:"required"` | ||||
| } | ||||
|  | ||||
| @@ -16,6 +17,7 @@ type BackupInfo struct { | ||||
| 	CreatedAt  time.Time `json:"createdAt"` | ||||
| 	Type       string    `json:"type"` | ||||
| 	Bucket     string    `json:"bucket"` | ||||
| 	BackupPath string    `json:"backupPath"` | ||||
| 	Vars       string    `json:"vars"` | ||||
| } | ||||
|  | ||||
| @@ -36,6 +38,7 @@ type CommonBackup struct { | ||||
| 	DetailName string `json:"detailName"` | ||||
| } | ||||
| 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"` | ||||
| 	Name       string `json:"name"` | ||||
| 	DetailName string `json:"detailName"` | ||||
| @@ -59,7 +62,7 @@ type BackupRecords 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"` | ||||
| 	FileName string `json:"fileName" validate:"required"` | ||||
| } | ||||
|   | ||||
| @@ -3,6 +3,8 @@ package dto | ||||
| type SearchWithPage struct { | ||||
| 	PageInfo | ||||
| 	Info    string `json:"info"` | ||||
| 	OrderBy string `json:"orderBy"` | ||||
| 	Order   string `json:"order"` | ||||
| } | ||||
|  | ||||
| type PageInfo struct { | ||||
| @@ -12,7 +14,7 @@ type PageInfo struct { | ||||
|  | ||||
| type UpdateDescription struct { | ||||
| 	ID          uint   `json:"id" validate:"required"` | ||||
| 	Description string `json:"description"` | ||||
| 	Description string `json:"description" validate:"max=256"` | ||||
| } | ||||
|  | ||||
| type OperationWithName struct { | ||||
| @@ -23,6 +25,10 @@ type OperateByID struct { | ||||
| 	ID uint `json:"id" validate:"required"` | ||||
| } | ||||
|  | ||||
| type Operate struct { | ||||
| 	Operation string `json:"operation" validate:"required"` | ||||
| } | ||||
|  | ||||
| type BatchDeleteReq struct { | ||||
| 	Ids []uint `json:"ids" validate:"required"` | ||||
| } | ||||
|   | ||||
| @@ -5,6 +5,8 @@ import "time" | ||||
| type PageContainer struct { | ||||
| 	PageInfo | ||||
| 	Name    string `json:"name"` | ||||
| 	OrderBy string `json:"orderBy"` | ||||
| 	Order   string `json:"order"` | ||||
| 	Filters string `json:"filters"` | ||||
| } | ||||
|  | ||||
| @@ -22,18 +24,29 @@ type ContainerInfo struct { | ||||
| 	State       string `json:"state"` | ||||
| 	RunTime     string `json:"runTime"` | ||||
|  | ||||
| 	Ports []string `json:"ports"` | ||||
|  | ||||
| 	IsFromApp     bool `json:"isFromApp"` | ||||
| 	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"` | ||||
| 	Image           string         `json:"image"` | ||||
| 	Network         string         `json:"network"` | ||||
| 	PublishAllPorts bool           `json:"publishAllPorts"` | ||||
| 	ExposedPorts    []PortHelper   `json:"exposedPorts"` | ||||
| 	Cmd             []string       `json:"cmd"` | ||||
| 	NanoCPUs        int64          `json:"nanoCPUs"` | ||||
| 	Memory          int64          `json:"memory"` | ||||
| 	CPUShares       int64          `json:"cpuShares"` | ||||
| 	NanoCPUs        float64        `json:"nanoCPUs"` | ||||
| 	Memory          float64        `json:"memory"` | ||||
| 	AutoRemove      bool           `json:"autoRemove"` | ||||
| 	Volumes         []VolumeHelper `json:"volumes"` | ||||
| 	Labels          []string       `json:"labels"` | ||||
| @@ -41,7 +54,19 @@ type ContainerCreate struct { | ||||
| 	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"` | ||||
| 	Memory     float64 `json:"memory"` | ||||
| 	Cache      float64 `json:"cache"` | ||||
| @@ -59,13 +84,10 @@ type VolumeHelper struct { | ||||
| 	Mode         string `json:"mode"` | ||||
| } | ||||
| type PortHelper struct { | ||||
| 	ContainerPort int `json:"containerPort"` | ||||
| 	HostPort      int `json:"hostPort"` | ||||
| } | ||||
|  | ||||
| type ContainerLog struct { | ||||
| 	ContainerID string `json:"containerID" validate:"required"` | ||||
| 	Mode        string `json:"mode" validate:"required"` | ||||
| 	HostIP        string `json:"hostIP"` | ||||
| 	HostPort      string `json:"hostPort"` | ||||
| 	ContainerPort string `json:"containerPort"` | ||||
| 	Protocol      string `json:"protocol"` | ||||
| } | ||||
|  | ||||
| type ContainerOperation struct { | ||||
| @@ -74,6 +96,16 @@ type ContainerOperation struct { | ||||
| 	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 { | ||||
| 	ID         string    `json:"id"` | ||||
| 	Name       string    `json:"name"` | ||||
| @@ -85,7 +117,7 @@ type Network struct { | ||||
| 	CreatedAt  time.Time `json:"createdAt"` | ||||
| 	Attachable bool      `json:"attachable"` | ||||
| } | ||||
| type NetworkCreat struct { | ||||
| type NetworkCreate struct { | ||||
| 	Name    string   `json:"name"` | ||||
| 	Driver  string   `json:"driver"` | ||||
| 	Options []string `json:"options"` | ||||
| @@ -102,7 +134,7 @@ type Volume struct { | ||||
| 	Mountpoint string    `json:"mountpoint"` | ||||
| 	CreatedAt  time.Time `json:"createdAt"` | ||||
| } | ||||
| type VolumeCreat struct { | ||||
| type VolumeCreate struct { | ||||
| 	Name    string   `json:"name"` | ||||
| 	Driver  string   `json:"driver"` | ||||
| 	Options []string `json:"options"` | ||||
| @@ -140,6 +172,7 @@ type ComposeOperation struct { | ||||
| 	Name      string `json:"name" validate:"required"` | ||||
| 	Path      string `json:"path" validate:"required"` | ||||
| 	Operation string `json:"operation" validate:"required,oneof=start stop down"` | ||||
| 	WithFile  bool   `json:"withFile"` | ||||
| } | ||||
| type ComposeUpdate struct { | ||||
| 	Name    string `json:"name" validate:"required"` | ||||
|   | ||||
| @@ -6,12 +6,14 @@ type CronjobCreate struct { | ||||
| 	Name     string `json:"name" validate:"required"` | ||||
| 	Type     string `json:"type" 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"` | ||||
| 	Hour     int    `json:"hour" validate:"number"` | ||||
| 	Minute   int    `json:"minute" validate:"number"` | ||||
| 	Second   int    `json:"second" validate:"number"` | ||||
|  | ||||
| 	Script         string `json:"script"` | ||||
| 	ContainerName  string `json:"containerName"` | ||||
| 	Website        string `json:"website"` | ||||
| 	ExclusionRules string `json:"exclusionRules"` | ||||
| 	DBName         string `json:"dbName"` | ||||
| @@ -26,12 +28,14 @@ type CronjobUpdate struct { | ||||
| 	ID       uint   `json:"id" validate:"required"` | ||||
| 	Name     string `json:"name" 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"` | ||||
| 	Hour     int    `json:"hour" validate:"number"` | ||||
| 	Minute   int    `json:"minute" validate:"number"` | ||||
| 	Second   int    `json:"second" validate:"number"` | ||||
|  | ||||
| 	Script         string `json:"script"` | ||||
| 	ContainerName  string `json:"containerName"` | ||||
| 	Website        string `json:"website"` | ||||
| 	ExclusionRules string `json:"exclusionRules"` | ||||
| 	DBName         string `json:"dbName"` | ||||
| @@ -52,6 +56,16 @@ type CronjobDownload struct { | ||||
| 	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 { | ||||
| 	ID       uint   `json:"id"` | ||||
| 	Name     string `json:"name"` | ||||
| @@ -61,8 +75,10 @@ type CronjobInfo struct { | ||||
| 	Day      int    `json:"day"` | ||||
| 	Hour     int    `json:"hour"` | ||||
| 	Minute   int    `json:"minute"` | ||||
| 	Second   int    `json:"second"` | ||||
|  | ||||
| 	Script         string `json:"script"` | ||||
| 	ContainerName  string `json:"containerName"` | ||||
| 	Website        string `json:"website"` | ||||
| 	ExclusionRules string `json:"exclusionRules"` | ||||
| 	DBName         string `json:"dbName"` | ||||
| @@ -73,7 +89,7 @@ type CronjobInfo struct { | ||||
| 	TargetDirID    int    `json:"targetDirID"` | ||||
| 	RetainCopies   int    `json:"retainCopies"` | ||||
|  | ||||
| 	LastRecrodTime string `json:"lastRecrodTime"` | ||||
| 	LastRecordTime string `json:"lastRecordTime"` | ||||
| 	Status         string `json:"status"` | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -3,13 +3,6 @@ package dto | ||||
| import "time" | ||||
|  | ||||
| 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"` | ||||
| 	DatabaseNumber    int `json:"databaseNumber"` | ||||
| 	CronjobNumber     int `json:"cronjobNumber"` | ||||
| @@ -55,8 +48,21 @@ type DashboardCurrent struct { | ||||
| 	IOReadBytes  uint64 `json:"ioReadBytes"` | ||||
| 	IOWriteBytes uint64 `json:"ioWriteBytes"` | ||||
| 	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"` | ||||
| 	Free        uint64  `json:"free"` | ||||
| 	Used        uint64  `json:"used"` | ||||
| @@ -66,9 +72,4 @@ type DashboardCurrent struct { | ||||
| 	InodesUsed        uint64  `json:"inodesUsed"` | ||||
| 	InodesFree        uint64  `json:"inodesFree"` | ||||
| 	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 { | ||||
| 	IsSwarm      bool     `json:"isSwarm"` | ||||
| 	Status       string   `json:"status"` | ||||
| 	Version      string   `json:"version"` | ||||
| 	Mirrors      []string `json:"registryMirrors"` | ||||
| 	Registries   []string `json:"insecureRegistries"` | ||||
| 	LiveRestore  bool     `json:"liveRestore"` | ||||
| 	IPTables     bool     `json:"iptables"` | ||||
| 	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 { | ||||
| 	StopSocket  bool   `json:"stopSocket"` | ||||
| 	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"` | ||||
| } | ||||
| @@ -12,8 +12,10 @@ type HostOperate struct { | ||||
| 	Port             uint   `json:"port" validate:"required,number,max=65535,min=1"` | ||||
| 	User             string `json:"user" validate:"required"` | ||||
| 	AuthMode         string `json:"authMode" validate:"oneof=password key"` | ||||
| 	PrivateKey string `json:"privateKey"` | ||||
| 	Password         string `json:"password"` | ||||
| 	PrivateKey       string `json:"privateKey"` | ||||
| 	PassPhrase       string `json:"passPhrase"` | ||||
| 	RememberPassword bool   `json:"rememberPassword"` | ||||
|  | ||||
| 	Description string `json:"description"` | ||||
| } | ||||
| @@ -23,8 +25,9 @@ type HostConnTest struct { | ||||
| 	Port       uint   `json:"port" validate:"required,number,max=65535,min=1"` | ||||
| 	User       string `json:"user" validate:"required"` | ||||
| 	AuthMode   string `json:"authMode" validate:"oneof=password key"` | ||||
| 	PrivateKey string `json:"privateKey"` | ||||
| 	Password   string `json:"password"` | ||||
| 	PrivateKey string `json:"privateKey"` | ||||
| 	PassPhrase string `json:"passPhrase"` | ||||
| } | ||||
|  | ||||
| type SearchHostWithPage struct { | ||||
| @@ -52,6 +55,10 @@ type HostInfo struct { | ||||
| 	Port             uint      `json:"port"` | ||||
| 	User             string    `json:"user"` | ||||
| 	AuthMode         string    `json:"authMode"` | ||||
| 	Password         string    `json:"password"` | ||||
| 	PrivateKey       string    `json:"privateKey"` | ||||
| 	PassPhrase       string    `json:"passPhrase"` | ||||
| 	RememberPassword bool      `json:"rememberPassword"` | ||||
|  | ||||
| 	Description string `json:"description"` | ||||
| } | ||||
|   | ||||
| @@ -10,35 +10,35 @@ type ImageInfo struct { | ||||
| } | ||||
|  | ||||
| type ImageLoad struct { | ||||
| 	Path string `josn:"path" validate:"required"` | ||||
| 	Path string `json:"path" validate:"required"` | ||||
| } | ||||
|  | ||||
| type ImageBuild struct { | ||||
| 	From       string   `josn:"from" validate:"required"` | ||||
| 	From       string   `json:"from" validate:"required"` | ||||
| 	Name       string   `json:"name" validate:"required"` | ||||
| 	Dockerfile string   `josn:"dockerfile" validate:"required"` | ||||
| 	Tags       []string `josn:"tags"` | ||||
| 	Dockerfile string   `json:"dockerfile" validate:"required"` | ||||
| 	Tags       []string `json:"tags"` | ||||
| } | ||||
|  | ||||
| type ImagePull struct { | ||||
| 	RepoID    uint   `josn:"repoID"` | ||||
| 	ImageName string `josn:"imageName" validate:"required"` | ||||
| 	RepoID    uint   `json:"repoID"` | ||||
| 	ImageName string `json:"imageName" validate:"required"` | ||||
| } | ||||
|  | ||||
| type ImageTag struct { | ||||
| 	RepoID     uint   `josn:"repoID"` | ||||
| 	RepoID     uint   `json:"repoID"` | ||||
| 	SourceID   string `json:"sourceID" validate:"required"` | ||||
| 	TargetName string `josn:"targetName" validate:"required"` | ||||
| 	TargetName string `json:"targetName" validate:"required"` | ||||
| } | ||||
|  | ||||
| type ImagePush struct { | ||||
| 	RepoID  uint   `josn:"repoID" validate:"required"` | ||||
| 	RepoID  uint   `json:"repoID" validate:"required"` | ||||
| 	TagName string `json:"tagName" validate:"required"` | ||||
| 	Name    string `json:"name" validate:"required"` | ||||
| } | ||||
|  | ||||
| type ImageSave struct { | ||||
| 	TagName string `json:"tagName" validate:"required"` | ||||
| 	Path    string `josn:"path" validate:"required"` | ||||
| 	Path    string `json:"path" validate:"required"` | ||||
| 	Name    string `json:"name" validate:"required"` | ||||
| } | ||||
|   | ||||
| @@ -28,13 +28,20 @@ type NginxParam struct { | ||||
| 	Params      []string | ||||
| } | ||||
|  | ||||
| type NginxAuth struct { | ||||
| 	Username string `json:"username"` | ||||
| 	Remark   string `json:"remark"` | ||||
| } | ||||
|  | ||||
| type NginxKey string | ||||
|  | ||||
| const ( | ||||
| 	Index      NginxKey = "index" | ||||
| 	LimitConn  NginxKey = "limit-conn" | ||||
| 	SSL        NginxKey = "ssl" | ||||
| 	CACHE      NginxKey = "cache" | ||||
| 	HttpPer    NginxKey = "http-per" | ||||
| 	ProxyCache NginxKey = "proxy-cache" | ||||
| ) | ||||
|  | ||||
| var ScopeKeyMap = map[NginxKey][]string{ | ||||
| @@ -47,4 +54,6 @@ var ScopeKeyMap = map[NginxKey][]string{ | ||||
| var StaticFileKeyMap = map[NginxKey]struct { | ||||
| }{ | ||||
| 	SSL:        {}, | ||||
| 	CACHE:      {}, | ||||
| 	ProxyCache: {}, | ||||
| } | ||||
|   | ||||
| @@ -18,6 +18,18 @@ type AppInstallCreate struct { | ||||
| 	Params      map[string]interface{} `json:"params"` | ||||
| 	Name        string                 `json:"name" validate:"required"` | ||||
| 	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 { | ||||
| @@ -51,6 +63,12 @@ type AppInstalledOperate struct { | ||||
| type AppInstalledUpdate struct { | ||||
| 	InstallId uint                   `json:"installId" 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 { | ||||
|   | ||||
| @@ -22,6 +22,7 @@ type FileCreate struct { | ||||
| 	IsLink    bool   `json:"isLink"` | ||||
| 	IsSymlink bool   `json:"isSymlink"` | ||||
| 	LinkPath  string `json:"linkPath"` | ||||
| 	Sub       bool   `json:"sub"` | ||||
| } | ||||
|  | ||||
| type FileDelete struct { | ||||
| @@ -81,6 +82,11 @@ type FileDownload struct { | ||||
| 	Compress bool     `json:"compress" validate:"required"` | ||||
| } | ||||
|  | ||||
| type FileChunkDownload struct { | ||||
| 	Path string `json:"path" validate:"required"` | ||||
| 	Name string `json:"name" validate:"required"` | ||||
| } | ||||
|  | ||||
| type DirSizeReq struct { | ||||
| 	Path string `json:"path" validate:"required"` | ||||
| } | ||||
| @@ -88,3 +94,10 @@ type DirSizeReq struct { | ||||
| type FileProcessReq struct { | ||||
| 	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"` | ||||
| 	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 { | ||||
| 	dto.PageInfo | ||||
| 	Name           string `json:"name"` | ||||
| 	OrderBy        string `json:"orderBy"` | ||||
| 	Order          string `json:"order"` | ||||
| 	WebsiteGroupID uint   `json:"websiteGroupId"` | ||||
| } | ||||
|  | ||||
| @@ -18,17 +20,28 @@ type WebsiteCreate struct { | ||||
| 	OtherDomains   string `json:"otherDomains"` | ||||
| 	Proxy          string `json:"proxy"` | ||||
| 	WebsiteGroupID uint   `json:"webSiteGroupID" validate:"required"` | ||||
| 	IPV6           bool   `json:"IPV6"` | ||||
|  | ||||
| 	AppType      string        `json:"appType" validate:"oneof=new installed"` | ||||
| 	AppInstall   NewAppInstall `json:"appInstall"` | ||||
| 	AppID        uint          `json:"appID"` | ||||
| 	AppInstallID uint          `json:"appInstallID"` | ||||
|  | ||||
| 	RuntimeID uint `json:"runtimeID"` | ||||
| 	RuntimeConfig | ||||
| } | ||||
|  | ||||
| type RuntimeConfig struct { | ||||
| 	ProxyType string `json:"proxyType"` | ||||
| 	Port      int    `json:"port"` | ||||
| } | ||||
|  | ||||
| type NewAppInstall struct { | ||||
| 	Name        string                 `json:"name"` | ||||
| 	AppDetailId uint                   `json:"appDetailID"` | ||||
| 	Params      map[string]interface{} `json:"params"` | ||||
|  | ||||
| 	AppContainerConfig | ||||
| } | ||||
|  | ||||
| type WebsiteInstallCheckReq struct { | ||||
| @@ -41,6 +54,7 @@ type WebsiteUpdate struct { | ||||
| 	Remark         string `json:"remark"` | ||||
| 	WebsiteGroupID uint   `json:"webSiteGroupID" validate:"required"` | ||||
| 	ExpireDate     string `json:"expireDate"` | ||||
| 	IPV6           bool   `json:"IPV6"` | ||||
| } | ||||
|  | ||||
| type WebsiteDelete struct { | ||||
| @@ -107,7 +121,10 @@ type WebsiteHTTPSOp struct { | ||||
| 	Type            string   `json:"type"  validate:"oneof=existed auto manual"` | ||||
| 	PrivateKey      string   `json:"privateKey"` | ||||
| 	Certificate     string   `json:"certificate"` | ||||
| 	HttpConfig   string   `json:"HttpConfig"  validate:"oneof=HTTPSOnly HTTPAlso HTTPToHTTPS"` | ||||
| 	PrivateKeyPath  string   `json:"privateKeyPath"` | ||||
| 	CertificatePath string   `json:"certificatePath"` | ||||
| 	ImportType      string   `json:"importType"` | ||||
| 	HttpConfig      string   `json:"httpConfig"  validate:"oneof=HTTPSOnly HTTPAlso HTTPToHTTPS"` | ||||
| 	SSLProtocol     []string `json:"SSLProtocol"` | ||||
| 	Algorithm       string   `json:"algorithm"` | ||||
| } | ||||
| @@ -126,3 +143,49 @@ type WebsiteLogReq struct { | ||||
| type WebsiteDefaultUpdate struct { | ||||
| 	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 { | ||||
| 	dto.PageInfo | ||||
| 	AcmeAccountID string `json:"acmeAccountID"` | ||||
| } | ||||
|  | ||||
| type WebsiteSSLCreate struct { | ||||
|   | ||||
| @@ -1,8 +1,10 @@ | ||||
| package response | ||||
|  | ||||
| import ( | ||||
| 	"github.com/1Panel-dev/1Panel/backend/app/model" | ||||
| 	"github.com/1Panel-dev/1Panel/backend/app/dto/request" | ||||
| 	"time" | ||||
|  | ||||
| 	"github.com/1Panel-dev/1Panel/backend/app/model" | ||||
| ) | ||||
|  | ||||
| type AppRes struct { | ||||
| @@ -11,13 +13,13 @@ type AppRes struct { | ||||
| } | ||||
|  | ||||
| type AppUpdateRes struct { | ||||
| 	Version      string `json:"version"` | ||||
| 	CanUpdate            bool `json:"canUpdate"` | ||||
| 	DownloadPath string `json:"downloadPath"` | ||||
| 	AppStoreLastModified int  `json:"appStoreLastModified"` | ||||
| } | ||||
|  | ||||
| type AppDTO struct { | ||||
| 	model.App | ||||
| 	Installed bool        `json:"installed"` | ||||
| 	Versions  []string    `json:"versions"` | ||||
| 	Tags      []model.Tag `json:"tags"` | ||||
| } | ||||
| @@ -43,6 +45,14 @@ type AppDetailDTO struct { | ||||
| 	model.AppDetail | ||||
| 	Enable bool        `json:"enable"` | ||||
| 	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 { | ||||
| @@ -52,6 +62,13 @@ type AppInstalledDTO struct { | ||||
| 	AppName   string `json:"appName"` | ||||
| 	Icon      string `json:"icon"` | ||||
| 	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 { | ||||
| @@ -70,4 +87,11 @@ type AppParam struct { | ||||
| 	Type      string      `json:"type"` | ||||
| 	Values    interface{} `json:"values"` | ||||
| 	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 | ||||
|  | ||||
| import "github.com/1Panel-dev/1Panel/backend/app/dto" | ||||
|  | ||||
| type NginxStatus struct { | ||||
| 	Active   string `json:"active"` | ||||
| 	Accepts  string `json:"accepts"` | ||||
| @@ -14,3 +16,21 @@ type NginxParam struct { | ||||
| 	Name   string   `json:"name"` | ||||
| 	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"` | ||||
| 	SitePath      string `json:"sitePath"` | ||||
| 	AppName       string `json:"appName"` | ||||
| 	RuntimeName   string `json:"runtimeName"` | ||||
| } | ||||
|  | ||||
| type WebsitePreInstallCheck struct { | ||||
| @@ -42,3 +43,13 @@ type WebsiteLog struct { | ||||
| 	Enable  bool   `json:"enable"` | ||||
| 	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 { | ||||
| 	UserName      string `json:"userName"` | ||||
| 	Email         string `json:"email"` | ||||
| 	SystemIP      string `json:"systemIP"` | ||||
| 	SystemVersion string `json:"systemVersion"` | ||||
|  | ||||
| 	SessionTimeout string `json:"sessionTimeout"` | ||||
| 	LocalTime      string `json:"localTime"` | ||||
| 	TimeZone       string `json:"timeZone"` | ||||
| 	NtpSite        string `json:"ntpSite"` | ||||
|  | ||||
| 	Port      string `json:"port"` | ||||
| 	PanelName string `json:"panelName"` | ||||
| @@ -16,14 +19,20 @@ type SettingInfo struct { | ||||
| 	Language  string `json:"language"` | ||||
|  | ||||
| 	ServerPort             string `json:"serverPort"` | ||||
| 	SSL                    string `json:"ssl"` | ||||
| 	SSLType                string `json:"sslType"` | ||||
| 	BindDomain             string `json:"bindDomain"` | ||||
| 	AllowIPs               string `json:"allowIPs"` | ||||
| 	SecurityEntrance       string `json:"securityEntrance"` | ||||
| 	ExpirationDays         string `json:"expirationDays"` | ||||
| 	ExpirationTime         string `json:"expirationTime"` | ||||
| 	ComplexityVerification string `json:"complexityVerification"` | ||||
| 	MFAStatus              string `json:"mfaStatus"` | ||||
| 	MFASecret              string `json:"mfaSecret"` | ||||
| 	MFAInterval            string `json:"mfaInterval"` | ||||
|  | ||||
| 	MonitorStatus    string `json:"monitorStatus"` | ||||
| 	MonitorInterval  string `json:"monitorInterval"` | ||||
| 	MonitorStoreDays string `json:"monitorStoreDays"` | ||||
|  | ||||
| 	MessageType string `json:"messageType"` | ||||
| @@ -32,6 +41,7 @@ type SettingInfo struct { | ||||
| 	DingVars    string `json:"dingVars"` | ||||
|  | ||||
| 	AppStoreVersion      string `json:"appStoreVersion"` | ||||
| 	AppStoreLastModified string `json:"appStoreLastModified"` | ||||
| } | ||||
|  | ||||
| type SettingUpdate struct { | ||||
| @@ -39,6 +49,23 @@ type SettingUpdate struct { | ||||
| 	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 { | ||||
| 	OldPassword string `json:"oldPassword" validate:"required"` | ||||
| 	NewPassword string `json:"newPassword" validate:"required"` | ||||
| @@ -49,8 +76,8 @@ type PortUpdate struct { | ||||
| } | ||||
|  | ||||
| type SnapshotCreate struct { | ||||
| 	From        string `json:"from" validate:"required,oneof=OSS S3 SFTP MINIO"` | ||||
| 	Description string `json:"description"` | ||||
| 	From        string `json:"from" validate:"required,oneof=OSS S3 SFTP MINIO COS KODO OneDrive"` | ||||
| 	Description string `json:"description" validate:"max=256"` | ||||
| } | ||||
| type SnapshotRecover struct { | ||||
| 	IsNew      bool `json:"isNew"` | ||||
| @@ -60,12 +87,12 @@ type SnapshotRecover struct { | ||||
| type SnapshotImport struct { | ||||
| 	From        string   `json:"from"` | ||||
| 	Names       []string `json:"names"` | ||||
| 	Description string   `json:"description"` | ||||
| 	Description string   `json:"description" validate:"max=256"` | ||||
| } | ||||
| type SnapshotInfo struct { | ||||
| 	ID          uint      `json:"id"` | ||||
| 	Name        string    `json:"name"` | ||||
| 	Description string    `json:"description"` | ||||
| 	Description string    `json:"description" validate:"max=256"` | ||||
| 	From        string    `json:"from"` | ||||
| 	Status      string    `json:"status"` | ||||
| 	Message     string    `json:"message"` | ||||
| @@ -87,6 +114,10 @@ type UpgradeInfo struct { | ||||
| 	ReleaseNote   string `json:"releaseNote"` | ||||
| } | ||||
|  | ||||
| type SyncTime struct { | ||||
| 	NtpSite string `json:"ntpSite"` | ||||
| } | ||||
|  | ||||
| type Upgrade struct { | ||||
| 	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"` | ||||
| } | ||||
| @@ -3,20 +3,24 @@ package model | ||||
| type App struct { | ||||
| 	BaseModel | ||||
| 	Name               string `json:"name" gorm:"type:varchar(64);not null"` | ||||
| 	Key                string      `json:"key" gorm:"type:varchar(64);not null;uniqueIndex"` | ||||
| 	ShortDescZh        string      `json:"shortDescZh" gorm:"type:longtext;"` | ||||
| 	ShortDescEn        string      `json:"shortDescEn" gorm:"type:longtext;"` | ||||
| 	Key                string `json:"key" gorm:"type:varchar(64);not null;"` | ||||
| 	ShortDescZh        string `json:"shortDescZh" yaml:"shortDescZh" gorm:"type:longtext;"` | ||||
| 	ShortDescEn        string `json:"shortDescEn" yaml:"shortDescEn" gorm:"type:longtext;"` | ||||
| 	Icon               string `json:"icon" gorm:"type:longtext;"` | ||||
| 	Type               string `json:"type" 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"` | ||||
| 	Limit              int    `json:"limit" gorm:"type:Integer;not null"` | ||||
| 	Website            string `json:"website" 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"` | ||||
| 	Recommend          int    `json:"recommend" gorm:"type:Integer;not null"` | ||||
| 	Resource           string `json:"resource" gorm:"type:varchar;not null;default:remote"` | ||||
| 	ReadMe             string `json:"readMe" gorm:"type:varchar;"` | ||||
| 	LastModified       int    `json:"lastModified" gorm:"type:Integer;"` | ||||
|  | ||||
| 	Details []AppDetail `json:"-" gorm:"-:migration"` | ||||
| 	TagsKey            []string    `json:"-" gorm:"-"` | ||||
| 	TagsKey []string    `json:"tags" yaml:"tags" gorm:"-"` | ||||
| 	AppTags []AppTag    `json:"-" gorm:"-:migration"` | ||||
| } | ||||
|   | ||||
| @@ -5,8 +5,12 @@ type AppDetail struct { | ||||
| 	AppId               uint   `json:"appId" gorm:"type:integer;not null"` | ||||
| 	Version             string `json:"version" gorm:"type:varchar(64);not null"` | ||||
| 	Params              string `json:"-" gorm:"type:longtext;"` | ||||
| 	DockerCompose string `json:"-"  gorm:"type:longtext;not null"` | ||||
| 	Readme        string `json:"readme"  gorm:"type:longtext;"` | ||||
| 	DockerCompose       string `json:"dockerCompose"  gorm:"type:longtext;"` | ||||
| 	Status              string `json:"status" gorm:"type:varchar(64);not null"` | ||||
| 	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 ( | ||||
| 	"path" | ||||
| 	"strings" | ||||
|  | ||||
| 	"github.com/1Panel-dev/1Panel/backend/constant" | ||||
| ) | ||||
| @@ -26,9 +27,21 @@ type AppInstall struct { | ||||
| } | ||||
|  | ||||
| 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 { | ||||
| 	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"` | ||||
| 	AccessKey  string `gorm:"type:varchar(256)" json:"accessKey"` | ||||
| 	Credential string `gorm:"type:varchar(256)" json:"credential"` | ||||
| 	BackupPath string `gorm:"type:varchar(256)" json:"backupPath"` | ||||
| 	Vars       string `gorm:"type:longText" json:"vars"` | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -13,7 +13,9 @@ type Cronjob struct { | ||||
| 	Day      uint64 `gorm:"type:decimal" json:"day"` | ||||
| 	Hour     uint64 `gorm:"type:decimal" json:"hour"` | ||||
| 	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"` | ||||
| 	Website        string `gorm:"type:varchar(64)" json:"website"` | ||||
| 	DBName         string `gorm:"type:varchar(64)" json:"dbName"` | ||||
|   | ||||
| @@ -2,6 +2,7 @@ package model | ||||
|  | ||||
| type Host struct { | ||||
| 	BaseModel | ||||
|  | ||||
| 	GroupID          uint   `gorm:"type:decimal;not null" json:"group_id"` | ||||
| 	Name             string `gorm:"type:varchar(64);not null" json:"name"` | ||||
| 	Addr             string `gorm:"type:varchar(16);not null" json:"addr"` | ||||
| @@ -10,6 +11,8 @@ type Host struct { | ||||
| 	AuthMode         string `gorm:"type:varchar(16);not null" json:"authMode"` | ||||
| 	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"` | ||||
| } | ||||
|   | ||||
							
								
								
									
										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,21 +4,32 @@ import "time" | ||||
|  | ||||
| type Website struct { | ||||
| 	BaseModel | ||||
| 	Protocol       string          `gorm:"type:varchar(64);not null" json:"protocol"` | ||||
| 	PrimaryDomain  string          `gorm:"type:varchar(128);not null" json:"primaryDomain"` | ||||
| 	Type           string          `gorm:"type:varchar(64);not null" json:"type"` | ||||
| 	Alias          string          `gorm:"type:varchar(128);not null" json:"alias"` | ||||
| 	Protocol      string    `gorm:"type:varchar;not null" json:"protocol"` | ||||
| 	PrimaryDomain string    `gorm:"type:varchar;not null" json:"primaryDomain"` | ||||
| 	Type          string    `gorm:"type:varchar;not null" json:"type"` | ||||
| 	Alias         string    `gorm:"type:varchar;not null" json:"alias"` | ||||
| 	Remark        string    `gorm:"type:longtext;" json:"remark"` | ||||
| 	Status         string          `gorm:"type:varchar(64);not null" json:"status"` | ||||
| 	HttpConfig     string          `gorm:"type:varchar(64);not null" json:"httpConfig"` | ||||
| 	Status        string    `gorm:"type:varchar;not null" json:"status"` | ||||
| 	HttpConfig    string    `gorm:"type:varchar;not null" json:"httpConfig"` | ||||
| 	ExpireDate    time.Time `json:"expireDate"` | ||||
| 	AppInstallID   uint            `gorm:"type:integer" json:"appInstallId"` | ||||
| 	WebsiteGroupID uint            `gorm:"type:integer" json:"webSiteGroupId"` | ||||
| 	WebsiteSSLID   uint            `gorm:"type:integer" json:"webSiteSSLId"` | ||||
| 	Proxy          string          `gorm:"type:varchar(128);not null" json:"proxy"` | ||||
|  | ||||
| 	Proxy         string `gorm:"type:varchar;" json:"proxy"` | ||||
| 	ProxyType     string `gorm:"type:varchar;" json:"proxyType"` | ||||
| 	SiteDir       string `gorm:"type:varchar;" json:"siteDir"` | ||||
| 	ErrorLog      bool   `json:"errorLog"` | ||||
| 	AccessLog     bool   `json:"accessLog"` | ||||
| 	DefaultServer bool   `json:"defaultServer"` | ||||
| 	IPV6          bool   `json:"IPV6"` | ||||
| 	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"` | ||||
| } | ||||
|   | ||||
| @@ -11,6 +11,26 @@ import ( | ||||
| 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 { | ||||
| 	return func(db *gorm.DB) *gorm.DB { | ||||
| 		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) { | ||||
| 	var apps []model.App | ||||
| 	db := getDb(opts...).Model(&model.App{}) | ||||
| 	count := int64(0) | ||||
| 	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 | ||||
| } | ||||
|  | ||||
| @@ -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 { | ||||
| 	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" | ||||
| 	"github.com/1Panel-dev/1Panel/backend/app/model" | ||||
| 	"gorm.io/gorm" | ||||
| 	"gorm.io/gorm/clause" | ||||
| ) | ||||
|  | ||||
| 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 { | ||||
| 	return func(g *gorm.DB) *gorm.DB { | ||||
| 		return g.Where("version = ?", version) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (a AppDetailRepo) WithAppId(id uint) DBOption { | ||||
| 	return func(g *gorm.DB) *gorm.DB { | ||||
| 		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) { | ||||
| 	var detail model.AppDetail | ||||
| 	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 | ||||
| } | ||||
|  | ||||
| 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 ( | ||||
| 	"context" | ||||
| 	"encoding/json" | ||||
| 	"gorm.io/gorm/clause" | ||||
|  | ||||
| 	"github.com/1Panel-dev/1Panel/backend/app/model" | ||||
| 	"github.com/1Panel-dev/1Panel/backend/global" | ||||
| @@ -11,6 +12,33 @@ import ( | ||||
|  | ||||
| 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 { | ||||
| 	return func(g *gorm.DB) *gorm.DB { | ||||
| 		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 { | ||||
| 	return func(g *gorm.DB) *gorm.DB { | ||||
| 		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 { | ||||
| 	return func(db *gorm.DB) *gorm.DB { | ||||
| 		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 | ||||
| } | ||||
|  | ||||
| func (a *AppInstallRepo) Create(ctx context.Context, install *model.AppInstall) error { | ||||
| 	db := getTx(ctx).Model(&model.AppInstall{}) | ||||
| 	return db.Create(&install).Error | ||||
| func (a *AppInstallRepo) GetFirstByCtx(ctx context.Context, opts ...DBOption) (model.AppInstall, error) { | ||||
| 	var install model.AppInstall | ||||
| 	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 { | ||||
| 	return getDb().Save(&install).Error | ||||
| func (a *AppInstallRepo) Create(ctx context.Context, install *model.AppInstall) 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 { | ||||
| @@ -112,9 +159,11 @@ type RootInfo struct { | ||||
| 	ID            uint   `json:"id"` | ||||
| 	Name          string `json:"name"` | ||||
| 	Port          int64  `json:"port"` | ||||
| 	HttpsPort     int64  `json:"httpsPort"` | ||||
| 	Password      string `json:"password"` | ||||
| 	UserPassword  string `json:"userPassword"` | ||||
| 	ContainerName string `json:"containerName"` | ||||
| 	ServiceName   string `json:"serviceName"` | ||||
| 	Param         string `json:"param"` | ||||
| 	Env           string `json:"env"` | ||||
| 	Key           string `json:"key"` | ||||
| @@ -152,7 +201,9 @@ func (a *AppInstallRepo) LoadBaseInfo(key string, name string) (*RootInfo, error | ||||
| 		info.UserPassword = userPassword | ||||
| 	} | ||||
| 	info.Port = int64(appInstall.HttpPort) | ||||
| 	info.HttpsPort = int64(appInstall.HttpsPort) | ||||
| 	info.ID = appInstall.ID | ||||
| 	info.ServiceName = appInstall.ServiceName | ||||
| 	info.ContainerName = appInstall.ContainerName | ||||
| 	info.Name = appInstall.Name | ||||
| 	info.Env = appInstall.Env | ||||
|   | ||||
| @@ -11,6 +11,21 @@ import ( | ||||
| 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 { | ||||
| 	return func(db *gorm.DB) *gorm.DB { | ||||
| 		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 { | ||||
| 	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 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 { | ||||
| 	return getTx(ctx).Create(&tags).Error | ||||
| } | ||||
|   | ||||
| @@ -20,6 +20,8 @@ type IBackupRepo interface { | ||||
| 	Delete(opts ...DBOption) error | ||||
| 	DeleteRecord(ctx context.Context, opts ...DBOption) error | ||||
| 	WithByDetailName(detailName string) DBOption | ||||
| 	WithByFileName(fileName string) DBOption | ||||
| 	WithByType(backupType string) DBOption | ||||
| } | ||||
|  | ||||
| func NewIBackupRepo() IBackupRepo { | ||||
|   | ||||
| @@ -15,6 +15,7 @@ type ICommandRepo interface { | ||||
| 	Create(command *model.Command) error | ||||
| 	Update(id uint, vars map[string]interface{}) error | ||||
| 	Delete(opts ...DBOption) error | ||||
| 	Get(opts ...DBOption) (model.Command, error) | ||||
| } | ||||
|  | ||||
| func NewICommandRepo() ICommandRepo { | ||||
|   | ||||
| @@ -2,6 +2,7 @@ package repo | ||||
|  | ||||
| import ( | ||||
| 	"context" | ||||
| 	"fmt" | ||||
| 	"time" | ||||
|  | ||||
| 	"github.com/1Panel-dev/1Panel/backend/constant" | ||||
| @@ -16,15 +17,21 @@ type ICommonRepo interface { | ||||
| 	WithByName(name string) DBOption | ||||
| 	WithByType(tp string) DBOption | ||||
| 	WithOrderBy(orderStr string) DBOption | ||||
| 	WithOrderRuleBy(orderBy, order string) DBOption | ||||
| 	WithByGroupID(groupID uint) DBOption | ||||
| 	WithLikeName(name string) DBOption | ||||
| 	WithIdsIn(ids []uint) DBOption | ||||
| 	WithByDate(startTime, endTime time.Time) DBOption | ||||
| 	WithByStartDate(startTime time.Time) DBOption | ||||
| 	WithByStatus(status string) DBOption | ||||
| } | ||||
|  | ||||
| type CommonRepo struct{} | ||||
|  | ||||
| func NewCommonRepo() ICommonRepo { | ||||
| 	return &CommonRepo{} | ||||
| } | ||||
|  | ||||
| func (c *CommonRepo) WithByID(id uint) DBOption { | ||||
| 	return func(g *gorm.DB) *gorm.DB { | ||||
| 		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 { | ||||
| 	return func(g *gorm.DB) *gorm.DB { | ||||
| 		return g.Where("id in (?)", ids) | ||||
|   | ||||
| @@ -15,8 +15,10 @@ type IComposeTemplateRepo interface { | ||||
| 	Update(id uint, vars map[string]interface{}) error | ||||
| 	Delete(opts ...DBOption) error | ||||
|  | ||||
| 	GetRecord(opts ...DBOption) (model.Compose, error) | ||||
| 	CreateRecord(compose *model.Compose) error | ||||
| 	DeleteRecord(opts ...DBOption) error | ||||
| 	ListRecord() ([]model.Compose, error) | ||||
| } | ||||
|  | ||||
| func NewIComposeTemplateRepo() IComposeTemplateRepo { | ||||
| @@ -71,6 +73,16 @@ func (u *ComposeTemplateRepo) Delete(opts ...DBOption) 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) { | ||||
| 	var composes []model.Compose | ||||
| 	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) | ||||
| 	Create(cronjob *model.Cronjob) error | ||||
| 	WithByJobID(id int) DBOption | ||||
| 	WithByBackupID(id uint) DBOption | ||||
| 	WithByRecordDropID(id int) DBOption | ||||
| 	Save(id uint, cronjob model.Cronjob) error | ||||
| 	Update(id uint, vars map[string]interface{}) error | ||||
| 	Delete(opts ...DBOption) error | ||||
| 	DeleteRecord(opts ...DBOption) error | ||||
| 	StartRecords(cronjobID uint, fromLocal bool, targetPath string) model.JobRecords | ||||
| 	EndRecords(record model.JobRecords, status, message, records string) | ||||
| 	PageRecords(page, size int, opts ...DBOption) (int64, []model.JobRecords, error) | ||||
| } | ||||
|  | ||||
| func NewICronjobRepo() ICronjobRepo { | ||||
| @@ -80,7 +83,7 @@ func (u *CronjobRepo) Page(page, size int, opts ...DBOption) (int64, []model.Cro | ||||
| 	} | ||||
| 	count := int64(0) | ||||
| 	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 | ||||
| } | ||||
|  | ||||
| @@ -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 { | ||||
| 	var record model.JobRecords | ||||
| 	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{} | ||||
| } | ||||
|  | ||||
| func (u *HostRepo) Get(opts ...DBOption) (model.Host, error) { | ||||
| func (h *HostRepo) Get(opts ...DBOption) (model.Host, error) { | ||||
| 	var host model.Host | ||||
| 	db := global.DB | ||||
| 	for _, opt := range opts { | ||||
| @@ -35,7 +35,7 @@ func (u *HostRepo) Get(opts ...DBOption) (model.Host, error) { | ||||
| 	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 | ||||
| 	db := global.DB.Model(&model.Host{}) | ||||
| 	for _, opt := range opts { | ||||
| @@ -45,7 +45,7 @@ func (u *HostRepo) GetList(opts ...DBOption) ([]model.Host, error) { | ||||
| 	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 | ||||
| 	db := global.DB.Model(&model.Host{}) | ||||
| 	for _, opt := range opts { | ||||
| @@ -57,7 +57,7 @@ func (u *HostRepo) Page(page, size int, opts ...DBOption) (int64, []model.Host, | ||||
| 	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 { | ||||
| 		if len(info) == 0 { | ||||
| 			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 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 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 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 { | ||||
| 		if len(group) == 0 { | ||||
| 			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 | ||||
| } | ||||
|  | ||||
| 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 | ||||
| } | ||||
|  | ||||
| func (u *HostRepo) Delete(opts ...DBOption) error { | ||||
| func (h *HostRepo) Delete(opts ...DBOption) error { | ||||
| 	db := global.DB | ||||
| 	for _, opt := range opts { | ||||
| 		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 | ||||
|  | ||||
| import ( | ||||
| 	"time" | ||||
|  | ||||
| 	"github.com/1Panel-dev/1Panel/backend/app/model" | ||||
| 	"github.com/1Panel-dev/1Panel/backend/global" | ||||
| 	"gorm.io/gorm" | ||||
| @@ -14,6 +16,13 @@ type ISettingRepo interface { | ||||
| 	Create(key, value string) error | ||||
| 	Update(key, value string) error | ||||
| 	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 { | ||||
| @@ -57,3 +66,22 @@ func (c *SettingRepo) WithByKey(key string) DBOption { | ||||
| func (u *SettingRepo) Update(key, value string) 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 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 { | ||||
| 	return getTx(ctx).Create(&tags).Error | ||||
| } | ||||
|   | ||||
| @@ -17,6 +17,7 @@ type IWebsiteRepo interface { | ||||
| 	WithGroupID(groupId uint) DBOption | ||||
| 	WithDefaultServer() DBOption | ||||
| 	WithDomainLike(domain string) DBOption | ||||
| 	WithRuntimeID(runtimeID uint) DBOption | ||||
| 	Page(page, size int, opts ...DBOption) (int64, []model.Website, error) | ||||
| 	List(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 { | ||||
| 	return func(db *gorm.DB) *gorm.DB { | ||||
| 		return db.Where("primary_domain = ?", domain) | ||||
|   | ||||
| @@ -7,6 +7,19 @@ import ( | ||||
| 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) { | ||||
| 	var accounts []model.WebsiteDnsAccount | ||||
| 	db := getDb(opts...).Model(&model.WebsiteDnsAccount{}) | ||||
|   | ||||
| @@ -10,6 +10,23 @@ import ( | ||||
| 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 { | ||||
| 	return func(db *gorm.DB) *gorm.DB { | ||||
| 		return db.Where("website_id = ?", websiteId) | ||||
|   | ||||
| @@ -5,24 +5,26 @@ import ( | ||||
| 	"encoding/base64" | ||||
| 	"encoding/json" | ||||
| 	"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/request" | ||||
| 	"github.com/1Panel-dev/1Panel/backend/app/dto/response" | ||||
| 	"github.com/1Panel-dev/1Panel/backend/app/model" | ||||
| 	"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/global" | ||||
| 	"github.com/1Panel-dev/1Panel/backend/i18n" | ||||
| 	"github.com/1Panel-dev/1Panel/backend/utils/common" | ||||
| 	"github.com/1Panel-dev/1Panel/backend/utils/docker" | ||||
| 	"github.com/1Panel-dev/1Panel/backend/utils/files" | ||||
| 	http2 "github.com/1Panel-dev/1Panel/backend/utils/http" | ||||
| 	"gopkg.in/yaml.v3" | ||||
| 	"io" | ||||
| 	"net/http" | ||||
| 	"os" | ||||
| 	"path" | ||||
| 	"strconv" | ||||
| 	"strings" | ||||
| ) | ||||
|  | ||||
| type AppService struct { | ||||
| @@ -32,10 +34,13 @@ type IAppService interface { | ||||
| 	PageApp(req request.AppSearch) (interface{}, error) | ||||
| 	GetAppTags() ([]response.TagDTO, 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) | ||||
| 	SyncAppList() error | ||||
| 	SyncAppListFromRemote() error | ||||
| 	GetAppUpdate() (*response.AppUpdateRes, error) | ||||
| 	GetAppDetailByID(id uint) (*response.AppDetailDTO, error) | ||||
| 	SyncAppListFromLocal() | ||||
| 	GetIgnoredApp() ([]response.IgnoredApp, error) | ||||
| } | ||||
|  | ||||
| func NewIAppService() IAppService { | ||||
| @@ -79,12 +84,16 @@ func (a AppService) PageApp(req request.AppSearch) (interface{}, error) { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	var appDTOs []*response.AppDTO | ||||
| 	for _, a := range apps { | ||||
| 	for _, ap := range apps { | ||||
| 		ap.ReadMe = "" | ||||
| 		ap.Website = "" | ||||
| 		ap.Document = "" | ||||
| 		ap.Github = "" | ||||
| 		appDTO := &response.AppDTO{ | ||||
| 			App: a, | ||||
| 			App: ap, | ||||
| 		} | ||||
| 		appDTOs = append(appDTOs, appDTO) | ||||
| 		appTags, err := appTagRepo.GetByAppId(a.ID) | ||||
| 		appTags, err := appTagRepo.GetByAppId(ap.ID) | ||||
| 		if err != nil { | ||||
| 			continue | ||||
| 		} | ||||
| @@ -97,6 +106,8 @@ func (a AppService) PageApp(req request.AppSearch) (interface{}, error) { | ||||
| 			continue | ||||
| 		} | ||||
| 		appDTO.Tags = tags | ||||
| 		installs, _ := appInstallRepo.ListBy(appInstallRepo.WithAppId(ap.ID)) | ||||
| 		appDTO.Installed = len(installs) > 0 | ||||
| 	} | ||||
| 	res.Items = appDTOs | ||||
| 	res.Total = total | ||||
| @@ -138,7 +149,7 @@ func (a AppService) GetApp(key string) (*response.AppDTO, error) { | ||||
| 	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 ( | ||||
| 		appDetailDTO response.AppDetailDTO | ||||
| 		opts         []repo.DBOption | ||||
| @@ -148,13 +159,60 @@ func (a AppService) GetAppDetail(appId uint, version string) (response.AppDetail | ||||
| 	if err != nil { | ||||
| 		return appDetailDTO, err | ||||
| 	} | ||||
| 	appDetailDTO.AppDetail = detail | ||||
| 	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.AppDetail = detail | ||||
| 		appDetailDTO.Params = paramMap | ||||
| 	appDetailDTO.Enable = true | ||||
| 	} | ||||
|  | ||||
| 	app, err := appRepo.GetFirst(commonRepo.WithByID(detail.AppId)) | ||||
| 	if err != nil { | ||||
| @@ -165,101 +223,419 @@ func (a AppService) GetAppDetail(appId uint, version string) (response.AppDetail | ||||
| 	} | ||||
| 	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) { | ||||
| 	if err := docker.CreateDefaultDockerNetwork(); err != nil { | ||||
| 		return nil, buserr.WithDetail(constant.Err1PanelNetworkFailed, err.Error(), nil) | ||||
| func (a AppService) GetIgnoredApp() ([]response.IgnoredApp, error) { | ||||
| 	var res []response.IgnoredApp | ||||
| 	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 { | ||||
| 		return nil, buserr.New(constant.ErrNameIsExist) | ||||
| 		err = buserr.New(constant.ErrNameIsExist) | ||||
| 		return | ||||
| 	} | ||||
| 	httpPort, err := checkPort("PANEL_APP_PORT_HTTP", req.Params) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	var ( | ||||
| 		httpPort  int | ||||
| 		httpsPort int | ||||
| 		appDetail model.AppDetail | ||||
| 		app       model.App | ||||
| 	) | ||||
| 	for key := range req.Params { | ||||
| 		if !strings.Contains(key, "PANEL_APP_PORT") { | ||||
| 			continue | ||||
| 		} | ||||
| 	httpsPort, err := checkPort("PANEL_APP_PORT_HTTPS", req.Params) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 		var port int | ||||
| 		if port, err = checkPort(key, req.Params); err == nil { | ||||
| 			if key == "PANEL_APP_PORT_HTTP" { | ||||
| 				httpPort = port | ||||
| 			} | ||||
| 	appDetail, err := appDetailRepo.GetFirst(commonRepo.WithByID(req.AppDetailId)) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 			if key == "PANEL_APP_PORT_HTTPS" { | ||||
| 				httpsPort = port | ||||
| 			} | ||||
| 	app, err := appRepo.GetFirst(commonRepo.WithByID(appDetail.AppId)) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 		} else { | ||||
| 			return | ||||
| 		} | ||||
| 	if err := checkRequiredAndLimit(app); err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
|  | ||||
| 	paramByte, err := json.Marshal(req.Params) | ||||
| 	appDetail, err = appDetailRepo.GetFirst(commonRepo.WithByID(req.AppDetailId)) | ||||
| 	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, | ||||
| 		AppId:       appDetail.AppId, | ||||
| 		AppDetailId: appDetail.ID, | ||||
| 		Version:     appDetail.Version, | ||||
| 		Status:      constant.Installing, | ||||
| 		Env:         string(paramByte), | ||||
| 		HttpPort:    httpPort, | ||||
| 		HttpsPort:   httpsPort, | ||||
| 		App:         app, | ||||
| 	} | ||||
| 	composeMap := make(map[string]interface{}) | ||||
| 	if err := yaml.Unmarshal([]byte(appDetail.DockerCompose), &composeMap); err != nil { | ||||
| 		return nil, err | ||||
| 	if req.EditCompose { | ||||
| 		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"] | ||||
| 	if !ok { | ||||
| 		return nil, buserr.New("") | ||||
| 		err = buserr.New(constant.ErrFileParse) | ||||
| 		return | ||||
| 	} | ||||
| 	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 | ||||
| 	for k := range servicesMap { | ||||
| 		serviceName := k + "-" + common.RandStr(4) | ||||
| 		changeKeys[k] = serviceName | ||||
| 		containerName := constant.ContainerPrefix + k + "-" + common.RandStr(4) | ||||
| 		appInstall.ServiceName = k | ||||
| 		if index > 0 { | ||||
| 			continue | ||||
| 		} | ||||
| 		req.Params["CONTAINER_NAME"] = containerName | ||||
| 		appInstall.ServiceName = serviceName | ||||
| 		appInstall.ContainerName = containerName | ||||
| 		index++ | ||||
| 	} | ||||
| 	for k, v := range changeKeys { | ||||
| 		servicesMap[v] = servicesMap[k] | ||||
| 		delete(servicesMap, k) | ||||
|  | ||||
| 	if err = addDockerComposeCommonParam(composeMap, appInstall.ServiceName, req.AppContainerConfig, req.Params); err != nil { | ||||
| 		return | ||||
| 	} | ||||
| 	composeByte, err := yaml.Marshal(composeMap) | ||||
| 	var ( | ||||
| 		composeByte []byte | ||||
| 		paramByte   []byte | ||||
| 	) | ||||
|  | ||||
| 	composeByte, err = yaml.Marshal(composeMap) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 		return | ||||
| 	} | ||||
| 	appInstall.DockerCompose = string(composeByte) | ||||
|  | ||||
| 	if err := copyAppData(app.Key, appDetail.Version, req.Name, req.Params); err != nil { | ||||
| 		return nil, err | ||||
| 	defer func() { | ||||
| 		if err != nil { | ||||
| 			hErr := handleAppInstallErr(ctx, appInstall) | ||||
| 			if hErr != nil { | ||||
| 				global.LOG.Errorf("delete app dir error %s", hErr.Error()) | ||||
| 			} | ||||
|  | ||||
| 	fileOp := files.NewFileOp() | ||||
| 	if err := fileOp.WriteFile(appInstall.GetComposePath(), strings.NewReader(string(composeByte)), 0775); err != nil { | ||||
| 		return nil, err | ||||
| 		} | ||||
|  | ||||
| 	if err := appInstallRepo.Create(ctx, &appInstall); err != nil { | ||||
| 		return nil, err | ||||
| 	}() | ||||
| 	paramByte, err = json.Marshal(req.Params) | ||||
| 	if err != nil { | ||||
| 		return | ||||
| 	} | ||||
| 	if err := createLink(ctx, app, &appInstall, req.Params); err != nil { | ||||
| 		return nil, err | ||||
| 	appInstall.Env = string(paramByte) | ||||
| 	if err = appInstallRepo.Create(ctx, appInstall); err != nil { | ||||
| 		return | ||||
| 	} | ||||
| 	go upApp(appInstall.GetComposePath(), appInstall) | ||||
| 	if err = createLink(ctx, app, appInstall, req.Params); err != nil { | ||||
| 		return | ||||
| 	} | ||||
| 	go func() { | ||||
| 		if err = copyData(app, appDetail, appInstall, req); err != nil { | ||||
| 			if appInstall.Status == constant.Installing { | ||||
| 				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) | ||||
| 	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) { | ||||
| @@ -270,45 +646,59 @@ func (a AppService) GetAppUpdate() (*response.AppUpdateRes, error) { | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	versionUrl := fmt.Sprintf("%s/%s/%s/appstore/apps.json", global.CONF.System.RepoUrl, global.CONF.System.Mode, setting.SystemVersion) | ||||
| 	versionRes, err := http.Get(versionUrl) | ||||
| 	global.LOG.Infof("get current version from [%s]", versionUrl) | ||||
| 	versionUrl := fmt.Sprintf("%s/%s/1panel.json.version.txt", global.CONF.System.AppRepo, global.CONF.System.Mode) | ||||
| 	versionRes, err := http2.GetHttpRes(versionUrl) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	defer versionRes.Body.Close() | ||||
| 	body, err := ioutil.ReadAll(versionRes.Body) | ||||
| 	body, err := io.ReadAll(versionRes.Body) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	list := &dto.AppList{} | ||||
| 	if err = json.Unmarshal(body, list); err != nil { | ||||
| 	lastModifiedStr := string(body) | ||||
|  | ||||
| 	lastModified, err := strconv.Atoi(lastModifiedStr) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	res.Version = list.Version | ||||
| 	if setting.AppStoreVersion == "" || common.CompareVersion(list.Version, setting.AppStoreVersion) { | ||||
| 	appStoreLastModified, _ := strconv.Atoi(setting.AppStoreLastModified) | ||||
| 	if setting.AppStoreLastModified == "" || lastModified != appStoreLastModified { | ||||
| 		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, 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() | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	if !updateRes.CanUpdate { | ||||
| 		global.LOG.Infof("The latest version is [%s] The app store is already up to date", updateRes.Version) | ||||
| 		return nil | ||||
| 	} | ||||
| 	if err := getAppFromRepo(updateRes.DownloadPath, updateRes.Version); err != nil { | ||||
| 		global.LOG.Errorf("get app from oss  error: %s", err.Error()) | ||||
| 	if err = getAppFromRepo(fmt.Sprintf("%s/%s/1panel.json.zip", global.CONF.System.AppRepo, global.CONF.System.Mode)); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	appDir := constant.AppResourceDir | ||||
| 	listFile := path.Join(appDir, "list.json") | ||||
| 	listFile := path.Join(constant.ResourceDir, "1panel.json") | ||||
| 	content, err := os.ReadFile(listFile) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| @@ -317,158 +707,207 @@ func (a AppService) SyncAppList() error { | ||||
| 	if err := json.Unmarshal(content, list); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	var ( | ||||
| 		tags      []*model.Tag | ||||
| 		appTags   []*model.AppTag | ||||
| 		oldAppIds []uint | ||||
| 	) | ||||
| 	for _, t := range list.Tags { | ||||
| 	for _, t := range list.Extra.Tags { | ||||
| 		tags = append(tags, &model.Tag{ | ||||
| 			Key:  t.Key, | ||||
| 			Name: t.Name, | ||||
| 		}) | ||||
| 	} | ||||
| 	oldApps, err := appRepo.GetBy() | ||||
| 	oldApps, err := appRepo.GetBy(appRepo.WithResource(constant.AppResourceRemote)) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	appsMap := getApps(oldApps, list.Items) | ||||
| 	for _, l := range list.Items { | ||||
| 		app := appsMap[l.Key] | ||||
| 		icon, err := os.ReadFile(path.Join(appDir, l.Key, "metadata", "logo.png")) | ||||
| 		if err != nil { | ||||
| 			global.LOG.Errorf("get [%s] icon error: %s", l.Name, err.Error()) | ||||
| 			continue | ||||
| 	for _, old := range oldApps { | ||||
| 		oldAppIds = append(oldAppIds, old.ID) | ||||
| 	} | ||||
| 		iconStr := base64.StdEncoding.EncodeToString(icon) | ||||
|  | ||||
| 	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 { | ||||
| 			return err | ||||
| 		} | ||||
| 		body, err := io.ReadAll(iconRes.Body) | ||||
| 		if err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 		iconStr := base64.StdEncoding.EncodeToString(body) | ||||
| 		app.Icon = iconStr | ||||
| 		app.TagsKey = l.Tags | ||||
| 		if l.Recommend > 0 { | ||||
| 			app.Recommend = l.Recommend | ||||
| 		app.TagsKey = l.AppProperty.Tags | ||||
| 		if l.AppProperty.Recommend > 0 { | ||||
| 			app.Recommend = l.AppProperty.Recommend | ||||
| 		} else { | ||||
| 			app.Recommend = 9999 | ||||
| 		} | ||||
|  | ||||
| 		app.ReadMe = l.ReadMe | ||||
| 		app.LastModified = l.LastModified | ||||
| 		versions := l.Versions | ||||
| 		detailsMap := getAppDetails(app.Details, versions) | ||||
|  | ||||
| 		for _, v := range versions { | ||||
| 			detail := detailsMap[v] | ||||
| 			detailPath := path.Join(appDir, l.Key, "versions", v) | ||||
| 			if _, err := os.Stat(detailPath); err != nil { | ||||
| 				global.LOG.Errorf("get [%s] folder error: %s", detailPath, err.Error()) | ||||
| 				continue | ||||
| 			} | ||||
| 			readmeStr, err := os.ReadFile(path.Join(detailPath, "README.md")) | ||||
| 			version := v.Name | ||||
| 			detail := detailsMap[version] | ||||
|  | ||||
| 			dockerComposeUrl := fmt.Sprintf("%s/%s/%s/%s", baseRemoteUrl, app.Key, version, "docker-compose.yml") | ||||
| 			composeRes, err := http.Get(dockerComposeUrl) | ||||
| 			if err != nil { | ||||
| 				global.LOG.Errorf("get [%s] README error: %s", detailPath, err.Error()) | ||||
| 				return err | ||||
| 			} | ||||
| 			detail.Readme = string(readmeStr) | ||||
| 			dockerComposeStr, err := os.ReadFile(path.Join(detailPath, "docker-compose.yml")) | ||||
| 			bodyContent, err := io.ReadAll(composeRes.Body) | ||||
| 			if err != nil { | ||||
| 				global.LOG.Errorf("get [%s] docker-compose.yml error: %s", detailPath, err.Error()) | ||||
| 				continue | ||||
| 				return err | ||||
| 			} | ||||
| 			detail.DockerCompose = string(dockerComposeStr) | ||||
| 			paramStr, err := os.ReadFile(path.Join(detailPath, "config.json")) | ||||
| 			if err != nil { | ||||
| 				global.LOG.Errorf("get [%s] form.json error: %s", detailPath, err.Error()) | ||||
| 			} | ||||
| 			detail.Params = string(paramStr) | ||||
| 			detailsMap[v] = detail | ||||
| 			detail.DockerCompose = string(bodyContent) | ||||
|  | ||||
| 			paramByte, _ := json.Marshal(v.AppForm) | ||||
| 			detail.Params = string(paramByte) | ||||
| 			detail.DownloadUrl = v.DownloadUrl | ||||
| 			detail.DownloadCallBackUrl = v.DownloadCallBackUrl | ||||
| 			detail.Update = true | ||||
| 			detail.LastModified = v.LastModified | ||||
| 			detailsMap[version] = detail | ||||
| 		} | ||||
| 		var newDetails []model.AppDetail | ||||
| 		for _, v := range detailsMap { | ||||
| 			newDetails = append(newDetails, v) | ||||
| 		for _, detail := range detailsMap { | ||||
| 			newDetails = append(newDetails, detail) | ||||
| 		} | ||||
| 		app.Details = newDetails | ||||
| 		appsMap[l.Key] = app | ||||
| 		appsMap[l.AppProperty.Key] = app | ||||
| 	} | ||||
|  | ||||
| 	var ( | ||||
| 		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 { | ||||
| 		if v.ID == 0 { | ||||
| 			addAppArray = append(addAppArray, v) | ||||
| 		} 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() | ||||
| 	defer tx.Rollback() | ||||
| 	if len(addAppArray) > 0 { | ||||
| 		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 | ||||
| 		} | ||||
| 	} | ||||
| 	if err := tagRepo.DeleteAll(ctx); err != nil { | ||||
| 		tx.Rollback() | ||||
| 		return err | ||||
| 	} | ||||
| 	if len(tags) > 0 { | ||||
| 		if err := tagRepo.BatchCreate(ctx, tags); err != nil { | ||||
| 			tx.Rollback() | ||||
| 			return err | ||||
| 		} | ||||
| 		for _, t := range tags { | ||||
| 			tagMap[t.Key] = t.ID | ||||
| 		} | ||||
| 	} | ||||
| 	for _, update := range updateArray { | ||||
| 	for _, update := range updateAppArray { | ||||
| 		if err := appRepo.Save(ctx, &update); err != nil { | ||||
| 			tx.Rollback() | ||||
| 			return err | ||||
| 		} | ||||
| 	} | ||||
| 	apps := append(addAppArray, updateArray...) | ||||
| 	apps := append(addAppArray, updateAppArray...) | ||||
|  | ||||
| 	var ( | ||||
| 		addDetails    []model.AppDetail | ||||
| 		updateDetails []model.AppDetail | ||||
| 		deleteDetails []model.AppDetail | ||||
| 	) | ||||
| 	for _, a := range apps { | ||||
| 		for _, t := range a.TagsKey { | ||||
| 	for _, app := range apps { | ||||
| 		for _, t := range app.TagsKey { | ||||
| 			tagId, ok := tagMap[t] | ||||
| 			if ok { | ||||
| 				appTags = append(appTags, &model.AppTag{ | ||||
| 					AppId: a.ID, | ||||
| 					AppId: app.ID, | ||||
| 					TagId: tagId, | ||||
| 				}) | ||||
| 			} | ||||
| 		} | ||||
| 		for _, d := range a.Details { | ||||
| 			d.AppId = a.ID | ||||
| 		for _, d := range app.Details { | ||||
| 			d.AppId = app.ID | ||||
| 			if d.ID == 0 { | ||||
| 				addDetails = append(addDetails, d) | ||||
| 			} else { | ||||
| 				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 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 | ||||
| 		} | ||||
| 	} | ||||
| 	for _, u := range updateDetails { | ||||
| 		if err := appDetailRepo.Update(ctx, u); err != nil { | ||||
| 			tx.Rollback() | ||||
| 			return err | ||||
| 		} | ||||
| 	} | ||||
| 	if err := appTagRepo.DeleteAll(ctx); err != nil { | ||||
| 		tx.Rollback() | ||||
|  | ||||
| 	if len(oldAppIds) > 0 { | ||||
| 		if err := appTagRepo.DeleteByAppIds(ctx, oldAppIds); err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	if len(appTags) > 0 { | ||||
| 		if err := appTagRepo.BatchCreate(ctx, appTags); err != nil { | ||||
| 			tx.Rollback() | ||||
| 			return err | ||||
| 		} | ||||
| 	} | ||||
| 	tx.Commit() | ||||
| 	if err := NewISettingService().Update("AppStoreLastModified", strconv.Itoa(list.LastModified)); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|   | ||||
| @@ -1,9 +1,10 @@ | ||||
| package service | ||||
|  | ||||
| import ( | ||||
| 	"context" | ||||
| 	"encoding/json" | ||||
| 	"fmt" | ||||
| 	"io/ioutil" | ||||
| 	"github.com/1Panel-dev/1Panel/backend/i18n" | ||||
| 	"math" | ||||
| 	"os" | ||||
| 	"path" | ||||
| @@ -11,6 +12,9 @@ import ( | ||||
| 	"strconv" | ||||
| 	"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/nginx" | ||||
| 	"github.com/joho/godotenv" | ||||
| @@ -38,14 +42,15 @@ type IAppInstallService interface { | ||||
| 	Page(req request.AppInstalledSearch) (int64, []response.AppInstalledDTO, error) | ||||
| 	CheckExist(key string) (*response.AppInstalledCheck, error) | ||||
| 	LoadPort(key string) (int64, error) | ||||
| 	LoadPassword(key string) (string, error) | ||||
| 	LoadConnInfo(key string) (response.DatabaseConn, error) | ||||
| 	SearchForWebsite(req request.AppInstalledSearch) ([]response.AppInstalledDTO, error) | ||||
| 	Operate(req request.AppInstalledOperate) error | ||||
| 	Update(req request.AppInstalledUpdate) error | ||||
| 	IgnoreUpgrade(req request.AppInstalledIgnoreUpgrade) error | ||||
| 	SyncAll(systemInit bool) error | ||||
| 	GetServices(key string) ([]response.AppService, error) | ||||
| 	GetUpdateVersions(installId uint) ([]dto.AppVersion, error) | ||||
| 	GetParams(id uint) ([]response.AppParam, error) | ||||
| 	GetParams(id uint) (*response.AppConfig, error) | ||||
| 	ChangeAppPort(req request.PortUpdate) error | ||||
| 	GetDefaultConfigByKey(key string) (string, error) | ||||
| 	DeleteCheck(installId uint) ([]dto.AppResource, error) | ||||
| @@ -133,12 +138,16 @@ func (a *AppInstallService) LoadPort(key string) (int64, error) { | ||||
| 	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, "") | ||||
| 	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) { | ||||
| @@ -175,10 +184,13 @@ func (a *AppInstallService) SearchForWebsite(req request.AppInstalledSearch) ([] | ||||
| } | ||||
|  | ||||
| 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 { | ||||
| 		return err | ||||
| 	} | ||||
| 	if !req.ForceDelete && !files.NewFileOp().Stat(install.GetPath()) { | ||||
| 		return buserr.New(constant.ErrInstallDirNotFound) | ||||
| 	} | ||||
| 	dockerComposePath := install.GetComposePath() | ||||
| 	switch req.Operate { | ||||
| 	case constant.Rebuild: | ||||
| @@ -202,17 +214,14 @@ func (a *AppInstallService) Operate(req request.AppInstalledOperate) error { | ||||
| 		} | ||||
| 		return syncById(install.ID) | ||||
| 	case constant.Delete: | ||||
| 		tx, ctx := getTxAndContext() | ||||
| 		if err := deleteAppInstall(ctx, install, req.DeleteBackup, req.ForceDelete, req.DeleteDB); err != nil && !req.ForceDelete { | ||||
| 			tx.Rollback() | ||||
| 		if err := deleteAppInstall(install, req.DeleteBackup, req.ForceDelete, req.DeleteDB); err != nil && !req.ForceDelete { | ||||
| 			return err | ||||
| 		} | ||||
| 		tx.Commit() | ||||
| 		return nil | ||||
| 	case constant.Sync: | ||||
| 		return syncById(install.ID) | ||||
| 	case constant.Upgrade: | ||||
| 		return updateInstall(install.ID, req.DetailId) | ||||
| 		return upgradeInstall(install.ID, req.DetailId) | ||||
| 	default: | ||||
| 		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") | ||||
| 	oldEnvMaps, err := godotenv.Read(envPath) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	backupEnvMaps := oldEnvMaps | ||||
| 	handleMap(req.Params, oldEnvMaps) | ||||
| 	paramByte, err := json.Marshal(oldEnvMaps) | ||||
| 	if err != nil { | ||||
| @@ -262,54 +313,76 @@ func (a *AppInstallService) Update(req request.AppInstalledUpdate) error { | ||||
| 	if err := env.Write(oldEnvMaps, envPath); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	_ = appInstallRepo.Save(&installed) | ||||
|  | ||||
| 	fileOp := files.NewFileOp() | ||||
| 	_ = fileOp.WriteFile(installed.GetComposePath(), strings.NewReader(installed.DockerCompose), 0755) | ||||
| 	if err := rebuildApp(installed); err != nil { | ||||
| 		_ = env.Write(backupEnvMaps, envPath) | ||||
| 		_ = fileOp.WriteFile(installed.GetComposePath(), strings.NewReader(backupDockerCompose), 0755) | ||||
| 		return err | ||||
| 	} | ||||
| 	installed.Status = constant.Running | ||||
| 	_ = appInstallRepo.Save(context.Background(), &installed) | ||||
|  | ||||
| 	website, _ := websiteRepo.GetFirst(websiteRepo.WithAppInstallId(installed.ID)) | ||||
| 	if changePort && website.ID != 0 && website.Status == constant.Running { | ||||
| 		go func() { | ||||
| 			nginxInstall, err := getNginxFull(&website) | ||||
| 			if err != nil { | ||||
| 			return buserr.WithErr(constant.ErrUpdateBuWebsite, err) | ||||
| 				global.LOG.Errorf(buserr.WithErr(constant.ErrUpdateBuWebsite, err).Error()) | ||||
| 				return | ||||
| 			} | ||||
| 			config := nginxInstall.SiteConfig.Config | ||||
| 			servers := config.FindServers() | ||||
| 			if len(servers) == 0 { | ||||
| 			return buserr.WithErr(constant.ErrUpdateBuWebsite, errors.New("nginx config is not valid")) | ||||
| 				global.LOG.Errorf(buserr.WithErr(constant.ErrUpdateBuWebsite, errors.New("nginx config is not valid")).Error()) | ||||
| 				return | ||||
| 			} | ||||
| 			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 { | ||||
| 			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) | ||||
| 				global.LOG.Errorf(buserr.WithErr(constant.ErrUpdateBuWebsite, err).Error()) | ||||
| 				return | ||||
| 			} | ||||
| 		}() | ||||
| 	} | ||||
| 	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 { | ||||
| 	allList, err := appInstallRepo.ListBy() | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	for _, i := range allList { | ||||
| 		if i.Status == constant.Installing { | ||||
| 		if i.Status == constant.Installing || i.Status == constant.Upgrading { | ||||
| 			if systemInit { | ||||
| 				i.Status = constant.Error | ||||
| 				i.Message = "System restart causes application exception" | ||||
| 				_ = appInstallRepo.Save(&i) | ||||
| 				i.Message = "1Panel restart causes the task to terminate" | ||||
| 				_ = appInstallRepo.Save(context.Background(), &i) | ||||
| 			} | ||||
| 			continue | ||||
| 		} | ||||
| 		if !systemInit { | ||||
| 			if err := syncById(i.ID); err != nil { | ||||
| 				global.LOG.Errorf("sync install app[%s] error,mgs: %s", i.Name, err.Error()) | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| @@ -352,6 +425,12 @@ func (a *AppInstallService) GetUpdateVersions(installId uint) ([]dto.AppVersion, | ||||
| 		return versions, err | ||||
| 	} | ||||
| 	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) { | ||||
| 			versions = append(versions, dto.AppVersion{ | ||||
| 				Version:  detail.Version, | ||||
| @@ -400,7 +479,6 @@ func (a *AppInstallService) DeleteCheck(installId uint) ([]dto.AppResource, erro | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	if app.Type == "website" { | ||||
| 	websites, _ := websiteRepo.GetBy(websiteRepo.WithAppInstallId(appInstall.ID)) | ||||
| 	for _, website := range websites { | ||||
| 		res = append(res, dto.AppResource{ | ||||
| @@ -408,7 +486,6 @@ func (a *AppInstallService) DeleteCheck(installId uint) ([]dto.AppResource, erro | ||||
| 			Name: website.PrimaryDomain, | ||||
| 		}) | ||||
| 	} | ||||
| 	} | ||||
| 	if app.Key == constant.AppOpenresty { | ||||
| 		websites, _ := websiteRepo.GetBy() | ||||
| 		for _, website := range websites { | ||||
| @@ -436,7 +513,16 @@ func (a *AppInstallService) GetDefaultConfigByKey(key string) (string, error) { | ||||
| 	if err != nil { | ||||
| 		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 { | ||||
| 		filePath = path.Join(filePath, "my.cnf") | ||||
| 	} | ||||
| @@ -453,11 +539,12 @@ func (a *AppInstallService) GetDefaultConfigByKey(key string) (string, error) { | ||||
| 	return string(contentByte), nil | ||||
| } | ||||
|  | ||||
| func (a *AppInstallService) GetParams(id uint) ([]response.AppParam, error) { | ||||
| func (a *AppInstallService) GetParams(id uint) (*response.AppConfig, error) { | ||||
| 	var ( | ||||
| 		res     []response.AppParam | ||||
| 		params  []response.AppParam | ||||
| 		appForm dto.AppForm | ||||
| 		envs    = make(map[string]interface{}) | ||||
| 		res     response.AppConfig | ||||
| 	) | ||||
| 	install, err := appInstallRepo.GetFirst(commonRepo.WithByID(id)) | ||||
| 	if err != nil { | ||||
| @@ -499,10 +586,18 @@ func (a *AppInstallService) GetParams(id uint) ([]response.AppParam, error) { | ||||
| 				} | ||||
| 				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 { | ||||
| @@ -513,12 +608,10 @@ func syncById(installId uint) error { | ||||
| 	if appInstall.Status == constant.Installing { | ||||
| 		return nil | ||||
| 	} | ||||
|  | ||||
| 	containerNames, err := getContainerNames(appInstall) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	cli, err := docker.NewClient() | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| @@ -544,16 +637,16 @@ func syncById(installId uint) error { | ||||
| 			errorContainers = append(errorContainers, n.Names[0]) | ||||
| 		} | ||||
| 	} | ||||
| 	for _, old := range containerNames { | ||||
| 	for _, name := range containerNames { | ||||
| 		exist := false | ||||
| 		for _, new := range containers { | ||||
| 			if common.ExistWithStrArray(old, new.Names) { | ||||
| 		for _, container := range containers { | ||||
| 			if common.ExistWithStrArray(name, container.Names) { | ||||
| 				exist = true | ||||
| 				break | ||||
| 			} | ||||
| 		} | ||||
| 		if !exist { | ||||
| 			notFoundContainers = append(notFoundContainers, old) | ||||
| 			notFoundContainers = append(notFoundContainers, name) | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| @@ -566,16 +659,16 @@ func syncById(installId uint) error { | ||||
|  | ||||
| 	if containerCount == 0 { | ||||
| 		appInstall.Status = constant.Error | ||||
| 		appInstall.Message = "container is not found" | ||||
| 		return appInstallRepo.Save(&appInstall) | ||||
| 		appInstall.Message = i18n.GetMsgWithMap("ErrContainerNotFound", map[string]interface{}{"name": strings.Join(containerNames, ",")}) | ||||
| 		return appInstallRepo.Save(context.Background(), &appInstall) | ||||
| 	} | ||||
| 	if errCount == 0 && existedCount == 0 { | ||||
| 	if errCount == 0 && existedCount == 0 && notFoundCount == 0 { | ||||
| 		appInstall.Status = constant.Running | ||||
| 		return appInstallRepo.Save(&appInstall) | ||||
| 		return appInstallRepo.Save(context.Background(), &appInstall) | ||||
| 	} | ||||
| 	if existedCount == normalCount { | ||||
| 		appInstall.Status = constant.Stopped | ||||
| 		return appInstallRepo.Save(&appInstall) | ||||
| 		return appInstallRepo.Save(context.Background(), &appInstall) | ||||
| 	} | ||||
| 	if errCount == normalCount { | ||||
| 		appInstall.Status = constant.Error | ||||
| @@ -584,23 +677,15 @@ func syncById(installId uint) error { | ||||
| 		appInstall.Status = constant.UnHealthy | ||||
| 	} | ||||
|  | ||||
| 	var errMsg strings.Builder | ||||
| 	var errMsg string | ||||
| 	if errCount > 0 { | ||||
| 		errMsg.Write([]byte(string(rune(errCount)) + " error containers:")) | ||||
| 		for _, e := range errorContainers { | ||||
| 			errMsg.Write([]byte(e)) | ||||
| 		} | ||||
| 		errMsg.Write([]byte("\n")) | ||||
| 		errMsg += i18n.GetMsgWithMap("ErrContainerMsg", map[string]interface{}{"name": strings.Join(errorContainers, ",")}) | ||||
| 	} | ||||
| 	if notFoundCount > 0 { | ||||
| 		errMsg.Write([]byte(string(rune(notFoundCount)) + " not found containers:")) | ||||
| 		for _, e := range notFoundContainers { | ||||
| 			errMsg.Write([]byte(e)) | ||||
| 		errMsg += i18n.GetMsgWithMap("ErrContainerNotFound", map[string]interface{}{"name": strings.Join(notFoundContainers, ",")}) | ||||
| 	} | ||||
| 		errMsg.Write([]byte("\n")) | ||||
| 	} | ||||
| 	appInstall.Message = errMsg.String() | ||||
| 	return appInstallRepo.Save(&appInstall) | ||||
| 	appInstall.Message = errMsg | ||||
| 	return appInstallRepo.Save(context.Background(), &appInstall) | ||||
| } | ||||
|  | ||||
| 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 | ||||
| 	} | ||||
| 	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 { | ||||
| 		return err | ||||
| 	} | ||||
|   | ||||
| @@ -2,12 +2,21 @@ package service | ||||
|  | ||||
| import ( | ||||
| 	"context" | ||||
| 	"encoding/base64" | ||||
| 	"encoding/json" | ||||
| 	"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" | ||||
| 	"net/http" | ||||
| 	"os" | ||||
| 	"os/exec" | ||||
| 	"path" | ||||
| 	"reflect" | ||||
| 	"regexp" | ||||
| 	"strconv" | ||||
| 	"strings" | ||||
|  | ||||
| @@ -23,7 +32,9 @@ import ( | ||||
| 	"github.com/1Panel-dev/1Panel/backend/global" | ||||
| 	"github.com/1Panel-dev/1Panel/backend/utils/common" | ||||
| 	"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" | ||||
| 	dockerTypes "github.com/docker/docker/api/types" | ||||
| 	"github.com/pkg/errors" | ||||
| ) | ||||
|  | ||||
| @@ -37,7 +48,19 @@ var ( | ||||
| func checkPort(key string, params map[string]interface{}) (int, error) { | ||||
| 	port, ok := params[key] | ||||
| 	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)) | ||||
| 		if len(oldInstalled) > 0 { | ||||
| @@ -125,7 +148,23 @@ func createLink(ctx context.Context, app model.App, appInstall *model.AppInstall | ||||
| 	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() | ||||
| 	appDir := install.GetPath() | ||||
| 	dir, _ := os.Stat(appDir) | ||||
| @@ -134,36 +173,34 @@ func deleteAppInstall(ctx context.Context, install model.AppInstall, deleteBacku | ||||
| 		if err != nil && !forceDelete { | ||||
| 			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 { | ||||
| 		return err | ||||
| 	} | ||||
| 	if err := deleteLink(ctx, &install, deleteDB, forceDelete); err != nil && !forceDelete { | ||||
| 		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) | ||||
| 	if _, err := os.Stat(uploadDir); err == nil { | ||||
| 		_ = os.RemoveAll(uploadDir) | ||||
| 	} | ||||
| 	if deleteBackup { | ||||
| 		localDir, err := loadLocalDir() | ||||
| 		if err != nil && !forceDelete { | ||||
| 			return err | ||||
| 		} | ||||
| 		localDir, _ := loadLocalDir() | ||||
| 		backupDir := fmt.Sprintf("%s/app/%s/%s", localDir, install.App.Key, install.Name) | ||||
| 		if _, err := os.Stat(backupDir); err == nil { | ||||
| 			_ = os.RemoveAll(backupDir) | ||||
| 		} | ||||
| 		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)) | ||||
| 	_ = backupRepo.DeleteRecord(ctx, commonRepo.WithByType(install.App.Key)) | ||||
| 	if install.App.Key == constant.AppMysql { | ||||
| 		_ = mysqlRepo.DeleteAll(ctx) | ||||
| 	} | ||||
| 	_ = op.DeleteDir(appDir) | ||||
| 	tx.Commit() | ||||
| 	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)) | ||||
| } | ||||
|  | ||||
| func updateInstall(installId uint, detailId uint) error { | ||||
| func upgradeInstall(installId uint, detailId uint) error { | ||||
| 	install, err := appInstallRepo.GetFirst(commonRepo.WithByID(installId)) | ||||
| 	if err != nil { | ||||
| 		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 { | ||||
| 		return err | ||||
| 	} | ||||
| 	if _, err = compose.Down(install.GetComposePath()); err != nil { | ||||
| 		return err | ||||
|  | ||||
| 	install.Status = constant.Upgrading | ||||
|  | ||||
| 	go func() { | ||||
| 		var upErr error | ||||
| 		defer func() { | ||||
| 			if upErr != nil { | ||||
| 				install.Status = constant.UpgradeErr | ||||
| 				install.Message = upErr.Error() | ||||
| 				_ = appInstallRepo.Save(context.Background(), &install) | ||||
| 			} | ||||
| 	install.DockerCompose = detail.DockerCompose | ||||
| 		}() | ||||
|  | ||||
| 		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() | ||||
| 	if err := fileOp.WriteFile(install.GetComposePath(), strings.NewReader(install.DockerCompose), 0775); err != nil { | ||||
| 		return err | ||||
| 		envParams := make(map[string]string, len(envs)) | ||||
| 		handleMap(envs, envParams) | ||||
| 		if err = env.Write(envParams, install.GetEnvPath()); err != nil { | ||||
| 			return | ||||
| 		} | ||||
| 	if _, err = compose.Up(install.GetComposePath()); err != nil { | ||||
| 		return err | ||||
| 		if upErr = fileOp.WriteFile(install.GetComposePath(), strings.NewReader(install.DockerCompose), 0775); upErr != nil { | ||||
| 			return | ||||
| 		} | ||||
| 	return appInstallRepo.Save(&install) | ||||
| 		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) { | ||||
| 	composeMap := install.DockerCompose | ||||
| 	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) | ||||
| 	envStr, err := coverEnvJsonToStr(install.Env) | ||||
| 	if err != nil { | ||||
| 		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() { | ||||
| 		if service.ContainerName == "${CONTAINER_NAME}" || service.ContainerName == "" { | ||||
| 			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 | ||||
| } | ||||
|  | ||||
| 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 { | ||||
| 	if app.Limit > 0 { | ||||
| 		installs, err := appInstallRepo.ListBy(appInstallRepo.WithAppId(app.ID)) | ||||
| @@ -256,40 +413,9 @@ func checkLimit(app model.App) error { | ||||
| } | ||||
|  | ||||
| func checkRequiredAndLimit(app model.App) error { | ||||
|  | ||||
| 	if err := checkLimit(app); err != nil { | ||||
| 		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 | ||||
| } | ||||
|  | ||||
| @@ -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() | ||||
| 	resourceDir := path.Join(constant.AppResourceDir, key, "versions", version) | ||||
| 	installAppDir := path.Join(constant.AppInstallDir, key) | ||||
| 	if !appDetail.Update && fileOp.Stat(appVersionDir) { | ||||
| 		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 err = fileOp.CreateDir(installAppDir, 0755); err != nil { | ||||
| 			return | ||||
| 		} | ||||
| 	} | ||||
| 	appDir := path.Join(installAppDir, installName) | ||||
| 	appDir := path.Join(installAppDir, req.Name) | ||||
| 	if fileOp.Stat(appDir) { | ||||
| 		if err = fileOp.DeleteDir(appDir); err != nil { | ||||
| 			return | ||||
| @@ -325,33 +508,101 @@ func copyAppData(key, version, installName string, params map[string]interface{} | ||||
| 	if err = fileOp.Copy(resourceDir, installAppDir); err != nil { | ||||
| 		return | ||||
| 	} | ||||
| 	versionDir := path.Join(installAppDir, version) | ||||
| 	versionDir := path.Join(installAppDir, appDetail.Version) | ||||
| 	if err = fileOp.Rename(versionDir, appDir); err != nil { | ||||
| 		return | ||||
| 	} | ||||
| 	envPath := path.Join(appDir, ".env") | ||||
|  | ||||
| 	envParams := make(map[string]string, len(params)) | ||||
| 	handleMap(params, envParams) | ||||
| 	envParams := make(map[string]string, len(req.Params)) | ||||
| 	handleMap(req.Params, envParams) | ||||
| 	if err = env.Write(envParams, envPath); err != nil { | ||||
| 		return | ||||
| 	} | ||||
| 	if err := fileOp.WriteFile(appInstall.GetComposePath(), strings.NewReader(appInstall.DockerCompose), 0755); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	return | ||||
| } | ||||
|  | ||||
| func upApp(composeFilePath string, appInstall model.AppInstall) { | ||||
| 	out, err := compose.Up(composeFilePath) | ||||
| // 处理文件夹权限等问题 | ||||
| func upAppPre(app model.App, appInstall *model.AppInstall) error { | ||||
| 	if app.Key == "nexus" { | ||||
| 		dataPath := path.Join(appInstall.GetPath(), "data") | ||||
| 		if err := files.NewFileOp().Chown(dataPath, 200, 0); err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 	} | ||||
| 	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 != "" { | ||||
| 			appInstall.Message = out | ||||
| 		} else { | ||||
| 			appInstall.Message = err.Error() | ||||
| 						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 | ||||
| 		_ = appInstallRepo.Save(&appInstall) | ||||
| 	} else { | ||||
| 		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) | ||||
| } | ||||
|  | ||||
| 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)) | ||||
| 	for _, old := range details { | ||||
| 		old.Status = constant.AppTakeDown | ||||
| 		appDetails[old.Version] = old | ||||
| 	} | ||||
|  | ||||
| 	for _, v := range versions { | ||||
| 		detail, ok := appDetails[v] | ||||
| 		version := v.Name | ||||
| 		detail, ok := appDetails[version] | ||||
| 		if ok { | ||||
| 			detail.Status = constant.AppNormal | ||||
| 			appDetails[v] = detail | ||||
| 			appDetails[version] = detail | ||||
| 		} else { | ||||
| 			appDetails[v] = model.AppDetail{ | ||||
| 				Version: v, | ||||
| 			appDetails[version] = model.AppDetail{ | ||||
| 				Version: version, | ||||
| 				Status:  constant.AppNormal, | ||||
| 			} | ||||
| 		} | ||||
| @@ -397,27 +648,103 @@ func getApps(oldApps []model.App, items []dto.AppDefine) map[string]model.App { | ||||
| 		apps[old.Key] = old | ||||
| 	} | ||||
| 	for _, item := range items { | ||||
| 		app, ok := apps[item.Key] | ||||
| 		config := item.AppProperty | ||||
| 		key := config.Key | ||||
| 		app, ok := apps[key] | ||||
| 		if !ok { | ||||
| 			app = model.App{} | ||||
| 		} | ||||
| 		app.Resource = constant.AppResourceRemote | ||||
| 		app.Name = item.Name | ||||
| 		app.Limit = item.Limit | ||||
| 		app.Key = item.Key | ||||
| 		app.ShortDescZh = item.ShortDescZh | ||||
| 		app.ShortDescEn = item.ShortDescEn | ||||
| 		app.Website = item.Website | ||||
| 		app.Document = item.Document | ||||
| 		app.Github = item.Github | ||||
| 		app.Type = item.Type | ||||
| 		app.CrossVersionUpdate = item.CrossVersionUpdate | ||||
| 		app.Required = item.GetRequired() | ||||
| 		app.Limit = config.Limit | ||||
| 		app.Key = key | ||||
| 		app.ShortDescZh = config.ShortDescZh | ||||
| 		app.ShortDescEn = config.ShortDescEn | ||||
| 		app.Website = config.Website | ||||
| 		app.Document = config.Document | ||||
| 		app.Github = config.Github | ||||
| 		app.Type = config.Type | ||||
| 		app.CrossVersionUpdate = config.CrossVersionUpdate | ||||
| 		app.Status = constant.AppNormal | ||||
| 		apps[item.Key] = app | ||||
| 		app.LastModified = item.LastModified | ||||
| 		app.ReadMe = item.ReadMe | ||||
| 		apps[key] = app | ||||
| 	} | ||||
| 	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 { | ||||
| 	reErr := err | ||||
| 	install.Message = err.Error() | ||||
| @@ -426,38 +753,19 @@ func handleErr(install model.AppInstall, err error, out string) error { | ||||
| 		reErr = errors.New(out) | ||||
| 		install.Status = constant.Error | ||||
| 	} | ||||
| 	_ = appInstallRepo.Save(&install) | ||||
| 	_ = appInstallRepo.Save(context.Background(), &install) | ||||
| 	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) { | ||||
| 	var res []response.AppInstalledDTO | ||||
| 	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{ | ||||
| 			AppInstall: installed, | ||||
| 			Path:       installed.GetPath(), | ||||
| 		} | ||||
| 		app, err := appRepo.GetFirst(commonRepo.WithByID(installed.AppId)) | ||||
| 		if err != nil { | ||||
| @@ -469,9 +777,18 @@ func handleInstalled(appInstallList []model.AppInstall, updated bool) ([]respons | ||||
| 		} | ||||
| 		var versions []string | ||||
| 		for _, detail := range details { | ||||
| 			if detail.IgnoreUpgrade { | ||||
| 				continue | ||||
| 			} | ||||
| 			if common.IsCrossVersion(installed.Version, detail.Version) && !app.CrossVersionUpdate { | ||||
| 				continue | ||||
| 			} | ||||
| 			versions = append(versions, detail.Version) | ||||
| 		} | ||||
| 		versions = common.GetSortedVersions(versions) | ||||
| 		if len(versions) == 0 { | ||||
| 			continue | ||||
| 		} | ||||
| 		lastVersion := versions[0] | ||||
| 		if common.IsCrossVersion(installed.Version, lastVersion) { | ||||
| 			installDTO.CanUpdate = app.CrossVersionUpdate | ||||
| @@ -501,7 +818,7 @@ func getAppInstallByKey(key string) (model.AppInstall, error) { | ||||
| 	return appInstall, nil | ||||
| } | ||||
|  | ||||
| func updateToolApp(installed model.AppInstall) { | ||||
| func updateToolApp(installed *model.AppInstall) { | ||||
| 	tooKey, ok := dto.AppToolMap[installed.App.Key] | ||||
| 	if !ok { | ||||
| 		return | ||||
| @@ -537,7 +854,7 @@ func updateToolApp(installed model.AppInstall) { | ||||
| 		return | ||||
| 	} | ||||
| 	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()) | ||||
| 		return | ||||
| 	} | ||||
| @@ -550,3 +867,108 @@ func updateToolApp(installed model.AppInstall) { | ||||
| 		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 | ||||
|  | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"strconv" | ||||
| 	"time" | ||||
|  | ||||
| 	"github.com/1Panel-dev/1Panel/backend/app/dto" | ||||
| 	"github.com/1Panel-dev/1Panel/backend/constant" | ||||
| @@ -19,45 +17,27 @@ import ( | ||||
| type AuthService struct{} | ||||
|  | ||||
| type IAuthService interface { | ||||
| 	SafetyStatus(c *gin.Context) error | ||||
| 	CheckIsFirst() bool | ||||
| 	InitUser(c *gin.Context, req dto.InitUser) error | ||||
| 	CheckIsSafety(code string) (string, error) | ||||
| 	VerifyCode(code string) (bool, error) | ||||
| 	SafeEntrance(c *gin.Context, code string) error | ||||
| 	Login(c *gin.Context, info dto.Login) (*dto.UserLoginInfo, error) | ||||
| 	LogOut(c *gin.Context) error | ||||
| 	MFALogin(c *gin.Context, info dto.MFALogin) (*dto.UserLoginInfo, error) | ||||
| } | ||||
|  | ||||
| func NewIAuthService() IAuthService { | ||||
| 	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) { | ||||
| 	nameSetting, err := settingRepo.Get(settingRepo.WithByKey("UserName")) | ||||
| 	if err != nil { | ||||
| 		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 { | ||||
| 		return nil, errors.WithMessage(constant.ErrRecordNotFound, err.Error()) | ||||
| 	} | ||||
| 	pass, err := encrypt.StringDecrypt(passwrodSetting.Value) | ||||
| 	pass, err := encrypt.StringDecrypt(passwordSetting.Value) | ||||
| 	if err != nil { | ||||
| 		return nil, constant.ErrAuth | ||||
| 	} | ||||
| @@ -80,11 +60,11 @@ func (u *AuthService) MFALogin(c *gin.Context, info dto.MFALogin) (*dto.UserLogi | ||||
| 	if err != nil { | ||||
| 		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 { | ||||
| 		return nil, errors.WithMessage(constant.ErrRecordNotFound, err.Error()) | ||||
| 	} | ||||
| 	pass, err := encrypt.StringDecrypt(passwrodSetting.Value) | ||||
| 	pass, err := encrypt.StringDecrypt(passwordSetting.Value) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| @@ -96,7 +76,11 @@ func (u *AuthService) MFALogin(c *gin.Context, info dto.MFALogin) (*dto.UserLogi | ||||
| 	if err != nil { | ||||
| 		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 { | ||||
| 		return nil, constant.ErrAuth | ||||
| 	} | ||||
| @@ -118,7 +102,7 @@ func (u *AuthService) generateSession(c *gin.Context, name, authMethod string) ( | ||||
| 		j := jwt.NewJWT() | ||||
| 		claims := j.CreateClaims(jwt.BaseClaims{ | ||||
| 			Name: name, | ||||
| 		}, lifeTime) | ||||
| 		}) | ||||
| 		token, err := j.CreateToken(claims) | ||||
| 		if err != nil { | ||||
| 			return nil, err | ||||
| @@ -129,7 +113,7 @@ func (u *AuthService) generateSession(c *gin.Context, name, authMethod string) ( | ||||
| 	sessionUser, err := global.SESSION.Get(sID) | ||||
| 	if err != nil { | ||||
| 		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) | ||||
| 		if err != nil { | ||||
| 			return nil, err | ||||
| @@ -163,57 +147,16 @@ func (u *AuthService) VerifyCode(code string) (bool, error) { | ||||
| 	return setting.Value == code, nil | ||||
| } | ||||
|  | ||||
| func (u *AuthService) SafetyStatus(c *gin.Context) error { | ||||
| 	setting, err := settingRepo.Get(settingRepo.WithByKey("SecurityEntrance")) | ||||
| func (u *AuthService) CheckIsSafety(code string) (string, error) { | ||||
| 	status, err := settingRepo.Get(settingRepo.WithByKey("SecurityEntrance")) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 		return "", err | ||||
| 	} | ||||
| 	codeWithEcrypt, err := c.Cookie(encrypt.Md5(setting.Value)) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	if len(status.Value) == 0 { | ||||
| 		return "disable", nil | ||||
| 	} | ||||
| 	code, err := encrypt.StringDecrypt(codeWithEcrypt) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	if status.Value == code { | ||||
| 		return "pass", nil | ||||
| 	} | ||||
| 	if code != encrypt.Md5(setting.Value) { | ||||
| 		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) | ||||
| 	return "unpass", nil | ||||
| } | ||||
|   | ||||
| @@ -2,17 +2,23 @@ package service | ||||
|  | ||||
| import ( | ||||
| 	"context" | ||||
| 	"encoding/base64" | ||||
| 	"encoding/json" | ||||
| 	"fmt" | ||||
| 	"io" | ||||
| 	"net/http" | ||||
| 	"net/url" | ||||
| 	"os" | ||||
| 	"path" | ||||
| 	"strings" | ||||
|  | ||||
| 	"github.com/1Panel-dev/1Panel/backend/app/dto" | ||||
| 	"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/global" | ||||
| 	"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/pkg/errors" | ||||
| ) | ||||
| @@ -22,11 +28,12 @@ type BackupService struct{} | ||||
| type IBackupService interface { | ||||
| 	List() ([]dto.BackupInfo, error) | ||||
| 	SearchRecordsWithPage(search dto.RecordSearch) (int64, []dto.BackupRecords, error) | ||||
| 	LoadOneDriveInfo() (string, error) | ||||
| 	DownloadRecord(info dto.DownloadRecord) (string, error) | ||||
| 	Create(backupDto dto.BackupOperate) error | ||||
| 	GetBuckets(backupDto dto.ForBuckets) ([]interface{}, error) | ||||
| 	Update(ireq dto.BackupOperate) error | ||||
| 	BatchDelete(ids []uint) error | ||||
| 	Delete(id uint) error | ||||
| 	BatchDeleteRecord(ids []uint) error | ||||
| 	NewClient(backup *model.BackupAccount) (cloud_storage.CloudStorageClient, error) | ||||
|  | ||||
| @@ -53,36 +60,14 @@ func NewIBackupService() IBackupService { | ||||
| func (u *BackupService) List() ([]dto.BackupInfo, error) { | ||||
| 	ops, err := backupRepo.List(commonRepo.WithOrderBy("created_at desc")) | ||||
| 	var dtobas []dto.BackupInfo | ||||
| 	ossExist, s3Exist, sftpExist, minioExist := false, false, false, false | ||||
| 	for _, group := range ops { | ||||
| 		switch group.Type { | ||||
| 		case "OSS": | ||||
| 			ossExist = true | ||||
| 		case "S3": | ||||
| 			s3Exist = true | ||||
| 		case "SFTP": | ||||
| 			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"}) | ||||
| 	} | ||||
| 	dtobas = append(dtobas, u.loadByType("LOCAL", ops)) | ||||
| 	dtobas = append(dtobas, u.loadByType("OSS", ops)) | ||||
| 	dtobas = append(dtobas, u.loadByType("S3", ops)) | ||||
| 	dtobas = append(dtobas, u.loadByType("SFTP", ops)) | ||||
| 	dtobas = append(dtobas, u.loadByType("MINIO", ops)) | ||||
| 	dtobas = append(dtobas, u.loadByType("COS", ops)) | ||||
| 	dtobas = append(dtobas, u.loadByType("KODO", ops)) | ||||
| 	dtobas = append(dtobas, u.loadByType("OneDrive", ops)) | ||||
| 	return dtobas, err | ||||
| } | ||||
|  | ||||
| @@ -105,6 +90,18 @@ func (u *BackupService) SearchRecordsWithPage(search dto.RecordSearch) (int64, [ | ||||
| 	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) { | ||||
| 	if info.Source == "LOCAL" { | ||||
| 		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 { | ||||
| 		return "", err | ||||
| 	} | ||||
| 	varMap["type"] = backup.Type | ||||
| 	varMap["bucket"] = backup.Bucket | ||||
| 	switch backup.Type { | ||||
| 	case constant.Sftp: | ||||
| 		varMap["username"] = backup.AccessKey | ||||
| 		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["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 { | ||||
| 		return "", fmt.Errorf("new cloud storage client failed, err: %v", err) | ||||
| 	} | ||||
| 	tempPath := fmt.Sprintf("%sdownload%s", constant.DataDir, info.FileDir) | ||||
| 	if _, err := os.Stat(tempPath); err != nil && os.IsNotExist(err) { | ||||
| 		if err = os.MkdirAll(tempPath, os.ModePerm); err != nil { | ||||
| 			global.LOG.Errorf("mkdir %s failed, err: %v", tempPath, err) | ||||
| 	targetPath := fmt.Sprintf("%s/download/%s/%s", constant.DataDir, info.FileDir, info.FileName) | ||||
| 	if _, err := os.Stat(path.Dir(targetPath)); err != nil && os.IsNotExist(err) { | ||||
| 		if err = os.MkdirAll(path.Dir(targetPath), os.ModePerm); err != nil { | ||||
| 			global.LOG.Errorf("mkdir %s failed, err: %v", path.Dir(targetPath), err) | ||||
| 		} | ||||
| 	} | ||||
| 	targetPath := tempPath + info.FileName | ||||
| 	if _, err = os.Stat(targetPath); err != nil && os.IsNotExist(err) { | ||||
| 		isOK, err := backClient.Download(info.FileName, targetPath) | ||||
| 	srcPath := fmt.Sprintf("%s/%s", info.FileDir, info.FileName) | ||||
| 	if len(backup.BackupPath) != 0 { | ||||
| 		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 { | ||||
| 			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 { | ||||
| 		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 { | ||||
| 		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 { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	varMap["type"] = backupDto.Type | ||||
| 	switch backupDto.Type { | ||||
| 	case constant.Sftp: | ||||
| 		varMap["username"] = backupDto.AccessKey | ||||
| 		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["secretKey"] = backupDto.Credential | ||||
| 	} | ||||
| 	client, err := cloud_storage.NewCloudStorageClient(varMap) | ||||
| 	client, err := cloud_storage.NewCloudStorageClient(backupDto.Type, varMap) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	return client.ListBuckets() | ||||
| } | ||||
|  | ||||
| func (u *BackupService) BatchDelete(ids []uint) error { | ||||
| 	return backupRepo.Delete(commonRepo.WithIdsIn(ids)) | ||||
| func (u *BackupService) Delete(id uint) error { | ||||
| 	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 { | ||||
| @@ -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) | ||||
| 			} | ||||
| 		} else { | ||||
| 			backupAccount, err := backupRepo.Get(commonRepo.WithByName(record.Source)) | ||||
| 			backupAccount, err := backupRepo.Get(commonRepo.WithByType(record.Source)) | ||||
| 			if err != nil { | ||||
| 				return err | ||||
| 			} | ||||
| @@ -231,7 +243,17 @@ func (u *BackupService) Update(req dto.BackupOperate) error { | ||||
| 	upMap := make(map[string]interface{}) | ||||
| 	upMap["bucket"] = req.Bucket | ||||
| 	upMap["credential"] = req.Credential | ||||
| 	upMap["backup_path"] = req.BackupPath | ||||
| 	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 { | ||||
| 		return err | ||||
| 	} | ||||
| @@ -241,7 +263,7 @@ func (u *BackupService) Update(req dto.BackupOperate) error { | ||||
| 				if strings.HasSuffix(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})) | ||||
| 					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 { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	varMap["type"] = backup.Type | ||||
| 	if backup.Type == "LOCAL" { | ||||
| 		return nil, errors.New("not support") | ||||
| 	} | ||||
| @@ -277,12 +298,14 @@ func (u *BackupService) NewClient(backup *model.BackupAccount) (cloud_storage.Cl | ||||
| 	case constant.Sftp: | ||||
| 		varMap["username"] = backup.AccessKey | ||||
| 		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["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 { | ||||
| 		return nil, err | ||||
| 	} | ||||
| @@ -290,6 +313,66 @@ func (u *BackupService) NewClient(backup *model.BackupAccount) (cloud_storage.Cl | ||||
| 	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) { | ||||
| 	backup, err := backupRepo.Get(commonRepo.WithByType("LOCAL")) | ||||
| 	if err != nil { | ||||
| @@ -314,18 +397,33 @@ func loadLocalDir() (string, error) { | ||||
| 	return "", fmt.Errorf("error type dir: %T", varMap["dir"]) | ||||
| } | ||||
|  | ||||
| func updateBackupDir(dir, oldDir string) error { | ||||
| 	if _, err := os.Stat(dir); err != nil && os.IsNotExist(err) { | ||||
| 		if err = os.MkdirAll(dir, os.ModePerm); err != nil { | ||||
| func copyDir(src, dst string) error { | ||||
| 	srcInfo, err := os.Stat(src) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	if err = os.MkdirAll(dst, srcInfo.Mode()); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	if strings.HasSuffix(oldDir, "/") { | ||||
| 		oldDir = oldDir[:strings.LastIndex(oldDir, "/")] | ||||
| 	} | ||||
| 	stdout, err := cmd.Execf("cp -r %s/* %s", oldDir, dir) | ||||
| 	files, err := os.ReadDir(src) | ||||
| 	if err != nil { | ||||
| 		return errors.New(string(stdout)) | ||||
| 		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) | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
|   | ||||
| @@ -1,6 +1,7 @@ | ||||
| package service | ||||
|  | ||||
| import ( | ||||
| 	"context" | ||||
| 	"encoding/json" | ||||
| 	"fmt" | ||||
| 	"io/fs" | ||||
| @@ -32,6 +33,7 @@ func (u *BackupService) AppBackup(req dto.CommonBackup) error { | ||||
| 		return err | ||||
| 	} | ||||
| 	timeNow := time.Now().Format("20060102150405") | ||||
|  | ||||
| 	backupDir := fmt.Sprintf("%s/app/%s/%s", localDir, req.Name, req.DetailName) | ||||
|  | ||||
| 	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 | ||||
| 	} | ||||
|  | ||||
| 	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 { | ||||
| 		return err | ||||
| 	} | ||||
| @@ -146,7 +148,7 @@ func handleAppRecover(install *model.AppInstall, recoverFile string, isRollback | ||||
| 	if err := json.Unmarshal(appjson, &oldInstall); err != nil { | ||||
| 		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") | ||||
| 	} | ||||
|  | ||||
| @@ -170,6 +172,7 @@ func handleAppRecover(install *model.AppInstall, recoverFile string, isRollback | ||||
| 		}() | ||||
| 	} | ||||
|  | ||||
| 	newEnvFile := "" | ||||
| 	resource, _ := appInstallResourceRepo.GetFirst(appInstallResourceRepo.WithAppInstallId(install.ID)) | ||||
| 	if resource.ID != 0 && install.App.Key != "mysql" { | ||||
| 		mysqlInfo, err := appInstallRepo.LoadBaseInfo(resource.Key, "") | ||||
| @@ -180,7 +183,22 @@ func handleAppRecover(install *model.AppInstall, recoverFile string, isRollback | ||||
| 		if err != nil { | ||||
| 			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) | ||||
| 			return err | ||||
| 		} | ||||
| @@ -191,11 +209,50 @@ func handleAppRecover(install *model.AppInstall, recoverFile string, isRollback | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	oldInstall.Status = constant.Running | ||||
| 	if err := appInstallRepo.Save(install); err != nil { | ||||
| 	if len(newEnvFile) != 0 { | ||||
| 		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) | ||||
| 		return err | ||||
| 	} | ||||
| 	isOk = true | ||||
| 	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 ( | ||||
| 	"fmt" | ||||
| 	"io/ioutil" | ||||
| 	"os" | ||||
| 	"path" | ||||
| 	"strings" | ||||
| @@ -38,7 +37,7 @@ func (u *BackupService) RedisBackup() error { | ||||
| 	timeNow := time.Now().Format("20060102150405") | ||||
| 	fileName := fmt.Sprintf("%s.rdb", timeNow) | ||||
| 	if appendonly == "yes" { | ||||
| 		if redisInfo.Version == "6.0.16" { | ||||
| 		if strings.HasPrefix(redisInfo.Version, "6.") { | ||||
| 			fileName = fmt.Sprintf("%s.aof", timeNow) | ||||
| 		} else { | ||||
| 			fileName = fmt.Sprintf("%s.tar.gz", timeNow) | ||||
| @@ -121,10 +120,10 @@ func handleRedisRecover(redisInfo *repo.RootInfo, recoverFile string, isRollback | ||||
| 	} | ||||
|  | ||||
| 	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) | ||||
| 		} | ||||
| 		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) | ||||
| 		} | ||||
| 	} else { | ||||
| @@ -138,7 +137,7 @@ func handleRedisRecover(redisInfo *repo.RootInfo, recoverFile string, isRollback | ||||
| 	if !isRollback { | ||||
| 		suffix := "rdb" | ||||
| 		if appendonly == "yes" { | ||||
| 			if redisInfo.Version == "6.0.16" { | ||||
| 			if strings.HasPrefix(redisInfo.Version, "6.") { | ||||
| 				suffix = "aof" | ||||
| 			} else { | ||||
| 				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 { | ||||
| 		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) | ||||
| 		if err := handleUnTar(recoverFile, redisDataDir); err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 	} else { | ||||
| 		itemName := "dump.rdb" | ||||
| 		if appendonly == "yes" && redisInfo.Version == "6.0.16" { | ||||
| 		if appendonly == "yes" && strings.HasPrefix(redisInfo.Version, "6.") { | ||||
| 			itemName = "appendonly.aof" | ||||
| 		} | ||||
| 		input, err := ioutil.ReadFile(recoverFile) | ||||
| 		input, err := os.ReadFile(recoverFile) | ||||
| 		if err != nil { | ||||
| 			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 | ||||
| 		} | ||||
| 	} | ||||
|   | ||||
| @@ -11,6 +11,7 @@ import ( | ||||
|  | ||||
| 	"github.com/1Panel-dev/1Panel/backend/app/dto" | ||||
| 	"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/global" | ||||
| 	"github.com/1Panel-dev/1Panel/backend/utils/cmd" | ||||
| @@ -80,11 +81,11 @@ func handleWebsiteRecover(website *model.Website, recoverFile string, isRollback | ||||
|  | ||||
| 	temPathWithName := tmpPath + "/" + website.Alias | ||||
| 	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 !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 | ||||
| @@ -95,8 +96,9 @@ func handleWebsiteRecover(website *model.Website, recoverFile string, isRollback | ||||
| 	if err := json.Unmarshal(websiteJson, &oldWebsite); err != nil { | ||||
| 		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 | ||||
| @@ -155,6 +157,7 @@ func handleWebsiteRecover(website *model.Website, recoverFile string, isRollback | ||||
| 		return errors.New(string(stdout)) | ||||
| 	} | ||||
|  | ||||
| 	oldWebsite.ID = website.ID | ||||
| 	if err := websiteRepo.SaveWithoutCtx(&oldWebsite); err != nil { | ||||
| 		global.LOG.Errorf("handle save website data failed, err: %v", err) | ||||
| 		return err | ||||
| @@ -212,3 +215,29 @@ func handleWebsiteBackup(website *model.Website, backupDir, fileName string) err | ||||
|  | ||||
| 	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 ( | ||||
| 	"context" | ||||
| 	"encoding/json" | ||||
| 	"errors" | ||||
| 	"fmt" | ||||
| 	"io/ioutil" | ||||
| 	"io" | ||||
| 	"os" | ||||
| 	"os/exec" | ||||
| 	"path/filepath" | ||||
| 	"sort" | ||||
| 	"strconv" | ||||
| 	"strings" | ||||
| 	"sync" | ||||
| 	"time" | ||||
|  | ||||
| 	"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/filters" | ||||
| 	"github.com/docker/docker/api/types/network" | ||||
| 	"github.com/docker/docker/client" | ||||
| 	"github.com/docker/go-connections/nat" | ||||
| 	"github.com/gorilla/websocket" | ||||
| 	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 IContainerService interface { | ||||
| 	Page(req dto.PageContainer) (int64, interface{}, error) | ||||
| 	List() ([]string, error) | ||||
| 	PageNetwork(req dto.SearchWithPage) (int64, interface{}, error) | ||||
| 	ListNetwork() ([]dto.Options, error) | ||||
| 	PageVolume(req dto.SearchWithPage) (int64, interface{}, error) | ||||
| 	ListVolume() ([]dto.Options, error) | ||||
| 	PageCompose(req dto.SearchWithPage) (int64, interface{}, error) | ||||
| 	CreateCompose(req dto.ComposeCreate) (string, 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 | ||||
| 	ContainerLogs(param dto.ContainerLog) (string, error) | ||||
| 	ContainerStats(id string) (*dto.ContainterStats, error) | ||||
| 	ContainerLogs(wsConn *websocket.Conn, container, since, tail string, follow bool) error | ||||
| 	ContainerStats(id string) (*dto.ContainerStats, error) | ||||
| 	Inspect(req dto.InspectReq) (string, error) | ||||
| 	DeleteNetwork(req dto.BatchDelete) error | ||||
| 	CreateNetwork(req dto.NetworkCreat) error | ||||
| 	CreateNetwork(req dto.NetworkCreate) 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 { | ||||
| @@ -55,7 +72,6 @@ func (u *ContainerService) Page(req dto.PageContainer) (int64, interface{}, erro | ||||
| 	var ( | ||||
| 		records []types.Container | ||||
| 		list    []types.Container | ||||
| 		backDatas []dto.ContainerInfo | ||||
| 	) | ||||
| 	client, err := docker.NewDockerClient() | ||||
| 	if err != nil { | ||||
| @@ -71,19 +87,40 @@ func (u *ContainerService) Page(req dto.PageContainer) (int64, interface{}, erro | ||||
| 		return 0, nil, err | ||||
| 	} | ||||
| 	if len(req.Name) != 0 { | ||||
| 		lenth, count := len(list), 0 | ||||
| 		for count < lenth { | ||||
| 		length, count := len(list), 0 | ||||
| 		for count < length { | ||||
| 			if !strings.Contains(list[count].Names[0][1:], req.Name) { | ||||
| 				list = append(list[:count], list[(count+1):]...) | ||||
| 				lenth-- | ||||
| 				length-- | ||||
| 			} else { | ||||
| 				count++ | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 	switch req.OrderBy { | ||||
| 	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 | ||||
| 	if start > total { | ||||
| 		records = make([]types.Container, 0) | ||||
| @@ -94,31 +131,87 @@ func (u *ContainerService) Page(req dto.PageContainer) (int64, interface{}, erro | ||||
| 		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 | ||||
| 		if _, ok := container.Labels[composeProjectLabel]; ok { | ||||
| 		if _, ok := item.Labels[composeProjectLabel]; ok { | ||||
| 			IsFromCompose = true | ||||
| 		} | ||||
| 		IsFromApp := false | ||||
| 		if created, ok := container.Labels[composeCreatedBy]; ok && created == "Apps" { | ||||
| 		if created, ok := item.Labels[composeCreatedBy]; ok && created == "Apps" { | ||||
| 			IsFromApp = true | ||||
| 		} | ||||
| 		backDatas = append(backDatas, dto.ContainerInfo{ | ||||
| 			ContainerID:   container.ID, | ||||
| 			CreateTime:    time.Unix(container.Created, 0).Format("2006-01-02 15:04:05"), | ||||
| 			Name:          container.Names[0][1:], | ||||
| 			ImageId:       strings.Split(container.ImageID, ":")[1], | ||||
| 			ImageName:     container.Image, | ||||
| 			State:         container.State, | ||||
| 			RunTime:       container.Status, | ||||
|  | ||||
| 		var ports []string | ||||
| 		for _, port := range item.Ports { | ||||
| 			itemPortStr := fmt.Sprintf("%v/%s", port.PrivatePort, port.Type) | ||||
| 			if port.PublicPort != 0 { | ||||
| 				itemPortStr = fmt.Sprintf("%s:%v->%v/%s", port.IP, port.PublicPort, port.PrivatePort, port.Type) | ||||
| 			} | ||||
| 			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, | ||||
| 			IsFromCompose: IsFromCompose, | ||||
| 		}) | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	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) { | ||||
| 	client, err := docker.NewDockerClient() | ||||
| 	if err != nil { | ||||
| @@ -143,67 +236,262 @@ func (u *ContainerService) Inspect(req dto.InspectReq) (string, error) { | ||||
| 	return string(bytes), nil | ||||
| } | ||||
|  | ||||
| func (u *ContainerService) ContainerCreate(req dto.ContainerCreate) error { | ||||
| 	if len(req.ExposedPorts) != 0 { | ||||
| 		for _, port := range req.ExposedPorts { | ||||
| 			if common.ScanPort(port.HostPort) { | ||||
| 				return buserr.WithDetail(constant.ErrPortInUsed, port.HostPort, nil) | ||||
| func (u *ContainerService) Prune(req dto.ContainerPrune) (dto.ContainerPruneReport, error) { | ||||
| 	report := dto.ContainerPruneReport{} | ||||
| 	client, err := docker.NewDockerClient() | ||||
| 	if err != 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() | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	config := &container.Config{ | ||||
| 		Image:  req.Image, | ||||
| 		Cmd:    req.Cmd, | ||||
| 		Env:    req.Env, | ||||
| 		Labels: stringsToMap(req.Labels), | ||||
| 	} | ||||
| 	hostConf := &container.HostConfig{ | ||||
| 		AutoRemove:      req.AutoRemove, | ||||
| 		PublishAllPorts: req.PublishAllPorts, | ||||
| 		RestartPolicy:   container.RestartPolicy{Name: req.RestartPolicy}, | ||||
| 	} | ||||
| 	if req.RestartPolicy == "on-failure" { | ||||
| 		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)) | ||||
| 	ctx := context.Background() | ||||
| 	newContainer, _ := client.ContainerInspect(ctx, req.Name) | ||||
| 	if newContainer.ContainerJSONBase != nil { | ||||
| 		return buserr.New(constant.ErrContainerName) | ||||
| 	} | ||||
|  | ||||
| 	var config container.Config | ||||
| 	var hostConf container.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 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 { | ||||
| 		_ = 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 | ||||
| 	} | ||||
| 	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 { | ||||
| 		_ = client.ContainerRemove(context.Background(), req.Name, types.ContainerRemoveOptions{RemoveVolumes: true, Force: true}) | ||||
| 	if err := client.ContainerStart(ctx, container.ID, types.ContainerStartOptions{}); err != nil { | ||||
| 		_ = client.ContainerRemove(ctx, req.Name, types.ContainerRemoveOptions{RemoveVolumes: true, Force: true}) | ||||
| 		return fmt.Errorf("create successful but start failed, err: %v", err) | ||||
| 	} | ||||
| 	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 { | ||||
| 	var err error | ||||
| 	ctx := context.Background() | ||||
| @@ -216,9 +504,9 @@ func (u *ContainerService) ContainerOperation(req dto.ContainerOperation) error | ||||
| 	case constant.ContainerOpStart: | ||||
| 		err = client.ContainerStart(ctx, req.Name, types.ContainerStartOptions{}) | ||||
| 	case constant.ContainerOpStop: | ||||
| 		err = client.ContainerStop(ctx, req.Name, nil) | ||||
| 		err = client.ContainerStop(ctx, req.Name, container.StopOptions{}) | ||||
| 	case constant.ContainerOpRestart: | ||||
| 		err = client.ContainerRestart(ctx, req.Name, nil) | ||||
| 		err = client.ContainerRestart(ctx, req.Name, container.StopOptions{}) | ||||
| 	case constant.ContainerOpKill: | ||||
| 		err = client.ContainerKill(ctx, req.Name, "SIGKILL") | ||||
| 	case constant.ContainerOpPause: | ||||
| @@ -226,6 +514,10 @@ func (u *ContainerService) ContainerOperation(req dto.ContainerOperation) error | ||||
| 	case constant.ContainerOpUnpause: | ||||
| 		err = client.ContainerUnpause(ctx, req.Name) | ||||
| 	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) | ||||
| 	case constant.ContainerOpRemove: | ||||
| 		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 | ||||
| } | ||||
|  | ||||
| func (u *ContainerService) ContainerLogs(req dto.ContainerLog) (string, error) { | ||||
| 	cmd := exec.Command("docker", "logs", req.ContainerID) | ||||
| 	if req.Mode != "all" { | ||||
| 		cmd = exec.Command("docker", "logs", req.ContainerID, "--since", req.Mode) | ||||
| 	} | ||||
| 	stdout, err := cmd.CombinedOutput() | ||||
| func (u *ContainerService) ContainerLogClean(req dto.OperationWithName) error { | ||||
| 	client, err := docker.NewDockerClient() | ||||
| 	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() | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| @@ -256,18 +601,18 @@ func (u *ContainerService) ContainerStats(id string) (*dto.ContainterStats, erro | ||||
| 	} | ||||
| 	defer res.Body.Close() | ||||
|  | ||||
| 	body, err := ioutil.ReadAll(res.Body) | ||||
| 	body, err := io.ReadAll(res.Body) | ||||
| 	if err != nil { | ||||
| 		res.Body.Close() | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	res.Body.Close() | ||||
| 	var stats *types.StatsJSON | ||||
| 	if err := json.Unmarshal(body, &stats); err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	var data dto.ContainterStats | ||||
| 	previousCPU := stats.PreCPUStats.CPUUsage.TotalUsage | ||||
| 	previousSystem := stats.PreCPUStats.SystemUsage | ||||
| 	data.CPUPercent = calculateCPUPercentUnix(previousCPU, previousSystem, stats) | ||||
| 	var data dto.ContainerStats | ||||
| 	data.CPUPercent = calculateCPUPercentUnix(stats) | ||||
| 	data.IORead, data.IOWrite = calculateBlockIO(stats.BlkioStats) | ||||
| 	data.Memory = float64(stats.MemoryStats.Usage) / 1024 / 1024 | ||||
| 	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 { | ||||
| 	var lableMap = make(map[string]string) | ||||
| 	for _, label := range list { | ||||
| 		sps := strings.Split(label, "=") | ||||
| 		if len(sps) > 1 { | ||||
| 		if strings.Contains(label, "=") { | ||||
| 			sps := strings.SplitN(label, "=", 2) | ||||
| 			lableMap[sps[0]] = sps[1] | ||||
| 		} | ||||
| 	} | ||||
| 	return lableMap | ||||
| } | ||||
| func calculateCPUPercentUnix(previousCPU, previousSystem uint64, v *types.StatsJSON) float64 { | ||||
| 	var ( | ||||
| 		cpuPercent  = 0.0 | ||||
| 		cpuDelta    = float64(v.CPUStats.CPUUsage.TotalUsage) - float64(previousCPU) | ||||
| 		systemDelta = float64(v.CPUStats.SystemUsage) - float64(previousSystem) | ||||
| 	) | ||||
|  | ||||
| func calculateCPUPercentUnix(stats *types.StatsJSON) float64 { | ||||
| 	cpuPercent := 0.0 | ||||
| 	cpuDelta := float64(stats.CPUStats.CPUUsage.TotalUsage) - float64(stats.PreCPUStats.CPUUsage.TotalUsage) | ||||
| 	systemDelta := float64(stats.CPUStats.SystemUsage) - float64(stats.PreCPUStats.SystemUsage) | ||||
|  | ||||
| 	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 | ||||
| } | ||||
| 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) { | ||||
| 	for _, bioEntry := range blkio.IoServiceBytesRecursive { | ||||
| 		switch strings.ToLower(bioEntry.Op) { | ||||
| @@ -321,3 +677,137 @@ func calculateNetwork(network map[string]types.NetworkStats) (float64, float64) | ||||
| 	} | ||||
| 	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" | ||||
| 	"errors" | ||||
| 	"fmt" | ||||
| 	"io" | ||||
| 	"os" | ||||
| 	"os/exec" | ||||
| 	"path" | ||||
| @@ -91,7 +92,7 @@ func (u *ContainerService) PageCompose(req dto.SearchWithPage) (int64, interface | ||||
| 		} | ||||
| 	} | ||||
| 	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) | ||||
| 		} | ||||
| 	} | ||||
| @@ -100,11 +101,11 @@ func (u *ContainerService) PageCompose(req dto.SearchWithPage) (int64, interface | ||||
| 		records = append(records, value) | ||||
| 	} | ||||
| 	if len(req.Info) != 0 { | ||||
| 		lenth, count := len(records), 0 | ||||
| 		for count < lenth { | ||||
| 		length, count := len(records), 0 | ||||
| 		for count < length { | ||||
| 			if !strings.Contains(records[count].Name, req.Info) { | ||||
| 				records = append(records[:count], records[(count+1):]...) | ||||
| 				lenth-- | ||||
| 				length-- | ||||
| 			} else { | ||||
| 				count++ | ||||
| 			} | ||||
| @@ -126,6 +127,10 @@ func (u *ContainerService) PageCompose(req dto.SearchWithPage) (int64, interface | ||||
| } | ||||
|  | ||||
| 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 { | ||||
| 		return false, err | ||||
| 	} | ||||
| @@ -154,9 +159,10 @@ func (u *ContainerService) CreateCompose(req dto.ComposeCreate) (string, error) | ||||
| 	go func() { | ||||
| 		defer file.Close() | ||||
| 		cmd := exec.Command("docker-compose", "-f", req.Path, "up", "-d") | ||||
| 		stdout, err := cmd.CombinedOutput() | ||||
| 		_, _ = file.Write(stdout) | ||||
| 		if err != nil { | ||||
| 		multiWriter := io.MultiWriter(os.Stdout, file) | ||||
| 		cmd.Stdout = multiWriter | ||||
| 		cmd.Stderr = multiWriter | ||||
| 		if err := cmd.Run(); err != nil { | ||||
| 			global.LOG.Errorf("docker-compose up %s failed, err: %v", req.Name, err) | ||||
| 			_, _ = compose.Down(req.Path) | ||||
| 			_, _ = 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) | ||||
| 	if req.Operation == "down" { | ||||
| 		_ = 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 | ||||
| @@ -211,15 +219,7 @@ func (u *ContainerService) ComposeUpdate(req dto.ComposeUpdate) error { | ||||
| } | ||||
|  | ||||
| func (u *ContainerService) loadPath(req *dto.ComposeCreate) error { | ||||
| 	if req.From == "template" { | ||||
| 		template, err := composeRepo.Get(commonRepo.WithByID(req.Template)) | ||||
| 		if err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 		req.From = "edit" | ||||
| 		req.File = template.Content | ||||
| 	} | ||||
| 	if req.From == "edit" { | ||||
| 	if req.From == "template" || req.From == "edit" { | ||||
| 		dir := fmt.Sprintf("%s/docker/compose/%s", constant.DataDir, req.Name) | ||||
| 		if _, err := os.Stat(dir); err != nil && os.IsNotExist(err) { | ||||
| 			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 | ||||
| 	} | ||||
| 	if len(req.Info) != 0 { | ||||
| 		lenth, count := len(list), 0 | ||||
| 		for count < lenth { | ||||
| 		length, count := len(list), 0 | ||||
| 		for count < length { | ||||
| 			if !strings.Contains(list[count].Name, req.Info) { | ||||
| 				list = append(list[:count], list[(count+1):]...) | ||||
| 				lenth-- | ||||
| 				length-- | ||||
| 			} else { | ||||
| 				count++ | ||||
| 			} | ||||
| @@ -75,6 +75,26 @@ func (u *ContainerService) PageNetwork(req dto.SearchWithPage) (int64, interface | ||||
|  | ||||
| 	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 { | ||||
| 	client, err := docker.NewDockerClient() | ||||
| 	if err != nil { | ||||
| @@ -90,7 +110,7 @@ func (u *ContainerService) DeleteNetwork(req dto.BatchDelete) error { | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
| func (u *ContainerService) CreateNetwork(req dto.NetworkCreat) error { | ||||
| func (u *ContainerService) CreateNetwork(req dto.NetworkCreate) error { | ||||
| 	client, err := docker.NewDockerClient() | ||||
| 	if err != nil { | ||||
| 		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