Compare commits

...
386 Commits
Author SHA1 Message Date
CityFun 6c3f56516f feat: Remove test code (#11883) 2026-02-12 10:20:08 +08:00
CityFun 4833cf2d3c fix: Resolve issue where Feishu configuration does not take effect (#11878) 2026-02-11 22:07:06 +08:00
maninhill b5ca015632 Revise Key Features section in README.md
Updated the README.md to enhance the Key Features section with improved formatting and clarity.
2026-02-11 19:07:00 +08:00
maninhill 1c82034111 Revise README for improved feature presentation
Updated the README to enhance feature descriptions and improve clarity.
2026-02-11 18:58:28 +08:00
CityFun e45d723f3d style: change manualModel to checkbox (#11874) 2026-02-11 07:35:07 +00:00
ssongliu 627e6852db chore: Modify FTP account deletion prompt (#11873) 2026-02-11 15:06:58 +08:00
CityFun 1725767789 feat: add token reset for agent (#11872) 2026-02-11 14:54:59 +08:00
ssongliu 3e1f2b4934 fix: Fix order by sorting rule not taking effect issue (#11871) 2026-02-11 06:39:07 +00:00
ssongliu 140d20a21d chore: Remove memo double-click event (#11870) 2026-02-11 14:08:14 +08:00
CityFun 6e933fc83c feat: change website ssl sort logic (#11869) 2026-02-11 14:02:54 +08:00
ssongliu 79ff565995 fix: Firewall remove status filtering (#11868) 2026-02-11 14:02:34 +08:00
CityFun 39691698c9 feat: change order function (#11865) 2026-02-11 04:17:06 +00:00
ssongliu 4bedddb557 chore: Remove some error prints (#11864) 2026-02-11 03:45:07 +00:00
ssongliu f9547752f5 fix: Fix memo caching issue (#11863) 2026-02-11 03:13:07 +00:00
CityFun 96b37cb024 fix: Resolve HTTPS certificate configuration failure for websites (#11862) 2026-02-11 02:39:06 +00:00
蘭 30d874bc91 style: Modify the style of alert settings (#11860) 2026-02-10 13:07:06 +00:00
ssongliu 32bc8d922b fix: Fix orderby dependency injection issue (#11858) 2026-02-10 10:35:06 +00:00
MonkeyCode-AI a95d9aa27a fix: Add CheckIllegal validation to prevent command injection in 8 API endpoints (#11857)
- SSH log query: validate req.Info before grep concatenation
- iptables rule search: validate chain param in ReadFilterRulesByChain/LoadDefaultStrategy
- Firewalld port forward: validate port/protocol/targetIP/targetPort params
- Pure-FTPd user management: validate username/path in UserAdd/UserDel/SetPath/SetStatus
- PostgreSQL remote backup/recover: validate password/address/user/dbname
- MySQL remote backup/recover: validate password/address/user/dbname/format
- Website Composer: validate user/mirror/command/extCommand
- Disk operations: validate device/mountPoint/filesystem in Partition/Mount/Unmount
2026-02-10 18:32:42 +08:00
7f4f5a0a47 fix: Prevent SQL injection via orderBy parameter in WithOrderBy and WithOrderRuleBy (#11856)
Co-authored-by: maosite <naocanmonster@gmail.com>
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: monkeycode-ai <monkeycode-ai@chaitin.com>
2026-02-10 17:51:59 +08:00
CityFun 65a7193bc6 fix: resolve issue with minimax functionality (#11855) 2026-02-10 09:31:06 +00:00
ssongliu 64f79b4fdd perf: Optimize login expiration prompt information (#11854) 2026-02-10 09:17:06 +00:00
ssongliu 31d2af554e feat: Add carousel settings to overview page (#11853) 2026-02-10 08:45:06 +00:00
蘭 e0f2910710 fix: Fix all website/ssl alert error issues (#11852) 2026-02-10 16:42:19 +08:00
CityFun f89ab607b4 feat: update website traffic limit description text (#11850) 2026-02-10 07:39:05 +00:00
CityFun 61fa07b8f2 feat: Support up to three-level directory structure for website directory (#11847)
Refs https://github.com/1Panel-dev/1Panel/issues/11694
2026-02-10 06:55:05 +00:00
蘭 9653df185a ref: Adjust some alarm internationalization content (#11845) 2026-02-10 05:37:06 +00:00
ssongliu 8f7d3df76d fix: Fix system information refresh not taking effect (#11844) 2026-02-10 03:07:04 +00:00
CityFun 2aa89699c1 feat: change button name (#11840) 2026-02-09 13:13:03 +00:00
CityFun 46b8d3bd80 feat: add model switching functionality for AI agent (#11839) 2026-02-09 09:51:03 +00:00
蘭 8a7be481b0 ref: Adjust the docs address (#11838) 2026-02-09 08:57:02 +00:00
蘭 21bd94d3a3 fix: Fix the issue with editing alert settings (#11837) 2026-02-09 08:47:04 +00:00
CityFun d337190b59 fix: remove some code (#11836) 2026-02-09 15:38:06 +08:00
CityFun 7073e83d94 feat: change agent logic (#11835) 2026-02-09 15:33:15 +08:00
KOMATA 537ff49c0b f (#11834) 2026-02-09 15:33:05 +08:00
CityFun 4b1f643c70 feat: add Feishu configuration for OpenClaw (#11832) 2026-02-09 06:39:02 +00:00
ssongliu b8b873836d feat: Add agent to menu hidden items (#11831) 2026-02-09 14:25:23 +08:00
ssongliu 021334c2c8 fix: Fix disk and network card settings not taking effect issue (#11828)
Refs #11825
2026-02-09 06:13:02 +00:00
KOMATA 635d86a1a5 fix: update app details handling and sync logic in app_sync_task (#11830)
- Modified the logic to retain app details computed from remote sources, only falling back to existing details when necessary.
- Enhanced the synchronization process to ensure only remote resources are included in the synced app IDs for both addition and update scenarios.
2026-02-09 13:38:39 +08:00
ssongliu 607eae3ca8 chore: Modify model account prompt information (#11815) 2026-02-06 12:26:58 +00:00
ssongliu 66428c0a8e chore: Adjust menu names and positions (#11814) 2026-02-06 07:48:57 +00:00
ssongliu c0404a4ec5 feat: Optimize agent style and prompt information (#11813) 2026-02-06 07:46:56 +00:00
KOMATA 77ef3b7096 fix: replace DownloadFileWithProxy to stream method (#11811)
* refactor: replace DownloadFileWithProxy with DownloadFileWithProxyStream for improved file downloading logic

* refactor: remove commented-out DownloadFileWithProxy function to clean up code
2026-02-06 15:16:12 +08:00
sfwwslm 498052f4c9 style: Adjust the vertical alignment of the drop-down menu (#11812) 2026-02-06 15:13:02 +08:00
ssongliu 958c55eab8 chore: Rename dependency file names (#11809) 2026-02-06 10:51:18 +08:00
CityFun d3cddd175a feat: change style for agent (#11805) 2026-02-05 11:32:55 +00:00
ssongliu 6cd130677e feat: Add force image pull to container compose editing (#11804) 2026-02-05 11:30:57 +00:00
蘭 33b90e3979 feat: Webhook URL default hidden display (#11803) 2026-02-05 11:28:56 +00:00
CityFun e1e2b97e88 feat: Add an Agent Management Page (#11801) 2026-02-05 08:54:55 +00:00
ssongliu 8e4e1dc749 fix: Fix hostname cache refresh abnormality on overview page (#11802) 2026-02-05 08:26:55 +00:00
ssongliu 1eb5830238 fix: Fix service name loading failure in some scenarios (#11800)
Refs #11789
2026-02-05 08:24:56 +00:00
ssongliu 5db2ccbb22 perf: Optimize passkey prompt information (#11798) 2026-02-05 07:08:54 +00:00
蘭 47e04a14ad fix: Fix some issues with webhook alerts (#11797) 2026-02-05 06:40:54 +00:00
KOMATA a13c58eb18 fix: enhance app icon download logic to include file existence check (#11796)
* fix: enhance app icon download logic to include file existence check

- Updated the downloadAppIcon function to parse the icon field for the file name and ETag.
- Added a condition to check if the icon file exists before setting the If-None-Match header for the request.

* fix: improve app icon retrieval logic by adding filename check

- Updated GetAppIcon method to include filename in the response.
- Added a condition to ensure the ETag header is set only if both ETag and filename are present, enhancing the integrity of the caching mechanism.
2026-02-05 13:45:25 +08:00
KOMATA 15d77ceb46 fix: change sync logic to aggressive process (#11795)
* f

* f

* f
2026-02-05 13:44:59 +08:00
MoeShin af8fa6763f fix: Fix website https url (#11792) 2026-02-05 13:43:54 +08:00
ssongliu ed66d22bdb feat: Panel management adds status and redirection (#11794) 2026-02-05 03:42:54 +00:00
ssongliu 5209db3abc style: Adjust memo style (#11793) 2026-02-05 10:32:37 +08:00
MoeShin 35ea1a1d6c feat: Show container log timestamps (#11785) 2026-02-04 17:42:37 +08:00
蘭 b0a053b397 fix: Fix batch modification file/folder permission timeout issue (#11775)
#10737
2026-02-03 05:54:52 +00:00
王贺 a8b3390ce9 Revise installation instructions in README
Updated installation command and removed outdated references.
2026-02-02 19:24:05 +08:00
maninhill 4266e39d84 Revise README to highlight OpenClaw integration
Updated the description of 1Panel to emphasize its capabilities with OpenClaw and Ollama.
2026-02-02 19:21:07 +08:00
CityFun 5620b6e736 fix: resolve migration failure when reinstalling OpenResty (#11771) 2026-02-02 18:28:00 +08:00
ssongliu 289e4190ff perf: Optimize installed application password parameter display (#11767)
Refs #11636
2026-02-02 07:46:51 +00:00
ssongliu 9250412296 feat: Remove database transaction monitoring logs (#11765) 2026-02-02 06:48:51 +00:00
ssongliu 9c5370508e perf: Optimize application redirection style (#11764) 2026-02-02 06:42:52 +00:00
ssongliu a145bfdc5b fix: Improve application parameter modification logs (#11763)
Refs #11726
2026-02-02 06:40:51 +00:00
KOMATA db2b38eedb fix: update browser cache handling in website proxy service (#11762)
* fix: update browser cache handling in website proxy service

- Adjusted cache time condition to allow for negative values, introducing a no-cache option.
- Updated TypeScript interface to specify browser cache options as 'enable', 'disable', or 'noModify' for better clarity.

* refactor: clean up imports and enhance browser cache comments in website proxy service

- Removed unused import statements for better code clarity.
- Added detailed comments to clarify the behavior of the cache time settings in the OperateProxy function.
2026-02-02 11:14:30 +08:00
MoeShin 9303ee399f feat: Optimize HTTP to HTTPS redirection rules for non-443 port websites (#11761) 2026-02-02 11:07:15 +08:00
KOMATA d64cb3dc58 feat: add keepdeps.go to preserve mod clean in opensource version (#11752)
* feat: add keepdeps.go to preserve enterprise-only dependencies in OSS builds

* feat: migrate keepdeps.go to core/deps for OSS builds
2026-01-30 17:22:37 +08:00
KOMATA 62871acfbb fix: update cert reload logic and translation in Mux mode (#11751)
* fix: update SSL setting check to include 'Mux' option in GetSystemSSL function

* fix: update language files to clarify muxHelper descriptions for security implications

* feat: add keepdeps.go to preserve enterprise-only dependencies in OSS builds

* revert: revert cherrypick
2026-01-30 16:43:26 +08:00
ssongliu 7401906b03 fix: Fix compatibility issue with some OneDrive addition failures (#11748)
Refs #11741  #11685
2026-01-30 08:40:51 +00:00
蘭 5aa3c29684 feat: Support more alert method (#11742) 2026-01-30 08:38:50 +00:00
KOMATA 91745f592f feat: add auto passkey trial tracking to login form (#11749)
- Introduced functionality to track if the auto passkey login has been attempted using session storage.
- Implemented methods to initialize and mark the auto passkey trial status.
- Automatically trigger passkey login if conditions are met during settings retrieval.
2026-01-30 14:54:22 +08:00
KOMATA 564333ea03 feat: enhance app icon retrieval logic in home view (#11745)
- Updated app icon handling to use a dedicated function for improved clarity and maintainability.
- Introduced a new method, getAppIconSrc, to manage icon source retrieval based on app properties.
- Ensured proper handling of base64 and URL-based icons for better performance and consistency.
2026-01-30 14:53:58 +08:00
KOMATAandcopilot-swe-agent[bot] 7b543ea5fe feat: support Memo feature (#11744)
* Initial plan

* Add dashboard memo feature

Co-authored-by: HynoR <20227709+HynoR@users.noreply.github.com>

* Address code review feedback

Co-authored-by: HynoR <20227709+HynoR@users.noreply.github.com>

* fix: small fix

* feat: add tooltip for sensitive info and memo features in multiple languages

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
2026-01-30 14:53:44 +08:00
KOMATA fc5e583481 feat: implement app icon management and caching mechanism (#11719)
* feat: implement app icon management and caching mechanism

* feat: enhance app synchronization and icon management

- Refactored app synchronization tasks to improve structure and clarity.
- Introduced shared context for managing app sync state and metadata.
- Updated icon handling to ensure proper content type and caching.
- Adjusted cache control settings for app icons to extend cache duration.
- Improved error handling and logging during app sync processes.

* refactor: streamline app icon retrieval by removing unused fileName return

- Removed the fileName return value from GetAppIcon function as it was not utilized.
- Enhanced the GetAppIcon method in BaseApi to improve clarity and maintainability.
- Ensured proper caching headers are set for app icons.
2026-01-27 14:02:38 +08:00
CityFun 1767b1a9c1 feat: revert website name validation changes (#11728)
https://github.com/1Panel-dev/1Panel/issues/11718
2026-01-27 05:44:47 +00:00
KOMATA 930c340e37 fix: ensure local connection is properly closed in CheckLocalConn method (#11730) 2026-01-27 13:37:14 +08:00
ssongliu 58531d3995 fix: Fix scheduled task execution abnormality issue (#11725)
Refs  #11707
2026-01-26 08:22:48 +00:00
ssongliu 4b7c1390fb fix: Fix container compose env loading abnormality issue (#11723)
Refs #11724
2026-01-26 08:20:47 +00:00
ssongliu 2337d6f13a fix: Fix clam scan status display abnormality issue (#11722)
Refs #11686
2026-01-26 08:18:47 +00:00
CityFun cb8ecd55ed feat: Change openresty upgrade logic (#11714) 2026-01-23 17:49:07 +08:00
CityFun 33ad31dba2 fix: fix website redirects not displaying when disabled (#11713) 2026-01-23 17:48:52 +08:00
CityFun 294c3f128b feat: add application upgrade support to overview page (#11711) 2026-01-22 16:02:48 +08:00
CityFun 0899f4e9db feat: add retry mechanism for SSL certificate application (#11710) 2026-01-22 08:02:22 +00:00
CityFun 788f4d8307 feat: optimize IPv6 support for websites (#11708) 2026-01-22 15:59:53 +08:00
CityFun 2451d6bed2 feat: add support for EAB verification in custom ACME accounts (#11704)
https://github.com/1Panel-dev/1Panel/issues/11705
2026-01-21 10:02:19 +00:00
CityFun c547de875a feat: disable redirect configuration when website is stopped (#11700) 2026-01-21 17:59:48 +08:00
CityFun ffead783aa feat: Add parent website display in website list (#11695) 2026-01-21 17:59:38 +08:00
KOMATA d742c7d333 feat: add support for passkey login method (#11601)
* feat: Add passkey authentication support with registration and login endpoints

* style: Refactor import statements and improve button formatting in login and settings views

* chore: Update dependencies in go.mod and go.sum, including adding go-webauthn and updating indirect dependencies

* refactor: Consolidate passkey session management and update related structures for improved clarity and functionality

* feat: Add passkey support in multiple languages with corresponding error messages and management options

* feat: Implement passkey reset command and enhance internationalization support for passkey-related messages

* refactor: Simplify login form structure by removing commented sections and unused passkey login function

* fix: Update passkey failure messages across multiple languages to include SSL certificate verification instructions

* chore: update go mod

* refactor: Update passkey handling by consolidating status checks and renaming related fields for clarity

* style: Format i18n file

* refactor: Enhance passkey configuration checks
2026-01-20 10:28:04 +08:00
KOMATA 1025cd3535 refactor: improve code logic and format in app remote sync task (#11691)
* feat: Implement AppStore synchronization task

- Introduced a new `syncAppStoreTask` function to handle the synchronization of applications from the AppStore.
- The function checks for updates, retrieves the application list, and manages the synchronization process, including downloading app icons and updating app details.
- Refactored the existing app synchronization logic from `SyncAppListFromRemote` into the new task for improved maintainability and clarity.
- Added logging for progress tracking during the synchronization process.

* feat: Refactor HTTP request handling to support custom client

- Introduced `HandleRequestWithClient` function to allow custom HTTP clients for requests.
- Moved the transport loading logic into the new function for better separation of concerns.
- Updated `HandleRequest` to utilize the new function, enhancing flexibility in request handling.

* refactor: Simplify HTTP request handling in app synchronization

- Replaced direct HTTP request handling with `HandleRequestWithClient` for downloading app icons and Docker Compose files.
- Removed unused variables and logging related to icon download updates for cleaner code.
- Improved error handling and logging for failed requests.
2026-01-19 17:15:19 +08:00
Scars de402ea724 feat: Support batch import for Docker images (#11640)
* feat: Support batch import for Docker images

* perf: Avoid duplicate Docker client creation in ImageLoad

* style: Remove unnecessary comments in FileList component
2026-01-19 10:05:17 +08:00
KOMATA 3af07a42d6 feat: support file remark ability in file management (#11626)
* feat: Add file remark ability in file management

* refactor: improve mark get logic

* feat: Implement remark filtering for file management buttons
2026-01-16 17:05:32 +08:00
KOMATA e5b7e99cce feat: retrieve listening processes in firewall port page (#11665)
* feat: Add endpoint to retrieve listening processes and update related services

* fix: Update listening process retrieval logic to include socket type checks

* refactor:  accept context for improved request handling
2026-01-16 16:54:15 +08:00
KOMATA 365936bf57 feat: Add pullImage option to ComposeCreate to swith pull operation (#11667)
* feat: Add pullImage option to ComposeCreate and update related logic

- Introduced a new optional field `pullImage` in the `ComposeCreate` struct to control image pulling behavior.
- Updated `CreateCompose` method to handle the `pullImage` flag when invoking the compose up command.
- Modified `UpWithTask` function to conditionally skip image pulling based on the `pullImages` parameter.
- Enhanced frontend form to include a checkbox for the `pullImage` option, with default value set to true.

* feat: Add label for environment variable input in Compose form
2026-01-16 16:40:55 +08:00
ssongliu 2b7562f546 feat: Add snapshot restore timeout duration (#11660) 2026-01-16 11:09:05 +08:00
ssongliu e7f4e312b9 fix: Fix container image cleanup uninitialized issue (#11655) 2026-01-15 10:09:59 +00:00
ssongliu 98d3870477 fix: Fix incomplete scanning of unassociated backups issue (#11654) 2026-01-15 10:08:00 +00:00
CityFun b1ceb2e53d feat: fix redirection issue caused by incorrect PHP PATH_INFO config (#11650)
Refs https://github.com/1Panel-dev/1Panel/issues/11651
2026-01-15 07:26:01 +00:00
ssongliu 075d2468ea feat: Add file copy command timeout duration (#11652)
Refs #11639
2026-01-15 07:23:59 +00:00
ssongliu 8b9e7aef4a fix: Fix container image cleanup count display abnormality issue (#11649) 2026-01-15 14:47:57 +08:00
ssongliu 15e499c1e9 perf: Optimize cache cleanup logic (#11648) 2026-01-15 06:45:59 +00:00
55ccb9fe61 fix: DNS Manual SSL certificate issues for wildcard domains (#11642)
This fix addresses three bugs in the DNS Manual SSL certificate flow:

1. **Order caching fails when Expires is zero**: ACME orders often have
   zero Expires initially. The condition `!Expires.IsZero()` caused valid
   cached orders to be deleted and recreated with different TXT values.
   Fixed by checking `Expires.IsZero() || Expires.After(now)`.

2. **Wildcard and base domain TXT records overwrite each other**: When
   requesting SSL for both `example.com` and `*.example.com`, both
   authorizations have identifier `example.com`, causing one TXT value
   to overwrite the other. Fixed by using `*.domain` as the map key.

3. **Only first TXT record checked**: When multiple TXT records exist,
   only the first was checked. Fixed by returning all TXT values and
   checking if expected value exists in any of them.

```release-note
Fix DNS Manual SSL certificate issues for wildcard domains
```

Co-authored-by: DeployThemAll <deploythemall@users.noreply.github.com>
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-15 10:42:59 +08:00
蘭 a5816f1dc9 fix: Fix alert log issues (#11637) 2026-01-14 08:47:56 +00:00
ssongliu 95a613f59f fix: Fix GPU monitoring page refresh abnormality issue (#11635) 2026-01-14 06:57:57 +00:00
J.Knight 6777941dee feat: add support for Porkbun DNS provider (#11614) 2026-01-14 10:38:12 +08:00
ssongliu e55128b7fa perf: Optimize security portal prompt information (#11613) 2026-01-09 16:50:01 +08:00
CityFun 9ac11002f8 perf: Optimize app store icon display (#11612) 2026-01-09 08:41:52 +00:00
ssongliu 1cc7bfb4a9 fix: Fix cache cleanup checkbox abnormality issue (#11611) 2026-01-09 16:18:37 +08:00
CityFun e59534e54f feat: change appstore sync logic for v2.0.17 (#11610) 2026-01-09 07:17:52 +00:00
ssongliu 8c2368c1d2 fix: Fix snapshot cleanup abnormality issue (#11608) 2026-01-09 05:47:52 +00:00
CityFun 8e8f181cae fix: Fixed issue with website log display error in clean cache (#11607) 2026-01-09 04:01:51 +00:00
蘭 2fcab77565 fix: Fix more button overflow screen issues (#11605) 2026-01-09 11:17:26 +08:00
ssongliu d29f0c6c31 style: Modify container image pull style (#11604) 2026-01-09 11:14:15 +08:00
CityFun 79f1b1458b fix: Fix infinite log reading issue (#11603) 2026-01-09 11:14:07 +08:00
蘭 b279ee6b82 fix: Fix the issue of opening folder path errors (#11600) 2026-01-08 22:01:54 +08:00
蘭 c64a58d814 fix: Fix the issue of not sending SMS alerts (#11599) 2026-01-08 17:33:27 +08:00
ssongliu 57c60ab3f8 perf: MySQL backup parameter deduplication (#11597) 2026-01-08 15:27:00 +08:00
ssongliu e37d90d6e1 fix: Fix node overview cannot operate container terminal issue (#11593) 2026-01-08 06:17:49 +00:00
KOMATA 22055ed99a refactor: enhance Mux connection handling in server (#11590)
* refactor: enhance Mux connection handling in server

* fix: Increase maximum allowed length for RequestURI in Mux connection handling

* feat: Add support for HTTP/2 in Mux mode
2026-01-08 14:03:04 +08:00
KOMATA e263331b94 feat: Enhance entrance pattern validation in settings API to exclude specific segments and assets (#11592) 2026-01-08 14:01:43 +08:00
ssongliu eb3d613e3d feat: Set overview as default menu for multi-node management (#11588) 2026-01-07 22:06:13 +08:00
CityFun cc23444776 feat: change listNodes api timeout (#11586) 2026-01-07 09:29:48 +00:00
ssongliu 1931d3f262 perf: Optimize SSL selection style (#11585) 2026-01-07 16:08:56 +08:00
CityFun 79cbbbdf0a perf: Optimize display for long website names (#11581) 2026-01-07 07:09:47 +00:00
ssongliu 38cd785aa3 fix: Fix node overview operation abnormality issue (#11580) 2026-01-07 07:07:48 +00:00
ssongliu dabec3f87a fix: Fix cache cleanup abnormality issue (#11577) 2026-01-06 22:09:11 +08:00
ssongliu 6c94cb2bdf feat: MySQL backup supports custom parameters (#11575) 2026-01-06 16:41:55 +08:00
KOMATA f67bdf0c22 feat: Implement connection closure handling in status 444 (#11574)
* feat: Implement connection closure handling in security module

* refactor: Rename closeConn to CloseDirectly for clarity in security module
2026-01-06 16:41:20 +08:00
蘭 6c51b31270 fix: Fix more button overflow screen issues (#11573) 2026-01-06 07:39:44 +00:00
ssongliu ef6e342737 fix: Fix ICMP ping disable status update abnormality issue (#11571) 2026-01-06 04:01:43 +00:00
蘭 ac0ef98dde feat: Add panel name and IP address to email alert content (#11567) 2026-01-05 22:10:27 +08:00
CityFun 523775e56b fix: Fix log viewing errors when switching nodes (#11565) 2026-01-05 18:06:36 +08:00
CityFun b6e741a150 feat: change node select params rules (#11563) 2026-01-05 17:29:51 +08:00
CityFun 54357fbe2b feat: Add backend api timeout (#11562) 2026-01-05 08:41:42 +00:00
ssongliu 040dda5bec feat: Add node overview description information (#11561) 2026-01-05 08:25:42 +00:00
CityFun 77b4d58fb8 feat: Add required validation for pushNodes (#11560) 2026-01-05 08:23:42 +00:00
CityFun 0c9e551492 fix: Fix API to prevent frontend code startup errors (#11559) 2026-01-05 07:47:42 +00:00
KOMATA 2ba1e2e22e fix: Update regex patterns for log highlighting in custom component (#11556)
* fix: Update regex patterns for log highlighting in custom component

* feat: Add new regex pattern for HTTP status codes highlighting in custom log component

* refactor: Update HTTP status code regex patterns in custom log component for improved highlighting
2026-01-05 14:49:09 +08:00
ssongliu d8a08a6c0c fix: Fix iptables rule display abnormality issue (#11555) 2026-01-05 14:48:49 +08:00
ssongliu 2c92226f88 chore: Modify cache cleanup internationalization (#11551) 2026-01-04 18:32:46 +08:00
CityFun cd9cb9b9c9 feat: Update website name validation rules (#11550) 2026-01-04 10:29:43 +00:00
CityFun b2777055bc fix: Fix website log cleanup selection state (#11549) 2026-01-04 10:27:40 +00:00
ssongliu c8673b60bb fix: Fix the issue of abnormal cache cleanup display (#11547) 2026-01-04 09:31:43 +00:00
ssongliu 21975dff2d chore: Rollback S3 backup account SDK version (#11546)
Refs #11486

aws v2 存在部分兼容性问题 https://github.com/aws/aws-sdk-go-v2/issues/3007
2026-01-04 08:51:40 +00:00
CityFun 6ab14185e0 perf: Optimize supervisor log reading (#11545)
Refs https://github.com/1Panel-dev/1Panel/issues/11515
2026-01-04 08:49:40 +00:00
CityFun 1f70646952 feat: Add enable UDP option for TCP/UDP website configuration (#11543) 2026-01-04 15:23:56 +08:00
ssongliu c90ed9e873 feat: Cache database default connections (#11540)
Refs  #10819
2026-01-04 06:39:40 +00:00
CityFun e4ee07d36b feat: Support batch installation of applications (#11539) 2026-01-04 05:43:40 +00:00
ssongliu 8fb3e78554 chore: Fix some internationalization content (#11528)
Refs #11517
2025-12-31 14:35:38 +00:00
CityFun 01ab004876 fix: update MdEditor component to prevent potential XSS attacks (#11527) 2025-12-31 14:33:38 +00:00
ssongliu 6205eac51c feat: Support enabling proxy when adding nodes (#11525) 2025-12-31 22:31:43 +08:00
王贺 d9a5418029 chore: update copyright (#11524) 2025-12-31 09:03:56 +08:00
CityFun 74a8e61f3e feat: change website http to https logic (#11522)
Refs https://github.com/1Panel-dev/1Panel/issues/11521
2025-12-30 06:33:36 +00:00
CityFun e18472f1a3 feat: optimize disk mount logic (#11519) 2025-12-30 06:31:35 +00:00
蘭 e3dea80cb7 fix: Fix the issue of garbled Chinese characters in recipient names and email titles in email alerts (#11516)
#11264
2025-12-29 09:37:35 +00:00
ssongliu 50947f4ddc feat: ICMP ping disable compatibility with Debian 13 (#11514)
Refs  #11472
2025-12-29 09:35:36 +00:00
KOMATA d302bc07b3 feat: Add support for Mux SSL mode and update related settings (#11509)
* feat: Add support for Mux SSL mode and update related settings

- Introduced a new SSL mode "Mux" in the settings, allowing for HTTP to HTTPS redirection.
- Updated the `SSL` field in the `SettingUpdate` struct to include "Mux" as a valid option.
- Modified the server logic to handle Mux connections, including certificate management and HTTP redirection.
- Updated frontend components to reflect the new SSL options and improved user guidance in multiple languages.

* fix: Update HTTPS related messages for improved clarity and security guidance in multiple languages
2025-12-29 15:41:15 +08:00
CityFun c6df20e565 feat: support sync 404 html page to websites (#11513)
Refs https://github.com/1Panel-dev/1Panel/issues/11489
2025-12-29 07:17:34 +00:00
CityFun 6f9a1321d1 feat: support custom DNS server for manual DNS mode when applying certificates (#11512) 2025-12-29 07:13:35 +00:00
ssongliu eb4eb31578 feat: Multi-machine management supports viewing node logs (#11510) 2025-12-29 07:11:34 +00:00
CityFun f8f5fcfca2 fix: Fixed Website configuration file automatically keeps adding HTTPS redirects (#11506)
#11490
2025-12-29 07:09:35 +00:00
l061x 4e7b654090 fix: Fix command argument formatting in terminal initlinux (#11484) 2025-12-26 22:03:24 +08:00
ssongliu e8059d3676 fix: Fix refresh issue during compose creation (#11482)
Refs #11463
2025-12-26 13:55:03 +00:00
ssongliu ffadc6721b fix: Fix issue where compose cannot be created when empty (#11481) 2025-12-26 09:13:04 +00:00
CityFun e2e774d215 feat: add website log clean option for clean cache (#11480) 2025-12-26 09:11:03 +00:00
ssongliu f23d3678fb fix: Fix backup account echo abnormality issue (#11479)
Refs #11478
2025-12-26 09:09:03 +00:00
ssongliu dfade50bd8 fix: Fix snapshot backup application image failure issue (#11477) 2025-12-26 16:49:03 +08:00
ssongliu d25b5f4f2a feat: Add quick operation features to node overview page (#11476) 2025-12-26 08:35:03 +00:00
CityFun 9a17bc74b0 feat: add 'nofail' option for disk mounting (#11475)
Refs https://github.com/1Panel-dev/1Panel/issues/11468
2025-12-26 08:33:03 +00:00
CityFun 015605526d fix: add missing intermediate certificates for IP certificate requests (#11470)
Refs https://github.com/1Panel-dev/1Panel/issues/11462
2025-12-26 08:31:03 +00:00
CityFun 099896dc96 feat: add app temporary download file cleanup option to cache cleaning (#11460) 2025-12-25 10:33:03 +00:00
CityFun aa1bc6fa8e feat: add website log cleanup option to cache cleaning (#11457) 2025-12-25 07:43:02 +00:00
ssongliu f27a429fc5 feat: Cache cleanup supports cleaning up unassociated backups (#11452) 2025-12-25 13:49:47 +08:00
ssongliu f4587d0ae5 fix: Fix panic issue caused by 1pctl command (#11453)
Refs  #11449
2025-12-25 04:17:02 +00:00
CityFun 41bae1e636 perf: improve display of IP certificate editing interface (#11451)
Refs https://github.com/1Panel-dev/1Panel/issues/11449
2025-12-25 03:17:02 +00:00
CityFun 5295fd08d4 feat: update Let's Encrypt to official endpoint (#11444) 2025-12-24 11:24:17 +08:00
CityFun 36fdf05d2b feat: support apply ip Certificate (#11437) 2025-12-23 13:35:38 +08:00
ssongliu 5794dbc0a6 Merge pull request #11435 from 1Panel-dev/pr@dev-v2@fix_compose_log
fix: Fix compose log download failure issue
2025-12-23 13:35:14 +08:00
ssongliu c10474db69 fix: Fix typo (#11436) 2025-12-23 05:35:06 +00:00
wanghe d68309fd44 Merge pull request #11432 from HynoR/fix/firewall
feat: Add UDP port 443 accept rule to Pre-firewall config
2025-12-22 19:14:21 +08:00
CityFun 1c634756a0 fix: resolve issue with quick navigation failure in applications (#11434) 2025-12-22 18:50:22 +08:00
CityFun ae536550c3 style: adjust node card display (#11431) 2025-12-22 18:49:58 +08:00
ssongliu 63d5d70314 feat: Retain historical settings when toggling watermark (#11429)
Refs #11392
2025-12-22 10:23:39 +00:00
KOMATA 0be73600e5 feat: Implement Punycode utility functions and integrate into domain dispay (#11433)
* feat: Implement Punycode utility functions and integrate into domain display

* chore: Add punycode package and update import path in utility functions
2025-12-22 18:22:29 +08:00
ssongliu c9cc064f51 fix: Fix container orchestration creation failure issue (#11430) 2025-12-22 18:21:51 +08:00
HynoR dc47eb5ae4 feat: Add UDP port 443 acceptance rule to firewall and iptables configuration 2025-12-22 15:55:47 +08:00
ssongliu 955d9db699 style: Modify terminal connection style issues (#11425)
Refs  #11413
2025-12-22 06:33:29 +00:00
CityFun 3c1549e288 fix: resolve limitation of fetching only 100 redirects for websites (#11424) 2025-12-22 14:31:40 +08:00
CityFun 9fd030b6c3 feat: improve scrolling effect of left-side on website configuration page (#11423)
Refs https://github.com/1Panel-dev/1Panel/issues/11412
2025-12-22 06:01:30 +00:00
CityFun 63a10691b3 feat: limit website name length in configuration (#11422) 2025-12-22 05:59:30 +00:00
ssongliu 5674673c5d feat: Support display and operations for multi-YAML compose startup (#11421)
Refs #11405
2025-12-22 05:57:30 +00:00
ssongliu 74f29e3dc3 chore: Adjust image dependency warning messages (#11407) 2025-12-19 10:08:32 +00:00
ssongliu 1dac8593ae feat: Add database transaction monitoring logs (#11406) 2025-12-19 10:06:32 +00:00
ssongliu 71fd123be1 fix: Fix snapshot application checkbox abnormality issue (#11404) 2025-12-19 10:04:33 +00:00
CityFun d4d66cad2e feat: improve prompt messages when synchronizing WAF IP groups (#11402) 2025-12-19 07:41:02 +00:00
CityFun c78a6ec047 style: change table tooltip style (#11399) 2025-12-19 15:33:57 +08:00
CityFun 1ae3198b10 fix: clear stream configuration when closing website creation page (#11400) 2025-12-19 07:27:03 +00:00
CityFun a6c172b1ab feat: remove Cache-Control: no-cache for create proxy website (#11398) 2025-12-19 07:25:03 +00:00
CityFun 9e9d68f841 style: adjust status column styling in websites list page (#11397) 2025-12-19 07:23:02 +00:00
CityFun 123e6b90a3 feat: update MySQL parameter input validation rules (#11396) 2025-12-19 05:42:11 +00:00
CityFun 00b4a6caf5 feat: update MySQL parameter input validation rules (#11395)
https://github.com/1Panel-dev/1Panel/issues/11394
2025-12-19 03:41:27 +00:00
ssongliu 8642e20d19 perf: Optimize container task log output (#11393) 2025-12-19 03:39:40 +00:00
ssongliu 2b81f7ef7a feat: Output last incomplete line in shell cronjob (#11391) 2025-12-18 21:58:24 +08:00
ssongliu c08e83e5d4 fix: Fix firewalld range port forwarding failure issue (#11390) 2025-12-18 09:21:13 +00:00
CityFun 863ee5c172 feat: Adjust tooltip positions on some pages (#11389) 2025-12-18 09:01:14 +00:00
ssongliu a91f372fbd chore: Revert container command input style (#11387)
revert for #10724

https://bbs.fit2cloud.com/t/topic/16496
2025-12-18 08:59:14 +00:00
ssongliu 825acd5b46 feat: Optimize snapshot backup items (#11385) 2025-12-18 07:43:13 +00:00
CityFun 70e64e648f feat: Adjust prompt for creating stream website (#11384) 2025-12-18 07:41:13 +00:00
ssongliu 7855e89ca9 chore: Modify orchestration environment variable style (#11381) 2025-12-17 14:20:12 +00:00
CityFun 75273abda7 fix: Resolve issue where website names are not displayed in logs for stream-type websites (#11379) 2025-12-17 08:50:11 +00:00
ssongliu 02b278ecff feat: Add version printing after rollback (#11378)
Refs #11265
2025-12-17 08:48:10 +00:00
CityFun e43d7b7a59 fix: Fix issue where backing up stream websites fails (#11377) 2025-12-17 16:46:51 +08:00
CityFun 61f321a04d fix: Fix issue where disabling logs for stream websites fails (#11376) 2025-12-17 16:46:41 +08:00
CityFun 7885b00213 feat: Optimize display issues after creating TCP/UDP proxies (#11375) 2025-12-17 08:46:11 +00:00
CityFun 2ad882b7c2 fix: Fixed issue where deleting symbolic link files fails (#11373)
Refs https://github.com/1Panel-dev/1Panel/issues/11324
2025-12-17 08:44:10 +00:00
ssongliu 2546809013 chore: Adjust task log output format (#11372) 2025-12-17 06:24:09 +00:00
CityFun 4fcf3e11cd fix: Resolve issue where bulk certificate setup creates duplicate server certificates (#11371) 2025-12-17 05:52:10 +00:00
CityFun 1f6cd6f4e9 feat: increase timeout duration for some API interfaces (#11370)
Refs https://github.com/1Panel-dev/1Panel/issues/11348
2025-12-17 05:50:09 +00:00
ssongliu 5c1ce232db chore: Modify i18n initialization method (#11368) 2025-12-17 13:46:32 +08:00
CityFun 172e9e212b feat: Adjust the display position of PHP runtime environment prompts (#11366) 2025-12-17 05:46:09 +00:00
CityFun 279a16c256 feat: Optimize HTTP to HTTPS redirection rules for non-443 port websites (#11364)
Refs https://github.com/1Panel-dev/1Panel/issues/11360
2025-12-17 05:44:09 +00:00
ssongliu e772cdff3c fix: Fix task log printing garbled characters issue (#11363) 2025-12-17 13:42:23 +08:00
CityFun 8881827f9d feat: Optimize the structure of installed apps in the App Store (#11361) 2025-12-17 13:42:13 +08:00
ssongliu d9579901fe fix: Fix incomplete log printing for compose save errors (#11357) 2025-12-16 08:41:58 +00:00
蘭 682d78698f ref: Optimize the button layout on the file upload page (#11354) 2025-12-16 08:39:58 +00:00
CityFun 2cc8567c2f feat: relocate PHP extension installation prompts (#11352)
https://github.com/1Panel-dev/1Panel/issues/11293
2025-12-16 08:38:00 +00:00
ssongliu 184a214bff feat: Add hint messages for compose environment variables (#11351) 2025-12-16 08:35:59 +00:00
CityFun c6376541ed feat: add OpenResty version restriction for TCP/UDP proxy (#11350) 2025-12-16 08:33:59 +00:00
ssongliu 6aca579854 fix: Fix ufw IPv6 rule configuration issue (#11349)
Refs #11211
2025-12-16 08:31:59 +00:00
CityFun 15bc6cf884 feat: add prompt for TCP/UDP proxy (#11343) 2025-12-15 18:56:07 +08:00
ssongliu 895dde74dd fix: Fix local application backup failure in snapshots (#11341) 2025-12-15 18:42:05 +08:00
CityFun c477d20a05 feat: support batch SSL configuration for websites (#11339)
Refs https://github.com/1Panel-dev/1Panel/issues/8250
2025-12-15 10:41:38 +00:00
ssongliu 2fdb1700e5 feat: Database restore supports timeout settings (#11338) 2025-12-15 18:40:37 +08:00
KOMATA 11764e1ba5 feat: improve website create function and interface (#11331)
* feat: improve website create function and interface

* feat: update website create tag

* feat: update i18n files

* feat: update valid domain regex

* feat: reorder website types to improve structure

* feat: update domain labels for consistency in the domain creation form

* feat: update i18n
2025-12-15 16:01:21 +08:00
ssongliu 3212e23f43 feat: Terminal connection supports reset (#11336) 2025-12-15 07:19:21 +00:00
CityFun 84a58f2bb1 fix: resolve issue where crossVersionUpdate setting not taking effect (#11333)
Refs https://github.com/1Panel-dev/1Panel/issues/11328
2025-12-15 07:17:21 +00:00
KOMATA 191396c78a fix: Validate StreamPorts in StreamConfig to prevent empty values (#11332) 2025-12-15 10:11:09 +08:00
CityFun 956a64c692 feat: Support managing application upgrades for all nodes on a single page (#11325) 2025-12-12 10:49:10 +00:00
ssongliu 6f9a8d5afe fix: Fix version upgrade failure in some scenarios 2025-12-12 18:22:49 +08:00
ssongliu 9424d061fa fix: Fix version upgrade failure in some scenarios (#11322) 2025-12-12 18:13:35 +08:00
CityFun 3db8e47679 feat: optimize prompt for syncing WAF IP groups (#11317) 2025-12-12 08:11:11 +00:00
CityFun 5602741550 feat: improve container deletion prompt (#11316) 2025-12-12 08:09:11 +00:00
CityFun 1ef3f540c2 feat: Task center supports switching nodes (#11315) 2025-12-12 08:07:12 +00:00
ssongliu 51846895d5 fix: Fix lock issue during monitoring input insertion (#11312) 2025-12-12 08:05:11 +00:00
ssongliu 551542c99c perf: Optimize GPU monitoring style (#11303) 2025-12-11 14:11:05 +00:00
CityFun ab13ddec48 feat: The website supports TCP/UDP proxying. (#11302)
Refs https://github.com/1Panel-dev/1Panel/issues/5135
Refs https://github.com/1Panel-dev/1Panel/issues/6976
2025-12-11 10:33:03 +00:00
CityFun 2d1792c6de fix: Fix the issue where the website app deployment fails (#11301) 2025-12-11 09:03:03 +00:00
KOMATA 3aedb7e82d chore: Add trusted dependencies for vue-office packages to support bun toolkit (#11291) 2025-12-11 17:00:34 +08:00
ssongliu a9c9f0272a fix: Fix cronjob manual stop not working (#11300) 2025-12-11 17:00:22 +08:00
Kadxy 8606427e8c feat(frontend): add autocomplete attributes for MFA input (#11297) 2025-12-11 16:59:58 +08:00
ssongliu 3d3d22dc06 fix: Fix local PostgreSQL database backup failure (#11295) 2025-12-10 19:02:24 +08:00
ssongliu 23a3a5cf74 Fix typo (#11287) 2025-12-10 14:31:19 +08:00
ssongliu 0f5410d225 fix: Fix focus loss issue in file selection component (#11281)
Refs #11272
2025-12-09 10:38:56 +00:00
ssongliu 1b7be836ef fix: Fix container orchestration format validation issues (#11280)
Refs #11227
2025-12-09 10:36:57 +00:00
CityFun 66e7223db9 feat: Support quick installation of PHP lz4 extension (#11279) 2025-12-09 09:42:56 +00:00
ssongliu d801b811d4 style: Optimize container-associated resource styles (#11278)
Refs #11268
2025-12-09 09:40:57 +00:00
ssongliu 307029272e fix: Fix port forwarding initialization status judgment issue (#11276)
Refs #11267
2025-12-09 09:38:57 +00:00
ssongliu 9aa3caab16 feat: Container compose supports .env file operations (#11274) 2025-12-09 09:36:58 +00:00
KOMATA b47a4525dc feat: Enhance browser cache management and localization support (#11277)
* feat: Enhance browser cache management and localization support

- Updated browser cache handling in WebsiteService to include a 'noModify' option.
- Introduced new method AddBroswerNoCache in Location to manage no-cache directives.
- Added localization for 'noModify' in multiple languages.
- Updated frontend to support the new browser cache options in the proxy configuration.

* fix: Update cache display logic in proxy configuration

- Adjusted the rendering of cache tags in the proxy configuration to conditionally display based on cache time values.
- Improved user interface by adding margin classes for better spacing between cache tags.
2025-12-09 17:32:23 +08:00
KOMATA 38ab0d9ca0 feat: Implement process management and network data handling (#11270)
- Introduced ProcessStore for managing process and network data via WebSocket.
- Enhanced TableSearch component to synchronize search parameters with props.
- Updated network and process views to utilize ProcessStore for data fetching and state management.
- Improved data filtering and sorting in network and process views.
- Added WebSocket connection management with polling for real-time updates.
2025-12-09 17:31:23 +08:00
KOMATA 38985671c6 perf: improve getNetConnections function and Websocket handling (#11269)
* feat: Enhance WebSocket client functionality and improve data processing

- Reduced message queue size in WebSocket client from 100 to 32.
- Introduced atomic boolean to track client closure state.
- Added SendPayload method to handle message sending with queue management.
- Updated ProcessData function to utilize SendPayload for sending responses.
- Expanded netTypes to include both IPv4 and IPv6 protocols in network connection retrieval.
- Improved net connection processing by using a map for process names, enhancing efficiency.

* feat: Enhance WebSocket client and process data handling

- Added synchronization with sync.Once for safe closure of WebSocket client.
- Updated message queue size to a constant for better maintainability.
- Implemented context timeouts for process data retrieval to prevent blocking.
- Improved network connection handling by utilizing a more efficient method for retrieving connections.
- Introduced a new function to determine connection types based on protocol family.

* feat: Enhance network connection retrieval and process name mapping

- Updated getNetConnections function to improve efficiency by using maps for process names and connections.
- Introduced a new helper function to retrieve process names from the filesystem or process context.
- Enhanced filtering logic for network connections based on process ID, name, and port.
- Increased initial capacity for connection results to optimize performance.

* refactor: Rename SendPayload method to Send in WebSocket client

- Updated the SendPayload method to be more succinctly named Send for clarity.
- Ensured the method continues to handle message sending while maintaining existing functionality.

* refactor: Update ProcessData and getNetConnections for improved clarity and efficiency

- Replaced SendPayload method calls with Send for consistency in WebSocket message handling.
- Enhanced getNetConnections function by refining process name retrieval and filtering logic.
- Improved error handling in getProcessNameWithContext for better robustness.

* refactor: Simplify WebSocket client closure and reading logic

- Removed unnecessary synchronization for closing the WebSocket client.
- Updated the Read method to handle message reading directly without a separate Close method.
- Ensured the Socket is closed properly after reading messages to prevent resource leaks.
2025-12-09 17:30:12 +08:00
ssongliu d1c2a69820 pref: Optimize backup account code structure (#11261) 2025-12-08 18:22:59 +08:00
KOMATA 3a1b9fedd1 perf: Simplify process data handling and improve performance (#11236)
* refactor: Simplify process data handling and improve performance

- Replaced goroutine-based processing with a direct loop for handling process data.
- Introduced context support for process and connection retrieval.
- Enhanced error handling and data structuring for process information.
- Improved SSH session retrieval by mapping users by host for better efficiency.

* chore: go fmt
2025-12-08 17:34:52 +08:00
蘭 8119ba75fc ref: Adjust Swagger format (#11207)
* ref: Adjust Swagger format

* ref: Adjust Swagger format
2025-12-08 17:34:24 +08:00
ssongliu be4c22012e fix: Fix HTTP/HTTPS docker proxy issues (#11258)
Refs #11254
2025-12-08 09:20:38 +00:00
ssongliu 60fef48cf0 chore: Remove system upgrade temporary files (#11255)
Refs #11241
2025-12-08 09:18:37 +00:00
KOMATA bb72ea0fc0 fix: Improve SSH session retrieval by grouping users by host in process_data.go (#11210) 2025-12-08 16:39:08 +08:00
KOMATA af48fed3c2 feat: Add file preview functionality and interface (#11200)
* feat: Add file content preview functionality

- Implemented a new API endpoint for previewing file content.
- Added PreviewContent method in BaseApi to handle requests.
- Introduced GetPreviewContent method in FileService to retrieve file previews.
- Updated frontend to include a TextPreview component for displaying file previews.
- Added localization support for preview-related messages in multiple languages.
- Enhanced file management view to support previewing large files.

* feat: Update file preview functionality and interface

- Added PreviewContentReq interface to define request parameters for file preview.
- Updated getPreviewContent function to use the new PreviewContentReq type.
- Modified text-preview component to align with updated API, removing unnecessary parameters.
2025-12-08 16:37:50 +08:00
endymx ce3fa94708 feat(iptables): range forward (#11188) 2025-12-08 16:35:01 +08:00
ssongliu 7f9f4ae740 fix: Fix PostgreSQL backup failure with $ character (#11252) 2025-12-08 15:24:08 +08:00
CityFun 8bec6f2b28 fix: Fixed network search returning empty results with no page update (#11251) 2025-12-08 07:10:37 +00:00
CityFun b397363c9d fix: Fixed process search returning empty results with no page update (#11246) 2025-12-08 05:54:36 +00:00
KOMATA acb800b505 fix: Update compose operation handling to ensure correct path usage and detail loading after operations (#11245) 2025-12-08 13:41:55 +08:00
CityFun ec37af2f18 fix: Fixed missing redirect after installing runtime environment (#11243) 2025-12-08 04:44:36 +00:00
CityFun d2b030e698 feat: Added support for auto-filling remote MySQL root password in App Store (#11240) 2025-12-08 11:39:08 +08:00
ssongliu c6f496f2a5 fix: Fix the issue of container compose log cleanup failure (#11238) 2025-12-08 11:38:05 +08:00
CityFun 722e1929f1 fix: Fixed file comparison highlighting loss issue (#11237) 2025-12-08 03:28:35 +00:00
王贺 f9a20ec443 Fix typo (#11229) 2025-12-07 11:36:20 +08:00
ssongliu f5568d4874 fix: Fix style issues caused by element upgrade (#11224) 2025-12-06 14:16:34 +00:00
ssongliu 87bbe4ee12 fix: Fix the issue of abnormal compose directory redirection (#11223) 2025-12-06 14:14:28 +00:00
ssongliu a034b03c1d fix: Task framework adds timeout exit parameter (#11206) 2025-12-05 18:42:23 +08:00
CityFun 470f4e4db7 style: Updated layout page save button styling (#11198) 2025-12-05 03:24:23 +00:00
CityFun 9428efd8c0 fix: Fixed CAPTCHA loading issue in certain scenarios (#11186) 2025-12-04 10:12:22 +00:00
CityFun bf4d91b68b fix: Fixed login CAPTCHA bypass vulnerability (#11185) 2025-12-04 10:10:23 +00:00
KOMATA 781155b029 perf: remove useless stat checking logic (#11178)
* feat: Enhance proxy initialization and error handling

* Add a timeout to the dialer for Unix socket connections
* Improve error response by including the error message in the "Bad Gateway" response

* refactor: Change sockPath variable to constant in proxy initialization

* Update sockPath to a constant SockPath for improved clarity and consistency
* Ensure the new constant is used in the dialer function for Unix socket connections
2025-12-04 18:07:47 +08:00
KOMATA 3caf0ac529 fix: Ensure proper client closure in volume and device clean operations (#11177) 2025-12-04 18:07:16 +08:00
CityFun 7d25228e55 perf: Optimized tensorrtLLM display performanceperf (#11184) 2025-12-04 17:57:19 +08:00
ssongliu 4fb099912e fix: Fix the issue of remote database connection failure (#11181)
Refs #11173
2025-12-04 09:56:23 +00:00
ssongliu b3350b54b3 fix: Add iptables execution timeout (#11176) 2025-12-04 11:22:25 +08:00
CityFun cd4e0374d0 refactor: Refactored agent core.db loading logic (#11175) 2025-12-04 11:15:40 +08:00
KOMATA 5d2084fda4 feat: improve linux distribution information get logic (#11171)
* feat: Add PrettyDistro field to dashboard information and update related services and frontend components

* fix: Trim whitespace and parentheses from detected Linux distribution name in GetDistro method

* fix: Correctly trim parentheses from detected Linux distribution name in GetDistro method

* refactor: Simplify Linux distribution detection by removing unnecessary checks and consolidating logic
2025-12-03 14:14:35 +08:00
ssongliu 8c32fa3e6c fix: Fix the issue of abnormal clam scan status judgment (#11172) 2025-12-03 14:13:22 +08:00
KOMATA fef13211a0 feat: Enhance CPU metrics by adding CPU detailed information (#11169)
* fix: Update CPU usage retrieval to include detailed percentage information

* feat: Enhance CPU metrics by adding CPU frequency and detailed usage percentages

* feat: Add CPU frequency metric to dashboard base information
2025-12-03 13:37:57 +08:00
CityFun 5ff5b0a5ad feat: revised some of the translations (#11168) 2025-12-03 03:58:14 +00:00
CityFun 8ac9d022b6 fix: fix missing version issue during mssql installation (#11167) 2025-12-03 03:56:15 +00:00
CityFun 2c02dc07e9 fix: resolve i18n file loading failure issue (#11164) 2025-12-03 10:08:20 +08:00
ssongliu ba999c516a chore: Modify the default runtime value on the overview page (#11159) 2025-12-02 10:21:42 +00:00
AirboZH 28303ffa82 feat: Add support for AWS Route 53 DNS provider (#11158) 2025-12-02 18:20:35 +08:00
ssongliu 1f9a3aa51a feat: Add refresh after resource release in container overview (#11157) 2025-12-02 09:29:41 +00:00
ssongliu 656dda234c perf: Optimize GPU monitoring display (#11156) 2025-12-02 09:21:41 +00:00
KOMATA 63b62f4074 fix: Enhance error handling in file saving process (#11152)
* fix: Enhance error handling in file saving process

* f

* f
2025-12-02 17:19:22 +08:00
KOMATA 158180a052 fix: Enhance error handling for additional HTTP status codes in RequestHttp class (#11151) 2025-12-02 17:11:55 +08:00
ssongliu dbe479e920 fix: Fix agent packaging failure issue (#11150) 2025-12-02 02:57:40 +00:00
蘭 7aefa34871 fix: Fix the issue of software link creation errors (#11149) 2025-12-02 02:53:41 +00:00
ssongliu 7d92436b92 perf: Change container overview page disk usage to asynchronous requests (#11148) 2025-12-02 02:29:39 +00:00
AirboZH 66a070c239 feat: Add support for AliESA DNS provider (#11147) 2025-12-02 10:11:46 +08:00
ssongliu bb9d9042f0 chore: Modify iptables initialization logic (#11145) 2025-12-01 22:20:17 +08:00
ssongliu a5d86d4c00 fix: Fix the issue where container details cannot be fully selected and copied (#11144) 2025-12-01 22:06:59 +08:00
KOMATA e867deb547 feat: Add copy function for raw JSON in container and network detail views (#11141)
* feat: Add copy button for raw JSON in container and network detail views

* refactor: Change CodemirrorPro component from disabled to readonly in container and network detail views

* feat: Add readonly prop to CodemirrorPro component for enhanced editing control
2025-12-01 18:13:52 +08:00
ssongliu af15659d99 fix: Fix display issues caused by default table height (#11143) 2025-12-01 10:05:37 +00:00
ssongliu f63658db9e fix: Fix the issue of extremely slow container list retrieval (#11142)
Refs #11129
2025-12-01 10:03:37 +00:00
ssongliu d4edf2f874 fix: Fix the issue of abnormal time filtering in GPU monitoring (#11139) 2025-12-01 08:43:38 +00:00
ssongliu 94f7d78cc9 fix: Fix iptables state persistence issue (#11137)
Refs #11126
2025-12-01 06:33:38 +00:00
ssongliu 5cede39e5c fix: Fix log display issue when backup account is unavailable during … (#11135)
Refs #11123
2025-12-01 02:15:37 +00:00
KOMATA 508ac2ae84 fix: Improve error handling in file download API (#11127) 2025-12-01 09:40:48 +08:00
KOMATA 997b820a42 perf: use gzipped data for improved load speed in large response (#11128)
* perf: use gzipped data for improved load speed in big files

* perf: switch to gzipped response for GetFileTree to enhance performance

* perf: switch to gzipped response for SearchApp to improve performance

* perf: update file size conditions for gzipped response to enhance performance
2025-12-01 09:40:32 +08:00
KOMATA 5de83985e3 feat: Add path protection mechanism to prevent deletion of system dir (#11130)
* feat: Add path protection mechanism to prevent deletion of critical system directories

* feat: Enhance recycle bin service with path protection for deletion requests
2025-12-01 09:40:00 +08:00
ssongliu c0220dbed0 fix: Disable caching for recommended apps on the overview page (#11117) 2025-11-28 10:19:27 +00:00
ssongliu 2e9305b9b1 fix: Fix some overview page caching issues (#11114) 2025-11-28 09:05:29 +00:00
王贺 b18bdea1c9 chore: limit gitee sync workflow to main repo (#11112) 2025-11-28 16:47:37 +08:00
ssongliu 25fb431163 fix: Fix the upgrade service file location issue (#11109) 2025-11-28 11:07:49 +08:00
CityFun ac43f00273 perf: optimize login API logic (#11104) 2025-11-28 03:05:27 +00:00
ssongliu 8ccf1fcbf3 fix: Fix the issue of abnormal runtime refresh on the overview page (#11106) 2025-11-27 10:37:24 +00:00
ssongliu b2803f574b chore: Remove some unused code (#11105) 2025-11-27 17:59:48 +08:00
ssongliu bbc14bb723 feat: Add GPU monitoring settings redirection (#11103) 2025-11-27 09:57:25 +00:00
KOMATA e89311d5d4 feat: Implement dashboard caching and refresh functionality (#11100)
* feat: Implement dashboard caching and refresh functionality

* feat: Enhance dashboard and launcher with hover refresh functionality and improve caching strategy

* feat: Introduce dashboard caching utility functions for improved data management
2025-11-27 17:54:54 +08:00
CityFun b5e56c6b65 feat: optimize IP whitelist validation logic (#11102) 2025-11-27 16:29:31 +08:00
ssongliu 3d2023858c feat: Compatible with XPU monitoring (#11099) 2025-11-27 05:57:24 +00:00
KOMATA 74b48a18be fix: Improve app list loading by switching to JSON decoder from file stream (#11095) 2025-11-27 13:47:03 +08:00
CityFun 42f61f60fb fix: Fixed issue with edit runtime port failed (#11096)
Refs https://github.com/1Panel-dev/1Panel/issues/11091
2025-11-27 03:51:24 +00:00
KOMATA e32a11910e fix: adjust WebSocket buffer sizes in terminal and host API (#11094) 2025-11-27 11:22:33 +08:00
CityFun dc9fdf897b feat: add more fields to process detail view (#11093) 2025-11-27 11:21:44 +08:00
CityFun 35c946a064 feat: support creating databases with custom charset and collation during app installation (#11092) 2025-11-27 03:21:23 +00:00
KOMATAand王贺 747a0c45ee feat: async load data of top CPU and memory processes data (#11081)
* feat: async load data of top CPU and memory processes data

* refactor: Update CPU and memory top toggle functionality and state management

---------

Co-authored-by: 王贺 <wanghe@fit2cloud.com>
2025-11-26 23:02:09 +08:00
ssongliu 8e03b24b0c feat: Compatible with XPU monitoring (#11088) 2025-11-26 22:30:15 +08:00
ssongliu 18c65c3096 fix: Fix the issue of snapshots not automatically restarting after restoration (#11087) 2025-11-26 22:29:59 +08:00
ssongliu 3b306f990a chore: Optimize iptables initial state loading speed (#11085) 2025-11-26 22:29:48 +08:00
CityFun 92fa6552b1 style: change file management terminal style (#11084) 2025-11-26 10:35:21 +00:00
ssongliu a39dc33770 fix: Handle empty character set in MariaDB compatibility (#11082) 2025-11-26 09:21:21 +00:00
KOMATA 0a42d4942c perf: replace gopsutil sampling with cached metrics and optimize system info retrieval (#11079)
* refactor: change psutil method
2025-11-26 16:33:47 +08:00
ssongliu 8086d5cecb fix: Fix uppercase exclusion in container compose (#11080) 2025-11-26 07:53:24 +00:00
ssongliu a6875614f6 style: Optimize container compose page style (#11076) 2025-11-26 04:39:20 +00:00
CityFun 6b061fa327 perf: change db connect params (#11075) 2025-11-26 11:11:11 +08:00
KOMATA b416e6b6f1 refactor: Optimize log file reading logic (#11064)
* refactor: Optimize log file reading with buffered reader pool and improve pagination logic

* refactor: Enhance file reading logic with improved buffer management and constant usage

* refactor: Adjust maximum read file size and enhance line reading functionality
2025-11-25 22:42:29 +08:00
ssongliu ac1f49f8dd fix: Fix the issue of abnormal image list retrieval in snapshots (#11074)
Refs #11030
2025-11-25 14:25:18 +00:00
ssongliu 6ddee2649d feat: Add default values for curl scheduled tasks (#11073) 2025-11-25 14:17:18 +00:00
ssongliu fd14f53cb9 style: Adjust container network details style (#11072) 2025-11-25 10:51:18 +00:00
CityFun 029e2210f5 perf: change db connect params (#11071) 2025-11-25 18:48:47 +08:00
ssongliu 69769e6203 fix: Fix the issue of database connections not being released (#11070) 2025-11-25 10:07:19 +00:00
ssongliu 2a21f194d3 feat: Optimize orchestration creation logs (#11069)
Refs  #11068
2025-11-25 09:21:18 +00:00
ssongliu e46d8c5812 feat: Support MySQL data synchronization collation (#11067) 2025-11-25 15:02:37 +08:00
CityFun 938605a169 fix: Resolve application list display error caused by docker-compose.yml modifications (#11065) 2025-11-25 06:27:18 +00:00
ssongliu c3cc26a136 fix: Fix the issue of abnormal iptables persistence loading (#11066) 2025-11-25 14:21:24 +08:00
KOMATA dda83b6307 feat: Implement Gzip compression for successful log API responses (#11063)
* feat: Implement Gzip compression for successful API responses

* refactor: Optimize Gzip compression handling in API responses using sync.Pool
2025-11-25 13:39:08 +08:00
CityFun 48e2e01a57 feat: Add MySQL character set and collation configuration during application installation (#11062) 2025-11-25 03:51:17 +00:00
ssongliu d6b00967ca fix: Fix the issue of database backup failure (#11060) 2025-11-25 03:19:18 +00:00
ssongliu 6d4c1f8e79 fix: Fix the issue of abnormal container list sorting (#11059) 2025-11-25 11:17:34 +08:00
ssongliu e88fe0ac2f fix: Fix the issue of database creation failure (#11057) 2025-11-24 14:35:16 +00:00
ssongliu 496c0b50b4 fix: Fix the issue of abnormal iptables rule persistence (#11056)
Refs #11027
2025-11-24 14:33:15 +00:00
wanghe-fit2cloud 9d73d74c63 chore: remove UPX compression 2025-11-24 18:27:54 +08:00
CityFun 7e4e12107a perf: change db connect params (#11055) 2025-11-24 10:03:15 +00:00
CityFun 5b15de91e6 fix: Fixed issue with build failed (#11054) 2025-11-24 16:29:48 +08:00
KOMATA 98b3dc9d7d refactor: update Swagger serving and processing method (#11052)
* feat: Add Swagger UI support and update Go module dependencies
2025-11-24 16:14:06 +08:00
ssongliu c05e610b13 feat: Support more MySQL character sets and collations (#11053) 2025-11-24 08:07:14 +00:00
ssongliu 63f9368e26 feat: GPU monitoring data supports persistence (#11051)
Refs #9496
2025-11-24 07:59:15 +00:00
ssongliu 3f47a6e701 feat: Support multiple URL access for cronjob (#11050)
Refs #10279
2025-11-24 07:57:18 +00:00
ssongliu d7c9b3b192 feat: Support favorite operations for containers and images (#11049)
Refs #10035 #3372
2025-11-24 07:55:14 +00:00
CityFun 063ed23acb perf: Optimize disk management formatting process (#11047)
https://github.com/1Panel-dev/1Panel/issues/11031
2025-11-24 07:51:15 +00:00
ssongliu 7c33dd9026 feat: PG remote databases version 18.x support backup and restore (#11048)
Refs #10917
2025-11-24 07:47:14 +00:00
CityFun a853d8c869 perf: change base64Captcha (#11046) 2025-11-24 14:47:58 +08:00
KOMATA 4ddeb81972 refactor: Update database connection settings to limit concurrency and idle connections (#11045) 2025-11-24 14:04:54 +08:00
CityFun 4dd9f00637 perf: Increase supervisor API timeout duration (#11043)
Refs https://github.com/1Panel-dev/1Panel/issues/11041
2025-11-24 03:59:14 +00:00
ssongliu 49f3779fd2 fix: Fix the issue of abnormal WebDAV connection (#11038) 2025-11-24 11:36:58 +08:00
KOMATAandssongliu 7389aad122 feat: Add ComposeDetail view for enhanced container management (#10981)
* feat: Add ComposeDetail view for enhanced container management

* feat: Update container log management with new table layout and improved stats display

* feat: Update log search mode to 'all' for comprehensive log retrieval

* cleanup

* feat: Enhance log container and compose management with new UI components and improved functionality

* refactor: Remove unused ComposeDetail route and enhance UI components in container management

* refactor: Improve UI styling and interaction for compose list items in container management

* refactor: Update styling and hover effects for compose list items in container management

* refactor: Enhance container compose header with additional context and improve port handling in inspect view

* refactor: Update .env label to use translation for improved localization in container compose view

* refactor: Adjust height and placeholder text for YAML editor in container compose view

* refactor: Adjust docker compose layout

---------

Co-authored-by: ssongliu <songlius11@163.com>
2025-11-21 18:20:19 +08:00
KOMATA bf095c677c feat: Migrate AWS SDK to v2 (#10983)
* refactor: Migrate S3 client to AWS SDK v2

* refactor: Migrate S3 client to AWS SDK v2 and update dependencies

* refactor: Simplify endpoint handling in S3 client initialization

* refactor: Update S3 client method receivers to pointer types for consistency
2025-11-21 16:18:38 +08:00
ssongliu fb929687d8 fix: Fix the issue of abnormal sshd service auto-start status (#11022)
Refs #11017
2025-11-21 08:13:06 +00:00
CityFun bddcf76fc3 fix: Fixed issue with uninstall extension failed (#11021) 2025-11-21 07:59:08 +00:00
KOMATA 9370bdb5ac fix: Update Cache-Control headers for static resources in router (#11020)
* fix: Update Cache-Control headers for static resources in router

* chore: Remove Cache-Control header from static resource response in router
2025-11-21 15:47:48 +08:00
KOMATA ef6d8bb17b fix: improve logic and fix bug in i18n module (#11019)
* feat: Implement language caching and improve language handling in i18n module

* refactor: Optimize i18n initialization with sync.Once for thread safety

* fix: Replace logging with fmt.Println for language file loading errors in i18n module

* fix: Correct format string in error logging for language file loading in i18n module

* fix: Update language files and improve error handling in i18n module

* fix: Update Malay language file extension from .yml to .yaml and add new translations in i18n module

* fix: Improve error messages for language file loading in i18n module

* fix: Ensure cached database language is set correctly during i18n initialization

* fix: Enhance language detection in i18n module by using Accept-Language header
2025-11-21 15:37:01 +08:00
CityFun 99c2eb04c9 feat: Add memory requirement field display for local applications (#11018)
Refs https://github.com/1Panel-dev/1Panel/issues/11015
2025-11-21 04:05:05 +00:00
CityFun d83b673c90 perf: significantly improve app store homepage loading speed (#11013) 2025-11-20 18:57:49 +08:00
ssongliu 7498f3f66b feat: Container networks support multi-NIC operations (#11011)
Refs #11007
2025-11-20 08:35:03 +00:00
ssongliu 147eda529c fix: Fix the issue with PostgreSQL configuration file abnormalities (#11010)
Refs #11004
2025-11-20 08:33:02 +00:00
CityFun c1f0da4248 refactor: remove debug cmd for agent and core (#11009) 2025-11-20 08:31:03 +00:00
KOMATA 8656dada06 fix: correct suitable log queue size to improve performance (#11008)
* fix: Update log queue size to improve performance and ensure proper handling of log entries

* fix: Adjust log queue size to maintain consistency with queue size limit
2025-11-20 16:28:03 +08:00
541 changed files with 30319 additions and 12664 deletions
+1
View File
@@ -6,6 +6,7 @@ permissions:
jobs:
generic_handler:
name: Add Labels to PR
if: github.repository == '1Panel-dev/1Panel'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
+1 -1
View File
@@ -21,7 +21,7 @@ jobs:
- name: Setup Go
uses: actions/setup-go@v5
with:
go-version: '1.23'
go-version: '1.24'
- name: Build Release
uses: goreleaser/goreleaser-action@v6
with:
+1 -1
View File
@@ -21,7 +21,7 @@ jobs:
- name: Setup Go
uses: actions/setup-go@v5
with:
go-version: '1.23'
go-version: '1.24'
- name: Build Release
uses: goreleaser/goreleaser-action@v6
with:
+1
View File
@@ -8,6 +8,7 @@ on:
jobs:
sonarcloud:
name: SonarCloud
if: github.repository == '1Panel-dev/1Panel'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
+1
View File
@@ -2,6 +2,7 @@ name: Synchronize to Gitee
on: [push]
jobs:
repo-sync:
if: github.repository == '1Panel-dev/1Panel'
runs-on: ubuntu-latest
steps:
- name: Mirror the Github organization repos to Gitee.
+13 -2
View File
@@ -44,12 +44,17 @@ frontend/src/xpack
agent/xpack
agent/router/entry_xpack.go
agent/server/init_xpack.go
agent/utils/xpack/xpack.go
agent/utils/xpack/xpack_xpack.go
core/xpack
core/router/entry_xpack.go
core/server/init_xpack.go
core/utils/xpack/xpack.go
core/utils/xpack/xpack_xpack.go
xpack
core/xpack-ee
core/router/entry_xpackee.go
core/server/init_xpackee.go
core/utils/xpack/xpack_xpackee.go
.history/
dist/
@@ -62,4 +67,10 @@ cmd/server/fileList.txt
1Panel.code-workspace
core/.golangci.yml
agent/.golangci.yml
agent/.golangci.yml
.opencode
openspec
CLAUDE.md
AGENTS.md
opencode.json
-6
View File
@@ -33,9 +33,6 @@ builds:
- ppc64le
- s390x
- riscv64
hooks:
post:
- ./ci/script.sh compress_binary {{ .Path }} {{ .Arch }}
- id: core
dir: core
@@ -59,9 +56,6 @@ builds:
- ppc64le
- s390x
- riscv64
hooks:
post:
- ./ci/script.sh compress_binary {{ .Path }} {{ .Arch }}
archives:
- formats: [ 'tar.gz' ]
+1 -1
View File
@@ -46,4 +46,4 @@ build_agent_on_darwin:
build_all: build_frontend build_core_on_linux build_agent_on_linux
build_on_local: clean_assets build_frontend build_core_on_darwin build_agent_on_darwin upx_bin
build_on_local: clean_assets build_frontend build_core_on_darwin build_agent_on_darwin
+23 -13
View File
@@ -1,5 +1,4 @@
<p align="center"><a href="https://1panel.pro"><img src="https://resource.1panel.pro/img/1panel-logo.png" alt="1Panel" width="300" /></a></p>
<p align="center"><b>Top-Rated Web-based Linux Server Management Tool</b><br>Best VPS control panel<br>新一代的 Linux 服务器运维管理面板</p>
<p align="center">
<a href="https://trendshift.io/repositories/2462" target="_blank"><img src="https://trendshift.io/api/badge/repositories/2462" alt="1Panel-dev%2F1Panel | Trendshift" style="width: 240px; height: auto;" /></a>
</p>
@@ -30,27 +29,38 @@
</p>
------------------------------
## What is 1Panel?
1Panel is an open-source, modern web-based control panel for Linux server management.
1Panel is a modern, open-source Linux server management panel that streamlines administration through an intuitive, clean web interface.
- **Efficient Management**: Through a user-friendly web graphical interface, 1Panel enables users to effortlessly manage their Linux servers. Key features include host monitoring, file management, database administration, container management, LLMs management.
- **Rapid Website Deployment**: With deep integration of the popular open-source website building software WordPress, 1Panel streamlines the process of domain binding and SSL certificate configuration, all achievable with just one click.
- **Application Store**: 1Panel curates a wide range of high-quality open-source tools and applications, facilitating easy installation and updates for its users.
- **Security and Reliability**: By leveraging containerization and secure application deployment practices, 1Panel minimizes vulnerability exposure. It further enhances security through integrated firewall management and log auditing capabilities.
- **One-Click Backup & Restore**: Data protection is made simple with 1Panel's one-click backup and restore functionality, supporting various cloud storage solutions to ensure data integrity and availability.
## Key Features
🔹 **AI Integration**
Seamlessly deploy OpenClaw agents, host local LLMs via Ollama, and monitor GPU utilization—all within a unified workspace engineered for efficient AI development and inference.
🔹 **One-Click Website Deployment**
Launch production-ready websites instantly with automatic domain binding, SSL certificate provisioning, and configuration—zero manual setup required.
🔹 **Curated App Marketplace**
Discover, install, and update trusted open-source applications (WordPress, MySQL, Redis, and more) with a single click.
🔹 **Centralized Server Management**
Monitor system resources and manage files, databases, containers, and services effortlessly through a unified, intuitive dashboard.
🔹 **Enterprise-Grade Security**
Strengthen defenses with container isolation, firewall controls, audit logging, and secure deployment practices—ensuring compliance and peace of mind.
🔹 **Intelligent Backup & Restore**
Schedule automated backups or perform instant restores to local storage or leading cloud platforms (AWS S3, Cloudflare R2, and more), with flexible retention and destination options.
## Quick Start
Execute the script below and follow the prompts to install 1Panel:
```bash
curl -sSL https://resource.1panel.pro/quick_start.sh -o quick_start.sh && bash quick_start.sh
bash -c "$(curl -sSL https://resource.fit2cloud.com/1panel/package/v2/quick_start.sh)"
```
Please refer to our [documentation](https://docs.1panel.pro/quick_start/) for more details.
中国用户请使用这个 [安装脚本](https://1panel.cn/docs/installation/online_installation/),其应用数量比国际版本更丰富。
## Screenshot
![UI Display](https://resource.1panel.pro/img/1panel.png)
@@ -73,4 +83,4 @@ Licensed under The GNU General Public License version 3 (GPLv3) (the "License")
<https://www.gnu.org/licenses/gpl-3.0.html>
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
+292
View File
@@ -0,0 +1,292 @@
package v2
import (
"github.com/1Panel-dev/1Panel/agent/app/api/v2/helper"
"github.com/1Panel-dev/1Panel/agent/app/dto"
"github.com/gin-gonic/gin"
)
// @Tags AI
// @Summary Create Agent
// @Accept json
// @Param request body dto.AgentCreateReq true "request"
// @Success 200 {object} dto.AgentItem
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /ai/agents [post]
func (b *BaseApi) CreateAgent(c *gin.Context) {
var req dto.AgentCreateReq
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}
res, err := agentService.Create(req)
if err != nil {
helper.BadRequest(c, err)
return
}
helper.SuccessWithData(c, res)
}
// @Tags AI
// @Summary Page Agents
// @Accept json
// @Param request body dto.SearchWithPage true "request"
// @Success 200 {object} dto.PageResult
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /ai/agents/search [post]
func (b *BaseApi) PageAgents(c *gin.Context) {
var req dto.SearchWithPage
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}
total, list, err := agentService.Page(req)
if err != nil {
helper.BadRequest(c, err)
return
}
helper.SuccessWithData(c, dto.PageResult{
Items: list,
Total: total,
})
}
// @Tags AI
// @Summary Delete Agent
// @Accept json
// @Param request body dto.AgentDeleteReq true "request"
// @Success 200
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /ai/agents/delete [post]
func (b *BaseApi) DeleteAgent(c *gin.Context) {
var req dto.AgentDeleteReq
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}
if err := agentService.Delete(req); err != nil {
helper.BadRequest(c, err)
return
}
helper.Success(c)
}
// @Tags AI
// @Summary Reset Agent token
// @Accept json
// @Param request body dto.AgentTokenResetReq true "request"
// @Success 200
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /ai/agents/token/reset [post]
func (b *BaseApi) ResetAgentToken(c *gin.Context) {
var req dto.AgentTokenResetReq
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}
if err := agentService.ResetToken(req); err != nil {
helper.BadRequest(c, err)
return
}
helper.Success(c)
}
// @Tags AI
// @Summary Update Agent model config
// @Accept json
// @Param request body dto.AgentModelConfigUpdateReq true "request"
// @Success 200
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /ai/agents/model/update [post]
func (b *BaseApi) UpdateAgentModelConfig(c *gin.Context) {
var req dto.AgentModelConfigUpdateReq
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}
if err := agentService.UpdateModelConfig(req); err != nil {
helper.BadRequest(c, err)
return
}
helper.Success(c)
}
// @Tags AI
// @Summary Get Providers
// @Success 200 {object} []dto.ProviderInfo
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /ai/agents/providers [get]
func (b *BaseApi) GetAgentProviders(c *gin.Context) {
list, err := agentService.GetProviders()
if err != nil {
helper.BadRequest(c, err)
return
}
helper.SuccessWithData(c, list)
}
// @Tags AI
// @Summary Create Agent account
// @Accept json
// @Param request body dto.AgentAccountCreateReq true "request"
// @Success 200
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /ai/agents/accounts [post]
func (b *BaseApi) CreateAgentAccount(c *gin.Context) {
var req dto.AgentAccountCreateReq
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}
if err := agentService.CreateAccount(req); err != nil {
helper.BadRequest(c, err)
return
}
helper.Success(c)
}
// @Tags AI
// @Summary Update Agent account
// @Accept json
// @Param request body dto.AgentAccountUpdateReq true "request"
// @Success 200
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /ai/agents/accounts/update [post]
func (b *BaseApi) UpdateAgentAccount(c *gin.Context) {
var req dto.AgentAccountUpdateReq
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}
if err := agentService.UpdateAccount(req); err != nil {
helper.BadRequest(c, err)
return
}
helper.Success(c)
}
// @Tags AI
// @Summary Page Agent accounts
// @Accept json
// @Param request body dto.AgentAccountSearch true "request"
// @Success 200 {object} dto.PageResult
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /ai/agents/accounts/search [post]
func (b *BaseApi) PageAgentAccounts(c *gin.Context) {
var req dto.AgentAccountSearch
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}
total, list, err := agentService.PageAccounts(req)
if err != nil {
helper.BadRequest(c, err)
return
}
helper.SuccessWithData(c, dto.PageResult{
Items: list,
Total: total,
})
}
// @Tags AI
// @Summary Verify Agent account
// @Accept json
// @Param request body dto.AgentAccountVerifyReq true "request"
// @Success 200
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /ai/agents/accounts/verify [post]
func (b *BaseApi) VerifyAgentAccount(c *gin.Context) {
var req dto.AgentAccountVerifyReq
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}
if err := agentService.VerifyAccount(req); err != nil {
helper.BadRequest(c, err)
return
}
helper.Success(c)
}
// @Tags AI
// @Summary Delete Agent account
// @Accept json
// @Param request body dto.AgentAccountDeleteReq true "request"
// @Success 200
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /ai/agents/accounts/delete [post]
func (b *BaseApi) DeleteAgentAccount(c *gin.Context) {
var req dto.AgentAccountDeleteReq
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}
if err := agentService.DeleteAccount(req); err != nil {
helper.BadRequest(c, err)
return
}
helper.Success(c)
}
// @Tags AI
// @Summary Get Agent Feishu channel config
// @Accept json
// @Param request body dto.AgentFeishuConfigReq true "request"
// @Success 200 {object} dto.AgentFeishuConfig
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /ai/agents/channel/feishu/get [post]
func (b *BaseApi) GetAgentFeishuConfig(c *gin.Context) {
var req dto.AgentFeishuConfigReq
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}
data, err := agentService.GetFeishuConfig(req)
if err != nil {
helper.BadRequest(c, err)
return
}
helper.SuccessWithData(c, data)
}
// @Tags AI
// @Summary Update Agent Feishu channel config
// @Accept json
// @Param request body dto.AgentFeishuConfigUpdateReq true "request"
// @Success 200
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /ai/agents/channel/feishu/update [post]
func (b *BaseApi) UpdateAgentFeishuConfig(c *gin.Context) {
var req dto.AgentFeishuConfigUpdateReq
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}
if err := agentService.UpdateFeishuConfig(req); err != nil {
helper.BadRequest(c, err)
return
}
helper.Success(c)
}
// @Tags AI
// @Summary Approve Agent Feishu pairing code
// @Accept json
// @Param request body dto.AgentFeishuPairingApproveReq true "request"
// @Success 200
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /ai/agents/channel/feishu/approve [post]
func (b *BaseApi) ApproveAgentFeishuPairing(c *gin.Context) {
var req dto.AgentFeishuPairingApproveReq
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}
if err := agentService.ApproveFeishuPairing(req); err != nil {
helper.BadRequest(c, err)
return
}
helper.Success(c)
}
+63 -2
View File
@@ -1,10 +1,13 @@
package v2
import (
"net/http"
"github.com/1Panel-dev/1Panel/agent/app/api/v2/helper"
"github.com/1Panel-dev/1Panel/agent/app/dto"
"github.com/1Panel-dev/1Panel/agent/app/dto/request"
"github.com/1Panel-dev/1Panel/agent/i18n"
"github.com/1Panel-dev/1Panel/agent/utils/appicon"
"github.com/gin-gonic/gin"
)
@@ -26,7 +29,7 @@ func (b *BaseApi) SearchApp(c *gin.Context) {
helper.InternalServer(c, err)
return
}
helper.SuccessWithData(c, list)
helper.SuccessWithDataGzipped(c, list)
}
// @Tags App
@@ -161,7 +164,7 @@ func (b *BaseApi) InstallApp(c *gin.Context) {
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}
install, err := appService.Install(req)
install, err := appService.Install(req, true)
if err != nil {
helper.InternalServer(c, err)
return
@@ -192,3 +195,61 @@ func (b *BaseApi) GetAppListUpdate(c *gin.Context) {
}
helper.SuccessWithData(c, res)
}
// @Tags App
// @Summary Get app icon by app_id
// @Accept json
// @Param appId path integer true "app id"
// @Success 200 {file} file "app icon"
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /apps/icon/:key [get]
func (b *BaseApi) GetAppIcon(c *gin.Context) {
appKey, err := helper.GetStrParamByKey(c, "key")
if err != nil {
helper.BadRequest(c, err)
return
}
iconBytes, filename, etag, err := appService.GetAppIcon(appKey)
if err != nil {
helper.InternalServer(c, err)
return
}
if len(iconBytes) == 0 {
c.Status(http.StatusNoContent)
return
}
c.Header("Cache-Control", "public, max-age=2592000")
if etag != "" && filename != "" {
c.Header("ETag", etag)
if c.GetHeader("If-None-Match") == etag {
c.Status(http.StatusNotModified)
return
}
}
c.Data(http.StatusOK, appicon.ContentTypePNG, iconBytes)
}
// @Tags App
// @Summary Search app detail by appkey and version
// @Accept json
// @Param appId path integer true "app key"
// @Param version path string true "app version"
// @Success 200 {object} response.AppDetailSimpleDTO
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /apps/detail/node/:appKey/:version [get]
func (b *BaseApi) GetAppDetailForNode(c *gin.Context) {
appKey := c.Param("appKey")
version := c.Param("version")
appDetailDTO, err := appService.GetAppDetailByKey(appKey, version)
if err != nil {
helper.InternalServer(c, err)
return
}
helper.SuccessWithData(c, appDetailDTO)
}
+1 -1
View File
@@ -294,7 +294,7 @@ func (b *BaseApi) GetParams(c *gin.Context) {
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /apps/installed/params/update [post]
// @x-panel-log {"bodyKeys":["installId"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"应用参数修改 [installId]","formatEN":"Application param update [installId]"}
// @x-panel-log {"bodyKeys":["installId"],"paramKeys":[],"BeforeFunctions":[{"input_column":"id","input_value":"installId","isList":false,"db":"app_installs","output_column":"name","output_value":"name"}],"formatZH":"应用参数修改 [name]","formatEN":"Application param update [name]"}
func (b *BaseApi) UpdateInstalled(c *gin.Context) {
var req request.AppInstalledUpdate
if err := helper.CheckBindAndValidate(&req, c); err != nil {
+17
View File
@@ -25,6 +25,23 @@ func (b *BaseApi) CheckBackupUsed(c *gin.Context) {
helper.Success(c)
}
// @Tags Backup Account
// @Summary Check backup account
// @Accept json
// @Param request body dto.BackupOperate true "request"
// @Success 200
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /backups/check [post]
func (b *BaseApi) CheckBackup(c *gin.Context) {
var req dto.BackupOperate
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}
helper.SuccessWithData(c, backupService.CheckConn(req))
}
// @Tags Backup Account
// @Summary Create backup account
// @Accept json
+68 -2
View File
@@ -263,6 +263,27 @@ func (b *BaseApi) ContainerListStats(c *gin.Context) {
helper.SuccessWithData(c, data)
}
// @Summary Load container stats size
// @Accept json
// @Param request body dto.OperationWithName true "request"
// @Success 200 {object} dto.ContainerItemStats
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /containers/item/stats [post]
func (b *BaseApi) ContainerItemStats(c *gin.Context) {
var req dto.OperationWithName
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}
data, err := containerService.ContainerItemStats(req)
if err != nil {
helper.InternalServer(c, err)
return
}
helper.SuccessWithData(c, data)
}
// @Tags Container
// @Summary Create container
// @Accept json
@@ -372,6 +393,28 @@ func (b *BaseApi) CleanContainerLog(c *gin.Context) {
helper.Success(c)
}
// @Tags Container
// @Summary Clean compose log
// @Accept json
// @Param request body dto.ComposeLogClean true "request"
// @Success 200
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /containers/compose/clean/log [post]
// @x-panel-log {"bodyKeys":["name"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"清理容器编排 [name] 日志","formatEN":"clean compose [name] logs"}
func (b *BaseApi) CleanComposeLog(c *gin.Context) {
var req dto.ComposeLogClean
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}
if err := containerService.ComposeLogClean(req); err != nil {
helper.InternalServer(c, err)
return
}
helper.Success(c)
}
// @Tags Container
// @Summary Rename Container
// @Accept json
@@ -481,13 +524,12 @@ func (b *BaseApi) Inspect(c *gin.Context) {
helper.SuccessWithData(c, result)
}
// @Router /containers/download/log [post]
func (b *BaseApi) DownloadContainerLogs(c *gin.Context) {
var req dto.ContainerLog
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}
err := containerService.DownloadContainerLogs(req.ContainerType, req.Container, req.Since, strconv.Itoa(int(req.Tail)), c)
err := containerService.DownloadContainerLogs(req.ContainerType, req.Container, req.Since, strconv.Itoa(int(req.Tail)), req.Timestamp, c)
if err != nil {
helper.InternalServer(c, err)
}
@@ -689,12 +731,34 @@ func (b *BaseApi) ComposeUpdate(c *gin.Context) {
helper.Success(c)
}
// @Tags Container Compose
// @Summary Load compose environment variables
// @Accept json
// @Param request body dto.FilePath true "request"
// @Success 200 {array} string
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /containers/compose/env [post]
func (b *BaseApi) LoadComposeEnv(c *gin.Context) {
var req dto.FilePath
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}
data, err := containerService.LoadComposeEnv(req.Path)
if err != nil {
helper.InternalServer(c, err)
}
helper.SuccessWithData(c, data)
}
// @Tags Container
// @Summary Container logs
// @Param container query string false "容器名称"
// @Param since query string false "时间筛选"
// @Param follow query string false "是否追踪"
// @Param tail query string false "显示行号"
// @Param timestamp query string false "是否显示时间"
// @Success 200
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /containers/search/log [get]
@@ -707,6 +771,7 @@ func (b *BaseApi) ContainerStreamLogs(c *gin.Context) {
since := c.Query("since")
follow := c.Query("follow") == "true"
tail := c.Query("tail")
timestamp := c.Query("timestamp") == "true"
container := c.Query("container")
compose := c.Query("compose")
@@ -716,6 +781,7 @@ func (b *BaseApi) ContainerStreamLogs(c *gin.Context) {
Since: since,
Follow: follow,
Tail: tail,
Timestamp: timestamp,
Type: "container",
}
if compose != "" {
+26 -4
View File
@@ -27,7 +27,7 @@ func (b *BaseApi) LoadDashboardOsInfo(c *gin.Context) {
// @Tags Dashboard
// @Summary Load app launcher
// @Accept json
// @Success 200 {Array} dto.AppLauncher
// @Success 200 {array} dto.AppLauncher
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /dashboard/app/launcher [get]
@@ -37,14 +37,14 @@ func (b *BaseApi) LoadAppLauncher(c *gin.Context) {
helper.InternalServer(c, err)
return
}
helper.SuccessWithData(c, data)
helper.SuccessWithDataGzipped(c, data)
}
// @Tags Dashboard
// @Summary Load app launcher options
// @Accept json
// @Param request body dto.SearchByFilter true "request"
// @Success 200 {Array} dto.LauncherOption
// @Success 200 {array} dto.LauncherOption
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /dashboard/app/launcher/option [post]
@@ -85,7 +85,7 @@ func (b *BaseApi) UpdateAppLauncher(c *gin.Context) {
// @Tags Dashboard
// @Summary Load quick jump options
// @Success 200 {Array} dto.QuickJump
// @Success 200 {array} dto.QuickJump
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /dashboard/quick/option [get]
@@ -179,6 +179,28 @@ func (b *BaseApi) LoadDashboardCurrentInfo(c *gin.Context) {
helper.SuccessWithData(c, data)
}
// @Tags Dashboard
// @Summary Load top cpu processes
// @Success 200 {array} dto.Process
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /dashboard/current/top/cpu [get]
func (b *BaseApi) LoadDashboardTopCPU(c *gin.Context) {
data := dashboardService.LoadTopCPU()
helper.SuccessWithData(c, data)
}
// @Tags Dashboard
// @Summary Load top memory processes
// @Success 200 {array} dto.Process
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /dashboard/current/top/mem [get]
func (b *BaseApi) LoadDashboardTopMem(c *gin.Context) {
data := dashboardService.LoadTopMem()
helper.SuccessWithData(c, data)
}
// @Tags Dashboard
// @Summary System restart
// @Accept json
+9 -10
View File
@@ -195,27 +195,26 @@ func (b *BaseApi) SearchMysql(c *gin.Context) {
}
// @Tags Database Mysql
// @Summary List mysql database names
// @Summary List mysql database format collation options
// @Accept json
// @Param request body dto.PageInfo true "request"
// @Success 200 {array} dto.MysqlOption
// @Param request body dto.OperationWithName true "request"
// @Success 200 {array} dto.MysqlFormatCollationOption
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /databases/options [get]
func (b *BaseApi) ListDBName(c *gin.Context) {
list, err := mysqlService.ListDBOption()
if err != nil {
helper.InternalServer(c, err)
// @Router /databases/format/options [post]
func (b *BaseApi) ListDBFormatCollationOptions(c *gin.Context) {
var req dto.OperationWithName
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}
helper.SuccessWithData(c, list)
helper.SuccessWithData(c, mysqlService.LoadFormatOption(req))
}
// @Tags Database Mysql
// @Summary Load mysql database from remote
// @Accept json
// @Param request body dto.MysqlLoadDB true "request"
// @Success 200
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /databases/load [post]
+1
View File
@@ -167,6 +167,7 @@ func (b *BaseApi) SearchPostgresql(c *gin.Context) {
// @Summary Load postgresql database from remote
// @Accept json
// @Param request body dto.PostgresqlLoadDB true "request"
// @Success 200
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /databases/pg/:database/load [post]
+1 -1
View File
@@ -27,7 +27,7 @@ func (b *BaseApi) LoadDeviceBaseInfo(c *gin.Context) {
// @Tags Device
// @Summary list time zone options
// @Accept json
// @Success 200 {Array} string
// @Success 200 {array} string
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /toolbox/device/zone/options [get]
+1
View File
@@ -20,6 +20,7 @@ var (
aiToolService = service.NewIAIToolService()
mcpServerService = service.NewIMcpServerService()
tensorrtLLMService = service.NewITensorRTLLMService()
agentService = service.NewIAgentService()
containerService = service.NewIContainerService()
composeTemplateService = service.NewIComposeTemplateService()
+4 -2
View File
@@ -28,7 +28,7 @@ func (b *BaseApi) LoadFail2BanBaseInfo(c *gin.Context) {
// @Summary Page fail2ban ip list
// @Accept json
// @Param request body dto.Fail2BanSearch true "request"
// @Success 200 {Array} string
// @Success 200 {array} string
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /toolbox/fail2ban/search [post]
@@ -51,6 +51,7 @@ func (b *BaseApi) SearchFail2Ban(c *gin.Context) {
// @Summary Operate fail2ban
// @Accept json
// @Param request body dto.Operate true "request"
// @Success 200
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /toolbox/fail2ban/operate [post]
@@ -72,7 +73,8 @@ func (b *BaseApi) OperateFail2Ban(c *gin.Context) {
// @Tags Fail2ban
// @Summary Operate sshd of fail2ban
// @Accept json
// @Param request body dto.Operate true "request"
// @Param request body dto.Fail2BanSet true "request"
// @Success 200
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /toolbox/fail2ban/operate/sshd [post]
+76 -3
View File
@@ -90,7 +90,7 @@ func (b *BaseApi) GetFileTree(c *gin.Context) {
helper.InternalServer(c, err)
return
}
helper.SuccessWithData(c, tree)
helper.SuccessWithDataGzipped(c, tree)
}
// @Tags File
@@ -265,6 +265,33 @@ func (b *BaseApi) GetContent(c *gin.Context) {
helper.InternalServer(c, err)
return
}
if info.Size > 2*1024 && info.Size < 5*1024*1024 {
helper.SuccessWithDataGzipped(c, info)
} else {
helper.SuccessWithData(c, info)
}
}
// @Tags File
// @Summary Preview file content
// @Accept json
// @Param request body request.FileContentReq true "request"
// @Success 200 {object} response.FileInfo
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /files/preview [post]
// @x-panel-log {"bodyKeys":["path"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"预览文件内容 [path]","formatEN":"Preview file content [path]"}
func (b *BaseApi) PreviewContent(c *gin.Context) {
var req request.FileContentReq
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}
info, err := fileService.GetPreviewContent(req)
if err != nil {
helper.InternalServer(c, err)
return
}
helper.SuccessWithData(c, info)
}
@@ -521,9 +548,14 @@ func (b *BaseApi) Download(c *gin.Context) {
file, err := os.Open(filePath)
if err != nil {
helper.InternalServer(c, err)
return
}
defer file.Close()
info, _ := file.Stat()
info, err := file.Stat()
if err != nil {
helper.InternalServer(c, err)
return
}
c.Header("Content-Length", strconv.FormatInt(info.Size(), 10))
c.Header("Content-Disposition", "attachment; filename*=utf-8''"+url.PathEscape(info.Name()))
http.ServeContent(c.Writer, c.Request, info.Name(), info.ModTime(), file)
@@ -841,7 +873,11 @@ func (b *BaseApi) ReadFileByLine(c *gin.Context) {
helper.InternalServer(c, err)
return
}
helper.SuccessWithData(c, res)
if res.TotalLines > 100 {
helper.SuccessWithDataGzipped(c, res)
} else {
helper.SuccessWithData(c, res)
}
}
// @Tags File
@@ -942,3 +978,40 @@ func (b *BaseApi) ConvertLog(c *gin.Context) {
Total: total,
})
}
// @Tags File
// @Summary Batch get file remarks
// @Accept json
// @Param request body request.FileRemarkBatch true "request"
// @Success 200 {object} response.FileRemarksRes
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /files/remarks [post]
func (b *BaseApi) BatchGetFileRemarks(c *gin.Context) {
var req request.FileRemarkBatch
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}
remarks := fileService.BatchGetRemarks(req)
helper.SuccessWithData(c, response.FileRemarksRes{Remarks: remarks})
}
// @Tags File
// @Summary Set file remark
// @Accept json
// @Param request body request.FileRemarkUpdate true "request"
// @Success 200
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /files/remark [post]
func (b *BaseApi) SetFileRemark(c *gin.Context) {
var req request.FileRemarkUpdate
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}
if err := fileService.SetRemark(req); err != nil {
helper.InternalServer(c, err)
return
}
helper.Success(c)
}
+1 -1
View File
@@ -269,7 +269,7 @@ func (b *BaseApi) OperateFilterRule(c *gin.Context) {
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}
if err := iptablesService.OperateRule(req); err != nil {
if err := iptablesService.OperateRule(req, true); err != nil {
helper.InternalServer(c, err)
return
}
+1
View File
@@ -54,6 +54,7 @@ func (b *BaseApi) LoadFtpLogInfo(c *gin.Context) {
// @Summary Operate FTP
// @Accept json
// @Param request body dto.Operate true "request"
// @Success 200
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /toolbox/ftp/operate [post]
+40
View File
@@ -1,10 +1,14 @@
package helper
import (
"compress/gzip"
"context"
"encoding/json"
"fmt"
"net/http"
"strconv"
"strings"
"sync"
"github.com/1Panel-dev/1Panel/agent/global"
"gorm.io/gorm"
@@ -46,6 +50,42 @@ func SuccessWithData(ctx *gin.Context, data interface{}) {
ctx.Abort()
}
var gzipWriterPool = sync.Pool{
New: func() interface{} {
return gzip.NewWriter(nil)
},
}
func SuccessWithDataGzipped(ctx *gin.Context, data interface{}) {
if !strings.Contains(ctx.GetHeader("Accept-Encoding"), "gzip") {
SuccessWithData(ctx, data)
return
}
if data == nil {
data = gin.H{}
}
res := dto.Response{
Code: http.StatusOK,
Data: data,
}
jsonBytes, err := json.Marshal(res)
if err != nil {
ErrorWithDetail(ctx, http.StatusInternalServerError, "ErrInternalServer", err)
return
}
ctx.Header("Content-Encoding", "gzip")
ctx.Header("Content-Type", "application/json; charset=utf-8")
ctx.Status(http.StatusOK)
gz := gzipWriterPool.Get().(*gzip.Writer)
gz.Reset(ctx.Writer)
_, _ = gz.Write(jsonBytes)
_ = gz.Close()
gzipWriterPool.Put(gz)
ctx.Abort()
}
func Success(ctx *gin.Context) {
res := dto.Response{
Code: http.StatusOK,
+25
View File
@@ -28,6 +28,27 @@ func (b *BaseApi) LoadMonitor(c *gin.Context) {
helper.InternalServer(c, err)
return
}
helper.SuccessWithDataGzipped(c, data)
}
// @Tags Monitor
// @Summary Load monitor data
// @Param request body dto.MonitorGPUSearch true "request"
// @Success 200 {object} dto.MonitorGPUData
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /hosts/monitor/gpu/search [post]
func (b *BaseApi) LoadGPUMonitor(c *gin.Context) {
var req dto.MonitorGPUSearch
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}
data, err := monitorService.LoadGPUMonitorData(req)
if err != nil {
helper.InternalServer(c, err)
return
}
helper.SuccessWithData(c, data)
}
@@ -105,3 +126,7 @@ func (b *BaseApi) GetIOOptions(c *gin.Context) {
sort.Strings(options)
helper.SuccessWithData(c, options)
}
func (b *BaseApi) GetCPUOptions(c *gin.Context) {
helper.SuccessWithData(c, monitorService.LoadGPUOptions())
}
+15
View File
@@ -57,3 +57,18 @@ func (b *BaseApi) GetProcessInfoByPID(c *gin.Context) {
}
helper.SuccessWithData(c, data)
}
// @Tags Process
// @Summary Get Listening Process
// @Success 200
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /process/listening [post]
func (b *BaseApi) GetListeningProcess(c *gin.Context) {
procs, err := processService.GetListeningProcess(c)
if err != nil {
helper.BadRequest(c, err)
return
}
helper.SuccessWithData(c, procs)
}
+48 -2
View File
@@ -80,13 +80,38 @@ func (b *BaseApi) LoadLocalConn(c *gin.Context) {
}
func (b *BaseApi) CheckLocalConn(c *gin.Context) {
_, err := loadLocalConn()
client, err := loadLocalConn()
if err == nil && client != nil {
client.Close()
}
helper.SuccessWithData(c, err == nil)
}
// @Tags System Setting
// @Summary Update local is conn
// @Accept json
// @Param request body dto.SSHDefaultConn true "request"
// @Success 200
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /settings/ssh/conn/default [post]
// @x-panel-log {"bodyKeys":["defaultConn"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"本地终端默认连接 [defaultConn]","formatEN":"update system default conn [defaultConn]"}
func (b *BaseApi) SetDefaultIsConn(c *gin.Context) {
var req dto.SSHDefaultConn
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}
if err := settingService.SetDefaultIsConn(req); err != nil {
helper.InternalServer(c, err)
return
}
helper.Success(c)
}
// @Tags System Setting
// @Summary Check local conn info
// @Success 200 {bool} isOk
// @Success 200 {boolean} isOk
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /settings/ssh/check/info [post]
@@ -155,3 +180,24 @@ func (b *BaseApi) GetSettingByKey(c *gin.Context) {
value := settingService.GetSettingByKey(key)
helper.SuccessWithData(c, value)
}
// @Tags System Setting
// @Summary Save common description
// @Accept json
// @Param request body dto.CommonDescription true "request"
// @Success 200
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /settings/description/save [post]
func (b *BaseApi) SaveDescription(c *gin.Context) {
var req dto.CommonDescription
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}
if err := settingService.SaveDescription(req); err != nil {
helper.InternalServer(c, err)
return
}
helper.Success(c)
}
+1 -1
View File
@@ -9,7 +9,7 @@ import (
// @Tags Host
// @Summary Check if a system component exists
// @Accept json
// @Param name query string true "Component name to check (e.g., rsync, docker)"
// @Param name path string true "Component name to check (e.g., rsync, docker)"
// @Success 200 {object} response.ComponentInfo
// @Security ApiKeyAuth
// @Security Timestamp
+2 -2
View File
@@ -228,8 +228,8 @@ func wshandleError(ws *websocket.Conn, err error) bool {
}
var upGrader = websocket.Upgrader{
ReadBufferSize: 1024,
WriteBufferSize: 1024 * 1024 * 10,
ReadBufferSize: 4096,
WriteBufferSize: 16384,
CheckOrigin: func(r *http.Request) bool {
return true
},
+41 -1
View File
@@ -73,7 +73,7 @@ func (b *BaseApi) GetWebsiteOptions(c *gin.Context) {
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /websites [post]
// @x-panel-log {"bodyKeys":["primaryDomain"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"创建网站 [primaryDomain]","formatEN":"Create website [primaryDomain]"}
// @x-panel-log {"bodyKeys":["alias"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"创建网站 [alias]","formatEN":"Create website [alias]"}
func (b *BaseApi) CreateWebsite(c *gin.Context) {
var req request.WebsiteCreate
if err := helper.CheckBindAndValidate(&req, c); err != nil {
@@ -1223,3 +1223,43 @@ func (b *BaseApi) UpdateCORSConfig(c *gin.Context) {
}
helper.Success(c)
}
// @Tags Website
// @Summary Update Stream Config
// @Accept json
// @Param request body request.StreamUpdate true "request"
// @Success 200
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /websites/stream/update [post]
func (b *BaseApi) UpdateStreamConfig(c *gin.Context) {
var req request.StreamUpdate
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}
if err := websiteService.UpdateStream(req); err != nil {
helper.InternalServer(c, err)
return
}
helper.Success(c)
}
// @Tags Website
// @Summary Batch set HTTPS for websites
// @Accept json
// @Param request body request.BatchWebsiteHttps true "request"
// @Success 200
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /websites/batch/https [post]
func (b *BaseApi) BatchSetHttps(c *gin.Context) {
var req request.BatchWebsiteHttps
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}
if err := websiteService.BatchSetHttps(c, req); err != nil {
helper.InternalServer(c, err)
return
}
helper.Success(c)
}
+31 -19
View File
@@ -1,14 +1,14 @@
package v2
import (
"github.com/1Panel-dev/1Panel/agent/app/model"
"io"
"mime/multipart"
"net/http"
"net/url"
"reflect"
"strconv"
"github.com/1Panel-dev/1Panel/agent/app/model"
"github.com/1Panel-dev/1Panel/agent/app/api/v2/helper"
"github.com/1Panel-dev/1Panel/agent/app/dto"
"github.com/1Panel-dev/1Panel/agent/app/dto/request"
@@ -25,27 +25,39 @@ import (
// @Router /websites/ssl/search [post]
func (b *BaseApi) PageWebsiteSSL(c *gin.Context) {
var req request.WebsiteSSLSearch
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}
total, accounts, err := websiteSSLService.Page(req)
if err != nil {
helper.InternalServer(c, err)
return
}
helper.SuccessWithDataGzipped(c, dto.PageResult{
Total: total,
Items: accounts,
})
}
// @Tags Website SSL
// @Summary List website ssl
// @Accept json
// @Param request body request.WebsiteSSLListReq true "request"
// @Success 200 {array} response.WebsiteSSLDTO
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /websites/ssl/list [post]
func (b *BaseApi) ListWebsiteSSL(c *gin.Context) {
var req request.WebsiteSSLListReq
if err := helper.CheckBind(&req, c); err != nil {
return
}
if !reflect.DeepEqual(req.PageInfo, dto.PageInfo{}) {
total, accounts, err := websiteSSLService.Page(req)
if err != nil {
helper.InternalServer(c, err)
return
}
helper.SuccessWithData(c, dto.PageResult{
Total: total,
Items: accounts,
})
} else {
list, err := websiteSSLService.Search(req)
if err != nil {
helper.InternalServer(c, err)
return
}
helper.SuccessWithData(c, list)
list, err := websiteSSLService.Search(req)
if err != nil {
helper.InternalServer(c, err)
return
}
helper.SuccessWithDataGzipped(c, list)
}
// @Tags Website SSL
+151
View File
@@ -0,0 +1,151 @@
package dto
import "time"
type AgentCreateReq struct {
Name string `json:"name" validate:"required"`
AppVersion string `json:"appVersion" validate:"required"`
WebUIPort int `json:"webUIPort" validate:"required"`
BridgePort int `json:"bridgePort" validate:"required"`
Provider string `json:"provider" validate:"required"`
Model string `json:"model" validate:"required"`
AccountID uint `json:"accountId"`
APIKey string `json:"apiKey"`
BaseURL string `json:"baseURL"`
Token string `json:"token"`
TaskID string `json:"taskID"`
Advanced bool `json:"advanced"`
ContainerName string `json:"containerName"`
AllowPort bool `json:"allowPort"`
SpecifyIP string `json:"specifyIP"`
RestartPolicy string `json:"restartPolicy"`
CpuQuota float64 `json:"cpuQuota"`
MemoryLimit float64 `json:"memoryLimit"`
MemoryUnit string `json:"memoryUnit"`
PullImage bool `json:"pullImage"`
EditCompose bool `json:"editCompose"`
DockerCompose string `json:"dockerCompose"`
}
type AgentItem struct {
ID uint `json:"id"`
Name string `json:"name"`
Provider string `json:"provider"`
ProviderName string `json:"providerName"`
Model string `json:"model"`
BaseURL string `json:"baseUrl"`
APIKey string `json:"apiKey"`
Token string `json:"token"`
Status string `json:"status"`
Message string `json:"message"`
AppInstallID uint `json:"appInstallId"`
AccountID uint `json:"accountId"`
AppVersion string `json:"appVersion"`
Container string `json:"containerName"`
WebUIPort int `json:"webUIPort"`
BridgePort int `json:"bridgePort"`
Path string `json:"path"`
ConfigPath string `json:"configPath"`
Upgradable bool `json:"upgradable"`
CreatedAt time.Time `json:"createdAt"`
}
type AgentDeleteReq struct {
ID uint `json:"id" validate:"required"`
TaskID string `json:"taskID"`
ForceDelete bool `json:"forceDelete"`
}
type AgentTokenResetReq struct {
ID uint `json:"id" validate:"required"`
}
type AgentModelConfigUpdateReq struct {
AgentID uint `json:"agentId" validate:"required"`
AccountID uint `json:"accountId" validate:"required"`
Model string `json:"model" validate:"required"`
}
type AgentAccountCreateReq struct {
Provider string `json:"provider" validate:"required"`
Name string `json:"name" validate:"required"`
APIKey string `json:"apiKey" validate:"required"`
BaseURL string `json:"baseURL"`
Remark string `json:"remark"`
}
type AgentAccountUpdateReq struct {
ID uint `json:"id" validate:"required"`
Name string `json:"name" validate:"required"`
APIKey string `json:"apiKey" validate:"required"`
BaseURL string `json:"baseURL"`
Remark string `json:"remark"`
SyncAgents bool `json:"syncAgents"`
}
type AgentAccountVerifyReq struct {
Provider string `json:"provider" validate:"required"`
APIKey string `json:"apiKey" validate:"required"`
BaseURL string `json:"baseURL"`
}
type AgentAccountDeleteReq struct {
ID uint `json:"id" validate:"required"`
}
type AgentAccountSearch struct {
PageInfo
Provider string `json:"provider"`
Name string `json:"name"`
}
type AgentAccountInfo struct {
ID uint `json:"id"`
Provider string `json:"provider"`
ProviderName string `json:"providerName"`
Name string `json:"name"`
APIKey string `json:"apiKey"`
BaseURL string `json:"baseUrl"`
Verified bool `json:"verified"`
Remark string `json:"remark"`
CreatedAt time.Time `json:"createdAt"`
}
type ProviderModelInfo struct {
ID string `json:"id"`
Name string `json:"name"`
}
type ProviderInfo struct {
Sort uint `json:"-"`
Provider string `json:"provider"`
DisplayName string `json:"displayName"`
BaseURL string `json:"baseUrl"`
Models []ProviderModelInfo `json:"models"`
}
type AgentFeishuConfigReq struct {
AgentID uint `json:"agentId" validate:"required"`
}
type AgentFeishuConfigUpdateReq struct {
AgentID uint `json:"agentId" validate:"required"`
BotName string `json:"botName" validate:"required"`
AppID string `json:"appId" validate:"required"`
AppSecret string `json:"appSecret" validate:"required"`
Enabled bool `json:"enabled"`
DmPolicy string `json:"dmPolicy" validate:"required"`
}
type AgentFeishuPairingApproveReq struct {
AgentID uint `json:"agentId" validate:"required"`
PairingCode string `json:"pairingCode" validate:"required"`
}
type AgentFeishuConfig struct {
Enabled bool `json:"enabled"`
DmPolicy string `json:"dmPolicy"`
BotName string `json:"botName"`
AppID string `json:"appId"`
AppSecret string `json:"appSecret"`
}
+10
View File
@@ -330,3 +330,13 @@ type AlertEmailConfig struct {
Encryption string `json:"encryption"` // "ssl" / "tls" / "none"
Recipient string `json:"recipient"`
}
type AgentInfo struct {
NodeName string `json:"nodeName"`
NodeAddr string `json:"nodeAddr"`
}
type AlertWebhookConfig struct {
DisplayName string `json:"displayName"`
Url string `json:"url"`
}
+22 -19
View File
@@ -12,6 +12,8 @@ type AppDatabase struct {
DbUser string `json:"PANEL_DB_USER"`
Password string `json:"PANEL_DB_USER_PASSWORD"`
DatabaseName string `json:"DATABASE_NAME"`
Format string `json:"format"`
Collation string `json:"collation"`
}
type AuthParam struct {
@@ -82,25 +84,26 @@ type ExtraProperties struct {
}
type AppProperty struct {
Name string `json:"name"`
Type string `json:"type"`
Tags []string `json:"tags"`
ShortDescZh string `json:"shortDescZh" yaml:"shortDescZh"`
ShortDescEn string `json:"shortDescEn" yaml:"shortDescEn"`
Description Locale `json:"description"`
Key string `json:"key"`
Required []string `json:"Required"`
CrossVersionUpdate bool `json:"crossVersionUpdate"`
Limit int `json:"limit"`
Recommend int `json:"recommend"`
Website string `json:"website"`
Github string `json:"github"`
Document string `json:"document"`
Architectures []string `json:"architectures"`
MemoryRequired int `json:"memoryRequired"`
GpuSupport bool `json:"gpuSupport"`
Version float64 `json:"version"`
Deprecated float64 `json:"deprecated"`
Name string `json:"name"`
Type string `json:"type"`
Tags []string `json:"tags"`
ShortDescZh string `json:"shortDescZh" yaml:"shortDescZh"`
ShortDescEn string `json:"shortDescEn" yaml:"shortDescEn"`
Description Locale `json:"description"`
Key string `json:"key"`
Required []string `json:"Required"`
CrossVersionUpdate bool `json:"crossVersionUpdate" yaml:"crossVersionUpdate"`
Limit int `json:"limit" yaml:"limit"`
Recommend int `json:"recommend" yaml:"recommend"`
Website string `json:"website"`
Github string `json:"github"`
Document string `json:"document"`
Architectures []string `json:"architectures"`
MemoryRequired int `json:"memoryRequired" yaml:"memoryRequired"`
GpuSupport bool `json:"gpuSupport" yaml:"gpuSupport"`
Version float64 `json:"version"`
Deprecated float64 `json:"deprecated"`
BatchInstallSupport bool `json:"batchInstallSupport"`
}
type AppConfigVersion struct {
+14 -6
View File
@@ -33,6 +33,12 @@ type BackupInfo struct {
RememberAuth bool `json:"rememberAuth"`
}
type BackupCheckRes struct {
IsOk bool `json:"isOk"`
Msg string `json:"msg"`
Token string `json:"token"`
}
type ForBuckets struct {
Type string `json:"type" validate:"required"`
AccessKey string `json:"accessKey"`
@@ -59,12 +65,13 @@ type UploadForRecover struct {
}
type CommonBackup struct {
Type string `json:"type" validate:"required,oneof=app mysql mariadb redis website postgresql mysql-cluster postgresql-cluster redis-cluster"`
Name string `json:"name"`
DetailName string `json:"detailName"`
Secret string `json:"secret"`
TaskID string `json:"taskID"`
FileName string `json:"fileName"`
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"`
TaskID string `json:"taskID"`
FileName string `json:"fileName"`
Args []string `json:"args"`
Description string `json:"description"`
}
@@ -77,6 +84,7 @@ type CommonRecover struct {
Secret string `json:"secret"`
TaskID string `json:"taskID"`
BackupRecordID uint `json:"backupRecordID"`
Timeout int `json:"timeout"`
}
type RecordSearch struct {
+64 -42
View File
@@ -15,8 +15,9 @@ type PageContainer struct {
}
type InspectReq struct {
ID string `json:"id" validate:"required"`
Type string `json:"type" validate:"required"`
ID string `json:"id" validate:"required"`
Type string `json:"type" validate:"required"`
Detail string `json:"detail"`
}
type ContainerInfo struct {
@@ -31,15 +32,15 @@ type ContainerInfo struct {
Network []string `json:"network"`
Ports []string `json:"ports"`
SizeRw int64 `json:"sizeRw"`
SizeRootFs int64 `json:"sizeRootFs"`
IsFromApp bool `json:"isFromApp"`
IsFromCompose bool `json:"isFromCompose"`
AppName string `json:"appName"`
AppInstallName string `json:"appInstallName"`
Websites []string `json:"websites"`
IsPinned bool `json:"isPinned"`
Description string `json:"description"`
}
type ContainerOptions struct {
@@ -63,15 +64,6 @@ type ContainerStatus struct {
NetworkCount int `json:"networkCount"`
VolumeCount int `json:"volumeCount"`
RepoCount int `json:"repoCount"`
ContainerUsage int64 `json:"containerUsage"`
ContainerReclaimable int64 `json:"containerReclaimable"`
ImageUsage int64 `json:"imageUsage"`
ImageReclaimable int64 `json:"imageReclaimable"`
VolumeUsage int64 `json:"volumeUsage"`
VolumeReclaimable int64 `json:"volumeReclaimable"`
BuildCacheUsage int64 `json:"buildCacheUsage"`
BuildCacheReclaimable int64 `json:"buildCacheReclaimable"`
}
type ResourceLimit struct {
CPU int `json:"cpu"`
@@ -79,17 +71,16 @@ type ResourceLimit struct {
}
type ContainerOperate struct {
TaskID string `json:"taskID"`
ForcePull bool `json:"forcePull"`
Name string `json:"name" validate:"required"`
Image string `json:"image" validate:"required"`
Network string `json:"network"`
Hostname string `json:"hostname"`
DomainName string `json:"domainName"`
MacAddr string `json:"macAddr"`
DNS []string `json:"dns"`
Ipv4 string `json:"ipv4"`
Ipv6 string `json:"ipv6"`
TaskID string `json:"taskID"`
ForcePull bool `json:"forcePull"`
Name string `json:"name" validate:"required"`
Image string `json:"image" validate:"required"`
Hostname string `json:"hostname"`
DomainName string `json:"domainName"`
DNS []string `json:"dns"`
Networks []ContainerNetwork `json:"networks"`
PublishAllPorts bool `json:"publishAllPorts"`
ExposedPorts []PortHelper `json:"exposedPorts"`
Tty bool `json:"tty"`
@@ -108,6 +99,12 @@ type ContainerOperate struct {
Env []string `json:"env"`
RestartPolicy string `json:"restartPolicy"`
}
type ContainerNetwork struct {
Network string `json:"network"`
Ipv4 string `json:"ipv4"`
Ipv6 string `json:"ipv6"`
MacAddr string `json:"macAddr"`
}
type ContainerCreateByCommand struct {
TaskID string `json:"taskID"`
@@ -121,6 +118,19 @@ type ContainerUpgrade struct {
ForcePull bool `json:"forcePull"`
}
type ContainerItemStats struct {
SizeRw int64 `json:"sizeRw"`
SizeRootFs int64 `json:"sizeRootFs"`
ContainerUsage int64 `json:"containerUsage"`
ContainerReclaimable int64 `json:"containerReclaimable"`
ImageUsage int64 `json:"imageUsage"`
ImageReclaimable int64 `json:"imageReclaimable"`
VolumeUsage int64 `json:"volumeUsage"`
VolumeReclaimable int64 `json:"volumeReclaimable"`
BuildCacheUsage int64 `json:"buildCacheUsage"`
BuildCacheReclaimable int64 `json:"buildCacheReclaimable"`
}
type ContainerListStats struct {
ContainerID string `json:"containerID"`
@@ -252,41 +262,52 @@ type ComposeInfo struct {
Workdir string `json:"workdir"`
Path string `json:"path"`
Containers []ComposeContainer `json:"containers"`
Env []string `json:"env"`
Env string `json:"env"`
}
type ComposeContainer struct {
ContainerID string `json:"containerID"`
Name string `json:"name"`
CreateTime string `json:"createTime"`
State string `json:"state"`
ContainerID string `json:"containerID"`
Name string `json:"name"`
CreateTime string `json:"createTime"`
State string `json:"state"`
Ports []string `json:"ports"`
}
type ComposeCreate struct {
TaskID string `json:"taskID"`
Name string `json:"name"`
From string `json:"from" validate:"required,oneof=edit path template"`
File string `json:"file"`
Path string `json:"path"`
Template uint `json:"template"`
Env []string `json:"env"`
TaskID string `json:"taskID"`
Name string `json:"name"`
From string `json:"from" validate:"required,oneof=edit path template"`
File string `json:"file"`
Path string `json:"path"`
Template uint `json:"template"`
Env string `json:"env"`
ForcePull bool `json:"forcePull"`
}
type ComposeOperation struct {
Name string `json:"name" validate:"required"`
Path string `json:"path"`
Operation string `json:"operation" validate:"required,oneof=up start restart stop down delete"`
WithFile bool `json:"withFile"`
Force bool `josn:"force"`
Force bool `json:"force"`
}
type ComposeUpdate struct {
Name string `json:"name" validate:"required"`
Path string `json:"path" validate:"required"`
Content string `json:"content" validate:"required"`
Env []string `json:"env"`
TaskID string `json:"taskID"`
Name string `json:"name" validate:"required"`
Path string `json:"path" validate:"required"`
DetailPath string `json:"detailPath"`
Content string `json:"content" validate:"required"`
Env string `json:"env"`
ForcePull bool `json:"forcePull"`
}
type ComposeLogClean struct {
Name string `json:"name" validate:"required"`
Path string `json:"path" validate:"required"`
DetailPath string `json:"detailPath"`
}
type ContainerLog struct {
Container string `json:"container" validate:"required"`
Since string `json:"since"`
Tail uint `json:"tail"`
Timestamp bool `json:"timestamp"`
ContainerType string `json:"containerType"`
}
@@ -296,5 +317,6 @@ type StreamLog struct {
Since string
Follow bool
Tail string
Timestamp bool
Type string
}
+3
View File
@@ -49,6 +49,7 @@ type CronjobOperate struct {
Timeout uint `json:"timeout" validate:"number,min=1"`
IgnoreErr bool `json:"ignoreErr"`
Secret string `json:"secret"`
Args string `json:"args"`
AlertCount uint `json:"alertCount"`
AlertTitle string `json:"alertTitle"`
@@ -123,6 +124,7 @@ type CronjobInfo struct {
LastRecordTime string `json:"lastRecordTime"`
Status string `json:"status"`
Secret string `json:"secret"`
Args string `json:"args"`
AlertCount uint `json:"alertCount"`
}
@@ -162,6 +164,7 @@ type CronjobTrans struct {
IgnoreErr bool `json:"ignoreErr"`
SnapshotRule SnapshotTransHelper `json:"snapshotRule"`
Secret string `json:"secret"`
Args string `json:"args"`
SourceAccounts []string `json:"sourceAccounts"`
DownloadAccount string `json:"downloadAccount"`
+15 -10
View File
@@ -13,15 +13,17 @@ type DashboardBase struct {
Platform string `json:"platform"`
PlatformFamily string `json:"platformFamily"`
PlatformVersion string `json:"platformVersion"`
PrettyDistro string `json:"prettyDistro"`
KernelArch string `json:"kernelArch"`
KernelVersion string `json:"kernelVersion"`
VirtualizationSystem string `json:"virtualizationSystem"`
IpV4Addr string `json:"ipV4Addr"`
SystemProxy string `json:"systemProxy"`
CPUCores int `json:"cpuCores"`
CPULogicalCores int `json:"cpuLogicalCores"`
CPUModelName string `json:"cpuModelName"`
CPUCores int `json:"cpuCores"`
CPULogicalCores int `json:"cpuLogicalCores"`
CPUModelName string `json:"cpuModelName"`
CPUMhz float64 `json:"cpuMhz"`
QuickJumps []QuickJump `json:"quickJump"`
CurrentInfo DashboardCurrent `json:"currentInfo"`
@@ -48,6 +50,7 @@ type OsInfo struct {
PlatformFamily string `json:"platformFamily"`
KernelArch string `json:"kernelArch"`
KernelVersion string `json:"kernelVersion"`
PrettyDistro string `json:"prettyDistro"`
DiskSize int64 `json:"diskSize"`
}
@@ -58,9 +61,10 @@ type NodeCurrent struct {
Load15 float64 `json:"load15"`
LoadUsagePercent float64 `json:"loadUsagePercent"`
CPUUsedPercent float64 `json:"cpuUsedPercent"`
CPUUsed float64 `json:"cpuUsed"`
CPUTotal int `json:"cpuTotal"`
CPUUsedPercent float64 `json:"cpuUsedPercent"`
CPUUsed float64 `json:"cpuUsed"`
CPUTotal int `json:"cpuTotal"`
CPUDetailedPercent []float64 `json:"cpuDetailedPercent"`
MemoryTotal uint64 `json:"memoryTotal"`
MemoryAvailable uint64 `json:"memoryAvailable"`
@@ -84,10 +88,11 @@ type DashboardCurrent struct {
Load15 float64 `json:"load15"`
LoadUsagePercent float64 `json:"loadUsagePercent"`
CPUPercent []float64 `json:"cpuPercent"`
CPUUsedPercent float64 `json:"cpuUsedPercent"`
CPUUsed float64 `json:"cpuUsed"`
CPUTotal int `json:"cpuTotal"`
CPUPercent []float64 `json:"cpuPercent"`
CPUUsedPercent float64 `json:"cpuUsedPercent"`
CPUUsed float64 `json:"cpuUsed"`
CPUTotal int `json:"cpuTotal"`
CPUDetailedPercent []float64 `json:"cpuDetailedPercent"`
MemoryTotal uint64 `json:"memoryTotal"`
MemoryUsed uint64 `json:"memoryUsed"`
+17 -8
View File
@@ -38,6 +38,7 @@ type MysqlDBInfo struct {
From string `json:"from"`
MysqlName string `json:"mysqlName"`
Format string `json:"format"`
Collation string `json:"collation"`
Username string `json:"username"`
Password string `json:"password"`
Permission string `json:"permission"`
@@ -57,13 +58,19 @@ type MysqlDBCreate struct {
Name string `json:"name" validate:"required"`
From string `json:"from" validate:"required,oneof=local remote"`
Database string `json:"database" validate:"required"`
Format string `json:"format" validate:"required,oneof=utf8mb4 utf8 gbk big5"`
Format string `json:"format" validate:"required"`
Collation string `json:"collation"`
Username string `json:"username" validate:"required"`
Password string `json:"password" validate:"required"`
Permission string `json:"permission" validate:"required"`
Description string `json:"description"`
}
type MysqlFormatCollationOption struct {
Format string `json:"format"`
Collations []string `json:"collations"`
}
type BindUser struct {
Database string `json:"database" validate:"required"`
DB string `json:"db" validate:"required"`
@@ -251,6 +258,7 @@ type DatabaseInfo struct {
Version string `json:"version"`
Address string `json:"address"`
Port uint `json:"port"`
InitialDB string `json:"initialDB"`
Username string `json:"username"`
Password string `json:"password"`
@@ -302,13 +310,14 @@ type DatabaseCreate struct {
}
type DatabaseUpdate struct {
ID uint `json:"id"`
Type string `json:"type" validate:"required"`
Version string `json:"version" validate:"required"`
Address string `json:"address"`
Port uint `json:"port"`
Username string `json:"username" validate:"required"`
Password string `json:"password"`
ID uint `json:"id"`
Type string `json:"type" validate:"required"`
Version string `json:"version" validate:"required"`
Address string `json:"address"`
Port uint `json:"port"`
InitialDB string `json:"initialDB"`
Username string `json:"username" validate:"required"`
Password string `json:"password"`
SSL bool `json:"ssl"`
RootCert string `json:"rootCert"`
+1 -1
View File
@@ -19,7 +19,7 @@ type RuleSearch struct {
}
type FirewallOperation struct {
Operation string `json:"operation" validate:"required,oneof=start stop restart disablePing enablePing"`
Operation string `json:"operation" validate:"required,oneof=start stop restart disableBanPing enableBanPing"`
WithDockerRestart bool `json:"withDockerRestart"`
}
+5 -2
View File
@@ -15,11 +15,14 @@ type ImageInfo struct {
IsUsed bool `json:"isUsed"`
Tags []string `json:"tags"`
Size int64 `json:"size"`
IsPinned bool `json:"isPinned"`
Description string `json:"description"`
}
type ImageLoad struct {
TaskID string `json:"taskID"`
Path string `json:"path" validate:"required"`
TaskID string `json:"taskID"`
Paths []string `json:"paths" validate:"required,dive,required"`
}
type ImageBuild struct {
+43 -1
View File
@@ -11,7 +11,7 @@ type MonitorSearch struct {
}
type MonitorData struct {
Param string `json:"param" validate:"required,oneof=cpu memory load io network"`
Param string `json:"param"`
Date []time.Time `json:"date"`
Value []interface{} `json:"value"`
}
@@ -37,3 +37,45 @@ type MonitorSettingUpdate struct {
Key string `json:"key" validate:"required,oneof=MonitorStatus MonitorStoreDays MonitorInterval DefaultNetwork DefaultIO"`
Value string `json:"value"`
}
type MonitorGPUOptions struct {
GPUType string `json:"gpuType"`
ChartHide []GPUChartHide `json:"chartHide"`
Options []string `json:"options"`
}
type GPUChartHide struct {
ProductName string `json:"productName"`
Process bool `json:"process"`
GPU bool `json:"gpu"`
Memory bool `json:"memory"`
Power bool `json:"power"`
Temperature bool `json:"temperature"`
Speed bool `json:"speed"`
}
type MonitorGPUSearch struct {
ProductName string `json:"productName"`
StartTime time.Time `json:"startTime"`
EndTime time.Time `json:"endTime"`
}
type MonitorGPUData struct {
Date []time.Time `json:"date"`
GPUValue []float64 `json:"gpuValue"`
TemperatureValue []float64 `json:"temperatureValue"`
PowerTotal []float64 `json:"powerTotal"`
PowerUsed []float64 `json:"powerUsed"`
PowerPercent []float64 `json:"powerPercent"`
MemoryTotal []float64 `json:"memoryTotal"`
MemoryUsed []float64 `json:"memoryUsed"`
MemoryPercent []float64 `json:"memoryPercent"`
SpeedValue []int `json:"speedValue"`
ProcessCount []int `json:"processCount"`
GPUProcesses [][]GPUProcess `json:"gpuProcesses"`
}
type GPUProcess struct {
Pid string `json:"pid"`
Type string `json:"type"`
ProcessName string `json:"processName"`
UsedMemory string `json:"usedMemory"`
}
+17 -7
View File
@@ -22,7 +22,16 @@ type AppInstallCreate struct {
Name string `json:"name" validate:"required"`
Services map[string]string `json:"services"`
TaskID string `json:"taskID"`
AppContainerConfig
NodePushConfig
}
type NodePushConfig struct {
Nodes []string `json:"nodes"`
PushNode bool `json:"pushNode"`
AppKey string `json:"appKey"`
Version string `json:"version"`
}
type AppContainerConfig struct {
@@ -45,13 +54,14 @@ type AppContainerConfig struct {
type AppInstalledSearch struct {
dto.PageInfo
Type string `json:"type"`
Name string `json:"name"`
Tags []string `json:"tags"`
Update bool `json:"update"`
Unused bool `json:"unused"`
All bool `json:"all"`
Sync bool `json:"sync"`
Type string `json:"type"`
Name string `json:"name"`
Tags []string `json:"tags"`
Update bool `json:"update"`
Unused bool `json:"unused"`
All bool `json:"all"`
Sync bool `json:"sync"`
CheckUpdate bool `json:"checkUpdate"`
}
type AppInstalledInfo struct {
+1
View File
@@ -13,6 +13,7 @@ type DiskMountRequest struct {
MountPoint string `json:"mountPoint" validate:"required"`
Filesystem string `json:"filesystem" validate:"required,oneof=ext4 xfs"`
AutoMount bool `json:"autoMount"`
NoFail bool `json:"noFail"`
}
type DiskUnmountRequest struct {
+9 -1
View File
@@ -13,7 +13,6 @@ type FileContentReq struct {
Path string `json:"path" validate:"required"`
IsDetail bool `json:"isDetail"`
}
type SearchUploadWithPage struct {
dto.PageInfo
Path string `json:"path" validate:"required"`
@@ -164,3 +163,12 @@ type FileConvertRequest struct {
DeleteSource bool `json:"deleteSource"`
TaskID string `json:"taskID"`
}
type FileRemarkBatch struct {
Paths []string `json:"paths" validate:"required"`
}
type FileRemarkUpdate struct {
Path string `json:"path" validate:"required"`
Remark string `json:"remark"`
}
+36
View File
@@ -38,6 +38,22 @@ type WebsiteCreate struct {
FtpConfig
DataBaseConfig
SSLConfig
StreamConfig
}
type StreamConfig struct {
StreamPorts string `json:"streamPorts"`
Name string `json:"name"`
Algorithm string `json:"algorithm"`
UDP bool `json:"udp"`
Servers []dto.NginxUpstreamServer `json:"servers"`
}
type StreamUpdate struct {
WebsiteID uint `json:"websiteID" validate:"required"`
StreamConfig
}
type WebsiteOptionReq struct {
@@ -114,6 +130,25 @@ type BatchWebsiteGroup struct {
GroupID uint `json:"groupID" validate:"required"`
}
type BatchWebsiteHttps struct {
IDs []uint `json:"ids" validate:"required"`
TaskID string `json:"taskID" validate:"required"`
WebsiteSSLID uint `json:"websiteSSLId"`
Type string `json:"type" validate:"oneof=existed auto manual"`
PrivateKey string `json:"privateKey"`
Certificate string `json:"certificate"`
PrivateKeyPath string `json:"privateKeyPath"`
CertificatePath string `json:"certificatePath"`
ImportType string `json:"importType"`
HttpConfig string `json:"httpConfig" validate:"oneof=HTTPSOnly HTTPAlso HTTPToHTTPS"`
SSLProtocol []string `json:"SSLProtocol"`
Algorithm string `json:"algorithm"`
Hsts bool `json:"hsts"`
HstsIncludeSubDomains bool `json:"hstsIncludeSubDomains"`
HttpsPorts []int `json:"httpsPorts"`
Http3 bool `json:"http3"`
}
type WebsiteRedirectUpdate struct {
WebsiteID uint `json:"websiteId" validate:"required"`
Key string `json:"key" validate:"required"`
@@ -276,6 +311,7 @@ type WebsiteHtmlReq struct {
type WebsiteHtmlUpdate struct {
Type string `json:"type" validate:"required"`
Content string `json:"content" validate:"required"`
Sync bool `json:"sync"`
}
type WebsiteLBCreate struct {
+6
View File
@@ -10,6 +10,10 @@ type WebsiteSSLSearch struct {
Order string `json:"order" validate:"required,oneof=null ascending descending"`
}
type WebsiteSSLListReq struct {
AcmeAccountID string `json:"acmeAccountID"`
}
type WebsiteSSLCreate struct {
PrimaryDomain string `json:"primaryDomain" validate:"required"`
OtherDomains string `json:"otherDomains"`
@@ -31,6 +35,7 @@ type WebsiteSSLCreate struct {
Shell string `json:"shell"`
PushNode bool `json:"pushNode"`
Nodes string `json:"nodes"`
IsIp bool `json:"isIp"`
}
type WebsiteDNSReq struct {
@@ -62,6 +67,7 @@ type WebsiteAcmeAccountCreate struct {
EabHmacKey string `json:"eabHmacKey"`
UseProxy bool `json:"useProxy"`
CaDirURL string `json:"caDirURL"`
UseEAB bool `json:"useEAB"`
}
type WebsiteAcmeAccountUpdate struct {
+16 -16
View File
@@ -29,22 +29,18 @@ type AppDTO struct {
}
type AppItem struct {
Name string `json:"name"`
Key string `json:"key"`
ID uint `json:"id"`
Description string `json:"description"`
Icon string `json:"icon"`
Type string `json:"type"`
Status string `json:"status"`
Resource string `json:"resource"`
Installed bool `json:"installed"`
Versions []string `json:"versions"`
Limit int `json:"limit"`
Tags []TagDTO `json:"tags"`
Github string `json:"github"`
Website string `json:"website"`
GpuSupport bool `json:"gpuSupport"`
Recommend int `json:"recommend"`
Name string `json:"name"`
Key string `json:"key"`
ID uint `json:"id"`
Description string `json:"description"`
Status string `json:"status"`
Installed bool `json:"installed"`
Limit int `json:"limit"`
Tags []string `json:"tags"`
GpuSupport bool `json:"gpuSupport"`
Recommend int `json:"recommend"`
Type string `json:"type"`
BatchInstallSupport bool `json:"batchInstallSupport"`
}
type TagDTO struct {
@@ -80,6 +76,10 @@ type AppDetailDTO struct {
GpuSupport bool `json:"gpuSupport"`
}
type AppDetailSimpleDTO struct {
ID uint `json:"id"`
}
type IgnoredApp struct {
Icon string `json:"icon"`
Name string `json:"name"`
+4
View File
@@ -81,3 +81,7 @@ type FileConvertLog struct {
Status string `json:"status"`
Message string `json:"message"`
}
type FileRemarksRes struct {
Remarks map[string]string `json:"remarks"`
}
+6
View File
@@ -3,6 +3,7 @@ package response
import (
"time"
"github.com/1Panel-dev/1Panel/agent/app/dto"
"github.com/1Panel-dev/1Panel/agent/app/model"
)
@@ -16,6 +17,10 @@ type WebsiteDTO struct {
RuntimeType string `json:"runtimeType"`
SiteDir string `json:"siteDir"`
OpenBaseDir bool `json:"openBaseDir"`
Algorithm string `json:"algorithm"`
UDP bool `json:"udp"`
Servers []dto.NginxUpstreamServer `json:"servers"`
}
type WebsiteRes struct {
@@ -35,6 +40,7 @@ type WebsiteRes struct {
SSLStatus string `json:"sslStatus"`
AppInstallID uint `json:"appInstallId"`
ChildSites []string `json:"childSites"`
ParentSite string `json:"parentSite"`
RuntimeType string `json:"runtimeType"`
Favorite bool `json:"favorite"`
IPV6 bool `json:"IPV6"`
+13
View File
@@ -37,6 +37,7 @@ type SyncTime struct {
type CleanData struct {
SystemClean []CleanTree `json:"systemClean"`
BackupClean []CleanTree `json:"backupClean"`
UploadClean []CleanTree `json:"uploadClean"`
DownloadClean []CleanTree `json:"downloadClean"`
SystemLogClean []CleanTree `json:"systemLogClean"`
@@ -62,6 +63,10 @@ type Clean struct {
Size uint64 `json:"size"`
}
type SSHDefaultConn struct {
WithReset bool `json:"withReset"`
DefaultConn string `json:"defaultConn"`
}
type SSHConnData struct {
Addr string `json:"addr" validate:"required"`
Port uint `json:"port" validate:"required,number,max=65535,min=1"`
@@ -81,3 +86,11 @@ type SystemProxy struct {
User string `json:"user"`
Password string `json:"password"`
}
type CommonDescription struct {
ID string `json:"id" validate:"required"`
Type string `json:"type" validate:"required"`
DetailType string `json:"detailType"`
IsPinned bool `json:"isPinned"`
Description string `json:"description"`
}
+1
View File
@@ -52,6 +52,7 @@ type DataTree struct {
Label string `json:"label"`
Key string `json:"key"`
Name string `json:"name"`
IsLocal bool `json:"isLocal"`
Size uint64 `json:"size"`
IsCheck bool `json:"isCheck"`
IsDisable bool `json:"isDisable"`
+16
View File
@@ -0,0 +1,16 @@
package model
type Agent struct {
BaseModel
Name string `json:"name" gorm:"not null;unique"`
Provider string `json:"provider"`
Model string `json:"model"`
BaseURL string `json:"baseUrl"`
APIKey string `json:"apiKey"`
Token string `json:"token"`
Status string `json:"status"`
Message string `json:"message"`
AppInstallID uint `json:"appInstallId"`
AccountID uint `json:"accountId"`
ConfigPath string `json:"configPath"`
}
+15
View File
@@ -0,0 +1,15 @@
package model
type AgentAccount struct {
BaseModel
Provider string `json:"provider"`
Name string `json:"name"`
APIKey string `json:"apiKey"`
BaseURL string `json:"baseUrl"`
Verified bool `json:"verified"`
Remark string `json:"remark"`
}
func (AgentAccount) TableName() string {
return "agent_provider_accounts"
}
+1 -1
View File
@@ -11,7 +11,7 @@ type Alert struct {
Status string `gorm:"type:varchar(64);not null" json:"status"`
Method string `gorm:"type:varchar(64);not null" json:"method"`
SendCount uint `gorm:"type:integer" json:"sendCount"`
AdvancedParams string `gorm:"type:longText" json:"advancedParam"`
AdvancedParams string `gorm:"type:longText" json:"advancedParams"`
}
type AlertTask struct {
+1
View File
@@ -34,6 +34,7 @@ type App struct {
MemoryRequired int `json:"memoryRequired"`
GpuSupport bool `json:"gpuSupport"`
RequiredPanelVersion float64 `json:"requiredPanelVersion"`
BatchInstallSupport bool `json:"batchInstallSupport" yaml:"batchInstallSupport"`
Details []AppDetail `json:"-" gorm:"-:migration"`
TagsKey []string `json:"tags" yaml:"tags" gorm:"-"`
+1
View File
@@ -37,6 +37,7 @@ type Cronjob struct {
Timeout uint `json:"timeout"`
IgnoreErr bool `json:"ignoreErr"`
RetainCopies uint64 `json:"retainCopies"`
Args string `json:"args"`
IsExecuting bool `json:"isExecuting"`
Status string `json:"status"`
+1
View File
@@ -6,6 +6,7 @@ type DatabaseMysql struct {
From string `json:"from" gorm:"not null;default:local"`
MysqlName string `json:"mysqlName" gorm:"not null"`
Format string `json:"format" gorm:"not null"`
Collation string `json:"collation"`
Username string `json:"username" gorm:"not null"`
Password string `json:"password" gorm:"not null"`
Permission string `json:"permission" gorm:"not null"`
+13
View File
@@ -31,3 +31,16 @@ type MonitorNetwork struct {
Up float64 `json:"up"`
Down float64 `json:"down"`
}
type MonitorGPU struct {
BaseModel
ProductName string `json:"productName"`
GPUUtil float64 `json:"gpuUtil"`
Temperature float64 `json:"temperature"`
PowerDraw float64 `json:"powerDraw"`
MaxPowerLimit float64 `json:"maxPowerLimit"`
MemUsed float64 `json:"memUsed"`
MemTotal float64 `json:"memTotal"`
FanSpeed int `json:"fanSpeed"`
Processes string `json:"processes"`
}
+8
View File
@@ -7,6 +7,14 @@ type Setting struct {
About string `json:"about"`
}
type CommonDescription struct {
ID string `json:"id"`
Type string `json:"type"`
DetailType string `json:"detailType"`
IsPinned bool `json:"isPinned"`
Description string `json:"description"`
}
type NodeInfo struct {
Scope string `json:"scope"`
BaseDir string `json:"baseDir"`
+2
View File
@@ -37,6 +37,8 @@ type Website struct {
Favorite bool `json:"favorite"`
StreamPorts string `json:"streamPorts"`
Domains []WebsiteDomain `json:"domains" gorm:"-:migration"`
WebsiteSSL WebsiteSSL `json:"webSiteSSL" gorm:"-:migration"`
}
+1
View File
@@ -11,6 +11,7 @@ type WebsiteAcmeAccount struct {
KeyType string `gorm:"not null;default:2048" json:"keyType"`
UseProxy bool `gorm:"default:false" json:"useProxy"`
CaDirURL string `json:"caDirURL"`
UseEAB bool `json:"useEAB"`
}
func (w WebsiteAcmeAccount) TableName() string {
+1
View File
@@ -41,6 +41,7 @@ type WebsiteSSL struct {
PushNode bool `json:"pushNode"`
PrivateKeyPath string `json:"privateKeyPath"`
CertPath string `json:"certPath"`
IsIp bool `json:"isIP"`
AcmeAccount WebsiteAcmeAccount `json:"acmeAccount" gorm:"-:migration"`
DnsAccount WebsiteDnsAccount `json:"dnsAccount" gorm:"-:migration"`
+201
View File
@@ -0,0 +1,201 @@
package provider
import "strings"
type Model struct {
ID string
Name string
}
type Meta struct {
Key string
DisplayName string
Sort uint
DefaultBaseURL string
EnvKey string
Models []Model
Enabled bool
}
var catalog = map[string]Meta{
"ollama": {
Key: "ollama",
DisplayName: "Ollama",
Sort: 1,
Enabled: true,
},
"deepseek": {
Key: "deepseek",
DisplayName: "DeepSeek",
Sort: 2,
DefaultBaseURL: "https://api.deepseek.com/v1",
EnvKey: "DEEPSEEK_API_KEY",
Enabled: true,
Models: []Model{
{ID: "deepseek/deepseek-chat", Name: "DeepSeek Chat"},
{ID: "deepseek/deepseek-reasoner", Name: "DeepSeek Reasoner"},
{ID: "deepseek/deepseek-r1:1.5b", Name: "DeepSeek R1 1.5B"},
},
},
"openai": {
Key: "openai",
DisplayName: "OpenAI",
Sort: 3,
DefaultBaseURL: "https://api.openai.com/v1",
EnvKey: "OPENAI_API_KEY",
Enabled: true,
Models: []Model{
{ID: "openai/codex-mini-latest", Name: "Codex Mini"},
{ID: "openai/gpt-4.1", Name: "GPT-4.1"},
{ID: "openai/gpt-4o", Name: "GPT-4o"},
{ID: "openai/gpt-4o-mini", Name: "GPT-4o Mini"},
{ID: "openai/gpt-5", Name: "GPT-5"},
{ID: "openai/gpt-5-mini", Name: "GPT-5 Mini"},
},
},
"anthropic": {
Key: "anthropic",
DisplayName: "Anthropic",
Sort: 4,
DefaultBaseURL: "https://api.anthropic.com",
EnvKey: "ANTHROPIC_API_KEY",
Enabled: true,
Models: []Model{
{ID: "anthropic/claude-3-haiku-20240307", Name: "Claude 3 Haiku"},
{ID: "anthropic/claude-3-5-haiku-latest", Name: "Claude 3.5 Haiku"},
{ID: "anthropic/claude-3-5-sonnet-20241022", Name: "Claude 3.5 Sonnet"},
{ID: "anthropic/claude-3-7-sonnet-20250219", Name: "Claude 3.7 Sonnet"},
{ID: "anthropic/claude-opus-4-1", Name: "Claude Opus 4.1"},
},
},
"gemini": {
Key: "gemini",
DisplayName: "Gemini",
Sort: 5,
DefaultBaseURL: "https://generativelanguage.googleapis.com",
EnvKey: "GEMINI_API_KEY",
Enabled: true,
Models: []Model{
{ID: "google/gemini-1.5-flash", Name: "Gemini 1.5 Flash"},
{ID: "google/gemini-1.5-pro", Name: "Gemini 1.5 Pro"},
{ID: "google/gemini-2.0-flash", Name: "Gemini 2.0 Flash"},
{ID: "google/gemini-2.5-flash", Name: "Gemini 2.5 Flash"},
{ID: "google/gemini-2.5-pro", Name: "Gemini 2.5 Pro"},
{ID: "google/gemini-3-flash-preview", Name: "Gemini 3 Flash Preview"},
},
},
"minimax": {
Key: "minimax",
DisplayName: "MiniMax (CN)",
Sort: 6,
DefaultBaseURL: "https://api.minimaxi.com/anthropic",
EnvKey: "MINIMAX_API_KEY",
Enabled: true,
Models: []Model{
{ID: "minimax/MiniMax-M2.1", Name: "MiniMax M2.1"},
{ID: "minimax/MiniMax-M2.1-lightning", Name: "MiniMax M2.1 Lightning"},
},
},
"moonshot": {
Key: "moonshot",
DisplayName: "Moonshot (Global)",
Sort: 7,
DefaultBaseURL: "https://api.moonshot.ai/v1",
EnvKey: "MOONSHOT_API_KEY",
Enabled: true,
Models: []Model{
{ID: "moonshot/kimi-k2.5", Name: "Kimi K2.5"},
{ID: "moonshot/kimi-k2-0905-preview", Name: "Kimi K2 0905 Preview"},
{ID: "moonshot/kimi-k2-thinking", Name: "Kimi K2 Thinking"},
},
},
"kimi": {
Key: "kimi",
DisplayName: "Kimi (CN)",
Sort: 8,
DefaultBaseURL: "https://api.moonshot.cn/v1",
EnvKey: "KIMI_API_KEY",
Enabled: true,
Models: []Model{
{ID: "kimi/kimi-k2.5", Name: "Kimi K2.5"},
{ID: "kimi/kimi-k2-0905-preview", Name: "Kimi K2 0905 Preview"},
{ID: "kimi/kimi-k2-thinking", Name: "Kimi K2 Thinking"},
},
},
"kimi-coding": {
Key: "kimi-coding",
DisplayName: "Kimi Coding",
Sort: 9,
DefaultBaseURL: "https://api.moonshot.cn/anthropic/v1",
EnvKey: "KIMI_API_KEY",
Enabled: true,
Models: []Model{
{ID: "kimi-coding/k2p5", Name: "Kimi K2.5"},
},
},
"qwen": {
Key: "qwen",
DisplayName: "Qwen",
Sort: 10,
DefaultBaseURL: "https://dashscope.aliyuncs.com/compatible-mode/v1",
EnvKey: "QWEN_API_KEY",
Enabled: false,
},
}
func Get(key string) (Meta, bool) {
meta, ok := catalog[strings.ToLower(strings.TrimSpace(key))]
if !ok {
return Meta{}, false
}
return cloneMeta(meta), true
}
func All() map[string]Meta {
result := make(map[string]Meta, len(catalog))
for key, meta := range catalog {
result[key] = cloneMeta(meta)
}
return result
}
func IsEnabled(key string) bool {
meta, ok := catalog[strings.ToLower(strings.TrimSpace(key))]
return ok && meta.Enabled
}
func DefaultBaseURL(key string) (string, bool) {
meta, ok := catalog[strings.ToLower(strings.TrimSpace(key))]
if !ok || strings.TrimSpace(meta.DefaultBaseURL) == "" {
return "", false
}
return meta.DefaultBaseURL, true
}
func EnvKey(key string) string {
meta, ok := catalog[strings.ToLower(strings.TrimSpace(key))]
if !ok {
return ""
}
return meta.EnvKey
}
func DisplayName(key string) string {
meta, ok := catalog[strings.ToLower(strings.TrimSpace(key))]
if !ok {
return key
}
if strings.TrimSpace(meta.DisplayName) == "" {
return key
}
return meta.DisplayName
}
func cloneMeta(meta Meta) Meta {
clone := meta
if len(meta.Models) > 0 {
clone.Models = make([]Model, len(meta.Models))
copy(clone.Models, meta.Models)
}
return clone
}
+75
View File
@@ -0,0 +1,75 @@
package repo
import (
"context"
"github.com/1Panel-dev/1Panel/agent/app/model"
)
type AgentRepo struct{}
type IAgentRepo interface {
Page(page, size int, opts ...DBOption) (int64, []model.Agent, error)
GetFirst(opts ...DBOption) (*model.Agent, error)
Create(agent *model.Agent) error
Save(agent *model.Agent) error
DeleteByID(id uint) error
DeleteByAppInstallID(appInstallID uint) error
DeleteByAppInstallIDWithCtx(ctx context.Context, appInstallID uint) error
List(opts ...DBOption) ([]model.Agent, error)
}
func NewIAgentRepo() IAgentRepo {
return &AgentRepo{}
}
func (a AgentRepo) Page(page, size int, opts ...DBOption) (int64, []model.Agent, error) {
var agents []model.Agent
db := getDb(opts...).Model(&model.Agent{})
count := int64(0)
db = db.Count(&count)
err := db.Limit(size).Offset(size * (page - 1)).Find(&agents).Error
return count, agents, err
}
func (a AgentRepo) GetFirst(opts ...DBOption) (*model.Agent, error) {
var agent model.Agent
if err := getDb(opts...).First(&agent).Error; err != nil {
return nil, err
}
return &agent, nil
}
func (a AgentRepo) Create(agent *model.Agent) error {
return getDb().Create(agent).Error
}
func (a AgentRepo) Save(agent *model.Agent) error {
return getDb().Save(agent).Error
}
func (a AgentRepo) DeleteByID(id uint) error {
return getDb().Delete(&model.Agent{}, id).Error
}
func (a AgentRepo) DeleteByAppInstallID(appInstallID uint) error {
if appInstallID == 0 {
return nil
}
return getDb().Where("app_install_id = ?", appInstallID).Delete(&model.Agent{}).Error
}
func (a AgentRepo) DeleteByAppInstallIDWithCtx(ctx context.Context, appInstallID uint) error {
if appInstallID == 0 {
return nil
}
return getTx(ctx).Where("app_install_id = ?", appInstallID).Delete(&model.Agent{}).Error
}
func (a AgentRepo) List(opts ...DBOption) ([]model.Agent, error) {
var agents []model.Agent
if err := getDb(opts...).Find(&agents).Error; err != nil {
return nil, err
}
return agents, nil
}
+55
View File
@@ -0,0 +1,55 @@
package repo
import "github.com/1Panel-dev/1Panel/agent/app/model"
type AgentAccountRepo struct{}
type IAgentAccountRepo interface {
Page(page, size int, opts ...DBOption) (int64, []model.AgentAccount, error)
GetFirst(opts ...DBOption) (*model.AgentAccount, error)
Create(account *model.AgentAccount) error
Save(account *model.AgentAccount) error
DeleteByID(id uint) error
List(opts ...DBOption) ([]model.AgentAccount, error)
}
func NewIAgentAccountRepo() IAgentAccountRepo {
return &AgentAccountRepo{}
}
func (a AgentAccountRepo) Page(page, size int, opts ...DBOption) (int64, []model.AgentAccount, error) {
var accounts []model.AgentAccount
db := getDb(opts...).Model(&model.AgentAccount{})
count := int64(0)
db = db.Count(&count)
err := db.Limit(size).Offset(size * (page - 1)).Find(&accounts).Error
return count, accounts, err
}
func (a AgentAccountRepo) GetFirst(opts ...DBOption) (*model.AgentAccount, error) {
var account model.AgentAccount
if err := getDb(opts...).First(&account).Error; err != nil {
return nil, err
}
return &account, nil
}
func (a AgentAccountRepo) Create(account *model.AgentAccount) error {
return getDb().Create(account).Error
}
func (a AgentAccountRepo) Save(account *model.AgentAccount) error {
return getDb().Save(account).Error
}
func (a AgentAccountRepo) DeleteByID(id uint) error {
return getDb().Delete(&model.AgentAccount{}, id).Error
}
func (a AgentAccountRepo) List(opts ...DBOption) ([]model.AgentAccount, error) {
var accounts []model.AgentAccount
if err := getDb(opts...).Find(&accounts).Error; err != nil {
return nil, err
}
return accounts, nil
}
+9 -2
View File
@@ -20,6 +20,7 @@ type IAppRepo interface {
WithResource(resource string) DBOption
WithNotLocal() DBOption
WithByLikeName(name string) DBOption
WithKeyIn(keys []string) DBOption
WithArch(arch string) DBOption
WithPanelVersion(panelVersion string) DBOption
@@ -33,7 +34,7 @@ type IAppRepo interface {
DeleteByIDs(ctx context.Context, ids []uint) error
DeleteBy(opts ...DBOption) error
GetTopRecomment() ([]string, error)
GetTopRecommend() ([]string, error)
}
func NewIAppRepo() IAppRepo {
@@ -55,6 +56,12 @@ func (a AppRepo) WithKey(key string) DBOption {
}
}
func (a AppRepo) WithKeyIn(keys []string) DBOption {
return func(db *gorm.DB) *gorm.DB {
return db.Where("`key` in (?)", keys)
}
}
func (a AppRepo) WithType(typeStr string) DBOption {
return func(g *gorm.DB) *gorm.DB {
return g.Where("type = ?", typeStr)
@@ -124,7 +131,7 @@ func (a AppRepo) GetBy(opts ...DBOption) ([]model.App, error) {
return apps, nil
}
func (a AppRepo) GetTopRecomment() ([]string, error) {
func (a AppRepo) GetTopRecommend() ([]string, error) {
var (
apps []model.App
names []string
+47 -7
View File
@@ -7,6 +7,7 @@ import (
"github.com/1Panel-dev/1Panel/agent/constant"
"github.com/1Panel-dev/1Panel/agent/global"
"github.com/1Panel-dev/1Panel/agent/utils/re"
"gorm.io/gorm"
)
@@ -72,11 +73,43 @@ func WithByDetailName(detailName string) DBOption {
}
}
func WithByProvider(provider string) DBOption {
return func(g *gorm.DB) *gorm.DB {
if len(provider) == 0 {
return g
}
return g.Where("provider = ?", provider)
}
}
func WithByModel(model string) DBOption {
return func(g *gorm.DB) *gorm.DB {
if len(model) == 0 {
return g
}
return g.Where("model = ?", model)
}
}
func WithByAccountID(accountID uint) DBOption {
return func(g *gorm.DB) *gorm.DB {
if accountID == 0 {
return g
}
return g.Where("account_id = ?", accountID)
}
}
func WithByType(tp string) DBOption {
return func(g *gorm.DB) *gorm.DB {
return g.Where("`type` = ?", tp)
}
}
func WithByDetailType(tp string) DBOption {
return func(g *gorm.DB) *gorm.DB {
return g.Where("`detail_type` = ?", tp)
}
}
func WithTypes(types []string) DBOption {
return func(db *gorm.DB) *gorm.DB {
@@ -119,23 +152,30 @@ func WithByCreatedAt(startTime, endTime time.Time) DBOption {
}
}
func WithOrderBy(orderStr string) DBOption {
if orderStr == "createdAt" {
orderStr = "created_at"
}
return func(g *gorm.DB) *gorm.DB {
return g.Order(orderStr)
}
func WithOrderDesc(orderBy string) DBOption {
return WithOrderRuleBy(orderBy, constant.Desc)
}
func WithOrderAsc(orderBy string) DBOption {
return WithOrderRuleBy(orderBy, constant.Asc)
}
func WithOrderRuleBy(orderBy, order string) DBOption {
if orderBy == "createdAt" {
orderBy = "created_at"
}
if !re.GetRegex(re.OrderByValidationPattern).MatchString(orderBy) {
orderBy = "created_at"
}
switch order {
case constant.OrderDesc:
order = "desc"
case constant.OrderAsc:
order = "asc"
case constant.Desc:
order = "desc"
case constant.Asc:
order = "asc"
default:
orderBy = "created_at"
order = "desc"
+27
View File
@@ -5,21 +5,27 @@ import (
"github.com/1Panel-dev/1Panel/agent/app/model"
"github.com/1Panel-dev/1Panel/agent/global"
"gorm.io/gorm"
)
type MonitorRepo struct{}
type IMonitorRepo interface {
GetBase(opts ...DBOption) ([]model.MonitorBase, error)
GetGPU(opts ...DBOption) ([]model.MonitorGPU, error)
GetIO(opts ...DBOption) ([]model.MonitorIO, error)
GetNetwork(opts ...DBOption) ([]model.MonitorNetwork, error)
CreateMonitorBase(model model.MonitorBase) error
BatchCreateMonitorGPU(list []model.MonitorGPU) error
BatchCreateMonitorIO(ioList []model.MonitorIO) error
BatchCreateMonitorNet(ioList []model.MonitorNetwork) error
DelMonitorBase(timeForDelete time.Time) error
DelMonitorGPU(timeForDelete time.Time) error
DelMonitorIO(timeForDelete time.Time) error
DelMonitorNet(timeForDelete time.Time) error
WithByProductName(name string) DBOption
}
func NewIMonitorRepo() IMonitorRepo {
@@ -53,10 +59,22 @@ func (u *MonitorRepo) GetNetwork(opts ...DBOption) ([]model.MonitorNetwork, erro
err := db.Find(&data).Error
return data, err
}
func (u *MonitorRepo) GetGPU(opts ...DBOption) ([]model.MonitorGPU, error) {
var data []model.MonitorGPU
db := global.GPUMonitorDB
for _, opt := range opts {
db = opt(db)
}
err := db.Find(&data).Error
return data, err
}
func (u *MonitorRepo) CreateMonitorBase(model model.MonitorBase) error {
return global.MonitorDB.Create(&model).Error
}
func (s *MonitorRepo) BatchCreateMonitorGPU(list []model.MonitorGPU) error {
return global.GPUMonitorDB.CreateInBatches(&list, len(list)).Error
}
func (u *MonitorRepo) BatchCreateMonitorIO(ioList []model.MonitorIO) error {
return global.MonitorDB.CreateInBatches(ioList, len(ioList)).Error
}
@@ -72,3 +90,12 @@ func (u *MonitorRepo) DelMonitorIO(timeForDelete time.Time) error {
func (u *MonitorRepo) DelMonitorNet(timeForDelete time.Time) error {
return global.MonitorDB.Where("created_at < ?", timeForDelete).Delete(&model.MonitorNetwork{}).Error
}
func (s *MonitorRepo) DelMonitorGPU(timeForDelete time.Time) error {
return global.GPUMonitorDB.Where("created_at < ?", timeForDelete).Delete(&model.MonitorGPU{}).Error
}
func (s *MonitorRepo) WithByProductName(name string) DBOption {
return func(g *gorm.DB) *gorm.DB {
return g.Where("product_name = ?", name)
}
}
+40 -26
View File
@@ -2,7 +2,6 @@ package repo
import (
"errors"
"time"
"github.com/1Panel-dev/1Panel/agent/app/model"
"github.com/1Panel-dev/1Panel/agent/global"
@@ -19,13 +18,14 @@ type ISettingRepo interface {
Update(key, value string) error
WithByKey(key string) DBOption
CreateMonitorBase(model model.MonitorBase) error
BatchCreateMonitorIO(ioList []model.MonitorIO) error
BatchCreateMonitorNet(ioList []model.MonitorNetwork) error
DelMonitorBase(timeForDelete time.Time) error
DelMonitorIO(timeForDelete time.Time) error
DelMonitorNet(timeForDelete time.Time) error
UpdateOrCreate(key, value string) error
GetDescription(opts ...DBOption) (model.CommonDescription, error)
GetDescriptionList(opts ...DBOption) ([]model.CommonDescription, error)
CreateDescription(data *model.CommonDescription) error
UpdateDescription(id string, val map[string]interface{}) error
DelDescription(id string) error
WithByDescriptionID(id string) DBOption
}
func NewISettingRepo() ISettingRepo {
@@ -78,25 +78,6 @@ func (s *SettingRepo) Update(key, value string) error {
return global.DB.Model(&model.Setting{}).Where("key = ?", key).Updates(map[string]interface{}{"value": value}).Error
}
func (s *SettingRepo) CreateMonitorBase(model model.MonitorBase) error {
return global.MonitorDB.Create(&model).Error
}
func (s *SettingRepo) BatchCreateMonitorIO(ioList []model.MonitorIO) error {
return global.MonitorDB.CreateInBatches(ioList, len(ioList)).Error
}
func (s *SettingRepo) BatchCreateMonitorNet(ioList []model.MonitorNetwork) error {
return global.MonitorDB.CreateInBatches(ioList, len(ioList)).Error
}
func (s *SettingRepo) DelMonitorBase(timeForDelete time.Time) error {
return global.MonitorDB.Where("created_at < ?", timeForDelete).Delete(&model.MonitorBase{}).Error
}
func (s *SettingRepo) DelMonitorIO(timeForDelete time.Time) error {
return global.MonitorDB.Where("created_at < ?", timeForDelete).Delete(&model.MonitorIO{}).Error
}
func (s *SettingRepo) DelMonitorNet(timeForDelete time.Time) error {
return global.MonitorDB.Where("created_at < ?", timeForDelete).Delete(&model.MonitorNetwork{}).Error
}
func (s *SettingRepo) UpdateOrCreate(key, value string) error {
var setting model.Setting
result := global.DB.Where("key = ?", key).First(&setting)
@@ -108,3 +89,36 @@ func (s *SettingRepo) UpdateOrCreate(key, value string) error {
}
return global.DB.Model(&setting).UpdateColumn("value", value).Error
}
func (s *SettingRepo) GetDescriptionList(opts ...DBOption) ([]model.CommonDescription, error) {
var lists []model.CommonDescription
db := global.DB.Model(&model.CommonDescription{})
for _, opt := range opts {
db = opt(db)
}
err := db.Find(&lists).Error
return lists, err
}
func (s *SettingRepo) GetDescription(opts ...DBOption) (model.CommonDescription, error) {
var data model.CommonDescription
db := global.DB.Model(&model.CommonDescription{})
for _, opt := range opts {
db = opt(db)
}
err := db.First(&data).Error
return data, err
}
func (s *SettingRepo) CreateDescription(data *model.CommonDescription) error {
return global.DB.Create(data).Error
}
func (s *SettingRepo) UpdateDescription(id string, val map[string]interface{}) error {
return global.DB.Model(&model.CommonDescription{}).Where("id = ?", id).Updates(val).Error
}
func (s *SettingRepo) DelDescription(id string) error {
return global.DB.Where("id = ?", id).Delete(&model.CommonDescription{}).Error
}
func (s *SettingRepo) WithByDescriptionID(id string) DBOption {
return func(g *gorm.DB) *gorm.DB {
return g.Where("id = ?", id)
}
}
File diff suppressed because it is too large Load Diff
+2 -2
View File
@@ -60,7 +60,7 @@ func (a AlertService) PageAlert(search dto.AlertSearch) (int64, []dto.AlertDTO,
if search.Type != "" {
opts = append(opts, alertRepo.WithByType(search.Type))
}
opts = append(opts, repo.WithOrderBy("created_at desc"))
opts = append(opts, repo.WithOrderDesc("created_at"))
total, alerts, err := alertRepo.Page(search.Page, search.PageSize, opts...)
if err != nil {
@@ -343,7 +343,7 @@ func (a AlertService) PageAlertLogs(search dto.AlertLogSearch) (int64, []dto.Ale
if search.Count != 0 {
opts = append(opts, alertRepo.WithByCount(search.Count))
}
opts = append(opts, repo.WithOrderBy("created_at desc"))
opts = append(opts, repo.WithOrderDesc("created_at"))
total, alerts, err := alertRepo.PageLog(search.Page, search.PageSize, opts...)
if err != nil {
+101 -27
View File
@@ -2,6 +2,7 @@ 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"
@@ -9,6 +10,7 @@ import (
"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/1Panel-dev/1Panel/agent/utils/psutil"
versionUtil "github.com/1Panel-dev/1Panel/agent/utils/version"
"github.com/1Panel-dev/1Panel/agent/utils/xpack"
"github.com/shirou/gopsutil/v4/cpu"
@@ -109,7 +111,7 @@ func (m *AlertTaskHelper) getClassifiedAlerts() (baseAlerts, resourceAlerts []dt
func handleBaseAlerts(baseAlerts []dto.AlertDTO) {
if len(baseAlerts) == 0 {
stopResourceJob()
stopBaseJob()
return
}
if global.AlertBaseJobID == 0 {
@@ -226,11 +228,30 @@ func loadSSLInfo(alert dto.AlertDTO) {
return
}
sender := NewAlertSender(alert, projectJSON)
minDays := math.MaxInt
maxDays := 0
allDomains := make([]string, 0)
for daysDiff, domains := range daysDiffMap {
domainStr := strings.Join(domains, ",")
params := createAlertBaseParams(strconv.Itoa(len(domains)), strconv.Itoa(daysDiff))
sender.Send(domainStr, params)
allDomains = append(allDomains, domains...)
if daysDiff < minDays {
minDays = daysDiff
}
if daysDiff > maxDays {
maxDays = daysDiff
}
}
if len(allDomains) == 0 {
return
}
var daysStr string
if len(allDomains) == 1 {
daysStr = strconv.Itoa(minDays)
} else {
daysStr = strconv.Itoa(minDays) + "-" + strconv.Itoa(maxDays)
}
domainStr := strings.Join(allDomains, ",")
params := createAlertBaseParams(strconv.Itoa(len(daysDiffMap)), daysStr)
sender.Send(domainStr, params)
}
func loadWebsiteInfo(alert dto.AlertDTO) {
@@ -246,11 +267,30 @@ func loadWebsiteInfo(alert dto.AlertDTO) {
return
}
sender := NewAlertSender(alert, projectJSON)
minDays := math.MaxInt
maxDays := 0
allDomains := make([]string, 0)
for daysDiff, domains := range daysDiffMap {
domainStr := strings.Join(domains, ",")
params := createAlertBaseParams(strconv.Itoa(len(domains)), strconv.Itoa(daysDiff))
sender.Send(domainStr, params)
allDomains = append(allDomains, domains...)
if daysDiff < minDays {
minDays = daysDiff
}
if daysDiff > maxDays {
maxDays = daysDiff
}
}
if len(allDomains) == 0 {
return
}
var daysStr string
if len(allDomains) == 1 {
daysStr = strconv.Itoa(minDays)
} else {
daysStr = strconv.Itoa(minDays) + "-" + strconv.Itoa(maxDays)
}
domainStr := strings.Join(allDomains, ",")
params := createAlertBaseParams(strconv.Itoa(len(daysDiffMap)), daysStr)
sender.Send(domainStr, params)
}
func loadPanelPwd(alert dto.AlertDTO) {
@@ -352,7 +392,7 @@ func loadLoadInfo(alert dto.AlertDTO) {
return
}
var loadValue float64
CPUTotal, _ := cpu.Counts(true)
CPUTotal, _ := psutil.CPUInfo.GetLogicalCores(false)
switch alert.Cycle {
case 1:
loadValue = avgStat.Load1 / (float64(CPUTotal*2) * 0.75) * 100
@@ -579,10 +619,12 @@ func sendAlerts(alert dto.AlertDTO, alertType, quota, quotaType string, params [
AlertId: alert.ID,
Count: todayCount + 1,
}
_ = xpack.CreateSMSAlertLog(alertType, alert, create, quotaType, params, constant.SMS)
alertErr := xpack.CreateSMSAlertLog(alertType, alert, create, quotaType, params, constant.SMS)
if alertErr != nil {
global.LOG.Infof("%s alert sms push faild, err: %v", alertType, alertErr.Error())
continue
}
alertUtil.CreateNewAlertTask(quota, alertType, quotaType, constant.SMS)
global.LOG.Infof("%s alert sms push successful", alertType)
case constant.Email:
todayCount, isValid := canSendAlertToday(alertType, quotaType, alert.SendCount, constant.Email)
if !isValid {
@@ -598,9 +640,32 @@ func sendAlerts(alert dto.AlertDTO, alertType, quota, quotaType string, params [
create.AlertRule = alertUtil.ProcessAlertRule(alert)
create.AlertDetail = alertUtil.ProcessAlertDetail(alertInfo, quotaType, params, constant.Email)
transport := xpack.LoadRequestTransport()
_ = alertUtil.CreateEmailAlertLog(create, alertInfo, params, transport)
agentInfo, _ := xpack.GetAgentInfo()
alertErr := alertUtil.CreateEmailAlertLog(create, alertInfo, params, transport, agentInfo)
if alertErr != nil {
global.LOG.Infof("%s alert email push faild, err: %v", alertType, alertErr.Error())
continue
}
alertUtil.CreateNewAlertTask(quota, alertType, quotaType, constant.Email)
global.LOG.Infof("%s alert email push successful", alertType)
case constant.WeCom, constant.DingTalk, constant.FeiShu:
todayCount, isValid := canSendAlertToday(alertType, quotaType, alert.SendCount, m)
if !isValid {
continue
}
var create = dto.AlertLogCreate{
Type: alertUtil.GetCronJobType(alert.Type),
AlertId: alert.ID,
Count: todayCount + 1,
}
transport := xpack.LoadRequestTransport()
agentInfo, _ := xpack.GetAgentInfo()
err := xpack.CreateWebhookAlertLog(alertType, alert, create, quotaType, params, m, transport, agentInfo)
if err != nil {
global.LOG.Infof("%s alert webhook %s push faild, err: %v", alertType, m, err)
continue
}
alertUtil.CreateNewAlertTask(quota, alertType, quotaType, m)
default:
}
}
}
@@ -640,7 +705,9 @@ func calculateSSLExpiryDays(sslList []model.WebsiteSSL, cycle uint) (map[int][]s
projectMap := make(map[uint][]time.Time)
for _, ssl := range sslList {
daysDiff := int(ssl.ExpireDate.Sub(currentDate).Hours() / 24)
daysDiff := int(math.Ceil(
ssl.ExpireDate.Sub(currentDate).Hours() / 24,
))
if daysDiff > 0 && int(cycle) >= daysDiff {
daysDiffMap[daysDiff] = append(daysDiffMap[daysDiff], ssl.PrimaryDomain)
projectMap[ssl.ID] = append(projectMap[ssl.ID], ssl.ExpireDate)
@@ -655,7 +722,9 @@ func calculateWebsiteExpiryDays(websites []model.Website, cycle uint) (map[int][
projectMap := make(map[uint][]time.Time)
for _, website := range websites {
daysDiff := int(website.ExpireDate.Sub(currentDate).Hours() / 24)
daysDiff := int(math.Ceil(
website.ExpireDate.Sub(currentDate).Hours() / 24,
))
if daysDiff > 0 && int(cycle) >= daysDiff {
daysDiffMap[daysDiff] = append(daysDiffMap[daysDiff], website.PrimaryDomain)
projectMap[website.ID] = append(projectMap[website.ID], website.ExpireDate)
@@ -819,30 +888,36 @@ func processAllDisks(alert dto.AlertDTO) error {
global.LOG.Errorf("error getting disk list, err: %v", err)
return err
}
var errMsgs []string
for _, item := range diskList {
if success, err := checkAndCreateDiskAlert(alert, item.Path); err == nil && success {
global.LOG.Infof("disk alert pushed successfully for %s", item.Path)
err := checkAndCreateDiskAlert(alert, item.Path)
if err != nil {
errMsg := fmt.Sprintf("disk path %s process failed: %v", item.Path, err)
errMsgs = append(errMsgs, errMsg)
global.LOG.Errorf(errMsg)
continue
}
}
if len(errMsgs) > 0 {
return fmt.Errorf("batch process disks failed, error count: %d, details: %s", len(errMsgs), strings.Join(errMsgs, "; "))
}
return nil
}
func processSingleDisk(alert dto.AlertDTO) error {
success, err := checkAndCreateDiskAlert(alert, alert.Project)
err := checkAndCreateDiskAlert(alert, alert.Project)
if err != nil {
global.LOG.Errorf(err.Error())
return err
}
if success {
global.LOG.Infof("disk alert pushed successfully for %s", alert.Project)
}
return nil
}
func checkAndCreateDiskAlert(alert dto.AlertDTO, path string) (bool, error) {
usageStat, err := disk.Usage(path)
func checkAndCreateDiskAlert(alert dto.AlertDTO, path string) error {
usageStat, err := psutil.DISK.GetUsage(path, false)
if err != nil {
global.LOG.Errorf("error getting disk usage for %s, err: %v", path, err)
return false, err
return err
}
usedTotal, usedStr := calculateUsedTotal(alert.Cycle, usageStat)
@@ -851,13 +926,12 @@ func checkAndCreateDiskAlert(alert dto.AlertDTO, path string) (bool, error) {
commonTotal *= 1024 * 1024 * 1024
}
if usedTotal < commonTotal {
return false, nil
return nil
}
global.LOG.Infof("disk「 %s 」usage: %s", path, usedStr)
params := createAlertDiskParams(path, usedStr)
sender := NewAlertSender(alert, alert.Project)
sender.ResourceSend(path, params)
return true, nil
return nil
}
func calculateUsedTotal(cycle uint, usageStat *disk.UsageStat) (float64, string) {
+60 -7
View File
@@ -30,6 +30,8 @@ func (s *AlertSender) Send(quota string, params []dto.Param) {
s.sendSMS(quota, params)
case constant.Email:
s.sendEmail(quota, params)
case constant.WeCom, constant.DingTalk, constant.FeiShu:
s.sendWebhook(quota, params, method)
}
}
}
@@ -43,6 +45,8 @@ func (s *AlertSender) ResourceSend(quota string, params []dto.Param) {
s.sendResourceSMS(quota, params)
case constant.Email:
s.sendResourceEmail(quota, params)
case constant.WeCom, constant.DingTalk, constant.FeiShu:
s.sendResourceWebhook(quota, params, method)
}
}
}
@@ -64,9 +68,12 @@ func (s *AlertSender) sendSMS(quota string, params []dto.Param) {
Type: s.alert.Type,
}
_ = xpack.CreateSMSAlertLog(s.alert.Type, s.alert, create, quota, params, constant.SMS)
err := xpack.CreateSMSAlertLog(s.alert.Type, s.alert, create, quota, params, constant.SMS)
if err != nil {
global.LOG.Errorf("%s alert sms push failed: %v", s.alert.Type, err)
return
}
alertUtil.CreateNewAlertTask(quota, s.alert.Type, s.quotaType, constant.SMS)
global.LOG.Infof("%s alert sms push successful", s.alert.Type)
}
func (s *AlertSender) sendEmail(quota string, params []dto.Param) {
@@ -85,9 +92,35 @@ func (s *AlertSender) sendEmail(quota string, params []dto.Param) {
}
transport := xpack.LoadRequestTransport()
_ = alertUtil.CreateEmailAlertLog(create, s.alert, params, transport)
agentInfo, _ := xpack.GetAgentInfo()
err := alertUtil.CreateEmailAlertLog(create, s.alert, params, transport, agentInfo)
if err != nil {
global.LOG.Errorf("%s alert email push failed: %v", s.alert.Type, err)
return
}
alertUtil.CreateNewAlertTask(quota, s.alert.Type, s.quotaType, constant.Email)
global.LOG.Infof("%s alert email push successful", s.alert.Type)
}
func (s *AlertSender) sendWebhook(quota string, params []dto.Param, method string) {
totalCount, isValid := s.canSendAlert(method)
if !isValid {
return
}
create := dto.AlertLogCreate{
Status: constant.AlertSuccess,
Count: totalCount + 1,
AlertId: s.alert.ID,
Type: s.alert.Type,
}
transport := xpack.LoadRequestTransport()
agentInfo, _ := xpack.GetAgentInfo()
err := xpack.CreateWebhookAlertLog(s.alert.Type, s.alert, create, quota, params, method, transport, agentInfo)
if err != nil {
global.LOG.Errorf("%s alert %s webhook push failed: %v", s.alert.Type, method, err)
return
}
alertUtil.CreateNewAlertTask(quota, s.alert.Type, s.quotaType, method)
}
func (s *AlertSender) sendResourceSMS(quota string, params []dto.Param) {
@@ -112,7 +145,6 @@ func (s *AlertSender) sendResourceSMS(quota string, params []dto.Param) {
return
}
alertUtil.CreateNewAlertTask(quota, s.alert.Type, s.quotaType, constant.SMS)
global.LOG.Infof("%s alert sms push successful", s.alert.Type)
}
func (s *AlertSender) sendResourceEmail(quota string, params []dto.Param) {
@@ -131,12 +163,33 @@ func (s *AlertSender) sendResourceEmail(quota string, params []dto.Param) {
}
transport := xpack.LoadRequestTransport()
if err := alertUtil.CreateEmailAlertLog(create, s.alert, params, transport); err != nil {
agentInfo, _ := xpack.GetAgentInfo()
if err := alertUtil.CreateEmailAlertLog(create, s.alert, params, transport, agentInfo); err != nil {
global.LOG.Errorf("failed to send Email alert: %v", err)
return
}
alertUtil.CreateNewAlertTask(quota, s.alert.Type, s.quotaType, constant.Email)
global.LOG.Infof("%s alert email push successful", s.alert.Type)
}
func (s *AlertSender) sendResourceWebhook(quota string, params []dto.Param, method string) {
todayCount, isValid := s.canResourceSendAlert(method)
if !isValid {
return
}
create := dto.AlertLogCreate{
Status: constant.AlertSuccess,
Count: todayCount + 1,
AlertId: s.alert.ID,
Type: s.alert.Type,
}
transport := xpack.LoadRequestTransport()
agentInfo, _ := xpack.GetAgentInfo()
if err := xpack.CreateWebhookAlertLog(s.alert.Type, s.alert, create, quota, params, method, transport, agentInfo); err != nil {
global.LOG.Errorf("%s alert %s webhook push failed: %v", s.alert.Type, method, err)
return
}
alertUtil.CreateNewAlertTask(quota, s.alert.Type, s.quotaType, method)
}
func (s *AlertSender) canSendAlert(method string) (uint, bool) {
+151 -287
View File
@@ -12,6 +12,7 @@ import (
"reflect"
"strconv"
"strings"
"sync"
"github.com/gin-gonic/gin"
@@ -25,6 +26,7 @@ import (
"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/appicon"
"github.com/1Panel-dev/1Panel/agent/utils/common"
"github.com/1Panel-dev/1Panel/agent/utils/docker"
"github.com/1Panel-dev/1Panel/agent/utils/files"
@@ -33,26 +35,33 @@ import (
"gopkg.in/yaml.v3"
)
var (
appStoreSyncMu sync.Mutex
appStoreSyncing bool
)
type AppService struct {
}
type IAppService interface {
PageApp(ctx *gin.Context, req request.AppSearch) (interface{}, error)
PageApp(ctx *gin.Context, req request.AppSearch) (*response.AppRes, error)
GetAppTags(ctx *gin.Context) ([]response.TagDTO, error)
GetApp(ctx *gin.Context, key string) (*response.AppDTO, error)
GetAppDetail(appId uint, version, appType string) (response.AppDetailDTO, error)
Install(req request.AppInstallCreate) (*model.AppInstall, error)
Install(req request.AppInstallCreate, executeScript bool) (*model.AppInstall, error)
SyncAppListFromRemote(taskID string) error
GetAppUpdate() (*response.AppUpdateRes, error)
GetAppDetailByID(id uint) (*response.AppDetailDTO, error)
SyncAppListFromLocal(taskID string)
GetAppIcon(key string) ([]byte, string, string, error)
GetAppDetailByKey(appKey, version string) (response.AppDetailSimpleDTO, error)
}
func NewIAppService() IAppService {
return &AppService{}
}
func (a AppService) PageApp(ctx *gin.Context, req request.AppSearch) (interface{}, error) {
func (a AppService) PageApp(ctx *gin.Context, req request.AppSearch) (*response.AppRes, error) {
var opts []repo.DBOption
opts = append(opts, appRepo.OrderByRecommend())
if req.Name != "" {
@@ -98,7 +107,7 @@ func (a AppService) PageApp(ctx *gin.Context, req request.AppSearch) (interface{
}
opts = append(opts, repo.WithByIDs(appIds))
}
var res response.AppRes
res := &response.AppRes{}
total, apps, err := appRepo.Page(req.Page, req.PageSize, opts...)
if err != nil {
@@ -117,25 +126,24 @@ func (a AppService) PageApp(ctx *gin.Context, req request.AppSearch) (interface{
}
}
appDTO := &response.AppItem{
ID: ap.ID,
Name: ap.Name,
Key: ap.Key,
Type: ap.Type,
Icon: ap.Icon,
Resource: ap.Resource,
Limit: ap.Limit,
Website: ap.Website,
Github: ap.Github,
GpuSupport: ap.GpuSupport,
Recommend: ap.Recommend,
Description: ap.GetDescription(ctx),
ID: ap.ID,
Name: ap.Name,
Key: ap.Key,
Limit: ap.Limit,
GpuSupport: ap.GpuSupport,
Recommend: ap.Recommend,
Description: ap.GetDescription(ctx),
Type: ap.Type,
BatchInstallSupport: ap.BatchInstallSupport,
}
appDTOs = append(appDTOs, appDTO)
tags, err := getAppTags(ap.ID, lang)
if err != nil {
continue
}
appDTO.Tags = tags
for _, tag := range tags {
appDTO.Tags = append(appDTO.Tags, tag.Name)
}
if ap.Type == constant.RuntimePHP || ap.Type == constant.RuntimeGo || ap.Type == constant.RuntimeNode || ap.Type == constant.RuntimePython || ap.Type == constant.RuntimeJava || ap.Type == constant.RuntimeDotNet {
details, _ := appDetailRepo.GetBy(appDetailRepo.WithAppId(ap.ID))
var ids []uint
@@ -195,24 +203,7 @@ func (a AppService) GetApp(ctx *gin.Context, key string) (*response.AppDTO, erro
if err != nil {
return nil, err
}
var versionsRaw []string
hasLatest := false
latestVersion := ""
for _, detail := range details {
if strings.Contains(detail.Version, "latest") {
hasLatest = true
latestVersion = detail.Version
continue
}
if key == "openresty" && !common.CompareAppVersion(detail.Version, "1.27") {
continue
}
versionsRaw = append(versionsRaw, detail.Version)
}
appDTO.Versions = common.GetSortedVersions(versionsRaw)
if hasLatest {
appDTO.Versions = append([]string{latestVersion}, appDTO.Versions...)
}
appDTO.Versions = getAppVersions(key, details)
tags, err := getAppTags(app.ID, strings.ToLower(common.GetLang(ctx)))
if err != nil {
return nil, err
@@ -221,6 +212,20 @@ func (a AppService) GetApp(ctx *gin.Context, key string) (*response.AppDTO, erro
return &appDTO, nil
}
func (a AppService) GetAppDetailByKey(appKey, version string) (response.AppDetailSimpleDTO, error) {
var appDetailDTO response.AppDetailSimpleDTO
app, err := appRepo.GetFirst(appRepo.WithKey(appKey))
if err != nil {
return appDetailDTO, err
}
appDetail, err := appDetailRepo.GetFirst(appDetailRepo.WithAppId(app.ID), appDetailRepo.WithVersion(version))
if err != nil {
return appDetailDTO, err
}
appDetailDTO.ID = appDetail.ID
return appDetailDTO, nil
}
func (a AppService) GetAppDetail(appID uint, version, appType string) (response.AppDetailDTO, error) {
var (
appDetailDTO response.AppDetailDTO
@@ -334,7 +339,7 @@ func (a AppService) GetAppDetailByID(id uint) (*response.AppDetailDTO, error) {
return res, nil
}
func (a AppService) Install(req request.AppInstallCreate) (appInstall *model.AppInstall, err error) {
func (a AppService) Install(req request.AppInstallCreate, executeScript bool) (appInstall *model.AppInstall, err error) {
if err = docker.CreateDefaultDockerNetwork(); err != nil {
err = buserr.WithDetail("Err1PanelNetworkFailed", err.Error(), nil)
return
@@ -404,11 +409,21 @@ func (a AppService) Install(req request.AppInstallCreate) (appInstall *model.App
App: app,
}
composeMap := make(map[string]interface{})
var composeRes []byte
if req.EditCompose {
if err = yaml.Unmarshal([]byte(req.DockerCompose), &composeMap); err != nil {
return
}
} else {
if appDetail.DockerCompose == "" {
dockerComposeUrl := fmt.Sprintf("%s/%s/1panel/%s/%s/docker-compose.yml", global.CONF.RemoteURL.AppRepo, global.CONF.Base.Mode, app.Key, appDetail.Version)
_, composeRes, err = req_helper.HandleRequest(dockerComposeUrl, http.MethodGet, constant.TimeOut20s)
if err != nil {
return
}
appDetail.DockerCompose = string(composeRes)
_ = appDetailRepo.Update(context.Background(), appDetail)
}
if err = yaml.Unmarshal([]byte(appDetail.DockerCompose), &composeMap); err != nil {
return
}
@@ -518,8 +533,10 @@ func (a AppService) Install(req request.AppInstallCreate) (appInstall *model.App
if err = copyData(t, app, appDetail, appInstall, req); err != nil {
return err
}
if err = runScript(t, appInstall, "init"); err != nil {
return err
if executeScript {
if err = runScript(t, appInstall, "init"); err != nil {
return err
}
}
if app.Key == "openresty" {
if err = handleSiteDir(app, appDetail, req, t); err != nil {
@@ -794,6 +811,11 @@ func (a AppService) GetAppUpdate() (*response.AppUpdateRes, error) {
res := &response.AppUpdateRes{
CanUpdate: false,
}
mysql, _ := appRepo.GetFirst(appRepo.WithKey("mysql"))
if !mysql.BatchInstallSupport {
res.CanUpdate = true
return res, nil
}
versionUrl := fmt.Sprintf("%s/%s/1panel.json.version.txt", global.CONF.RemoteURL.AppRepo, global.CONF.Base.Mode)
_, versionRes, err := req_helper.HandleRequest(versionUrl, http.MethodGet, constant.TimeOut20s)
@@ -826,6 +848,13 @@ func (a AppService) GetAppUpdate() (*response.AppUpdateRes, error) {
res.CanUpdate = true
return res, err
}
if appicon.IsIconFile(app.Icon) {
fileName, _ := appicon.ParseIconField(app.Icon)
if fileName == "" || !appicon.IconFileExists(fileName) {
res.CanUpdate = true
return res, err
}
}
}
list, err := getAppList()
@@ -864,13 +893,15 @@ func getAppList() (*dto.AppList, error) {
return nil, err
}
listFile := filepath.Join(global.Dir.ResourceDir, "1panel.json")
content, err := os.ReadFile(listFile)
file, err := os.Open(listFile)
if err != nil {
return nil, err
}
if err = json.Unmarshal(content, list); err != nil {
defer file.Close()
if err = json.NewDecoder(file).Decode(list); err != nil {
return nil, err
}
return list, nil
}
@@ -881,283 +912,116 @@ var InitTypes = map[string]struct{}{
}
func deleteCustomApp() {
installs, err := appInstallRepo.ListBy(context.Background())
if err != nil {
global.LOG.Errorf("[AppStore] deleteCustomApp: failed to list installs, skipping: %v", err)
return
}
var appIDS []uint
installs, _ := appInstallRepo.ListBy(context.Background())
for _, install := range installs {
appIDS = append(appIDS, install.AppId)
}
var ops []repo.DBOption
ops = append(ops, repo.WithByIDNotIn(appIDS))
if len(appIDS) > 0 {
ops = append(ops, repo.WithByIDNotIn(appIDS))
}
apps, _ := appRepo.GetBy(ops...)
apps, err := appRepo.GetBy(ops...)
if err != nil {
global.LOG.Errorf("[AppStore] deleteCustomApp: failed to get apps, skipping: %v", err)
return
}
var deleteIDS []uint
for _, app := range apps {
if app.Resource == constant.AppResourceCustom {
deleteIDS = append(deleteIDS, app.ID)
}
}
_ = appRepo.DeleteByIDs(context.Background(), deleteIDS)
_ = appDetailRepo.DeleteByAppIds(context.Background(), deleteIDS)
if len(deleteIDS) == 0 {
return
}
if err = appRepo.DeleteByIDs(context.Background(), deleteIDS); err != nil {
global.LOG.Errorf("[AppStore] deleteCustomApp: failed to delete apps: %v", err)
}
if err = appDetailRepo.DeleteByAppIds(context.Background(), deleteIDS); err != nil {
global.LOG.Errorf("[AppStore] deleteCustomApp: failed to delete app details: %v", err)
}
}
func (a AppService) SyncAppListFromRemote(taskID string) (err error) {
if xpack.IsUseCustomApp() {
return nil
}
appStoreSyncMu.Lock()
global.LOG.Info("[AppStore] sync app from remote task create start")
if appStoreSyncing {
appStoreSyncMu.Unlock()
global.LOG.Info("[AppStore] sync already in progress, skipping")
return nil
}
appStoreSyncing = true
appStoreSyncMu.Unlock()
syncTask, err := task.NewTaskWithOps(i18n.GetMsgByKey("App"), task.TaskSync, task.TaskScopeAppStore, taskID, 0)
if err != nil {
appStoreSyncMu.Lock()
appStoreSyncing = false
appStoreSyncMu.Unlock()
return err
}
syncTask.AddSubTask(task.GetTaskName(i18n.GetMsgByKey("App"), task.TaskSync, task.TaskScopeAppStore), func(t *task.Task) (err error) {
updateRes, err := a.GetAppUpdate()
if err != nil {
return err
}
if !updateRes.CanUpdate {
if updateRes.IsSyncing {
t.Log(i18n.GetMsgByKey("AppStoreIsSyncing"))
return nil
}
t.Log(i18n.GetMsgByKey("AppStoreIsUpToDate"))
return nil
}
list := &dto.AppList{}
if updateRes.AppList == nil {
list, err = getAppList()
if err != nil {
return err
}
} else {
list = updateRes.AppList
}
settingService := NewISettingService()
_ = settingService.Update("AppStoreSyncStatus", constant.StatusSyncing)
setting, err := settingService.GetSettingInfo()
if err != nil {
return err
}
var (
appTags []*model.AppTag
oldAppIds []uint
)
if err = SyncTags(list.Extra); err != nil {
return err
}
deleteCustomApp()
oldApps, err := appRepo.GetBy(appRepo.WithNotLocal())
if err != nil {
return err
}
for _, old := range oldApps {
oldAppIds = append(oldAppIds, old.ID)
}
var sharedCtx *appSyncContext
baseRemoteUrl := fmt.Sprintf("%s/%s/1panel", global.CONF.RemoteURL.AppRepo, global.CONF.Base.Mode)
appsMap := getApps(oldApps, list.Apps, setting.SystemVersion, t)
t.LogStart(i18n.GetMsgByKey("SyncAppDetail"))
for _, l := range list.Apps {
app, ok := appsMap[l.AppProperty.Key]
if !ok {
continue
}
iconStr := ""
_, iconRes, err := req_helper.HandleRequest(l.Icon, http.MethodGet, constant.TimeOut20s)
if err == nil {
if !strings.Contains(string(iconRes), "<xml>") {
iconStr = base64.StdEncoding.EncodeToString(iconRes)
}
}
app.Icon = iconStr
app.TagsKey = l.AppProperty.Tags
if l.AppProperty.Recommend > 0 {
app.Recommend = l.AppProperty.Recommend
} else {
app.Recommend = 9999
}
app.ReadMe = l.ReadMe
app.LastModified = l.LastModified
versions := l.Versions
detailsMap := getAppDetails(app.Details, versions)
for _, v := range versions {
version := v.Name
detail := detailsMap[version]
versionUrl := fmt.Sprintf("%s/%s/%s", baseRemoteUrl, app.Key, version)
paramByte, _ := json.Marshal(v.AppForm)
var appForm dto.AppForm
_ = json.Unmarshal(paramByte, &appForm)
if appForm.SupportVersion > 0 && common.CompareVersion(strconv.FormatFloat(appForm.SupportVersion, 'f', -1, 64), setting.SystemVersion) {
delete(detailsMap, version)
continue
}
if _, ok := InitTypes[app.Type]; ok {
dockerComposeUrl := fmt.Sprintf("%s/%s", versionUrl, "docker-compose.yml")
_, composeRes, err := req_helper.HandleRequest(dockerComposeUrl, http.MethodGet, constant.TimeOut20s)
if err == nil {
detail.DockerCompose = string(composeRes)
}
} else {
detail.DockerCompose = ""
}
detail.Params = string(paramByte)
detail.DownloadUrl = fmt.Sprintf("%s/%s", versionUrl, app.Key+"-"+version+".tar.gz")
detail.DownloadCallBackUrl = v.DownloadCallBackUrl
detail.Update = true
detail.LastModified = v.LastModified
detailsMap[version] = detail
}
var newDetails []model.AppDetail
for _, detail := range detailsMap {
newDetails = append(newDetails, detail)
}
app.Details = newDetails
appsMap[l.AppProperty.Key] = app
}
t.LogSuccess(i18n.GetMsgByKey("SyncAppDetail"))
tags, _ := tagRepo.All()
var (
addAppArray []model.App
updateAppArray []model.App
deleteAppArray []model.App
deleteIds []uint
tagMap = make(map[string]uint, len(tags))
)
for _, v := range appsMap {
if v.ID == 0 {
addAppArray = append(addAppArray, v)
} else {
if v.Status == constant.AppTakeDown {
installs, _ := appInstallRepo.ListBy(context.Background(), appInstallRepo.WithAppId(v.ID))
if len(installs) > 0 {
updateAppArray = append(updateAppArray, v)
continue
}
deleteAppArray = append(deleteAppArray, v)
deleteIds = append(deleteIds, v.ID)
} else {
updateAppArray = append(updateAppArray, v)
}
}
}
tx, ctx := getTxAndContext()
defer func() {
if err != nil {
tx.Rollback()
return
}
}()
if len(addAppArray) > 0 {
if err = appRepo.BatchCreate(ctx, addAppArray); err != nil {
return
}
}
if len(deleteAppArray) > 0 {
if err = appRepo.BatchDelete(ctx, deleteAppArray); err != nil {
return
}
if err = appDetailRepo.DeleteByAppIds(ctx, deleteIds); err != nil {
return
}
}
for _, tag := range tags {
tagMap[tag.Key] = tag.ID
}
for _, update := range updateAppArray {
if err = appRepo.Save(ctx, &update); err != nil {
return
}
}
apps := append(addAppArray, updateAppArray...)
var (
addDetails []model.AppDetail
updateDetails []model.AppDetail
deleteDetails []model.AppDetail
)
for _, app := range apps {
for _, tag := range app.TagsKey {
tagId, ok := tagMap[tag]
if ok {
exist, _ := appTagRepo.GetFirst(ctx, appTagRepo.WithByTagID(tagId), appTagRepo.WithByAppID(app.ID))
if exist == nil {
appTags = append(appTags, &model.AppTag{
AppId: app.ID,
TagId: tagId,
})
}
}
}
for _, d := range app.Details {
d.AppId = app.ID
if d.ID == 0 {
addDetails = append(addDetails, d)
} else {
if d.Status == constant.AppTakeDown {
runtime, _ := runtimeRepo.GetFirst(ctx, runtimeRepo.WithDetailId(d.ID))
if runtime != nil {
updateDetails = append(updateDetails, d)
continue
}
installs, _ := appInstallRepo.ListBy(ctx, appInstallRepo.WithDetailIdsIn([]uint{d.ID}))
if len(installs) > 0 {
updateDetails = append(updateDetails, d)
continue
}
deleteDetails = append(deleteDetails, d)
} else {
updateDetails = append(updateDetails, d)
}
}
}
}
if len(addDetails) > 0 {
if err = appDetailRepo.BatchCreate(ctx, addDetails); err != nil {
return
}
}
if len(deleteDetails) > 0 {
if err = appDetailRepo.BatchDelete(ctx, deleteDetails); err != nil {
return
}
}
for _, u := range updateDetails {
if err = appDetailRepo.Update(ctx, u); err != nil {
return
}
}
if len(oldAppIds) > 0 {
if err = appTagRepo.DeleteByAppIds(ctx, deleteIds); err != nil {
return
}
}
if len(appTags) > 0 {
if err = appTagRepo.BatchCreate(ctx, appTags); err != nil {
return
}
}
tx.Commit()
_ = settingService.Update("AppStoreSyncStatus", constant.StatusSyncSuccess)
_ = settingService.Update("AppStoreLastModified", strconv.Itoa(list.LastModified))
return nil
}, nil)
syncTask.AddSubTask(task.GetTaskName(i18n.GetMsgByKey("App"), task.TaskSync, task.TaskScopeAppStore), a.createSyncAppStoreTask(&sharedCtx), nil)
syncTask.AddSubTask(i18n.GetMsgByKey("SyncAppDetail"), a.createSyncAppStoreMetaTask(&sharedCtx), nil)
go func() {
if err = syncTask.Execute(); err != nil {
_ = NewISettingService().Update("AppStoreLastModified", "0")
_ = NewISettingService().Update("AppStoreSyncStatus", constant.StatusError)
defer func() {
if r := recover(); r != nil {
global.LOG.Errorf("[AppStore] sync goroutine recovered from panic: %v", r)
if updateErr := NewISettingService().Update("AppStoreSyncStatus", constant.StatusError); updateErr != nil {
global.LOG.Warnf("[AppStore] failed to update sync status after panic: %v", updateErr)
}
}
appStoreSyncMu.Lock()
appStoreSyncing = false
appStoreSyncMu.Unlock()
}()
if err := syncTask.Execute(); err != nil {
if updateErr := NewISettingService().Update("AppStoreLastModified", "0"); updateErr != nil {
global.LOG.Warnf("[AppStore] failed to reset last modified: %v", updateErr)
}
if updateErr := NewISettingService().Update("AppStoreSyncStatus", constant.StatusError); updateErr != nil {
global.LOG.Warnf("[AppStore] failed to update sync status to error: %v", updateErr)
}
return
}
}()
global.LOG.Info("[AppStore] sync app from remote task create ok")
return nil
}
func (a AppService) GetAppIcon(key string) ([]byte, string, string, error) {
app, err := appRepo.GetFirst(appRepo.WithKey(key))
if err != nil {
return nil, "", "", err
}
if appicon.IsIconFile(app.Icon) {
fileName, etag := appicon.ParseIconField(app.Icon)
iconBytes, err := appicon.ReadIconFile(fileName)
if err != nil {
global.LOG.Warnf("[AppIcon] read icon file failed key=%s, file=%s, err=%v", key, fileName, err)
return nil, "", "", nil
}
return iconBytes, fileName, etag, nil
}
iconBytes, err := base64.StdEncoding.DecodeString(app.Icon)
if err != nil {
global.LOG.Warnf("[AppIcon] decode base64 icon failed key=%s, err=%v", key, err)
return nil, "", "", nil
}
return iconBytes, "", "", nil
}
+3 -2
View File
@@ -121,7 +121,7 @@ func (a *AppInstallService) Page(req request.AppInstalledSearch) (int64, []respo
}
}
installDTOs, _ := handleInstalled(installs, req.Update, req.Sync)
installDTOs, _ := handleInstalled(installs, req.Update, req.Sync, req.CheckUpdate)
if req.Update {
total = int64(len(installDTOs))
}
@@ -238,7 +238,7 @@ func (a *AppInstallService) SearchForWebsite(req request.AppInstalledSearch) ([]
}
}
return handleInstalled(installs, false, true)
return handleInstalled(installs, false, true, false)
}
func (a *AppInstallService) Operate(req request.AppInstalledOperate) error {
@@ -515,6 +515,7 @@ func (a *AppInstallService) GetServices(key string) ([]response.AppService, erro
} else {
service.From = constant.AppResourceRemote
service.Status = constant.StatusRunning
service.Config = map[string]string{"PANEL_DB_ROOT_PASSWORD": db.Password, "PANEL_DB_ROOT_USER": db.Username}
}
res = append(res, service)
}
+499
View File
@@ -0,0 +1,499 @@
package service
import (
"context"
"encoding/json"
"fmt"
"net/http"
"strconv"
"strings"
"time"
"github.com/1Panel-dev/1Panel/agent/app/dto"
"github.com/1Panel-dev/1Panel/agent/app/model"
"github.com/1Panel-dev/1Panel/agent/app/task"
"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/appicon"
"github.com/1Panel-dev/1Panel/agent/utils/common"
"github.com/1Panel-dev/1Panel/agent/utils/req_helper"
"github.com/1Panel-dev/1Panel/agent/utils/xpack"
)
type appSyncContext struct {
task *task.Task
httpClient http.Client
baseRemoteUrl string
systemVersion string
appsMap map[string]model.App
settingService ISettingService
list *dto.AppList
oldAppIds []uint
appTags []*model.AppTag
skipMetaSync bool
pendingIcons map[string]string
}
func (a AppService) createSyncAppStoreTask(sharedCtx **appSyncContext) func(t *task.Task) error {
return func(t *task.Task) (err error) {
t.LogStart(i18n.GetMsgByKey("AppStore") + " " + i18n.GetMsgByKey("TaskSync"))
updateRes, err := a.GetAppUpdate()
if err != nil {
t.LogFailedWithErr(i18n.GetMsgByKey("CheckAppStoreUpdate"), err)
return err
}
if !updateRes.CanUpdate {
if updateRes.IsSyncing {
t.Log(i18n.GetMsgByKey("AppStoreIsSyncing"))
return nil
}
global.LOG.Infof("[AppStore] Appstore is up to date")
t.Log(i18n.GetMsgByKey("AppStoreIsUpToDate"))
*sharedCtx = &appSyncContext{skipMetaSync: true}
t.LogSuccess(i18n.GetMsgByKey("AppStore") + " " + i18n.GetMsgByKey("TaskSync"))
return nil
}
list := &dto.AppList{}
if updateRes.AppList == nil {
list, err = getAppList()
if err != nil {
t.LogFailedWithErr(i18n.GetMsgByKey("DownloadAppList"), err)
return err
}
} else {
list = updateRes.AppList
}
settingService := NewISettingService()
if err := settingService.Update("AppStoreSyncStatus", constant.StatusSyncing); err != nil {
global.LOG.Warnf("[AppStore] failed to update sync status to syncing: %v", err)
}
setting, err := settingService.GetSettingInfo()
if err != nil {
t.LogFailedWithErr("GetSettingInfo", err)
return err
}
ctx := &appSyncContext{
task: t,
httpClient: http.Client{Timeout: time.Duration(constant.TimeOut20s) * time.Second, Transport: xpack.LoadRequestTransport()},
baseRemoteUrl: fmt.Sprintf("%s/%s/1panel", global.CONF.RemoteURL.AppRepo, global.CONF.Base.Mode),
systemVersion: setting.SystemVersion,
settingService: settingService,
list: list,
appTags: make([]*model.AppTag, 0),
pendingIcons: make(map[string]string),
}
if err = SyncTags(list.Extra); err != nil {
t.LogFailedWithErr(i18n.GetMsgByKey("SyncTags"), err)
return err
}
deleteCustomApp()
oldApps, err := appRepo.GetBy(appRepo.WithNotLocal())
if err != nil {
t.LogFailedWithErr(i18n.GetMsgByKey("LoadLocalApps"), err)
return err
}
ctx.oldAppIds = make([]uint, 0, len(oldApps))
for _, old := range oldApps {
ctx.oldAppIds = append(ctx.oldAppIds, old.ID)
}
ctx.appsMap, ctx.pendingIcons = getApps(oldApps, list.Apps, setting.SystemVersion, t)
var addCount, updateCount, deleteCount int
if err = ctx.classifyAndPersistAppsWithStats(&addCount, &updateCount, &deleteCount); err != nil {
t.LogFailedWithErr(i18n.GetMsgByKey("PersistApps"), err)
return err
}
if err := settingService.Update("AppStoreSyncStatus", constant.StatusSyncSuccess); err != nil {
global.LOG.Warnf("[AppStore] failed to update sync status to success: %v", err)
}
if err := settingService.Update("AppStoreLastModified", strconv.Itoa(list.LastModified)); err != nil {
global.LOG.Warnf("[AppStore] failed to update last modified: %v", err)
}
global.LOG.Infof("[AppStore] Appstore sync completed")
*sharedCtx = ctx
t.LogSuccess(i18n.GetMsgByKey("AppStore") + " " + i18n.GetMsgByKey("TaskSync"))
return nil
}
}
func (c *appSyncContext) syncAppIconsAndDetails() error {
total := len(c.list.Apps)
global.LOG.Infof("[AppStore] sync app detail start, total apps: %d", total)
var (
icon200Count = 0
icon304Count = 0
iconFailCount = 0
)
for i, l := range c.list.Apps {
if (i+1)%10 == 0 {
c.task.LogWithProgress(i18n.GetMsgByKey("SyncAppDetail"), i+1, total)
}
app, ok := c.appsMap[l.AppProperty.Key]
if !ok {
continue
}
iconUrl, hasPending := c.pendingIcons[l.AppProperty.Key]
if hasPending {
status, iconField := c.downloadAppIcon(iconUrl, l.AppProperty.Key, app.Icon)
switch status {
case http.StatusOK:
app.Icon = iconField
icon200Count++
case http.StatusNotModified:
icon304Count++
default:
global.LOG.Warnf("[AppStore] download icon failed url=%s, appKey=%s", iconUrl, l.AppProperty.Key)
iconFailCount++
}
}
app.TagsKey = l.AppProperty.Tags
if l.AppProperty.Recommend > 0 {
app.Recommend = l.AppProperty.Recommend
} else {
app.Recommend = 9999
}
app.ReadMe = l.ReadMe
app.LastModified = l.LastModified
versions := l.Versions
detailsMap := getAppDetails(app.Details, versions)
for _, v := range versions {
version := v.Name
detail := detailsMap[version]
versionUrl := fmt.Sprintf("%s/%s/%s", c.baseRemoteUrl, app.Key, version)
paramByte, _ := json.Marshal(v.AppForm)
var appForm dto.AppForm
_ = json.Unmarshal(paramByte, &appForm)
if appForm.SupportVersion > 0 && common.CompareVersion(strconv.FormatFloat(appForm.SupportVersion, 'f', -1, 64), c.systemVersion) {
delete(detailsMap, version)
continue
}
if _, ok := InitTypes[app.Type]; ok {
dockerComposeUrl := fmt.Sprintf("%s/%s", versionUrl, "docker-compose.yml")
_, composeRes, err := req_helper.HandleRequestWithClient(&c.httpClient, dockerComposeUrl, http.MethodGet, constant.TimeOut20s)
if err == nil {
detail.DockerCompose = string(composeRes)
}
} else {
detail.DockerCompose = ""
}
detail.Params = string(paramByte)
detail.DownloadUrl = fmt.Sprintf("%s/%s", versionUrl, app.Key+"-"+version+".tar.gz")
detail.DownloadCallBackUrl = v.DownloadCallBackUrl
detail.Update = true
detail.LastModified = v.LastModified
detailsMap[version] = detail
}
var newDetails []model.AppDetail
for _, detail := range detailsMap {
newDetails = append(newDetails, detail)
}
app.Details = newDetails
c.appsMap[l.AppProperty.Key] = app
}
global.LOG.Infof("[AppStore] icon download completed - total: %d, success(200): %d, cached(304): %d, failed: %d",
total, icon200Count, icon304Count, iconFailCount)
return nil
}
func (c *appSyncContext) downloadAppIcon(iconUrl, appKey, oldIcon string) (status int, iconField string) {
iconFileName, existingEtag := appicon.ParseIconField(oldIcon)
reqHeaders := make(map[string]string)
if existingEtag != "" && iconFileName != "" && appicon.IconFileExists(iconFileName) {
reqHeaders["If-None-Match"] = existingEtag
}
resp, err := req_helper.HandleRequestWithHeaders(&c.httpClient, iconUrl, http.MethodGet, constant.TimeOut20s, reqHeaders)
if err != nil {
global.LOG.Warnf("[AppStore] request icon failed url=%s, err=%v", iconUrl, err)
return 0, ""
}
if resp.StatusCode == http.StatusNotModified {
return http.StatusNotModified, ""
}
if resp.StatusCode != http.StatusOK {
global.LOG.Warnf("[AppStore] download icon failed url=%s, status=%d", iconUrl, resp.StatusCode)
return 0, ""
}
if len(resp.Body) == 0 {
global.LOG.Warnf("[AppStore] download icon empty body url=%s", iconUrl)
return 0, ""
}
if resp.Body[0] == '<' {
global.LOG.Warnf("[AppStore] download icon got HTML response url=%s", iconUrl)
return 0, ""
}
contentType := resp.Header.Get("Content-Type")
ct := strings.TrimSpace(strings.Split(contentType, ";")[0])
if strings.ToLower(ct) != "image/png" {
global.LOG.Warnf("[AppStore] unexpected icon content-type: %s, expected image/png, url=%s", ct, iconUrl)
}
fileName, err := appicon.WriteIconFile(appKey, resp.Body)
if err != nil {
global.LOG.Warnf("[AppStore] write icon file failed appKey=%s, err=%v", appKey, err)
return 0, ""
}
newEtag := resp.Header.Get("ETag")
iconField = appicon.BuildIconField(fileName, newEtag)
return http.StatusOK, iconField
}
func (a AppService) createSyncAppStoreMetaTask(sharedCtx **appSyncContext) func(t *task.Task) error {
return func(t *task.Task) (err error) {
t.LogStart(i18n.GetMsgByKey("SyncAppDetail"))
ctx := *sharedCtx
if ctx == nil {
global.LOG.Warnf("[AppStore] meta sync skipped: shared context is nil")
t.Log(i18n.GetMsgByKey("SyncAppDetail") + " skipped: shared context is nil")
return nil
}
if ctx.skipMetaSync {
global.LOG.Infof("[AppStore] meta sync skipped: no update needed")
t.Log(i18n.GetMsgByKey("SyncAppDetail") + " skipped: no update needed")
return nil
}
if ctx.list == nil || ctx.appsMap == nil {
global.LOG.Errorf("[AppStore] meta sync failed: shared context data not initialized")
err := fmt.Errorf("shared context data not initialized")
t.LogFailedWithErr(i18n.GetMsgByKey("SyncAppDetail"), err)
return err
}
t.Logf("%s: %d apps", i18n.GetMsgByKey("SyncAppDetail"), len(ctx.list.Apps))
ctx.task = t
ctx.appTags = make([]*model.AppTag, 0)
if err = ctx.syncAppIconsAndDetails(); err != nil {
t.LogFailedWithErr(i18n.GetMsgByKey("SyncAppDetail"), err)
return err
}
if err = ctx.classifyAndPersistApps(); err != nil {
t.LogFailedWithErr(i18n.GetMsgByKey("PersistAppDetails"), err)
return err
}
global.LOG.Infof("[AppStore] Appstore meta sync completed")
return nil
}
}
func (c *appSyncContext) classifyAndPersistApps() (err error) {
var addCount, updateCount, deleteCount int
return c.classifyAndPersistAppsWithStats(&addCount, &updateCount, &deleteCount)
}
func (c *appSyncContext) classifyAndPersistAppsWithStats(addCount, updateCount, deleteCount *int) (err error) {
tags, _ := tagRepo.All()
var (
addAppArray []model.App
updateAppArray []model.App
deleteAppArray []model.App
deleteIds []uint
tagMap = make(map[string]uint, len(tags))
)
for _, v := range c.appsMap {
if v.ID == 0 {
addAppArray = append(addAppArray, v)
} else {
if v.Status == constant.AppTakeDown {
installs, _ := appInstallRepo.ListBy(context.Background(), appInstallRepo.WithAppId(v.ID))
if len(installs) > 0 {
updateAppArray = append(updateAppArray, v)
continue
}
deleteAppArray = append(deleteAppArray, v)
deleteIds = append(deleteIds, v.ID)
} else {
updateAppArray = append(updateAppArray, v)
}
}
}
if len(addAppArray) > 0 {
addKeys := make([]string, 0, len(addAppArray))
for _, app := range addAppArray {
addKeys = append(addKeys, app.Key)
}
existingApps, _ := appRepo.GetBy(appRepo.WithKeyIn(addKeys))
if len(existingApps) > 0 {
existingMap := make(map[string]model.App, len(existingApps))
for _, e := range existingApps {
existingMap[e.Key] = e
}
filteredAdd := make([]model.App, 0, len(addAppArray))
for _, app := range addAppArray {
if existing, ok := existingMap[app.Key]; ok {
app.ID = existing.ID
if len(app.Details) == 0 {
app.Details = existing.Details
}
updateAppArray = append(updateAppArray, app)
} else {
filteredAdd = append(filteredAdd, app)
}
}
addAppArray = filteredAdd
}
}
*addCount = len(addAppArray)
*updateCount = len(updateAppArray)
*deleteCount = len(deleteAppArray)
tx, ctx := getTxAndContext()
defer func() {
if err != nil {
tx.Rollback()
return
}
}()
if len(addAppArray) > 0 {
if err = appRepo.BatchCreate(ctx, addAppArray); err != nil {
return
}
}
if len(deleteAppArray) > 0 {
if err = appRepo.BatchDelete(ctx, deleteAppArray); err != nil {
return
}
if err = appDetailRepo.DeleteByAppIds(ctx, deleteIds); err != nil {
return
}
}
for _, tag := range tags {
tagMap[tag.Key] = tag.ID
}
if len(updateAppArray) > 0 {
for _, update := range updateAppArray {
if err = appRepo.Save(ctx, &update); err != nil {
return
}
}
}
apps := append(addAppArray, updateAppArray...)
var (
addDetails []model.AppDetail
updateDetails []model.AppDetail
deleteDetails []model.AppDetail
)
totalDetails := 0
for _, app := range apps {
for _, tag := range app.TagsKey {
tagId, ok := tagMap[tag]
if ok {
exist, _ := appTagRepo.GetFirst(ctx, appTagRepo.WithByTagID(tagId), appTagRepo.WithByAppID(app.ID))
if exist == nil {
c.appTags = append(c.appTags, &model.AppTag{
AppId: app.ID,
TagId: tagId,
})
}
}
}
for _, d := range app.Details {
totalDetails++
d.AppId = app.ID
if d.ID == 0 {
addDetails = append(addDetails, d)
} else {
if d.Status == constant.AppTakeDown {
runtime, _ := runtimeRepo.GetFirst(ctx, runtimeRepo.WithDetailId(d.ID))
if runtime != nil {
updateDetails = append(updateDetails, d)
continue
}
installs, _ := appInstallRepo.ListBy(ctx, appInstallRepo.WithDetailIdsIn([]uint{d.ID}))
if len(installs) > 0 {
updateDetails = append(updateDetails, d)
continue
}
deleteDetails = append(deleteDetails, d)
} else {
updateDetails = append(updateDetails, d)
}
}
}
}
if len(addDetails) > 0 {
if err = appDetailRepo.BatchCreate(ctx, addDetails); err != nil {
return
}
}
if len(deleteDetails) > 0 {
if err = appDetailRepo.BatchDelete(ctx, deleteDetails); err != nil {
return
}
}
if len(updateDetails) > 0 {
for _, u := range updateDetails {
if err = appDetailRepo.Update(ctx, u); err != nil {
return
}
}
}
if len(c.oldAppIds) > 0 {
if err = appTagRepo.DeleteByAppIds(ctx, deleteIds); err != nil {
return
}
}
if len(c.appTags) > 0 {
if err = appTagRepo.BatchCreate(ctx, c.appTags); err != nil {
return
}
}
if err = tx.Commit().Error; err != nil {
return
}
return nil
}
+131 -22
View File
@@ -6,7 +6,6 @@ import (
"encoding/base64"
"encoding/json"
"fmt"
"log"
"maps"
"math"
"net/http"
@@ -45,6 +44,7 @@ import (
"github.com/compose-spec/compose-go/v2/types"
"github.com/docker/docker/api/types/container"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
"github.com/subosito/gotenv"
"gopkg.in/yaml.v3"
)
@@ -300,10 +300,11 @@ func createLink(ctx context.Context, installTask *task.Task, app model.App, appI
createMysql.Name = dbConfig.DbName
createMysql.Username = dbConfig.DbUser
createMysql.Database = database.Name
createMysql.Format = "utf8mb4"
createMysql.Format = dbConfig.Format
createMysql.Permission = "%"
createMysql.Password = dbConfig.Password
createMysql.From = database.From
createMysql.Collation = dbConfig.Collation
mysqldb, err := NewIMysqlService().Create(ctx, createMysql)
if err != nil {
return err
@@ -391,6 +392,10 @@ func deleteAppInstall(deleteReq request.AppInstallDelete) error {
if err = appInstallRepo.Delete(ctx, install); err != nil {
return err
}
appKey := install.App.Key
if appKey == constant.AppOpenclaw {
_ = agentRepo.DeleteByAppInstallIDWithCtx(ctx, install.ID)
}
resources, _ := appInstallResourceRepo.GetBy(appInstallResourceRepo.WithAppInstallId(install.ID))
if len(resources) > 0 {
@@ -540,8 +545,18 @@ func handleUpgradeCompose(install model.AppInstall, detail model.AppDetail) (map
if !ok || oldValue == nil {
return nil, buserr.New("ErrFileParse")
}
oldValueMap := oldValue.(map[string]interface{})
oldServiceValue := oldValueMap[install.ServiceName].(map[string]interface{})
oldValueMap, ok := oldValue.(map[string]interface{})
if !ok {
return nil, buserr.New("ErrFileParse")
}
oldServiceValueInterface, ok := oldValueMap[install.ServiceName]
if !ok || oldServiceValueInterface == nil {
return nil, buserr.New("ErrFileParse")
}
oldServiceValue, ok := oldServiceValueInterface.(map[string]interface{})
if !ok {
return nil, buserr.New("ErrFileParse")
}
if oldServiceValue["deploy"] != nil {
serviceValue["deploy"] = oldServiceValue["deploy"]
}
@@ -753,6 +768,30 @@ func upgradeInstall(req request.AppInstallUpgrade) error {
command := exec.Command("/bin/bash", "-c", fmt.Sprintf("cp -rn %s/* %s || true", detailDir, install.GetPath()))
_, _ = command.CombinedOutput()
if install.App.Key == constant.AppOpenresty {
installBuildDir := path.Join(install.GetPath(), "build")
detailBuildDir := path.Join(detailDir, "build")
if !fileOp.Stat(installBuildDir) {
if err := fileOp.CreateDir(installBuildDir, constant.DirPerm); err != nil {
return err
}
}
if err := fileOp.DeleteDir(path.Join(installBuildDir, "tmp")); err != nil {
return err
}
if err := fileOp.CopyDir(path.Join(detailBuildDir, "tmp"), installBuildDir); err != nil {
return err
}
if err := fileOp.CopyFile(path.Join(detailBuildDir, "Dockerfile"), installBuildDir); err != nil {
return err
}
if err := fileOp.CopyFile(path.Join(detailBuildDir, "nginx.conf"), installBuildDir); err != nil {
return err
}
if err := fileOp.CopyFile(path.Join(detailBuildDir, "nginx.vh.default.conf"), installBuildDir); err != nil {
return err
}
}
sourceScripts := path.Join(detailDir, "scripts")
if fileOp.Stat(sourceScripts) {
dstScripts := path.Join(install.GetPath(), "scripts")
@@ -948,7 +987,7 @@ func handleMap(params map[string]interface{}, envParams map[string]string) {
}
}
func downloadApp(app model.App, appDetail model.AppDetail, appInstall *model.AppInstall, logger *log.Logger) (err error) {
func downloadApp(app model.App, appDetail model.AppDetail, appInstall *model.AppInstall, logger *logrus.Logger) (err error) {
if app.IsLocalApp() || app.IsCustomApp() {
return nil
}
@@ -1260,8 +1299,9 @@ func getAppDetails(details []model.AppDetail, versions []dto.AppConfigVersion) m
return appDetails
}
func getApps(oldApps []model.App, items []dto.AppDefine, systemVersion string, task *task.Task) map[string]model.App {
func getApps(oldApps []model.App, items []dto.AppDefine, systemVersion string, task *task.Task) (map[string]model.App, map[string]string) {
apps := make(map[string]model.App, len(oldApps))
pendingIcons := make(map[string]string, len(items))
for _, old := range oldApps {
old.Status = constant.AppTakeDown
apps[old.Key] = old
@@ -1301,9 +1341,13 @@ func getApps(oldApps []model.App, items []dto.AppDefine, systemVersion string, t
app.MemoryRequired = config.MemoryRequired
app.Architectures = strings.Join(config.Architectures, ",")
app.GpuSupport = config.GpuSupport
app.BatchInstallSupport = config.BatchInstallSupport
if item.Icon != "" {
pendingIcons[key] = item.Icon
}
apps[key] = app
}
return apps
return apps, pendingIcons
}
func handleLocalAppDetail(versionDir string, appDetail *model.AppDetail) error {
@@ -1414,6 +1458,7 @@ func handleLocalApp(appDir string) (app *model.App, err error) {
app.Key = "local" + appDefine.Key
app.Architectures = strings.Join(appDefine.Architectures, ",")
app.GpuSupport = appDefine.GpuSupport
app.MemoryRequired = appDefine.MemoryRequired
app.Resource = constant.AppResourceLocal
app.Status = constant.AppNormal
@@ -1518,7 +1563,7 @@ func synAppInstall(containers map[string]container.Summary, appInstall *model.Ap
_ = appInstallRepo.Save(context.Background(), appInstall)
}
func handleInstalled(appInstallList []model.AppInstall, updated bool, sync bool) ([]response.AppInstallDTO, error) {
func handleInstalled(appInstallList []model.AppInstall, updated, sync, checkUpdate bool) ([]response.AppInstallDTO, error) {
var (
res []response.AppInstallDTO
containersMap map[string]container.Summary
@@ -1541,6 +1586,7 @@ func handleInstalled(appInstallList []model.AppInstall, updated bool, sync bool)
if updated && ignoreUpdate(installed) {
continue
}
if sync && !doNotNeedSync(installed) {
synAppInstall(containersMap, &installed, false)
}
@@ -1555,7 +1601,6 @@ func handleInstalled(appInstallList []model.AppInstall, updated bool, sync bool)
Message: installed.Message,
HttpPort: installed.HttpPort,
HttpsPort: installed.HttpsPort,
Icon: installed.App.Icon,
AppName: installed.App.Name,
AppKey: installed.App.Key,
AppType: installed.App.Type,
@@ -1571,20 +1616,30 @@ func handleInstalled(appInstallList []model.AppInstall, updated bool, sync bool)
Container: installed.ContainerName,
ServiceName: strings.ToLower(installed.ServiceName),
}
if !updated {
if !updated && !checkUpdate {
installDTO.LinkDB = hasLinkDB(installed.ID)
res = append(res, installDTO)
continue
}
if installed.Version == "latest" {
if checkUpdate {
installDTO.CanUpdate = false
installDTO.LinkDB = hasLinkDB(installed.ID)
res = append(res, installDTO)
}
continue
}
installDTO.DockerCompose = installed.DockerCompose
installDTO.IsEdit = isEditCompose(installed)
details, err := appDetailRepo.GetBy(appDetailRepo.WithAppId(installed.App.ID))
if err != nil {
return nil, err
}
var versions []string
for _, appDetail := range details {
ignores, _ := appIgnoreUpgradeRepo.List(runtimeRepo.WithDetailId(appDetail.ID), appIgnoreUpgradeRepo.WithScope("version"))
@@ -1596,11 +1651,19 @@ func handleInstalled(appInstallList []model.AppInstall, updated bool, sync bool)
}
versions = append(versions, appDetail.Version)
}
if len(versions) == 0 {
if checkUpdate {
installDTO.CanUpdate = false
installDTO.LinkDB = hasLinkDB(installed.ID)
res = append(res, installDTO)
}
continue
}
versions = common.GetSortedVersions(versions)
lastVersion := versions[0]
if installed.App.Key == constant.AppMysql || installed.App.Key == constant.AppMysqlCluster {
for _, version := range versions {
majorVersion := getMajorVersion(installed.Version)
@@ -1612,15 +1675,23 @@ func handleInstalled(appInstallList []model.AppInstall, updated bool, sync bool)
}
}
}
if common.IsCrossVersion(installed.Version, lastVersion) {
installDTO.CanUpdate = installed.App.CrossVersionUpdate
} else {
installDTO.CanUpdate = common.CompareVersion(lastVersion, installed.Version)
}
if installDTO.CanUpdate {
if updated {
if installDTO.CanUpdate {
res = append(res, installDTO)
}
} else if checkUpdate {
installDTO.LinkDB = hasLinkDB(installed.ID)
res = append(res, installDTO)
}
}
return res, nil
}
@@ -1949,23 +2020,26 @@ func handleSiteDir(app model.App, appDetail model.AppDetail, req request.AppInst
req.Params["WEBSITE_DIR"] = siteDir
oldWebStePath, _ := settingRepo.GetValueByKey("WEBSITE_DIR")
fileOp := files.NewFileOp()
movePath := func(src, dst string) error {
return cmd.NewCommandMgr().Run("mv", src, dst)
}
if oldWebStePath != "" && oldWebStePath != siteDir && fileOp.Stat(oldWebStePath) {
t.Log(i18n.GetWithName("MoveSiteDir", siteDir))
if fileOp.Stat(siteDir) {
if fileOp.Stat(path.Join(siteDir, "conf.d")) {
_ = fileOp.Rename(path.Join(siteDir, "conf.d"), path.Join(siteDir, "conf.d.bak"))
_ = movePath(path.Join(siteDir, "conf.d"), path.Join(siteDir, "conf.d.bak"))
}
if fileOp.Stat(path.Join(siteDir, "sites")) {
_ = fileOp.Rename(path.Join(siteDir, "sites"), path.Join(siteDir, "sites.bak"))
_ = movePath(path.Join(siteDir, "sites"), path.Join(siteDir, "sites.bak"))
}
if err := fileOp.Rename(path.Join(oldWebStePath, "sites"), path.Join(siteDir, "sites")); err != nil {
if err := movePath(path.Join(oldWebStePath, "sites"), path.Join(siteDir, "sites")); err != nil {
return err
}
if err := fileOp.Rename(path.Join(oldWebStePath, "conf.d"), path.Join(siteDir, "conf.d")); err != nil {
if err := movePath(path.Join(oldWebStePath, "conf.d"), path.Join(siteDir, "conf.d")); err != nil {
return err
}
} else {
err := fileOp.Rename(oldWebStePath, siteDir)
err := movePath(oldWebStePath, siteDir)
if err != nil {
return err
}
@@ -2080,15 +2154,21 @@ func handleSSLConfig(appInstall *model.AppInstall, hasDefaultWebsite bool, sslRe
return nil
}
func SyncTags(remoteProperties dto.ExtraProperties) error {
func SyncTags(remoteProperties dto.ExtraProperties) (err error) {
tx, ctx := getTxAndContext()
defer tx.Rollback()
localTags, _ := tagRepo.All()
defer func() {
if err != nil {
tx.Rollback()
}
}()
localTags, err := tagRepo.All()
if err != nil {
return err
}
localTagsMap := make(map[string]*model.Tag)
for i := range localTags {
localTagsMap[localTags[i].Key] = &localTags[i]
}
var err error
remoteTagsMap := make(map[string]*dto.Tag)
for i := range remoteProperties.Tags {
remoteTagsMap[remoteProperties.Tags[i].Key] = &remoteProperties.Tags[i]
@@ -2096,7 +2176,9 @@ func SyncTags(remoteProperties dto.ExtraProperties) error {
for key, localTag := range localTagsMap {
if _, exists := remoteTagsMap[key]; !exists {
_ = tagRepo.DeleteByID(ctx, localTag.ID)
if err = tagRepo.DeleteByID(ctx, localTag.ID); err != nil {
return err
}
}
}
@@ -2126,7 +2208,9 @@ func SyncTags(remoteProperties dto.ExtraProperties) error {
}
}
tx.Commit()
if err = tx.Commit().Error; err != nil {
return err
}
return nil
}
@@ -2163,3 +2247,28 @@ func isEditCompose(installed model.AppInstall) bool {
}
return false
}
func getAppVersions(key string, details []model.AppDetail) []string {
var (
versionsRaw []string
versions []string
)
hasLatest := false
latestVersion := ""
for _, detail := range details {
if key != "mssql" && strings.Contains(detail.Version, "latest") {
hasLatest = true
latestVersion = detail.Version
continue
}
if key == "openresty" && !common.CompareAppVersion(detail.Version, "1.27") {
continue
}
versionsRaw = append(versionsRaw, detail.Version)
}
versions = common.GetSortedVersions(versionsRaw)
if hasLatest {
versions = append([]string{latestVersion}, versions...)
}
return versions
}
+52 -39
View File
@@ -34,6 +34,7 @@ type IBackupService interface {
LoadBackupOptions() ([]dto.BackupOption, error)
SearchWithPage(search dto.SearchPageWithType) (int64, interface{}, error)
Create(backupDto dto.BackupOperate) error
CheckConn(req dto.BackupOperate) dto.BackupCheckRes
GetBuckets(backupDto dto.ForBuckets) ([]interface{}, error)
Update(req dto.BackupOperate) error
Delete(id uint) error
@@ -71,7 +72,7 @@ func (u *BackupService) GetLocalDir() (string, error) {
}
func (u *BackupService) SearchWithPage(req dto.SearchPageWithType) (int64, interface{}, error) {
options := []repo.DBOption{repo.WithOrderBy("created_at desc")}
options := []repo.DBOption{repo.WithOrderDesc("created_at")}
if len(req.Type) != 0 {
options = append(options, repo.WithByType(req.Type))
}
@@ -123,6 +124,43 @@ func (u *BackupService) SearchWithPage(req dto.SearchPageWithType) (int64, inter
return count, data, nil
}
func (u *BackupService) CheckConn(req dto.BackupOperate) dto.BackupCheckRes {
var res dto.BackupCheckRes
var backup model.BackupAccount
if err := copier.Copy(&backup, &req); err != nil {
res.Msg = i18n.GetMsgWithDetail("ErrStructTransform", err.Error())
return res
}
itemAccessKey, err := base64.StdEncoding.DecodeString(backup.AccessKey)
if err != nil {
res.Msg = err.Error()
return res
}
backup.AccessKey = string(itemAccessKey)
itemCredential, err := base64.StdEncoding.DecodeString(backup.Credential)
if err != nil {
res.Msg = err.Error()
return res
}
backup.Credential = string(itemCredential)
if req.Type == constant.OneDrive || req.Type == constant.GoogleDrive {
refreshToken, err := loadRefreshTokenByCode(&backup)
if err != nil {
res.Msg = err.Error()
return res
}
res.Token = base64.StdEncoding.EncodeToString([]byte(refreshToken))
}
isOk, err := u.checkBackupConn(&backup)
if err != nil {
res.Msg = err.Error()
return res
}
res.IsOk = isOk
return res
}
func (u *BackupService) Create(req dto.BackupOperate) error {
if req.Type == constant.Local {
return buserr.New("ErrBackupLocalCreate")
@@ -147,19 +185,6 @@ func (u *BackupService) Create(req dto.BackupOperate) error {
return err
}
backup.Credential = string(itemCredential)
if req.Type == constant.OneDrive || req.Type == constant.GoogleDrive || req.Type == constant.ALIYUN {
if err := loadRefreshTokenByCode(&backup); err != nil {
return err
}
}
if req.Type != constant.Local {
isOk, err := u.checkBackupConn(&backup)
if err != nil || !isOk {
return buserr.WithMap("ErrBackupCheck", map[string]interface{}{"err": err.Error()}, err)
}
}
backup.AccessKey, err = encrypt.StringEncrypt(backup.AccessKey)
if err != nil {
return err
@@ -248,16 +273,7 @@ func (u *BackupService) Update(req dto.BackupOperate) error {
global.Dir.LocalBackupDir = newBackup.BackupPath
}
if newBackup.Type == constant.OneDrive || newBackup.Type == constant.GoogleDrive || newBackup.Type == constant.ALIYUN {
if err := loadRefreshTokenByCode(&newBackup); err != nil {
return err
}
}
if backup.Type != constant.Local {
isOk, err := u.checkBackupConn(&newBackup)
if err != nil || !isOk {
return buserr.WithMap("ErrBackupCheck", map[string]interface{}{"err": err.Error()}, err)
}
newBackup.AccessKey, err = encrypt.StringEncrypt(newBackup.AccessKey)
if err != nil {
return err
@@ -356,7 +372,7 @@ func (u *BackupService) checkBackupConn(backup *model.BackupAccount) (bool, erro
}
func (u *BackupService) LoadBackupOptions() ([]dto.BackupOption, error) {
accounts, err := backupRepo.List(repo.WithOrderBy("created_at desc"))
accounts, err := backupRepo.List(repo.WithOrderDesc("created_at"))
if err != nil {
return nil, err
}
@@ -514,37 +530,34 @@ func newClient(account *model.BackupAccount, isEncrypt bool) (cloud_storage.Clou
return client, nil
}
func loadRefreshTokenByCode(backup *model.BackupAccount) error {
func loadRefreshTokenByCode(backup *model.BackupAccount) (string, error) {
varMap := make(map[string]interface{})
if err := json.Unmarshal([]byte(backup.Vars), &varMap); err != nil {
return fmt.Errorf("unmarshal backup vars failed, err: %v", err)
return "", fmt.Errorf("unmarshal backup vars failed, err: %v", err)
}
if token, ok := varMap["refresh_token"]; ok && len(token.(string)) != 0 {
return "", nil
}
refreshToken := ""
var err error
if backup.Type == constant.GoogleDrive {
switch backup.Type {
case constant.GoogleDrive:
refreshToken, err = client.RefreshGoogleToken("authorization_code", "refreshToken", varMap)
if err != nil {
return err
return "", err
}
}
if backup.Type == constant.OneDrive {
case constant.OneDrive:
refreshToken, err = client.RefreshToken("authorization_code", "refreshToken", varMap)
if err != nil {
return err
return "", err
}
}
if backup.Type != constant.ALIYUN {
delete(varMap, "code")
varMap["refresh_token"] = refreshToken
}
varMap["refresh_status"] = constant.StatusSuccess
varMap["refresh_time"] = time.Now().Format(constant.DateTimeLayout)
itemVars, err := json.Marshal(varMap)
if err != nil {
return fmt.Errorf("json marshal var map failed, err: %v", err)
}
itemVars, _ := json.Marshal(varMap)
backup.Vars = string(itemVars)
return nil
return refreshToken, nil
}
func loadBackupNamesByID(accountIDs string, downloadID uint) ([]string, string, error) {
+25 -13
View File
@@ -8,17 +8,14 @@ import (
"strings"
"time"
"github.com/1Panel-dev/1Panel/agent/app/repo"
"github.com/1Panel-dev/1Panel/agent/constant"
"github.com/1Panel-dev/1Panel/agent/i18n"
"github.com/1Panel-dev/1Panel/agent/buserr"
"github.com/1Panel-dev/1Panel/agent/app/dto"
"github.com/1Panel-dev/1Panel/agent/app/model"
"github.com/1Panel-dev/1Panel/agent/app/repo"
"github.com/1Panel-dev/1Panel/agent/app/task"
"github.com/1Panel-dev/1Panel/agent/buserr"
"github.com/1Panel-dev/1Panel/agent/constant"
"github.com/1Panel-dev/1Panel/agent/global"
"github.com/1Panel-dev/1Panel/agent/i18n"
"github.com/1Panel-dev/1Panel/agent/utils/common"
"github.com/1Panel-dev/1Panel/agent/utils/files"
"github.com/1Panel-dev/1Panel/agent/utils/mysql/client"
@@ -47,7 +44,7 @@ func (u *BackupService) MysqlBackup(req dto.CommonBackup) error {
return err
}
databaseHelper := DatabaseHelper{Database: req.Name, DBType: req.Type, Name: req.DetailName}
databaseHelper := DatabaseHelper{Database: req.Name, DBType: req.Type, Name: req.DetailName, Args: req.Args}
if err := handleMysqlBackup(databaseHelper, nil, record.ID, targetDir, fileName, req.TaskID, req.Secret); err != nil {
backupRepo.UpdateRecordByMap(record.ID, map[string]interface{}{"status": constant.StatusFailed, "message": err.Error()})
return err
@@ -60,11 +57,11 @@ func (u *BackupService) MysqlRecover(req dto.CommonRecover) error {
}
func (u *BackupService) MysqlRecoverByUpload(req dto.CommonRecover) error {
recoveFile, err := loadSqlFile(req.File)
recoverFile, err := loadSqlFile(req.File)
if err != nil {
return err
}
req.File = recoveFile
req.File = recoverFile
if err := handleMysqlRecover(req, nil, false); err != nil {
return err
@@ -90,11 +87,16 @@ func handleMysqlBackup(db DatabaseHelper, parentTask *task.Task, recordID uint,
}
}
itemHandler := func() error { return doMysqlBackup(db, targetDir, fileName, secret) }
itemHandler := func() error {
if len(db.Args) != 0 {
backupTask.Logf("%s: %v", i18n.GetMsgByKey("Arg"), db.Args)
}
return doMysqlBackup(db, targetDir, fileName, secret)
}
if parentTask != nil {
return itemHandler()
}
backupTask.AddSubTaskWithOps(task.GetTaskName(itemName, task.TaskBackup, task.TaskScopeBackup), func(t *task.Task) error { return itemHandler() }, nil, 3, time.Hour)
backupTask.AddSubTaskWithOps(task.GetTaskName(itemName, task.TaskBackup, task.TaskScopeBackup), func(t *task.Task) error { return itemHandler() }, nil, 0, 3*time.Hour)
go func() {
if err := backupTask.Execute(); err != nil {
backupRepo.UpdateRecordByMap(recordID, map[string]interface{}{"status": constant.StatusFailed, "message": err.Error()})
@@ -196,7 +198,16 @@ func handleMysqlRecover(req dto.CommonRecover, parentTask *task.Task, isRollback
return recoverDatabase(parentTask)
}
itemTask.AddSubTaskWithOps(i18n.GetMsgByKey("TaskRecover"), recoverDatabase, nil, 3, time.Hour)
var timeout time.Duration
switch req.Timeout {
case -1:
timeout = 0
case 0:
timeout = 3 * time.Hour
default:
timeout = time.Duration(req.Timeout) * time.Second
}
itemTask.AddSubTaskWithOps(i18n.GetMsgByKey("TaskRecover"), recoverDatabase, nil, 0, timeout)
go func() {
_ = itemTask.Execute()
}()
@@ -219,6 +230,7 @@ func doMysqlBackup(db DatabaseHelper, targetDir, fileName, secret string) error
Format: dbInfo.Format,
TargetDir: targetDir,
FileName: fileName,
Args: db.Args,
}
if err := cli.Backup(backupInfo); err != nil {
return err
+24 -8
View File
@@ -17,7 +17,6 @@ import (
"github.com/1Panel-dev/1Panel/agent/utils/common"
"github.com/1Panel-dev/1Panel/agent/utils/files"
"github.com/1Panel-dev/1Panel/agent/utils/postgresql/client"
pgclient "github.com/1Panel-dev/1Panel/agent/utils/postgresql/client"
)
func (u *BackupService) PostgresqlBackup(req dto.CommonBackup) error {
@@ -56,11 +55,11 @@ func (u *BackupService) PostgresqlRecover(req dto.CommonRecover) error {
}
func (u *BackupService) PostgresqlRecoverByUpload(req dto.CommonRecover) error {
recoveFile, err := loadSqlFile(req.File)
recoverFile, err := loadSqlFile(req.File)
if err != nil {
return err
}
req.File = recoveFile
req.File = recoverFile
if err := handlePostgresqlRecover(req, nil, false); err != nil {
return err
}
@@ -82,11 +81,11 @@ func handlePostgresqlBackup(db DatabaseHelper, parentTask *task.Task, recordID u
}
}
itemHandler := func() error { return doPostgresqlgBackup(db, targetDir, fileName, secret) }
itemHandler := func() error { return doPostgresqlBackup(db, targetDir, fileName, secret, backupTask) }
if parentTask != nil {
return itemHandler()
}
backupTask.AddSubTaskWithOps(task.GetTaskName(itemName, task.TaskBackup, task.TaskScopeBackup), func(t *task.Task) error { return itemHandler() }, nil, 3, time.Hour)
backupTask.AddSubTaskWithOps(task.GetTaskName(itemName, task.TaskBackup, task.TaskScopeBackup), func(t *task.Task) error { return itemHandler() }, nil, 0, 3*time.Hour)
go func() {
if err := backupTask.Execute(); err != nil {
backupRepo.UpdateRecordByMap(recordID, map[string]interface{}{"status": constant.StatusFailed, "message": err.Error()})
@@ -130,10 +129,12 @@ func handlePostgresqlRecover(req dto.CommonRecover, parentTask *task.Task, isRol
if !isRollback {
rollbackFile := path.Join(global.Dir.TmpDir, fmt.Sprintf("database/%s/%s_%s.sql.gz", req.Type, req.DetailName, time.Now().Format(constant.DateTimeSlimLayout)))
if err := cli.Backup(client.BackupInfo{
Database: req.Name,
Name: req.DetailName,
TargetDir: path.Dir(rollbackFile),
FileName: path.Base(rollbackFile),
Task: t,
Timeout: 300,
}); err != nil {
return fmt.Errorf("backup postgresql db %s for rollback before recover failed, err: %v", req.DetailName, err)
@@ -142,9 +143,11 @@ func handlePostgresqlRecover(req dto.CommonRecover, parentTask *task.Task, isRol
if !isOk {
global.LOG.Info("recover failed, start to rollback now")
if err := cli.Recover(client.RecoverInfo{
Database: req.Name,
Name: req.DetailName,
SourceFile: rollbackFile,
Task: t,
Timeout: 300,
}); err != nil {
global.LOG.Errorf("rollback postgresql db %s from %s failed, err: %v", req.DetailName, rollbackFile, err)
@@ -167,9 +170,11 @@ func handlePostgresqlRecover(req dto.CommonRecover, parentTask *task.Task, isRol
t.LogWithStatus(i18n.GetMsgByKey("Decrypt"), err)
}
if err := cli.Recover(client.RecoverInfo{
Database: req.Name,
Name: req.DetailName,
SourceFile: req.File,
Username: dbInfo.Username,
Task: t,
Timeout: 300,
}); err != nil {
global.LOG.Errorf("recover postgresql db %s from %s failed, err: %v", req.DetailName, req.File, err)
@@ -182,24 +187,35 @@ func handlePostgresqlRecover(req dto.CommonRecover, parentTask *task.Task, isRol
return recoverDatabase(parentTask)
}
itemTask.AddSubTaskWithOps(i18n.GetMsgByKey("TaskRecover"), recoverDatabase, nil, 3, time.Hour)
var timeout time.Duration
switch req.Timeout {
case -1:
timeout = 0
case 0:
timeout = 3 * time.Hour
default:
timeout = time.Duration(req.Timeout) * time.Second
}
itemTask.AddSubTaskWithOps(i18n.GetMsgByKey("TaskRecover"), recoverDatabase, nil, 0, timeout)
go func() {
_ = itemTask.Execute()
}()
return nil
}
func doPostgresqlgBackup(db DatabaseHelper, targetDir, fileName, secret string) error {
func doPostgresqlBackup(db DatabaseHelper, targetDir, fileName, secret string, task *task.Task) error {
cli, err := LoadPostgresqlClientByFrom(db.Database)
if err != nil {
return err
}
defer cli.Close()
backupInfo := pgclient.BackupInfo{
backupInfo := client.BackupInfo{
Database: db.Database,
Name: db.Name,
TargetDir: targetDir,
FileName: fileName,
Task: task,
Timeout: 300,
}
if err := cli.Backup(backupInfo); err != nil {
+5 -5
View File
@@ -38,7 +38,7 @@ func NewIBackupRecordService() IBackupRecordService {
func (u *BackupRecordService) SearchRecordsWithPage(search dto.RecordSearch) (int64, []dto.BackupRecords, error) {
total, records, err := backupRepo.PageRecord(
search.Page, search.PageSize,
repo.WithOrderBy("created_at desc"),
repo.WithOrderDesc("created_at"),
repo.WithByName(search.Name),
repo.WithByType(search.Type),
repo.WithByDetailName(search.DetailName),
@@ -69,7 +69,7 @@ func (u *BackupRecordService) SearchRecordsWithPage(search dto.RecordSearch) (in
func (u *BackupRecordService) SearchRecordsByCronjobWithPage(search dto.RecordSearchByCronjob) (int64, []dto.BackupRecords, error) {
total, records, err := backupRepo.PageRecord(
search.Page, search.PageSize,
repo.WithOrderBy("created_at desc"),
repo.WithOrderDesc("created_at"),
backupRepo.WithByCronID(search.CronjobID),
)
if err != nil {
@@ -166,7 +166,7 @@ func (u *BackupRecordService) BatchDeleteRecord(ids []uint) error {
func (u *BackupRecordService) ListAppRecords(name, detailName, fileName string) ([]model.BackupRecord, error) {
records, err := backupRepo.ListRecord(
repo.WithOrderBy("created_at asc"),
repo.WithOrderAsc("created_at"),
repo.WithByName(name),
repo.WithByType("app"),
backupRepo.WithFileNameStartWith(fileName),
@@ -221,7 +221,7 @@ func (u *BackupRecordService) LoadRecordSize(req dto.SearchForSize) ([]dto.Recor
list = append(list, backupSizeHelper{ID: item.ID, DownloadID: item.DownloadAccountID, FilePath: fmt.Sprintf("system_snapshot/%s.tar.gz", item.Name)})
}
case "cronjob":
_, records, err := backupRepo.PageRecord(req.Page, req.PageSize, repo.WithOrderBy("created_at desc"), backupRepo.WithByCronID(req.CronjobID))
_, records, err := backupRepo.PageRecord(req.Page, req.PageSize, repo.WithOrderDesc("created_at"), backupRepo.WithByCronID(req.CronjobID))
if err != nil {
return nil, err
}
@@ -231,7 +231,7 @@ func (u *BackupRecordService) LoadRecordSize(req dto.SearchForSize) ([]dto.Recor
default:
_, records, err := backupRepo.PageRecord(
req.Page, req.PageSize,
repo.WithOrderBy("created_at desc"),
repo.WithOrderDesc("created_at"),
repo.WithByName(req.Name),
repo.WithByType(req.Type),
repo.WithByDetailName(req.DetailName),
+2 -2
View File
@@ -131,7 +131,7 @@ func handleWebsiteRecover(website *model.Website, parentTask *task.Task, recover
if err != nil {
return err
}
if err = fileOp.CopyFile(fmt.Sprintf("%s/%s.conf", tmpPath, website.Alias), GetOpenrestyDir(SiteConfDir)); err != nil {
if err = fileOp.CopyFile(fmt.Sprintf("%s/%s.conf", tmpPath, website.Alias), GetConfDir(*website)); err != nil {
return err
}
@@ -259,7 +259,7 @@ func doWebsiteBackup(website *model.Website, parentTask *task.Task, backupDir, f
if err := fileOp.SaveFile(tmpDir+"/website.json", string(remarkInfo), fs.ModePerm); err != nil {
return err
}
nginxConfFile := GetSitePath(*website, SiteConf)
nginxConfFile := GetWebsiteConfigPath(*website)
if err := fileOp.CopyFile(nginxConfFile, tmpDir); err != nil {
return err
}
+3 -3
View File
@@ -96,7 +96,7 @@ func (c *ClamService) LoadBaseInfo() (dto.ClamBaseInfo, error) {
}
}
} else {
_ = clam.StopAllClamJob(false, clamRepo)
_ = clam.CheckWithStopAll(false, clamRepo)
}
if baseInfo.FreshIsActive {
version, err := cmd.RunDefaultWithStdoutBashC("freshclam --version")
@@ -313,7 +313,7 @@ func (c *ClamService) Delete(req dto.ClamDelete) error {
}
func (c *ClamService) HandleOnce(id uint) error {
if active := clam.StopAllClamJob(true, clamRepo); !active {
if active := clam.CheckWithStopAll(true, clamRepo); !active {
return buserr.New("ErrClamdscanNotFound")
}
clamItem, _ := clamRepo.Get(repo.WithByID(id))
@@ -337,9 +337,9 @@ func (c *ClamService) HandleOnce(id uint) error {
clamRepo.EndRecords(record, constant.StatusFailed, err.Error())
return
}
handleAlert(record.InfectedFiles, clamItem.Name, clamItem.ID)
clam.AnalysisFromLog(taskItem.LogFile, &record)
clamRepo.EndRecords(record, constant.StatusDone, "")
handleAlert(record.InfectedFiles, clamItem.Name, clamItem.ID)
}()
return nil
}
+240 -191
View File
@@ -19,12 +19,8 @@ import (
"syscall"
"time"
"github.com/1Panel-dev/1Panel/agent/app/repo"
"github.com/gin-gonic/gin"
"github.com/pkg/errors"
"github.com/1Panel-dev/1Panel/agent/app/dto"
"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"
@@ -45,7 +41,9 @@ import (
"github.com/docker/docker/api/types/volume"
"github.com/docker/docker/client"
"github.com/docker/go-connections/nat"
"github.com/gin-gonic/gin"
v1 "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/pkg/errors"
"github.com/shirou/gopsutil/v4/cpu"
"github.com/shirou/gopsutil/v4/mem"
)
@@ -61,29 +59,35 @@ type IContainerService interface {
ListNetwork() ([]dto.Options, error)
PageVolume(req dto.SearchWithPage) (int64, interface{}, error)
ListVolume() ([]dto.Options, error)
PageCompose(req dto.SearchWithPage) (int64, interface{}, error)
LoadComposeEnv(name string) (string, error)
CreateCompose(req dto.ComposeCreate) error
ComposeOperation(req dto.ComposeOperation) error
TestCompose(req dto.ComposeCreate) (bool, error)
ComposeUpdate(req dto.ComposeUpdate) error
ComposeLogClean(req dto.ComposeLogClean) error
ContainerCreate(req dto.ContainerOperate, inThread bool) error
ContainerCreateByCommand(req dto.ContainerCreateByCommand) error
ContainerUpdate(req dto.ContainerOperate) error
ContainerUpgrade(req dto.ContainerUpgrade) error
ContainerInfo(req dto.OperationWithName) (*dto.ContainerOperate, error)
ContainerListStats() ([]dto.ContainerListStats, error)
ContainerItemStats(req dto.OperationWithName) (dto.ContainerItemStats, error)
LoadResourceLimit() (*dto.ResourceLimit, error)
ContainerRename(req dto.ContainerRename) error
ContainerCommit(req dto.ContainerCommit) error
ContainerLogClean(req dto.OperationWithName) error
ContainerOperation(req dto.ContainerOperation) error
DownloadContainerLogs(containerType, container, since, tail string, c *gin.Context) error
DownloadContainerLogs(containerType, container, since, tail string, timestamp bool, c *gin.Context) error
ContainerStats(id string) (*dto.ContainerStats, error)
Inspect(req dto.InspectReq) (string, error)
DeleteNetwork(req dto.BatchDelete) error
CreateNetwork(req dto.NetworkCreate) error
DeleteVolume(req dto.BatchDelete) error
CreateVolume(req dto.VolumeCreate) error
TestCompose(req dto.ComposeCreate) (bool, error)
ComposeUpdate(req dto.ComposeUpdate) error
Prune(req dto.ContainerPrune) error
LoadUsers(req dto.OperationWithName) []string
@@ -96,19 +100,12 @@ func NewIContainerService() IContainerService {
}
func (u *ContainerService) Page(req dto.PageContainer) (int64, interface{}, error) {
var (
records []container.Summary
list []container.Summary
)
client, err := docker.NewDockerClient()
if err != nil {
return 0, nil, err
}
defer client.Close()
options := container.ListOptions{
All: true,
Size: true,
}
options := container.ListOptions{All: true}
if len(req.Filters) != 0 {
options.Filters = filters.NewArgs()
options.Filters.Add("label", req.Filters)
@@ -117,114 +114,32 @@ func (u *ContainerService) Page(req dto.PageContainer) (int64, interface{}, erro
if err != nil {
return 0, nil, err
}
if req.ExcludeAppStore {
for _, item := range containers {
if created, ok := item.Labels[composeCreatedBy]; ok && created == "Apps" {
continue
}
list = append(list, item)
}
} else {
list = containers
}
records := searchWithFilter(req, containers)
if len(req.Name) != 0 {
length, count := len(list), 0
for count < length {
if !strings.Contains(list[count].Names[0][1:], req.Name) && !strings.Contains(list[count].Image, req.Name) {
list = append(list[:count], list[(count+1):]...)
length--
} else {
count++
}
}
}
if req.State != "all" {
length, count := len(list), 0
for count < length {
if list[count].State != req.State {
list = append(list[:count], list[(count+1):]...)
length--
} else {
count++
}
}
}
switch req.OrderBy {
case "name":
sort.Slice(list, func(i, j int) bool {
if req.Order == constant.OrderAsc {
return list[i].Names[0][1:] < list[j].Names[0][1:]
}
return list[i].Names[0][1:] > list[j].Names[0][1:]
})
default:
sort.Slice(list, func(i, j int) bool {
if req.Order == constant.OrderAsc {
return list[i].Created < list[j].Created
}
return list[i].Created > list[j].Created
})
}
total, start, end := len(list), (req.Page-1)*req.PageSize, req.Page*req.PageSize
var backData []dto.ContainerInfo
total, start, end := len(records), (req.Page-1)*req.PageSize, req.Page*req.PageSize
if start > total {
records = make([]container.Summary, 0)
backData = make([]dto.ContainerInfo, 0)
} else {
if end >= total {
end = total
}
records = list[start:end]
backData = records[start:end]
}
backDatas := make([]dto.ContainerInfo, len(records))
for i := 0; i < len(records); i++ {
item := records[i]
IsFromCompose := false
if _, ok := item.Labels[composeProjectLabel]; ok {
IsFromCompose = true
}
IsFromApp := false
if created, ok := item.Labels[composeCreatedBy]; ok && created == "Apps" {
IsFromApp = true
}
exposePorts := transPortToStr(records[i].Ports)
info := dto.ContainerInfo{
ContainerID: item.ID,
CreateTime: time.Unix(item.Created, 0).Format(constant.DateTimeLayout),
Name: item.Names[0][1:],
ImageId: strings.Split(item.ImageID, ":")[1],
ImageName: item.Image,
State: item.State,
RunTime: item.Status,
Ports: exposePorts,
IsFromApp: IsFromApp,
IsFromCompose: IsFromCompose,
SizeRw: item.SizeRw,
SizeRootFs: item.SizeRootFs,
}
install, _ := appInstallRepo.GetFirst(appInstallRepo.WithContainerName(info.Name))
for i := 0; i < len(backData); i++ {
install, _ := appInstallRepo.GetFirst(appInstallRepo.WithContainerName(backData[i].Name))
if install.ID > 0 {
info.AppInstallName = install.Name
info.AppName = install.App.Name
backData[i].AppInstallName = install.Name
backData[i].AppName = install.App.Name
websites, _ := websiteRepo.GetBy(websiteRepo.WithAppInstallId(install.ID))
for _, website := range websites {
info.Websites = append(info.Websites, website.PrimaryDomain)
backData[i].Websites = append(backData[i].Websites, website.PrimaryDomain)
}
}
backDatas[i] = info
if item.NetworkSettings != nil && len(item.NetworkSettings.Networks) > 0 {
networks := make([]string, 0, len(item.NetworkSettings.Networks))
for key := range item.NetworkSettings.Networks {
networks = append(networks, item.NetworkSettings.Networks[key].IPAddress)
}
sort.Strings(networks)
backDatas[i].Network = networks
}
}
return int64(total), backDatas, nil
return int64(total), backData, nil
}
func (u *ContainerService) List() []dto.ContainerOptions {
@@ -287,13 +202,60 @@ func (u *ContainerService) LoadStatus() (dto.ContainerStatus, error) {
defer client.Close()
c := context.Background()
usage, err := client.DiskUsage(c, types.DiskUsageOptions{})
images, _ := client.ImageList(c, image.ListOptions{All: true})
data.ImageCount = len(images)
repo, _ := imageRepoRepo.List()
data.RepoCount = len(repo)
templates, _ := composeRepo.List()
data.ComposeTemplateCount = len(templates)
networks, _ := client.NetworkList(c, network.ListOptions{})
data.NetworkCount = len(networks)
volumes, _ := client.VolumeList(c, volume.ListOptions{})
data.VolumeCount = len(volumes.Volumes)
data.ComposeCount = loadComposeCount(client)
containers, _ := client.ContainerList(c, container.ListOptions{All: true})
data.ContainerCount = len(containers)
for _, item := range containers {
switch item.State {
case "created":
data.Created++
case "running":
data.Running++
case "paused":
data.Paused++
case "restarting":
data.Restarting++
case "dead":
data.Dead++
case "exited":
data.Exited++
case "removing":
data.Removing++
}
}
return data, nil
}
func (u *ContainerService) ContainerItemStats(req dto.OperationWithName) (dto.ContainerItemStats, error) {
var data dto.ContainerItemStats
client, err := docker.NewDockerClient()
if err != nil {
return data, err
}
if req.Name != "system" {
defer client.Close()
containerInfo, _, err := client.ContainerInspectWithRaw(context.Background(), req.Name, true)
if err != nil {
return data, err
}
data.SizeRw = *containerInfo.SizeRw
data.SizeRootFs = *containerInfo.SizeRootFs
return data, nil
}
data.ImageCount = len(usage.Images)
data.VolumeCount = len(usage.Volumes)
usage, err := client.DiskUsage(context.Background(), types.DiskUsageOptions{})
if err != nil {
return data, err
}
for _, item := range usage.Images {
data.ImageUsage += item.Size
if item.Containers < 1 {
@@ -315,34 +277,6 @@ func (u *ContainerService) LoadStatus() (dto.ContainerStatus, error) {
for _, item := range usage.BuildCache {
data.BuildCacheUsage += item.Size
}
repo, _ := imageRepoRepo.List()
data.RepoCount = len(repo)
templates, _ := composeRepo.List()
data.ComposeTemplateCount = len(templates)
networks, _ := client.NetworkList(c, network.ListOptions{})
data.NetworkCount = len(networks)
volumes, _ := client.VolumeList(c, volume.ListOptions{})
data.VolumeCount = len(volumes.Volumes)
data.ComposeCount = loadComposeCount(client)
data.ContainerCount = len(usage.Containers)
for _, item := range usage.Containers {
switch item.State {
case "created":
data.Created++
case "running":
data.Running++
case "paused":
data.Paused++
case "restarting":
data.Restarting++
case "dead":
data.Dead++
case "exited":
data.Exited++
case "removing":
data.Removing++
}
}
return data, nil
}
func (u *ContainerService) ContainerListStats() ([]dto.ContainerListStats, error) {
@@ -430,6 +364,9 @@ func (u *ContainerService) Inspect(req dto.InspectReq) (string, error) {
}
for _, container := range containers {
config := container.Labels[composeConfigLabel]
if len(req.Detail) != 0 && strings.Contains(config, req.Detail) {
config = req.Detail
}
workdir := container.Labels[composeWorkdirLabel]
if len(config) != 0 && len(workdir) != 0 && strings.Contains(config, workdir) {
filePath = config
@@ -502,7 +439,6 @@ func (u *ContainerService) Prune(req dto.ContainerPrune) error {
}
}
taskItem.Log(i18n.GetMsgByKey("PruneStart"))
DeletedNumber := 0
SpaceReclaimed := 0
switch req.PruneType {
case "container":
@@ -510,21 +446,18 @@ func (u *ContainerService) Prune(req dto.ContainerPrune) error {
if err != nil {
return err
}
DeletedNumber = len(rep.ContainersDeleted)
SpaceReclaimed = int(rep.SpaceReclaimed)
case "image":
rep, err := client.ImagesPrune(context.Background(), pruneFilters)
if err != nil {
return err
}
DeletedNumber = len(rep.ImagesDeleted)
SpaceReclaimed = int(rep.SpaceReclaimed)
case "network":
rep, err := client.NetworksPrune(context.Background(), pruneFilters)
_, err := client.NetworksPrune(context.Background(), pruneFilters)
if err != nil {
return err
}
DeletedNumber = len(rep.NetworksDeleted)
case "volume":
versions, err := client.ServerVersion(context.Background())
if err != nil {
@@ -537,7 +470,6 @@ func (u *ContainerService) Prune(req dto.ContainerPrune) error {
if err != nil {
return err
}
DeletedNumber = len(rep.VolumesDeleted)
SpaceReclaimed = int(rep.SpaceReclaimed)
case "buildcache":
opts := build.CachePruneOptions{}
@@ -546,10 +478,9 @@ func (u *ContainerService) Prune(req dto.ContainerPrune) error {
if err != nil {
return err
}
DeletedNumber = len(rep.CachesDeleted)
SpaceReclaimed = int(rep.SpaceReclaimed)
}
taskItem.Log(i18n.GetMsgWithMap("PruneHelper", map[string]interface{}{"name": i18n.GetMsgByKey(name), "count": DeletedNumber, "size": common.LoadSizeUnit2F(float64(SpaceReclaimed))}))
taskItem.Log(i18n.GetMsgWithMap("PruneHelper", map[string]interface{}{"name": i18n.GetMsgByKey(name), "size": common.LoadSizeUnit2F(float64(SpaceReclaimed))}))
return nil
}, nil)
go func() {
@@ -667,27 +598,27 @@ func (u *ContainerService) ContainerInfo(req dto.OperationWithName) (*dto.Contai
data.Name = strings.ReplaceAll(oldContainer.Name, "/", "")
data.Image = oldContainer.Config.Image
if oldContainer.NetworkSettings != nil {
for network := range oldContainer.NetworkSettings.Networks {
data.Network = network
break
for net, val := range oldContainer.NetworkSettings.Networks {
netItem := dto.ContainerNetwork{
Network: net,
MacAddr: val.MacAddress,
}
if val.IPAMConfig != nil {
if netItem.Network != "bridge" {
netItem.Ipv4 = val.IPAMConfig.IPv4Address
netItem.Ipv6 = val.IPAMConfig.IPv6Address
}
} else {
if netItem.Network != "bridge" {
netItem.Ipv4 = val.IPAddress
}
}
data.Networks = append(data.Networks, netItem)
}
}
exposePorts, _ := loadPortByInspect(oldContainer.ID, client)
data.ExposedPorts = loadContainerPortForInfo(exposePorts)
networkSettings := oldContainer.NetworkSettings
bridgeNetworkSettings := networkSettings.Networks[data.Network]
if bridgeNetworkSettings.IPAMConfig != nil {
data.MacAddr = bridgeNetworkSettings.MacAddress
if data.Network != "bridge" {
data.Ipv4 = bridgeNetworkSettings.IPAMConfig.IPv4Address
data.Ipv6 = bridgeNetworkSettings.IPAMConfig.IPv6Address
}
} else {
if data.Network != "bridge" {
data.Ipv4 = bridgeNetworkSettings.IPAddress
}
}
data.Hostname = oldContainer.Config.Hostname
data.DNS = oldContainer.HostConfig.DNS
data.DomainName = oldContainer.Config.Domainname
@@ -1025,6 +956,9 @@ func collectLogs(done <-chan struct{}, params dto.StreamLog, messageChan chan<-
if params.Follow {
cmdArgs = append(cmdArgs, "-f")
}
if params.Timestamp {
cmdArgs = append(cmdArgs, "-t")
}
if params.Tail != "0" {
cmdArgs = append(cmdArgs, "--tail", params.Tail)
}
@@ -1037,12 +971,18 @@ func collectLogs(done <-chan struct{}, params dto.StreamLog, messageChan chan<-
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...)
dockerComposeCmd := common.GetDockerComposeCommand()
var yamlFiles []string
for _, item := range strings.Split(params.Compose, ",") {
if len(item) != 0 {
yamlFiles = append(yamlFiles, "-f", item)
}
}
if dockerComposeCmd == "docker-compose" {
newCmdArgs := append(yamlFiles, cmdArgs...)
dockerCmd = exec.Command(dockerComposeCmd, newCmdArgs...)
} else {
newCmdArgs := append([]string{"compose", "-f", params.Compose}, cmdArgs...)
newCmdArgs := append(append([]string{"compose"}, yamlFiles...), cmdArgs...)
dockerCmd = exec.Command("docker", newCmdArgs...)
}
} else {
@@ -1115,17 +1055,23 @@ func collectLogs(done <-chan struct{}, params dto.StreamLog, messageChan chan<-
_ = dockerCmd.Wait()
}
func (u *ContainerService) DownloadContainerLogs(containerType, container, since, tail string, c *gin.Context) error {
func (u *ContainerService) DownloadContainerLogs(containerType, container, since, tail string, timestamp bool, c *gin.Context) error {
if cmd.CheckIllegal(container, since, tail) {
return buserr.New("ErrCmdIllegal")
}
commandArg := []string{"logs", container}
dockerCommand := global.CONF.DockerConfig.Command
if containerType == "compose" {
var yamlFiles []string
for _, item := range strings.Split(container, ",") {
if len(item) != 0 {
yamlFiles = append(yamlFiles, "-f", item)
}
}
if dockerCommand == "docker-compose" {
commandArg = []string{"-f", container, "logs"}
commandArg = append(yamlFiles, "logs")
} else {
commandArg = []string{"compose", "-f", container, "logs"}
commandArg = append(append([]string{"compose"}, yamlFiles...), "logs")
}
}
@@ -1137,6 +1083,9 @@ func (u *ContainerService) DownloadContainerLogs(containerType, container, since
commandArg = append(commandArg, "--since")
commandArg = append(commandArg, since)
}
if timestamp {
commandArg = append(commandArg, "-t")
}
var dockerCmd *exec.Cmd
if containerType == "compose" && dockerCommand == "docker-compose" {
dockerCmd = exec.Command("docker-compose", commandArg...)
@@ -1212,10 +1161,8 @@ func (u *ContainerService) ContainerStats(id string) (*dto.ContainerStats, error
body, err := io.ReadAll(res.Body)
if err != nil {
res.Body.Close()
return nil, err
}
res.Body.Close()
var stats *container.StatsResponse
if err := json.Unmarshal(body, &stats); err != nil {
return nil, err
@@ -1505,25 +1452,25 @@ func loadConfigInfo(isCreate bool, req dto.ContainerOperate, oldContainer *conta
config.User = req.User
config.WorkingDir = req.WorkingDir
if len(req.Network) != 0 {
switch req.Network {
case "host", "none", "bridge":
hostConf.NetworkMode = container.NetworkMode(req.Network)
}
if req.Ipv4 != "" || req.Ipv6 != "" {
networkConf.EndpointsConfig = map[string]*network.EndpointSettings{req.Network: {
IPAMConfig: &network.EndpointIPAMConfig{
IPv4Address: req.Ipv4,
IPv6Address: req.Ipv6,
}, MacAddress: req.MacAddr}}
} else {
networkConf.EndpointsConfig = map[string]*network.EndpointSettings{req.Network: {}}
if len(req.Networks) != 0 {
networkConf.EndpointsConfig = make(map[string]*network.EndpointSettings)
for _, item := range req.Networks {
switch item.Network {
case "host", "none", "bridge":
hostConf.NetworkMode = container.NetworkMode(item.Network)
}
if item.Ipv4 != "" || item.Ipv6 != "" {
networkConf.EndpointsConfig[item.Network] = &network.EndpointSettings{
IPAMConfig: &network.EndpointIPAMConfig{
IPv4Address: item.Ipv4,
IPv6Address: item.Ipv6,
}, MacAddress: item.MacAddr}
} else {
networkConf.EndpointsConfig[item.Network] = &network.EndpointSettings{}
}
}
} else {
if req.Ipv4 != "" || req.Ipv6 != "" {
return nil, nil, nil, fmt.Errorf("please set up the network")
}
networkConf = network.NetworkingConfig{}
return nil, nil, nil, fmt.Errorf("please set up the network")
}
hostConf.Privileged = req.Privileged
@@ -1771,3 +1718,105 @@ func loadContainerPortForInfo(itemPorts []container.Port) []dto.PortHelper {
}
return exposedPorts
}
func searchWithFilter(req dto.PageContainer, containers []container.Summary) []dto.ContainerInfo {
var (
records []dto.ContainerInfo
list []container.Summary
)
if req.ExcludeAppStore {
for _, item := range containers {
if created, ok := item.Labels[composeCreatedBy]; ok && created == "Apps" {
continue
}
list = append(list, item)
}
} else {
list = containers
}
if len(req.Name) != 0 {
length, count := len(list), 0
for count < length {
if !strings.Contains(list[count].Names[0][1:], req.Name) && !strings.Contains(list[count].Image, req.Name) {
list = append(list[:count], list[(count+1):]...)
length--
} else {
count++
}
}
}
if req.State != "all" {
length, count := len(list), 0
for count < length {
if list[count].State != req.State {
list = append(list[:count], list[(count+1):]...)
length--
} else {
count++
}
}
}
switch req.OrderBy {
case "name":
sort.Slice(list, func(i, j int) bool {
if req.Order == constant.OrderAsc {
return list[i].Names[0][1:] < list[j].Names[0][1:]
}
return list[i].Names[0][1:] > list[j].Names[0][1:]
})
default:
sort.Slice(list, func(i, j int) bool {
if req.Order == constant.OrderAsc {
return list[i].Created < list[j].Created
}
return list[i].Created > list[j].Created
})
}
for _, item := range list {
IsFromCompose := false
if _, ok := item.Labels[composeProjectLabel]; ok {
IsFromCompose = true
}
IsFromApp := false
if created, ok := item.Labels[composeCreatedBy]; ok && created == "Apps" {
IsFromApp = true
}
exposePorts := transPortToStr(item.Ports)
info := dto.ContainerInfo{
ContainerID: item.ID,
CreateTime: time.Unix(item.Created, 0).Format(constant.DateTimeLayout),
Name: item.Names[0][1:],
Ports: exposePorts,
ImageId: strings.Split(item.ImageID, ":")[1],
ImageName: item.Image,
State: item.State,
RunTime: item.Status,
IsFromApp: IsFromApp,
IsFromCompose: IsFromCompose,
}
if item.NetworkSettings != nil && len(item.NetworkSettings.Networks) > 0 {
networks := make([]string, 0, len(item.NetworkSettings.Networks))
for key := range item.NetworkSettings.Networks {
networks = append(networks, item.NetworkSettings.Networks[key].IPAddress)
}
sort.Strings(networks)
info.Network = networks
}
records = append(records, info)
}
descriptions, _ := settingRepo.GetDescriptionList(repo.WithByType("container"))
for i := 0; i < len(records); i++ {
for _, desc := range descriptions {
if desc.ID == records[i].ContainerID {
records[i].Description = desc.Description
records[i].IsPinned = desc.IsPinned
}
}
}
sort.Slice(records, func(i, j int) bool {
return records[i].IsPinned && !records[j].IsPinned
})
return records
}
+119 -57
View File
@@ -2,10 +2,12 @@ package service
import (
"bufio"
"context"
"errors"
"fmt"
"os"
"path"
"path/filepath"
"sort"
"strings"
"time"
@@ -23,7 +25,6 @@ import (
"github.com/1Panel-dev/1Panel/agent/utils/docker"
"github.com/docker/docker/api/types/container"
"github.com/docker/docker/api/types/filters"
"golang.org/x/net/context"
)
const composeProjectLabel = "com.docker.compose.project"
@@ -73,6 +74,7 @@ func (u *ContainerService) PageCompose(req dto.SearchWithPage) (int64, interface
Name: container.Names[0][1:],
State: container.State,
CreateTime: time.Unix(container.Created, 0).Format(constant.DateTimeLayout),
Ports: transPortToStr(container.Ports),
}
if compose, has := composeMap[name]; has {
compose.ContainerCount++
@@ -179,7 +181,7 @@ func (u *ContainerService) TestCompose(req dto.ComposeCreate) (bool, error) {
cmd := getComposeCmd(req.Path, "config")
stdout, err := cmd.CombinedOutput()
if err != nil {
return false, errors.New(string(stdout))
return false, fmt.Errorf("docker-compose config failed, std: %s, err: %v", string(stdout), err)
}
return true, nil
}
@@ -203,14 +205,13 @@ func (u *ContainerService) CreateCompose(req dto.ComposeCreate) error {
}
go func() {
taskItem.AddSubTask(i18n.GetMsgByKey("ComposeCreate"), func(t *task.Task) error {
cmd := getComposeCmd(req.Path, "up")
out, err := cmd.CombinedOutput()
taskItem.Log(i18n.GetWithName("ComposeCreateRes", string(out)))
err := compose.UpWithTask(req.Path, t, req.ForcePull)
t.LogWithStatus(i18n.GetMsgByKey("ComposeCreate"), err)
if err != nil {
_, _ = compose.Down(req.Path)
return err
}
_ = composeRepo.CreateRecord(&model.Compose{Name: req.Name, Path: req.Path})
_ = composeRepo.CreateRecord(&model.Compose{Name: strings.ToLower(req.Name), Path: req.Path})
return nil
}, nil)
_ = taskItem.Execute()
@@ -232,24 +233,24 @@ func (u *ContainerService) ComposeOperation(req dto.ComposeOperation) error {
return err
}
if req.WithFile {
_ = os.RemoveAll(path.Dir(req.Path))
for _, item := range strings.Split(req.Path, ",") {
if len(item) != 0 {
_ = os.RemoveAll(path.Dir(item))
}
}
}
_ = composeRepo.DeleteRecord(repo.WithByName(req.Name))
return nil
}
if _, err := os.Stat(req.Path); err != nil {
return fmt.Errorf("load file with path %s failed, %v", req.Path, err)
}
if req.Operation == "up" {
if stdout, err := compose.Up(req.Path); err != nil {
return errors.New(string(stdout))
return fmt.Errorf("docker-compose up failed, std: %s, err: %v", stdout, err)
}
} else {
if stdout, err := compose.Operate(req.Path, req.Operation); err != nil {
return errors.New(string(stdout))
return fmt.Errorf("docker-compose %s failed, std: %s, err: %v", req.Operation, stdout, err)
}
}
global.LOG.Infof("docker-compose %s %s successful", req.Operation, req.Name)
return nil
}
@@ -257,34 +258,107 @@ func (u *ContainerService) ComposeUpdate(req dto.ComposeUpdate) error {
if cmd.CheckIllegal(req.Name, req.Path) {
return buserr.New("ErrCmdIllegal")
}
oldFile, err := os.ReadFile(req.Path)
if err != nil {
return fmt.Errorf("load file with path %s failed, %v", req.Path, err)
}
file, err := os.OpenFile(req.Path, os.O_WRONLY|os.O_TRUNC, 0640)
taskItem, err := task.NewTaskWithOps(req.Name, task.TaskUpdate, task.TaskScopeCompose, req.TaskID, 1)
if err != nil {
global.LOG.Errorf("new task for update compose failed, err: %v", err)
return err
}
defer file.Close()
write := bufio.NewWriter(file)
_, _ = write.WriteString(req.Content)
write.Flush()
go func() {
taskItem.AddSubTask(i18n.GetMsgByKey("TaskUpdate"), func(t *task.Task) error {
oldFile, err := os.ReadFile(req.DetailPath)
if err != nil {
return fmt.Errorf("load file with path %s failed, %v", req.DetailPath, err)
}
file, err := os.OpenFile(req.DetailPath, os.O_WRONLY|os.O_TRUNC, 0640)
if err != nil {
return err
}
defer file.Close()
write := bufio.NewWriter(file)
_, _ = write.WriteString(req.Content)
write.Flush()
global.LOG.Infof("docker-compose.yml %s has been replaced, now start to docker-compose restart", req.Path)
if err := newComposeEnv(req.Path, req.Env); err != nil {
return err
}
global.LOG.Infof("docker-compose.yml %s has been replaced, now start to docker-compose restart", req.DetailPath)
if err := newComposeEnv(req.DetailPath, req.Env); err != nil {
return err
}
if stdout, err := compose.Up(req.Path); err != nil {
if err := recreateCompose(string(oldFile), req.Path); err != nil {
return fmt.Errorf("update failed when handle compose up, err: %s, recreate failed: %v", string(stdout), err)
}
return fmt.Errorf("update failed when handle compose up, err: %s", string(stdout))
}
if err := compose.UpWithTask(req.Path, t, req.ForcePull); err != nil {
global.LOG.Errorf("update failed when handle compose up, err: %s, now try to recreate the old compose file", err)
if err := recreateCompose(string(oldFile), req.Path); err != nil {
return fmt.Errorf("update failed and recreate old compose file also failed, err: %v", err)
}
return fmt.Errorf("update failed when handle compose up, err: %s", err)
}
return nil
}, nil)
_ = taskItem.Execute()
}()
return nil
}
func (u *ContainerService) ComposeLogClean(req dto.ComposeLogClean) error {
client, err := docker.NewDockerClient()
if err != nil {
return err
}
defer client.Close()
options := container.ListOptions{All: true}
options.Filters = filters.NewArgs()
options.Filters.Add("label", composeProjectLabel)
list, err := client.ContainerList(context.Background(), options)
if err != nil {
return err
}
ctx := context.Background()
for _, item := range list {
if name, ok := item.Labels[composeProjectLabel]; ok {
if name != req.Name {
continue
}
containerItem, err := client.ContainerInspect(ctx, item.ID)
if err != nil {
return err
}
if err := client.ContainerStop(ctx, containerItem.ID, container.StopOptions{}); err != nil {
return err
}
file, err := os.OpenFile(containerItem.LogPath, os.O_RDWR|os.O_CREATE, constant.FilePerm)
if err != nil {
return err
}
defer file.Close()
if err = file.Truncate(0); err != nil {
return err
}
_, _ = file.Seek(0, 0)
files, _ := filepath.Glob(fmt.Sprintf("%s.*", containerItem.LogPath))
for _, file := range files {
_ = os.Remove(file)
}
}
}
return u.ComposeOperation(dto.ComposeOperation{
Name: req.Name,
Path: req.Path,
Operation: "restart",
})
}
func (u *ContainerService) LoadComposeEnv(name string) (string, error) {
envFilePath := path.Join(path.Dir(name), ".env")
file, err := os.ReadFile(envFilePath)
if err != nil {
return "", err
}
return string(file), nil
}
func (u *ContainerService) loadPath(req *dto.ComposeCreate) error {
if req.From == "template" || req.From == "edit" {
dir := fmt.Sprintf("%s/docker/compose/%s", global.Dir.DataDir, req.Name)
@@ -309,11 +383,8 @@ func (u *ContainerService) loadPath(req *dto.ComposeCreate) error {
}
func removeContainerForCompose(composeName, composePath string) error {
if _, err := os.Stat(composePath); err == nil {
if stdout, err := compose.Operate(composePath, "down"); err != nil {
return errors.New(stdout)
}
return nil
if stdout, err := compose.Operate(composePath, "down"); err != nil {
return errors.New(stdout)
}
var options container.ListOptions
options.All = true
@@ -353,40 +424,31 @@ func recreateCompose(content, path string) error {
func loadEnv(list []dto.ComposeInfo) []dto.ComposeInfo {
for i := 0; i < len(list); i++ {
envFilePath := path.Join(path.Dir(list[i].Path), "1panel.env")
tmpPath := list[i].Path
if strings.Contains(list[i].Path, ",") {
tmpPath = strings.Split(list[i].Path, ",")[0]
}
envFilePath := path.Join(path.Dir(tmpPath), ".env")
file, err := os.ReadFile(envFilePath)
if err != nil {
continue
}
lines := strings.Split(string(file), "\n")
for _, line := range lines {
lineItem := strings.TrimSpace(line)
if len(lineItem) != 0 && !strings.HasPrefix(lineItem, "#") {
list[i].Env = append(list[i].Env, lineItem)
}
}
list[i].Env = string(file)
}
return list
}
func newComposeEnv(pathItem string, env []string) error {
if len(env) == 0 {
return nil
}
envFilePath := path.Join(path.Dir(pathItem), "1panel.env")
func newComposeEnv(pathItem string, env string) error {
envFilePath := path.Join(path.Dir(pathItem), ".env")
file, err := os.OpenFile(envFilePath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, constant.FilePerm)
if err != nil {
global.LOG.Errorf("failed to create env file: %v", err)
return err
}
defer file.Close()
for _, env := range env {
envItem := strings.TrimSpace(env)
if _, err := file.WriteString(fmt.Sprintf("%s\n", envItem)); err != nil {
global.LOG.Errorf("failed to write env to file: %v", err)
return err
}
if _, err := file.WriteString(env); err != nil {
global.LOG.Errorf("failed to write env to file: %v", err)
return err
}
global.LOG.Infof("1panel.env file successfully created or updated with env variables in %s", envFilePath)
return nil
}
+3 -6
View File
@@ -117,10 +117,8 @@ func (u *ContainerService) CreateNetwork(req dto.NetworkCreate) error {
return err
}
defer client.Close()
var (
ipams []network.IPAMConfig
enableV6 bool
)
var ipams []network.IPAMConfig
if req.Ipv4 {
var itemIpam network.IPAMConfig
if len(req.AuxAddress) != 0 {
@@ -141,7 +139,6 @@ func (u *ContainerService) CreateNetwork(req dto.NetworkCreate) error {
ipams = append(ipams, itemIpam)
}
if req.Ipv6 {
enableV6 = true
var itemIpam network.IPAMConfig
if len(req.AuxAddress) != 0 {
itemIpam.AuxAddress = make(map[string]string)
@@ -162,7 +159,7 @@ func (u *ContainerService) CreateNetwork(req dto.NetworkCreate) error {
}
options := network.CreateOptions{
EnableIPv6: &enableV6,
EnableIPv6: &req.Ipv6,
Driver: req.Driver,
Options: stringsToMap(req.Options),
Labels: stringsToMap(req.Labels),
+2 -15
View File
@@ -5,7 +5,6 @@ import (
"sort"
"strings"
"time"
"unicode"
"github.com/1Panel-dev/1Panel/agent/app/dto"
"github.com/1Panel-dev/1Panel/agent/buserr"
@@ -20,6 +19,7 @@ func (u *ContainerService) PageVolume(req dto.SearchWithPage) (int64, interface{
if err != nil {
return 0, nil, err
}
defer client.Close()
list, err := client.VolumeList(context.TODO(), volume.ListOptions{})
if err != nil {
return 0, nil, err
@@ -57,7 +57,7 @@ func (u *ContainerService) PageVolume(req dto.SearchWithPage) (int64, interface{
var volume dto.Volume
volume.Driver = item.Driver
volume.Mountpoint = item.Mountpoint
volume.Name = simplifyVolumeName(item.Name)
volume.Name = item.Name
for key, val := range item.Labels {
volume.Labels = append(volume.Labels, dto.VolumeOption{Key: key, Value: val})
}
@@ -143,16 +143,3 @@ func (u *ContainerService) CreateVolume(req dto.VolumeCreate) error {
}
return nil
}
func simplifyVolumeName(name string) string {
if len(name) != 64 {
return name
}
for _, char := range name {
if !unicode.Is(unicode.ASCII_Hex_Digit, char) {
return name
}
}
return name[:12]
}
+11 -8
View File
@@ -143,6 +143,7 @@ func (u *CronjobService) Export(req dto.OperateByIDs) (string, error) {
Timeout: cronjob.Timeout,
IgnoreErr: cronjob.IgnoreErr,
Secret: cronjob.Secret,
Args: cronjob.Args,
}
switch cronjob.Type {
case "app":
@@ -237,6 +238,7 @@ func (u *CronjobService) Import(req []dto.CronjobTrans) error {
Timeout: item.Timeout,
IgnoreErr: item.IgnoreErr,
Secret: item.Secret,
Args: item.Args,
}
hasNotFound := false
switch item.Type {
@@ -447,7 +449,7 @@ func (u *CronjobService) SearchRecords(search dto.SearchRecord) (int64, interfac
func (u *CronjobService) LoadNextHandle(specStr string) ([]string, error) {
spec := cron.NewParser(cron.Minute | cron.Hour | cron.Dom | cron.Month | cron.Dow)
now := time.Now()
var nexts [5]string
var next [5]string
if strings.HasPrefix(specStr, "@every ") {
duration := time.Minute
if strings.HasSuffix(specStr, "s") {
@@ -462,10 +464,10 @@ func (u *CronjobService) LoadNextHandle(specStr string) ([]string, error) {
}
for i := 0; i < 5; i++ {
nextTime := now.Add(time.Duration(durationItem) * duration)
nexts[i] = nextTime.Format(constant.DateTimeLayout)
next[i] = nextTime.Format(constant.DateTimeLayout)
now = nextTime
}
return nexts[:], nil
return next[:], nil
}
sched, err := spec.Parse(specStr)
if err != nil {
@@ -473,10 +475,10 @@ func (u *CronjobService) LoadNextHandle(specStr string) ([]string, error) {
}
for i := 0; i < 5; i++ {
nextTime := sched.Next(now)
nexts[i] = nextTime.Format(constant.DateTimeLayout)
next[i] = nextTime.Format(constant.DateTimeLayout)
now = nextTime
}
return nexts[:], nil
return next[:], nil
}
func (u *CronjobService) LoadRecordLog(req dto.OperateByID) string {
@@ -633,8 +635,8 @@ func (u *CronjobService) HandleStop(id uint) error {
if len(record.TaskID) == 0 {
return nil
}
if cancle, ok := global.TaskCtxMap[record.TaskID]; ok {
cancle()
if cancel, ok := global.TaskCtxMap[record.TaskID]; ok {
cancel()
}
return nil
}
@@ -733,6 +735,7 @@ func (u *CronjobService) Update(id uint, req dto.CronjobOperate) error {
upMap["timeout"] = req.Timeout
upMap["ignore_err"] = req.IgnoreErr
upMap["secret"] = req.Secret
upMap["args"] = req.Args
err = cronjobRepo.Update(id, upMap)
if err != nil {
return err
@@ -762,7 +765,7 @@ func (u *CronjobService) UpdateStatus(id uint, status string) error {
)
if status == constant.StatusEnable {
entryIDs, err = u.StartJob(&cronjob, false)
entryIDs, err = u.StartJob(&cronjob, true)
if err != nil {
return err
}
+18 -13
View File
@@ -13,6 +13,7 @@ import (
"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/i18n"
"github.com/1Panel-dev/1Panel/agent/app/dto"
@@ -21,7 +22,6 @@ import (
"github.com/1Panel-dev/1Panel/agent/global"
"github.com/1Panel-dev/1Panel/agent/utils/common"
"github.com/1Panel-dev/1Panel/agent/utils/files"
"github.com/pkg/errors"
)
func (u *CronjobService) handleApp(cronjob model.Cronjob, startTime time.Time, taskItem *task.Task) error {
@@ -32,7 +32,7 @@ func (u *CronjobService) handleApp(cronjob model.Cronjob, startTime time.Time, t
}
accountMap := NewBackupClientMap(strings.Split(cronjob.SourceAccountIDs, ","))
if !accountMap[fmt.Sprintf("%d", cronjob.DownloadAccountID)].isOk {
return errors.New(i18n.GetMsgWithDetail("LoadBackupFailed", accountMap[fmt.Sprintf("%d", cronjob.DownloadAccountID)].message))
return buserr.New(i18n.GetMsgWithDetail("LoadBackupFailed", accountMap[fmt.Sprintf("%d", cronjob.DownloadAccountID)].message))
}
for _, app := range apps {
retry := 0
@@ -90,7 +90,7 @@ func (u *CronjobService) handleWebsite(cronjob model.Cronjob, startTime time.Tim
}
accountMap := NewBackupClientMap(strings.Split(cronjob.SourceAccountIDs, ","))
if !accountMap[fmt.Sprintf("%d", cronjob.DownloadAccountID)].isOk {
return errors.New(i18n.GetMsgWithDetail("LoadBackupFailed", accountMap[fmt.Sprintf("%d", cronjob.DownloadAccountID)].message))
return buserr.New(i18n.GetMsgWithDetail("LoadBackupFailed", accountMap[fmt.Sprintf("%d", cronjob.DownloadAccountID)].message))
}
for _, web := range webs {
retry := 0
@@ -149,7 +149,7 @@ func (u *CronjobService) handleDatabase(cronjob model.Cronjob, startTime time.Ti
}
accountMap := NewBackupClientMap(strings.Split(cronjob.SourceAccountIDs, ","))
if !accountMap[fmt.Sprintf("%d", cronjob.DownloadAccountID)].isOk {
return errors.New(i18n.GetMsgWithDetail("LoadBackupFailed", accountMap[fmt.Sprintf("%d", cronjob.DownloadAccountID)].message))
return buserr.New(i18n.GetMsgWithDetail("LoadBackupFailed", accountMap[fmt.Sprintf("%d", cronjob.DownloadAccountID)].message))
}
for _, dbInfo := range dbs {
retry := 0
@@ -178,7 +178,7 @@ func (u *CronjobService) handleDatabase(cronjob model.Cronjob, startTime time.Ti
}
}
} else {
if err := doPostgresqlgBackup(dbInfo, backupDir, record.FileName, cronjob.Secret); err != nil {
if err := doPostgresqlBackup(dbInfo, backupDir, record.FileName, cronjob.Secret, taskItem); err != nil {
if retry < int(cronjob.RetryTimes) || !cronjob.IgnoreErr {
retry++
return err
@@ -217,7 +217,7 @@ func (u *CronjobService) handleDatabase(cronjob model.Cronjob, startTime time.Ti
func (u *CronjobService) handleDirectory(cronjob model.Cronjob, startTime time.Time, taskItem *task.Task) error {
accountMap := NewBackupClientMap(strings.Split(cronjob.SourceAccountIDs, ","))
if !accountMap[fmt.Sprintf("%d", cronjob.DownloadAccountID)].isOk {
return errors.New(i18n.GetMsgWithDetail("LoadBackupFailed", accountMap[fmt.Sprintf("%d", cronjob.DownloadAccountID)].message))
return buserr.New(i18n.GetMsgWithDetail("LoadBackupFailed", accountMap[fmt.Sprintf("%d", cronjob.DownloadAccountID)].message))
}
taskItem.AddSubTaskWithOps(task.GetTaskName(cronjob.SourceDir, task.TaskBackup, task.TaskScopeCronjob), func(task *task.Task) error {
fileName := fmt.Sprintf("%s.tar.gz", startTime.Format(constant.DateTimeSlimLayout)+common.RandStrAndNum(2))
@@ -267,7 +267,7 @@ func (u *CronjobService) handleDirectory(cronjob model.Cronjob, startTime time.T
func (u *CronjobService) handleSystemLog(cronjob model.Cronjob, startTime time.Time, taskItem *task.Task) error {
accountMap := NewBackupClientMap(strings.Split(cronjob.SourceAccountIDs, ","))
if !accountMap[fmt.Sprintf("%d", cronjob.DownloadAccountID)].isOk {
return errors.New(i18n.GetMsgWithDetail("LoadBackupFailed", accountMap[fmt.Sprintf("%d", cronjob.DownloadAccountID)].message))
return buserr.New(i18n.GetMsgWithDetail("LoadBackupFailed", accountMap[fmt.Sprintf("%d", cronjob.DownloadAccountID)].message))
}
taskItem.AddSubTaskWithOps(task.GetTaskName(i18n.GetMsgByKey("SystemLog"), task.TaskBackup, task.TaskScopeCronjob), func(task *task.Task) error {
nameItem := startTime.Format(constant.DateTimeSlimLayout) + common.RandStrAndNum(5)
@@ -303,7 +303,7 @@ func (u *CronjobService) handleSystemLog(cronjob model.Cronjob, startTime time.T
func (u *CronjobService) handleSnapshot(cronjob model.Cronjob, jobRecord model.JobRecords, taskItem *task.Task) error {
accountMap := NewBackupClientMap(strings.Split(cronjob.SourceAccountIDs, ","))
if !accountMap[fmt.Sprintf("%d", cronjob.DownloadAccountID)].isOk {
return errors.New(i18n.GetMsgWithDetail("LoadBackupFailed", accountMap[fmt.Sprintf("%d", cronjob.DownloadAccountID)].message))
return buserr.New(i18n.GetMsgWithDetail("LoadBackupFailed", accountMap[fmt.Sprintf("%d", cronjob.DownloadAccountID)].message))
}
var record model.BackupRecord
record.Status = constant.StatusSuccess
@@ -379,6 +379,7 @@ type DatabaseHelper struct {
DBType string
Database string
Name string
Args []string
}
func addSkipTask(source string, taskItem *task.Task) {
@@ -400,6 +401,7 @@ func loadDbsForJob(cronjob model.Cronjob) []DatabaseHelper {
DBType: cronjob.DBType,
Database: mysql.Database,
Name: mysql.Name,
Args: strings.Split(cronjob.Args, ","),
})
}
} else {
@@ -410,6 +412,7 @@ func loadDbsForJob(cronjob model.Cronjob) []DatabaseHelper {
DBType: cronjob.DBType,
Database: pg.PostgresqlName,
Name: pg.Name,
Args: strings.Split(cronjob.Args, ","),
})
}
}
@@ -425,6 +428,7 @@ func loadDbsForJob(cronjob model.Cronjob) []DatabaseHelper {
DBType: cronjob.DBType,
Database: mysqlItem.MysqlName,
Name: mysqlItem.Name,
Args: strings.Split(cronjob.Args, ","),
})
} else {
pgItem, _ := postgresqlRepo.Get(repo.WithByID(uint(itemID)))
@@ -433,6 +437,7 @@ func loadDbsForJob(cronjob model.Cronjob) []DatabaseHelper {
DBType: cronjob.DBType,
Database: pgItem.PostgresqlName,
Name: pgItem.Name,
Args: strings.Split(cronjob.Args, ","),
})
}
}
@@ -440,10 +445,10 @@ func loadDbsForJob(cronjob model.Cronjob) []DatabaseHelper {
}
func loadWebsForJob(cronjob model.Cronjob) []model.Website {
var weblist []model.Website
var list []model.Website
if cronjob.Website == "all" {
weblist, _ = websiteRepo.List()
return weblist
list, _ = websiteRepo.List()
return list
}
websites := strings.Split(cronjob.Website, ",")
var idItems []uint
@@ -451,8 +456,8 @@ func loadWebsForJob(cronjob model.Cronjob) []model.Website {
itemID, _ := strconv.Atoi(websites[i])
idItems = append(idItems, uint(itemID))
}
weblist, _ = websiteRepo.GetBy(repo.WithByIDs(idItems))
return weblist
list, _ = websiteRepo.GetBy(repo.WithByIDs(idItems))
return list
}
func handleBackupLogs(taskItem *task.Task, targetDir, fileName string, secret string) error {
+26 -16
View File
@@ -8,14 +8,13 @@ import (
"io"
"net/http"
"os"
"path"
pathUtils "path"
"path/filepath"
"strings"
"time"
"github.com/1Panel-dev/1Panel/agent/buserr"
"github.com/1Panel-dev/1Panel/agent/utils/alert_push"
"github.com/pkg/errors"
"github.com/1Panel-dev/1Panel/agent/app/dto"
"github.com/1Panel-dev/1Panel/agent/app/model"
@@ -65,7 +64,7 @@ func (u *CronjobService) HandleJob(cronjob *model.Cronjob) {
return
}
if err = u.loadTask(cronjob, &record, taskItem); err != nil {
global.LOG.Debugf("preper to handle cron job [%s] %s failed, err: %v", cronjob.Type, cronjob.Name, err)
global.LOG.Debugf("prepare to handle cron job [%s] %s failed, err: %v", cronjob.Type, cronjob.Name, err)
item, _ := taskRepo.GetFirst(taskRepo.WithByID(record.TaskID))
if len(item.ID) == 0 {
record.TaskID = ""
@@ -147,7 +146,7 @@ func (u *CronjobService) handleShell(cronjob model.Cronjob, taskItem *task.Task)
if len(cronjob.ContainerName) != 0 {
scriptItem := cronjob.Script
if cronjob.ScriptMode == "select" {
scriptItem = path.Join("/tmp", path.Base(cronjob.Script))
scriptItem = pathUtils.Join("/tmp", pathUtils.Base(cronjob.Script))
if err := cmdMgr.Run("docker", "cp", cronjob.Script, cronjob.ContainerName+":"+scriptItem); err != nil {
return err
}
@@ -195,10 +194,17 @@ func (u *CronjobService) handleShell(cronjob model.Cronjob, taskItem *task.Task)
}
func (u *CronjobService) handleCurl(cronjob model.Cronjob, taskItem *task.Task) {
taskItem.AddSubTaskWithOps(i18n.GetWithName("HandleShell", cronjob.Name), func(t *task.Task) error {
cmdMgr := cmd.NewCommandMgr(cmd.WithTask(*taskItem))
return cmdMgr.Run("curl", cronjob.URL)
}, nil, int(cronjob.RetryTimes), time.Duration(cronjob.Timeout)*time.Second)
urls := strings.Split(cronjob.URL, ",")
for _, url := range urls {
if len(strings.TrimSpace(url)) == 0 {
continue
}
taskItem.AddSubTaskWithOps(i18n.GetWithName("HandleCurl", url), func(t *task.Task) error {
taskItem.LogStart(i18n.GetWithName("HandleCurl", url))
cmdMgr := cmd.NewCommandMgr(cmd.WithTask(*taskItem))
return cmdMgr.Run("curl", url)
}, nil, int(cronjob.RetryTimes), time.Duration(cronjob.Timeout)*time.Second)
}
}
func (u *CronjobService) handleNtpSync(cronjob model.Cronjob, taskItem *task.Task) {
@@ -245,11 +251,11 @@ func (u *CronjobService) handleCleanLog(cronjob model.Cronjob, taskItem *task.Ta
if appInstall.ID > 0 {
curStr := i18n.GetWithName("CleanLogByName", "OpenResty")
t.LogStart(curStr)
accessLogPath := path.Join(appInstall.GetPath(), "log", "access.log")
accessLogPath := pathUtils.Join(appInstall.GetPath(), "log", "access.log")
if err := os.Truncate(accessLogPath, 0); err != nil {
t.LogFailedWithErr(curStr, err)
}
errLogPath := path.Join(appInstall.GetPath(), "log", "error.log")
errLogPath := pathUtils.Join(appInstall.GetPath(), "log", "error.log")
if err := os.Truncate(errLogPath, 0); err != nil {
t.LogFailedWithErr(curStr, err)
}
@@ -267,8 +273,12 @@ func (u *CronjobService) handleSyncIpGroup(cronjob model.Cronjob, taskItem *task
if err != nil {
return err
}
ipGroupDir := path.Join(appInstall.GetPath(), "1pwaf", "data", "rules", "ip_group")
urlDir := path.Join(ipGroupDir, "ip_group_url")
ipGroupDir := pathUtils.Join(appInstall.GetPath(), "1pwaf", "data", "rules", "ip_group")
urlDir := pathUtils.Join(ipGroupDir, "ip_group_url")
_, err = os.Stat(urlDir)
if err != nil && os.IsNotExist(err) {
return buserr.New("WafIpGroupNotFound")
}
urlsFiles, err := os.ReadDir(urlDir)
if err != nil {
@@ -332,7 +342,7 @@ func (u *CronjobService) handleSyncIpGroup(cronjob model.Cronjob, taskItem *task
func (u *CronjobService) handleCutWebsiteLog(cronjob *model.Cronjob, startTime time.Time, taskItem *task.Task) error {
clientMap := NewBackupClientMap([]string{fmt.Sprintf("%v", cronjob.DownloadAccountID)})
if !clientMap[fmt.Sprintf("%d", cronjob.DownloadAccountID)].isOk {
return errors.New(i18n.GetMsgWithDetail("LoadBackupFailed", clientMap[fmt.Sprintf("%d", cronjob.DownloadAccountID)].message))
return buserr.New(i18n.GetMsgWithDetail("LoadBackupFailed", clientMap[fmt.Sprintf("%d", cronjob.DownloadAccountID)].message))
}
taskItem.AddSubTaskWithOps(i18n.GetWithName("CutWebsiteLog", cronjob.Name), func(t *task.Task) error {
websites := loadWebsForJob(*cronjob)
@@ -408,7 +418,7 @@ func (u *CronjobService) removeExpiredBackup(cronjob model.Cronjob, accountMap m
var opts []repo.DBOption
opts = append(opts, repo.WithByFrom("cronjob"))
opts = append(opts, backupRepo.WithByCronID(cronjob.ID))
opts = append(opts, repo.WithOrderBy("created_at desc"))
opts = append(opts, repo.WithOrderDesc("created_at"))
if record.ID != 0 {
opts = append(opts, repo.WithByType(record.Type))
opts = append(opts, repo.WithByName(record.Name))
@@ -451,7 +461,7 @@ func (u *CronjobService) removeExpiredBackup(cronjob model.Cronjob, accountMap m
}
func (u *CronjobService) removeExpiredLog(cronjob model.Cronjob) {
records, _ := cronjobRepo.ListRecord(cronjobRepo.WithByJobID(int(cronjob.ID)), repo.WithOrderBy("created_at desc"))
records, _ := cronjobRepo.ListRecord(cronjobRepo.WithByJobID(int(cronjob.ID)), repo.WithOrderDesc("created_at"))
if len(records) <= int(cronjob.RetainCopies) {
return
}
@@ -464,7 +474,7 @@ func (u *CronjobService) removeExpiredLog(cronjob model.Cronjob) {
}
_ = cronjobRepo.DeleteRecord(repo.WithByID(records[i].ID))
_ = taskRepo.Delete(taskRepo.WithByID(records[i].TaskID))
_ = os.Remove(path.Join(global.CONF.Base.InstallDir, "1panel/log/task/Cronjob", records[i].TaskID+".log"))
_ = os.Remove(pathUtils.Join(global.CONF.Base.InstallDir, "1panel/log/task/Cronjob", records[i].TaskID+".log"))
}
}
+86 -56
View File
@@ -1,6 +1,7 @@
package service
import (
"cmp"
"context"
"encoding/json"
"fmt"
@@ -23,10 +24,9 @@ import (
"github.com/1Panel-dev/1Panel/agent/utils/common"
"github.com/1Panel-dev/1Panel/agent/utils/controller"
"github.com/1Panel-dev/1Panel/agent/utils/copier"
"github.com/1Panel-dev/1Panel/agent/utils/psutil"
"github.com/gin-gonic/gin"
"github.com/shirou/gopsutil/v4/cpu"
"github.com/shirou/gopsutil/v4/disk"
"github.com/shirou/gopsutil/v4/host"
"github.com/shirou/gopsutil/v4/load"
"github.com/shirou/gopsutil/v4/mem"
"github.com/shirou/gopsutil/v4/net"
@@ -39,6 +39,8 @@ type IDashboardService interface {
LoadBaseInfo(ioOption string, netOption string) (*dto.DashboardBase, error)
LoadCurrentInfoForNode() *dto.NodeCurrent
LoadCurrentInfo(ioOption string, netOption string) *dto.DashboardCurrent
LoadTopCPU() []dto.Process
LoadTopMem() []dto.Process
LoadQuickOptions() []dto.QuickJump
ChangeQuick(req dto.ChangeQuicks) error
@@ -77,7 +79,7 @@ func (u *DashboardService) Restart(operation string) error {
func (u *DashboardService) LoadOsInfo() (*dto.OsInfo, error) {
var baseInfo dto.OsInfo
hostInfo, err := host.Info()
hostInfo, err := psutil.HOST.GetHostInfo(false)
if err != nil {
return nil, err
}
@@ -86,8 +88,9 @@ func (u *DashboardService) LoadOsInfo() (*dto.OsInfo, error) {
baseInfo.PlatformFamily = hostInfo.PlatformFamily
baseInfo.KernelArch = hostInfo.KernelArch
baseInfo.KernelVersion = hostInfo.KernelVersion
baseInfo.PrettyDistro = psutil.HOST.GetDistro()
diskInfo, err := disk.Usage(global.Dir.BaseDir)
diskInfo, err := psutil.DISK.GetUsage(global.Dir.BaseDir, false)
if err == nil {
baseInfo.DiskSize = int64(diskInfo.Free)
}
@@ -104,12 +107,17 @@ func (u *DashboardService) LoadOsInfo() (*dto.OsInfo, error) {
func (u *DashboardService) LoadCurrentInfoForNode() *dto.NodeCurrent {
var currentInfo dto.NodeCurrent
currentInfo.CPUTotal, _ = cpu.Counts(true)
totalPercent, _ := cpu.Percent(100*time.Millisecond, false)
if len(totalPercent) == 1 {
currentInfo.CPUUsedPercent = totalPercent[0]
currentInfo.CPUUsed = currentInfo.CPUUsedPercent * 0.01 * float64(currentInfo.CPUTotal)
currentInfo.CPUTotal, _ = psutil.CPUInfo.GetLogicalCores(false)
cpuUsedPercent, perCore, cpuDetailedPercent := psutil.CPU.GetCPUUsage()
if len(perCore) == 0 {
currentInfo.CPUTotal = psutil.CPU.NumCPU()
} else {
currentInfo.CPUTotal = len(perCore)
}
currentInfo.CPUUsedPercent = cpuUsedPercent
currentInfo.CPUUsed = cpuUsedPercent * 0.01 * float64(currentInfo.CPUTotal)
currentInfo.CPUDetailedPercent = cpuDetailedPercent
loadInfo, _ := load.Avg()
currentInfo.Load1 = loadInfo.Load1
@@ -134,38 +142,39 @@ func (u *DashboardService) LoadCurrentInfoForNode() *dto.NodeCurrent {
func (u *DashboardService) LoadBaseInfo(ioOption string, netOption string) (*dto.DashboardBase, error) {
var baseInfo dto.DashboardBase
hostInfo, err := host.Info()
hostInfo, err := psutil.HOST.GetHostInfo(true)
if err != nil {
return nil, err
}
baseInfo.Hostname = hostInfo.Hostname
baseInfo.OS = hostInfo.OS
baseInfo.Platform = hostInfo.Platform
baseInfo.PlatformFamily = hostInfo.PlatformFamily
baseInfo.PlatformVersion = hostInfo.PlatformVersion
baseInfo.KernelArch = hostInfo.KernelArch
baseInfo.KernelVersion = hostInfo.KernelVersion
ss, _ := json.Marshal(hostInfo)
baseInfo.VirtualizationSystem = string(ss)
baseInfo.IpV4Addr = loadOutboundIP()
httpProxy := os.Getenv("http_proxy")
if httpProxy == "" {
httpProxy = os.Getenv("HTTP_PROXY")
baseInfo = dto.DashboardBase{
Hostname: hostInfo.Hostname,
OS: hostInfo.OS,
Platform: hostInfo.Platform,
PlatformFamily: hostInfo.PlatformFamily,
PlatformVersion: hostInfo.PlatformVersion,
PrettyDistro: psutil.HOST.GetDistro(),
KernelArch: hostInfo.KernelArch,
KernelVersion: hostInfo.KernelVersion,
VirtualizationSystem: string(ss),
IpV4Addr: loadOutboundIP(),
SystemProxy: "noProxy",
}
if httpProxy != "" {
baseInfo.SystemProxy = httpProxy
if proxy := cmp.Or(os.Getenv("http_proxy"), os.Getenv("HTTP_PROXY")); proxy != "" {
baseInfo.SystemProxy = proxy
}
baseInfo.SystemProxy = "noProxy"
loadQuickJump(&baseInfo)
cpuInfo, err := cpu.Info()
if err == nil {
cpuInfo, err := psutil.CPUInfo.GetCPUInfo(false)
if err == nil && len(cpuInfo) > 0 {
baseInfo.CPUModelName = cpuInfo[0].ModelName
}
baseInfo.CPUCores, _ = cpu.Counts(false)
baseInfo.CPULogicalCores, _ = cpu.Counts(true)
baseInfo.CPUCores, _ = psutil.CPUInfo.GetPhysicalCores(false)
baseInfo.CPULogicalCores, _ = psutil.CPUInfo.GetLogicalCores(false)
baseInfo.CPUMhz = cpuInfo[0].Mhz
baseInfo.CurrentInfo = *u.LoadCurrentInfo(ioOption, netOption)
return &baseInfo, nil
@@ -173,18 +182,22 @@ func (u *DashboardService) LoadBaseInfo(ioOption string, netOption string) (*dto
func (u *DashboardService) LoadCurrentInfo(ioOption string, netOption string) *dto.DashboardCurrent {
var currentInfo dto.DashboardCurrent
hostInfo, _ := host.Info()
hostInfo, _ := psutil.HOST.GetHostInfo(false)
currentInfo.Uptime = hostInfo.Uptime
currentInfo.TimeSinceUptime = time.Now().Add(-time.Duration(hostInfo.Uptime) * time.Second).Format(constant.DateTimeLayout)
currentInfo.TimeSinceUptime = time.Unix(int64(hostInfo.BootTime), 0).Format(constant.DateTimeLayout)
currentInfo.Procs = hostInfo.Procs
currentInfo.CPUTotal, _ = psutil.CPUInfo.GetLogicalCores(false)
currentInfo.CPUTotal, _ = cpu.Counts(true)
totalPercent, _ := cpu.Percent(100*time.Millisecond, false)
if len(totalPercent) == 1 {
currentInfo.CPUUsedPercent = totalPercent[0]
currentInfo.CPUUsed = currentInfo.CPUUsedPercent * 0.01 * float64(currentInfo.CPUTotal)
cpuUsedPercent, perCore, cpuDetailedPercent := psutil.CPU.GetCPUUsage()
if len(perCore) == 0 {
currentInfo.CPUTotal = psutil.CPU.NumCPU()
} else {
currentInfo.CPUTotal = len(perCore)
}
currentInfo.CPUPercent, _ = cpu.Percent(100*time.Millisecond, true)
currentInfo.CPUPercent = perCore
currentInfo.CPUUsedPercent = cpuUsedPercent
currentInfo.CPUUsed = cpuUsedPercent * 0.01 * float64(currentInfo.CPUTotal)
currentInfo.CPUDetailedPercent = cpuDetailedPercent
loadInfo, _ := load.Avg()
currentInfo.Load1 = loadInfo.Load1
@@ -211,9 +224,6 @@ func (u *DashboardService) LoadCurrentInfo(ioOption string, netOption string) *d
currentInfo.GPUData = loadGPUInfo()
currentInfo.XPUData = loadXpuInfo()
currentInfo.TopCPUItems = loadTopCPU()
currentInfo.TopMemItems = loadTopMem()
if ioOption == "all" {
diskInfo, _ := disk.IOCounters()
for _, state := range diskInfo {
@@ -246,6 +256,7 @@ func (u *DashboardService) LoadCurrentInfo(ioOption string, netOption string) *d
if state.Name == netOption {
currentInfo.NetBytesSent = state.BytesSent
currentInfo.NetBytesRecv = state.BytesRecv
break
}
}
}
@@ -254,6 +265,14 @@ func (u *DashboardService) LoadCurrentInfo(ioOption string, netOption string) *d
return &currentInfo
}
func (u *DashboardService) LoadTopCPU() []dto.Process {
return loadTopCPU()
}
func (u *DashboardService) LoadTopMem() []dto.Process {
return loadTopMem()
}
func (u *DashboardService) LoadAppLauncher(ctx *gin.Context) ([]dto.AppLauncher, error) {
var data []dto.AppLauncher
appInstalls, err := appInstallRepo.ListBy(context.Background())
@@ -266,7 +285,7 @@ func (u *DashboardService) LoadAppLauncher(ctx *gin.Context) ([]dto.AppLauncher,
}
showList, err := launcherRepo.ListName()
defaultList, err := appRepo.GetTopRecomment()
defaultList, err := appRepo.GetTopRecommend()
if err != nil {
return data, nil
}
@@ -455,41 +474,52 @@ func loadDiskInfo() []dto.DiskInfo {
)
wg.Add(len(mounts))
for i := 0; i < len(mounts); i++ {
go func(timeoutCh <-chan time.Time, mount diskInfo) {
go func(mount diskInfo) {
defer wg.Done()
var itemData dto.DiskInfo
itemData.Path = mount.Mount
itemData.Type = mount.Type
itemData.Device = mount.Device
type diskResult struct {
state *disk.UsageStat
err error
}
resultCh := make(chan diskResult, 1)
go func() {
state, err := psutil.DISK.GetUsage(mount.Mount, false)
resultCh <- diskResult{state: state, err: err}
}()
select {
case <-timeoutCh:
case <-time.After(5 * time.Second):
mu.Lock()
datas = append(datas, 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 {
case result := <-resultCh:
if result.err != nil {
mu.Lock()
datas = append(datas, itemData)
mu.Unlock()
global.LOG.Errorf("load disk info from %s failed, err: %v", mount.Mount, err)
global.LOG.Errorf("load disk info from %s failed, err: %v", mount.Mount, result.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
itemData.Total = result.state.Total
itemData.Free = result.state.Free
itemData.Used = result.state.Used
itemData.UsedPercent = result.state.UsedPercent
itemData.InodesTotal = result.state.InodesTotal
itemData.InodesUsed = result.state.InodesUsed
itemData.InodesFree = result.state.InodesFree
itemData.InodesUsedPercent = result.state.InodesUsedPercent
mu.Lock()
datas = append(datas, itemData)
mu.Unlock()
}
}(time.After(5*time.Second), mounts[i])
}(mounts[i])
}
wg.Wait()
+24 -19
View File
@@ -119,9 +119,10 @@ func (u *DatabaseService) CheckDatabase(req dto.DatabaseCreate) bool {
if req.Timeout == 0 {
req.Timeout = 30
}
var err error
switch req.Type {
case constant.AppPostgresql:
_, err := postgresql.NewPostgresqlClient(pgclient.DBInfo{
_, err = postgresql.NewPostgresqlClient(pgclient.DBInfo{
From: "remote",
Address: req.Address,
Port: req.Port,
@@ -130,17 +131,15 @@ func (u *DatabaseService) CheckDatabase(req dto.DatabaseCreate) bool {
Password: req.Password,
Timeout: req.Timeout,
})
return err == nil
case constant.AppRedis:
_, err := redisclient.NewRedisClient(redisclient.DBInfo{
_, err = redisclient.NewRedisClient(redisclient.DBInfo{
Address: req.Address,
Port: req.Port,
Password: req.Password,
Timeout: req.Timeout,
})
return err == nil
case "mysql", "mariadb":
_, err := mysql.NewMysqlClient(client.DBInfo{
_, err = mysql.NewMysqlClient(client.DBInfo{
From: "remote",
Address: req.Address,
Port: req.Port,
@@ -154,10 +153,13 @@ func (u *DatabaseService) CheckDatabase(req dto.DatabaseCreate) bool {
SkipVerify: req.SkipVerify,
Timeout: req.Timeout,
})
return err == nil
}
if err != nil {
global.LOG.Errorf("check database connection failed, err: %v", err)
return false
}
return false
return true
}
func (u *DatabaseService) Create(req dto.DatabaseCreate) error {
@@ -174,12 +176,13 @@ func (u *DatabaseService) Create(req dto.DatabaseCreate) error {
switch req.Type {
case constant.AppPostgresql:
if _, err := postgresql.NewPostgresqlClient(pgclient.DBInfo{
From: "remote",
Address: req.Address,
Port: req.Port,
Username: req.Username,
Password: req.Password,
Timeout: req.Timeout,
From: "remote",
Address: req.Address,
Port: req.Port,
InitialDB: req.InitialDB,
Username: req.Username,
Password: req.Password,
Timeout: req.Timeout,
}); err != nil {
return err
}
@@ -275,12 +278,13 @@ func (u *DatabaseService) Update(req dto.DatabaseUpdate) error {
switch req.Type {
case constant.AppPostgresql:
if _, err := postgresql.NewPostgresqlClient(pgclient.DBInfo{
From: "remote",
Address: req.Address,
Port: req.Port,
Username: req.Username,
Password: req.Password,
Timeout: req.Timeout,
From: "remote",
Address: req.Address,
Port: req.Port,
InitialDB: req.InitialDB,
Username: req.Username,
Password: req.Password,
Timeout: req.Timeout,
}); err != nil {
return err
}
@@ -326,6 +330,7 @@ func (u *DatabaseService) Update(req dto.DatabaseUpdate) error {
upMap["port"] = req.Port
upMap["username"] = req.Username
upMap["password"] = pass
upMap["initial_db"] = req.InitialDB
upMap["timeout"] = req.Timeout
upMap["description"] = req.Description
upMap["ssl"] = req.SSL
+15 -1
View File
@@ -5,8 +5,10 @@ import (
"fmt"
"os"
"path"
"strings"
"github.com/1Panel-dev/1Panel/agent/app/dto"
"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"
@@ -51,7 +53,14 @@ func (u *DBCommonService) LoadDatabaseFile(req dto.OperationWithNameAndType) (st
case "mariadb-conf":
filePath = path.Join(global.Dir.DataDir, fmt.Sprintf("apps/mariadb/%s/conf/my.cnf", req.Name))
case "postgresql-conf":
database, err := databaseRepo.Get(repo.WithByName(req.Name))
if err != nil {
return "", err
}
filePath = path.Join(global.Dir.DataDir, fmt.Sprintf("apps/postgresql/%s/data/postgresql.conf", req.Name))
if strings.HasPrefix(database.Version, "18.") {
filePath = path.Join(global.Dir.DataDir, fmt.Sprintf("apps/postgresql/%s/data/18/docker/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":
@@ -76,8 +85,13 @@ func (u *DBCommonService) UpdateConfByFile(req dto.DBConfUpdateByFile) error {
switch req.Type {
case constant.AppMariaDB, constant.AppMysql, constant.AppMysqlCluster:
path = fmt.Sprintf("%s/%s/%s/conf/my.cnf", global.Dir.AppInstallDir, req.Type, app.Name)
case constant.AppPostgresql, constant.AppPostgresqlCluster:
case constant.AppPostgresqlCluster:
path = fmt.Sprintf("%s/%s/%s/data/postgresql.conf", global.Dir.AppInstallDir, req.Type, app.Name)
case constant.AppPostgresql:
path = fmt.Sprintf("%s/%s/%s/data/postgresql.conf", global.Dir.AppInstallDir, req.Type, app.Name)
if strings.HasPrefix(app.Version, "18.") {
path = fmt.Sprintf("%s/%s/%s/data/18/docker/postgresql.conf", global.Dir.AppInstallDir, req.Type, app.Name)
}
case constant.AppRedis, constant.AppRedisCluster:
path = fmt.Sprintf("%s/%s/%s/conf/redis.conf", global.Dir.AppInstallDir, req.Type, app.Name)
}
+18 -3
View File
@@ -23,8 +23,8 @@ import (
"github.com/1Panel-dev/1Panel/agent/utils/compose"
"github.com/1Panel-dev/1Panel/agent/utils/encrypt"
"github.com/1Panel-dev/1Panel/agent/utils/mysql"
"github.com/1Panel-dev/1Panel/agent/utils/re"
"github.com/1Panel-dev/1Panel/agent/utils/mysql/client"
"github.com/1Panel-dev/1Panel/agent/utils/re"
_ "github.com/go-sql-driver/mysql"
"github.com/jinzhu/copier"
"github.com/pkg/errors"
@@ -45,6 +45,7 @@ type IMysqlService interface {
DeleteCheck(req dto.MysqlDBDeleteCheck) ([]dto.DBResource, error)
Delete(ctx context.Context, req dto.MysqlDBDelete) error
LoadFormatOption(req dto.OperationWithName) []dto.MysqlFormatCollationOption
LoadStatus(req dto.OperationWithNameAndType) (*dto.MysqlStatus, error)
LoadVariables(req dto.OperationWithNameAndType) (*dto.MysqlVariables, error)
LoadRemoteAccess(req dto.OperationWithNameAndType) (bool, error)
@@ -99,7 +100,7 @@ func (u *MysqlService) ListDBOption() ([]dto.MysqlOption, error) {
}
func (u *MysqlService) Create(ctx context.Context, req dto.MysqlDBCreate) (*model.DatabaseMysql, error) {
if cmd.CheckIllegal(req.Name, req.Username, req.Password, req.Format, req.Permission) {
if cmd.CheckIllegal(req.Name, req.Username, req.Password, req.Format, req.Collation, req.Permission) {
return nil, buserr.New("ErrCmdIllegal")
}
@@ -114,7 +115,7 @@ func (u *MysqlService) Create(ctx context.Context, req dto.MysqlDBCreate) (*mode
}
if req.From == "local" && req.Username == "root" {
return nil, errors.New("Cannot set root as user name")
return nil, errors.New("cannot set root as user name")
}
cli, version, err := LoadMysqlClientByFrom(req.Database)
@@ -126,6 +127,7 @@ func (u *MysqlService) Create(ctx context.Context, req dto.MysqlDBCreate) (*mode
if err := cli.Create(client.CreateInfo{
Name: req.Name,
Format: req.Format,
Collation: req.Collation,
Username: req.Username,
Password: req.Password,
Permission: req.Permission,
@@ -584,6 +586,19 @@ func (u *MysqlService) LoadStatus(req dto.OperationWithNameAndType) (*dto.MysqlS
return &info, nil
}
func (u *MysqlService) LoadFormatOption(req dto.OperationWithName) []dto.MysqlFormatCollationOption {
defaultList := []dto.MysqlFormatCollationOption{{Format: "utf8mb4"}, {Format: "utf8mb3"}, {Format: "gbk"}, {Format: "big5"}}
client, _, err := LoadMysqlClientByFrom(req.Name)
if err != nil {
return defaultList
}
options, err := client.LoadFormatCollation(3)
if err != nil {
return defaultList
}
return options
}
func executeSqlForMaps(containerName, dbType, password, command string) (map[string]string, error) {
if dbType == "mysql-cluster" {
dbType = "mysql"
+1 -5
View File
@@ -21,7 +21,6 @@ import (
"github.com/1Panel-dev/1Panel/agent/utils/postgresql/client"
_ "github.com/jackc/pgx/v5/stdlib"
"github.com/jinzhu/copier"
"github.com/pkg/errors"
)
type PostgresqlService struct{}
@@ -138,10 +137,6 @@ func (u *PostgresqlService) Create(ctx context.Context, req dto.PostgresqlDBCrea
return nil, buserr.WithDetail("ErrStructTransform", err.Error(), nil)
}
if req.From == "local" && req.Username == "root" {
return nil, errors.New("Cannot set root as user name")
}
cli, err := LoadPostgresqlClientByFrom(req.Database)
if err != nil {
return nil, err
@@ -185,6 +180,7 @@ func LoadPostgresqlClientByFrom(database string) (postgresql.PostgresqlClient, e
dbInfo.Port = databaseItem.Port
dbInfo.Username = databaseItem.Username
dbInfo.Password = databaseItem.Password
dbInfo.InitialDB = databaseItem.InitialDB
} else {
app, err := appInstallRepo.LoadBaseInfo(databaseItem.Type, database)
if err != nil {
+3 -3
View File
@@ -80,9 +80,9 @@ func (u *RedisService) CheckHasCli() bool {
func (u *RedisService) InstallCli() error {
item := dto.ContainerOperate{
Name: "1Panel-redis-cli-tools",
Image: "redis:7.4.4",
Network: "1panel-network",
Name: "1Panel-redis-cli-tools",
Image: "redis:7.4.4",
Networks: []dto.ContainerNetwork{{Network: "1panel-network"}},
}
return NewIContainerService().ContainerCreate(item, false)
}
+2
View File
@@ -20,6 +20,7 @@ import (
"github.com/1Panel-dev/1Panel/agent/utils/common"
"github.com/1Panel-dev/1Panel/agent/utils/controller"
"github.com/1Panel-dev/1Panel/agent/utils/ntp"
"github.com/1Panel-dev/1Panel/agent/utils/psutil"
"github.com/shirou/gopsutil/v4/mem"
)
@@ -134,6 +135,7 @@ func (u *DeviceService) Update(key, value string) error {
if err := cmd.RunDefaultBashCf("%s hostnamectl set-hostname %s", cmd.SudoHandleCmd(), value); err != nil {
return err
}
_, _ = psutil.HOST.GetHostInfo(true)
case "Ntp", "LocalTime":
if cmd.CheckIllegal(value) {
return buserr.New("ErrCmdIllegal")
+534 -124
View File
@@ -11,36 +11,27 @@ import (
"strings"
"time"
"github.com/1Panel-dev/1Panel/agent/constant"
"github.com/1Panel-dev/1Panel/agent/i18n"
"github.com/1Panel-dev/1Panel/agent/utils/controller"
"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"
"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/constant"
"github.com/1Panel-dev/1Panel/agent/global"
"github.com/1Panel-dev/1Panel/agent/i18n"
"github.com/1Panel-dev/1Panel/agent/utils/common"
"github.com/1Panel-dev/1Panel/agent/utils/docker"
fileUtils "github.com/1Panel-dev/1Panel/agent/utils/files"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/build"
"github.com/docker/docker/api/types/filters"
"github.com/google/uuid"
)
const (
upgradePath = "1panel/tmp/upgrade"
snapshotTmpPath = "1panel/tmp/system"
rollbackPath = "1panel/tmp"
cachePath = "1panel/cache"
oldOriginalPath = "original"
oldAppBackupPath = "1panel/resource/apps_bak"
oldDownloadPath = "1panel/tmp/download"
oldUpgradePath = "1panel/tmp"
tmpUploadPath = "1panel/tmp/upload"
uploadPath = "1panel/uploads"
downloadPath = "1panel/download"
rollbackPath = "1panel/tmp"
upgradePath = "1panel/tmp/upgrade"
uploadPath = "1panel/uploads"
downloadPath = "1panel/download"
)
func (u *DeviceService) Scan() dto.CleanData {
@@ -61,54 +52,10 @@ func (u *DeviceService) Scan() dto.CleanData {
Type: "1panel_original",
Children: loadTreeWithDir(true, "1panel_original", originalPath, fileOp),
})
treeData = append(treeData, loadUpgradeTree(fileOp))
treeData = append(treeData, loadAgentPackage(fileOp))
upgradePath := path.Join(global.Dir.BaseDir, upgradePath)
upgradeSize, _ := fileOp.GetDirSize(upgradePath)
upgradeTree := dto.CleanTree{
ID: uuid.NewString(),
Label: "upgrade",
Size: uint64(upgradeSize),
IsCheck: false,
IsRecommend: true,
Type: "upgrade",
Children: loadTreeWithDir(true, "upgrade", upgradePath, fileOp),
}
if len(upgradeTree.Children) != 0 {
sort.Slice(upgradeTree.Children, func(i, j int) bool {
return common.CompareVersion(upgradeTree.Children[i].Label, upgradeTree.Children[j].Label)
})
if global.IsMaster {
var copiesSeeting model.Setting
_ = global.CoreDB.Where("key = ?", "UpgradeBackupCopies").First(&copiesSeeting).Error
copies, _ := strconv.Atoi(copiesSeeting.Value)
if copies == 0 || copies > len(upgradeTree.Children) {
copies = len(upgradeTree.Children)
}
for i := 0; i < copies; i++ {
upgradeTree.Children[i].IsCheck = false
upgradeTree.Children[i].IsRecommend = false
}
} else {
upgradeTree.Children[0].IsCheck = false
upgradeTree.Children[0].IsRecommend = false
}
}
treeData = append(treeData, upgradeTree)
tmpBackupTree := loadTmpBackupTree(fileOp)
tmpBackupSize := uint64(0)
for _, tmp := range tmpBackupTree {
tmpBackupSize += tmp.Size
}
treeData = append(treeData, dto.CleanTree{
ID: uuid.NewString(),
Label: "backup",
Size: tmpBackupSize,
IsCheck: tmpBackupSize > 0,
IsRecommend: true,
Type: "backup",
Children: tmpBackupTree,
})
SystemClean.BackupClean = loadBackupTree(fileOp)
rollBackTree := loadRollBackTree(fileOp)
rollbackSize := uint64(0)
@@ -124,17 +71,6 @@ func (u *DeviceService) Scan() dto.CleanData {
Type: "rollback",
Children: rollBackTree,
})
cachePath := path.Join(global.Dir.BaseDir, cachePath)
cacheSize, _ := fileOp.GetDirSize(cachePath)
treeData = append(treeData, dto.CleanTree{
ID: uuid.NewString(),
Label: "cache",
Size: uint64(cacheSize),
IsCheck: false,
IsRecommend: false,
Type: "cache",
})
SystemClean.SystemClean = treeData
uploadTreeData := loadUploadTree(fileOp)
@@ -154,7 +90,6 @@ func (u *DeviceService) Scan() dto.CleanData {
func (u *DeviceService) Clean(req []dto.Clean) {
size := uint64(0)
restart := false
for _, item := range req {
size += item.Size
switch item.TreeType {
@@ -164,13 +99,17 @@ func (u *DeviceService) Clean(req []dto.Clean) {
case "upgrade":
dropFileOrDir(path.Join(global.Dir.BaseDir, upgradePath, item.Name))
case "backup":
dropFileOrDir(path.Join(global.Dir.LocalBackupDir, "tmp/app"))
dropFileOrDir(path.Join(global.Dir.LocalBackupDir, "tmp/database"))
dropFileOrDir(path.Join(global.Dir.LocalBackupDir, "tmp/website"))
dropFileOrDir(path.Join(global.Dir.LocalBackupDir, "tmp/directory"))
dropFileOrDir(path.Join(global.Dir.LocalBackupDir, "tmp/log"))
dropFileOrDir(path.Join(global.Dir.LocalBackupDir, "tmp/system"))
case "agent":
dropFileOrDir(path.Join(global.Dir.BaseDir, "1panel/agent/package", item.Name))
case "tmp_backup":
dropFileOrDir(path.Join(global.Dir.LocalBackupDir, "tmp"))
case "unknown_backup":
if strings.HasPrefix(item.Name, path.Join(global.Dir.LocalBackupDir, "log/website")) {
dropFileOrDir(item.Name)
} else {
dropFile(item.Name)
}
case "rollback":
dropFileOrDir(path.Join(global.Dir.BaseDir, rollbackPath, "app"))
@@ -183,25 +122,14 @@ func (u *DeviceService) Clean(req []dto.Clean) {
case "rollback_website":
dropFileOrDir(path.Join(global.Dir.BaseDir, rollbackPath, "website", item.Name))
case "cache":
dropFileOrDir(path.Join(global.Dir.BaseDir, cachePath, item.Name))
restart = true
case "upload":
dropFileOrDir(path.Join(global.Dir.BaseDir, uploadPath, item.Name))
if len(item.Name) == 0 {
dropFileOrDir(path.Join(global.Dir.BaseDir, tmpUploadPath))
}
case "upload_tmp":
dropFileOrDir(path.Join(global.Dir.BaseDir, tmpUploadPath, item.Name))
case "upload_app":
dropFileOrDir(path.Join(global.Dir.BaseDir, uploadPath, "app", item.Name))
case "upload_database":
dropFileOrDir(path.Join(global.Dir.BaseDir, uploadPath, "database", item.Name))
case "upload_website":
dropFileOrDir(path.Join(global.Dir.BaseDir, uploadPath, "website", item.Name))
case "upload_directory":
dropFileOrDir(path.Join(global.Dir.BaseDir, uploadPath, "directory", item.Name))
case "download":
dropFileOrDir(path.Join(global.Dir.BaseDir, downloadPath, item.Name))
case "download_app":
@@ -210,8 +138,6 @@ func (u *DeviceService) Clean(req []dto.Clean) {
dropFileOrDir(path.Join(global.Dir.BaseDir, downloadPath, "database", item.Name))
case "download_website":
dropFileOrDir(path.Join(global.Dir.BaseDir, downloadPath, "website", item.Name))
case "download_directory":
dropFileOrDir(path.Join(global.Dir.BaseDir, downloadPath, "directory", item.Name))
case "system_log":
if len(item.Name) == 0 {
@@ -243,6 +169,8 @@ func (u *DeviceService) Clean(req []dto.Clean) {
} else {
dropTaskLog(path.Join(global.Dir.TaskDir, item.Name))
}
case "website_log":
dropWebsiteLog(item.Name)
case "script":
dropFileOrDir(path.Join(global.Dir.TmpDir, "script", item.Name))
case "images":
@@ -253,16 +181,15 @@ func (u *DeviceService) Clean(req []dto.Clean) {
_, _ = dropVolumes()
case "build_cache":
_, _ = dropBuildCache()
case "app_tmp_download_version":
dropFileOrDir(path.Join(global.Dir.RemoteAppResourceDir, item.Name))
}
}
_ = cleanEmptyDirs(global.Dir.LocalBackupDir)
_ = settingRepo.Update("LastCleanTime", time.Now().Format(constant.DateTimeLayout))
_ = settingRepo.Update("LastCleanSize", fmt.Sprintf("%v", size))
_ = settingRepo.Update("LastCleanData", fmt.Sprintf("%v", len(req)))
if restart {
go controller.RestartPanel(false, true, false)
}
}
func doSystemClean(taskItem *task.Task) func(t *task.Task) error {
@@ -284,25 +211,20 @@ func doSystemClean(taskItem *task.Task) func(t *task.Task) error {
}
}
dropWithTask(path.Join(global.Dir.BaseDir, snapshotTmpPath), taskItem, &size, &fileCount)
dropWithTask(path.Join(global.Dir.LocalBackupDir, "tmp/system"), taskItem, &size, &fileCount)
dropWithTask(path.Join(global.Dir.BaseDir, rollbackPath, "app"), taskItem, &size, &fileCount)
dropWithTask(path.Join(global.Dir.BaseDir, rollbackPath, "website"), taskItem, &size, &fileCount)
dropWithTask(path.Join(global.Dir.BaseDir, rollbackPath, "database"), taskItem, &size, &fileCount)
dropWithTask(path.Join(global.Dir.BaseDir, oldOriginalPath), taskItem, &size, &fileCount)
dropWithTask(path.Join(global.Dir.BaseDir, oldAppBackupPath), taskItem, &size, &fileCount)
dropWithTask(path.Join(global.Dir.BaseDir, oldDownloadPath), taskItem, &size, &fileCount)
oldUpgradePath := path.Join(global.Dir.BaseDir, oldUpgradePath)
oldUpgradeFiles, _ := os.ReadDir(oldUpgradePath)
upgrades := path.Join(global.Dir.BaseDir, upgradePath)
oldUpgradeFiles, _ := os.ReadDir(upgrades)
if len(oldUpgradeFiles) != 0 {
for i := 0; i < len(oldUpgradeFiles); i++ {
dropWithTask(path.Join(oldUpgradePath, oldUpgradeFiles[i].Name()), taskItem, &size, &fileCount)
dropWithTask(path.Join(upgrades, oldUpgradeFiles[i].Name()), taskItem, &size, &fileCount)
}
}
dropWithTask(path.Join(global.Dir.BaseDir, tmpUploadPath), taskItem, &size, &fileCount)
dropWithExclude(path.Join(global.Dir.BaseDir, uploadPath), []string{"theme"}, taskItem, &size, &fileCount)
dropWithTask(path.Join(global.Dir.BaseDir, downloadPath), taskItem, &size, &fileCount)
@@ -338,17 +260,340 @@ func doSystemClean(taskItem *task.Task) func(t *task.Task) error {
}
}
func loadTmpBackupTree(fileOp fileUtils.FileOp) []dto.CleanTree {
func loadUpgradeTree(fileOp fileUtils.FileOp) dto.CleanTree {
upgradePath := path.Join(global.Dir.BaseDir, upgradePath)
upgradeSize, _ := fileOp.GetDirSize(upgradePath)
upgradeTree := dto.CleanTree{
ID: uuid.NewString(),
Label: "upgrade",
Size: uint64(upgradeSize),
IsCheck: false,
IsRecommend: true,
Type: "upgrade",
Children: loadTreeWithDir(true, "upgrade", upgradePath, fileOp),
}
if len(upgradeTree.Children) != 0 {
sort.Slice(upgradeTree.Children, func(i, j int) bool {
return common.CompareVersion(upgradeTree.Children[i].Label, upgradeTree.Children[j].Label)
})
if global.IsMaster {
var copiesSetting model.Setting
_ = global.CoreDB.Where("key = ?", "UpgradeBackupCopies").First(&copiesSetting).Error
copies, _ := strconv.Atoi(copiesSetting.Value)
if copies == 0 || copies > len(upgradeTree.Children) {
copies = len(upgradeTree.Children)
}
for i := 0; i < copies; i++ {
upgradeTree.Children[i].IsCheck = false
upgradeTree.Children[i].IsRecommend = false
}
} else {
upgradeTree.Children[0].IsCheck = false
upgradeTree.Children[0].IsRecommend = false
}
}
return upgradeTree
}
func loadAgentPackage(fileOp fileUtils.FileOp) dto.CleanTree {
pathItem := path.Join(global.Dir.BaseDir, "1panel/agent/package")
itemTree := dto.CleanTree{
ID: uuid.NewString(),
Label: "agent_packages",
IsCheck: false,
IsRecommend: true,
Type: "agent",
}
files, _ := os.ReadDir(pathItem)
for _, file := range files {
if file.IsDir() {
itemSize, _ := fileOp.GetDirSize(path.Join(pathItem, file.Name()))
itemTree.Size += uint64(itemSize)
itemTree.Children = append(itemTree.Children, dto.CleanTree{
ID: uuid.NewString(),
Label: file.Name(),
Name: file.Name(),
Size: uint64(itemSize),
IsCheck: true,
IsRecommend: true,
Type: "agent",
})
} else {
itemSize, _ := file.Info()
itemName := file.Name()
isCurrentVersion := strings.HasPrefix(itemName, fmt.Sprintf("1panel-agent_%s_", global.CONF.Base.Version))
if isCurrentVersion {
continue
}
itemTree.Size += uint64(itemSize.Size())
itemTree.Children = append(itemTree.Children, dto.CleanTree{
ID: uuid.NewString(),
Label: itemName,
Name: itemName,
Size: uint64(itemSize.Size()),
IsCheck: !isCurrentVersion,
IsRecommend: true,
Type: "agent",
})
}
}
if itemTree.Size == 0 {
itemTree.IsCheck = false
}
return itemTree
}
func loadBackupTree(fileOp fileUtils.FileOp) []dto.CleanTree {
var treeData []dto.CleanTree
treeData = loadTreeWithCheck(treeData, path.Join(global.Dir.LocalBackupDir, "tmp/app"), "tmp_backup_app", fileOp)
treeData = loadTreeWithCheck(treeData, path.Join(global.Dir.LocalBackupDir, "tmp/website"), "tmp_backup_website", fileOp)
treeData = loadTreeWithCheck(treeData, path.Join(global.Dir.LocalBackupDir, "tmp/database"), "tmp_backup_database", fileOp)
treeData = loadTreeWithCheck(treeData, path.Join(global.Dir.LocalBackupDir, "tmp/system"), "tmp_backup_snapshot", fileOp)
treeData = loadTreeWithCheck(treeData, path.Join(global.Dir.LocalBackupDir, "tmp/directory"), "tmp_backup_directory", fileOp)
treeData = loadTreeWithCheck(treeData, path.Join(global.Dir.LocalBackupDir, "tmp/log"), "tmp_backup_log", fileOp)
tmpSize, _ := fileOp.GetDirSize(path.Join(global.Dir.LocalBackupDir, "tmp"))
treeData = append(treeData, dto.CleanTree{
ID: uuid.NewString(),
Label: "tmp_backup",
Size: uint64(tmpSize),
IsCheck: tmpSize != 0,
IsRecommend: true,
Type: "tmp_backup",
})
backupRecords, _ := backupRepo.ListRecord()
var recordMap = make(map[string][]string)
for _, record := range backupRecords {
if val, ok := recordMap[record.FileDir]; ok {
val = append(val, record.FileName)
recordMap[record.FileDir] = val
} else {
recordMap[record.FileDir] = []string{record.FileName}
}
}
treeData = append(treeData, loadUnknownApps(fileOp, recordMap))
treeData = append(treeData, loadUnknownDbs(fileOp, recordMap))
treeData = append(treeData, loadUnknownWebsites(fileOp, recordMap))
treeData = append(treeData, loadUnknownSnapshot(fileOp))
treeData = append(treeData, loadUnknownWebsiteLog(fileOp))
return treeData
}
func loadUnknownApps(fileOp fileUtils.FileOp, recordMap map[string][]string) dto.CleanTree {
apps, _ := appInstallRepo.ListBy(context.Background())
var excludePaths []string
for _, app := range apps {
itemName := fmt.Sprintf("app/%s/%s", app.App.Key, app.Name)
if val, ok := recordMap[itemName]; ok {
for _, item := range val {
excludePaths = append(excludePaths, path.Join(global.Dir.LocalBackupDir, itemName, item))
}
}
}
backupPath := path.Join(global.Dir.LocalBackupDir, "app")
treeData := dto.CleanTree{
ID: uuid.NewString(),
Label: "unknown_app",
IsCheck: false,
IsRecommend: false,
Name: backupPath,
Type: "unknown_backup",
}
_ = loadFileOrDirWithExclude(fileOp, 0, backupPath, &treeData, excludePaths)
return treeData
}
func loadUnknownDbs(fileOp fileUtils.FileOp, recordMap map[string][]string) dto.CleanTree {
dbs, _ := databaseRepo.GetList()
var excludePaths []string
dbMap := make(map[string]struct{})
for _, db := range dbs {
dbMap[fmt.Sprintf("database/%s/%s", db.Type, db.Name)] = struct{}{}
}
for key, val := range recordMap {
itemName := path.Dir(key)
if _, ok := dbMap[itemName]; ok {
for _, item := range val {
excludePaths = append(excludePaths, path.Join(global.Dir.LocalBackupDir, key, item))
}
}
}
backupPath := path.Join(global.Dir.LocalBackupDir, "database")
treeData := dto.CleanTree{
ID: uuid.NewString(),
Label: "unknown_database",
Name: backupPath,
IsCheck: false,
IsRecommend: false,
Type: "unknown_backup",
}
_ = loadFileOrDirWithExclude(fileOp, 0, backupPath, &treeData, excludePaths)
return treeData
}
func loadUnknownWebsites(fileOp fileUtils.FileOp, recordMap map[string][]string) dto.CleanTree {
websites, _ := websiteRepo.List()
var excludePaths []string
for _, website := range websites {
itemName := fmt.Sprintf("website/%s", website.Alias)
if val, ok := recordMap[itemName]; ok {
for _, item := range val {
excludePaths = append(excludePaths, path.Join(global.Dir.LocalBackupDir, itemName, item))
}
}
}
backupPath := path.Join(global.Dir.LocalBackupDir, "website")
treeData := dto.CleanTree{
ID: uuid.NewString(),
Label: "unknown_website",
Name: backupPath,
IsCheck: false,
IsRecommend: false,
Type: "unknown_backup",
}
_ = loadFileOrDirWithExclude(fileOp, 0, backupPath, &treeData, excludePaths)
return treeData
}
func loadUnknownSnapshot(fileOp fileUtils.FileOp) dto.CleanTree {
snaps, _ := snapshotRepo.GetList()
var excludePaths []string
for _, item := range snaps {
excludePaths = append(excludePaths, path.Join(global.Dir.LocalBackupDir, "system_snapshot", item.Name+".tar.gz"))
}
backupPath := path.Join(global.Dir.LocalBackupDir, "system_snapshot")
treeData := dto.CleanTree{
ID: uuid.NewString(),
Label: "unknown_snapshot",
Name: backupPath,
IsCheck: false,
IsRecommend: false,
Type: "unknown_backup",
}
entries, _ := os.ReadDir(backupPath)
for _, entry := range entries {
childPath := filepath.Join(backupPath, entry.Name())
if isExactPathMatch(childPath, excludePaths) {
continue
}
childNode := dto.CleanTree{
ID: uuid.NewString(),
Label: entry.Name(),
IsCheck: false,
IsRecommend: false,
Name: childPath,
Type: "unknown_backup",
}
if entry.IsDir() {
itemSize, _ := fileOp.GetDirSize(childPath)
childNode.Size = uint64(itemSize)
childNode.IsCheck = true
childNode.IsRecommend = true
treeData.Size += childNode.Size
} else {
info, _ := entry.Info()
childNode.Size = uint64(info.Size())
treeData.Size += childNode.Size
}
treeData.Children = append(treeData.Children, childNode)
}
return treeData
}
func loadUnknownWebsiteLog(fileOp fileUtils.FileOp) dto.CleanTree {
treeData := dto.CleanTree{
ID: uuid.NewString(),
Label: "unknown_website_log",
IsCheck: false,
IsRecommend: true,
Type: "unknown_backup",
}
dir := path.Join(global.Dir.LocalBackupDir, "log/website")
websites, _ := websiteRepo.List()
websiteMap := make(map[string]struct{})
for _, website := range websites {
websiteMap[website.Alias] = struct{}{}
}
entries, _ := os.ReadDir(dir)
for _, entry := range entries {
if !entry.IsDir() {
continue
}
dirName := entry.Name()
if _, ok := websiteMap[dirName]; !ok {
dirPath := path.Join(dir, dirName)
itemSize, _ := fileOp.GetDirSize(dirPath)
childData := dto.CleanTree{
ID: uuid.NewString(),
Label: dirName,
IsCheck: true,
IsRecommend: true,
Name: dirPath,
Type: "unknown_backup",
Size: uint64(itemSize),
}
treeData.Size += uint64(itemSize)
treeData.Children = append(treeData.Children, childData)
}
}
if treeData.Size > 0 {
treeData.IsCheck = true
}
return treeData
}
func loadFileOrDirWithExclude(fileOp fileUtils.FileOp, index uint, dir string, rootTree *dto.CleanTree, excludes []string) error {
index++
entries, err := os.ReadDir(dir)
if err != nil {
return err
}
for _, entry := range entries {
childPath := filepath.Join(dir, entry.Name())
if isExactPathMatch(childPath, excludes) {
continue
}
childNode := dto.CleanTree{
ID: uuid.NewString(),
Label: entry.Name(),
IsCheck: false,
IsRecommend: false,
Name: childPath,
Type: "unknown_backup",
}
if entry.IsDir() {
if index < 4 {
if err = loadFileOrDirWithExclude(fileOp, index, childPath, &childNode, excludes); err != nil {
return err
}
childNode.Size = 0
for _, child := range childNode.Children {
childNode.Size += child.Size
}
rootTree.Size += childNode.Size
} else {
itemSize, _ := fileOp.GetDirSize(childPath)
childNode.Size = uint64(itemSize)
rootTree.Size += childNode.Size
}
} else {
info, _ := entry.Info()
childNode.Size = uint64(info.Size())
rootTree.Size += childNode.Size
}
rootTree.Children = append(rootTree.Children, childNode)
}
return nil
}
func isExactPathMatch(path string, excludePaths []string) bool {
cleanPath := filepath.Clean(path)
for _, excludePath := range excludePaths {
cleanExclude := filepath.Clean(excludePath)
if cleanPath == cleanExclude {
return true
}
}
return false
}
func loadRollBackTree(fileOp fileUtils.FileOp) []dto.CleanTree {
var treeData []dto.CleanTree
treeData = loadTreeWithCheck(treeData, path.Join(global.Dir.BaseDir, rollbackPath, "app"), "rollback_app", fileOp)
@@ -360,7 +605,6 @@ func loadRollBackTree(fileOp fileUtils.FileOp) []dto.CleanTree {
func loadUploadTree(fileOp fileUtils.FileOp) []dto.CleanTree {
var treeData []dto.CleanTree
treeData = loadTreeWithCheck(treeData, path.Join(global.Dir.BaseDir, tmpUploadPath), "upload_tmp", fileOp)
treeData = loadTreeWithCheck(treeData, path.Join(global.Dir.BaseDir, uploadPath, "app"), "upload_app", fileOp)
treeData = loadTreeWithCheck(treeData, path.Join(global.Dir.BaseDir, uploadPath, "website"), "upload_website", fileOp)
treeData = loadTreeWithCheck(treeData, path.Join(global.Dir.BaseDir, uploadPath, "database"), "upload_database", fileOp)
@@ -382,6 +626,22 @@ func loadDownloadTree(fileOp fileUtils.FileOp) []dto.CleanTree {
uploadTreeData := loadTreeWithAllFile(true, path5, "download", path5, fileOp)
treeData = append(treeData, uploadTreeData...)
appTmpDownloadTree := loadAppTmpDownloadTree(fileOp)
if len(appTmpDownloadTree) > 0 {
parentTree := dto.CleanTree{
ID: uuid.NewString(),
Label: "app_tmp_download",
IsCheck: true,
IsRecommend: true,
Type: "app_tmp_download",
Name: "apps",
}
for _, child := range appTmpDownloadTree {
parentTree.Size += child.Size
}
parentTree.Children = appTmpDownloadTree
treeData = append(treeData, parentTree)
}
return treeData
}
@@ -393,26 +653,113 @@ func loadLogTree(fileOp fileUtils.FileOp) []dto.CleanTree {
for _, file := range list1 {
size += file.Size
}
treeData = append(treeData, dto.CleanTree{ID: uuid.NewString(), Label: "system_log", Size: uint64(size), Children: list1, Type: "system_log", IsRecommend: true})
treeData = append(treeData, dto.CleanTree{ID: uuid.NewString(), Label: "system_log", Size: size, Children: list1, Type: "system_log", IsRecommend: true})
path2 := path.Join(global.Dir.TaskDir)
list2 := loadTreeWithDir(false, "task_log", path2, fileOp)
size2, _ := fileOp.GetDirSize(path2)
treeData = append(treeData, dto.CleanTree{ID: uuid.NewString(), Label: "task_log", Size: uint64(size2), Children: list2, Type: "task_log"})
path3 := path.Join(global.Dir.TmpDir, "script")
list3 := loadTreeWithAllFile(true, path3, "script", path3, fileOp)
size3, _ := fileOp.GetDirSize(path3)
treeData = append(treeData, dto.CleanTree{ID: uuid.NewString(), Label: "script", Size: uint64(size3), Children: list3, Type: "script", IsRecommend: true})
websiteLogList := loadWebsiteLogTree(fileOp)
logTotalSize := uint64(0)
for _, websiteLog := range websiteLogList {
logTotalSize += websiteLog.Size
}
treeData = append(treeData, dto.CleanTree{ID: uuid.NewString(), Label: "website_log", Size: logTotalSize, Children: websiteLogList, Type: "website_log", IsRecommend: false})
return treeData
}
func loadWebsiteLogTree(fileOp fileUtils.FileOp) []dto.CleanTree {
websites, _ := websiteRepo.List()
if len(websites) == 0 {
return nil
}
var res []dto.CleanTree
for _, website := range websites {
size3, _ := fileOp.GetDirSize(path.Join(GetSiteDir(website.Alias), "log"))
res = append(res, dto.CleanTree{
ID: uuid.NewString(),
Label: website.PrimaryDomain,
Size: uint64(size3),
Type: "website_log",
Name: website.Alias,
})
}
return res
}
func loadAppTmpDownloadTree(fileOp fileUtils.FileOp) []dto.CleanTree {
appDirs, err := os.ReadDir(global.Dir.RemoteAppResourceDir)
if err != nil {
return nil
}
var res []dto.CleanTree
for _, appDir := range appDirs {
if !appDir.IsDir() {
continue
}
appKey := appDir.Name()
app, _ := appRepo.GetFirst(appRepo.WithKey(appKey))
if app.ID == 0 {
continue
}
appPath := filepath.Join(global.Dir.RemoteAppResourceDir, appKey)
versionDirs, err := os.ReadDir(appPath)
if err != nil {
continue
}
appDetails, _ := appDetailRepo.GetBy(appDetailRepo.WithAppId(app.ID))
existingVersions := make(map[string]bool)
for _, appDetail := range appDetails {
existingVersions[appDetail.Version] = true
}
var missingVersions []string
for _, versionDir := range versionDirs {
if !versionDir.IsDir() {
continue
}
version := versionDir.Name()
if !existingVersions[version] {
missingVersions = append(missingVersions, version)
}
}
if len(missingVersions) > 0 {
var appTree dto.CleanTree
appTree.ID = uuid.NewString()
appTree.Label = app.Name
appTree.Type = "app_tmp_download"
appTree.Name = appKey
appTree.IsRecommend = true
appTree.IsCheck = true
for _, version := range missingVersions {
versionPath := filepath.Join(appPath, version)
size, _ := fileOp.GetDirSize(versionPath)
appTree.Size += uint64(size)
appTree.Children = append(appTree.Children, dto.CleanTree{
ID: uuid.NewString(),
Label: version,
Size: uint64(size),
IsCheck: true,
IsRecommend: true,
Type: "app_tmp_download_version",
Name: path.Join(appKey, version),
})
}
res = append(res, appTree)
}
}
return res
}
func loadContainerTree() []dto.CleanTree {
var treeData []dto.CleanTree
client, err := docker.NewDockerClient()
if err != nil {
return treeData
}
defer client.Close()
diskUsage, err := client.DiskUsage(context.Background(), types.DiskUsageOptions{})
if err != nil {
return treeData
@@ -527,7 +874,7 @@ func loadTreeWithAllFile(isCheck bool, originalPath, treeType, pathItem string,
ID: uuid.NewString(),
Label: file.Name(),
Type: treeType,
Size: uint64(size),
Size: size,
Name: name,
IsCheck: isCheck,
IsRecommend: isCheck,
@@ -545,6 +892,18 @@ func dropFileOrDir(itemPath string) {
global.LOG.Errorf("drop file %s failed, err %v", itemPath, err)
}
}
func dropFile(itemPath string) {
info, err := os.Stat(itemPath)
if err != nil {
return
}
if info.IsDir() {
return
}
if err := os.Remove(itemPath); err != nil {
global.LOG.Errorf("drop file %s failed, err %v", itemPath, err)
}
}
func dropBuildCache() (int, int) {
client, err := docker.NewDockerClient()
@@ -552,6 +911,7 @@ func dropBuildCache() (int, int) {
global.LOG.Errorf("do not get docker client")
return 0, 0
}
defer client.Close()
opts := build.CachePruneOptions{}
opts.All = true
res, err := client.BuildCachePrune(context.Background(), opts)
@@ -568,6 +928,7 @@ func dropImages() (int, int) {
global.LOG.Errorf("do not get docker client")
return 0, 0
}
defer client.Close()
pruneFilters := filters.NewArgs()
pruneFilters.Add("dangling", "false")
res, err := client.ImagesPrune(context.Background(), pruneFilters)
@@ -584,6 +945,7 @@ func dropContainers() (int, int) {
global.LOG.Errorf("do not get docker client")
return 0, 0
}
defer client.Close()
pruneFilters := filters.NewArgs()
res, err := client.ContainersPrune(context.Background(), pruneFilters)
if err != nil {
@@ -599,6 +961,7 @@ func dropVolumes() (int, int) {
global.LOG.Errorf("do not get docker client")
return 0, 0
}
defer client.Close()
pruneFilters := filters.NewArgs()
versions, err := client.ServerVersion(context.Background())
if err != nil {
@@ -616,6 +979,18 @@ func dropVolumes() (int, int) {
return len(res.VolumesDeleted), int(res.SpaceReclaimed)
}
func dropWebsiteLog(alias string) {
accessLogPath := path.Join(GetSiteDir(alias), "log", "access.log")
errorLogPath := path.Join(GetSiteDir(alias), "log", "error.log")
if err := os.Truncate(accessLogPath, 0); err != nil {
global.LOG.Errorf("truncate access log %s failed, err %v", accessLogPath, err)
}
if err := os.Truncate(errorLogPath, 0); err != nil {
global.LOG.Errorf("truncate error log %s failed, err %v", errorLogPath, err)
}
}
func dropTaskLog(logDir string) {
files, err := os.ReadDir(logDir)
if err != nil {
@@ -647,11 +1022,13 @@ func dropTaskLog(logDir string) {
if err == nil {
_ = xpackDB.Table("tampers").Where("task_id != ?", "").Select("task_id").Find(&usedTasks).Error
}
defer common.CloseDB(xpackDB)
case "System":
xpackDB, err := common.LoadDBConnByPathWithErr(path.Join(global.CONF.Base.InstallDir, "1panel/db/xpack.db"), "xpack.db")
if err == nil {
_ = xpackDB.Model("nodes").Where("task_id != ?", "").Select("task_id").Find(&usedTasks).Error
}
defer common.CloseDB(xpackDB)
default:
dropFileOrDir(logDir)
_ = taskRepo.Delete(repo.WithByType(taskType))
@@ -725,3 +1102,36 @@ func scanFile(pathItem string, size *int64, count *int) {
}
}
}
func cleanEmptyDirs(root string) error {
dirsToCheck := make([]string, 0)
err := filepath.WalkDir(root, func(path string, d os.DirEntry, err error) error {
if err != nil {
return nil
}
if d.IsDir() {
dirsToCheck = append(dirsToCheck, path)
}
return nil
})
if err != nil {
return err
}
for i := len(dirsToCheck) - 1; i >= 0; i-- {
dir := dirsToCheck[i]
if dir == root {
continue
}
entries, err := os.ReadDir(dir)
if err != nil {
continue
}
if len(entries) == 0 {
_ = os.Remove(dir)
}
}
return nil
}
+25 -13
View File
@@ -32,25 +32,27 @@ func (s *DiskService) GetCompleteDiskInfo() (*response.CompleteDiskInfo, error)
output, err := cmd.RunDefaultWithStdoutBashC("lsblk -J -o NAME,SIZE,TYPE,MOUNTPOINT,FSTYPE,MODEL,SERIAL,TRAN,ROTA")
if err == nil {
diskInfos, err = parseLsblkJsonOutput(output)
if err != nil {
return nil, fmt.Errorf("failed to parse lsblk output: %v", err)
}
} else {
output, err = cmd.RunDefaultWithStdoutBashC("lsblk -P -o NAME,SIZE,TYPE,MOUNTPOINT,FSTYPE,MODEL,SERIAL,TRAN,ROTA")
if err != nil {
return nil, fmt.Errorf("failed to run lsblk command: %v", err)
}
diskInfos, err = parseLsblkOutput(output)
if err != nil {
return nil, fmt.Errorf("failed to parse lsblk output: %v", err)
if err == nil {
result := organizeDiskInfo(diskInfos)
return &result, nil
}
}
output, err = cmd.RunDefaultWithStdoutBashC("lsblk -P -o NAME,SIZE,TYPE,MOUNTPOINT,FSTYPE,MODEL,SERIAL,TRAN,ROTA")
if err != nil {
return nil, fmt.Errorf("failed to run lsblk command: %v", err)
}
diskInfos, err = parseLsblkOutput(output)
if err != nil {
return nil, fmt.Errorf("failed to parse lsblk output: %v", err)
}
result := organizeDiskInfo(diskInfos)
return &result, nil
}
func (s *DiskService) PartitionDisk(req request.DiskPartitionRequest) (string, error) {
if cmd.CheckIllegal(req.Device, req.Filesystem, req.MountPoint, req.Label) {
return "", buserr.New("ErrCmdIllegal")
}
if !strings.HasPrefix("/dev", req.Device) {
req.Device = "/dev/" + req.Device
}
@@ -105,6 +107,9 @@ func (s *DiskService) PartitionDisk(req request.DiskPartitionRequest) (string, e
}
func (s *DiskService) MountDisk(req request.DiskMountRequest) error {
if cmd.CheckIllegal(req.Device, req.MountPoint, req.Filesystem) {
return buserr.New("ErrCmdIllegal")
}
if !deviceExists(req.Device) {
return buserr.WithName("DeviceNotFound", req.Device)
}
@@ -132,7 +137,11 @@ func (s *DiskService) MountDisk(req request.DiskMountRequest) error {
return buserr.WithErr("MountDiskErr", err)
}
if req.AutoMount {
if err := addToFstabWithOptions(req.Device, req.MountPoint, req.Filesystem, ""); err != nil {
options := ""
if req.NoFail {
options = "defaults,nofail"
}
if err := addToFstabWithOptions(req.Device, req.MountPoint, req.Filesystem, options); err != nil {
return buserr.WithErr("MountDiskErr", err)
}
}
@@ -141,6 +150,9 @@ func (s *DiskService) MountDisk(req request.DiskMountRequest) error {
}
func (s *DiskService) UnmountDisk(req request.DiskUnmountRequest) error {
if cmd.CheckIllegal(req.MountPoint) {
return buserr.New("ErrCmdIllegal")
}
if !isPointMounted(req.MountPoint) {
return buserr.New("MountDiskErr")
}

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