Compare commits

..
181 Commits
Author SHA1 Message Date
CityFun 04b9cbd87a fix: Fixed issue with recover postgres-cluster database failed (#9732) 2025-07-29 16:19:39 +08:00
ssongliu 4565c5b300 fix: Fix fatabase backup cronjobimport failures (#9731) 2025-07-29 16:03:55 +08:00
ssongliu bf9a9bf17a fix: Fix execution refresh issues for cronjob (#9728) 2025-07-29 14:51:34 +08:00
ssongliu 70df4dc6c4 fix: Fix import failures for cronjob (#9727) 2025-07-29 14:32:25 +08:00
ssongliu ed3e738fff fix: Fix import issues of shell cronjob (#9726) 2025-07-29 04:09:49 +00:00
ssongliu bc376ec495 fix: script-type scheduled task import/export issues (#9713) 2025-07-28 19:04:35 +08:00
Snrat 40314e9079 Fix Issue with Podman Unable to Change PHP Runtime in Website Page (#9711)
* Update container.go

* Update website.go
2025-07-28 18:34:05 +08:00
ssongliu 82f978c683 fix: Add file upload restrictions for cronjob (#9710) 2025-07-28 18:33:15 +08:00
CityFun ed288368ea fix: Fixed issue with recover mysql-cluster with backup files (#9707) 2025-07-28 18:01:05 +08:00
sheng.wang db8fa144eb fix: Fix crash when adding custom spec if specs is null (#9708) 2025-07-28 18:00:35 +08:00
ssongliu 57b76d97ec fix: Optimize task log tracking mechanism (#9704) 2025-07-28 17:12:42 +08:00
蘭 28d8210ce6 fix: add nil checks for transport.DialContext (#9702) 2025-07-28 17:02:46 +08:00
ssongliu 80ce60c56c fix: Fix port forwarding error issues (#9700)
Refs #9658
2025-07-28 08:29:40 +00:00
Snrat 2758ab07dc Fix Issue with Podman Unable to Create PHP Runtime Website (#9697)
* Update website.go

* Update container.go
2025-07-28 15:38:27 +08:00
CityFun 0828804de2 fix: Fix the issue where the host IP is shown as 127.0.0.1 after enabling 'allowPort' in the application parameter settings. (#9699) 2025-07-28 15:36:25 +08:00
ssongliu 134b17d2f0 fix: Resolve certificate validate failure Issues (#9698) 2025-07-28 15:36:10 +08:00
ssongliu 3af254cdda fix: Add lightweight object storage compatibility for COS backup acco… (#9673)
Refs #9494
2025-07-25 11:07:36 +00:00
蘭 f3d3d54722 feat: Customize login page buttons and link colors (#9671)
refs  #9061
2025-07-25 11:05:37 +00:00
CityFun fa419809f6 fix: Fixed issue with create website with openresty defaultHttpsPort (#9672) 2025-07-25 19:04:19 +08:00
CityFun da606826c6 fix: Fixed Changing the runtime container name may cause it to fail to start (#9670)
Refs https://github.com/1Panel-dev/1Panel/issues/9666
2025-07-25 11:03:35 +00:00
ssongliu b3d9cc156a fix: Optimize filename generation for directory backup (#9669)
Refs #9433
2025-07-25 09:21:35 +00:00
ssongliu ba03d26b2c fix: Optimize cronjob report loading (#9665)
Refs #9412
2025-07-25 08:35:36 +00:00
ssongliu 40f02889fe feat: Add import/export support for cronjob (#9663)
Refs #5157
2025-07-25 07:15:35 +00:00
CityFun 4f8fb26d89 feat: Adapt default website domain to non-standard OpenResty port during creation (#9664) 2025-07-25 15:15:33 +08:00
ssongliu 0ca11e2b88 fix: Fix node switching style issues (#9641) 2025-07-24 11:30:08 +08:00
ssongliu 0daf0d690c fix: Resolve deleted node status display issue (#9633) 2025-07-24 10:23:45 +08:00
蘭 0cfdf0f335 feat: alert count limit (#9621) 2025-07-23 21:34:20 +08:00
蘭 cca1863b63 feat: Adjust alarm settings (#9616) 2025-07-23 08:58:24 +00:00
CityFun 033f08a6d2 fix: Fix the issue where an error occurs on the runtime environment page when Docker is not installed. (#9615) 2025-07-23 15:39:48 +08:00
CityFun a83a6690ff fix: Fix the issue where application labels are displayed incorrectly… (#9611) 2025-07-23 04:04:23 +00:00
ssongliu 83df457379 fix: Add backup master missing alert (#9610) 2025-07-23 04:02:23 +00:00
蘭 657e55cde3 fix: Fix offline alert issue (#9608) 2025-07-22 18:39:26 +08:00
ssongliu 90e76e3a0f fix: Fix Redis performance tuning abnormalities (#9607) 2025-07-22 10:00:07 +00:00
CityFun 2af1993383 fix: Fix the issue of stale data caused by node deletion (#9606) 2025-07-22 16:12:00 +08:00
蘭 6e650d977e fix: Fix some issues with the alert (#9605) 2025-07-22 07:10:06 +00:00
ssongliu 8a76171dbe fix: Resolve backup to server disk failure for cronjob (#9603) 2025-07-22 14:56:13 +08:00
ssongliu 949b6fe944 fix: Fix node state synchronization issues (#9599) 2025-07-22 03:48:06 +00:00
CityFun 3f90333090 feat: Application installation supports using existing remote databases (#9598) 2025-07-22 03:20:05 +00:00
ssongliu aeaca0c5cd fix: Change container creation to asynchronous operation (#9597) 2025-07-22 11:00:16 +08:00
ssongliu fb7f638a54 fix: Optimize container filter component for cronjob (#9595)
<img width="633" height="304" alt="image" src="https://github.com/user-attachments/assets/b5077719-ddf8-438c-9ce4-ca6bb76d7217" />
2025-07-22 02:34:05 +00:00
CityFun 7994193a39 feat: Added Docker detection to the runtime environment (#9596) 2025-07-22 10:32:14 +08:00
蘭 3334bf594d fix: Fix some issues with the alert (#9594) 2025-07-22 02:11:59 +00:00
蘭 71193cc912 ref: Optimize IPv4 forwarding and preset chain existence check (#9592) 2025-07-21 11:04:42 +00:00
蘭 9fde8c447d fix: Improve alert notifications (#9591) 2025-07-21 11:02:42 +00:00
ssongliu 117faa952e fix: Fix container edit task log abnormalities (#9590) 2025-07-21 10:14:41 +00:00
CityFun 6427337728 feat: Adapt to Redis Cluster (#9588) 2025-07-21 09:56:42 +00:00
ssongliu 81f97f6f88 fix: Fix overview page load metrics localization issues (#9587) 2025-07-21 09:54:42 +00:00
ssongliu b3b472d711 fix: Optimize container creation page exit behavior (#9586)
Refs #9424
2025-07-21 09:52:43 +00:00
CityFun 1c22e1cf99 fix: Fix the issue where remote databases cannot be selected during application installation (#9589) 2025-07-21 17:52:35 +08:00
ssongliu 3110d91bf6 fix: Fix app upload/restore failure issues (#9583)
Refs #9572
2025-07-21 09:50:43 +00:00
ssongliu 624a11407a fix: Fix data synchronization during backup account modification (#9585) 2025-07-21 17:49:56 +08:00
ssongliu ead6d1268f feat: Optimize node addition notification messages (#9582) 2025-07-21 15:53:23 +08:00
CityFun 80c00c5a8c fix: fix some bugs with app-cluster (#9581) 2025-07-21 15:53:11 +08:00
ssongliu da0f6acd01 fix: Optimize snapshot synchronization options (#9579) 2025-07-21 02:36:41 +00:00
ssongliu 485aa99dea fix: Fix DNS configuration editing issue for containers (#9578)
Refs #9570
2025-07-21 02:34:41 +00:00
蘭 5f68d7ce90 feat: Dealing with compatibility issues with alert history data (#9571) 2025-07-18 22:43:19 +08:00
ssongliu 685fca6c8c fix: Add 'Deleted' Node State (#9569) 2025-07-18 22:43:07 +08:00
蘭 9c609da332 refactor: Improve alarm prompt information (#9564) 2025-07-18 07:43:22 +00:00
蘭 84608b7d42 feat: Alert notification supports email service (#9554) 2025-07-18 11:44:20 +08:00
CityFun 02cf2a80cb fix: Fixed issue with install Mailserver failed (#9562)
Refs https://github.com/1Panel-dev/1Panel/issues/9539
2025-07-18 03:43:23 +00:00
CityFun a74c6dd522 feat: Add installation support for Redis Cluster (#9561) 2025-07-18 03:41:23 +00:00
ssongliu 5d4ab23d95 style: Optimize node switching transition effects (#9560)
Refs #9556
2025-07-18 03:39:23 +00:00
ssongliu a8faba3206 style: Optimize drawer closing mechanism (#9559)
Refs #9371
2025-07-18 03:37:22 +00:00
ssongliu c603495b12 fix: Fix container log filtering anddownload issues (#9553)
Refs #9269 #9457
2025-07-17 10:13:21 +00:00
ssongliu adce6103bd fix: Fix log printing issues caused by container log tracking switches (#9552) 2025-07-17 17:51:23 +08:00
ssongliu 6b673017c5 fix: Fix the problem of abnormal request forwarding (#9548) 2025-07-17 07:43:20 +00:00
ssongliu e66bd43fc6 feat: Add customizable snapshot timeout support (#9537)
Refs #9502
2025-07-16 10:43:52 +00:00
CityFun ff5cdb9a02 feat: The container-compose module is adapted for Docker Compose. (#9536) 2025-07-16 10:41:52 +00:00
CityFun 1aec5e443d fix: Fix the issue where some applications incorrectly displayed a redirect button on error (#9535)
Refs https://github.com/1Panel-dev/1Panel/issues/9477
2025-07-16 10:17:53 +00:00
ssongliu 0b1f7e9322 fix: Change backup temporary file directory (#9533)
Refs #9428
2025-07-16 10:15:52 +00:00
CityFun 3ec2dec49b feat: Remove the required field prompt for HSTS in the website HTTPS … (#9532)
Refs https://github.com/1Panel-dev/1Panel/issues/9465
2025-07-16 09:59:52 +00:00
ssongliu 7cbebc8236 feat: Support backup for master (#9498) 2025-07-16 17:53:04 +08:00
CityFun 700076f278 feat: Application installation supports existing remote databases (#9531) 2025-07-16 17:47:27 +08:00
ssongliu 7bf1bf61a7 fix: Fix memory usage display issue on dashboard (#9530)
Refs #9497
2025-07-16 09:45:53 +00:00
CityFun ef2cb1b315 feat: Adapt to PostgreSQL cluster (#9529) 2025-07-16 17:44:20 +08:00
CityFun 75bfc3a114 feat: Scheduled tasks support backing up MySQL cluster databases (#9526) 2025-07-16 15:52:04 +08:00
CityFun 70f63090fe fix: Resolve the issue of failed slave installation (#9519) 2025-07-15 07:39:56 +00:00
CityFun ab2cf0d0c3 feat: add cluster status page (#9514) 2025-07-14 19:08:57 +08:00
CityFun ea1a34f57a feat: Optimize node management logic (#9510) 2025-07-14 18:59:26 +08:00
CityFun 765add6184 fix: Fix the issue where some applications fail to execute scripts (#9508) 2025-07-14 04:21:51 +00:00
CityFun e5263d6c05 fix: Modify the header of the application logs page (#9507)
Refs https://github.com/1Panel-dev/1Panel/issues/9501
2025-07-14 04:19:51 +00:00
CityFun c391177d34 feat: Add MySQL master-slave installation feature (#9505) 2025-07-13 22:36:21 +08:00
CityFun 7d5f0aa906 fix: Fix the issue of failing to save IPv6 self-signed certificate (#9495) 2025-07-11 17:35:39 +08:00
CityFun 2694a725d6 feat: Add quick installation for the sodium extension (#9481) 2025-07-10 06:27:19 +00:00
蘭 54ca40bbcd fix: Fix container log download ANSI control code garbled issue (#9479) 2025-07-10 11:21:13 +08:00
蘭 b20411f8f1 fix: Fix the issue of calculating size incorrectly during sorting (#9474) 2025-07-09 11:02:55 +00:00
ssongliu 355e656caa fix: Fix the issue where the demo configuration takes effect (#9468) 2025-07-09 02:10:49 +00:00
ssongliu 383bdd5f15 fix: Change container image building to asynchronous operation (#9462)
Refs #8463
2025-07-08 11:06:47 +00:00
CityFun 96208c786d feat: The runtime terminal supports specifying a user (#9461) 2025-07-08 10:34:14 +00:00
蘭 4c7b04cf27 feat: Perfect Türkiye language configuration (#9458) 2025-07-08 10:20:51 +00:00
蘭 a067a97e21 fix: Fix the issue of garbled characters when opening UTF-8 encoded files (#9454)
refs: #9411
2025-07-08 06:09:54 +00:00
CityFun 4a07603a7c Fix: change loadBalanceHelper translate (#9453) 2025-07-08 06:07:54 +00:00
ssongliu 7196c5b656 fix: Fix https image registry deletion failure (#9451) 2025-07-08 06:05:55 +00:00
CityFun b5de2df119 Fix: change loadBalance translate (#9448) 2025-07-08 02:47:54 +00:00
Sinan Eldem 2f72eb37ee Turkish Language Files for v.dev-v2 (#9390) 2025-07-08 10:45:19 +08:00
CityFun 0128cd3138 Fix: fix issue with element-plus tab style wrong (#9441) 2025-07-07 18:34:00 +08:00
ssongliu 157d3093f8 fix: Update license validation error messages (#9438) 2025-07-07 08:17:50 +00:00
ssongliu 4ca188973e fix: Add rule validation for FTP account creation (#9437) 2025-07-07 06:25:49 +00:00
wanghe-fit2cloud f387d89a45 docs: Optimize README 2025-07-07 12:09:52 +08:00
CityFun 54567db22f Fix: Resolved issue where domain binding failed in MCP due to OpenResty not using 80 port (#9435)
Refs https://github.com/1Panel-dev/1Panel/issues/9431
2025-07-07 04:07:50 +00:00
ssongliu 2bee68dd40 fix: Fix timezone modification reboot exception (#9432)
Refs #9422
2025-07-07 04:05:50 +00:00
CityFun e2b6ea5253 Fix: Resolved unInstall app failed due to run compose down timeout (#9436) 2025-07-07 04:03:49 +00:00
CityFun 9974c5fa20 feat: add DownloadCallBack for install php runtime (#9430) 2025-07-07 03:05:50 +00:00
ssongliu 997b706d8a fix: Fix dashboard refresh exception issue (#9429) 2025-07-07 03:03:49 +00:00
ssongliu 3f26412585 fix: Modify OneDrive default connection settings (#9403) 2025-07-04 09:03:41 +00:00
ssongliu ac99d77fbb fix: Fix snap docker startup failure due to empty configuration (#9402) 2025-07-04 07:21:40 +00:00
ssongliu 4f6f7632f5 fix: Fix the problem of re-logging during the snapshot recovery process (#9401) 2025-07-04 03:59:40 +00:00
CityFun f74248f0d7 fix: Fixed issue with open app log failed with docker-compose (#9400) 2025-07-04 10:23:02 +08:00
ssongliu bb8a15ca56 fix: Fix system load spike during disk mount failures (#9399)
Refs #8827
2025-07-04 02:19:39 +00:00
ssongliu 52432002c7 fix: Fix port acquisition exception issue (#9398) 2025-07-03 16:31:34 +08:00
ssongliu 3fade48f8f fix: Add error ignore functionality to cronjob backups (#9396) 2025-07-03 16:31:25 +08:00
ssongliu fc81801c4c fix: Fix user settings loading failure after reinstallation (#9395) 2025-07-03 14:52:04 +08:00
ssongliu c23c5b2859 style: Add more pagination options to table (#9392)
Refs #5621
2025-07-03 05:55:38 +00:00
ssongliu ccec57e32d fix: Fix user settings loading failure after reinstallation (#9391) 2025-07-03 02:49:37 +00:00
CityFun 56ae5fbf70 fix: Fixed issue with build core failed (#9387) 2025-07-02 23:32:26 +08:00
CityFun 735b44856e style: Fix the problem where dropdown text is too long to display properly. (#9378) 2025-07-02 10:49:34 +00:00
ssongliu 1f6ea9f494 fix: Support docker configuration modifications for snap installations (#9377) 2025-07-02 09:43:35 +00:00
ssongliu afa643519f fix: Modify default values for recommended apps on dashboard (#9375) 2025-07-02 09:41:36 +00:00
ssongliu 1767b84026 fix: Fix overview page data loading/closing exception (#9373) 2025-07-02 09:39:35 +00:00
ssongliu e855e7692e fix: Fix permission errors when running script library as non-root User (#9370) 2025-07-02 04:25:34 +00:00
CityFun 94d37eb68a fix: Resolve the php runtime start failed (#9369) 2025-07-02 04:23:35 +00:00
CityFun e10b19bcde fix: Fixed issue with the mounted directory configuration cannot be displayed in runtime (#9368)
Refs https://github.com/1Panel-dev/1Panel/issues/9293
2025-07-02 04:21:35 +00:00
ssongliu e9192e75ea fix: Fix file existence check when deleting node upgrade records (#9365) 2025-07-01 14:29:33 +00:00
ssongliu 2fa4f68b1e fix: Update backup account documentation links (#9364) 2025-07-01 10:13:32 +00:00
ssongliu 08f53a4a37 fix: Support configurable docker config file snapshotting (#9363) 2025-07-01 07:49:32 +00:00
CityFun c522295c56 fix: Resolve the issue of database uninstallation failure (#9361) 2025-07-01 15:46:21 +08:00
BugKing 7a45548387 feat: add terminal icon for installed App (#9331)
* feat: add terminal icon for installed App

* refactor: 应用卡片打开容器终端,适配多个容器情况
2025-07-01 15:45:57 +08:00
ssongliu 810ea9c421 style: Handle overflow for node names with ellipsis truncation (#9360) 2025-07-01 07:45:32 +00:00
CityFun ba6a4fe6d7 fix: Resolve the issue of lost GPU configuration after upgrade ollama (#9359) 2025-07-01 07:43:32 +00:00
ssongliu 4d144f7a8e fix: Optimize backup account switching logic (#9358) 2025-07-01 06:31:32 +00:00
ssongliu 45c41184c1 fix: Optimize 1pctl restore command output (#9355) 2025-07-01 03:51:33 +00:00
ssongliu 5c29c5e7dd fix: Implement chunked upload for google drive file transfers (#9354) 2025-07-01 03:49:32 +00:00
蘭 4da9a84363 fix: Disable button when clearing cache and selecting size 0B (#9349) 2025-06-30 14:29:31 +00:00
蘭 22d8fb7867 fix: Fix the display error of the sub node website icon (#9348) 2025-06-30 11:11:31 +00:00
ssongliu c7058cb3e6 fix: Optimize container image repo restart detection logic (#9347)
Refs #8978
2025-06-30 11:09:30 +00:00
ssongliu e6092b2658 fix: Fix the compilation exception problem caused by dependencies (#9345) 2025-06-30 08:49:30 +00:00
ssongliu ae19b72e61 feat: Add root/sudo password-free access check for ssh (#9344) 2025-06-30 08:21:30 +00:00
dependabot[bot] da117582d6 build(deps): bump github.com/go-viper/mapstructure/v2 in /agent (#9343)
Bumps [github.com/go-viper/mapstructure/v2](https://github.com/go-viper/mapstructure) from 2.2.1 to 2.3.0.
- [Release notes](https://github.com/go-viper/mapstructure/releases)
- [Changelog](https://github.com/go-viper/mapstructure/blob/main/CHANGELOG.md)
- [Commits](https://github.com/go-viper/mapstructure/compare/v2.2.1...v2.3.0)

---
updated-dependencies:
- dependency-name: github.com/go-viper/mapstructure/v2
  dependency-version: 2.3.0
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-06-30 12:00:18 +08:00
CityFun c7ca1ccbd7 core: Remove unused code (#9342) 2025-06-30 03:55:30 +00:00
ssongliu 911b5c406d style: Optimize cronjob report page styling (#9341) 2025-06-30 03:53:30 +00:00
wanghe-fit2cloud 71ed4b946a feat: Adjust the logo’s aspect ratio to prevent it from appearing stretched 2025-06-29 23:19:55 +08:00
蘭 4ef9fcde59 fix: Default display of hidden files (#9327)
#9325 
#9324
2025-06-27 10:23:24 +00:00
ssongliu 7889b5ad69 fix: Optimize script-type cronjob execution output (#9326) 2025-06-27 17:57:40 +08:00
ssongliu be6d3ea337 fix: Update compression backup exclusion rules (#9323) 2025-06-27 09:25:23 +00:00
CityFun 64fd715973 perf: optimization php config (#9321) 2025-06-27 16:56:13 +08:00
ssongliu 315f99a03a feat: Add application exclusion for cronjob snapshots (#9320)
Refs #8914
2025-06-27 08:47:26 +00:00
ssongliu 7e1351c543 fix: resolve code warnings and improve code quality (#9322) 2025-06-27 16:47:01 +08:00
CityFun bd3c829653 fix: translate the requestTimeout msg for frontend (#9316)
Refs https://github.com/1Panel-dev/1Panel/issues/9308
2025-06-27 05:49:24 +00:00
蘭 4f6dde02d2 fix: Fix the issue of missing line breaks in copied log content (#9314)
#9099
2025-06-27 05:47:25 +00:00
蘭 8cc7d1f67f fix: Fix the issue of multiple selection moving folder errors (#9317) 2025-06-27 05:45:23 +00:00
ssongliu 2bbba9d755 style: Adjust node option popup interaction (#9312)
Refs #9290
2025-06-27 02:23:23 +00:00
CityFun e9e4755348 Revert "fix: Fix missing line breaks in copied logs on the log page (… (#9311) 2025-06-27 02:19:26 +00:00
CityFun 9fbb63df95 fix: Resolve command execution failures in the PHP runtime environmen… (#9299)
Refs https://github.com/1Panel-dev/1Panel/issues/9286
2025-06-26 08:31:23 +00:00
ssongliu 5b5a649589 fix: resolve Google Drive token refresh failure (#9298)
Refs #9223
2025-06-26 07:37:22 +00:00
蘭 79556affbd fix: Fix the preview image error of child nodes (#9296)
#9245
2025-06-26 06:43:22 +00:00
蘭 9cba19d4db fix: Fix the process status error issue (#9285) 2025-06-25 22:05:29 +08:00
蘭 4f377c04b8 fix: Fix missing line breaks in copied logs on the log page (#9283) 2025-06-25 22:05:12 +08:00
ssongliu 763df5606b fix: Fix snapshot restoration missing website backup detection (#9282) 2025-06-25 22:04:53 +08:00
ssongliu e1d20e3ec9 fix: Optimize snapshot and snapshot recovery mechanism (#9279) 2025-06-25 16:29:15 +08:00
CityFun 215c082a04 perf: add some config for website_default.conf (#9278)
Refs https://github.com/1Panel-dev/1Panel/issues/8837
2025-06-25 08:23:21 +00:00
ssongliu 984985e650 fix: Fix snapshot restoration for container configuration errors (#9277)
Refs #9216
2025-06-25 08:21:21 +00:00
蘭 249b3c3245 fix: Fixed logging errors for uploaded files (#9273) 2025-06-25 08:19:20 +00:00
Swt242 725786d6aa fix: Fixed logging errors for uploaded files (#9204) 2025-06-25 11:46:27 +08:00
蘭 2edddaf487 feat: Specify the interpretation code based on the file content (#9267) 2025-06-24 14:17:14 +00:00
ssongliu 49cbab08c1 fix: Modify snapshot synchronization and import name restrictions (#9266) 2025-06-24 18:43:56 +08:00
ssongliu a75df66151 fix: Rollback snapshot code (#9263) 2025-06-24 10:41:14 +00:00
CityFun 43e9c13c5e perf: Optimize and Edit PHP Runtime Environment Version Switching (#9265) 2025-06-24 18:40:53 +08:00
CityFun 5ad06f121b fix: fix issue with subsite dir error (#9259) 2025-06-24 10:39:14 +00:00
wanghe-fit2cloud 0eaaf7aacb feat: Adjust the logo’s aspect ratio to prevent it from appearing stretched 2025-06-24 17:29:15 +08:00
CityFun dd44fdd1f2 fix: Fixed issue with tar file failed with exist 1 (#9261) 2025-06-24 16:57:27 +08:00
CityFun 669855de70 fix: Fixed the issue that caused the error (bufio.Scanner: token too long) when viewing container log (#9256)
Refs https://github.com/1Panel-dev/1Panel/issues/9241
2025-06-24 05:53:13 +00:00
CityFun 193ae2b7d6 fix: fix issue with enable website ssl with ipv6 failed (#9255) 2025-06-24 13:44:17 +08:00
Alan af4b23e0e0 fix: fixed dashboard operating system platform Version (#9247) 2025-06-24 09:31:14 +08:00
蘭 c35991315d fix: Fix page loading issue with no data status (#9248) 2025-06-23 22:14:48 +08:00
ssongliu 9aa852370c fix: Split snapshot application backup (#9244) 2025-06-23 10:40:13 +00:00
CityFun 8f18497551 feat: add remark for runtime (#9242) 2025-06-23 18:37:37 +08:00
CityFun 4c59951e8c style: change dnsAccount select option style in create ssl page (#9239) 2025-06-23 06:48:12 +00:00
CityFun eb2a80d216 style: hightlight ipv6 address in task log (#9238)
Refs https://github.com/1Panel-dev/1Panel/issues/9118
2025-06-23 06:46:13 +00:00
CityFun 41d0175a6d style: change show diff button style for upgrade app (#9237)
Refs https://github.com/1Panel-dev/1Panel/issues/9207
2025-06-23 06:44:11 +00:00
igophper 2f6aa0f45b fix: Fix docker status flicker issue (#9218) 2025-06-23 13:46:03 +08:00
igophper 17c685bc4c fix: Fix the apps store docker status error (#9217) 2025-06-23 13:44:09 +08:00
324 changed files with 16308 additions and 2354 deletions
+1 -1
View File
@@ -1,3 +1,3 @@
{
"ansible.python.interpreterPath": "/opt/homebrew/bin/python3"
}
}
+1 -1
View File
@@ -125,4 +125,4 @@ enforcement ladder](https://github.com/mozilla/diversity).
For answers to common questions about this code of conduct, see the FAQ at
https://www.contributor-covenant.org/faq. Translations are available at
https://www.contributor-covenant.org/translations.
https://www.contributor-covenant.org/translations.
+200
View File
@@ -0,0 +1,200 @@
package v2
import (
"errors"
"github.com/1Panel-dev/1Panel/agent/app/api/v2/helper"
"github.com/1Panel-dev/1Panel/agent/app/dto"
"github.com/gin-gonic/gin"
)
func (b *BaseApi) PageAlert(c *gin.Context) {
var req dto.AlertSearch
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}
total, alerts, err := alertService.PageAlert(req)
if err != nil {
helper.InternalServer(c, err)
return
}
helper.SuccessWithData(c, dto.PageResult{
Total: total,
Items: alerts,
})
}
func (b *BaseApi) GetAlerts(c *gin.Context) {
alerts, err := alertService.GetAlerts()
if err != nil {
helper.InternalServer(c, err)
return
}
helper.SuccessWithData(c, alerts)
}
func (b *BaseApi) CreateAlert(c *gin.Context) {
var req dto.AlertCreate
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}
err := alertService.CreateAlert(req)
if err != nil {
helper.InternalServer(c, err)
return
}
helper.Success(c)
}
func (b *BaseApi) DeleteAlert(c *gin.Context) {
var req dto.DeleteRequest
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}
err := alertService.DeleteAlert(req.ID)
if err != nil {
helper.InternalServer(c, err)
return
}
helper.Success(c)
}
func (b *BaseApi) UpdateAlert(c *gin.Context) {
var req dto.AlertUpdate
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}
if err := alertService.UpdateAlert(req); err != nil {
helper.InternalServer(c, err)
return
}
helper.Success(c)
}
func (b *BaseApi) GetAlert(c *gin.Context) {
id, err := helper.GetParamID(c)
if err != nil {
helper.BadRequest(c, errors.New("no such id in request param"))
return
}
alert, err := alertService.GetAlert(id)
if err != nil {
helper.InternalServer(c, err)
return
}
helper.SuccessWithData(c, alert)
}
func (b *BaseApi) UpdateAlertStatus(c *gin.Context) {
var req dto.AlertUpdateStatus
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}
if err := alertService.UpdateStatus(req.ID, req.Status); err != nil {
helper.InternalServer(c, err)
return
}
helper.Success(c)
}
func (b *BaseApi) GetDisks(c *gin.Context) {
alerts, err := alertService.GetDisks()
if err != nil {
helper.InternalServer(c, err)
return
}
helper.SuccessWithData(c, alerts)
}
func (b *BaseApi) PageAlertLogs(c *gin.Context) {
var req dto.AlertLogSearch
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}
total, alertLogs, err := alertService.PageAlertLogs(req)
if err != nil {
helper.InternalServer(c, err)
return
}
helper.SuccessWithData(c, dto.PageResult{
Total: total,
Items: alertLogs,
})
}
func (b *BaseApi) CleanAlertLogs(c *gin.Context) {
if err := alertService.CleanAlertLogs(); err != nil {
helper.InternalServer(c, err)
return
}
helper.Success(c)
}
func (b *BaseApi) GetClams(c *gin.Context) {
clams, err := alertService.GetClams()
if err != nil {
helper.InternalServer(c, err)
return
}
helper.SuccessWithData(c, clams)
}
func (b *BaseApi) GetCronJobs(c *gin.Context) {
var req dto.CronJobReq
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}
cronJobs, err := alertService.GetCronJobs(req)
if err != nil {
helper.InternalServer(c, err)
return
}
helper.SuccessWithData(c, cronJobs)
}
func (b *BaseApi) GetAlertConfig(c *gin.Context) {
config, err := alertService.GetAlertConfig()
if err != nil {
helper.InternalServer(c, err)
return
}
helper.SuccessWithData(c, config)
}
func (b *BaseApi) UpdateAlertConfig(c *gin.Context) {
var req dto.AlertConfigUpdate
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}
if err := alertService.UpdateAlertConfig(req); err != nil {
helper.InternalServer(c, err)
return
}
helper.Success(c)
}
func (b *BaseApi) DeleteAlertConfig(c *gin.Context) {
var req dto.DeleteRequest
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}
err := alertService.DeleteAlertConfig(req.ID)
if err != nil {
helper.InternalServer(c, err)
return
}
helper.Success(c)
}
func (b *BaseApi) TestAlertConfig(c *gin.Context) {
var req dto.AlertConfigTest
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}
flag, err := alertService.TestAlertConfig(req)
if err != nil {
helper.InternalServer(c, err)
return
}
helper.SuccessWithData(c, flag)
}
+22
View File
@@ -327,3 +327,25 @@ func (b *BaseApi) UpdateAppConfig(c *gin.Context) {
}
helper.Success(c)
}
// @Tags App
// @Summary Get app install info
// @Accept json
// @Param appInstallId path integer true "App install id"
// @Success 200 {object} dto.AppInstallInfo
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /apps/installed/info/:appInstallId [get]
func (b *BaseApi) GetAppInstallInfo(c *gin.Context) {
appInstallId, err := helper.GetIntParamByKey(c, "appInstallId")
if err != nil {
helper.BadRequest(c, err)
return
}
info, err := appInstallService.GetAppInstallInfo(appInstallId)
if err != nil {
helper.InternalServer(c, err)
return
}
helper.SuccessWithData(c, info)
}
+8 -8
View File
@@ -371,12 +371,12 @@ func (b *BaseApi) Backup(c *gin.Context) {
helper.InternalServer(c, err)
return
}
case "mysql", "mariadb":
case "mysql", "mariadb", constant.AppMysqlCluster:
if err := backupService.MysqlBackup(req); err != nil {
helper.InternalServer(c, err)
return
}
case constant.AppPostgresql:
case constant.AppPostgresql, constant.AppPostgresqlCluster:
if err := backupService.PostgresqlBackup(req); err != nil {
helper.InternalServer(c, err)
return
@@ -386,7 +386,7 @@ func (b *BaseApi) Backup(c *gin.Context) {
helper.InternalServer(c, err)
return
}
case "redis":
case "redis", constant.AppRedisCluster:
if err := backupService.RedisBackup(req); err != nil {
helper.InternalServer(c, err)
return
@@ -421,12 +421,12 @@ func (b *BaseApi) Recover(c *gin.Context) {
}
req.File = downloadPath
switch req.Type {
case "mysql", "mariadb":
case "mysql", "mariadb", constant.AppMysqlCluster:
if err := backupService.MysqlRecover(req); err != nil {
helper.InternalServer(c, err)
return
}
case constant.AppPostgresql:
case constant.AppPostgresql, constant.AppPostgresqlCluster:
if err := backupService.PostgresqlRecover(req); err != nil {
helper.InternalServer(c, err)
return
@@ -436,7 +436,7 @@ func (b *BaseApi) Recover(c *gin.Context) {
helper.InternalServer(c, err)
return
}
case "redis":
case "redis", constant.AppRedisCluster:
if err := backupService.RedisRecover(req); err != nil {
helper.InternalServer(c, err)
return
@@ -466,12 +466,12 @@ func (b *BaseApi) RecoverByUpload(c *gin.Context) {
}
switch req.Type {
case "mysql", "mariadb":
case "mysql", "mariadb", constant.AppMysqlCluster:
if err := backupService.MysqlRecoverByUpload(req); err != nil {
helper.InternalServer(c, err)
return
}
case constant.AppPostgresql:
case constant.AppPostgresql, constant.AppPostgresqlCluster:
if err := backupService.PostgresqlRecoverByUpload(req); err != nil {
helper.InternalServer(c, err)
return
+2 -7
View File
@@ -50,12 +50,7 @@ func (b *BaseApi) LoadContainerUsers(c *gin.Context) {
return
}
list, err := containerService.LoadUsers(req)
if err != nil {
helper.InternalServer(c, err)
return
}
helper.SuccessWithData(c, list)
helper.SuccessWithData(c, containerService.LoadUsers(req))
}
// @Tags Container
@@ -266,7 +261,7 @@ func (b *BaseApi) ContainerCreate(c *gin.Context) {
return
}
if err := containerService.ContainerCreate(req); err != nil {
if err := containerService.ContainerCreate(req, true); err != nil {
helper.InternalServer(c, err)
return
}
+45
View File
@@ -1,6 +1,8 @@
package v2
import (
"net/http"
"strings"
"time"
"github.com/1Panel-dev/1Panel/agent/app/api/v2/helper"
@@ -53,6 +55,49 @@ func (b *BaseApi) LoadCronjobInfo(c *gin.Context) {
helper.SuccessWithData(c, data)
}
// @Tags Cronjob
// @Summary Export cronjob list
// @Accept json
// @Param request body dto.OperateByIDs true "request"
// @Success 200
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /cronjobs/export [post]
func (b *BaseApi) ExportCronjob(c *gin.Context) {
var req dto.OperateByIDs
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}
content, err := cronjobService.Export(req)
if err != nil {
helper.InternalServer(c, err)
return
}
http.ServeContent(c.Writer, c.Request, "", time.Now(), strings.NewReader(content))
}
// @Tags Cronjob
// @Summary Import cronjob list
// @Accept json
// @Param request body dto.CronjobImport true "request"
// @Success 200
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /cronjobs/import [post]
func (b *BaseApi) ImportCronjob(c *gin.Context) {
var req dto.CronjobImport
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}
if err := cronjobService.Import(req.Cronjobs); err != nil {
helper.InternalServer(c, err)
return
}
helper.Success(c)
}
// @Tags Cronjob
// @Summary Load script options
// @Success 200 {array} dto.ScriptOptions
+6 -6
View File
@@ -11,13 +11,13 @@ import (
// @Tags Database Redis
// @Summary Load redis status info
// @Accept json
// @Param request body dto.OperationWithName true "request"
// @Param request body dto.LoadRedisStatus true "request"
// @Success 200 {object} dto.RedisStatus
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /databases/redis/status [post]
func (b *BaseApi) LoadRedisStatus(c *gin.Context) {
var req dto.OperationWithName
var req dto.LoadRedisStatus
if err := helper.CheckBind(&req, c); err != nil {
return
}
@@ -33,13 +33,13 @@ func (b *BaseApi) LoadRedisStatus(c *gin.Context) {
// @Tags Database Redis
// @Summary Load redis conf
// @Accept json
// @Param request body dto.OperationWithName true "request"
// @Param request body dto.LoadRedisStatus true "request"
// @Success 200 {object} dto.RedisConf
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /databases/redis/conf [post]
func (b *BaseApi) LoadRedisConf(c *gin.Context) {
var req dto.OperationWithName
var req dto.LoadRedisStatus
if err := helper.CheckBind(&req, c); err != nil {
return
}
@@ -55,13 +55,13 @@ func (b *BaseApi) LoadRedisConf(c *gin.Context) {
// @Tags Database Redis
// @Summary Load redis persistence conf
// @Accept json
// @Param request body dto.OperationWithName true "request"
// @Param request body dto.LoadRedisStatus true "request"
// @Success 200 {object} dto.RedisPersistence
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /databases/redis/persistence/conf [post]
func (b *BaseApi) LoadPersistenceConf(c *gin.Context) {
var req dto.OperationWithName
var req dto.LoadRedisStatus
if err := helper.CheckBind(&req, c); err != nil {
return
}
+1
View File
@@ -70,4 +70,5 @@ var (
websiteCAService = service.NewIWebsiteCAService()
taskService = service.NewITaskService()
groupService = service.NewIGroupService()
alertService = service.NewIAlertService()
)
+1 -1
View File
@@ -296,7 +296,7 @@ func (b *BaseApi) SaveContent(c *gin.Context) {
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /files/upload [post]
// @x-panel-log {"bodyKeys":["path"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"上传文件 [path]","formatEN":"Upload file [path]"}
// @x-panel-log {"bodyKeys":["path", "file"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"上传文件 [path]/[file]","formatEN":"Upload file [path]/[file]"}
func (b *BaseApi) UploadFiles(c *gin.Context) {
form, err := c.MultipartForm()
if err != nil {
+4 -4
View File
@@ -98,20 +98,20 @@ func (b *BaseApi) CreateRepo(c *gin.Context) {
// @Tags Container Image-repo
// @Summary Delete image repo
// @Accept json
// @Param request body dto.ImageRepoDelete true "request"
// @Param request body dto.OperateByID true "request"
// @Produce json
// @Success 200
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /containers/repo/del [post]
// @x-panel-log {"bodyKeys":["ids"],"paramKeys":[],"BeforeFunctions":[{"input_column":"id","input_value":"ids","isList":true,"db":"image_repos","output_column":"name","output_value":"names"}],"formatZH":"删除镜像仓库 [names]","formatEN":"delete image repo [names]"}
// @x-panel-log {"bodyKeys":["id"],"paramKeys":[],"BeforeFunctions":[{"input_column":"id","input_value":"id","isList":false,"db":"image_repos","output_column":"name","output_value":"name"}],"formatZH":"删除镜像仓库 [name]","formatEN":"delete image repo [name]"}
func (b *BaseApi) DeleteRepo(c *gin.Context) {
var req dto.ImageRepoDelete
var req dto.OperateByID
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}
if err := imageRepoService.BatchDelete(req); err != nil {
if err := imageRepoService.Delete(req); err != nil {
helper.InternalServer(c, err)
return
}
+20
View File
@@ -535,3 +535,23 @@ func (b *BaseApi) GetPHPContainerConfig(c *gin.Context) {
}
helper.SuccessWithData(c, data)
}
// @Tags Runtime
// @Summary Update runtime remark
// @Accept json
// @Param request body request.RuntimeRemarkUpdate true "request"
// @Success 200
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /runtimes/remark [post]
func (b *BaseApi) UpdateRuntimeRemark(c *gin.Context) {
var req request.RuntimeRemark
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}
if err := runtimeService.UpdateRemark(req); err != nil {
helper.InternalServer(c, err)
return
}
helper.Success(c)
}
+4 -4
View File
@@ -89,8 +89,8 @@ func (b *BaseApi) ContainerWsSSH(c *gin.Context) {
var containerID string
var initCmd []string
switch source {
case "redis":
containerID, initCmd, err = loadRedisInitCmd(c)
case "redis", "redis-cluster":
containerID, initCmd, err = loadRedisInitCmd(c, source)
case "ollama":
containerID, initCmd, err = loadOllamaInitCmd(c)
case "container":
@@ -127,7 +127,7 @@ func (b *BaseApi) ContainerWsSSH(c *gin.Context) {
_ = wsConn.WriteControl(websocket.CloseMessage, nil, dt)
}
func loadRedisInitCmd(c *gin.Context) (string, []string, error) {
func loadRedisInitCmd(c *gin.Context, redisType string) (string, []string, error) {
name := c.Query("name")
from := c.Query("from")
commands := []string{"exec", "-it"}
@@ -136,7 +136,7 @@ func loadRedisInitCmd(c *gin.Context) (string, []string, error) {
return "", nil, fmt.Errorf("no such database in db, err: %v", err)
}
if from == "local" {
redisInfo, err := appInstallService.LoadConnInfo(dto.OperationWithNameAndType{Name: name, Type: "redis"})
redisInfo, err := appInstallService.LoadConnInfo(dto.OperationWithNameAndType{Name: name, Type: redisType})
if err != nil {
return "", nil, fmt.Errorf("no such app in db, err: %v", err)
}
+311 -4
View File
@@ -1,10 +1,17 @@
package dto
import (
"encoding/json"
"github.com/1Panel-dev/1Panel/agent/app/model"
"time"
)
type CreateOrUpdateAlert struct {
AlertTitle string `json:"alertTitle"`
AlertType string `json:"alertType"`
AlertCount uint `json:"alertCount"`
EntryID uint `json:"entryID"`
AlertTitle string `json:"alertTitle"`
AlertType string `json:"alertType"`
AlertMethod string `json:"alertMethod"`
AlertCount uint `json:"alertCount"`
EntryID uint `json:"entryID"`
}
type AlertBase struct {
@@ -18,3 +25,303 @@ type PushAlert struct {
EntryID uint `json:"entryID"`
Param string `json:"param"`
}
type AlertSearch struct {
PageInfo
OrderBy string `json:"orderBy" validate:"required,oneof=created_at"`
Order string `json:"order" validate:"required,oneof=null ascending descending"`
Type string `json:"type"`
Status string `json:"status"`
Method string `json:"method"`
}
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"`
}
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 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"`
}
type DeleteRequest struct {
ID uint `json:"id" validate:"required"`
}
type AlertUpdateStatus struct {
ID uint `json:"id" validate:"required"`
Status string `json:"status" validate:"required"`
}
type DiskDTO struct {
Path string `json:"path"`
Type string `json:"type"`
Device string `json:"device"`
Total uint64 `json:"total"`
Free uint64 `json:"free"`
Used uint64 `json:"used"`
UsedPercent float64 `json:"usedPercent"`
InodesTotal uint64 `json:"inodesTotal"`
InodesUsed uint64 `json:"inodesUsed"`
InodesFree uint64 `json:"inodesFree"`
InodesUsedPercent float64 `json:"inodesUsedPercent"`
}
type AlertLogSearch struct {
PageInfo
Count uint `json:"count"`
Status string `json:"status"`
}
type AlertLogDTO struct {
ID uint `json:"id"`
Type string `json:"type"`
Count uint `json:"count"`
AlertId uint `json:"alertId"`
AlertDetail AlertDetail `json:"alertDetail"`
AlertRule AlertRule `json:"alertRule"`
Status string `json:"status"`
Method string `json:"method"`
Message string `json:"message"`
CreatedAt time.Time `json:"createdAt"`
UpdatedAt time.Time `json:"updatedAt"`
}
type AlertLogCreate struct {
Type string `json:"type" validate:"required"`
Count uint `json:"count" validate:"required"`
AlertId uint `json:"alertId" validate:"required"`
AlertDetail string `json:"alertDetail" validate:"required"`
AlertRule string `json:"alertRule" validate:"required"`
Status string `json:"status" validate:"required"`
Method string `json:"method" validate:"required"`
Message string `json:"message"`
RecordId uint `json:"recordId"`
LicenseId string `json:"licenseId" validate:"required"`
}
type AlertLog struct {
ID uint `json:"id" validate:"required"`
}
type AlertDetail struct {
LicenseId string `json:"licenseId"`
Type string `json:"type"`
SubType string `json:"subType"`
Title string `json:"title"`
Method string `json:"method"`
LicenseCode string `json:"licenseCode"`
DeviceId string `json:"deviceId"`
Project string `json:"project"`
Params []Param `json:"params"`
Phone string `json:"phone"`
}
type AlertRule 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"`
}
type Param struct {
Index string `json:"index"`
Key string `json:"key"`
Value string `json:"value"`
}
type ClamDTO struct {
ID uint `json:"id"`
CreatedAt time.Time `json:"createdAt"`
UpdatedAt time.Time `json:"updatedAt"`
Name string `json:"name"`
Status string `json:"status"`
Path string `json:"path"`
}
type CronJobDTO struct {
ID uint `json:"id"`
CreatedAt time.Time `json:"createdAt"`
UpdatedAt time.Time `json:"updatedAt"`
Name string `json:"name"`
Status string `json:"status"`
Type string `json:"type"`
}
type CronJobReq struct {
Name string `json:"name"`
Status string `json:"status"`
Type string `json:"type"`
}
type UpgradeInfo struct {
TestVersion string `json:"testVersion"`
NewVersion string `json:"newVersion"`
LatestVersion string `json:"latestVersion"`
ReleaseNote string `json:"releaseNote"`
}
type AlertDiskInfo struct {
Type string
Mount string
Device string
}
type SyncResult struct {
ID uint `json:"id"`
LicenseID string `json:"licenseId"`
SendStatus string `json:"sendStatus"`
CreateTime string `json:"createTime"`
UpdateTime string `json:"updateTime"`
Remarks string `json:"remarks"`
MsgCount int `json:"msgCount"`
MsgCountMax int `json:"msgCountMax"`
}
type QueryRequest struct {
QueryIds []uint `json:"queryIds"`
LicenseId string `json:"licenseId"`
}
type AlertResponse struct {
Result bool `json:"result"`
Data json.RawMessage `json:"data"`
Message string `json:"message"`
}
type PushResult struct {
RecordId uint `json:"recordId"`
LicenseId string `json:"licenseId"`
}
type UpdateOfflineAlertLog struct {
ID uint `json:"id"`
Status string `json:"status"`
Message string `json:"message"`
RecordId uint `json:"recordId"`
LicenseId string `json:"licenseId"`
}
type SyncOfflineAlertLogDTO struct {
QueryRequest string `json:"queryRequest"`
Ids []uint `json:"ids"`
AlertLogs []model.AlertLog `json:"alertLogs"`
LicenseId string `json:"licenseId"`
}
type OfflineAlertResponse struct {
ID uint `json:"id"`
Type string `json:"type"`
RemoteErr error `json:"remoteErr"`
ResponseStruct AlertResponse `json:"responseStruct"`
AlertLogs []model.AlertLog `json:"alertLogs"`
Ids []uint `json:"ids"`
LicenseId string `json:"licenseId"`
}
type OfflineAlertLogDTO struct {
ID uint `json:"id"`
Type string `json:"type"`
Count uint `json:"count"`
AlertId uint `json:"alertId"`
AlertDetail string `json:"alertDetail"`
AlertRule string `json:"alertRule"`
Status string `json:"status"`
Method string `json:"method"`
Message string `json:"message"`
RecordId uint `json:"recordId"`
LicenseId string `json:"licenseId"`
CreatedAt time.Time `json:"createdAt"`
UpdatedAt time.Time `json:"updatedAt"`
}
type OfflineQueryRequest struct {
Ids []uint `json:"ids"`
QueryIds []uint `json:"queryIds"`
LicenseId string `json:"licenseId"`
}
type AlertConfigUpdate struct {
ID uint `json:"id"`
Type string `json:"type"`
Title string `json:"title"`
Status string `json:"status"`
Config string `json:"config"`
}
type AlertConfigTest struct {
Host string `json:"host"`
Port int `json:"port"`
Sender string `json:"sender"`
Password string `json:"password"`
DisplayName string `json:"displayName"`
Encryption string `json:"encryption"` // "ssl" / "tls" / "none"
Recipient string `json:"recipient"`
}
type AlertSendTimeRange struct {
NoticeAlert struct {
SendTimeRange string `json:"sendTimeRange"`
Type []string `json:"type"`
} `json:"noticeAlert"`
ResourceAlert struct {
SendTimeRange string `json:"sendTimeRange"`
Type []string `json:"type"`
} `json:"resourceAlert"`
}
type AlertCommonConfig struct {
IsOffline string `json:"isOffline"`
AlertSendTimeRange AlertSendTimeRange `json:"alertSendTimeRange"`
}
type AlertSmsConfig struct {
Phone string `json:"phone"`
AlertDailyNum uint `json:"alertDailyNum"`
}
type AlertEmailConfig struct {
Host string `json:"host"`
Port int `json:"port"`
Sender string `json:"sender"`
Password string `json:"password"`
DisplayName string `json:"displayName"`
Encryption string `json:"encryption"` // "ssl" / "tls" / "none"
Recipient string `json:"recipient"`
}
+6 -4
View File
@@ -7,10 +7,11 @@ import (
)
type AppDatabase struct {
ServiceName string `json:"PANEL_DB_HOST"`
DbName string `json:"PANEL_DB_NAME"`
DbUser string `json:"PANEL_DB_USER"`
Password string `json:"PANEL_DB_USER_PASSWORD"`
ServiceName string `json:"PANEL_DB_HOST"`
DbName string `json:"PANEL_DB_NAME"`
DbUser string `json:"PANEL_DB_USER"`
Password string `json:"PANEL_DB_USER_PASSWORD"`
DatabaseName string `json:"DATABASE_NAME"`
}
type AuthParam struct {
@@ -126,6 +127,7 @@ type Locale struct {
ZhHant string `json:"zh-hant" yaml:"zh-hant"`
Zh string `json:"zh"`
Ko string `json:"ko"`
Tr string `json:"tr"`
}
type AppForm struct {
+2 -2
View File
@@ -54,7 +54,7 @@ type BackupOption struct {
}
type CommonBackup struct {
Type string `json:"type" validate:"required,oneof=app mysql mariadb redis website postgresql"`
Type string `json:"type" validate:"required,oneof=app mysql mariadb redis website postgresql mysql-cluster postgresql-cluster redis-cluster"`
Name string `json:"name"`
DetailName string `json:"detailName"`
Secret string `json:"secret"`
@@ -65,7 +65,7 @@ type CommonBackup struct {
}
type CommonRecover struct {
DownloadAccountID uint `json:"downloadAccountID" validate:"required"`
Type string `json:"type" validate:"required,oneof=app mysql mariadb redis website postgresql"`
Type string `json:"type" validate:"required,oneof=app mysql mariadb redis website postgresql mysql-cluster postgresql-cluster redis-cluster"`
Name string `json:"name"`
DetailName string `json:"detailName"`
File string `json:"file"`
+3
View File
@@ -34,6 +34,7 @@ type ClamInfo struct {
Spec string `json:"spec"`
Description string `json:"description"`
AlertCount uint `json:"alertCount"`
AlertMethod string `json:"alertMethod"`
}
type ClamLogSearch struct {
@@ -74,6 +75,7 @@ type ClamCreate struct {
Description string `json:"description"`
AlertCount uint `json:"alertCount"`
AlertTitle string `json:"alertTitle"`
AlertMethod string `json:"alertMethod"`
}
type ClamUpdate struct {
@@ -87,6 +89,7 @@ type ClamUpdate struct {
Description string `json:"description"`
AlertCount uint `json:"alertCount"`
AlertTitle string `json:"alertTitle"`
AlertMethod string `json:"alertMethod"`
}
type ClamUpdateStatus struct {
+3
View File
@@ -28,6 +28,9 @@ type OperationWithName struct {
type OperateByID struct {
ID uint `json:"id" validate:"required"`
}
type OperateByIDs struct {
IDs []uint `json:"ids"`
}
type Operate struct {
Operation string `json:"operation" validate:"required"`
+7
View File
@@ -39,6 +39,11 @@ type ContainerInfo struct {
Websites []string `json:"websites"`
}
type ContainerOptions struct {
Name string `json:"name"`
State string `json:"state"`
}
type ContainerStatus struct {
All uint `json:"all"`
Created uint `json:"created"`
@@ -164,6 +169,8 @@ type ContainerCommit struct {
Comment string `json:"comment"`
Author string `json:"author"`
Pause bool `json:"pause"`
TaskID string `json:"taskID"`
}
type ContainerPrune struct {
+84 -23
View File
@@ -29,25 +29,33 @@ type CronjobOperate struct {
ContainerName string `json:"containerName"`
User string `json:"user"`
ScriptID uint `json:"scriptID"`
AppID string `json:"appID"`
Website string `json:"website"`
ExclusionRules string `json:"exclusionRules"`
DBType string `json:"dbType"`
DBName string `json:"dbName"`
URL string `json:"url"`
IsDir bool `json:"isDir"`
SourceDir string `json:"sourceDir"`
ScriptID uint `json:"scriptID"`
AppID string `json:"appID"`
Website string `json:"website"`
ExclusionRules string `json:"exclusionRules"`
DBType string `json:"dbType"`
DBName string `json:"dbName"`
URL string `json:"url"`
IsDir bool `json:"isDir"`
SourceDir string `json:"sourceDir"`
SnapshotRule SnapshotRule `json:"snapshotRule"`
SourceAccountIDs string `json:"sourceAccountIDs"`
DownloadAccountID uint `json:"downloadAccountID"`
RetainCopies int `json:"retainCopies" validate:"number,min=1"`
RetryTimes int `json:"retryTimes" validate:"number,min=0"`
Timeout uint `json:"timeout" validate:"number,min=1"`
IgnoreErr bool `json:"ignoreErr"`
Secret string `json:"secret"`
AlertCount uint `json:"alertCount"`
AlertTitle string `json:"alertTitle"`
AlertCount uint `json:"alertCount"`
AlertTitle string `json:"alertTitle"`
AlertMethod string `json:"alertMethod"`
}
type SnapshotRule struct {
WithImage bool `json:"withImage"`
IgnoreAppIDs []uint `json:"ignoreAppIDs"`
}
type CronjobUpdateStatus struct {
@@ -87,18 +95,20 @@ type CronjobInfo struct {
ContainerName string `json:"containerName"`
User string `json:"user"`
ScriptID uint `json:"scriptID"`
AppID string `json:"appID"`
Website string `json:"website"`
ExclusionRules string `json:"exclusionRules"`
DBType string `json:"dbType"`
DBName string `json:"dbName"`
URL string `json:"url"`
IsDir bool `json:"isDir"`
SourceDir string `json:"sourceDir"`
RetainCopies int `json:"retainCopies"`
RetryTimes int `json:"retryTimes"`
Timeout uint `json:"timeout"`
ScriptID uint `json:"scriptID"`
AppID string `json:"appID"`
Website string `json:"website"`
ExclusionRules string `json:"exclusionRules"`
DBType string `json:"dbType"`
DBName string `json:"dbName"`
URL string `json:"url"`
IsDir bool `json:"isDir"`
SourceDir string `json:"sourceDir"`
RetainCopies int `json:"retainCopies"`
RetryTimes int `json:"retryTimes"`
Timeout uint `json:"timeout"`
IgnoreErr bool `json:"ignoreErr"`
SnapshotRule SnapshotRule `json:"snapshotRule"`
SourceAccounts []string `json:"sourceAccounts"`
DownloadAccount string `json:"downloadAccount"`
@@ -113,6 +123,57 @@ type CronjobInfo struct {
AlertCount uint `json:"alertCount"`
}
type CronjobImport struct {
Cronjobs []CronjobTrans `json:"cronjobs"`
}
type CronjobTrans struct {
Name string `json:"name"`
Type string `json:"type"`
SpecCustom bool `json:"specCustom"`
Spec string `json:"spec"`
Executor string `json:"executor"`
ScriptMode string `json:"scriptMode"`
Script string `json:"script"`
Command string `json:"command"`
ContainerName string `json:"containerName"`
User string `json:"user"`
URL string `json:"url"`
ScriptName string `json:"scriptName"`
Apps []TransHelper `json:"apps"`
Websites []string `json:"websites"`
DBType string `json:"dbType"`
DBNames []TransHelper `json:"dbName"`
ExclusionRules string `json:"exclusionRules"`
IsDir bool `json:"isDir"`
SourceDir string `json:"sourceDir"`
RetainCopies uint64 `json:"retainCopies"`
RetryTimes uint `json:"retryTimes"`
Timeout uint `json:"timeout"`
IgnoreErr bool `json:"ignoreErr"`
SnapshotRule SnapshotTransHelper `json:"snapshotRule"`
Secret string `json:"secret"`
SourceAccounts []string `json:"sourceAccounts"`
DownloadAccount string `json:"downloadAccount"`
AlertCount uint `json:"alertCount"`
AlertTitle string `json:"alertTitle"`
AlertMethod string `json:"alertMethod"`
}
type TransHelper struct {
Name string `json:"name"`
DetailName string `json:"detailName"`
}
type SnapshotTransHelper struct {
WithImage bool `json:"withImage"`
IgnoreApps []TransHelper `json:"ignoreApps"`
}
type ScriptOptions struct {
ID uint `json:"id"`
Name string `json:"name"`
+4 -1
View File
@@ -74,8 +74,11 @@ type DashboardCurrent struct {
CPUTotal int `json:"cpuTotal"`
MemoryTotal uint64 `json:"memoryTotal"`
MemoryAvailable uint64 `json:"memoryAvailable"`
MemoryUsed uint64 `json:"memoryUsed"`
MemoryFree uint64 `json:"memoryFree"`
MemoryShard uint64 `json:"memoryShard"`
MemoryCache uint64 `json:"memoryCache"`
MemoryAvailable uint64 `json:"memoryAvailable"`
MemoryUsedPercent float64 `json:"memoryUsedPercent"`
SwapMemoryTotal uint64 `json:"swapMemoryTotal"`
+13 -6
View File
@@ -4,14 +4,14 @@ import "time"
// common
type DBConfUpdateByFile struct {
Type string `json:"type" validate:"required,oneof=mysql mariadb postgresql redis"`
Type string `json:"type" validate:"required,oneof=mysql mariadb postgresql redis mysql-cluster postgresql-cluster"`
Database string `json:"database" validate:"required"`
File string `json:"file"`
}
type ChangeDBInfo struct {
ID uint `json:"id"`
From string `json:"from" validate:"required,oneof=local remote"`
Type string `json:"type" validate:"required,oneof=mysql mariadb postgresql"`
Type string `json:"type" validate:"required,oneof=mysql mariadb postgresql mysql-cluster postgresql-cluster"`
Database string `json:"database" validate:"required"`
Value string `json:"value" validate:"required"`
}
@@ -74,19 +74,19 @@ type BindUser struct {
type MysqlLoadDB struct {
From string `json:"from" validate:"required,oneof=local remote"`
Type string `json:"type" validate:"required,oneof=mysql mariadb"`
Type string `json:"type" validate:"required,oneof=mysql mariadb mysql-cluster"`
Database string `json:"database" validate:"required"`
}
type MysqlDBDeleteCheck struct {
ID uint `json:"id" validate:"required"`
Type string `json:"type" validate:"required,oneof=mysql mariadb"`
Type string `json:"type" validate:"required,oneof=mysql mariadb mysql-cluster"`
Database string `json:"database" validate:"required"`
}
type MysqlDBDelete struct {
ID uint `json:"id" validate:"required"`
Type string `json:"type" validate:"required,oneof=mysql mariadb"`
Type string `json:"type" validate:"required,oneof=mysql mariadb mysql-cluster"`
Database string `json:"database" validate:"required"`
ForceDelete bool `json:"forceDelete"`
DeleteBackup bool `json:"deleteBackup"`
@@ -153,7 +153,7 @@ type MysqlVariables struct {
}
type MysqlVariablesUpdate struct {
Type string `json:"type" validate:"required,oneof=mysql mariadb"`
Type string `json:"type" validate:"required,oneof=mysql mariadb mysql-cluster"`
Database string `json:"database" validate:"required"`
Variables []MysqlVariablesUpdateHelper `json:"variables"`
}
@@ -174,6 +174,7 @@ type RedisConfUpdate struct {
Timeout string `json:"timeout"`
Maxclients string `json:"maxclients"`
Maxmemory string `json:"maxmemory"`
DBType string `json:"dbType" validate:"required,oneof=redis redis-cluster"`
}
type RedisConfPersistenceUpdate struct {
Database string `json:"database" validate:"required"`
@@ -181,6 +182,7 @@ type RedisConfPersistenceUpdate struct {
Appendonly string `json:"appendonly"`
Appendfsync string `json:"appendfsync"`
Save string `json:"save"`
DBType string `json:"dbType" validate:"required,oneof=redis redis-cluster"`
}
type RedisConf struct {
@@ -319,3 +321,8 @@ type DatabaseDelete struct {
ForceDelete bool `json:"forceDelete"`
DeleteBackup bool `json:"deleteBackup"`
}
type LoadRedisStatus struct {
Name string `json:"name" validate:"required"`
Type string `json:"type" validate:"required"`
}
+4 -4
View File
@@ -6,8 +6,8 @@ type ImageRepoCreate struct {
Name string `json:"name" validate:"required"`
DownloadUrl string `json:"downloadUrl"`
Protocol string `json:"protocol"`
Username string `json:"username" validate:"max=256"`
Password string `json:"password" validate:"max=256"`
Username string `json:"username"`
Password string `json:"password"`
Auth bool `json:"auth"`
}
@@ -15,8 +15,8 @@ type ImageRepoUpdate struct {
ID uint `json:"id"`
DownloadUrl string `json:"downloadUrl"`
Protocol string `json:"protocol"`
Username string `json:"username" validate:"max=256"`
Password string `json:"password" validate:"max=256"`
Username string `json:"username"`
Password string `json:"password"`
Auth bool `json:"auth"`
}
+6 -5
View File
@@ -92,11 +92,12 @@ type FileWget struct {
}
type FileMove struct {
Type string `json:"type" validate:"required"`
OldPaths []string `json:"oldPaths" validate:"required"`
NewPath string `json:"newPath" validate:"required"`
Name string `json:"name"`
Cover bool `json:"cover"`
Type string `json:"type" validate:"required"`
OldPaths []string `json:"oldPaths" validate:"required"`
NewPath string `json:"newPath" validate:"required"`
Name string `json:"name"`
Cover bool `json:"cover"`
CoverPaths []string `json:"coverPaths"`
}
type FileDownload struct {
+26 -17
View File
@@ -12,15 +12,17 @@ type RuntimeSearch struct {
}
type RuntimeCreate struct {
AppDetailID uint `json:"appDetailId"`
Name string `json:"name"`
Params map[string]interface{} `json:"params"`
Resource string `json:"resource"`
Image string `json:"image"`
Type string `json:"type"`
Version string `json:"version"`
Source string `json:"source"`
CodeDir string `json:"codeDir"`
AppDetailID uint `json:"appDetailId"`
Name string `json:"name"`
Resource string `json:"resource"`
Image string `json:"image"`
Type string `json:"type"`
Version string `json:"version"`
Source string `json:"source"`
CodeDir string `json:"codeDir"`
Remark string `json:"remark"`
Params map[string]interface{} `json:"params"`
NodeConfig
}
@@ -53,14 +55,16 @@ type RuntimeDelete struct {
}
type RuntimeUpdate struct {
Name string `json:"name"`
ID uint `json:"id"`
Params map[string]interface{} `json:"params"`
Image string `json:"image"`
Version string `json:"version"`
Rebuild bool `json:"rebuild"`
Source string `json:"source"`
CodeDir string `json:"codeDir"`
Name string `json:"name"`
ID uint `json:"id"`
Image string `json:"image"`
Version string `json:"version"`
Rebuild bool `json:"rebuild"`
Source string `json:"source"`
CodeDir string `json:"codeDir"`
Remark string `json:"remark"`
Params map[string]interface{} `json:"params"`
NodeConfig
}
@@ -131,3 +135,8 @@ type PHPContainerConfig struct {
Environments []Environment `json:"environments"`
Volumes []Volume `json:"volumes"`
}
type RuntimeRemark struct {
ID uint `json:"id" validate:"required"`
Remark string `json:"remark"`
}
+2
View File
@@ -32,6 +32,8 @@ type WebsiteCreate struct {
TaskID string `json:"taskID"`
ParentWebsiteID uint `json:"parentWebsiteID"`
SiteDir string `json:"siteDir"`
RuntimeConfig
FtpConfig
DataBaseConfig
+14
View File
@@ -129,6 +129,19 @@ type AppInstallDTO struct {
Container string `json:"container"`
}
type AppInstallInfo struct {
ID uint `json:"id"`
Name string `json:"name"`
Version string `json:"version"`
Status string `json:"status"`
Message string `json:"message"`
HttpPort int `json:"HttpPort"`
Container string `json:"container"`
ComposePath string `json:"composePath"`
Env map[string]interface{} `json:"env"`
}
type DatabaseConn struct {
Status string `json:"status"`
Username string `json:"username"`
@@ -158,6 +171,7 @@ type AppParam struct {
ShowValue string `json:"showValue"`
Required bool `json:"required"`
Multiple bool `json:"multiple"`
Label dto.Locale `json:"label"`
}
type AppConfig struct {
+9 -6
View File
@@ -1,8 +1,9 @@
package response
import (
"github.com/1Panel-dev/1Panel/agent/utils/files"
"time"
"github.com/1Panel-dev/1Panel/agent/utils/files"
)
type FileInfo struct {
@@ -37,11 +38,12 @@ type FileWgetRes struct {
}
type FileLineContent struct {
Content string `json:"content"`
End bool `json:"end"`
Path string `json:"path"`
Total int `json:"total"`
Lines []string `json:"lines"`
Content string `json:"content"`
End bool `json:"end"`
Path string `json:"path"`
Total int `json:"total"`
TaskStatus string `json:"taskStatus"`
Lines []string `json:"lines"`
}
type FileExist struct {
@@ -53,6 +55,7 @@ type ExistFileInfo struct {
Path string `json:"path"`
Size int64 `json:"size"`
ModTime time.Time `json:"modTime"`
IsDir bool `json:"isDir"`
}
type UserInfo struct {
+2
View File
@@ -30,6 +30,7 @@ type RuntimeDTO struct {
Volumes []request.Volume `json:"volumes"`
ContainerStatus string `json:"containerStatus"`
Container string `json:"container"`
Remark string `json:"remark"`
}
type PackageScripts struct {
@@ -53,6 +54,7 @@ func NewRuntimeDTO(runtime model.Runtime) RuntimeDTO {
Port: runtime.Port,
Path: runtime.GetPath(),
Container: runtime.ContainerName,
Remark: runtime.Remark,
}
}
-2
View File
@@ -23,8 +23,6 @@ type SettingInfo struct {
AppStoreSyncStatus string `json:"appStoreSyncStatus"`
FileRecycleBin string `json:"fileRecycleBin"`
SnapshotIgnore string `json:"snapshotIgnore"`
}
type SettingUpdate struct {
+11 -5
View File
@@ -18,16 +18,20 @@ type SnapshotCreate struct {
Description string `json:"description" validate:"max=256"`
Secret string `json:"secret"`
InterruptStep string `json:"interruptStep"`
Timeout uint `json:"timeout"`
AppData []DataTree `json:"appData"`
BackupData []DataTree `json:"backupData"`
PanelData []DataTree `json:"panelData"`
WithDockerConf bool `json:"withDockerConf"`
WithMonitorData bool `json:"withMonitorData"`
WithLoginLog bool `json:"withLoginLog"`
WithOperationLog bool `json:"withOperationLog"`
WithSystemLog bool `json:"withSystemLog"`
WithTaskLog bool `json:"withTaskLog"`
IgnoreFiles []string `json:"ignoreFiles"`
}
type SnapshotData struct {
@@ -35,11 +39,13 @@ type SnapshotData struct {
BackupData []DataTree `json:"backupData"`
PanelData []DataTree `json:"panelData"`
WithMonitorData bool `json:"withMonitorData"`
WithLoginLog bool `json:"withLoginLog"`
WithOperationLog bool `json:"withOperationLog"`
WithSystemLog bool `json:"withSystemLog"`
WithTaskLog bool `json:"withTaskLog"`
WithDockerConf bool `json:"withDockerConf"`
WithMonitorData bool `json:"withMonitorData"`
WithLoginLog bool `json:"withLoginLog"`
WithOperationLog bool `json:"withOperationLog"`
WithSystemLog bool `json:"withSystemLog"`
WithTaskLog bool `json:"withTaskLog"`
IgnoreFiles []string `json:"ignoreFiles"`
}
type DataTree struct {
ID string `json:"id"`
+45
View File
@@ -0,0 +1,45 @@
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"`
}
type AlertTask struct {
BaseModel
Type string `gorm:"type:varchar(64);not null" json:"type"`
Quota string `gorm:"type:varchar(64)" json:"quota"`
QuotaType string `gorm:"type:varchar(64)" json:"quotaType"`
Method string `gorm:"type:varchar(64);not null;default:'sms" json:"method"`
}
type AlertLog struct {
BaseModel
Type string `gorm:"type:varchar(64);not null" json:"type"`
Status string `gorm:"type:varchar(64);not null" json:"status"`
AlertId uint `gorm:"type:integer;not null" json:"alertId"`
AlertDetail string `gorm:"type:varchar(256);not null" json:"alertDetail"`
AlertRule string `gorm:"type:varchar(256);not null" json:"alertRule"`
Count uint `gorm:"type:integer;not null" json:"count"`
Message string `gorm:"type:varchar(256);" json:"message"`
RecordId uint `gorm:"type:integer;" json:"recordId"`
LicenseId string `gorm:"type:varchar(256);not null;" json:"licenseId" `
Method string `gorm:"type:varchar(64);not null;default:'sms" json:"method"`
}
type AlertConfig struct {
BaseModel
Type string `gorm:"type:varchar(64);not null" json:"type"`
Title string `gorm:"type:varchar(64);not null" json:"title"`
Status string `gorm:"type:varchar(64);not null" json:"status"`
Config string `gorm:"type:varchar(256);not null" json:"config"`
}
+2
View File
@@ -29,12 +29,14 @@ type Cronjob struct {
URL string `json:"url"`
IsDir bool `json:"isDir"`
SourceDir string `json:"sourceDir"`
SnapshotRule string `json:"snapshotRule"`
ExclusionRules string `json:"exclusionRules"`
SourceAccountIDs string `json:"sourceAccountIDs"`
DownloadAccountID uint `json:"downloadAccountID"`
RetryTimes uint `json:"retryTimes"`
Timeout uint `json:"timeout"`
IgnoreErr bool `json:"ignoreErr"`
RetainCopies uint64 `json:"retainCopies"`
Status string `json:"status"`
+1
View File
@@ -23,6 +23,7 @@ type Runtime struct {
Message string `json:"message"`
CodeDir string `json:"codeDir"`
ContainerName string `json:"containerName"`
Remark string `json:"remark"`
}
func (r *Runtime) GetComposePath() string {
+7 -3
View File
@@ -10,19 +10,23 @@ type Snapshot struct {
Status string `json:"status"`
Message string `json:"message"`
Version string `json:"version"`
Timeout uint `json:"timeout"`
TaskID string `json:"taskID"`
TaskRecoverID string `json:"taskRecoverID"`
TaskRollbackID string `json:"taskRollbackID"`
AppData string `json:"appData"`
PanelData string `json:"panelData"`
BackupData string `json:"backupData"`
AppData string `json:"appData"`
PanelData string `json:"panelData"`
BackupData string `json:"backupData"`
WithDockerConf bool `json:"withDockerConf"`
WithMonitorData bool `json:"withMonitorData"`
WithLoginLog bool `json:"withLoginLog"`
WithOperationLog bool `json:"withOperationLog"`
WithSystemLog bool `json:"withSystemLog"`
WithTaskLog bool `json:"withTaskLog"`
IgnoreFiles string `json:"ignoreFiles"`
InterruptStep string `json:"interruptStep"`
RecoverStatus string `json:"recoverStatus"`
+337
View File
@@ -0,0 +1,337 @@
package repo
import (
"github.com/1Panel-dev/1Panel/agent/app/model"
"github.com/1Panel-dev/1Panel/agent/constant"
"github.com/1Panel-dev/1Panel/agent/global"
"google.golang.org/genproto/googleapis/type/date"
"gorm.io/gorm"
"time"
)
type AlertRepo struct{}
type IAlertRepo interface {
WithByType(alertType string) DBOption
WithByStatusIn(status []string) DBOption
WithByProject(project string) DBOption
WithByCount(count uint) DBOption
WithByAlertId(alertId uint) DBOption
WithByCreateAt(date *date.Date) DBOption
WithByLicenseId(licenseId string) DBOption
WithByRecordId(recordId uint) DBOption
WithByMethod(method string) DBOption
Create(alert *model.Alert) error
Get(opts ...DBOption) (model.Alert, error)
Page(page, size int, opts ...DBOption) (int64, []model.Alert, error)
List(opts ...DBOption) ([]model.Alert, error)
Delete(opts ...DBOption) error
Save(alert *model.Alert) error
Update(maps map[string]interface{}, opts ...DBOption) error
GetLog(opts ...DBOption) (model.AlertLog, error)
CreateLog(alertLog *model.AlertLog) error
PageLog(limit, offset int, opts ...DBOption) (int64, []model.AlertLog, error)
ListLog(opts ...DBOption) ([]model.AlertLog, error)
UpdateLog(id uint, maps map[string]interface{}) error
BatchUpdateLogBy(maps map[string]interface{}, opts ...DBOption) error
DeleteLog(opts ...DBOption) error
CleanAlertLogs() error
CreateAlertTask(alertTaskBase *model.AlertTask) error
DeleteAlertTask(opts ...DBOption) error
GetAlertTask(opts ...DBOption) (model.AlertTask, error)
LoadTaskCount(alertType string, project string, method string) (uint, uint, error)
GetTaskLog(alertType string, alertId uint) (time.Time, error)
GetLicensePushCount(method string) (uint, error)
GetConfig(opts ...DBOption) (model.AlertConfig, error)
AlertConfigList(opts ...DBOption) ([]model.AlertConfig, error)
UpdateAlertConfig(maps map[string]interface{}, opts ...DBOption) error
CreateAlertConfig(config *model.AlertConfig) error
DeleteAlertConfig(opts ...DBOption) error
SyncAll(data []model.AlertConfig) error
}
func NewIAlertRepo() IAlertRepo {
return &AlertRepo{}
}
func (a *AlertRepo) WithByType(alertType string) DBOption {
return func(g *gorm.DB) *gorm.DB {
return g.Where("`type` = ?", alertType)
}
}
func (a *AlertRepo) WithByStatusIn(status []string) DBOption {
return func(g *gorm.DB) *gorm.DB {
return g.Where("status in (?)", status)
}
}
func (a *AlertRepo) WithByCount(count uint) DBOption {
return func(g *gorm.DB) *gorm.DB {
return g.Where("count = ?", count)
}
}
func (a *AlertRepo) WithByProject(project string) DBOption {
return func(g *gorm.DB) *gorm.DB {
return g.Where("project = ?", project)
}
}
func (a *AlertRepo) WithByAlertId(alertId uint) DBOption {
return func(g *gorm.DB) *gorm.DB {
return g.Where("alert_id = ?", alertId)
}
}
func (a *AlertRepo) WithByLicenseId(licenseId string) DBOption {
return func(g *gorm.DB) *gorm.DB {
return g.Where("license_id = ?", licenseId)
}
}
func (a *AlertRepo) WithByRecordId(recordId uint) DBOption {
return func(g *gorm.DB) *gorm.DB {
return g.Where("record_id = ?", recordId)
}
}
func (a *AlertRepo) WithByMethod(method string) DBOption {
return func(g *gorm.DB) *gorm.DB {
return g.Where("method = ?", method)
}
}
func (a *AlertRepo) WithByCreateAt(createAt *date.Date) DBOption {
return func(g *gorm.DB) *gorm.DB {
return g.Where("DATE(created_at) = DATE(?)", createAt)
}
}
func (a *AlertRepo) Create(alert *model.Alert) error {
return global.AlertDB.Model(&model.Alert{}).Create(alert).Error
}
func (a *AlertRepo) Save(alert *model.Alert) error {
return global.AlertDB.Save(alert).Error
}
func (a *AlertRepo) Get(opts ...DBOption) (model.Alert, error) {
var alert model.Alert
db, _ := getAlertDB(opts...)
err := db.First(&alert).Error
return alert, err
}
func (a *AlertRepo) Page(page, size int, opts ...DBOption) (int64, []model.Alert, error) {
var alerts []model.Alert
alertDb, _ := getAlertDB(opts...)
db := alertDb.Model(&model.Alert{})
count := int64(0)
db = db.Count(&count)
err := db.Limit(size).Offset(size * (page - 1)).Find(&alerts).Error
return count, alerts, err
}
func (a *AlertRepo) List(opts ...DBOption) ([]model.Alert, error) {
var alert []model.Alert
db, _ := getAlertDB(opts...)
err := db.Find(&alert).Error
return alert, err
}
func (a *AlertRepo) Update(maps map[string]interface{}, opts ...DBOption) error {
db, _ := getAlertDB(opts...)
return db.Model(&model.Alert{}).Updates(maps).Error
}
func (a *AlertRepo) Delete(opts ...DBOption) error {
db, _ := getAlertDB(opts...)
return db.Delete(&model.Alert{}).Error
}
func (a *AlertRepo) GetLog(opts ...DBOption) (model.AlertLog, error) {
var alertLog model.AlertLog
db, _ := getAlertDB(opts...)
err := db.First(&alertLog).Error
return alertLog, err
}
func (a *AlertRepo) CreateLog(log *model.AlertLog) error {
return global.AlertDB.Model(&model.AlertLog{}).Create(&log).Error
}
func (a *AlertRepo) UpdateLog(id uint, maps map[string]interface{}) error {
return global.AlertDB.Model(&model.AlertLog{}).Where("id = ?", id).Updates(maps).Error
}
func (a *AlertRepo) BatchUpdateLogBy(maps map[string]interface{}, opts ...DBOption) error {
db, _ := getAlertDB(opts...)
if len(opts) == 0 {
db = db.Where("1=1")
}
return db.Model(&model.AlertLog{}).Updates(&maps).Error
}
func (a *AlertRepo) PageLog(page, size int, opts ...DBOption) (int64, []model.AlertLog, error) {
var alerts []model.AlertLog
db := global.AlertDB.Model(&model.AlertLog{})
for _, opt := range opts {
db = opt(db)
}
count := int64(0)
db = db.Order("created_at desc").Count(&count)
err := db.Limit(size).Offset(size * (page - 1)).Find(&alerts).Error
return count, alerts, err
}
func (a *AlertRepo) ListLog(opts ...DBOption) ([]model.AlertLog, error) {
var alertLog []model.AlertLog
db, _ := getAlertDB(opts...)
err := db.Find(&alertLog).Error
return alertLog, err
}
func (a *AlertRepo) DeleteLog(opts ...DBOption) error {
db, _ := getAlertDB(opts...)
return db.Delete(&model.AlertLog{}).Error
}
func (a *AlertRepo) CleanAlertLogs() error {
return global.AlertDB.Where("1 = 1").Delete(&model.AlertLog{}).Error
}
func (a *AlertRepo) CreateAlertTask(alertTaskBase *model.AlertTask) error {
return global.AlertDB.Model(&model.AlertTask{}).Create(&alertTaskBase).Error
}
func (a *AlertRepo) DeleteAlertTask(opts ...DBOption) error {
db, _ := getAlertDB(opts...)
return db.Delete(&model.AlertTask{}).Error
}
func (a *AlertRepo) GetAlertTask(opts ...DBOption) (model.AlertTask, error) {
var data model.AlertTask
db, _ := getAlertDB(opts...)
err := db.First(&data).Error
return data, err
}
func (a *AlertRepo) LoadTaskCount(alertType string, project string, method string) (uint, uint, error) {
var (
todayCount int64
totalCount int64
)
_ = global.AlertDB.Model(&model.AlertTask{}).Where("type = ? AND quota_type = ? AND method = ?", alertType, project, method).Count(&totalCount).Error
now := time.Now()
todayMidnight := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, now.Location())
tomorrowMidnight := todayMidnight.Add(24 * time.Hour)
err := global.AlertDB.Model(&model.AlertTask{}).Where("type = ? AND quota_type = ? AND method = ? AND created_at > ? AND created_at < ?", alertType, project, method, todayMidnight, tomorrowMidnight).Count(&todayCount).Error
return uint(todayCount), uint(totalCount), err
}
func (a *AlertRepo) GetTaskLog(alertType string, alertId uint) (time.Time, error) {
var newDate time.Time
status := []string{constant.AlertSuccess, constant.AlertPushSuccess, constant.AlertSyncError, constant.AlertPushing}
now := time.Now()
todayMidnight := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, now.Location())
tomorrowMidnight := todayMidnight.Add(24 * time.Hour)
err := global.AlertDB.Model(&model.AlertLog{}).
Where("type = ? AND alert_id = ? AND status in ? AND created_at > ? AND created_at < ?", alertType, alertId, status, todayMidnight, tomorrowMidnight).
Order("created_at DESC").
Limit(1).
Pluck("created_at", &newDate).Error
if err != nil {
return time.Time{}, err
}
if newDate.IsZero() {
return time.Time{}, nil
}
return newDate, nil
}
func getAlertDB(opts ...DBOption) (*gorm.DB, error) {
var db *gorm.DB
db = global.AlertDB
for _, opt := range opts {
db = opt(db)
}
return db, nil
}
func (a *AlertRepo) GetLicensePushCount(method string) (uint, error) {
var (
todayCount int64
)
now := time.Now()
todayMidnight := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, now.Location())
tomorrowMidnight := todayMidnight.Add(24 * time.Hour)
err := global.AlertDB.Model(&model.AlertTask{}).Where("created_at > ? AND created_at < ? AND method = ?", todayMidnight, tomorrowMidnight, method).Count(&todayCount).Error
return uint(todayCount), err
}
func (a *AlertRepo) AlertConfigList(opts ...DBOption) ([]model.AlertConfig, error) {
var config []model.AlertConfig
db, _ := getAlertDB(opts...)
err := db.Find(&config).Error
return config, err
}
func (a *AlertRepo) UpdateAlertConfig(maps map[string]interface{}, opts ...DBOption) error {
db, _ := getAlertDB(opts...)
return db.Model(&model.AlertConfig{}).Updates(maps).Error
}
func (a *AlertRepo) CreateAlertConfig(config *model.AlertConfig) error {
return global.AlertDB.Model(&model.AlertConfig{}).Create(config).Error
}
func (a *AlertRepo) DeleteAlertConfig(opts ...DBOption) error {
db, _ := getAlertDB(opts...)
return db.Delete(&model.AlertConfig{}).Error
}
func (a *AlertRepo) GetConfig(opts ...DBOption) (model.AlertConfig, error) {
var alertConfig model.AlertConfig
db, _ := getAlertDB(opts...)
err := db.First(&alertConfig).Error
return alertConfig, err
}
func (a *AlertRepo) SyncAll(data []model.AlertConfig) error {
tx := global.AlertDB.Begin()
var oldConfigs []model.AlertConfig
_ = tx.Find(&oldConfigs).Error
oldConfigMap := make(map[string]uint)
for _, item := range oldConfigs {
oldConfigMap[item.Type] = item.ID
}
for _, item := range data {
if val, ok := oldConfigMap[item.Type]; ok {
item.ID = val
delete(oldConfigMap, item.Type)
} else {
item.ID = 0
}
if err := tx.Model(model.AlertConfig{}).Where("id = ?", item.ID).Save(&item).Error; err != nil {
tx.Rollback()
return err
}
}
for _, val := range oldConfigMap {
if err := tx.Where("id = ?", val).Delete(&model.AlertConfig{}).Error; err != nil {
tx.Rollback()
return err
}
}
tx.Commit()
return nil
}
+24 -3
View File
@@ -36,6 +36,7 @@ type IAppInstallRepo interface {
Page(page, size int, opts ...DBOption) (int64, []model.AppInstall, error)
BatchUpdateBy(maps map[string]interface{}, opts ...DBOption) error
LoadBaseInfo(key string, name string) (*RootInfo, error)
LoadInstallAppByKeyAndName(key string, name string) (*model.AppInstall, error)
GetFirstByCtx(ctx context.Context, opts ...DBOption) (model.AppInstall, error)
}
@@ -206,17 +207,17 @@ func (a *AppInstallRepo) LoadBaseInfo(key string, name string) (*RootInfo, error
return nil, err
}
switch app.Key {
case "mysql", "mariadb":
case "mysql", "mariadb", constant.AppMysqlCluster:
password, ok := envMap["PANEL_DB_ROOT_PASSWORD"].(string)
if ok {
info.Password = password
}
case "redis":
case "redis", constant.AppRedisCluster:
password, ok := envMap["PANEL_REDIS_ROOT_PASSWORD"].(string)
if ok {
info.Password = password
}
case "mongodb", constant.AppPostgresql:
case "mongodb", constant.AppPostgresql, constant.AppPostgresqlCluster:
user, ok := envMap["PANEL_DB_ROOT_USER"].(string)
if ok {
info.UserName = user
@@ -246,3 +247,23 @@ func (a *AppInstallRepo) LoadBaseInfo(key string, name string) (*RootInfo, error
info.Status = appInstall.Status
return &info, nil
}
func (a *AppInstallRepo) LoadInstallAppByKeyAndName(key string, name string) (*model.AppInstall, error) {
var (
app model.App
appInstall model.AppInstall
)
if err := global.DB.Where("key = ?", key).First(&app).Error; err != nil {
return nil, err
}
if len(name) == 0 {
if err := global.DB.Where("app_id = ?", app.ID).First(&appInstall).Error; err != nil {
return nil, err
}
} else {
if err := global.DB.Where("app_id = ? AND name = ?", app.ID, name).First(&appInstall).Error; err != nil {
return nil, err
}
}
return &appInstall, nil
}
+507
View File
@@ -0,0 +1,507 @@
package service
import (
"encoding/json"
"fmt"
"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/i18n"
"github.com/1Panel-dev/1Panel/agent/utils/cmd"
"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"
"sort"
"strings"
"sync"
"time"
)
type AlertService struct{}
type IAlertService interface {
PageAlert(req dto.AlertSearch) (int64, []dto.AlertDTO, error)
GetAlerts() ([]dto.AlertDTO, error)
CreateAlert(create dto.AlertCreate) error
UpdateAlert(req dto.AlertUpdate) error
DeleteAlert(id uint) error
GetAlert(id uint) (dto.AlertDTO, error)
UpdateStatus(id uint, status string) error
ExternalUpdateAlert(req dto.AlertCreate) error
GetDisks() ([]dto.DiskDTO, error)
PageAlertLogs(req dto.AlertLogSearch) (int64, []dto.AlertLogDTO, error)
CleanAlertLogs() error
GetClams() ([]dto.ClamDTO, error)
GetCronJobs(req dto.CronJobReq) ([]dto.CronJobDTO, error)
GetAlertConfig() ([]model.AlertConfig, error)
UpdateAlertConfig(req dto.AlertConfigUpdate) error
DeleteAlertConfig(id uint) error
TestAlertConfig(req dto.AlertConfigTest) (bool, error)
}
func NewIAlertService() IAlertService {
return &AlertService{}
}
func (a AlertService) PageAlert(search dto.AlertSearch) (int64, []dto.AlertDTO, error) {
var (
opts []repo.DBOption
result []dto.AlertDTO
)
if search.Status != "" {
opts = append(opts, repo.WithByStatus(search.Status))
}
if search.Type != "" {
opts = append(opts, alertRepo.WithByType(search.Type))
}
opts = append(opts, repo.WithOrderBy("created_at desc"))
total, alerts, err := alertRepo.Page(search.Page, search.PageSize, opts...)
if err != nil {
return 0, nil, err
}
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,
})
}
return total, result, err
}
func (a AlertService) GetAlerts() ([]dto.AlertDTO, error) {
var (
opts []repo.DBOption
result []dto.AlertDTO
)
opts = append(opts, repo.WithByStatus(constant.AlertEnable))
alerts, err := alertRepo.List(opts...)
if err != nil {
return nil, err
}
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,
})
}
return result, err
}
func (a AlertService) CreateAlert(create dto.AlertCreate) error {
var alertID uint
var alertInfo model.Alert
if create.Project != "" {
alertInfo, _ := alertRepo.Get(alertRepo.WithByType(create.Type), alertRepo.WithByProject(create.Project))
alertID = alertInfo.ID
} else {
alertInfo, _ := alertRepo.Get(alertRepo.WithByType(create.Type))
alertID = alertInfo.ID
}
if alertID != 0 {
var upAlert dto.AlertUpdate
if err := copier.Copy(&upAlert, &create); err != nil {
return buserr.WithErr("ErrStructTransform", err)
}
upAlert.ID = alertID
err := a.UpdateAlert(upAlert)
if err != nil {
return err
}
} else {
alertInfo.Status = constant.AlertEnable
if err := copier.Copy(&alertInfo, &create); err != nil {
return buserr.WithErr("ErrStructTransform", err)
}
if err := alertRepo.Create(&alertInfo); err != nil {
return err
}
NewIAlertTaskHelper().InitTask(alertInfo.Type)
}
return nil
}
func (a AlertService) UpdateAlert(req dto.AlertUpdate) error {
upMap := make(map[string]interface{})
upMap["id"] = req.ID
upMap["type"] = req.Type
upMap["cycle"] = req.Cycle
upMap["count"] = req.Count
upMap["method"] = req.Method
upMap["title"] = req.Title
upMap["project"] = req.Project
upMap["status"] = req.Status
upMap["send_count"] = req.SendCount
if err := alertRepo.Update(upMap, repo.WithByID(req.ID)); err != nil {
return err
}
NewIAlertTaskHelper().InitTask(req.Type)
return nil
}
func (a AlertService) DeleteAlert(id uint) error {
return alertRepo.Delete(repo.WithByID(id))
}
func (a AlertService) GetAlert(id uint) (dto.AlertDTO, error) {
var res dto.AlertDTO
alertInfo, err := alertRepo.Get(repo.WithByID(id))
if err != nil {
return res, err
}
_ = copier.Copy(&res, &alertInfo)
return res, nil
}
func (a AlertService) UpdateStatus(id uint, status string) error {
alertInfo, _ := alertRepo.Get(repo.WithByID(id))
if alertInfo.ID == 0 {
return buserr.New("ErrRecordNotFound")
}
err := alertRepo.Update(map[string]interface{}{"status": status}, repo.WithByID(alertInfo.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) GetDisks() ([]dto.DiskDTO, error) {
var disks []dto.DiskDTO
excludes := map[string]struct{}{
"/mnt/cdrom": {}, "/boot": {}, "/boot/efi": {}, "/dev": {}, "/dev/shm": {},
"/run/lock": {}, "/run": {}, "/run/shm": {}, "/run/user": {},
}
stdout, err := executeDiskCommand()
if err != nil {
return disks, nil
}
lines := strings.Split(stdout, "\n")
var mounts []dto.AlertDiskInfo
for _, line := range lines {
fields := strings.Fields(line)
if len(fields) < 7 {
continue
}
mountPoint := strings.Join(fields[6:], " ")
if shouldExclude(fields, mountPoint, excludes) {
continue
}
mounts = append(mounts, dto.AlertDiskInfo{Type: fields[1], Device: fields[0], Mount: mountPoint})
}
var (
wg sync.WaitGroup
mu sync.Mutex
)
wg.Add(len(mounts))
for i := 0; i < len(mounts); i++ {
go func(timeoutCh <-chan time.Time, mount dto.AlertDiskInfo) {
defer wg.Done()
var itemData dto.DiskDTO
itemData.Path = mount.Mount
itemData.Type = mount.Type
itemData.Device = mount.Device
select {
case <-timeoutCh:
mu.Lock()
disks = append(disks, itemData)
mu.Unlock()
global.LOG.Errorf("load disk info from %s failed, err: timeout", mount.Mount)
default:
state, err := disk.Usage(mount.Mount)
if err != nil {
mu.Lock()
disks = append(disks, itemData)
mu.Unlock()
global.LOG.Errorf("load disk info from %s failed, err: %v", mount.Mount, err)
return
}
itemData.Total = state.Total
itemData.Free = state.Free
itemData.Used = state.Used
itemData.UsedPercent = state.UsedPercent
itemData.InodesTotal = state.InodesTotal
itemData.InodesUsed = state.InodesUsed
itemData.InodesFree = state.InodesFree
itemData.InodesUsedPercent = state.InodesUsedPercent
mu.Lock()
disks = append(disks, itemData)
mu.Unlock()
}
}(time.After(5*time.Second), mounts[i])
}
wg.Wait()
sort.Slice(disks, func(i, j int) bool {
return disks[i].Path < disks[j].Path
})
return disks, nil
}
func executeDiskCommand() (string, error) {
cmdMgr := cmd.NewCommandMgr(cmd.WithTimeout(2 * time.Second))
stdout, err := cmdMgr.RunWithStdoutBashC("df -hT -P | grep '/' | grep -v tmpfs | grep -v 'snap/core' | grep -v udev")
if err != nil {
cmdMgr2 := cmd.NewCommandMgr(cmd.WithTimeout(1 * time.Second))
stdout, err = cmdMgr2.RunWithStdoutBashC("df -lhT -P | grep '/' | grep -v tmpfs | grep -v 'snap/core' | grep -v udev")
}
return stdout, err
}
func shouldExclude(fields []string, mountPoint string, excludes map[string]struct{}) bool {
if strings.HasPrefix(mountPoint, "/snap") || len(strings.Split(mountPoint, "/")) > 10 {
return true
}
if strings.TrimSpace(fields[1]) == "tmpfs" {
return true
}
if strings.Contains(fields[2], "K") {
return true
}
if strings.Contains(mountPoint, "docker") {
return true
}
_, excluded := excludes[mountPoint]
return excluded
}
func (a AlertService) PageAlertLogs(search dto.AlertLogSearch) (int64, []dto.AlertLogDTO, error) {
var (
opts []repo.DBOption
result []dto.AlertLogDTO
)
if search.Status != "" {
opts = append(opts, repo.WithByStatus(search.Status))
}
if search.Count != 0 {
opts = append(opts, alertRepo.WithByCount(search.Count))
}
opts = append(opts, repo.WithOrderBy("created_at desc"))
total, alerts, err := alertRepo.PageLog(search.Page, search.PageSize, opts...)
if err != nil {
return 0, nil, err
}
for _, item := range alerts {
alertLogDTO, err := a.parseAlertLog(item)
if err != nil {
return 0, nil, err
}
result = append(result, alertLogDTO)
}
return total, result, err
}
func (a AlertService) parseAlertLog(item model.AlertLog) (dto.AlertLogDTO, error) {
var alertDetail dto.AlertDetail
var alertRule dto.AlertRule
if err := unmarshalAlertInfo(item.AlertDetail, &alertDetail); err != nil {
return dto.AlertLogDTO{}, err
}
if err := unmarshalAlertInfo(item.AlertRule, &alertRule); err != nil {
return dto.AlertLogDTO{}, err
}
return dto.AlertLogDTO{
ID: item.ID,
Count: item.Count,
Type: item.Type,
Status: item.Status,
Method: item.Method,
Message: item.Message,
AlertId: item.AlertId,
AlertDetail: alertDetail,
AlertRule: alertRule,
CreatedAt: item.CreatedAt,
UpdatedAt: item.UpdatedAt,
}, nil
}
func unmarshalAlertInfo(data string, v interface{}) error {
if err := json.Unmarshal([]byte(data), v); err != nil {
return fmt.Errorf("unmarshal alert info vars failed, err: %v", err)
}
return nil
}
func (a AlertService) CleanAlertLogs() error {
return alertRepo.CleanAlertLogs()
}
func (a AlertService) GetClams() ([]dto.ClamDTO, error) {
var clams []dto.ClamDTO
clamList, err := clamRepo.List()
for _, clam := range clamList {
var clamDTO dto.ClamDTO
clamDTO.ID = clam.ID
clamDTO.Name = clam.Name
clamDTO.Path = clam.Path
clamDTO.Status = clam.Status
clamDTO.UpdatedAt = clam.UpdatedAt
clamDTO.CreatedAt = clam.CreatedAt
clams = append(clams, clamDTO)
}
return clams, err
}
func (a AlertService) GetCronJobs(req dto.CronJobReq) ([]dto.CronJobDTO, error) {
var cronJobs []dto.CronJobDTO
var (
opts []repo.DBOption
)
if req.Status != "" {
opts = append(opts, repo.WithByStatus(req.Status))
}
if req.Type != "" {
opts = append(opts, repo.WithByType(req.Type))
}
cronjobList, err := cronjobRepo.List(opts...)
for _, cronJob := range cronjobList {
var cronJobDTO dto.CronJobDTO
cronJobDTO.ID = cronJob.ID
cronJobDTO.Name = cronJob.Name
cronJobDTO.Status = cronJob.Status
cronJobDTO.Type = cronJob.Type
cronJobDTO.UpdatedAt = cronJob.UpdatedAt
cronJobDTO.CreatedAt = cronJob.CreatedAt
cronJobs = append(cronJobs, cronJobDTO)
}
return cronJobs, err
}
func (a AlertService) GetAlertConfig() ([]model.AlertConfig, error) {
var (
opts []repo.DBOption
configs []model.AlertConfig
)
opts = append(opts, repo.WithByStatus(constant.AlertEnable))
configs, err := alertRepo.AlertConfigList(opts...)
return configs, err
}
func (a AlertService) UpdateAlertConfig(req dto.AlertConfigUpdate) error {
if req.ID != 0 {
upMap := make(map[string]interface{})
upMap["id"] = req.ID
upMap["type"] = req.Type
upMap["title"] = req.Title
upMap["status"] = req.Status
upMap["config"] = req.Config
if err := alertRepo.UpdateAlertConfig(upMap, repo.WithByID(req.ID)); err != nil {
return err
}
} else {
var alertConfig model.AlertConfig
if err := copier.Copy(&alertConfig, &req); err != nil {
return buserr.WithErr("ErrStructTransform", err)
}
if err := alertRepo.CreateAlertConfig(&alertConfig); err != nil {
return err
}
}
return nil
}
func (a AlertService) DeleteAlertConfig(id uint) error {
return alertRepo.DeleteAlertConfig(repo.WithByID(id))
}
func (a AlertService) TestAlertConfig(req dto.AlertConfigTest) (bool, error) {
cfg := email.SMTPConfig{
Host: req.Host,
Port: req.Port,
Username: req.Sender,
Password: req.Password,
From: fmt.Sprintf("%s <%s>", req.DisplayName, req.Sender),
Encryption: req.Encryption,
Recipient: req.Recipient,
}
msg := email.EmailMessage{
Subject: i18n.GetMsgByKey("TestAlertTitle"),
Body: i18n.GetMsgByKey("TestAlert"),
IsHTML: false,
}
transport := xpack.LoadRequestTransport()
if err := email.SendMail(cfg, msg, transport); err != nil {
return false, err
}
return true, nil
}
func (a AlertService) ExternalUpdateAlert(updateAlert dto.AlertCreate) error {
upMap := make(map[string]interface{})
if updateAlert.SendCount == 0 {
upMap["status"] = constant.AlertDisable
} else {
upMap["status"] = constant.AlertEnable
upMap["send_count"] = updateAlert.SendCount
}
upMap["method"] = updateAlert.Method
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
}
} else {
updateAlert.Status = constant.AlertEnable
err := a.CreateAlert(updateAlert)
if err != nil {
return err
}
}
return nil
}
+894
View File
@@ -0,0 +1,894 @@
package service
import (
"encoding/json"
"github.com/1Panel-dev/1Panel/agent/app/dto"
"github.com/1Panel-dev/1Panel/agent/app/model"
versionUtil "github.com/1Panel-dev/1Panel/agent/utils/version"
"github.com/1Panel-dev/1Panel/agent/utils/xpack"
"math"
"sort"
"strconv"
"strings"
"time"
"github.com/1Panel-dev/1Panel/agent/app/repo"
"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/common"
"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"
)
type AlertTaskHelper struct {
DiskIO chan []disk.IOCountersStat
NetIO chan []net.IOCountersStat
}
type IAlertTaskHelper interface {
StopTask()
StartTask()
ResetTask()
InitTask(alertType string)
}
var cpuLoad1, cpuLoad5, cpuLoad15 []float64
var memoryLoad1, memoryLoad5, memoryLoad15 []float64
const ResourceAlertInterval = 30
var baseTypes = map[string]bool{"ssl": true, "siteEndTime": true, "panelPwdEndTime": true, "panelUpdate": true}
var resourceTypes = map[string]bool{"cpu": true, "memory": true, "disk": true, "load": true}
func NewIAlertTaskHelper() IAlertTaskHelper {
return &AlertTaskHelper{}
}
func (m *AlertTaskHelper) StartTask() {
baseAlert, resourceAlert := handleTask()
if len(baseAlert) == 0 && len(resourceAlert) == 0 {
return
}
handleBaseAlerts(baseAlert)
handleResourceAlerts(resourceAlert)
}
func (m *AlertTaskHelper) StopTask() {
stopBaseJob()
stopResourceJob()
}
func (m *AlertTaskHelper) ResetTask() {
m.StopTask()
m.StartTask()
}
func (m *AlertTaskHelper) InitTask(alertType string) {
if alertType == "cpu" {
cpuLoad1 = []float64{}
cpuLoad5 = []float64{}
cpuLoad15 = []float64{}
}
if alertType == "memory" {
memoryLoad1 = []float64{}
memoryLoad5 = []float64{}
memoryLoad15 = []float64{}
}
if baseTypes[alertType] {
stopBaseJob()
} else if resourceTypes[alertType] {
stopResourceJob()
}
m.StartTask()
}
func resourceTask(resourceAlert []dto.AlertDTO) {
for _, alert := range resourceAlert {
if !alertUtil.CheckSendTimeRange(alert.Type) {
continue
}
switch alert.Type {
case "cpu":
loadCPUUsage(alert)
case "memory":
loadMemUsage(alert)
case "load":
loadLoadInfo(alert)
case "disk":
loadDiskUsage(alert)
default:
}
}
}
func baseTask(baseAlert []dto.AlertDTO) {
for _, alert := range baseAlert {
if !alertUtil.CheckSendTimeRange(alert.Type) {
continue
}
switch alert.Type {
case "ssl":
loadSSLInfo(alert)
case "siteEndTime":
loadWebsiteInfo(alert)
case "panelPwdEndTime":
if global.IsMaster {
loadPanelPwd(alert)
}
case "panelUpdate":
if global.IsMaster {
loadPanelUpdate(alert)
}
default:
}
}
}
func handleTask() (baseAlert []dto.AlertDTO, resourceAlert []dto.AlertDTO) {
alertList, _ := NewIAlertService().GetAlerts()
baseAlert, resourceAlert = classifyAlerts(alertList)
return baseAlert, resourceAlert
}
func classifyAlerts(alertList []dto.AlertDTO) (baseAlert, resourceAlert []dto.AlertDTO) {
for _, alert := range alertList {
if baseTypes[alert.Type] {
baseAlert = append(baseAlert, alert)
} else if resourceTypes[alert.Type] {
resourceAlert = append(resourceAlert, alert)
}
}
return
}
func handleBaseAlerts(baseAlert []dto.AlertDTO) {
if len(baseAlert) > 0 {
if global.AlertBaseJobID == 0 {
baseTask(baseAlert)
jobID, err := global.Cron.AddFunc("*/30 * * * *", func() {
baseTask(baseAlert)
})
if err != nil {
global.LOG.Errorf("alert base job start failed: %v", err)
return
}
global.AlertBaseJobID = jobID
global.LOG.Info("start alert base job")
}
} else {
stopBaseJob()
}
}
func handleResourceAlerts(resourceAlert []dto.AlertDTO) {
if len(resourceAlert) > 0 {
if global.AlertResourceJobID == 0 {
jobID, err := global.Cron.AddFunc("*/1 * * * *", func() {
resourceTask(resourceAlert)
})
if err != nil {
global.LOG.Errorf("alert resource job start failed: %v", err)
return
}
global.AlertResourceJobID = jobID
global.LOG.Info("start alert resource job")
}
} else {
stopResourceJob()
}
}
func stopBaseJob() {
if global.AlertBaseJobID != 0 {
global.Cron.Remove(global.AlertBaseJobID)
global.AlertBaseJobID = 0
global.LOG.Info("stop alert base job")
}
}
func stopResourceJob() {
if global.AlertResourceJobID != 0 {
global.Cron.Remove(global.AlertResourceJobID)
global.AlertResourceJobID = 0
global.LOG.Info("stop alert resource job")
}
}
func loadSSLInfo(alert dto.AlertDTO) {
var opts []repo.DBOption
if alert.Project != "all" {
itemID, _ := strconv.Atoi(alert.Project)
opts = append(opts, repo.WithByID(uint(itemID)))
}
sslList, _ := repo.NewISSLRepo().List(opts...)
currentDate := time.Now()
daysDifferenceMap := make(map[int][]string)
projectMap := make(map[uint][]time.Time)
for _, ssl := range sslList {
daysDifference := int(ssl.ExpireDate.Sub(currentDate).Hours() / 24)
if daysDifference > 0 && int(alert.Cycle) >= daysDifference {
daysDifferenceMap[daysDifference] = append(daysDifferenceMap[daysDifference], ssl.PrimaryDomain)
projectMap[ssl.ID] = append(projectMap[ssl.ID], ssl.ExpireDate)
}
}
projectJSON := serializeAndSortProjects(projectMap)
if projectJSON == "" {
return
}
if len(daysDifferenceMap) > 0 {
for daysDifference, ssl := range daysDifferenceMap {
primaryDomain := strings.Join(ssl, ",")
var params []dto.Param
params = createAlertBaseParams(strconv.Itoa(len(ssl)), strconv.Itoa(daysDifference))
methods := strings.Split(alert.Method, ",")
for _, m := range methods {
m = strings.TrimSpace(m)
switch m {
case constant.SMS:
todayCount, totalCount, err := alertRepo.LoadTaskCount(alert.Type, projectJSON, constant.SMS)
if err != nil || todayCount >= 1 || alert.SendCount <= totalCount {
continue
}
create := dto.AlertLogCreate{
Status: constant.AlertSuccess,
Count: totalCount + 1,
AlertId: alert.ID,
Type: alert.Type,
}
if !alertUtil.CheckTaskFrequency(constant.SMS) {
continue
}
_ = xpack.CreateSMSAlertLog(alert, create, primaryDomain, params, constant.SMS)
alertUtil.CreateNewAlertTask(alert.Project, alert.Type, projectJSON, constant.SMS)
case constant.Email:
todayCount, totalCount, err := alertRepo.LoadTaskCount(alert.Type, projectJSON, constant.Email)
if err != nil || todayCount >= 1 || alert.SendCount <= totalCount {
continue
}
create := dto.AlertLogCreate{
Status: constant.AlertSuccess,
Count: totalCount + 1,
AlertId: alert.ID,
Type: alert.Type,
}
alertDetail := alertUtil.ProcessAlertDetail(alert, primaryDomain, params, constant.Email)
alertRule := alertUtil.ProcessAlertRule(alert)
create.AlertRule = alertRule
create.AlertDetail = alertDetail
transport := xpack.LoadRequestTransport()
_ = alertUtil.CreateEmailAlertLog(create, alert, params, transport)
alertUtil.CreateNewAlertTask(alert.Project, alert.Type, projectJSON, constant.Email)
default:
}
}
}
global.LOG.Info("SSL alert push successful")
}
}
func loadWebsiteInfo(alert dto.AlertDTO) {
var opts []repo.DBOption
if alert.Project != "all" {
itemID, _ := strconv.Atoi(alert.Project)
opts = append(opts, repo.WithByID(uint(itemID)))
}
websiteList, _ := websiteRepo.List(opts...)
currentDate := time.Now()
daysDifferenceMap := make(map[int][]string)
projectMap := make(map[uint][]time.Time)
for _, website := range websiteList {
daysDifference := int(website.ExpireDate.Sub(currentDate).Hours() / 24)
if daysDifference > 0 && int(alert.Cycle) >= daysDifference {
daysDifferenceMap[daysDifference] = append(daysDifferenceMap[daysDifference], website.PrimaryDomain)
projectMap[website.ID] = append(projectMap[website.ID], website.ExpireDate)
}
}
projectJSON := serializeAndSortProjects(projectMap)
if projectJSON == "" {
return
}
if len(daysDifferenceMap) > 0 {
methods := strings.Split(alert.Method, ",")
for daysDifference, websites := range daysDifferenceMap {
primaryDomain := strings.Join(websites, ",")
var params []dto.Param
params = createAlertBaseParams(strconv.Itoa(len(websites)), strconv.Itoa(daysDifference))
for _, m := range methods {
m = strings.TrimSpace(m)
switch m {
case constant.SMS:
if !alertUtil.CheckTaskFrequency(constant.SMS) {
continue
}
todayCount, totalCount, err := alertRepo.LoadTaskCount(alert.Type, projectJSON, constant.SMS)
if err != nil || todayCount >= 1 || alert.SendCount <= totalCount {
continue
}
create := dto.AlertLogCreate{
Status: constant.AlertSuccess,
Count: totalCount + 1,
AlertId: alert.ID,
Type: alert.Type,
}
_ = xpack.CreateSMSAlertLog(alert, create, primaryDomain, params, constant.SMS)
alertUtil.CreateNewAlertTask(alert.Project, alert.Type, projectJSON, constant.SMS)
case constant.Email:
todayCount, totalCount, err := alertRepo.LoadTaskCount(alert.Type, projectJSON, constant.Email)
if err != nil || todayCount >= 1 || alert.SendCount <= totalCount {
continue
}
create := dto.AlertLogCreate{
Status: constant.AlertSuccess,
Count: totalCount + 1,
AlertId: alert.ID,
Type: alert.Type,
}
alertDetail := alertUtil.ProcessAlertDetail(alert, primaryDomain, params, constant.Email)
alertRule := alertUtil.ProcessAlertRule(alert)
create.AlertDetail = alertDetail
create.AlertRule = alertRule
transport := xpack.LoadRequestTransport()
_ = alertUtil.CreateEmailAlertLog(create, alert, params, transport)
alertUtil.CreateNewAlertTask(alert.Project, alert.Type, projectJSON, constant.Email)
default:
}
}
}
global.LOG.Info("website expiration alert push successful")
}
}
func loadPanelPwd(alert dto.AlertDTO) {
// only master alert
var expirationDays model.Setting
if err := global.CoreDB.Model(&model.Setting{}).Where("key = ?", "ExpirationDays").First(&expirationDays).Error; err != nil {
global.LOG.Errorf("load %s from db setting failed, err: %v", "ExpirationDays", err)
return
}
if expirationDays.Value == "0" {
global.LOG.Info("panel password expiration setting not enabled, skip")
return
}
var expirationTime model.Setting
if err := global.CoreDB.Model(&model.Setting{}).Where("key = ?", "ExpirationTime").First(&expirationTime).Error; err != nil {
global.LOG.Errorf("load %s from db setting failed, err: %v", "ExpirationTime", err)
return
}
var params []dto.Param
defaultDate, _ := time.Parse(constant.DateTimeLayout, expirationTime.Value)
daysDifference := calculateDaysDifference(defaultDate)
if daysDifference >= 0 && int(alert.Cycle) >= daysDifference {
params = createAlertPwdParams(strconv.Itoa(daysDifference))
methods := strings.Split(alert.Method, ",")
for _, m := range methods {
m = strings.TrimSpace(m)
switch m {
case constant.SMS:
if !alertUtil.CheckTaskFrequency(constant.SMS) {
continue
}
todayCount, totalCount, err := alertRepo.LoadTaskCount(alert.Type, expirationTime.Value, constant.SMS)
if err != nil || todayCount >= 1 || alert.SendCount <= totalCount {
continue
}
create := dto.AlertLogCreate{
Count: totalCount + 1,
AlertId: alert.ID,
Type: alert.Type,
}
_ = xpack.CreateSMSAlertLog(alert, create, strconv.Itoa(daysDifference), params, constant.SMS)
alertUtil.CreateNewAlertTask(expirationTime.Value, alert.Type, expirationTime.Value, constant.SMS)
case constant.Email:
todayCount, totalCount, err := alertRepo.LoadTaskCount(alert.Type, expirationTime.Value, constant.Email)
if err != nil || todayCount >= 1 || alert.SendCount <= totalCount {
continue
}
create := dto.AlertLogCreate{
Count: totalCount + 1,
AlertId: alert.ID,
Type: alert.Type,
}
alertDetail := alertUtil.ProcessAlertDetail(alert, strconv.Itoa(daysDifference), params, constant.Email)
alertRule := alertUtil.ProcessAlertRule(alert)
create.AlertRule = alertRule
create.AlertDetail = alertDetail
transport := xpack.LoadRequestTransport()
_ = alertUtil.CreateEmailAlertLog(create, alert, params, transport)
alertUtil.CreateNewAlertTask(expirationTime.Value, alert.Type, expirationTime.Value, constant.Email)
default:
}
}
global.LOG.Info("panel password expiration alert push successful")
}
}
func loadPanelUpdate(alert dto.AlertDTO) {
// only master alert
info, err := versionUtil.GetUpgradeVersionInfo()
if err != nil {
global.LOG.Errorf("error getting version, err: %s", err)
return
}
// 获取版本信息
var version string
// 检查哪个版本字段不为空,并赋值
if info.NewVersion != "" {
version = info.NewVersion
} else if info.TestVersion != "" {
version = info.TestVersion
} else if info.LatestVersion != "" {
version = info.LatestVersion
}
if version == "" {
return
}
var params []dto.Param
methods := strings.Split(alert.Method, ",")
for _, m := range methods {
m = strings.TrimSpace(m)
switch m {
case constant.SMS:
if !alertUtil.CheckTaskFrequency(constant.SMS) {
continue
}
todayCount, totalCount, err := alertRepo.LoadTaskCount(alert.Type, version, constant.SMS)
if err != nil || todayCount >= 1 || alert.SendCount <= totalCount {
continue
}
var create = dto.AlertLogCreate{
Type: alert.Type,
AlertId: alert.ID,
Count: totalCount + 1,
}
_ = xpack.CreateSMSAlertLog(alert, create, version, params, constant.SMS)
alertUtil.CreateNewAlertTask(version, alert.Type, version, constant.SMS)
case constant.Email:
todayCount, totalCount, err := alertRepo.LoadTaskCount(alert.Type, version, constant.Email)
if err != nil || todayCount >= 1 || alert.SendCount <= totalCount {
continue
}
var create = dto.AlertLogCreate{
Type: alert.Type,
AlertId: alert.ID,
Count: totalCount + 1,
}
alertDetail := alertUtil.ProcessAlertDetail(alert, version, params, constant.Email)
alertRule := alertUtil.ProcessAlertRule(alert)
create.AlertRule = alertRule
create.AlertDetail = alertDetail
transport := xpack.LoadRequestTransport()
_ = alertUtil.CreateEmailAlertLog(create, alert, params, transport)
alertUtil.CreateNewAlertTask(version, alert.Type, version, constant.Email)
default:
}
}
global.LOG.Info("panel update alert push successful")
}
// 获取 CPU 使用率数据并发送到通道
func loadCPUUsage(alert dto.AlertDTO) {
percent, err := cpu.Percent(3*time.Second, false)
if err != nil {
global.LOG.Errorf("error getting cpu usage, err: %v", err)
return
}
if len(percent) > 0 {
var cpuLoad *[]float64
var threshold int
switch alert.Cycle {
case 1:
cpuLoad = &cpuLoad1
threshold = 1
case 5:
cpuLoad = &cpuLoad5
threshold = 5
case 15:
cpuLoad = &cpuLoad15
threshold = 15
default:
return
}
if checkAndSendAlert(alert, percent[0], cpuLoad, threshold) {
global.LOG.Info("cpu alert push successful")
}
}
}
// 获取内存使用情况数据并发送到通道
func loadMemUsage(alert dto.AlertDTO) {
memStat, err := mem.VirtualMemory()
if err != nil {
global.LOG.Errorf("error getting memory usage, err: %v", err)
return
}
percent := memStat.UsedPercent
var memoryLoad *[]float64
var threshold int
switch alert.Cycle {
case 1:
memoryLoad = &memoryLoad1
threshold = 1
case 5:
memoryLoad = &memoryLoad5
threshold = 5
case 15:
memoryLoad = &memoryLoad15
threshold = 15
default:
return
}
if checkAndSendAlert(alert, percent, memoryLoad, threshold) {
global.LOG.Info("memory alert push successful")
}
}
// 获取系统负载数据并发送到通道
func loadLoadInfo(alert dto.AlertDTO) {
avgStat, err := load.Avg()
if err != nil {
global.LOG.Errorf("error getting load usage, err: %v", err)
return
}
var loadValue float64
CPUTotal, _ := cpu.Counts(true)
switch alert.Cycle {
case 1:
loadValue = avgStat.Load1 / (float64(CPUTotal*2) * 0.75) * 100
case 5:
loadValue = avgStat.Load5 / (float64(CPUTotal*2) * 0.75) * 100
case 15:
loadValue = avgStat.Load15 / (float64(CPUTotal*2) * 0.75) * 100
default:
return
}
newDate, err := alertRepo.GetTaskLog(alert.Type, alert.ID)
if err != nil {
global.LOG.Errorf("task log record not found, err: %v", err)
}
if newDate.IsZero() || calculateMinutesDifference(newDate) > ResourceAlertInterval {
if loadValue >= float64(alert.Count) {
global.LOG.Infof("%d minute load: %f,detail: %v", alert.Cycle, loadValue, avgStat)
createAndLogAlert(alert, loadValue)
global.LOG.Info("load alert task push successful")
}
}
}
// 内存/cpu检查是否需要发送告警并处理相关逻辑
func checkAndSendAlert(alert dto.AlertDTO, currentUsage float64, usageLoad *[]float64, threshold int) bool {
newDate, err := alertRepo.GetTaskLog(alert.Type, alert.ID)
if err != nil {
global.LOG.Errorf("record not found, err: %v", err)
return false
}
*usageLoad = append(*usageLoad, currentUsage)
if len(*usageLoad) > threshold {
*usageLoad = (*usageLoad)[1:]
}
if newDate.IsZero() || calculateMinutesDifference(newDate) > ResourceAlertInterval {
if len(*usageLoad) == threshold {
avgUsage := average(*usageLoad)
if avgUsage >= float64(alert.Count) {
global.LOG.Infof("%d minute %s: %f , usage: %v", threshold, alert.Type, avgUsage, usageLoad)
createAndLogAlert(alert, avgUsage)
return true
}
}
}
return false
}
// 检查是否超过今日发送次数限制
func checkTaskFrequency(alertType, quotaType string, sendCount uint, method string) (uint, bool) {
todayCount, _, err := alertRepo.LoadTaskCount(alertType, quotaType, method)
if err != nil {
global.LOG.Errorf("error getting task info, err: %v", err)
return todayCount, false
}
if todayCount >= sendCount {
return todayCount, false
}
return todayCount, true
}
// 创建告警日志和详情
func createAndLogAlert(alert dto.AlertDTO, avgUsage float64) {
avgUsagePercent := common.FormatPercent(avgUsage)
params := createAlertAvgParams(strconv.Itoa(int(alert.Cycle)), getModule(alert.Type), avgUsagePercent)
methods := strings.Split(alert.Method, ",")
for _, m := range methods {
m = strings.TrimSpace(m)
switch m {
case constant.SMS:
if !alertUtil.CheckTaskFrequency(constant.SMS) {
continue
}
todayCount, isValid := checkTaskFrequency(alert.Type, strconv.Itoa(int(alert.Cycle)), alert.SendCount, constant.SMS)
if !isValid {
continue
}
create := dto.AlertLogCreate{
Status: constant.AlertSuccess,
Count: todayCount + 1,
AlertId: alert.ID,
Type: alert.Type,
}
_ = xpack.CreateSMSAlertLog(alert, create, avgUsagePercent, params, constant.SMS)
alertUtil.CreateNewAlertTask(avgUsagePercent, alert.Type, strconv.Itoa(int(alert.Cycle)), constant.SMS)
case constant.Email:
todayCount, isValid := checkTaskFrequency(alert.Type, strconv.Itoa(int(alert.Cycle)), alert.SendCount, constant.Email)
if !isValid {
continue
}
create := dto.AlertLogCreate{
Status: constant.AlertSuccess,
Count: todayCount + 1,
AlertId: alert.ID,
Type: alert.Type,
}
alertDetail := alertUtil.ProcessAlertDetail(alert, avgUsagePercent, params, constant.Email)
alertRule := alertUtil.ProcessAlertRule(alert)
create.AlertRule = alertRule
create.AlertDetail = alertDetail
transport := xpack.LoadRequestTransport()
_ = alertUtil.CreateEmailAlertLog(create, alert, params, transport)
alertUtil.CreateNewAlertTask(avgUsagePercent, alert.Type, strconv.Itoa(int(alert.Cycle)), constant.Email)
default:
}
}
}
func getModule(alertType string) string {
var module string
switch alertType {
case "cpu":
module = " CPU "
case "memory":
module = "内存"
case "load":
module = "负载"
default:
}
return module
}
func loadDiskUsage(alert dto.AlertDTO) {
newDate, err := alertRepo.GetTaskLog(alert.Type, alert.ID)
if err != nil {
global.LOG.Errorf("record not found, err: %v", err)
}
if newDate.IsZero() || calculateMinutesDifference(newDate) > ResourceAlertInterval {
if strings.Contains(alert.Project, "all") {
err = processAllDisks(alert)
} else {
err = processSingleDisk(alert)
}
if err != nil {
global.LOG.Errorf("error processing disk usage, err: %v", err)
}
}
}
func processAllDisks(alert dto.AlertDTO) error {
diskList, err := NewIAlertService().GetDisks()
if err != nil {
global.LOG.Errorf("error getting disk list, err: %v", err)
return err
}
var flag bool
for _, item := range diskList {
if success, err := checkAndCreateDiskAlert(alert, item.Path); err == nil && success {
flag = true
}
}
if flag {
global.LOG.Info("all disk alert push successful")
}
return nil
}
func processSingleDisk(alert dto.AlertDTO) error {
success, err := checkAndCreateDiskAlert(alert, alert.Project)
if err != nil {
return err
}
if success {
global.LOG.Info("disk alert push successful")
}
return nil
}
func checkAndCreateDiskAlert(alert dto.AlertDTO, path string) (bool, error) {
usageStat, err := disk.Usage(path)
if err != nil {
global.LOG.Errorf("error getting disk usage for %s, err: %v", path, err)
return false, err
}
usedTotal, usedStr := calculateUsedTotal(alert.Cycle, usageStat)
commonTotal := float64(alert.Count)
if alert.Cycle == 1 {
commonTotal *= 1024 * 1024 * 1024
}
if usedTotal < commonTotal {
return false, nil
}
global.LOG.Infof("disk「 %s 」usage: %s", path, usedStr)
var params []dto.Param
params = createAlertDiskParams(path, usedStr)
methods := strings.Split(alert.Method, ",")
for _, m := range methods {
m = strings.TrimSpace(m)
switch m {
case constant.SMS:
if !alertUtil.CheckTaskFrequency(constant.SMS) {
continue
}
todayCount, isValid := checkTaskFrequency(alert.Type, alert.Project, alert.SendCount, constant.SMS)
if !isValid {
continue
}
create := dto.AlertLogCreate{
Status: constant.AlertSuccess,
Count: todayCount + 1,
AlertId: alert.ID,
Type: alert.Type,
}
_ = xpack.CreateSMSAlertLog(alert, create, path, params, constant.SMS)
alertUtil.CreateNewAlertTask(strconv.Itoa(int(alert.Cycle)), alert.Type, alert.Project, constant.SMS)
case constant.Email:
todayCount, isValid := checkTaskFrequency(alert.Type, alert.Project, alert.SendCount, constant.Email)
if !isValid {
continue
}
create := dto.AlertLogCreate{
Status: constant.AlertSuccess,
Count: todayCount + 1,
AlertId: alert.ID,
Type: alert.Type,
}
alertDetail := alertUtil.ProcessAlertDetail(alert, path, params, constant.Email)
alertRule := alertUtil.ProcessAlertRule(alert)
create.AlertRule = alertRule
create.AlertDetail = alertDetail
transport := xpack.LoadRequestTransport()
_ = alertUtil.CreateEmailAlertLog(create, alert, params, transport)
alertUtil.CreateNewAlertTask(strconv.Itoa(int(alert.Cycle)), alert.Type, alert.Project, constant.Email)
default:
}
}
return true, nil
}
func calculateUsedTotal(cycle uint, usageStat *disk.UsageStat) (float64, string) {
if cycle == 1 {
return float64(usageStat.Used), common.FormatBytes(usageStat.Used)
}
return usageStat.UsedPercent, common.FormatPercent(usageStat.UsedPercent)
}
func calculateDaysDifference(expirationTime time.Time) int {
currentDate := time.Now()
formattedTime := currentDate.Format(constant.DateTimeLayout)
parsedTime, _ := time.Parse(constant.DateTimeLayout, formattedTime)
timeGap := expirationTime.Sub(parsedTime).Milliseconds()
if timeGap < 0 {
return -1
}
daysDifference := int(math.Floor(float64(timeGap) / (3600 * 1000 * 24)))
return daysDifference
}
func calculateMinutesDifference(newDate time.Time) int {
now := time.Now()
if newDate.After(now) {
return -1
}
minutesDifference := int(now.Sub(newDate).Minutes())
return minutesDifference
}
func average(arr []float64) float64 {
total := 0.0
for _, v := range arr {
total += v
}
return total / float64(len(arr))
}
func createAlertBaseParams(project, cycle string) []dto.Param {
return []dto.Param{
{
Index: "1",
Key: "project",
Value: project,
},
{
Index: "2",
Key: "cycle",
Value: cycle,
},
}
}
func createAlertPwdParams(cycle string) []dto.Param {
return []dto.Param{
{
Index: "1",
Key: "cycle",
Value: cycle,
},
}
}
func createAlertAvgParams(cycle, module, count string) []dto.Param {
return []dto.Param{
{
Index: "1",
Key: "cycle",
Value: cycle,
},
{
Index: "2",
Key: "module",
Value: module,
},
{
Index: "3",
Key: "count",
Value: count,
},
}
}
func createAlertDiskParams(project, count string) []dto.Param {
return []dto.Param{
{
Index: "1",
Key: "project",
Value: project,
},
{
Index: "2",
Key: "count",
Value: count,
},
}
}
func serializeAndSortProjects(projectMap map[uint][]time.Time) string {
keys := make([]int, 0, len(projectMap))
for k := range projectMap {
keys = append(keys, int(k))
}
sort.Ints(keys)
projectJSON, err := json.Marshal(projectMap)
if err != nil {
global.LOG.Errorf("Failed to serialize projectMap: %v", err)
return ""
}
return string(projectJSON)
}
+1
View File
@@ -483,6 +483,7 @@ func (a AppService) Install(req request.AppInstallCreate) (appInstall *model.App
req.Params["PANEL_DB_HOST"] = database.Address
req.Params["PANEL_DB_PORT"] = database.Port
req.Params["PANEL_DB_HOST_NAME"] = hostName
req.Params["DATABASE_NAME"] = database.Name
}
}
paramByte, err = json.Marshal(req.Params)
+62 -21
View File
@@ -4,7 +4,6 @@ import (
"context"
"encoding/json"
"fmt"
"github.com/docker/docker/api/types/container"
"math"
"net/http"
"os"
@@ -15,28 +14,25 @@ import (
"strconv"
"strings"
"github.com/1Panel-dev/1Panel/agent/utils/files"
"github.com/1Panel-dev/1Panel/agent/utils/req_helper"
"gopkg.in/yaml.v3"
"github.com/1Panel-dev/1Panel/agent/utils/env"
"github.com/1Panel-dev/1Panel/agent/utils/nginx"
"github.com/joho/godotenv"
"github.com/1Panel-dev/1Panel/agent/app/dto"
"github.com/1Panel-dev/1Panel/agent/app/dto/request"
"github.com/1Panel-dev/1Panel/agent/app/dto/response"
"github.com/1Panel-dev/1Panel/agent/buserr"
"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/buserr"
"github.com/1Panel-dev/1Panel/agent/constant"
"github.com/1Panel-dev/1Panel/agent/global"
"github.com/1Panel-dev/1Panel/agent/utils/common"
"github.com/1Panel-dev/1Panel/agent/utils/compose"
"github.com/1Panel-dev/1Panel/agent/utils/docker"
"github.com/1Panel-dev/1Panel/agent/utils/env"
"github.com/1Panel-dev/1Panel/agent/utils/files"
"github.com/1Panel-dev/1Panel/agent/utils/nginx"
"github.com/1Panel-dev/1Panel/agent/utils/req_helper"
"github.com/docker/docker/api/types/container"
"github.com/joho/godotenv"
"github.com/pkg/errors"
"gopkg.in/yaml.v3"
)
type AppInstallService struct {
@@ -60,6 +56,7 @@ type IAppInstallService interface {
UpdateAppConfig(req request.AppConfigUpdate) error
GetInstallList() ([]dto.AppInstallInfo, error)
GetAppInstallInfo(appInstallID uint) (*response.AppInstallInfo, error)
}
func NewIAppInstalledService() IAppInstallService {
@@ -124,7 +121,7 @@ func (a *AppInstallService) Page(req request.AppInstalledSearch) (int64, []respo
}
}
installDTOs, err := handleInstalled(installs, req.Update, req.Sync)
installDTOs, _ := handleInstalled(installs, req.Update, req.Sync)
if req.Update {
total = int64(len(installDTOs))
}
@@ -476,10 +473,20 @@ func (a *AppInstallService) SyncAll(systemInit bool) error {
func (a *AppInstallService) GetServices(key string) ([]response.AppService, error) {
var res []response.AppService
if DatabaseKeys[key] > 0 {
types := []string{key}
if key == constant.AppPostgres {
key = constant.AppPostgresql
}
dbs, _ := databaseRepo.GetList(repo.WithByType(key))
switch key {
case constant.AppMysql:
types = []string{constant.AppMysql, constant.AppMysqlCluster}
case constant.AppPostgresql:
types = []string{constant.AppPostgresql, constant.AppPostgresqlCluster}
case constant.AppRedis:
types = []string{constant.AppRedis, constant.AppRedisCluster}
}
dbs, _ := databaseRepo.GetList(repo.WithTypes(types))
if len(dbs) == 0 {
return res, nil
}
@@ -635,10 +642,14 @@ func (a *AppInstallService) DeleteCheck(installID uint) ([]dto.AppResource, erro
resources, _ := appInstallResourceRepo.GetBy(appInstallResourceRepo.WithLinkId(appInstall.ID), repo.WithByFrom(constant.AppResourceLocal))
for _, resource := range resources {
linkInstall, _ := appInstallRepo.GetFirst(repo.WithByID(resource.AppInstallId))
res = append(res, dto.AppResource{
Type: "app",
Name: linkInstall.Name,
})
if linkInstall.ID > 0 {
res = append(res, dto.AppResource{
Type: "app",
Name: linkInstall.Name,
})
} else {
_ = appInstallResourceRepo.DeleteBy(context.Background(), appInstallResourceRepo.WithAppInstallId(resource.AppInstallId))
}
}
return res, nil
}
@@ -658,7 +669,7 @@ func (a *AppInstallService) GetDefaultConfigByKey(key, name string) (string, err
return "", buserr.New("ErrPathNotFound")
}
if key == constant.AppMysql || key == constant.AppMariaDB {
if key == constant.AppMysql || key == constant.AppMariaDB || key == constant.AppMysqlCluster {
filePath = path.Join(filePath, "my.cnf")
}
if key == constant.AppRedis {
@@ -710,6 +721,7 @@ func (a *AppInstallService) GetParams(id uint) (*response.AppConfig, error) {
}
appParam.LabelZh = form.LabelZh
appParam.LabelEn = form.LabelEn
appParam.Label = form.Label
appParam.Value = v
if form.Type == "service" {
appInstall, _ := appInstallRepo.GetFirst(appInstallRepo.WithServiceName(v.(string)))
@@ -763,6 +775,7 @@ func (a *AppInstallService) GetParams(id uint) (*response.AppConfig, error) {
Values: form.Values,
Multiple: form.Multiple,
Required: form.Required,
Label: form.Label,
})
}
}
@@ -898,3 +911,31 @@ func updateInstallInfoInDB(appKey, appName, param string, value interface{}) err
}
return nil
}
func (a *AppInstallService) GetAppInstallInfo(installID uint) (*response.AppInstallInfo, error) {
appInstall, _ := appInstallRepo.GetFirst(repo.WithByID(installID))
if appInstall.ID == 0 {
return &response.AppInstallInfo{
Status: constant.StatusDeleted,
}, nil
}
_ = syncAppInstallStatus(&appInstall, false)
appInstall, _ = appInstallRepo.GetFirst(repo.WithByID(installID))
var envMap map[string]interface{}
err := json.Unmarshal([]byte(appInstall.Env), &envMap)
if err != nil {
return nil, err
}
res := &response.AppInstallInfo{
ID: appInstall.ID,
Name: appInstall.Name,
Version: appInstall.Version,
Container: appInstall.ContainerName,
HttpPort: appInstall.HttpPort,
Status: appInstall.Status,
Message: appInstall.Message,
Env: envMap,
ComposePath: appInstall.GetComposePath(),
}
return res, nil
}
+103 -142
View File
@@ -5,7 +5,6 @@ import (
"encoding/base64"
"encoding/json"
"fmt"
"github.com/compose-spec/compose-go/v2/types"
"log"
"math"
"net/http"
@@ -19,39 +18,33 @@ import (
"strings"
"time"
"github.com/1Panel-dev/1Panel/agent/app/repo"
"github.com/1Panel-dev/1Panel/agent/utils/nginx"
"github.com/1Panel-dev/1Panel/agent/utils/nginx/parser"
"github.com/1Panel-dev/1Panel/agent/utils/xpack"
"github.com/1Panel-dev/1Panel/agent/app/task"
"github.com/1Panel-dev/1Panel/agent/utils/req_helper"
"github.com/docker/docker/api/types/container"
"github.com/1Panel-dev/1Panel/agent/utils/cmd"
"github.com/1Panel-dev/1Panel/agent/app/api/v2/helper"
"github.com/1Panel-dev/1Panel/agent/app/dto/request"
"github.com/1Panel-dev/1Panel/agent/i18n"
"github.com/subosito/gotenv"
"gopkg.in/yaml.v3"
"github.com/1Panel-dev/1Panel/agent/utils/env"
"github.com/1Panel-dev/1Panel/agent/app/dto/response"
"github.com/1Panel-dev/1Panel/agent/buserr"
"github.com/1Panel-dev/1Panel/agent/app/dto"
"github.com/1Panel-dev/1Panel/agent/app/dto/request"
"github.com/1Panel-dev/1Panel/agent/app/dto/response"
"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/cmd"
"github.com/1Panel-dev/1Panel/agent/utils/common"
"github.com/1Panel-dev/1Panel/agent/utils/compose"
"github.com/1Panel-dev/1Panel/agent/utils/docker"
composeV2 "github.com/1Panel-dev/1Panel/agent/utils/docker"
"github.com/1Panel-dev/1Panel/agent/utils/env"
"github.com/1Panel-dev/1Panel/agent/utils/files"
"github.com/1Panel-dev/1Panel/agent/utils/nginx"
"github.com/1Panel-dev/1Panel/agent/utils/nginx/parser"
"github.com/1Panel-dev/1Panel/agent/utils/req_helper"
"github.com/1Panel-dev/1Panel/agent/utils/xpack"
"github.com/compose-spec/compose-go/v2/types"
"github.com/docker/docker/api/types/container"
"github.com/pkg/errors"
"github.com/subosito/gotenv"
"gopkg.in/yaml.v3"
)
type DatabaseOp string
@@ -123,13 +116,16 @@ func checkPortExist(port int) error {
}
var DatabaseKeys = map[string]uint{
constant.AppMysql: 3306,
constant.AppMariaDB: 3306,
constant.AppPostgresql: 5432,
constant.AppPostgres: 5432,
constant.AppMongodb: 27017,
constant.AppRedis: 6379,
constant.AppMemcached: 11211,
constant.AppMysql: 3306,
constant.AppMariaDB: 3306,
constant.AppPostgresql: 5432,
constant.AppPostgres: 5432,
constant.AppMongodb: 27017,
constant.AppRedis: 6379,
constant.AppMemcached: 11211,
constant.AppMysqlCluster: 3306,
constant.AppPostgresqlCluster: 5432,
constant.AppRedisCluster: 6379,
}
var ToolKeys = map[string]uint{
@@ -145,7 +141,6 @@ func createLink(ctx context.Context, installTask *task.Task, app model.App, appI
}
_ = deleteLink(del)
}
var dbConfig dto.AppDatabase
if DatabaseKeys[app.Key] > 0 {
handleDataBaseApp := func(task *task.Task) error {
database := &model.Database{
@@ -177,11 +172,11 @@ func createLink(ctx context.Context, installTask *task.Task, app model.App, appI
}
switch app.Key {
case constant.AppMysql, constant.AppMariaDB, constant.AppPostgresql, constant.AppMongodb:
case constant.AppMysql, constant.AppMariaDB, constant.AppPostgresql, constant.AppMongodb, constant.AppMysqlCluster, constant.AppPostgresqlCluster:
if password, ok := params["PANEL_DB_ROOT_PASSWORD"]; ok {
if password != "" {
database.Password = password.(string)
if app.Key == "mysql" || app.Key == "mariadb" {
if app.Key == "mysql" || app.Key == "mariadb" || app.Key == constant.AppMysqlCluster {
database.Username = "root"
}
if rootUser, ok := params["PANEL_DB_ROOT_USER"]; ok {
@@ -199,7 +194,7 @@ func createLink(ctx context.Context, installTask *task.Task, app model.App, appI
}
}
case constant.AppRedis:
case constant.AppRedis, constant.AppRedisCluster:
if password, ok := params["PANEL_REDIS_ROOT_PASSWORD"]; ok {
authParam := dto.RedisAuthParam{
RootPassword: "",
@@ -236,6 +231,7 @@ func createLink(ctx context.Context, installTask *task.Task, app model.App, appI
}
}
var dbConfig dto.AppDatabase
if app.Type == "website" || app.Type == "tool" {
paramByte, err := json.Marshal(params)
if err != nil {
@@ -259,8 +255,11 @@ func createLink(ctx context.Context, installTask *task.Task, app model.App, appI
var resourceId uint
if dbConfig.DbName != "" && dbConfig.DbUser != "" && dbConfig.Password != "" {
switch database.Type {
case constant.AppPostgresql, constant.AppPostgres:
case constant.AppPostgresql, constant.AppPostgres, constant.AppPostgresqlCluster:
oldPostgresqlDb, _ := postgresqlRepo.Get(repo.WithByName(dbConfig.DbName), repo.WithByFrom(constant.ResourceLocal))
if oldPostgresqlDb.ID == 0 {
oldPostgresqlDb, _ = postgresqlRepo.Get(repo.WithByName(dbConfig.DbName), postgresqlRepo.WithByPostgresqlName(dbConfig.DatabaseName), repo.WithByFrom(constant.AppResourceRemote))
}
resourceId = oldPostgresqlDb.ID
if oldPostgresqlDb.ID > 0 {
if oldPostgresqlDb.Username != dbConfig.DbUser || oldPostgresqlDb.Password != dbConfig.Password {
@@ -281,8 +280,11 @@ func createLink(ctx context.Context, installTask *task.Task, app model.App, appI
}
resourceId = pgdb.ID
}
case constant.AppMysql, constant.AppMariaDB:
case constant.AppMysql, constant.AppMariaDB, constant.AppMysqlCluster:
oldMysqlDb, _ := mysqlRepo.Get(repo.WithByName(dbConfig.DbName), repo.WithByFrom(constant.ResourceLocal))
if oldMysqlDb.ID == 0 {
oldMysqlDb, _ = mysqlRepo.Get(repo.WithByName(dbConfig.DbName), mysqlRepo.WithByMysqlName(dbConfig.DatabaseName), repo.WithByFrom(constant.AppResourceRemote))
}
resourceId = oldMysqlDb.ID
if oldMysqlDb.ID > 0 {
if oldMysqlDb.Username != dbConfig.DbUser || oldMysqlDb.Password != dbConfig.Password {
@@ -386,21 +388,25 @@ func deleteAppInstall(deleteReq request.AppInstallDelete) error {
}
resources, _ := appInstallResourceRepo.GetBy(appInstallResourceRepo.WithAppInstallId(install.ID))
if deleteReq.DeleteDB && len(resources) > 0 {
del := dto.DelAppLink{
Ctx: ctx,
Install: &install,
ForceDelete: deleteReq.ForceDelete,
Task: uninstallTask,
}
t.LogWithOps(task.TaskDelete, i18n.GetMsgByKey("Database"))
if err = deleteLink(del); err != nil {
t.LogFailedWithOps(task.TaskDelete, i18n.GetMsgByKey("Database"), err)
if !deleteReq.ForceDelete {
return err
if len(resources) > 0 {
if deleteReq.DeleteDB {
del := dto.DelAppLink{
Ctx: ctx,
Install: &install,
ForceDelete: deleteReq.ForceDelete,
Task: uninstallTask,
}
t.LogWithOps(task.TaskDelete, i18n.GetMsgByKey("Database"))
if err = deleteLink(del); err != nil {
t.LogFailedWithOps(task.TaskDelete, i18n.GetMsgByKey("Database"), err)
if !deleteReq.ForceDelete {
return err
}
}
t.LogSuccessWithOps(task.TaskDelete, i18n.GetMsgByKey("Database"))
} else {
_ = appInstallResourceRepo.DeleteBy(ctx, appInstallResourceRepo.WithAppInstallId(install.ID))
}
t.LogSuccessWithOps(task.TaskDelete, i18n.GetMsgByKey("Database"))
}
if DatabaseKeys[install.App.Key] > 0 {
@@ -408,9 +414,9 @@ func deleteAppInstall(deleteReq request.AppInstallDelete) error {
}
switch install.App.Key {
case constant.AppMysql, constant.AppMariaDB:
case constant.AppMysql, constant.AppMariaDB, constant.AppMysqlCluster:
_ = mysqlRepo.Delete(ctx, mysqlRepo.WithByMysqlName(install.Name))
case constant.AppPostgresql:
case constant.AppPostgresql, constant.AppPostgresqlCluster:
_ = postgresqlRepo.Delete(ctx, postgresqlRepo.WithByPostgresqlName(install.Name))
}
@@ -489,17 +495,14 @@ func deleteLink(del dto.DelAppLink) error {
return appInstallResourceRepo.DeleteBy(del.Ctx, appInstallResourceRepo.WithAppInstallId(install.ID))
}
func getUpgradeCompose(install model.AppInstall, detail model.AppDetail) (string, error) {
if detail.DockerCompose == "" {
return "", nil
}
func handleUpgradeCompose(install model.AppInstall, detail model.AppDetail) (map[string]interface{}, error) {
composeMap := make(map[string]interface{})
if err := yaml.Unmarshal([]byte(detail.DockerCompose), &composeMap); err != nil {
return "", err
return nil, err
}
value, ok := composeMap["services"]
if !ok || value == nil {
return "", buserr.New("ErrFileParse")
return nil, buserr.New("ErrFileParse")
}
servicesMap := value.(map[string]interface{})
if len(servicesMap) == 1 {
@@ -517,6 +520,34 @@ func getUpgradeCompose(install model.AppInstall, detail model.AppDetail) (string
delete(servicesMap, oldServiceName)
}
}
serviceValue := servicesMap[install.ServiceName].(map[string]interface{})
oldComposeMap := make(map[string]interface{})
if err := yaml.Unmarshal([]byte(install.DockerCompose), &oldComposeMap); err != nil {
return nil, err
}
oldValue, ok := oldComposeMap["services"]
if !ok || oldValue == nil {
return nil, buserr.New("ErrFileParse")
}
oldValueMap := oldValue.(map[string]interface{})
oldServiceValue := oldValueMap[install.ServiceName].(map[string]interface{})
if oldServiceValue["deploy"] != nil {
serviceValue["deploy"] = oldServiceValue["deploy"]
}
servicesMap[install.ServiceName] = serviceValue
composeMap["services"] = servicesMap
return composeMap, nil
}
func getUpgradeCompose(install model.AppInstall, detail model.AppDetail) (string, error) {
if detail.DockerCompose == "" {
return "", nil
}
composeMap, err := handleUpgradeCompose(install, detail)
if err != nil {
return "", err
}
envs := make(map[string]interface{})
if err := json.Unmarshal([]byte(install.Env), &envs); err != nil {
return "", err
@@ -617,7 +648,7 @@ func upgradeInstall(req request.AppInstallUpgrade) error {
if err != nil {
return err
}
dockerCLi, err := docker.NewClient()
dockerCLi, _ := docker.NewClient()
if req.PullImage {
images, err := composeV2.GetDockerComposeImagesV2(content, []byte(detail.DockerCompose))
if err != nil {
@@ -634,10 +665,7 @@ func upgradeInstall(req request.AppInstallUpgrade) error {
}
command := exec.Command("/bin/bash", "-c", fmt.Sprintf("cp -rn %s/* %s || true", detailDir, install.GetPath()))
stdout, _ := command.CombinedOutput()
if stdout != nil {
t.Logger.Printf("upgrade app [%s] [%s] cp file log : %s ", install.App.Key, install.Name, string(stdout))
}
_, _ = command.CombinedOutput()
sourceScripts := path.Join(detailDir, "scripts")
if fileOp.Stat(sourceScripts) {
dstScripts := path.Join(install.GetPath(), "scripts")
@@ -674,63 +702,6 @@ func upgradeInstall(req request.AppInstallUpgrade) error {
}
envParams := make(map[string]string, len(envs))
handleMap(envs, envParams)
if install.App.Key == "openresty" && install.App.Resource == "remote" && !common.CompareVersion(install.Version, "1.25.3.2-0-1") {
t.Log(i18n.GetMsgByKey("MoveSiteDir"))
siteDir := path.Join(global.Dir.DataDir, "www")
envParams["WEBSITE_DIR"] = siteDir
oldSiteDir := path.Join(install.GetPath(), "www")
t.Log(i18n.GetWithName("MoveSiteToDir", siteDir))
if err := fileOp.CopyDir(oldSiteDir, global.Dir.DataDir); err != nil {
t.Log(i18n.GetMsgByKey("ErrMoveSiteDir"))
return err
}
newConfDir := path.Join(global.Dir.DataDir, "www", "conf.d")
_ = fileOp.CreateDir(newConfDir, constant.DirPerm)
oldConfDir := path.Join(install.GetPath(), "conf/conf.d")
items, err := os.ReadDir(oldConfDir)
if err != nil {
return err
}
for _, item := range items {
itemPath := path.Join(oldConfDir, item.Name())
if item.IsDir() {
_ = fileOp.Mv(itemPath, newConfDir)
}
if item.Name() != "default.conf" && item.Name() != "00.default.conf" {
_ = fileOp.Mv(itemPath, newConfDir)
}
}
nginxConfPath := path.Join(install.GetPath(), "conf", "nginx.conf")
parse, err := parser.NewParser(nginxConfPath)
if err != nil {
return err
}
config, err := parse.Parse()
if err != nil {
return err
}
config.FilePath = nginxConfPath
httpDirective := config.FindHttp()
httpDirective.UpdateDirective("include", []string{"/usr/local/openresty/nginx/conf/default/*.conf"})
if err = nginx.WriteConfig(config, nginx.IndentedStyle); err != nil {
return buserr.WithErr("ErrUpdateBuWebsite", err)
}
t.Log(i18n.GetMsgByKey("DeleteRuntimePHP"))
_ = fileOp.DeleteDir(path.Join(global.Dir.RuntimeDir, "php"))
websites, _ := websiteRepo.List(repo.WithByType("runtime"))
for _, website := range websites {
runtime, _ := runtimeRepo.GetFirst(context.Background(), repo.WithByID(website.RuntimeID))
if runtime != nil && runtime.Type == "php" {
website.Type = constant.Static
website.RuntimeID = 0
_ = websiteRepo.SaveWithoutCtx(&website)
}
}
_ = runtimeRepo.DeleteBy(repo.WithByType("php"))
t.Log(i18n.GetMsgByKey("MoveSiteDirSuccess"))
}
if err = env.Write(envParams, install.GetEnvPath()); err != nil {
return err
}
@@ -1003,14 +974,16 @@ func runScript(task *task.Task, appInstall *model.AppInstall, operate string) er
case "uninstall":
scriptPath = path.Join(workDir, "scripts", "uninstall.sh")
}
if !files.NewFileOp().Stat(scriptPath) {
fileOp := files.NewFileOp()
if !fileOp.Stat(scriptPath) {
return nil
}
_ = fileOp.ChmodR(scriptPath, constant.DirPerm, false)
logStr := i18n.GetWithName("ExecShell", operate)
task.LogStart(logStr)
cmdMgr := cmd.NewCommandMgr(cmd.WithTimeout(10*time.Minute), cmd.WithScriptPath(scriptPath), cmd.WithWorkDir(workDir))
out, err := cmdMgr.RunWithStdout("bash")
cmdMgr := cmd.NewCommandMgr(cmd.WithTimeout(10*time.Minute), cmd.WithWorkDir(workDir))
out, err := cmdMgr.RunWithStdoutBashCf(scriptPath)
if err != nil {
if out != "" {
err = errors.New(out)
@@ -1055,12 +1028,12 @@ func upApp(task *task.Task, appInstall *model.AppInstall, pullImages bool) error
errMsg string
)
if pullImages && appInstall.App.Type != "php" {
projectName := strings.ToLower(appInstall.Name)
//projectName := strings.ToLower(appInstall.Name)
envByte, err := files.NewFileOp().GetContent(appInstall.GetEnvPath())
if err != nil {
return err
}
images, err := composeV2.GetDockerComposeImages(projectName, envByte, []byte(appInstall.DockerCompose))
images, err := composeV2.GetImagesFromDockerCompose(envByte, []byte(appInstall.DockerCompose))
if err != nil {
return err
}
@@ -1525,7 +1498,7 @@ func handleInstalled(appInstallList []model.AppInstall, updated bool, sync bool)
continue
}
lastVersion := versions[0]
if app.Key == constant.AppMysql {
if app.Key == constant.AppMysql || app.Key == constant.AppMysqlCluster {
for _, version := range versions {
majorVersion := getMajorVersion(installed.Version)
if !strings.HasPrefix(version, majorVersion) {
@@ -1802,10 +1775,7 @@ func ignoreUpdate(installed model.AppInstall) bool {
return true
}
ignores, _ := appIgnoreUpgradeRepo.List(appDetailRepo.WithAppId(installed.AppId), appIgnoreUpgradeRepo.WithScope("all"))
if len(ignores) > 0 {
return true
}
return false
return len(ignores) > 0
}
func RequestDownloadCallBack(downloadCallBackUrl string) {
@@ -1830,24 +1800,15 @@ func getAppTags(appID uint, lang string) ([]response.TagDTO, error) {
}
var res []response.TagDTO
for _, t := range tags {
if t.Name != "" {
var translations = make(map[string]string)
_ = json.Unmarshal([]byte(t.Translations), &translations)
if name, ok := translations[lang]; ok {
tagDTO := response.TagDTO{
ID: t.ID,
Key: t.Key,
Name: t.Name,
Name: name,
}
res = append(res, tagDTO)
} else {
var translations = make(map[string]string)
_ = json.Unmarshal([]byte(t.Translations), &translations)
if name, ok := translations[lang]; ok {
tagDTO := response.TagDTO{
ID: t.ID,
Key: t.Key,
Name: name,
}
res = append(res, tagDTO)
}
}
}
return res, nil
+6 -2
View File
@@ -290,8 +290,6 @@ func (u *BackupService) RefreshToken(req dto.OperateByID) error {
switch backup.Type {
case constant.OneDrive:
refreshToken, err = client.RefreshToken("refresh_token", "refreshToken", varMap)
case constant.GoogleDrive:
refreshToken, err = client.RefreshGoogleToken("refresh_token", "refreshToken", varMap)
case constant.ALIYUN:
refreshToken, err = client.RefreshALIToken(varMap)
}
@@ -543,11 +541,17 @@ func changeLocalBackup(oldPath, newPath string) error {
return err
}
}
if fileOp.Stat(path.Join(oldPath, "master")) {
if err := fileOp.CopyDir(path.Join(oldPath, "master"), newPath); err != nil {
return err
}
}
_ = fileOp.RmRf(path.Join(oldPath, "app"))
_ = fileOp.RmRf(path.Join(oldPath, "database"))
_ = fileOp.RmRf(path.Join(oldPath, "directory"))
_ = fileOp.RmRf(path.Join(oldPath, "system_snapshot"))
_ = fileOp.RmRf(path.Join(oldPath, "website"))
_ = fileOp.RmRf(path.Join(oldPath, "log"))
_ = fileOp.RmRf(path.Join(oldPath, "master"))
return nil
}
+1 -2
View File
@@ -5,7 +5,6 @@ import (
"fmt"
"os"
"path"
"strings"
"sync"
"github.com/1Panel-dev/1Panel/agent/app/dto"
@@ -194,7 +193,7 @@ func (u *BackupRecordService) ListFiles(req dto.OperateByID) []string {
var datas []string
for _, file := range files {
fileName := path.Base(file)
if len(file) != 0 && (strings.HasPrefix(fileName, "1panel-v2.") || strings.HasPrefix(fileName, "snapshot-1panel-v2.")) {
if len(file) != 0 && checkSnapshotIsOk(fileName) {
datas = append(datas, path.Base(file))
}
}
+8 -7
View File
@@ -23,7 +23,7 @@ import (
)
func (u *BackupService) RedisBackup(req dto.CommonBackup) error {
redisInfo, err := appInstallRepo.LoadBaseInfo("redis", req.Name)
redisInfo, err := appInstallRepo.LoadBaseInfo(req.Type, req.Name)
if err != nil {
return err
}
@@ -64,7 +64,7 @@ func (u *BackupService) RedisBackup(req dto.CommonBackup) error {
}
func (u *BackupService) RedisRecover(req dto.CommonRecover) error {
redisInfo, err := appInstallRepo.LoadBaseInfo("redis", req.Name)
redisInfo, err := appInstallRepo.LoadBaseInfo(req.Type, req.Name)
if err != nil {
return err
}
@@ -76,6 +76,7 @@ func (u *BackupService) RedisRecover(req dto.CommonRecover) error {
}
func handleRedisBackup(redisInfo *repo.RootInfo, parentTask *task.Task, backupDir, fileName, secret, taskID string) error {
var (
err error
itemTask *task.Task
@@ -102,7 +103,7 @@ func handleRedisBackup(redisInfo *repo.RootInfo, parentTask *task.Task, backupDi
}
if strings.HasSuffix(fileName, ".tar.gz") {
redisDataDir := fmt.Sprintf("%s/%s/%s/data/appendonlydir", global.Dir.AppInstallDir, "redis", redisInfo.Name)
redisDataDir := fmt.Sprintf("%s/%s/%s/data/appendonlydir", global.Dir.AppInstallDir, redisInfo.Key, redisInfo.Name)
if err := fileOp.TarGzCompressPro(true, redisDataDir, path.Join(backupDir, fileName), secret, ""); err != nil {
return err
}
@@ -111,14 +112,14 @@ func handleRedisBackup(redisInfo *repo.RootInfo, parentTask *task.Task, backupDi
if strings.HasSuffix(fileName, ".aof") {
stdout1, err := cmd.RunDefaultWithStdoutBashCf("docker cp %s:/data/appendonly.aof %s/%s", redisInfo.ContainerName, backupDir, fileName)
if err != nil {
return errors.New(string(stdout1))
return errors.New(stdout1)
}
return nil
}
stdout1, err1 := cmd.RunDefaultWithStdoutBashCf("docker cp %s:/data/dump.rdb %s/%s", redisInfo.ContainerName, backupDir, fileName)
if err1 != nil {
return errors.New(string(stdout1))
return errors.New(stdout1)
}
return nil
}
@@ -196,12 +197,12 @@ func handleRedisRecover(redisInfo *repo.RootInfo, parentTask *task.Task, recover
}
}()
}
composeDir := fmt.Sprintf("%s/redis/%s", global.Dir.AppInstallDir, redisInfo.Name)
composeDir := fmt.Sprintf("%s/%s/%s", global.Dir.AppInstallDir, redisInfo.Key, redisInfo.Name)
if _, err := compose.Down(composeDir + "/docker-compose.yml"); err != nil {
return err
}
if appendonly == "yes" && strings.HasPrefix(redisInfo.Version, "7.") {
redisDataDir := fmt.Sprintf("%s/%s/%s/data", global.Dir.AppInstallDir, "redis", redisInfo.Name)
redisDataDir := fmt.Sprintf("%s/%s/%s/data", global.Dir.AppInstallDir, redisInfo.Key, redisInfo.Name)
if err := fileOp.TarGzExtractPro(recoverFile, redisDataDir, secret); err != nil {
return err
}
+22 -24
View File
@@ -3,6 +3,7 @@ package service
import (
"bufio"
"fmt"
"github.com/1Panel-dev/1Panel/agent/utils/alert_push"
"os"
"os/exec"
"path"
@@ -158,9 +159,10 @@ func (c *ClamService) SearchWithPage(req dto.SearchClamWithPage) (int64, interfa
AlertType: "clams",
EntryID: datas[i].ID,
}
alertCount := xpack.GetAlert(alertBase)
if alertCount != 0 {
datas[i].AlertCount = alertCount
alertInfo, _ := alertRepo.Get(alertRepo.WithByType(alertBase.AlertType), alertRepo.WithByProject(strconv.Itoa(int(alertBase.EntryID))), repo.WithByStatus(constant.AlertEnable))
datas[i].AlertMethod = alertInfo.Method
if alertInfo.SendCount != 0 {
datas[i].AlertCount = alertInfo.SendCount
} else {
datas[i].AlertCount = 0
}
@@ -191,13 +193,15 @@ func (c *ClamService) Create(req dto.ClamCreate) error {
return err
}
if req.AlertCount != 0 {
createAlert := dto.CreateOrUpdateAlert{
AlertTitle: req.AlertTitle,
AlertCount: req.AlertCount,
AlertType: "clams",
EntryID: clam.ID,
createAlert := dto.AlertCreate{
Title: req.AlertTitle,
SendCount: req.AlertCount,
Method: req.AlertMethod,
Type: "clams",
Project: strconv.Itoa(int(clam.ID)),
Status: constant.AlertEnable,
}
err := xpack.CreateAlert(createAlert)
err := NewIAlertService().CreateAlert(createAlert)
if err != nil {
return err
}
@@ -247,13 +251,14 @@ func (c *ClamService) Update(req dto.ClamUpdate) error {
if err := clamRepo.Update(req.ID, upMap); err != nil {
return err
}
updateAlert := dto.CreateOrUpdateAlert{
AlertTitle: req.AlertTitle,
AlertType: "clams",
AlertCount: req.AlertCount,
EntryID: clam.ID,
updateAlert := dto.AlertCreate{
Title: req.AlertTitle,
SendCount: req.AlertCount,
Method: req.AlertMethod,
Type: "clams",
Project: strconv.Itoa(int(clam.ID)),
}
err := xpack.UpdateAlert(updateAlert)
err := NewIAlertService().ExternalUpdateAlert(updateAlert)
if err != nil {
return err
}
@@ -297,11 +302,7 @@ func (c *ClamService) Delete(req dto.ClamDelete) error {
if err := clamRepo.Delete(repo.WithByID(id)); err != nil {
return err
}
alertBase := dto.AlertBase{
AlertType: "clams",
EntryID: clam.ID,
}
err := xpack.DeleteAlert(alertBase)
err := alertRepo.Delete(alertRepo.WithByProject(strconv.Itoa(int(clam.ID))), alertRepo.WithByType("clams"))
if err != nil {
return err
}
@@ -639,10 +640,7 @@ func handleAlert(stdout, clamName string, clamId uint) {
EntryID: clamId,
Param: strconv.Itoa(infectedFiles),
}
err := xpack.PushAlert(pushAlert)
if err != nil {
global.LOG.Errorf("clamdscan push failed, err: %v", err)
}
_ = alert_push.PushAlert(pushAlert)
break
}
}
+184 -112
View File
@@ -12,6 +12,7 @@ import (
"os"
"os/exec"
"path/filepath"
"regexp"
"sort"
"strconv"
"strings"
@@ -33,7 +34,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/docker"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/build"
"github.com/docker/docker/api/types/container"
"github.com/docker/docker/api/types/filters"
"github.com/docker/docker/api/types/image"
@@ -52,7 +53,7 @@ type ContainerService struct{}
type IContainerService interface {
Page(req dto.PageContainer) (int64, interface{}, error)
List() []string
List() []dto.ContainerOptions
LoadStatus() (dto.ContainerStatus, error)
PageNetwork(req dto.SearchWithPage) (int64, interface{}, error)
ListNetwork() ([]dto.Options, error)
@@ -61,7 +62,7 @@ type IContainerService interface {
PageCompose(req dto.SearchWithPage) (int64, interface{}, error)
CreateCompose(req dto.ComposeCreate) error
ComposeOperation(req dto.ComposeOperation) error
ContainerCreate(req dto.ContainerOperate) error
ContainerCreate(req dto.ContainerOperate, inThread bool) error
ContainerCreateByCommand(req dto.ContainerCreateByCommand) error
ContainerUpdate(req dto.ContainerOperate) error
ContainerUpgrade(req dto.ContainerUpgrade) error
@@ -83,7 +84,7 @@ type IContainerService interface {
ComposeUpdate(req dto.ComposeUpdate) error
Prune(req dto.ContainerPrune) (dto.ContainerPruneReport, error)
LoadUsers(req dto.OperationWithName) ([]string, error)
LoadUsers(req dto.OperationWithName) []string
StreamLogs(ctx *gin.Context, params dto.StreamLog)
}
@@ -94,8 +95,8 @@ func NewIContainerService() IContainerService {
func (u *ContainerService) Page(req dto.PageContainer) (int64, interface{}, error) {
var (
records []types.Container
list []types.Container
records []container.Summary
list []container.Summary
)
client, err := docker.NewDockerClient()
if err != nil {
@@ -165,7 +166,7 @@ func (u *ContainerService) Page(req dto.PageContainer) (int64, interface{}, erro
total, start, end := len(list), (req.Page-1)*req.PageSize, req.Page*req.PageSize
if start > total {
records = make([]types.Container, 0)
records = make([]container.Summary, 0)
} else {
if end >= total {
end = total
@@ -221,7 +222,8 @@ func (u *ContainerService) Page(req dto.PageContainer) (int64, interface{}, erro
return int64(total), backDatas, nil
}
func (u *ContainerService) List() []string {
func (u *ContainerService) List() []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)
@@ -230,19 +232,18 @@ func (u *ContainerService) List() []string {
defer client.Close()
containers, err := client.ContainerList(context.Background(), container.ListOptions{All: true})
if err != nil {
global.LOG.Errorf("load contianer list failed, err: %v", err)
global.LOG.Errorf("load container list failed, err: %v", err)
return nil
}
var datas []string
for _, container := range containers {
for _, name := range container.Names {
if len(name) != 0 {
datas = append(datas, strings.TrimPrefix(name, "/"))
options = append(options, dto.ContainerOptions{Name: strings.TrimPrefix(name, "/"), State: container.State})
}
}
}
return datas
return options
}
func (u *ContainerService) LoadStatus() (dto.ContainerStatus, error) {
@@ -307,7 +308,7 @@ func (u *ContainerService) ContainerListStats() ([]dto.ContainerListStats, error
var wg sync.WaitGroup
wg.Add(len(list))
for i := 0; i < len(list); i++ {
go func(item types.Container) {
go func(item container.Summary) {
datas = append(datas, loadCpuAndMem(client, item.ID))
wg.Done()
}(list[i])
@@ -466,7 +467,7 @@ func (u *ContainerService) Prune(req dto.ContainerPrune) (dto.ContainerPruneRepo
report.DeletedNumber = len(rep.VolumesDeleted)
report.SpaceReclaimed = int(rep.SpaceReclaimed)
case "buildcache":
opts := types.BuildCachePruneOptions{}
opts := build.CachePruneOptions{}
opts.All = true
rep, err := client.BuildCachePrune(context.Background(), opts)
if err != nil {
@@ -495,7 +496,7 @@ func (u *ContainerService) LoadResourceLimit() (*dto.ResourceLimit, error) {
return &data, nil
}
func (u *ContainerService) ContainerCreate(req dto.ContainerOperate) error {
func (u *ContainerService) ContainerCreate(req dto.ContainerOperate, inThread bool) error {
client, err := docker.NewDockerClient()
if err != nil {
return err
@@ -513,63 +514,62 @@ func (u *ContainerService) ContainerCreate(req dto.ContainerOperate) error {
return err
}
var wg sync.WaitGroup
wg.Add(1)
go func() {
defer wg.Done()
taskItem.AddSubTask(i18n.GetWithName("ContainerImagePull", req.Image), func(t *task.Task) error {
if !checkImageExist(client, req.Image) || req.ForcePull {
if err := pullImages(taskItem, client, req.Image); err != nil {
if !req.ForcePull {
return err
}
taskItem.AddSubTask(i18n.GetWithName("ContainerImagePull", req.Image), func(t *task.Task) error {
if !checkImageExist(client, req.Image) || req.ForcePull {
if err := pullImages(taskItem, client, req.Image); err != nil {
if !req.ForcePull {
return err
}
}
return nil
}, nil)
taskItem.AddSubTask(i18n.GetMsgByKey("ContainerImageCheck"), func(t *task.Task) error {
imageInfo, _, err := client.ImageInspectWithRaw(ctx, req.Image)
if err != nil {
return err
}
if len(req.Entrypoint) == 0 {
req.Entrypoint = imageInfo.Config.Entrypoint
}
if len(req.Cmd) == 0 {
req.Cmd = imageInfo.Config.Cmd
}
return nil
}, nil)
taskItem.AddSubTask(i18n.GetWithName("ContainerCreate", req.Name), func(t *task.Task) error {
config, hostConf, networkConf, err := loadConfigInfo(true, req, nil)
taskItem.LogWithStatus(i18n.GetMsgByKey("ContainerLoadInfo"), err)
if err != nil {
return err
}
con, err := client.ContainerCreate(ctx, config, hostConf, networkConf, &v1.Platform{}, req.Name)
if err != nil {
taskItem.Log(i18n.GetMsgByKey("ContainerCreateFailed"))
_ = client.ContainerRemove(ctx, req.Name, container.RemoveOptions{RemoveVolumes: true, Force: true})
return err
}
err = client.ContainerStart(ctx, con.ID, container.StartOptions{})
taskItem.LogWithStatus(i18n.GetMsgByKey("ContainerStartCheck"), err)
if err != nil {
taskItem.Log(i18n.GetMsgByKey("ContainerCreateFailed"))
_ = client.ContainerRemove(ctx, req.Name, container.RemoveOptions{RemoveVolumes: true, Force: true})
return fmt.Errorf("create successful but start failed, err: %v", err)
}
return nil
}, nil)
if err := taskItem.Execute(); err != nil {
global.LOG.Error(err.Error())
}
}()
wg.Wait()
return nil
return nil
}, nil)
taskItem.AddSubTask(i18n.GetMsgByKey("ContainerImageCheck"), func(t *task.Task) error {
imageInfo, _, err := client.ImageInspectWithRaw(ctx, req.Image)
if err != nil {
return err
}
if len(req.Entrypoint) == 0 {
req.Entrypoint = imageInfo.Config.Entrypoint
}
if len(req.Cmd) == 0 {
req.Cmd = imageInfo.Config.Cmd
}
return nil
}, nil)
taskItem.AddSubTask(i18n.GetWithName("ContainerCreate", req.Name), func(t *task.Task) error {
config, hostConf, networkConf, err := loadConfigInfo(true, req, nil)
taskItem.LogWithStatus(i18n.GetMsgByKey("ContainerLoadInfo"), err)
if err != nil {
return err
}
con, err := client.ContainerCreate(ctx, config, hostConf, networkConf, &v1.Platform{}, req.Name)
if err != nil {
taskItem.Log(i18n.GetMsgByKey("ContainerCreateFailed"))
_ = client.ContainerRemove(ctx, req.Name, container.RemoveOptions{RemoveVolumes: true, Force: true})
return err
}
err = client.ContainerStart(ctx, con.ID, container.StartOptions{})
taskItem.LogWithStatus(i18n.GetMsgByKey("ContainerStartCheck"), err)
if err != nil {
taskItem.Log(i18n.GetMsgByKey("ContainerCreateFailed"))
_ = client.ContainerRemove(ctx, req.Name, container.RemoveOptions{RemoveVolumes: true, Force: true})
return fmt.Errorf("create successful but start failed, err: %v", err)
}
return nil
}, nil)
if inThread {
go func() {
if err := taskItem.Execute(); err != nil {
global.LOG.Error(err.Error())
}
}()
return nil
}
return taskItem.Execute()
}
func (u *ContainerService) ContainerInfo(req dto.OperationWithName) (*dto.ContainerOperate, error) {
@@ -676,7 +676,7 @@ func (u *ContainerService) ContainerUpdate(req dto.ContainerOperate) error {
taskItem.AddSubTask(i18n.GetWithName("ContainerCreate", req.Name), func(t *task.Task) error {
err := client.ContainerRemove(ctx, req.ContainerID, container.RemoveOptions{Force: true})
taskItem.LogWithStatus(i18n.GetMsgByKey("ContainerRemoveOld"), err)
taskItem.LogWithStatus(i18n.GetWithName("ContainerRemoveOld", req.Name), err)
if err != nil {
return err
}
@@ -752,7 +752,7 @@ func (u *ContainerService) ContainerUpgrade(req dto.ContainerUpgrade) error {
}
}
err := client.ContainerRemove(ctx, req.Name, container.RemoveOptions{Force: true})
taskItem.LogWithStatus(i18n.GetMsgByKey("ContainerRemoveOld"), err)
taskItem.LogWithStatus(i18n.GetWithName("ContainerRemoveOld", req.Name), err)
if err != nil {
return err
}
@@ -809,10 +809,23 @@ func (u *ContainerService) ContainerCommit(req dto.ContainerCommit) error {
Pause: req.Pause,
Config: nil,
}
_, err = client.ContainerCommit(ctx, req.ContainerId, options)
taskItem, err := task.NewTaskWithOps(req.NewImageName, task.TaskCommit, task.TaskScopeContainer, req.TaskID, 1)
if err != nil {
return fmt.Errorf("failed to commit container, err: %v", err)
return fmt.Errorf("new task for container commit failed, err: %v", err)
}
go func() {
taskItem.AddSubTask(i18n.GetWithName("TaskCommit", req.NewImageName), func(t *task.Task) error {
res, err := client.ContainerCommit(ctx, req.ContainerId, options)
if err != nil {
return fmt.Errorf("failed to commit container, err: %v", err)
}
taskItem.Log(res.ID)
return nil
}, nil)
_ = taskItem.Execute()
}()
return nil
}
@@ -915,9 +928,6 @@ func collectLogs(done <-chan struct{}, params dto.StreamLog, messageChan chan<-
defer close(messageChan)
defer close(errorChan)
var cmdArgs []string
if params.Type == "compose" {
cmdArgs = []string{"compose", "-f", params.Compose}
}
cmdArgs = append(cmdArgs, "logs")
if params.Follow {
cmdArgs = append(cmdArgs, "-f")
@@ -932,7 +942,20 @@ func collectLogs(done <-chan struct{}, params dto.StreamLog, messageChan chan<-
cmdArgs = append(cmdArgs, params.Container)
}
dockerCmd := exec.Command("docker", cmdArgs...)
var dockerCmd *exec.Cmd
if params.Type == "compose" {
dockerComposCmd := common.GetDockerComposeCommand()
if dockerComposCmd == "docker-compose" {
newCmdArgs := append([]string{"-f", params.Compose}, cmdArgs...)
dockerCmd = exec.Command(dockerComposCmd, newCmdArgs...)
} else {
newCmdArgs := append([]string{"compose", "-f", params.Compose}, cmdArgs...)
dockerCmd = exec.Command("docker", newCmdArgs...)
}
} else {
dockerCmd = exec.Command("docker", cmdArgs...)
}
dockerCmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true}
stdout, err := dockerCmd.StdoutPipe()
@@ -958,7 +981,7 @@ func collectLogs(done <-chan struct{}, params dto.StreamLog, messageChan chan<-
}
}()
scanner := bufio.NewScanner(stdout)
reader := bufio.NewReader(stdout)
processKilled := false
go func() {
@@ -972,19 +995,30 @@ func collectLogs(done <-chan struct{}, params dto.StreamLog, messageChan chan<-
}
}()
for scanner.Scan() {
message := scanner.Text()
for {
line, err := reader.ReadString('\n')
if err != nil {
if err == io.EOF {
if len(line) > 0 {
line = strings.TrimSuffix(line, "\n")
select {
case messageChan <- line:
case <-done:
return
}
}
break
}
errorChan <- fmt.Errorf("reader error: %v", err)
return
}
line = strings.TrimSuffix(line, "\n")
select {
case messageChan <- message:
case messageChan <- line:
case <-done:
return
}
}
if err = scanner.Err(); err != nil && err != io.EOF {
errorChan <- fmt.Errorf("scanner error: %v", err)
}
_ = dockerCmd.Wait()
}
@@ -993,9 +1027,15 @@ func (u *ContainerService) DownloadContainerLogs(containerType, container, since
return buserr.New("ErrCmdIllegal")
}
commandArg := []string{"logs", container}
dockerCommand := global.CONF.DockerConfig.Command
if containerType == "compose" {
commandArg = []string{"compose", "-f", container, "logs"}
if dockerCommand == "docker-compose" {
commandArg = []string{"-f", container, "logs"}
} else {
commandArg = []string{"compose", "-f", container, "logs"}
}
}
if tail != "0" {
commandArg = append(commandArg, "--tail")
commandArg = append(commandArg, tail)
@@ -1004,16 +1044,20 @@ func (u *ContainerService) DownloadContainerLogs(containerType, container, since
commandArg = append(commandArg, "--since")
commandArg = append(commandArg, since)
}
cmd := exec.Command("docker", commandArg...)
stdout, err := cmd.StdoutPipe()
var dockerCmd *exec.Cmd
if containerType == "compose" && dockerCommand == "docker-compose" {
dockerCmd = exec.Command("docker-compose", commandArg...)
} else {
dockerCmd = exec.Command("docker", commandArg...)
}
stdout, err := dockerCmd.StdoutPipe()
if err != nil {
_ = cmd.Process.Signal(syscall.SIGTERM)
_ = dockerCmd.Process.Signal(syscall.SIGTERM)
return err
}
cmd.Stderr = cmd.Stdout
if err := cmd.Start(); err != nil {
_ = cmd.Process.Signal(syscall.SIGTERM)
dockerCmd.Stderr = dockerCmd.Stdout
if err := dockerCmd.Start(); err != nil {
_ = dockerCmd.Process.Signal(syscall.SIGTERM)
return err
}
@@ -1030,9 +1074,11 @@ func (u *ContainerService) DownloadContainerLogs(containerType, container, since
errCh := make(chan error)
go func() {
scanner := bufio.NewScanner(stdout)
var ansiRegex = regexp.MustCompile(`\x1b\[[0-9;?]*[A-Za-z]|\x1b=|\x1b>`)
for scanner.Scan() {
line := scanner.Text()
if _, err := tempFile.WriteString(line + "\n"); err != nil {
cleanLine := ansiRegex.ReplaceAllString(line, "")
if _, err := tempFile.WriteString(cleanLine + "\n"); err != nil {
errCh <- err
return
}
@@ -1093,19 +1139,19 @@ func (u *ContainerService) ContainerStats(id string) (*dto.ContainerStats, error
return &data, nil
}
func (u *ContainerService) LoadUsers(req dto.OperationWithName) ([]string, error) {
func (u *ContainerService) LoadUsers(req dto.OperationWithName) []string {
var users []string
std, err := cmd.RunDefaultWithStdoutBashCf("docker exec %s cat /etc/passwd", req.Name)
if err != nil {
return nil, err
return users
}
var users []string
lines := strings.Split(string(std), "\n")
for _, line := range lines {
if strings.Contains(line, ":") {
users = append(users, strings.Split(line, ":")[0])
}
}
return users, nil
return users
}
func stringsToMap(list []string) map[string]string {
@@ -1194,6 +1240,32 @@ func checkImageExist(client *client.Client, imageItem string) bool {
return false
}
func checkImageLike(client *client.Client, imageName string) bool {
if client == nil {
var err error
client, err = docker.NewDockerClient()
if err != nil {
return false
}
}
images, err := client.ImageList(context.Background(), image.ListOptions{})
if err != nil {
return false
}
for _, img := range images {
for _, tag := range img.RepoTags {
parts := strings.Split(tag, "/")
imageNameWithTag := parts[len(parts)-1]
if imageNameWithTag == imageName {
return true
}
}
}
return false
}
func pullImages(task *task.Task, client *client.Client, imageName string) error {
dockerCli := docker.NewClientWithExist(client)
options := image.PullOptions{}
@@ -1300,7 +1372,7 @@ func checkPortStats(ports []dto.PortHelper) (nat.PortMap, error) {
return portMap, nil
}
func loadConfigInfo(isCreate bool, req dto.ContainerOperate, oldContainer *types.ContainerJSON) (*container.Config, *container.HostConfig, *network.NetworkingConfig, error) {
func loadConfigInfo(isCreate bool, req dto.ContainerOperate, oldContainer *container.InspectResponse) (*container.Config, *container.HostConfig, *network.NetworkingConfig, error) {
var config container.Config
var hostConf container.HostConfig
if !isCreate {
@@ -1383,7 +1455,7 @@ func loadConfigInfo(isCreate bool, req dto.ContainerOperate, oldContainer *types
return &config, &hostConf, &networkConf, nil
}
func reCreateAfterUpdate(name string, client *client.Client, config *container.Config, hostConf *container.HostConfig, networkConf *types.NetworkSettings) {
func reCreateAfterUpdate(name string, client *client.Client, config *container.Config, hostConf *container.HostConfig, networkConf *container.NetworkSettings) {
ctx := context.Background()
var oldNetworkConf network.NetworkingConfig
@@ -1405,7 +1477,7 @@ func reCreateAfterUpdate(name string, client *client.Client, config *container.C
global.LOG.Info("recreate after container update successful")
}
func loadVolumeBinds(binds []types.MountPoint) []dto.VolumeHelper {
func loadVolumeBinds(binds []container.MountPoint) []dto.VolumeHelper {
var datas []dto.VolumeHelper
for _, bind := range binds {
var volumeItem dto.VolumeHelper
@@ -1425,13 +1497,13 @@ func loadVolumeBinds(binds []types.MountPoint) []dto.VolumeHelper {
return datas
}
func loadPortByInspect(id string, client *client.Client) ([]types.Port, error) {
container, err := client.ContainerInspect(context.Background(), id)
func loadPortByInspect(id string, client *client.Client) ([]container.Port, error) {
containerItem, err := client.ContainerInspect(context.Background(), id)
if err != nil {
return nil, err
}
var itemPorts []types.Port
for key, val := range container.ContainerJSONBase.HostConfig.PortBindings {
var itemPorts []container.Port
for key, val := range containerItem.ContainerJSONBase.HostConfig.PortBindings {
if !strings.Contains(string(key), "/") {
continue
}
@@ -1440,15 +1512,15 @@ func loadPortByInspect(id string, client *client.Client) ([]types.Port, error) {
for _, itemVal := range val {
publicPort, _ := strconv.ParseUint(itemVal.HostPort, 10, 16)
itemPorts = append(itemPorts, types.Port{PrivatePort: uint16(itemPort), Type: item[1], PublicPort: uint16(publicPort), IP: itemVal.HostIP})
itemPorts = append(itemPorts, container.Port{PrivatePort: uint16(itemPort), Type: item[1], PublicPort: uint16(publicPort), IP: itemVal.HostIP})
}
}
return itemPorts, nil
}
func transPortToStr(ports []types.Port) []string {
func transPortToStr(ports []container.Port) []string {
var (
ipv4Ports []types.Port
ipv6Ports []types.Port
ipv4Ports []container.Port
ipv6Ports []container.Port
)
for _, port := range ports {
if strings.Contains(port.IP, ":") {
@@ -1461,7 +1533,7 @@ func transPortToStr(ports []types.Port) []string {
list2 := simplifyPort(ipv6Ports)
return append(list1, list2...)
}
func simplifyPort(ports []types.Port) []string {
func simplifyPort(ports []container.Port) []string {
var datas []string
if len(ports) == 0 {
return datas
@@ -1557,7 +1629,7 @@ func loadComposeCount(client *client.Client) int {
return len(composeMap)
}
func loadContainerPortForInfo(itemPorts []types.Port) []dto.PortHelper {
func loadContainerPortForInfo(itemPorts []container.Port) []dto.PortHelper {
var exposedPorts []dto.PortHelper
samePortMap := make(map[string]dto.PortHelper)
ports := transPortToStr(itemPorts)
+2 -3
View File
@@ -5,7 +5,6 @@ import (
"errors"
"fmt"
"os"
"os/exec"
"path"
"sort"
"strings"
@@ -174,7 +173,7 @@ func (u *ContainerService) TestCompose(req dto.ComposeCreate) (bool, error) {
if err := newComposeEnv(req.Path, req.Env); err != nil {
return false, err
}
cmd := exec.Command("docker", "compose", "-f", req.Path, "config")
cmd := getComposeCmd(req.Path, "config")
stdout, err := cmd.CombinedOutput()
if err != nil {
return false, errors.New(string(stdout))
@@ -201,7 +200,7 @@ func (u *ContainerService) CreateCompose(req dto.ComposeCreate) error {
}
go func() {
taskItem.AddSubTask(i18n.GetMsgByKey("ComposeCreate"), func(t *task.Task) error {
cmd := exec.Command("docker", "compose", "-f", req.Path, "up", "-d")
cmd := getComposeCmd(req.Path, "up")
out, err := cmd.CombinedOutput()
taskItem.Log(i18n.GetWithName("ComposeCreateRes", string(out)))
if err != nil {
+339 -27
View File
@@ -1,6 +1,7 @@
package service
import (
"context"
"encoding/json"
"fmt"
"os"
@@ -9,14 +10,13 @@ import (
"strings"
"time"
"github.com/1Panel-dev/1Panel/agent/app/repo"
"github.com/1Panel-dev/1Panel/agent/buserr"
"github.com/1Panel-dev/1Panel/agent/utils/xpack"
"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/docker"
"github.com/jinzhu/copier"
"github.com/pkg/errors"
"github.com/robfig/cron/v3"
@@ -37,6 +37,8 @@ type ICronjobService interface {
StartJob(cronjob *model.Cronjob, isUpdate bool) (string, error)
CleanRecord(req dto.CronjobClean) error
Export(req dto.OperateByIDs) (string, error)
Import(req []dto.CronjobTrans) error
LoadScriptOptions() []dto.ScriptOptions
LoadInfo(req dto.OperateByID) (*dto.CronjobOperate, error)
@@ -67,12 +69,15 @@ func (u *CronjobService) SearchWithPage(search dto.PageCronjob) (int64, interfac
AlertType: cronjob.Type,
EntryID: cronjob.ID,
}
alertCount := xpack.GetAlert(alertBase)
if alertCount != 0 {
item.AlertCount = alertCount
alertInfo, _ := alertRepo.Get(alertRepo.WithByType(alertBase.AlertType), alertRepo.WithByProject(strconv.Itoa(int(alertBase.EntryID))), repo.WithByStatus(constant.AlertEnable))
if alertInfo.SendCount != 0 {
item.AlertCount = alertInfo.SendCount
} else {
item.AlertCount = 0
}
if cronjob.Type == "snapshot" && len(cronjob.SnapshotRule) != 0 {
_ = json.Unmarshal([]byte(cronjob.SnapshotRule), &item.SnapshotRule)
}
dtoCronjobs = append(dtoCronjobs, item)
}
return total, dtoCronjobs, err
@@ -88,15 +93,303 @@ func (u *CronjobService) LoadInfo(req dto.OperateByID) (*dto.CronjobOperate, err
AlertType: cronjob.Type,
EntryID: cronjob.ID,
}
alertCount := xpack.GetAlert(alertBase)
if alertCount != 0 {
item.AlertCount = alertCount
alertInfo, _ := alertRepo.Get(alertRepo.WithByType(alertBase.AlertType), alertRepo.WithByProject(strconv.Itoa(int(alertBase.EntryID))), repo.WithByStatus(constant.AlertEnable))
item.AlertMethod = alertInfo.Method
if alertInfo.SendCount != 0 {
item.AlertCount = alertInfo.SendCount
} else {
item.AlertCount = 0
}
if cronjob.Type == "snapshot" && len(cronjob.SnapshotRule) != 0 {
_ = json.Unmarshal([]byte(cronjob.SnapshotRule), &item.SnapshotRule)
}
return &item, err
}
func (u *CronjobService) Export(req dto.OperateByIDs) (string, error) {
cronjobs, err := cronjobRepo.List(repo.WithByIDs(req.IDs))
if err != nil {
return "", err
}
var data []dto.CronjobTrans
for _, cronjob := range cronjobs {
item := dto.CronjobTrans{
Name: cronjob.Name,
Type: cronjob.Type,
SpecCustom: cronjob.SpecCustom,
Spec: cronjob.Spec,
Executor: cronjob.Executor,
ScriptMode: cronjob.ScriptMode,
Script: cronjob.Script,
Command: cronjob.Command,
ContainerName: cronjob.ContainerName,
User: cronjob.User,
URL: cronjob.URL,
DBType: cronjob.DBType,
ExclusionRules: cronjob.ExclusionRules,
IsDir: cronjob.IsDir,
SourceDir: cronjob.SourceDir,
RetainCopies: cronjob.RetainCopies,
RetryTimes: cronjob.RetryTimes,
Timeout: cronjob.Timeout,
IgnoreErr: cronjob.IgnoreErr,
Secret: cronjob.Secret,
}
switch cronjob.Type {
case "app":
if cronjob.AppID == "all" {
break
}
apps := loadAppsForJob(cronjob)
for _, app := range apps {
item.Apps = append(item.Apps, dto.TransHelper{Name: app.App.Key, DetailName: app.Name})
}
case "website", "cutWebsiteLog":
if cronjob.Website == "all" {
break
}
websites := loadWebsForJob(cronjob)
for _, website := range websites {
item.Websites = append(item.Websites, website.Alias)
}
case "database":
if cronjob.DBName == "all" {
break
}
databases := loadDbsForJob(cronjob)
for _, db := range databases {
item.DBNames = append(item.DBNames, dto.TransHelper{Name: db.Database, DetailName: db.Name})
}
case "shell":
if cronjob.ScriptMode == "library" {
script, err := scriptRepo.Get(repo.WithByID(cronjob.ScriptID))
if err != nil {
return "", err
}
item.ScriptName = script.Name
}
case "snapshot":
if len(cronjob.SnapshotRule) == 0 {
break
}
var snapRule dto.SnapshotRule
if err := json.Unmarshal([]byte(cronjob.SnapshotRule), &snapRule); err != nil {
return "", err
}
item.SnapshotRule.WithImage = snapRule.WithImage
if len(snapRule.IgnoreAppIDs) != 0 {
ignoreApps, _ := appInstallRepo.ListBy(context.Background(), repo.WithByIDs(snapRule.IgnoreAppIDs))
for _, app := range ignoreApps {
item.SnapshotRule.IgnoreApps = append(item.SnapshotRule.IgnoreApps, dto.TransHelper{Name: app.App.Key, DetailName: app.Name})
}
}
}
item.SourceAccounts, item.DownloadAccount, _ = loadBackupNamesByID(cronjob.SourceAccountIDs, cronjob.DownloadAccountID)
alertInfo, _ := alertRepo.Get(alertRepo.WithByType(cronjob.Type), alertRepo.WithByProject(strconv.Itoa(int(cronjob.ID))), repo.WithByStatus(constant.AlertEnable))
if alertInfo.SendCount != 0 {
item.AlertCount = alertInfo.SendCount
item.AlertTitle = alertInfo.Title
item.AlertMethod = alertInfo.Method
} else {
item.AlertCount = 0
}
data = append(data, item)
}
itemJson, err := json.Marshal(&data)
if err != nil {
return "", err
}
return string(itemJson), nil
}
func (u *CronjobService) Import(req []dto.CronjobTrans) error {
for _, item := range req {
cronjobItem, _ := cronjobRepo.Get(repo.WithByName(item.Name))
if cronjobItem.ID != 0 {
continue
}
cronjob := model.Cronjob{
Name: item.Name,
Type: item.Type,
SpecCustom: item.SpecCustom,
Spec: item.Spec,
Executor: item.Executor,
ScriptMode: item.ScriptMode,
Command: item.Command,
ContainerName: item.ContainerName,
User: item.User,
URL: item.URL,
DBType: item.DBType,
ExclusionRules: item.ExclusionRules,
IsDir: item.IsDir,
RetainCopies: item.RetainCopies,
RetryTimes: item.RetryTimes,
Timeout: item.Timeout,
IgnoreErr: item.IgnoreErr,
Secret: item.Secret,
}
hasNotFound := false
switch item.Type {
case "app":
if len(item.Apps) == 0 {
cronjob.AppID = "all"
break
}
var appIDs []string
for _, app := range item.Apps {
appItem, err := appInstallRepo.LoadInstallAppByKeyAndName(app.Name, app.DetailName)
if err != nil {
hasNotFound = true
continue
}
appIDs = append(appIDs, fmt.Sprintf("%v", appItem.ID))
}
cronjob.AppID = strings.Join(appIDs, ",")
case "website", "cutWebsiteLog":
if len(item.Websites) == 0 {
cronjob.Website = "all"
break
}
var webIDs []string
for _, web := range item.Websites {
webItem, err := websiteRepo.GetFirst(websiteRepo.WithAlias(web))
if err != nil {
hasNotFound = true
continue
}
webIDs = append(webIDs, fmt.Sprintf("%v", webItem.ID))
}
cronjob.Website = strings.Join(webIDs, ",")
case "database":
if len(item.DBNames) == 0 {
cronjob.DBName = "all"
break
}
var dbIDs []string
if strings.Contains(cronjob.DBType, "postgresql") {
for _, db := range item.DBNames {
dbItem, err := postgresqlRepo.Get(postgresqlRepo.WithByPostgresqlName(db.Name), repo.WithByName(db.DetailName))
if err != nil {
hasNotFound = true
continue
}
dbIDs = append(dbIDs, fmt.Sprintf("%v", dbItem.ID))
}
} else {
for _, db := range item.DBNames {
dbItem, err := mysqlRepo.Get(mysqlRepo.WithByMysqlName(db.Name), repo.WithByName(db.DetailName))
if err != nil {
hasNotFound = true
continue
}
dbIDs = append(dbIDs, fmt.Sprintf("%v", dbItem.ID))
}
}
cronjob.DBName = strings.Join(dbIDs, ",")
case "shell":
if len(item.ContainerName) != 0 {
cronjob.Script = item.Script
client, err := docker.NewDockerClient()
if err != nil {
hasNotFound = true
break
}
defer client.Close()
if _, err := client.ContainerStats(context.Background(), item.ContainerName, false); err != nil {
hasNotFound = true
break
}
}
switch item.ScriptMode {
case "library":
library, _ := scriptRepo.Get(repo.WithByName(item.ScriptName))
if library.ID == 0 {
hasNotFound = true
break
}
cronjob.ScriptID = library.ID
case "select":
if _, err := os.Stat(item.Script); err != nil {
hasNotFound = true
break
}
cronjob.Script = item.Script
case "input":
cronjob.Script = item.Script
}
case "directory":
if item.IsDir {
if _, err := os.Stat(item.SourceDir); err != nil {
hasNotFound = true
break
}
cronjob.SourceDir = item.SourceDir
} else {
fileList := strings.Split(item.SourceDir, ",")
var newFiles []string
for _, item := range fileList {
if len(item) == 0 {
continue
}
if _, err := os.Stat(item); err != nil {
hasNotFound = true
continue
}
newFiles = append(newFiles, item)
}
cronjob.SourceDir = strings.Join(newFiles, ",")
}
case "snapshot":
if len(item.SnapshotRule.IgnoreApps) == 0 && !item.SnapshotRule.WithImage {
break
}
var itemRules dto.SnapshotRule
itemRules.WithImage = item.SnapshotRule.WithImage
for _, app := range item.SnapshotRule.IgnoreApps {
appItem, err := appInstallRepo.LoadInstallAppByKeyAndName(app.Name, app.DetailName)
if err != nil {
hasNotFound = true
continue
}
itemRules.IgnoreAppIDs = append(itemRules.IgnoreAppIDs, appItem.ID)
}
itemRulesStr, _ := json.Marshal(itemRules)
cronjob.SnapshotRule = string(itemRulesStr)
}
var acIDs []string
for _, ac := range item.SourceAccounts {
backup, err := backupRepo.Get(repo.WithByName(ac))
if err != nil {
hasNotFound = true
continue
}
if ac == item.DownloadAccount {
cronjob.DownloadAccountID = backup.ID
}
acIDs = append(acIDs, fmt.Sprintf("%v", backup.ID))
}
cronjob.SourceAccountIDs = strings.Join(acIDs, ",")
if hasNotFound {
cronjob.Status = constant.StatusPending
} else {
cronjob.Status = constant.StatusDisable
}
_ = cronjobRepo.Create(&cronjob)
if item.AlertCount != 0 {
createAlert := dto.AlertCreate{
Title: item.AlertTitle,
SendCount: item.AlertCount,
Method: item.AlertMethod,
Type: cronjob.Type,
Project: strconv.Itoa(int(cronjob.ID)),
Status: constant.AlertEnable,
}
_ = NewIAlertService().CreateAlert(createAlert)
}
}
return nil
}
func (u *CronjobService) LoadScriptOptions() []dto.ScriptOptions {
scripts, err := scriptRepo.List()
if err != nil {
@@ -279,6 +572,13 @@ func (u *CronjobService) Create(req dto.CronjobOperate) error {
if err := copier.Copy(&cronjob, &req); err != nil {
return buserr.WithDetail("ErrStructTransform", err.Error(), nil)
}
if cronjob.Type == "snapshot" {
rule, err := json.Marshal(req.SnapshotRule)
if err != nil {
return err
}
cronjob.SnapshotRule = string(rule)
}
if cronjob.Type == "cutWebsiteLog" {
backupAccount, err := backupRepo.Get(repo.WithByType(constant.Local))
if backupAccount.ID == 0 {
@@ -300,13 +600,15 @@ func (u *CronjobService) Create(req dto.CronjobOperate) error {
return err
}
if req.AlertCount != 0 {
createAlert := dto.CreateOrUpdateAlert{
AlertTitle: req.AlertTitle,
AlertCount: req.AlertCount,
AlertType: cronjob.Type,
EntryID: cronjob.ID,
createAlert := dto.AlertCreate{
Title: req.AlertTitle,
SendCount: req.AlertCount,
Method: req.AlertMethod,
Type: cronjob.Type,
Project: strconv.Itoa(int(cronjob.ID)),
Status: constant.AlertEnable,
}
err := xpack.CreateAlert(createAlert)
err := NewIAlertService().CreateAlert(createAlert)
if err != nil {
return err
}
@@ -354,11 +656,7 @@ func (u *CronjobService) Delete(req dto.CronjobBatchDelete) error {
if err := cronjobRepo.Delete(repo.WithByID(id)); err != nil {
return err
}
alertBase := dto.AlertBase{
AlertType: cronjob.Type,
EntryID: cronjob.ID,
}
err := xpack.DeleteAlert(alertBase)
err := alertRepo.Delete(alertRepo.WithByProject(strconv.Itoa(int(cronjob.ID))), alertRepo.WithByType(cronjob.Type))
if err != nil {
return err
}
@@ -372,6 +670,13 @@ func (u *CronjobService) Update(id uint, req dto.CronjobOperate) error {
if err := copier.Copy(&cronjob, &req); err != nil {
return buserr.WithDetail("ErrStructTransform", err.Error(), nil)
}
if req.Type == "snapshot" {
itemRule, err := json.Marshal(req.SnapshotRule)
if err != nil {
return err
}
cronjob.SnapshotRule = string(itemRule)
}
cronModel, err := cronjobRepo.Get(repo.WithByID(id))
if err != nil {
return buserr.New("ErrRecordNotFound")
@@ -394,6 +699,9 @@ func (u *CronjobService) Update(id uint, req dto.CronjobOperate) error {
}
}
if cronModel.Status == constant.StatusPending {
upMap["status"] = constant.StatusEnable
}
upMap["name"] = req.Name
upMap["spec_custom"] = req.SpecCustom
upMap["spec"] = spec
@@ -411,25 +719,29 @@ func (u *CronjobService) Update(id uint, req dto.CronjobOperate) error {
upMap["db_type"] = req.DBType
upMap["db_name"] = req.DBName
upMap["url"] = req.URL
upMap["is_dir"] = req.IsDir
upMap["source_dir"] = req.SourceDir
upMap["snapshot_rule"] = cronjob.SnapshotRule
upMap["source_account_ids"] = req.SourceAccountIDs
upMap["download_account_id"] = req.DownloadAccountID
upMap["retain_copies"] = req.RetainCopies
upMap["retry_times"] = req.RetryTimes
upMap["timeout"] = req.Timeout
upMap["ignore_err"] = req.IgnoreErr
upMap["secret"] = req.Secret
err = cronjobRepo.Update(id, upMap)
if err != nil {
return err
}
updateAlert := dto.CreateOrUpdateAlert{
AlertTitle: req.AlertTitle,
AlertType: cronModel.Type,
AlertCount: req.AlertCount,
EntryID: cronModel.ID,
updateAlert := dto.AlertCreate{
Title: req.AlertTitle,
SendCount: req.AlertCount,
Method: req.AlertMethod,
Type: cronjob.Type,
Project: strconv.Itoa(int(cronModel.ID)),
}
err = xpack.UpdateAlert(updateAlert)
err = NewIAlertService().ExternalUpdateAlert(updateAlert)
if err != nil {
return err
}
+149 -37
View File
@@ -2,9 +2,11 @@ package service
import (
"context"
"encoding/json"
"fmt"
"os"
"path"
"path/filepath"
"strconv"
"strings"
"time"
@@ -23,22 +25,7 @@ import (
)
func (u *CronjobService) handleApp(cronjob model.Cronjob, startTime time.Time, taskItem *task.Task) error {
var apps []model.AppInstall
if cronjob.AppID == "all" {
apps, _ = appInstallRepo.ListBy(context.Background())
} else {
appIds := strings.Split(cronjob.AppID, ",")
var idItems []uint
for i := 0; i < len(appIds); i++ {
itemID, _ := strconv.Atoi(appIds[i])
idItems = append(idItems, uint(itemID))
}
appItems, err := appInstallRepo.ListBy(context.Background(), repo.WithByIDs(idItems))
if err != nil {
return err
}
apps = appItems
}
apps := loadAppsForJob(cronjob)
if len(apps) == 0 {
return errors.New("no such app in database!")
}
@@ -47,6 +34,7 @@ func (u *CronjobService) handleApp(cronjob model.Cronjob, startTime time.Time, t
return err
}
for _, app := range apps {
retry := 0
taskItem.AddSubTaskWithOps(task.GetTaskName(app.Name, task.TaskBackup, task.TaskScopeCronjob), func(task *task.Task) error {
var record model.BackupRecord
record.From = "cronjob"
@@ -55,14 +43,25 @@ func (u *CronjobService) handleApp(cronjob model.Cronjob, startTime time.Time, t
record.Name = app.App.Key
record.DetailName = app.Name
record.DownloadAccountID, record.SourceAccountIDs = cronjob.DownloadAccountID, cronjob.SourceAccountIDs
backupDir := path.Join(global.Dir.TmpDir, fmt.Sprintf("app/%s/%s", app.App.Key, app.Name))
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))
if err := doAppBackup(&app, task, backupDir, record.FileName, cronjob.ExclusionRules, cronjob.Secret); err != nil {
return err
if retry < int(cronjob.RetryTimes) || !cronjob.IgnoreErr {
retry++
return err
} else {
task.Log(i18n.GetMsgWithDetail("IgnoreBackupErr", err.Error()))
return nil
}
}
downloadPath, err := u.uploadCronjobBackFile(cronjob, task, accountMap, path.Join(backupDir, record.FileName))
if err != nil {
return err
if retry < int(cronjob.RetryTimes) || !cronjob.IgnoreErr {
retry++
return err
}
task.Log(i18n.GetMsgWithDetail("IgnoreUploadErr", err.Error()))
return nil
}
record.FileDir = path.Dir(downloadPath)
if err := backupRepo.CreateRecord(&record); err != nil {
@@ -86,6 +85,7 @@ func (u *CronjobService) handleWebsite(cronjob model.Cronjob, startTime time.Tim
return err
}
for _, web := range webs {
retry := 0
taskItem.AddSubTaskWithOps(task.GetTaskName(web.Alias, task.TaskBackup, task.TaskScopeCronjob), func(task *task.Task) error {
var record model.BackupRecord
record.From = "cronjob"
@@ -94,16 +94,27 @@ func (u *CronjobService) handleWebsite(cronjob model.Cronjob, startTime time.Tim
record.Name = web.Alias
record.DetailName = web.Alias
record.DownloadAccountID, record.SourceAccountIDs = cronjob.DownloadAccountID, cronjob.SourceAccountIDs
backupDir := path.Join(global.Dir.TmpDir, fmt.Sprintf("website/%s", web.Alias))
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))
if err := doWebsiteBackup(&web, taskItem, backupDir, record.FileName, cronjob.ExclusionRules, cronjob.Secret); err != nil {
return err
if retry < int(cronjob.RetryTimes) || !cronjob.IgnoreErr {
retry++
return err
} else {
task.Log(i18n.GetMsgWithDetail("IgnoreBackupErr", err.Error()))
return nil
}
}
downloadPath, err := u.uploadCronjobBackFile(cronjob, task, accountMap, path.Join(backupDir, record.FileName))
if err != nil {
return err
if retry < int(cronjob.RetryTimes) || !cronjob.IgnoreErr {
retry++
return err
}
task.Log(i18n.GetMsgWithDetail("IgnoreUploadErr", err.Error()))
return nil
}
record.FileDir = path.Dir(downloadPath)
if err := backupRepo.CreateRecord(&record); err != nil {
@@ -127,6 +138,7 @@ func (u *CronjobService) handleDatabase(cronjob model.Cronjob, startTime time.Ti
return err
}
for _, dbInfo := range dbs {
retry := 0
itemName := fmt.Sprintf("%s[%s] - %s", dbInfo.Database, dbInfo.DBType, dbInfo.Name)
taskItem.AddSubTaskWithOps(task.GetTaskName(itemName, task.TaskBackup, task.TaskScopeCronjob), func(task *task.Task) error {
var record model.BackupRecord
@@ -137,21 +149,38 @@ func (u *CronjobService) handleDatabase(cronjob model.Cronjob, startTime time.Ti
record.DetailName = dbInfo.Name
record.DownloadAccountID, record.SourceAccountIDs = cronjob.DownloadAccountID, cronjob.SourceAccountIDs
backupDir := path.Join(global.Dir.TmpDir, fmt.Sprintf("database/%s/%s/%s", dbInfo.DBType, record.Name, dbInfo.Name))
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))
if cronjob.DBType == "mysql" || cronjob.DBType == "mariadb" {
if cronjob.DBType == "mysql" || cronjob.DBType == "mariadb" || cronjob.DBType == "mysql-cluster" {
if err := doMysqlBackup(dbInfo, backupDir, record.FileName); err != nil {
return err
if retry < int(cronjob.RetryTimes) || !cronjob.IgnoreErr {
retry++
return err
} else {
task.Log(i18n.GetMsgWithDetail("IgnoreBackupErr", err.Error()))
return nil
}
}
} else {
if err := doPostgresqlgBackup(dbInfo, backupDir, record.FileName); err != nil {
return err
if retry < int(cronjob.RetryTimes) || !cronjob.IgnoreErr {
retry++
return err
} else {
task.Log(i18n.GetMsgWithDetail("IgnoreBackupErr", err.Error()))
return nil
}
}
}
downloadPath, err := u.uploadCronjobBackFile(cronjob, task, accountMap, path.Join(backupDir, record.FileName))
if err != nil {
return err
if retry < int(cronjob.RetryTimes) || !cronjob.IgnoreErr {
retry++
return err
}
task.Log(i18n.GetMsgWithDetail("IgnoreUploadErr", err.Error()))
return nil
}
record.FileDir = path.Dir(downloadPath)
if err := backupRepo.CreateRecord(&record); err != nil {
@@ -171,8 +200,11 @@ func (u *CronjobService) handleDirectory(cronjob model.Cronjob, startTime time.T
if err != nil {
return err
}
fileName := fmt.Sprintf("%s.tar.gz", startTime.Format(constant.DateTimeSlimLayout)+common.RandStrAndNum(5))
backupDir := path.Join(global.Dir.TmpDir, fmt.Sprintf("%s/%s", cronjob.Type, cronjob.Name))
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)
}
backupDir := path.Join(global.Dir.LocalBackupDir, fmt.Sprintf("tmp/%s/%s", cronjob.Type, cronjob.Name))
fileOp := files.NewFileOp()
if cronjob.IsDir {
@@ -218,7 +250,7 @@ func (u *CronjobService) handleSystemLog(cronjob model.Cronjob, startTime time.T
}
nameItem := startTime.Format(constant.DateTimeSlimLayout) + common.RandStrAndNum(5)
fileName := fmt.Sprintf("system_log_%s.tar.gz", nameItem)
backupDir := path.Join(global.Dir.TmpDir, "log", nameItem)
backupDir := path.Join(global.Dir.LocalBackupDir, "tmp/log", nameItem)
if err := handleBackupLogs(taskItem, backupDir, fileName, cronjob.Secret); err != nil {
return err
}
@@ -250,10 +282,6 @@ func (u *CronjobService) handleSnapshot(cronjob model.Cronjob, jobRecord model.J
if err != nil {
return err
}
itemData, err := NewISnapshotService().LoadSnapshotData()
if err != nil {
return err
}
var record model.BackupRecord
record.From = "cronjob"
@@ -268,6 +296,11 @@ func (u *CronjobService) handleSnapshot(cronjob model.Cronjob, jobRecord model.J
if !global.IsMaster {
scope = "agent"
}
itemData, err := loadSnapWithRule(cronjob)
if err != nil {
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,
@@ -278,11 +311,13 @@ func (u *CronjobService) handleSnapshot(cronjob model.Cronjob, jobRecord model.J
AppData: itemData.AppData,
PanelData: itemData.PanelData,
BackupData: itemData.BackupData,
WithDockerConf: true,
WithMonitorData: true,
WithLoginLog: true,
WithOperationLog: true,
WithSystemLog: true,
WithTaskLog: true,
IgnoreFiles: strings.Split(cronjob.ExclusionRules, ","),
}
if err := NewISnapshotService().SnapshotCreate(taskItem, req, jobRecord.ID, cronjob.RetryTimes, cronjob.Timeout); err != nil {
@@ -298,6 +333,23 @@ func (u *CronjobService) handleSnapshot(cronjob model.Cronjob, jobRecord model.J
return nil
}
func loadAppsForJob(cronjob model.Cronjob) []model.AppInstall {
var apps []model.AppInstall
if cronjob.AppID == "all" {
apps, _ = appInstallRepo.ListBy(context.Background())
} else {
appIds := strings.Split(cronjob.AppID, ",")
var idItems []uint
for i := 0; i < len(appIds); i++ {
itemID, _ := strconv.Atoi(appIds[i])
idItems = append(idItems, uint(itemID))
}
appItems, _ := appInstallRepo.ListBy(context.Background(), repo.WithByIDs(idItems))
apps = appItems
}
return apps
}
type DatabaseHelper struct {
ID uint
DBType string
@@ -308,7 +360,7 @@ type DatabaseHelper struct {
func loadDbsForJob(cronjob model.Cronjob) []DatabaseHelper {
var dbs []DatabaseHelper
if cronjob.DBName == "all" {
if cronjob.DBType == "mysql" || cronjob.DBType == "mariadb" {
if cronjob.DBType == "mysql" || cronjob.DBType == "mariadb" || cronjob.DBType == "mysql-cluster" {
databaseService := NewIDatabaseService()
mysqlItems, _ := databaseService.LoadItems(cronjob.DBType)
for _, mysql := range mysqlItems {
@@ -335,7 +387,7 @@ func loadDbsForJob(cronjob model.Cronjob) []DatabaseHelper {
dbNames := strings.Split(cronjob.DBName, ",")
for _, name := range dbNames {
itemID, _ := strconv.Atoi(name)
if cronjob.DBType == "mysql" || cronjob.DBType == "mariadb" {
if cronjob.DBType == "mysql" || cronjob.DBType == "mariadb" || cronjob.DBType == "mysql-cluster" {
mysqlItem, _ := mysqlRepo.Get(repo.WithByID(uint(itemID)))
dbs = append(dbs, DatabaseHelper{
ID: mysqlItem.ID,
@@ -397,7 +449,7 @@ func handleBackupLogs(taskItem *task.Task, targetDir, fileName string, secret st
}
}
}
itemDir2 := path.Join(global.Dir.LocalBackupDir, "log/website", website.Alias)
itemDir2 := path.Join(global.Dir.LocalBackupDir, "tmp/log/website", website.Alias)
logFiles2, _ := os.ReadDir(itemDir2)
if len(logFiles2) != 0 {
for i := 0; i < len(logFiles2); i++ {
@@ -451,3 +503,63 @@ func handleBackupLogs(taskItem *task.Task, targetDir, fileName string, secret st
}()
return nil
}
func loadSnapWithRule(cronjob model.Cronjob) (dto.SnapshotData, error) {
itemData, err := NewISnapshotService().LoadSnapshotData()
if err != nil {
return itemData, err
}
if len(cronjob.SnapshotRule) == 0 {
return itemData, nil
}
var snapRule dto.SnapshotRule
if err := json.Unmarshal([]byte(cronjob.SnapshotRule), &snapRule); err != nil {
return itemData, err
}
if len(snapRule.IgnoreAppIDs) == 0 && !snapRule.WithImage {
return itemData, nil
}
var ignoreApps []model.AppInstall
if len(snapRule.IgnoreAppIDs) != 0 {
ignoreApps, _ = appInstallRepo.ListBy(context.Background(), repo.WithByIDs(snapRule.IgnoreAppIDs))
}
if len(ignoreApps) == 0 && !snapRule.WithImage {
return itemData, nil
}
for i := 0; i < len(itemData.AppData); i++ {
isIgnore := false
for _, ignore := range ignoreApps {
if ignore.App.Key == itemData.AppData[i].Key && ignore.Name == itemData.AppData[i].Name {
isIgnore = true
itemData.AppData[i].IsCheck = false
for j := 0; j < len(itemData.AppData[i].Children); j++ {
if itemData.AppData[i].Children[j].Label == "appData" {
itemData.AppData[i].Children[j].IsCheck = false
}
}
break
}
}
if snapRule.WithImage && !isIgnore {
for j := 0; j < len(itemData.AppData[i].Children); j++ {
if itemData.AppData[i].Children[j].Label == "appImage" {
itemData.AppData[i].Children[j].IsCheck = true
}
}
}
}
return itemData, nil
}
func loadFileName(src string) string {
dirs := strings.Split(filepath.ToSlash(src), "/")
var keyPart string
if len(dirs) >= 3 {
keyPart = filepath.Join(dirs[len(dirs)-3], dirs[len(dirs)-2], dirs[len(dirs)-1])
}
cleanName := strings.ReplaceAll(keyPart, string(filepath.Separator), "_")
timestamp := time.Now().Format(constant.DateTimeSlimLayout)
return fmt.Sprintf("%s_%s_%s.tar.gz", cleanName, timestamp, common.RandStrAndNum(2))
}
+9 -7
View File
@@ -9,6 +9,8 @@ import (
"strings"
"time"
"github.com/1Panel-dev/1Panel/agent/utils/alert_push"
"github.com/1Panel-dev/1Panel/agent/app/dto"
"github.com/1Panel-dev/1Panel/agent/app/model"
"github.com/1Panel-dev/1Panel/agent/app/repo"
@@ -19,7 +21,6 @@ import (
"github.com/1Panel-dev/1Panel/agent/utils/cmd"
"github.com/1Panel-dev/1Panel/agent/utils/files"
"github.com/1Panel-dev/1Panel/agent/utils/ntp"
"github.com/1Panel-dev/1Panel/agent/utils/xpack"
)
func (u *CronjobService) HandleJob(cronjob *model.Cronjob) {
@@ -48,6 +49,7 @@ func (u *CronjobService) HandleJob(cronjob *model.Cronjob) {
global.LOG.Debugf("preper to handle cron job [%s] %s failed, err: %v", cronjob.Type, cronjob.Name, err)
record.TaskID = ""
cronjobRepo.EndRecords(record, constant.StatusFailed, err.Error(), record.Records)
handleCronJobAlert(cronjob)
return
}
if err := taskItem.Execute(); err != nil {
@@ -268,9 +270,13 @@ func (u *CronjobService) uploadCronjobBackFile(cronjob model.Cronjob, task *task
}()
var errItem error
accounts := strings.Split(cronjob.SourceAccountIDs, ",")
cloudSrc := strings.TrimPrefix(file, global.Dir.TmpDir+"/")
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{}{
@@ -355,9 +361,5 @@ func handleCronJobAlert(cronjob *model.Cronjob) {
EntryID: cronjob.ID,
Param: cronjob.Type,
}
err := xpack.PushAlert(pushAlert)
if err != nil {
global.LOG.Errorf("cronjob alert push failed, err: %v", err)
return
}
_ = alert_push.PushAlert(pushAlert)
}
+7 -4
View File
@@ -113,7 +113,7 @@ func (u *DashboardService) LoadCurrentInfoForNode() *dto.NodeCurrent {
memoryInfo, _ := mem.VirtualMemory()
currentInfo.MemoryTotal = memoryInfo.Total
currentInfo.MemoryAvailable = memoryInfo.Available
currentInfo.MemoryUsed = memoryInfo.Used
currentInfo.MemoryUsed = memoryInfo.Used + memoryInfo.Shared
currentInfo.MemoryUsedPercent = memoryInfo.UsedPercent
swapInfo, _ := mem.SwapMemory()
@@ -210,8 +210,11 @@ func (u *DashboardService) LoadCurrentInfo(ioOption string, netOption string) *d
memoryInfo, _ := mem.VirtualMemory()
currentInfo.MemoryTotal = memoryInfo.Total
currentInfo.MemoryUsed = memoryInfo.Used + memoryInfo.Shared
currentInfo.MemoryFree = memoryInfo.Free
currentInfo.MemoryCache = memoryInfo.Cached + memoryInfo.Buffers
currentInfo.MemoryShard = memoryInfo.Shared
currentInfo.MemoryAvailable = memoryInfo.Available
currentInfo.MemoryUsed = memoryInfo.Used
currentInfo.MemoryUsedPercent = memoryInfo.UsedPercent
swapInfo, _ := mem.SwapMemory()
@@ -390,10 +393,10 @@ type diskInfo struct {
func loadDiskInfo() []dto.DiskInfo {
var datas []dto.DiskInfo
cmdMgr := cmd.NewCommandMgr(cmd.WithTimeout(2 * time.Second))
stdout, err := cmdMgr.RunWithStdoutBashC("df -hT -P|grep '/'|grep -v tmpfs|grep -v 'snap/core'|grep -v udev")
stdout, err := cmdMgr.RunWithStdoutBashC("timeout 2 df -hT -P | awk 'NR>1 && !/tmpfs|snap\\/core|udev/ {print}'")
if err != nil {
cmdMgr2 := cmd.NewCommandMgr(cmd.WithTimeout(1 * time.Second))
stdout, err = cmdMgr2.RunWithStdoutBashC("df -lhT -P|grep '/'|grep -v tmpfs|grep -v 'snap/core'|grep -v udev")
stdout, err = cmdMgr2.RunWithStdoutBashC("timeout 1 df -lhT -P | awk 'NR>1 && !/tmpfs|snap\\/core|udev/ {print}'")
if err != nil {
return datas
}
+1 -1
View File
@@ -92,7 +92,7 @@ func (u *DatabaseService) LoadItems(dbType string) ([]dto.DatabaseItem, error) {
dbs, err := databaseRepo.GetList(databaseRepo.WithTypeList(dbType))
var datas []dto.DatabaseItem
for _, db := range dbs {
if dbType == "postgresql" {
if dbType == constant.AppPostgresql || dbType == constant.AppPostgresqlCluster {
items, _ := postgresqlRepo.List(postgresqlRepo.WithByPostgresqlName(db.Name))
for _, item := range items {
var dItem dto.DatabaseItem
+7 -1
View File
@@ -34,7 +34,7 @@ func (u *DBCommonService) LoadBaseInfo(req dto.OperationWithNameAndType) (*dto.D
}
data.ContainerName = app.ContainerName
data.Name = app.Name
data.Port = int64(app.Port)
data.Port = app.Port
return &data, nil
}
@@ -42,6 +42,10 @@ func (u *DBCommonService) LoadBaseInfo(req dto.OperationWithNameAndType) (*dto.D
func (u *DBCommonService) LoadDatabaseFile(req dto.OperationWithNameAndType) (string, error) {
filePath := ""
switch req.Type {
case "mysql-cluster-conf":
filePath = path.Join(global.Dir.DataDir, fmt.Sprintf("apps/mysql-cluster/%s/conf/my.cnf", req.Name))
case "postgresql-cluster-conf":
filePath = path.Join(global.Dir.DataDir, fmt.Sprintf("apps/postgresql-cluster/%s/data/postgresql.conf", req.Name))
case "mysql-conf":
filePath = path.Join(global.Dir.DataDir, fmt.Sprintf("apps/mysql/%s/conf/my.cnf", req.Name))
case "mariadb-conf":
@@ -50,6 +54,8 @@ func (u *DBCommonService) LoadDatabaseFile(req dto.OperationWithNameAndType) (st
filePath = path.Join(global.Dir.DataDir, fmt.Sprintf("apps/postgresql/%s/data/postgresql.conf", req.Name))
case "redis-conf":
filePath = path.Join(global.Dir.DataDir, fmt.Sprintf("apps/redis/%s/conf/redis.conf", req.Name))
case "redis-cluster-conf":
filePath = path.Join(global.Dir.DataDir, fmt.Sprintf("apps/redis-cluster/%s/conf/redis.conf", req.Name))
}
if _, err := os.Stat(filePath); err != nil {
return "", buserr.New("ErrHttpReqNotFound")
+9 -3
View File
@@ -534,7 +534,7 @@ func (u *MysqlService) LoadStatus(req dto.OperationWithNameAndType) (*dto.MysqlS
info.File = "OFF"
info.Position = "OFF"
masterStatus := "show master status;"
if common.CompareAppVersion(app.Version, "8.4.0") && req.Type == constant.AppMysql {
if common.CompareAppVersion(app.Version, "8.4.0") && (req.Type == constant.AppMysql || req.Type == constant.AppMysqlCluster) {
masterStatus = "show binary log status;"
}
rows, err := executeSqlForRows(app.ContainerName, app.Key, app.Password, masterStatus)
@@ -553,10 +553,13 @@ func (u *MysqlService) LoadStatus(req dto.OperationWithNameAndType) (*dto.MysqlS
}
func executeSqlForMaps(containerName, dbType, password, command string) (map[string]string, error) {
if dbType == "mysql-cluster" {
dbType = "mysql"
}
cmd := exec.Command("docker", "exec", containerName, dbType, "-uroot", "-p"+password, "-e", command)
stdout, err := cmd.CombinedOutput()
stdStr := strings.ReplaceAll(string(stdout), "mysql: [Warning] Using a password on the command line interface can be insecure.\n", "")
if err != nil || strings.HasPrefix(string(stdStr), "ERROR ") {
if err != nil || strings.HasPrefix(stdStr, "ERROR ") {
return nil, errors.New(stdStr)
}
@@ -572,10 +575,13 @@ func executeSqlForMaps(containerName, dbType, password, command string) (map[str
}
func executeSqlForRows(containerName, dbType, password, command string) ([]string, error) {
if dbType == "mysql-cluster" {
dbType = "mysql"
}
cmd := exec.Command("docker", "exec", containerName, dbType, "-uroot", "-p"+password, "-e", command)
stdout, err := cmd.CombinedOutput()
stdStr := strings.ReplaceAll(string(stdout), "mysql: [Warning] Using a password on the command line interface can be insecure.\n", "")
if err != nil || strings.HasPrefix(string(stdStr), "ERROR ") {
if err != nil || strings.HasPrefix(stdStr, "ERROR ") {
return nil, errors.New(stdStr)
}
return strings.Split(stdStr, "\n"), nil
+17 -17
View File
@@ -28,9 +28,9 @@ type IRedisService interface {
UpdatePersistenceConf(req dto.RedisConfPersistenceUpdate) error
ChangePassword(info dto.ChangeRedisPass) error
LoadStatus(req dto.OperationWithName) (*dto.RedisStatus, error)
LoadConf(req dto.OperationWithName) (*dto.RedisConf, error)
LoadPersistenceConf(req dto.OperationWithName) (*dto.RedisPersistence, error)
LoadStatus(req dto.LoadRedisStatus) (*dto.RedisStatus, error)
LoadConf(req dto.LoadRedisStatus) (*dto.RedisConf, error)
LoadPersistenceConf(req dto.LoadRedisStatus) (*dto.RedisPersistence, error)
CheckHasCli() bool
InstallCli() error
@@ -41,7 +41,7 @@ func NewIRedisService() IRedisService {
}
func (u *RedisService) UpdateConf(req dto.RedisConfUpdate) error {
redisInfo, err := appInstallRepo.LoadBaseInfo("redis", req.Database)
redisInfo, err := appInstallRepo.LoadBaseInfo(req.DBType, req.Database)
if err != nil {
return err
}
@@ -50,7 +50,7 @@ func (u *RedisService) UpdateConf(req dto.RedisConfUpdate) error {
confs = append(confs, redisConfig{key: "timeout", value: req.Timeout})
confs = append(confs, redisConfig{key: "maxclients", value: req.Maxclients})
confs = append(confs, redisConfig{key: "maxmemory", value: req.Maxmemory})
if err := confSet(redisInfo.Name, "", confs); err != nil {
if err := confSet(redisInfo.Name, req.DBType, "", confs); err != nil {
return err
}
if _, err := compose.Restart(fmt.Sprintf("%s/redis/%s/docker-compose.yml", global.Dir.AppInstallDir, redisInfo.Name)); err != nil {
@@ -84,7 +84,7 @@ func (u *RedisService) InstallCli() error {
Image: "redis:7.4.4",
Network: "1panel-network",
}
return NewIContainerService().ContainerCreate(item)
return NewIContainerService().ContainerCreate(item, false)
}
func (u *RedisService) ChangePassword(req dto.ChangeRedisPass) error {
@@ -107,7 +107,7 @@ func (u *RedisService) ChangePassword(req dto.ChangeRedisPass) error {
}
func (u *RedisService) UpdatePersistenceConf(req dto.RedisConfPersistenceUpdate) error {
redisInfo, err := appInstallRepo.LoadBaseInfo("redis", req.Database)
redisInfo, err := appInstallRepo.LoadBaseInfo(req.DBType, req.Database)
if err != nil {
return err
}
@@ -119,18 +119,18 @@ func (u *RedisService) UpdatePersistenceConf(req dto.RedisConfPersistenceUpdate)
confs = append(confs, redisConfig{key: "appendonly", value: req.Appendonly})
confs = append(confs, redisConfig{key: "appendfsync", value: req.Appendfsync})
}
if err := confSet(redisInfo.Name, req.Type, confs); err != nil {
if err := confSet(redisInfo.Name, req.DBType, req.Type, confs); err != nil {
return err
}
if _, err := compose.Restart(fmt.Sprintf("%s/redis/%s/docker-compose.yml", global.Dir.AppInstallDir, redisInfo.Name)); err != nil {
if _, err := compose.Restart(fmt.Sprintf("%s/%s/%s/docker-compose.yml", global.Dir.AppInstallDir, req.DBType, redisInfo.Name)); err != nil {
return err
}
return nil
}
func (u *RedisService) LoadStatus(req dto.OperationWithName) (*dto.RedisStatus, error) {
redisInfo, err := appInstallRepo.LoadBaseInfo("redis", req.Name)
func (u *RedisService) LoadStatus(req dto.LoadRedisStatus) (*dto.RedisStatus, error) {
redisInfo, err := appInstallRepo.LoadBaseInfo(req.Type, req.Name)
if err != nil {
return nil, err
}
@@ -157,8 +157,8 @@ func (u *RedisService) LoadStatus(req dto.OperationWithName) (*dto.RedisStatus,
return &info, nil
}
func (u *RedisService) LoadConf(req dto.OperationWithName) (*dto.RedisConf, error) {
redisInfo, err := appInstallRepo.LoadBaseInfo("redis", req.Name)
func (u *RedisService) LoadConf(req dto.LoadRedisStatus) (*dto.RedisConf, error) {
redisInfo, err := appInstallRepo.LoadBaseInfo(req.Type, req.Name)
if err != nil {
return nil, err
}
@@ -174,8 +174,8 @@ func (u *RedisService) LoadConf(req dto.OperationWithName) (*dto.RedisConf, erro
return &item, nil
}
func (u *RedisService) LoadPersistenceConf(req dto.OperationWithName) (*dto.RedisPersistence, error) {
redisInfo, err := appInstallRepo.LoadBaseInfo("redis", req.Name)
func (u *RedisService) LoadPersistenceConf(req dto.LoadRedisStatus) (*dto.RedisPersistence, error) {
redisInfo, err := appInstallRepo.LoadBaseInfo(req.Type, req.Name)
if err != nil {
return nil, err
}
@@ -214,8 +214,8 @@ type redisConfig struct {
value string
}
func confSet(redisName string, updateType string, changeConf []redisConfig) error {
path := fmt.Sprintf("%s/redis/%s/conf/redis.conf", global.Dir.AppInstallDir, redisName)
func confSet(redisName string, redisType string, updateType string, changeConf []redisConfig) error {
path := fmt.Sprintf("%s/%s/%s/conf/redis.conf", global.Dir.AppInstallDir, redisType, redisName)
lineBytes, err := os.ReadFile(path)
if err != nil {
return err
+1 -1
View File
@@ -120,7 +120,7 @@ func (u *DeviceService) Update(key, value string) error {
if err := ntp.UpdateSystemTimeZone(value); err != nil {
return err
}
go common.RestartService(true, true, false)
go common.RestartService(global.IsMaster, true, false)
case "DNS":
if err := updateDNS(strings.Split(value, ",")); err != nil {
return err
+2 -1
View File
@@ -13,6 +13,7 @@ import (
"github.com/1Panel-dev/1Panel/agent/constant"
"github.com/1Panel-dev/1Panel/agent/utils/docker"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/build"
"github.com/docker/docker/api/types/filters"
"github.com/1Panel-dev/1Panel/agent/app/dto"
@@ -663,7 +664,7 @@ func dropBuildCache() {
if err != nil {
global.LOG.Errorf("do not get docker client")
}
opts := types.BuildCachePruneOptions{}
opts := build.CachePruneOptions{}
opts.All = true
_, err = client.BuildCachePrune(context.Background(), opts)
if err != nil {
-27
View File
@@ -220,16 +220,6 @@ func (u *DockerService) UpdateConf(req dto.SettingUpdate, withRestart bool) erro
daemonMap["proxies"] = proxies
}
}
if len(daemonMap) == 0 {
if len(file) == 0 {
return nil
}
_ = os.Remove(constant.DaemonJsonPath)
if withRestart {
return restartDocker()
}
return nil
}
newJson, err := json.MarshalIndent(daemonMap, "", "\t")
if err != nil {
return err
@@ -277,11 +267,6 @@ func (u *DockerService) UpdateLogOption(req dto.LogOption) error {
_ = json.Unmarshal(file, &daemonMap)
changeLogOption(daemonMap, req.LogMaxFile, req.LogMaxSize)
if len(daemonMap) == 0 {
_ = os.Remove(constant.DaemonJsonPath)
_ = restartDocker()
return nil
}
newJson, err := json.MarshalIndent(daemonMap, "", "\t")
if err != nil {
return err
@@ -321,11 +306,6 @@ func (u *DockerService) UpdateIpv6Option(req dto.Ipv6Option) error {
if req.Experimental {
daemonMap["experimental"] = req.Experimental
}
if len(daemonMap) == 0 {
_ = os.Remove(constant.DaemonJsonPath)
_ = restartDocker()
return nil
}
newJson, err := json.MarshalIndent(daemonMap, "", "\t")
if err != nil {
return err
@@ -345,13 +325,6 @@ func (u *DockerService) UpdateIpv6Option(req dto.Ipv6Option) error {
}
func (u *DockerService) UpdateConfByFile(req dto.DaemonJsonUpdateByFile) error {
if len(req.File) == 0 {
_ = os.Remove(constant.DaemonJsonPath)
if err := restartDocker(); err != nil {
return err
}
return nil
}
err := createIfNotExistDaemonJsonFile()
if err != nil {
return err
+1
View File
@@ -50,4 +50,5 @@ var (
taskRepo = repo.NewITaskRepo()
groupRepo = repo.NewIGroupRepo()
alertRepo = repo.NewIAlertRepo()
)
+31 -11
View File
@@ -4,7 +4,6 @@ import (
"bufio"
"context"
"fmt"
"github.com/jinzhu/copier"
"io"
"io/fs"
"os"
@@ -18,6 +17,9 @@ import (
"unicode/utf8"
"github.com/1Panel-dev/1Panel/agent/app/dto"
"github.com/jinzhu/copier"
"golang.org/x/text/encoding"
"golang.org/x/text/encoding/simplifiedchinese"
"github.com/1Panel-dev/1Panel/agent/app/repo"
@@ -27,7 +29,6 @@ import (
"github.com/1Panel-dev/1Panel/agent/constant"
"golang.org/x/net/html/charset"
"golang.org/x/sys/unix"
"golang.org/x/text/encoding/simplifiedchinese"
"golang.org/x/text/transform"
"github.com/1Panel-dev/1Panel/agent/global"
@@ -337,10 +338,17 @@ func (f *FileService) GetContent(op request.FileContentReq) (response.FileInfo,
}
if !utf8.Valid(content) {
_, decodeName, _ := charset.DetermineEncoding(content, "")
if decodeName == "windows-1252" || strings.ToLower(decodeName) == "gbk" {
decoder := files.GetDecoderByName(decodeName)
if decoder != nil {
reader := strings.NewReader(info.Content)
item := transform.NewReader(reader, simplifiedchinese.GBK.NewDecoder())
contents, err := io.ReadAll(item)
var dec *encoding.Decoder
if decodeName == "windows-1252" {
dec = simplifiedchinese.GBK.NewDecoder()
} else {
dec = decoder.NewDecoder()
}
decodedReader := transform.NewReader(reader, dec)
contents, err := io.ReadAll(decodedReader)
if err != nil {
return response.FileInfo{}, err
}
@@ -390,10 +398,18 @@ func (f *FileService) MvFile(m request.FileMove) error {
return buserr.New("ErrMovePathFailed")
}
}
var errs []error
if m.Type == "cut" {
if len(m.CoverPaths) > 0 {
for _, src := range m.CoverPaths {
if err := fo.CopyAndReName(src, m.NewPath, "", true); err != nil {
errs = append(errs, err)
global.LOG.Errorf("cut copy file [%s] to [%s] failed, err: %s", src, m.NewPath, err.Error())
}
}
}
return fo.Cut(m.OldPaths, m.NewPath, m.Name, m.Cover)
}
var errs []error
if m.Type == "copy" {
for _, src := range m.OldPaths {
if err := fo.CopyAndReName(src, m.NewPath, m.Name, m.Cover); err != nil {
@@ -463,6 +479,7 @@ func (f *FileService) DepthDirSize(req request.DirSizeReq) ([]response.DepthDirS
func (f *FileService) ReadLogByLine(req request.FileReadByLineReq) (*response.FileLineContent, error) {
logFilePath := ""
taskStatus := ""
switch req.Type {
case constant.TypeWebsite:
website, err := websiteRepo.GetFirst(repo.WithByID(req.ID))
@@ -518,6 +535,7 @@ func (f *FileService) ReadLogByLine(req request.FileReadByLineReq) (*response.Fi
return nil, err
}
logFilePath = taskModel.LogFile
taskStatus = taskModel.Status
case "mysql-slow-logs":
logFilePath = path.Join(global.Dir.DataDir, fmt.Sprintf("apps/mysql/%s/data/1Panel-slow.log", req.Name))
case "mariadb-slow-logs":
@@ -536,11 +554,12 @@ func (f *FileService) ReadLogByLine(req request.FileReadByLineReq) (*response.Fi
lines = append(preLines, lines...)
}
res := &response.FileLineContent{
Content: strings.Join(lines, "\n"),
End: isEndOfFile,
Path: logFilePath,
Total: total,
Lines: lines,
Content: strings.Join(lines, "\n"),
End: isEndOfFile,
Path: logFilePath,
Total: total,
TaskStatus: taskStatus,
Lines: lines,
}
return res, nil
}
@@ -565,6 +584,7 @@ func (f *FileService) BatchCheckFiles(req request.FilePathsCheck) []response.Exi
Name: info.Name(),
Path: filePath,
ModTime: info.ModTime(),
IsDir: info.IsDir(),
})
}
}
+1 -1
View File
@@ -602,7 +602,7 @@ func getProcessStatus(config *response.SupervisorProcessConfig, containerName st
out, err = exec.Command("supervisorctl", processNames...).Output()
output = string(out)
}
if containerName == "" && err != nil {
if output == "" && err != nil {
return err
}
lines := strings.Split(output, "\n")
+3 -3
View File
@@ -22,7 +22,7 @@ import (
"github.com/1Panel-dev/1Panel/agent/global"
"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"
"github.com/docker/docker/api/types/container"
"github.com/docker/docker/api/types/image"
"github.com/docker/docker/api/types/registry"
@@ -189,7 +189,7 @@ func (u *ImageService) ImageBuild(req dto.ImageBuild) error {
return err
}
opts := types.ImageBuildOptions{
opts := build.ImageBuildOptions{
Dockerfile: fileName,
Tags: []string{req.Name},
Remove: true,
@@ -432,7 +432,7 @@ func formatFileSize(fileSize int64) (size string) {
}
}
func checkUsed(imageID string, containers []types.Container) bool {
func checkUsed(imageID string, containers []container.Summary) bool {
for _, container := range containers {
if container.ImageID == imageID {
return true
+74 -46
View File
@@ -28,7 +28,7 @@ type IImageRepoService interface {
Login(req dto.OperateByID) error
Create(req dto.ImageRepoCreate) error
Update(req dto.ImageRepoUpdate) error
BatchDelete(req dto.ImageRepoDelete) error
Delete(req dto.OperateByID) error
}
func NewIImageRepoService() IImageRepoService {
@@ -91,28 +91,7 @@ func (u *ImageRepoService) Create(req dto.ImageRepoCreate) error {
if err := u.handleRegistries(req.DownloadUrl, "", "create"); err != nil {
return fmt.Errorf("create registry %s failed, err: %v", req.DownloadUrl, err)
}
if err := restartDocker(); err != nil {
return err
}
ticker := time.NewTicker(3 * time.Second)
defer ticker.Stop()
ctx, cancel := context.WithTimeout(context.Background(), time.Second*20)
if err := func() error {
for range ticker.C {
select {
case <-ctx.Done():
cancel()
return errors.New("the docker service cannot be restarted")
default:
stdout, err := cmd.RunDefaultWithStdoutBashC("systemctl is-active docker")
if string(stdout) == "active\n" && err == nil {
global.LOG.Info("docker restart with new conf successful!")
return nil
}
}
}
return nil
}(); err != nil {
if err := stopBeforeUpdateRepo(); err != nil {
return err
}
}
@@ -131,15 +110,32 @@ func (u *ImageRepoService) Create(req dto.ImageRepoCreate) error {
return imageRepoRepo.Create(&imageRepo)
}
func (u *ImageRepoService) BatchDelete(req dto.ImageRepoDelete) error {
for _, id := range req.Ids {
if id == 1 {
return errors.New("The default value cannot be edit !")
}
func (u *ImageRepoService) Delete(req dto.OperateByID) error {
if req.ID == 1 {
return errors.New("The default value cannot be edit !")
}
if err := imageRepoRepo.Delete(repo.WithByIDs(req.Ids)); err != nil {
itemRepo, _ := imageRepoRepo.Get(repo.WithByID(req.ID))
if itemRepo.ID == 0 {
return buserr.New("ErrRecordNotFound")
}
if itemRepo.Auth {
_, _ = cmd.NewCommandMgr().RunWithStdout("docker", "logout", "-i", itemRepo.DownloadUrl)
}
if itemRepo.Protocol == "https" {
return imageRepoRepo.Delete(repo.WithByID(req.ID))
}
if err := u.handleRegistries("", itemRepo.DownloadUrl, "delete"); err != nil {
return fmt.Errorf("delete registry %s failed, err: %v", itemRepo.DownloadUrl, err)
}
if err := validateDockerConfig(); err != nil {
return err
}
if err := imageRepoRepo.Delete(repo.WithByID(req.ID)); err != nil {
return err
}
go func() {
_ = restartDocker()
}()
return nil
}
@@ -154,38 +150,39 @@ func (u *ImageRepoService) Update(req dto.ImageRepoUpdate) error {
if err != nil {
return err
}
needRestart := false
if repo.Protocol == "http" && req.Protocol == "https" {
if err := u.handleRegistries("", repo.DownloadUrl, "delete"); err != nil {
return fmt.Errorf("delete registry %s failed, err: %v", repo.DownloadUrl, err)
}
needRestart = true
}
if repo.Protocol == "http" && req.Protocol == "http" {
if err := u.handleRegistries(req.DownloadUrl, repo.DownloadUrl, "update"); err != nil {
return fmt.Errorf("update registry %s => %s failed, err: %v", repo.DownloadUrl, req.DownloadUrl, err)
}
needRestart = repo.DownloadUrl == req.DownloadUrl
}
if repo.Protocol == "https" && req.Protocol == "http" {
if err := u.handleRegistries(req.DownloadUrl, "", "create"); err != nil {
return fmt.Errorf("create registry %s failed, err: %v", req.DownloadUrl, err)
}
}
if repo.Auth != req.Auth || repo.DownloadUrl != req.DownloadUrl {
if repo.Auth {
cmdMgr := cmd.NewCommandMgr()
_, _ = cmdMgr.RunWithStdout("docker", "logout", "-i", repo.DownloadUrl)
}
if req.Auth {
if err := u.CheckConn(req.DownloadUrl, req.Username, req.Password); err != nil {
return err
if req.DownloadUrl != repo.DownloadUrl {
if err := u.handleRegistries(req.DownloadUrl, repo.DownloadUrl, "update"); err != nil {
return fmt.Errorf("update registry %s => %s failed, err: %v", repo.DownloadUrl, req.DownloadUrl, err)
}
needRestart = true
}
}
if err := validateDockerConfig(); err != nil {
return err
if needRestart {
if err := stopBeforeUpdateRepo(); err != nil {
return err
}
}
if err := restartDocker(); err != nil {
return err
if repo.Auth {
_, _ = cmd.NewCommandMgr().RunWithStdout("docker", "logout", "-i", repo.DownloadUrl)
}
if req.Auth {
if err := u.CheckConn(req.DownloadUrl, req.Username, req.Password); err != nil {
return err
}
}
upMap := make(map[string]interface{})
@@ -260,3 +257,34 @@ func (u *ImageRepoService) handleRegistries(newHost, delHost, handle string) err
}
return nil
}
func stopBeforeUpdateRepo() error {
if err := validateDockerConfig(); err != nil {
return err
}
if err := restartDocker(); err != nil {
return err
}
ticker := time.NewTicker(3 * time.Second)
defer ticker.Stop()
ctx, cancel := context.WithTimeout(context.Background(), time.Second*30)
if err := func() error {
for range ticker.C {
select {
case <-ctx.Done():
cancel()
return errors.New("the docker service cannot be restarted")
default:
stdout, err := cmd.RunDefaultWithStdoutBashC("systemctl is-active docker")
if string(stdout) == "active\n" && err == nil {
global.LOG.Info("docker restart with new conf successful!")
return nil
}
}
}
return nil
}(); err != nil {
return err
}
return nil
}
+10 -2
View File
@@ -283,11 +283,19 @@ func (m McpServerService) BindDomain(req request.McpBindDomain) error {
}
}
group, _ := groupRepo.Get(groupRepo.WithByWebsiteDefault())
domain, err := ParseDomain(req.Domain)
if err != nil {
return err
}
if domain.Port == 0 {
domain.Port = nginxInstall.HttpPort
}
createWebsiteReq := request.WebsiteCreate{
Domains: []request.WebsiteDomain{
{
Domain: req.Domain,
Port: 80,
Domain: domain.Domain,
Port: domain.Port,
},
},
Alias: strings.ToLower(req.Domain),
+36 -17
View File
@@ -52,6 +52,7 @@ type IRuntimeService interface {
SyncForRestart() error
SyncRuntimeStatus() error
DeleteCheck(installID uint) ([]dto.AppResource, error)
UpdateRemark(req request.RuntimeRemark) error
GetPHPExtensions(runtimeID uint) (response.PHPExtensionRes, error)
InstallPHPExtension(req request.PHPExtensionInstallReq) error
@@ -108,6 +109,7 @@ func (r *RuntimeService) Create(create request.RuntimeCreate) (*model.Runtime, e
Type: create.Type,
Version: create.Version,
Status: constant.StatusNormal,
Remark: create.Remark,
}
return nil, runtimeRepo.Create(context.Background(), runtime)
}
@@ -168,6 +170,7 @@ func (r *RuntimeService) Create(create request.RuntimeCreate) (*model.Runtime, e
Version: create.Version,
ContainerName: containerName.(string),
Port: strings.Join(hostPorts, ","),
Remark: create.Remark,
}
switch create.Type {
@@ -208,6 +211,9 @@ func (r *RuntimeService) Page(req request.RuntimeSearch) (int64, []response.Runt
if err != nil {
return 0, nil, err
}
if len(runtimes) == 0 {
return 0, res, nil
}
if err = SyncRuntimesStatus(runtimes); err != nil {
return 0, nil, err
}
@@ -444,6 +450,7 @@ func (r *RuntimeService) Update(req request.RuntimeUpdate) error {
if err := checkContainerName(containerName.(string)); err != nil {
return err
}
runtime.ContainerName = containerName.(string)
}
projectDir := path.Join(global.Dir.RuntimeDir, runtime.Type, runtime.Name)
@@ -454,6 +461,7 @@ func (r *RuntimeService) Update(req request.RuntimeUpdate) error {
Params: req.Params,
CodeDir: req.CodeDir,
Version: req.Version,
Remark: req.Remark,
NodeConfig: request.NodeConfig{
Install: true,
ExposedPorts: req.ExposedPorts,
@@ -465,6 +473,7 @@ func (r *RuntimeService) Update(req request.RuntimeUpdate) error {
if err != nil {
return err
}
runtime.Remark = req.Remark
runtime.Env = string(envContent)
runtime.DockerCompose = string(composeContent)
@@ -719,25 +728,27 @@ func (r *RuntimeService) InstallPHPExtension(req request.PHPExtensionInstallReq)
return err
}
client, err := docker.NewClient()
if err != nil {
return err
}
defer client.Close()
if err == nil {
oldImageID, err := client.GetImageIDByName(runtime.Image)
if err != nil {
return err
}
err = cmdMgr.RunBashCf("docker commit %s %s", runtime.ContainerName, runtime.Image)
if err != nil {
return err
}
newImageID, err := client.GetImageIDByName(runtime.Image)
if err == nil && newImageID != oldImageID {
if err := client.DeleteImage(oldImageID); err != nil {
t.Log(fmt.Sprintf("delete old image error %v", err))
} else {
t.Log("delete old image success")
}
oldImageID, err := client.GetImageIDByName(runtime.Image)
if err != nil {
return err
}
err = cmdMgr.RunBashCf("docker commit %s %s", runtime.ContainerName, runtime.Image)
if err != nil {
return err
}
newImageID, err := client.GetImageIDByName(runtime.Image)
if err == nil && newImageID != oldImageID {
if err := client.DeleteImage(oldImageID); err != nil {
t.Log(fmt.Sprintf("delete old image error %v", err))
} else {
t.Log("delete old image success")
}
}
handlePHPDir(*runtime)
if err = restartRuntime(runtime); err != nil {
return err
}
@@ -887,7 +898,6 @@ func (r *RuntimeService) UpdatePHPConfig(req request.PHPConfigUpdate) (err error
pattern := "^" + regexp.QuoteMeta("disable_functions") + "\\s*=\\s*.*$"
if matched, _ := regexp.MatchString(pattern, line); matched {
lines[i] = "disable_functions" + " = " + strings.Join(req.DisableFunctions, ",")
break
}
case "upload_max_filesize":
pattern := "^" + regexp.QuoteMeta("post_max_size") + "\\s*=\\s*.*$"
@@ -1143,3 +1153,12 @@ func (r *RuntimeService) OperateSupervisorProcessFile(req request.PHPSupervisorP
logFile := path.Join(supervisorDir, "log", fmt.Sprintf("%s.out.log", req.SupervisorProcessFileReq.Name))
return handleSupervisorFile(req.SupervisorProcessFileReq, configDir, runtime.ContainerName, logFile)
}
func (r *RuntimeService) UpdateRemark(req request.RuntimeRemark) error {
runtime, err := runtimeRepo.GetFirst(context.Background(), repo.WithByID(req.ID))
if err != nil {
return err
}
runtime.Remark = req.Remark
return runtimeRepo.Save(runtime)
}
+87 -10
View File
@@ -6,7 +6,6 @@ import (
"context"
"encoding/json"
"fmt"
cmd2 "github.com/1Panel-dev/1Panel/agent/utils/cmd"
"io"
"os"
"os/exec"
@@ -17,6 +16,8 @@ import (
"strings"
"time"
cmd2 "github.com/1Panel-dev/1Panel/agent/utils/cmd"
"github.com/1Panel-dev/1Panel/agent/i18n"
"github.com/1Panel-dev/1Panel/agent/utils/common"
@@ -89,7 +90,7 @@ func handlePHP(create request.RuntimeCreate, runtime *model.Runtime, fileOp file
version, ok := create.Params["PHP_VERSION"]
if ok {
extensionsDir := path.Join(projectDir, "extensions", getExtensionDir(version.(string)))
_ = fileOp.CreateDir(extensionsDir, 0644)
_ = fileOp.CreateDir(extensionsDir, 0755)
}
composeContent, envContent, forms, err := handleParams(create, projectDir)
@@ -101,6 +102,13 @@ func handlePHP(create request.RuntimeCreate, runtime *model.Runtime, fileOp file
runtime.Params = string(forms)
runtime.Status = constant.StatusBuilding
go func() {
appDetail, err := appDetailRepo.GetFirst(repo.WithByID(runtime.AppDetailID))
if err == nil {
RequestDownloadCallBack(appDetail.DownloadCallBackUrl)
}
}()
go buildRuntime(runtime, "", "", false)
return
}
@@ -141,11 +149,27 @@ func reCreateRuntime(runtime *model.Runtime) {
}
}
func runComposeCmdWithLog(operate string, composePath string, logPath string) error {
cmd := exec.Command("docker", "compose", "-f", composePath, operate)
if operate == "up" {
cmd = exec.Command("docker", "compose", "-f", composePath, operate, "-d")
func getComposeCmd(composePath, operate string) *exec.Cmd {
dockerCommand := global.CONF.DockerConfig.Command
var cmd *exec.Cmd
if dockerCommand == "docker-compose" {
if operate == "up" {
cmd = exec.Command("docker-compose", "-f", composePath, operate, "-d")
} else {
cmd = exec.Command("docker-compose", "-f", composePath, operate)
}
} else {
if operate == "up" {
cmd = exec.Command("docker", "compose", "-f", composePath, operate, "-d")
} else {
cmd = exec.Command("docker", "compose", "-f", composePath, operate)
}
}
return cmd
}
func runComposeCmdWithLog(operate string, composePath string, logPath string) error {
cmd := getComposeCmd(composePath, operate)
logFile, err := os.OpenFile(logPath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, constant.FilePerm)
if err != nil {
global.LOG.Errorf("Failed to open log file: %v", err)
@@ -287,10 +311,22 @@ func buildRuntime(runtime *model.Runtime, oldImageID string, oldEnv string, rebu
_ = logFile.Close()
}()
newPHPVersion := getRuntimeEnv(runtime.Env, "PHP_VERSION")
oldPHPVersion := getRuntimeEnv(oldEnv, "PHP_VERSION")
if newPHPVersion != oldPHPVersion {
_ = os.Rename(path.Join(runtimePath, "extensions", getExtensionDir(oldPHPVersion)), path.Join(runtimePath, "extensions", getExtensionDir(newPHPVersion)))
}
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Hour)
defer cancel()
cmd := exec.CommandContext(ctx, "docker", "compose", "-f", composePath, "build")
dockerCommand := global.CONF.DockerConfig.Command
var cmd *exec.Cmd
if dockerCommand == "docker-compose" {
cmd = exec.CommandContext(ctx, "docker-compose", "-f", composePath, "build")
} else {
cmd = exec.CommandContext(ctx, "docker", "compose", "-f", composePath, "build")
}
cmd.Stdout = logFile
var stderrBuf bytes.Buffer
multiWriterStderr := io.MultiWriter(&stderrBuf, logFile)
@@ -373,6 +409,7 @@ func buildRuntime(runtime *model.Runtime, oldImageID string, oldEnv string, rebu
if deleteImageID != "" {
deleteImageByID(deleteImageID, runtime.Image, client)
}
handlePHPDir(*runtime)
if out, err := compose.DownAndUp(composePath); err != nil {
runtime.Status = constant.StatusStartErr
runtime.Message = out
@@ -553,7 +590,7 @@ func handleCompose(env gotenv.Env, composeContent []byte, create request.Runtime
serviceName = name
serviceValue = service.(map[string]interface{})
delete(serviceValue, "ports")
if create.ExposedPorts != nil && len(create.ExposedPorts) > 0 {
if len(create.ExposedPorts) > 0 {
var ports []interface{}
for i, port := range create.ExposedPorts {
containerPortStr := fmt.Sprintf("CONTAINER_PORT_%d", i)
@@ -567,12 +604,12 @@ func handleCompose(env gotenv.Env, composeContent []byte, create request.Runtime
create.Params[hostIPStr] = port.HostIP
}
if create.Type == constant.RuntimePHP {
ports = append(ports, fmt.Sprintf("127.0.0.1:${PANEL_APP_PORT_HTTP}:9000"))
ports = append(ports, "127.0.0.1:${PANEL_APP_PORT_HTTP}:9000")
}
serviceValue["ports"] = ports
} else {
if create.Type == constant.RuntimePHP {
serviceValue["ports"] = []interface{}{fmt.Sprintf("127.0.0.1:${PANEL_APP_PORT_HTTP}:9000")}
serviceValue["ports"] = []interface{}{"127.0.0.1:${PANEL_APP_PORT_HTTP}:9000"}
}
}
var environments []interface{}
@@ -959,3 +996,43 @@ func handleRuntimeDTO(res *response.RuntimeDTO, runtime model.Runtime) error {
}
return nil
}
func handlePHPDir(runtime model.Runtime) {
fileOp := files.NewFileOp()
dirs := []string{
path.Join(runtime.GetPath(), "conf"),
path.Join(runtime.GetPath(), "extensions"),
path.Join(runtime.GetPath(), "composer"),
path.Join(runtime.GetPath(), "log"),
}
for _, dir := range dirs {
_ = fileOp.ChmodR(dir, 0755, true)
_ = fileOp.ChownR(dir, strconv.Itoa(1000), strconv.Itoa(1000), true)
}
}
func HandleOldPHPRuntime() {
runtimes, _ := runtimeRepo.List(repo.WithByType(constant.RuntimePHP))
if len(runtimes) == 0 {
return
}
fileOp := files.NewFileOp()
for _, runtime := range runtimes {
composePtah := runtime.GetComposePath()
composeBytes, _ := fileOp.GetContent(composePtah)
composeContent := strings.ReplaceAll(string(composeBytes), "./conf:/usr/local/etc/php", "./conf/php.ini:/usr/local/etc/php/php.ini")
composeContent = strings.ReplaceAll(composeContent, "./conf/php-fpm.conf:/usr/local/etc/php-fpm.d/www.conf", "./conf/php-fpm.conf:/usr/local/etc/php-fpm.conf")
composeContent = strings.ReplaceAll(composeContent, "./extensions:${EXTENSION_DIR}", "./extensions:/usr/local/lib/php/extensions")
_ = fileOp.WriteFile(composePtah, strings.NewReader(composeContent), constant.DirPerm)
_ = fileOp.WriteFile(runtime.GetFPMPath(), bytes.NewReader(nginx_conf.GetWebsiteFile("php-fpm.conf")), constant.DirPerm)
supervisorConfigPath := path.Join(runtime.GetPath(), "supervisor", "supervisor.d", "php-fpm.ini")
supervisorConfigBytes, _ := fileOp.GetContent(supervisorConfigPath)
if !strings.Contains(string(supervisorConfigBytes), "nodaemonize") {
newConfigContent := strings.ReplaceAll(string(supervisorConfigBytes), "command=php-fpm", "command=php-fpm --nodaemonize")
_ = fileOp.WriteFile(supervisorConfigPath, bytes.NewReader([]byte(newConfigContent)), constant.DirPerm)
}
go func() {
_ = restartRuntime(&runtime)
}()
}
}
+18 -7
View File
@@ -68,7 +68,7 @@ func (u *SnapshotService) SnapshotImport(req dto.SnapshotImport) error {
}
for _, snapName := range req.Names {
if !strings.HasPrefix(snapName, "1panel-v2.") && !strings.HasPrefix(snapName, "snapshot-1panel-v2.") {
if !checkSnapshotIsOk(snapName) {
return fmt.Errorf("incorrect snapshot name format of %s", snapName)
}
snap, _ := snapshotRepo.Get(repo.WithByName(strings.ReplaceAll(snapName, ".tar.gz", "")))
@@ -77,11 +77,11 @@ func (u *SnapshotService) SnapshotImport(req dto.SnapshotImport) error {
}
}
for _, snap := range req.Names {
shortName := strings.TrimPrefix(snap, "snapshot_")
nameItems := strings.Split(shortName, "-")
if !strings.HasPrefix(shortName, "1panel-v") || !strings.HasSuffix(shortName, ".tar.gz") || len(nameItems) < 3 {
return fmt.Errorf("incorrect snapshot name format of %s", shortName)
}
shortName := strings.ReplaceAll(snap, "snapshot-", "")
shortName = strings.ReplaceAll(shortName, "1panel-", "")
shortName = strings.ReplaceAll(shortName, "core-", "")
shortName = strings.ReplaceAll(shortName, "agent-", "")
nameItems := strings.Split(shortName, "-linux")
if strings.HasSuffix(snap, ".tar.gz") {
snap = strings.ReplaceAll(snap, ".tar.gz", "")
}
@@ -89,7 +89,7 @@ func (u *SnapshotService) SnapshotImport(req dto.SnapshotImport) error {
Name: snap,
SourceAccountIDs: fmt.Sprintf("%v", req.BackupAccountID),
DownloadAccountID: req.BackupAccountID,
Version: nameItems[1],
Version: nameItems[0],
Description: req.Description,
Status: constant.StatusSuccess,
}
@@ -110,6 +110,7 @@ func (u *SnapshotService) LoadSnapshotData() (dto.SnapshotData, error) {
if err != nil {
return data, err
}
data.WithDockerConf = true
data.PanelData, err = loadPanelFile(fileOp)
if err != nil {
return data, err
@@ -403,3 +404,13 @@ func loadFile(pathItem string, index int, fileOp fileUtils.FileOp) ([]dto.DataTr
}
return data, nil
}
func checkSnapshotIsOk(name string) bool {
names := []string{"1panel-core-v2.", "1panel-agent-v2.", "1panel-v2.", "snapshot-1panel-core-v2.", "snapshot-1panel-agent-v2."}
for _, item := range names {
if strings.HasPrefix(name, item) {
return true
}
}
return false
}
+39 -33
View File
@@ -6,6 +6,7 @@ import (
"fmt"
"os"
"path"
"path/filepath"
"strings"
"sync"
"time"
@@ -34,7 +35,7 @@ func (u *SnapshotService) SnapshotCreate(parentTask *task.Task, req dto.Snapshot
scope = "agent"
}
if jobID == 0 {
req.Name = fmt.Sprintf("1panel-%s-%s-linux-%s-%s", versionItem.Value, scope, loadOs(), time.Now().Format(constant.DateTimeSlimLayout))
req.Name = fmt.Sprintf("1panel-%s-%s-linux-%s-%s", scope, versionItem.Value, loadOs(), time.Now().Format(constant.DateTimeSlimLayout))
}
appItem, _ := json.Marshal(req.AppData)
panelItem, _ := json.Marshal(req.PanelData)
@@ -50,14 +51,17 @@ func (u *SnapshotService) SnapshotCreate(parentTask *task.Task, req dto.Snapshot
AppData: string(appItem),
PanelData: string(panelItem),
BackupData: string(backupItem),
WithDockerConf: req.WithDockerConf,
WithMonitorData: req.WithMonitorData,
WithLoginLog: req.WithLoginLog,
WithOperationLog: req.WithOperationLog,
WithTaskLog: req.WithTaskLog,
WithSystemLog: req.WithSystemLog,
IgnoreFiles: strings.Join(req.IgnoreFiles, ","),
Version: versionItem.Value,
Status: constant.StatusWaiting,
Timeout: req.Timeout,
}
if err := snapshotRepo.Create(&snap); err != nil {
global.LOG.Errorf("create snapshot record to db failed, err: %v", err)
@@ -76,7 +80,7 @@ func (u *SnapshotService) SnapshotCreate(parentTask *task.Task, req dto.Snapshot
}
if jobID == 0 {
go func() {
_ = handleSnapshot(req, taskItem, jobID, 3, 0)
_ = handleSnapshot(req, taskItem, jobID, 3, snap.Timeout)
}()
return nil
}
@@ -106,28 +110,25 @@ func (u *SnapshotService) SnapshotReCreate(id uint) error {
return err
}
req.TaskID = taskModel.ID
taskItem, err := task.NewTaskWithOps(req.Name, task.TaskCreate, task.TaskScopeSnapshot, req.TaskID, req.ID)
taskItem, err := task.ReNewTaskWithOps(req.Name, task.TaskCreate, task.TaskScopeSnapshot, req.TaskID, req.ID)
if err != nil {
global.LOG.Errorf("new task for create snapshot failed, err: %v", err)
return err
}
_ = snapshotRepo.Update(req.ID, map[string]interface{}{"status": constant.StatusWaiting, "message": ""})
go func() {
_ = handleSnapshot(req, taskItem, 0, 0, 0)
_ = handleSnapshot(req, taskItem, 0, 3, 0)
}()
return nil
}
func handleSnapshot(req dto.SnapshotCreate, taskItem *task.Task, jobID, retry, timeout uint) error {
rootDir := path.Join(global.Dir.TmpDir, "system", req.Name)
rootDir := path.Join(global.Dir.LocalBackupDir, "tmp/system", req.Name)
openrestyDir, _ := settingRepo.GetValueByKey("WEBSITE_DIR")
itemHelper := snapHelper{SnapID: req.ID, Task: *taskItem, FileOp: files.NewFileOp(), Ctx: context.Background(), OpenrestyDir: openrestyDir}
baseDir := path.Join(rootDir, "base")
_ = os.MkdirAll(baseDir, os.ModePerm)
if timeout == 0 {
timeout = 1800
}
taskItem.AddSubTaskWithAliasAndOps(
"SnapDBInfo",
func(t *task.Task) error {
@@ -145,7 +146,7 @@ func handleSnapshot(req dto.SnapshotCreate, taskItem *task.Task, jobID, retry, t
if len(req.InterruptStep) == 0 || req.InterruptStep == "SnapBaseInfo" {
taskItem.AddSubTaskWithAliasAndOps(
"SnapBaseInfo",
func(t *task.Task) error { return snapBaseData(itemHelper, baseDir) },
func(t *task.Task) error { return snapBaseData(itemHelper, baseDir, req.WithDockerConf) },
nil, int(retry), time.Duration(timeout)*time.Second,
)
req.InterruptStep = ""
@@ -233,6 +234,7 @@ func loadDbConn(snap *snapHelper, targetDir string, req dto.SnapshotCreate) erro
if err != nil {
return err
}
_ = os.Remove(path.Join(targetDir, "db/session.db"))
agentDb, err := common.LoadDBConnByPathWithErr(path.Join(targetDir, "db/agent.db"), "agent.db")
snap.Task.LogWithStatus(i18n.GetWithName("SnapNewDB", "agent"), err)
@@ -284,7 +286,7 @@ func loadDbConn(snap *snapHelper, targetDir string, req dto.SnapshotCreate) erro
return nil
}
func snapBaseData(snap snapHelper, targetDir string) error {
func snapBaseData(snap snapHelper, targetDir string, withDockerConf bool) error {
snap.Task.Log("---------------------- 2 / 8 ----------------------")
snap.Task.LogStart(i18n.GetMsgByKey("SnapBaseInfo"))
@@ -321,11 +323,13 @@ func snapBaseData(snap snapHelper, targetDir string) error {
return err
}
if snap.FileOp.Stat("/etc/docker/daemon.json") {
err = snap.FileOp.CopyFile("/etc/docker/daemon.json", targetDir)
snap.Task.LogWithStatus(i18n.GetWithName("SnapCopy", "/etc/docker/daemon.json"), err)
if err != nil {
return err
if withDockerConf {
if snap.FileOp.Stat(constant.DaemonJsonPath) {
err = snap.FileOp.CopyFile(constant.DaemonJsonPath, targetDir)
snap.Task.LogWithStatus(i18n.GetWithName("SnapCopy", constant.DaemonJsonPath), err)
if err != nil {
return err
}
}
}
@@ -377,8 +381,8 @@ func snapAppImage(snap snapHelper, req dto.SnapshotCreate, targetDir string) err
}
}
snap.Task.Log(strings.Join(imageList, " "))
if len(imageList) != 0 {
snap.Task.Log(strings.Join(imageList, " "))
snap.Task.Logf("docker save %s | gzip -c > %s", strings.Join(imageList, " "), path.Join(targetDir, "images.tar.gz"))
std, err := cmd.NewCommandMgr(cmd.WithTimeout(10*time.Minute)).RunWithStdoutBashCf("docker save %s | gzip -c > %s", strings.Join(imageList, " "), path.Join(targetDir, "images.tar.gz"))
if err != nil {
@@ -386,6 +390,8 @@ func snapAppImage(snap snapHelper, req dto.SnapshotCreate, targetDir string) err
return errors.New(std)
}
snap.Task.LogSuccess(i18n.GetMsgByKey("SnapDockerSave"))
} else {
snap.Task.Log(i18n.GetMsgByKey("SnapInstallAppImageEmpty"))
}
return nil
}
@@ -395,7 +401,9 @@ func snapBackupData(snap snapHelper, req dto.SnapshotCreate, targetDir string) e
snap.Task.LogStart(i18n.GetMsgByKey("SnapLocalBackup"))
excludes := loadBackupExcludes(snap, req.BackupData)
excludes = append(excludes, req.IgnoreFiles...)
excludes = append(excludes, "./system_snapshot")
excludes = append(excludes, "./tmp")
for _, item := range req.AppData {
for _, itemApp := range item.Children {
if itemApp.Label == "appBackup" {
@@ -418,7 +426,8 @@ func loadBackupExcludes(snap snapHelper, req []dto.DataTree) []string {
if err := snap.snapAgentDB.Where("file_dir = ? AND file_name = ?", strings.TrimPrefix(path.Dir(item.Path), global.Dir.LocalBackupDir+"/"), path.Base(item.Path)).Delete(&model.BackupRecord{}).Error; err != nil {
snap.Task.LogWithStatus("delete backup file from database", err)
}
excludes = append(excludes, "."+strings.TrimPrefix(item.Path, global.Dir.LocalBackupDir))
itemDir, _ := filepath.Rel(item.Path, global.Dir.LocalBackupDir)
excludes = append(excludes, itemDir)
} else {
excludes = append(excludes, loadBackupExcludes(snap, item.Children)...)
}
@@ -430,7 +439,8 @@ func loadAppBackupExcludes(req []dto.DataTree) []string {
for _, item := range req {
if len(item.Children) == 0 {
if !item.IsCheck {
excludes = append(excludes, "."+strings.TrimPrefix(item.Path, path.Join(global.Dir.LocalBackupDir)))
itemDir, _ := filepath.Rel(item.Path, global.Dir.LocalBackupDir)
excludes = append(excludes, itemDir)
}
} else {
excludes = append(excludes, loadAppBackupExcludes(item.Children)...)
@@ -463,26 +473,21 @@ func snapPanelData(snap snapHelper, req dto.SnapshotCreate, targetDir string) er
rootDir := global.Dir.DataDir
if strings.Contains(global.Dir.LocalBackupDir, rootDir) {
excludes = append(excludes, "."+strings.ReplaceAll(global.Dir.LocalBackupDir, rootDir, ""))
itemDir, _ := filepath.Rel(rootDir, global.Dir.LocalBackupDir)
excludes = append(excludes, itemDir)
}
if len(snap.OpenrestyDir) != 0 && strings.Contains(snap.OpenrestyDir, rootDir) {
excludes = append(excludes, "."+strings.ReplaceAll(snap.OpenrestyDir, rootDir, ""))
}
ignoreVal, _ := settingRepo.Get(settingRepo.WithByKey("SnapshotIgnore"))
rules := strings.Split(ignoreVal.Value, ",")
for _, ignore := range rules {
if len(ignore) == 0 || cmd.CheckIllegal(ignore) {
continue
}
excludes = append(excludes, "."+strings.ReplaceAll(ignore, rootDir, ""))
itemDir, _ := filepath.Rel(rootDir, snap.OpenrestyDir)
excludes = append(excludes, itemDir)
}
excludes = append(excludes, req.IgnoreFiles...)
err := snap.FileOp.TarGzCompressPro(false, rootDir, path.Join(targetDir, "1panel_data.tar.gz"), "", strings.Join(excludes, ","))
snap.Task.LogWithStatus(i18n.GetMsgByKey("SnapCompressPanel"), err)
if err != nil {
return err
}
if len(snap.OpenrestyDir) != 0 {
err := snap.FileOp.TarGzCompressPro(false, snap.OpenrestyDir, path.Join(targetDir, "website.tar.gz"), "", "")
err := snap.FileOp.TarGzCompressPro(false, snap.OpenrestyDir, path.Join(targetDir, "website.tar.gz"), "", strings.Join(req.IgnoreFiles, ","))
snap.Task.LogWithStatus(i18n.GetMsgByKey("SnapWebsite"), err)
if err != nil {
return err
@@ -496,7 +501,8 @@ func loadPanelExcludes(req []dto.DataTree) []string {
for _, item := range req {
if len(item.Children) == 0 {
if !item.IsCheck {
excludes = append(excludes, "."+strings.TrimPrefix(item.Path, path.Join(global.Dir.BaseDir, "1panel")))
itemDir, _ := filepath.Rel(item.Path, path.Join(global.Dir.BaseDir, "1panel"))
excludes = append(excludes, itemDir)
}
} else {
excludes = append(excludes, loadPanelExcludes(item.Children)...)
@@ -509,7 +515,7 @@ func snapCompress(snap snapHelper, rootDir string, secret string) error {
snap.Task.Log("---------------------- 7 / 8 ----------------------")
snap.Task.LogStart(i18n.GetMsgByKey("SnapCompress"))
tmpDir := path.Join(global.Dir.TmpDir, "system")
tmpDir := path.Join(global.Dir.LocalBackupDir, "tmp/system")
fileName := fmt.Sprintf("%s.tar.gz", path.Base(rootDir))
err := snap.FileOp.TarGzCompressPro(true, rootDir, path.Join(tmpDir, fileName), secret, "")
snap.Task.LogWithStatus(i18n.GetMsgByKey("SnapCompressFile"), err)
@@ -533,7 +539,7 @@ func snapUpload(snap snapHelper, accounts string, file string) error {
snap.Task.Log("---------------------- 8 / 8 ----------------------")
snap.Task.LogStart(i18n.GetMsgByKey("SnapUpload"))
source := path.Join(global.Dir.TmpDir, "system", path.Base(file))
source := path.Join(global.Dir.LocalBackupDir, "tmp/system", path.Base(file))
accountMap, err := NewBackupClientMap(strings.Split(accounts, ","))
snap.Task.LogWithStatus(i18n.GetMsgByKey("SnapLoadBackup"), err)
if err != nil {
+55 -22
View File
@@ -38,6 +38,18 @@ func (u *SnapshotService) SnapshotRecover(req dto.SnapshotRecover) error {
_ = snapshotRepo.Update(snap.ID, map[string]interface{}{"recover_status": constant.StatusFailed, "recover_message": errInfo})
return errors.New(errInfo)
}
if !strings.Contains(snap.Name, "-v2.") {
return errors.New("snapshots are currently not supported for recovery across major versions")
}
if !strings.Contains(snap.Name, "-core") && !strings.Contains(snap.Name, "-agent") {
return errors.New("the name of the snapshot file does not conform to the format")
}
if strings.Contains(snap.Name, "-core") && !global.IsMaster {
return errors.New("the snapshot of the master node cannot be restored on the agent nodes")
}
if strings.Contains(snap.Name, "-agent") && global.IsMaster {
return errors.New("the snapshot of the agent node cannot be restored on the master node")
}
if len(snap.RollbackStatus) != 0 && snap.RollbackStatus != constant.StatusSuccess {
req.IsNew = true
}
@@ -55,12 +67,18 @@ func (u *SnapshotService) SnapshotRecover(req dto.SnapshotRecover) error {
} else {
_ = snapshotRepo.Update(snap.ID, map[string]interface{}{"task_recover_id": req.TaskID})
}
taskItem, err := task.NewTaskWithOps(snap.Name, task.TaskRecover, task.TaskScopeSnapshot, req.TaskID, snap.ID)
var taskItem *task.Task
if req.IsNew {
taskItem, err = task.NewTaskWithOps(snap.Name, task.TaskRecover, task.TaskScopeSnapshot, req.TaskID, snap.ID)
} else {
taskItem, err = task.ReNewTaskWithOps(snap.Name, task.TaskRecover, task.TaskScopeSnapshot, req.TaskID, snap.ID)
}
if err != nil {
global.LOG.Errorf("new task for create snapshot failed, err: %v", err)
return err
}
rootDir := path.Join(global.Dir.TmpDir, "system", snap.Name)
rootDir := path.Join(global.Dir.LocalBackupDir, "tmp/system", snap.Name)
if _, err := os.Stat(rootDir); err != nil && os.IsNotExist(err) {
_ = os.MkdirAll(rootDir, os.ModePerm)
}
@@ -84,7 +102,7 @@ func (u *SnapshotService) SnapshotRecover(req dto.SnapshotRecover) error {
taskItem.AddSubTaskWithAlias(
"RecoverDecompress",
func(t *task.Task) error {
itemHelper.Task.Log("---------------------- 2 / 10 ----------------------")
itemHelper.Task.Log("---------------------- 2 / 11 ----------------------")
itemHelper.Task.LogStart(i18n.GetWithName("RecoverDecompress", snap.Name))
err := itemHelper.FileOp.TarGzExtractPro(fmt.Sprintf("%s/%s.tar.gz", rootDir, snap.Name), rootDir, req.Secret)
itemHelper.Task.LogWithStatus(i18n.GetMsgByKey("Decompress"), err)
@@ -140,7 +158,7 @@ func (u *SnapshotService) SnapshotRecover(req dto.SnapshotRecover) error {
taskItem.AddSubTaskWithAlias(
"RecoverBackups",
func(t *task.Task) error {
itemHelper.Task.Log("---------------------- 8 / 10 ----------------------")
itemHelper.Task.Log("---------------------- 8 / 11 ----------------------")
itemHelper.Task.LogStart(i18n.GetWithName("RecoverBackups", snap.Name))
err := itemHelper.FileOp.TarGzExtractPro(path.Join(rootDir, snap.Name, "/1panel_backup.tar.gz"), snapJson.BackupDataDir, "")
itemHelper.Task.LogWithStatus(i18n.GetMsgByKey("Decompress"), err)
@@ -150,11 +168,30 @@ func (u *SnapshotService) SnapshotRecover(req dto.SnapshotRecover) error {
)
req.IsNew = true
}
if req.IsNew || snap.InterruptStep == "RecoverWebsite" {
taskItem.AddSubTaskWithAlias(
"RecoverWebsite",
func(t *task.Task) error {
itemHelper.Task.Log("---------------------- 9 / 11 ----------------------")
itemHelper.Task.LogStart(i18n.GetWithName("RecoverWebsite", snap.Name))
webFile := path.Join(rootDir, snap.Name, "/website.tar.gz")
_ = itemHelper.FileOp.CreateDir(snapJson.OperestyDir, os.ModePerm)
var err error
if itemHelper.FileOp.Stat(webFile) {
err = itemHelper.FileOp.TarGzExtractPro(webFile, snapJson.OperestyDir, "")
}
itemHelper.Task.LogWithStatus(i18n.GetMsgByKey("Decompress"), err)
return err
},
nil,
)
req.IsNew = true
}
if req.IsNew || snap.InterruptStep == "RecoverPanelData" {
taskItem.AddSubTaskWithAlias(
"RecoverPanelData",
func(t *task.Task) error {
itemHelper.Task.Log("---------------------- 9 / 10 ----------------------")
itemHelper.Task.Log("---------------------- 10 / 11 ----------------------")
itemHelper.Task.LogStart(i18n.GetWithName("RecoverPanelData", snap.Name))
err := itemHelper.FileOp.TarGzExtractPro(path.Join(rootDir, snap.Name, "/1panel_data.tar.gz"), path.Join(snapJson.BaseDir, "1panel"), "")
itemHelper.Task.LogWithStatus(i18n.GetMsgByKey("Decompress"), err)
@@ -195,7 +232,7 @@ func (u *SnapshotService) SnapshotRecover(req dto.SnapshotRecover) error {
}
func handleDownloadSnapshot(itemHelper *snapRecoverHelper, snap model.Snapshot, targetDir string) error {
itemHelper.Task.Log("---------------------- 1 / 10 ----------------------")
itemHelper.Task.Log("---------------------- 1 / 11 ----------------------")
itemHelper.Task.LogStart(i18n.GetMsgByKey("RecoverDownload"))
account, client, err := NewBackupClientWithID(snap.DownloadAccountID)
@@ -214,7 +251,7 @@ func handleDownloadSnapshot(itemHelper *snapRecoverHelper, snap model.Snapshot,
}
func backupBeforeRecover(name string, itemHelper *snapRecoverHelper) error {
itemHelper.Task.Log("---------------------- 3 / 10 ----------------------")
itemHelper.Task.Log("---------------------- 3 / 11 ----------------------")
itemHelper.Task.LogStart(i18n.GetMsgByKey("BackupBeforeRecover"))
rootDir := fmt.Sprintf("%s/1panel_original/original_%s", global.Dir.BaseDir, name)
@@ -273,9 +310,9 @@ func backupBeforeRecover(name string, itemHelper *snapRecoverHelper) error {
if err != nil {
return err
}
if itemHelper.FileOp.Stat("/etc/docker/daemon.json") {
err = itemHelper.FileOp.CopyFile("/etc/docker/daemon.json", baseDir)
itemHelper.Task.LogWithStatus(i18n.GetWithName("SnapCopy", "/etc/docker/daemon.json"), err)
if itemHelper.FileOp.Stat(constant.DaemonJsonPath) {
err = itemHelper.FileOp.CopyFile(constant.DaemonJsonPath, baseDir)
itemHelper.Task.LogWithStatus(i18n.GetWithName("SnapCopy", constant.DaemonJsonPath), err)
if err != nil {
return err
}
@@ -284,7 +321,7 @@ func backupBeforeRecover(name string, itemHelper *snapRecoverHelper) error {
}
func readFromJson(rootDir string, itemHelper *snapRecoverHelper) (SnapshotJson, error) {
itemHelper.Task.Log("---------------------- 4 / 10 ----------------------")
itemHelper.Task.Log("---------------------- 4 / 11 ----------------------")
itemHelper.Task.LogStart(i18n.GetMsgByKey("Readjson"))
snapJsonPath := path.Join(rootDir, "base/snapshot.json")
@@ -308,7 +345,7 @@ func readFromJson(rootDir string, itemHelper *snapRecoverHelper) (SnapshotJson,
}
func recoverAppData(src string, itemHelper *snapRecoverHelper) error {
itemHelper.Task.Log("---------------------- 5 / 10 ----------------------")
itemHelper.Task.Log("---------------------- 5 / 11 ----------------------")
itemHelper.Task.LogStart(i18n.GetMsgByKey("RecoverApp"))
if _, err := os.Stat(path.Join(src, "images.tar.gz")); err != nil {
@@ -325,7 +362,7 @@ func recoverAppData(src string, itemHelper *snapRecoverHelper) error {
}
func recoverBaseData(src string, itemHelper *snapRecoverHelper) error {
itemHelper.Task.Log("---------------------- 6 / 10 ----------------------")
itemHelper.Task.Log("---------------------- 6 / 11 ----------------------")
itemHelper.Task.LogStart(i18n.GetMsgByKey("SnapBaseInfo"))
if global.IsMaster {
@@ -356,15 +393,11 @@ func recoverBaseData(src string, itemHelper *snapRecoverHelper) error {
return err
}
daemonJsonPath := "/etc/docker/daemon.json"
_, errSrc := os.Stat(path.Join(src, "docker/daemon.json"))
_, errPath := os.Stat(daemonJsonPath)
if os.IsNotExist(errSrc) && os.IsNotExist(errPath) {
if !itemHelper.FileOp.Stat(path.Join(src, "daemon.json")) {
itemHelper.Task.Log(i18n.GetMsgByKey("RecoverDaemonJsonEmpty"))
return nil
}
if errSrc == nil {
err = itemHelper.FileOp.CopyFile(path.Join(src, "docker/daemon.json"), "/etc/docker")
} else {
err = itemHelper.FileOp.CopyFile(path.Join(src, "daemon.json"), path.Dir(constant.DaemonJsonPath))
itemHelper.Task.Log(i18n.GetMsgByKey("RecoverDaemonJson"))
if err != nil {
return fmt.Errorf("recover docker daemon.json failed, err: %v", err)
@@ -378,7 +411,7 @@ func recoverBaseData(src string, itemHelper *snapRecoverHelper) error {
}
func recoverDBData(src string, itemHelper *snapRecoverHelper) error {
itemHelper.Task.Log("---------------------- 7 / 10 ----------------------")
itemHelper.Task.Log("---------------------- 7 / 11 ----------------------")
itemHelper.Task.LogStart(i18n.GetMsgByKey("RecoverDBData"))
err := itemHelper.FileOp.CopyDirWithExclude(src, global.Dir.DataDir, nil)
@@ -387,7 +420,7 @@ func recoverDBData(src string, itemHelper *snapRecoverHelper) error {
}
func restartCompose(composePath string, itemHelper *snapRecoverHelper) error {
itemHelper.Task.Log("---------------------- 10 / 10 ----------------------")
itemHelper.Task.Log("---------------------- 11 / 11 ----------------------")
itemHelper.Task.LogStart(i18n.GetMsgByKey("RecoverCompose"))
composes, err := composeRepo.ListRecord()
+5 -2
View File
@@ -76,9 +76,12 @@ func (u *SnapshotService) SnapshotRollback(req dto.SnapshotRecover) error {
nil,
)
taskItem.AddSubTask(
i18n.GetWithName("SnapCopy", "/etc/docker/daemon.json"),
i18n.GetWithName("SnapCopy", constant.DaemonJsonPath),
func(t *task.Task) error {
return FileOp.CopyFile(path.Join(baseDir, "daemon.json"), "/etc/docker")
if FileOp.Stat(path.Join(baseDir, "daemon.json")) {
return FileOp.CopyFile(path.Join(baseDir, "daemon.json"), path.Dir(constant.DaemonJsonPath))
}
return nil
},
nil,
)
+25 -31
View File
@@ -257,7 +257,7 @@ func (w WebsiteService) CreateWebsite(create request.WebsiteCreate) (err error)
var (
domains []model.WebsiteDomain
)
domains, _, _, err = getWebsiteDomains(create.Domains, defaultHttpPort, 0)
domains, _, _, err = getWebsiteDomains(create.Domains, defaultHttpPort, nginxInstall.HttpsPort, 0)
if err != nil {
return err
}
@@ -388,7 +388,7 @@ func (w WebsiteService) CreateWebsite(create request.WebsiteCreate) (err error)
switch runtime.Type {
case constant.RuntimePHP:
if runtime.Resource == constant.ResourceAppstore {
if !checkImageExist(nil, runtime.Image) {
if !checkImageLike(nil, runtime.Image) {
return buserr.WithName("ErrImageNotExist", runtime.Name)
}
website.Proxy = fmt.Sprintf("127.0.0.1:%s", runtime.Port)
@@ -415,6 +415,7 @@ func (w WebsiteService) CreateWebsite(create request.WebsiteCreate) (err error)
return err
}
website.ParentWebsiteID = parentWebsite.ID
website.SiteDir = create.SiteDir
}
configNginx := func(t *task.Task) error {
@@ -679,7 +680,7 @@ func (w WebsiteService) CreateWebsiteDomain(create request.WebsiteDomainCreate)
domainModels []model.WebsiteDomain
addPorts []int
)
httpPort, _, err := getAppInstallPort(constant.AppOpenresty)
httpPort, httpsPort, err := getAppInstallPort(constant.AppOpenresty)
if err != nil {
return nil, err
}
@@ -688,7 +689,7 @@ func (w WebsiteService) CreateWebsiteDomain(create request.WebsiteDomainCreate)
return nil, err
}
domainModels, addPorts, _, err = getWebsiteDomains(create.Domains, httpPort, create.WebsiteID)
domainModels, addPorts, _, err = getWebsiteDomains(create.Domains, httpPort, httpsPort, create.WebsiteID)
if err != nil {
return nil, err
}
@@ -922,18 +923,12 @@ func (w WebsiteService) GetWebsiteHTTPS(websiteId uint) (response.WebsiteHTTPS,
res response.WebsiteHTTPS
httpsPorts []string
)
websiteDomains, _ := websiteDomainRepo.GetBy(websiteDomainRepo.WithWebsiteId(websiteId))
for _, domain := range websiteDomains {
if domain.SSL {
httpsPorts = append(httpsPorts, strconv.Itoa(domain.Port))
}
}
if len(httpsPorts) == 0 {
nginxInstall, _ := getAppInstallByKey(constant.AppOpenresty)
res.HttpsPort = strconv.Itoa(nginxInstall.HttpsPort)
} else {
res.HttpsPort = strings.Join(httpsPorts, ",")
httpsPortsMap := getHttpsPort(websiteId)
for port := range httpsPortsMap {
httpsPorts = append(httpsPorts, strconv.Itoa(port))
}
res.HttpsPort = strings.Join(httpsPorts, ",")
if website.WebsiteSSLID == 0 {
res.Enable = false
return res, nil
@@ -981,10 +976,6 @@ func (w WebsiteService) OpWebsiteHTTPS(ctx context.Context, req request.WebsiteH
res response.WebsiteHTTPS
websiteSSL model.WebsiteSSL
)
nginxInstall, err := getAppInstallByKey(constant.AppOpenresty)
if err != nil {
return nil, err
}
if err = ChangeHSTSConfig(req.Hsts, req.Http3, website); err != nil {
return nil, err
}
@@ -995,24 +986,27 @@ func (w WebsiteService) OpWebsiteHTTPS(ctx context.Context, req request.WebsiteH
website.Protocol = constant.ProtocolHTTP
website.WebsiteSSLID = 0
httpsPorts, err := getHttpsPort(&website)
websiteDomains, _ := websiteDomainRepo.GetBy(websiteDomainRepo.WithWebsiteId(website.ID))
ports := make(map[int]struct{})
for _, domain := range websiteDomains {
ports[domain.Port] = struct{}{}
}
for port := range ports {
if err = removeSSLListen(website, []string{strconv.Itoa(port)}); err != nil {
return nil, err
}
}
nginxInstall, err := getAppInstallByKey(constant.AppOpenresty)
if err != nil {
return nil, err
}
if len(httpsPorts) == 1 && httpsPorts[0] == nginxInstall.HttpsPort {
httpsPortStr := strconv.Itoa(httpsPorts[0])
if _, ok := ports[nginxInstall.HttpsPort]; !ok {
httpsPortStr := strconv.Itoa(nginxInstall.HttpsPort)
if err = deleteListenAndServerName(website, []string{httpsPortStr, "[::]:" + httpsPortStr}, []string{}); err != nil {
return nil, err
}
} else {
for _, port := range httpsPorts {
httpsPortStr := strconv.Itoa(port)
if err = removeSSLListen(website, []string{httpsPortStr}); err != nil {
return nil, err
}
}
}
nginxParams := getNginxParamsFromStaticFile(dto.SSL, nil)
nginxParams = append(nginxParams,
dto.NginxParam{
@@ -1360,7 +1354,7 @@ func (w WebsiteService) ChangePHPVersion(req request.WebsitePHPVersionReq) error
return err
}
defer client.Close()
if !checkImageExist(client, oldRuntime.Image) {
if !checkImageLike(client, oldRuntime.Image) {
return buserr.WithName("ErrImageNotExist", oldRuntime.Name)
}
}
+1 -1
View File
@@ -524,7 +524,7 @@ func (w WebsiteSSLService) Update(update request.WebsiteSSLUpdate) error {
if update.OtherDomains != "" {
otherDomainArray := strings.Split(update.OtherDomains, "\n")
for _, domain := range otherDomainArray {
if !common.IsValidDomain(domain) {
if websiteSSL.Provider != constant.SelfSigned && !common.IsValidDomain(domain) {
return buserr.WithName("ErrDomainFormat", domain)
}
domains = append(domains, domain)
+84 -46
View File
@@ -5,6 +5,7 @@ import (
"encoding/json"
"fmt"
"log"
"net"
"os"
"path"
"path/filepath"
@@ -182,8 +183,8 @@ func configDefaultNginx(website *model.Website, domains []model.WebsiteDomain, a
return err
}
configPath := GetSitePath(*website, SiteConf)
nginxContent := string(nginx_conf.WebsiteDefault)
config, err := parser.NewStringParser(nginxContent).Parse()
nginxContent := nginx_conf.GetWebsiteFile("website_default.conf")
config, err := parser.NewStringParser(string(nginxContent)).Parse()
if err != nil {
return err
}
@@ -651,22 +652,33 @@ func createPemFile(website model.Website, websiteSSL model.WebsiteSSL) error {
return nil
}
func getHttpsPort(website *model.Website) ([]int, error) {
websiteDomains, _ := websiteDomainRepo.GetBy(websiteDomainRepo.WithWebsiteId(website.ID))
var httpsPorts []int
for _, domain := range websiteDomains {
if domain.SSL {
httpsPorts = append(httpsPorts, domain.Port)
func getHttpsPort(websiteID uint) map[int]struct{} {
domains, err := websiteDomainRepo.GetBy(websiteDomainRepo.WithWebsiteId(websiteID))
if err != nil {
return nil
}
httpsPorts := make(map[int]struct{})
nginxInstall, _ := getAppInstallByKey(constant.AppOpenresty)
hasDefaultPort := false
for _, domain := range domains {
if domain.Port == nginxInstall.HttpPort {
hasDefaultPort = true
}
if domain.SSL {
httpsPorts[domain.Port] = struct{}{}
}
}
if hasDefaultPort {
httpsPorts[nginxInstall.HttpsPort] = struct{}{}
}
if len(httpsPorts) == 0 {
nginxInstall, err := getAppInstallByKey(constant.AppOpenresty)
if err != nil {
return nil, err
for _, domain := range domains {
if !domain.SSL {
httpsPorts[domain.Port] = struct{}{}
}
}
httpsPorts = append(httpsPorts, nginxInstall.HttpsPort)
}
return httpsPorts, nil
return httpsPorts
}
func applySSL(website *model.Website, websiteSSL model.WebsiteSSL, req request.WebsiteHTTPSOp) error {
@@ -678,63 +690,71 @@ func applySSL(website *model.Website, websiteSSL model.WebsiteSSL, req request.W
if err != nil {
return nil
}
noDefaultPort := true
httpPorts := make(map[int]struct{})
httpsPorts := make(map[int]struct{})
hasDefaultPort := false
for _, domain := range domains {
if domain.Port == 80 {
noDefaultPort = false
if domain.Port == nginxFull.Install.HttpPort {
hasDefaultPort = true
}
if domain.Port != 80 && !domain.SSL {
if domain.SSL {
httpsPorts[domain.Port] = struct{}{}
} else {
httpPorts[domain.Port] = struct{}{}
}
}
if hasDefaultPort {
httpsPorts[nginxFull.Install.HttpsPort] = struct{}{}
}
if len(httpsPorts) == 0 {
for port := range httpPorts {
httpsPorts[port] = struct{}{}
}
}
config := nginxFull.SiteConfig.Config
server := config.FindServers()[0]
httpPort := strconv.Itoa(nginxFull.Install.HttpPort)
httpsPort, err := getHttpsPort(website)
if err != nil {
return err
}
httpPortIPV6 := "[::]:" + httpPort
defaultHttpPort := strconv.Itoa(nginxFull.Install.HttpPort)
defaultHttpPortIPV6 := "[::]:" + defaultHttpPort
for _, port := range httpsPort {
if _, ok := httpPorts[port]; !ok {
server.DeleteListen(strconv.Itoa(port))
}
setListen(server, strconv.Itoa(port), website.IPV6, req.Http3, website.DefaultServer, true)
for port := range httpsPorts {
portStr := strconv.Itoa(port)
server.RemoveListenByBind(portStr)
server.RemoveListenByBind("[::]:" + portStr)
setListen(server, portStr, website.IPV6, req.Http3, website.DefaultServer, true)
}
server.UpdateDirective("http2", []string{"on"})
switch req.HttpConfig {
case constant.HTTPSOnly:
server.RemoveListenByBind(httpPort)
server.RemoveListenByBind(httpPortIPV6)
server.RemoveListenByBind(defaultHttpPort)
server.RemoveListenByBind(defaultHttpPortIPV6)
server.RemoveDirective("if", []string{"($scheme"})
case constant.HTTPToHTTPS:
if !noDefaultPort {
server.UpdateListen(httpPort, website.DefaultServer)
}
if website.IPV6 {
server.UpdateListen(httpPortIPV6, website.DefaultServer)
if hasDefaultPort {
server.UpdateListen(defaultHttpPort, website.DefaultServer)
if website.IPV6 {
server.UpdateListen(defaultHttpPortIPV6, website.DefaultServer)
}
}
server.AddHTTP2HTTPS()
case constant.HTTPAlso:
if !noDefaultPort {
server.UpdateListen(httpPort, website.DefaultServer)
if hasDefaultPort {
server.UpdateListen(defaultHttpPort, website.DefaultServer)
if website.IPV6 {
server.UpdateListen(defaultHttpPortIPV6, website.DefaultServer)
}
}
server.RemoveDirective("if", []string{"($scheme"})
if website.IPV6 {
server.UpdateListen(httpPortIPV6, website.DefaultServer)
}
}
if !req.Hsts {
server.RemoveDirective("add_header", []string{"Strict-Transport-Security", "\"max-age=31536000\""})
}
if !req.Http3 {
for _, port := range httpsPort {
for port := range httpsPorts {
server.RemoveListen(strconv.Itoa(port), "quic")
if website.IPV6 {
httpsPortIPV6 := "[::]:" + strconv.Itoa(port)
@@ -1011,7 +1031,7 @@ func chownRootDir(path string) error {
return nil
}
func getWebsiteDomains(domains []request.WebsiteDomain, defaultPort int, websiteID uint) (domainModels []model.WebsiteDomain, addPorts []int, addDomains []string, err error) {
func getWebsiteDomains(domains []request.WebsiteDomain, defaultHTTPPort, defaultHTTPsPort int, websiteID uint) (domainModels []model.WebsiteDomain, addPorts []int, addDomains []string, err error) {
var (
ports = make(map[int]struct{})
existPort = make(map[int]struct{})
@@ -1036,7 +1056,7 @@ func getWebsiteDomains(domains []request.WebsiteDomain, defaultPort int, website
domainModel.Domain = strings.ToLower(domainModel.Domain)
domainModel.Port = domain.Port
if domain.Port == 0 {
domain.Port = defaultPort
domain.Port = defaultHTTPPort
}
domainModel.SSL = domain.SSL
domainModel.WebsiteID = websiteID
@@ -1057,7 +1077,7 @@ func getWebsiteDomains(domains []request.WebsiteDomain, defaultPort int, website
}
for port := range ports {
if port == defaultPort {
if port == defaultHTTPPort || port == defaultHTTPsPort {
addPorts = append(addPorts, port)
continue
}
@@ -1078,7 +1098,7 @@ func getWebsiteDomains(domains []request.WebsiteDomain, defaultPort int, website
err = buserr.WithMap("ErrPortExist", errMap, nil)
return
}
if common.ScanPort(port) {
if port != defaultHTTPsPort && common.ScanPort(port) {
err = buserr.WithDetail("ErrPortInUsed", port, nil)
return
}
@@ -1117,7 +1137,6 @@ func saveCertificateFile(websiteSSL *model.WebsiteSSL, logger *log.Logger) {
func GetSystemSSL() (bool, uint) {
sslSetting, err := settingRepo.Get(settingRepo.WithByKey("SSL"))
if err != nil {
global.LOG.Errorf("load service ssl from setting failed, err: %v", err)
return false, 0
}
if sslSetting.Value == "enable" {
@@ -1425,3 +1444,22 @@ func hasDefaultServer(params []string) bool {
}
return false
}
func ParseDomain(domain string) (*model.WebsiteDomain, error) {
host, portStr, err := net.SplitHostPort(domain)
if err != nil {
return &model.WebsiteDomain{
Domain: domain,
Port: 0,
}, nil
}
port, err := strconv.Atoi(portStr)
if err != nil {
return nil, fmt.Errorf("invalid port in domain %s: %v", domain, err)
}
return &model.WebsiteDomain{
Domain: host,
Port: port,
}, nil
}
+36 -1
View File
@@ -63,6 +63,7 @@ const (
TaskSync = "TaskSync"
TaskBuild = "TaskBuild"
TaskPull = "TaskPull"
TaskCommit = "TaskCommit"
TaskPush = "TaskPush"
TaskHandle = "TaskHandle"
)
@@ -142,6 +143,40 @@ func NewTask(name, operate, taskScope, taskID string, resourceID uint) (*Task, e
return task, nil
}
func ReNewTaskWithOps(resourceName, operate, scope, taskID string, resourceID uint) (*Task, error) {
return ReNewTask(GetTaskName(resourceName, operate, scope), operate, scope, taskID, resourceID)
}
func ReNewTask(name, operate, taskScope, taskID string, resourceID uint) (*Task, error) {
if taskID == "" {
return NewTask(name, operate, taskScope, taskID, resourceID)
}
logDir := path.Join(global.Dir.TaskDir, taskScope)
if _, err := os.Stat(logDir); err != nil {
if err = os.MkdirAll(logDir, constant.DirPerm); err != nil {
return nil, fmt.Errorf("failed to create log directory: %w", err)
}
}
logPath := path.Join(global.Dir.TaskDir, taskScope, taskID+".log")
file, err := os.OpenFile(logPath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, constant.FilePerm)
if err != nil {
return nil, fmt.Errorf("failed to open log file: %w", err)
}
writer := bufio.NewWriter(file)
logger := log.New(file, "", log.LstdFlags)
taskModel := &model.Task{
ID: taskID,
Name: name,
Type: taskScope,
LogFile: logPath,
Status: constant.StatusExecuting,
ResourceID: resourceID,
Operate: operate,
}
taskRepo := repo.NewITaskRepo()
task := &Task{Name: name, logFile: file, Logger: logger, taskRepo: taskRepo, Task: taskModel, Writer: writer}
return task, nil
}
func (t *Task) AddSubTask(name string, action ActionFunc, rollback RollbackFunc) {
subTask := &SubTask{RootTask: t, Name: name, Retry: 0, Timeout: 30 * time.Minute, Action: action, Rollback: rollback}
t.SubTasks = append(t.SubTasks, subTask)
@@ -158,7 +193,7 @@ func (t *Task) AddSubTaskWithOps(name string, action ActionFunc, rollback Rollba
}
func (t *Task) AddSubTaskWithAliasAndOps(key string, action ActionFunc, rollback RollbackFunc, retry int, timeout time.Duration) {
subTask := &SubTask{RootTask: t, Name: i18n.GetMsgByKey(key), Retry: retry, Timeout: timeout, Action: action, Rollback: rollback}
subTask := &SubTask{RootTask: t, Name: i18n.GetMsgByKey(key), StepAlias: key, Retry: retry, Timeout: timeout, Action: action, Rollback: rollback}
t.SubTasks = append(t.SubTasks, subTask)
}
+1
View File
@@ -1,6 +1,7 @@
base:
install_dir: /opt
mode: dev
is_demo: false
remote_url:
app_repo: https://apps-assets.fit2cloud.com
+1 -4
View File
@@ -9,9 +9,6 @@ import (
//go:embed ssl.conf
var SSL []byte
//go:embed website_default.conf
var WebsiteDefault []byte
//go:embed index.html
var Index []byte
@@ -48,7 +45,7 @@ var Upstream []byte
//go:embed sse.conf
var SSE []byte
//go:embed *.json
//go:embed *.json *.conf
var websitesFiles embed.FS
func GetWebsiteFile(filename string) []byte {
+16
View File
@@ -0,0 +1,16 @@
[global]
error_log = /var/log/php/fpm.error.log
log_level = notice
[www]
user = www-data
group = www-data
listen = 0.0.0.0:9000
pm = dynamic
pm.max_children = 10
pm.start_servers = 2
pm.min_spare_servers = 1
pm.max_spare_servers = 3
pm.status_path = /status
slowlog = /var/log/php/fpm.slow.log
request_slowlog_timeout = 3
catch_workers_output = yes
@@ -376,5 +376,12 @@
"file": "pcntl.so",
"versions": ["56","70", "71", "72", "73", "74", "80", "81", "82", "83","84"],
"installed": false
},
{
"name": "sodium",
"check": "sodium",
"file": "sodium.so",
"versions": ["70", "71", "72", "73", "74", "80", "81", "82", "83","84"],
"installed": false
}
]
@@ -15,9 +15,16 @@ server {
access_log /www/sites/domain/log/access.log main;
error_log /www/sites/domain/log/error.log;
location ~ ^/(\.user.ini|\.htaccess|\.git|\.env|\.svn|\.project|LICENSE|README.md){
return 404;
}
location ^~ /.well-known{
allow all;
root /usr/share/nginx/html;
}
if ( $uri ~ "^/\.well-known/.*\.(php|jsp|py|js|css|lua|ts|go|zip|tar\.gz|rar|7z|sql|bak)$" ) {
return 403;
}
}
+15
View File
@@ -10,3 +10,18 @@ const (
AlertPushError = "PushError"
AlertPushSuccess = "PushSuccess"
)
const (
CommonConfig = "common"
SMSConfig = "sms"
EmailConfig = "email"
)
const (
WeChat = "wechat"
SMS = "sms"
Email = "mail"
WeCom = "weCom"
DingTalk = "dingTalk"
FeiShu = "feiShu"
)
+11 -8
View File
@@ -6,14 +6,17 @@ const (
AppNormal = "Normal"
AppTakeDown = "TakeDown"
AppOpenresty = "openresty"
AppMysql = "mysql"
AppMariaDB = "mariadb"
AppPostgresql = "postgresql"
AppRedis = "redis"
AppPostgres = "postgres"
AppMongodb = "mongodb"
AppMemcached = "memcached"
AppOpenresty = "openresty"
AppMysql = "mysql"
AppMariaDB = "mariadb"
AppPostgresql = "postgresql"
AppRedis = "redis"
AppPostgres = "postgres"
AppMongodb = "mongodb"
AppMemcached = "memcached"
AppMysqlCluster = "mysql-cluster"
AppPostgresqlCluster = "postgresql-cluster"
AppRedisCluster = "redis-cluster"
AppResourceLocal = "local"
AppResourceRemote = "remote"
+2 -2
View File
@@ -13,6 +13,6 @@ const (
ComposeOpStop = "stop"
ComposeOpRestart = "restart"
ComposeOpRemove = "remove"
DaemonJsonPath = "/etc/docker/daemon.json"
)
var DaemonJsonPath = "/etc/docker/daemon.json"
+3 -3
View File
@@ -41,11 +41,11 @@ var RuntimeDefaultVolumes = map[string]string{
var PHPDefaultVolumes = map[string]string{
"${PANEL_WEBSITE_DIR}": "/www/",
"./conf": "/usr/local/etc/php",
"./conf/php.ini": "/usr/local/etc/php/php.ini",
"./conf/conf.d": "/usr/local/etc/php/conf.d",
"./conf/php-fpm.conf": "/usr/local/etc/php-fpm.d/www.conf",
"./conf/php-fpm.conf": "/usr/local/etc/php-fpm.conf",
"./log": "/var/log/php",
"./extensions": "${EXTENSION_DIR}",
"./extensions": "/usr/local/lib/php/extensions",
"./supervisor/supervisord.conf": "/etc/supervisord.conf",
"./supervisor/supervisor.d/php-fpm.ini": "/etc/supervisor.d/php-fpm.ini",
"./supervisor/supervisor.d": "/etc/supervisor.d",
+1
View File
@@ -5,6 +5,7 @@ const (
StatusCanceled = "Canceled"
StatusDone = "Done"
StatusWaiting = "Waiting"
StatusPending = "Pending"
StatusSuccess = "Success"
StatusFailed = "Failed"
StatusUploading = "Uploading"
+1 -3
View File
@@ -18,7 +18,7 @@ func NewBackupJob() *backup {
func (b *backup) Run() {
var backups []model.BackupAccount
_ = global.DB.Where("`type` in (?) AND is_public = 0", []string{constant.OneDrive, constant.ALIYUN, constant.GoogleDrive}).Find(&backups)
_ = global.DB.Where("`type` in (?) AND is_public = 0", []string{constant.OneDrive, constant.ALIYUN}).Find(&backups)
if len(backups) == 0 {
return
}
@@ -39,8 +39,6 @@ func (b *backup) Run() {
switch backupItem.Type {
case constant.OneDrive:
refreshToken, err = client.RefreshToken("refresh_token", "refreshToken", varMap)
case constant.GoogleDrive:
refreshToken, err = client.RefreshGoogleToken("refresh_token", "refreshToken", varMap)
case constant.ALIYUN:
refreshToken, err = client.RefreshALIToken(varMap)
}
+4
View File
@@ -15,6 +15,7 @@ var (
MonitorDB *gorm.DB
TaskDB *gorm.DB
CoreDB *gorm.DB
AlertDB *gorm.DB
LOG *logrus.Logger
CONF ServerConfig
@@ -30,4 +31,7 @@ var (
IsMaster bool
I18n *i18n.Localizer
AlertBaseJobID cron.EntryID
AlertResourceJobID cron.EntryID
)
+12 -12
View File
@@ -40,6 +40,7 @@ require (
github.com/shirou/gopsutil/v3 v3.24.5
github.com/sirupsen/logrus v1.9.3
github.com/spf13/afero v1.11.0
github.com/spf13/cobra v1.9.1
github.com/spf13/viper v1.19.0
github.com/studio-b12/gowebdav v0.9.0
github.com/subosito/gotenv v1.6.0
@@ -47,13 +48,13 @@ require (
github.com/upyun/go-sdk v2.1.0+incompatible
golang.org/x/crypto v0.39.0
golang.org/x/net v0.41.0
golang.org/x/oauth2 v0.29.0
golang.org/x/oauth2 v0.30.0
golang.org/x/sys v0.33.0
golang.org/x/text v0.26.0
google.golang.org/genproto v0.0.0-20241021214115-324edc3d5d38
gopkg.in/ini.v1 v1.67.0
gopkg.in/yaml.v3 v3.0.1
gorm.io/gorm v1.25.11
gorm.io/gorm v1.30.0
)
require (
@@ -98,7 +99,7 @@ require (
github.com/fvbommel/sortorder v1.1.0 // indirect
github.com/gabriel-vasile/mimetype v1.4.8 // indirect
github.com/gin-contrib/sse v0.1.0 // indirect
github.com/glebarez/go-sqlite v1.21.2 // indirect
github.com/glebarez/go-sqlite v1.22.0 // indirect
github.com/go-ini/ini v1.67.0 // indirect
github.com/go-jose/go-jose/v4 v4.0.5 // indirect
github.com/go-logr/logr v1.4.2 // indirect
@@ -106,7 +107,7 @@ require (
github.com/go-ole/go-ole v1.2.6 // indirect
github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/go-viper/mapstructure/v2 v2.2.1 // indirect
github.com/go-viper/mapstructure/v2 v2.3.0 // indirect
github.com/goccy/go-json v0.10.5 // indirect
github.com/gofrs/flock v0.12.1 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
@@ -114,7 +115,6 @@ require (
github.com/golang/snappy v0.0.4 // indirect
github.com/google/go-cmp v0.7.0 // indirect
github.com/google/go-querystring v1.1.0 // indirect
github.com/google/pprof v0.0.0-20240525223248-4bfdf5a9a2af // indirect
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect
github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.1 // indirect
github.com/h2non/filetype v1.1.1 // indirect
@@ -162,6 +162,7 @@ require (
github.com/morikuni/aec v1.0.0 // indirect
github.com/mozillazg/go-httpheader v0.2.1 // indirect
github.com/namedotcom/go v0.0.0-20180403034216-08470befbe04 // indirect
github.com/ncruces/go-strftime v0.1.9 // indirect
github.com/nrdcg/dnspod-go v0.4.0 // indirect
github.com/nrdcg/freemyip v0.3.0 // indirect
github.com/nrdcg/mailinabox v0.2.0 // indirect
@@ -184,7 +185,6 @@ require (
github.com/shoenig/go-m1cpu v0.1.6 // indirect
github.com/sourcegraph/conc v0.3.0 // indirect
github.com/spf13/cast v1.6.0 // indirect
github.com/spf13/cobra v1.9.1 // indirect
github.com/spf13/pflag v1.0.6 // indirect
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.1128 // indirect
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dnspod v1.0.1128 // indirect
@@ -224,17 +224,17 @@ require (
go.uber.org/multierr v1.9.0 // indirect
go4.org v0.0.0-20200411211856-f5505b9728dd // indirect
golang.org/x/arch v0.8.0 // indirect
golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0 // indirect
golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b // indirect
golang.org/x/mod v0.25.0 // indirect
golang.org/x/sync v0.15.0 // indirect
golang.org/x/time v0.11.0 // indirect
golang.org/x/tools v0.33.0 // indirect
golang.org/x/tools v0.34.0 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20250218202821-56aae31c358a // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20250313205543-e70fdf4c4cb4 // indirect
google.golang.org/grpc v1.72.2 // indirect
google.golang.org/protobuf v1.36.6 // indirect
modernc.org/libc v1.22.5 // indirect
modernc.org/mathutil v1.5.0 // indirect
modernc.org/memory v1.5.0 // indirect
modernc.org/sqlite v1.23.1 // indirect
modernc.org/libc v1.66.1 // indirect
modernc.org/mathutil v1.7.1 // indirect
modernc.org/memory v1.11.0 // indirect
modernc.org/sqlite v1.38.0 // indirect
)
+43 -23
View File
@@ -257,8 +257,8 @@ github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU=
github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y=
github.com/glebarez/go-sqlite v1.21.2 h1:3a6LFC4sKahUunAmynQKLZceZCOzUthkRkEAl9gAXWo=
github.com/glebarez/go-sqlite v1.21.2/go.mod h1:sfxdZyhQjTM2Wry3gVYWaW072Ri1WMdWJi0k6+3382k=
github.com/glebarez/go-sqlite v1.22.0 h1:uAcMJhaA6r3LHMTFgP0SifzgXg46yJkgxqyuyec+ruQ=
github.com/glebarez/go-sqlite v1.22.0/go.mod h1:PlBIdHe0+aUEFn+r2/uthrWq4FxbzugL0L8Li6yQJbc=
github.com/glebarez/sqlite v1.11.0 h1:wSG0irqzP6VurnMEpFGer5Li19RpIRi2qvQz++w0GMw=
github.com/glebarez/sqlite v1.11.0/go.mod h1:h8/o8j5wiAsqSPoWELDUdJXhjAhsVliSn7bWZjOhrgQ=
github.com/go-acme/lego/v4 v4.23.1 h1:lZ5fGtGESA2L9FB8dNTvrQUq3/X4QOb8ExkKyY7LSV4=
@@ -311,8 +311,8 @@ github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpv
github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
github.com/go-viper/mapstructure/v2 v2.2.1 h1:ZAaOCxANMuZx5RCeg0mBdEZk7DZasvvZIxtHqx8aGss=
github.com/go-viper/mapstructure/v2 v2.2.1/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
github.com/go-viper/mapstructure/v2 v2.3.0 h1:27XbWsHIqhbdR5TIC911OfYvgSaW93HM+dX7970Q7jk=
github.com/go-viper/mapstructure/v2 v2.3.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
github.com/go-zookeeper/zk v1.0.2/go.mod h1:nOB03cncLtlp4t+UAkGSV+9beXP/akpekBwL+UX1Qcw=
github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4=
github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
@@ -396,8 +396,8 @@ github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hf
github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20240525223248-4bfdf5a9a2af h1:kmjWCqn2qkEml422C2Rrd27c3VGxi6a/6HNq8QmHRKM=
github.com/google/pprof v0.0.0-20240525223248-4bfdf5a9a2af/go.mod h1:K1liHPHnj73Fdn/EKuT8nrFqBihUSKXoLYU0BuatOYo=
github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e h1:ijClszYn+mADRFY17kjQEVQ1XRhq2/JR1M3sGqeJoxs=
github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4=
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ=
@@ -571,9 +571,8 @@ github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6T
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/mattn/go-shellwords v1.0.12 h1:M2zGm7EW6UQJvDeQxo4T51eKPurbeFbe8WtebGE2xrk=
github.com/mattn/go-shellwords v1.0.12/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y=
github.com/mattn/go-sqlite3 v1.14.0 h1:mLyGNKR8+Vv9CAU7PphKa2hkEqxxhn8i32J6FPj1/QA=
github.com/mattn/go-sqlite3 v1.14.0/go.mod h1:JIl7NbARA7phWnGvh0LKTyg7S9BA+6gx71ShQilpsus=
github.com/mattn/go-sqlite3 v1.14.16 h1:yOQRA0RpS5PFz/oikGwBEqvAWhWg5ufRz4ETLjwpU1Y=
github.com/mattn/go-sqlite3 v1.14.16/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/mholt/archiver/v4 v4.0.0-alpha.8 h1:tRGQuDVPh66WCOelqe6LIGh0gwmfwxUrSSDunscGsRM=
github.com/mholt/archiver/v4 v4.0.0-alpha.8/go.mod h1:5f7FUYGXdJWUjESffJaYR4R60VhnHxb2X3T1teMyv5A=
@@ -650,6 +649,8 @@ github.com/nats-io/nats.go v1.12.1/go.mod h1:BPko4oXsySz4aSWeFgOHLZs3G4Jq4ZAyE6/
github.com/nats-io/nkeys v0.2.0/go.mod h1:XdZpAbhgyyODYqjTawOnIOI7VlbKSarI9Gfy1tqEu/s=
github.com/nats-io/nkeys v0.3.0/go.mod h1:gvUNGjVcM2IPr5rCsRsC6Wb3Hr2CQAm08dsxtV6A5y4=
github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c=
github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4=
github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
github.com/nicksnyder/go-i18n/v2 v2.4.0 h1:3IcvPOAvnCKwNm0TB0dLDTuawWEj+ax/RERNC+diLMM=
github.com/nicksnyder/go-i18n/v2 v2.4.0/go.mod h1:nxYSZE9M0bf3Y70gPQjN9ha7XNHX7gMc814+6wVyEI4=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
@@ -755,7 +756,6 @@ github.com/qiniu/go-sdk/v7 v7.21.1/go.mod h1:8EM2awITynlem2VML2dXGHkMYP2UyECsGLO
github.com/qiniu/x v1.10.5/go.mod h1:03Ni9tj+N2h2aKnAz+6N0Xfl8FwMEDRC2PAlxekASDs=
github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
@@ -1004,8 +1004,8 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0
golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0 h1:R84qjqJb5nVJMxqWYb3np9L5ZsaDtB+a39EqjV0JSUM=
golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0/go.mod h1:S9Xr4PYopiDyqSyp5NjCrhFrqg6A5zA2E/iPHPhqnS8=
golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b h1:M2rDM6z3Fhozi9O7NWsxAkg/yqS/lQJ6PmkyIV3YP+o=
golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b/go.mod h1:3//PLf8L/X+8b4vuAfHzxeRUl04Adcb341+IGKfnqS8=
golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
@@ -1092,6 +1092,8 @@ golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ
golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.29.0 h1:WdYw2tdTK1S8olAzWHdgeqfy+Mtm9XNhv/xJsY65d98=
golang.org/x/oauth2 v0.29.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8=
golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI=
golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -1261,8 +1263,8 @@ golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/tools v0.33.0 h1:4qz2S3zmRxbGIhDIAgjxvFutSvH5EfnsYrRBj0UI0bc=
golang.org/x/tools v0.33.0/go.mod h1:CIJMaWEY88juyUfo7UbgPqbC8rU2OqfAV1h2Qp0oMYI=
golang.org/x/tools v0.34.0 h1:qIpSLOxeCYGg9TrcJokLBG4KFA6d795g0xkBkiESGlo=
golang.org/x/tools v0.34.0/go.mod h1:pAP9OwEaY1CAW3HOmg3hLZC5Z0CCmzjAF2UQMSqNARg=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
@@ -1396,8 +1398,8 @@ gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C
gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gorm.io/gorm v1.25.11 h1:/Wfyg1B/je1hnDx3sMkX+gAlxrlZpn6X0BXRlwXlvHg=
gorm.io/gorm v1.25.11/go.mod h1:xh7N7RHfYlNc5EmcI/El95gXusucDrQnHXe0+CgWcLQ=
gorm.io/gorm v1.30.0 h1:qbT5aPv1UH8gI99OsRlvDToLxW5zR7FzS9acZDOZcgs=
gorm.io/gorm v1.30.0/go.mod h1:8Z33v652h4//uMA76KjeDH8mJXPm1QNCYrMeatR0DOE=
gotest.tools/v3 v3.5.2 h1:7koQfIKdy+I8UTetycgUqXWSDwpgv193Ka+qRsmBY8Q=
gotest.tools/v3 v3.5.2/go.mod h1:LtdLGcnqToBH83WByAAi/wiwSFCArdFIUV/xxN4pcjA=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
@@ -1407,14 +1409,32 @@ honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWh
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
modernc.org/libc v1.22.5 h1:91BNch/e5B0uPbJFgqbxXuOnxBQjlS//icfQEGmvyjE=
modernc.org/libc v1.22.5/go.mod h1:jj+Z7dTNX8fBScMVNRAYZ/jF91K8fdT2hYMThc3YjBY=
modernc.org/mathutil v1.5.0 h1:rV0Ko/6SfM+8G+yKiyI830l3Wuz1zRutdslNoQ0kfiQ=
modernc.org/mathutil v1.5.0/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E=
modernc.org/memory v1.5.0 h1:N+/8c5rE6EqugZwHii4IFsaJ7MUhoWX07J5tC/iI5Ds=
modernc.org/memory v1.5.0/go.mod h1:PkUhL0Mugw21sHPeskwZW4D6VscE/GQJOnIpCnW6pSU=
modernc.org/sqlite v1.23.1 h1:nrSBg4aRQQwq59JpvGEQ15tNxoO5pX/kUjcRNwSAGQM=
modernc.org/sqlite v1.23.1/go.mod h1:OrDj17Mggn6MhE+iPbBNf7RGKODDE9NFT0f3EwDzJqk=
modernc.org/cc/v4 v4.26.1 h1:+X5NtzVBn0KgsBCBe+xkDC7twLb/jNVj9FPgiwSQO3s=
modernc.org/cc/v4 v4.26.1/go.mod h1:uVtb5OGqUKpoLWhqwNQo/8LwvoiEBLvZXIQ/SmO6mL0=
modernc.org/ccgo/v4 v4.28.0 h1:rjznn6WWehKq7dG4JtLRKxb52Ecv8OUGah8+Z/SfpNU=
modernc.org/ccgo/v4 v4.28.0/go.mod h1:JygV3+9AV6SmPhDasu4JgquwU81XAKLd3OKTUDNOiKE=
modernc.org/fileutil v1.3.3 h1:3qaU+7f7xxTUmvU1pJTZiDLAIoJVdUSSauJNHg9yXoA=
modernc.org/fileutil v1.3.3/go.mod h1:HxmghZSZVAz/LXcMNwZPA/DRrQZEVP9VX0V4LQGQFOc=
modernc.org/gc/v2 v2.6.5 h1:nyqdV8q46KvTpZlsw66kWqwXRHdjIlJOhG6kxiV/9xI=
modernc.org/gc/v2 v2.6.5/go.mod h1:YgIahr1ypgfe7chRuJi2gD7DBQiKSLMPgBQe9oIiito=
modernc.org/goabi0 v0.0.3 h1:y81b9r3asCh6Xtse6Nz85aYGB0cG3M3U6222yap1KWI=
modernc.org/goabi0 v0.0.3/go.mod h1:CEFRnnJhKvWT1c1JTI3Avm+tgOWbkOu5oPA8eH8LnMI=
modernc.org/libc v1.66.1 h1:4uQsntXbVyAgrV+j6NhKvDiUypoJL48BWQx6sy9y8ok=
modernc.org/libc v1.66.1/go.mod h1:AiZxInURfEJx516LqEaFcrC+X38rt9G7+8ojIXQKHbo=
modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU=
modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg=
modernc.org/memory v1.11.0 h1:o4QC8aMQzmcwCK3t3Ux/ZHmwFPzE6hf2Y5LbkRs+hbI=
modernc.org/memory v1.11.0/go.mod h1:/JP4VbVC+K5sU2wZi9bHoq2MAkCnrt2r98UGeSK7Mjw=
modernc.org/opt v0.1.4 h1:2kNGMRiUjrp4LcaPuLY2PzUfqM/w9N23quVwhKt5Qm8=
modernc.org/opt v0.1.4/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns=
modernc.org/sortutil v1.2.1 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w=
modernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE=
modernc.org/sqlite v1.38.0 h1:+4OrfPQ8pxHKuWG4md1JpR/EYAh3Md7TdejuuzE7EUI=
modernc.org/sqlite v1.38.0/go.mod h1:1Bj+yES4SVvBZ4cBOpVZ6QgesMCKpJZDq0nxYzOpmNE=
modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0=
modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A=
modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50=
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
+1
View File
@@ -133,6 +133,7 @@ func Init() {
_, _ = bundle.LoadMessageFileFS(fs, "lang/ru.yaml")
_, _ = bundle.LoadMessageFileFS(fs, "lang/ms.yaml")
_, _ = bundle.LoadMessageFileFS(fs, "lang/ko.yaml")
_, _ = bundle.LoadMessageFileFS(fs, "lang/tr.yaml")
lang := GetLanguageFromDB()
global.I18n = i18n.NewLocalizer(bundle, lang)
}
+20 -2
View File
@@ -177,7 +177,7 @@ ErrPgImagePull: 'Image pull timed out, please configure image acceleration or ma
#runtime
ErrFileNotExist: '{{ .detail }} file does not exist! Please check the integrity of the source file!'
ErrImageBuildErr: 'Image build failed'
ErrImageExist: 'Image already exists!'
ErrImageExist: "Image already exists! Please modify the image name."
ErrDelWithWebsite: 'The operating environment is already associated with a website and cannot be deleted'
ErrRuntimeStart: 'Startup failed'
ErrPackageJsonNotFound: 'package.json file does not exist'
@@ -205,6 +205,8 @@ SystemLog: 'System Log'
CutWebsiteLog: 'Rotate Website Log'
FileOrDir: 'Directory / File'
UploadFile: 'Uploading backup file {{ .file }} to {{ .backup }}'
IgnoreBackupErr: 'Backup failed, error: {{ .detail }}, ignoring this error...'
IgnoreUploadErr: 'Upload failed, error: {{ .detail }}, ignoring this error...'
#toolbox
ErrNotExistUser: 'The current user does not exist, please modify and try again!'
@@ -285,6 +287,7 @@ TaskBackup: 'Backup'
TaskRecover: 'Recover'
TaskRollback: 'Rollback'
TaskPull: 'Pull'
TaskCommit: 'Commit'
TaskBuild: 'Build'
TaskPush: 'Push'
TaskHandle: 'Execute'
@@ -333,6 +336,7 @@ SnapDeleteLoginLog: 'Delete access log'
SnapDeleteMonitor: 'Delete monitoring data'
SnapRemoveSystemIP: 'Remove system IP'
SnapBaseInfo: 'Write 1Panel basic information'
SnapInstallAppImageEmpty: 'No application images selected, skipping...'
SnapInstallApp: 'Backup 1Panel installed applications'
SnapDockerSave: 'Compress installed applications'
SnapLocalBackup: 'Backup 1Panel local backup directory'
@@ -420,4 +424,18 @@ ErrAlertSync: 'Alarm information synchronization error, please check and try aga
ErrAlertRemote: 'Alarm message remote error, please check and try again!'
#task - runtime
ErrInstallExtension: "An installation task is already in progress, please wait for the task to finish"
ErrInstallExtension: "An installation task is already in progress, please wait for the task to finish"
# alert mail template
PanelAlertTitle: "Panel Alert Notification"
TestAlertTitle: "Test Email - Verify Email Connectivity"
TestAlert: "This is a test email to verify that your email sending configuration is correct."
LicenseExpirationAlert: "Your 1Panel license will expire in {{ .day }} days. Please log in to the panel for details."
CronJobFailedAlert: "Your 1Panel scheduled task '{{ .name }}' has failed. Please log in to the panel for details."
ClamAlert: "Your 1Panel virus scan task has detected {{ .num }} infected files. Please log in to the panel for details."
WebSiteAlert: "There are {{ .num }} websites on your 1Panel that will expire in {{ .day }} days. Please log in to the panel for details."
SSLAlert: "There are {{ .num }} SSL certificates on your 1Panel that will expire in {{ .day }} days. Please log in to the panel for details."
DiskUsedAlert: "Your 1Panel disk '{{ .name }}' has used {{ .used }}. Please log in to the panel for details."
ResourceAlert: "The average {{ .name }} usage over {{ .time }} minutes on your 1Panel is {{ .used }}. Please log in to the panel for details."
PanelVersionAlert: "A new version of 1Panel is available. Please log in to the panel to upgrade."
PanelPwdExpirationAlert: "Your 1Panel password will expire in {{ .day }} days. Please log in to the panel for details."
+22 -4
View File
@@ -177,7 +177,7 @@ ErrPgImagePull: 'イメージのプルがタイムアウトしました。イメ
#runtime
ErrFileNotExist: '{{ .detail }} ファイルが存在しません。ソース ファイルの整合性を確認してください。'
ErrImageBuildErr: 'イメージのビルドに失敗しました'
ErrImageExist: '画像がすでに存在します!'
ErrImageExist: "イメージはすでに存在します!イメージ名を変更してください。"
ErrDelWithWebsite: 'オペレーティング環境は既に Web サイトに関連付けられているため、削除できません'
ErrRuntimeStart: '起動に失敗しました'
ErrPackageJsonNotFound: 'package.json ファイルが存在しません'
@@ -205,6 +205,8 @@ SystemLog: 'システムログ'
CutWebsiteLog: 'ウェブサイトログのローテーション'
FileOrDir: 'ディレクトリ / ファイル'
UploadFile: 'バックアップファイル {{ .file }} を {{ .backup }} にアップロード中'
IgnoreBackupErr: 'バックアップ失敗、エラー:{{ .detail }}、このエラーを無視します...'
IgnoreUploadErr: 'アップロード失敗、エラー:{{ .detail }}、このエラーを無視します...'
#toolbox
ErrNotExistUser: '現在のユーザーは存在しません。変更してもう一度お試しください。'
@@ -285,6 +287,7 @@ TaskBackup: 'バックアップ'
TaskRecover: '回復'
TaskRollback: 'ロールバック'
TaskPull: 'プル'
TaskCommit: 'コミット'
TaskBuild: 'ビルド'
TaskPush: 'プッシュ'
TaskHandle: '実行'
@@ -323,7 +326,7 @@ CustomAppstore: 'カスタム アプリケーション ウェアハウス'
OllamaModelPull: 'Ollama モデル {{ .name }} をプルします'
OllamaModelSize: "Ollama モデル {{ .name }} のサイズを取得します"
#task - snapshot
#task - snapshot
Snapshot: 'スナップショット'
SnapDBInfo: '1Panel データベース情報を書き込む'
SnapCopy: 'ファイルとディレクトリ {{ .name }} をコピーします'
@@ -333,7 +336,8 @@ SnapDeleteLoginLog: 'アクセスログを削除する'
SnapDeleteMonitor: '監視データを削除する'
SnapRemoveSystemIP: 'システム IP を削除します'
SnapBaseInfo: '1Panel の基本情報を書き込む'
SnapInstallApp: '1Panel にインストールされたアプリケーションをバックアップする'
SnapInstallAppImageEmpty: 'アプリケーションイメージが選択されていないため、スキップします...'
SnapInstallApp: '1Panelインストール済みアプリケーションのバックアップ'
SnapDockerSave: 'インストールされたアプリケーションを圧縮する'
SnapLocalBackup: '1Panel ローカル バックアップ ディレクトリをバックアップします'
SnapCompressBackup: 'ローカル バックアップ ディレクトリを圧縮する'
@@ -420,4 +424,18 @@ ErrAlertSync: 'アラーム情報の同期エラーです。確認してもう
ErrAlertRemote: 'アラーム メッセージのリモート エラーです。確認してもう一度お試しください。'
#task - runtime
ErrInstallExtension: "インストールタスクが進行中です、タスクが終了するのを待ってください"
ErrInstallExtension: "インストールタスクが進行中です、タスクが終了するのを待ってください"
# alert mail template
PanelAlertTitle: "パネルアラート通知"
TestAlertTitle: "テストメール - メール接続の確認"
TestAlert: "これはテストメールです。メール送信設定が正しく構成されているかを確認します。"
LicenseExpirationAlert: "お使いの 1Panel ライセンスは {{ .day }} 日後に有効期限が切れます。詳細はパネルにログインしてご確認ください。"
CronJobFailedAlert: "1Panel のスケジュールタスク '{{ .name }}' の実行に失敗しました。詳細はパネルでご確認ください。"
ClamAlert: "1Panel のウイルススキャンで {{ .num }} 件の感染ファイルが検出されました。詳細はパネルでご確認ください。"
WebSiteAlert: "1Panel にある {{ .num }} 件のウェブサイトが {{ .day }} 日後に有効期限を迎えます。詳細はパネルでご確認ください。"
SSLAlert: "1Panel にある {{ .num }} 枚のSSL証明書が {{ .day }} 日後に有効期限を迎えます。詳細はパネルでご確認ください。"
DiskUsedAlert: "1Panel のディスク '{{ .name }}' は {{ .used }} 使用されています。詳細はパネルでご確認ください。"
ResourceAlert: "1Panel の {{ .time }} 分間の平均 {{ .name }} 使用率は {{ .used }} です。詳細はパネルでご確認ください。"
PanelVersionAlert: "1Panel に新しいバージョンが利用可能です。アップグレードはパネルから行ってください。"
PanelPwdExpirationAlert: "1Panel のパスワードは {{ .day }} 日後に期限切れとなります。詳細はパネルでご確認ください。"
+20 -2
View File
@@ -177,7 +177,7 @@ ErrPgImagePull: '이미지 풀링 시간이 초과되었습니다. 이미지 가
#실행 시간
ErrFileNotExist: '{{ .detail }} 파일이 존재하지 않습니다! 소스 파일의 무결성을 확인하세요!'
ErrImageBuildErr: '이미지 빌드 실패'
ErrImageExist: '이미지가 이미 존재합니다!'
ErrImageExist: "이미지가 이미 존재합니다! 이미지 이름을 수정하세요."
ErrDelWithWebsite: '운영 환경이 이미 웹사이트와 연결되어 있어 삭제할 수 없습니다'
ErrRuntimeStart: '시작 실패'
ErrPackageJsonNotFound: 'package.json 파일이 존재하지 않습니다'
@@ -205,6 +205,8 @@ SystemLog: '시스템 로그'
CutWebsiteLog: '웹사이트 로그 회전'
FileOrDir: '디렉터리 / 파일'
UploadFile: '백업 파일 {{ .file }} 을(를) {{ .backup }}(으)로 업로드 중'
IgnoreBackupErr: 'Sandaran gagal, ralat: {{ .detail }}, abaikan ralat ini...'
IgnoreUploadErr: 'Muat naik gagal, ralat: {{ .detail }}, abaikan ralat ini...'
#도구상자
ErrNotExistUser: '현재 사용자가 존재하지 않습니다. 수정한 후 다시 시도하세요!'
@@ -285,6 +287,7 @@ TaskBackup: '백업'
TaskRecover: '복구'
TaskRollback: '롤백'
TaskPull: '당기기'
TaskCommit: '커밋'
TaskBuild: '빌드'
TaskPush: '푸시'
TaskHandle: '실행'
@@ -333,6 +336,7 @@ SnapDeleteLoginLog: '액세스 로그 삭제'
SnapDeleteMonitor: '모니터링 데이터 삭제'
SnapRemoveSystemIP: '시스템 IP 제거'
SnapBaseInfo: '1패널 기본 정보 쓰기'
SnapInstallAppImageEmpty: '애플리케이션 이미지가 선택되지 않아 건너뜁니다...'
SnapInstallApp: '1Panel 설치된 애플리케이션 백업'
SnapDockerSave: '설치된 애플리케이션 압축'
SnapLocalBackup: '1Panel 로컬 백업 디렉토리 백업'
@@ -420,4 +424,18 @@ ErrAlertSync: '알람 정보 동기화 오류입니다. 확인하고 다시 시
ErrAlertRemote: '알람 메시지 원격 오류, 확인하고 다시 시도하세요!'
#task - runtime
ErrInstallExtension: "이미 설치 작업이 진행 중입니다. 작업이 완료될 때까지 기다려 주세요."
ErrInstallExtension: "이미 설치 작업이 진행 중입니다. 작업이 완료될 때까지 기다려 주세요."
# alert mail template
PanelAlertTitle: "패널 알림 통지"
TestAlertTitle: "테스트 이메일 - 이메일 연결 확인"
TestAlert: "이것은 테스트 이메일이며 이메일 발송 설정이 올바른지 확인합니다."
LicenseExpirationAlert: "귀하의 1Panel 라이선스는 {{ .day }}일 후에 만료됩니다. 자세한 내용은 패널에 로그인하세요."
CronJobFailedAlert: "1Panel 예약 작업 '{{ .name }}' 실행에 실패했습니다. 자세한 내용은 패널에서 확인하세요."
ClamAlert: "1Panel 바이러스 검사에서 {{ .num }}개의 감염된 파일이 발견되었습니다. 자세한 내용은 패널에서 확인하세요."
WebSiteAlert: "1Panel 에 있는 {{ .num }}개의 웹사이트가 {{ .day }}일 후에 만료됩니다. 자세한 내용은 패널에서 확인하세요."
SSLAlert: "1Panel 에 있는 {{ .num }}개의 SSL 인증서가 {{ .day }}일 후에 만료됩니다. 자세한 내용은 패널에서 확인하세요."
DiskUsedAlert: "1Panel 디스크 '{{ .name }}'의 사용량은 {{ .used }}입니다. 자세한 내용은 패널에서 확인하세요."
ResourceAlert: "1Panel 의 평균 {{ .time }}분 동안 {{ .name }} 사용률은 {{ .used }}입니다. 자세한 내용은 패널에서 확인하세요."
PanelVersionAlert: "1Panel 의 새로운 버전이 이용 가능합니다. 패널에 로그인하여 업그레이드하세요."
PanelPwdExpirationAlert: "1Panel 비밀번호가 {{ .day }}일 후에 만료됩니다. 자세한 내용은 패널에서 확인하세요."
+21 -3
View File
@@ -176,7 +176,7 @@ ErrPgImagePull: 'Tarikh imej tamat masa, sila konfigurasikan pecutan imej atau t
#masa berjalan
ErrFileNotExist: 'Fail {{ .detail }} tidak wujud! Sila semak integriti fail sumber!'
ErrImageBuildErr: 'Pembinaan imej gagal'
ErrImageExist: 'Imej sudah wujud!'
ErrImageExist: "Imej sudah wujud! Sila ubah nama imej."
ErrDelWithWebsite: 'Persekitaran pengendalian sudah dikaitkan dengan tapak web dan tidak boleh dipadamkan'
ErrRuntimeStart: 'Permulaan gagal'
ErrPackageJsonNotFound: 'fail package.json tidak wujud'
@@ -204,6 +204,8 @@ SystemLog: 'Log Sistem'
CutWebsiteLog: 'Putar Log Laman Web'
FileOrDir: 'Direktori / Fail'
UploadFile: 'Muat naik fail sandaran {{ .file }} ke {{ .backup }}'
IgnoreBackupErr: 'Sandaran gagal, ralat: {{ .detail }}, abaikan ralat ini...'
IgnoreUploadErr: 'Muat naik gagal, ralat: {{ .detail }}, abaikan ralat ini...'
#kotak alat
ErrNotExistUser: 'Pengguna semasa tidak wujud, sila ubah suai dan cuba lagi!'
@@ -284,6 +286,7 @@ TaskBackup: 'Sandaran'
TaskRecover: 'Pulihkan'
TaskRollback: 'Rollback'
TaskPull: 'Tarik'
TaskCommit: 'Komit'
TaskBuild: 'Bina'
TaskPush: 'Tolak'
TaskHandle: 'Laksanakan'
@@ -332,7 +335,8 @@ SnapDeleteLoginLog: 'Padam log akses'
SnapDeleteMonitor: 'Padamkan data pemantauan'
SnapRemoveSystemIP: 'Alih keluar IP sistem'
SnapBaseInfo: 'Tulis maklumat asas 1Panel'
SnapInstallApp: 'Aplikasi yang dipasang sandaran 1Panel'
SnapInstallAppImageEmpty: 'Tiada imej aplikasi dipilih, dilangkau...'
SnapInstallApp: 'Sandaran aplikasi terpasang 1Panel'
SnapDockerSave: 'Mampatkan aplikasi yang dipasang'
SnapLocalBackup: 'Sandaran 1Panel direktori sandaran tempatan'
SnapCompressBackup: 'Mampatkan direktori sandaran tempatan'
@@ -419,4 +423,18 @@ ErrAlertSync: 'Ralat penyegerakan maklumat penggera, sila semak dan cuba lagi!'
ErrAlertRemote: 'Ralat jauh mesej penggera, sila semak dan cuba lagi!'
#task - runtime
ErrInstallExtension: "Tugas pemasangan sudah sedang berjalan, silakan tunggu tugas selesai"
ErrInstallExtension: "Tugas pemasangan sudah sedang berjalan, silakan tunggu tugas selesai"
# alert mail template
PanelAlertTitle: "Notifikasi Amaran Panel"
TestAlertTitle: "E-mel Ujian - Sahkan Sambungan E-mel"
TestAlert: "Ini adalah e-mel ujian untuk mengesahkan konfigurasi penghantaran e-mel anda betul."
LicenseExpirationAlert: "Lesen 1Panel anda akan tamat dalam {{ .day }} hari. Sila log masuk ke panel untuk maklumat lanjut."
CronJobFailedAlert: "Tugas berjadual 1Panel '{{ .name }}' gagal dilaksanakan. Sila log masuk ke panel untuk butiran lanjut."
ClamAlert: "Tugas imbasan virus 1Panel menemui {{ .num }} fail yang dijangkiti. Sila log masuk ke panel untuk maklumat lanjut."
WebSiteAlert: "Terdapat {{ .num }} laman web dalam 1Panel anda yang akan tamat dalam {{ .day }} hari. Sila log masuk ke panel untuk maklumat lanjut."
SSLAlert: "Terdapat {{ .num }} sijil SSL dalam 1Panel anda yang akan tamat dalam {{ .day }} hari. Sila log masuk ke panel untuk maklumat lanjut."
DiskUsedAlert: "Cakera '{{ .name }}' dalam 1Panel anda telah menggunakan {{ .used }}. Sila log masuk ke panel untuk maklumat lanjut."
ResourceAlert: "Penggunaan purata {{ .name }} selama {{ .time }} minit dalam 1Panel anda ialah {{ .used }}. Sila log masuk ke panel untuk maklumat lanjut."
PanelVersionAlert: "Versi baru 1Panel tersedia. Sila log masuk ke panel untuk menaik taraf."
PanelPwdExpirationAlert: "Kata laluan 1Panel anda akan tamat dalam {{ .day }} hari. Sila log masuk ke panel untuk maklumat lanjut."
+21 -3
View File
@@ -177,7 +177,7 @@ ErrPgImagePull: 'Tempo limite para extração de imagem. Configure a aceleraçã
#tempo de execução
ErrFileNotExist: 'O arquivo {{ .detail }} não existe! Verifique a integridade do arquivo de origem!'
ErrImageBuildErr: 'Falha na criação da imagem'
ErrImageExist: 'A imagem já existe!'
ErrImageExist: "A imagem já existe! Por favor, modifique o nome da imagem."
ErrDelWithWebsite: 'O ambiente operacional já está associado a um site e não pode ser excluído'
ErrRuntimeStart: 'Falha na inicialização'
ErrPackageJsonNotFound: 'o arquivo package.json não existe'
@@ -205,6 +205,8 @@ SystemLog: 'Log do Sistema'
CutWebsiteLog: 'Rotacionar Log do Website'
FileOrDir: 'Diretório / Arquivo'
UploadFile: 'Enviando arquivo de backup {{ .file }} para {{ .backup }}'
IgnoreBackupErr: 'Backup falhou, erro: {{ .detail }}, ignorando este erro...'
IgnoreUploadErr: 'Upload falhou, erro: {{ .detail }}, ignorando este erro...'
#caixa de ferramentas
ErrNotExistUser: 'O usuário atual não existe, modifique e tente novamente!'
@@ -285,6 +287,7 @@ Backup de Tarefa: 'Backup'
TaskRecover: 'Recuperar'
TaskRollback: 'Reverter'
TaskPull: 'Puxar'
TaskCommit: 'Commit'
TaskBuild: 'Construir'
TaskPush: 'Empurrar'
TaskHandle: 'Executar'
@@ -333,7 +336,8 @@ SnapDeleteLoginLog: 'Excluir log de acesso'
SnapDeleteMonitor: 'Excluir dados de monitoramento'
SnapRemoveSystemIP: 'Remover IP do sistema'
SnapBaseInfo: 'Escreva informações básicas do 1Panel'
SnapInstallApp: 'Fazer backup dos aplicativos instalados do 1Panel'
SnapInstallAppImageEmpty: 'Nenhuma imagem de aplicativo selecionada, pulando...'
SnapInstallApp: 'Backup dos aplicativos instalados do 1Panel'
SnapDockerSave: 'Compactar aplicativos instalados'
SnapLocalBackup: 'Backup do diretório de backup local do 1Panel'
SnapCompressBackup: 'Compactar diretório de backup local'
@@ -420,4 +424,18 @@ ErrAlertSync: 'Erro de sincronização de informações de alarme, verifique e t
ErrAlertRemote: 'Erro remoto na mensagem de alarme, verifique e tente novamente!'
#task - runtime
ErrInstallExtension: "Já existe uma tarefa de instalação em andamento, aguarde a conclusão da tarefa"
ErrInstallExtension: "Já existe uma tarefa de instalação em andamento, aguarde a conclusão da tarefa"
# alert mail template
PanelAlertTitle: "Notificação de Alerta do Painel"
TestAlertTitle: "E-mail de Teste - Verificar Conectividade de E-mail"
TestAlert: "Este é um e-mail de teste para verificar se sua configuração de envio de e-mails está correta."
LicenseExpirationAlert: "A licença do seu 1Panel expirará em {{ .day }} dias. Acesse o painel para mais detalhes."
CronJobFailedAlert: "A tarefa agendada '{{ .name }}' do seu 1Panel falhou. Acesse o painel para mais detalhes."
ClamAlert: "A verificação de vírus do 1Panel detectou {{ .num }} arquivos infectados. Acesse o painel para mais detalhes."
WebSiteAlert: "{{ .num }} sites do seu 1Panel expirarão em {{ .day }} dias. Acesse o painel para mais detalhes."
SSLAlert: "{{ .num }} certificados SSL do seu 1Panel expirarão em {{ .day }} dias. Acesse o painel para mais detalhes."
DiskUsedAlert: "O disco '{{ .name }}' do seu 1Panel está com uso de {{ .used }}. Acesse o painel para mais detalhes."
ResourceAlert: "O uso médio de {{ .name }} em {{ .time }} minutos no seu 1Panel é de {{ .used }}. Acesse o painel para mais detalhes."
PanelVersionAlert: "Uma nova versão do 1Panel está disponível. Acesse o painel para atualizá-lo."
PanelPwdExpirationAlert: "A senha do 1Panel expirará em {{ .day }} dias. Acesse o painel para mais detalhes."
+21 -3
View File
@@ -177,7 +177,7 @@ ErrPgImagePull: 'Время извлечения изображения исте
#время выполнения
ErrFileNotExist: 'Файл {{ .detail }} не существует! Проверьте целостность исходного файла!'
ErrImageBuildErr: 'Сборка образа не удалась'
ErrImageExist: 'Изображение уже существует!'
ErrImageExist: "Изображение уже существует! Пожалуйста, измените имя изображения."
ErrDelWithWebsite: 'Операционная среда уже связана с веб-сайтом и не может быть удалена'
ErrRuntimeStart: 'Запуск не удался'
ErrPackageJsonNotFound: 'файл package.json не существует'
@@ -205,6 +205,8 @@ SystemLog: 'Системный лог'
CutWebsiteLog: 'Ротация логов сайта'
FileOrDir: 'Каталог / Файл'
UploadFile: 'Загрузка файла резервной копии {{ .file }} в {{ .backup }}'
IgnoreBackupErr: 'Ошибка резервного копирования: {{ .detail }}, игнорируем эту ошибку...'
IgnoreUploadErr: 'Ошибка загрузки: {{ .detail }}, игнорируем эту ошибку...'
#ящик для инструментов
ErrNotExistUser: 'Текущий пользователь не существует, измените его и повторите попытку!'
@@ -285,6 +287,7 @@ TaskBackup: 'Резервное копирование'
TaskRecover: 'Восстановить'
TaskRollback: 'Откат'
TaskPull: 'Вытянуть'
TaskCommit: 'Kоммит'
ЗадачаСборка: 'Сборка'
TaskPush: 'Push'
TaskHandle: 'Выполнить'
@@ -333,7 +336,8 @@ SnapDeleteLoginLog: 'Удалить журнал доступа'
SnapDeleteMonitor: 'Удалить данные мониторинга'
SnapRemoveSystemIP: 'Удалить системный IP'
SnapBaseInfo: 'Записать основную информацию 1Panel'
SnapInstallApp: 'Резервное копирование установленных приложений 1Panel'
SnapInstallAppImageEmpty: 'Образы приложений не выбраны, пропускаем...'
SnapInstallApp: 'Резервное копирование приложений, установленных в 1Panel'
SnapDockerSave: 'Сжать установленные приложения'
SnapLocalBackup: 'Резервное копирование локального каталога резервных копий 1Panel'
SnapCompressBackup: 'Сжать локальный каталог резервных копий'
@@ -420,4 +424,18 @@ ErrAlertSync: 'Ошибка синхронизации информации о
ErrAlertRemote: 'Ошибка удаленного сообщения об ошибке, проверьте и повторите попытку!'
#task - runtime
ErrInstallExtension: "Уже выполняется задача установки, подождите, пока задача завершится"
ErrInstallExtension: "Уже выполняется задача установки, подождите, пока задача завершится"
# alert mail template
PanelAlertTitle: "Уведомление панели"
TestAlertTitle: "Тестовое письмо - проверка подключения"
TestAlert: "Это тестовое письмо для проверки правильности настройки отправки писем."
LicenseExpirationAlert: "Срок действия лицензии на 1Panel истекает через {{ .day }} дней. Подробности смотрите в панели."
CronJobFailedAlert: "Запланированное задание '{{ .name }}' на вашем 1Panel завершилось с ошибкой. Подробности в панели."
ClamAlert: "Антивирусная проверка 1Panel обнаружила {{ .num }} заражённых файлов. Подробности смотрите в панели."
WebSiteAlert: "На вашем 1Panel {{ .num }} веб-сайтов истекают через {{ .day }} дней. Подробности смотрите в панели."
SSLAlert: "На вашем 1Panel {{ .num }} SSL-сертификатов истекают через {{ .day }} дней. Подробности смотрите в панели."
DiskUsedAlert: "Диск '{{ .name }}' на вашем 1Panel использует {{ .used }}. Подробности смотрите в панели."
ResourceAlert: "Средняя загрузка {{ .name }} за {{ .time }} минут составляет {{ .used }}. Подробности смотрите в панели."
PanelVersionAlert: "Доступна новая версия 1Panel. Обновитесь через панель."
PanelPwdExpirationAlert: "Пароль для 1Panel истекает через {{ .day }} дней. Подробности смотрите в панели."
+439
View File
@@ -0,0 +1,439 @@
ErrInvalidParams: 'İstek parametre hatası: {{ .detail }}'
ErrTokenParse: 'Token oluşturma hatası: {{ .detail }}'
ErrInitialPassword: 'Orijinal şifre yanlış'
ErrInternalServer: 'Dahili sunucu hatası: {{ .detail }}'
ErrRecordExist: 'Kayıt zaten mevcut'
ErrRecordNotFound: 'Kayıt bulunamadı'
ErrStructTransform: 'Tür dönüştürme başarısız: {{ .err }}'
ErrNotLogin: 'Kullanıcı giriş yapmamış: {{ .detail }}'
ErrPasswordExpired: 'Mevcut şifrenin süresi dolmuş: {{ .detail }}'
ErrNotSupportType: 'Sistem mevcut türü desteklemiyor: {{ .name }}'
ErrProxy: 'İstek hatası, lütfen düğüm durumunu kontrol edin: {{ .detail }}'
ErrApiConfigStatusInvalid: 'API arayüzüne erişim yasak: {{ .detail }}'
ErrApiConfigKeyInvalid: 'API arayüz anahtarı hatası: {{ .detail }}'
ErrApiConfigIPInvalid: 'API arayüzünü çağırmak için kullanılan IP beyaz listede değil: {{ .detail }}'
ErrApiConfigDisable: 'Bu arayüz API arayüz çağrılarının kullanımını yasaklıyor: {{ .detail }}'
ErrApiConfigKeyTimeInvalid: 'API arayüz zaman damgası hatası: {{ .detail }}'
#common
ErrUsernameIsExist: 'Kullanıcı adı zaten mevcut'
ErrNameIsExist: 'İsim zaten mevcut'
ErrDemoEnvironment: 'Demo sunucu, bu işlem yasak!'
ErrCmdTimeout: 'Komut yürütme zaman aşımına uğradı!'
ErrCmdIllegal: 'Yürütme komutunda yasak karakterler var, lütfen değiştirin ve tekrar deneyin!'
ErrPortExist: '{{ .port }} portu zaten {{ .type }} [{{ .name }}] tarafından kullanılıyor'
TYPE_APP: 'Uygulama'
TYPE_RUNTIME: 'Çalışma ortamı'
TYPE_DOMAIN: 'Alan Adı'
ErrTypePort: 'Port {{ .name }} formatı yanlış'
ErrTypePortRange: 'Port aralığının 1-65535 arasında olması gerekiyor'
Success: 'Başarılı'
Failed: 'Başarısız'
SystemRestart: 'Sistem yeniden başlatması nedeniyle görev kesildi'
ErrGroupIsDefault: 'Varsayılan grup, silinemez'
ErrGroupIsInWebsiteUse: 'Grup başka bir web sitesi tarafından kullanılıyor ve silinemez.'
#backup
ErrBackupInUsed: 'Yedekleme hesabı zamanlanmış görevde kullanıldı ve silinemez.'
ErrBackupCheck: 'Yedekleme hesabı test bağlantısı başarısız {{ .err }}'
ErrBackupLocalDelete: 'Yerel sunucu yedekleme hesabını silme henüz desteklenmiyor'
ErrBackupLocalCreate: 'Yerel sunucu yedekleme hesapları oluşturma henüz desteklenmiyor'
#app
ErrPortInUsed: '{{ .detail }} portu zaten kullanılıyor!'
ErrAppLimit: 'Yüklenen uygulama sayısı sınırı aştı'
ErrNotInstall: 'Uygulama yüklenmedi'
ErrPortInOtherApp: '{{ .port }} portu zaten {{ .apps }} uygulaması tarafından kullanılıyor!'
ErrDbUserNotValid: 'Mevcut veritabanı, kullanıcı adı ve şifre eşleşmiyor!'
ErrUpdateBuWebsite: 'Uygulama başarıyla güncellendi, ancak web sitesi yapılandırma dosyası değiştirme başarısız. Lütfen yapılandırmayı kontrol edin! '
Err1PanelNetworkFailed: 'Varsayılan konteyner ağ oluşturma başarısız! {{ .detail }}'
ErrFileParse: 'Uygulama docker-compose dosya ayrıştırma başarısız!'
ErrInstallDirNotFound: 'Kurulum dizini mevcut değil. Kaldırmanız gerekiyorsa, lütfen Zorla Kaldır seçeneğini seçin'
AppStoreIsUpToDate: 'Uygulama mağazası zaten en son sürüm'
LocalAppVersionNull: '{{ .name }} uygulaması sürüme senkronize edilmedi! Uygulama listesine eklenemez'
LocalAppVersionErr: '{{ .name }} senkronizasyon sürümü {{ .version }} başarısız! {{ .err }}'
ErrFileNotFound: '{{ .name }} dosyası mevcut değil'
ErrFileParseApp: '{{ .name }} dosya ayrıştırma başarısız {{ .err }}'
ErrAppDirNull: 'Sürüm klasörü mevcut değil'
LocalAppErr: 'Uygulama {{ .name }} senkronizasyonu başarısız! {{ .err }}'
ErrContainerName: 'Konteyner adı zaten mevcut'
ErrCreateHttpClient: 'İstek oluşturma başarısız {{ .err }}'
ErrHttpReqTimeOut: 'İstek zaman aşımı {{ .err }}'
ErrHttpReqFailed: 'İstek başarısız {{ .err }}'
ErrNoSuchHost: 'İstenen sunucu bulunamıyor {{ .err }}'
ErrHttpReqNotFound: 'İstenen kaynak {{ .err }} bulunamadı'
ErrImagePullTimeOut: 'Image çekme zaman aşımı'
ErrContainerNotFound: '{{ .name }} konteyneri mevcut değil'
ErrContainerMsg: '{{ .name }} konteyneri anormal. Lütfen ayrıntılar için konteyner sayfasındaki günlüğü kontrol edin'
ErrAppBackup: '{{ .name }} uygulama yedeklemesi başarısız {{ .err }}'
ErrVersionTooLow: 'Mevcut 1Panel sürümü Uygulama Mağazasını güncellemek için çok düşük. Lütfen işlemden önce sürümü yükseltin.'
ErrAppNameExist: 'Uygulama adı zaten mevcut'
AppStoreIsSyncing: 'Uygulama Mağazası senkronize ediliyor, lütfen daha sonra tekrar deneyin'
ErrGetCompose: 'docker-compose.yml dosyası alınamadı! {{ .detail }}'
ErrAppWarn: 'Anormal durum, lütfen günlüğü kontrol edin'
ErrAppParamKey: 'Parametre {{ .name }} alanı anormal'
ErrAppUpgrade: 'Uygulama {{ .name }} yükseltmesi başarısız {{ .err }}'
AppRecover: 'Uygulama {{ .name }} geri alma'
PullImageStart: 'Image {{ .name }} çekmeye başla'
PullImageSuccess: 'Image çekme başarılı'
AppStoreIsLastVersion: 'Uygulama Mağazası zaten en son sürüm'
AppStoreSyncSuccess: 'Uygulama Mağazası senkronizasyonu başarılı'
SyncAppDetail: 'Uygulama yapılandırmasını senkronize et'
AppVersionNotMatch: '{{ .name }} uygulaması daha yüksek 1Panel sürümü gerektiriyor, senkronizasyon atlanıyor'
MoveSiteDir: 'Mevcut yükseltme OpenResty web sitesi dizininin taşınmasını gerektiriyor'
MoveSiteToDir: 'Site dizinini {{ .name }} konumuna taşı'
ErrMoveSiteDir: 'Site dizini taşıma başarısız'
MoveSiteDirSuccess: 'Web sitesi dizini taşıma başarılı'
DeleteRuntimePHP: 'PHP çalışma zamanını sil'
CustomAppStoreFileValid: 'Uygulama mağazası paketlerinin .tar.gz formatında olması gerekiyor'
PullImageTimeout: 'Image çekme zaman aşımı, lütfen image hızlandırmayı artırmayı deneyin veya başka bir image hızlandırmaya geçin'
ErrAppIsDown: '{{ .name }} uygulama durumu anormal, lütfen kontrol edin'
ErrCustomApps: 'Yüklü bir uygulama var, lütfen önce kaldırın'
ErrCustomRuntimes: 'Yüklü bir çalışma ortamı var, lütfen önce silin'
ErrAppVersionDeprecated: "{{ .name }} uygulaması mevcut 1Panel sürümü ile uyumlu değil, atlandı"
ErrDockerFailed: "Docker durumu anormal, lütfen servis durumunu kontrol edin"
ErrDockerComposeCmdNotFound: "Docker Compose komutu mevcut değil, lütfen önce bu komutu host makinesine yükleyin"
#file
ErrFileCanNotRead: 'Bu dosya önizlemeyi desteklemiyor'
ErrFileToLarge: 'Dosya 10M'dan büyük ve açılamıyor'
ErrPathNotFound: 'Dizin mevcut değil'
ErrMovePathFailed: 'Hedef yol orijinal yolu içeremez!'
ErrLinkPathNotFound: 'Hedef yol mevcut değil!'
ErrFileIsExist: 'Dosya veya klasör zaten mevcut!'
ErrFileUpload: '{{ .name }} dosya yükleme başarısız {{ .detail }}'
ErrFileDownloadDir: 'Klasör indirme desteklenmiyor'
ErrCmdNotFound: '{{ .name}} komutu mevcut değil, lütfen önce bu komutu host'a yükleyin'
ErrSourcePathNotFound: 'Kaynak dizin mevcut değil'
ErrFavoriteExist: 'Bu yol zaten favorilere eklendi'
ErrInvalidChar: 'Yasak karakterlere izin verilmiyor'
ErrPathNotDelete: 'Seçilen dizin silinemez'
ErrLogFileToLarge: "Günlük dosyası 500MB'ı aşıyor ve açılamıyor"
#website
ErrAliasIsExist: 'Takma ad zaten mevcut'
ErrBackupMatch: 'Yedekleme dosyası mevcut web sitesi verilerinin bir kısmıyla eşleşmiyor {{ .detail }}'
ErrBackupExist: 'Yedekleme dosyasındaki kaynak verilerin karşılık gelen kısmı mevcut değil {{ .detail }}'
ErrPHPResource: 'Yerel işletim ortamı değiştirmeyi desteklemiyor! '
ErrPathPermission: 'Dizin dizininde 1000:1000 olmayan izinlere sahip bir klasör tespit edildi, bu web sitesinde Erişim reddedildi hatasına neden olabilir. Lütfen yukarıdaki Kaydet düğmesine tıklayın'
ErrDomainIsUsed: 'Alan adı zaten [{{ .name }}] web sitesi tarafından kullanılıyor'
ErrDomainFormat: '{{ .name }} alan adı formatı yanlış'
ErrDefaultAlias: 'default ayrılmış bir kod, lütfen başka bir kod kullanın'
ErrParentWebsite: 'Önce {{ .name }} alt sitesini silmeniz gerekiyor'
ErrBuildDirNotFound: 'Yapı dizini mevcut değil'
ErrImageNotExist: 'İşletim ortamı {{ .name }} image mevcut değil, lütfen işletim ortamını yeniden düzenleyin'
ErrProxyIsUsed: "Yük dengeleme ters proxy tarafından kullanıldı, silinemez"
ErrSSLValid: 'Sertifika dosyası anormal, lütfen sertifika durumunu kontrol edin!'
#ssl
ErrSSLCannotDelete: '{{ .name }} sertifikası bir web sitesi tarafından kullanılıyor ve silinemez'
ErrAccountCannotDelete: 'Hesap bir sertifikayla ilişkili ve silinemez'
ErrSSLApply: 'Sertifika yenileme başarılı, openresty yeniden yükleme başarısız, lütfen yapılandırmayı kontrol edin!'
ErrEmailIsExist: 'Posta kutusu zaten mevcut'
ErrSSLKeyNotFound: 'Özel anahtar dosyası mevcut değil'
ErrSSLCertificateNotFound: 'Sertifika dosyası mevcut değil'
ErrSSLKeyFormat: 'Özel anahtar dosyası doğrulama başarısız'
ErrSSLCertificateFormat: 'Sertifika dosyası formatı yanlış, lütfen pem formatını kullanın'
ErrEabKidOrEabHmacKeyCannotBlank: 'EabKid veya EabHmacKey boş olamaz'
ErrOpenrestyNotFound: 'Http modu önce Openresty yüklenmesini gerektiriyor'
ApplySSLStart: 'Sertifika başvurusu başlatılıyor, alan adı [{{ .domain }}] başvuru yöntemi [{{ .type }}] '
dnsAccount: 'DNS Otomatik'
dnsManual: 'DNS Manuel'
http: 'HTTP'
ApplySSLFailed: '[{{ .domain }}] sertifika başvurusu başarısız, {{ .detail }} '
ApplySSLSuccess: '[{{ .domain }}] sertifika başvurusu başarılı! !'
DNSAccountName: 'DNS hesabı [{{ .name }}] sağlayıcı [{{ .type }}]'
PushDirLog: 'Sertifika [{{ .path }}] dizinine gönderildi {{ .status }}'
ErrDeleteCAWithSSL: 'Mevcut kuruluşun verilmiş bir sertifikası var ve silinemez.'
ErrDeleteWithPanelSSL: 'Panel SSL yapılandırması bu sertifikayı kullanıyor ve silinemez'
ErrDefaultCA: 'Varsayılan otorite silinemez'
ApplyWebSiteSSLLog: '{{ .name }} web sitesi sertifikası yenilemeyi başlatıyor'
ErrUpdateWebsiteSSL: '{{ .name }} web sitesi sertifika güncelleme başarısız: {{ .err }}'
ApplyWebSiteSSLSuccess: 'Web sitesi sertifikası güncelleme başarılı'
ErrExecShell: 'Betik yürütme başarısız {{ .err }}'
ExecShellStart: 'Betik yürütmeyi başlat'
ExecShellSuccess: 'Betik yürütme başarılı'
StartUpdateSystemSSL: 'Sistem sertifikası güncellemeyi başlat'
UpdateSystemSSLSuccess: 'Sistem sertifikası güncelleme başarılı'
ErrWildcardDomain: 'HTTP modunda joker alan adı sertifikası başvurusu yapılamıyor'
#mysql
ErrUserIsExist: 'Mevcut kullanıcı zaten mevcut, lütfen yeniden girin'
ErrDatabaseIsExist: 'Mevcut veritabanı zaten mevcut, lütfen yeniden girin'
ErrExecTimeOut: 'SQL yürütme zaman aşımı, lütfen veritabanını kontrol edin'
ErrRemoteExist: 'Uzak veritabanında bu adla zaten mevcut, lütfen değiştirin ve tekrar deneyin'
ErrLocalExist: 'Yerel veritabanında bu ad zaten mevcut, lütfen değiştirin ve tekrar deneyin'
#redis
ErrTypeOfRedis: 'Kurtarma dosyası türü mevcut kalıcılık yöntemiyle eşleşmiyor, lütfen değiştirin ve tekrar deneyin'
#container
ErrInUsed: '{{ .detail }} kullanımda ve silinemez'
ErrObjectInUsed: 'Nesne kullanımda ve silinemez'
ErrObjectBeDependent: 'Bu image diğer imagelere bağlı ve silinemez'
ErrPortRules: 'Port numarası eşleşmiyor, lütfen yeniden girin!'
ErrPgImagePull: 'Image çekme zaman aşımı, lütfen image hızlandırma yapılandırın veya manuel olarak {{ .name }} imageını çekin ve tekrar deneyin'
#runtime
ErrFileNotExist: '{{ .detail }} dosyası mevcut değil! Lütfen kaynak dosyanın bütünlüğünü kontrol edin!'
ErrImageBuildErr: 'Image yapı başarısız'
ErrImageExist: "Görüntü zaten var! Lütfen görüntü adını değiştirin."
ErrDelWithWebsite: 'İşletim ortamı zaten bir web sitesiyle ilişkili ve silinemez'
ErrRuntimeStart: 'Başlatma başarısız'
ErrPackageJsonNotFound: 'package.json dosyası mevcut değil'
ErrScriptsNotFound: 'package.json içinde scripts yapılandırma öğesi bulunamadı'
ErrContainerNameNotFound: 'Konteyner adı alınamıyor, lütfen .env dosyasını kontrol edin'
ErrNodeModulesNotFound: 'node_modules klasörü mevcut değil! Lütfen çalışma ortamını düzenleyin veya çalışma ortamının başarıyla başlatılmasını bekleyin'
ErrContainerNameIsNull: 'Konteyner adı mevcut değil'
ErrPHPPortIsDefault: "9000 portu varsayılan port, lütfen değiştirin ve tekrar deneyin"
ErrPHPRuntimePortFailed: "{{ .name }} portu mevcut çalışma ortamı tarafından zaten kullanılıyor, lütfen değiştirin ve tekrar deneyin"
#tool
ErrConfigNotFound: 'Yapılandırma dosyası mevcut değil'
ErrConfigParse: 'Yapılandırma dosyası formatı yanlış'
ErrConfigIsNull: 'Yapılandırma dosyası boş olamaz'
ErrConfigDirNotFound: 'Çalışma dizini mevcut değil'
ErrConfigAlreadyExist: 'Aynı ada sahip bir yapılandırma dosyası zaten mevcut'
ErrUserFindErr: 'Kullanıcı {{ .name }} arama başarısız {{ .err }}'
#cronjob
CutWebsiteLogSuccess: '{{ .name }} web sitesi günlüğü başarıyla kesildi, yedekleme yolu {{ .path }}'
HandleShell: 'Betik {{ .name }} yürüt'
HandleNtpSync: 'Sistem zaman senkronizasyonu'
HandleSystemClean: 'Sistem önbellek temizliği'
SystemLog: 'Sistem Günlüğü'
CutWebsiteLog: 'Web Sitesi Günlüğünü Döndür'
FileOrDir: 'Dizin / Dosya'
UploadFile: 'Yedekleme dosyası {{ .file }} {{ .backup }} konumuna yükleniyor'
#toolbox
ErrNotExistUser: 'Mevcut kullanıcı mevcut değil, lütfen değiştirin ve tekrar deneyin!'
ErrBanAction: 'Ayarlama başarısız. Mevcut {{ .name }} servisi kullanılamıyor. Lütfen kontrol edin ve tekrar deneyin!'
ErrClamdscanNotFound: 'clamdscan komutu tespit edilmedi, lütfen yüklemek için belgeleri inceleyin!'
#waf
ErrScope: 'Bu yapılandırmayı değiştirme desteklenmiyor'
ErrStateChange: 'Durum değiştirme başarısız'
ErrRuleExist: 'Kural zaten mevcut'
ErrRuleNotExist: 'Kural mevcut değil'
ErrParseIP: 'Yanlış IP formatı'
ErrDefaultIP: 'default ayrılmış bir isim, lütfen başka bir isme değiştirin'
ErrGroupInUse: 'IP grubu kara liste/beyaz liste tarafından kullanılıyor ve silinemez'
ErrIPGroupAclUse: "IP grubu {{ .name }} web sitesinin özel kuralları tarafından kullanılıyor, silinemez"
ErrGroupExist: 'IP grup adı zaten mevcut'
ErrIPRange: 'Yanlış IP aralığı'
ErrIPExist: 'IP zaten mevcut'
urlDefense: 'URL kuralları'
urlHelper: 'Yasak URL'
dirFilter: 'Dizin filtresi'
xss: 'XSS'
phpExec: 'PHP betik yürütme'
oneWordTrojan: 'Tek Kelime Truva Atı'
appFilter: 'Tehlikeli dizin filtreleme uygula'
webshell: 'Webshell'
args: 'Parametre kuralları'
protocolFilter: 'Protokol filtreleme'
javaFileter: 'Java tehlikeli dosya filtresi'
scannerFilter: 'Tarayıcı filtresi'
escapeFilter: 'kaçış filtresi'
customRule: 'Özel kural'
httpMethod: 'HTTP yöntem filtreleme'
fileExt: 'Dosya yükleme kısıtlamaları'
defaultIpBlack: 'Kötü niyetli IP grubu'
cookie: 'Cookie Kuralları'
urlBlack: 'URL kara listesi'
uaBlack: 'User-Agent kara listesi'
attackCount: 'Saldırı sıklığı sınırı'
fileExtCheck: 'Dosya yükleme kısıtlamaları'
geoRestrict: 'Bölgesel erişim kısıtlamaları'
unknownWebsite: 'Yetkisiz alan adı erişimi'
notFoundCount: '404 Hız Sınırı'
headerDefense: 'Başlık kuralları'
defaultUaBlack: 'User-Agent kuralları'
methodWhite: 'HTTP kuralları'
captcha: 'insan-makine doğrulaması'
fiveSeconds: '5 saniye doğrulaması'
vulnCheck: 'Tamamlayıcı kurallar'
acl: 'Özel kurallar'
sql: 'SQL enjeksiyonu'
cc: 'Erişim sıklığı sınırı'
defaultUrlBlack: 'URL kuralları'
sqlInject: 'SQL enjeksiyonu'
ErrDBNotExist: 'Veritabanı mevcut değil'
allow: 'izin ver'
deny: 'reddet'
OpenrestyNotFound: 'Openresty yüklü değil'
remoteIpIsNull: "IP listesi boş"
#task
TaskStart: '{{ .name }} görevi başlıyor [BAŞLAT]'
TaskEnd: '{{ .name }} Görev tamamlandı [TAMAMLANDI]'
TaskFailed: '{{ .name }} görev başarısız'
TaskTimeout: '{{ .name }} zaman aşımına uğradı'
TaskSuccess: '{{ .name }} Görev başarılı'
TaskRetry: '{{ .name }}. yeniden denemeyi başlat'
SubTaskSuccess: '{{ .name }} başarılı'
SubTaskFailed: '{{ .name }} başarısız: {{ .err }}'
TaskInstall: 'Yükle'
TaskUninstall: 'Kaldır'
TaskCreate: 'Oluştur'
TaskDelete: 'Sil'
TaskUpgrade: 'Yükselt'
TaskUpdate: 'Güncelle'
TaskRestart: 'Yeniden Başlat'
TaskBackup: 'Yedekle'
TaskRecover: 'Kurtar'
TaskRollback: 'Geri Al'
TaskPull: 'Çek'
TaskCommit: 'işleme'
TaskBuild: 'Yapı'
TaskPush: 'Gönder'
TaskHandle: 'Yürüt'
Website: 'Web Sitesi'
App: 'Uygulama'
Runtime: 'Çalışma ortamı'
Database: 'Veritabanı'
ConfigFTP: 'FTP kullanıcısı {{ .name }} oluştur'
ConfigOpenresty: 'Openresty yapılandırma dosyası oluştur'
InstallAppSuccess: 'Uygulama {{ .name }} başarıyla yüklendi'
ConfigRuntime: 'Çalışma ortamını yapılandır'
ConfigApp: 'Uygulama Yapılandırması'
SuccessStatus: '{{ .name }} başarılı'
FailedStatus: '{{ .name }} başarısız {{ .err }}'
HandleLink: 'Uygulama ilişkisini işle'
HandleDatabaseApp: 'Uygulama parametrelerini işle'
ExecShell: '{{ .name }} betiğini yürüt'
PullImage: 'Image çek'
Start: 'Başlat'
Run: 'Başlat'
Stop: 'Durdur'
Image: 'Ayna'
Compose: 'Düzenleme'
Container: 'Konteyner'
AppLink: 'Bağlantılı Uygulama'
EnableSSL: 'HTTPS Etkinleştir'
AppStore: 'Uygulama Mağazası'
TaskSync: 'Senkronize Et'
LocalApp: 'Yerel Uygulama'
SubTask: 'Alt görev'
RuntimeExtension: 'Çalışma Ortamı Uzantısı'
TaskIsExecuting: 'Görev çalışıyor'
CustomAppstore: 'Özel uygulama deposu'
# task - ai
OllamaModelPull: 'Ollama modeli {{ .name }} çek'
OllamaModelSize: 'Ollama modeli {{ .name }} boyutunu al'
# task-snapshot
Snapshot: 'Anlık Görüntü'
SnapDBInfo: '1Panel veritabanı bilgilerini yaz'
SnapCopy: 'Dosya ve dizinleri {{ .name }} kopyala'
SnapNewDB: 'Veritabanı {{ .name }} bağlantısını başlat'
SnapDeleteOperationLog: 'İşlem günlüğünü sil'
SnapDeleteLoginLog: 'Erişim günlüğünü sil'
SnapDeleteMonitor: 'İzleme verilerini sil'
SnapRemoveSystemIP: 'Sistem IP'sini kaldır'
SnapBaseInfo: '1Panel temel bilgilerini yaz'
SnapInstallAppImageEmpty: 'Uygulama imageı seçilmedi, atlanıyor...'
SnapInstallApp: '1Panel yüklü uygulamaları yedekle'
SnapDockerSave: 'Yüklü uygulamaları sıkıştır'
SnapLocalBackup: '1Panel yerel yedekleme dizinini yedekle'
SnapCompressBackup: 'Yerel yedekleme dizinini sıkıştır'
SnapPanelData: '1Panel veri dizinini yedekle'
SnapCompressPanel: 'Sıkıştırılmış Veri Dizini'
SnapWebsite: '1Panel web sitesi dizinini yedekle'
SnapCloseDBConn: 'Veritabanı bağlantısını kapat'
SnapCompress: 'Anlık görüntü dosyaları oluştur'
SnapCompressFile: 'Anlık görüntü dosyasını sıkıştır'
SnapCheckCompress: 'Anlık görüntü sıkıştırma dosyasını kontrol et'
SnapCompressSize: 'Anlık görüntü dosya boyutu {{ .name }}'
SnapUpload: 'Anlık görüntü dosyasını yükle'
SnapLoadBackup: 'Yedekleme hesabı bilgilerini al'
SnapUploadTo: 'Anlık görüntü dosyasını {{ .name }} konumuna yükle'
SnapUploadRes: 'Anlık görüntü dosyasını {{ .name }} konumuna yükle'
SnapshotRecover: 'Anlık Görüntü Geri Yükleme'
RecoverDownload: 'Anlık görüntü dosyasını indir'
Download: 'İndir'
RecoverDownloadAccount: 'Anlık görüntü indirme yedek hesabı al {{ .name }}'
RecoverDecompress: 'Anlık görüntü sıkıştırılmış dosyalarını açma'
Decompress: 'Açma'
BackupBeforeRecover: 'Anlık görüntü öncesi sistem ilgili verileri yedekleme'
Readjson: 'Anlık görüntüdeki Json dosyasını oku'
ReadjsonPath: 'Anlık görüntüdeki Json dosya yolunu al'
ReadjsonContent: 'Json dosyasını oku'
ReadjsonMarshal: 'Json kaçış işlemesi'
RecoverApp: 'Yüklü uygulamaları geri yükle'
RecoverWebsite: 'Web sitesi dizinini kurtar'
RecoverAppImage: 'Anlık görüntü imaj yedeklemesini geri yükle'
RecoverCompose: 'Diğer besteci içeriğini geri yükle'
RecoverComposeList: 'Geri yüklenecek tüm bestecileri al'
RecoverComposeItem: 'Besteci geri yükle {{ .name }}'
RecoverAppEmpty: 'Anlık görüntü dosyasında uygulama imaj yedeklemesi bulunamadı'
RecoverBaseData: 'Temel veri ve dosyaları kurtar'
RecoverDaemonJsonEmpty: 'Hem anlık görüntü dosyası hem de mevcut makine konteyner yapılandırma daemon.json dosyasına sahip değil'
RecoverDaemonJson: 'Konteyner yapılandırma daemon.json dosyasını geri yükle'
RecoverDBData: 'Veritabanı verilerini kurtar'
RecoverBackups: 'Yerel yedekleme dizinini geri yükle'
RecoverPanelData: 'Veri dizinini kurtar'
# task - container
ContainerNewCliet: 'Docker İstemcisini Başlat'
ContainerImagePull: 'Konteyner imajını çek {{ .name }}'
ContainerRemoveOld: 'Orijinal konteyneri kaldır {{ .name }}'
ContainerImageCheck: 'İmajın normal çekilip çekilmediğini kontrol et'
ContainerLoadInfo: 'Temel konteyner bilgilerini al'
ContainerRecreate: 'Konteyner güncellemesi başarısız, şimdi orijinal konteyneri geri yüklemeye başlıyor'
ContainerCreate: 'Yeni konteyner oluştur {{ .name }}'
ContainerCreateFailed: 'Konteyner oluşturma başarısız, başarısız konteyneri sil'
ContainerStartCheck: 'Konteynerin başlatılıp başlatılmadığını kontrol et'
# task - image
ImageBuild: 'İmaj Oluşturma'
ImageBuildStdoutCheck: 'İmaj çıktı içeriğini ayrıştır'
ImageBuildRes: 'İmaj oluşturma çıktısı: {{ .name }}'
ImagePull: 'İmaj çek'
ImageRepoAuthFromDB: 'Veritabanından depo kimlik doğrulama bilgilerini al'
ImaegPullRes: 'İmaj çekme çıktısı: {{ .name }}'
ImagePush: 'İmaj gönder'
ImageRenameTag: 'İmaj etiketini değiştir'
ImageNewTag: 'Yeni imaj etiketi {{ .name }}'
ImaegPushRes: 'İmaj gönderme çıktısı: {{ .name }}'
ComposeCreate: 'Kompozisyon oluştur'
ComposeCreateRes: 'Compose oluşturma çıktısı: {{ .name }}'
# task - website
BackupNginxConfig: 'Web sitesi OpenResty yapılandırma dosyasını yedekle'
CompressFileSuccess: 'Dizin başarıyla sıkıştırıldı, şuraya sıkıştırıldı {{ .name }}'
CompressDir: 'Sıkıştırma dizini'
DeCompressFile: 'Dosyayı aç {{ .name }}'
ErrCheckValid: 'Yedekleme dosyası doğrulaması başarısız, {{ .name }}'
Rollback: 'Geri alma'
websiteDir: 'Web Sitesi Dizini'
RecoverFailedStartRollBack: 'Kurtarma başarısız, geri almayı başlat'
AppBackupFileIncomplete: 'Yedekleme dosyası eksik ve app.json veya app.tar.gz dosyalarından yoksun'
AppAttributesNotMatch: 'Uygulama türü veya adı eşleşmiyor'
#alert
ErrAlert: 'Uyarı mesajının formatı yanlış, lütfen kontrol edin ve tekrar deneyin!'
ErrAlertPush: 'Uyarı bilgisi gönderiminde hata, lütfen kontrol edin ve tekrar deneyin!'
ErrAlertSave: 'Alarm bilgisini kaydetmede hata, lütfen kontrol edin ve tekrar deneyin!'
ErrAlertSync: 'Alarm bilgisi senkronizasyon hatası, lütfen kontrol edin ve tekrar deneyin!'
ErrAlertRemote: 'Alarm mesajı uzak hata, lütfen kontrol edin ve tekrar deneyin!'
#task - runtime
ErrInstallExtension: "Zaten devam eden bir kurulum görevi var, görevin bitmesini bekleyin"
# alert mail template
PanelAlertTitle: "Panel Uyarı Bildirimi"
TestAlertTitle: "Test E-postası - E-posta Bağlantısını Doğrula"
TestAlert: "Bu, e-posta gönderim ayarlarınızın doğru yapılandırıldığını doğrulamak için gönderilen bir test e-postasıdır."
LicenseExpirationAlert: "1Panel lisansınız {{ .day }} gün içinde sona erecek. Detaylar için panele giriş yapın."
CronJobFailedAlert: "1Panel zamanlanmış görevi '{{ .name }}' başarısız oldu. Detaylar için panele giriş yapın."
ClamAlert: "1Panel virüs taraması {{ .num }} enfekte dosya tespit etti. Detaylar için panele giriş yapın."
WebSiteAlert: "1Panel üzerindeki {{ .num }} web sitesi {{ .day }} gün içinde sona erecek. Detaylar için panele giriş yapın."
SSLAlert: "1Panel üzerindeki {{ .num }} SSL sertifikası {{ .day }} gün içinde sona erecek. Detaylar için panele giriş yapın."
DiskUsedAlert: "1Panel diski '{{ .name }}' {{ .used }} kullanıldı. Detaylar için panele giriş yapın."
ResourceAlert: "1Panel üzerinde ortalama {{ .time }} dakikalık {{ .name }} kullanım oranı {{ .used }}. Detaylar için panele giriş yapın."
PanelVersionAlert: "1Panel için yeni bir sürüm mevcut. Güncellemek için panele giriş yapın."
PanelPwdExpirationAlert: "1Panel şifreniz {{ .day }} gün içinde sona erecek. Detaylar için panele giriş yapın."
+20 -2
View File
@@ -176,7 +176,7 @@ ErrPgImagePull: '鏡像拉取逾時,請配置鏡像加速或手動拉取{{ .na
#runtime
ErrFileNotExist: '{{ .detail }} 檔案不存在!請檢查來源檔案完整性!'
ErrImageBuildErr: '鏡像build 失敗'
ErrImageExist: '鏡像已存在!'
ErrImageExist: "鏡像已存在!請修改鏡像名稱。"
ErrDelWithWebsite: '執行環境已經關聯網站,無法刪除'
ErrRuntimeStart: '啟動失敗'
ErrPackageJsonNotFound: 'package.json 檔案不存在'
@@ -204,6 +204,8 @@ SystemLog: '系統日誌'
CutWebsiteLog: '切割網站日誌'
FileOrDir: '目錄 / 檔案'
UploadFile: '上傳備份文件 {{ .file }} 到 {{ .backup }}'
IgnoreBackupErr: '備份失敗,錯誤:{{ .detail }},忽略本次錯誤...'
IgnoreUploadErr: '上傳失敗,錯誤:{{ .detail }},忽略本次錯誤...'
#toolbox
ErrNotExistUser: '目前使用者不存在,請修改後重試!'
@@ -284,6 +286,7 @@ TaskBackup: '備份'
TaskRecover: '復原'
TaskRollback: '回滾'
TaskPull: '拉取'
TaskCommit: '制作'
TaskBuild: '建置'
TaskPush: '推送'
TaskHandle: '執行'
@@ -332,7 +335,8 @@ SnapDeleteLoginLog: '刪除存取日誌'
SnapDeleteMonitor: '刪除監控資料'
SnapRemoveSystemIP: '移除系統IP'
SnapBaseInfo: '寫入1Panel 基本資料'
SnapInstallApp: '備份1Panel 已安裝應用程式'
SnapInstallAppImageEmpty: '當前未勾選應用鏡像,跳過...'
SnapInstallApp: '備份 1Panel 已安裝應用'
SnapDockerSave: '壓縮已安裝應用'
SnapLocalBackup: '備份1Panel 本機備份目錄'
SnapCompressBackup: '壓縮本機備份目錄'
@@ -420,3 +424,17 @@ ErrAlertRemote: '警告訊息遠端錯誤,請檢查後重試!'
#task - runtime
ErrInstallExtension: "已有安裝任務正在進行,請等待任務結束"
# alert mail template
PanelAlertTitle: "面板警示通知"
TestAlertTitle: "測試郵件 - 驗證郵箱連通性"
TestAlert: "這是一封測試郵件,用於確認您的郵件發送設定是否正確。"
LicenseExpirationAlert: "您的 1Panel 授權將於 {{ .day }} 天後到期,詳情請登入面板查看。"
CronJobFailedAlert: "您的 1Panel 面板中,計畫任務 '{{ .name }}' 執行失敗,詳情請登入面板查看。"
ClamAlert: "您的 1Panel 面板病毒掃描任務發現 {{ .num }} 個受感染的檔案,詳情請登入面板查看。"
WebSiteAlert: "您的 1Panel 面板中有 {{ .num }} 個網站將於 {{ .day }} 天後到期,詳情請登入面板查看。"
SSLAlert: "您的 1Panel 面板中有 {{ .num }} 張 SSL 憑證將於 {{ .day }} 天後到期,詳情請登入面板查看。"
DiskUsedAlert: "您的 1Panel 面板磁碟 '{{ .name }}' 已使用 {{ .used }},詳情請登入面板查看。"
ResourceAlert: "您的 1Panel 面板於 {{ .time }} 分鐘內的平均 {{ .name }} 使用率為 {{ .used }},詳情請登入面板查看。"
PanelVersionAlert: "您的 1Panel 面板有可升級的新版本,詳情請登入面板查看。"
PanelPwdExpirationAlert: "您的 1Panel 面板密碼將於 {{ .day }} 天後到期,詳情請登入面板查看。"
+42 -24
View File
@@ -176,7 +176,7 @@ ErrPgImagePull: "镜像拉取超时,请配置镜像加速或手动拉取 {{ .n
#runtime
ErrFileNotExist: "{{ .detail }} 文件不存在!请检查源文件完整性!"
ErrImageBuildErr: "镜像 build 失败"
ErrImageExist: "镜像已存在!"
ErrImageExist: "镜像已存在!请修改镜像名称"
ErrDelWithWebsite: "运行环境已经关联网站,无法删除"
ErrRuntimeStart: "启动失败"
ErrPackageJsonNotFound: "package.json 文件不存在"
@@ -204,6 +204,8 @@ SystemLog: "系统日志"
CutWebsiteLog: "切割网站日志"
FileOrDir: "目录 / 文件"
UploadFile: "上传备份文件 {{ .file }} 到 {{ .backup }}"
IgnoreBackupErr: "备份失败,错误:{{ .detail }},忽略本次错误..."
IgnoreUploadErr: "上传失败,错误:{{ .detail }},忽略本次错误..."
#toolbox
ErrNotExistUser: "当前用户不存在,请修改后重试!"
@@ -284,6 +286,7 @@ TaskBackup: "备份"
TaskRecover: "恢复"
TaskRollback: "回滚"
TaskPull: "拉取"
TaskCommit: "制作"
TaskBuild: "构建"
TaskPush: "推送"
TaskHandle: "执行"
@@ -296,7 +299,7 @@ ConfigOpenresty: "创建 Openresty 配置文件"
InstallAppSuccess: "应用 {{ .name }} 安装成功"
ConfigRuntime: "配置运行环境"
ConfigApp: "配置应用"
SuccessStatus: "{{ .name }} 成功"
SuccessStatus: "{{ .name }} 成功"
FailedStatus: "{{ .name }} 失败 {{ .err }}"
HandleLink: "处理应用关联"
HandleDatabaseApp: "处理应用参数"
@@ -322,7 +325,7 @@ CustomAppstore: "自定义应用仓库"
OllamaModelPull: "拉取 Ollama 模型 {{ .name }} "
OllamaModelSize: "获取 Ollama 模型 {{ .name }} 大小 "
# task - snapshot
# task - snapshot
Snapshot: "快照"
SnapDBInfo: "写入 1Panel 数据库信息"
SnapCopy: "复制文件&目录 {{ .name }} "
@@ -332,6 +335,7 @@ SnapDeleteLoginLog: "删除访问日志"
SnapDeleteMonitor: "删除监控数据"
SnapRemoveSystemIP: "移除系统 IP"
SnapBaseInfo: "写入 1Panel 基本信息"
SnapInstallAppImageEmpty: "当前未勾选应用镜像,跳过..."
SnapInstallApp: "备份 1Panel 已安装应用"
SnapDockerSave: "压缩已安装应用"
SnapLocalBackup: "备份 1Panel 本地备份目录"
@@ -375,29 +379,29 @@ RecoverBackups: "恢复本地备份目录"
RecoverPanelData: "恢复数据目录"
# task - container
ContainerNewCliet: "初始化 Docker Client"
ContainerImagePull: "拉取容器镜像 {{ .name }}"
ContainerNewCliet: "初始化 Docker Client"
ContainerImagePull: "拉取容器镜像 {{ .name }}"
ContainerRemoveOld: "删除原容器 {{ .name }}"
ContainerImageCheck: "检查镜像是否正常拉取"
ContainerLoadInfo: "获取容器基本信息"
ContainerRecreate: "容器更新失败,现在开始恢复原容器"
ContainerCreate: "创建新容器 {{ .name }}"
ContainerCreateFailed: "容器创建失败,删除失败容器"
ContainerStartCheck: "检查容器是否已启动"
ContainerImageCheck: "检查镜像是否正常拉取"
ContainerLoadInfo: "获取容器基本信息"
ContainerRecreate: "容器更新失败,现在开始恢复原容器"
ContainerCreate: "创建新容器 {{ .name }}"
ContainerCreateFailed: "容器创建失败,删除失败容器"
ContainerStartCheck: "检查容器是否已启动"
# task - image
ImageBuild: "镜像构建"
ImageBuildStdoutCheck: "解析镜像输出内容"
ImageBuildRes: "镜像构建输出:{{ .name }}"
ImagePull: "拉取镜像"
ImageRepoAuthFromDB: "从数据库获取仓库认证信息"
ImaegPullRes: "镜像拉取输出:{{ .name }}"
ImagePush: "推送镜像"
ImageRenameTag: "修改镜像 Tag"
ImageNewTag: "新镜像 Tag {{ .name }}"
ImaegPushRes: "镜像推送输出:{{ .name }}"
ComposeCreate: "创建编排"
ComposeCreateRes: "编排创建输出:{{ .name }}"
ImageBuild: "镜像构建"
ImageBuildStdoutCheck: "解析镜像输出内容"
ImageBuildRes: "镜像构建输出:{{ .name }}"
ImagePull: "拉取镜像"
ImageRepoAuthFromDB: "从数据库获取仓库认证信息"
ImaegPullRes: "镜像拉取输出:{{ .name }}"
ImagePush: "推送镜像"
ImageRenameTag: "修改镜像 Tag"
ImageNewTag: "新镜像 Tag {{ .name }}"
ImaegPushRes: "镜像推送输出:{{ .name }}"
ComposeCreate: "创建编排"
ComposeCreateRes: "编排创建输出:{{ .name }}"
# task - website
BackupNginxConfig: "备份网站 OpenResty 配置文件"
@@ -419,4 +423,18 @@ ErrAlertSync: "告警信息同步错误,请检查后重试!"
ErrAlertRemote: "告警信息远端错误,请检查后重试!"
#task - runtime
ErrInstallExtension: "已有安装任务正在进行,请等待任务结束"
ErrInstallExtension: "已有安装任务正在进行,请等待任务结束"
# alert mail template
PanelAlertTitle: "面板告警通知"
TestAlertTitle: "测试邮件 - 验证邮箱连通性"
TestAlert: "这是一封测试邮件,旨在验证您的邮箱发件配置是否正确。"
LicenseExpirationAlert: "您的 1Panel 面板,许可证将在 {{ .day }} 天后到期,详情请登录面板查看。"
CronJobFailedAlert: "您的 1Panel 面板,计划任务-{{ .name }}执行失败,详情请登录面板查看。"
ClamAlert: "您的 1Panel 面板,病毒扫描任务发现 {{ .num }} 个感染文件,详情请登录面板查看。"
WebSiteAlert: "您的 1Panel 面板,有 {{ .num }} 个网站将在 {{ .day }} 天后到期,详情请登录面板查看。"
SSLAlert: "您的 1Panel 面板,有 {{ .num }} 张SSL证书将在 {{ .day }} 天后到期,详情请登录面板查看。"
DiskUsedAlert: "您的 1Panel 面板,磁盘 {{ .name }} 已使用 {{ .used }},详情请登录面板查看。"
ResourceAlert: "您的 1Panel 面板,平均 {{ .time }} 分钟内的 {{ .name }} 使用率为 {{ .used }},详情请登录面板查看。"
PanelVersionAlert: "您的 1Panel 面板,有最新面板版本可供升级,详情请登录面板查看。"
PanelPwdExpirationAlert: "您的 1Panel 面板,面板密码将在 {{ .day }} 天后到期,详情请登录面板查看。"
+1
View File
@@ -11,6 +11,7 @@ func Init() {
global.DB = common.LoadDBConnByPath(path.Join(global.Dir.DbDir, "agent.db"), "agent")
global.TaskDB = common.LoadDBConnByPath(path.Join(global.Dir.DbDir, "task.db"), "task")
global.MonitorDB = common.LoadDBConnByPath(path.Join(global.Dir.DbDir, "monitor.db"), "monitor")
global.AlertDB = common.LoadDBConnByPath(path.Join(global.Dir.DbDir, "alert.db"), "alert")
if global.IsMaster {
global.CoreDB = common.LoadDBConnByPath(path.Join(global.Dir.DbDir, "core.db"), "core")

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