mirror of
https://github.com/1Panel-dev/1Panel.git
synced 2026-10-09 16:00:29 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6c3f56516f | ||
|
|
4833cf2d3c | ||
|
|
b5ca015632 | ||
|
|
1c82034111 | ||
|
|
e45d723f3d | ||
|
|
627e6852db | ||
|
|
1725767789 | ||
|
|
3e1f2b4934 | ||
|
|
140d20a21d | ||
|
|
6e933fc83c | ||
|
|
79ff565995 | ||
|
|
39691698c9 | ||
|
|
4bedddb557 | ||
|
|
f9547752f5 | ||
|
|
96b37cb024 | ||
|
|
30d874bc91 | ||
|
|
32bc8d922b | ||
|
|
a95d9aa27a | ||
|
|
7f4f5a0a47 | ||
|
|
65a7193bc6 | ||
|
|
64f79b4fdd | ||
|
|
31d2af554e | ||
|
|
e0f2910710 | ||
|
|
f89ab607b4 | ||
|
|
61fa07b8f2 | ||
|
|
9653df185a | ||
|
|
8f7d3df76d | ||
|
|
2aa89699c1 | ||
|
|
46b8d3bd80 | ||
|
|
8a7be481b0 | ||
|
|
21bd94d3a3 | ||
|
|
d337190b59 | ||
|
|
7073e83d94 | ||
|
|
537ff49c0b | ||
|
|
4b1f643c70 | ||
|
|
b8b873836d | ||
|
|
021334c2c8 | ||
|
|
635d86a1a5 | ||
|
|
607eae3ca8 | ||
|
|
66428c0a8e | ||
|
|
c0404a4ec5 | ||
|
|
77ef3b7096 | ||
|
|
498052f4c9 | ||
|
|
958c55eab8 | ||
|
|
d3cddd175a | ||
|
|
6cd130677e | ||
|
|
33b90e3979 | ||
|
|
e1e2b97e88 | ||
|
|
8e4e1dc749 | ||
|
|
1eb5830238 | ||
|
|
5db2ccbb22 | ||
|
|
47e04a14ad | ||
|
|
a13c58eb18 | ||
|
|
15d77ceb46 | ||
|
|
af8fa6763f | ||
|
|
ed66d22bdb | ||
|
|
5209db3abc | ||
|
|
35ea1a1d6c | ||
|
|
b0a053b397 | ||
|
|
a8b3390ce9 | ||
|
|
4266e39d84 | ||
|
|
5620b6e736 | ||
|
|
289e4190ff | ||
|
|
9250412296 | ||
|
|
9c5370508e | ||
|
|
a145bfdc5b | ||
|
|
db2b38eedb | ||
|
|
9303ee399f | ||
|
|
d64cb3dc58 | ||
|
|
62871acfbb | ||
|
|
7401906b03 | ||
|
|
5aa3c29684 | ||
|
|
91745f592f | ||
|
|
564333ea03 | ||
|
|
7b543ea5fe | ||
|
|
fc5e583481 | ||
|
|
1767b1a9c1 | ||
|
|
930c340e37 | ||
|
|
58531d3995 | ||
|
|
4b7c1390fb | ||
|
|
2337d6f13a | ||
|
|
cb8ecd55ed | ||
|
|
33ad31dba2 | ||
|
|
294c3f128b | ||
|
|
0899f4e9db | ||
|
|
788f4d8307 | ||
|
|
2451d6bed2 | ||
|
|
c547de875a | ||
|
|
ffead783aa | ||
|
|
d742c7d333 | ||
|
|
1025cd3535 | ||
|
|
de402ea724 | ||
|
|
3af07a42d6 | ||
|
|
e5b7e99cce | ||
|
|
365936bf57 |
+6
-1
@@ -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/
|
||||
|
||||
@@ -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
|
||||
|
||||

|
||||
@@ -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.
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 != "" {
|
||||
|
||||
@@ -20,6 +20,7 @@ var (
|
||||
aiToolService = service.NewIAIToolService()
|
||||
mcpServerService = service.NewIMcpServerService()
|
||||
tensorrtLLMService = service.NewITensorRTLLMService()
|
||||
agentService = service.NewIAgentService()
|
||||
|
||||
containerService = service.NewIContainerService()
|
||||
composeTemplateService = service.NewIComposeTemplateService()
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"`
|
||||
}
|
||||
@@ -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"`
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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"`
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -81,3 +81,7 @@ type FileConvertLog struct {
|
||||
Status string `json:"status"`
|
||||
Message string `json:"message"`
|
||||
}
|
||||
|
||||
type FileRemarksRes struct {
|
||||
Remarks map[string]string `json:"remarks"`
|
||||
}
|
||||
|
||||
@@ -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"`
|
||||
|
||||
@@ -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"`
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
@@ -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
@@ -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 {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
@@ -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
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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 := ""
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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...)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 ""
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
@@ -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{}
|
||||
|
||||
@@ -24,4 +24,5 @@ const (
|
||||
WeCom = "weCom"
|
||||
DingTalk = "dingTalk"
|
||||
FeiShu = "feiShu"
|
||||
Custom = "custom"
|
||||
)
|
||||
|
||||
@@ -7,6 +7,7 @@ const (
|
||||
AppTakeDown = "TakeDown"
|
||||
|
||||
AppOpenresty = "openresty"
|
||||
AppOpenclaw = "openclaw"
|
||||
AppMysql = "mysql"
|
||||
AppMariaDB = "mariadb"
|
||||
AppPostgresql = "postgresql"
|
||||
|
||||
@@ -40,4 +40,6 @@ const (
|
||||
|
||||
OrderDesc = "descending"
|
||||
OrderAsc = "ascending"
|
||||
Desc = "desc"
|
||||
Asc = "asc"
|
||||
)
|
||||
|
||||
@@ -34,6 +34,7 @@ type SystemDir struct {
|
||||
|
||||
AppDir string
|
||||
ResourceDir string
|
||||
IconCacheDir string
|
||||
AppResourceDir string
|
||||
AppInstallDir string
|
||||
LocalAppResourceDir string
|
||||
|
||||
+14
-1
@@ -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"
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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"
|
||||
|
||||
@@ -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
@@ -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"
|
||||
|
||||
@@ -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.'
|
||||
|
||||
@@ -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
@@ -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"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"))
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
@@ -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)})
|
||||
|
||||
@@ -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:
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,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) {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user