Compare commits

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

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

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

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

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

* f

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

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

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

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

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

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

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

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

* Add dashboard memo feature

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

* Address code review feedback

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

* fix: small fix

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

---------

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

* feat: enhance app synchronization and icon management

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

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

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

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

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

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

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

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

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

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

* chore: update go mod

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

* style: Format i18n file

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

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

* feat: Refactor HTTP request handling to support custom client

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

* refactor: Simplify HTTP request handling in app synchronization

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

* perf: Avoid duplicate Docker client creation in ImageLoad

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

* refactor: improve mark get logic

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

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

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

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

* feat: Add label for environment variable input in Compose form
2026-01-16 16:40:55 +08:00
238 changed files with 11062 additions and 1653 deletions
+6 -1
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/
+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)
}
+20 -7
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
@@ -210,15 +210,28 @@ func (b *BaseApi) GetAppIcon(c *gin.Context) {
helper.BadRequest(c, err)
return
}
iconBytes, err := appService.GetAppIcon(appKey)
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
+151
View File
@@ -0,0 +1,151 @@
package dto
import "time"
type AgentCreateReq struct {
Name string `json:"name" validate:"required"`
AppVersion string `json:"appVersion" validate:"required"`
WebUIPort int `json:"webUIPort" validate:"required"`
BridgePort int `json:"bridgePort" validate:"required"`
Provider string `json:"provider" validate:"required"`
Model string `json:"model" validate:"required"`
AccountID uint `json:"accountId"`
APIKey string `json:"apiKey"`
BaseURL string `json:"baseURL"`
Token string `json:"token"`
TaskID string `json:"taskID"`
Advanced bool `json:"advanced"`
ContainerName string `json:"containerName"`
AllowPort bool `json:"allowPort"`
SpecifyIP string `json:"specifyIP"`
RestartPolicy string `json:"restartPolicy"`
CpuQuota float64 `json:"cpuQuota"`
MemoryLimit float64 `json:"memoryLimit"`
MemoryUnit string `json:"memoryUnit"`
PullImage bool `json:"pullImage"`
EditCompose bool `json:"editCompose"`
DockerCompose string `json:"dockerCompose"`
}
type AgentItem struct {
ID uint `json:"id"`
Name string `json:"name"`
Provider string `json:"provider"`
ProviderName string `json:"providerName"`
Model string `json:"model"`
BaseURL string `json:"baseUrl"`
APIKey string `json:"apiKey"`
Token string `json:"token"`
Status string `json:"status"`
Message string `json:"message"`
AppInstallID uint `json:"appInstallId"`
AccountID uint `json:"accountId"`
AppVersion string `json:"appVersion"`
Container string `json:"containerName"`
WebUIPort int `json:"webUIPort"`
BridgePort int `json:"bridgePort"`
Path string `json:"path"`
ConfigPath string `json:"configPath"`
Upgradable bool `json:"upgradable"`
CreatedAt time.Time `json:"createdAt"`
}
type AgentDeleteReq struct {
ID uint `json:"id" validate:"required"`
TaskID string `json:"taskID"`
ForceDelete bool `json:"forceDelete"`
}
type AgentTokenResetReq struct {
ID uint `json:"id" validate:"required"`
}
type AgentModelConfigUpdateReq struct {
AgentID uint `json:"agentId" validate:"required"`
AccountID uint `json:"accountId" validate:"required"`
Model string `json:"model" validate:"required"`
}
type AgentAccountCreateReq struct {
Provider string `json:"provider" validate:"required"`
Name string `json:"name" validate:"required"`
APIKey string `json:"apiKey" validate:"required"`
BaseURL string `json:"baseURL"`
Remark string `json:"remark"`
}
type AgentAccountUpdateReq struct {
ID uint `json:"id" validate:"required"`
Name string `json:"name" validate:"required"`
APIKey string `json:"apiKey" validate:"required"`
BaseURL string `json:"baseURL"`
Remark string `json:"remark"`
SyncAgents bool `json:"syncAgents"`
}
type AgentAccountVerifyReq struct {
Provider string `json:"provider" validate:"required"`
APIKey string `json:"apiKey" validate:"required"`
BaseURL string `json:"baseURL"`
}
type AgentAccountDeleteReq struct {
ID uint `json:"id" validate:"required"`
}
type AgentAccountSearch struct {
PageInfo
Provider string `json:"provider"`
Name string `json:"name"`
}
type AgentAccountInfo struct {
ID uint `json:"id"`
Provider string `json:"provider"`
ProviderName string `json:"providerName"`
Name string `json:"name"`
APIKey string `json:"apiKey"`
BaseURL string `json:"baseUrl"`
Verified bool `json:"verified"`
Remark string `json:"remark"`
CreatedAt time.Time `json:"createdAt"`
}
type ProviderModelInfo struct {
ID string `json:"id"`
Name string `json:"name"`
}
type ProviderInfo struct {
Sort uint `json:"-"`
Provider string `json:"provider"`
DisplayName string `json:"displayName"`
BaseURL string `json:"baseUrl"`
Models []ProviderModelInfo `json:"models"`
}
type AgentFeishuConfigReq struct {
AgentID uint `json:"agentId" validate:"required"`
}
type AgentFeishuConfigUpdateReq struct {
AgentID uint `json:"agentId" validate:"required"`
BotName string `json:"botName" validate:"required"`
AppID string `json:"appId" validate:"required"`
AppSecret string `json:"appSecret" validate:"required"`
Enabled bool `json:"enabled"`
DmPolicy string `json:"dmPolicy" validate:"required"`
}
type AgentFeishuPairingApproveReq struct {
AgentID uint `json:"agentId" validate:"required"`
PairingCode string `json:"pairingCode" validate:"required"`
}
type AgentFeishuConfig struct {
Enabled bool `json:"enabled"`
DmPolicy string `json:"dmPolicy"`
BotName string `json:"botName"`
AppID string `json:"appId"`
AppSecret string `json:"appSecret"`
}
+5
View File
@@ -335,3 +335,8 @@ type AgentInfo struct {
NodeName string `json:"nodeName"`
NodeAddr string `json:"nodeAddr"`
}
type AlertWebhookConfig struct {
DisplayName string `json:"displayName"`
Url string `json:"url"`
}
+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
}
+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"`
+16
View File
@@ -0,0 +1,16 @@
package model
type Agent struct {
BaseModel
Name string `json:"name" gorm:"not null;unique"`
Provider string `json:"provider"`
Model string `json:"model"`
BaseURL string `json:"baseUrl"`
APIKey string `json:"apiKey"`
Token string `json:"token"`
Status string `json:"status"`
Message string `json:"message"`
AppInstallID uint `json:"appInstallId"`
AccountID uint `json:"accountId"`
ConfigPath string `json:"configPath"`
}
+15
View File
@@ -0,0 +1,15 @@
package model
type AgentAccount struct {
BaseModel
Provider string `json:"provider"`
Name string `json:"name"`
APIKey string `json:"apiKey"`
BaseURL string `json:"baseUrl"`
Verified bool `json:"verified"`
Remark string `json:"remark"`
}
func (AgentAccount) TableName() string {
return "agent_provider_accounts"
}
+1 -1
View File
@@ -11,7 +11,7 @@ type Alert struct {
Status string `gorm:"type:varchar(64);not null" json:"status"`
Method string `gorm:"type:varchar(64);not null" json:"method"`
SendCount uint `gorm:"type:integer" json:"sendCount"`
AdvancedParams string `gorm:"type:longText" json:"advancedParam"`
AdvancedParams string `gorm:"type:longText" json:"advancedParams"`
}
type AlertTask struct {
+1
View File
@@ -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 {
+201
View File
@@ -0,0 +1,201 @@
package provider
import "strings"
type Model struct {
ID string
Name string
}
type Meta struct {
Key string
DisplayName string
Sort uint
DefaultBaseURL string
EnvKey string
Models []Model
Enabled bool
}
var catalog = map[string]Meta{
"ollama": {
Key: "ollama",
DisplayName: "Ollama",
Sort: 1,
Enabled: true,
},
"deepseek": {
Key: "deepseek",
DisplayName: "DeepSeek",
Sort: 2,
DefaultBaseURL: "https://api.deepseek.com/v1",
EnvKey: "DEEPSEEK_API_KEY",
Enabled: true,
Models: []Model{
{ID: "deepseek/deepseek-chat", Name: "DeepSeek Chat"},
{ID: "deepseek/deepseek-reasoner", Name: "DeepSeek Reasoner"},
{ID: "deepseek/deepseek-r1:1.5b", Name: "DeepSeek R1 1.5B"},
},
},
"openai": {
Key: "openai",
DisplayName: "OpenAI",
Sort: 3,
DefaultBaseURL: "https://api.openai.com/v1",
EnvKey: "OPENAI_API_KEY",
Enabled: true,
Models: []Model{
{ID: "openai/codex-mini-latest", Name: "Codex Mini"},
{ID: "openai/gpt-4.1", Name: "GPT-4.1"},
{ID: "openai/gpt-4o", Name: "GPT-4o"},
{ID: "openai/gpt-4o-mini", Name: "GPT-4o Mini"},
{ID: "openai/gpt-5", Name: "GPT-5"},
{ID: "openai/gpt-5-mini", Name: "GPT-5 Mini"},
},
},
"anthropic": {
Key: "anthropic",
DisplayName: "Anthropic",
Sort: 4,
DefaultBaseURL: "https://api.anthropic.com",
EnvKey: "ANTHROPIC_API_KEY",
Enabled: true,
Models: []Model{
{ID: "anthropic/claude-3-haiku-20240307", Name: "Claude 3 Haiku"},
{ID: "anthropic/claude-3-5-haiku-latest", Name: "Claude 3.5 Haiku"},
{ID: "anthropic/claude-3-5-sonnet-20241022", Name: "Claude 3.5 Sonnet"},
{ID: "anthropic/claude-3-7-sonnet-20250219", Name: "Claude 3.7 Sonnet"},
{ID: "anthropic/claude-opus-4-1", Name: "Claude Opus 4.1"},
},
},
"gemini": {
Key: "gemini",
DisplayName: "Gemini",
Sort: 5,
DefaultBaseURL: "https://generativelanguage.googleapis.com",
EnvKey: "GEMINI_API_KEY",
Enabled: true,
Models: []Model{
{ID: "google/gemini-1.5-flash", Name: "Gemini 1.5 Flash"},
{ID: "google/gemini-1.5-pro", Name: "Gemini 1.5 Pro"},
{ID: "google/gemini-2.0-flash", Name: "Gemini 2.0 Flash"},
{ID: "google/gemini-2.5-flash", Name: "Gemini 2.5 Flash"},
{ID: "google/gemini-2.5-pro", Name: "Gemini 2.5 Pro"},
{ID: "google/gemini-3-flash-preview", Name: "Gemini 3 Flash Preview"},
},
},
"minimax": {
Key: "minimax",
DisplayName: "MiniMax (CN)",
Sort: 6,
DefaultBaseURL: "https://api.minimaxi.com/anthropic",
EnvKey: "MINIMAX_API_KEY",
Enabled: true,
Models: []Model{
{ID: "minimax/MiniMax-M2.1", Name: "MiniMax M2.1"},
{ID: "minimax/MiniMax-M2.1-lightning", Name: "MiniMax M2.1 Lightning"},
},
},
"moonshot": {
Key: "moonshot",
DisplayName: "Moonshot (Global)",
Sort: 7,
DefaultBaseURL: "https://api.moonshot.ai/v1",
EnvKey: "MOONSHOT_API_KEY",
Enabled: true,
Models: []Model{
{ID: "moonshot/kimi-k2.5", Name: "Kimi K2.5"},
{ID: "moonshot/kimi-k2-0905-preview", Name: "Kimi K2 0905 Preview"},
{ID: "moonshot/kimi-k2-thinking", Name: "Kimi K2 Thinking"},
},
},
"kimi": {
Key: "kimi",
DisplayName: "Kimi (CN)",
Sort: 8,
DefaultBaseURL: "https://api.moonshot.cn/v1",
EnvKey: "KIMI_API_KEY",
Enabled: true,
Models: []Model{
{ID: "kimi/kimi-k2.5", Name: "Kimi K2.5"},
{ID: "kimi/kimi-k2-0905-preview", Name: "Kimi K2 0905 Preview"},
{ID: "kimi/kimi-k2-thinking", Name: "Kimi K2 Thinking"},
},
},
"kimi-coding": {
Key: "kimi-coding",
DisplayName: "Kimi Coding",
Sort: 9,
DefaultBaseURL: "https://api.moonshot.cn/anthropic/v1",
EnvKey: "KIMI_API_KEY",
Enabled: true,
Models: []Model{
{ID: "kimi-coding/k2p5", Name: "Kimi K2.5"},
},
},
"qwen": {
Key: "qwen",
DisplayName: "Qwen",
Sort: 10,
DefaultBaseURL: "https://dashscope.aliyuncs.com/compatible-mode/v1",
EnvKey: "QWEN_API_KEY",
Enabled: false,
},
}
func Get(key string) (Meta, bool) {
meta, ok := catalog[strings.ToLower(strings.TrimSpace(key))]
if !ok {
return Meta{}, false
}
return cloneMeta(meta), true
}
func All() map[string]Meta {
result := make(map[string]Meta, len(catalog))
for key, meta := range catalog {
result[key] = cloneMeta(meta)
}
return result
}
func IsEnabled(key string) bool {
meta, ok := catalog[strings.ToLower(strings.TrimSpace(key))]
return ok && meta.Enabled
}
func DefaultBaseURL(key string) (string, bool) {
meta, ok := catalog[strings.ToLower(strings.TrimSpace(key))]
if !ok || strings.TrimSpace(meta.DefaultBaseURL) == "" {
return "", false
}
return meta.DefaultBaseURL, true
}
func EnvKey(key string) string {
meta, ok := catalog[strings.ToLower(strings.TrimSpace(key))]
if !ok {
return ""
}
return meta.EnvKey
}
func DisplayName(key string) string {
meta, ok := catalog[strings.ToLower(strings.TrimSpace(key))]
if !ok {
return key
}
if strings.TrimSpace(meta.DisplayName) == "" {
return key
}
return meta.DisplayName
}
func cloneMeta(meta Meta) Meta {
clone := meta
if len(meta.Models) > 0 {
clone.Models = make([]Model, len(meta.Models))
copy(clone.Models, meta.Models)
}
return clone
}
+75
View File
@@ -0,0 +1,75 @@
package repo
import (
"context"
"github.com/1Panel-dev/1Panel/agent/app/model"
)
type AgentRepo struct{}
type IAgentRepo interface {
Page(page, size int, opts ...DBOption) (int64, []model.Agent, error)
GetFirst(opts ...DBOption) (*model.Agent, error)
Create(agent *model.Agent) error
Save(agent *model.Agent) error
DeleteByID(id uint) error
DeleteByAppInstallID(appInstallID uint) error
DeleteByAppInstallIDWithCtx(ctx context.Context, appInstallID uint) error
List(opts ...DBOption) ([]model.Agent, error)
}
func NewIAgentRepo() IAgentRepo {
return &AgentRepo{}
}
func (a AgentRepo) Page(page, size int, opts ...DBOption) (int64, []model.Agent, error) {
var agents []model.Agent
db := getDb(opts...).Model(&model.Agent{})
count := int64(0)
db = db.Count(&count)
err := db.Limit(size).Offset(size * (page - 1)).Find(&agents).Error
return count, agents, err
}
func (a AgentRepo) GetFirst(opts ...DBOption) (*model.Agent, error) {
var agent model.Agent
if err := getDb(opts...).First(&agent).Error; err != nil {
return nil, err
}
return &agent, nil
}
func (a AgentRepo) Create(agent *model.Agent) error {
return getDb().Create(agent).Error
}
func (a AgentRepo) Save(agent *model.Agent) error {
return getDb().Save(agent).Error
}
func (a AgentRepo) DeleteByID(id uint) error {
return getDb().Delete(&model.Agent{}, id).Error
}
func (a AgentRepo) DeleteByAppInstallID(appInstallID uint) error {
if appInstallID == 0 {
return nil
}
return getDb().Where("app_install_id = ?", appInstallID).Delete(&model.Agent{}).Error
}
func (a AgentRepo) DeleteByAppInstallIDWithCtx(ctx context.Context, appInstallID uint) error {
if appInstallID == 0 {
return nil
}
return getTx(ctx).Where("app_install_id = ?", appInstallID).Delete(&model.Agent{}).Error
}
func (a AgentRepo) List(opts ...DBOption) ([]model.Agent, error) {
var agents []model.Agent
if err := getDb(opts...).Find(&agents).Error; err != nil {
return nil, err
}
return agents, nil
}
+55
View File
@@ -0,0 +1,55 @@
package repo
import "github.com/1Panel-dev/1Panel/agent/app/model"
type AgentAccountRepo struct{}
type IAgentAccountRepo interface {
Page(page, size int, opts ...DBOption) (int64, []model.AgentAccount, error)
GetFirst(opts ...DBOption) (*model.AgentAccount, error)
Create(account *model.AgentAccount) error
Save(account *model.AgentAccount) error
DeleteByID(id uint) error
List(opts ...DBOption) ([]model.AgentAccount, error)
}
func NewIAgentAccountRepo() IAgentAccountRepo {
return &AgentAccountRepo{}
}
func (a AgentAccountRepo) Page(page, size int, opts ...DBOption) (int64, []model.AgentAccount, error) {
var accounts []model.AgentAccount
db := getDb(opts...).Model(&model.AgentAccount{})
count := int64(0)
db = db.Count(&count)
err := db.Limit(size).Offset(size * (page - 1)).Find(&accounts).Error
return count, accounts, err
}
func (a AgentAccountRepo) GetFirst(opts ...DBOption) (*model.AgentAccount, error) {
var account model.AgentAccount
if err := getDb(opts...).First(&account).Error; err != nil {
return nil, err
}
return &account, nil
}
func (a AgentAccountRepo) Create(account *model.AgentAccount) error {
return getDb().Create(account).Error
}
func (a AgentAccountRepo) Save(account *model.AgentAccount) error {
return getDb().Save(account).Error
}
func (a AgentAccountRepo) DeleteByID(id uint) error {
return getDb().Delete(&model.AgentAccount{}, id).Error
}
func (a AgentAccountRepo) List(opts ...DBOption) ([]model.AgentAccount, error) {
var accounts []model.AgentAccount
if err := getDb(opts...).Find(&accounts).Error; err != nil {
return nil, err
}
return accounts, nil
}
+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 {
+87 -23
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) {
@@ -586,8 +625,6 @@ func sendAlerts(alert dto.AlertDTO, alertType, quota, quotaType string, params [
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 {
@@ -610,7 +647,25 @@ func sendAlerts(alert dto.AlertDTO, alertType, quota, quotaType string, params [
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:
}
}
}
@@ -650,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)
@@ -665,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)
@@ -829,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)
@@ -861,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) {
+57 -6
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) {
@@ -86,9 +93,34 @@ func (s *AlertSender) sendEmail(quota string, params []dto.Param) {
transport := xpack.LoadRequestTransport()
agentInfo, _ := xpack.GetAgentInfo()
_ = alertUtil.CreateEmailAlertLog(create, s.alert, params, transport, agentInfo)
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) {
@@ -113,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) {
@@ -138,7 +169,27 @@ func (s *AlertSender) sendResourceEmail(quota string, params []dto.Param) {
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) {
+92 -255
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(key string) ([]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 {
@@ -839,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()
@@ -896,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(key string) ([]byte, error) {
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 := ""
+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
}
+15 -5
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
@@ -952,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)
}
@@ -1048,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")
}
@@ -1076,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)
+1 -1
View File
@@ -765,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
}
+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
}
+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 {
+4 -25
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
@@ -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 {
+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 {
+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
+14 -1
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.'
@@ -527,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"
+13
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 }}'
+14 -1
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: 'バックアップ アカウントはスケジュールされたタスクで使用されているため、削除できません。'
@@ -526,4 +539,4 @@ PartitionDiskErr: "パーティションに失敗しました、{{ .err }}"
FormatDiskErr: "ディスクのフォーマットに失敗しました、{{ .err }}"
MountDiskErr: "ディスクのマウントに失敗しました、{{ .err }}"
UnMountDiskErr: "ディスクのアンマウントに失敗しました、{{ .err }}"
XfsNotFound: "xfs ファイルシステムが検出されませんでした、最初に xfsprogs をインストールしてください"
XfsNotFound: "xfs ファイルシステムが検出されませんでした、最初に xfsprogs をインストールしてください"
+14 -1
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: '백업 계정이 예약된 작업에 사용되었으므로 삭제할 수 없습니다.'
@@ -527,4 +540,4 @@ PartitionDiskErr: "파티션 분할에 실패했습니다, {{ .err }}"
FormatDiskErr: "디스크 포맷에 실패했습니다, {{ .err }}"
MountDiskErr: "디스크 마운트에 실패했습니다, {{ .err }}"
UnMountDiskErr: "디스크 마운트 해제에 실패했습니다, {{ .err }}"
XfsNotFound: "xfs 파일 시스템이 감지되지 않았습니다, 먼저 xfsprogs 를 설치하세요"
XfsNotFound: "xfs 파일 시스템이 감지되지 않았습니다, 먼저 xfsprogs 를 설치하세요"
+14 -1
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.'
@@ -527,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"
+14 -1
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.'
@@ -527,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"
+14 -1
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: 'Учетная запись резервного копирования использовалась в запланированной задаче и не может быть удалена.'
@@ -527,4 +540,4 @@ PartitionDiskErr: "Не удалось разделить, {{ .err }}"
FormatDiskErr: "Не удалось отформатировать диск, {{ .err }}"
MountDiskErr: "Не удалось подключить диск, {{ .err }}"
UnMountDiskErr: "Не удалось отключить диск, {{ .err }}"
XfsNotFound: "Файловая система xfs не обнаружена, сначала установите xfsprogs"
XfsNotFound: "Файловая система xfs не обнаружена, сначала установите xfsprogs"
+13
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.'
+19 -6
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: '代號已存在'
@@ -492,11 +505,11 @@ AppBackupFileIncomplete: '備份檔案不完整缺少 app.json 或 app.tar.gz
AppAttributesNotMatch: '應用程式類型或名稱不一致'
#alert
ErrAlert: '警告訊息格式錯誤,請檢查後重試!'
ErrAlertPush: '警告訊息推送錯誤,請檢查後重試!'
ErrAlertSave: '警告訊息儲存錯誤,請檢查後重試!'
ErrAlertSync: '警告訊息同步錯誤,請檢查後重試!'
ErrAlertRemote: '警告訊息遠端錯誤,請檢查後重試!'
ErrAlert: '警告訊息格式錯誤,請檢查後重試!!'
ErrAlertPush: '警告訊息推送錯誤,請檢查後重試!!'
ErrAlertSave: '警告訊息儲存錯誤,請檢查後重試!!'
ErrAlertSync: '警告訊息同步錯誤,請檢查後重試!!'
ErrAlertRemote: '警告訊息遠端錯誤,請檢查後重試!!'
#task - runtime
ErrInstallExtension: "已有安裝任務正在進行,請等待任務結束"
+21 -8
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: "代号已存在"
@@ -263,7 +276,7 @@ WafIpGroupNotFound: "WAF IP组未找到,请先到【高级功能-WAF-黑白名
#toolbox
ErrNotExistUser: "当前用户不存在,请修改后重试!"
ErrBanAction: "设置失败,当前 {{ .name }} 服务不可用,请检查后重试!"
ErrBanAction: "设置失败,当前 {{ .name }} 服务不可用,请检查后重试!!"
ErrClamdscanNotFound: "未检测到 clamdscan 命令,请参考文档安装!"
TaskSwapSet: "设置 Swap"
SetSwap: "设置 Swap {{ .path }} - {{ .size }}"
@@ -494,11 +507,11 @@ AppBackupFileIncomplete: "备份文件不完整 缺少 app.json 或者 app.tar.
AppAttributesNotMatch: "应用类型或者名称不一致"
#alert
ErrAlert: "告警信息格式错误,请检查后重试!"
ErrAlertPush: "告警信息推送错误,请检查后重试!"
ErrAlertSave: "告警信息保存错误,请检查后重试!"
ErrAlertSync: "告警信息同步错误,请检查后重试!"
ErrAlertRemote: "告警信息远端错误,请检查后重试!"
ErrAlert: "告警信息格式错误,请检查后重试!!"
ErrAlertPush: "告警信息推送错误,请检查后重试!!"
ErrAlertSave: "告警信息保存错误,请检查后重试!!"
ErrAlertSync: "告警信息同步错误,请检查后重试!!"
ErrAlertRemote: "告警信息远端错误,请检查后重试!!"
#task - runtime
ErrInstallExtension: "已有安装任务正在进行,请等待任务结束"
@@ -528,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"))
+3
View File
@@ -62,6 +62,9 @@ func InitAgentDB() {
migrations.InitPingStatus,
migrations.UpdateApp,
migrations.AddCronjobArgs,
migrations.AddWebsiteAcmeAccountColumn,
migrations.AddAgentTables,
migrations.MigrateOpenclawAgents,
})
if err := m.Migrate(); err != nil {
global.LOG.Error(err)
+127
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"
@@ -814,3 +816,128 @@ var AddCronjobArgs = &gormigrate.Migration{
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
},
}
@@ -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
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)
}
}
+1
View File
@@ -14,6 +14,7 @@ func (a *WebsiteSSLRouter) InitRouter(Router *gin.RouterGroup) {
baseApi := v2.ApiGroupApp.BaseApi
{
groupRouter.POST("/search", baseApi.PageWebsiteSSL)
groupRouter.POST("/list", baseApi.ListWebsiteSSL)
groupRouter.POST("", baseApi.CreateWebsiteSSL)
groupRouter.POST("/resolve", baseApi.GetDNSResolve)
groupRouter.POST("/del", baseApi.DeleteWebsiteSSL)
+17 -11
View File
@@ -49,19 +49,24 @@ func CreateEmailAlertLog(create dto.AlertLogCreate, alert dto.AlertDTO, params [
return err
}
create.Method = constant.Email
emailConfig, err := alertRepo.GetConfig(alertRepo.WithByType(constant.EmailConfig))
if err != nil {
return err
}
var emailInfo dto.AlertEmailConfig
err = json.Unmarshal([]byte(emailConfig.Config), &emailInfo)
if err != nil {
return err
}
if emailInfo.Host == "" {
create.Message = "email config is required"
create.Status = constant.AlertError
return SaveAlertLog(create, &alertLog)
}
if !global.IsMaster && cfg.IsOffline == constant.StatusEnable {
create.Status = constant.AlertPushing
return SaveAlertLog(create, &alertLog)
} else {
emailConfig, err := alertRepo.GetConfig(alertRepo.WithByType(constant.EmailConfig))
if err != nil {
return err
}
var emailInfo dto.AlertEmailConfig
err = json.Unmarshal([]byte(emailConfig.Config), &emailInfo)
if err != nil {
return err
}
username := emailInfo.UserName
if username == "" {
username = emailInfo.Sender
@@ -76,7 +81,7 @@ func CreateEmailAlertLog(create dto.AlertLogCreate, alert dto.AlertDTO, params [
Encryption: emailInfo.Encryption,
Recipient: emailInfo.Recipient,
}
content := GetEmailContent(alert.Type, params, agentInfo)
content := GetSendContent(alert.Type, params, agentInfo)
if content == "" {
content = i18n.GetMsgWithMap("CommonAlert", map[string]interface{}{"msg": alert.Title})
}
@@ -122,6 +127,7 @@ func CreateNewAlertTask(quota, alertType, quotaType, method string) {
if err != nil {
global.LOG.Errorf("error creating alert tasks, err: %v", err)
}
global.LOG.Infof("%s alert %s push completed", alertType, method)
}
func ProcessAlertDetail(alert dto.AlertDTO, project string, params []dto.Param, method string) string {
@@ -300,7 +306,7 @@ func isWithinTimeRange(savedTimeString string) bool {
return now.After(skipTime) && now.Before(endSkipTime)
}
func GetEmailContent(alertType string, params []dto.Param, agentInfo *dto.AgentInfo) string {
func GetSendContent(alertType string, params []dto.Param, agentInfo *dto.AgentInfo) string {
switch GetCronJobType(alertType) {
case "ssl":
return i18n.GetMsgWithMap("SSLAlert", map[string]interface{}{"num": getValueByIndex(params, "1"), "day": getValueByIndex(params, "2"), "node": getNodeName(agentInfo), "ip": getNodeIp(agentInfo)})
+25 -4
View File
@@ -42,9 +42,12 @@ func PushAlert(pushAlert dto.PushAlert) error {
AlertId: alert.ID,
Count: todayCount + 1,
}
_ = xpack.CreateTaskScanSMSAlertLog(alert, alert.Type, create, pushAlert, constant.SMS)
err = xpack.CreateTaskScanSMSAlertLog(alert, alert.Type, create, pushAlert, constant.SMS)
if err != nil {
global.LOG.Errorf("%s alert sms push failed: %v", alert.Type, err)
continue
}
alertUtil.CreateNewAlertTask(strconv.Itoa(int(pushAlert.EntryID)), alertUtil.GetCronJobType(alert.Type), strconv.Itoa(int(pushAlert.EntryID)), constant.SMS)
global.LOG.Infof("%s %s alert push successful", alert.Type, constant.SMS)
case constant.Email:
todayCount, _, err := alertRepo.LoadTaskCount(alertUtil.GetCronJobType(alert.Type), strconv.Itoa(int(pushAlert.EntryID)), constant.Email)
if err != nil || alert.SendCount <= todayCount {
@@ -59,10 +62,28 @@ func PushAlert(pushAlert dto.PushAlert) error {
agentInfo, _ := xpack.GetAgentInfo()
err = alertUtil.CreateTaskScanEmailAlertLog(alert, create, pushAlert, constant.Email, transport, agentInfo)
if err != nil {
return err
global.LOG.Errorf("%s alert email push failed: %v", alert.Type, err)
continue
}
alertUtil.CreateNewAlertTask(strconv.Itoa(int(pushAlert.EntryID)), alertUtil.GetCronJobType(alert.Type), strconv.Itoa(int(pushAlert.EntryID)), constant.Email)
global.LOG.Infof("%s %s alert push successful", alert.Type, constant.Email)
case constant.WeCom, constant.DingTalk, constant.FeiShu:
todayCount, _, err := alertRepo.LoadTaskCount(alertUtil.GetCronJobType(alert.Type), strconv.Itoa(int(pushAlert.EntryID)), m)
if err != nil || alert.SendCount <= todayCount {
continue
}
var create = dto.AlertLogCreate{
Type: alertUtil.GetCronJobType(alert.Type),
AlertId: alert.ID,
Count: todayCount + 1,
}
transport := xpack.LoadRequestTransport()
agentInfo, _ := xpack.GetAgentInfo()
err = xpack.CreateTaskScanWebhookAlertLog(alert, alert.Type, create, pushAlert, m, transport, agentInfo)
if err != nil {
global.LOG.Errorf("%s alert %s webhook push failed: %v", alert.Type, m, err)
continue
}
alertUtil.CreateNewAlertTask(strconv.Itoa(int(pushAlert.EntryID)), alertUtil.GetCronJobType(alert.Type), strconv.Itoa(int(pushAlert.EntryID)), m)
default:
}
}
+93
View File
@@ -0,0 +1,93 @@
package appicon
import (
"fmt"
"net/url"
"os"
"path"
"path/filepath"
"strings"
"github.com/1Panel-dev/1Panel/agent/global"
)
const IconPrefix = "app_"
func IsIconFile(icon string) bool {
return strings.HasPrefix(icon, IconPrefix)
}
func ParseIconField(icon string) (fileName, etag string) {
if !IsIconFile(icon) {
return "", ""
}
parts := strings.SplitN(icon, "?", 2)
fileName = parts[0]
if len(parts) == 2 {
values, err := url.ParseQuery(parts[1])
if err == nil {
etag = values.Get("etag")
}
}
return
}
func BuildIconField(fileName, etag string) string {
if etag == "" {
return fileName
}
return fmt.Sprintf("%s?etag=%s", fileName, url.QueryEscape(etag))
}
func GetIconFilePath(fileName string) string {
return path.Join(global.Dir.IconCacheDir, fileName)
}
func BuildIconFileName(appKey, ext string) string {
return fmt.Sprintf("%s%s.%s", IconPrefix, appKey, ext)
}
const ContentTypePNG = "image/png"
func WriteIconFile(appKey string, data []byte) (fileName string, err error) {
fileName = BuildIconFileName(appKey, "png")
filePath := GetIconFilePath(fileName)
_ = CleanOldIconFiles(appKey)
err = os.WriteFile(filePath, data, 0644)
return
}
func CleanOldIconFiles(appKey string) error {
pattern := path.Join(global.Dir.IconCacheDir, fmt.Sprintf("%s%s.*", IconPrefix, appKey))
matches, err := filepath.Glob(pattern)
if err != nil {
return err
}
keepFileName := BuildIconFileName(appKey, "png")
for _, match := range matches {
baseName := filepath.Base(match)
if baseName == keepFileName {
continue
}
_ = os.Remove(match)
}
return nil
}
func ReadIconFile(fileName string) ([]byte, error) {
filePath := GetIconFilePath(fileName)
return os.ReadFile(filePath)
}
func IconFileExists(fileName string) bool {
filePath := GetIconFilePath(fileName)
_, err := os.Stat(filePath)
return err == nil
}
func GetETagFromIconField(icon string) string {
_, etag := ParseIconField(icon)
return etag
}
+4 -4
View File
@@ -63,15 +63,15 @@ func AnalysisFromLog(pathItem string, record *model.ClamRecord) {
}
}
func StopAllClamJob(withCheck bool, clamRepo repo.IClamRepo) bool {
func CheckWithStopAll(withCheck bool, clamRepo repo.IClamRepo) bool {
if withCheck {
isExist, _ := controller.CheckExist("clam")
if !isExist {
return false
}
isActive, _ := controller.CheckActive("clam")
if !isActive {
return false
if isActive {
return true
}
}
clams, _ := clamRepo.List(repo.WithByStatus(constant.StatusEnable))
@@ -79,5 +79,5 @@ func StopAllClamJob(withCheck bool, clamRepo repo.IClamRepo) bool {
global.Cron.Remove(cron.EntryID(clams[i].EntryID))
_ = clamRepo.Update(clams[i].ID, map[string]interface{}{"status": constant.StatusDisable, "entry_id": 0})
}
return true
return false
}
-4
View File
@@ -5,7 +5,6 @@ import (
"log"
"os"
"path"
"strings"
"time"
"github.com/1Panel-dev/1Panel/agent/global"
@@ -68,9 +67,6 @@ func GetDBWithPath(dbPath string) (*gorm.DB, error) {
if err != nil {
return nil, err
}
if strings.HasSuffix(dbPath, "core.db") || strings.HasSuffix(dbPath, "agent.db") {
initializeTxWatch(db)
}
sqlDB, dbError := db.DB()
if dbError != nil {
return nil, dbError
-67
View File
@@ -1,67 +0,0 @@
package common
import (
"context"
"fmt"
"runtime"
"strings"
"time"
"github.com/1Panel-dev/1Panel/agent/global"
"gorm.io/gorm"
)
type contextKey string
func initializeTxWatch(db *gorm.DB) {
_ = db.Callback().Create().Before("gorm:begin_transaction").Register(
"my_plugin:before_begin",
func(db *gorm.DB) {
var caller []string
for i := 0; ; i++ {
pc, file, line, ok := runtime.Caller(i)
if !ok {
break
}
funcName := runtime.FuncForPC(pc).Name()
if !strings.HasPrefix(funcName, "github.com/1Panel-dev/1Panel") {
continue
}
fileParts := strings.Split(file, "/")
fileName := fileParts[len(fileParts)-1]
caller = append(caller, fmt.Sprintf("%s/%s:%d", funcName, fileName, line))
}
txID := generateTransactionID()
db.Statement.Context = context.WithValue(
db.Statement.Context,
contextKey("tx_id"), txID,
)
db.Statement.Context = context.WithValue(
db.Statement.Context,
contextKey("tx_start"), time.Now(),
)
global.LOG.Debugf("[%s] tx start \n%s", txID, strings.Join(caller, "\n"))
},
)
_ = db.Callback().Create().After("gorm:commit_or_rollback_transaction").Register(
"my_plugin:after_commit_or_rollback",
func(db *gorm.DB) {
ctx := db.Statement.Context
txID, _ := ctx.Value(contextKey("tx_id")).(string)
startTime, _ := ctx.Value(contextKey("tx_start")).(time.Time)
if txID != "" {
duration := time.Since(startTime)
if db.Error != nil {
global.LOG.Debugf("[%s] tx rollback! time: %v, err: %v", txID, duration, db.Error)
} else {
global.LOG.Debugf("[%s] tx commit! time: %v", txID, duration)
}
}
},
)
}
func generateTransactionID() string {
return fmt.Sprintf("tx_%d", time.Now().UnixNano())
}
+36 -9
View File
@@ -36,7 +36,14 @@ func Up(filePath string) (string, error) {
return cmd.NewCommandMgr(cmd.WithTimeout(20*time.Minute)).RunWithStdoutBashCf("%s %s up -d", global.CONF.DockerConfig.Command, loadFiles(filePath))
}
func UpWithTask(filePath string, task *task.Task) error {
func UpWithTask(filePath string, task *task.Task, forcePull bool) error {
if err := pullComposeImages(filePath, forcePull, task); err != nil {
return err
}
return cmd.NewCommandMgr(cmd.WithTask(*task)).RunBashCf("%s %s up -d", global.CONF.DockerConfig.Command, loadFiles(filePath))
}
func pullComposeImages(filePath string, forcePull bool, task *task.Task) error {
content, err := os.ReadFile(filePath)
if err != nil {
return err
@@ -54,11 +61,28 @@ func UpWithTask(filePath string, task *task.Task) error {
if err != nil {
return err
}
errMsg := ""
for _, image := range images {
task.Log(i18n.GetWithName("PullImageStart", image))
if err = dockerCLi.PullImageWithProcess(task, image); err != nil {
errOur := err.Error()
if !forcePull {
if exist, _ := dockerCLi.ImageExists(image); exist {
if task != nil {
task.Log(i18n.GetMsgByKey("UseExistImage"))
}
continue
}
}
if task != nil {
task.Log(i18n.GetWithName("PullImageStart", image))
}
pullErr := error(nil)
if task != nil {
pullErr = dockerCLi.PullImageWithProcess(task, image)
} else {
pullErr = docker.PullImage(image)
}
if pullErr != nil {
errMsg := ""
errOur := pullErr.Error()
if errOur != "" {
if strings.Contains(errOur, "no such host") {
errMsg = i18n.GetMsgByKey("ErrNoSuchHost") + ":"
@@ -69,18 +93,21 @@ func UpWithTask(filePath string, task *task.Task) error {
}
message := errMsg + errOur
installErr := errors.New(message)
task.LogFailedWithErr(i18n.GetMsgByKey("PullImage"), installErr)
if task != nil {
task.LogFailedWithErr(i18n.GetMsgByKey("PullImage"), installErr)
}
if exist, _ := dockerCLi.ImageExists(image); !exist {
return installErr
} else {
}
if task != nil {
task.Log(i18n.GetMsgByKey("UseExistImage"))
}
} else {
} else if task != nil {
task.Log(i18n.GetMsgByKey("PullImageSuccess"))
}
}
return cmd.NewCommandMgr(cmd.WithTask(*task)).RunBashCf("%s %s up -d", global.CONF.DockerConfig.Command, loadFiles(filePath))
return nil
}
func Down(filePath string) (string, error) {
+79 -3
View File
@@ -33,6 +33,11 @@ import (
"github.com/spf13/afero"
)
const (
cmdDefaultTimeout = 10 * time.Second
cmdRecursiveTimeout = 5 * time.Minute
)
var protectedPaths = []string{
"/",
"/bin",
@@ -222,7 +227,11 @@ func (f FileOp) ChownR(dst string, uid string, gid string, sub bool) error {
if sub {
cmdStr = fmt.Sprintf(`chown -R %s:%s "%s"`, uid, gid, dst)
}
cmdMgr := cmd.NewCommandMgr(cmd.WithTimeout(10 * time.Second))
timeout := cmdDefaultTimeout
if sub {
timeout = cmdRecursiveTimeout
}
cmdMgr := cmd.NewCommandMgr(cmd.WithTimeout(timeout))
if err := cmdMgr.RunBashC(cmdStr); err != nil {
return err
}
@@ -234,7 +243,11 @@ func (f FileOp) ChmodR(dst string, mode int64, sub bool) error {
if sub {
cmdStr = fmt.Sprintf(`%s chmod -R %v "%s"`, cmd.SudoHandleCmd(), fmt.Sprintf("%04o", mode), dst)
}
cmdMgr := cmd.NewCommandMgr(cmd.WithTimeout(10 * time.Second))
timeout := cmdDefaultTimeout
if sub {
timeout = cmdRecursiveTimeout
}
cmdMgr := cmd.NewCommandMgr(cmd.WithTimeout(timeout))
if err := cmdMgr.RunBashC(cmdStr); err != nil {
return err
}
@@ -246,7 +259,70 @@ func (f FileOp) ChmodRWithMode(dst string, mode fs.FileMode, sub bool) error {
if sub {
cmdStr = fmt.Sprintf(`%s chmod -R %v "%s"`, cmd.SudoHandleCmd(), fmt.Sprintf("%o", mode.Perm()), dst)
}
cmdMgr := cmd.NewCommandMgr(cmd.WithTimeout(10 * time.Second))
timeout := cmdDefaultTimeout
if sub {
timeout = cmdRecursiveTimeout
}
cmdMgr := cmd.NewCommandMgr(cmd.WithTimeout(timeout))
if err := cmdMgr.RunBashC(cmdStr); err != nil {
return err
}
return nil
}
func (f FileOp) ChownRPaths(paths []string, uid string, gid string, sub bool) error {
if len(paths) == 0 {
return nil
}
if len(paths) == 1 {
return f.ChownR(paths[0], uid, gid, sub)
}
quoted := make([]string, len(paths))
for i, p := range paths {
quoted[i] = fmt.Sprintf(`"%s"`, p)
}
args := strings.Join(quoted, " ")
var cmdStr string
if sub {
cmdStr = fmt.Sprintf(`chown -R %s:%s %s`, uid, gid, args)
} else {
cmdStr = fmt.Sprintf(`%s chown %s:%s %s`, cmd.SudoHandleCmd(), uid, gid, args)
}
timeout := cmdDefaultTimeout
if sub {
timeout = cmdRecursiveTimeout
}
cmdMgr := cmd.NewCommandMgr(cmd.WithTimeout(timeout))
if err := cmdMgr.RunBashC(cmdStr); err != nil {
return err
}
return nil
}
func (f FileOp) ChmodRPaths(paths []string, mode int64, sub bool) error {
if len(paths) == 0 {
return nil
}
if len(paths) == 1 {
return f.ChmodR(paths[0], mode, sub)
}
quoted := make([]string, len(paths))
for i, p := range paths {
quoted[i] = fmt.Sprintf(`"%s"`, p)
}
args := strings.Join(quoted, " ")
modeStr := fmt.Sprintf("%04o", mode)
var cmdStr string
if sub {
cmdStr = fmt.Sprintf(`%s chmod -R %s %s`, cmd.SudoHandleCmd(), modeStr, args)
} else {
cmdStr = fmt.Sprintf(`%s chmod %s %s`, cmd.SudoHandleCmd(), modeStr, args)
}
timeout := cmdDefaultTimeout
if sub {
timeout = cmdRecursiveTimeout
}
cmdMgr := cmd.NewCommandMgr(cmd.WithTimeout(timeout))
if err := cmdMgr.RunBashC(cmdStr); err != nil {
return err
}
+3
View File
@@ -196,6 +196,9 @@ func (f *Firewall) RichRules(rule FireInfo, operation string) error {
}
func (f *Firewall) PortForward(info Forward, operation string) error {
if cmd.CheckIllegal(operation, info.Port, info.Protocol, info.TargetIP, info.TargetPort) {
return buserr.New("ErrCmdIllegal")
}
ruleStr := fmt.Sprintf("firewall-cmd --zone=public --%s-forward-port=port=%s:proto=%s:toport=%s --permanent", operation, info.Port, info.Protocol, info.TargetPort)
if info.TargetIP != "" && info.TargetIP != "127.0.0.1" && info.TargetIP != "localhost" {
ruleStr = fmt.Sprintf("firewall-cmd --zone=public --%s-forward-port=port=%s:proto=%s:toaddr=%s:toport=%s --permanent", operation, info.Port, info.Protocol, info.TargetIP, info.TargetPort)
@@ -5,6 +5,7 @@ import (
"strings"
"time"
"github.com/1Panel-dev/1Panel/agent/buserr"
"github.com/1Panel-dev/1Panel/agent/global"
"github.com/1Panel-dev/1Panel/agent/utils/cmd"
)
@@ -70,6 +71,9 @@ func DeleteFilterRule(chain string, policy FilterRules) error {
func ReadFilterRulesByChain(chain string) ([]FilterRules, error) {
var rules []FilterRules
if cmd.CheckIllegal(chain) {
return rules, buserr.New("ErrCmdIllegal")
}
cmdMgr := cmd.NewCommandMgr(cmd.WithIgnoreExist1(), cmd.WithTimeout(20*time.Second))
stdout, err := cmdMgr.RunWithStdoutBashCf("%s iptables -w -t %s -nL %s", cmd.SudoHandleCmd(), FilterTab, chain)
if err != nil {
@@ -100,6 +104,9 @@ func ReadFilterRulesByChain(chain string) ([]FilterRules, error) {
}
func LoadDefaultStrategy(chain string) (string, error) {
if cmd.CheckIllegal(chain) {
return "", buserr.New("ErrCmdIllegal")
}
cmdMgr := cmd.NewCommandMgr(cmd.WithIgnoreExist1(), cmd.WithTimeout(20*time.Second))
stdout, err := cmdMgr.RunWithStdoutBashCf("%s iptables -w -t %s -L %s", cmd.SudoHandleCmd(), FilterTab, chain)
if err != nil {
+7
View File
@@ -17,6 +17,7 @@ import (
"github.com/1Panel-dev/1Panel/agent/buserr"
"github.com/1Panel-dev/1Panel/agent/constant"
"github.com/1Panel-dev/1Panel/agent/global"
"github.com/1Panel-dev/1Panel/agent/utils/cmd"
"github.com/1Panel-dev/1Panel/agent/utils/common"
"github.com/1Panel-dev/1Panel/agent/utils/files"
"github.com/docker/docker/api/types/image"
@@ -234,6 +235,9 @@ func (r *Remote) ChangeAccess(info AccessChangeInfo) error {
}
func (r *Remote) Backup(info BackupInfo) error {
if cmd.CheckIllegal(r.Password, r.Address, r.User, info.Name, info.Format) {
return buserr.New("ErrCmdIllegal")
}
fileOp := files.NewFileOp()
if !fileOp.Stat(info.TargetDir) {
if err := os.MkdirAll(info.TargetDir, os.ModePerm); err != nil {
@@ -285,6 +289,9 @@ func (r *Remote) Backup(info BackupInfo) error {
}
func (r *Remote) Recover(info RecoverInfo) error {
if cmd.CheckIllegal(r.Password, r.Address, r.User, info.Name, info.Format) {
return buserr.New("ErrCmdIllegal")
}
fi, _ := os.Open(info.SourceFile)
defer fi.Close()
+7
View File
@@ -14,6 +14,7 @@ import (
"github.com/1Panel-dev/1Panel/agent/app/model"
"github.com/1Panel-dev/1Panel/agent/global"
"github.com/1Panel-dev/1Panel/agent/i18n"
"github.com/1Panel-dev/1Panel/agent/utils/cmd"
"github.com/docker/docker/api/types/image"
"github.com/pkg/errors"
@@ -119,6 +120,9 @@ func (r *Remote) ChangePassword(info PasswordChangeInfo) error {
}
func (r *Remote) Backup(info BackupInfo) error {
if cmd.CheckIllegal(r.Password, r.Address, r.User, info.Name) {
return buserr.New("ErrCmdIllegal")
}
imageTag, err := loadImageTag(info.Database)
if err != nil {
return err
@@ -157,6 +161,9 @@ func (r *Remote) Backup(info BackupInfo) error {
}
func (r *Remote) Recover(info RecoverInfo) error {
if cmd.CheckIllegal(r.Password, r.Address, r.User, info.Name, info.Username) {
return buserr.New("ErrCmdIllegal")
}
imageTag, err := loadImageTag(info.Database)
if err != nil {
return err

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