Compare commits

..
141 Commits
Author SHA1 Message Date
CityFun 8644579eda feat: Agent Model Account support custom config (#11933)
* feat: Agent Model Account support custom config

* feat: Add agent gatewayControlUi config
2026-02-24 18:35:38 +08:00
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
282 changed files with 12819 additions and 2162 deletions
+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
+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)
}
+22 -9
View File
@@ -2,12 +2,12 @@ package v2
import (
"net/http"
"time"
"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"
)
@@ -164,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
@@ -203,22 +203,35 @@ func (b *BaseApi) GetAppListUpdate(c *gin.Context) {
// @Success 200 {file} file "app icon"
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /apps/icon/:appId [get]
// @Router /apps/icon/:key [get]
func (b *BaseApi) GetAppIcon(c *gin.Context) {
appID, err := helper.GetIntParamByKey(c, "appID")
appKey, err := helper.GetStrParamByKey(c, "key")
if err != nil {
helper.BadRequest(c, err)
return
}
iconBytes, err := appService.GetAppIcon(appID)
iconBytes, filename, etag, err := appService.GetAppIcon(appKey)
if err != nil {
helper.InternalServer(c, err)
return
}
c.Header("Content-Type", "image/png")
c.Header("Cache-Control", "public, max-age=31536000, immutable")
c.Header("Last-Modified", time.Now().UTC().Format(http.TimeFormat))
c.Data(http.StatusOK, "image/png", iconBytes)
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
+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 {
+24 -1
View File
@@ -529,7 +529,7 @@ func (b *BaseApi) DownloadContainerLogs(c *gin.Context) {
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)
}
@@ -731,12 +731,33 @@ 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
@@ -750,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")
@@ -759,6 +781,7 @@ func (b *BaseApi) ContainerStreamLogs(c *gin.Context) {
Since: since,
Follow: follow,
Tail: tail,
Timestamp: timestamp,
Type: "container",
}
if compose != "" {
+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()
+37
View File
@@ -978,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)
}
+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)
}
+4 -1
View File
@@ -80,7 +80,10 @@ 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)
}
+29 -18
View File
@@ -5,7 +5,6 @@ import (
"mime/multipart"
"net/http"
"net/url"
"reflect"
"strconv"
"github.com/1Panel-dev/1Panel/agent/app/model"
@@ -26,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.SuccessWithDataGzipped(c, dto.PageResult{
Total: total,
Items: accounts,
})
} else {
list, err := websiteSSLService.Search(req)
if err != nil {
helper.InternalServer(c, err)
return
}
helper.SuccessWithDataGzipped(c, list)
list, err := websiteSSLService.Search(req)
if err != nil {
helper.InternalServer(c, err)
return
}
helper.SuccessWithDataGzipped(c, list)
}
// @Tags Website SSL
+169
View File
@@ -0,0 +1,169 @@
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"`
APIType string `json:"apiType"`
MaxTokens int `json:"maxTokens"`
ContextWindow int `json:"contextWindow"`
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"`
APIType string `json:"apiType"`
MaxTokens int `json:"maxTokens"`
ContextWindow int `json:"contextWindow"`
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"`
Model string `json:"model"`
APIType string `json:"apiType"`
MaxTokens int `json:"maxTokens"`
ContextWindow int `json:"contextWindow"`
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"`
Model string `json:"model"`
APIType string `json:"apiType"`
MaxTokens int `json:"maxTokens"`
ContextWindow int `json:"contextWindow"`
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"`
Model string `json:"model"`
APIType string `json:"apiType"`
MaxTokens int `json:"maxTokens"`
ContextWindow int `json:"contextWindow"`
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"`
}
+7 -6
View File
@@ -65,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"`
}
+12 -7
View File
@@ -272,13 +272,14 @@ type ComposeContainer struct {
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"`
@@ -288,11 +289,13 @@ type ComposeOperation struct {
Force bool `json:"force"`
}
type ComposeUpdate struct {
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"`
@@ -304,6 +307,7 @@ 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"`
}
@@ -313,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"`
+2 -2
View File
@@ -21,8 +21,8 @@ type ImageInfo struct {
}
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 {
+8 -7
View File
@@ -54,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 {
+9
View File
@@ -163,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"`
}
+5
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"`
@@ -63,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 {
+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"`
}
+1
View File
@@ -40,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"`
+19
View File
@@ -0,0 +1,19 @@
package model
type Agent struct {
BaseModel
Name string `json:"name" gorm:"not null;unique"`
Provider string `json:"provider"`
Model string `json:"model"`
APIType string `json:"apiType"`
MaxTokens int `json:"maxTokens"`
ContextWindow int `json:"contextWindow"`
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"`
}
+19
View File
@@ -0,0 +1,19 @@
package model
type AgentAccount struct {
BaseModel
Provider string `json:"provider"`
Name string `json:"name"`
APIKey string `json:"apiKey"`
BaseURL string `json:"baseUrl"`
Model string `json:"model"`
APIType string `json:"apiType"`
MaxTokens int `json:"maxTokens"`
ContextWindow int `json:"contextWindow"`
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
@@ -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
@@ -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 {
+212
View File
@@ -0,0 +1,212 @@
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: 10,
Enabled: true,
},
"deepseek": {
Key: "deepseek",
DisplayName: "DeepSeek",
Sort: 20,
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"},
},
},
"custom": {
Key: "custom",
DisplayName: "Custom",
Sort: 30,
DefaultBaseURL: "",
EnvKey: "CUSTOM_API_KEY",
Enabled: true,
Models: []Model{},
},
"openai": {
Key: "openai",
DisplayName: "OpenAI",
Sort: 40,
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: 50,
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: 60,
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: 70,
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: 80,
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: 90,
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: 100,
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: 110,
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
}
+7
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
@@ -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)
+42 -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,6 +73,33 @@ 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)
@@ -124,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"
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 {
+98 -25
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"
@@ -110,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 {
@@ -227,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) {
@@ -247,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) {
@@ -580,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 {
@@ -599,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:
}
}
}
@@ -641,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)
@@ -656,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)
@@ -820,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) {
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)
@@ -852,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) {
+98 -256
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,6 +35,11 @@ import (
"gopkg.in/yaml.v3"
)
var (
appStoreSyncMu sync.Mutex
appStoreSyncing bool
)
type AppService struct {
}
@@ -41,12 +48,12 @@ type IAppService interface {
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(appID uint) ([]byte, error)
GetAppIcon(key string) ([]byte, string, string, error)
GetAppDetailByKey(appKey, version string) (response.AppDetailSimpleDTO, error)
}
@@ -332,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
@@ -526,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 {
@@ -802,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)
@@ -834,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()
@@ -891,295 +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() {
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 {
_ = NewISettingService().Update("AppStoreLastModified", "0")
_ = NewISettingService().Update("AppStoreSyncStatus", constant.StatusError)
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(appID uint) ([]byte, error) {
app, err := appRepo.GetFirst(repo.WithByID(appID))
func (a AppService) GetAppIcon(key string) ([]byte, string, string, error) {
app, err := appRepo.GetFirst(appRepo.WithKey(key))
if err != nil {
return nil, err
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 {
return nil, err
global.LOG.Warnf("[AppIcon] decode base64 icon failed key=%s, err=%v", key, err)
return nil, "", "", nil
}
return iconBytes, nil
return iconBytes, "", "", nil
}
+2 -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 {
+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
}
+88 -16
View File
@@ -392,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 {
@@ -764,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")
@@ -1271,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
@@ -1313,9 +1342,12 @@ func getApps(oldApps []model.App, items []dto.AppDefine, systemVersion string, t
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 {
@@ -1531,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
@@ -1554,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)
}
@@ -1583,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"))
@@ -1608,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)
@@ -1624,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
}
@@ -1961,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
}
@@ -2092,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]
@@ -2108,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
}
}
}
@@ -2138,7 +2208,9 @@ func SyncTags(remoteProperties dto.ExtraProperties) error {
}
}
tx.Commit()
if err = tx.Commit().Error; err != nil {
return err
}
return nil
}
+3 -3
View File
@@ -72,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))
}
@@ -372,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
}
@@ -535,7 +535,7 @@ func loadRefreshTokenByCode(backup *model.BackupAccount) (string, error) {
if err := json.Unmarshal([]byte(backup.Vars), &varMap); err != nil {
return "", fmt.Errorf("unmarshal backup vars failed, err: %v", err)
}
if _, ok := varMap["refresh_token"]; ok {
if token, ok := varMap["refresh_token"]; ok && len(token.(string)) != 0 {
return "", nil
}
refreshToken := ""
+8 -2
View File
@@ -44,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
@@ -87,7 +87,12 @@ 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()
}
@@ -225,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
+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),
+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
}
+17 -13
View File
@@ -59,9 +59,15 @@ 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
@@ -74,16 +80,14 @@ type IContainerService interface {
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
ComposeLogClean(req dto.ComposeLogClean) error
Prune(req dto.ContainerPrune) error
LoadUsers(req dto.OperationWithName) []string
@@ -435,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":
@@ -443,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 {
@@ -470,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{}
@@ -479,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() {
@@ -958,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)
}
@@ -1054,7 +1055,7 @@ 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")
}
@@ -1082,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...)
+42 -21
View File
@@ -205,7 +205,7 @@ func (u *ContainerService) CreateCompose(req dto.ComposeCreate) error {
}
go func() {
taskItem.AddSubTask(i18n.GetMsgByKey("ComposeCreate"), func(t *task.Task) error {
err := compose.UpWithTask(req.Path, t)
err := compose.UpWithTask(req.Path, t, req.ForcePull)
t.LogWithStatus(i18n.GetMsgByKey("ComposeCreate"), err)
if err != nil {
_, _ = compose.Down(req.Path)
@@ -258,31 +258,43 @@ func (u *ContainerService) ComposeUpdate(req dto.ComposeUpdate) error {
if cmd.CheckIllegal(req.Name, req.Path) {
return buserr.New("ErrCmdIllegal")
}
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)
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.DetailPath)
if err := newComposeEnv(req.DetailPath, 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 {
global.LOG.Errorf("update failed when handle compose up, std: %s, err: %s, now try to recreate the old compose file", stdout, 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, std: %v, err: %s", stdout, err)
}
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
}
@@ -338,6 +350,15 @@ func (u *ContainerService) ComposeLogClean(req dto.ComposeLogClean) error {
})
}
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)
+4 -1
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 {
@@ -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
}
+5
View File
@@ -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, ","),
})
}
}
+2 -2
View File
@@ -418,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))
@@ -461,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
}
+1 -1
View File
@@ -142,7 +142,7 @@ func (u *DashboardService) LoadCurrentInfoForNode() *dto.NodeCurrent {
func (u *DashboardService) LoadBaseInfo(ioOption string, netOption string) (*dto.DashboardBase, error) {
var baseInfo dto.DashboardBase
hostInfo, err := psutil.HOST.GetHostInfo(false)
hostInfo, err := psutil.HOST.GetHostInfo(true)
if err != nil {
return nil, err
}
+87 -53
View File
@@ -105,7 +105,11 @@ func (u *DeviceService) Clean(req []dto.Clean) {
case "tmp_backup":
dropFileOrDir(path.Join(global.Dir.LocalBackupDir, "tmp"))
case "unknown_backup":
dropFileOrDir(item.Name)
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"))
@@ -182,6 +186,7 @@ func (u *DeviceService) Clean(req []dto.Clean) {
}
}
_ = 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)))
@@ -295,7 +300,7 @@ func loadAgentPackage(fileOp fileUtils.FileOp) dto.CleanTree {
itemTree := dto.CleanTree{
ID: uuid.NewString(),
Label: "agent_packages",
IsCheck: true,
IsCheck: false,
IsRecommend: true,
Type: "agent",
}
@@ -314,17 +319,19 @@ func loadAgentPackage(fileOp fileUtils.FileOp) dto.CleanTree {
Type: "agent",
})
} else {
if strings.HasPrefix(file.Name(), fmt.Sprintf("1panel-agent_%s_a", global.CONF.Base.Version)) {
itemSize, _ := file.Info()
itemName := file.Name()
isCurrentVersion := strings.HasPrefix(itemName, fmt.Sprintf("1panel-agent_%s_", global.CONF.Base.Version))
if isCurrentVersion {
continue
}
itemSize, _ := file.Info()
itemTree.Size += uint64(itemSize.Size())
itemTree.Children = append(itemTree.Children, dto.CleanTree{
ID: uuid.NewString(),
Label: file.Name(),
Name: file.Name(),
Label: itemName,
Name: itemName,
Size: uint64(itemSize.Size()),
IsCheck: true,
IsCheck: !isCurrentVersion,
IsRecommend: true,
Type: "agent",
})
@@ -363,7 +370,7 @@ func loadBackupTree(fileOp fileUtils.FileOp) []dto.CleanTree {
treeData = append(treeData, loadUnknownDbs(fileOp, recordMap))
treeData = append(treeData, loadUnknownWebsites(fileOp, recordMap))
treeData = append(treeData, loadUnknownSnapshot(fileOp))
treeData = append(treeData, loadUnknownWebsiteLog(fileOp, recordMap))
treeData = append(treeData, loadUnknownWebsiteLog(fileOp))
return treeData
}
@@ -376,70 +383,68 @@ func loadUnknownApps(fileOp fileUtils.FileOp, recordMap map[string][]string) dto
for _, item := range val {
excludePaths = append(excludePaths, path.Join(global.Dir.LocalBackupDir, itemName, item))
}
} else {
excludePaths = append(excludePaths, path.Join(global.Dir.LocalBackupDir, itemName))
}
}
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, path.Join(global.Dir.LocalBackupDir, "app"), &treeData, excludePaths)
loadBackupIsCheck(&treeData)
_ = loadFileOrDirWithExclude(fileOp, 0, backupPath, &treeData, excludePaths)
return treeData
}
func loadUnknownDbs(fileOp fileUtils.FileOp, recordMap map[string][]string) dto.CleanTree {
apps, _ := appInstallRepo.ListBy(context.Background())
dbs, _ := databaseRepo.GetList()
var excludePaths []string
appMap := make(map[string]struct{})
for _, app := range apps {
appMap[fmt.Sprintf("database/%s/%s", app.App.Key, app.Name)] = struct{}{}
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 := appMap[itemName]; ok {
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, path.Join(global.Dir.LocalBackupDir, "database"), &treeData, excludePaths)
loadBackupIsCheck(&treeData)
_ = 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.PrimaryDomain)
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))
}
} else {
excludePaths = append(excludePaths, path.Join(global.Dir.LocalBackupDir, itemName))
}
}
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, path.Join(global.Dir.LocalBackupDir, "website"), &treeData, excludePaths)
loadBackupIsCheck(&treeData)
_ = loadFileOrDirWithExclude(fileOp, 0, backupPath, &treeData, excludePaths)
return treeData
}
func loadUnknownSnapshot(fileOp fileUtils.FileOp) dto.CleanTree {
@@ -448,21 +453,21 @@ func loadUnknownSnapshot(fileOp fileUtils.FileOp) dto.CleanTree {
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",
}
dir := path.Join(global.Dir.LocalBackupDir, "system_snapshot")
entries, _ := os.ReadDir(dir)
entries, _ := os.ReadDir(backupPath)
for _, entry := range entries {
childPath := filepath.Join(dir, entry.Name())
childPath := filepath.Join(backupPath, entry.Name())
if isExactPathMatch(childPath, excludePaths) {
continue
}
childNode := dto.CleanTree{
ID: uuid.NewString(),
Label: entry.Name(),
@@ -488,7 +493,7 @@ func loadUnknownSnapshot(fileOp fileUtils.FileOp) dto.CleanTree {
return treeData
}
func loadUnknownWebsiteLog(fileOp fileUtils.FileOp, recordMap map[string][]string) dto.CleanTree {
func loadUnknownWebsiteLog(fileOp fileUtils.FileOp) dto.CleanTree {
treeData := dto.CleanTree{
ID: uuid.NewString(),
Label: "unknown_website_log",
@@ -497,14 +502,19 @@ func loadUnknownWebsiteLog(fileOp fileUtils.FileOp, recordMap map[string][]strin
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()
itemName := fmt.Sprintf("log/website/%s", dirName)
if _, ok := recordMap[itemName]; !ok {
if _, ok := websiteMap[dirName]; !ok {
dirPath := path.Join(dir, dirName)
itemSize, _ := fileOp.GetDirSize(dirPath)
childData := dto.CleanTree{
@@ -537,7 +547,6 @@ func loadFileOrDirWithExclude(fileOp fileUtils.FileOp, index uint, dir string, r
if isExactPathMatch(childPath, excludes) {
continue
}
childNode := dto.CleanTree{
ID: uuid.NewString(),
Label: entry.Name(),
@@ -572,26 +581,6 @@ func loadFileOrDirWithExclude(fileOp fileUtils.FileOp, index uint, dir string, r
return nil
}
func loadBackupIsCheck(treeData *dto.CleanTree) {
for i := 0; i < len(treeData.Children); i++ {
if len(treeData.Children[i].Children) == 0 {
treeData.Children[i].IsCheck = true
treeData.Children[i].IsRecommend = true
continue
}
if treeData.Label != "unknown_database" && treeData.Label != "unknown_app" {
continue
}
for j := 0; j < len(treeData.Children[i].Children); j++ {
if len(treeData.Children[i].Children[j].Children) == 0 {
treeData.Children[i].Children[j].IsCheck = true
treeData.Children[i].Children[j].IsRecommend = true
continue
}
}
}
}
func isExactPathMatch(path string, excludePaths []string) bool {
cleanPath := filepath.Clean(path)
@@ -903,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()
@@ -1101,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
}
+9
View File
@@ -50,6 +50,9 @@ func (s *DiskService) GetCompleteDiskInfo() (*response.CompleteDiskInfo, error)
}
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
}
@@ -104,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)
}
@@ -144,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")
}
+5 -3
View File
@@ -12,9 +12,11 @@ var (
appInstallResourceRepo = repo.NewIAppInstallResourceRpo()
appIgnoreUpgradeRepo = repo.NewIAppIgnoreUpgradeRepo()
aiRepo = repo.NewIAiRepo()
mcpServerRepo = repo.NewIMcpServerRepo()
tensorrtLLMRepo = repo.NewITensorRTLLMRepo()
aiRepo = repo.NewIAiRepo()
mcpServerRepo = repo.NewIMcpServerRepo()
tensorrtLLMRepo = repo.NewITensorRTLLMRepo()
agentRepo = repo.NewIAgentRepo()
agentAccountRepo = repo.NewIAgentAccountRepo()
mysqlRepo = repo.NewIMysqlRepo()
postgresqlRepo = repo.NewIPostgresqlRepo()
+93 -9
View File
@@ -3,6 +3,7 @@ package service
import (
"bufio"
"context"
"encoding/base64"
"encoding/json"
"fmt"
"io"
@@ -75,12 +76,19 @@ type IFileService interface {
GetUsersAndGroups() (*response.UserGroupResponse, error)
Convert(req request.FileConvertRequest)
ConvertLog(req dto.PageInfo) (int64, []response.FileConvertLog, error)
BatchGetRemarks(req request.FileRemarkBatch) map[string]string
SetRemark(req request.FileRemarkUpdate) error
}
var filteredPaths = []string{
"/.1panel_clash",
}
const (
fileRemarkXattr = "user.1panel.remark"
fileRemarkEncodedMaxLen = 256
)
func NewIFileService() IFileService {
return &FileService{}
}
@@ -307,19 +315,18 @@ func (f *FileService) ChangeMode(op request.FileCreate) error {
func (f *FileService) BatchChangeModeAndOwner(op request.FileRoleReq) error {
fo := files.NewFileOp()
for _, path := range op.Paths {
if !fo.Stat(path) {
for _, p := range op.Paths {
if !fo.Stat(p) {
return buserr.New("ErrPathNotFound")
}
if err := fo.ChownR(path, op.User, op.Group, op.Sub); err != nil {
return err
}
if err := fo.ChmodR(path, op.Mode, op.Sub); err != nil {
return err
}
}
if err := fo.ChownRPaths(op.Paths, op.User, op.Group, op.Sub); err != nil {
return err
}
if err := fo.ChmodRPaths(op.Paths, op.Mode, op.Sub); err != nil {
return err
}
return nil
}
func (f *FileService) ChangeOwner(req request.FileRoleUpdate) error {
@@ -760,6 +767,52 @@ func (f *FileService) GetUsersAndGroups() (*response.UserGroupResponse, error) {
}, nil
}
func (f *FileService) BatchGetRemarks(req request.FileRemarkBatch) map[string]string {
remarks := make(map[string]string)
for _, filePath := range req.Paths {
remark, err := getFileRemark(filePath)
if err != nil {
if isXattrNotSupported(err) {
return map[string]string{}
}
continue
}
if remark == "" {
continue
}
remarks[filePath] = remark
}
return remarks
}
func (f *FileService) SetRemark(req request.FileRemarkUpdate) error {
if req.Remark == "" {
if err := unix.Lremovexattr(req.Path, fileRemarkXattr); err != nil {
if isXattrNotFound(err) {
return nil
}
if isXattrNotSupported(err) {
return buserr.WithDetail("ErrInvalidParams", "xattr not supported", err)
}
return err
}
return nil
}
encoded := base64.StdEncoding.EncodeToString([]byte(req.Remark))
if len(encoded) >= fileRemarkEncodedMaxLen {
return buserr.WithDetail("ErrInvalidParams", "remark length must be less than 256", nil)
}
if err := unix.Lsetxattr(req.Path, fileRemarkXattr, []byte(encoded), 0); err != nil {
if isXattrNotSupported(err) {
return buserr.WithDetail("ErrInvalidParams", "xattr not supported", err)
}
return err
}
return nil
}
func getValidGroups() (map[string]bool, error) {
groupFile, err := os.Open("/etc/group")
if err != nil {
@@ -786,6 +839,37 @@ func getValidGroups() (map[string]bool, error) {
return groupMap, nil
}
func getFileRemark(filePath string) (string, error) {
size, err := unix.Lgetxattr(filePath, fileRemarkXattr, nil)
if err != nil {
if isXattrNotFound(err) {
return "", nil
}
return "", err
}
if size == 0 {
return "", nil
}
buf := make([]byte, size)
n, err := unix.Lgetxattr(filePath, fileRemarkXattr, buf)
if err != nil {
return "", err
}
decoded, err := base64.StdEncoding.DecodeString(string(buf[:n]))
if err != nil {
return "", err
}
return string(decoded), nil
}
func isXattrNotSupported(err error) bool {
return errors.Is(err, unix.ENOTSUP) || errors.Is(err, unix.EOPNOTSUPP)
}
func isXattrNotFound(err error) bool {
return errors.Is(err, unix.ENODATA)
}
func getValidUsers(validGroups map[string]bool) ([]response.UserInfo, map[string]struct{}, error) {
passwdFile, err := os.Open("/etc/passwd")
if err != nil {
+6 -27
View File
@@ -112,29 +112,15 @@ func (u *FirewallService) SearchWithPage(req dto.RuleSearch) (int64, interface{}
}
}
var datasFilterStatus []fireClient.FireInfo
if len(req.Status) != 0 {
for _, data := range datas {
if req.Status == "free" && len(data.UsedStatus) == 0 {
datasFilterStatus = append(datasFilterStatus, data)
}
if req.Status == "used" && len(data.UsedStatus) != 0 {
datasFilterStatus = append(datasFilterStatus, data)
}
}
} else {
datasFilterStatus = datas
}
var datasFilterStrategy []fireClient.FireInfo
if len(req.Strategy) != 0 {
for _, data := range datasFilterStatus {
for _, data := range datas {
if req.Strategy == data.Strategy {
datasFilterStrategy = append(datasFilterStrategy, data)
}
}
} else {
datasFilterStrategy = datasFilterStatus
datasFilterStrategy = datas
}
total, start, end := len(datasFilterStrategy), (req.Page-1)*req.PageSize, req.Page*req.PageSize
@@ -203,15 +189,15 @@ func (u *FirewallService) OperateFirewall(req dto.FirewallOperation) error {
needRestartDocker = true
case "disableBanPing":
if err := firewall.UpdatePingStatus("0"); err != nil {
_ = settingRepo.Update("BanPing", constant.StatusDisable)
return err
}
_ = settingRepo.Update("BanPing", constant.StatusDisable)
return nil
case "enableBanPing":
if err := firewall.UpdatePingStatus("1"); err != nil {
_ = settingRepo.Update("BanPing", constant.StatusEnable)
return err
}
_ = settingRepo.Update("BanPing", constant.StatusEnable)
return nil
default:
return fmt.Errorf("not supported operation: %s", req.Operation)
@@ -720,16 +706,9 @@ func checkPortUsed(ports, proto string, apps []portOfApp) string {
for _, app := range apps {
if app.HttpPort == ports || app.HttpsPort == ports {
return fmt.Sprintf("(%s)", app.AppName)
return app.AppName
}
}
port, err := strconv.Atoi(ports)
if err != nil {
global.LOG.Errorf(" convert string %v to int failed, err: %v", port, err)
return ""
}
if common.ScanPortWithProto(port, proto) {
return "inUsed"
}
return ""
}
+1 -1
View File
@@ -76,7 +76,7 @@ func (u *FtpService) Operate(operation string) error {
}
func (f *FtpService) SearchWithPage(req dto.SearchWithPage) (int64, interface{}, error) {
total, lists, err := ftpRepo.Page(req.Page, req.PageSize, ftpRepo.WithLikeUser(req.Info), repo.WithOrderBy("created_at desc"))
total, lists, err := ftpRepo.Page(req.Page, req.PageSize, ftpRepo.WithLikeUser(req.Info), repo.WithOrderDesc("created_at"))
if err != nil {
return 0, nil, err
}
+2 -2
View File
@@ -24,8 +24,8 @@ func NewIGroupService() IGroupService {
func (u *GroupService) List(req dto.OperateByType) ([]dto.GroupInfo, error) {
options := []repo.DBOption{
repo.WithOrderBy("is_default desc"),
repo.WithOrderBy("created_at desc"),
repo.WithOrderDesc("is_default"),
repo.WithOrderDesc("created_at"),
}
if len(req.Type) != 0 {
options = append(options, repo.WithByType(req.Type))
+30 -22
View File
@@ -321,36 +321,44 @@ func (u *ImageService) ImagePull(req dto.ImagePull) error {
}
func (u *ImageService) ImageLoad(req dto.ImageLoad) error {
taskItem, err := task.NewTaskWithOps(req.Path, task.TaskImport, task.TaskScopeImage, req.TaskID, 1)
taskItem, err := task.NewTaskWithOps(strings.Join(req.Paths, ","), task.TaskImport, task.TaskScopeImage, req.TaskID, 1)
if err != nil {
return fmt.Errorf("new task for image import failed, err: %v", err)
}
taskItem.AddSubTask(i18n.GetWithName("TaskImport", req.Path), func(t *task.Task) error {
file, err := os.Open(req.Path)
if err != nil {
return err
}
defer file.Close()
go func() {
client, err := docker.NewDockerClient()
if err != nil {
return err
taskItem.Log("Failed to create Docker client: " + err.Error())
return
}
defer client.Close()
res, err := client.ImageLoad(context.TODO(), file)
if err != nil {
return err
for _, itemPath := range req.Paths {
currentPath := itemPath
itemName := path.Base(currentPath)
taskItem.AddSubTask(i18n.GetWithName("TaskImport", itemName), func(t *task.Task) error {
taskItem.Logf("----------------- %s -----------------", itemName)
file, err := os.Open(currentPath)
if err != nil {
return err
}
defer file.Close()
res, err := client.ImageLoad(context.TODO(), file)
if err != nil {
return err
}
defer res.Body.Close()
content, err := io.ReadAll(res.Body)
if err != nil {
return err
}
if strings.Contains(string(content), "Error") {
return errors.New(string(content))
}
return nil
}, nil)
}
defer res.Body.Close()
content, err := io.ReadAll(res.Body)
if err != nil {
return err
}
if strings.Contains(string(content), "Error") {
return errors.New(string(content))
}
return nil
}, nil)
go func() {
_ = taskItem.Execute()
}()
return nil
+2 -2
View File
@@ -37,7 +37,7 @@ func NewIImageRepoService() IImageRepoService {
}
func (u *ImageRepoService) Page(req dto.SearchWithPage) (int64, interface{}, error) {
total, ops, err := imageRepoRepo.Page(req.Page, req.PageSize, repo.WithByLikeName(req.Info), repo.WithOrderBy("created_at desc"))
total, ops, err := imageRepoRepo.Page(req.Page, req.PageSize, repo.WithByLikeName(req.Info), repo.WithOrderDesc("created_at"))
var dtoOps []dto.ImageRepoInfo
for _, op := range ops {
var item dto.ImageRepoInfo
@@ -65,7 +65,7 @@ func (u *ImageRepoService) Login(req dto.OperateByID) error {
}
func (u *ImageRepoService) List() ([]dto.ImageRepoOption, error) {
ops, err := imageRepoRepo.List(repo.WithOrderBy("created_at desc"))
ops, err := imageRepoRepo.List(repo.WithOrderDesc("created_at"))
var dtoOps []dto.ImageRepoOption
for _, op := range ops {
if op.Status == constant.StatusSuccess {
+12
View File
@@ -230,6 +230,18 @@ func (s *IptablesService) Operate(req dto.IptablesOp) error {
}
_ = settingRepo.Update("IptablesStatus", constant.StatusEnable)
return nil
case "bind-base-without-init":
if err := iptables.BindChain(iptables.FilterTab, iptables.ChainInput, iptables.Chain1PanelBasicBefore, 1); err != nil {
return err
}
if err := iptables.BindChain(iptables.FilterTab, iptables.ChainInput, iptables.Chain1PanelBasic, 2); err != nil {
return err
}
if err := iptables.BindChain(iptables.FilterTab, iptables.ChainInput, iptables.Chain1PanelBasicAfter, 3); err != nil {
return err
}
_ = settingRepo.Update("IptablesStatus", constant.StatusEnable)
return nil
case "unbind-base":
if err := iptables.UnbindChain(iptables.FilterTab, iptables.ChainInput, iptables.Chain1PanelBasicAfter); err != nil {
return err
+58 -4
View File
@@ -2,15 +2,19 @@ package service
import (
"bufio"
"context"
"fmt"
"github.com/1Panel-dev/1Panel/agent/app/dto/request"
"github.com/1Panel-dev/1Panel/agent/utils/common"
"github.com/1Panel-dev/1Panel/agent/utils/websocket"
"github.com/shirou/gopsutil/v4/process"
"os"
"strconv"
"strings"
"syscall"
"time"
"github.com/1Panel-dev/1Panel/agent/app/dto/request"
"github.com/1Panel-dev/1Panel/agent/utils/common"
"github.com/1Panel-dev/1Panel/agent/utils/websocket"
"github.com/shirou/gopsutil/v4/net"
"github.com/shirou/gopsutil/v4/process"
)
type ProcessService struct{}
@@ -18,6 +22,7 @@ type ProcessService struct{}
type IProcessService interface {
StopProcess(req request.ProcessReq) error
GetProcessInfoByPID(pid int32) (*websocket.PsProcessData, error)
GetListeningProcess(c context.Context) ([]ListeningProcess, error)
}
func NewIProcessService() IProcessService {
@@ -35,6 +40,55 @@ func (ps *ProcessService) StopProcess(req request.ProcessReq) error {
return nil
}
type ListeningProcess struct {
PID int32
Port map[uint32]struct{}
Protocol uint32
Name string
}
func (ps *ProcessService) GetListeningProcess(c context.Context) ([]ListeningProcess, error) {
conn, err := net.ConnectionsMaxWithContext(c, "inet", 32768)
if err != nil {
return nil, err
}
procCache := make(map[int32]ListeningProcess, 64)
for _, conn := range conn {
if conn.Pid == 0 {
continue
}
if (conn.Status == "LISTEN" && conn.Type == syscall.SOCK_STREAM) || (conn.Type == syscall.SOCK_DGRAM && conn.Raddr.Port == 0) {
if _, exists := procCache[conn.Pid]; !exists {
proc, err := process.NewProcess(conn.Pid)
if err != nil {
continue
}
procData := ListeningProcess{
PID: conn.Pid,
}
procData.Name, _ = proc.Name()
procData.Port = make(map[uint32]struct{})
procData.Port[conn.Laddr.Port] = struct{}{}
procData.Protocol = conn.Type
procCache[conn.Pid] = procData
} else {
p := procCache[conn.Pid]
p.Port[conn.Laddr.Port] = struct{}{}
procCache[conn.Pid] = p
}
}
}
procs := make([]ListeningProcess, 0, len(procCache))
for _, proc := range procCache {
procs = append(procs, proc)
}
return procs, nil
}
func (ps *ProcessService) GetProcessInfoByPID(pid int32) (*websocket.PsProcessData, error) {
p, err := process.NewProcess(pid)
if err != nil {
+30 -28
View File
@@ -101,15 +101,15 @@ func (u *SnapshotService) SnapshotRecover(req dto.SnapshotRecover) error {
}()
if req.IsNew || snap.InterruptStep == "RecoverDownload" || req.ReDownload {
taskItem.AddSubTaskWithAlias(
taskItem.AddSubTaskWithAliasAndOps(
"RecoverDownload",
func(t *task.Task) error { return handleDownloadSnapshot(&itemHelper, snap, rootDir) },
nil,
nil, 0, 90*time.Minute,
)
req.IsNew = true
}
if req.IsNew || snap.InterruptStep == "RecoverDecompress" {
taskItem.AddSubTaskWithAlias(
taskItem.AddSubTaskWithAliasAndOps(
"RecoverDecompress",
func(t *task.Task) error {
itemHelper.Task.Log("---------------------- 2 / 11 ----------------------")
@@ -118,54 +118,54 @@ func (u *SnapshotService) SnapshotRecover(req dto.SnapshotRecover) error {
itemHelper.Task.LogWithStatus(i18n.GetMsgByKey("Decompress"), err)
return err
},
nil,
nil, 0, 90*time.Minute,
)
req.IsNew = true
}
if req.IsNew || snap.InterruptStep == "BackupBeforeRecover" {
taskItem.AddSubTaskWithAlias(
taskItem.AddSubTaskWithAliasAndOps(
"BackupBeforeRecover",
func(t *task.Task) error { return backupBeforeRecover(snap.Name, &itemHelper) },
nil,
nil, 0, 90*time.Minute,
)
req.IsNew = true
}
var snapJson SnapshotJson
taskItem.AddSubTaskWithAlias(
taskItem.AddSubTaskWithAliasAndOps(
"Readjson",
func(t *task.Task) error {
snapJson, err = readFromJson(path.Join(rootDir, snap.Name), &itemHelper)
return err
},
nil,
nil, 0, 90*time.Minute,
)
if req.IsNew || snap.InterruptStep == "RecoverApp" {
taskItem.AddSubTaskWithAlias(
taskItem.AddSubTaskWithAliasAndOps(
"RecoverApp",
func(t *task.Task) error { return recoverAppData(path.Join(rootDir, snap.Name), &itemHelper) },
nil,
nil, 0, 90*time.Minute,
)
req.IsNew = true
}
if req.IsNew || snap.InterruptStep == "RecoverBaseData" {
taskItem.AddSubTaskWithAlias(
taskItem.AddSubTaskWithAliasAndOps(
"RecoverBaseData",
func(t *task.Task) error { return recoverBaseData(path.Join(rootDir, snap.Name, "base"), &itemHelper) },
nil,
nil, 0, 90*time.Minute,
)
req.IsNew = true
}
if req.IsNew || snap.InterruptStep == "RecoverDBData" {
taskItem.AddSubTaskWithAlias(
taskItem.AddSubTaskWithAliasAndOps(
"RecoverDBData",
func(t *task.Task) error { return recoverDBData(path.Join(rootDir, snap.Name, "db"), &itemHelper) },
nil,
nil, 0, 90*time.Minute,
)
req.IsNew = true
}
if req.IsNew || snap.InterruptStep == "RecoverBackups" {
taskItem.AddSubTaskWithAlias(
taskItem.AddSubTaskWithAliasAndOps(
"RecoverBackups",
func(t *task.Task) error {
itemHelper.Task.Log("---------------------- 8 / 11 ----------------------")
@@ -174,12 +174,12 @@ func (u *SnapshotService) SnapshotRecover(req dto.SnapshotRecover) error {
itemHelper.Task.LogWithStatus(i18n.GetMsgByKey("Decompress"), err)
return err
},
nil,
nil, 0, 90*time.Minute,
)
req.IsNew = true
}
if req.IsNew || snap.InterruptStep == "RecoverWebsite" {
taskItem.AddSubTaskWithAlias(
taskItem.AddSubTaskWithAliasAndOps(
"RecoverWebsite",
func(t *task.Task) error {
itemHelper.Task.Log("---------------------- 9 / 11 ----------------------")
@@ -193,12 +193,12 @@ func (u *SnapshotService) SnapshotRecover(req dto.SnapshotRecover) error {
itemHelper.Task.LogWithStatus(i18n.GetMsgByKey("Decompress"), err)
return err
},
nil,
nil, 0, 90*time.Minute,
)
req.IsNew = true
}
if req.IsNew || snap.InterruptStep == "RecoverPanelData" {
taskItem.AddSubTaskWithAlias(
taskItem.AddSubTaskWithAliasAndOps(
"RecoverPanelData",
func(t *task.Task) error {
itemHelper.Task.Log("---------------------- 10 / 11 ----------------------")
@@ -218,16 +218,16 @@ func (u *SnapshotService) SnapshotRecover(req dto.SnapshotRecover) error {
}
return err
},
nil,
nil, 0, 90*time.Minute,
)
req.IsNew = true
}
taskItem.AddSubTaskWithAlias(
taskItem.AddSubTaskWithAliasAndOps(
"RecoverDBData",
func(t *task.Task) error {
return restartCompose(path.Join(snapJson.BaseDir, "1panel/docker/compose"), &itemHelper)
},
nil,
nil, 0, 90*time.Minute,
)
if err := taskItem.Execute(); err != nil {
@@ -277,14 +277,15 @@ func backupBeforeRecover(name string, itemHelper *snapRecoverHelper) error {
return err
}
}
err := itemHelper.FileOp.CopyDirWithExclude(global.Dir.DataDir, rootDir, []string{"cache", "tmp"})
itemHelper.Task.LogWithStatus(i18n.GetWithName("SnapCopy", global.Dir.DataDir), err)
dataDir := global.Dir.DataDir
err := itemHelper.FileOp.CopyDirWithExclude(dataDir, rootDir, []string{"cache", "tmp", "backup"})
itemHelper.Task.LogWithStatus(i18n.GetWithName("SnapCopy", dataDir), err)
if err != nil {
return err
}
openrestyDir, _ := settingRepo.GetValueByKey("WEBSITE_DIR")
if len(openrestyDir) != 0 && !strings.Contains(openrestyDir, global.Dir.DataDir) {
if len(openrestyDir) != 0 && !strings.Contains(openrestyDir, dataDir) {
err := itemHelper.FileOp.CopyDirWithExclude(openrestyDir, rootDir, nil)
itemHelper.Task.LogWithStatus(i18n.GetWithName("SnapCopy", openrestyDir), err)
if err != nil {
@@ -292,9 +293,10 @@ func backupBeforeRecover(name string, itemHelper *snapRecoverHelper) error {
}
}
if len(global.Dir.LocalBackupDir) != 0 && !strings.Contains(global.Dir.LocalBackupDir, global.Dir.DataDir) {
err = itemHelper.FileOp.CopyDirWithExclude(global.Dir.LocalBackupDir, rootDir, []string{"system_snapshot"})
itemHelper.Task.LogWithStatus(i18n.GetWithName("SnapCopy", global.Dir.LocalBackupDir), err)
backupDir := global.Dir.LocalBackupDir
if len(backupDir) != 0 && (backupDir == dataDir+"/backup" || !strings.HasPrefix(backupDir, dataDir)) {
err = itemHelper.FileOp.CopyDirWithExclude(backupDir, rootDir, []string{"system_snapshot", "tmp"})
itemHelper.Task.LogWithStatus(i18n.GetWithName("SnapCopy", backupDir), err)
if err != nil {
return err
}
+3 -2
View File
@@ -4,6 +4,7 @@ import (
"fmt"
"os"
"path"
"strings"
"github.com/1Panel-dev/1Panel/agent/app/repo"
@@ -88,7 +89,7 @@ func (u *SnapshotService) SnapshotRollback(req dto.SnapshotRecover) error {
taskItem.AddSubTask(
i18n.GetWithName("SnapCopy", global.Dir.LocalBackupDir),
func(t *task.Task) error {
return FileOp.CopyDir(path.Join(rootDir, "backup"), global.Dir.LocalBackupDir)
return FileOp.CopyDir(path.Join(rootDir, "backup"), strings.TrimSuffix(global.Dir.LocalBackupDir, "/backup"))
},
nil,
)
@@ -97,7 +98,7 @@ func (u *SnapshotService) SnapshotRollback(req dto.SnapshotRecover) error {
taskItem.AddSubTask(
i18n.GetWithName("SnapCopy", openrestyDir),
func(t *task.Task) error {
return FileOp.CopyDir(path.Join(rootDir, "www"), openrestyDir)
return FileOp.CopyDir(path.Join(rootDir, "www"), strings.TrimSuffix(openrestyDir, "/www"))
},
nil,
)
+3
View File
@@ -465,6 +465,9 @@ func (u *SSHService) LoadLog(ctx *gin.Context, req dto.SearchSSHLog) (int64, []d
command := ""
if len(req.Info) != 0 {
if cmd.CheckIllegal(req.Info) {
return 0, data, buserr.New("ErrCmdIllegal")
}
command = fmt.Sprintf(" | grep '%s'", req.Info)
}
+1 -1
View File
@@ -19,7 +19,7 @@ func NewITaskService() ITaskLogService {
func (u *TaskLogService) Page(req dto.SearchTaskLogReq) (int64, []dto.TaskDTO, error) {
opts := []repo.DBOption{
repo.WithOrderBy("created_at desc"),
repo.WithOrderDesc("created_at"),
}
if req.Status != "" {
opts = append(opts, repo.WithByStatus(req.Status))
+43 -18
View File
@@ -208,6 +208,13 @@ func (w WebsiteService) PageWebsite(req request.WebsiteSearch) (int64, []respons
IPV6: web.IPV6,
}
if siteDTO.Type == constant.Subsite {
parentWeb, _ := websiteRepo.GetFirst(repo.WithByID(web.ParentWebsiteID))
if parentWeb.ID != 0 {
siteDTO.ParentSite = parentWeb.PrimaryDomain
}
}
sites, _ := websiteRepo.List(websiteRepo.WithParentID(web.ID))
if len(sites) > 0 {
for _, site := range sites {
@@ -376,7 +383,7 @@ func (w WebsiteService) CreateWebsite(create request.WebsiteCreate) (err error)
req.AppDetailId = create.AppInstall.AppDetailId
req.Params = create.AppInstall.Params
req.AppContainerConfig = create.AppInstall.AppContainerConfig
install, err = NewIAppService().Install(req)
install, err = NewIAppService().Install(req, true)
if err != nil {
return err
}
@@ -1780,7 +1787,7 @@ func (w WebsiteService) GetRedirect(id uint) (res []response.NginxRedirectConfig
continue
}
fileName := entry.Name()
if !strings.HasSuffix(fileName, ".conf") && !strings.HasSuffix(fileName, ".conf.bak") {
if !strings.HasSuffix(fileName, ".conf") && !strings.HasSuffix(fileName, ".bak") {
continue
}
redirectConfig := response.NginxRedirectConfig{
@@ -1936,25 +1943,40 @@ func (w WebsiteService) LoadWebsiteDirConfig(req request.WebsiteCommonReq) (*res
return nil, err
}
res.Dirs = []string{"/"}
for _, file := range indexFiles {
if !file.IsDir() || file.Name() == "node_modules" || file.Name() == "vendor" {
continue
}
res.Dirs = append(res.Dirs, fmt.Sprintf("/%s", file.Name()))
fileInfo, _ := file.Info()
if fileInfo.Sys().(*syscall.Stat_t).Uid != 1000 || fileInfo.Sys().(*syscall.Stat_t).Gid != 1000 {
res.Msg = i18n.GetMsgByKey("ErrPathPermission")
}
childFiles, _ := os.ReadDir(absoluteIndexPath + "/" + file.Name())
for _, childFile := range childFiles {
if !childFile.IsDir() {
checkAndAppendDirs := func(relPath string, entries []os.DirEntry) {
for _, entry := range entries {
if !entry.IsDir() || entry.Name() == "node_modules" || entry.Name() == "vendor" {
continue
}
childInfo, _ := childFile.Info()
if childInfo.Sys().(*syscall.Stat_t).Uid != 1000 || childInfo.Sys().(*syscall.Stat_t).Gid != 1000 {
res.Msg = i18n.GetMsgByKey("ErrPathPermission")
nextRelPath := path.Join(relPath, entry.Name())
res.Dirs = append(res.Dirs, "/"+strings.TrimPrefix(nextRelPath, "/"))
entryInfo, _ := entry.Info()
if entryInfo != nil {
if stat, ok := entryInfo.Sys().(*syscall.Stat_t); ok {
if stat.Uid != 1000 || stat.Gid != 1000 {
res.Msg = i18n.GetMsgByKey("ErrPathPermission")
}
}
}
res.Dirs = append(res.Dirs, fmt.Sprintf("/%s/%s", file.Name(), childFile.Name()))
}
}
checkAndAppendDirs("", indexFiles)
for _, firstDir := range indexFiles {
if !firstDir.IsDir() || firstDir.Name() == "node_modules" || firstDir.Name() == "vendor" {
continue
}
secondLevelPath := path.Join(absoluteIndexPath, firstDir.Name())
secondLevelDirs, _ := os.ReadDir(secondLevelPath)
checkAndAppendDirs(firstDir.Name(), secondLevelDirs)
for _, secondDir := range secondLevelDirs {
if !secondDir.IsDir() || secondDir.Name() == "node_modules" || secondDir.Name() == "vendor" {
continue
}
thirdLevelPath := path.Join(secondLevelPath, secondDir.Name())
thirdLevelDirs, _ := os.ReadDir(thirdLevelPath)
checkAndAppendDirs(path.Join(firstDir.Name(), secondDir.Name()), thirdLevelDirs)
}
}
@@ -2275,6 +2297,9 @@ func (w WebsiteService) OperateCrossSiteAccess(req request.CrossSiteAccessOp) er
}
func (w WebsiteService) ExecComposer(req request.ExecComposerReq) error {
if cmd.CheckIllegal(req.User, req.Mirror, req.Command, req.ExtCommand) {
return buserr.New("ErrCmdIllegal")
}
website, err := websiteRepo.GetFirst(repo.WithByID(req.WebsiteID))
if err != nil {
return err
+3 -2
View File
@@ -25,7 +25,7 @@ func NewIWebsiteAcmeAccountService() IWebsiteAcmeAccountService {
}
func (w WebsiteAcmeAccountService) Page(search dto.PageInfo) (int64, []response.WebsiteAcmeAccountDTO, error) {
total, accounts, err := websiteAcmeRepo.Page(search.Page, search.PageSize, repo.WithOrderBy("created_at desc"))
total, accounts, err := websiteAcmeRepo.Page(search.Page, search.PageSize, repo.WithOrderDesc("created_at"))
var accountDTOs []response.WebsiteAcmeAccountDTO
for _, account := range accounts {
accountDTOs = append(accountDTOs, response.WebsiteAcmeAccountDTO{
@@ -45,9 +45,10 @@ func (w WebsiteAcmeAccountService) Create(create request.WebsiteAcmeAccountCreat
Type: create.Type,
KeyType: create.KeyType,
UseProxy: create.UseProxy,
UseEAB: create.UseEAB,
}
if create.Type == "google" || create.Type == "freessl" {
if create.Type == "google" || create.Type == "freessl" || (create.Type == "custom" && create.UseEAB) {
if create.EabKid == "" || create.EabHmacKey == "" {
return nil, buserr.New("ErrEabKidOrEabHmacKeyCannotBlank")
}
+1 -1
View File
@@ -51,7 +51,7 @@ func NewIWebsiteCAService() IWebsiteCAService {
}
func (w WebsiteCAService) Page(search request.WebsiteCASearch) (int64, []response.WebsiteCADTO, error) {
total, cas, err := websiteCARepo.Page(search.Page, search.PageSize, repo.WithOrderBy("created_at desc"))
total, cas, err := websiteCARepo.Page(search.Page, search.PageSize, repo.WithOrderDesc("created_at"))
if err != nil {
return 0, nil, err
}
+1 -1
View File
@@ -27,7 +27,7 @@ func NewIWebsiteDnsAccountService() IWebsiteDnsAccountService {
}
func (w WebsiteDnsAccountService) Page(search dto.PageInfo) (int64, []response.WebsiteDnsAccountDTO, error) {
total, accounts, err := websiteDnsRepo.Page(search.Page, search.PageSize, repo.WithOrderBy("created_at desc"))
total, accounts, err := websiteDnsRepo.Page(search.Page, search.PageSize, repo.WithOrderDesc("created_at"))
var accountDTOs []response.WebsiteDnsAccountDTO
for _, account := range accounts {
auth := make(map[string]string)
+10 -4
View File
@@ -4,6 +4,10 @@ import (
"bytes"
"errors"
"fmt"
"path"
"strconv"
"strings"
"github.com/1Panel-dev/1Panel/agent/app/dto"
"github.com/1Panel-dev/1Panel/agent/app/dto/request"
"github.com/1Panel-dev/1Panel/agent/app/dto/response"
@@ -17,9 +21,6 @@ import (
"github.com/1Panel-dev/1Panel/agent/utils/nginx/components"
"github.com/1Panel-dev/1Panel/agent/utils/nginx/parser"
"github.com/1Panel-dev/1Panel/agent/utils/re"
"path"
"strconv"
"strings"
)
func (w WebsiteService) OperateProxy(req request.WebsiteProxyConfig) (err error) {
@@ -119,8 +120,13 @@ func (w WebsiteService) OperateProxy(req request.WebsiteProxyConfig) (err error)
location.RemoveServerCache(fmt.Sprintf("proxy_cache_zone_of_%s", website.Alias))
}
// Browser Cache Settings
if req.CacheTime != 0 {
// cacheTime > 0: enable expires;
// cacheTime == 0: use upstream cache-control, remove self-set;
// cacheTime < 0: force no-cache
if req.CacheTime > 0 {
location.AddBrowserCache(req.CacheTime, req.CacheUnit)
} else if req.CacheTime < 0 {
location.AddBroswerNoCache()
} else {
location.RemoveBrowserCache()
}
+4 -4
View File
@@ -37,7 +37,7 @@ type WebsiteSSLService struct {
type IWebsiteSSLService interface {
Page(search request.WebsiteSSLSearch) (int64, []response.WebsiteSSLDTO, error)
GetSSL(id uint) (*response.WebsiteSSLDTO, error)
Search(req request.WebsiteSSLSearch) ([]response.WebsiteSSLDTO, error)
Search(req request.WebsiteSSLListReq) ([]response.WebsiteSSLDTO, error)
Create(create request.WebsiteSSLCreate) (request.WebsiteSSLCreate, error)
GetDNSResolve(req request.WebsiteDNSReq) ([]response.WebsiteDNSRes, error)
GetWebsiteSSL(websiteId uint) (response.WebsiteSSLDTO, error)
@@ -62,7 +62,7 @@ func (w WebsiteSSLService) Page(search request.WebsiteSSLSearch) (int64, []respo
if search.OrderBy != "" && search.Order != "null" {
opts = append(opts, repo.WithOrderRuleBy(search.OrderBy, search.Order))
} else {
opts = append(opts, repo.WithOrderBy("created_at desc"))
opts = append(opts, repo.WithOrderDesc("created_at"))
}
if search.Domain != "" {
opts = append(opts, websiteSSLRepo.WithByDomain(search.Domain))
@@ -90,12 +90,12 @@ func (w WebsiteSSLService) GetSSL(id uint) (*response.WebsiteSSLDTO, error) {
return &res, nil
}
func (w WebsiteSSLService) Search(search request.WebsiteSSLSearch) ([]response.WebsiteSSLDTO, error) {
func (w WebsiteSSLService) Search(search request.WebsiteSSLListReq) ([]response.WebsiteSSLDTO, error) {
var (
opts []repo.DBOption
result []response.WebsiteSSLDTO
)
opts = append(opts, repo.WithOrderBy("created_at desc"))
opts = append(opts, repo.WithOrderDesc("created_at"))
if search.AcmeAccountID != "" {
acmeAccountID, err := strconv.ParseUint(search.AcmeAccountID, 10, 64)
if err != nil {
+20 -2
View File
@@ -849,6 +849,17 @@ func applySSL(website *model.Website, websiteSSL model.WebsiteSSL, req request.W
nginxParams[i].Params = []string{"ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA256:!aNULL:!eNULL:!EXPORT:!DSS:!DES:!RC4:!3DES:!MD5:!PSK:!KRB5:!SRP:!CAMELLIA:!SEED"}
}
}
if param.Name == "error_page" {
if len(param.Params) < 2 {
continue
}
code := param.Params[0]
if code == "497" {
if sslPort != 443 && param.Params[1] == "https://$host$request_uri" {
param.Params[1] = fmt.Sprintf("https://$host:%d$request_uri", sslPort)
}
}
}
}
if req.Hsts {
var hstsValue string
@@ -966,6 +977,8 @@ func opWebsite(website *model.Website, operate string) error {
server.RemoveDirective("include", []string{proxyInclude})
rewriteInclude := fmt.Sprintf("/www/sites/%s/rewrite/%s.conf", website.Alias, website.Alias)
server.RemoveDirective("include", []string{rewriteInclude})
redirectInclude := fmt.Sprintf("/www/sites/%s/redirect/%s.conf", website.Alias, website.Alias)
server.RemoveDirective("include", []string{redirectInclude})
switch website.Type {
case constant.Deployment:
@@ -996,6 +1009,11 @@ func opWebsite(website *model.Website, operate string) error {
if fileOp.Stat(absoluteRewritePath) {
server.UpdateDirective("include", []string{rewriteInclude})
}
redirectInclude := fmt.Sprintf("/www/sites/%s/redirect/%s.conf", website.Alias, website.Alias)
absoluteRedirectPath := GetSitePath(*website, SiteRedirectDir)
if fileOp.Stat(absoluteRedirectPath) {
server.UpdateDirective("include", []string{redirectInclude})
}
rootIndex := path.Join("/www/sites", website.Alias, "index")
if website.SiteDir != "/" {
rootIndex = path.Join(rootIndex, website.SiteDir)
@@ -1115,7 +1133,7 @@ func getWebsiteDomains(domains []request.WebsiteDomain, defaultHTTPPort, default
if domain.Domain == "" {
continue
}
if !common.IsValidNginxServerName(domain.Domain) {
if !(common.IsValidNginxServerName(domain.Domain) || common.IsValidIP(domain.Domain)) {
err = buserr.WithName("ErrDomainFormat", domain.Domain)
return
}
@@ -1226,7 +1244,7 @@ func saveCertificateFile(websiteSSL *model.WebsiteSSL, logger *log.Logger) {
func GetSystemSSL() (bool, uint) {
var sslSetting model.Setting
_ = global.CoreDB.Model(&model.Setting{}).Where("key = ?", "SSL").First(&sslSetting).Error
if sslSetting.Value == "Enable" {
if sslSetting.Value == "Enable" || sslSetting.Value == "Mux" {
var sslIDSetting model.Setting
_ = global.CoreDB.Model(&model.Setting{}).Where("key = ?", "SSLID").First(&sslIDSetting).Error
idValue, _ := strconv.Atoi(sslIDSetting.Value)
+15 -1
View File
@@ -7,6 +7,7 @@ import (
"os"
"path"
"strconv"
"strings"
"time"
"github.com/1Panel-dev/1Panel/agent/buserr"
@@ -353,7 +354,20 @@ func (t *Task) LogSuccessWithOps(operate, msg string) {
}
func (t *Task) LogFailedWithOps(operate, msg string, err error) {
t.Logger.Printf("%s%s%s : %s ", i18n.GetMsgByKey(operate), msg, i18n.GetMsgByKey("Failed"), err.Error())
t.Logger.Printf("%s%s : %s ", msg, i18n.GetMsgByKey("Failed"), err.Error())
}
func (t *Task) LogWithProgress(msg string, current int, total int) {
const barWidth = 10
filled := int(float64(current) / float64(total) * 100 / 10)
if filled > barWidth {
filled = barWidth
}
if filled < 0 {
filled = 0
}
bar := strings.Repeat("=", filled) + strings.Repeat("-", barWidth-filled)
t.Logger.Printf("%s [%s] %.2f%%", msg, bar, float64(current)/float64(total)*100)
}
type SimpleFormatter struct{}
+1
View File
@@ -24,4 +24,5 @@ const (
WeCom = "weCom"
DingTalk = "dingTalk"
FeiShu = "feiShu"
Custom = "custom"
)
+1
View File
@@ -7,6 +7,7 @@ const (
AppTakeDown = "TakeDown"
AppOpenresty = "openresty"
AppOpenclaw = "openclaw"
AppMysql = "mysql"
AppMariaDB = "mariadb"
AppPostgresql = "postgresql"
+2
View File
@@ -40,4 +40,6 @@ const (
OrderDesc = "descending"
OrderAsc = "ascending"
Desc = "desc"
Asc = "asc"
)
+1
View File
@@ -34,6 +34,7 @@ type SystemDir struct {
AppDir string
ResourceDir string
IconCacheDir string
AppResourceDir string
AppInstallDir string
LocalAppResourceDir string
+1
View File
@@ -195,6 +195,7 @@ require (
github.com/nrdcg/freemyip v0.3.0 // indirect
github.com/nrdcg/goacmedns v0.2.0 // indirect
github.com/nrdcg/namesilo v0.5.0 // indirect
github.com/nrdcg/porkbun v0.4.0 // indirect
github.com/nwaples/rardecode/v2 v2.2.0 // indirect
github.com/opencontainers/go-digest v1.0.0 // indirect
github.com/ovh/go-ovh v1.9.0 // indirect
+2
View File
@@ -764,6 +764,8 @@ github.com/nrdcg/goacmedns v0.2.0 h1:ADMbThobzEMnr6kg2ohs4KGa3LFqmgiBA22/6jUWJR0
github.com/nrdcg/goacmedns v0.2.0/go.mod h1:T5o6+xvSLrQpugmwHvrSNkzWht0UGAwj2ACBMhh73Cg=
github.com/nrdcg/namesilo v0.5.0 h1:6QNxT/XxE+f5B+7QlfWorthNzOzcGlBLRQxqi6YeBrE=
github.com/nrdcg/namesilo v0.5.0/go.mod h1:4UkwlwQfDt74kSGmhLaDylnBrD94IfflnpoEaj6T2qw=
github.com/nrdcg/porkbun v0.4.0 h1:rWweKlwo1PToQ3H+tEO9gPRW0wzzgmI/Ob3n2Guticw=
github.com/nrdcg/porkbun v0.4.0/go.mod h1:/QMskrHEIM0IhC/wY7iTCUgINsxdT2WcOphktJ9+Q54=
github.com/nwaples/rardecode/v2 v2.2.0 h1:4ufPGHiNe1rYJxYfehALLjup4Ls3ck42CWwjKiOqu0A=
github.com/nwaples/rardecode/v2 v2.2.0/go.mod h1:7uz379lSxPe6j9nvzxUZ+n7mnJNgjsRNb6IbvGVHRmw=
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
+29 -15
View File
@@ -38,6 +38,19 @@ ErrGroupIsDefault: 'Default group, cannot be deleted'
ErrGroupIsInWebsiteUse: 'The group is being used by another website and cannot be deleted.'
Decrypt: "Decrypt"
#agent
ErrAgentAccountBound: 'This account is bound to an agent and cannot be deleted. Please check and try again!'
ErrAgentAccountUnavailable: 'The account connection information is unavailable. Error: {{ .err }}. Please check and try again!'
ErrAgentProviderNotSupported: 'This agent provider is not supported. Please check and try again!'
ErrAgentAccountRequired: 'Please select an agent account and try again.'
ErrAgentAccountNotVerified: 'The account has not been verified. Please check and try again!'
ErrAgentProviderMismatch: 'The account provider does not match. Please check and try again!'
ErrAgentBaseURLRequired: 'Base URL cannot be empty. Please check and try again!'
ErrAgentApiKeyRequired: 'API Key cannot be empty. Please check and try again!'
ErrAgentComposeRequired: 'Custom compose content cannot be empty. Please check and try again!'
ErrAgentIDRequired: 'Agent ID cannot be empty. Please check and try again!'
ErrAgentAccountIDRequired: 'Account ID cannot be empty. Please check and try again!'
#backup
Localhost: 'Local Machine'
ErrBackupInUsed: 'The backup account has been used in the scheduled task and cannot be deleted.'
@@ -201,6 +214,7 @@ ErrRemoteExist: 'The remote database already exists with this name, please modif
ErrLocalExist: 'The name already exists in the local database, please modify it and try again'
RemoteBackup: "To back up the remote database, the local container database service needs to be started first using the image {{ .name }}, please wait..."
RemoteRecover: "To restore the remote database, the local container database service needs to be started first using the image {{ .name }}, please wait..."
Arg: "Argument"
#redis
ErrTypeOfRedis: 'The recovery file type does not match the current persistence method, please modify it and try again'
@@ -211,7 +225,7 @@ ErrObjectInUsed: "This object is in use and cannot be deleted!"
ErrObjectBeDependent: "This image is depended on by other images and cannot be deleted!"
ErrPortRules: 'Port number does not match, please re-enter!'
ErrPgImagePull: 'Image pull timed out, please configure image acceleration or manually pull the {{ .name }} image and try again'
PruneHelper: "This cleanup removed {{ .name }} {{ .count }} items, freeing {{ .size }} disk space"
PruneHelper: "This cleanup {{ .name }} freed disk space {{ .size }}"
ImageRemoveHelper: "Deleted image {{ .name }}, freeing {{ .size }} disk space"
BuildCache: "Build cache"
Volume: "Storage volume"
@@ -505,19 +519,19 @@ ErrInstallExtension: "An installation task is already in progress, please wait f
PanelAlertTitle: "Panel Alert Notification"
TestAlertTitle: "Test Email - Verify Email Connectivity"
TestAlert: "This is a test email to verify that your email sending configuration is correct."
LicenseExpirationAlert: "Your 1Panel license will expire in {{ .day }} days. Please log in to the panel for details."
CronJobFailedAlert: "Your 1Panel scheduled task '{{ .name }}' has failed. Please log in to the panel for details."
ClamAlert: "Your 1Panel virus scan task has detected {{ .num }} infected files. Please log in to the panel for details."
WebSiteAlert: "There are {{ .num }} websites on your 1Panel that will expire in {{ .day }} days. Please log in to the panel for details."
SSLAlert: "There are {{ .num }} SSL certificates on your 1Panel that will expire in {{ .day }} days. Please log in to the panel for details."
DiskUsedAlert: "Your 1Panel disk '{{ .name }}' has used {{ .used }}. Please log in to the panel for details."
ResourceAlert: "The average {{ .name }} usage over {{ .time }} minutes on your 1Panel is {{ .used }}. Please log in to the panel for details."
PanelVersionAlert: "A new version of 1Panel is available. Please log in to the panel to upgrade."
PanelPwdExpirationAlert: "Your 1Panel password will expire in {{ .day }} days. Please log in to the panel for details."
CommonAlert: "Your 1Panel, {{ .msg }}, please log in to the panel for details."
NodeExceptionAlert: "Your 1Panel, {{ .num }} nodes are abnormal, please log in to the panel for details."
LicenseExceptionAlert: "Your 1Panel, {{ .num }} licenses are abnormal, please log in to the panel for details."
SSHAndPanelLoginAlert: "Your 1Panel, abnormal panel {{ .name }} login from {{ .ip }}, please log in to the panel for details."
LicenseExpirationAlert: "Your {{ .node }}{{ .ip }} Panel, the license will expire in {{ .day }} days. Please log in to the panel for details."
CronJobFailedAlert: "Your {{ .node }}{{ .ip }} Panel, scheduled task - {{ .name }} execution failed. Please log in to the panel for details."
ClamAlert: "Your {{ .node }}{{ .ip }} Panel, the virus scan task detected {{ .num }} infected files. Please log in to the panel for details."
WebSiteAlert: "Your {{ .node }}{{ .ip }} Panel, {{ .num }} websites will expire in {{ .day }} days. Please log in to the panel for details."
SSLAlert: "Your {{ .node }}{{ .ip }} Panel, {{ .num }} SSL certificates will expire in {{ .day }} days. Please log in to the panel for details."
DiskUsedAlert: "Your {{ .node }}{{ .ip }} Panel, disk {{ .name }} usage has reached {{ .used }}. Please log in to the panel for details."
ResourceAlert: "Your {{ .node }}{{ .ip }} Panel, the average {{ .name }} usage rate in {{ .time }} minutes is {{ .used }}. Please log in to the panel for details."
PanelVersionAlert: "Your {{ .node }}{{ .ip }} Panel, the latest panel version is available for upgrade. Please log in to the panel for details."
PanelPwdExpirationAlert: "Your {{ .node }}{{ .ip }} Panel, the panel password will expire in {{ .day }} days. Please log in to the panel for details."
CommonAlert: "Your {{ .node }}{{ .ip }} Panel, {{ .msg }}. Please log in to the panel for details."
NodeExceptionAlert: "Your {{ .node }}{{ .ip }} Panel, {{ .num }} nodes are abnormal. Please log in to the panel for details."
LicenseExceptionAlert: "Your {{ .node }}{{ .ip }} Panel, {{ .num }} licenses are abnormal. Please log in to the panel for details."
SSHAndPanelLoginAlert: "Your {{ .node }}{{ .ip }} Panel, {{ .name }} login from {{ .loginIp }} is abnormal. Please log in to the panel for details."
#disk
DeviceNotFound: "Device {{ .name }} not found"
@@ -526,4 +540,4 @@ PartitionDiskErr: "Failed to partition, {{ .err }}"
FormatDiskErr: "Failed to format disk, {{ .err }}"
MountDiskErr: "Failed to mount disk, {{ .err }}"
UnMountDiskErr: "Failed to unmount disk, {{ .err }}"
XfsNotFound: "xfs filesystem not detected, please install xfsprogs first"
XfsNotFound: "xfs filesystem not detected, please install xfsprogs first"
+28 -14
View File
@@ -38,6 +38,19 @@ ErrGroupIsDefault: 'Grupo predeterminado, no se puede eliminar'
ErrGroupIsInWebsiteUse: 'El grupo está siendo usado por otro sitio web y no se puede eliminar.'
Decrypt: "Descifrar"
#agent
ErrAgentAccountBound: 'Esta cuenta está vinculada a un agente y no se puede eliminar. Verifique e inténtelo de nuevo!'
ErrAgentAccountUnavailable: 'La información de conexión de la cuenta no está disponible. Error: {{ .err }}. Verifique e inténtelo de nuevo!'
ErrAgentProviderNotSupported: 'Este proveedor de agente no es compatible. Verifique e inténtelo de nuevo!'
ErrAgentAccountRequired: 'Seleccione una cuenta de agente y vuelva a intentarlo.'
ErrAgentAccountNotVerified: 'La cuenta no está verificada. Verifique e inténtelo de nuevo!'
ErrAgentProviderMismatch: 'El proveedor de la cuenta no coincide. Verifique e inténtelo de nuevo!'
ErrAgentBaseURLRequired: 'Base URL no puede estar vacío. Verifique e inténtelo de nuevo!'
ErrAgentApiKeyRequired: 'API Key no puede estar vacío. Verifique e inténtelo de nuevo!'
ErrAgentComposeRequired: 'El contenido personalizado de compose no puede estar vacío. Verifique e inténtelo de nuevo!'
ErrAgentIDRequired: 'El ID del agente no puede estar vacío. Verifique e inténtelo de nuevo!'
ErrAgentAccountIDRequired: 'El ID de la cuenta no puede estar vacío. Verifique e inténtelo de nuevo!'
#backup
ErrBackupInUsed: 'La cuenta de respaldo está siendo utilizada en una tarea programada y no se puede eliminar.'
ErrBackupCheck: 'Error al probar la conexión de la cuenta de respaldo {{ .err }}'
@@ -200,6 +213,7 @@ ErrRemoteExist: 'La base de datos remota ya existe con ese nombre, modifíquelo
ErrLocalExist: 'El nombre ya existe en la base de datos local, modifíquelo e intente de nuevo'
RemoteBackup: "Para hacer una copia de seguridad de la base de datos remota, primero debe iniciarse el servicio de base de datos del contenedor local utilizando la imagen {{ .name }}, espere por favor..."
RemoteRecover: "Para restaurar la base de datos remota, primero debe iniciarse el servicio de base de datos del contenedor local utilizando la imagen {{ .name }}, espere por favor..."
Arg: "Argumento"
#redis
ErrTypeOfRedis: 'El tipo de archivo de recuperación no coincide con el método de persistencia actual, modifíquelo e intente'
@@ -210,7 +224,7 @@ ErrObjectInUsed: "Este objeto está en uso y no se puede eliminar!"
ErrObjectBeDependent: "Esta imagen es dependida por otras imágenes y no se puede eliminar!"
ErrPortRules: 'El número de puerto no coincide, ingrese de nuevo'
ErrPgImagePull: 'Tiempo de espera al descargar la imagen, configure la aceleración o descárguela manualmente ({{ .name }}) e intente de nuevo'
PruneHelper: "Esta limpieza eliminó {{ .name }} {{ .count }} elementos, liberando {{ .size }} de espacio en disco"
PruneHelper: "Esta limpieza {{ .name }} liberó espacio en disco {{ .size }}"
ImageRemoveHelper: "Imagen {{ .name }} eliminada, liberando {{ .size }} de espacio en disco"
BuildCache: "Caché de compilación"
Volume: "Volumen de almacenamiento"
@@ -495,10 +509,6 @@ ErrAlertPush: 'Error al enviar la alerta, ¡revise e intente de nuevo!'
ErrAlertSave: 'Error al guardar la alerta, ¡revise e intente de nuevo!'
ErrAlertSync: 'Error en la sincronización de la alerta, ¡revise e intente de nuevo!'
ErrAlertRemote: 'Error remoto de alerta, ¡revise e intente de nuevo!'
CommonAlert: "Su 1Panel: {{ .msg }}, por favor inicie sesión en el panel para más detalles."
NodeExceptionAlert: "Su 1Panel tiene {{ .num }} nodos anómalos, inicie sesión para más detalles."
LicenseExceptionAlert: "Su 1Panel tiene {{ .num }} licencias anómalas, inicie sesión para más detalles."
SSHAndPanelLoginAlert: "Su 1Panel detectó un inicio de sesión anómalo en el panel {{ .name }} desde {{ .ip }}, revise en el panel para más detalles."
#task - runtime
ErrInstallExtension: "Ya hay una tarea de instalación en curso, espere a que termine"
@@ -507,15 +517,19 @@ ErrInstallExtension: "Ya hay una tarea de instalación en curso, espere a que te
PanelAlertTitle: "Notificación de alerta del panel"
TestAlertTitle: "Correo de prueba - Verificar conectividad de correo"
TestAlert: "Este es un correo de prueba para verificar que la configuración de envío de correos es correcta."
LicenseExpirationAlert: "Su licencia de 1Panel expirará en {{ .day }} días. Inicie sesión para más detalles."
CronJobFailedAlert: "Su tarea programada '{{ .name }}' ha fallado. Inicie sesión en el panel para más detalles."
ClamAlert: "La tarea de escaneo de virus detectó {{ .num }} archivos infectados. Inicie sesión para detalles."
WebSiteAlert: "Hay {{ .num }} sitios web en su 1Panel que expirarán en {{ .day }} días. Inicie sesión para más detalles."
SSLAlert: "Hay {{ .num }} certificados SSL en su 1Panel que expirarán en {{ .day }} días. Inicie sesión para detalles."
DiskUsedAlert: "El disco '{{ .name }}' de su 1Panel ha utilizado {{ .used }}. Inicie sesión para más detalles."
ResourceAlert: "El uso promedio de {{ .name }} en {{ .time }} minutos es de {{ .used }}. Inicie sesión para más detalles."
PanelVersionAlert: "Hay una nueva versión de 1Panel disponible. Inicie sesión para actualizar."
PanelPwdExpirationAlert: "Su contraseña de 1Panel expirará en {{ .day }} días. Inicie sesión para más detalles."
LicenseExpirationAlert: "Su Panel {{ .node }}{{ .ip }}, la licencia expirará en {{ .day }} días. Inicie sesión en el panel para ver los detalles."
CronJobFailedAlert: "Su Panel {{ .node }}{{ .ip }}, la ejecución de la tarea programada {{ .name }} falló. Inicie sesión en el panel para ver los detalles."
ClamAlert: "Su Panel {{ .node }}{{ .ip }}, la tarea de escaneo antivirus detectó {{ .num }} archivos infectados. Inicie sesión en el panel para ver los detalles."
WebSiteAlert: "Su Panel {{ .node }}{{ .ip }}, {{ .num }} sitios web expirarán en {{ .day }} días. Inicie sesión en el panel para ver los detalles."
SSLAlert: "Su Panel {{ .node }}{{ .ip }}, {{ .num }} certificados SSL expirarán en {{ .day }} días. Inicie sesión en el panel para ver los detalles."
DiskUsedAlert: "Su Panel {{ .node }}{{ .ip }}, el uso del disco {{ .name }} ha alcanzado {{ .used }}. Inicie sesión en el panel para ver los detalles."
ResourceAlert: "Su Panel {{ .node }}{{ .ip }}, la tasa de uso promedio de {{ .name }} en {{ .time }} minutos es {{ .used }}. Inicie sesión en el panel para ver los detalles."
PanelVersionAlert: "Su Panel {{ .node }}{{ .ip }}, está disponible la última versión del panel para actualizar. Inicie sesión en el panel para ver los detalles."
PanelPwdExpirationAlert: "Su Panel {{ .node }}{{ .ip }}, la contraseña del panel expirará en {{ .day }} días. Inicie sesión en el panel para ver los detalles."
CommonAlert: "Su Panel {{ .node }}{{ .ip }}, {{ .msg }}. Inicie sesión en el panel para ver los detalles."
NodeExceptionAlert: "Su Panel {{ .node }}{{ .ip }}, {{ .num }} nodos son anómalos. Inicie sesión en el panel para ver los detalles."
LicenseExceptionAlert: "Su Panel {{ .node }}{{ .ip }}, {{ .num }} licencias son anómalas. Inicie sesión en el panel para ver los detalles."
SSHAndPanelLoginAlert: "Su Panel {{ .node }}{{ .ip }}, el inicio de sesión {{ .name }} desde {{ .loginIp }} es anómalo. Inicie sesión en el panel para ver los detalles."
#disk
DeviceNotFound: "Dispositivo {{ .name }} no encontrado"
+29 -15
View File
@@ -37,6 +37,19 @@ ErrGroupIsDefault: 'デフォルト グループ、削除できません'
ErrGroupIsInWebsiteUse: 'グループは別の Web サイトで使用されているため、削除できません。'
Decrypt: "復号化"
#agent
ErrAgentAccountBound: 'このアカウントはエージェントに紐付いているため削除できません。確認して再試行してください!'
ErrAgentAccountUnavailable: 'アカウント接続情報を利用できません。エラー: {{ .err }}。確認して再試行してください!'
ErrAgentProviderNotSupported: 'このエージェント提供元はサポートされていません。確認して再試行してください!'
ErrAgentAccountRequired: 'エージェントアカウントを選択して再試行してください。'
ErrAgentAccountNotVerified: 'アカウントの検証が完了していません。確認して再試行してください!'
ErrAgentProviderMismatch: 'アカウントの提供元が一致しません。確認して再試行してください!'
ErrAgentBaseURLRequired: 'Base URL を空にできません。確認して再試行してください!'
ErrAgentApiKeyRequired: 'API Key を空にできません。確認して再試行してください!'
ErrAgentComposeRequired: 'カスタム compose 内容を空にできません。確認して再試行してください!'
ErrAgentIDRequired: 'エージェント ID を空にできません。確認して再試行してください!'
ErrAgentAccountIDRequired: 'アカウント ID を空にできません。確認して再試行してください!'
#backup
Localhost: 'ローカルマシン'
ErrBackupInUsed: 'バックアップ アカウントはスケジュールされたタスクで使用されているため、削除できません。'
@@ -200,6 +213,7 @@ ErrRemoteExist: 'この名前のリモート データベースは既に存在
ErrLocalExist: '名前はローカル データベースに既に存在します。変更してもう一度お試しください'
RemoteBackup: "リモートデータベースをバックアップするには、まずイメージ {{ .name }} を使用してローカルコンテナデータベースサービスを起動する必要があります。しばらくお待ちください..."
RemoteRecover: "リモートデータベースを復元するには、まずイメージ {{ .name }} を使用してローカルコンテナデータベースサービスを起動する必要があります。しばらくお待ちください..."
Arg: "引数"
#redis
ErrTypeOfRedis: 'リカバリ ファイルの種類が現在の永続化方法と一致しません。変更して再試行してください'
@@ -210,7 +224,7 @@ ErrObjectInUsed: "このオブジェクトは使用中であり、削除でき
ErrObjectBeDependent: "このイメージは他のイメージに依存されているため、削除できません!"
ErrPortRules: 'ポート番号が一致しません。再入力してください。'
ErrPgImagePull: 'イメージのプルがタイムアウトしました。イメージのアクセラレーションを設定するか、{{ .name }} イメージを手動でプルして再試行してください'
PruneHelper: "今回のクリーンアップで{{ .name }} {{ .count }}個を削除し、{{ .size }}のディスク領域を解放しました"
PruneHelper: "今回のクリーンアップ {{ .name }} でディスク領域 {{ .size }} を解放しました"
ImageRemoveHelper: "イメージ{{ .name }}を削除し、{{ .size }}のディスク領域を解放しました"
BuildCache: "ビルドキャッシュ"
Volume: "ストレージボリューム"
@@ -504,19 +518,19 @@ ErrInstallExtension: "インストールタスクが進行中です、タスク
PanelAlertTitle: "パネルアラート通知"
TestAlertTitle: "テストメール - メール接続の確認"
TestAlert: "これはテストメールです。メール送信設定が正しく構成されているかを確認します。"
LicenseExpirationAlert: "お使いの 1Panel ライセンスは {{ .day }} 日後に有効期限が切れます。詳細はパネルにログインしてご確認ください。"
CronJobFailedAlert: "1Panel のスケジュールタスク '{{ .name }}' の実行に失敗しました。詳細はパネルでご確認ください。"
ClamAlert: "1Panel のウイルススキャンで {{ .num }} 件の感染ファイルが検出されました。詳細はパネルでご確認ください。"
WebSiteAlert: "1Panel にある {{ .num }} 件のウェブサイトが {{ .day }} 日後に有効期限を迎えます。詳細はパネルでご確認ください。"
SSLAlert: "1Panel にある {{ .num }} 枚のSSL証明書が {{ .day }} 日後に有効期限を迎えます。詳細はパネルでご確認ください。"
DiskUsedAlert: "1Panel のディスク '{{ .name }}' は {{ .used }} 使用されています。詳細はパネルでご確認ください。"
ResourceAlert: "1Panel の {{ .time }} 分間の平均 {{ .name }} 使用率は {{ .used }} です。詳細はパネルでご確認ください。"
PanelVersionAlert: "1Panel に新しいバージョンが利用可能です。アップグレードはパネルから行ってください。"
PanelPwdExpirationAlert: "1Panel のパスワードは {{ .day }} 日後に期限切れとなります。詳細はパネルでご確認ください。"
CommonAlert: "お使いの1Panel、{{ .msg }}、詳細はパネルにログインしてご確認ください。"
NodeExceptionAlert: "お使いの1Panel、{{ .num }}個のノードに異常があります。詳細はパネルにログインしてご確認ください。"
LicenseExceptionAlert: "お使いの1Panel、{{ .num }}個のライセンスに異常があります。詳細はパネルにログインしてご確認ください。"
SSHAndPanelLoginAlert: "お使いの1Panel、パネル{{ .name }}が{{ .ip }}から異常ログインしました。詳細はパネルにログインしてご確認ください。"
LicenseExpirationAlert: "あなたの {{ .node }}{{ .ip }} パネル、ライセンスは {{ .day }} 日後に期限切れになります。詳細はパネルにログインしてご確認ください。"
CronJobFailedAlert: "あなたの {{ .node }}{{ .ip }} パネル、スケジュールタスク {{ .name }} の実行に失敗しました。詳細はパネルにログインしてご確認ください。"
ClamAlert: "あなたの {{ .node }}{{ .ip }} パネル、ウイルススキャンタスクで {{ .num }} 個の感染ファイルを検出しました。詳細はパネルにログインしてご確認ください。"
WebSiteAlert: "あなたの {{ .node }}{{ .ip }} パネル、{{ .num }} 個のウェブサイトが {{ .day }} 日後に期限切れになります。詳細はパネルにログインしてご確認ください。"
SSLAlert: "あなたの {{ .node }}{{ .ip }} パネル、{{ .num }} 枚のSSL証明書が {{ .day }} 日後に期限切れになります。詳細はパネルにログインしてご確認ください。"
DiskUsedAlert: "あなたの {{ .node }}{{ .ip }} パネル、ディスク {{ .name }} の使用率が {{ .used }} に達しました。詳細はパネルにログインしてご確認ください。"
ResourceAlert: "あなたの {{ .node }}{{ .ip }} パネル、過去 {{ .time }} 分間の {{ .name }} 平均使用率は {{ .used }} です。詳細はパネルにログインしてご確認ください。"
PanelVersionAlert: "あなたの {{ .node }}{{ .ip }} パネル、最新バージョンがアップグレード可能です。詳細はパネルにログインしてご確認ください。"
PanelPwdExpirationAlert: "あなたの {{ .node }}{{ .ip }} パネル、パネルパスワードは {{ .day }} 日後に期限切れになります。詳細はパネルにログインしてご確認ください。"
CommonAlert: "あなたの {{ .node }}{{ .ip }} パネル、{{ .msg }}。詳細はパネルにログインしてご確認ください。"
NodeExceptionAlert: "あなたの {{ .node }}{{ .ip }} パネル、{{ .num }} 個のノードに異常が発生しています。詳細はパネルにログインしてご確認ください。"
LicenseExceptionAlert: "あなたの {{ .node }}{{ .ip }} パネル、{{ .num }} 個のライセンスに異常が発生しています。詳細はパネルにログインしてご確認ください。"
SSHAndPanelLoginAlert: "あなたの {{ .node }}{{ .ip }} パネル、{{ .loginIp }} からの {{ .name }} ログインに異常があります。詳細はパネルにログインしてご確認ください。"
#disk
DeviceNotFound: "デバイス {{ .name }} が見つかりません"
@@ -525,4 +539,4 @@ PartitionDiskErr: "パーティションに失敗しました、{{ .err }}"
FormatDiskErr: "ディスクのフォーマットに失敗しました、{{ .err }}"
MountDiskErr: "ディスクのマウントに失敗しました、{{ .err }}"
UnMountDiskErr: "ディスクのアンマウントに失敗しました、{{ .err }}"
XfsNotFound: "xfs ファイルシステムが検出されませんでした、最初に xfsprogs をインストールしてください"
XfsNotFound: "xfs ファイルシステムが検出されませんでした、最初に xfsprogs をインストールしてください"
+29 -15
View File
@@ -38,6 +38,19 @@ ErrGroupIsDefault: '기본 그룹, 삭제할 수 없습니다'
ErrGroupIsInWebsiteUse: '그룹이 다른 웹사이트에서 사용 중이므로 삭제할 수 없습니다.'
Decrypt: "복호화"
#agent
ErrAgentAccountBound: '이 계정은 에이전트에 바인딩되어 있어 삭제할 수 없습니다. 확인 후 다시 시도하세요!'
ErrAgentAccountUnavailable: '계정 연결 정보를 사용할 수 없습니다. 오류: {{ .err }}. 확인 후 다시 시도하세요!'
ErrAgentProviderNotSupported: '이 에이전트 제공자는 지원되지 않습니다. 확인 후 다시 시도하세요!'
ErrAgentAccountRequired: '에이전트 계정을 선택한 후 다시 시도하세요.'
ErrAgentAccountNotVerified: '계정이 검증되지 않았습니다. 확인 후 다시 시도하세요!'
ErrAgentProviderMismatch: '계정 제공자가 일치하지 않습니다. 확인 후 다시 시도하세요!'
ErrAgentBaseURLRequired: 'Base URL은 비워 둘 수 없습니다. 확인 후 다시 시도하세요!'
ErrAgentApiKeyRequired: 'API Key는 비워 둘 수 없습니다. 확인 후 다시 시도하세요!'
ErrAgentComposeRequired: '사용자 정의 compose 내용은 비워 둘 수 없습니다. 확인 후 다시 시도하세요!'
ErrAgentIDRequired: '에이전트 ID는 비워 둘 수 없습니다. 확인 후 다시 시도하세요!'
ErrAgentAccountIDRequired: '계정 ID는 비워 둘 수 없습니다. 확인 후 다시 시도하세요!'
#지원
Localhost: '로컬 머신'
ErrBackupInUsed: '백업 계정이 예약된 작업에 사용되었으므로 삭제할 수 없습니다.'
@@ -201,6 +214,7 @@ ErrRemoteExist: '이 이름을 가진 원격 데이터베이스가 이미 존재
ErrLocalExist: '이름이 로컬 데이터베이스에 이미 존재합니다. 이름을 수정하고 다시 시도하세요'
RemoteBackup: "원격 데이터베이스를 백업하려면 먼저 이미지 {{ .name }}을(를) 사용하여 로컬 컨테이너 데이터베이스 서비스를 시작해야 합니다. 잠시만 기다려 주세요..."
RemoteRecover: "원격 데이터베이스를 복원하려면 먼저 이미지 {{ .name }}을(를) 사용하여 로컬 컨테이너 데이터베이스 서비스를 시작해야 합니다. 잠시만 기다려 주세요..."
Arg: "인수"
#레디스
ErrTypeOfRedis: '복구 파일 유형이 현재 지속성 방법과 일치하지 않습니다. 수정하고 다시 시도하세요'
@@ -211,7 +225,7 @@ ErrObjectInUsed: "이 객체는 사용 중이어서 삭제할 수 없습니다!"
ErrObjectBeDependent: "이 이미지는 다른 이미지에 의존되어 있어 삭제할 수 없습니다!"
ErrPortRules: '포트 번호가 일치하지 않습니다. 다시 입력하세요!'
ErrPgImagePull: '이미지 풀링 시간이 초과되었습니다. 이미지 가속을 구성하거나 {{ .name }} 이미지를 수동으로 풀링한 다음 다시 시도하세요.'
PruneHelper: "이번 정리에서 {{ .name }} {{ .count }}개를 제거하여 {{ .size }} 디스크 공간을 확보했습니다"
PruneHelper: "이번 정리 {{ .name }}가 디스크 공간 {{ .size }}을(를) 확보했습니다"
ImageRemoveHelper: "이미지 {{ .name }} 삭제, {{ .size }} 디스크 공간 확보"
BuildCache: "빌드 캐시"
Volume: "스토리지 볼륨"
@@ -505,19 +519,19 @@ ErrInstallExtension: "이미 설치 작업이 진행 중입니다. 작업이 완
PanelAlertTitle: "패널 알림 통지"
TestAlertTitle: "테스트 이메일 - 이메일 연결 확인"
TestAlert: "이것은 테스트 이메일이며 이메일 발송 설정이 올바른지 확인합니다."
LicenseExpirationAlert: "귀하의 1Panel 라이선스는 {{ .day }}일 후에 만료됩니다. 자세한 내용은 패널에 로그인하세요."
CronJobFailedAlert: "1Panel 예약 작업 '{{ .name }}' 실행에 실패했습니다. 자세한 내용은 패널에서 확인하세요."
ClamAlert: "1Panel 바이러스 검사에서 {{ .num }}개의 감염된 파일이 발견되었습니다. 자세한 내용은 패널에서 확인하세요."
WebSiteAlert: "1Panel 에 있는 {{ .num }}개의 웹사이트가 {{ .day }}일 후에 만료됩니다. 자세한 내용은 패널에서 확인하세요."
SSLAlert: "1Panel 에 있는 {{ .num }}개의 SSL 인증서가 {{ .day }}일 후에 만료됩니다. 자세한 내용은 패널에서 확인하세요."
DiskUsedAlert: "1Panel 디스크 '{{ .name }}'의 사용량은 {{ .used }}입니다. 자세한 내용은 패널에서 확인하세요."
ResourceAlert: "1Panel 의 평균 {{ .time }}분 동안 {{ .name }} 사용률은 {{ .used }}입니다. 자세한 내용은 패널에서 확인하세요."
PanelVersionAlert: "1Panel 의 새로운 버전이 이용 가능합니다. 패널에 로그인하여 업그레이드하세요."
PanelPwdExpirationAlert: "1Panel 비밀번호가 {{ .day }}일 후에 만료됩니다. 자세한 내용은 패널에서 확인하세요."
CommonAlert: "귀하의 1Panel, {{ .msg }}. 자세한 내용은 패널에 로그인하여 확인하세요."
NodeExceptionAlert: "귀하의 1Panel, {{ .num }}개의 노드에 이상이 있습니다. 자세한 내용은 패널에 로그인하여 확인하세요."
LicenseExceptionAlert: "귀하의 1Panel, {{ .num }}개의 라이선스에 이상이 있습니다. 자세한 내용은 패널에 로그인하여 확인하세요."
SSHAndPanelLoginAlert: "귀하의 1Panel, 패널 {{ .name }}이(가) {{ .ip }}에서 비정상 로그인했습니다. 자세한 내용은 패널에 로그인하여 확인하세요."
LicenseExpirationAlert: "귀하의 {{ .node }}{{ .ip }} 패널, 라이센스가 {{ .day }}일 후에 만료됩니다. 자세한 내용은 패널에 로그인하십시오."
CronJobFailedAlert: "귀하의 {{ .node }}{{ .ip }} 패널, 예약 작업 {{ .name }} 실행에 실패했습니다. 자세한 내용은 패널에 로그인하십시오."
ClamAlert: "귀하의 {{ .node }}{{ .ip }} 패널, 바이러스 스캔 작업에서 {{ .num }}개의 감염 파일이 감지되었습니다. 자세한 내용은 패널에 로그인하십시오."
WebSiteAlert: "귀하의 {{ .node }}{{ .ip }} 패널, {{ .num }}개의 웹사이트가 {{ .day }}일 후에 만료됩니다. 자세한 내용은 패널에 로그인하십시오."
SSLAlert: "귀하의 {{ .node }}{{ .ip }} 패널, {{ .num }}개의 SSL 인증서가 {{ .day }}일 후에 만료됩니다. 자세한 내용은 패널에 로그인하십시오."
DiskUsedAlert: "귀하의 {{ .node }}{{ .ip }} 패널, 디스크 {{ .name }} 사용률이 {{ .used }}에 도달했습니다. 자세한 내용은 패널에 로그인하십시오."
ResourceAlert: "귀하의 {{ .node }}{{ .ip }} 패널, 최근 {{ .time }}분간 {{ .name }} 평균 사용률은 {{ .used }}입니다. 자세한 내용은 패널에 로그인하십시오."
PanelVersionAlert: "귀하의 {{ .node }}{{ .ip }} 패널, 최신 버전으로 업그레이드할 수 있습니다. 자세한 내용은 패널에 로그인하십시오."
PanelPwdExpirationAlert: "귀하의 {{ .node }}{{ .ip }} 패널, 패널 비밀번호가 {{ .day }}일 후에 만료됩니다. 자세한 내용은 패널에 로그인하십시오."
CommonAlert: "귀하의 {{ .node }}{{ .ip }} 패널, {{ .msg }}。자세한 내용은 패널에 로그인하십시오."
NodeExceptionAlert: "귀하의 {{ .node }}{{ .ip }} 패널, {{ .num }}개의 노드에 이상이 있습니다. 자세한 내용은 패널에 로그인하십시오."
LicenseExceptionAlert: "귀하의 {{ .node }}{{ .ip }} 패널, {{ .num }}개의 라이센스에 이상이 있습니다. 자세한 내용은 패널에 로그인하십시오."
SSHAndPanelLoginAlert: "귀하의 {{ .node }}{{ .ip }} 패널, {{ .loginIp }}에서의 {{ .name }} 로그인에 이상이 있습니다. 자세한 내용은 패널에 로그인하십시오."
#disk
DeviceNotFound: "장치 {{ .name }} 을(를) 찾을 수 없습니다"
@@ -526,4 +540,4 @@ PartitionDiskErr: "파티션 분할에 실패했습니다, {{ .err }}"
FormatDiskErr: "디스크 포맷에 실패했습니다, {{ .err }}"
MountDiskErr: "디스크 마운트에 실패했습니다, {{ .err }}"
UnMountDiskErr: "디스크 마운트 해제에 실패했습니다, {{ .err }}"
XfsNotFound: "xfs 파일 시스템이 감지되지 않았습니다, 먼저 xfsprogs 를 설치하세요"
XfsNotFound: "xfs 파일 시스템이 감지되지 않았습니다, 먼저 xfsprogs 를 설치하세요"
+29 -15
View File
@@ -41,6 +41,19 @@ ErrGroupIsDefault: 'Kumpulan lalai, tidak boleh dipadamkan'
ErrGroupIsInWebsiteUse: 'Kumpulan sedang digunakan oleh tapak web lain dan tidak boleh dipadamkan.'
Decrypt: "Dekripsi"
#agent
ErrAgentAccountBound: 'Akaun ini telah dipautkan kepada ejen dan tidak boleh dipadam. Sila semak dan cuba lagi!'
ErrAgentAccountUnavailable: 'Maklumat sambungan akaun tidak tersedia. Ralat: {{ .err }}. Sila semak dan cuba lagi!'
ErrAgentProviderNotSupported: 'Penyedia ejen ini tidak disokong. Sila semak dan cuba lagi!'
ErrAgentAccountRequired: 'Sila pilih akaun ejen dan cuba lagi.'
ErrAgentAccountNotVerified: 'Akaun belum disahkan. Sila semak dan cuba lagi!'
ErrAgentProviderMismatch: 'Penyedia akaun tidak sepadan. Sila semak dan cuba lagi!'
ErrAgentBaseURLRequired: 'Base URL tidak boleh kosong. Sila semak dan cuba lagi!'
ErrAgentApiKeyRequired: 'API Key tidak boleh kosong. Sila semak dan cuba lagi!'
ErrAgentComposeRequired: 'Kandungan compose tersuai tidak boleh kosong. Sila semak dan cuba lagi!'
ErrAgentIDRequired: 'ID ejen tidak boleh kosong. Sila semak dan cuba lagi!'
ErrAgentAccountIDRequired: 'ID akaun tidak boleh kosong. Sila semak dan cuba lagi!'
#sandaran
Localhost: 'Mesin Tempatan'
ErrBackupInUsed: 'Akaun sandaran telah digunakan dalam tugas yang dijadualkan dan tidak boleh dipadamkan.'
@@ -201,6 +214,7 @@ ErrRemoteExist: 'Pangkalan data jauh sudah wujud dengan nama ini, sila ubah suai
ErrLocalExist: 'Nama sudah wujud dalam pangkalan data tempatan, sila ubah suai dan cuba lagi'
RemoteBackup: "Untuk menyandarkan pangkalan data jauh, perkhidmatan pangkalan data bekas tempatan perlu dimulakan terlebih dahulu menggunakan imej {{ .name }}, sila tunggu..."
RemoteRecover: "Untuk memulihkan pangkalan data jauh, perkhidmatan pangkalan data bekas tempatan perlu dimulakan terlebih dahulu menggunakan imej {{ .name }}, sila tunggu..."
Arg: "Hujah"
#redis
ErrTypeOfRedis: 'Jenis fail pemulihan tidak sepadan dengan kaedah kegigihan semasa, sila ubah suai dan cuba lagi'
@@ -211,7 +225,7 @@ ErrObjectInUsed: "Objek ini sedang digunakan dan tidak boleh dipadam!"
ErrObjectBeDependent: "Imej ini digantungkan oleh imej lain dan tidak boleh dipadam!"
ErrPortRules: 'Nombor port tidak sepadan, sila masukkan semula!'
ErrPgImagePull: 'Tarikh imej tamat masa, sila konfigurasikan pecutan imej atau tarik imej {{ .name }} secara manual dan cuba lagi'
PruneHelper: "Pembersihan ini membuang {{ .name }} {{ .count }} item, membebaskan {{ .size }} ruang cakera"
PruneHelper: "Pembersihan ini {{ .name }} membebaskan ruang cakera {{ .size }}"
ImageRemoveHelper: "Padam imej {{ .name }}, membebaskan {{ .size }} ruang cakera"
BuildCache: "Cache binaan"
Volume: "Jilid storan"
@@ -505,19 +519,19 @@ ErrInstallExtension: "Tugas pemasangan sudah sedang berjalan, silakan tunggu tug
PanelAlertTitle: "Notifikasi Amaran Panel"
TestAlertTitle: "E-mel Ujian - Sahkan Sambungan E-mel"
TestAlert: "Ini adalah e-mel ujian untuk mengesahkan konfigurasi penghantaran e-mel anda betul."
LicenseExpirationAlert: "Lesen 1Panel anda akan tamat dalam {{ .day }} hari. Sila log masuk ke panel untuk maklumat lanjut."
CronJobFailedAlert: "Tugas berjadual 1Panel '{{ .name }}' gagal dilaksanakan. Sila log masuk ke panel untuk butiran lanjut."
ClamAlert: "Tugas imbasan virus 1Panel menemui {{ .num }} fail yang dijangkiti. Sila log masuk ke panel untuk maklumat lanjut."
WebSiteAlert: "Terdapat {{ .num }} laman web dalam 1Panel anda yang akan tamat dalam {{ .day }} hari. Sila log masuk ke panel untuk maklumat lanjut."
SSLAlert: "Terdapat {{ .num }} sijil SSL dalam 1Panel anda yang akan tamat dalam {{ .day }} hari. Sila log masuk ke panel untuk maklumat lanjut."
DiskUsedAlert: "Cakera '{{ .name }}' dalam 1Panel anda telah menggunakan {{ .used }}. Sila log masuk ke panel untuk maklumat lanjut."
ResourceAlert: "Penggunaan purata {{ .name }} selama {{ .time }} minit dalam 1Panel anda ialah {{ .used }}. Sila log masuk ke panel untuk maklumat lanjut."
PanelVersionAlert: "Versi baru 1Panel tersedia. Sila log masuk ke panel untuk menaik taraf."
PanelPwdExpirationAlert: "Kata laluan 1Panel anda akan tamat dalam {{ .day }} hari. Sila log masuk ke panel untuk maklumat lanjut."
CommonAlert: "1Panel anda, {{ .msg }}, sila log masuk ke panel untuk maklumat lanjut."
NodeExceptionAlert: "1Panel anda, {{ .num }} nod bermasalah, sila log masuk ke panel untuk maklumat lanjut."
LicenseExceptionAlert: "1Panel anda, {{ .num }} lesen bermasalah, sila log masuk ke panel untuk maklumat lanjut."
SSHAndPanelLoginAlert: "1Panel anda, log masuk panel {{ .name }} yang tidak normal dari {{ .ip }}, sila log masuk ke panel untuk maklumat lanjut."
LicenseExpirationAlert: "Panel {{ .node }}{{ .ip }} Anda, lesen akan tamat tempoh dalam {{ .day }} hari. Sila log masuk ke panel untuk butiran lanjut."
CronJobFailedAlert: "Panel {{ .node }}{{ .ip }} Anda, tugas jadual {{ .name }} gagal dilaksanakan. Sila log masuk ke panel untuk butiran lanjut."
ClamAlert: "Panel {{ .node }}{{ .ip }} Anda, tugas imbasan virus mengesan {{ .num }} fail yang dijangkiti. Sila log masuk ke panel untuk butiran lanjut."
WebSiteAlert: "Panel {{ .node }}{{ .ip }} Anda, {{ .num }} laman web akan tamat tempoh dalam {{ .day }} hari. Sila log masuk ke panel untuk butiran lanjut."
SSLAlert: "Panel {{ .node }}{{ .ip }} Anda, {{ .num }} sijil SSL akan tamat tempoh dalam {{ .day }} hari. Sila log masuk ke panel untuk butiran lanjut."
DiskUsedAlert: "Panel {{ .node }}{{ .ip }} Anda, penggunaan cakera {{ .name }} telah mencapai {{ .used }}. Sila log masuk ke panel untuk butiran lanjut."
ResourceAlert: "Panel {{ .node }}{{ .ip }} Anda, kadar penggunaan purata {{ .name }} dalam tempoh {{ .time }} minit ialah {{ .used }}. Sila log masuk ke panel untuk butiran lanjut."
PanelVersionAlert: "Panel {{ .node }}{{ .ip }} Anda, versi panel terkini tersedia untuk dinaik taraf. Sila log masuk ke panel untuk butiran lanjut."
PanelPwdExpirationAlert: "Panel {{ .node }}{{ .ip }} Anda, kata laluan panel akan tamat tempoh dalam {{ .day }} hari. Sila log masuk ke panel untuk butiran lanjut."
CommonAlert: "Panel {{ .node }}{{ .ip }} Anda, {{ .msg }}. Sila log masuk ke panel untuk butiran lanjut."
NodeExceptionAlert: "Panel {{ .node }}{{ .ip }} Anda, {{ .num }} nod tidak normal. Sila log masuk ke panel untuk butiran lanjut."
LicenseExceptionAlert: "Panel {{ .node }}{{ .ip }} Anda, {{ .num }} lesen tidak normal. Sila log masuk ke panel untuk butiran lanjut."
SSHAndPanelLoginAlert: "Panel {{ .node }}{{ .ip }} Anda, log masuk {{ .name }} dari {{ .loginIp }} tidak normal. Sila log masuk ke panel untuk butiran lanjut."
#disk
DeviceNotFound: "Peranti {{ .name }} tidak ditemui"
@@ -526,4 +540,4 @@ PartitionDiskErr: "Gagal membahagikan, {{ .err }}"
FormatDiskErr: "Gagal memformat cakera, {{ .err }}"
MountDiskErr: "Gagal mengkaitkan cakera, {{ .err }}"
UnMountDiskErr: "Gagal nyahkaitkan cakera, {{ .err }}"
XfsNotFound: "Sistem fail xfs tidak dikesan, sila pasang xfsprogs terlebih dahulu"
XfsNotFound: "Sistem fail xfs tidak dikesan, sila pasang xfsprogs terlebih dahulu"
+29 -15
View File
@@ -41,6 +41,19 @@ ErrGroupIsDefault: 'Grupo padrão, não pode ser excluído'
ErrGroupIsInWebsiteUse: 'O grupo está sendo usado por outro site e não pode ser excluído.'
Decrypt: "Descriptografar"
#agent
ErrAgentAccountBound: 'Esta conta está vinculada a um agente e não pode ser excluída. Verifique e tente novamente!'
ErrAgentAccountUnavailable: 'As informações de conexão da conta não estão disponíveis. Erro: {{ .err }}. Verifique e tente novamente!'
ErrAgentProviderNotSupported: 'Este provedor de agente não é suportado. Verifique e tente novamente!'
ErrAgentAccountRequired: 'Selecione uma conta de agente e tente novamente.'
ErrAgentAccountNotVerified: 'A conta não foi verificada. Verifique e tente novamente!'
ErrAgentProviderMismatch: 'O provedor da conta não corresponde. Verifique e tente novamente!'
ErrAgentBaseURLRequired: 'Base URL não pode estar vazio. Verifique e tente novamente!'
ErrAgentApiKeyRequired: 'API Key não pode estar vazio. Verifique e tente novamente!'
ErrAgentComposeRequired: 'O conteúdo personalizado do compose não pode estar vazio. Verifique e tente novamente!'
ErrAgentIDRequired: 'ID do agente não pode estar vazio. Verifique e tente novamente!'
ErrAgentAccountIDRequired: 'ID da conta não pode estar vazio. Verifique e tente novamente!'
#backup
Localhost: 'Máquina Local'
ErrBackupInUsed: 'A conta de backup foi usada na tarefa agendada e não pode ser excluída.'
@@ -201,6 +214,7 @@ ErrRemoteExist: 'O banco de dados remoto já existe com este nome, modifique-o e
ErrLocalExist: 'O nome já existe no banco de dados local, modifique-o e tente novamente'
RemoteBackup: "Para fazer backup do banco de dados remoto, o serviço de banco de dados do contêiner local precisa ser iniciado primeiro usando a imagem {{ .name }}, por favor aguarde..."
RemoteRecover: "Para restaurar o banco de dados remoto, o serviço de banco de dados do contêiner local precisa ser iniciado primeiro usando a imagem {{ .name }}, por favor aguarde..."
Arg: "Argumento"
#redis
ErrTypeOfRedis: 'O tipo de arquivo de recuperação não corresponde ao método de persistência atual, modifique-o e tente novamente'
@@ -211,7 +225,7 @@ ErrObjectInUsed: "Este objeto está em uso e não pode ser excluído!"
ErrObjectBeDependent: "Esta imagem é dependida por outras imagens e não pode ser excluída!"
ErrPortRules: 'O número da porta não corresponde, digite novamente!'
ErrPgImagePull: 'Tempo limite para extração de imagem. Configure a aceleração de imagem ou extraia manualmente a imagem {{ .name }} e tente novamente'
PruneHelper: "Esta limpeza removeu {{ .name }} {{ .count }} itens, liberando {{ .size }} de espaço em disco"
PruneHelper: "Esta limpeza {{ .name }} liberou espaço em disco {{ .size }}"
ImageRemoveHelper: "Excluída a imagem {{ .name }}, liberando {{ .size }} de espaço em disco"
BuildCache: "Cache de construção"
Volume: "Volume de armazenamento"
@@ -505,19 +519,19 @@ ErrInstallExtension: "Já existe uma tarefa de instalação em andamento, aguard
PanelAlertTitle: "Notificação de Alerta do Painel"
TestAlertTitle: "E-mail de Teste - Verificar Conectividade de E-mail"
TestAlert: "Este é um e-mail de teste para verificar se sua configuração de envio de e-mails está correta."
LicenseExpirationAlert: "A licença do seu 1Panel expirará em {{ .day }} dias. Acesse o painel para mais detalhes."
CronJobFailedAlert: "A tarefa agendada '{{ .name }}' do seu 1Panel falhou. Acesse o painel para mais detalhes."
ClamAlert: "A verificação de vírus do 1Panel detectou {{ .num }} arquivos infectados. Acesse o painel para mais detalhes."
WebSiteAlert: "{{ .num }} sites do seu 1Panel expirarão em {{ .day }} dias. Acesse o painel para mais detalhes."
SSLAlert: "{{ .num }} certificados SSL do seu 1Panel expirarão em {{ .day }} dias. Acesse o painel para mais detalhes."
DiskUsedAlert: "O disco '{{ .name }}' do seu 1Panel está com uso de {{ .used }}. Acesse o painel para mais detalhes."
ResourceAlert: "O uso médio de {{ .name }} em {{ .time }} minutos no seu 1Panel é de {{ .used }}. Acesse o painel para mais detalhes."
PanelVersionAlert: "Uma nova versão do 1Panel está disponível. Acesse o painel para atualizá-lo."
PanelPwdExpirationAlert: "A senha do 1Panel expirará em {{ .day }} dias. Acesse o painel para mais detalhes."
CommonAlert: "Seu 1Panel, {{ .msg }}. Para mais detalhes, faça login no painel."
NodeExceptionAlert: "Seu 1Panel, {{ .num }} nós estão com problemas. Para mais detalhes, faça login no painel."
LicenseExceptionAlert: "Seu 1Panel, {{ .num }} licenças estão com problemas. Para mais detalhes, faça login no painel."
SSHAndPanelLoginAlert: "Seu 1Panel, login anormal no painel {{ .name }} a partir de {{ .ip }}. Para mais detalhes, faça login no painel."
LicenseExpirationAlert: "Seu Painel {{ .node }}{{ .ip }}, a licença expirará em {{ .day }} dias. Faça login no painel para obter detalhes."
CronJobFailedAlert: "Seu Painel {{ .node }}{{ .ip }}, a execução da tarefa agendada {{ .name }} falhou. Faça login no painel para obter detalhes."
ClamAlert: "Seu Painel {{ .node }}{{ .ip }}, a tarefa de verificação de vírus detectou {{ .num }} arquivos infectados. Faça login no painel para obter detalhes."
WebSiteAlert: "Seu Painel {{ .node }}{{ .ip }}, {{ .num }} sites expirarão em {{ .day }} dias. Faça login no painel para obter detalhes."
SSLAlert: "Seu Painel {{ .node }}{{ .ip }}, {{ .num }} certificados SSL expirarão em {{ .day }} dias. Faça login no painel para obter detalhes."
DiskUsedAlert: "Seu Painel {{ .node }}{{ .ip }}, o uso do disco {{ .name }} atingiu {{ .used }}. Faça login no painel para obter detalhes."
ResourceAlert: "Seu Painel {{ .node }}{{ .ip }}, a taxa de uso médio do {{ .name }} nos últimos {{ .time }} minutos é {{ .used }}. Faça login no painel para obter detalhes."
PanelVersionAlert: "Seu Painel {{ .node }}{{ .ip }}, a versão mais recente do painel está disponível para atualização. Faça login no painel para obter detalhes."
PanelPwdExpirationAlert: "Seu Painel {{ .node }}{{ .ip }}, a senha do painel expirará em {{ .day }} dias. Faça login no painel para obter detalhes."
CommonAlert: "Seu Painel {{ .node }}{{ .ip }}, {{ .msg }}. Faça login no painel para obter detalhes."
NodeExceptionAlert: "Seu Painel {{ .node }}{{ .ip }}, {{ .num }} nós estão anormais. Faça login no painel para obter detalhes."
LicenseExceptionAlert: "Seu Painel {{ .node }}{{ .ip }}, {{ .num }} licenças estão anormais. Faça login no painel para obter detalhes."
SSHAndPanelLoginAlert: "Seu Painel {{ .node }}{{ .ip }}, o login {{ .name }} a partir de {{ .loginIp }} é anormal. Faça login no painel para obter detalhes."
#disk
DeviceNotFound: "Dispositivo {{ .name }} não encontrado"
@@ -526,4 +540,4 @@ PartitionDiskErr: "Falha ao particionar, {{ .err }}"
FormatDiskErr: "Falha ao formatar disco, {{ .err }}"
MountDiskErr: "Falha ao montar disco, {{ .err }}"
UnMountDiskErr: "Falha ao desmontar disco, {{ .err }}"
XfsNotFound: "Sistema de arquivos xfs não detectado, por favor instale xfsprogs primeiro"
XfsNotFound: "Sistema de arquivos xfs não detectado, por favor instale xfsprogs primeiro"
+29 -15
View File
@@ -41,6 +41,19 @@ ErrGroupIsDefault: 'Группа по умолчанию, не может быт
ErrGroupIsInWebsiteUse: 'Группа используется другим веб-сайтом и не может быть удалена.'
Decrypt: "Расшифровать"
#agent
ErrAgentAccountBound: 'Эта учетная запись привязана к агенту и не может быть удалена. Проверьте и повторите попытку!'
ErrAgentAccountUnavailable: 'Информация о подключении учетной записи недоступна. Ошибка: {{ .err }}. Проверьте и повторите попытку!'
ErrAgentProviderNotSupported: 'Этот провайдер агента не поддерживается. Проверьте и повторите попытку!'
ErrAgentAccountRequired: 'Выберите учетную запись агента и повторите попытку.'
ErrAgentAccountNotVerified: 'Учетная запись не прошла проверку. Проверьте и повторите попытку!'
ErrAgentProviderMismatch: 'Провайдер учетной записи не совпадает. Проверьте и повторите попытку!'
ErrAgentBaseURLRequired: 'Base URL не может быть пустым. Проверьте и повторите попытку!'
ErrAgentApiKeyRequired: 'API Key не может быть пустым. Проверьте и повторите попытку!'
ErrAgentComposeRequired: 'Пользовательское содержимое compose не может быть пустым. Проверьте и повторите попытку!'
ErrAgentIDRequired: 'ID агента не может быть пустым. Проверьте и повторите попытку!'
ErrAgentAccountIDRequired: 'ID учетной записи не может быть пустым. Проверьте и повторите попытку!'
#резервное копирование
Localhost: 'Локальная машина'
ErrBackupInUsed: 'Учетная запись резервного копирования использовалась в запланированной задаче и не может быть удалена.'
@@ -201,6 +214,7 @@ ErrRemoteExist: 'Удаленная база данных с таким имен
ErrLocalExist: 'Имя уже существует в локальной базе данных, измените его и повторите попытку'
RemoteBackup: "Для резервного копирования удаленной базы данных необходимо сначала запустить службу базы данных локального контейнера с помощью образа {{ .name }}, пожалуйста, подождите..."
RemoteRecover: "Для восстановления удаленной базы данных необходимо сначала запустить службу базы данных локального контейнера с помощью образа {{ .name }}, пожалуйста, подождите..."
Arg: "Аргумент"
#редис
ErrTypeOfRedis: 'Тип файла восстановления не соответствует текущему методу сохранения. Измените его и повторите попытку'
@@ -211,7 +225,7 @@ ErrObjectInUsed: "Этот объект используется и не мож
ErrObjectBeDependent: "Этот образ зависит от других образов и не может быть удален!"
ErrPortRules: 'Номер порта не совпадает, введите заново!'
ErrPgImagePull: 'Время извлечения изображения истекло. Настройте ускорение изображения или вручную извлеките изображение {{ .name }} и повторите попытку'
PruneHelper: "Очистка удалила {{ .name }} в количестве {{ .count }}, освободив {{ .size }} дискового пространства"
PruneHelper: "Эта очистка {{ .name }} освободила дисковое пространство {{ .size }}"
ImageRemoveHelper: "Удалён образ {{ .name }}, освобождено {{ .size }} дискового пространства"
BuildCache: "Кэш сборки"
Volume: "Том хранилища"
@@ -505,19 +519,19 @@ ErrInstallExtension: "Уже выполняется задача установ
PanelAlertTitle: "Уведомление панели"
TestAlertTitle: "Тестовое письмо - проверка подключения"
TestAlert: "Это тестовое письмо для проверки правильности настройки отправки писем."
LicenseExpirationAlert: "Срок действия лицензии на 1Panel истекает через {{ .day }} дней. Подробности смотрите в панели."
CronJobFailedAlert: "Запланированное задание '{{ .name }}' на вашем 1Panel завершилось с ошибкой. Подробности в панели."
ClamAlert: "Антивирусная проверка 1Panel обнаружила {{ .num }} заражённых файлов. Подробности смотрите в панели."
WebSiteAlert: "На вашем 1Panel {{ .num }} веб-сайтов истекают через {{ .day }} дней. Подробности смотрите в панели."
SSLAlert: "На вашем 1Panel {{ .num }} SSL-сертификатов истекают через {{ .day }} дней. Подробности смотрите в панели."
DiskUsedAlert: "Диск '{{ .name }}' на вашем 1Panel использует {{ .used }}. Подробности смотрите в панели."
ResourceAlert: "Средняя загрузка {{ .name }} за {{ .time }} минут составляет {{ .used }}. Подробности смотрите в панели."
PanelVersionAlert: "Доступна новая версия 1Panel. Обновитесь через панель."
PanelPwdExpirationAlert: "Пароль для 1Panel истекает через {{ .day }} дней. Подробности смотрите в панели."
CommonAlert: "Ваш 1Panel, {{ .msg }}. Подробности смотрите, войдя в панель."
NodeExceptionAlert: "Ваш 1Panel, {{ .num }} узлов работают неправильно. Подробности смотрите, войдя в панель."
LicenseExceptionAlert: "Ваш 1Panel, {{ .num }} лицензий работают неправильно. Подробности смотрите, войдя в панель."
SSHAndPanelLoginAlert: "Ваш 1Panel, обнаружен аномальный вход в панель {{ .name }} с {{ .ip }}. Подробности смотрите, войдя в панель."
LicenseExpirationAlert: "Ваш панель {{ .node }}{{ .ip }}, лицензия истечет через {{ .day }} дней. Войдите в панель для получения деталей."
CronJobFailedAlert: "Ваш панель {{ .node }}{{ .ip }}, выполнение запланированной задачи {{ .name }} не удалось. Войдите в панель для получения деталей."
ClamAlert: "Ваш панель {{ .node }}{{ .ip }}, задача сканирования на вирусы обнаружила {{ .num }} зараженных файлов. Войдите в панель для получения деталей."
WebSiteAlert: "Ваш панель {{ .node }}{{ .ip }}, {{ .num }} веб-сайтов истечут через {{ .day }} дней. Войдите в панель для получения деталей."
SSLAlert: "Ваш панель {{ .node }}{{ .ip }}, {{ .num }} сертификатов SSL истечут через {{ .day }} дней. Войдите в панель для получения деталей."
DiskUsedAlert: "Ваш панель {{ .node }}{{ .ip }}, использование диска {{ .name }} достигло {{ .used }}. Войдите в панель для получения деталей."
ResourceAlert: "Ваш панель {{ .node }}{{ .ip }}, средний уровень использования {{ .name }} за {{ .time }} минут составляет {{ .used }}. Войдите в панель для получения деталей."
PanelVersionAlert: "Ваш панель {{ .node }}{{ .ip }}, доступна последняя версия панели для обновления. Войдите в панель для получения деталей."
PanelPwdExpirationAlert: "Ваш панель {{ .node }}{{ .ip }}, пароль панели истечет через {{ .day }} дней. Войдите в панель для получения деталей."
CommonAlert: "Ваш панель {{ .node }}{{ .ip }}, {{ .msg }}. Войдите в панель для получения деталей."
NodeExceptionAlert: "Ваш панель {{ .node }}{{ .ip }}, {{ .num }} узлов работают с ошибками. Войдите в панель для получения деталей."
LicenseExceptionAlert: "Ваш панель {{ .node }}{{ .ip }}, {{ .num }} лицензий имеют ошибки. Войдите в панель для получения деталей."
SSHAndPanelLoginAlert: "Ваш панель {{ .node }}{{ .ip }}, вход {{ .name }} с адреса {{ .loginIp }} является аномальным. Войдите в панель для получения деталей."
#disk
DeviceNotFound: "Устройство {{ .name }} не найдено"
@@ -526,4 +540,4 @@ PartitionDiskErr: "Не удалось разделить, {{ .err }}"
FormatDiskErr: "Не удалось отформатировать диск, {{ .err }}"
MountDiskErr: "Не удалось подключить диск, {{ .err }}"
UnMountDiskErr: "Не удалось отключить диск, {{ .err }}"
XfsNotFound: "Файловая система xfs не обнаружена, сначала установите xfsprogs"
XfsNotFound: "Файловая система xfs не обнаружена, сначала установите xfsprogs"
+28 -14
View File
@@ -41,6 +41,19 @@ ErrGroupIsDefault: 'Varsayılan grup, silinemez'
ErrGroupIsInWebsiteUse: 'Grup başka bir web sitesi tarafından kullanılıyor ve silinemez.'
Decrypt: "Şifre Çöz"
#agent
ErrAgentAccountBound: 'Bu hesap bir ajana bağlı ve silinemez. Kontrol edip tekrar deneyin!'
ErrAgentAccountUnavailable: 'Hesap bağlantı bilgileri kullanılamıyor. Hata: {{ .err }}. Kontrol edip tekrar deneyin!'
ErrAgentProviderNotSupported: 'Bu ajan sağlayıcısı desteklenmiyor. Kontrol edip tekrar deneyin!'
ErrAgentAccountRequired: 'Lütfen bir ajan hesabı seçip tekrar deneyin.'
ErrAgentAccountNotVerified: 'Hesap doğrulanmadı. Kontrol edip tekrar deneyin!'
ErrAgentProviderMismatch: 'Hesap sağlayıcısı eşleşmiyor. Kontrol edip tekrar deneyin!'
ErrAgentBaseURLRequired: 'Base URL boş olamaz. Kontrol edip tekrar deneyin!'
ErrAgentApiKeyRequired: 'API Key boş olamaz. Kontrol edip tekrar deneyin!'
ErrAgentComposeRequired: 'Özel compose içeriği boş olamaz. Kontrol edip tekrar deneyin!'
ErrAgentIDRequired: 'Ajan ID boş olamaz. Kontrol edip tekrar deneyin!'
ErrAgentAccountIDRequired: 'Hesap ID boş olamaz. Kontrol edip tekrar deneyin!'
#backup
Localhost: 'Yerel Makine'
ErrBackupInUsed: 'Yedekleme hesabı zamanlanmış görevde kullanıldı ve silinemez.'
@@ -202,6 +215,7 @@ ErrRemoteExist: 'Uzak veritabanında bu adla zaten mevcut, lütfen değiştirin
ErrLocalExist: 'Yerel veritabanında bu ad zaten mevcut, lütfen değiştirin ve tekrar deneyin'
RemoteBackup: "Uzak veritabanını yedeklemek için önce {{ .name }} görüntüsü kullanılarak yerel konteyner veritabanı hizmetinin başlatılması gerekiyor, lütfen bekleyin..."
RemoteRecover: "Uzak veritabanını geri yüklemek için önce {{ .name }} görüntüsü kullanılarak yerel konteyner veritabanı hizmetinin başlatılması gerekiyor, lütfen bekleyin..."
Arg: "Argüman"
#redis
ErrTypeOfRedis: 'Kurtarma dosyası türü mevcut kalıcılık yöntemiyle eşleşmiyor, lütfen değiştirin ve tekrar deneyin'
@@ -212,7 +226,7 @@ ErrObjectInUsed: "Bu nesne kullanımda ve silinemez!"
ErrObjectBeDependent: "Bu görüntü diğer görüntüler tarafından bağımlıdır ve silinemez!"
ErrPortRules: 'Port numarası eşleşmiyor, lütfen yeniden girin!'
ErrPgImagePull: 'Image çekme zaman aşımı, lütfen image hızlandırma yapılandırın veya manuel olarak {{ .name }} imageını çekin ve tekrar deneyin'
PruneHelper: "Bu temizleme {{ .name }} {{ .count }} öğe kaldırdı, {{ .size }} disk alanı boşalttı"
PruneHelper: "Bu temizlik {{ .name }} disk alanı {{ .size }} boşalttı"
ImageRemoveHelper: "{{ .name }} imajı silindi, {{ .size }} disk alanı boşalttı"
BuildCache: "Derleme önbelleği"
Volume: "Depolama hacmi"
@@ -506,19 +520,19 @@ ErrInstallExtension: "Zaten devam eden bir kurulum görevi var, görevin bitmesi
PanelAlertTitle: "Panel Uyarı Bildirimi"
TestAlertTitle: "Test E-postası - E-posta Bağlantısını Doğrula"
TestAlert: "Bu, e-posta gönderim ayarlarınızın doğru yapılandırıldığını doğrulamak için gönderilen bir test e-postasıdır."
LicenseExpirationAlert: "1Panel lisansınız {{ .day }} gün içinde sona erecek. Detaylar için panele giriş yapın."
CronJobFailedAlert: "1Panel zamanlanmış görevi '{{ .name }}' başarısız oldu. Detaylar için panele giriş yapın."
ClamAlert: "1Panel virüs taraması {{ .num }} enfekte dosya tespit etti. Detaylar için panele giriş yapın."
WebSiteAlert: "1Panel üzerindeki {{ .num }} web sitesi {{ .day }} gün içinde sona erecek. Detaylar için panele giriş yapın."
SSLAlert: "1Panel üzerindeki {{ .num }} SSL sertifikası {{ .day }} gün içinde sona erecek. Detaylar için panele giriş yapın."
DiskUsedAlert: "1Panel diski '{{ .name }}' {{ .used }} kullanıldı. Detaylar için panele giriş yapın."
ResourceAlert: "1Panel üzerinde ortalama {{ .time }} dakikalık {{ .name }} kullanım oranı {{ .used }}. Detaylar için panele giriş yapın."
PanelVersionAlert: "1Panel için yeni bir sürüm mevcut. Güncellemek için panele giriş yapın."
PanelPwdExpirationAlert: "1Panel şifreniz {{ .day }} gün içinde sona erecek. Detaylar için panele giriş yapın."
CommonAlert: "1Panel'iniz, {{ .msg }}. Detaylar için panele giriş yapınız."
NodeExceptionAlert: "1Panel'iniz, {{ .num }} düğümde sorun var. Detaylar için panele giriş yapınız."
LicenseExceptionAlert: "1Panel'iniz, {{ .num }} lisansda sorun var. Detaylar için panele giriş yapınız."
SSHAndPanelLoginAlert: "1Panel'iniz, {{ .ip }} adresinden {{ .name }} paneline anormal giriş tespit edildi. Detaylar için panele giriş yapınız."
LicenseExpirationAlert: "Paneliniz {{ .node }}{{ .ip }}, lisans {{ .day }} gün içinde süresi dolacak. Detaylar için paneline giriş yapın."
CronJobFailedAlert: "Paneliniz {{ .node }}{{ .ip }}, zamanlanmış görev {{ .name }} çalıştırma başarısız oldu. Detaylar için paneline giriş yapın."
ClamAlert: "Paneliniz {{ .node }}{{ .ip }}, virüs tarama görevi {{ .num }} enfekte dosya tespit etti. Detaylar için paneline giriş yapın."
WebSiteAlert: "Paneliniz {{ .node }}{{ .ip }}, {{ .num }} web sitesi {{ .day }} gün içinde süresi dolacak. Detaylar için paneline giriş yapın."
SSLAlert: "Paneliniz {{ .node }}{{ .ip }}, {{ .num }} SSL sertifikası {{ .day }} gün içinde süresi dolacak. Detaylar için paneline giriş yapın."
DiskUsedAlert: "Paneliniz {{ .node }}{{ .ip }}, {{ .name }} diski kullanımı {{ .used }}'a ulaştı. Detaylar için paneline giriş yapın."
ResourceAlert: "Paneliniz {{ .node }}{{ .ip }}, son {{ .time }} dakika içindeki ortalama {{ .name }} kullanım oranı {{ .used }}'dır. Detaylar için paneline giriş yapın."
PanelVersionAlert: "Paneliniz {{ .node }}{{ .ip }}, en son panel sürümü yükseltme için mevcut. Detaylar için paneline giriş yapın."
PanelPwdExpirationAlert: "Paneliniz {{ .node }}{{ .ip }}, panel şifresi {{ .day }} gün içinde süresi dolacak. Detaylar için paneline giriş yapın."
CommonAlert: "Paneliniz {{ .node }}{{ .ip }}, {{ .msg }}. Detaylar için paneline giriş yapın."
NodeExceptionAlert: "Paneliniz {{ .node }}{{ .ip }}, {{ .num }} düğüm anormal durumda. Detaylar için paneline giriş yapın."
LicenseExceptionAlert: "Paneliniz {{ .node }}{{ .ip }}, {{ .num }} lisans anormal durumda. Detaylar için paneline giriş yapın."
SSHAndPanelLoginAlert: "Paneliniz {{ .node }}{{ .ip }}, {{ .loginIp }} adresinden {{ .name }} girişi anormal. Detaylar için paneline giriş yapın."
#disk
DeviceNotFound: "Cihaz {{ .name }} bulunamadı"
+34 -20
View File
@@ -38,6 +38,19 @@ ErrGroupIsDefault: '預設分組,無法刪除'
ErrGroupIsInWebsiteUse: '分組正在被其他網站使用,無法刪除'
Decrypt: "解密"
#agent
ErrAgentAccountBound: '該帳號已綁定到智能體,無法刪除,請檢查後重試!!'
ErrAgentAccountUnavailable: '帳號連線資訊不可用,錯誤:{{ .err }},請檢查後重試!'
ErrAgentProviderNotSupported: '暫不支援該智能體提供商,請檢查後重試!'
ErrAgentAccountRequired: '請選擇智能體帳號後重試'
ErrAgentAccountNotVerified: '帳號未驗證通過,請檢查後重試!'
ErrAgentProviderMismatch: '帳號提供商不匹配,請檢查後重試!'
ErrAgentBaseURLRequired: 'Base URL 不能為空,請檢查後重試!'
ErrAgentApiKeyRequired: 'API Key 不能為空,請檢查後重試!'
ErrAgentComposeRequired: '自訂編排內容不能為空,請檢查後重試!'
ErrAgentIDRequired: '智能體 ID 不能為空,請檢查後重試!'
ErrAgentAccountIDRequired: '帳號 ID 不能為空,請檢查後重試!'
#backup
Localhost: '本機'
ErrBackupInUsed: '此備份帳號已在排程任務中使用,無法刪除'
@@ -124,7 +137,7 @@ ErrLogFileToLarge: "日誌檔案超過 500M,無法打開"
FileDropFailed: "清理文件 {{ .name }} 失敗: {{ .err }}"
FileDropSuccess: "清理文件 {{ .name }} 成功,清理 {{ .count }} 個文件,釋放 {{ .size }} 磁碟空間"
FileDropSum: "文件清理完成,共清理 {{ .count }} 個文件,共釋放 {{ .size }} 磁碟空間"
ErrBadDecrypt: "壓縮檔解壓密碼錯誤,解壓失敗,請檢查後重試!"
ErrBadDecrypt: "壓縮檔解壓密碼錯誤,解壓失敗,請檢查後重試!!"
#website
ErrAliasIsExist: '代號已存在'
@@ -200,6 +213,7 @@ ErrRemoteExist: '遠端資料庫已存在該名稱,請修改後重試'
ErrLocalExist: '本機資料庫已存在該名稱,請修改後重試'
RemoteBackup: "備份遠端資料庫需要先使用映像 {{ .name }} 啟動本機容器資料庫服務,請稍候..."
RemoteRecover: "恢復遠端資料庫需要先使用映像 {{ .name }} 啟動本機容器資料庫服務,請稍候..."
Arg: "參數"
#redis
ErrTypeOfRedis: '復原檔案類型與目前持久化方式不符,請修改後重試'
@@ -210,7 +224,7 @@ ErrObjectInUsed: "該對象正被使用,無法刪除!"
ErrObjectBeDependent: "該映像正被其他映像所依賴,無法刪除!"
ErrPortRules: '連接埠數目不匹配,請重新輸入!'
ErrPgImagePull: '鏡像拉取逾時,請配置鏡像加速或手動拉取{{ .name }} 鏡像後重試'
PruneHelper: "本次清理 {{ .name }} {{ .count }} 個,釋放磁碟空間 {{ .size }}"
PruneHelper: "本次清理 {{ .name }} 釋放磁碟空間 {{ .size }}"
ImageRemoveHelper: "刪除鏡像 {{ .name }} ,釋放磁碟空間 {{ .size }}"
BuildCache: "構建快取"
Volume: "磁碟區"
@@ -491,11 +505,11 @@ AppBackupFileIncomplete: '備份檔案不完整缺少 app.json 或 app.tar.gz
AppAttributesNotMatch: '應用程式類型或名稱不一致'
#alert
ErrAlert: '警告訊息格式錯誤,請檢查後重試!'
ErrAlertPush: '警告訊息推送錯誤,請檢查後重試!'
ErrAlertSave: '警告訊息儲存錯誤,請檢查後重試!'
ErrAlertSync: '警告訊息同步錯誤,請檢查後重試!'
ErrAlertRemote: '警告訊息遠端錯誤,請檢查後重試!'
ErrAlert: '警告訊息格式錯誤,請檢查後重試!!'
ErrAlertPush: '警告訊息推送錯誤,請檢查後重試!!'
ErrAlertSave: '警告訊息儲存錯誤,請檢查後重試!!'
ErrAlertSync: '警告訊息同步錯誤,請檢查後重試!!'
ErrAlertRemote: '警告訊息遠端錯誤,請檢查後重試!!'
#task - runtime
ErrInstallExtension: "已有安裝任務正在進行,請等待任務結束"
@@ -504,19 +518,19 @@ ErrInstallExtension: "已有安裝任務正在進行,請等待任務結束"
PanelAlertTitle: "面板警示通知"
TestAlertTitle: "測試郵件 - 驗證信箱連通性"
TestAlert: "這是一封測試郵件,用於確認您的郵件發送設定是否正確。"
LicenseExpirationAlert: "您的 1Panel 授權將於 {{ .day }} 天後到期,詳情請登入面板查看。"
CronJobFailedAlert: "您的 1Panel 面板中,計畫任務 '{{ .name }}' 執行失敗,詳情請登入面板查看。"
ClamAlert: "您的 1Panel 面板病毒掃描任務發現 {{ .num }} 個受感染的檔案,詳情請登入面板查看。"
WebSiteAlert: "您的 1Panel 面板中有 {{ .num }} 個網站將於 {{ .day }} 天後到期,詳情請登入面板查看。"
SSLAlert: "您的 1Panel 面板中有 {{ .num }} 張 SSL 憑證將於 {{ .day }} 天後到期,詳情請登入面板查看。"
DiskUsedAlert: "您的 1Panel 面板磁碟 '{{ .name }}' 已使用 {{ .used }},詳情請登入面板查看。"
ResourceAlert: "您的 1Panel 面板於 {{ .time }} 分鐘內的平均 {{ .name }} 使用率為 {{ .used }},詳情請登入面板查看。"
PanelVersionAlert: "您的 1Panel 面板有可升級的新版本,詳情請登入面板查看。"
PanelPwdExpirationAlert: "您的 1Panel 面板密碼將於 {{ .day }} 天後到期,詳情請登入面板查看。"
CommonAlert: "您的 1Panel 面板,{{ .msg }},詳情請登入面板查看。"
NodeExceptionAlert: "您的 1Panel 面板,{{ .num }} 個節點存在異常,詳情請登入面板查看。"
LicenseExceptionAlert: "您的 1Panel 面板,{{ .num }} 個許可證存在異常,詳情請登入面板查看。"
SSHAndPanelLoginAlert: "您的 1Panel 面板,面板{{ .name }}從 {{ .ip }} 登入異常,詳情請登入面板查看。"
LicenseExpirationAlert: "您的 {{ .node }}{{ .ip }} 面板,授權將於 {{ .day }} 天後到期,詳情請登入面板查看。"
CronJobFailedAlert: "您的 {{ .node }}{{ .ip }} 面板,排程任務{{ .name }}執行失敗,詳情請登入面板查看。"
ClamAlert: "您的 {{ .node }}{{ .ip }} 面板,病毒掃描任務偵測到 {{ .num }} 個感染檔案,詳情請登入面板查看。"
WebSiteAlert: "您的 {{ .node }}{{ .ip }} 面板,{{ .num }} 個網站將於 {{ .day }} 天後到期,詳情請登入面板查看。"
SSLAlert: "您的 {{ .node }}{{ .ip }} 面板,{{ .num }} 張SSL憑證將於 {{ .day }} 天後到期,詳情請登入面板查看。"
DiskUsedAlert: "您的 {{ .node }}{{ .ip }} 面板,磁碟 {{ .name }} 使用率已達 {{ .used }},詳情請登入面板查看。"
ResourceAlert: "您的 {{ .node }}{{ .ip }} 面板,過去 {{ .time }} 分鐘內 {{ .name }} 平均使用率為 {{ .used }},詳情請登入面板查看。"
PanelVersionAlert: "您的 {{ .node }}{{ .ip }} 面板,已有最新版本可供升級,詳情請登入面板查看。"
PanelPwdExpirationAlert: "您的 {{ .node }}{{ .ip }} 面板,面板密碼將於 {{ .day }} 天後到期,詳情請登入面板查看。"
CommonAlert: "您的 {{ .node }}{{ .ip }} 面板,{{ .msg }},詳情請登入面板查看。"
NodeExceptionAlert: "您的 {{ .node }}{{ .ip }} 面板,{{ .num }} 個節點出現異常,詳情請登入面板查看。"
LicenseExceptionAlert: "您的 {{ .node }}{{ .ip }} 面板,{{ .num }} 個授權出現異常,詳情請登入面板查看。"
SSHAndPanelLoginAlert: "您的 {{ .node }}{{ .ip }} 面板,來自 {{ .loginIp }} 的 {{ .name }} 登入出現異常,詳情請登入面板查看。"
#disk
DeviceNotFound: "裝置 {{ .name }} 未找到"
+36 -22
View File
@@ -38,6 +38,19 @@ ErrGroupIsDefault: "默认分组,无法删除"
ErrGroupIsInWebsiteUse: "分组正在被其他网站使用,无法删除"
Decrypt: "解密"
#agent
ErrAgentAccountBound: "该账号已经绑定到智能体,无法删除,请检查后重试!!"
ErrAgentAccountUnavailable: "账号连接信息不可用,错误:{{ .err }},请检查后重试!"
ErrAgentProviderNotSupported: "暂不支持该智能体提供商,请检查后重试!"
ErrAgentAccountRequired: "请选择智能体账号后重试"
ErrAgentAccountNotVerified: "账号未验证通过,请检查后重试!"
ErrAgentProviderMismatch: "账号提供商不匹配,请检查后重试!"
ErrAgentBaseURLRequired: "Base URL 不能为空,请检查后重试!"
ErrAgentApiKeyRequired: "API Key 不能为空,请检查后重试!"
ErrAgentComposeRequired: "自定义编排内容不能为空,请检查后重试!"
ErrAgentIDRequired: "智能体 ID 不能为空,请检查后重试!"
ErrAgentAccountIDRequired: "账号 ID 不能为空,请检查后重试!"
#backup
Localhost: '本机'
ErrBackupInUsed: "该备份账号已在计划任务中使用,无法删除"
@@ -125,7 +138,7 @@ ErrLogFileToLarge: "日志文件超过 500M,无法打开"
FileDropFailed: "清理文件 {{ .name }} 失败 {{ .err }}"
FileDropSuccess: "清理文件 {{ .name }} 成功,清理文件 {{ .count }},释放磁盘空间 {{ .size }}"
FileDropSum: "文件清理成功,清理文件共计 {{ .count }},释放磁盘空间共计 {{ .size }}"
ErrBadDecrypt: "压缩包解压密码错误,解压失败,请检查后重试!"
ErrBadDecrypt: "压缩包解压密码错误,解压失败,请检查后重试!!"
#website
ErrAliasIsExist: "代号已存在"
@@ -201,6 +214,7 @@ ErrRemoteExist: "远程数据库已存在该名称,请修改后重试"
ErrLocalExist: "本地数据库已存在该名称,请修改后重试"
RemoteBackup: "备份远程数据库需要先使用镜像 {{ .name }} 启动本地容器数据库服务,请稍候..."
RemoteRecover: "恢复远程数据库需要先使用镜像 {{ .name }} 启动本地容器数据库服务,请稍候..."
Arg: "参数"
#redis
ErrTypeOfRedis: "恢复文件类型与当前持久化方式不符,请修改后重试"
@@ -211,7 +225,7 @@ ErrObjectInUsed: "该对象正被使用,无法删除!"
ErrObjectBeDependent: "该镜像正被其他镜像所依赖,无法删除!"
ErrPortRules: "端口数目不匹配,请重新输入!"
ErrPgImagePull: "镜像拉取超时,请配置镜像加速或手动拉取 {{ .name }} 镜像后重试"
PruneHelper: "本次清理 {{ .name }} {{ .count }} 个,释放磁盘空间 {{ .size }}"
PruneHelper: "本次清理 {{ .name }} 释放磁盘空间 {{ .size }}"
ImageRemoveHelper: "删除镜像 {{ .name }} ,释放磁盘空间 {{ .size }}"
BuildCache: "构建缓存"
Volume: "存储卷"
@@ -262,7 +276,7 @@ WafIpGroupNotFound: "WAF IP组未找到,请先到【高级功能-WAF-黑白名
#toolbox
ErrNotExistUser: "当前用户不存在,请修改后重试!"
ErrBanAction: "设置失败,当前 {{ .name }} 服务不可用,请检查后重试!"
ErrBanAction: "设置失败,当前 {{ .name }} 服务不可用,请检查后重试!!"
ErrClamdscanNotFound: "未检测到 clamdscan 命令,请参考文档安装!"
TaskSwapSet: "设置 Swap"
SetSwap: "设置 Swap {{ .path }} - {{ .size }}"
@@ -493,11 +507,11 @@ AppBackupFileIncomplete: "备份文件不完整 缺少 app.json 或者 app.tar.
AppAttributesNotMatch: "应用类型或者名称不一致"
#alert
ErrAlert: "告警信息格式错误,请检查后重试!"
ErrAlertPush: "告警信息推送错误,请检查后重试!"
ErrAlertSave: "告警信息保存错误,请检查后重试!"
ErrAlertSync: "告警信息同步错误,请检查后重试!"
ErrAlertRemote: "告警信息远端错误,请检查后重试!"
ErrAlert: "告警信息格式错误,请检查后重试!!"
ErrAlertPush: "告警信息推送错误,请检查后重试!!"
ErrAlertSave: "告警信息保存错误,请检查后重试!!"
ErrAlertSync: "告警信息同步错误,请检查后重试!!"
ErrAlertRemote: "告警信息远端错误,请检查后重试!!"
#task - runtime
ErrInstallExtension: "已有安装任务正在进行,请等待任务结束"
@@ -506,19 +520,19 @@ ErrInstallExtension: "已有安装任务正在进行,请等待任务结束"
PanelAlertTitle: "面板告警通知"
TestAlertTitle: "测试邮件 - 验证邮箱连通性"
TestAlert: "这是一封测试邮件,旨在验证您的邮箱发件配置是否正确。"
LicenseExpirationAlert: "您的 1Panel 面板,许可证将在 {{ .day }} 天后到期,详情请登录面板查看。"
CronJobFailedAlert: "您的 1Panel 面板,计划任务-{{ .name }}执行失败,详情请登录面板查看。"
ClamAlert: "您的 1Panel 面板,病毒扫描任务发现 {{ .num }} 个感染文件,详情请登录面板查看。"
WebSiteAlert: "您的 1Panel 面板,有 {{ .num }} 个网站将在 {{ .day }} 天后到期,详情请登录面板查看。"
SSLAlert: "您的 1Panel 面板,有 {{ .num }} 张SSL证书将在 {{ .day }} 天后到期,详情请登录面板查看。"
DiskUsedAlert: "您的 1Panel 面板,磁盘 {{ .name }} 已使用 {{ .used }},详情请登录面板查看。"
ResourceAlert: "您的 1Panel 面板,平均 {{ .time }} 分钟内的 {{ .name }} 使用率为 {{ .used }},详情请登录面板查看。"
PanelVersionAlert: "您的 1Panel 面板,有最新面板版本可供升级,详情请登录面板查看。"
PanelPwdExpirationAlert: "您的 1Panel 面板,面板密码将在 {{ .day }} 天后到期,详情请登录面板查看。"
CommonAlert: "您的 1Panel 面板,{{ .msg }},详情请登录面板查看。"
NodeExceptionAlert: "您的 1Panel 面板,{{ .num }} 个节点存在异常,详情请登录面板查看。"
LicenseExceptionAlert: "您的 1Panel 面板,{{ .num }} 个许可证存在异常,详情请登录面板查看。"
SSHAndPanelLoginAlert: "您的 1Panel 面板,面板{{ .name }}登录{{ .ip }}异常,详情请登录面板查看。"
LicenseExpirationAlert: "您的 {{ .node }}{{ .ip }} 面板,许可证将在 {{ .day }} 天后到期,详情请登录面板查看。"
CronJobFailedAlert: "您的 {{ .node }}{{ .ip }} 面板,计划任务-{{ .name }}执行失败,详情请登录面板查看。"
ClamAlert: "您的 {{ .node }}{{ .ip }} 面板,病毒扫描任务发现 {{ .num }} 个感染文件,详情请登录面板查看。"
WebSiteAlert: "您的 {{ .node }}{{ .ip }} 面板,有 {{ .num }} 个网站将在 {{ .day }} 天后到期,详情请登录面板查看。"
SSLAlert: "您的 {{ .node }}{{ .ip }} 面板,有 {{ .num }} 张SSL证书将在 {{ .day }} 天后到期,详情请登录面板查看。"
DiskUsedAlert: "您的 {{ .node }}{{ .ip }} 面板,磁盘 {{ .name }} 已使用 {{ .used }},详情请登录面板查看。"
ResourceAlert: "您的 {{ .node }}{{ .ip }} 面板,平均 {{ .time }} 分钟内的 {{ .name }} 使用率为 {{ .used }},详情请登录面板查看。"
PanelVersionAlert: "您的 {{ .node }}{{ .ip }} 面板,有最新面板版本可供升级,详情请登录面板查看。"
PanelPwdExpirationAlert: "您的 {{ .node }}{{ .ip }} 面板,面板密码将在 {{ .day }} 天后到期,详情请登录面板查看。"
CommonAlert: "您的 {{ .node }}{{ .ip }} 面板,{{ .msg }},详情请登录面板查看。"
NodeExceptionAlert: "您的 {{ .node }}{{ .ip }} 面板,{{ .num }} 个节点存在异常,详情请登录面板查看。"
LicenseExceptionAlert: "您的 {{ .node }}{{ .ip }} 面板,{{ .num }} 个许可证存在异常,详情请登录面板查看。"
SSHAndPanelLoginAlert: "您的 {{ .node }}{{ .ip }} 面板,面板{{ .name }}登录{{ .loginIp }}异常,详情请登录面板查看。"
#disk
DeviceNotFound: "设备 {{ .name }} 未找到"
@@ -527,4 +541,4 @@ PartitionDiskErr: "分区失败,{{ .err }}"
FormatDiskErr: "格式化磁盘失败,{{ .err }}"
MountDiskErr: "挂载磁盘失败,{{ .err }}"
UnMountDiskErr: "卸载磁盘失败,{{ .err }}"
XfsNotFound: "未检测到 xfs 文件系统,请先安装 xfsprogs"
XfsNotFound: "未检测到 xfs 文件系统,请先安装 xfsprogs"
+4 -1
View File
@@ -23,7 +23,10 @@ func syncApp() {
if global.CONF.Base.IsOffLine {
return
}
_ = service.NewISettingService().Update("AppStoreSyncStatus", constant.StatusSyncSuccess)
setting, err := service.NewISettingService().GetSettingInfo()
if err == nil && setting.AppStoreSyncStatus == constant.StatusSyncing {
_ = service.NewISettingService().Update("AppStoreSyncStatus", constant.StatusSyncSuccess)
}
if err := service.NewIAppService().SyncAppListFromRemote(""); err != nil {
global.LOG.Errorf("App Store synchronization failed")
return
+1
View File
@@ -21,6 +21,7 @@ func Init() {
global.Dir.AppDir, _ = fileOp.CreateDirWithPath(true, path.Join(baseDir, "1panel/apps"))
global.Dir.ResourceDir, _ = fileOp.CreateDirWithPath(true, path.Join(baseDir, "1panel/resource"))
global.Dir.IconCacheDir, _ = fileOp.CreateDirWithPath(true, path.Join(baseDir, "1panel/resource/icon"))
global.Dir.AppResourceDir, _ = fileOp.CreateDirWithPath(true, path.Join(baseDir, "1panel/resource/apps"))
global.Dir.AppInstallDir, _ = fileOp.CreateDirWithPath(true, path.Join(baseDir, "1panel/apps"))
global.Dir.LocalAppResourceDir, _ = fileOp.CreateDirWithPath(true, path.Join(baseDir, "1panel/resource/apps/local"))
+1 -1
View File
@@ -79,7 +79,7 @@ func Init() {
iptablesService := service.IptablesService{}
iptablesStatus, _ := settingRepo.GetValueByKey("IptablesStatus")
if iptablesStatus == constant.StatusEnable {
if err := iptablesService.Operate(dto.IptablesOp{Operate: "bind-base"}); err != nil {
if err := iptablesService.Operate(dto.IptablesOp{Operate: "bind-base-without-init"}); err != nil {
global.LOG.Errorf("bind base chains failed, err: %v", err)
return
}
+5
View File
@@ -61,6 +61,11 @@ func InitAgentDB() {
migrations.AddisIPtoWebsiteSSL,
migrations.InitPingStatus,
migrations.UpdateApp,
migrations.AddCronjobArgs,
migrations.AddWebsiteAcmeAccountColumn,
migrations.AddAgentTables,
migrations.MigrateOpenclawAgents,
migrations.AddAgentCustomModelFields,
})
if err := m.Migrate(); err != nil {
global.LOG.Error(err)
+150
View File
@@ -2,6 +2,7 @@ package migrations
import (
"encoding/json"
"errors"
"fmt"
"os"
"os/user"
@@ -16,6 +17,7 @@ import (
"github.com/1Panel-dev/1Panel/agent/app/service"
"github.com/1Panel-dev/1Panel/agent/constant"
"github.com/1Panel-dev/1Panel/agent/global"
migrationutils "github.com/1Panel-dev/1Panel/agent/init/migration/migrations/utils"
"github.com/1Panel-dev/1Panel/agent/utils/common"
"github.com/1Panel-dev/1Panel/agent/utils/copier"
"github.com/1Panel-dev/1Panel/agent/utils/encrypt"
@@ -807,3 +809,151 @@ var UpdateApp = &gormigrate.Migration{
return tx.AutoMigrate(&model.App{})
},
}
var AddCronjobArgs = &gormigrate.Migration{
ID: "20260106-add-cronjob-args",
Migrate: func(tx *gorm.DB) error {
return tx.AutoMigrate(&model.Cronjob{})
},
}
var AddWebsiteAcmeAccountColumn = &gormigrate.Migration{
ID: "20260110-add-website-acme-account",
Migrate: func(tx *gorm.DB) error {
return tx.AutoMigrate(&model.WebsiteAcmeAccount{})
},
}
var AddAgentTables = &gormigrate.Migration{
ID: "20260205-add-agent-tables",
Migrate: func(tx *gorm.DB) error {
return tx.AutoMigrate(
&model.Agent{},
&model.AgentAccount{},
)
},
}
var MigrateOpenclawAgents = &gormigrate.Migration{
ID: "20260207-migrate-openclaw-agents",
Migrate: func(tx *gorm.DB) error {
var installs []model.AppInstall
if err := tx.Preload("App").Find(&installs).Error; err != nil {
return err
}
for _, install := range installs {
appKey := install.App.Key
if appKey == "" || install.App.Resource == "" {
var app model.App
if err := tx.First(&app, install.AppId).Error; err == nil {
install.App = app
appKey = app.Key
}
}
if appKey != constant.AppOpenclaw {
continue
}
var count int64
if err := tx.Model(&model.Agent{}).Where("app_install_id = ?", install.ID).Count(&count).Error; err != nil {
return err
}
if count > 0 {
continue
}
envMap := map[string]interface{}{}
if strings.TrimSpace(install.Env) != "" {
_ = json.Unmarshal([]byte(install.Env), &envMap)
}
configPath := path.Join(install.GetPath(), "data", "conf", "openclaw.json")
cfgMeta := migrationutils.OpenclawMeta{}
if fileData, err := os.ReadFile(configPath); err == nil {
cfgMeta = migrationutils.ParseOpenclawMeta(fileData)
}
provider := strings.ToLower(migrationutils.GetEnvStr(envMap, "PROVIDER"))
if provider == "" {
provider = strings.ToLower(cfgMeta.Provider)
}
if provider == "" {
continue
}
provider = migrationutils.NormalizeOpenclawProvider(provider, cfgMeta.BaseURL)
modelName := migrationutils.GetEnvStr(envMap, "MODEL")
if modelName == "" {
modelName = cfgMeta.Model
}
baseURL := migrationutils.GetEnvStr(envMap, "BASE_URL")
if baseURL == "" {
baseURL = cfgMeta.BaseURL
}
apiKey := migrationutils.GetEnvStr(envMap, "API_KEY")
if apiKey == "" {
apiKey = cfgMeta.APIKey
}
token := migrationutils.GetEnvStr(envMap, "OPENCLAW_GATEWAY_TOKEN")
if token == "" {
token = cfgMeta.Token
}
if provider != "ollama" {
if baseURL == "" {
if defaultURL, ok := migrationutils.DefaultBaseURL(provider); ok {
baseURL = defaultURL
}
}
}
if provider == "ollama" && baseURL == "" {
continue
}
var account model.AgentAccount
err := tx.Where("provider = ? AND api_key = ? AND base_url = ?", provider, apiKey, baseURL).First(&account).Error
if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
account = model.AgentAccount{
Provider: provider,
Name: install.Name,
APIKey: apiKey,
BaseURL: baseURL,
Verified: apiKey != "" || provider == "ollama",
}
if err := tx.Create(&account).Error; err != nil {
return err
}
} else {
return err
}
}
agent := model.Agent{
Name: install.Name,
Provider: provider,
Model: modelName,
BaseURL: baseURL,
APIKey: apiKey,
Token: token,
Status: install.Status,
Message: install.Message,
AppInstallID: install.ID,
AccountID: account.ID,
ConfigPath: configPath,
}
if err := tx.Create(&agent).Error; err != nil {
return err
}
}
return nil
},
}
var AddAgentCustomModelFields = &gormigrate.Migration{
ID: "20260224-add-agent-custom-model-fields",
Migrate: func(tx *gorm.DB) error {
if err := tx.AutoMigrate(&model.Agent{}, &model.AgentAccount{}); err != nil {
return err
}
if err := tx.Model(&model.AgentAccount{}).Where("api_type = '' OR api_type IS NULL").Update("api_type", "openai-completions").Error; err != nil {
return err
}
if err := tx.Model(&model.Agent{}).Where("api_type = '' OR api_type IS NULL").Update("api_type", "openai-completions").Error; err != nil {
return err
}
return nil
},
}
@@ -0,0 +1,164 @@
package utils
import (
"encoding/json"
"fmt"
"strings"
providercatalog "github.com/1Panel-dev/1Panel/agent/app/provider"
)
type OpenclawMeta struct {
Provider string
Model string
BaseURL string
APIKey string
Token string
}
func GetEnvStr(envMap map[string]interface{}, key string) string {
if envMap == nil {
return ""
}
if value, ok := envMap[key]; ok {
switch v := value.(type) {
case string:
return strings.TrimSpace(v)
default:
return strings.TrimSpace(fmt.Sprintf("%v", v))
}
}
return ""
}
func ParseOpenclawMeta(fileData []byte) OpenclawMeta {
meta := OpenclawMeta{}
content := map[string]interface{}{}
if err := json.Unmarshal(fileData, &content); err != nil {
return meta
}
meta.Token = getNestedString(content, "gateway", "auth", "token")
meta.Model = getNestedString(content, "agents", "defaults", "model", "primary")
providerKey := ""
if parts := strings.SplitN(meta.Model, "/", 2); len(parts) == 2 {
providerKey = strings.TrimSpace(parts[0])
}
providers := getNestedMap(content, "models", "providers")
providerConfig := map[string]interface{}{}
if providerKey != "" {
if cfg, ok := providers[providerKey].(map[string]interface{}); ok {
providerConfig = cfg
}
}
if len(providerConfig) == 0 && len(providers) == 1 {
for key, value := range providers {
if cfg, ok := value.(map[string]interface{}); ok {
providerKey = key
providerConfig = cfg
break
}
}
}
meta.Provider = providerKey
meta.BaseURL = getString(providerConfig, "baseUrl")
meta.APIKey = getString(providerConfig, "apiKey")
if meta.Model == "" && providerKey != "" {
if modelID := getProviderFirstModelID(providerConfig); modelID != "" {
meta.Model = providerKey + "/" + modelID
}
}
return meta
}
func NormalizeOpenclawProvider(provider, baseURL string) string {
p := strings.ToLower(strings.TrimSpace(provider))
base := strings.ToLower(strings.TrimSpace(baseURL))
switch p {
case "minimax-portal":
return "minimax"
case "moonshot":
if strings.Contains(base, "moonshot.cn") {
return "kimi"
}
return "moonshot"
default:
return p
}
}
func DefaultBaseURL(provider string) (string, bool) {
return providercatalog.DefaultBaseURL(provider)
}
func getNestedMap(data map[string]interface{}, keys ...string) map[string]interface{} {
current := data
for _, key := range keys {
next, ok := current[key].(map[string]interface{})
if !ok {
return map[string]interface{}{}
}
current = next
}
return current
}
func getNestedString(data map[string]interface{}, keys ...string) string {
current := data
for i, key := range keys {
value, ok := current[key]
if !ok {
return ""
}
if i == len(keys)-1 {
switch v := value.(type) {
case string:
return strings.TrimSpace(v)
default:
return strings.TrimSpace(fmt.Sprintf("%v", v))
}
}
next, ok := value.(map[string]interface{})
if !ok {
return ""
}
current = next
}
return ""
}
func getString(data map[string]interface{}, key string) string {
if data == nil {
return ""
}
value, ok := data[key]
if !ok {
return ""
}
switch v := value.(type) {
case string:
return strings.TrimSpace(v)
default:
return strings.TrimSpace(fmt.Sprintf("%v", v))
}
}
func getProviderFirstModelID(data map[string]interface{}) string {
if data == nil {
return ""
}
rawModels, ok := data["models"].([]interface{})
if !ok || len(rawModels) == 0 {
return ""
}
first, ok := rawModels[0].(map[string]interface{})
if !ok {
return ""
}
return getString(first, "id")
}
+15
View File
@@ -39,5 +39,20 @@ func (a *AIToolsRouter) InitRouter(Router *gin.RouterGroup) {
aiToolsRouter.POST("/tensorrt/update", baseApi.UpdateTensorRTLLM)
aiToolsRouter.POST("/tensorrt/delete", baseApi.DeleteTensorRTLLM)
aiToolsRouter.POST("/tensorrt/operate", baseApi.OperateTensorRTLLM)
aiToolsRouter.POST("/agents", baseApi.CreateAgent)
aiToolsRouter.POST("/agents/search", baseApi.PageAgents)
aiToolsRouter.POST("/agents/delete", baseApi.DeleteAgent)
aiToolsRouter.POST("/agents/token/reset", baseApi.ResetAgentToken)
aiToolsRouter.POST("/agents/model/update", baseApi.UpdateAgentModelConfig)
aiToolsRouter.GET("/agents/providers", baseApi.GetAgentProviders)
aiToolsRouter.POST("/agents/accounts", baseApi.CreateAgentAccount)
aiToolsRouter.POST("/agents/accounts/update", baseApi.UpdateAgentAccount)
aiToolsRouter.POST("/agents/accounts/search", baseApi.PageAgentAccounts)
aiToolsRouter.POST("/agents/accounts/verify", baseApi.VerifyAgentAccount)
aiToolsRouter.POST("/agents/accounts/delete", baseApi.DeleteAgentAccount)
aiToolsRouter.POST("/agents/channel/feishu/get", baseApi.GetAgentFeishuConfig)
aiToolsRouter.POST("/agents/channel/feishu/update", baseApi.UpdateAgentFeishuConfig)
aiToolsRouter.POST("/agents/channel/feishu/approve", baseApi.ApproveAgentFeishuPairing)
}
}
+1 -1
View File
@@ -23,7 +23,7 @@ func (a *AppRouter) InitRouter(Router *gin.RouterGroup) {
appRouter.GET("/details/:id", baseApi.GetAppDetailByID)
appRouter.POST("/install", baseApi.InstallApp)
appRouter.GET("/tags", baseApi.GetAppTags)
appRouter.GET("/icon/:appID", baseApi.GetAppIcon)
appRouter.GET("/icon/:key", baseApi.GetAppIcon)
appRouter.POST("/installed/check", baseApi.CheckAppInstalled)
appRouter.POST("/installed/loadport", baseApi.LoadPort)
+1
View File
@@ -46,6 +46,7 @@ func (s *ContainerRouter) InitRouter(Router *gin.RouterGroup) {
baRouter.POST("/compose/search", baseApi.SearchCompose)
baRouter.POST("/compose", baseApi.CreateCompose)
baRouter.POST("/compose/env", baseApi.LoadComposeEnv)
baRouter.POST("/compose/test", baseApi.TestCompose)
baRouter.POST("/compose/operate", baseApi.OperatorCompose)
baRouter.POST("/compose/clean/log", baseApi.CleanComposeLog)
+2
View File
@@ -25,6 +25,8 @@ func (f *FileRouter) InitRouter(Router *gin.RouterGroup) {
fileRouter.POST("/content", baseApi.GetContent)
fileRouter.POST("/preview", baseApi.PreviewContent)
fileRouter.POST("/save", baseApi.SaveContent)
fileRouter.POST("/remarks", baseApi.BatchGetFileRemarks)
fileRouter.POST("/remark", baseApi.SetFileRemark)
fileRouter.POST("/check", baseApi.CheckFile)
fileRouter.POST("/batch/check", baseApi.BatchCheckFiles)
fileRouter.POST("/upload", baseApi.UploadFiles)
+1
View File
@@ -14,6 +14,7 @@ func (f *ProcessRouter) InitRouter(Router *gin.RouterGroup) {
{
processRouter.GET("/ws", baseApi.ProcessWs)
processRouter.POST("/stop", baseApi.StopProcess)
processRouter.POST("/listening", baseApi.GetListeningProcess)
processRouter.GET("/:pid", baseApi.GetProcessInfoByPID)
}
}

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