Compare commits

...
208 Commits
Author SHA1 Message Date
蘭 725d03b41e fix: Fix the issue of generating Swagger documents with actual parameters (#10379) 2025-09-15 16:36:41 +08:00
CityFun 7e8581a7de feat: The certificate list supports sorting by expiration date. (#10372)
Refs https://github.com/1Panel-dev/1Panel/issues/10371
2025-09-15 05:37:27 +00:00
蘭 0ba83d1353 ref: Update swagger api docs (#10351) 2025-09-12 08:24:24 +00:00
CityFun a302e100ec fix: Fixed issue with add large black ip to fail2ban failed (#10348)
Refs https://github.com/1Panel-dev/1Panel/issues/10346
2025-09-12 08:20:25 +00:00
蘭 4f58fd492d ref: Update swagger api docs (#10343)
#10342
2025-09-11 14:03:31 +00:00
CityFun 0b06a4fac6 feat: Adapt for directly mounting an unpartitioned disk (#10341) 2025-09-11 09:05:30 +00:00
CityFun 4e1a7bf42c feat: optimization disk zie display (#10339) 2025-09-11 15:12:45 +08:00
wanghe-fit2cloud ef2fb1c62e chore: Modify prompt msg 2025-09-11 14:02:13 +08:00
蘭 f5fcbcd87a fix: Fix the decompression issue of TGZ files (#10338) 2025-09-11 05:41:29 +00:00
CityFun 986bffaf2b feat: add process detail for supervisor process (#10336)
Refs https://github.com/1Panel-dev/1Panel/issues/5172
2025-09-11 03:47:30 +00:00
CityFun 766a2a0546 feat: optimization disk zie display (#10337) 2025-09-11 11:46:16 +08:00
蘭 291b594ad9 fix: Fix file decompression issue (#10333) 2025-09-10 14:23:25 +00:00
CityFun 7742e3da38 fix: Fixed issue with mcp externalUrl config failed (#10331)
Refs https://github.com/1Panel-dev/1Panel/issues/10323
2025-09-10 09:06:51 +00:00
ssongliu c5f70eae4c feat: Add debounce effect to file selection (#10330) 2025-09-10 09:04:52 +00:00
CityFun 8bdf02c4c0 perf: Optimize process management performance (#10329) 2025-09-10 09:02:53 +00:00
蘭 8abe132c25 style: Optimize log background color (#10327)
#10203
2025-09-10 09:00:52 +00:00
CityFun 3a447b02a1 feat: Adjust hard drive mounting logic (#10326) 2025-09-10 05:50:51 +00:00
ssongliu aa718eef88 fix: Fix the issue of terminal key connection exception (#10325) 2025-09-10 03:32:51 +00:00
CityFun 60b1c9ba32 feat: Adjust disk management menu order (#10324) 2025-09-10 03:30:52 +00:00
蘭 9b50581fd4 fix: Fix alert issue remove some alert logs (#10322) 2025-09-10 03:28:50 +00:00
蘭 ee33fdab89 fix: Fix file decompression issue (#10320) 2025-09-09 11:14:44 +00:00
CityFun c8f19467ba fix: Fixed issue with push node not work when create ssl (#10319) 2025-09-09 09:56:45 +00:00
ssongliu 1fc0f1c71b fix: Fix the issue of terminal key connection exception (#10318) 2025-09-09 09:54:45 +00:00
CityFun 2258ba108d feat: add rsync check for file exchange (#10317) 2025-09-09 08:38:44 +00:00
ssongliu d21f4ffae6 style: Add fullscreen support for some drawers (#10316)
Refs #10312
2025-09-09 08:02:43 +00:00
ssongliu 4bd9c4c0fc fix: Fix snapshot interface timeout configuration issue (#10315) 2025-09-09 07:18:43 +00:00
CityFun 29165f0f6f perf: Optimize the display of HTML-type logs (#10311)
Refs https://github.com/1Panel-dev/1Panel/issues/10303
2025-09-09 06:52:44 +00:00
ssongliu f75b786916 fix: Add backward compatibility for overview page api (#10310) 2025-09-09 06:50:44 +00:00
CityFun 011914339e feat: Extend Openresty build time (#10309)
Refs https://github.com/1Panel-dev/1Panel/issues/10289
2025-09-09 06:48:43 +00:00
ssongliu 08a07214ef fix: Optimize backup logic for cronjob (#10308) 2025-09-09 04:20:43 +00:00
ssongliu f682f07dbc fix: Optimize duplicate execution error messages for cronjob (#10307) 2025-09-09 03:44:43 +00:00
CityFun 50e99f1f4c fix: Fixed issue with can not disable cache in antiLeech (#10306) 2025-09-09 03:22:43 +00:00
蘭 feabc488fe feat: SMTP supports login authentication (#10304) 2025-09-09 03:20:44 +00:00
wanghe-fit2cloud 72d3f5071b chore: Upgrade Node version 2025-09-09 11:19:05 +08:00
CityFun 27dc662430 fix: Fixed issue with build frontend failed (#10305) 2025-09-09 03:00:43 +00:00
ssongliu 80e233c4a6 fix: Fix terminal connection exception issues (#10301) 2025-09-08 14:08:42 +00:00
ssongliu a099308a82 fix: Solve the problem of internationalization of operation logs (#10300)
Refs #10273
2025-09-08 10:02:34 +00:00
ssongliu 6f0673f184 fix: Fix the problem of uploaded backup files being deleted after res… (#10299)
…toration
2025-09-08 09:57:46 +00:00
CityFun b428e699db feat: Disk Management Compatible with Lower Version Operating Systems (#10298) 2025-09-08 17:04:07 +08:00
蘭 f4334eadc0 ref: The default display quantity for the list is 20 (#10297) 2025-09-08 17:03:50 +08:00
ssongliu a2a6503030 fix: Refactor terminal connection logic (#10283)
Refs #9617
2025-09-05 09:39:36 +00:00
CityFunandwanghe-fit2cloud 9c22005b79 feat: Add disk management (#10282)
Co-authored-by: wanghe-fit2cloud <wanghe@fit2cloud.com>
2025-09-05 17:15:58 +08:00
蘭 7448953b7d fix: Fix alert issue (#10281) 2025-09-05 08:41:35 +00:00
蘭 18214095ab fix: Fix the issue where the task did not stop after deleting the alert (#10280) 2025-09-05 07:55:36 +00:00
ssongliu bcfc78e510 fix: Optimize the trigger method for table selection (#10268)
Refs #10262
2025-09-04 10:23:32 +00:00
ssongliu cc6b506176 fix: Solve the problem of abnormal status judgment in docker (#10266)
Refs #10258
2025-09-04 09:15:32 +00:00
ssongliu c61828fb39 fix: Adjust the display logic of the clam scan report (#10264)
Refs #10251
2025-09-04 09:13:32 +00:00
CityFun 23876ed186 feat: Mcp Server support uvx (#10261)
Refs https://github.com/1Panel-dev/1Panel/issues/10118
2025-09-04 07:21:33 +00:00
KOMATA ac4962cf19 fix: Remove duplicate domains in the domain list initialization (#10260) 2025-09-04 15:20:38 +08:00
CityFun d9e7c8d880 fix: Resolve the issue where the page does not display correctly after enabling browser caching. (#10257)
Refs https://github.com/1Panel-dev/1Panel/issues/10245
2025-09-04 07:19:31 +00:00
ssongliu ea25b800d2 fix: Add Docker restart option to firewall operations (#10253)
Refs #10247
2025-09-03 09:29:30 +00:00
CityFun 7ba06e73d7 feat: support closing log dialog with ESC key and outside click (#10252)
Refs https://github.com/1Panel-dev/1Panel/issues/10240
2025-09-03 09:27:30 +00:00
CityFun 39cf26628f feat: Optimize log file reading (#10249) 2025-09-03 09:25:30 +00:00
ssongliu ea7f324a9d style: Optimize version update changelog display (#10248)
Refs #10242
2025-09-03 06:49:28 +00:00
ssongliu 789d451ea2 feat: Add ZIP support for database backup uploads (#10246)
Refs #7179
2025-09-03 05:59:28 +00:00
CityFun 7313f3d591 fix: Resolve the issue of still receiving update prompts after ignoring the application. (#10244)
Refs https://github.com/1Panel-dev/1Panel/issues/10241
2025-09-03 05:57:28 +00:00
ssongliu 1662546bef fix: Fix container image upgrade command loss issue (#10239)
Refs #10227
2025-09-02 10:48:10 +00:00
ssongliu 53f75086ec feat: Optimize quick navigation for dashboard (#10238)
Refs #5167
2025-09-02 09:58:10 +00:00
ssongliu 81fcc61ec9 feat: Add local file selection for import/restore operations (#10234) 2025-09-02 17:55:46 +08:00
ssongliu 324a47966b fix: Optimize duplicate execution prevention for cronjob (#10233)
Refs #4973
2025-09-02 09:54:10 +00:00
ssongliu 5a0c50b9a2 style: Optimize volume list display (#10231)
Refs #6601
2025-09-02 09:48:10 +00:00
ssongliu 66465beb39 fix: Optimize tamper-protected File deletion warning messages (#10230) 2025-09-02 17:45:57 +08:00
CityFun c5d403f10a feat: Add pagination cache (#10229) 2025-09-02 15:23:27 +08:00
蘭 598098c2ed feat: Login page username automatically obtains focus (#10228) 2025-09-02 15:12:08 +08:00
ssongliu bff1a8ad60 fix: Optimize warning messages for database deletion (#10226) 2025-09-02 05:43:44 +00:00
蘭 592e315f21 refactor: Adjust the default page size for the file management list (#10225) 2025-09-02 05:41:45 +00:00
CityFun 76933bc6ef feat: Modify the configuration file to support tabs (#10224) 2025-09-02 05:39:45 +00:00
KOMATA 0dc312db64 fix: Fix typo of "Redis" in language files (#10222) 2025-09-02 10:21:51 +08:00
ssongliu 4deb92e715 fix: Optimize container port detection mechanism (#10220) 2025-09-01 10:14:49 +00:00
CityFun bc95e47b92 fix: Fixed the issue with The child node cannot update the certificate (#10219) 2025-09-01 17:39:56 +08:00
CityFun f2335d313c feat: Optimize file log reading (#10218) 2025-09-01 17:39:40 +08:00
蘭 4c287f4a2d feat: Add date to smtp service header (#10215) 2025-09-01 06:42:47 +00:00
CityFun 0a6e1c275b feat: Restrict sqlbot version upgrades (#10214) 2025-09-01 06:26:48 +00:00
ssongliu ff2dc05e7f fix: Optimize container port detection mechanism (#10209)
Refs #5827
2025-09-01 06:24:48 +00:00
蘭 f62ce085bf feat: MFA verification code automatically obtains focus (#10213)
refs #8032
2025-09-01 06:20:48 +00:00
CityFun 29306827ba feat: App Store - Installed: Pagination Cache Added (#10212) 2025-09-01 06:08:48 +00:00
ssongliu 222d07175a fix: Fix version upgrade information display issues (#10208)
Refs #10196
2025-09-01 06:00:48 +00:00
CityFun 663196c3d9 fix: Resolve the issue of failed push certificate to all nodes. (#10195) 2025-08-29 07:46:15 +00:00
蘭 df839714a3 fix: Fix issues with pasting files not loading and file retrieval path cursor (#10194) 2025-08-29 07:44:15 +00:00
CityFun dcb004e983 fix: Fixed issue with build frontend failed (#10193) 2025-08-29 06:20:15 +00:00
CityFun cee0604efc perf: Optimize front-end component imports (#10192) 2025-08-29 06:04:14 +00:00
蘭 dfbd364f60 fix: Fix the initialization issue of alert tasks (#10191) 2025-08-29 05:56:15 +00:00
CityFun 339d6732d7 fix: fixed issue with push ssl to node failed (#10188) 2025-08-29 05:54:16 +00:00
蘭 b0dbfb4c7c fix: Fix page loading flicker issue (#10187) 2025-08-29 05:52:17 +00:00
ssongliu 021603d25d fix: Optimize container compose deletion logic (#10186)
Refs #10184
2025-08-29 05:50:15 +00:00
CityFun a875e1ec5c fix: Resolving the Issue of Website Directory Not Migrated During OpenResty Reinstallation (#10185) 2025-08-29 13:48:20 +08:00
CityFun 5c5904216b feat: remove console (#10181) 2025-08-28 18:57:06 +08:00
蘭 1dae294d76 fix: Fix folder redirection issue (#10180) 2025-08-28 10:56:09 +00:00
ssongliu acbb151b15 fix: Update authorization IP regular expression (#10179)
Refs #10173
2025-08-28 10:52:10 +00:00
CityFun a1311a2a0d perf: optimization file component (#10178) 2025-08-28 18:50:53 +08:00
蘭 bc8d564ce8 fix: Fix some bugs in the alert (#10176) 2025-08-28 07:58:10 +00:00
CityFun 97cdf7bc78 feat: Optimized the runtime environment installation, but the application store still does not display it as installed. (#10175)
Refs https://github.com/1Panel-dev/1Panel/issues/10174
2025-08-28 07:56:08 +00:00
蘭 ce69b4de01 fix: Fix bug in file management with multiple tabs (#10170) 2025-08-28 04:10:09 +00:00
ssongliu e880df8142 fix: Fix snapshot cronjob execution issues (#10169) 2025-08-28 04:08:09 +00:00
ssongliu a35ca87778 fix: Remove deprecated google drive connection information (#10168) 2025-08-28 04:06:08 +00:00
ssongliu 5a34e2d380 fix: Optimize backup logic for cronjob (#10163) 2025-08-27 14:35:28 +08:00
蘭 abf3247c50 fix: Resolve file renaming and other operational conflicts (#10162) 2025-08-27 05:44:03 +00:00
ssongliu 8663ed129d fix: Fix image sorting anomalies (#10159) 2025-08-27 13:41:37 +08:00
cui c0a0f4c801 refactor: use maps (#10154) 2025-08-27 10:08:51 +08:00
KOMATA e84aa49003 feat: Enhance anti-leech configuration with new options and improved UI (#10149)
* feat: enhance anti-leech configuration with new options and improved UI

* fix: remove redundant code
2025-08-27 10:08:38 +08:00
ssongliu 6f45b90520 fix: Fix container resource cleanup result tracking (#10153) 2025-08-26 14:06:01 +00:00
蘭 0685984a80 ref: Adjust the content of alert information (#10152) 2025-08-26 14:04:01 +00:00
ssongliu 5f33c083bf style: Adjust compose log display height (#10151) 2025-08-26 09:41:59 +00:00
CityFun ab0e0f3f43 feat: Add restart runtime for config php (#10150) 2025-08-26 09:13:59 +00:00
蘭 6b62b84157 feat: File management supports multi tab management (#10147) 2025-08-26 09:10:00 +00:00
CityFun fd9627576b feat: Add table page size cache (#10146) 2025-08-26 17:08:13 +08:00
ssongliu 39f334f3a2 fix: Optimize backup logic for cronjob (#10145) 2025-08-26 17:07:41 +08:00
CityFun ca84909716 feat: confirm before leaving during file upload (#10143) 2025-08-26 06:19:58 +00:00
CityFun 283609cf2d style: change appstore tag style (#10142) 2025-08-26 06:13:59 +00:00
CityFun 9591409fe2 optimization: optimization the log file read (#10141) 2025-08-26 06:11:59 +00:00
CityFun 9cf52ccc97 fix: incorrect restart strategy display in app editor (#10136) 2025-08-25 18:56:58 +08:00
CityFun 261cb2061b feat: add NoLog prompt for log component (#10135) 2025-08-25 18:56:43 +08:00
CityFun f45af6e0cc style: change appstore tag style (#10134) 2025-08-25 10:55:57 +00:00
ssongliu c752ea4959 fix: Add gpu monitoring support for some machines (#10133)
Refs #9504 #8397
2025-08-25 10:53:57 +00:00
CityFun b2b578a5f0 feat: add timeout for app upgrade (#10132) 2025-08-25 10:51:56 +00:00
CityFun 47e60bfdcb refactor: reverse proxy rules (#10131) 2025-08-25 08:15:57 +00:00
ssongliu 780b6cdec9 fix: Optimize decryption implementation (#10130) 2025-08-25 08:13:56 +00:00
ssongliu 0a5b13a418 feat: Add restart support for container compose (#10129) 2025-08-25 08:11:56 +00:00
ssongliu ebdc3f05de fix: Optimize application recommendation logic (#10128) 2025-08-25 08:09:56 +00:00
CityFun aad79077bf feat: add restart button for php supervisor (#10127)
Refs https://github.com/1Panel-dev/1Panel/issues/10083
2025-08-25 08:07:57 +00:00
ssongliu 00b25b39c5 fix: Fix backup record loading timeout issue (#10126) 2025-08-25 08:05:57 +00:00
KOMATA dd6a709053 fix: resolve null destination when enabling AntiLeech for deployment mode websites (#10125)
* fix: Add proxy_pass directive for deployment model in UpdateAntiLeech

* fix: Update UpdateAntiLeech to use constant for deployment type
2025-08-25 16:00:35 +08:00
ssongliu 99b04e3635 fix: Fix SSH log export exception when logs are empty (#10114) 2025-08-22 14:37:52 +00:00
ssongliu 2e06b78409 style: Optimize cache cleaning interface styling (#10113) 2025-08-22 18:29:47 +08:00
CityFun c7d603e142 fix: resolve log loss in certain scenarios (#10111) 2025-08-22 09:15:52 +00:00
ssongliu 4fcf845934 feat: Add system information copy button for dashboard (#10109)
Refs #9725
2025-08-22 07:59:52 +00:00
CityFun 83cefb2a72 feat: improve empty state for app store (#10108) 2025-08-22 07:57:52 +00:00
ssongliu 7ab40996e0 style: Optimize version update log styling (#10107) 2025-08-22 07:55:52 +00:00
ssongliu bbe4d49947 feat: Add sorting support to image list (#10106)
Refs #9482
2025-08-22 07:53:52 +00:00
KOMATA b6fbacc992 fix: Optimized proxy configure drawer content (#10097)
* fix: Update proxy settings interface, add tabs and optimize style

* fix: update i18n text

* fix: reset modified data

* fix: Optimize proxy settings interface, remove unnecessary options and adjust SNI settings display logic

* fix: Optimize proxy settings interface, replace custom style tag to tailwind.css class

* fix: Update i18n text
2025-08-22 15:46:49 +08:00
ssongliu 89491eac56 fix: Optimize container image operation task logging (#10103)
Refs #9999
2025-08-22 03:23:50 +00:00
CityFun acf27693b2 feat: Add description field matching to application search (#10101)
Refs https://github.com/1Panel-dev/1Panel/issues/10071
2025-08-22 03:07:51 +00:00
ssongliu 5fd9d20f99 fix: Exclude system ip from snapshot backups (#10100)
Refs  #9991
2025-08-22 03:05:50 +00:00
ssongliu fd9c421969 fix: Optimize image push logic (#10096)
Refs #10030
2025-08-21 10:33:49 +00:00
ssongliu f5bae6d75c fix: Add service health check to node editing (#10095)
Refs #9935
2025-08-21 09:29:49 +00:00
CityFun 4ac8bc7bd2 feat: Optimize log file highlighting (#10093) 2025-08-21 17:26:52 +08:00
蘭 ec81d0d2c3 feat: Supports inline editing of file names (#10080) 2025-08-21 02:30:59 +00:00
CityFun 815b633fb8 fix: Fixed issue with website proxy proxy_ssl_name display error (#10079) 2025-08-21 10:18:48 +08:00
ssongliu c69c76f97d fix: Fix container image deletion warning anomalies (#10078) 2025-08-20 16:44:57 +08:00
ssongliu 75197db3e6 fix: Fix partial WebDAV upload failures (#10077) 2025-08-20 16:44:43 +08:00
CityFun d09d686378 feat: Support pushing certificates to other nodes (#10074)
Refs https://github.com/1Panel-dev/1Panel/issues/9103
2025-08-20 08:41:19 +00:00
蘭 d6f16cf700 feat: Support table row selection (#10072) 2025-08-20 08:39:17 +00:00
蘭 536a5fc1a0 feat: Optimize code (#10063) 2025-08-19 14:49:17 +00:00
蘭 a07df9d7fe feat: Optimize log file highlighting (#10062) 2025-08-19 14:47:15 +00:00
ssongliu e9c195c778 fix: Fix report display issues for cronjob (#10058) 2025-08-19 09:33:15 +00:00
蘭 0637ca308b feat: File list supports right menu operation (#10056) 2025-08-19 09:31:15 +00:00
ssongliu fd1e0e72a0 feat: Add default value support for file selection component (#10055) 2025-08-19 09:29:15 +00:00
ssongliu 34b80373c7 fix: Optimize cache cleanup logging (#10053)
Refs #10045
2025-08-19 06:59:15 +00:00
ssongliu 1ade1b8d8b fix: Fix temporary file leak during snapshot deletion (#10052) 2025-08-19 14:58:18 +08:00
ssongliu 19fa30fb96 fix: fix slow log state toggle exception (#10050)
Refs #10007
2025-08-19 06:13:14 +00:00
蘭 efe7815a7e feat: File list supports right-click operation (#10049)
#5620
2025-08-19 05:45:14 +00:00
CityFun e9b4ff1932 feat: Optimize App Store Tag Selection (#10047) 2025-08-19 05:43:15 +00:00
蘭 0dca3fc997 feat(alert): Support panel login/ssh login/license exception and node exception alerts (#10034)
#9635
2025-08-19 05:41:15 +00:00
CityFun 4e6ebb53ac feat: Change Website List Order Rule (#10036)
Refs https://github.com/1Panel-dev/1Panel/issues/9979
2025-08-18 10:35:13 +00:00
CityFun 83ed835c9e fix: Resolve the "No Port Specified" Error When Creating a Website Runtime Environment (#10041) 2025-08-18 18:34:24 +08:00
CityFun 7bcc897379 fix: Fixed issue with child nodes cannot enable "HTTPS Anti-Site Hijacking" (#10032)
Refs https://github.com/1Panel-dev/1Panel/issues/10031
2025-08-18 02:57:13 +00:00
CityFun cd2109fefe feat:Upgrade OpenResty and rebuild image (#10016) 2025-08-18 10:56:22 +08:00
CityFun d337c4726d feat: Add empty state prompt for app list (#10014) 2025-08-15 09:41:03 +00:00
CityFun 356e6a4ef6 feat: Add import/export for URL/UA allow/deny lists (#10012)
Refs https://github.com/1Panel-dev/1Panel/issues/9408
2025-08-15 09:39:03 +00:00
蘭 037da3a7fd feat: Support file management page drag and drop upload (#10011)
#9307
2025-08-15 09:37:04 +00:00
ssongliu 18462296ae fix: Fix 1pctl user-info retrieval exception (#10010)
Refs #10006
2025-08-15 09:35:04 +00:00
苦苦的码人 1d76e54b0d fix:Asynchronous log writing has not taken effect (#10000)
Make it effective by modifying the queue
2025-08-15 17:34:17 +08:00
CityFun 22c903f555 feat: Add import/export for WAF IP groups (#10008) 2025-08-15 14:26:49 +08:00
ssongliu 6d4d53e2c7 feat: Add historical release notes display (#10002)
Refs #9662
2025-08-14 14:34:54 +00:00
CityFun 6b81ae1b7b feat: Website Add Composer support (#9997)
Refs https://github.com/1Panel-dev/1Panel/issues/6030
2025-08-14 09:46:54 +00:00
ssongliu 5b62a0f2b3 feat: Add file verification for SSH file transfers (#9998) 2025-08-14 09:26:55 +00:00
ssongliu 137465683a fix: Optimize license error messaging (#9996) 2025-08-14 09:24:53 +00:00
ssongliu 6be7b4a75a fix: Fix terminal connection initialization exception (#9987) 2025-08-13 13:46:52 +00:00
ssongliu 58ca377418 feat: Optimize file selection component (#9983) 2025-08-13 09:56:51 +00:00
蘭 b173e808c7 fix: Fix file hiding display issue (#9981)
#9871
2025-08-13 08:40:50 +00:00
CityFun ec44ba9335 fix: Fix the issue of system certificates not auto-renewing. (#9978) 2025-08-13 16:14:07 +08:00
ssongliu f54ac59ff7 fix: Modify domain binding and authorization IP priority logic (#9977) 2025-08-13 15:40:42 +08:00
蘭 db44204c1a fix: Fix the display issue in the mobile task center (#9976) 2025-08-13 15:40:27 +08:00
蘭 ec01aef71a fix: Fix .tgz file decompression exception issue (#9974)
#9927
2025-08-13 07:38:51 +00:00
CityFun b706774b5b feat: Add quick jump to website directory button (#9973)
Refs https://github.com/1Panel-dev/1Panel/issues/3945
2025-08-13 07:36:50 +00:00
Snrat 9eee2d6b6f 添加启用HSTS子域名选项 (#9971)
* Add HSTS Subdomains

* Fix Prettier Error
2025-08-13 09:55:48 +08:00
KOMATA c9d4e2a4b5 fix: Optimize Nginx configure settings (#9962)
* fix: remove unnecessary proxy header settings to simplify default Nginx config

* fix: refactor directive appending to use AppendDirectives method in Block

* fix: migrate proxy header setting to UpdateRootProxy
2025-08-13 09:53:44 +08:00
CityFun 4c4df5cbb0 feat: Modify the regular expression for website alias (#9969) 2025-08-12 10:42:48 +00:00
ssongliu cb69128838 feat: Optimize compose navigation logic (#9968)
Refs #9959
2025-08-12 10:40:48 +00:00
ssongliu 81af207dc5 feat: Add right-click context menu support for table component (#9967) 2025-08-12 10:38:49 +00:00
CityFun 5e9c43fc71 style: Download progress adapted for dark mode (#9965)
Refs https://github.com/1Panel-dev/1Panel/issues/9926
2025-08-12 09:30:49 +00:00
CityFun fb44e3c1e2 fix: Fix the issue where SSH sessions keep loading indefinitely (#9964)
Refs https://github.com/1Panel-dev/1Panel/issues/9943
2025-08-12 09:28:48 +00:00
CityFun 7e23ae126a feat: Exit file upload with added prompt (#9963)
Refs https://github.com/1Panel-dev/1Panel/issues/9736
2025-08-12 07:40:47 +00:00
ssongliu 7e229c05e7 fix: Fix clamdscan timeout issues (#9961)
Refs #9626
2025-08-12 03:20:45 +00:00
CityFun 764fc8fec4 fix: Fixed the issue where IPv6 would turn off after favoriting a website. (#9946)
Refs https://github.com/1Panel-dev/1Panel/issues/9945
2025-08-12 02:20:48 +00:00
ssongliu ad17918c84 fix: Add custom execution cycle hint for cronjob (#9952) 2025-08-12 10:20:03 +08:00
ssongliu d7013727b9 fix: Add ctrl+c support in container terminal (#9951)
Refs #9125
2025-08-11 10:30:44 +00:00
ssongliu 6a8bd11fc9 feat: Add container orchestration navigation in app store (#9950)
Refs #8431
2025-08-11 10:28:44 +00:00
ssongliu 4df0c3e461 feat: Optimize node synchronization logic (#9947)
Refs #9691
2025-08-11 10:26:45 +00:00
CityFun f8f47b51e9 feat: Configure restart policy for application installation support (#9953)
Refs https://github.com/1Panel-dev/1Panel/issues/9895
2025-08-11 10:16:44 +00:00
CityFun b735178fcd feat: Restrict special characters in website aliases (#9944)
Refs https://github.com/1Panel-dev/1Panel/issues/9909
2025-08-11 10:14:44 +00:00
chiichen 1759c2c81e feat: support riscv64 (#9925)
* feat: add riscv64

* feat: correct arch related codes
2025-08-09 18:00:56 +08:00
wangdage12andfanbook-wangdage 2be7054b40 fix: Add margin to "View comparison" button and fix "Use custom version" button color issue (#9905)
* Tweak button spacing and fix color issue on App upgrade comparison pages

* update button styles and replace inline margin with utility class

---------

Co-authored-by: fanbook-wangdage <124357765+fanbook-wangdage@users.noreply.github.com>
2025-08-09 17:57:32 +08:00
CityFun c77d2f7dd6 fix: Fixed issue with create supervisorConfig failed in php runtime (#9919) 2025-08-08 20:29:34 +08:00
CityFun 3256ac9f44 feat: Remove database and add website association prompt (#9918) 2025-08-08 20:29:13 +08:00
ssongliu b156ef9476 feat: Support batch upgrades for container images (#9915) 2025-08-08 20:29:00 +08:00
蘭 c49c16cbb5 fix: Fix errors in creating cron job and clam alerts (#9910) 2025-08-08 20:28:35 +08:00
CityFun 2b7ec9ffe1 feat: Reverse proxy supports configuring server caching (#9914) 2025-08-08 16:59:49 +08:00
CityFun c2ad006a19 fix: Resolve the issue of missing tags in the synchronized app store (#9913) 2025-08-08 16:59:35 +08:00
ssongliu c12bab477c fix: Optimize toolbox cache cleaning functionality (#9902)
Refs #6550 #6582
2025-08-07 13:58:39 +00:00
CityFun fe39545cf6 feat: add fpm status for PHP runtime (#9891) 2025-08-07 13:56:40 +00:00
KOMATA 39d3b819fb fix: improve location retrieval in OperateProxy to handle modified nginx configs (#9901) 2025-08-07 21:49:57 +08:00
CityFun 04a0e15899 feat: Add a copy button to application parameters (#9892)
Refs https://github.com/1Panel-dev/1Panel/issues/2998
2025-08-07 13:44:38 +00:00
ssongliu 3dc81d85e9 feat: Optimize backup filename format for cronjob (#9890)
Refs #4673
2025-08-07 13:42:38 +00:00
ssongliu 5a8ddde495 feat: Support export for ssh logs (#9889)
Refs #3496
2025-08-07 13:40:39 +00:00
CityFun 4e6f872c02 feat: add root config for proxy website (#9881) 2025-08-07 21:39:46 +08:00
CityFun e76836f394 feat: Add concurrency configuration for PHP performance optimization (#9877) 2025-08-07 21:39:33 +08:00
CityFun 9fed5f4bb8 fix: Fixed issue with database locked (#9898)
Refs https://github.com/1Panel-dev/1Panel/issues/9896
2025-08-07 09:30:37 +00:00
449 changed files with 25718 additions and 9239 deletions
+1 -1
View File
@@ -12,7 +12,7 @@ jobs:
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: '20.2'
node-version: '22.19.0'
- name: Build Web
run: |
cd frontend && npm install && npm run build:pro
+1 -1
View File
@@ -12,7 +12,7 @@ jobs:
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: '20.2'
node-version: '22.19.0'
- name: Build Web
run: |
cd frontend && npm install && npm run build:pro
+2
View File
@@ -32,6 +32,7 @@ builds:
- arm
- ppc64le
- s390x
- riscv64
hooks:
post:
- ./ci/script.sh compress_binary {{ .Path }} {{ .Arch }}
@@ -57,6 +58,7 @@ builds:
- arm
- ppc64le
- s390x
- riscv64
hooks:
post:
- ./ci/script.sh compress_binary {{ .Path }} {{ .Arch }}
+1 -1
View File
@@ -60,7 +60,7 @@ func (b *BaseApi) RecreateOllamaModel(c *gin.Context) {
// @Success 200
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /ai/ollama/model/close [post]
// @Router /ai/ollama/close [post]
// @x-panel-log {"bodyKeys":["name"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"关闭 Ollama 模型连接 [name]","formatEN":"close conn for Ollama model [name]"}
func (b *BaseApi) CloseOllamaModel(c *gin.Context) {
var req dto.OllamaModelName
+1 -1
View File
@@ -109,7 +109,7 @@ func (b *BaseApi) LoadPort(c *gin.Context) {
// @Success 200 {object} response.DatabaseConn
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /apps/installed/conninfo/:key [get]
// @Router /apps/installed/conninfo [POST]
func (b *BaseApi) LoadConnInfo(c *gin.Context) {
var req dto.OperationWithNameAndType
if err := helper.CheckBindAndValidate(&req, c); err != nil {
+26 -4
View File
@@ -74,7 +74,7 @@ func (b *BaseApi) RefreshToken(c *gin.Context) {
// @Success 200 {array} object
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /buckets [post]
// @Router /backups/buckets [post]
func (b *BaseApi) ListBuckets(c *gin.Context) {
var req dto.ForBuckets
if err := helper.CheckBindAndValidate(&req, c); err != nil {
@@ -133,6 +133,28 @@ func (b *BaseApi) UpdateBackup(c *gin.Context) {
helper.Success(c)
}
// @Tags Backup Account
// @Summary Upload file for recover
// @Accept json
// @Param request body dto.UploadForRecover true "request"
// @Success 200
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /backups/upload [post]
// @x-panel-log {"bodyKeys":["filePath"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"上传备份文件 [filePath]","formatEN":"upload backup file [filePath]"}
func (b *BaseApi) UploadForRecover(c *gin.Context) {
var req dto.UploadForRecover
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}
if err := backupService.UploadForRecover(req); err != nil {
helper.InternalServer(c, err)
return
}
helper.Success(c)
}
// @Tags Backup Account
// @Summary Load backup account options
// @Accept json
@@ -273,7 +295,7 @@ func (b *BaseApi) SearchBackupRecordsByCronjob(c *gin.Context) {
// @Success 200 {string} filePath
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /backup/record/download [post]
// @Router /backups/record/download [post]
// @x-panel-log {"bodyKeys":["source","fileName"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"下载备份记录 [source][fileName]","formatEN":"download backup records [source][fileName]"}
func (b *BaseApi) DownloadRecord(c *gin.Context) {
var req dto.DownloadRecord
@@ -296,7 +318,7 @@ func (b *BaseApi) DownloadRecord(c *gin.Context) {
// @Success 200
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /backup/record/description/update [post]
// @Router /backups/record/description/update [post]
func (b *BaseApi) UpdateRecordDescription(c *gin.Context) {
var req dto.UpdateDescription
if err := helper.CheckBindAndValidate(&req, c); err != nil {
@@ -317,7 +339,7 @@ func (b *BaseApi) UpdateRecordDescription(c *gin.Context) {
// @Success 200
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /record/del [post]
// @Router /backups/record/del [post]
// @x-panel-log {"bodyKeys":["ids"],"paramKeys":[],"BeforeFunctions":[{"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
+4 -27
View File
@@ -104,7 +104,7 @@ func (b *BaseApi) SearchClam(c *gin.Context) {
// @Success 200 {object} dto.ClamBaseInfo
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /toolbox/clam/base [get]
// @Router /toolbox/clam/base [post]
func (b *BaseApi) LoadClamBaseInfo(c *gin.Context) {
info, err := clamService.LoadBaseInfo()
if err != nil {
@@ -152,7 +152,7 @@ func (b *BaseApi) CleanClamRecord(c *gin.Context) {
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}
if err := clamService.CleanRecord(req); err != nil {
if err := clamService.CleanRecord(req.ID); err != nil {
helper.InternalServer(c, err)
return
}
@@ -174,7 +174,7 @@ func (b *BaseApi) SearchClamRecord(c *gin.Context) {
return
}
total, list, err := clamService.LoadRecords(req)
total, list, err := clamService.SearchRecords(req)
if err != nil {
helper.InternalServer(c, err)
return
@@ -186,29 +186,6 @@ func (b *BaseApi) SearchClamRecord(c *gin.Context) {
})
}
// @Tags Clam
// @Summary Load clam record detail
// @Accept json
// @Param request body dto.ClamLogReq true "request"
// @Success 200 {string} content
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /toolbox/clam/record/log [post]
func (b *BaseApi) LoadClamRecordLog(c *gin.Context) {
var req dto.ClamLogReq
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}
content, err := clamService.LoadRecordLog(req)
if err != nil {
helper.InternalServer(c, err)
return
}
helper.SuccessWithData(c, content)
}
// @Tags Clam
// @Summary Load clam file
// @Accept json
@@ -289,7 +266,7 @@ func (b *BaseApi) HandleClamScan(c *gin.Context) {
return
}
if err := clamService.HandleOnce(req); err != nil {
if err := clamService.HandleOnce(req.ID); err != nil {
helper.InternalServer(c, err)
return
}
+19 -2
View File
@@ -57,7 +57,7 @@ func (b *BaseApi) LoadContainerUsers(c *gin.Context) {
// @Summary List containers
// @Accept json
// @Produce json
// @Success 200 {array} string
// @Success 200 {array} dto.ContainerOptions
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /containers/list [post]
@@ -65,6 +65,23 @@ func (b *BaseApi) ListContainer(c *gin.Context) {
helper.SuccessWithData(c, containerService.List())
}
// @Tags Container
// @Summary List containers by image
// @Accept json
// @Produce json
// @Success 200 {array} dto.ContainerOptions
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /containers/list/byimage [post]
func (b *BaseApi) ListContainerByImage(c *gin.Context) {
var req dto.OperationWithName
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}
helper.SuccessWithData(c, containerService.ListByImage(req.Name))
}
// @Tags Container
// @Summary Load containers status
// @Accept json
@@ -680,7 +697,7 @@ func (b *BaseApi) ComposeUpdate(c *gin.Context) {
// @Param tail query string false "显示行号"
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /containers/search/log [post]
// @Router /containers/search/log [get]
func (b *BaseApi) ContainerStreamLogs(c *gin.Context) {
c.Header("Content-Type", "text/event-stream")
c.Header("Cache-Control", "no-cache")
-24
View File
@@ -314,30 +314,6 @@ func (b *BaseApi) UpdateCronjobStatus(c *gin.Context) {
helper.Success(c)
}
// @Tags Cronjob
// @Summary Download cronjob records
// @Accept json
// @Param request body dto.CronjobDownload true "request"
// @Success 200
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /cronjobs/download [post]
// @x-panel-log {"bodyKeys":["recordID"],"paramKeys":[],"BeforeFunctions":[{"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 := helper.CheckBindAndValidate(&req, c); err != nil {
return
}
filePath, err := cronjobService.Download(req)
if err != nil {
helper.InternalServer(c, err)
return
}
c.File(filePath)
}
// @Tags Cronjob
// @Summary Handle cronjob once
// @Accept json
+32
View File
@@ -83,6 +83,38 @@ func (b *BaseApi) UpdateAppLauncher(c *gin.Context) {
helper.Success(c)
}
// @Tags Dashboard
// @Summary Load quick jump options
// @Success 200 {Array} dto.QuickJump
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /dashboard/quick/option [get]
func (b *BaseApi) LoadQuickOption(c *gin.Context) {
helper.SuccessWithData(c, dashboardService.LoadQuickOptions())
}
// @Tags Dashboard
// @Summary Update quick jump
// @Accept json
// @Param request body dto.ChangeQuicks true "request"
// @Success 200
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /dashboard/quick/change [post]
// @x-panel-log {"bodyKeys":[],"paramKeys":[],"BeforeFunctions":[],"formatZH":"切换快速跳转","formatEN":"change quick jump"}
func (b *BaseApi) UpdateQuickJump(c *gin.Context) {
var req dto.ChangeQuicks
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}
if err := dashboardService.ChangeQuick(req); err != nil {
helper.InternalServer(c, err)
return
}
helper.Success(c)
}
// @Tags Dashboard
// @Summary Load dashboard base info
// @Accept json
+1 -1
View File
@@ -160,7 +160,7 @@ func (b *BaseApi) GetDatabase(c *gin.Context) {
// @Success 200 {array} string
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /db/remote/del/check [post]
// @Router /databases/db/del/check [post]
func (b *BaseApi) DeleteCheckDatabase(c *gin.Context) {
var req dto.OperateByID
if err := helper.CheckBindAndValidate(&req, c); err != nil {
+99
View File
@@ -0,0 +1,99 @@
package v2
import (
"github.com/1Panel-dev/1Panel/agent/app/api/v2/helper"
"github.com/1Panel-dev/1Panel/agent/app/dto/request"
"github.com/gin-gonic/gin"
)
// @Tags Disk Management
// @Summary Get complete disk information
// @Description Get information about all disks including partitioned and unpartitioned disks
// @Produce json
// @Success 200 {object} response.CompleteDiskInfo
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /hosts/disks [get]
func (b *BaseApi) GetCompleteDiskInfo(c *gin.Context) {
diskInfo, err := diskService.GetCompleteDiskInfo()
if err != nil {
helper.InternalServer(c, err)
return
}
helper.SuccessWithData(c, diskInfo)
}
// @Tags Disk Management
// @Summary Partition disk
// @Description Create partition and format disk with specified filesystem
// @Accept json
// @Param request body request.DiskPartitionRequest true "partition request"
// @Success 200 {string} string "Partition created successfully"
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /hosts/disks/partition [post]
// @x-panel-log {"bodyKeys":["device", "filesystem", "mountPoint"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"对磁盘 [device] 进行分区,文件系统 [filesystem],挂载点 [mountPoint]","formatEN":"Partition disk [device] with filesystem [filesystem], mount point [mountPoint]"}
func (b *BaseApi) PartitionDisk(c *gin.Context) {
var req request.DiskPartitionRequest
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}
result, err := diskService.PartitionDisk(req)
if err != nil {
helper.InternalServer(c, err)
return
}
helper.SuccessWithData(c, result)
}
// @Tags Disk Management
// @Summary Mount disk
// @Description Mount partition to specified mount point
// @Accept json
// @Param request body request.DiskMountRequest true "mount request"
// @Success 200 {string} string "Disk mounted successfully"
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /hosts/disks/mount [post]
// @x-panel-log {"bodyKeys":["device", "mountPoint"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"挂载磁盘 [device] 到 [mountPoint]","formatEN":"Mount disk [device] to [mountPoint]"}
func (b *BaseApi) MountDisk(c *gin.Context) {
var req request.DiskMountRequest
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}
err := diskService.MountDisk(req)
if err != nil {
helper.InternalServer(c, err)
return
}
helper.Success(c)
}
// @Tags Disk Management
// @Summary Unmount disk
// @Description Unmount partition from mount point
// @Accept json
// @Param request body request.DiskUnmountRequest true "unmount request"
// @Success 200 {string} string "Disk unmounted successfully"
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /hosts/disks/unmount [post]
// @x-panel-log {"bodyKeys":["device", "mountPoint"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"卸载磁盘 [device] 从 [mountPoint]","formatEN":"Unmount disk [device] from [mountPoint]"}
func (b *BaseApi) UnmountDisk(c *gin.Context) {
var req request.DiskUnmountRequest
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}
err := diskService.UnmountDisk(req)
if err != nil {
helper.InternalServer(c, err)
return
}
helper.Success(c)
}
+3
View File
@@ -38,6 +38,7 @@ var (
sshService = service.NewISSHService()
firewallService = service.NewIFirewallService()
monitorService = service.NewIMonitorService()
systemService = service.NewISystemService()
deviceService = service.NewIDeviceService()
fail2banService = service.NewIFail2BanService()
@@ -71,4 +72,6 @@ var (
taskService = service.NewITaskService()
groupService = service.NewIGroupService()
alertService = service.NewIAlertService()
diskService = service.NewIDiskService()
)
+2 -2
View File
@@ -880,7 +880,7 @@ func (b *BaseApi) GetPathByType(c *gin.Context) {
// @Success 200 {object} dto.DiskInfo
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /mount [post]
// @Router /files/mount [post]
func (b *BaseApi) GetHostMount(c *gin.Context) {
disks := fileService.GetHostMount()
helper.SuccessWithData(c, disks)
@@ -892,7 +892,7 @@ func (b *BaseApi) GetHostMount(c *gin.Context) {
// @Success 200 {object} response.UserGroupResponse
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /user/group [post]
// @Router /files/user/group [post]
func (b *BaseApi) GetUsersAndGroups(c *gin.Context) {
res, err := fileService.GetUsersAndGroups()
if err != nil {
+1 -1
View File
@@ -63,7 +63,7 @@ func (b *BaseApi) OperateFirewall(c *gin.Context) {
return
}
if err := firewallService.OperateFirewall(req.Operation); err != nil {
if err := firewallService.OperateFirewall(req); err != nil {
helper.InternalServer(c, err)
return
}
+4 -4
View File
@@ -13,7 +13,7 @@ import (
// @Success 200
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /agent/groups [post]
// @Router /groups [post]
// @x-panel-log {"bodyKeys":["name","type"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"创建组 [name][type]","formatEN":"create group [name][type]"}
func (b *BaseApi) CreateGroup(c *gin.Context) {
var req dto.GroupCreate
@@ -35,7 +35,7 @@ func (b *BaseApi) CreateGroup(c *gin.Context) {
// @Success 200
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /agent/groups/del [post]
// @Router /groups/del [post]
// @x-panel-log {"bodyKeys":["id"],"paramKeys":[],"BeforeFunctions":[{"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
@@ -57,7 +57,7 @@ func (b *BaseApi) DeleteGroup(c *gin.Context) {
// @Success 200
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /agent/groups/update [post]
// @Router /groups/update [post]
// @x-panel-log {"bodyKeys":["name","type"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"更新组 [name][type]","formatEN":"update group [name][type]"}
func (b *BaseApi) UpdateGroup(c *gin.Context) {
var req dto.GroupUpdate
@@ -79,7 +79,7 @@ func (b *BaseApi) UpdateGroup(c *gin.Context) {
// @Success 200 {array} dto.OperateByType
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /agent/groups/search [post]
// @Router /groups/search [post]
func (b *BaseApi) ListGroup(c *gin.Context) {
var req dto.OperateByType
if err := helper.CheckBindAndValidate(&req, c); err != nil {
+7 -3
View File
@@ -116,7 +116,11 @@ func CheckBind(req interface{}, c *gin.Context) error {
return nil
}
func ErrResponse(ctx *gin.Context, code int) {
ctx.JSON(code, nil)
ctx.Abort()
func GetParamInt32(paramName string, c *gin.Context) (int32, error) {
idParam, ok := c.Params.Get(paramName)
if !ok {
return 0, errors.New("error id in path")
}
intNum, _ := strconv.Atoi(idParam)
return int32(intNum), nil
}
+1 -1
View File
@@ -35,7 +35,7 @@ func (b *BaseApi) GetToolStatus(c *gin.Context) {
// @Success 200
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /hosts/tool/create [post]
// @Router /hosts/tool/init [post]
// @x-panel-log {"bodyKeys":["type"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"创建 [type] 配置","formatEN":"create [type] config"}
func (b *BaseApi) InitToolConfig(c *gin.Context) {
var req request.HostToolCreate
+2 -2
View File
@@ -9,14 +9,14 @@ import (
// @Tags Container Image
// @Summary Page images
// @Accept json
// @Param request body dto.SearchWithPage true "request"
// @Param request body dto.PageImage true "request"
// @Produce json
// @Success 200 {object} dto.PageResult
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /containers/image/search [post]
func (b *BaseApi) SearchImage(c *gin.Context) {
var req dto.SearchWithPage
var req dto.PageImage
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}
+1 -1
View File
@@ -58,7 +58,7 @@ func (b *BaseApi) ListRepo(c *gin.Context) {
// @Success 200
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /containers/repo/status [get]
// @Router /containers/repo/status [post]
func (b *BaseApi) CheckRepoStatus(c *gin.Context) {
var req dto.OperateByID
if err := helper.CheckBindAndValidate(&req, c); err != nil {
+8 -8
View File
@@ -13,7 +13,7 @@ import (
// @Success 200 {object} response.McpServersRes
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /mcp/search [post]
// @Router /ai/mcp/search [post]
func (b *BaseApi) PageMcpServers(c *gin.Context) {
var req request.McpServerSearch
if err := helper.CheckBindAndValidate(&req, c); err != nil {
@@ -30,7 +30,7 @@ func (b *BaseApi) PageMcpServers(c *gin.Context) {
// @Success 200
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /mcp/server [post]
// @Router /ai/mcp/server [post]
func (b *BaseApi) CreateMcpServer(c *gin.Context) {
var req request.McpServerCreate
if err := helper.CheckBindAndValidate(&req, c); err != nil {
@@ -51,7 +51,7 @@ func (b *BaseApi) CreateMcpServer(c *gin.Context) {
// @Success 200
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /mcp/server/update [post]
// @Router /ai/mcp/server/update [post]
func (b *BaseApi) UpdateMcpServer(c *gin.Context) {
var req request.McpServerUpdate
if err := helper.CheckBindAndValidate(&req, c); err != nil {
@@ -72,7 +72,7 @@ func (b *BaseApi) UpdateMcpServer(c *gin.Context) {
// @Success 200
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /mcp/server/del [post]
// @Router /ai/mcp/server/del [post]
func (b *BaseApi) DeleteMcpServer(c *gin.Context) {
var req request.McpServerDelete
if err := helper.CheckBindAndValidate(&req, c); err != nil {
@@ -93,7 +93,7 @@ func (b *BaseApi) DeleteMcpServer(c *gin.Context) {
// @Success 200
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /mcp/server/op [post]
// @Router /ai/mcp/server/op [post]
func (b *BaseApi) OperateMcpServer(c *gin.Context) {
var req request.McpServerOperate
if err := helper.CheckBindAndValidate(&req, c); err != nil {
@@ -114,7 +114,7 @@ func (b *BaseApi) OperateMcpServer(c *gin.Context) {
// @Success 200
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /mcp/domain/bind [post]
// @Router /ai/mcp/domain/bind [post]
func (b *BaseApi) BindMcpDomain(c *gin.Context) {
var req request.McpBindDomain
if err := helper.CheckBindAndValidate(&req, c); err != nil {
@@ -135,7 +135,7 @@ func (b *BaseApi) BindMcpDomain(c *gin.Context) {
// @Success 200
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /mcp/domain/update [post]
// @Router /ai/mcp/domain/update [post]
func (b *BaseApi) UpdateMcpBindDomain(c *gin.Context) {
var req request.McpBindDomainUpdate
if err := helper.CheckBindAndValidate(&req, c); err != nil {
@@ -155,7 +155,7 @@ func (b *BaseApi) UpdateMcpBindDomain(c *gin.Context) {
// @Success 200 {object} response.McpBindDomainRes
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /mcp/domain/get [get]
// @Router /ai/mcp/domain/get [get]
func (b *BaseApi) GetMcpBindDomain(c *gin.Context) {
res, err := mcpServerService.GetBindDomain()
if err != nil {
+2 -2
View File
@@ -6,8 +6,8 @@ import (
"github.com/1Panel-dev/1Panel/agent/app/api/v2/helper"
"github.com/1Panel-dev/1Panel/agent/app/dto"
"github.com/gin-gonic/gin"
"github.com/shirou/gopsutil/v3/disk"
"github.com/shirou/gopsutil/v3/net"
"github.com/shirou/gopsutil/v4/disk"
"github.com/shirou/gopsutil/v4/net"
)
// @Tags Monitor
+1 -1
View File
@@ -129,7 +129,7 @@ func (b *BaseApi) BuildNginx(c *gin.Context) {
// @Success 200
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /openresty/module/update [post]
// @Router /openresty/modules/update [post]
// @x-panel-log {"bodyKeys":[],"paramKeys":[],"BeforeFunctions":[],"formatZH":"更新 OpenResty 模块","formatEN":"Update OpenResty module"}
func (b *BaseApi) UpdateNginxModule(c *gin.Context) {
var req request.NginxModuleUpdate
+21
View File
@@ -36,3 +36,24 @@ func (b *BaseApi) StopProcess(c *gin.Context) {
}
helper.Success(c)
}
// @Tags Process
// @Summary Get Process Info By PID
// @Param pid path int true "PID"
// @Success 200 {object} websocket.PsProcessData
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /process/{pid} [get]
func (b *BaseApi) GetProcessInfoByPID(c *gin.Context) {
pid, err := helper.GetParamInt32("pid", c)
if err != nil {
helper.BadRequest(c, err)
return
}
data, err := processService.GetProcessInfoByPID(pid)
if err != nil {
helper.BadRequest(c, err)
return
}
helper.SuccessWithData(c, data)
}
+28 -6
View File
@@ -81,7 +81,7 @@ func (b *BaseApi) DeleteRuntime(c *gin.Context) {
// @Success 200 {array} dto.AppResource
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /installed/delete/check/:id [get]
// @Router /runtimes/installed/delete/check/:id [get]
func (b *BaseApi) DeleteRuntimeCheck(c *gin.Context) {
runTimeId, err := helper.GetIntParamByKey(c, "id")
if err != nil {
@@ -459,7 +459,7 @@ func (b *BaseApi) GetSupervisorProcess(c *gin.Context) {
// @Success 200
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /runtimes/supervisor/process/operate [post]
// @Router /runtimes/supervisor/process [post]
func (b *BaseApi) OperateSupervisorProcess(c *gin.Context) {
var req request.PHPSupervisorProcessConfig
if err := helper.CheckBindAndValidate(&req, c); err != nil {
@@ -480,7 +480,7 @@ func (b *BaseApi) OperateSupervisorProcess(c *gin.Context) {
// @Success 200 {string} content
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /runtimes/supervisor/process/file/operate [post]
// @Router /runtimes/supervisor/process/file [post]
func (b *BaseApi) OperateSupervisorProcessFile(c *gin.Context) {
var req request.PHPSupervisorProcessFileReq
if err := helper.CheckBindAndValidate(&req, c); err != nil {
@@ -497,7 +497,7 @@ func (b *BaseApi) OperateSupervisorProcessFile(c *gin.Context) {
// @Tags Runtime
// @Summary Update PHP container config
// @Accept json
// @Param request body request.PHPContainerUpdate true "request"
// @Param request body request.PHPContainerConfig true "request"
// @Success 200
// @Security ApiKeyAuth
// @Security Timestamp
@@ -518,7 +518,7 @@ func (b *BaseApi) UpdatePHPContainer(c *gin.Context) {
// @Summary Get PHP container config
// @Accept json
// @Param id path integer true "request"
// @Success 200 {object} response.PHPContainerConfig
// @Success 200 {object} request.PHPContainerConfig
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /runtimes/php/container/:id [get]
@@ -539,7 +539,7 @@ func (b *BaseApi) GetPHPContainerConfig(c *gin.Context) {
// @Tags Runtime
// @Summary Update runtime remark
// @Accept json
// @Param request body request.RuntimeRemarkUpdate true "request"
// @Param request body request.RuntimeRemark true "request"
// @Success 200
// @Security ApiKeyAuth
// @Security Timestamp
@@ -555,3 +555,25 @@ func (b *BaseApi) UpdateRuntimeRemark(c *gin.Context) {
}
helper.Success(c)
}
// @Tags Runtime
// @Summary Get PHP runtime status
// @Accept json
// @Param id path integer true "request"
// @Success 200 {object} map[string]interface{}
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /runtimes/php/fpm/status/:id [get]
func (b *BaseApi) GetFPMStatus(c *gin.Context) {
id, err := helper.GetParamID(c)
if err != nil {
helper.BadRequest(c, err)
return
}
data, err := runtimeService.GetFPMStatus(id)
if err != nil {
helper.InternalServer(c, err)
return
}
helper.SuccessWithData(c, data)
}
+51 -35
View File
@@ -1,13 +1,12 @@
package v2
import (
"encoding/base64"
"encoding/json"
"os"
"os/user"
"path"
"github.com/1Panel-dev/1Panel/agent/app/api/v2/helper"
"github.com/1Panel-dev/1Panel/agent/app/dto"
"github.com/1Panel-dev/1Panel/agent/app/model"
"github.com/1Panel-dev/1Panel/agent/global"
"github.com/1Panel-dev/1Panel/agent/utils/ssh"
"github.com/gin-gonic/gin"
@@ -71,6 +70,35 @@ func (b *BaseApi) LoadBaseDir(c *gin.Context) {
helper.SuccessWithData(c, global.Dir.DataDir)
}
// @Tags System Setting
// @Summary Load local conn
// @Success 200 {object} dto.SSHConnData
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /settings/ssh/conn [get]
func (b *BaseApi) LoadLocalConn(c *gin.Context) {
connInfoInDB := settingService.GetSettingByKey("LocalSSHConn")
if len(connInfoInDB) == 0 {
helper.Success(c)
return
}
var data dto.SSHConnData
if err := json.Unmarshal([]byte(connInfoInDB), &data); err != nil {
helper.Success(c)
return
}
if len(data.Password) != 0 {
data.Password = base64.StdEncoding.EncodeToString([]byte(data.Password))
}
if len(data.PrivateKey) != 0 {
data.PrivateKey = base64.StdEncoding.EncodeToString([]byte(data.PrivateKey))
}
if len(data.PassPhrase) != 0 {
data.PassPhrase = base64.StdEncoding.EncodeToString([]byte(data.PassPhrase))
}
helper.SuccessWithData(c, data)
}
func (b *BaseApi) CheckLocalConn(c *gin.Context) {
_, err := loadLocalConn()
helper.SuccessWithData(c, err == nil)
@@ -87,6 +115,7 @@ func (b *BaseApi) CheckLocalConnByInfo(c *gin.Context) {
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}
helper.SuccessWithData(c, settingService.TestConnByInfo(req))
}
@@ -96,51 +125,38 @@ func (b *BaseApi) CheckLocalConnByInfo(c *gin.Context) {
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /settings/ssh [post]
func (b *BaseApi) SaveLocalConnInfo(c *gin.Context) {
func (b *BaseApi) SaveLocalConn(c *gin.Context) {
var req dto.SSHConnData
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}
helper.SuccessWithData(c, settingService.SaveConnInfo(req))
if err := settingService.SaveConnInfo(req); err != nil {
helper.InternalServer(c, err)
return
}
helper.Success(c)
}
func loadLocalConn() (*ssh.SSHClient, error) {
itemPath := ""
currentInfo, _ := user.Current()
if len(currentInfo.HomeDir) == 0 {
itemPath = "/root/.ssh/id_ed25519_1panel"
} else {
itemPath = path.Join(currentInfo.HomeDir, ".ssh/id_ed25519_1panel")
}
if _, err := os.Stat(itemPath); err != nil {
_ = sshService.CreateRootCert(dto.CreateRootCert{EncryptionMode: "ed25519", Name: "1panel", Description: "1Panel Terminal"})
}
privateKey, _ := os.ReadFile(itemPath)
connWithKey := ssh.ConnInfo{
Addr: "127.0.0.1",
User: "root",
Port: 22,
AuthMode: "key",
PrivateKey: privateKey,
}
client, err := ssh.NewClient(connWithKey)
if err == nil {
return client, nil
}
connInfoInDB, err := settingService.GetSSHInfo()
if err != nil {
return nil, err
}
connInfoInDB := settingService.GetSettingByKey("LocalSSHConn")
if len(connInfoInDB) == 0 {
return nil, errors.New("no such ssh conn info in db!")
}
var connInDB ssh.ConnInfo
var connInDB model.LocalConnInfo
if err := json.Unmarshal([]byte(connInfoInDB), &connInDB); err != nil {
return nil, err
}
return ssh.NewClient(connInDB)
sshInfo := ssh.ConnInfo{
Addr: connInDB.Addr,
Port: int(connInDB.Port),
User: connInDB.User,
AuthMode: connInDB.AuthMode,
Password: connInDB.Password,
PrivateKey: []byte(connInDB.PrivateKey),
PassPhrase: []byte(connInDB.PassPhrase),
}
return ssh.NewClient(sshInfo)
}
// @Tags System Setting
+2 -2
View File
@@ -36,7 +36,7 @@ func (b *BaseApi) CreateSnapshot(c *gin.Context) {
return
}
if err := snapshotService.SnapshotCreate(nil, req, 0, 0, 0); err != nil {
if err := snapshotService.SnapshotCreate(nil, req, 0, 3); err != nil {
helper.InternalServer(c, err)
return
}
@@ -50,7 +50,7 @@ func (b *BaseApi) CreateSnapshot(c *gin.Context) {
// @Success 200
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /settings/snapshot/recrete [post]
// @Router /settings/snapshot/recreate [post]
// @x-panel-log {"bodyKeys":["id"],"paramKeys":[],"BeforeFunctions":[{"input_column":"id","input_value":"id","isList":false,"db":"snapshots","output_column":"name","output_value":"name"}],"formatZH":"重试创建快照 [name]","formatEN":"recrete the snapshot [name]"}
func (b *BaseApi) RecreateSnapshot(c *gin.Context) {
var req dto.OperateByID
+29 -4
View File
@@ -74,7 +74,7 @@ func (b *BaseApi) UpdateSSH(c *gin.Context) {
// @Success 200
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /hosts/conffile/update [post]
// @Router /hosts/ssh/conffile/update [post]
// @x-panel-log {"bodyKeys":[],"paramKeys":[],"BeforeFunctions":[],"formatZH":"修改 SSH 配置文件","formatEN":"update SSH conf"}
func (b *BaseApi) UpdateSSHByfile(c *gin.Context) {
var req dto.SSHConf
@@ -201,7 +201,7 @@ func (b *BaseApi) DeleteRootCert(c *gin.Context) {
// @Summary Load host SSH logs
// @Accept json
// @Param request body dto.SearchSSHLog true "request"
// @Success 200 {object} dto.SSHLog
// @Success 200 {object} dto.PageResult
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /hosts/ssh/log [post]
@@ -211,12 +211,37 @@ func (b *BaseApi) LoadSSHLogs(c *gin.Context) {
return
}
data, err := sshService.LoadLog(c, req)
total, data, err := sshService.LoadLog(c, req)
if err != nil {
helper.InternalServer(c, err)
return
}
helper.SuccessWithData(c, data)
helper.SuccessWithData(c, dto.PageResult{
Total: total,
Items: data,
})
}
// @Tags SSH
// @Summary Export host SSH logs
// @Accept json
// @Param request body dto.SearchSSHLog true "request"
// @Success 200 {string} path
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /hosts/ssh/log/export [post]
func (b *BaseApi) ExportSSHLogs(c *gin.Context) {
var req dto.SearchSSHLog
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}
tmpFile, err := sshService.ExportLog(c, req)
if err != nil {
helper.InternalServer(c, err)
return
}
helper.SuccessWithData(c, tmpFile)
}
// @Tags SSH
+26
View File
@@ -0,0 +1,26 @@
package v2
import (
"errors"
"github.com/1Panel-dev/1Panel/agent/app/api/v2/helper"
"github.com/gin-gonic/gin"
)
// @Tags Host
// @Summary Check if a system component exists
// @Accept json
// @Param name query string true "Component name to check (e.g., rsync, docker)"
// @Success 200 {object} response.ComponentInfo
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /hosts/components/{name} [get]
func (b *BaseApi) CheckComponentExistence(c *gin.Context) {
name := c.Param("name")
if name == "" {
helper.BadRequest(c, errors.New("empty component name"))
return
}
info := systemService.IsComponentExist(name)
helper.SuccessWithData(c, info)
}
+23 -3
View File
@@ -527,7 +527,7 @@ func (b *BaseApi) UpdateProxyConfig(c *gin.Context) {
// @Success 200
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /websites/proxy/file [post]
// @Router /websites/proxies/file [post]
// @x-panel-log {"bodyKeys":["websiteID"],"paramKeys":[],"BeforeFunctions":[{"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
@@ -840,7 +840,7 @@ func (b *BaseApi) CreateLoadBalance(c *gin.Context) {
// @Success 200
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /websites/lbs/delete [post]
// @Router /websites/lbs/del [post]
func (b *BaseApi) DeleteLoadBalance(c *gin.Context) {
var req request.WebsiteLBDelete
if err := helper.CheckBindAndValidate(&req, c); err != nil {
@@ -953,7 +953,7 @@ func (b *BaseApi) GetProxyCache(c *gin.Context) {
// @Success 200
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /websites/realip [post]
// @Router /websites/realip/config [post]
// @x-panel-log {"bodyKeys":["websiteID"],"paramKeys":[],"BeforeFunctions":[{"input_column":"id","input_value":"websiteID","isList":false,"db":"websites","output_column":"primary_domain","output_value":"domain"}],"formatZH":"修改 [domain] 网站真实IP配置 ","formatEN":"Modify the real IP configuration of [domain] website"}
func (b *BaseApi) SetRealIPConfig(c *gin.Context) {
var req request.WebsiteRealIP
@@ -1121,3 +1121,23 @@ func (b *BaseApi) OperateCrossSiteAccess(c *gin.Context) {
}
helper.Success(c)
}
// @Tags Website
// @Summary Exec Composer
// @Accept json
// @Param request body request.ExecComposerReq true "request"
// @Success 200
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /websites/exec/composer [post]
func (b *BaseApi) ExecComposer(c *gin.Context) {
var req request.ExecComposerReq
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}
if err := websiteService.ExecComposer(req); err != nil {
helper.InternalServer(c, err)
return
}
helper.Success(c)
}
+13
View File
@@ -1,6 +1,7 @@
package v2
import (
"github.com/1Panel-dev/1Panel/agent/app/model"
"net/http"
"net/url"
"reflect"
@@ -245,3 +246,15 @@ func (b *BaseApi) DownloadWebsiteSSL(c *gin.Context) {
c.Header("Content-Disposition", "attachment; filename*=utf-8''"+url.PathEscape(info.Name()))
http.ServeContent(c.Writer, c.Request, info.Name(), info.ModTime(), file)
}
func (b *BaseApi) ImportMasterSSL(c *gin.Context) {
var req model.WebsiteSSL
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}
if err := websiteSSLService.ImportMasterSSL(req); err != nil {
helper.InternalServer(c, err)
return
}
helper.Success(c)
}
+31 -28
View File
@@ -36,40 +36,43 @@ type AlertSearch struct {
}
type AlertDTO struct {
ID uint `json:"id"`
Type string `json:"type"`
Cycle uint `json:"cycle"`
Count uint `json:"count"`
Method string `json:"method"`
Title string `json:"title"`
Project string `json:"project"`
Status string `json:"status"`
SendCount uint `json:"sendCount"`
CreatedAt time.Time `json:"createdAt"`
UpdatedAt time.Time `json:"updatedAt"`
ID uint `json:"id"`
Type string `json:"type"`
Cycle uint `json:"cycle"`
Count uint `json:"count"`
Method string `json:"method"`
Title string `json:"title"`
Project string `json:"project"`
Status string `json:"status"`
SendCount uint `json:"sendCount"`
AdvancedParams string `json:"advancedParams"`
CreatedAt time.Time `json:"createdAt"`
UpdatedAt time.Time `json:"updatedAt"`
}
type AlertCreate struct {
Type string `json:"type" validate:"required"`
Cycle uint `json:"cycle"`
Count uint `json:"count"`
Method string `json:"method" validate:"required"`
Title string `json:"title"`
Project string `json:"project"`
Status string `json:"status"`
SendCount uint `json:"sendCount"`
Type string `json:"type" validate:"required"`
Cycle uint `json:"cycle"`
Count uint `json:"count"`
Method string `json:"method" validate:"required"`
Title string `json:"title"`
Project string `json:"project"`
Status string `json:"status"`
SendCount uint `json:"sendCount"`
AdvancedParams string `json:"advancedParams"`
}
type AlertUpdate struct {
ID uint `json:"id" validate:"required"`
Type string `json:"type"`
Cycle uint `json:"cycle"`
Count uint `json:"count"`
Method string `json:"method"`
Title string `json:"title"`
Project string `json:"project"`
Status string `json:"status"`
SendCount uint `json:"sendCount"`
ID uint `json:"id" validate:"required"`
Type string `json:"type"`
Cycle uint `json:"cycle"`
Count uint `json:"count"`
Method string `json:"method"`
Title string `json:"title"`
Project string `json:"project"`
Status string `json:"status"`
SendCount uint `json:"sendCount"`
AdvancedParams string `json:"advancedParams"`
}
type DeleteRequest struct {
+5
View File
@@ -53,6 +53,11 @@ type BackupOption struct {
IsPublic bool `json:"isPublic"`
}
type UploadForRecover struct {
FilePath string `json:"filePath"`
TargetDir string `json:"targetDir"`
}
type CommonBackup struct {
Type string `json:"type" validate:"required,oneof=app mysql mariadb redis website postgresql mysql-cluster postgresql-cluster redis-cluster"`
Name string `json:"name"`
+13 -9
View File
@@ -30,7 +30,8 @@ type ClamInfo struct {
Path string `json:"path"`
InfectedStrategy string `json:"infectedStrategy"`
InfectedDir string `json:"infectedDir"`
LastHandleDate string `json:"lastHandleDate"`
LastRecordStatus string `json:"lastRecordStatus"`
LastRecordTime string `json:"lastRecordTime"`
Spec string `json:"spec"`
Description string `json:"description"`
AlertCount uint `json:"alertCount"`
@@ -41,6 +42,7 @@ type ClamLogSearch struct {
PageInfo
ClamID uint `json:"clamID"`
Status string `json:"status"`
StartTime time.Time `json:"startTime"`
EndTime time.Time `json:"endTime"`
}
@@ -56,13 +58,16 @@ type ClamFileReq struct {
Name string `json:"name" validate:"required"`
}
type ClamLog struct {
Name string `json:"name"`
ScanDate string `json:"scanDate"`
ScanTime string `json:"scanTime"`
InfectedFiles string `json:"infectedFiles"`
TotalError string `json:"totalError"`
Status string `json:"status"`
type ClamRecord struct {
ID uint `json:"id"`
TaskID string `json:"taskID"`
StartTime time.Time `json:"startTime"`
ScanTime string `json:"scanTime"`
InfectedFiles string `json:"infectedFiles"`
TotalError string `json:"totalError"`
Status string `json:"status"`
Message string `json:"message"`
}
type ClamCreate struct {
@@ -98,7 +103,6 @@ type ClamUpdateStatus struct {
}
type ClamDelete struct {
RemoveRecord bool `json:"removeRecord"`
RemoveInfected bool `json:"removeInfected"`
Ids []uint `json:"ids" validate:"required"`
}
+26 -20
View File
@@ -107,10 +107,10 @@ type ContainerCreateByCommand struct {
}
type ContainerUpgrade struct {
TaskID string `json:"taskID"`
Name string `json:"name" validate:"required"`
Image string `json:"image" validate:"required"`
ForcePull bool `json:"forcePull"`
TaskID string `json:"taskID"`
Names []string `json:"names" validate:"required"`
Image string `json:"image" validate:"required"`
ForcePull bool `json:"forcePull"`
}
type ContainerListStats struct {
@@ -209,11 +209,12 @@ type NetworkCreate struct {
}
type Volume struct {
Name string `json:"name"`
Labels []string `json:"labels"`
Driver string `json:"driver"`
Mountpoint string `json:"mountpoint"`
CreatedAt time.Time `json:"createdAt"`
Name string `json:"name"`
Labels []VolumeOption `json:"labels"`
Driver string `json:"driver"`
Mountpoint string `json:"mountpoint"`
CreatedAt time.Time `json:"createdAt"`
Options []VolumeOption `json:"options"`
}
type VolumeCreate struct {
Name string `json:"name" validate:"required"`
@@ -221,7 +222,10 @@ type VolumeCreate struct {
Options []string `json:"options"`
Labels []string `json:"labels"`
}
type VolumeOption struct {
Key string `json:"key"`
Value string `json:"value"`
}
type BatchDelete struct {
TaskID string `json:"taskID"`
Force bool `json:"force"`
@@ -229,15 +233,16 @@ type BatchDelete struct {
}
type ComposeInfo struct {
Name string `json:"name"`
CreatedAt string `json:"createdAt"`
CreatedBy string `json:"createdBy"`
ContainerNumber int `json:"containerNumber"`
ConfigFile string `json:"configFile"`
Workdir string `json:"workdir"`
Path string `json:"path"`
Containers []ComposeContainer `json:"containers"`
Env []string `json:"env"`
Name string `json:"name"`
CreatedAt string `json:"createdAt"`
CreatedBy string `json:"createdBy"`
ContainerCount int `json:"containerCount"`
RunningCount int `json:"runningCount"`
ConfigFile string `json:"configFile"`
Workdir string `json:"workdir"`
Path string `json:"path"`
Containers []ComposeContainer `json:"containers"`
Env []string `json:"env"`
}
type ComposeContainer struct {
ContainerID string `json:"containerID"`
@@ -257,8 +262,9 @@ type ComposeCreate struct {
type ComposeOperation struct {
Name string `json:"name" validate:"required"`
Path string `json:"path"`
Operation string `json:"operation" validate:"required,oneof=up start stop down delete"`
Operation string `json:"operation" validate:"required,oneof=up start restart stop down delete"`
WithFile bool `json:"withFile"`
Force bool `josn:"force"`
}
type ComposeUpdate struct {
Name string `json:"name" validate:"required"`
-5
View File
@@ -65,11 +65,6 @@ type CronjobUpdateStatus struct {
Status string `json:"status" validate:"required"`
}
type CronjobDownload struct {
RecordID uint `json:"recordID" validate:"required"`
BackupAccountID uint `json:"backupAccountID" validate:"required"`
}
type CronjobClean struct {
IsDelete bool `json:"isDelete"`
CleanData bool `json:"cleanData"`
+15
View File
@@ -23,9 +23,24 @@ type DashboardBase struct {
CPULogicalCores int `json:"cpuLogicalCores"`
CPUModelName string `json:"cpuModelName"`
QuickJumps []QuickJump `json:"quickJump"`
CurrentInfo DashboardCurrent `json:"currentInfo"`
}
type ChangeQuicks struct {
Quicks []QuickJump `json:"quicks"`
}
type QuickJump struct {
ID uint `json:"id"`
Name string `json:"name"`
Title string `json:"title"`
Detail string `json:"detail"`
Recommend int `json:"recommend"`
IsShow bool `json:"isShow"`
Router string `json:"router"`
}
type OsInfo struct {
OS string `json:"os"`
Platform string `json:"platform"`
+5
View File
@@ -329,3 +329,8 @@ type LoadRedisStatus struct {
Name string `json:"name" validate:"required"`
Type string `json:"type" validate:"required"`
}
type DBResource struct {
Type string `json:"type"`
Name string `json:"name"`
}
+25
View File
@@ -0,0 +1,25 @@
package dto
type LsblkDevice struct {
Name string `json:"name"`
Size string `json:"size"`
Type string `json:"type"`
MountPoint *string `json:"mountpoint"`
FsType *string `json:"fstype"`
Model *string `json:"model"`
Serial string `json:"serial"`
Tran string `json:"tran"`
Rota bool `json:"rota"`
Children []LsblkDevice `json:"children,omitempty"`
}
type LsblkOutput struct {
BlockDevices []LsblkDevice `json:"blockdevices"`
}
type DiskFormatRequest struct {
Device string `json:"device" `
Filesystem string `json:"filesystem" `
Label string `json:"label,omitempty" `
QuickFormat bool `json:"quickFormat,omitempty"`
}
+8
View File
@@ -0,0 +1,8 @@
package dto
type LogFileRes struct {
Lines []string `json:"lines"`
IsEndOfFile bool `json:"isEndOfFile"`
TotalPages int `json:"totalPages"`
TotalLines int `json:"totalLines"`
}
+2 -1
View File
@@ -17,7 +17,8 @@ type RuleSearch struct {
}
type FirewallOperation struct {
Operation string `json:"operation" validate:"required,oneof=start stop restart disablePing enablePing"`
Operation string `json:"operation" validate:"required,oneof=start stop restart disablePing enablePing"`
WithDockerRestart bool `json:"withDockerRestart"`
}
type PortRuleOperate struct {
+8 -1
View File
@@ -2,12 +2,19 @@ package dto
import "time"
type PageImage struct {
PageInfo
Name string `json:"name"`
OrderBy string `json:"orderBy" validate:"required,oneof=size tags createdAt isUsed"`
Order string `json:"order" validate:"required,oneof=null ascending descending"`
}
type ImageInfo struct {
ID string `json:"id"`
CreatedAt time.Time `json:"createdAt"`
IsUsed bool `json:"isUsed"`
Tags []string `json:"tags"`
Size string `json:"size"`
Size int64 `json:"size"`
}
type ImageLoad struct {
+1
View File
@@ -40,6 +40,7 @@ type AppContainerConfig struct {
WebUI string `json:"webUI"`
Type string `json:"type"`
SpecifyIP string `json:"specifyIP"`
RestartPolicy string `json:"restartPolicy" validate:"omitempty,oneof=always unless-stopped no on-failure"`
}
type AppInstalledSearch struct {
+20
View File
@@ -0,0 +1,20 @@
package request
type DiskPartitionRequest struct {
Device string `json:"device" validate:"required"`
Filesystem string `json:"filesystem" validate:"required,oneof=ext4 xfs"`
Label string `json:"label"`
AutoMount bool `json:"autoMount"`
MountPoint string `json:"mountPoint" validate:"required"`
}
type DiskMountRequest struct {
Device string `json:"device" validate:"required"`
MountPoint string `json:"mountPoint" validate:"required"`
Filesystem string `json:"filesystem" validate:"required,oneof=ext4 xfs"`
AutoMount bool `json:"autoMount"`
}
type DiskUnmountRequest struct {
MountPoint string `json:"mountPoint" validate:"required"`
}
+1
View File
@@ -20,6 +20,7 @@ type McpServerCreate struct {
HostIP string `json:"hostIP"`
StreamableHttpPath string `json:"streamableHttpPath"`
OutputTransport string `json:"outputTransport" validate:"required"`
Type string `json:"type" validate:"required"`
}
type McpServerUpdate struct {
+2 -2
View File
@@ -81,8 +81,8 @@ type NginxCommonReq struct {
type NginxAntiLeechUpdate struct {
WebsiteID uint `json:"websiteID" validate:"required"`
Extends string `json:"extends" validate:"required"`
Return string `json:"return" validate:"required"`
Extends string `json:"extends"`
Return string `json:"return"`
Enable bool `json:"enable" `
ServerNames []string `json:"serverNames"`
Cache bool `json:"cache"`
+46 -33
View File
@@ -152,21 +152,22 @@ type WebsiteDomainDelete struct {
}
type WebsiteHTTPSOp struct {
WebsiteID uint `json:"websiteId" validate:"required"`
Enable bool `json:"enable"`
WebsiteSSLID uint `json:"websiteSSLId"`
Type string `json:"type" validate:"oneof=existed auto manual"`
PrivateKey string `json:"privateKey"`
Certificate string `json:"certificate"`
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"`
Hsts bool `json:"hsts"`
HttpsPorts []int `json:"httpsPorts"`
Http3 bool `json:"http3"`
WebsiteID uint `json:"websiteId" validate:"required"`
Enable bool `json:"enable"`
WebsiteSSLID uint `json:"websiteSSLId"`
Type string `json:"type" validate:"oneof=existed auto manual"`
PrivateKey string `json:"privateKey"`
Certificate string `json:"certificate"`
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"`
Hsts bool `json:"hsts"`
HstsIncludeSubDomains bool `json:"hstsIncludeSubDomains"`
HttpsPorts []int `json:"httpsPorts"`
Http3 bool `json:"http3"`
}
type WebsiteNginxUpdate struct {
@@ -203,22 +204,24 @@ type WebsiteUpdateDirPermission struct {
}
type WebsiteProxyConfig struct {
ID uint `json:"id" validate:"required"`
Operate string `json:"operate" validate:"required"`
Enable bool `json:"enable" `
Cache bool `json:"cache" `
CacheTime int `json:"cacheTime" `
CacheUnit string `json:"cacheUnit"`
Name string `json:"name" validate:"required"`
Modifier string `json:"modifier"`
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"`
SNI bool `json:"sni"`
ProxySSLName string `json:"proxySSLName"`
ID uint `json:"id" validate:"required"`
Operate string `json:"operate" validate:"required"`
Enable bool `json:"enable" `
Cache bool `json:"cache" `
CacheTime int `json:"cacheTime"`
CacheUnit string `json:"cacheUnit"`
ServerCacheTime int `json:"serverCacheTime"`
ServerCacheUnit string `json:"serverCacheUnit"`
Name string `json:"name" validate:"required"`
Modifier string `json:"modifier"`
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"`
SNI bool `json:"sni"`
ProxySSLName string `json:"proxySSLName"`
}
type WebsiteProxyReq struct {
@@ -283,8 +286,8 @@ type WebsiteRealIP struct {
type ChangeDatabase struct {
WebsiteID uint `json:"websiteID" validate:"required"`
DatabaseID uint `json:"databaseID" validate:"required"`
DatabaseType string `json:"databaseType" validate:"required"`
DatabaseID uint `json:"databaseID" `
DatabaseType string `json:"databaseType" `
}
type WebsiteProxyDel struct {
@@ -296,3 +299,13 @@ type CrossSiteAccessOp struct {
WebsiteID uint `json:"websiteID" validate:"required"`
Operation string `json:"operation" validate:"required,oneof=Enable Disable"`
}
type ExecComposerReq struct {
Command string `json:"command" validate:"required"`
ExtCommand string `json:"extCommand"`
Mirror string `json:"mirror" validate:"required"`
Dir string `json:"dir" validate:"required"`
User string `json:"user" validate:"required"`
WebsiteID uint `json:"websiteID" validate:"required"`
TaskID string `json:"taskID" validate:"required"`
}
+6
View File
@@ -6,6 +6,8 @@ type WebsiteSSLSearch struct {
dto.PageInfo
AcmeAccountID string `json:"acmeAccountID"`
Domain string `json:"domain"`
OrderBy string `json:"orderBy" validate:"required,oneof=expire_date"`
Order string `json:"order" validate:"required,oneof=null ascending descending"`
}
type WebsiteSSLCreate struct {
@@ -27,6 +29,8 @@ type WebsiteSSLCreate struct {
Nameserver2 string `json:"nameserver2"`
ExecShell bool `json:"execShell"`
Shell string `json:"shell"`
PushNode bool `json:"pushNode"`
Nodes string `json:"nodes"`
}
type WebsiteDNSReq struct {
@@ -105,6 +109,8 @@ type WebsiteSSLUpdate struct {
Nameserver2 string `json:"nameserver2"`
ExecShell bool `json:"execShell"`
Shell string `json:"shell"`
PushNode bool `json:"pushNode"`
Nodes string `json:"nodes"`
}
type WebsiteSSLUpload struct {
+1
View File
@@ -66,6 +66,7 @@ type AppInstalledCheck struct {
InstallPath string `json:"installPath"`
HttpPort int `json:"httpPort"`
HttpsPort int `json:"httpsPort"`
WebsiteDir string `json:"websiteDir"`
}
type AppDetailDTO struct {
+37
View File
@@ -0,0 +1,37 @@
package response
type DiskInfo struct {
DiskBasicInfo
Partitions []DiskBasicInfo `json:"partitions"`
}
type DiskBasicInfo struct {
Device string `json:"device"`
Size string `json:"size"`
Model string `json:"model"`
DiskType string `json:"diskType"`
IsRemovable bool `json:"isRemovable"`
IsSystem bool `json:"isSystem"`
Filesystem string `json:"filesystem"`
Used string `json:"used"`
Avail string `json:"avail"`
UsePercent int `json:"usePercent"`
MountPoint string `json:"mountPoint"`
IsMounted bool `json:"isMounted"`
Serial string `json:"serial"`
}
type CompleteDiskInfo struct {
Disks []DiskInfo `json:"disks"`
UnpartitionedDisks []DiskBasicInfo `json:"unpartitionedDisks"`
SystemDisk *DiskInfo `json:"systemDisk"`
TotalDisks int `json:"totalDisks"`
TotalCapacity int64 `json:"totalCapacity"`
}
type MountInfo struct {
Device string `json:"device"`
MountPoint string `json:"mountPoint"`
Filesystem string `json:"filesystem"`
Options string `json:"options"`
}
+1
View File
@@ -44,6 +44,7 @@ type FileLineContent struct {
TaskStatus string `json:"taskStatus"`
Lines []string `json:"lines"`
Scope string `json:"scope"`
TotalLines int `json:"totalLines"`
}
type FileExist struct {
+5
View File
@@ -78,3 +78,8 @@ type PHPExtensionRes struct {
Extensions []string `json:"extensions"`
SupportExtensions []SupportExtension `json:"supportExtensions"`
}
type FpmStatusItem struct {
Key string `json:"key"`
Value interface{} `json:"value"`
}
+8
View File
@@ -0,0 +1,8 @@
package response
type ComponentInfo struct {
Exists bool `json:"exists"`
Version string `json:"version"`
Path string `json:"path"`
Error string `json:"error"`
}
+2
View File
@@ -37,6 +37,7 @@ type WebsiteRes struct {
ChildSites []string `json:"childSites"`
RuntimeType string `json:"runtimeType"`
Favorite bool `json:"favorite"`
IPV6 bool `json:"IPV6"`
}
type WebsiteOption struct {
@@ -64,6 +65,7 @@ type WebsiteHTTPS struct {
SSLProtocol []string `json:"SSLProtocol"`
Algorithm string `json:"algorithm"`
Hsts bool `json:"hsts"`
HstsIncludeSubDomains bool `json:"hstsIncludeSubDomains"`
HttpsPorts []int `json:"httpsPorts"`
HttpsPort string `json:"httpsPort"`
Http3 bool `json:"http3"`
-6
View File
@@ -54,12 +54,6 @@ type SearchSSHLog struct {
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"`
+18 -8
View File
@@ -3,14 +3,15 @@ package model
type Alert struct {
BaseModel
Title string `gorm:"type:varchar(256);not null" json:"title"`
Type string `gorm:"type:varchar(64);not null" json:"type"`
Cycle uint `gorm:"type:integer;not null" json:"cycle"`
Count uint `gorm:"type:integer;not null" json:"count"`
Project string `gorm:"type:varchar(64)" json:"project"`
Status string `gorm:"type:varchar(64);not null" json:"status"`
Method string `gorm:"type:varchar(64);not null" json:"method"`
SendCount uint `gorm:"type:integer" json:"sendCount"`
Title string `gorm:"type:varchar(256);not null" json:"title"`
Type string `gorm:"type:varchar(64);not null" json:"type"`
Cycle uint `gorm:"type:integer;not null" json:"cycle"`
Count uint `gorm:"type:integer;not null" json:"count"`
Project string `gorm:"type:varchar(64)" json:"project"`
Status string `gorm:"type:varchar(64);not null" json:"status"`
Method string `gorm:"type:varchar(64);not null" json:"method"`
SendCount uint `gorm:"type:integer" json:"sendCount"`
AdvancedParams string `gorm:"type:longText" json:"advancedParam"`
}
type AlertTask struct {
@@ -43,3 +44,12 @@ type AlertConfig struct {
Status string `gorm:"type:varchar(64);not null" json:"status"`
Config string `gorm:"type:varchar(256);not null" json:"config"`
}
type LoginLog struct {
BaseModel
IP string `json:"ip"`
Address string `json:"address"`
Agent string `json:"agent"`
Status string `json:"status"`
Message string `json:"message"`
}
+10
View File
@@ -4,3 +4,13 @@ type AppLauncher struct {
BaseModel
Key string `json:"key"`
}
type QuickJump struct {
BaseModel
Name string `json:"name"`
Title string `json:"title"`
Detail string `json:"detail"`
Recommend int `json:"recommend"`
IsShow bool `json:"isShow"`
Router string `json:"router"`
}
+18 -1
View File
@@ -1,14 +1,31 @@
package model
import "time"
type Clam struct {
BaseModel
Name string `gorm:"not null" json:"name"`
Status string `json:"status"`
Path string `gorm:"not null" json:"path"`
InfectedStrategy string `json:"infectedStrategy"`
InfectedDir string `json:"infectedDir"`
Spec string `json:"spec"`
EntryID int `json:"entryID"`
Description string `json:"description"`
Status string `json:"status"`
IsExecuting bool `json:"isExecuting"`
}
type ClamRecord struct {
BaseModel
ClamID uint `json:"clamID"`
TaskID string `json:"taskID"`
StartTime time.Time `json:"startTime"`
ScanTime string `json:"scanTime"`
InfectedFiles string `json:"infectedFiles"`
TotalError string `json:"totalError"`
Status string `json:"status"`
Message string `json:"message"`
}
+5 -6
View File
@@ -4,8 +4,6 @@ import (
"time"
)
// BackupAccounts ---> SourceAccountIDs
// BackupAccounts ---> DownloadAccountID
type Cronjob struct {
BaseModel
@@ -40,10 +38,11 @@ type Cronjob struct {
IgnoreErr bool `json:"ignoreErr"`
RetainCopies uint64 `json:"retainCopies"`
Status string `json:"status"`
EntryIDs string `json:"entryIDs"`
Records []JobRecords `json:"records"`
Secret string `json:"secret"`
IsExecuting bool `json:"isExecuting"`
Status string `json:"status"`
EntryIDs string `json:"entryIDs"`
Records []JobRecords `json:"records"`
Secret string `json:"secret"`
}
type JobRecords struct {
+1
View File
@@ -17,4 +17,5 @@ type McpServer struct {
HostIP string `json:"hostIP"`
StreamableHttpPath string `json:"streamableHttpPath"`
OutputTransport string `json:"outputTransport"`
Type string `json:"type"`
}
+10
View File
@@ -15,3 +15,13 @@ type NodeInfo struct {
ServerCrt string `json:"serverCrt"`
ServerKey string `json:"serverKey"`
}
type LocalConnInfo struct {
Addr string `json:"addr"`
Port uint `json:"port"`
User string `json:"user"`
AuthMode string `json:"authMode"`
Password string `json:"password"`
PrivateKey string `json:"privateKey"`
PassPhrase string `json:"passPhrase"`
}
+3
View File
@@ -36,6 +36,9 @@ type WebsiteSSL struct {
DisableCNAME bool `json:"disableCNAME"`
ExecShell bool `json:"execShell"`
Shell string `json:"shell"`
MasterSSLID uint `json:"masterSslId"`
Nodes string `json:"nodes"`
PushNode bool `json:"pushNode"`
AcmeAccount WebsiteAcmeAccount `json:"acmeAccount" gorm:"-:migration"`
DnsAccount WebsiteDnsAccount `json:"dnsAccount" gorm:"-:migration"`
+18 -1
View File
@@ -33,6 +33,8 @@ type IAppRepo interface {
BatchDelete(ctx context.Context, apps []model.App) error
DeleteByIDs(ctx context.Context, ids []uint) error
DeleteBy(opts ...DBOption) error
GetTopRecomment() ([]string, error)
}
func NewIAppRepo() IAppRepo {
@@ -44,7 +46,7 @@ func (a AppRepo) WithByLikeName(name string) DBOption {
if len(name) == 0 {
return g
}
return g.Where("name like ? or short_desc_zh like ? or short_desc_en like ?", "%"+name+"%", "%"+name+"%", "%"+name+"%")
return g.Where("name like ? or description like ? or short_desc_zh like ? or short_desc_en like ?", "%"+name+"%", "%"+name+"%", "%"+name+"%", "%"+name+"%")
}
}
@@ -123,6 +125,21 @@ func (a AppRepo) GetBy(opts ...DBOption) ([]model.App, error) {
return apps, nil
}
func (a AppRepo) GetTopRecomment() ([]string, error) {
var (
apps []model.App
names []string
)
db := getDb().Model(&model.App{})
if err := db.Order("recommend asc").Limit(6).Find(&apps).Error; err != nil {
return names, err
}
for _, item := range apps {
names = append(names, item.Key)
}
return names, nil
}
func (a AppRepo) BatchCreate(ctx context.Context, apps []model.App) error {
return getTx(ctx).Omit(clause.Associations).Create(&apps).Error
}
+46
View File
@@ -13,6 +13,10 @@ type ILauncherRepo interface {
Create(launcher *model.AppLauncher) error
Save(launcher *model.AppLauncher) error
Delete(opts ...DBOption) error
GetQuickJump(opts ...DBOption) (model.QuickJump, error)
ListQuickJump(withAll bool) []model.QuickJump
UpdateQuicks(quicks []model.QuickJump) error
}
func NewILauncherRepo() ILauncherRepo {
@@ -57,3 +61,45 @@ func (u *LauncherRepo) Delete(opts ...DBOption) error {
}
return db.Delete(&model.AppLauncher{}).Error
}
func (u *LauncherRepo) GetQuickJump(opts ...DBOption) (model.QuickJump, error) {
var launcher model.QuickJump
db := global.DB
for _, opt := range opts {
db = opt(db)
}
err := db.First(&launcher).Error
return launcher, err
}
func (u *LauncherRepo) ListQuickJump(withAll bool) []model.QuickJump {
var quicks []model.QuickJump
if withAll {
_ = global.DB.Find(&quicks).Error
} else {
_ = global.DB.Where("is_show = ?", true).Find(&quicks).Error
}
if !withAll && len(quicks) == 0 {
return []model.QuickJump{
{Name: "Website", Title: "menu.website", Recommend: 10, IsShow: true, Router: "/websites"},
{Name: "Database", Title: "menu.database", Recommend: 30, IsShow: true, Router: "/databases"},
{Name: "Cronjob", Title: "menu.cronjob", Recommend: 50, IsShow: true, Router: "/cronjobs"},
{Name: "AppInstalled", Title: "home.appInstalled", Recommend: 70, IsShow: true, Router: "/apps/installed"},
}
}
return quicks
}
func (u *LauncherRepo) UpdateQuicks(quicks []model.QuickJump) error {
tx := global.DB.Begin()
for _, item := range quicks {
if err := tx.Model(&model.QuickJump{}).Where("id = ?", item.ID).Updates(map[string]interface{}{
"is_show": item.IsShow,
"detail": item.Detail,
}).Error; err != nil {
tx.Rollback()
return err
}
}
tx.Commit()
return nil
}
+3 -3
View File
@@ -17,7 +17,7 @@ type IAppTagRepo interface {
GetByAppId(appId uint) ([]model.AppTag, error)
GetByTagIds(tagIds []uint) ([]model.AppTag, error)
DeleteBy(ctx context.Context, opts ...DBOption) error
GetFirst(opts ...DBOption) (*model.AppTag, error)
GetFirst(ctx context.Context, opts ...DBOption) (*model.AppTag, error)
WithByTagID(tagID uint) DBOption
WithByAppID(appId uint) DBOption
@@ -71,9 +71,9 @@ func (a AppTagRepo) DeleteBy(ctx context.Context, opts ...DBOption) error {
return getTx(ctx, opts...).Delete(&model.AppTag{}).Error
}
func (a AppTagRepo) GetFirst(opts ...DBOption) (*model.AppTag, error) {
func (a AppTagRepo) GetFirst(ctx context.Context, opts ...DBOption) (*model.AppTag, error) {
var appTag model.AppTag
if err := getDb(opts...).First(&appTag).Error; err != nil {
if err := getTx(ctx, opts...).First(&appTag).Error; err != nil {
return nil, err
}
return &appTag, nil
+79
View File
@@ -1,8 +1,13 @@
package repo
import (
"time"
"github.com/1Panel-dev/1Panel/agent/app/model"
"github.com/1Panel-dev/1Panel/agent/constant"
"github.com/1Panel-dev/1Panel/agent/global"
"github.com/google/uuid"
"gorm.io/gorm"
)
type ClamRepo struct{}
@@ -14,6 +19,14 @@ type IClamRepo interface {
Delete(opts ...DBOption) error
Get(opts ...DBOption) (model.Clam, error)
List(opts ...DBOption) ([]model.Clam, error)
WithByClamID(id uint) DBOption
ListRecord(opts ...DBOption) ([]model.ClamRecord, error)
RecordFirst(id uint) (model.ClamRecord, error)
DeleteRecord(opts ...DBOption) error
StartRecords(clamID uint) model.ClamRecord
EndRecords(record model.ClamRecord, status, message string)
PageRecords(page, size int, opts ...DBOption) (int64, []model.ClamRecord, error)
}
func NewIClamRepo() IClamRepo {
@@ -67,3 +80,69 @@ func (u *ClamRepo) Delete(opts ...DBOption) error {
}
return db.Delete(&model.Clam{}).Error
}
func (c *ClamRepo) WithByClamID(id uint) DBOption {
return func(g *gorm.DB) *gorm.DB {
return g.Where("clam_id = ?", id)
}
}
func (u *ClamRepo) ListRecord(opts ...DBOption) ([]model.ClamRecord, error) {
var record []model.ClamRecord
db := global.DB
for _, opt := range opts {
db = opt(db)
}
err := db.Find(&record).Error
return record, err
}
func (u *ClamRepo) RecordFirst(id uint) (model.ClamRecord, error) {
var record model.ClamRecord
err := global.DB.Where("clam_id = ?", id).Order("created_at desc").First(&record).Error
return record, err
}
func (u *ClamRepo) PageRecords(page, size int, opts ...DBOption) (int64, []model.ClamRecord, error) {
var records []model.ClamRecord
db := global.DB.Model(&model.ClamRecord{})
for _, opt := range opts {
db = opt(db)
}
count := int64(0)
db = db.Count(&count)
err := db.Order("created_at desc").Limit(size).Offset(size * (page - 1)).Find(&records).Error
return count, records, err
}
func (u *ClamRepo) StartRecords(clamID uint) model.ClamRecord {
var record model.ClamRecord
record.StartTime = time.Now()
record.ClamID = clamID
record.TaskID = uuid.New().String()
record.Status = constant.StatusWaiting
if err := global.DB.Create(&record).Error; err != nil {
global.LOG.Errorf("create record status failed, err: %v", err)
}
_ = u.Update(clamID, map[string]interface{}{"is_executing": true})
return record
}
func (u *ClamRepo) EndRecords(record model.ClamRecord, status, message string) {
upMap := make(map[string]interface{})
upMap["status"] = status
upMap["message"] = message
upMap["task_id"] = record.TaskID
upMap["scan_time"] = record.ScanTime
upMap["infected_files"] = record.InfectedFiles
upMap["total_error"] = record.TotalError
if err := global.DB.Model(&model.ClamRecord{}).Where("id = ?", record.ID).Updates(upMap).Error; err != nil {
global.LOG.Errorf("update record status failed, err: %v", err)
}
_ = u.Update(record.ClamID, map[string]interface{}{"is_executing": false})
}
func (u *ClamRepo) DeleteRecord(opts ...DBOption) error {
db := global.DB
for _, opt := range opts {
db = opt(db)
}
return db.Delete(&model.ClamRecord{}).Error
}
+15 -2
View File
@@ -29,9 +29,10 @@ type ICronjobRepo interface {
Update(id uint, vars map[string]interface{}) error
Delete(opts ...DBOption) error
DeleteRecord(opts ...DBOption) error
StartRecords(cronjobID uint, targetPath, cronjobType string) model.JobRecords
StartRecords(cronjobID uint) model.JobRecords
UpdateRecords(id uint, vars map[string]interface{}) error
EndRecords(record model.JobRecords, status, message, records string)
AddFailedRecord(cronjobID uint, message string)
PageRecords(page, size int, opts ...DBOption) (int64, []model.JobRecords, error)
}
@@ -143,7 +144,7 @@ func (c *CronjobRepo) WithByRecordDropID(id int) DBOption {
}
}
func (u *CronjobRepo) StartRecords(cronjobID uint, targetPath, cronjobType string) model.JobRecords {
func (u *CronjobRepo) StartRecords(cronjobID uint) model.JobRecords {
var record model.JobRecords
record.StartTime = time.Now()
record.CronjobID = cronjobID
@@ -152,6 +153,7 @@ func (u *CronjobRepo) StartRecords(cronjobID uint, targetPath, cronjobType strin
if err := global.DB.Create(&record).Error; err != nil {
global.LOG.Errorf("create record status failed, err: %v", err)
}
_ = u.Update(cronjobID, map[string]interface{}{"is_executing": true})
return record
}
func (u *CronjobRepo) EndRecords(record model.JobRecords, status, message, records string) {
@@ -165,6 +167,17 @@ func (u *CronjobRepo) EndRecords(record model.JobRecords, status, message, recor
if err := global.DB.Model(&model.JobRecords{}).Where("id = ?", record.ID).Updates(errMap).Error; err != nil {
global.LOG.Errorf("update record status failed, err: %v", err)
}
_ = u.Update(record.CronjobID, map[string]interface{}{"is_executing": false})
}
func (u *CronjobRepo) AddFailedRecord(cronjobID uint, message string) {
var record model.JobRecords
record.StartTime = time.Now()
record.CronjobID = cronjobID
record.Status = constant.StatusFailed
record.Message = message
if err := global.DB.Create(&record).Error; err != nil {
global.LOG.Errorf("create record status failed, err: %v", err)
}
}
func (u *CronjobRepo) Save(id uint, cronjob model.Cronjob) error {
+7
View File
@@ -17,6 +17,7 @@ type IRuntimeRepo interface {
WithNotId(id uint) DBOption
WithStatus(status string) DBOption
WithDetailId(id uint) DBOption
WithDetailIdsIn(ids []uint) DBOption
WithPort(port int) DBOption
WithNormalStatus(status string) DBOption
Page(page, size int, opts ...DBOption) (int64, []model.Runtime, error)
@@ -55,6 +56,12 @@ func (r *RuntimeRepo) WithDetailId(id uint) DBOption {
}
}
func (r *RuntimeRepo) WithDetailIdsIn(ids []uint) DBOption {
return func(g *gorm.DB) *gorm.DB {
return g.Where("app_detail_id in(?) ", ids)
}
}
func (r *RuntimeRepo) WithNotId(id uint) DBOption {
return func(g *gorm.DB) *gorm.DB {
return g.Where("id != ?", id)
+14
View File
@@ -19,6 +19,8 @@ type IWebsiteRepo interface {
WithRuntimeID(runtimeID uint) DBOption
WithParentID(websiteID uint) DBOption
WithType(websiteType string) DBOption
WithDBType(dbType string) DBOption
WithDBID(dbID uint) DBOption
Page(page, size int, opts ...DBOption) (int64, []model.Website, error)
List(opts ...DBOption) ([]model.Website, error)
@@ -100,6 +102,18 @@ func (w *WebsiteRepo) WithType(websiteType string) DBOption {
}
}
func (w *WebsiteRepo) WithDBType(dbType string) DBOption {
return func(db *gorm.DB) *gorm.DB {
return db.Where("db_type = ?", dbType)
}
}
func (w *WebsiteRepo) WithDBID(dbID uint) DBOption {
return func(db *gorm.DB) *gorm.DB {
return db.Where("db_id = ?", dbID)
}
}
func (w *WebsiteRepo) Page(page, size int, opts ...DBOption) (int64, []model.Website, error) {
var websites []model.Website
db := getDb(opts...).Model(&model.Website{})
+8
View File
@@ -17,6 +17,7 @@ type ISSLRepo interface {
WithByDnsAccountId(dnsAccountId uint) DBOption
WithByCAID(caID uint) DBOption
WithByDomain(domain string) DBOption
WithByMasterSSLID(sslID uint) DBOption
Page(page, size int, opts ...DBOption) (int64, []model.WebsiteSSL, error)
GetFirst(opts ...DBOption) (*model.WebsiteSSL, error)
List(opts ...DBOption) ([]model.WebsiteSSL, error)
@@ -52,12 +53,19 @@ func (w WebsiteSSLRepo) WithByCAID(caID uint) DBOption {
return db.Where("ca_id = ?", caID)
}
}
func (w WebsiteSSLRepo) WithByDomain(domain string) DBOption {
return func(db *gorm.DB) *gorm.DB {
return db.Where("primary_domain Like ? or domains Like ?", "%"+domain+"%", "%"+domain+"%")
}
}
func (w WebsiteSSLRepo) WithByMasterSSLID(sslID uint) DBOption {
return func(db *gorm.DB) *gorm.DB {
return db.Where("master_ssl_id = ?", sslID)
}
}
func (w WebsiteSSLRepo) Page(page, size int, opts ...DBOption) (int64, []model.WebsiteSSL, error) {
var sslList []model.WebsiteSSL
db := getDb(opts...).Model(&model.WebsiteSSL{})
+82 -34
View File
@@ -14,7 +14,7 @@ import (
"github.com/1Panel-dev/1Panel/agent/utils/copier"
"github.com/1Panel-dev/1Panel/agent/utils/email"
"github.com/1Panel-dev/1Panel/agent/utils/xpack"
"github.com/shirou/gopsutil/v3/disk"
"github.com/shirou/gopsutil/v4/disk"
"sort"
"strings"
"sync"
@@ -70,17 +70,18 @@ func (a AlertService) PageAlert(search dto.AlertSearch) (int64, []dto.AlertDTO,
for _, item := range alerts {
result = append(result, dto.AlertDTO{
ID: item.ID,
Type: item.Type,
Cycle: item.Cycle,
Count: item.Count,
Method: item.Method,
Title: item.Title,
Project: item.Project,
Status: item.Status,
SendCount: item.SendCount,
CreatedAt: item.CreatedAt,
UpdatedAt: item.UpdatedAt,
ID: item.ID,
Type: item.Type,
Cycle: item.Cycle,
Count: item.Count,
Method: item.Method,
Title: item.Title,
Project: item.Project,
Status: item.Status,
SendCount: item.SendCount,
AdvancedParams: item.AdvancedParams,
CreatedAt: item.CreatedAt,
UpdatedAt: item.UpdatedAt,
})
}
@@ -100,17 +101,18 @@ func (a AlertService) GetAlerts() ([]dto.AlertDTO, error) {
for _, item := range alerts {
result = append(result, dto.AlertDTO{
ID: item.ID,
Type: item.Type,
Cycle: item.Cycle,
Count: item.Count,
Method: item.Method,
Title: item.Title,
Project: item.Project,
Status: item.Status,
SendCount: item.SendCount,
CreatedAt: item.CreatedAt,
UpdatedAt: item.UpdatedAt,
ID: item.ID,
Type: item.Type,
Cycle: item.Cycle,
Count: item.Count,
Method: item.Method,
Title: item.Title,
Project: item.Project,
Status: item.Status,
SendCount: item.SendCount,
AdvancedParams: item.AdvancedParams,
CreatedAt: item.CreatedAt,
UpdatedAt: item.UpdatedAt,
})
}
@@ -165,6 +167,7 @@ func (a AlertService) UpdateAlert(req dto.AlertUpdate) error {
upMap["project"] = req.Project
upMap["status"] = req.Status
upMap["send_count"] = req.SendCount
upMap["advanced_params"] = req.AdvancedParams
if err := alertRepo.Update(upMap, repo.WithByID(req.ID)); err != nil {
return err
@@ -174,7 +177,24 @@ func (a AlertService) UpdateAlert(req dto.AlertUpdate) error {
}
func (a AlertService) DeleteAlert(id uint) error {
return alertRepo.Delete(repo.WithByID(id))
alertInfo, _ := alertRepo.Get(repo.WithByID(id))
if alertInfo.ID == 0 {
return buserr.New("ErrRecordNotFound")
}
err := alertRepo.Delete(repo.WithByID(id))
if err != nil {
return err
}
alerts, err := a.GetAlerts()
if err != nil {
return err
}
if len(alerts) > 0 {
NewIAlertTaskHelper().InitTask(alertInfo.Type)
} else {
NewIAlertTaskHelper().StopTask()
}
return nil
}
func (a AlertService) GetAlert(id uint) (dto.AlertDTO, error) {
@@ -483,23 +503,51 @@ func (a AlertService) TestAlertConfig(req dto.AlertConfigTest) (bool, error) {
func (a AlertService) ExternalUpdateAlert(updateAlert dto.AlertCreate) error {
upMap := make(map[string]interface{})
var newStatus string
if updateAlert.SendCount == 0 {
upMap["status"] = constant.AlertDisable
newStatus = constant.AlertDisable
} else {
upMap["status"] = constant.AlertEnable
newStatus = constant.AlertEnable
upMap["send_count"] = updateAlert.SendCount
if updateAlert.Method != "" {
upMap["method"] = updateAlert.Method
}
}
upMap["method"] = updateAlert.Method
alertInfo, _ := alertRepo.Get(alertRepo.WithByType(updateAlert.Type), alertRepo.WithByProject(updateAlert.Project))
upMap["status"] = newStatus
alertInfo, _ := alertRepo.Get(
alertRepo.WithByType(updateAlert.Type),
alertRepo.WithByProject(updateAlert.Project),
)
if alertInfo.ID > 0 {
if err := alertRepo.Update(upMap, alertRepo.WithByProject(updateAlert.Project), alertRepo.WithByType(updateAlert.Type)); err != nil {
return err
shouldUpdate := false
if alertInfo.Status != newStatus {
shouldUpdate = true
}
if val, ok := upMap["send_count"]; ok && val != alertInfo.SendCount {
shouldUpdate = true
}
if val, ok := upMap["method"]; ok && val != "" && val != alertInfo.Method {
shouldUpdate = true
}
if shouldUpdate {
if err := alertRepo.Update(
upMap,
alertRepo.WithByProject(updateAlert.Project),
alertRepo.WithByType(updateAlert.Type),
); err != nil {
return err
}
}
} else {
updateAlert.Status = constant.AlertEnable
err := a.CreateAlert(updateAlert)
if err != nil {
return err
if updateAlert.Method != "" && updateAlert.Title != "" {
updateAlert.Status = newStatus
if err := a.CreateAlert(updateAlert); err != nil {
return err
}
}
}
File diff suppressed because it is too large Load Diff
+165
View File
@@ -0,0 +1,165 @@
package service
import (
"github.com/1Panel-dev/1Panel/agent/app/dto"
"github.com/1Panel-dev/1Panel/agent/constant"
"github.com/1Panel-dev/1Panel/agent/global"
alertUtil "github.com/1Panel-dev/1Panel/agent/utils/alert"
"github.com/1Panel-dev/1Panel/agent/utils/xpack"
"strings"
)
type AlertSender struct {
alert dto.AlertDTO
quotaType string
}
func NewAlertSender(alert dto.AlertDTO, quotaType string) *AlertSender {
return &AlertSender{
alert: alert,
quotaType: quotaType,
}
}
func (s *AlertSender) Send(quota string, params []dto.Param) {
methods := strings.Split(s.alert.Method, ",")
for _, method := range methods {
method = strings.TrimSpace(method)
switch method {
case constant.SMS:
s.sendSMS(quota, params)
case constant.Email:
s.sendEmail(quota, params)
}
}
}
func (s *AlertSender) ResourceSend(quota string, params []dto.Param) {
methods := strings.Split(s.alert.Method, ",")
for _, method := range methods {
method = strings.TrimSpace(method)
switch method {
case constant.SMS:
s.sendResourceSMS(quota, params)
case constant.Email:
s.sendResourceEmail(quota, params)
}
}
}
func (s *AlertSender) sendSMS(quota string, params []dto.Param) {
if !alertUtil.CheckSMSSendLimit(constant.SMS) {
return
}
totalCount, isValid := s.canSendAlert(constant.SMS)
if !isValid {
return
}
create := dto.AlertLogCreate{
Status: constant.AlertSuccess,
Count: totalCount + 1,
AlertId: s.alert.ID,
Type: s.alert.Type,
}
_ = xpack.CreateSMSAlertLog(s.alert.Type, s.alert, create, quota, params, constant.SMS)
alertUtil.CreateNewAlertTask(quota, s.alert.Type, s.quotaType, constant.SMS)
global.LOG.Infof("%s alert sms push successful", s.alert.Type)
}
func (s *AlertSender) sendEmail(quota string, params []dto.Param) {
totalCount, isValid := s.canSendAlert(constant.Email)
if !isValid {
return
}
create := dto.AlertLogCreate{
Status: constant.AlertSuccess,
Count: totalCount + 1,
AlertId: s.alert.ID,
Type: s.alert.Type,
AlertRule: alertUtil.ProcessAlertRule(s.alert),
AlertDetail: alertUtil.ProcessAlertDetail(s.alert, quota, params, constant.Email),
}
transport := xpack.LoadRequestTransport()
_ = alertUtil.CreateEmailAlertLog(create, s.alert, params, transport)
alertUtil.CreateNewAlertTask(quota, s.alert.Type, s.quotaType, constant.Email)
global.LOG.Infof("%s alert email push successful", s.alert.Type)
}
func (s *AlertSender) sendResourceSMS(quota string, params []dto.Param) {
if !alertUtil.CheckSMSSendLimit(constant.SMS) {
return
}
todayCount, isValid := s.canResourceSendAlert(constant.SMS)
if !isValid {
return
}
create := dto.AlertLogCreate{
Status: constant.AlertSuccess,
Count: todayCount + 1,
AlertId: s.alert.ID,
Type: s.alert.Type,
}
if err := xpack.CreateSMSAlertLog(s.alert.Type, s.alert, create, quota, params, constant.SMS); err != nil {
global.LOG.Errorf("failed to send SMS alert: %v", err)
return
}
alertUtil.CreateNewAlertTask(quota, s.alert.Type, s.quotaType, constant.SMS)
global.LOG.Infof("%s alert sms push successful", s.alert.Type)
}
func (s *AlertSender) sendResourceEmail(quota string, params []dto.Param) {
todayCount, isValid := s.canResourceSendAlert(constant.Email)
if !isValid {
return
}
create := dto.AlertLogCreate{
Status: constant.AlertSuccess,
Count: todayCount + 1,
AlertId: s.alert.ID,
Type: s.alert.Type,
AlertRule: alertUtil.ProcessAlertRule(s.alert),
AlertDetail: alertUtil.ProcessAlertDetail(s.alert, quota, params, constant.Email),
}
transport := xpack.LoadRequestTransport()
if err := alertUtil.CreateEmailAlertLog(create, s.alert, params, transport); err != nil {
global.LOG.Errorf("failed to send Email alert: %v", err)
return
}
alertUtil.CreateNewAlertTask(quota, s.alert.Type, s.quotaType, constant.Email)
global.LOG.Infof("%s alert email push successful", s.alert.Type)
}
func (s *AlertSender) canSendAlert(method string) (uint, bool) {
todayCount, totalCount, err := alertRepo.LoadTaskCount(s.alert.Type, s.quotaType, method)
if err != nil {
global.LOG.Errorf("error getting task count: %v", err)
return totalCount, false
}
if todayCount >= 1 || s.alert.SendCount <= totalCount {
return totalCount, false
}
return totalCount, true
}
func (s *AlertSender) canResourceSendAlert(method string) (uint, bool) {
todayCount, _, err := alertRepo.LoadTaskCount(s.alert.Type, s.quotaType, method)
if err != nil {
global.LOG.Errorf("error getting task count: %v", err)
return todayCount, false
}
if s.alert.SendCount <= todayCount {
return todayCount, false
}
return todayCount, true
}
+27 -30
View File
@@ -7,7 +7,6 @@ import (
"fmt"
"net/http"
"os"
"path"
"path/filepath"
"reflect"
"strconv"
@@ -56,7 +55,7 @@ func (a AppService) PageApp(ctx *gin.Context, req request.AppSearch) (interface{
var opts []repo.DBOption
opts = append(opts, appRepo.OrderByRecommend())
if req.Name != "" {
opts = append(opts, appRepo.WithByLikeName(req.Name))
opts = append(opts, appRepo.WithByLikeName(strings.TrimSpace(req.Name)))
}
if req.Type != "" {
opts = append(opts, appRepo.WithType(req.Type))
@@ -100,7 +99,7 @@ func (a AppService) PageApp(ctx *gin.Context, req request.AppSearch) (interface{
if err != nil {
return nil, err
}
var appDTOs []*response.AppItem
appDTOs := make([]*response.AppItem, 0)
info := &dto.SettingInfo{}
if req.Type == "php" {
info, _ = NewISettingService().GetSettingInfo()
@@ -132,8 +131,21 @@ func (a AppService) PageApp(ctx *gin.Context, req request.AppSearch) (interface{
continue
}
appDTO.Tags = tags
installs, _ := appInstallRepo.ListBy(context.Background(), appInstallRepo.WithAppId(ap.ID))
appDTO.Installed = len(installs) > 0
if ap.Type == constant.RuntimePHP || ap.Type == constant.RuntimeGo || ap.Type == constant.RuntimeNode || ap.Type == constant.RuntimePython || ap.Type == constant.RuntimeJava || ap.Type == constant.RuntimeDotNet {
details, _ := appDetailRepo.GetBy(appDetailRepo.WithAppId(ap.ID))
var ids []uint
if len(details) == 0 {
continue
}
for _, d := range details {
ids = append(ids, d.ID)
}
runtimes, _ := runtimeRepo.List(runtimeRepo.WithDetailIdsIn(ids))
appDTO.Installed = len(runtimes) > 0
} else {
installs, _ := appInstallRepo.ListBy(context.Background(), appInstallRepo.WithAppId(ap.ID))
appDTO.Installed = len(installs) > 0
}
}
res.Items = appDTOs
res.Total = total
@@ -355,29 +367,6 @@ func (a AppService) Install(req request.AppInstallCreate) (appInstall *model.App
}
}
}
if app.Key == "openresty" && (app.Resource == "remote" || app.Resource == "custom") && common.CompareVersion(appDetail.Version, "1.27") {
if dir, ok := req.Params["WEBSITE_DIR"]; ok {
siteDir := dir.(string)
if siteDir == "" || !strings.HasPrefix(siteDir, "/") {
siteDir = path.Join(global.Dir.DataDir, dir.(string))
}
req.Params["WEBSITE_DIR"] = siteDir
oldWebStePath, _ := settingRepo.GetValueByKey("WEBSITE_DIR")
fileOp := files.NewFileOp()
if oldWebStePath != "" && oldWebStePath != siteDir && fileOp.Stat(oldWebStePath) {
_ = fileOp.Rename(oldWebStePath, siteDir)
}
if !fileOp.Stat(siteDir) {
_ = fileOp.CreateDir(siteDir, constant.DirPerm)
_ = fileOp.CreateDir(path.Join(siteDir, "conf.d"), constant.DirPerm)
}
err = settingRepo.UpdateOrCreate("WEBSITE_DIR", siteDir)
if err != nil {
return
}
go RestartPHPRuntime()
}
}
for key := range req.Params {
if !strings.Contains(key, "PANEL_APP_PORT") {
continue
@@ -486,6 +475,11 @@ func (a AppService) Install(req request.AppInstallCreate) (appInstall *model.App
req.Params["DATABASE_NAME"] = database.Name
}
}
if app.Key == "openresty" {
req.Params["CONTAINER_PACKAGE_URL"] = "http://archive.ubuntu.com/ubuntu/"
req.Params["RESTY_ADD_PACKAGE_BUILDDEPS"] = ""
req.Params["RESTY_CONFIG_OPTIONS_MORE"] = ""
}
paramByte, err = json.Marshal(req.Params)
if err != nil {
return
@@ -513,6 +507,9 @@ func (a AppService) Install(req request.AppInstallCreate) (appInstall *model.App
return err
}
if app.Key == "openresty" {
if err = handleSiteDir(app, appDetail, req, t); err != nil {
return err
}
if err = handleOpenrestyFile(appInstall); err != nil {
return err
}
@@ -1074,8 +1071,8 @@ func (a AppService) SyncAppListFromRemote(taskID string) (err error) {
for _, tag := range app.TagsKey {
tagId, ok := tagMap[tag]
if ok {
exist, _ := appTagRepo.GetFirst(appTagRepo.WithByTagID(tagId), appTagRepo.WithByAppID(app.ID))
if exist != nil {
exist, _ := appTagRepo.GetFirst(ctx, appTagRepo.WithByTagID(tagId), appTagRepo.WithByAppID(app.ID))
if exist == nil {
appTags = append(appTags, &model.AppTag{
AppId: app.ID,
TagId: tagId,
+6
View File
@@ -164,6 +164,11 @@ func (a *AppInstallService) CheckExist(req request.AppInstalledInfo) (*response.
res.HttpPort = appInstall.HttpPort
res.HttpsPort = appInstall.HttpsPort
if appInstall.App.Key == "openresty" {
websiteDir, _ := settingRepo.GetValueByKey("WEBSITE_DIR")
res.WebsiteDir = websiteDir
}
return res, nil
}
@@ -788,6 +793,7 @@ func (a *AppInstallService) GetParams(id uint) (*response.AppConfig, error) {
}
res.AppContainerConfig = config
res.HostMode = isHostModel(install.DockerCompose)
res.RestartPolicy = getRestartPolicy(install.DockerCompose)
res.WebUI = install.WebUI
res.Type = install.App.Type
return &res, nil
+172 -11
View File
@@ -1,11 +1,13 @@
package service
import (
"bufio"
"context"
"encoding/base64"
"encoding/json"
"fmt"
"log"
"maps"
"math"
"net/http"
"os"
@@ -357,7 +359,7 @@ func deleteAppInstall(deleteReq request.AppInstallDelete) error {
if err != nil {
return err
}
images, err := composeV2.GetDockerComposeImagesV2(content, []byte(install.DockerCompose))
images, err := composeV2.GetImagesFromDockerCompose(content, []byte(install.DockerCompose))
if err != nil {
return err
}
@@ -540,6 +542,9 @@ func handleUpgradeCompose(install model.AppInstall, detail model.AppDetail) (map
if oldServiceValue["deploy"] != nil {
serviceValue["deploy"] = oldServiceValue["deploy"]
}
if oldServiceValue["restart"] != nil {
serviceValue["restart"] = oldServiceValue["restart"]
}
servicesMap[install.ServiceName] = serviceValue
composeMap["services"] = servicesMap
return composeMap, nil
@@ -578,6 +583,76 @@ func getUpgradeCompose(install model.AppInstall, detail model.AppDetail) (string
return string(composeByte), nil
}
func buildNginx(parentTask *task.Task) error {
nginxInstall, err := getAppInstallByKey(constant.AppOpenresty)
if err != nil {
return err
}
fileOp := files.NewFileOp()
buildPath := path.Join(nginxInstall.GetPath(), "build")
if !fileOp.Stat(buildPath) {
return buserr.New("ErrBuildDirNotFound")
}
moduleConfigPath := path.Join(buildPath, "module.json")
moduleContent, err := fileOp.GetContent(moduleConfigPath)
if err != nil {
return err
}
var (
modules []dto.NginxModule
addModuleParams []string
addPackages []string
)
if len(moduleContent) > 0 {
_ = json.Unmarshal(moduleContent, &modules)
bashFile, err := os.OpenFile(path.Join(buildPath, "tmp", "pre.sh"), os.O_CREATE|os.O_WRONLY|os.O_TRUNC, constant.DirPerm)
if err != nil {
return err
}
defer bashFile.Close()
bashFileWriter := bufio.NewWriter(bashFile)
for _, module := range modules {
if !module.Enable {
continue
}
_, err = bashFileWriter.WriteString(module.Script + "\n")
if err != nil {
return err
}
addModuleParams = append(addModuleParams, module.Params)
addPackages = append(addPackages, module.Packages...)
}
err = bashFileWriter.Flush()
if err != nil {
return err
}
}
envs, err := gotenv.Read(nginxInstall.GetEnvPath())
if err != nil {
return err
}
envs["RESTY_CONFIG_OPTIONS_MORE"] = ""
envs["RESTY_ADD_PACKAGE_BUILDDEPS"] = ""
if len(addModuleParams) > 0 {
envs["RESTY_CONFIG_OPTIONS_MORE"] = strings.Join(addModuleParams, " ")
}
if len(addPackages) > 0 {
envs["RESTY_ADD_PACKAGE_BUILDDEPS"] = strings.Join(addPackages, " ")
}
_ = gotenv.Write(envs, nginxInstall.GetEnvPath())
if len(addModuleParams) == 0 && len(addPackages) == 0 {
return nil
}
logStr := fmt.Sprintf("%s %s", i18n.GetMsgByKey("TaskBuild"), i18n.GetMsgByKey("Image"))
parentTask.LogStart(logStr)
cmdMgr := cmd.NewCommandMgr(cmd.WithTask(*parentTask), cmd.WithTimeout(60*time.Minute))
if err = cmdMgr.RunBashCf("docker compose -f %s build", nginxInstall.GetComposePath()); err != nil {
return err
}
parentTask.LogSuccess(logStr)
return nil
}
func upgradeInstall(req request.AppInstallUpgrade) error {
install, err := appInstallRepo.GetFirst(repo.WithByID(req.InstallID))
if err != nil {
@@ -659,7 +734,7 @@ func upgradeInstall(req request.AppInstallUpgrade) error {
if req.DockerCompose != "" {
composeContent = []byte(req.DockerCompose)
}
images, err := composeV2.GetDockerComposeImagesV2(content, composeContent)
images, err := composeV2.GetImagesFromDockerCompose(content, composeContent)
if err != nil {
return err
}
@@ -710,6 +785,14 @@ func upgradeInstall(req request.AppInstallUpgrade) error {
return err
}
envParams := make(map[string]string, len(envs))
if install.App.Key == constant.AppOpenresty {
packageUrl, _ := env.GetEnvValueByKey(install.GetEnvPath(), "CONTAINER_PACKAGE_URL")
addPackage, _ := env.GetEnvValueByKey(install.GetEnvPath(), "RESTY_ADD_PACKAGE_BUILDDEPS")
options, _ := env.GetEnvValueByKey(install.GetEnvPath(), "RESTY_CONFIG_OPTIONS_MORE")
envParams["CONTAINER_PACKAGE_URL"] = packageUrl
envParams["RESTY_ADD_PACKAGE_BUILDDEPS"] = addPackage
envParams["RESTY_CONFIG_OPTIONS_MORE"] = options
}
handleMap(envs, envParams)
if err = env.Write(envParams, install.GetEnvPath()); err != nil {
return err
@@ -723,6 +806,13 @@ func upgradeInstall(req request.AppInstallUpgrade) error {
return err
}
if install.App.Key == constant.AppOpenresty {
if err = buildNginx(t); err != nil {
t.Log(err.Error())
return err
}
}
logStr := fmt.Sprintf("%s %s", i18n.GetMsgByKey("Run"), i18n.GetMsgByKey("App"))
t.Log(logStr)
if out, err := compose.Up(install.GetComposePath()); err != nil {
@@ -748,7 +838,7 @@ func upgradeInstall(req request.AppInstallUpgrade) error {
}
}
upgradeTask.AddSubTask(task.GetTaskName(install.Name, task.TaskScopeApp, task.TaskUpgrade), upgradeApp, rollBackApp)
upgradeTask.AddSubTaskWithOps(task.GetTaskName(install.Name, task.TaskScopeApp, task.TaskUpgrade), upgradeApp, rollBackApp, 0, 1*time.Hour)
go func() {
err = upgradeTask.Execute()
@@ -873,9 +963,9 @@ func downloadApp(app model.App, appDetail model.AppDetail, appInstall *model.App
_ = fileOp.CreateDir(appVersionDir, constant.DirPerm)
}
if logger == nil {
global.LOG.Infof("download app[%s] from %s", app.Name, appDetail.DownloadUrl)
global.LOG.Infof("download app [%s] from %s", app.Name, appDetail.DownloadUrl)
} else {
logger.Printf("download app[%s] from %s", app.Name, appDetail.DownloadUrl)
logger.Printf("download app [%s] from %s", app.Name, appDetail.DownloadUrl)
}
filePath := path.Join(appVersionDir, app.Key+"-"+appDetail.Version+".tar.gz")
@@ -891,9 +981,9 @@ func downloadApp(app model.App, appDetail model.AppDetail, appInstall *model.App
if err = files.DownloadFileWithProxy(appDetail.DownloadUrl, filePath); err != nil {
if logger == nil {
global.LOG.Errorf("download app[%s] error %v", app.Name, err)
global.LOG.Errorf("download app [%s] error %v", app.Name, err)
} else {
logger.Printf("download app[%s] error %v", app.Name, err)
logger.Printf("download app [%s] error %v", app.Name, err)
}
return
}
@@ -955,11 +1045,11 @@ func copyData(task *task.Task, app model.App, appDetail model.AppDetail, appInst
return
}
envPath := path.Join(appDir, ".env")
envParams := make(map[string]string, len(req.Params))
envParams := make(map[string]string)
if fileOp.Stat(envPath) {
envs, _ := gotenv.Read(envPath)
for k, v := range envs {
envParams[k] = v
if envParams = maps.Clone(envs); envParams == nil {
envParams = make(map[string]string)
}
}
handleMap(req.Params, envParams)
@@ -992,7 +1082,7 @@ func runScript(task *task.Task, appInstall *model.AppInstall, operate string) er
task.LogStart(logStr)
cmdMgr := cmd.NewCommandMgr(cmd.WithTimeout(10*time.Minute), cmd.WithWorkDir(workDir))
out, err := cmdMgr.RunWithStdoutBashCf(scriptPath)
out, err := cmdMgr.RunWithStdoutBashC(scriptPath)
if err != nil {
if out != "" {
err = errors.New(out)
@@ -1658,6 +1748,14 @@ func addDockerComposeCommonParam(composeMap map[string]interface{}, serviceName
deploy["resources"] = resource
serviceValue["deploy"] = deploy
if req.RestartPolicy != "" {
if req.RestartPolicy == "on-failure" {
serviceValue["restart"] = "on-failure:5"
} else {
serviceValue["restart"] = req.RestartPolicy
}
}
ports, ok := serviceValue["ports"].([]interface{})
if ok {
for i, port := range ports {
@@ -1760,6 +1858,17 @@ func isHostModel(dockerCompose string) bool {
return false
}
func getRestartPolicy(yml string) string {
var project docker.ComposeProject
if err := yaml.Unmarshal([]byte(yml), &project); err != nil {
return ""
}
for _, service := range project.Services {
return service.Restart
}
return ""
}
func getMajorVersion(version string) string {
parts := strings.Split(version, ".")
if len(parts) >= 2 {
@@ -1786,6 +1895,11 @@ func ignoreUpdate(installed model.AppInstall) bool {
}
return true
}
if installed.App.Key == "sqlbot" {
if common.CompareVersion("1.1.0", installed.Version) {
return true
}
}
return false
}
@@ -1825,6 +1939,53 @@ func getAppTags(appID uint, lang string) ([]response.TagDTO, error) {
return res, nil
}
func handleSiteDir(app model.App, appDetail model.AppDetail, req request.AppInstallCreate, t *task.Task) error {
if app.Key == "openresty" && (app.Resource == "remote" || app.Resource == "custom") && common.CompareVersion(appDetail.Version, "1.27") {
if dir, ok := req.Params["WEBSITE_DIR"]; ok {
siteDir := dir.(string)
if siteDir == "" || !strings.HasPrefix(siteDir, "/") {
siteDir = path.Join(global.Dir.DataDir, dir.(string))
}
req.Params["WEBSITE_DIR"] = siteDir
oldWebStePath, _ := settingRepo.GetValueByKey("WEBSITE_DIR")
fileOp := files.NewFileOp()
if oldWebStePath != "" && oldWebStePath != siteDir && fileOp.Stat(oldWebStePath) {
t.Log(i18n.GetWithName("MoveSiteDir", siteDir))
if fileOp.Stat(siteDir) {
if fileOp.Stat(path.Join(siteDir, "conf.d")) {
_ = fileOp.Rename(path.Join(siteDir, "conf.d"), path.Join(siteDir, "conf.d.bak"))
}
if fileOp.Stat(path.Join(siteDir, "sites")) {
_ = fileOp.Rename(path.Join(siteDir, "sites"), path.Join(siteDir, "sites.bak"))
}
if err := fileOp.Rename(path.Join(oldWebStePath, "sites"), path.Join(siteDir, "sites")); err != nil {
return err
}
if err := fileOp.Rename(path.Join(oldWebStePath, "conf.d"), path.Join(siteDir, "conf.d")); err != nil {
return err
}
} else {
err := fileOp.Rename(oldWebStePath, siteDir)
if err != nil {
return err
}
}
t.Log(i18n.GetMsgByKey("MoveSiteDirSuccess"))
}
if !fileOp.Stat(siteDir) {
_ = fileOp.CreateDir(siteDir, constant.DirPerm)
_ = fileOp.CreateDir(path.Join(siteDir, "conf.d"), constant.DirPerm)
}
err := settingRepo.UpdateOrCreate("WEBSITE_DIR", siteDir)
if err != nil {
return err
}
go RestartPHPRuntime()
}
}
return nil
}
func handleOpenrestyFile(appInstall *model.AppInstall) error {
websites, _ := websiteRepo.List()
hasDefaultWebsite := false
+68 -6
View File
@@ -14,9 +14,11 @@ import (
"github.com/1Panel-dev/1Panel/agent/app/dto"
"github.com/1Panel-dev/1Panel/agent/app/model"
"github.com/1Panel-dev/1Panel/agent/app/repo"
"github.com/1Panel-dev/1Panel/agent/app/task"
"github.com/1Panel-dev/1Panel/agent/buserr"
"github.com/1Panel-dev/1Panel/agent/constant"
"github.com/1Panel-dev/1Panel/agent/global"
"github.com/1Panel-dev/1Panel/agent/i18n"
"github.com/1Panel-dev/1Panel/agent/utils/cloud_storage"
"github.com/1Panel-dev/1Panel/agent/utils/cloud_storage/client"
"github.com/1Panel-dev/1Panel/agent/utils/encrypt"
@@ -37,6 +39,7 @@ type IBackupService interface {
Delete(id uint) error
RefreshToken(req dto.OperateByID) error
GetLocalDir() (string, error)
UploadForRecover(req dto.UploadForRecover) error
MysqlBackup(db dto.CommonBackup) error
PostgresqlBackup(db dto.CommonBackup) error
@@ -307,6 +310,16 @@ func (u *BackupService) RefreshToken(req dto.OperateByID) error {
return backupRepo.Save(&backup)
}
func (u *BackupService) UploadForRecover(req dto.UploadForRecover) error {
fileOp := files.NewFileOp()
if !fileOp.Stat(req.TargetDir) {
if err := fileOp.CreateDir(req.TargetDir, constant.DirPerm); err != nil {
return err
}
}
return fileOp.Copy(req.FilePath, req.TargetDir)
}
func (u *BackupService) checkBackupConn(backup *model.BackupAccount) (bool, error) {
client, err := newClient(backup, false)
if err != nil {
@@ -393,9 +406,13 @@ type backupClientHelper struct {
name string
backupPath string
client cloud_storage.CloudStorageClient
isOk bool
hasBackup bool
message string
}
func NewBackupClientMap(ids []string) (map[string]backupClientHelper, error) {
func NewBackupClientMap(ids []string) map[string]backupClientHelper {
var accounts []model.BackupAccount
var idItems []uint
for i := 0; i < len(ids); i++ {
@@ -406,18 +423,63 @@ func NewBackupClientMap(ids []string) (map[string]backupClientHelper, error) {
clientMap := make(map[string]backupClientHelper)
for _, item := range accounts {
backClient, err := newClient(&item, true)
if err != nil {
return nil, err
}
clientMap[fmt.Sprintf("%v", item.ID)] = backupClientHelper{
itemHelper := backupClientHelper{
client: backClient,
name: item.Name,
backupPath: item.BackupPath,
accountType: item.Type,
id: item.ID,
isOk: err == nil,
}
if err != nil {
itemHelper.message = err.Error()
}
clientMap[fmt.Sprintf("%v", item.ID)] = itemHelper
}
return clientMap, nil
return clientMap
}
func uploadWithMap(taskItem task.Task, accountMap map[string]backupClientHelper, src, dst, accountIDs string, downloadAccountID, retry uint) error {
accounts := strings.Split(accountIDs, ",")
for _, account := range accounts {
if len(account) == 0 {
continue
}
itemBackup, ok := accountMap[account]
if !ok {
continue
}
if itemBackup.hasBackup {
continue
}
if !itemBackup.isOk {
taskItem.LogFailed(i18n.GetMsgWithDetail("LoadBackupFailed", itemBackup.message))
continue
}
name := itemBackup.name
if itemBackup.name == "localhost" {
name = i18n.GetMsgByKey("Localhost")
}
taskItem.LogStart(i18n.GetMsgWithMap("UploadFile", map[string]interface{}{
"file": path.Join(itemBackup.backupPath, dst),
"backup": name,
}))
for i := 0; i < int(retry)+1; i++ {
_, err := itemBackup.client.Upload(src, path.Join(itemBackup.backupPath, dst))
taskItem.LogWithStatus(i18n.GetMsgByKey("Upload"), err)
if err != nil {
if account == fmt.Sprintf("%d", downloadAccountID) {
return err
}
} else {
break
}
}
itemBackup.hasBackup = true
accountMap[account] = itemBackup
}
os.RemoveAll(src)
return nil
}
func newClient(account *model.BackupAccount, isEncrypt bool) (cloud_storage.CloudStorageClient, error) {
+56 -37
View File
@@ -59,44 +59,11 @@ func (u *BackupService) MysqlRecover(req dto.CommonRecover) error {
}
func (u *BackupService) MysqlRecoverByUpload(req dto.CommonRecover) error {
file := req.File
fileName := path.Base(req.File)
if strings.HasSuffix(fileName, ".tar.gz") {
fileNameItem := time.Now().Format(constant.DateTimeSlimLayout)
dstDir := fmt.Sprintf("%s/%s", path.Dir(req.File), fileNameItem)
fileOp := files.NewFileOp()
if !fileOp.Stat(dstDir) {
if err := fileOp.CreateDir(dstDir, os.ModePerm); err != nil {
return fmt.Errorf("mkdir %s failed, err: %v", dstDir, err)
}
}
if err := fileOp.TarGzExtractPro(req.File, dstDir, ""); err != nil {
_ = os.RemoveAll(dstDir)
return err
}
global.LOG.Infof("decompress file %s successful, now start to check test.sql is exist", req.File)
hasTestSql := false
_ = filepath.Walk(dstDir, func(path string, info os.FileInfo, err error) error {
if err != nil {
return nil
}
if !info.IsDir() && info.Name() == "test.sql" {
hasTestSql = true
file = path
fileName = "test.sql"
}
return nil
})
if !hasTestSql {
_ = os.RemoveAll(dstDir)
return fmt.Errorf("no such file named test.sql in %s", fileName)
}
defer func() {
_ = os.RemoveAll(dstDir)
}()
recoveFile, err := loadSqlFile(req.File)
if err != nil {
return err
}
req.File = path.Dir(file) + "/" + fileName
req.File = recoveFile
if err := handleMysqlRecover(req, nil, false, req.TaskID); err != nil {
return err
}
@@ -243,3 +210,55 @@ func doMysqlBackup(db DatabaseHelper, targetDir, fileName string) error {
}
return cli.Backup(backupInfo)
}
func loadSqlFile(file string) (string, error) {
if !strings.HasSuffix(file, ".tar.gz") && !strings.HasSuffix(file, ".zip") {
return file, nil
}
fileName := path.Base(file)
fileDir := path.Dir(file)
fileNameItem := time.Now().Format(constant.DateTimeSlimLayout)
dstDir := fmt.Sprintf("%s/%s", fileDir, fileNameItem)
_ = os.Mkdir(dstDir, constant.DirPerm)
if strings.HasSuffix(fileName, ".tar.gz") {
fileOp := files.NewFileOp()
if err := fileOp.TarGzExtractPro(file, dstDir, ""); err != nil {
_ = os.RemoveAll(dstDir)
return "", err
}
}
if strings.HasSuffix(fileName, ".zip") {
archiver, err := files.NewShellArchiver(files.Zip)
if err != nil {
_ = os.RemoveAll(dstDir)
return "", err
}
if err := archiver.Extract(file, dstDir, ""); err != nil {
_ = os.RemoveAll(dstDir)
return "", err
}
}
global.LOG.Infof("decompress file %s successful, now start to check test.sql is exist", file)
var sqlFiles []string
hasTestSql := false
_ = filepath.Walk(dstDir, func(path string, info os.FileInfo, err error) error {
if err != nil {
return nil
}
if !info.IsDir() && strings.HasSuffix(info.Name(), ".sql") {
sqlFiles = append(sqlFiles, path)
if info.Name() == "test.sql" {
hasTestSql = true
}
}
return nil
})
if len(sqlFiles) == 1 {
return sqlFiles[0], nil
}
if !hasTestSql {
_ = os.RemoveAll(dstDir)
return "", fmt.Errorf("no such file named test.sql in %s", fileName)
}
return "", nil
}
+4 -39
View File
@@ -4,8 +4,6 @@ import (
"fmt"
"os"
"path"
"path/filepath"
"strings"
"time"
"github.com/1Panel-dev/1Panel/agent/app/repo"
@@ -59,44 +57,11 @@ func (u *BackupService) PostgresqlRecover(req dto.CommonRecover) error {
}
func (u *BackupService) PostgresqlRecoverByUpload(req dto.CommonRecover) error {
file := req.File
fileName := path.Base(req.File)
if strings.HasSuffix(fileName, ".tar.gz") {
fileNameItem := time.Now().Format(constant.DateTimeSlimLayout)
dstDir := fmt.Sprintf("%s/%s", path.Dir(req.File), fileNameItem)
fileOp := files.NewFileOp()
if !fileOp.Stat(dstDir) {
if err := fileOp.CreateDir(dstDir, os.ModePerm); err != nil {
return fmt.Errorf("mkdir %s failed, err: %v", dstDir, err)
}
}
if err := fileOp.TarGzExtractPro(req.File, dstDir, ""); err != nil {
_ = os.RemoveAll(dstDir)
return err
}
global.LOG.Infof("decompress file %s successful, now start to check test.sql is exist", req.File)
hasTestSql := false
_ = filepath.Walk(dstDir, func(path string, info os.FileInfo, err error) error {
if err != nil {
return nil
}
if !info.IsDir() && info.Name() == "test.sql" {
hasTestSql = true
file = path
fileName = "test.sql"
}
return nil
})
if !hasTestSql {
_ = os.RemoveAll(dstDir)
return fmt.Errorf("no such file named test.sql in %s", fileName)
}
defer func() {
_ = os.RemoveAll(dstDir)
}()
recoveFile, err := loadSqlFile(req.File)
if err != nil {
return err
}
req.File = path.Dir(file) + "/" + fileName
req.File = recoveFile
if err := handlePostgresqlRecover(req, nil, false); err != nil {
return err
}
+19 -6
View File
@@ -6,6 +6,7 @@ import (
"os"
"path"
"sync"
"time"
"github.com/1Panel-dev/1Panel/agent/app/dto"
"github.com/1Panel-dev/1Panel/agent/app/model"
@@ -249,20 +250,32 @@ func (u *BackupRecordService) LoadRecordSize(req dto.SearchForSize) ([]dto.Recor
recordIds = append(recordIds, fmt.Sprintf("%v", record.DownloadID))
}
}
clientMap, err := NewBackupClientMap(recordIds)
if err != nil {
return nil, err
}
clientMap := NewBackupClientMap(recordIds)
var datas []dto.RecordFileSize
var wg sync.WaitGroup
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
for i := 0; i < len(list); i++ {
datas = append(datas, dto.RecordFileSize{ID: list[i].ID})
if val, ok := clientMap[fmt.Sprintf("%v", list[i].DownloadID)]; ok {
if !val.isOk {
continue
}
wg.Add(1)
go func(index int) {
datas[index].Size, _ = val.client.Size(path.Join(val.backupPath, list[i].FilePath))
wg.Done()
defer wg.Done()
done := make(chan struct{}, 1)
go func() {
datas[index].Size, _ = val.client.Size(path.Join(val.backupPath, list[i].FilePath))
defer close(done)
}()
select {
case <-ctx.Done():
return
case <-done:
return
}
}(i)
}
}
+101 -236
View File
@@ -3,37 +3,28 @@ package service
import (
"bufio"
"fmt"
"github.com/1Panel-dev/1Panel/agent/utils/alert_push"
"os"
"os/exec"
"path"
"sort"
"strconv"
"strings"
"time"
"github.com/1Panel-dev/1Panel/agent/app/repo"
"github.com/1Panel-dev/1Panel/agent/app/dto"
"github.com/1Panel-dev/1Panel/agent/app/model"
"github.com/1Panel-dev/1Panel/agent/app/repo"
"github.com/1Panel-dev/1Panel/agent/app/task"
"github.com/1Panel-dev/1Panel/agent/buserr"
"github.com/1Panel-dev/1Panel/agent/constant"
"github.com/1Panel-dev/1Panel/agent/global"
"github.com/1Panel-dev/1Panel/agent/utils/alert_push"
"github.com/1Panel-dev/1Panel/agent/utils/clam"
"github.com/1Panel-dev/1Panel/agent/utils/cmd"
"github.com/1Panel-dev/1Panel/agent/utils/common"
"github.com/1Panel-dev/1Panel/agent/utils/systemctl"
"github.com/1Panel-dev/1Panel/agent/utils/xpack"
"github.com/jinzhu/copier"
"github.com/robfig/cron/v3"
)
const (
clamServiceNameCentOs = "clamd@scan.service"
clamServiceNameUbuntu = "clamav-daemon.service"
freshClamService = "clamav-freshclam.service"
resultDir = "clamav"
)
type ClamService struct {
serviceName string
}
@@ -46,13 +37,13 @@ type IClamService interface {
Update(req dto.ClamUpdate) error
UpdateStatus(id uint, status string) error
Delete(req dto.ClamDelete) error
HandleOnce(req dto.OperateByID) error
HandleOnce(id uint) error
LoadFile(req dto.ClamFileReq) (string, error)
UpdateFile(req dto.UpdateByNameAndFile) error
LoadRecords(req dto.ClamLogSearch) (int64, interface{}, error)
CleanRecord(req dto.OperateByID) error
LoadRecordLog(req dto.ClamLogReq) (string, error)
SearchRecords(req dto.ClamLogSearch) (int64, interface{}, error)
CleanRecord(id uint) error
}
func NewIClamService() IClamService {
@@ -63,22 +54,22 @@ func (c *ClamService) LoadBaseInfo() (dto.ClamBaseInfo, error) {
var baseInfo dto.ClamBaseInfo
baseInfo.Version = "-"
baseInfo.FreshVersion = "-"
exist1, _ := systemctl.IsExist(clamServiceNameCentOs)
exist1, _ := systemctl.IsExist(constant.ClamServiceNameCentOs)
if exist1 {
c.serviceName = clamServiceNameCentOs
c.serviceName = constant.ClamServiceNameCentOs
baseInfo.IsExist = true
baseInfo.IsActive, _ = systemctl.IsActive(clamServiceNameCentOs)
baseInfo.IsActive, _ = systemctl.IsActive(constant.ClamServiceNameCentOs)
}
exist2, _ := systemctl.IsExist(clamServiceNameUbuntu)
exist2, _ := systemctl.IsExist(constant.ClamServiceNameUbuntu)
if exist2 {
c.serviceName = clamServiceNameUbuntu
c.serviceName = constant.ClamServiceNameUbuntu
baseInfo.IsExist = true
baseInfo.IsActive, _ = systemctl.IsActive(clamServiceNameUbuntu)
baseInfo.IsActive, _ = systemctl.IsActive(constant.ClamServiceNameUbuntu)
}
freshExist, _ := systemctl.IsExist(freshClamService)
freshExist, _ := systemctl.IsExist(constant.FreshClamService)
if freshExist {
baseInfo.FreshIsExist = true
baseInfo.FreshIsActive, _ = systemctl.IsActive(freshClamService)
baseInfo.FreshIsActive, _ = systemctl.IsActive(constant.FreshClamService)
}
if !cmd.Which("clamdscan") {
baseInfo.IsActive = false
@@ -94,7 +85,7 @@ func (c *ClamService) LoadBaseInfo() (dto.ClamBaseInfo, error) {
}
}
} else {
_ = StopAllCronJob(false)
_ = clam.CheckClamIsActive(false, clamRepo)
}
if baseInfo.FreshIsActive {
version, err := cmd.RunDefaultWithStdoutBashC("freshclam --version")
@@ -118,7 +109,7 @@ func (c *ClamService) Operate(operate string) error {
}
return nil
case "fresh-start", "fresh-restart", "fresh-stop":
stdout, err := cmd.RunDefaultWithStdoutBashCf("systemctl %s %s", strings.TrimPrefix(operate, "fresh-"), freshClamService)
stdout, err := cmd.RunDefaultWithStdoutBashCf("systemctl %s %s", strings.TrimPrefix(operate, "fresh-"), constant.FreshClamService)
if err != nil {
return fmt.Errorf("%s the %s failed, err: %s", operate, c.serviceName, stdout)
}
@@ -129,31 +120,25 @@ func (c *ClamService) Operate(operate string) error {
}
func (c *ClamService) SearchWithPage(req dto.SearchClamWithPage) (int64, interface{}, error) {
total, commands, err := clamRepo.Page(req.Page, req.PageSize, repo.WithByLikeName(req.Info), repo.WithOrderRuleBy(req.OrderBy, req.Order))
total, clams, err := clamRepo.Page(req.Page, req.PageSize, repo.WithByLikeName(req.Info), repo.WithOrderRuleBy(req.OrderBy, req.Order))
if err != nil {
return 0, nil, err
}
var datas []dto.ClamInfo
for _, command := range commands {
for _, clam := range clams {
var item dto.ClamInfo
if err := copier.Copy(&item, &command); err != nil {
if err := copier.Copy(&item, &clam); err != nil {
return 0, nil, buserr.WithDetail("ErrStructTransform", err.Error(), nil)
}
item.LastHandleDate = "-"
datas = append(datas, item)
}
nyc, _ := time.LoadLocation(common.LoadTimeZoneByCmd())
for i := 0; i < len(datas); i++ {
logPaths := loadFileByName(datas[i].Name)
sort.Slice(logPaths, func(i, j int) bool {
return logPaths[i] > logPaths[j]
})
if len(logPaths) != 0 {
t1, err := time.ParseInLocation(constant.DateTimeSlimLayout, logPaths[0], nyc)
if err != nil {
continue
}
datas[i].LastHandleDate = t1.Format(constant.DateTimeLayout)
record, _ := clamRepo.RecordFirst(datas[i].ID)
if record.ID != 0 {
datas[i].LastRecordStatus = record.Status
datas[i].LastRecordTime = record.StartTime.Format(constant.DateTimeLayout)
} else {
datas[i].LastRecordTime = "-"
}
alertBase := dto.AlertBase{
AlertType: "clams",
@@ -175,6 +160,9 @@ func (c *ClamService) Create(req dto.ClamCreate) error {
if clam.ID != 0 {
return buserr.New("ErrRecordExist")
}
if cmd.CheckIllegal(req.Path) {
return buserr.New("ErrCmdIllegal")
}
if err := copier.Copy(&clam, &req); err != nil {
return buserr.WithDetail("ErrStructTransform", err.Error(), nil)
}
@@ -182,7 +170,7 @@ func (c *ClamService) Create(req dto.ClamCreate) error {
clam.InfectedDir = ""
}
if len(req.Spec) != 0 {
entryID, err := xpack.StartClam(clam, false)
entryID, err := xpack.StartClam(&clam, false)
if err != nil {
return err
}
@@ -192,7 +180,7 @@ func (c *ClamService) Create(req dto.ClamCreate) error {
if err := clamRepo.Create(&clam); err != nil {
return err
}
if req.AlertCount != 0 {
if req.AlertCount != 0 && req.AlertTitle != "" && req.AlertMethod != "" {
createAlert := dto.AlertCreate{
Title: req.AlertTitle,
SendCount: req.AlertCount,
@@ -210,6 +198,9 @@ func (c *ClamService) Create(req dto.ClamCreate) error {
}
func (c *ClamService) Update(req dto.ClamUpdate) error {
if cmd.CheckIllegal(req.Path) {
return buserr.New("ErrCmdIllegal")
}
clam, _ := clamRepo.Get(repo.WithByName(req.Name))
if clam.ID == 0 {
return buserr.New("ErrRecordNotFound")
@@ -232,7 +223,7 @@ func (c *ClamService) Update(req dto.ClamUpdate) error {
upMap["entry_id"] = 0
}
if len(req.Spec) != 0 && clam.Status != constant.StatusDisable {
newEntryID, err := xpack.StartClam(clamItem, true)
newEntryID, err := xpack.StartClam(&clamItem, true)
if err != nil {
return err
}
@@ -275,7 +266,7 @@ func (c *ClamService) UpdateStatus(id uint, status string) error {
err error
)
if status == constant.StatusEnable {
entryID, err = xpack.StartClam(clam, true)
entryID, err = xpack.StartClam(&clam, true)
if err != nil {
return err
}
@@ -293,9 +284,10 @@ func (c *ClamService) Delete(req dto.ClamDelete) error {
if clam.ID == 0 {
continue
}
if req.RemoveRecord {
_ = os.RemoveAll(path.Join(global.Dir.DataDir, resultDir, clam.Name))
if len(clam.Spec) != 0 {
global.Cron.Remove(cron.EntryID(clam.EntryID))
}
_ = c.CleanRecord(clam.ID)
if req.RemoveInfected {
_ = os.RemoveAll(path.Join(clam.InfectedDir, "1panel-infected", clam.Name))
}
@@ -310,128 +302,78 @@ func (c *ClamService) Delete(req dto.ClamDelete) error {
return nil
}
func (c *ClamService) HandleOnce(req dto.OperateByID) error {
if cleaned := StopAllCronJob(true); cleaned {
func (c *ClamService) HandleOnce(id uint) error {
if active := clam.CheckClamIsActive(true, clamRepo); !active {
return buserr.New("ErrClamdscanNotFound")
}
clam, _ := clamRepo.Get(repo.WithByID(req.ID))
if clam.ID == 0 {
clamItem, _ := clamRepo.Get(repo.WithByID(id))
if clamItem.ID == 0 {
return buserr.New("ErrRecordNotFound")
}
if cmd.CheckIllegal(clam.Path) {
return buserr.New("ErrCmdIllegal")
}
timeNow := time.Now().Format(constant.DateTimeSlimLayout)
logFile := path.Join(global.Dir.DataDir, resultDir, clam.Name, timeNow)
if _, err := os.Stat(path.Dir(logFile)); err != nil {
_ = os.MkdirAll(path.Dir(logFile), os.ModePerm)
record := clamRepo.StartRecords(clamItem.ID)
taskItem, err := task.NewTaskWithOps("clam-"+clamItem.Name, task.TaskScan, task.TaskScopeClam, record.TaskID, clamItem.ID)
if err != nil {
return fmt.Errorf("new task for exec shell failed, err: %v", err)
}
clam.AddScanTask(taskItem, clamItem, record.StartTime.Format(constant.DateTimeSlimLayout))
go func() {
strategy := ""
switch clam.InfectedStrategy {
case "remove":
strategy = "--remove"
case "move":
dir := path.Join(clam.InfectedDir, "1panel-infected", clam.Name, timeNow)
strategy = "--move=" + dir
if _, err := os.Stat(dir); err != nil {
_ = os.MkdirAll(dir, os.ModePerm)
}
case "copy":
dir := path.Join(clam.InfectedDir, "1panel-infected", clam.Name, timeNow)
strategy = "--copy=" + dir
if _, err := os.Stat(dir); err != nil {
_ = os.MkdirAll(dir, os.ModePerm)
}
err := taskItem.Execute()
taskRepo := repo.NewITaskRepo()
taskItem, _ := taskRepo.GetFirst(taskRepo.WithByID(record.TaskID))
if len(taskItem.ID) == 0 {
record.TaskID = ""
}
global.LOG.Debugf("clamdscan --fdpass %s %s -l %s", strategy, clam.Path, logFile)
stdout, err := cmd.RunDefaultWithStdoutBashCf("clamdscan --fdpass %s %s -l %s", strategy, clam.Path, logFile)
if err != nil {
global.LOG.Errorf("clamdscan failed, stdout: %v, err: %v", stdout, err)
clamRepo.EndRecords(record, constant.StatusFailed, err.Error())
return
}
handleAlert(stdout, clam.Name, clam.ID)
handleAlert(record.InfectedFiles, clamItem.Name, clamItem.ID)
clam.AnalysisFromLog(taskItem.LogFile, &record)
clamRepo.EndRecords(record, constant.StatusDone, "")
}()
return nil
}
func (c *ClamService) LoadRecords(req dto.ClamLogSearch) (int64, interface{}, error) {
func (c *ClamService) SearchRecords(req dto.ClamLogSearch) (int64, interface{}, error) {
clam, _ := clamRepo.Get(repo.WithByID(req.ClamID))
if clam.ID == 0 {
return 0, nil, buserr.New("ErrRecordNotFound")
}
logPaths := loadFileByName(clam.Name)
if len(logPaths) == 0 {
return 0, nil, nil
}
var filterFiles []string
nyc, _ := time.LoadLocation(common.LoadTimeZoneByCmd())
for _, item := range logPaths {
t1, err := time.ParseInLocation(constant.DateTimeSlimLayout, item, nyc)
if err != nil {
continue
total, records, err := clamRepo.PageRecords(req.Page, req.PageSize, clamRepo.WithByClamID(req.ClamID), repo.WithByStatus(req.Status), repo.WithByCreatedAt(req.StartTime, req.EndTime))
if err != nil {
return 0, nil, err
}
var datas []dto.ClamRecord
for _, record := range records {
var item dto.ClamRecord
if err := copier.Copy(&item, &record); err != nil {
return 0, nil, buserr.WithDetail("ErrStructTransform", err.Error(), nil)
}
if t1.After(req.StartTime) && t1.Before(req.EndTime) {
filterFiles = append(filterFiles, item)
}
}
if len(filterFiles) == 0 {
return 0, nil, nil
}
sort.Slice(filterFiles, func(i, j int) bool {
return filterFiles[i] > filterFiles[j]
})
var records []string
total, start, end := len(filterFiles), (req.Page-1)*req.PageSize, req.Page*req.PageSize
if start > total {
records = make([]string, 0)
} else {
if end >= total {
end = total
}
records = filterFiles[start:end]
}
var datas []dto.ClamLog
for i := 0; i < len(records); i++ {
item := loadResultFromLog(path.Join(global.Dir.DataDir, resultDir, clam.Name, records[i]))
datas = append(datas, item)
}
return int64(total), datas, nil
}
func (c *ClamService) LoadRecordLog(req dto.ClamLogReq) (string, error) {
logPath := path.Join(global.Dir.DataDir, resultDir, req.ClamName, req.RecordName)
var tail string
if req.Tail != "0" {
tail = req.Tail
} else {
tail = "+1"
}
cmd := exec.Command("tail", "-n", tail, logPath)
stdout, err := cmd.CombinedOutput()
if err != nil {
return "", fmt.Errorf("tail -n %v failed, err: %v", req.Tail, err)
}
return string(stdout), nil
}
func (c *ClamService) CleanRecord(req dto.OperateByID) error {
clam, _ := clamRepo.Get(repo.WithByID(req.ID))
if clam.ID == 0 {
return buserr.New("ErrRecordNotFound")
func (c *ClamService) CleanRecord(id uint) error {
record, _ := clamRepo.ListRecord()
for _, item := range record {
if len(item.TaskID) != 0 {
continue
}
taskItem, _ := taskRepo.GetFirst(taskRepo.WithByID(item.TaskID))
if len(taskItem.LogFile) != 0 {
_ = os.Remove(taskItem.LogFile)
}
}
pathItem := path.Join(global.Dir.DataDir, resultDir, clam.Name)
_ = os.RemoveAll(pathItem)
return nil
return clamRepo.DeleteRecord(clamRepo.WithByClamID(id))
}
func (c *ClamService) LoadFile(req dto.ClamFileReq) (string, error) {
filePath := ""
switch req.Name {
case "clamd":
if c.serviceName == clamServiceNameUbuntu {
if c.serviceName == constant.ClamServiceNameUbuntu {
filePath = "/etc/clamav/clamd.conf"
} else {
filePath = "/etc/clamd.d/scan.conf"
@@ -441,13 +383,13 @@ func (c *ClamService) LoadFile(req dto.ClamFileReq) (string, error) {
if len(filePath) != 0 {
break
}
if c.serviceName == clamServiceNameUbuntu {
if c.serviceName == constant.ClamServiceNameUbuntu {
filePath = "/var/log/clamav/clamav.log"
} else {
filePath = "/var/log/clamd.scan"
}
case "freshclam":
if c.serviceName == clamServiceNameUbuntu {
if c.serviceName == constant.ClamServiceNameUbuntu {
filePath = "/etc/clamav/freshclam.conf"
} else {
filePath = "/etc/freshclam.conf"
@@ -457,7 +399,7 @@ func (c *ClamService) LoadFile(req dto.ClamFileReq) (string, error) {
if len(filePath) != 0 {
break
}
if c.serviceName == clamServiceNameUbuntu {
if c.serviceName == constant.ClamServiceNameUbuntu {
filePath = "/var/log/clamav/freshclam.log"
} else {
filePath = "/var/log/freshclam.log"
@@ -487,15 +429,15 @@ func (c *ClamService) UpdateFile(req dto.UpdateByNameAndFile) error {
service := ""
switch req.Name {
case "clamd":
if c.serviceName == clamServiceNameUbuntu {
service = clamServiceNameUbuntu
if c.serviceName == constant.ClamServiceNameUbuntu {
service = constant.ClamServiceNameUbuntu
filePath = "/etc/clamav/clamd.conf"
} else {
service = clamServiceNameCentOs
service = constant.ClamServiceNameCentOs
filePath = "/etc/clamd.d/scan.conf"
}
case "freshclam":
if c.serviceName == clamServiceNameUbuntu {
if c.serviceName == constant.ClamServiceNameUbuntu {
filePath = "/etc/clamav/freshclam.conf"
} else {
filePath = "/etc/freshclam.conf"
@@ -517,86 +459,16 @@ func (c *ClamService) UpdateFile(req dto.UpdateByNameAndFile) error {
return nil
}
func StopAllCronJob(withCheck bool) bool {
if withCheck {
isActive := false
exist1, _ := systemctl.IsExist(clamServiceNameCentOs)
if exist1 {
isActive, _ = systemctl.IsActive(clamServiceNameCentOs)
}
exist2, _ := systemctl.IsExist(clamServiceNameUbuntu)
if exist2 {
isActive, _ = systemctl.IsActive(clamServiceNameUbuntu)
}
if isActive {
return false
}
}
clams, _ := clamRepo.List(repo.WithByStatus(constant.StatusEnable))
for i := 0; i < len(clams); i++ {
global.Cron.Remove(cron.EntryID(clams[i].EntryID))
_ = clamRepo.Update(clams[i].ID, map[string]interface{}{"status": constant.StatusDisable, "entry_id": 0})
}
return true
}
func loadFileByName(name string) []string {
var logPaths []string
pathItem := path.Join(global.Dir.DataDir, resultDir, name)
fileItems, err := os.ReadDir(pathItem)
if err != nil {
return logPaths
}
for _, item := range fileItems {
if !item.IsDir() {
logPaths = append(logPaths, item.Name())
}
}
return logPaths
}
func loadResultFromLog(pathItem string) dto.ClamLog {
var data dto.ClamLog
data.Name = path.Base(pathItem)
data.Status = constant.StatusWaiting
file, err := os.ReadFile(pathItem)
if err != nil {
return data
}
lines := strings.Split(string(file), "\n")
for _, line := range lines {
if strings.Contains(line, "- SCAN SUMMARY -") {
data.Status = constant.StatusDone
}
if data.Status != constant.StatusDone {
continue
}
switch {
case strings.HasPrefix(line, "Infected files:"):
data.InfectedFiles = strings.TrimPrefix(line, "Infected files:")
case strings.HasPrefix(line, "Total errors:"):
data.TotalError = strings.TrimPrefix(line, "Total errors:")
case strings.HasPrefix(line, "Time:"):
if strings.Contains(line, "(") {
data.ScanTime = strings.ReplaceAll(strings.Split(line, "(")[1], ")", "")
continue
}
data.ScanTime = strings.TrimPrefix(line, "Time:")
case strings.HasPrefix(line, "Start Date:"):
data.ScanDate = strings.TrimPrefix(line, "Start Date:")
}
}
return data
}
func (c *ClamService) loadLogPath(name string) string {
confPath := ""
if name == "clamd-log" {
if c.serviceName == clamServiceNameUbuntu {
if c.serviceName == constant.ClamServiceNameUbuntu {
confPath = "/etc/clamav/clamd.conf"
} else {
confPath = "/etc/clamd.d/scan.conf"
}
} else {
if c.serviceName == clamServiceNameUbuntu {
if c.serviceName == constant.ClamServiceNameUbuntu {
confPath = "/etc/clamav/freshclam.conf"
} else {
confPath = "/etc/freshclam.conf"
@@ -627,23 +499,16 @@ func (c *ClamService) loadLogPath(name string) string {
return ""
}
func handleAlert(stdout, clamName string, clamId uint) {
if strings.Contains(stdout, "- SCAN SUMMARY -") {
lines := strings.Split(stdout, "\n")
for _, line := range lines {
if strings.HasPrefix(line, "Infected files: ") {
infectedFiles, _ := strconv.Atoi(strings.TrimPrefix(line, "Infected files: "))
if infectedFiles > 0 {
pushAlert := dto.PushAlert{
TaskName: clamName,
AlertType: "clams",
EntryID: clamId,
Param: strconv.Itoa(infectedFiles),
}
_ = alert_push.PushAlert(pushAlert)
break
}
}
}
func handleAlert(infectedFiles, clamName string, clamId uint) {
itemInfected, _ := strconv.Atoi(strings.TrimSpace(infectedFiles))
if itemInfected < 0 {
return
}
pushAlert := dto.PushAlert{
TaskName: clamName,
AlertType: "clams",
EntryID: clamId,
Param: strconv.Itoa(itemInfected),
}
_ = alert_push.PushAlert(pushAlert)
}
+74 -38
View File
@@ -45,8 +45,8 @@ import (
"github.com/docker/docker/client"
"github.com/docker/go-connections/nat"
v1 "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/shirou/gopsutil/v3/cpu"
"github.com/shirou/gopsutil/v3/mem"
"github.com/shirou/gopsutil/v4/cpu"
"github.com/shirou/gopsutil/v4/mem"
)
type ContainerService struct{}
@@ -54,6 +54,7 @@ type ContainerService struct{}
type IContainerService interface {
Page(req dto.PageContainer) (int64, interface{}, error)
List() []dto.ContainerOptions
ListByImage(imageName string) []dto.ContainerOptions
LoadStatus() (dto.ContainerStatus, error)
PageNetwork(req dto.SearchWithPage) (int64, interface{}, error)
ListNetwork() ([]dto.Options, error)
@@ -246,6 +247,33 @@ func (u *ContainerService) List() []dto.ContainerOptions {
return options
}
func (u *ContainerService) ListByImage(imageName string) []dto.ContainerOptions {
var options []dto.ContainerOptions
client, err := docker.NewDockerClient()
if err != nil {
global.LOG.Errorf("load docker client for contianer list failed, err: %v", err)
return nil
}
defer client.Close()
containers, err := client.ContainerList(context.Background(), container.ListOptions{All: true})
if err != nil {
global.LOG.Errorf("load container list failed, err: %v", err)
return nil
}
for _, container := range containers {
if container.Image != imageName {
continue
}
for _, name := range container.Names {
if len(name) != 0 {
options = append(options, dto.ContainerOptions{Name: strings.TrimPrefix(name, "/"), State: container.State})
}
}
}
return options
}
func (u *ContainerService) LoadStatus() (dto.ContainerStatus, error) {
var data dto.ContainerStatus
client, err := docker.NewDockerClient()
@@ -745,17 +773,14 @@ func (u *ContainerService) ContainerUpgrade(req dto.ContainerUpgrade) error {
}
defer client.Close()
ctx := context.Background()
oldContainer, err := client.ContainerInspect(ctx, req.Name)
if err != nil {
return err
}
taskItem, err := task.NewTaskWithOps(req.Name, task.TaskUpgrade, task.TaskScopeContainer, req.TaskID, 1)
taskItem, err := task.NewTaskWithOps(req.Image, task.TaskUpgrade, task.TaskScopeImage, req.TaskID, 1)
if err != nil {
global.LOG.Errorf("new task for create container failed, err: %v", err)
return err
}
go func() {
taskItem.AddSubTask(i18n.GetWithName("ContainerImagePull", req.Image), func(t *task.Task) error {
taskItem.LogStart(i18n.GetWithName("ContainerImagePull", req.Image))
if !checkImageExist(client, req.Image) || req.ForcePull {
if err := pullImages(taskItem, client, req.Image); err != nil {
if !req.ForcePull {
@@ -766,38 +791,49 @@ func (u *ContainerService) ContainerUpgrade(req dto.ContainerUpgrade) error {
}
return nil
}, nil)
taskItem.AddSubTask(i18n.GetWithName("ContainerCreate", req.Name), func(t *task.Task) error {
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
for _, item := range req.Names {
var oldContainer container.InspectResponse
taskItem.AddSubTask(i18n.GetWithName("ContainerLoadInfo", item), func(t *task.Task) error {
taskItem.Logf("----------------- %s -----------------", item)
oldContainer, err = client.ContainerInspect(ctx, item)
if err != nil {
return err
}
}
err := client.ContainerRemove(ctx, req.Name, container.RemoveOptions{Force: true})
taskItem.LogWithStatus(i18n.GetWithName("ContainerRemoveOld", req.Name), err)
if err != nil {
return err
}
return nil
}, nil)
con, err := client.ContainerCreate(ctx, config, hostConf, &networkConf, &v1.Platform{}, req.Name)
if err != nil {
taskItem.Log(i18n.GetMsgByKey("ContainerRecreate"))
reCreateAfterUpdate(req.Name, client, oldContainer.Config, oldContainer.HostConfig, oldContainer.NetworkSettings)
return fmt.Errorf("upgrade container failed, err: %v", err)
}
err = client.ContainerStart(ctx, con.ID, container.StartOptions{})
taskItem.LogWithStatus(i18n.GetMsgByKey("ContainerStartCheck"), err)
if err != nil {
return fmt.Errorf("upgrade successful but start failed, err: %v", err)
}
return nil
}, nil)
taskItem.AddSubTask(i18n.GetWithName("ContainerCreate", item), func(t *task.Task) error {
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
}
}
err := client.ContainerRemove(ctx, item, container.RemoveOptions{Force: true})
taskItem.LogWithStatus(i18n.GetWithName("ContainerRemoveOld", item), err)
if err != nil {
return err
}
con, err := client.ContainerCreate(ctx, config, hostConf, &networkConf, &v1.Platform{}, item)
if err != nil {
taskItem.Log(i18n.GetMsgByKey("ContainerRecreate"))
reCreateAfterUpdate(item, client, oldContainer.Config, oldContainer.HostConfig, oldContainer.NetworkSettings)
return fmt.Errorf("upgrade container failed, err: %v", err)
}
err = client.ContainerStart(ctx, con.ID, container.StartOptions{})
taskItem.LogWithStatus(i18n.GetMsgByKey("ContainerStartCheck"), err)
if err != nil {
return fmt.Errorf("upgrade successful but start failed, err: %v", err)
}
return nil
}, nil)
}
if err := taskItem.Execute(); err != nil {
global.LOG.Error(err.Error())
}
@@ -1378,7 +1414,7 @@ func checkPortStats(ports []dto.PortHelper) (nat.PortMap, error) {
portMap[nat.Port(fmt.Sprintf("%d/%s", containerStart+i, port.Protocol))] = []nat.PortBinding{bindItem}
}
for i := hostStart; i <= hostEnd; i++ {
if common.ScanPort(i) {
if common.ScanPortWithIP(port.HostIP, i) {
return portMap, buserr.WithDetail("ErrPortInUsed", i, nil)
}
}
@@ -1389,7 +1425,7 @@ func checkPortStats(ports []dto.PortHelper) (nat.PortMap, error) {
} else {
portItem, _ = strconv.Atoi(port.HostPort)
}
if common.ScanPort(portItem) {
if common.ScanPortWithIP(port.HostIP, portItem) {
return portMap, buserr.WithDetail("ErrPortInUsed", portItem, nil)
}
bindItem := nat.PortBinding{HostPort: strconv.Itoa(portItem), HostIP: port.HostIP}
+52 -22
View File
@@ -10,12 +10,9 @@ import (
"strings"
"time"
"github.com/1Panel-dev/1Panel/agent/app/repo"
"github.com/docker/docker/api/types/container"
"github.com/1Panel-dev/1Panel/agent/app/dto"
"github.com/1Panel-dev/1Panel/agent/app/model"
"github.com/1Panel-dev/1Panel/agent/app/repo"
"github.com/1Panel-dev/1Panel/agent/app/task"
"github.com/1Panel-dev/1Panel/agent/buserr"
"github.com/1Panel-dev/1Panel/agent/constant"
@@ -24,6 +21,7 @@ import (
"github.com/1Panel-dev/1Panel/agent/utils/cmd"
"github.com/1Panel-dev/1Panel/agent/utils/compose"
"github.com/1Panel-dev/1Panel/agent/utils/docker"
"github.com/docker/docker/api/types/container"
"github.com/docker/docker/api/types/filters"
"golang.org/x/net/context"
)
@@ -54,14 +52,13 @@ func (u *ContainerService) PageCompose(req dto.SearchWithPage) (int64, interface
}
composeCreatedByLocal, _ := composeRepo.ListRecord()
composeLocalMap := make(map[string]dto.ComposeInfo)
for _, localItem := range composeCreatedByLocal {
composeItemLocal := dto.ComposeInfo{
ContainerNumber: 0,
CreatedAt: localItem.CreatedAt.Format(constant.DateTimeLayout),
ConfigFile: localItem.Path,
Workdir: strings.TrimSuffix(localItem.Path, "/docker-compose.yml"),
ContainerCount: 0,
CreatedAt: localItem.CreatedAt.Format(constant.DateTimeLayout),
ConfigFile: localItem.Path,
Workdir: strings.TrimSuffix(localItem.Path, "/docker-compose.yml"),
}
composeItemLocal.CreatedBy = "1Panel"
composeItemLocal.Path = localItem.Path
@@ -78,18 +75,24 @@ func (u *ContainerService) PageCompose(req dto.SearchWithPage) (int64, interface
CreateTime: time.Unix(container.Created, 0).Format(constant.DateTimeLayout),
}
if compose, has := composeMap[name]; has {
compose.ContainerNumber++
compose.ContainerCount++
if strings.ToLower(containerItem.State) == "running" {
compose.RunningCount++
}
compose.Containers = append(compose.Containers, containerItem)
composeMap[name] = compose
} else {
config := container.Labels[composeConfigLabel]
workdir := container.Labels[composeWorkdirLabel]
composeItem := dto.ComposeInfo{
ContainerNumber: 1,
CreatedAt: time.Unix(container.Created, 0).Format(constant.DateTimeLayout),
ConfigFile: config,
Workdir: workdir,
Containers: []dto.ComposeContainer{containerItem},
ContainerCount: 1,
CreatedAt: time.Unix(container.Created, 0).Format(constant.DateTimeLayout),
ConfigFile: config,
Workdir: workdir,
Containers: []dto.ComposeContainer{containerItem},
}
if strings.ToLower(containerItem.State) == "running" {
composeItem.RunningCount = 1
}
createdBy, ok := container.Labels[composeCreatedBy]
if ok {
@@ -118,8 +121,8 @@ func (u *ContainerService) PageCompose(req dto.SearchWithPage) (int64, interface
}
for key, item := range composeMap {
if existingItem, exists := mergedMap[key]; exists {
if item.ContainerNumber > 0 {
if existingItem.ContainerNumber <= 0 {
if item.ContainerCount > 0 {
if existingItem.ContainerCount <= 0 {
mergedMap[key] = item
}
}
@@ -224,12 +227,9 @@ func (u *ContainerService) ComposeOperation(req dto.ComposeOperation) error {
if cmd.CheckIllegal(req.Path, req.Operation) {
return buserr.New("ErrCmdIllegal")
}
if _, err := os.Stat(req.Path); err != nil {
return fmt.Errorf("load file with path %s failed, %v", req.Path, err)
}
if req.Operation == "delete" {
if stdout, err := compose.Operate(req.Path, "down"); err != nil {
return errors.New(string(stdout))
if err := removeContainerForCompose(req.Name, req.Path); err != nil && !req.Force {
return err
}
if req.WithFile {
_ = os.RemoveAll(path.Dir(req.Path))
@@ -237,6 +237,9 @@ func (u *ContainerService) ComposeOperation(req dto.ComposeOperation) error {
_ = composeRepo.DeleteRecord(repo.WithByName(req.Name))
return nil
}
if _, err := os.Stat(req.Path); err != nil {
return fmt.Errorf("load file with path %s failed, %v", req.Path, err)
}
if req.Operation == "up" {
if stdout, err := compose.Up(req.Path); err != nil {
return errors.New(string(stdout))
@@ -305,6 +308,33 @@ func (u *ContainerService) loadPath(req *dto.ComposeCreate) error {
return nil
}
func removeContainerForCompose(composeName, composePath string) error {
if _, err := os.Stat(composePath); err == nil {
if stdout, err := compose.Operate(composePath, "down"); err != nil {
return errors.New(stdout)
}
return nil
}
var options container.ListOptions
options.All = true
options.Filters = filters.NewArgs()
options.Filters.Add("label", "com.docker.compose.project="+composeName)
client, err := docker.NewDockerClient()
if err != nil {
return err
}
defer client.Close()
ctx := context.Background()
containers, err := client.ContainerList(ctx, options)
if err != nil {
return err
}
for _, c := range containers {
_ = client.ContainerRemove(ctx, c.ID, container.RemoveOptions{RemoveVolumes: true, Force: true})
}
return nil
}
func recreateCompose(content, path string) error {
file, err := os.OpenFile(path, os.O_WRONLY|os.O_TRUNC, 0640)
if err != nil {
+32 -16
View File
@@ -5,6 +5,7 @@ import (
"sort"
"strings"
"time"
"unicode"
"github.com/1Panel-dev/1Panel/agent/app/dto"
"github.com/1Panel-dev/1Panel/agent/buserr"
@@ -53,25 +54,27 @@ func (u *ContainerService) PageVolume(req dto.SearchWithPage) (int64, interface{
nyc, _ := time.LoadLocation(common.LoadTimeZoneByCmd())
for _, item := range records {
tag := make([]string, 0)
for _, val := range item.Labels {
tag = append(tag, val)
var volume dto.Volume
volume.Driver = item.Driver
volume.Mountpoint = item.Mountpoint
volume.Name = simplifyVolumeName(item.Name)
for key, val := range item.Labels {
volume.Labels = append(volume.Labels, dto.VolumeOption{Key: key, Value: val})
}
var createTime time.Time
if strings.Contains(item.CreatedAt, "Z") {
createTime, _ = time.ParseInLocation("2006-01-02T15:04:05Z", item.CreatedAt, nyc)
} else if strings.Contains(item.CreatedAt, "+") {
createTime, _ = time.ParseInLocation("2006-01-02T15:04:05+08:00", item.CreatedAt, nyc)
} else {
createTime, _ = time.ParseInLocation("2006-01-02T15:04:05", item.CreatedAt, nyc)
for key, val := range item.Options {
volume.Options = append(volume.Options, dto.VolumeOption{Key: key, Value: val})
}
data = append(data, dto.Volume{
CreatedAt: createTime,
Name: item.Name,
Driver: item.Driver,
Mountpoint: item.Mountpoint,
Labels: tag,
sort.Slice(volume.Options, func(i, j int) bool {
return volume.Options[i].Key < volume.Options[j].Key
})
if strings.Contains(item.CreatedAt, "Z") {
volume.CreatedAt, _ = time.ParseInLocation("2006-01-02T15:04:05Z", item.CreatedAt, nyc)
} else if strings.Contains(item.CreatedAt, "+") {
volume.CreatedAt, _ = time.ParseInLocation("2006-01-02T15:04:05+08:00", item.CreatedAt, nyc)
} else {
volume.CreatedAt, _ = time.ParseInLocation("2006-01-02T15:04:05", item.CreatedAt, nyc)
}
data = append(data, volume)
}
return int64(total), data, nil
@@ -140,3 +143,16 @@ func (u *ContainerService) CreateVolume(req dto.VolumeCreate) error {
}
return nil
}
func simplifyVolumeName(name string) string {
if len(name) != 64 {
return name
}
for _, char := range name {
if !unicode.Is(unicode.ASCII_Hex_Digit, char) {
return name
}
}
return name[:12]
}
+6 -33
View File
@@ -34,7 +34,6 @@ type ICronjobService interface {
UpdateStatus(id uint, status string) error
UpdateGroup(req dto.ChangeGroup) error
Delete(req dto.CronjobBatchDelete) error
Download(down dto.CronjobDownload) (string, error)
StartJob(cronjob *model.Cronjob, isUpdate bool) (string, error)
CleanRecord(req dto.CronjobClean) error
@@ -382,7 +381,7 @@ func (u *CronjobService) Import(req []dto.CronjobTrans) error {
cronjob.Status = constant.StatusDisable
}
_ = cronjobRepo.Create(&cronjob)
if item.AlertCount != 0 {
if item.AlertCount != 0 && item.AlertTitle != "" && item.AlertMethod != "" {
createAlert := dto.AlertCreate{
Title: item.AlertTitle,
SendCount: item.AlertCount,
@@ -498,10 +497,7 @@ func (u *CronjobService) CleanRecord(req dto.CronjobClean) error {
}
if req.CleanData {
if hasBackup(cronjob.Type) {
accountMap, err := NewBackupClientMap(strings.Split(cronjob.SourceAccountIDs, ","))
if err != nil {
return err
}
accountMap := NewBackupClientMap(strings.Split(cronjob.SourceAccountIDs, ","))
if !req.CleanRemoteData {
for key := range accountMap {
if key != constant.Local {
@@ -535,37 +531,14 @@ func (u *CronjobService) CleanRecord(req dto.CronjobClean) error {
return nil
}
func (u *CronjobService) Download(req dto.CronjobDownload) (string, error) {
record, _ := cronjobRepo.GetRecord(repo.WithByID(req.RecordID))
if record.ID == 0 {
return "", buserr.New("ErrRecordNotFound")
}
account, client, err := NewBackupClientWithID(req.BackupAccountID)
if err != nil {
return "", err
}
if account.Type == "LOCAL" || record.FromLocal {
if _, err := os.Stat(record.File); err != nil && os.IsNotExist(err) {
return "", err
}
return record.File, nil
}
tempPath := fmt.Sprintf("%s/download/%s", global.Dir.DataDir, record.File)
if _, err := os.Stat(tempPath); err != nil && os.IsNotExist(err) {
_ = os.MkdirAll(path.Dir(tempPath), os.ModePerm)
isOK, err := client.Download(record.File, tempPath)
if !isOK || err != nil {
return "", err
}
}
return tempPath, nil
}
func (u *CronjobService) HandleOnce(id uint) error {
cronjob, _ := cronjobRepo.Get(repo.WithByID(id))
if cronjob.ID == 0 {
return buserr.New("ErrRecordNotFound")
}
if cronjob.IsExecuting {
return buserr.New("InExecuting")
}
u.HandleJob(&cronjob)
return nil
}
@@ -606,7 +579,7 @@ func (u *CronjobService) Create(req dto.CronjobOperate) error {
if err := cronjobRepo.Create(&cronjob); err != nil {
return err
}
if req.AlertCount != 0 {
if req.AlertCount != 0 && req.AlertTitle != "" && req.AlertMethod != "" {
createAlert := dto.AlertCreate{
Title: req.AlertTitle,
SendCount: req.AlertCount,
+98 -54
View File
@@ -27,11 +27,12 @@ import (
func (u *CronjobService) handleApp(cronjob model.Cronjob, startTime time.Time, taskItem *task.Task) error {
apps := loadAppsForJob(cronjob)
if len(apps) == 0 {
return errors.New("no such app in database!")
addSkipTask("App", taskItem)
return nil
}
accountMap, err := NewBackupClientMap(strings.Split(cronjob.SourceAccountIDs, ","))
if err != nil {
return err
accountMap := NewBackupClientMap(strings.Split(cronjob.SourceAccountIDs, ","))
if !accountMap[fmt.Sprintf("%d", cronjob.DownloadAccountID)].isOk {
return errors.New(i18n.GetMsgWithDetail("LoadBackupFailed", accountMap[fmt.Sprintf("%d", cronjob.DownloadAccountID)].message))
}
for _, app := range apps {
retry := 0
@@ -44,31 +45,36 @@ func (u *CronjobService) handleApp(cronjob model.Cronjob, startTime time.Time, t
record.DetailName = app.Name
record.DownloadAccountID, record.SourceAccountIDs = cronjob.DownloadAccountID, cronjob.SourceAccountIDs
backupDir := path.Join(global.Dir.LocalBackupDir, fmt.Sprintf("tmp/app/%s/%s", app.App.Key, app.Name))
record.FileName = fmt.Sprintf("app_%s_%s.tar.gz", app.Name, startTime.Format(constant.DateTimeSlimLayout)+common.RandStrAndNum(5))
record.FileName = simplifiedFileName(fmt.Sprintf("app_%s_%s.tar.gz", app.Name, startTime.Format(constant.DateTimeSlimLayout)+common.RandStrAndNum(5)))
if err := doAppBackup(&app, task, backupDir, record.FileName, cronjob.ExclusionRules, cronjob.Secret); err != nil {
if retry < int(cronjob.RetryTimes) || !cronjob.IgnoreErr {
retry++
return err
} else {
task.Log(i18n.GetMsgWithDetail("IgnoreBackupErr", err.Error()))
cleanAccountMap(accountMap)
return nil
}
}
downloadPath, err := u.uploadCronjobBackFile(cronjob, task, accountMap, path.Join(backupDir, record.FileName))
if err != nil {
src := path.Join(backupDir, record.FileName)
dst := strings.TrimPrefix(src, global.Dir.LocalBackupDir+"/tmp/")
if err := uploadWithMap(*task, accountMap, src, dst, cronjob.SourceAccountIDs, cronjob.DownloadAccountID, cronjob.RetryTimes); err != nil {
if retry < int(cronjob.RetryTimes) || !cronjob.IgnoreErr {
retry++
return err
}
task.Log(i18n.GetMsgWithDetail("IgnoreUploadErr", err.Error()))
cleanAccountMap(accountMap)
return nil
}
record.FileDir = path.Dir(downloadPath)
record.FileDir = path.Dir(dst)
if err := backupRepo.CreateRecord(&record); err != nil {
global.LOG.Errorf("save backup record failed, err: %v", err)
return err
}
u.removeExpiredBackup(cronjob, accountMap, record)
cleanAccountMap(accountMap)
return nil
}, nil, int(cronjob.RetryTimes), time.Duration(cronjob.Timeout)*time.Second)
}
@@ -78,11 +84,12 @@ func (u *CronjobService) handleApp(cronjob model.Cronjob, startTime time.Time, t
func (u *CronjobService) handleWebsite(cronjob model.Cronjob, startTime time.Time, taskItem *task.Task) error {
webs := loadWebsForJob(cronjob)
if len(webs) == 0 {
return errors.New("no such website in database!")
addSkipTask("Website", taskItem)
return nil
}
accountMap, err := NewBackupClientMap(strings.Split(cronjob.SourceAccountIDs, ","))
if err != nil {
return err
accountMap := NewBackupClientMap(strings.Split(cronjob.SourceAccountIDs, ","))
if !accountMap[fmt.Sprintf("%d", cronjob.DownloadAccountID)].isOk {
return errors.New(i18n.GetMsgWithDetail("LoadBackupFailed", accountMap[fmt.Sprintf("%d", cronjob.DownloadAccountID)].message))
}
for _, web := range webs {
retry := 0
@@ -95,7 +102,7 @@ func (u *CronjobService) handleWebsite(cronjob model.Cronjob, startTime time.Tim
record.DetailName = web.Alias
record.DownloadAccountID, record.SourceAccountIDs = cronjob.DownloadAccountID, cronjob.SourceAccountIDs
backupDir := path.Join(global.Dir.LocalBackupDir, fmt.Sprintf("tmp/website/%s", web.Alias))
record.FileName = fmt.Sprintf("website_%s_%s.tar.gz", web.Alias, startTime.Format(constant.DateTimeSlimLayout)+common.RandStrAndNum(5))
record.FileName = simplifiedFileName(fmt.Sprintf("website_%s_%s.tar.gz", web.Alias, startTime.Format(constant.DateTimeSlimLayout)+common.RandStrAndNum(5)))
if err := doWebsiteBackup(&web, taskItem, backupDir, record.FileName, cronjob.ExclusionRules, cronjob.Secret); err != nil {
if retry < int(cronjob.RetryTimes) || !cronjob.IgnoreErr {
@@ -103,25 +110,29 @@ func (u *CronjobService) handleWebsite(cronjob model.Cronjob, startTime time.Tim
return err
} else {
task.Log(i18n.GetMsgWithDetail("IgnoreBackupErr", err.Error()))
cleanAccountMap(accountMap)
return nil
}
}
downloadPath, err := u.uploadCronjobBackFile(cronjob, task, accountMap, path.Join(backupDir, record.FileName))
if err != nil {
src := path.Join(backupDir, record.FileName)
dst := strings.TrimPrefix(src, global.Dir.LocalBackupDir+"/tmp/")
if err := uploadWithMap(*task, accountMap, src, dst, cronjob.SourceAccountIDs, cronjob.DownloadAccountID, cronjob.RetryTimes); err != nil {
if retry < int(cronjob.RetryTimes) || !cronjob.IgnoreErr {
retry++
return err
}
task.Log(i18n.GetMsgWithDetail("IgnoreUploadErr", err.Error()))
cleanAccountMap(accountMap)
return nil
}
record.FileDir = path.Dir(downloadPath)
record.FileDir = path.Dir(dst)
if err := backupRepo.CreateRecord(&record); err != nil {
global.LOG.Errorf("save backup record failed, err: %v", err)
return err
}
u.removeExpiredBackup(cronjob, accountMap, record)
cleanAccountMap(accountMap)
return nil
}, nil, int(cronjob.RetryTimes), time.Duration(cronjob.Timeout)*time.Second)
}
@@ -131,11 +142,12 @@ func (u *CronjobService) handleWebsite(cronjob model.Cronjob, startTime time.Tim
func (u *CronjobService) handleDatabase(cronjob model.Cronjob, startTime time.Time, taskItem *task.Task) error {
dbs := loadDbsForJob(cronjob)
if len(dbs) == 0 {
return errors.New("no such db in database!")
addSkipTask("Database", taskItem)
return nil
}
accountMap, err := NewBackupClientMap(strings.Split(cronjob.SourceAccountIDs, ","))
if err != nil {
return err
accountMap := NewBackupClientMap(strings.Split(cronjob.SourceAccountIDs, ","))
if !accountMap[fmt.Sprintf("%d", cronjob.DownloadAccountID)].isOk {
return errors.New(i18n.GetMsgWithDetail("LoadBackupFailed", accountMap[fmt.Sprintf("%d", cronjob.DownloadAccountID)].message))
}
for _, dbInfo := range dbs {
retry := 0
@@ -150,7 +162,7 @@ func (u *CronjobService) handleDatabase(cronjob model.Cronjob, startTime time.Ti
record.DownloadAccountID, record.SourceAccountIDs = cronjob.DownloadAccountID, cronjob.SourceAccountIDs
backupDir := path.Join(global.Dir.LocalBackupDir, fmt.Sprintf("tmp/database/%s/%s/%s", dbInfo.DBType, record.Name, dbInfo.Name))
record.FileName = fmt.Sprintf("db_%s_%s.sql.gz", dbInfo.Name, startTime.Format(constant.DateTimeSlimLayout)+common.RandStrAndNum(5))
record.FileName = simplifiedFileName(fmt.Sprintf("db_%s_%s.sql.gz", dbInfo.Name, startTime.Format(constant.DateTimeSlimLayout)+common.RandStrAndNum(5)))
if cronjob.DBType == "mysql" || cronjob.DBType == "mariadb" || cronjob.DBType == "mysql-cluster" {
if err := doMysqlBackup(dbInfo, backupDir, record.FileName); err != nil {
if retry < int(cronjob.RetryTimes) || !cronjob.IgnoreErr {
@@ -158,6 +170,7 @@ func (u *CronjobService) handleDatabase(cronjob model.Cronjob, startTime time.Ti
return err
} else {
task.Log(i18n.GetMsgWithDetail("IgnoreBackupErr", err.Error()))
cleanAccountMap(accountMap)
return nil
}
}
@@ -168,26 +181,30 @@ func (u *CronjobService) handleDatabase(cronjob model.Cronjob, startTime time.Ti
return err
} else {
task.Log(i18n.GetMsgWithDetail("IgnoreBackupErr", err.Error()))
cleanAccountMap(accountMap)
return nil
}
}
}
downloadPath, err := u.uploadCronjobBackFile(cronjob, task, accountMap, path.Join(backupDir, record.FileName))
if err != nil {
src := path.Join(backupDir, record.FileName)
dst := strings.TrimPrefix(src, global.Dir.LocalBackupDir+"/tmp/")
if err := uploadWithMap(*task, accountMap, src, dst, cronjob.SourceAccountIDs, cronjob.DownloadAccountID, cronjob.RetryTimes); err != nil {
if retry < int(cronjob.RetryTimes) || !cronjob.IgnoreErr {
retry++
return err
}
task.Log(i18n.GetMsgWithDetail("IgnoreUploadErr", err.Error()))
cleanAccountMap(accountMap)
return nil
}
record.FileDir = path.Dir(downloadPath)
record.FileDir = path.Dir(dst)
if err := backupRepo.CreateRecord(&record); err != nil {
global.LOG.Errorf("save backup record failed, err: %v", err)
return err
}
u.removeExpiredBackup(cronjob, accountMap, record)
cleanAccountMap(accountMap)
return nil
}, nil, int(cronjob.RetryTimes), time.Duration(cronjob.Timeout)*time.Second)
}
@@ -195,15 +212,16 @@ func (u *CronjobService) handleDatabase(cronjob model.Cronjob, startTime time.Ti
}
func (u *CronjobService) handleDirectory(cronjob model.Cronjob, startTime time.Time, taskItem *task.Task) error {
accountMap := NewBackupClientMap(strings.Split(cronjob.SourceAccountIDs, ","))
if !accountMap[fmt.Sprintf("%d", cronjob.DownloadAccountID)].isOk {
return errors.New(i18n.GetMsgWithDetail("LoadBackupFailed", accountMap[fmt.Sprintf("%d", cronjob.DownloadAccountID)].message))
}
taskItem.AddSubTaskWithOps(task.GetTaskName(cronjob.SourceDir, task.TaskBackup, task.TaskScopeCronjob), func(task *task.Task) error {
accountMap, err := NewBackupClientMap(strings.Split(cronjob.SourceAccountIDs, ","))
if err != nil {
return err
}
fileName := fmt.Sprintf("%s.tar.gz", startTime.Format(constant.DateTimeSlimLayout)+common.RandStrAndNum(2))
if cronjob.IsDir || len(strings.Split(cronjob.SourceDir, ",")) == 1 {
fileName = loadFileName(cronjob.SourceDir)
}
fileName = simplifiedFileName(fileName)
backupDir := path.Join(global.Dir.LocalBackupDir, fmt.Sprintf("tmp/%s/%s", cronjob.Type, cronjob.Name))
fileOp := files.NewFileOp()
@@ -225,15 +243,15 @@ func (u *CronjobService) handleDirectory(cronjob model.Cronjob, startTime time.T
record.CronjobID = cronjob.ID
record.Name = cronjob.Name
record.DownloadAccountID, record.SourceAccountIDs = cronjob.DownloadAccountID, cronjob.SourceAccountIDs
downloadPath, err := u.uploadCronjobBackFile(cronjob, task, accountMap, path.Join(backupDir, fileName))
if err != nil {
taskItem.LogFailedWithErr("Upload backup file", err)
src := path.Join(backupDir, fileName)
dst := strings.TrimPrefix(src, global.Dir.LocalBackupDir+"/tmp/")
if err := uploadWithMap(*task, accountMap, src, dst, cronjob.SourceAccountIDs, cronjob.DownloadAccountID, cronjob.RetryTimes); err != nil {
return err
}
record.FileDir = path.Dir(downloadPath)
record.FileDir = path.Dir(dst)
record.FileName = fileName
if err := backupRepo.CreateRecord(&record); err != nil {
taskItem.LogFailedWithErr("Save record", err)
return err
}
u.removeExpiredBackup(cronjob, accountMap, record)
@@ -243,11 +261,11 @@ func (u *CronjobService) handleDirectory(cronjob model.Cronjob, startTime time.T
}
func (u *CronjobService) handleSystemLog(cronjob model.Cronjob, startTime time.Time, taskItem *task.Task) error {
taskItem.AddSubTaskWithOps(task.GetTaskName(i18n.GetMsgByKey("BackupSystemLog"), task.TaskBackup, task.TaskScopeCronjob), func(task *task.Task) error {
accountMap, err := NewBackupClientMap(strings.Split(cronjob.SourceAccountIDs, ","))
if err != nil {
return err
}
accountMap := NewBackupClientMap(strings.Split(cronjob.SourceAccountIDs, ","))
if !accountMap[fmt.Sprintf("%d", cronjob.DownloadAccountID)].isOk {
return errors.New(i18n.GetMsgWithDetail("LoadBackupFailed", accountMap[fmt.Sprintf("%d", cronjob.DownloadAccountID)].message))
}
taskItem.AddSubTaskWithOps(task.GetTaskName(i18n.GetMsgByKey("SystemLog"), task.TaskBackup, task.TaskScopeCronjob), func(task *task.Task) error {
nameItem := startTime.Format(constant.DateTimeSlimLayout) + common.RandStrAndNum(5)
fileName := fmt.Sprintf("system_log_%s.tar.gz", nameItem)
backupDir := path.Join(global.Dir.LocalBackupDir, "tmp/log", nameItem)
@@ -260,15 +278,15 @@ func (u *CronjobService) handleSystemLog(cronjob model.Cronjob, startTime time.T
record.CronjobID = cronjob.ID
record.Name = cronjob.Name
record.DownloadAccountID, record.SourceAccountIDs = cronjob.DownloadAccountID, cronjob.SourceAccountIDs
downloadPath, err := u.uploadCronjobBackFile(cronjob, task, accountMap, path.Join(path.Dir(backupDir), fileName))
if err != nil {
taskItem.LogFailedWithErr("Upload backup file", err)
src := path.Join(path.Dir(backupDir), fileName)
dst := strings.TrimPrefix(src, global.Dir.LocalBackupDir+"/tmp/")
if err := uploadWithMap(*task, accountMap, src, dst, cronjob.SourceAccountIDs, cronjob.DownloadAccountID, cronjob.RetryTimes); err != nil {
return err
}
record.FileDir = path.Dir(downloadPath)
record.FileDir = path.Dir(dst)
record.FileName = fileName
if err := backupRepo.CreateRecord(&record); err != nil {
taskItem.LogFailedWithErr("Save record", err)
return err
}
u.removeExpiredBackup(cronjob, accountMap, record)
@@ -278,11 +296,10 @@ func (u *CronjobService) handleSystemLog(cronjob model.Cronjob, startTime time.T
}
func (u *CronjobService) handleSnapshot(cronjob model.Cronjob, jobRecord model.JobRecords, taskItem *task.Task) error {
accountMap, err := NewBackupClientMap(strings.Split(cronjob.SourceAccountIDs, ","))
if err != nil {
return err
accountMap := NewBackupClientMap(strings.Split(cronjob.SourceAccountIDs, ","))
if !accountMap[fmt.Sprintf("%d", cronjob.DownloadAccountID)].isOk {
return errors.New(i18n.GetMsgWithDetail("LoadBackupFailed", accountMap[fmt.Sprintf("%d", cronjob.DownloadAccountID)].message))
}
var record model.BackupRecord
record.From = "cronjob"
record.Type = "snapshot"
@@ -302,9 +319,10 @@ func (u *CronjobService) handleSnapshot(cronjob model.Cronjob, jobRecord model.J
return err
}
req := dto.SnapshotCreate{
Name: fmt.Sprintf("snapshot-1panel-%s-%s-linux-%s-%s", scope, versionItem.Value, loadOs(), jobRecord.StartTime.Format(constant.DateTimeSlimLayout)+common.RandStrAndNum(5)),
Secret: cronjob.Secret,
TaskID: jobRecord.TaskID,
Name: fmt.Sprintf("snapshot-1panel-%s-%s-linux-%s-%s", scope, versionItem.Value, loadOs(), jobRecord.StartTime.Format(constant.DateTimeSlimLayout)+common.RandStrAndNum(5)),
Secret: cronjob.Secret,
TaskID: jobRecord.TaskID,
Timeout: cronjob.Timeout,
SourceAccountIDs: record.SourceAccountIDs,
DownloadAccountID: cronjob.DownloadAccountID,
@@ -320,7 +338,7 @@ func (u *CronjobService) handleSnapshot(cronjob model.Cronjob, jobRecord model.J
IgnoreFiles: strings.Split(cronjob.ExclusionRules, ","),
}
if err := NewISnapshotService().SnapshotCreate(taskItem, req, jobRecord.ID, cronjob.RetryTimes, cronjob.Timeout); err != nil {
if err := NewISnapshotService().SnapshotCreate(taskItem, req, jobRecord.ID, cronjob.RetryTimes); err != nil {
return err
}
record.FileName = req.Name + ".tar.gz"
@@ -357,6 +375,13 @@ type DatabaseHelper struct {
Name string
}
func addSkipTask(source string, taskItem *task.Task) {
taskItem.AddSubTask(task.GetTaskName(i18n.GetMsgByKey(source), task.TaskBackup, task.TaskScopeCronjob), func(task *task.Task) error {
taskItem.Log(i18n.GetMsgByKey("NoSuchResource"))
return nil
}, nil)
}
func loadDbsForJob(cronjob model.Cronjob) []DatabaseHelper {
var dbs []DatabaseHelper
if cronjob.DBName == "all" {
@@ -460,6 +485,7 @@ func handleBackupLogs(taskItem *task.Task, targetDir, fileName string, secret st
}
}
}
taskItem.Logf("%s Website logs...", i18n.GetMsgByKey("TaskBackup"))
systemDir := path.Join(targetDir, "system")
if _, err := os.Stat(systemDir); err != nil && os.IsNotExist(err) {
@@ -468,7 +494,6 @@ func handleBackupLogs(taskItem *task.Task, targetDir, fileName string, secret st
}
}
taskItem.Logf("%s System logs...", i18n.GetMsgByKey("TaskBackup"))
systemLogFiles, _ := os.ReadDir(global.Dir.LogDir)
if len(systemLogFiles) != 0 {
for i := 0; i < len(systemLogFiles); i++ {
@@ -477,8 +502,8 @@ func handleBackupLogs(taskItem *task.Task, targetDir, fileName string, secret st
}
}
}
taskItem.Logf("%s System logs...", i18n.GetMsgByKey("TaskBackup"))
taskItem.Logf("%s SSH logs...", i18n.GetMsgByKey("TaskBackup"))
loginLogFiles, _ := os.ReadDir("/var/log")
loginDir := path.Join(targetDir, "login")
if _, err := os.Stat(loginDir); err != nil && os.IsNotExist(err) {
@@ -493,7 +518,7 @@ func handleBackupLogs(taskItem *task.Task, targetDir, fileName string, secret st
}
}
}
taskItem.Log("backup ssh log successful!")
taskItem.Logf("%s SSH logs...", i18n.GetMsgByKey("TaskBackup"))
if err := fileOp.TarGzCompressPro(true, targetDir, path.Join(path.Dir(targetDir), fileName), secret, ""); err != nil {
return err
@@ -563,3 +588,22 @@ func loadFileName(src string) string {
timestamp := time.Now().Format(constant.DateTimeSlimLayout)
return fmt.Sprintf("%s_%s_%s.tar.gz", cleanName, timestamp, common.RandStrAndNum(2))
}
func simplifiedFileName(name string) string {
name = strings.ReplaceAll(name, "/", "_")
name = strings.ReplaceAll(name, ":", "_")
name = strings.ReplaceAll(name, "*", "_")
name = strings.ReplaceAll(name, "?", "_")
name = strings.ReplaceAll(name, "\"", "_")
name = strings.ReplaceAll(name, "<", "_")
name = strings.ReplaceAll(name, ">", "_")
name = strings.ReplaceAll(name, "|", "_")
return name
}
func cleanAccountMap(accountMap map[string]backupClientHelper) {
for key, val := range accountMap {
val.hasBackup = false
accountMap[key] = val
}
}
+48 -55
View File
@@ -10,6 +10,7 @@ import (
"time"
"github.com/1Panel-dev/1Panel/agent/utils/alert_push"
"github.com/pkg/errors"
"github.com/1Panel-dev/1Panel/agent/app/dto"
"github.com/1Panel-dev/1Panel/agent/app/model"
@@ -24,34 +25,51 @@ import (
)
func (u *CronjobService) HandleJob(cronjob *model.Cronjob) {
record := cronjobRepo.StartRecords(cronjob.ID, "", cronjob.Type)
go func() {
taskItem, err := task.NewTaskWithOps(fmt.Sprintf("cronjob-%s", cronjob.Name), task.TaskHandle, task.TaskScopeCronjob, record.TaskID, cronjob.ID)
if err != nil {
global.LOG.Errorf("new task for exec shell failed, err: %v", err)
return
}
err = u.loadTask(cronjob, &record, taskItem)
if cronjob.Type == "snapshot" {
if err != nil {
taskItem, _ := taskRepo.GetFirst(taskRepo.WithByID(record.TaskID))
if len(taskItem.ID) == 0 {
record.TaskID = ""
cronjobItem, _ := cronjobRepo.Get(repo.WithByID(cronjob.ID))
if cronjobItem.IsExecuting {
cronjobRepo.AddFailedRecord(cronjob.ID, i18n.GetMsgByKey("InExecuting"))
return
}
record := cronjobRepo.StartRecords(cronjob.ID)
taskItem, err := task.NewTaskWithOps(fmt.Sprintf("cronjob-%s", cronjob.Name), task.TaskHandle, task.TaskScopeCronjob, record.TaskID, cronjob.ID)
if err != nil {
global.LOG.Errorf("new task for exec shell failed, err: %v", err)
return
}
if cronjob.Type == "snapshot" {
go func() {
_ = cronjobRepo.UpdateRecords(record.ID, map[string]interface{}{"records": record.Records})
if err := taskRepo.Save(context.Background(), taskItem.Task); err != nil {
global.LOG.Errorf("save task for snapshot cronjob failed, err: %v", err)
return
}
if err = u.handleSnapshot(*cronjob, record, taskItem); err != nil {
if len(taskItem.Task.CurrentStep) == 0 {
taskItem.Log(err.Error())
taskItem.Task.Status = constant.StatusFailed
taskItem.Task.ErrorMsg = err.Error()
taskItem.Task.EndAt = time.Now()
_ = taskRepo.Save(context.Background(), taskItem.Task)
}
cronjobRepo.EndRecords(record, constant.StatusFailed, err.Error(), record.Records)
handleCronJobAlert(cronjob)
return
}
cronjobRepo.EndRecords(record, constant.StatusSuccess, "", record.Records)
return
}
if err != nil {
global.LOG.Debugf("preper to handle cron job [%s] %s failed, err: %v", cronjob.Type, cronjob.Name, err)
}()
return
}
if err = u.loadTask(cronjob, &record, taskItem); err != nil {
global.LOG.Debugf("preper to handle cron job [%s] %s failed, err: %v", cronjob.Type, cronjob.Name, err)
item, _ := taskRepo.GetFirst(taskRepo.WithByID(record.TaskID))
if len(item.ID) == 0 {
record.TaskID = ""
cronjobRepo.EndRecords(record, constant.StatusFailed, err.Error(), record.Records)
handleCronJobAlert(cronjob)
return
}
cronjobRepo.EndRecords(record, constant.StatusFailed, err.Error(), record.Records)
handleCronJobAlert(cronjob)
return
}
go func() {
if err := taskItem.Execute(); err != nil {
taskItem, _ := taskRepo.GetFirst(taskRepo.WithByID(record.TaskID))
if len(taskItem.ID) == 0 {
@@ -109,9 +127,6 @@ func (u *CronjobService) loadTask(cronjob *model.Cronjob, record *model.JobRecor
err = u.handleDirectory(*cronjob, record.StartTime, taskItem)
case "log":
err = u.handleSystemLog(*cronjob, record.StartTime, taskItem)
case "snapshot":
_ = cronjobRepo.UpdateRecords(record.ID, map[string]interface{}{"records": record.Records})
err = u.handleSnapshot(*cronjob, *record, taskItem)
}
return err
}
@@ -191,14 +206,14 @@ func (u *CronjobService) handleNtpSync(cronjob model.Cronjob, taskItem *task.Tas
}
func (u *CronjobService) handleCutWebsiteLog(cronjob *model.Cronjob, startTime time.Time, taskItem *task.Task) error {
clientMap := NewBackupClientMap([]string{fmt.Sprintf("%v", cronjob.DownloadAccountID)})
if !clientMap[fmt.Sprintf("%d", cronjob.DownloadAccountID)].isOk {
return errors.New(i18n.GetMsgWithDetail("LoadBackupFailed", clientMap[fmt.Sprintf("%d", cronjob.DownloadAccountID)].message))
}
taskItem.AddSubTaskWithOps(i18n.GetWithName("CutWebsiteLog", cronjob.Name), func(t *task.Task) error {
websites := loadWebsForJob(*cronjob)
fileOp := files.NewFileOp()
baseDir := GetOpenrestyDir(SitesRootDir)
clientMap, err := NewBackupClientMap([]string{fmt.Sprintf("%v", cronjob.DownloadAccountID)})
if err != nil {
return fmt.Errorf("load local backup client failed, err: %v", err)
}
for _, website := range websites {
taskItem.Log(website.Alias)
var record model.BackupRecord
@@ -264,34 +279,6 @@ func (u *CronjobService) handleSystemClean(cronjob model.Cronjob, taskItem *task
taskItem.AddSubTaskWithOps(i18n.GetMsgByKey("HandleSystemClean"), cleanTask, nil, int(cronjob.RetryTimes), time.Duration(cronjob.Timeout)*time.Second)
}
func (u *CronjobService) uploadCronjobBackFile(cronjob model.Cronjob, task *task.Task, accountMap map[string]backupClientHelper, file string) (string, error) {
defer func() {
_ = os.Remove(file)
}()
var errItem error
accounts := strings.Split(cronjob.SourceAccountIDs, ",")
cloudSrc := strings.TrimPrefix(file, global.Dir.LocalBackupDir+"/tmp/")
for _, account := range accounts {
if len(account) != 0 {
task.LogStart(i18n.GetMsgWithMap("UploadFile", map[string]interface{}{
"file": pathUtils.Join(accountMap[account].backupPath, cloudSrc),
"backup": accountMap[account].name,
}))
_, err := accountMap[account].client.Upload(file, pathUtils.Join(accountMap[account].backupPath, cloudSrc))
task.LogWithStatus(
i18n.GetMsgWithMap("UploadFile", map[string]interface{}{
"file": pathUtils.Join(accountMap[account].backupPath, cloudSrc),
"backup": accountMap[account].name,
}), err)
if err != nil {
errItem = err
continue
}
}
}
return cloudSrc, errItem
}
func (u *CronjobService) removeExpiredBackup(cronjob model.Cronjob, accountMap map[string]backupClientHelper, record model.BackupRecord) {
var opts []repo.DBOption
opts = append(opts, repo.WithByFrom("cronjob"))
@@ -314,6 +301,9 @@ func (u *CronjobService) removeExpiredBackup(cronjob model.Cronjob, accountMap m
if _, ok := accountMap[account]; !ok {
continue
}
if !accountMap[account].isOk {
continue
}
_, _ = accountMap[account].client.Delete(pathUtils.Join(accountMap[account].backupPath, "system_snapshot", records[i].FileName))
}
}
@@ -324,6 +314,9 @@ func (u *CronjobService) removeExpiredBackup(cronjob model.Cronjob, accountMap m
if _, ok := accountMap[account]; !ok {
continue
}
if !accountMap[account].isOk {
continue
}
_, _ = accountMap[account].client.Delete(pathUtils.Join(accountMap[account].backupPath, records[i].FileDir, records[i].FileName))
}
}
+94 -51
View File
@@ -14,6 +14,7 @@ import (
"github.com/1Panel-dev/1Panel/agent/app/dto"
"github.com/1Panel-dev/1Panel/agent/app/model"
"github.com/1Panel-dev/1Panel/agent/app/repo"
"github.com/1Panel-dev/1Panel/agent/buserr"
"github.com/1Panel-dev/1Panel/agent/constant"
"github.com/1Panel-dev/1Panel/agent/global"
"github.com/1Panel-dev/1Panel/agent/utils/ai_tools/gpu"
@@ -22,12 +23,12 @@ import (
"github.com/1Panel-dev/1Panel/agent/utils/common"
"github.com/1Panel-dev/1Panel/agent/utils/copier"
"github.com/gin-gonic/gin"
"github.com/shirou/gopsutil/v3/cpu"
"github.com/shirou/gopsutil/v3/disk"
"github.com/shirou/gopsutil/v3/host"
"github.com/shirou/gopsutil/v3/load"
"github.com/shirou/gopsutil/v3/mem"
"github.com/shirou/gopsutil/v3/net"
"github.com/shirou/gopsutil/v4/cpu"
"github.com/shirou/gopsutil/v4/disk"
"github.com/shirou/gopsutil/v4/host"
"github.com/shirou/gopsutil/v4/load"
"github.com/shirou/gopsutil/v4/mem"
"github.com/shirou/gopsutil/v4/net"
)
type DashboardService struct{}
@@ -38,6 +39,9 @@ type IDashboardService interface {
LoadCurrentInfoForNode() *dto.NodeCurrent
LoadCurrentInfo(ioOption string, netOption string) *dto.DashboardCurrent
LoadQuickOptions() []dto.QuickJump
ChangeQuick(req dto.ChangeQuicks) error
LoadAppLauncher(ctx *gin.Context) ([]dto.AppLauncher, error)
ChangeShow(req dto.SettingUpdate) error
ListLauncherOption(filter string) ([]dto.LauncherOption, error)
@@ -140,7 +144,7 @@ func (u *DashboardService) LoadBaseInfo(ioOption string, netOption string) (*dto
baseInfo.KernelVersion = hostInfo.KernelVersion
ss, _ := json.Marshal(hostInfo)
baseInfo.VirtualizationSystem = string(ss)
baseInfo.IpV4Addr = GetOutboundIP()
baseInfo.IpV4Addr = loadOutboundIP()
httpProxy := os.Getenv("http_proxy")
if httpProxy == "" {
httpProxy = os.Getenv("HTTP_PROXY")
@@ -150,30 +154,7 @@ func (u *DashboardService) LoadBaseInfo(ioOption string, netOption string) (*dto
}
baseInfo.SystemProxy = "noProxy"
appInstall, err := appInstallRepo.ListBy(context.Background())
if err != nil {
return nil, err
}
baseInfo.AppInstalledNumber = len(appInstall)
postgresqlDbs, err := postgresqlRepo.List()
if err != nil {
return nil, err
}
mysqlDbs, err := mysqlRepo.List()
if err != nil {
return nil, err
}
baseInfo.DatabaseNumber = len(mysqlDbs) + len(postgresqlDbs)
website, err := websiteRepo.GetBy()
if err != nil {
return nil, err
}
baseInfo.WebsiteNumber = len(website)
cronjobs, err := cronjobRepo.List()
if err != nil {
return nil, err
}
baseInfo.CronjobNumber = len(cronjobs)
loadQuickJump(&baseInfo)
cpuInfo, err := cpu.Info()
if err == nil {
@@ -268,10 +249,7 @@ func (u *DashboardService) LoadCurrentInfo(ioOption string, netOption string) *d
}
func (u *DashboardService) LoadAppLauncher(ctx *gin.Context) ([]dto.AppLauncher, error) {
var (
data []dto.AppLauncher
recommendList []dto.AppLauncher
)
var data []dto.AppLauncher
appInstalls, err := appInstallRepo.ListBy(context.Background())
if err != nil {
return data, err
@@ -281,8 +259,11 @@ func (u *DashboardService) LoadAppLauncher(ctx *gin.Context) ([]dto.AppLauncher,
return data, err
}
showList, _ := launcherRepo.ListName()
defaultList := []string{"openresty", "mysql", "halo", "redis", "maxkb", "wordpress"}
showList, err := launcherRepo.ListName()
defaultList, err := appRepo.GetTopRecomment()
if err != nil {
return data, nil
}
allList := common.RemoveRepeatStr(append(defaultList, showList...))
for _, showItem := range allList {
var itemData dto.AppLauncher
@@ -317,24 +298,18 @@ func (u *DashboardService) LoadAppLauncher(ctx *gin.Context) ([]dto.AppLauncher,
})
}
}
if ArryContains(defaultList, showItem) && len(itemData.Detail) == 0 {
itemData.IsRecommend = true
recommendList = append(recommendList, itemData)
continue
if (ArryContains(showList, showItem) && len(itemData.Detail) != 0) ||
(ArryContains(defaultList, showItem) && len(itemData.Detail) == 0) {
data = append(data, itemData)
}
if !ArryContains(showList, showItem) && len(itemData.Detail) != 0 {
continue
}
data = append(data, itemData)
}
sort.Slice(recommendList, func(i, j int) bool {
return recommendList[i].Recommend < recommendList[j].Recommend
})
sort.Slice(data, func(i, j int) bool {
return data[i].Name < data[j].Name
if data[i].IsInstall != data[j].IsInstall {
return data[i].IsInstall
}
return data[i].Recommend < data[j].Recommend
})
data = append(data, recommendList...)
return data, nil
}
@@ -353,6 +328,39 @@ func (u *DashboardService) ChangeShow(req dto.SettingUpdate) error {
return nil
}
func (u *DashboardService) LoadQuickOptions() []dto.QuickJump {
quicks := launcherRepo.ListQuickJump(true)
var list []dto.QuickJump
for _, quick := range quicks {
var item dto.QuickJump
_ = copier.Copy(&item, &quick)
list = append(list, item)
}
return list
}
func (u *DashboardService) ChangeQuick(req dto.ChangeQuicks) error {
showCount := 0
var quicks []model.QuickJump
for _, item := range req.Quicks {
var quick model.QuickJump
if item.IsShow {
showCount++
}
if err := copier.Copy(&quick, &item); err != nil {
return err
}
quicks = append(quicks, quick)
}
if showCount == 0 {
return buserr.New("ErrMinQuickJump")
}
if showCount > 4 {
return buserr.New("ErrMaxQuickJump")
}
return launcherRepo.UpdateQuicks(quicks)
}
func (u *DashboardService) ListLauncherOption(filter string) ([]dto.LauncherOption, error) {
showList, _ := launcherRepo.ListName()
var data []dto.LauncherOption
@@ -551,7 +559,7 @@ func loadXpuInfo() []dto.XPUInfo {
return data
}
func GetOutboundIP() string {
func loadOutboundIP() string {
conn, err := network.Dial("udp", "8.8.8.8:80")
if err != nil {
@@ -562,3 +570,38 @@ func GetOutboundIP() string {
localAddr := conn.LocalAddr().(*network.UDPAddr)
return localAddr.IP.String()
}
func loadQuickJump(base *dto.DashboardBase) {
website, _ := websiteRepo.GetBy()
base.WebsiteNumber = len(website)
postgresqlDbs, _ := postgresqlRepo.List()
mysqlDbs, _ := mysqlRepo.List()
base.DatabaseNumber = len(mysqlDbs) + len(postgresqlDbs)
cronjobs, _ := cronjobRepo.List()
base.CronjobNumber = len(cronjobs)
appInstall, _ := appInstallRepo.ListBy(context.Background())
base.AppInstalledNumber = len(appInstall)
quicks := launcherRepo.ListQuickJump(false)
for i := 0; i < len(quicks); i++ {
switch quicks[i].Name {
case "Website":
quicks[i].Detail = fmt.Sprintf("%d", base.WebsiteNumber)
case "Database":
quicks[i].Detail = fmt.Sprintf("%d", base.DatabaseNumber)
case "Cronjob":
quicks[i].Detail = fmt.Sprintf("%d", base.CronjobNumber)
case "AppInstalled":
quicks[i].Detail = fmt.Sprintf("%d", base.AppInstalledNumber)
}
var item dto.QuickJump
_ = copier.Copy(&item, quicks[i])
base.QuickJumps = append(base.QuickJumps, item)
}
sort.Slice(quicks, func(i, j int) bool {
return quicks[i].Recommend < quicks[j].Recommend
})
}
+43 -11
View File
@@ -6,7 +6,7 @@ import (
"fmt"
"os"
"os/exec"
"path"
"path/filepath"
"regexp"
"strconv"
"strings"
@@ -42,7 +42,7 @@ type IMysqlService interface {
ChangePassword(info dto.ChangeDBInfo) error
UpdateVariables(req dto.MysqlVariablesUpdate) error
UpdateDescription(req dto.UpdateDescription) error
DeleteCheck(req dto.MysqlDBDeleteCheck) ([]string, error)
DeleteCheck(req dto.MysqlDBDeleteCheck) ([]dto.DBResource, error)
Delete(ctx context.Context, req dto.MysqlDBDelete) error
LoadStatus(req dto.OperationWithNameAndType) (*dto.MysqlStatus, error)
@@ -229,23 +229,41 @@ func (u *MysqlService) UpdateDescription(req dto.UpdateDescription) error {
return mysqlRepo.Update(req.ID, map[string]interface{}{"description": req.Description})
}
func (u *MysqlService) DeleteCheck(req dto.MysqlDBDeleteCheck) ([]string, error) {
var appInUsed []string
func (u *MysqlService) DeleteCheck(req dto.MysqlDBDeleteCheck) ([]dto.DBResource, error) {
var res []dto.DBResource
db, err := mysqlRepo.Get(repo.WithByID(req.ID))
if err != nil {
return appInUsed, err
return res, err
}
website, _ := websiteRepo.GetFirst(websiteRepo.WithDBType(constant.AppMysql), websiteRepo.WithDBID(req.ID))
if website.ID != 0 {
res = append(res, dto.DBResource{
Type: constant.TypeWebsite,
Name: website.PrimaryDomain,
})
}
website, _ = websiteRepo.GetFirst(websiteRepo.WithDBType(constant.AppMysqlCluster), websiteRepo.WithDBID(req.ID))
if website.ID != 0 {
res = append(res, dto.DBResource{
Type: constant.TypeWebsite,
Name: website.PrimaryDomain,
})
}
if db.From == "local" {
app, err := appInstallRepo.LoadBaseInfo(req.Type, req.Database)
if err != nil {
return appInUsed, err
return res, err
}
apps, _ := appInstallResourceRepo.GetBy(appInstallResourceRepo.WithLinkId(app.ID), appInstallResourceRepo.WithResourceId(db.ID))
for _, app := range apps {
appInstall, _ := appInstallRepo.GetFirst(repo.WithByID(app.AppInstallId))
if appInstall.ID != 0 {
appInUsed = append(appInUsed, appInstall.Name)
res = append(res, dto.DBResource{
Type: constant.TypeApp,
Name: appInstall.Name,
})
}
}
} else {
@@ -253,12 +271,15 @@ func (u *MysqlService) DeleteCheck(req dto.MysqlDBDeleteCheck) ([]string, error)
for _, app := range apps {
appInstall, _ := appInstallRepo.GetFirst(repo.WithByID(app.AppInstallId))
if appInstall.ID != 0 {
appInUsed = append(appInUsed, appInstall.Name)
res = append(res, dto.DBResource{
Type: constant.TypeApp,
Name: appInstall.Name,
})
}
}
}
return appInUsed, nil
return res, nil
}
func (u *MysqlService) Delete(ctx context.Context, req dto.MysqlDBDelete) error {
@@ -282,11 +303,11 @@ func (u *MysqlService) Delete(ctx context.Context, req dto.MysqlDBDelete) error
}
if req.DeleteBackup {
uploadDir := path.Join(global.Dir.DataDir, fmt.Sprintf("uploads/database/%s/%s/%s", req.Type, req.Database, db.Name))
uploadDir := filepath.Join(global.Dir.DataDir, fmt.Sprintf("uploads/database/%s/%s/%s", req.Type, req.Database, db.Name))
if _, err := os.Stat(uploadDir); err == nil {
_ = os.RemoveAll(uploadDir)
}
backupDir := path.Join(global.Dir.LocalBackupDir, fmt.Sprintf("database/%s/%s/%s", req.Type, db.MysqlName, db.Name))
backupDir := filepath.Join(global.Dir.LocalBackupDir, fmt.Sprintf("database/%s/%s/%s", req.Type, db.MysqlName, db.Name))
if _, err := os.Stat(backupDir); err == nil {
_ = os.RemoveAll(backupDir)
}
@@ -438,6 +459,17 @@ func (u *MysqlService) UpdateVariables(req dto.MysqlVariablesUpdate) error {
group := "[mysqld]"
for _, info := range req.Variables {
if info.Param == "slow_query_log" && info.Value == "ON" {
logFilePath := filepath.Join(global.Dir.DataDir, fmt.Sprintf("apps/%s/%s/data/1Panel-slow.log", app.Key, app.Name))
if req.Type == "mariadb" {
logFilePath = filepath.Join(global.Dir.DataDir, fmt.Sprintf("apps/%s/%s/db/data/1Panel-slow.log", app.Key, app.Name))
}
file, err := os.OpenFile(logFilePath, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0644)
if err != nil {
return err
}
defer file.Close()
}
if !strings.HasPrefix(app.Version, "5.7") && !strings.HasPrefix(app.Version, "5.6") {
if info.Param == "query_cache_size" {
continue
+34 -8
View File
@@ -7,6 +7,8 @@ import (
"path"
"strings"
"github.com/1Panel-dev/1Panel/agent/constant"
"github.com/1Panel-dev/1Panel/agent/app/repo"
"github.com/1Panel-dev/1Panel/agent/app/dto"
@@ -33,7 +35,7 @@ type IPostgresqlService interface {
ChangePrivileges(req dto.PostgresqlPrivileges) error
ChangePassword(info dto.ChangeDBInfo) error
UpdateDescription(req dto.UpdateDescription) error
DeleteCheck(req dto.PostgresqlDBDeleteCheck) ([]string, error)
DeleteCheck(req dto.PostgresqlDBDeleteCheck) ([]dto.DBResource, error)
Delete(ctx context.Context, req dto.PostgresqlDBDelete) error
}
@@ -250,23 +252,41 @@ func (u *PostgresqlService) UpdateDescription(req dto.UpdateDescription) error {
return postgresqlRepo.Update(req.ID, map[string]interface{}{"description": req.Description})
}
func (u *PostgresqlService) DeleteCheck(req dto.PostgresqlDBDeleteCheck) ([]string, error) {
var appInUsed []string
func (u *PostgresqlService) DeleteCheck(req dto.PostgresqlDBDeleteCheck) ([]dto.DBResource, error) {
var res []dto.DBResource
db, err := postgresqlRepo.Get(repo.WithByID(req.ID))
if err != nil {
return appInUsed, err
return res, err
}
website, _ := websiteRepo.GetFirst(websiteRepo.WithDBType(constant.AppPostgresql), websiteRepo.WithDBID(req.ID))
if website.ID != 0 {
res = append(res, dto.DBResource{
Type: constant.TypeWebsite,
Name: website.PrimaryDomain,
})
}
website, _ = websiteRepo.GetFirst(websiteRepo.WithDBType(constant.AppPostgresqlCluster), websiteRepo.WithDBID(req.ID))
if website.ID != 0 {
res = append(res, dto.DBResource{
Type: constant.TypeWebsite,
Name: website.PrimaryDomain,
})
}
if db.From == "local" {
app, err := appInstallRepo.LoadBaseInfo(req.Type, req.Database)
if err != nil {
return appInUsed, err
return res, err
}
apps, _ := appInstallResourceRepo.GetBy(appInstallResourceRepo.WithLinkId(app.ID), appInstallResourceRepo.WithResourceId(db.ID))
for _, app := range apps {
appInstall, _ := appInstallRepo.GetFirst(repo.WithByID(app.AppInstallId))
if appInstall.ID != 0 {
appInUsed = append(appInUsed, appInstall.Name)
res = append(res, dto.DBResource{
Type: constant.TypeApp,
Name: appInstall.Name,
})
}
}
} else {
@@ -274,12 +294,15 @@ func (u *PostgresqlService) DeleteCheck(req dto.PostgresqlDBDeleteCheck) ([]stri
for _, app := range apps {
appInstall, _ := appInstallRepo.GetFirst(repo.WithByID(app.AppInstallId))
if appInstall.ID != 0 {
appInUsed = append(appInUsed, appInstall.Name)
res = append(res, dto.DBResource{
Type: constant.TypeApp,
Name: appInstall.Name,
})
}
}
}
return appInUsed, nil
return res, nil
}
func (u *PostgresqlService) Delete(ctx context.Context, req dto.PostgresqlDBDelete) error {
@@ -298,6 +321,9 @@ func (u *PostgresqlService) Delete(ctx context.Context, req dto.PostgresqlDBDele
ForceDelete: req.ForceDelete,
Timeout: 300,
}); err != nil && !req.ForceDelete {
if strings.HasPrefix(err.Error(), "drop user") {
_ = postgresqlRepo.Update(db.ID, map[string]interface{}{"is_delete": true})
}
return err
}
+1 -1
View File
@@ -18,7 +18,7 @@ import (
"github.com/1Panel-dev/1Panel/agent/utils/cmd"
"github.com/1Panel-dev/1Panel/agent/utils/common"
"github.com/1Panel-dev/1Panel/agent/utils/ntp"
"github.com/shirou/gopsutil/v3/mem"
"github.com/shirou/gopsutil/v4/mem"
)
const defaultDNSPath = "/etc/resolv.conf"
+83 -194
View File
@@ -11,6 +11,7 @@ import (
"time"
"github.com/1Panel-dev/1Panel/agent/constant"
"github.com/1Panel-dev/1Panel/agent/i18n"
"github.com/1Panel-dev/1Panel/agent/utils/docker"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/build"
@@ -78,19 +79,19 @@ func (u *DeviceService) Scan() dto.CleanData {
}
treeData = append(treeData, upgradeTree)
snapTree := loadSnapshotTree(fileOp)
snapSize := uint64(0)
for _, snap := range snapTree {
snapSize += snap.Size
tmpBackupTree := loadTmpBackupTree(fileOp)
tmpBackupSize := uint64(0)
for _, tmp := range tmpBackupTree {
tmpBackupSize += tmp.Size
}
treeData = append(treeData, dto.CleanTree{
ID: uuid.NewString(),
Label: "snapshot",
Size: snapSize,
IsCheck: snapSize > 0,
Label: "backup",
Size: tmpBackupSize,
IsCheck: tmpBackupSize > 0,
IsRecommend: true,
Type: "snapshot",
Children: snapTree,
Type: "backup",
Children: tmpBackupTree,
})
rollBackTree := loadRollBackTree(fileOp)
@@ -118,21 +119,6 @@ func (u *DeviceService) Scan() dto.CleanData {
IsRecommend: false,
Type: "cache",
})
unusedTree := loadUnusedFile(fileOp)
unusedSize := uint64(0)
for _, unused := range unusedTree {
unusedSize += unused.Size
}
treeData = append(treeData, dto.CleanTree{
ID: uuid.NewString(),
Label: "unused",
Size: unusedSize,
IsCheck: unusedSize > 0,
IsRecommend: true,
Type: "unused",
Children: unusedTree,
})
SystemClean.SystemClean = treeData
uploadTreeData := loadUploadTree(fileOp)
@@ -162,13 +148,13 @@ func (u *DeviceService) Clean(req []dto.Clean) {
case "upgrade":
dropFileOrDir(path.Join(global.Dir.BaseDir, upgradePath, item.Name))
case "snapshot":
dropFileOrDir(path.Join(global.Dir.BaseDir, snapshotTmpPath, item.Name))
dropFileOrDir(path.Join(global.Dir.LocalBackupDir, "system", item.Name))
case "snapshot_tmp":
dropFileOrDir(path.Join(global.Dir.BaseDir, snapshotTmpPath, item.Name))
case "snapshot_local":
dropFileOrDir(path.Join(global.Dir.LocalBackupDir, "system", item.Name))
case "backup":
dropFileOrDir(path.Join(global.Dir.LocalBackupDir, "tmp/app"))
dropFileOrDir(path.Join(global.Dir.LocalBackupDir, "tmp/database"))
dropFileOrDir(path.Join(global.Dir.LocalBackupDir, "tmp/website"))
dropFileOrDir(path.Join(global.Dir.LocalBackupDir, "tmp/directory"))
dropFileOrDir(path.Join(global.Dir.LocalBackupDir, "tmp/log"))
dropFileOrDir(path.Join(global.Dir.LocalBackupDir, "tmp/system"))
case "rollback":
dropFileOrDir(path.Join(global.Dir.BaseDir, rollbackPath, "app"))
@@ -185,40 +171,6 @@ func (u *DeviceService) Clean(req []dto.Clean) {
dropFileOrDir(path.Join(global.Dir.BaseDir, cachePath, item.Name))
restart = true
case "unused":
dropFileOrDir(path.Join(global.Dir.BaseDir, oldOriginalPath))
dropFileOrDir(path.Join(global.Dir.BaseDir, oldAppBackupPath))
dropFileOrDir(path.Join(global.Dir.BaseDir, oldDownloadPath))
files, _ := os.ReadDir(path.Join(global.Dir.BaseDir, oldUpgradePath))
if len(files) == 0 {
continue
}
for _, file := range files {
if strings.HasPrefix(file.Name(), "upgrade_") {
dropFileOrDir(path.Join(global.Dir.BaseDir, oldUpgradePath, file.Name()))
}
}
case "old_original":
dropFileOrDir(path.Join(global.Dir.BaseDir, oldOriginalPath, item.Name))
case "old_apps_bak":
dropFileOrDir(path.Join(global.Dir.BaseDir, oldAppBackupPath, item.Name))
case "old_download":
dropFileOrDir(path.Join(global.Dir.BaseDir, oldDownloadPath, item.Name))
case "old_upgrade":
if len(item.Name) == 0 {
files, _ := os.ReadDir(path.Join(global.Dir.BaseDir, oldUpgradePath))
if len(files) == 0 {
continue
}
for _, file := range files {
if strings.HasPrefix(file.Name(), "upgrade_") {
dropFileOrDir(path.Join(global.Dir.BaseDir, oldUpgradePath, file.Name()))
}
}
} else {
dropFileOrDir(path.Join(global.Dir.BaseDir, oldUpgradePath, item.Name))
}
case "upload":
dropFileOrDir(path.Join(global.Dir.BaseDir, uploadPath, item.Name))
if len(item.Name) == 0 {
@@ -283,13 +235,13 @@ func (u *DeviceService) Clean(req []dto.Clean) {
case "script":
dropFileOrDir(path.Join(global.Dir.TmpDir, "script", item.Name))
case "images":
dropImages()
_, _ = dropImages()
case "containers":
dropContainers()
_, _ = dropContainers()
case "volumes":
dropVolumes()
_, _ = dropVolumes()
case "build_cache":
dropBuildCache()
_, _ = dropBuildCache()
}
}
@@ -322,7 +274,7 @@ func doSystemClean(taskItem *task.Task) func(t *task.Task) error {
}
dropWithTask(path.Join(global.Dir.BaseDir, snapshotTmpPath), taskItem, &size, &fileCount)
dropWithTask(path.Join(global.Dir.LocalBackupDir, "system"), taskItem, &size, &fileCount)
dropWithTask(path.Join(global.Dir.LocalBackupDir, "tmp/system"), taskItem, &size, &fileCount)
dropWithTask(path.Join(global.Dir.BaseDir, rollbackPath, "app"), taskItem, &size, &fileCount)
dropWithTask(path.Join(global.Dir.BaseDir, rollbackPath, "website"), taskItem, &size, &fileCount)
@@ -354,9 +306,17 @@ func doSystemClean(taskItem *task.Task) func(t *task.Task) error {
}
}
}
count1, size1 := dropVolumes()
size += int64(size1)
fileCount += count1
count2, size2 := dropBuildCache()
size += int64(size2)
fileCount += count2
timeNow := time.Now().Format(constant.DateTimeLayout)
if fileCount != 0 {
taskItem.LogSuccessF("%s: total clean: %s, total count: %d", timeNow, common.LoadSizeUnit2F(float64(size)), fileCount)
taskItem.Log(i18n.GetMsgWithMap("FileDropSum", map[string]interface{}{"size": common.LoadSizeUnit2F(float64(size)), "count": fileCount}))
}
_ = settingRepo.Update("LastCleanTime", timeNow)
@@ -367,106 +327,32 @@ func doSystemClean(taskItem *task.Task) func(t *task.Task) error {
}
}
func loadSnapshotTree(fileOp fileUtils.FileOp) []dto.CleanTree {
func loadTmpBackupTree(fileOp fileUtils.FileOp) []dto.CleanTree {
var treeData []dto.CleanTree
path1 := path.Join(global.Dir.BaseDir, snapshotTmpPath)
list1 := loadTreeWithAllFile(true, path1, "snapshot_tmp", path1, fileOp)
if len(list1) != 0 {
size, _ := fileOp.GetDirSize(path1)
treeData = append(treeData, dto.CleanTree{ID: uuid.NewString(), Label: "snapshot_tmp", Size: uint64(size), Children: list1, Type: "snapshot_tmp", IsRecommend: true})
}
path2 := path.Join(global.Dir.LocalBackupDir, "system")
list2 := loadTreeWithAllFile(true, path2, "snapshot_local", path2, fileOp)
if len(list2) != 0 {
size, _ := fileOp.GetDirSize(path2)
treeData = append(treeData, dto.CleanTree{ID: uuid.NewString(), Label: "snapshot_local", Size: uint64(size), Children: list2, Type: "snapshot_local", IsRecommend: true})
}
treeData = loadTreeWithCheck(treeData, path.Join(global.Dir.LocalBackupDir, "tmp/app"), "tmp_backup_app", fileOp)
treeData = loadTreeWithCheck(treeData, path.Join(global.Dir.LocalBackupDir, "tmp/website"), "tmp_backup_website", fileOp)
treeData = loadTreeWithCheck(treeData, path.Join(global.Dir.LocalBackupDir, "tmp/database"), "tmp_backup_database", fileOp)
treeData = loadTreeWithCheck(treeData, path.Join(global.Dir.LocalBackupDir, "tmp/system"), "tmp_backup_snapshot", fileOp)
treeData = loadTreeWithCheck(treeData, path.Join(global.Dir.LocalBackupDir, "tmp/directory"), "tmp_backup_directory", fileOp)
treeData = loadTreeWithCheck(treeData, path.Join(global.Dir.LocalBackupDir, "tmp/log"), "tmp_backup_log", fileOp)
return treeData
}
func loadRollBackTree(fileOp fileUtils.FileOp) []dto.CleanTree {
var treeData []dto.CleanTree
path1 := path.Join(global.Dir.BaseDir, rollbackPath, "app")
list1 := loadTreeWithAllFile(true, path1, "rollback_app", path1, fileOp)
size1, _ := fileOp.GetDirSize(path1)
treeData = append(treeData, dto.CleanTree{ID: uuid.NewString(), Label: "rollback_app", Size: uint64(size1), Children: list1, Type: "rollback_app", IsRecommend: true})
treeData = loadTreeWithCheck(treeData, path.Join(global.Dir.BaseDir, rollbackPath, "app"), "rollback_app", fileOp)
treeData = loadTreeWithCheck(treeData, path.Join(global.Dir.BaseDir, rollbackPath, "website"), "rollback_website", fileOp)
treeData = loadTreeWithCheck(treeData, path.Join(global.Dir.BaseDir, rollbackPath, "database"), "rollback_database", fileOp)
path2 := path.Join(global.Dir.BaseDir, rollbackPath, "website")
list2 := loadTreeWithAllFile(true, path2, "rollback_website", path2, fileOp)
size2, _ := fileOp.GetDirSize(path2)
treeData = append(treeData, dto.CleanTree{ID: uuid.NewString(), Label: "rollback_website", Size: uint64(size2), Children: list2, Type: "rollback_website", IsRecommend: true})
path3 := path.Join(global.Dir.BaseDir, rollbackPath, "database")
list3 := loadTreeWithAllFile(true, path3, "rollback_database", path3, fileOp)
size3, _ := fileOp.GetDirSize(path3)
treeData = append(treeData, dto.CleanTree{ID: uuid.NewString(), Label: "rollback_database", Size: uint64(size3), Children: list3, Type: "rollback_database", IsRecommend: true})
return treeData
}
func loadUnusedFile(fileOp fileUtils.FileOp) []dto.CleanTree {
var treeData []dto.CleanTree
path1 := path.Join(global.Dir.BaseDir, oldOriginalPath)
list1 := loadTreeWithAllFile(true, path1, "old_original", path1, fileOp)
if len(list1) != 0 {
size, _ := fileOp.GetDirSize(path1)
treeData = append(treeData, dto.CleanTree{ID: uuid.NewString(), Label: "old_original", Size: uint64(size), Children: list1, Type: "old_original"})
}
path2 := path.Join(global.Dir.BaseDir, oldAppBackupPath)
list2 := loadTreeWithAllFile(true, path2, "old_apps_bak", path2, fileOp)
if len(list2) != 0 {
size, _ := fileOp.GetDirSize(path2)
treeData = append(treeData, dto.CleanTree{ID: uuid.NewString(), Label: "old_apps_bak", Size: uint64(size), Children: list2, Type: "old_apps_bak"})
}
path3 := path.Join(global.Dir.BaseDir, oldDownloadPath)
list3 := loadTreeWithAllFile(true, path3, "old_download", path3, fileOp)
if len(list3) != 0 {
size, _ := fileOp.GetDirSize(path3)
treeData = append(treeData, dto.CleanTree{ID: uuid.NewString(), Label: "old_download", Size: uint64(size), Children: list3, Type: "old_download"})
}
path4 := path.Join(global.Dir.BaseDir, oldUpgradePath)
list4 := loadTreeWithDir(true, "old_upgrade", path4, fileOp)
itemSize := uint64(0)
for _, item := range list4 {
itemSize += item.Size
}
if len(list4) != 0 {
treeData = append(treeData, dto.CleanTree{ID: uuid.NewString(), Label: "old_upgrade", Size: itemSize, Children: list4, Type: "old_upgrade"})
}
return treeData
}
func loadUploadTree(fileOp fileUtils.FileOp) []dto.CleanTree {
var treeData []dto.CleanTree
path0 := path.Join(global.Dir.BaseDir, tmpUploadPath)
list0 := loadTreeWithAllFile(true, path0, "upload_tmp", path0, fileOp)
size0, _ := fileOp.GetDirSize(path0)
treeData = append(treeData, dto.CleanTree{ID: uuid.NewString(), Label: "upload_tmp", Size: uint64(size0), Children: list0, Type: "upload_tmp", IsRecommend: true})
path1 := path.Join(global.Dir.BaseDir, uploadPath, "app")
list1 := loadTreeWithAllFile(true, path1, "upload_app", path1, fileOp)
size1, _ := fileOp.GetDirSize(path1)
treeData = append(treeData, dto.CleanTree{ID: uuid.NewString(), Label: "upload_app", Size: uint64(size1), Children: list1, Type: "upload_app", IsRecommend: true})
path2 := path.Join(global.Dir.BaseDir, uploadPath, "website")
list2 := loadTreeWithAllFile(true, path2, "upload_website", path2, fileOp)
size2, _ := fileOp.GetDirSize(path2)
treeData = append(treeData, dto.CleanTree{ID: uuid.NewString(), Label: "upload_website", Size: uint64(size2), Children: list2, Type: "upload_website", IsRecommend: true})
path3 := path.Join(global.Dir.BaseDir, uploadPath, "database")
list3 := loadTreeWithAllFile(true, path3, "upload_database", path3, fileOp)
size3, _ := fileOp.GetDirSize(path3)
treeData = append(treeData, dto.CleanTree{ID: uuid.NewString(), Label: "upload_database", Size: uint64(size3), Children: list3, Type: "upload_database", IsRecommend: true})
path4 := path.Join(global.Dir.BaseDir, uploadPath, "directory")
list4 := loadTreeWithAllFile(true, path4, "upload_directory", path4, fileOp)
size4, _ := fileOp.GetDirSize(path4)
treeData = append(treeData, dto.CleanTree{ID: uuid.NewString(), Label: "upload_directory", Size: uint64(size4), Children: list4, Type: "upload_directory", IsRecommend: true})
treeData = loadTreeWithCheck(treeData, path.Join(global.Dir.BaseDir, tmpUploadPath), "upload_tmp", fileOp)
treeData = loadTreeWithCheck(treeData, path.Join(global.Dir.BaseDir, uploadPath, "app"), "upload_app", fileOp)
treeData = loadTreeWithCheck(treeData, path.Join(global.Dir.BaseDir, uploadPath, "website"), "upload_website", fileOp)
treeData = loadTreeWithCheck(treeData, path.Join(global.Dir.BaseDir, uploadPath, "database"), "upload_database", fileOp)
path5 := path.Join(global.Dir.BaseDir, uploadPath)
uploadTreeData := loadTreeWithAllFile(true, path5, "upload", path5, fileOp)
@@ -477,25 +363,9 @@ func loadUploadTree(fileOp fileUtils.FileOp) []dto.CleanTree {
func loadDownloadTree(fileOp fileUtils.FileOp) []dto.CleanTree {
var treeData []dto.CleanTree
path1 := path.Join(global.Dir.BaseDir, downloadPath, "app")
list1 := loadTreeWithAllFile(true, path1, "download_app", path1, fileOp)
size1, _ := fileOp.GetDirSize(path1)
treeData = append(treeData, dto.CleanTree{ID: uuid.NewString(), Label: "download_app", Size: uint64(size1), Children: list1, Type: "download_app", IsRecommend: true})
path2 := path.Join(global.Dir.BaseDir, downloadPath, "website")
list2 := loadTreeWithAllFile(true, path2, "download_website", path2, fileOp)
size2, _ := fileOp.GetDirSize(path2)
treeData = append(treeData, dto.CleanTree{ID: uuid.NewString(), Label: "download_website", Size: uint64(size2), Children: list2, Type: "download_website", IsRecommend: true})
path3 := path.Join(global.Dir.BaseDir, downloadPath, "database")
list3 := loadTreeWithAllFile(true, path3, "download_database", path3, fileOp)
size3, _ := fileOp.GetDirSize(path3)
treeData = append(treeData, dto.CleanTree{ID: uuid.NewString(), Label: "download_database", Size: uint64(size3), Children: list3, Type: "download_database", IsRecommend: true})
path4 := path.Join(global.Dir.BaseDir, downloadPath, "directory")
list4 := loadTreeWithAllFile(true, path4, "download_directory", path4, fileOp)
size4, _ := fileOp.GetDirSize(path4)
treeData = append(treeData, dto.CleanTree{ID: uuid.NewString(), Label: "download_directory", Size: uint64(size4), Children: list4, Type: "download_directory", IsRecommend: true})
treeData = loadTreeWithCheck(treeData, path.Join(global.Dir.BaseDir, downloadPath, "app"), "download_app", fileOp)
treeData = loadTreeWithCheck(treeData, path.Join(global.Dir.BaseDir, downloadPath, "website"), "download_website", fileOp)
treeData = loadTreeWithCheck(treeData, path.Join(global.Dir.BaseDir, downloadPath, "database"), "download_database", fileOp)
path5 := path.Join(global.Dir.BaseDir, downloadPath)
uploadTreeData := loadTreeWithAllFile(true, path5, "download", path5, fileOp)
@@ -558,7 +428,7 @@ func loadContainerTree() []dto.CleanTree {
volumeSize += uint64(file.UsageData.Size)
}
}
treeData = append(treeData, dto.CleanTree{ID: uuid.NewString(), Label: "container_volumes", Size: volumeSize, Children: nil, Type: "volumes", IsRecommend: true})
treeData = append(treeData, dto.CleanTree{ID: uuid.NewString(), Label: "container_volumes", Size: volumeSize, IsCheck: volumeSize > 0, Children: nil, Type: "volumes", IsRecommend: true})
var buildCacheTotalSize int64
for _, cache := range diskUsage.BuildCache {
@@ -566,7 +436,17 @@ func loadContainerTree() []dto.CleanTree {
buildCacheTotalSize += cache.Size
}
}
treeData = append(treeData, dto.CleanTree{ID: uuid.NewString(), Label: "build_cache", Size: uint64(buildCacheTotalSize), Type: "build_cache", IsRecommend: true})
treeData = append(treeData, dto.CleanTree{ID: uuid.NewString(), Label: "build_cache", Size: uint64(buildCacheTotalSize), IsCheck: buildCacheTotalSize > 0, Type: "build_cache", IsRecommend: true})
return treeData
}
func loadTreeWithCheck(treeData []dto.CleanTree, pathItem, treeType string, fileOp fileUtils.FileOp) []dto.CleanTree {
size, _ := fileOp.GetDirSize(pathItem)
if size == 0 {
return treeData
}
list := loadTreeWithAllFile(true, pathItem, treeType, pathItem, fileOp)
treeData = append(treeData, dto.CleanTree{ID: uuid.NewString(), Label: treeType, Size: uint64(size), IsCheck: size > 0, Children: list, Type: treeType, IsRecommend: true})
return treeData
}
@@ -577,9 +457,6 @@ func loadTreeWithDir(isCheck bool, treeType, pathItem string, fileOp fileUtils.F
return lists
}
for _, file := range files {
if treeType == "old_upgrade" {
continue
}
if file.Name() == "ssl" {
continue
}
@@ -659,67 +536,79 @@ func dropFileOrDir(itemPath string) {
}
}
func dropBuildCache() {
func dropBuildCache() (int, int) {
client, err := docker.NewDockerClient()
if err != nil {
global.LOG.Errorf("do not get docker client")
return 0, 0
}
opts := build.CachePruneOptions{}
opts.All = true
_, err = client.BuildCachePrune(context.Background(), opts)
res, err := client.BuildCachePrune(context.Background(), opts)
if err != nil {
global.LOG.Errorf("drop build cache failed, err %v", err)
return 0, 0
}
return len(res.CachesDeleted), int(res.SpaceReclaimed)
}
func dropImages() {
func dropImages() (int, int) {
client, err := docker.NewDockerClient()
if err != nil {
global.LOG.Errorf("do not get docker client")
return 0, 0
}
pruneFilters := filters.NewArgs()
pruneFilters.Add("dangling", "false")
_, err = client.ImagesPrune(context.Background(), pruneFilters)
res, err := client.ImagesPrune(context.Background(), pruneFilters)
if err != nil {
global.LOG.Errorf("drop images failed, err %v", err)
return 0, 0
}
return len(res.ImagesDeleted), int(res.SpaceReclaimed)
}
func dropContainers() {
func dropContainers() (int, int) {
client, err := docker.NewDockerClient()
if err != nil {
global.LOG.Errorf("do not get docker client")
return 0, 0
}
pruneFilters := filters.NewArgs()
_, err = client.ContainersPrune(context.Background(), pruneFilters)
res, err := client.ContainersPrune(context.Background(), pruneFilters)
if err != nil {
global.LOG.Errorf("drop containers failed, err %v", err)
return 0, 0
}
return len(res.ContainersDeleted), int(res.SpaceReclaimed)
}
func dropVolumes() {
func dropVolumes() (int, int) {
client, err := docker.NewDockerClient()
if err != nil {
global.LOG.Errorf("do not get docker client")
return 0, 0
}
pruneFilters := filters.NewArgs()
versions, err := client.ServerVersion(context.Background())
if err != nil {
global.LOG.Errorf("do not get docker api versions")
return 0, 0
}
if common.ComparePanelVersion(versions.APIVersion, "1.42") {
pruneFilters.Add("all", "true")
}
_, err = client.VolumesPrune(context.Background(), pruneFilters)
res, err := client.VolumesPrune(context.Background(), pruneFilters)
if err != nil {
global.LOG.Errorf("drop volumes failed, err %v", err)
return 0, 0
}
return len(res.VolumesDeleted), int(res.SpaceReclaimed)
}
func dropWithExclude(pathToDelete string, excludeSubDirs []string, taskItem *task.Task, size *int64, count *int) {
entries, err := os.ReadDir(pathToDelete)
if err != nil {
taskItem.LogFailed(fmt.Sprintf("read dir %s failed: %v", pathToDelete, err))
return
}
@@ -747,11 +636,11 @@ func dropWithTask(itemPath string, taskItem *task.Task, size *int64, count *int)
*size += itemSize
*count += itemCount
if err := os.RemoveAll(itemPath); err != nil {
taskItem.LogFailed(fmt.Sprintf("drop file %s, err %v", itemPath, err))
taskItem.Log(i18n.GetWithNameAndErr("FileDropFailed", itemPath, err))
return
}
if itemCount != 0 {
taskItem.LogSuccessF("drop file %s, size: %s, count: %d", itemPath, common.LoadSizeUnit2F(float64(itemSize)), itemCount)
taskItem.Log(i18n.GetMsgWithMap("FileDropSuccess", map[string]interface{}{"name": itemPath, "count": itemCount, "size": common.LoadSizeUnit2F(float64(itemSize))}))
}
}
+146
View File
@@ -0,0 +1,146 @@
package service
import (
"fmt"
"github.com/1Panel-dev/1Panel/agent/app/dto"
"github.com/1Panel-dev/1Panel/agent/buserr"
"github.com/1Panel-dev/1Panel/agent/global"
"github.com/1Panel-dev/1Panel/agent/utils/cmd"
"os"
"strings"
"time"
"github.com/1Panel-dev/1Panel/agent/app/dto/request"
"github.com/1Panel-dev/1Panel/agent/app/dto/response"
)
type DiskService struct{}
type IDiskService interface {
GetCompleteDiskInfo() (*response.CompleteDiskInfo, error)
PartitionDisk(req request.DiskPartitionRequest) (string, error)
MountDisk(req request.DiskMountRequest) error
UnmountDisk(req request.DiskUnmountRequest) error
}
func NewIDiskService() IDiskService {
return &DiskService{}
}
func (s *DiskService) GetCompleteDiskInfo() (*response.CompleteDiskInfo, error) {
output, err := cmd.RunDefaultWithStdoutBashC("lsblk -P -o NAME,SIZE,TYPE,MOUNTPOINT,FSTYPE,MODEL,SERIAL,TRAN,ROTA")
if err != nil {
return nil, fmt.Errorf("failed to execute lsblk command: %v", err)
}
diskInfos, err := parseLsblkOutput(output)
if err != nil {
return nil, fmt.Errorf("failed to parse lsblk output: %v", err)
}
result := organizeDiskInfo(diskInfos)
return &result, nil
}
func (s *DiskService) PartitionDisk(req request.DiskPartitionRequest) (string, error) {
if !strings.HasPrefix("/dev", req.Device) {
req.Device = "/dev/" + req.Device
}
if !deviceExists(req.Device) {
return "", buserr.WithName("DeviceNotFound", req.Device)
}
if isDeviceMounted(req.Device) {
return "", buserr.WithName("DeviceIsMounted", req.Device)
}
cmdMgr := cmd.NewCommandMgr(cmd.WithTimeout(10 * time.Second))
if err := cmdMgr.RunBashC(fmt.Sprintf("partprobe %s", req.Device)); err != nil {
return "", buserr.WithErr("PartitionDiskErr", err)
}
if err := cmdMgr.RunBashC(fmt.Sprintf("parted -s %s mklabel gpt", req.Device)); err != nil {
return "", buserr.WithErr("PartitionDiskErr", err)
}
if err := cmdMgr.RunBashC(fmt.Sprintf("parted -s %s mkpart primary 1MiB 100%%", req.Device)); err != nil {
return "", buserr.WithErr("PartitionDiskErr", err)
}
if err := cmdMgr.RunBashC(fmt.Sprintf("partprobe %s", req.Device)); err != nil {
return "", buserr.WithErr("PartitionDiskErr", err)
}
partition := req.Device + "1"
formatReq := dto.DiskFormatRequest{
Device: partition,
Filesystem: req.Filesystem,
Label: req.Label,
}
if err := formatDisk(formatReq); err != nil {
return "", buserr.WithErr("FormatDiskErr", err)
}
if req.MountPoint != "" {
mountReq := request.DiskMountRequest{
Device: partition,
MountPoint: req.MountPoint,
Filesystem: req.Filesystem,
AutoMount: req.AutoMount,
}
if err := s.MountDisk(mountReq); err != nil {
return "", buserr.WithErr("MountDiskErr", err)
}
}
return partition, nil
}
func (s *DiskService) MountDisk(req request.DiskMountRequest) error {
if !deviceExists(req.Device) {
return buserr.WithName("DeviceNotFound", req.Device)
}
if err := os.MkdirAll(req.MountPoint, 0755); err != nil {
return err
}
fileSystem, err := getFilesystemType(req.Device)
if err != nil {
return err
}
if fileSystem == "" {
formatReq := dto.DiskFormatRequest{
Device: req.Device,
Filesystem: req.Filesystem,
}
if err := formatDisk(formatReq); err != nil {
return buserr.WithErr("FormatDiskErr", err)
}
}
cmdMgr := cmd.NewCommandMgr(cmd.WithTimeout(1 * time.Minute))
if err := cmdMgr.RunBashC(fmt.Sprintf("mount -t %s %s %s", req.Filesystem, req.Device, req.MountPoint)); err != nil {
return buserr.WithErr("MountDiskErr", err)
}
if req.AutoMount {
if err := addToFstabWithOptions(req.Device, req.MountPoint, req.Filesystem, ""); err != nil {
return buserr.WithErr("MountDiskErr", err)
}
}
return nil
}
func (s *DiskService) UnmountDisk(req request.DiskUnmountRequest) error {
if !isPointMounted(req.MountPoint) {
return buserr.New("MountDiskErr")
}
if err := cmd.RunDefaultBashC(fmt.Sprintf("umount -f %s", req.MountPoint)); err != nil {
return buserr.WithErr("MountDiskErr", err)
}
if err := removeFromFstab(req.MountPoint); err != nil {
global.LOG.Errorf("remove %s mountPoint err: %v", req.MountPoint, err)
}
return nil
}
+440
View File
@@ -0,0 +1,440 @@
package service
import (
"bufio"
"fmt"
"github.com/1Panel-dev/1Panel/agent/app/dto"
"github.com/1Panel-dev/1Panel/agent/app/dto/response"
"github.com/1Panel-dev/1Panel/agent/buserr"
"github.com/1Panel-dev/1Panel/agent/utils/cmd"
"os"
"os/exec"
"regexp"
"strconv"
"strings"
)
func organizeDiskInfo(diskInfos []response.DiskBasicInfo) response.CompleteDiskInfo {
var result response.CompleteDiskInfo
var systemDisk *response.DiskInfo
diskMap := make(map[string]*response.DiskInfo)
partitions := make(map[string][]response.DiskBasicInfo)
for _, info := range diskInfos {
isPartition := isPartitionDevice(info.Device)
if isPartition {
parentDevice := getParentDevice(info.Device)
partitions[parentDevice] = append(partitions[parentDevice], info)
} else {
disk := &response.DiskInfo{
DiskBasicInfo: info,
Partitions: []response.DiskBasicInfo{},
}
diskMap[info.Device] = disk
}
}
for parentDevice, partList := range partitions {
if disk, exists := diskMap[parentDevice]; exists {
for index, part := range partList {
part.Device = fmt.Sprintf("/dev/%s", part.Device)
if part.IsSystem {
disk.IsSystem = true
}
partList[index] = part
}
disk.Partitions = partList
}
}
var totalCapacity int64
for _, disk := range diskMap {
capacity := parseSizeToBytes(disk.Size)
totalCapacity += capacity
if disk.IsSystem {
systemDisk = disk
} else if len(disk.Partitions) == 0 {
result.UnpartitionedDisks = append(result.UnpartitionedDisks, disk.DiskBasicInfo)
} else {
result.Disks = append(result.Disks, *disk)
}
}
result.SystemDisk = systemDisk
result.TotalDisks = len(diskMap)
result.TotalCapacity = totalCapacity
return result
}
func parseLsblkOutput(output string) ([]response.DiskBasicInfo, error) {
lines := strings.Split(strings.TrimSpace(output), "\n")
if len(lines) == 0 {
return nil, fmt.Errorf("invalid lsblk output")
}
var diskInfos []response.DiskBasicInfo
lvmMap := make(map[string]response.DiskBasicInfo)
var pendingDevices []map[string]string
for _, line := range lines {
line = strings.TrimSpace(line)
if line == "" {
continue
}
fields := parseKeyValuePairs(line)
name, ok := fields["NAME"]
if !ok {
continue
}
if strings.HasPrefix(name, "loop") ||
strings.HasPrefix(name, "dm-") {
continue
}
diskType := fields["TYPE"]
mountPoint := fields["MOUNTPOINT"]
fsType := fields["FSTYPE"]
size := fields["SIZE"]
if diskType == "lvm" {
total, used, avail, usePercent, _ := getDiskUsageInfo("/dev/mapper/" + name)
if total != "" && fsType != "" {
size = total
}
lvmInfo := response.DiskBasicInfo{
Device: name,
Size: size,
Model: fields["MODEL"],
DiskType: "LVM",
IsRemovable: false,
IsSystem: isSystemDisk(mountPoint),
Filesystem: fsType,
Used: used,
Avail: avail,
UsePercent: usePercent,
MountPoint: mountPoint,
IsMounted: mountPoint != "" && mountPoint != "-",
Serial: fields["SERIAL"],
}
lvmMap[name] = lvmInfo
} else if diskType == "disk" || diskType == "part" {
pendingDevices = append(pendingDevices, fields)
}
}
for _, fields := range pendingDevices {
name := fields["NAME"]
size := fields["SIZE"]
mountPoint := fields["MOUNTPOINT"]
fsType := fields["FSTYPE"]
model := fields["MODEL"]
serial := fields["SERIAL"]
tran := fields["TRAN"]
rota := fields["ROTA"]
var (
used, avail, totalSize string
usePercent int
)
if mountPoint != "" {
totalSize, used, avail, usePercent, _ = getDiskUsageInfo("/dev/" + name)
if totalSize != "" {
size = totalSize
}
}
actualMountPoint := mountPoint
actualFsType := fsType
actualUsed := used
actualAvail := avail
actualUsePercent := usePercent
isMounted := mountPoint != "" && mountPoint != "-"
isSystemPartition := isSystemDisk(mountPoint)
if fsType == "LVM2_member" {
for _, lvmInfo := range lvmMap {
if lvmInfo.IsMounted {
actualMountPoint = lvmInfo.MountPoint
actualFsType = lvmInfo.Filesystem
actualUsed = lvmInfo.Used
size = lvmInfo.Size
actualAvail = lvmInfo.Avail
actualUsePercent = lvmInfo.UsePercent
isMounted = true
isSystemPartition = lvmInfo.IsSystem
break
}
}
}
info := response.DiskBasicInfo{
Device: name,
Size: size,
Model: model,
DiskType: getDiskType(rota),
IsRemovable: tran == "usb",
IsSystem: isSystemPartition,
Filesystem: actualFsType,
Used: actualUsed,
Avail: actualAvail,
UsePercent: actualUsePercent,
MountPoint: actualMountPoint,
IsMounted: isMounted,
Serial: serial,
}
diskInfos = append(diskInfos, info)
}
return diskInfos, nil
}
func getDiskType(rota string) string {
if rota == "0" {
return "SSD"
} else if rota == "1" {
return "HDD"
}
return "Unknown"
}
var kvRe = regexp.MustCompile(`([A-Za-z0-9_]+)=("([^"\\]|\\.)*"|[^ \t]+)`)
func parseKeyValuePairs(line string) map[string]string {
fields := make(map[string]string)
matches := kvRe.FindAllStringSubmatch(line, -1)
for _, m := range matches {
key := m[1]
raw := m[2]
val := raw
if len(val) >= 2 && val[0] == '"' && val[len(val)-1] == '"' {
if unq, err := strconv.Unquote(val); err == nil {
val = unq
} else {
val = val[1 : len(val)-1]
}
}
fields[key] = val
}
return fields
}
func isPartitionDevice(device string) bool {
if strings.Contains(device, "nvme") {
return strings.Contains(device, "p") &&
strings.ContainsAny(device[strings.LastIndex(device, "p")+1:], "0123456789")
} else if strings.HasPrefix(device, "sd") || strings.HasPrefix(device, "hd") {
return len(device) > 3 &&
strings.ContainsAny(device[len(device)-1:], "0123456789")
} else if strings.HasPrefix(device, "vd") {
return len(device) > 3 &&
strings.ContainsAny(device[len(device)-1:], "0123456789")
}
return false
}
func getParentDevice(device string) string {
if strings.Contains(device, "nvme") {
if idx := strings.LastIndex(device, "p"); idx != -1 {
return device[:idx]
}
} else {
return strings.TrimRight(device, "0123456789")
}
return device
}
func getDiskUsageInfo(device string) (size, used, avail string, usePercent int, err error) {
output, err := cmd.RunDefaultWithStdoutBashC(fmt.Sprintf("df -h %s | tail -1", device))
if err != nil {
return "", "", "", 0, nil
}
fields := strings.Fields(output)
if len(fields) >= 5 {
size = fields[1]
used = fields[2]
avail = fields[3]
usePercentStr := strings.TrimSuffix(fields[4], "%")
usePercent, _ = strconv.Atoi(usePercentStr)
}
return size, used, avail, usePercent, nil
}
func formatDisk(req dto.DiskFormatRequest) error {
var mkfsCmd *exec.Cmd
switch req.Filesystem {
case "ext4":
mkfsCmd = exec.Command("mkfs.ext4", "-F", req.Device)
case "xfs":
if !cmd.Which("mkfs.xfs") {
return buserr.New("XfsNotFound")
}
mkfsCmd = exec.Command("mkfs.xfs", "-f", req.Device)
default:
return fmt.Errorf("unsupport type: %s", req.Filesystem)
}
if err := mkfsCmd.Run(); err != nil {
return err
}
return nil
}
func deviceExists(device string) bool {
_, err := os.Stat(device)
return err == nil
}
func isDeviceMounted(device string) bool {
file, err := os.Open("/proc/mounts")
if err != nil {
return false
}
defer file.Close()
scanner := bufio.NewScanner(file)
for scanner.Scan() {
fields := strings.Fields(scanner.Text())
if len(fields) >= 2 && fields[0] == device {
return true
}
}
return false
}
func isPointMounted(mountPoint string) bool {
file, err := os.Open("/proc/mounts")
if err != nil {
return false
}
defer file.Close()
scanner := bufio.NewScanner(file)
for scanner.Scan() {
fields := strings.Fields(scanner.Text())
if len(fields) >= 2 && fields[1] == mountPoint {
return true
}
}
return false
}
func addToFstabWithOptions(device, mountPoint, filesystem, options string) error {
if filesystem == "" {
fsType, err := getFilesystemType(device)
if err != nil {
filesystem = "auto"
} else {
filesystem = fsType
}
}
if options == "" {
options = "defaults"
}
entry := fmt.Sprintf("%s %s %s %s 0 2\n", device, mountPoint, filesystem, options)
file, err := os.OpenFile("/etc/fstab", os.O_APPEND|os.O_WRONLY, 0644)
if err != nil {
return err
}
defer file.Close()
_, err = file.WriteString(entry)
return err
}
func removeFromFstab(mountPoint string) error {
file, err := os.Open("/etc/fstab")
if err != nil {
return err
}
defer file.Close()
var lines []string
scanner := bufio.NewScanner(file)
for scanner.Scan() {
line := scanner.Text()
fields := strings.Fields(line)
if len(fields) >= 2 && fields[1] == mountPoint {
continue
}
lines = append(lines, line)
}
return os.WriteFile("/etc/fstab", []byte(strings.Join(lines, "\n")+"\n"), 0644)
}
func getFilesystemType(device string) (string, error) {
output, err := cmd.RunDefaultWithStdoutBashC(fmt.Sprintf("blkid -o value -s TYPE %s", device))
if err != nil {
return "", err
}
return strings.TrimSpace(output), nil
}
func isSystemDisk(mountPoint string) bool {
systemMountPoints := []string{
"/",
"/boot",
"/boot/efi",
"/usr",
"/var",
"/home",
}
for _, sysMount := range systemMountPoints {
if mountPoint == sysMount {
return true
}
}
return false
}
func parseSizeToBytes(sizeStr string) int64 {
if sizeStr == "" || sizeStr == "-" {
return 0
}
sizeStr = strings.TrimSpace(sizeStr)
if len(sizeStr) < 2 {
return 0
}
unit := strings.ToUpper(sizeStr[len(sizeStr)-1:])
valueStr := sizeStr[:len(sizeStr)-1]
value, err := strconv.ParseFloat(valueStr, 64)
if err != nil {
return 0
}
switch unit {
case "K":
return int64(value * 1024)
case "M":
return int64(value * 1024 * 1024)
case "G":
return int64(value * 1024 * 1024 * 1024)
case "T":
return int64(value * 1024 * 1024 * 1024 * 1024)
default:
val, _ := strconv.ParseInt(sizeStr, 10, 64)
return val
}
}
+1 -1
View File
@@ -60,7 +60,7 @@ func (u *DockerService) LoadDockerStatus() *dto.DockerStatus {
return &data
}
data.IsExist = true
data.IsActive, _ = systemctl.IsActive("docker")
data.IsActive = true
client, err := docker.NewDockerClient()
if err != nil {
global.LOG.Errorf("load docker client failed, err: %v", err)
+8 -11
View File
@@ -568,37 +568,34 @@ func (f *FileService) ReadLogByLine(req request.FileReadByLineReq) (*response.Fi
var (
lines []string
isEndOfFile bool
total int
scope string
logFileRes *dto.LogFileRes
)
if stat.Size() > 500*1024*1024 {
lines, err = files.TailFromEnd(logFilePath, req.PageSize)
isEndOfFile = true
scope = "tail"
} else {
lines, isEndOfFile, total, err = files.ReadFileByLine(logFilePath, req.Page, req.PageSize, req.Latest)
logFileRes, err = files.ReadFileByLine(logFilePath, req.Page, req.PageSize, req.Latest)
if err != nil {
return nil, err
}
if req.Latest && req.Page == 1 && len(lines) < 1000 && total > 1 {
preLines, _, _, err := files.ReadFileByLine(logFilePath, total-1, req.PageSize, false)
if err != nil {
return nil, err
}
lines = append(preLines, lines...)
}
scope = "page"
lines = logFileRes.Lines
}
res := &response.FileLineContent{
End: isEndOfFile,
Path: logFilePath,
Total: total,
TaskStatus: taskStatus,
Lines: lines,
Scope: scope,
}
if logFileRes != nil {
res.TotalLines = logFileRes.TotalLines
res.Total = logFileRes.TotalPages
res.End = logFileRes.IsEndOfFile
}
return res, nil
}
+5 -5
View File
@@ -28,7 +28,7 @@ type FirewallService struct{}
type IFirewallService interface {
LoadBaseInfo() (dto.FirewallBaseInfo, error)
SearchWithPage(search dto.RuleSearch) (int64, interface{}, error)
OperateFirewall(operation string) error
OperateFirewall(req dto.FirewallOperation) error
OperatePortRule(req dto.PortRuleOperate, reload bool) error
OperateForwardRule(req dto.ForwardRuleOperate) error
OperateAddressRule(req dto.AddrRuleOperate, reload bool) error
@@ -177,13 +177,13 @@ func (u *FirewallService) SearchWithPage(req dto.RuleSearch) (int64, interface{}
return int64(total), backDatas, nil
}
func (u *FirewallService) OperateFirewall(operation string) error {
func (u *FirewallService) OperateFirewall(req dto.FirewallOperation) error {
client, err := firewall.NewFirewallClient()
if err != nil {
return err
}
needRestartDocker := false
switch operation {
switch req.Operation {
case "start":
if err := client.Start(); err != nil {
return err
@@ -208,9 +208,9 @@ func (u *FirewallService) OperateFirewall(operation string) error {
case "enablePing":
return u.updatePingStatus("1")
default:
return fmt.Errorf("not supported operation: %s", operation)
return fmt.Errorf("not supported operation: %s", req.Operation)
}
if needRestartDocker {
if needRestartDocker && req.WithDockerRestart {
if err := restartDocker(); err != nil {
return err
}
+44 -24
View File
@@ -10,6 +10,7 @@ import (
"io"
"os"
"path"
"sort"
"strings"
"time"
@@ -34,7 +35,7 @@ import (
type ImageService struct{}
type IImageService interface {
Page(req dto.SearchWithPage) (int64, interface{}, error)
Page(req dto.PageImage) (int64, interface{}, error)
List() ([]dto.Options, error)
ListAll() ([]dto.ImageInfo, error)
ImageBuild(req dto.ImageBuild) error
@@ -49,7 +50,7 @@ type IImageService interface {
func NewIImageService() IImageService {
return &ImageService{}
}
func (u *ImageService) Page(req dto.SearchWithPage) (int64, interface{}, error) {
func (u *ImageService) Page(req dto.PageImage) (int64, interface{}, error) {
var (
list []image.Summary
records []dto.ImageInfo
@@ -65,12 +66,12 @@ func (u *ImageService) Page(req dto.SearchWithPage) (int64, interface{}, error)
return 0, nil, err
}
containers, _ := client.ContainerList(context.Background(), container.ListOptions{All: true})
if len(req.Info) != 0 {
if len(req.Name) != 0 {
length, count := len(list), 0
for count < length {
hasTag := false
for _, tag := range list[count].RepoTags {
if strings.Contains(tag, req.Info) {
if strings.Contains(tag, req.Name) {
hasTag = true
break
}
@@ -85,15 +86,48 @@ func (u *ImageService) Page(req dto.SearchWithPage) (int64, interface{}, error)
}
for _, image := range list {
size := formatFileSize(image.Size)
records = append(records, dto.ImageInfo{
ID: image.ID,
Tags: image.RepoTags,
IsUsed: checkUsed(image.ID, containers),
CreatedAt: time.Unix(image.Created, 0),
Size: size,
Size: image.Size,
})
}
switch req.OrderBy {
case "size":
sort.Slice(records, func(i, j int) bool {
if req.Order == constant.OrderAsc {
return records[i].Size < records[j].Size
}
return records[i].Size > records[j].Size
})
case "isUsed":
sort.Slice(records, func(i, j int) bool {
if req.Order == constant.OrderAsc {
return !records[i].IsUsed
}
return records[i].IsUsed
})
case "tags":
sort.Slice(records, func(i, j int) bool {
if len(records[i].Tags) == 0 || len(records[j].Tags) == 0 {
return true
}
if req.Order == constant.OrderAsc {
return records[i].Tags[0] < records[j].Tags[0]
}
return records[i].Tags[0] > records[j].Tags[0]
})
default:
sort.Slice(records, func(i, j int) bool {
if req.Order == constant.OrderAsc {
return records[i].CreatedAt.Before(records[j].CreatedAt)
}
return records[i].CreatedAt.After(records[j].CreatedAt)
})
}
total, start, end := len(records), (req.Page-1)*req.PageSize, req.Page*req.PageSize
if start > total {
backDatas = make([]dto.ImageInfo, 0)
@@ -120,13 +154,12 @@ func (u *ImageService) ListAll() ([]dto.ImageInfo, error) {
}
containers, _ := client.ContainerList(context.Background(), container.ListOptions{All: true})
for _, image := range list {
size := formatFileSize(image.Size)
records = append(records, dto.ImageInfo{
ID: image.ID,
Tags: image.RepoTags,
IsUsed: checkUsed(image.ID, containers),
CreatedAt: time.Unix(image.Created, 0),
Size: size,
Size: image.Size,
})
}
return records, nil
@@ -421,28 +454,15 @@ func (u *ImageService) ImageRemove(req dto.BatchDelete) error {
return nil
}, nil)
}
if len(req.TaskID) == 0 {
return taskItem.Execute()
}
go func() {
_ = taskItem.Execute()
}()
return nil
}
func formatFileSize(fileSize int64) (size string) {
if fileSize < 1024 {
return fmt.Sprintf("%.2fB", float64(fileSize)/float64(1))
} else if fileSize < (1024 * 1024) {
return fmt.Sprintf("%.2fKB", float64(fileSize)/float64(1024))
} else if fileSize < (1024 * 1024 * 1024) {
return fmt.Sprintf("%.2fMB", float64(fileSize)/float64(1024*1024))
} else if fileSize < (1024 * 1024 * 1024 * 1024) {
return fmt.Sprintf("%.2fGB", float64(fileSize)/float64(1024*1024*1024))
} else if fileSize < (1024 * 1024 * 1024 * 1024 * 1024) {
return fmt.Sprintf("%.2fTB", float64(fileSize)/float64(1024*1024*1024*1024))
} else {
return fmt.Sprintf("%.2fEB", float64(fileSize)/float64(1024*1024*1024*1024*1024))
}
}
func checkUsed(imageID string, containers []container.Summary) bool {
for _, container := range containers {
if container.ImageID == imageID {
+63 -29
View File
@@ -59,7 +59,11 @@ func (m McpServerService) Page(req request.McpServerSearch) response.McpServersR
Environments: make([]request.Environment, 0),
Volumes: make([]request.Volume, 0),
}
project, _ := docker.GetComposeProject(item.Name, path.Join(global.Dir.McpDir, item.Name), []byte(item.DockerCompose), []byte(item.Env), true)
project, err := docker.GetComposeProject(item.Name, path.Join(global.Dir.McpDir, item.Name), []byte(item.DockerCompose), []byte(item.Env), true)
if err != nil {
global.LOG.Errorf("get mcp compose project error: %s", err.Error())
continue
}
for _, service := range project.Services {
if service.Environment != nil {
for key, value := range service.Environment {
@@ -86,11 +90,7 @@ func (m McpServerService) Page(req request.McpServerSearch) response.McpServersR
}
func (m McpServerService) Update(req request.McpServerUpdate) error {
go func() {
if err := docker.PullImage("supercorp/supergateway:latest"); err != nil {
global.LOG.Errorf("docker pull mcp image error: %s", err.Error())
}
}()
go pullImage(req.Type)
mcpServer, err := mcpServerRepo.GetFirst(repo.WithByID(req.ID))
if err != nil {
return err
@@ -111,10 +111,14 @@ func (m McpServerService) Update(req request.McpServerUpdate) error {
mcpServer.Port = req.Port
mcpServer.Command = req.Command
mcpServer.BaseURL = req.BaseURL
mcpServer.SsePath = req.SsePath
mcpServer.HostIP = req.HostIP
mcpServer.StreamableHttpPath = req.StreamableHttpPath
mcpServer.OutputTransport = req.OutputTransport
mcpServer.Type = req.Type
if req.OutputTransport == "sse" {
mcpServer.SsePath = req.SsePath
} else {
mcpServer.StreamableHttpPath = req.StreamableHttpPath
}
if err := handleCreateParams(mcpServer, req.Environments, req.Volumes); err != nil {
return err
}
@@ -137,11 +141,7 @@ func (m McpServerService) Update(req request.McpServerUpdate) error {
}
func (m McpServerService) Create(create request.McpServerCreate) error {
go func() {
if err := docker.PullImage("supercorp/supergateway:latest"); err != nil {
global.LOG.Errorf("docker pull mcp image error: %s", err.Error())
}
}()
go pullImage(create.Type)
servers, _ := mcpServerRepo.List()
for _, server := range servers {
if server.Port == create.Port {
@@ -166,18 +166,23 @@ func (m McpServerService) Create(create request.McpServerCreate) error {
}
mcpDir := path.Join(global.Dir.McpDir, create.Name)
mcpServer := &model.McpServer{
Name: create.Name,
ContainerName: create.ContainerName,
Port: create.Port,
Command: create.Command,
Status: constant.StatusStarting,
BaseURL: create.BaseURL,
SsePath: create.SsePath,
Dir: mcpDir,
HostIP: create.HostIP,
StreamableHttpPath: create.StreamableHttpPath,
OutputTransport: create.OutputTransport,
Name: create.Name,
ContainerName: create.ContainerName,
Port: create.Port,
Command: create.Command,
Status: constant.StatusStarting,
BaseURL: create.BaseURL,
Dir: mcpDir,
HostIP: create.HostIP,
OutputTransport: create.OutputTransport,
Type: create.Type,
}
if create.OutputTransport == "sse" {
mcpServer.SsePath = create.SsePath
} else {
mcpServer.StreamableHttpPath = create.StreamableHttpPath
}
if err := handleCreateParams(mcpServer, create.Environments, create.Volumes); err != nil {
return err
}
@@ -447,8 +452,14 @@ func addProxy(server *model.McpServer) {
if !ok {
return
}
location.UpdateDirective("proxy_pass", []string{fmt.Sprintf("http://127.0.0.1:%d%s", server.Port, server.SsePath)})
location.ChangePath("^~", server.SsePath)
var proxyPath string
if server.OutputTransport == "sse" {
proxyPath = server.SsePath
} else {
proxyPath = server.StreamableHttpPath
}
location.UpdateDirective("proxy_pass", []string{fmt.Sprintf("http://127.0.0.1:%d%s", server.Port, proxyPath)})
location.ChangePath("^~", proxyPath)
if err = nginx.WriteConfig(config, nginx.IndentedStyle); err != nil {
global.LOG.Errorf("write config failed, err: %v", buserr.WithErr("ErrUpdateBuWebsite", err))
return
@@ -499,8 +510,14 @@ func addMCPProxy(websiteID uint) error {
err = errors.New("error")
return err
}
location.UpdateDirective("proxy_pass", []string{fmt.Sprintf("http://127.0.0.1:%d%s", server.Port, server.SsePath)})
location.ChangePath("^~", server.SsePath)
var proxyPath string
if server.OutputTransport == "sse" {
proxyPath = server.SsePath
} else {
proxyPath = server.StreamableHttpPath
}
location.UpdateDirective("proxy_pass", []string{fmt.Sprintf("http://127.0.0.1:%d%s", server.Port, proxyPath)})
location.ChangePath("^~", proxyPath)
if err = nginx.WriteConfig(config, nginx.IndentedStyle); err != nil {
return buserr.WithErr("ErrUpdateBuWebsite", err)
}
@@ -590,6 +607,11 @@ func handleCreateParams(mcpServer *model.McpServer, environments []request.Envir
}
serviceValue["volumes"] = volumeList
}
if mcpServer.Type == "npx" {
serviceValue["image"] = "supercorp/supergateway:latest"
} else {
serviceValue["image"] = "supercorp/supergateway:uvx"
}
services[mcpServer.Name] = serviceValue
composeByte, err := yaml.Marshal(composeMap)
@@ -641,7 +663,7 @@ func syncMcpServerContainerStatus(mcpServer *model.McpServer) error {
case "restarting":
mcpServer.Status = constant.StatusRestarting
default:
if mcpServer.Status != constant.StatusBuilding {
if mcpServer.Status != constant.StatusStarting {
mcpServer.Status = constant.StatusStopped
}
}
@@ -656,3 +678,15 @@ func GetWebsiteID() uint {
websiteIDUint, _ := strconv.ParseUint(websiteID.Value, 10, 64)
return uint(websiteIDUint)
}
func pullImage(imageType string) {
if imageType == "npx" {
if err := docker.PullImage("supercorp/supergateway:latest"); err != nil {
global.LOG.Errorf("docker pull mcp image error: %s", err.Error())
}
} else {
if err := docker.PullImage("supercorp/supergateway:uvx"); err != nil {
global.LOG.Errorf("docker pull mcp image error: %s", err.Error())
}
}
}
+5 -5
View File
@@ -16,11 +16,11 @@ import (
"github.com/1Panel-dev/1Panel/agent/global"
"github.com/1Panel-dev/1Panel/agent/utils/common"
"github.com/robfig/cron/v3"
"github.com/shirou/gopsutil/v3/cpu"
"github.com/shirou/gopsutil/v3/disk"
"github.com/shirou/gopsutil/v3/load"
"github.com/shirou/gopsutil/v3/mem"
"github.com/shirou/gopsutil/v3/net"
"github.com/shirou/gopsutil/v4/cpu"
"github.com/shirou/gopsutil/v4/disk"
"github.com/shirou/gopsutil/v4/load"
"github.com/shirou/gopsutil/v4/mem"
"github.com/shirou/gopsutil/v4/net"
)
type MonitorService struct {

Some files were not shown because too many files have changed in this diff Show More