mirror of
https://github.com/1Panel-dev/1Panel.git
synced 2026-10-09 08:00:27 +00:00
Compare commits
62
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
725d03b41e | ||
|
|
7e8581a7de | ||
|
|
0ba83d1353 | ||
|
|
a302e100ec | ||
|
|
4f58fd492d | ||
|
|
0b06a4fac6 | ||
|
|
4e1a7bf42c | ||
|
|
ef2fb1c62e | ||
|
|
f5fcbcd87a | ||
|
|
986bffaf2b | ||
|
|
766a2a0546 | ||
|
|
291b594ad9 | ||
|
|
7742e3da38 | ||
|
|
c5f70eae4c | ||
|
|
8bdf02c4c0 | ||
|
|
8abe132c25 | ||
|
|
3a447b02a1 | ||
|
|
aa718eef88 | ||
|
|
60b1c9ba32 | ||
|
|
9b50581fd4 | ||
|
|
ee33fdab89 | ||
|
|
c8f19467ba | ||
|
|
1fc0f1c71b | ||
|
|
2258ba108d | ||
|
|
d21f4ffae6 | ||
|
|
4bd9c4c0fc | ||
|
|
29165f0f6f | ||
|
|
f75b786916 | ||
|
|
011914339e | ||
|
|
08a07214ef | ||
|
|
f682f07dbc | ||
|
|
50e99f1f4c | ||
|
|
feabc488fe | ||
|
|
72d3f5071b | ||
|
|
27dc662430 | ||
|
|
80e233c4a6 | ||
|
|
a099308a82 | ||
|
|
6f0673f184 | ||
|
|
b428e699db | ||
|
|
f4334eadc0 | ||
|
|
a2a6503030 | ||
|
|
9c22005b79 | ||
|
|
7448953b7d | ||
|
|
18214095ab | ||
|
|
bcfc78e510 | ||
|
|
cc6b506176 | ||
|
|
c61828fb39 | ||
|
|
23876ed186 | ||
|
|
ac4962cf19 | ||
|
|
d9e7c8d880 | ||
|
|
ea25b800d2 | ||
|
|
7ba06e73d7 | ||
|
|
39cf26628f | ||
|
|
ea7f324a9d | ||
|
|
789d451ea2 | ||
|
|
7313f3d591 | ||
|
|
1662546bef | ||
|
|
53f75086ec | ||
|
|
81fcc61ec9 | ||
|
|
324a47966b | ||
|
|
5a0c50b9a2 | ||
|
|
66465beb39 |
@@ -12,7 +12,7 @@ jobs:
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '20.2'
|
||||
node-version: '22.19.0'
|
||||
- name: Build Web
|
||||
run: |
|
||||
cd frontend && npm install && npm run build:pro
|
||||
|
||||
@@ -12,7 +12,7 @@ jobs:
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '20.2'
|
||||
node-version: '22.19.0'
|
||||
- name: Build Web
|
||||
run: |
|
||||
cd frontend && npm install && npm run build:pro
|
||||
|
||||
@@ -60,7 +60,7 @@ func (b *BaseApi) RecreateOllamaModel(c *gin.Context) {
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /ai/ollama/model/close [post]
|
||||
// @Router /ai/ollama/close [post]
|
||||
// @x-panel-log {"bodyKeys":["name"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"关闭 Ollama 模型连接 [name]","formatEN":"close conn for Ollama model [name]"}
|
||||
func (b *BaseApi) CloseOllamaModel(c *gin.Context) {
|
||||
var req dto.OllamaModelName
|
||||
|
||||
@@ -109,7 +109,7 @@ func (b *BaseApi) LoadPort(c *gin.Context) {
|
||||
// @Success 200 {object} response.DatabaseConn
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /apps/installed/conninfo/:key [get]
|
||||
// @Router /apps/installed/conninfo [POST]
|
||||
func (b *BaseApi) LoadConnInfo(c *gin.Context) {
|
||||
var req dto.OperationWithNameAndType
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
|
||||
@@ -74,7 +74,7 @@ func (b *BaseApi) RefreshToken(c *gin.Context) {
|
||||
// @Success 200 {array} object
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /buckets [post]
|
||||
// @Router /backups/buckets [post]
|
||||
func (b *BaseApi) ListBuckets(c *gin.Context) {
|
||||
var req dto.ForBuckets
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
@@ -133,6 +133,28 @@ func (b *BaseApi) UpdateBackup(c *gin.Context) {
|
||||
helper.Success(c)
|
||||
}
|
||||
|
||||
// @Tags Backup Account
|
||||
// @Summary Upload file for recover
|
||||
// @Accept json
|
||||
// @Param request body dto.UploadForRecover true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /backups/upload [post]
|
||||
// @x-panel-log {"bodyKeys":["filePath"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"上传备份文件 [filePath]","formatEN":"upload backup file [filePath]"}
|
||||
func (b *BaseApi) UploadForRecover(c *gin.Context) {
|
||||
var req dto.UploadForRecover
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if err := backupService.UploadForRecover(req); err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
}
|
||||
helper.Success(c)
|
||||
}
|
||||
|
||||
// @Tags Backup Account
|
||||
// @Summary Load backup account options
|
||||
// @Accept json
|
||||
@@ -273,7 +295,7 @@ func (b *BaseApi) SearchBackupRecordsByCronjob(c *gin.Context) {
|
||||
// @Success 200 {string} filePath
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /backup/record/download [post]
|
||||
// @Router /backups/record/download [post]
|
||||
// @x-panel-log {"bodyKeys":["source","fileName"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"下载备份记录 [source][fileName]","formatEN":"download backup records [source][fileName]"}
|
||||
func (b *BaseApi) DownloadRecord(c *gin.Context) {
|
||||
var req dto.DownloadRecord
|
||||
@@ -296,7 +318,7 @@ func (b *BaseApi) DownloadRecord(c *gin.Context) {
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /backup/record/description/update [post]
|
||||
// @Router /backups/record/description/update [post]
|
||||
func (b *BaseApi) UpdateRecordDescription(c *gin.Context) {
|
||||
var req dto.UpdateDescription
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
@@ -317,7 +339,7 @@ func (b *BaseApi) UpdateRecordDescription(c *gin.Context) {
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /record/del [post]
|
||||
// @Router /backups/record/del [post]
|
||||
// @x-panel-log {"bodyKeys":["ids"],"paramKeys":[],"BeforeFunctions":[{"input_column":"id","input_value":"ids","isList":true,"db":"backup_records","output_column":"file_name","output_value":"files"}],"formatZH":"删除备份记录 [files]","formatEN":"delete backup records [files]"}
|
||||
func (b *BaseApi) DeleteBackupRecord(c *gin.Context) {
|
||||
var req dto.BatchDeleteReq
|
||||
|
||||
@@ -104,7 +104,7 @@ func (b *BaseApi) SearchClam(c *gin.Context) {
|
||||
// @Success 200 {object} dto.ClamBaseInfo
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /toolbox/clam/base [get]
|
||||
// @Router /toolbox/clam/base [post]
|
||||
func (b *BaseApi) LoadClamBaseInfo(c *gin.Context) {
|
||||
info, err := clamService.LoadBaseInfo()
|
||||
if err != nil {
|
||||
@@ -152,7 +152,7 @@ func (b *BaseApi) CleanClamRecord(c *gin.Context) {
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
if err := clamService.CleanRecord(req); err != nil {
|
||||
if err := clamService.CleanRecord(req.ID); err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
}
|
||||
@@ -174,7 +174,7 @@ func (b *BaseApi) SearchClamRecord(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
total, list, err := clamService.LoadRecords(req)
|
||||
total, list, err := clamService.SearchRecords(req)
|
||||
if err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
@@ -186,29 +186,6 @@ func (b *BaseApi) SearchClamRecord(c *gin.Context) {
|
||||
})
|
||||
}
|
||||
|
||||
// @Tags Clam
|
||||
// @Summary Load clam record detail
|
||||
// @Accept json
|
||||
// @Param request body dto.ClamLogReq true "request"
|
||||
// @Success 200 {string} content
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /toolbox/clam/record/log [post]
|
||||
func (b *BaseApi) LoadClamRecordLog(c *gin.Context) {
|
||||
var req dto.ClamLogReq
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
content, err := clamService.LoadRecordLog(req)
|
||||
if err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
helper.SuccessWithData(c, content)
|
||||
}
|
||||
|
||||
// @Tags Clam
|
||||
// @Summary Load clam file
|
||||
// @Accept json
|
||||
@@ -289,7 +266,7 @@ func (b *BaseApi) HandleClamScan(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
if err := clamService.HandleOnce(req); err != nil {
|
||||
if err := clamService.HandleOnce(req.ID); err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -697,7 +697,7 @@ func (b *BaseApi) ComposeUpdate(c *gin.Context) {
|
||||
// @Param tail query string false "显示行号"
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /containers/search/log [post]
|
||||
// @Router /containers/search/log [get]
|
||||
func (b *BaseApi) ContainerStreamLogs(c *gin.Context) {
|
||||
c.Header("Content-Type", "text/event-stream")
|
||||
c.Header("Cache-Control", "no-cache")
|
||||
|
||||
@@ -83,6 +83,38 @@ func (b *BaseApi) UpdateAppLauncher(c *gin.Context) {
|
||||
helper.Success(c)
|
||||
}
|
||||
|
||||
// @Tags Dashboard
|
||||
// @Summary Load quick jump options
|
||||
// @Success 200 {Array} dto.QuickJump
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /dashboard/quick/option [get]
|
||||
func (b *BaseApi) LoadQuickOption(c *gin.Context) {
|
||||
helper.SuccessWithData(c, dashboardService.LoadQuickOptions())
|
||||
}
|
||||
|
||||
// @Tags Dashboard
|
||||
// @Summary Update quick jump
|
||||
// @Accept json
|
||||
// @Param request body dto.ChangeQuicks true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /dashboard/quick/change [post]
|
||||
// @x-panel-log {"bodyKeys":[],"paramKeys":[],"BeforeFunctions":[],"formatZH":"切换快速跳转","formatEN":"change quick jump"}
|
||||
func (b *BaseApi) UpdateQuickJump(c *gin.Context) {
|
||||
var req dto.ChangeQuicks
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if err := dashboardService.ChangeQuick(req); err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
}
|
||||
helper.Success(c)
|
||||
}
|
||||
|
||||
// @Tags Dashboard
|
||||
// @Summary Load dashboard base info
|
||||
// @Accept json
|
||||
|
||||
@@ -160,7 +160,7 @@ func (b *BaseApi) GetDatabase(c *gin.Context) {
|
||||
// @Success 200 {array} string
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /db/remote/del/check [post]
|
||||
// @Router /databases/db/del/check [post]
|
||||
func (b *BaseApi) DeleteCheckDatabase(c *gin.Context) {
|
||||
var req dto.OperateByID
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
|
||||
@@ -0,0 +1,99 @@
|
||||
package v2
|
||||
|
||||
import (
|
||||
"github.com/1Panel-dev/1Panel/agent/app/api/v2/helper"
|
||||
"github.com/1Panel-dev/1Panel/agent/app/dto/request"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// @Tags Disk Management
|
||||
// @Summary Get complete disk information
|
||||
// @Description Get information about all disks including partitioned and unpartitioned disks
|
||||
// @Produce json
|
||||
// @Success 200 {object} response.CompleteDiskInfo
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /hosts/disks [get]
|
||||
func (b *BaseApi) GetCompleteDiskInfo(c *gin.Context) {
|
||||
diskInfo, err := diskService.GetCompleteDiskInfo()
|
||||
if err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithData(c, diskInfo)
|
||||
}
|
||||
|
||||
// @Tags Disk Management
|
||||
// @Summary Partition disk
|
||||
// @Description Create partition and format disk with specified filesystem
|
||||
// @Accept json
|
||||
// @Param request body request.DiskPartitionRequest true "partition request"
|
||||
// @Success 200 {string} string "Partition created successfully"
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /hosts/disks/partition [post]
|
||||
// @x-panel-log {"bodyKeys":["device", "filesystem", "mountPoint"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"对磁盘 [device] 进行分区,文件系统 [filesystem],挂载点 [mountPoint]","formatEN":"Partition disk [device] with filesystem [filesystem], mount point [mountPoint]"}
|
||||
func (b *BaseApi) PartitionDisk(c *gin.Context) {
|
||||
var req request.DiskPartitionRequest
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
result, err := diskService.PartitionDisk(req)
|
||||
if err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
helper.SuccessWithData(c, result)
|
||||
}
|
||||
|
||||
// @Tags Disk Management
|
||||
// @Summary Mount disk
|
||||
// @Description Mount partition to specified mount point
|
||||
// @Accept json
|
||||
// @Param request body request.DiskMountRequest true "mount request"
|
||||
// @Success 200 {string} string "Disk mounted successfully"
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /hosts/disks/mount [post]
|
||||
// @x-panel-log {"bodyKeys":["device", "mountPoint"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"挂载磁盘 [device] 到 [mountPoint]","formatEN":"Mount disk [device] to [mountPoint]"}
|
||||
func (b *BaseApi) MountDisk(c *gin.Context) {
|
||||
var req request.DiskMountRequest
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
err := diskService.MountDisk(req)
|
||||
if err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
helper.Success(c)
|
||||
}
|
||||
|
||||
// @Tags Disk Management
|
||||
// @Summary Unmount disk
|
||||
// @Description Unmount partition from mount point
|
||||
// @Accept json
|
||||
// @Param request body request.DiskUnmountRequest true "unmount request"
|
||||
// @Success 200 {string} string "Disk unmounted successfully"
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /hosts/disks/unmount [post]
|
||||
// @x-panel-log {"bodyKeys":["device", "mountPoint"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"卸载磁盘 [device] 从 [mountPoint]","formatEN":"Unmount disk [device] from [mountPoint]"}
|
||||
func (b *BaseApi) UnmountDisk(c *gin.Context) {
|
||||
var req request.DiskUnmountRequest
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
err := diskService.UnmountDisk(req)
|
||||
if err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
helper.Success(c)
|
||||
}
|
||||
@@ -38,6 +38,7 @@ var (
|
||||
sshService = service.NewISSHService()
|
||||
firewallService = service.NewIFirewallService()
|
||||
monitorService = service.NewIMonitorService()
|
||||
systemService = service.NewISystemService()
|
||||
|
||||
deviceService = service.NewIDeviceService()
|
||||
fail2banService = service.NewIFail2BanService()
|
||||
@@ -71,4 +72,6 @@ var (
|
||||
taskService = service.NewITaskService()
|
||||
groupService = service.NewIGroupService()
|
||||
alertService = service.NewIAlertService()
|
||||
|
||||
diskService = service.NewIDiskService()
|
||||
)
|
||||
|
||||
@@ -880,7 +880,7 @@ func (b *BaseApi) GetPathByType(c *gin.Context) {
|
||||
// @Success 200 {object} dto.DiskInfo
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /mount [post]
|
||||
// @Router /files/mount [post]
|
||||
func (b *BaseApi) GetHostMount(c *gin.Context) {
|
||||
disks := fileService.GetHostMount()
|
||||
helper.SuccessWithData(c, disks)
|
||||
@@ -892,7 +892,7 @@ func (b *BaseApi) GetHostMount(c *gin.Context) {
|
||||
// @Success 200 {object} response.UserGroupResponse
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /user/group [post]
|
||||
// @Router /files/user/group [post]
|
||||
func (b *BaseApi) GetUsersAndGroups(c *gin.Context) {
|
||||
res, err := fileService.GetUsersAndGroups()
|
||||
if err != nil {
|
||||
|
||||
@@ -63,7 +63,7 @@ func (b *BaseApi) OperateFirewall(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
if err := firewallService.OperateFirewall(req.Operation); err != nil {
|
||||
if err := firewallService.OperateFirewall(req); err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ import (
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /agent/groups [post]
|
||||
// @Router /groups [post]
|
||||
// @x-panel-log {"bodyKeys":["name","type"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"创建组 [name][type]","formatEN":"create group [name][type]"}
|
||||
func (b *BaseApi) CreateGroup(c *gin.Context) {
|
||||
var req dto.GroupCreate
|
||||
@@ -35,7 +35,7 @@ func (b *BaseApi) CreateGroup(c *gin.Context) {
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /agent/groups/del [post]
|
||||
// @Router /groups/del [post]
|
||||
// @x-panel-log {"bodyKeys":["id"],"paramKeys":[],"BeforeFunctions":[{"input_column":"id","input_value":"id","isList":false,"db":"groups","output_column":"name","output_value":"name"},{"input_column":"id","input_value":"id","isList":false,"db":"groups","output_column":"type","output_value":"type"}],"formatZH":"删除组 [type][name]","formatEN":"delete group [type][name]"}
|
||||
func (b *BaseApi) DeleteGroup(c *gin.Context) {
|
||||
var req dto.OperateByID
|
||||
@@ -57,7 +57,7 @@ func (b *BaseApi) DeleteGroup(c *gin.Context) {
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /agent/groups/update [post]
|
||||
// @Router /groups/update [post]
|
||||
// @x-panel-log {"bodyKeys":["name","type"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"更新组 [name][type]","formatEN":"update group [name][type]"}
|
||||
func (b *BaseApi) UpdateGroup(c *gin.Context) {
|
||||
var req dto.GroupUpdate
|
||||
@@ -79,7 +79,7 @@ func (b *BaseApi) UpdateGroup(c *gin.Context) {
|
||||
// @Success 200 {array} dto.OperateByType
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /agent/groups/search [post]
|
||||
// @Router /groups/search [post]
|
||||
func (b *BaseApi) ListGroup(c *gin.Context) {
|
||||
var req dto.OperateByType
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
|
||||
@@ -116,7 +116,11 @@ func CheckBind(req interface{}, c *gin.Context) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func ErrResponse(ctx *gin.Context, code int) {
|
||||
ctx.JSON(code, nil)
|
||||
ctx.Abort()
|
||||
func GetParamInt32(paramName string, c *gin.Context) (int32, error) {
|
||||
idParam, ok := c.Params.Get(paramName)
|
||||
if !ok {
|
||||
return 0, errors.New("error id in path")
|
||||
}
|
||||
intNum, _ := strconv.Atoi(idParam)
|
||||
return int32(intNum), nil
|
||||
}
|
||||
|
||||
@@ -35,7 +35,7 @@ func (b *BaseApi) GetToolStatus(c *gin.Context) {
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /hosts/tool/create [post]
|
||||
// @Router /hosts/tool/init [post]
|
||||
// @x-panel-log {"bodyKeys":["type"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"创建 [type] 配置","formatEN":"create [type] config"}
|
||||
func (b *BaseApi) InitToolConfig(c *gin.Context) {
|
||||
var req request.HostToolCreate
|
||||
|
||||
@@ -58,7 +58,7 @@ func (b *BaseApi) ListRepo(c *gin.Context) {
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /containers/repo/status [get]
|
||||
// @Router /containers/repo/status [post]
|
||||
func (b *BaseApi) CheckRepoStatus(c *gin.Context) {
|
||||
var req dto.OperateByID
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
|
||||
@@ -13,7 +13,7 @@ import (
|
||||
// @Success 200 {object} response.McpServersRes
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /mcp/search [post]
|
||||
// @Router /ai/mcp/search [post]
|
||||
func (b *BaseApi) PageMcpServers(c *gin.Context) {
|
||||
var req request.McpServerSearch
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
@@ -30,7 +30,7 @@ func (b *BaseApi) PageMcpServers(c *gin.Context) {
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /mcp/server [post]
|
||||
// @Router /ai/mcp/server [post]
|
||||
func (b *BaseApi) CreateMcpServer(c *gin.Context) {
|
||||
var req request.McpServerCreate
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
@@ -51,7 +51,7 @@ func (b *BaseApi) CreateMcpServer(c *gin.Context) {
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /mcp/server/update [post]
|
||||
// @Router /ai/mcp/server/update [post]
|
||||
func (b *BaseApi) UpdateMcpServer(c *gin.Context) {
|
||||
var req request.McpServerUpdate
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
@@ -72,7 +72,7 @@ func (b *BaseApi) UpdateMcpServer(c *gin.Context) {
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /mcp/server/del [post]
|
||||
// @Router /ai/mcp/server/del [post]
|
||||
func (b *BaseApi) DeleteMcpServer(c *gin.Context) {
|
||||
var req request.McpServerDelete
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
@@ -93,7 +93,7 @@ func (b *BaseApi) DeleteMcpServer(c *gin.Context) {
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /mcp/server/op [post]
|
||||
// @Router /ai/mcp/server/op [post]
|
||||
func (b *BaseApi) OperateMcpServer(c *gin.Context) {
|
||||
var req request.McpServerOperate
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
@@ -114,7 +114,7 @@ func (b *BaseApi) OperateMcpServer(c *gin.Context) {
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /mcp/domain/bind [post]
|
||||
// @Router /ai/mcp/domain/bind [post]
|
||||
func (b *BaseApi) BindMcpDomain(c *gin.Context) {
|
||||
var req request.McpBindDomain
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
@@ -135,7 +135,7 @@ func (b *BaseApi) BindMcpDomain(c *gin.Context) {
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /mcp/domain/update [post]
|
||||
// @Router /ai/mcp/domain/update [post]
|
||||
func (b *BaseApi) UpdateMcpBindDomain(c *gin.Context) {
|
||||
var req request.McpBindDomainUpdate
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
@@ -155,7 +155,7 @@ func (b *BaseApi) UpdateMcpBindDomain(c *gin.Context) {
|
||||
// @Success 200 {object} response.McpBindDomainRes
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /mcp/domain/get [get]
|
||||
// @Router /ai/mcp/domain/get [get]
|
||||
func (b *BaseApi) GetMcpBindDomain(c *gin.Context) {
|
||||
res, err := mcpServerService.GetBindDomain()
|
||||
if err != nil {
|
||||
|
||||
@@ -129,7 +129,7 @@ func (b *BaseApi) BuildNginx(c *gin.Context) {
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /openresty/module/update [post]
|
||||
// @Router /openresty/modules/update [post]
|
||||
// @x-panel-log {"bodyKeys":[],"paramKeys":[],"BeforeFunctions":[],"formatZH":"更新 OpenResty 模块","formatEN":"Update OpenResty module"}
|
||||
func (b *BaseApi) UpdateNginxModule(c *gin.Context) {
|
||||
var req request.NginxModuleUpdate
|
||||
|
||||
@@ -36,3 +36,24 @@ func (b *BaseApi) StopProcess(c *gin.Context) {
|
||||
}
|
||||
helper.Success(c)
|
||||
}
|
||||
|
||||
// @Tags Process
|
||||
// @Summary Get Process Info By PID
|
||||
// @Param pid path int true "PID"
|
||||
// @Success 200 {object} websocket.PsProcessData
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /process/{pid} [get]
|
||||
func (b *BaseApi) GetProcessInfoByPID(c *gin.Context) {
|
||||
pid, err := helper.GetParamInt32("pid", c)
|
||||
if err != nil {
|
||||
helper.BadRequest(c, err)
|
||||
return
|
||||
}
|
||||
data, err := processService.GetProcessInfoByPID(pid)
|
||||
if err != nil {
|
||||
helper.BadRequest(c, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithData(c, data)
|
||||
}
|
||||
|
||||
@@ -81,7 +81,7 @@ func (b *BaseApi) DeleteRuntime(c *gin.Context) {
|
||||
// @Success 200 {array} dto.AppResource
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /installed/delete/check/:id [get]
|
||||
// @Router /runtimes/installed/delete/check/:id [get]
|
||||
func (b *BaseApi) DeleteRuntimeCheck(c *gin.Context) {
|
||||
runTimeId, err := helper.GetIntParamByKey(c, "id")
|
||||
if err != nil {
|
||||
@@ -459,7 +459,7 @@ func (b *BaseApi) GetSupervisorProcess(c *gin.Context) {
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /runtimes/supervisor/process/operate [post]
|
||||
// @Router /runtimes/supervisor/process [post]
|
||||
func (b *BaseApi) OperateSupervisorProcess(c *gin.Context) {
|
||||
var req request.PHPSupervisorProcessConfig
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
@@ -480,7 +480,7 @@ func (b *BaseApi) OperateSupervisorProcess(c *gin.Context) {
|
||||
// @Success 200 {string} content
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /runtimes/supervisor/process/file/operate [post]
|
||||
// @Router /runtimes/supervisor/process/file [post]
|
||||
func (b *BaseApi) OperateSupervisorProcessFile(c *gin.Context) {
|
||||
var req request.PHPSupervisorProcessFileReq
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
@@ -497,7 +497,7 @@ func (b *BaseApi) OperateSupervisorProcessFile(c *gin.Context) {
|
||||
// @Tags Runtime
|
||||
// @Summary Update PHP container config
|
||||
// @Accept json
|
||||
// @Param request body request.PHPContainerUpdate true "request"
|
||||
// @Param request body request.PHPContainerConfig true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
@@ -518,7 +518,7 @@ func (b *BaseApi) UpdatePHPContainer(c *gin.Context) {
|
||||
// @Summary Get PHP container config
|
||||
// @Accept json
|
||||
// @Param id path integer true "request"
|
||||
// @Success 200 {object} response.PHPContainerConfig
|
||||
// @Success 200 {object} request.PHPContainerConfig
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /runtimes/php/container/:id [get]
|
||||
@@ -539,7 +539,7 @@ func (b *BaseApi) GetPHPContainerConfig(c *gin.Context) {
|
||||
// @Tags Runtime
|
||||
// @Summary Update runtime remark
|
||||
// @Accept json
|
||||
// @Param request body request.RuntimeRemarkUpdate true "request"
|
||||
// @Param request body request.RuntimeRemark true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
|
||||
+51
-35
@@ -1,13 +1,12 @@
|
||||
package v2
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"os"
|
||||
"os/user"
|
||||
"path"
|
||||
|
||||
"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/model"
|
||||
"github.com/1Panel-dev/1Panel/agent/global"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/ssh"
|
||||
"github.com/gin-gonic/gin"
|
||||
@@ -71,6 +70,35 @@ func (b *BaseApi) LoadBaseDir(c *gin.Context) {
|
||||
helper.SuccessWithData(c, global.Dir.DataDir)
|
||||
}
|
||||
|
||||
// @Tags System Setting
|
||||
// @Summary Load local conn
|
||||
// @Success 200 {object} dto.SSHConnData
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /settings/ssh/conn [get]
|
||||
func (b *BaseApi) LoadLocalConn(c *gin.Context) {
|
||||
connInfoInDB := settingService.GetSettingByKey("LocalSSHConn")
|
||||
if len(connInfoInDB) == 0 {
|
||||
helper.Success(c)
|
||||
return
|
||||
}
|
||||
var data dto.SSHConnData
|
||||
if err := json.Unmarshal([]byte(connInfoInDB), &data); err != nil {
|
||||
helper.Success(c)
|
||||
return
|
||||
}
|
||||
if len(data.Password) != 0 {
|
||||
data.Password = base64.StdEncoding.EncodeToString([]byte(data.Password))
|
||||
}
|
||||
if len(data.PrivateKey) != 0 {
|
||||
data.PrivateKey = base64.StdEncoding.EncodeToString([]byte(data.PrivateKey))
|
||||
}
|
||||
if len(data.PassPhrase) != 0 {
|
||||
data.PassPhrase = base64.StdEncoding.EncodeToString([]byte(data.PassPhrase))
|
||||
}
|
||||
helper.SuccessWithData(c, data)
|
||||
}
|
||||
|
||||
func (b *BaseApi) CheckLocalConn(c *gin.Context) {
|
||||
_, err := loadLocalConn()
|
||||
helper.SuccessWithData(c, err == nil)
|
||||
@@ -87,6 +115,7 @@ func (b *BaseApi) CheckLocalConnByInfo(c *gin.Context) {
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
helper.SuccessWithData(c, settingService.TestConnByInfo(req))
|
||||
}
|
||||
|
||||
@@ -96,51 +125,38 @@ func (b *BaseApi) CheckLocalConnByInfo(c *gin.Context) {
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /settings/ssh [post]
|
||||
func (b *BaseApi) SaveLocalConnInfo(c *gin.Context) {
|
||||
func (b *BaseApi) SaveLocalConn(c *gin.Context) {
|
||||
var req dto.SSHConnData
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
helper.SuccessWithData(c, settingService.SaveConnInfo(req))
|
||||
|
||||
if err := settingService.SaveConnInfo(req); err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
}
|
||||
helper.Success(c)
|
||||
}
|
||||
|
||||
func loadLocalConn() (*ssh.SSHClient, error) {
|
||||
itemPath := ""
|
||||
currentInfo, _ := user.Current()
|
||||
if len(currentInfo.HomeDir) == 0 {
|
||||
itemPath = "/root/.ssh/id_ed25519_1panel"
|
||||
} else {
|
||||
itemPath = path.Join(currentInfo.HomeDir, ".ssh/id_ed25519_1panel")
|
||||
}
|
||||
if _, err := os.Stat(itemPath); err != nil {
|
||||
_ = sshService.CreateRootCert(dto.CreateRootCert{EncryptionMode: "ed25519", Name: "id_ed25519_1panel", Description: "1Panel Terminal"})
|
||||
}
|
||||
|
||||
privateKey, _ := os.ReadFile(itemPath)
|
||||
connWithKey := ssh.ConnInfo{
|
||||
Addr: "127.0.0.1",
|
||||
User: "root",
|
||||
Port: 22,
|
||||
AuthMode: "key",
|
||||
PrivateKey: privateKey,
|
||||
}
|
||||
client, err := ssh.NewClient(connWithKey)
|
||||
if err == nil {
|
||||
return client, nil
|
||||
}
|
||||
|
||||
connInfoInDB, err := settingService.GetSSHInfo()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
connInfoInDB := settingService.GetSettingByKey("LocalSSHConn")
|
||||
if len(connInfoInDB) == 0 {
|
||||
return nil, errors.New("no such ssh conn info in db!")
|
||||
}
|
||||
var connInDB ssh.ConnInfo
|
||||
var connInDB model.LocalConnInfo
|
||||
if err := json.Unmarshal([]byte(connInfoInDB), &connInDB); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return ssh.NewClient(connInDB)
|
||||
sshInfo := ssh.ConnInfo{
|
||||
Addr: connInDB.Addr,
|
||||
Port: int(connInDB.Port),
|
||||
User: connInDB.User,
|
||||
AuthMode: connInDB.AuthMode,
|
||||
Password: connInDB.Password,
|
||||
PrivateKey: []byte(connInDB.PrivateKey),
|
||||
PassPhrase: []byte(connInDB.PassPhrase),
|
||||
}
|
||||
return ssh.NewClient(sshInfo)
|
||||
}
|
||||
|
||||
// @Tags System Setting
|
||||
|
||||
@@ -50,7 +50,7 @@ func (b *BaseApi) CreateSnapshot(c *gin.Context) {
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /settings/snapshot/recrete [post]
|
||||
// @Router /settings/snapshot/recreate [post]
|
||||
// @x-panel-log {"bodyKeys":["id"],"paramKeys":[],"BeforeFunctions":[{"input_column":"id","input_value":"id","isList":false,"db":"snapshots","output_column":"name","output_value":"name"}],"formatZH":"重试创建快照 [name]","formatEN":"recrete the snapshot [name]"}
|
||||
func (b *BaseApi) RecreateSnapshot(c *gin.Context) {
|
||||
var req dto.OperateByID
|
||||
|
||||
@@ -74,7 +74,7 @@ func (b *BaseApi) UpdateSSH(c *gin.Context) {
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /hosts/conffile/update [post]
|
||||
// @Router /hosts/ssh/conffile/update [post]
|
||||
// @x-panel-log {"bodyKeys":[],"paramKeys":[],"BeforeFunctions":[],"formatZH":"修改 SSH 配置文件","formatEN":"update SSH conf"}
|
||||
func (b *BaseApi) UpdateSSHByfile(c *gin.Context) {
|
||||
var req dto.SSHConf
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
package v2
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"github.com/1Panel-dev/1Panel/agent/app/api/v2/helper"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// @Tags Host
|
||||
// @Summary Check if a system component exists
|
||||
// @Accept json
|
||||
// @Param name query string true "Component name to check (e.g., rsync, docker)"
|
||||
// @Success 200 {object} response.ComponentInfo
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /hosts/components/{name} [get]
|
||||
func (b *BaseApi) CheckComponentExistence(c *gin.Context) {
|
||||
name := c.Param("name")
|
||||
if name == "" {
|
||||
helper.BadRequest(c, errors.New("empty component name"))
|
||||
return
|
||||
}
|
||||
|
||||
info := systemService.IsComponentExist(name)
|
||||
helper.SuccessWithData(c, info)
|
||||
}
|
||||
@@ -527,7 +527,7 @@ func (b *BaseApi) UpdateProxyConfig(c *gin.Context) {
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /websites/proxy/file [post]
|
||||
// @Router /websites/proxies/file [post]
|
||||
// @x-panel-log {"bodyKeys":["websiteID"],"paramKeys":[],"BeforeFunctions":[{"input_column":"id","input_value":"websiteID","isList":false,"db":"websites","output_column":"primary_domain","output_value":"domain"}],"formatZH":"更新反向代理文件 [domain]","formatEN":"Nginx conf proxy file update [domain]"}
|
||||
func (b *BaseApi) UpdateProxyConfigFile(c *gin.Context) {
|
||||
var req request.NginxProxyUpdate
|
||||
@@ -840,7 +840,7 @@ func (b *BaseApi) CreateLoadBalance(c *gin.Context) {
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /websites/lbs/delete [post]
|
||||
// @Router /websites/lbs/del [post]
|
||||
func (b *BaseApi) DeleteLoadBalance(c *gin.Context) {
|
||||
var req request.WebsiteLBDelete
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
@@ -953,7 +953,7 @@ func (b *BaseApi) GetProxyCache(c *gin.Context) {
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /websites/realip [post]
|
||||
// @Router /websites/realip/config [post]
|
||||
// @x-panel-log {"bodyKeys":["websiteID"],"paramKeys":[],"BeforeFunctions":[{"input_column":"id","input_value":"websiteID","isList":false,"db":"websites","output_column":"primary_domain","output_value":"domain"}],"formatZH":"修改 [domain] 网站真实IP配置 ","formatEN":"Modify the real IP configuration of [domain] website"}
|
||||
func (b *BaseApi) SetRealIPConfig(c *gin.Context) {
|
||||
var req request.WebsiteRealIP
|
||||
|
||||
@@ -53,6 +53,11 @@ type BackupOption struct {
|
||||
IsPublic bool `json:"isPublic"`
|
||||
}
|
||||
|
||||
type UploadForRecover struct {
|
||||
FilePath string `json:"filePath"`
|
||||
TargetDir string `json:"targetDir"`
|
||||
}
|
||||
|
||||
type CommonBackup struct {
|
||||
Type string `json:"type" validate:"required,oneof=app mysql mariadb redis website postgresql mysql-cluster postgresql-cluster redis-cluster"`
|
||||
Name string `json:"name"`
|
||||
|
||||
+13
-9
@@ -30,7 +30,8 @@ type ClamInfo struct {
|
||||
Path string `json:"path"`
|
||||
InfectedStrategy string `json:"infectedStrategy"`
|
||||
InfectedDir string `json:"infectedDir"`
|
||||
LastHandleDate string `json:"lastHandleDate"`
|
||||
LastRecordStatus string `json:"lastRecordStatus"`
|
||||
LastRecordTime string `json:"lastRecordTime"`
|
||||
Spec string `json:"spec"`
|
||||
Description string `json:"description"`
|
||||
AlertCount uint `json:"alertCount"`
|
||||
@@ -41,6 +42,7 @@ type ClamLogSearch struct {
|
||||
PageInfo
|
||||
|
||||
ClamID uint `json:"clamID"`
|
||||
Status string `json:"status"`
|
||||
StartTime time.Time `json:"startTime"`
|
||||
EndTime time.Time `json:"endTime"`
|
||||
}
|
||||
@@ -56,13 +58,16 @@ type ClamFileReq struct {
|
||||
Name string `json:"name" validate:"required"`
|
||||
}
|
||||
|
||||
type ClamLog struct {
|
||||
Name string `json:"name"`
|
||||
ScanDate string `json:"scanDate"`
|
||||
ScanTime string `json:"scanTime"`
|
||||
InfectedFiles string `json:"infectedFiles"`
|
||||
TotalError string `json:"totalError"`
|
||||
Status string `json:"status"`
|
||||
type ClamRecord struct {
|
||||
ID uint `json:"id"`
|
||||
TaskID string `json:"taskID"`
|
||||
StartTime time.Time `json:"startTime"`
|
||||
ScanTime string `json:"scanTime"`
|
||||
InfectedFiles string `json:"infectedFiles"`
|
||||
TotalError string `json:"totalError"`
|
||||
|
||||
Status string `json:"status"`
|
||||
Message string `json:"message"`
|
||||
}
|
||||
|
||||
type ClamCreate struct {
|
||||
@@ -98,7 +103,6 @@ type ClamUpdateStatus struct {
|
||||
}
|
||||
|
||||
type ClamDelete struct {
|
||||
RemoveRecord bool `json:"removeRecord"`
|
||||
RemoveInfected bool `json:"removeInfected"`
|
||||
Ids []uint `json:"ids" validate:"required"`
|
||||
}
|
||||
|
||||
@@ -209,11 +209,12 @@ type NetworkCreate struct {
|
||||
}
|
||||
|
||||
type Volume struct {
|
||||
Name string `json:"name"`
|
||||
Labels []string `json:"labels"`
|
||||
Driver string `json:"driver"`
|
||||
Mountpoint string `json:"mountpoint"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
Name string `json:"name"`
|
||||
Labels []VolumeOption `json:"labels"`
|
||||
Driver string `json:"driver"`
|
||||
Mountpoint string `json:"mountpoint"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
Options []VolumeOption `json:"options"`
|
||||
}
|
||||
type VolumeCreate struct {
|
||||
Name string `json:"name" validate:"required"`
|
||||
@@ -221,7 +222,10 @@ type VolumeCreate struct {
|
||||
Options []string `json:"options"`
|
||||
Labels []string `json:"labels"`
|
||||
}
|
||||
|
||||
type VolumeOption struct {
|
||||
Key string `json:"key"`
|
||||
Value string `json:"value"`
|
||||
}
|
||||
type BatchDelete struct {
|
||||
TaskID string `json:"taskID"`
|
||||
Force bool `json:"force"`
|
||||
|
||||
@@ -23,9 +23,24 @@ type DashboardBase struct {
|
||||
CPULogicalCores int `json:"cpuLogicalCores"`
|
||||
CPUModelName string `json:"cpuModelName"`
|
||||
|
||||
QuickJumps []QuickJump `json:"quickJump"`
|
||||
CurrentInfo DashboardCurrent `json:"currentInfo"`
|
||||
}
|
||||
|
||||
type ChangeQuicks struct {
|
||||
Quicks []QuickJump `json:"quicks"`
|
||||
}
|
||||
|
||||
type QuickJump struct {
|
||||
ID uint `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Title string `json:"title"`
|
||||
Detail string `json:"detail"`
|
||||
Recommend int `json:"recommend"`
|
||||
IsShow bool `json:"isShow"`
|
||||
Router string `json:"router"`
|
||||
}
|
||||
|
||||
type OsInfo struct {
|
||||
OS string `json:"os"`
|
||||
Platform string `json:"platform"`
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
package dto
|
||||
|
||||
type LsblkDevice struct {
|
||||
Name string `json:"name"`
|
||||
Size string `json:"size"`
|
||||
Type string `json:"type"`
|
||||
MountPoint *string `json:"mountpoint"`
|
||||
FsType *string `json:"fstype"`
|
||||
Model *string `json:"model"`
|
||||
Serial string `json:"serial"`
|
||||
Tran string `json:"tran"`
|
||||
Rota bool `json:"rota"`
|
||||
Children []LsblkDevice `json:"children,omitempty"`
|
||||
}
|
||||
|
||||
type LsblkOutput struct {
|
||||
BlockDevices []LsblkDevice `json:"blockdevices"`
|
||||
}
|
||||
|
||||
type DiskFormatRequest struct {
|
||||
Device string `json:"device" `
|
||||
Filesystem string `json:"filesystem" `
|
||||
Label string `json:"label,omitempty" `
|
||||
QuickFormat bool `json:"quickFormat,omitempty"`
|
||||
}
|
||||
@@ -17,7 +17,8 @@ type RuleSearch struct {
|
||||
}
|
||||
|
||||
type FirewallOperation struct {
|
||||
Operation string `json:"operation" validate:"required,oneof=start stop restart disablePing enablePing"`
|
||||
Operation string `json:"operation" validate:"required,oneof=start stop restart disablePing enablePing"`
|
||||
WithDockerRestart bool `json:"withDockerRestart"`
|
||||
}
|
||||
|
||||
type PortRuleOperate struct {
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
package request
|
||||
|
||||
type DiskPartitionRequest struct {
|
||||
Device string `json:"device" validate:"required"`
|
||||
Filesystem string `json:"filesystem" validate:"required,oneof=ext4 xfs"`
|
||||
Label string `json:"label"`
|
||||
AutoMount bool `json:"autoMount"`
|
||||
MountPoint string `json:"mountPoint" validate:"required"`
|
||||
}
|
||||
|
||||
type DiskMountRequest struct {
|
||||
Device string `json:"device" validate:"required"`
|
||||
MountPoint string `json:"mountPoint" validate:"required"`
|
||||
Filesystem string `json:"filesystem" validate:"required,oneof=ext4 xfs"`
|
||||
AutoMount bool `json:"autoMount"`
|
||||
}
|
||||
|
||||
type DiskUnmountRequest struct {
|
||||
MountPoint string `json:"mountPoint" validate:"required"`
|
||||
}
|
||||
@@ -20,6 +20,7 @@ type McpServerCreate struct {
|
||||
HostIP string `json:"hostIP"`
|
||||
StreamableHttpPath string `json:"streamableHttpPath"`
|
||||
OutputTransport string `json:"outputTransport" validate:"required"`
|
||||
Type string `json:"type" validate:"required"`
|
||||
}
|
||||
|
||||
type McpServerUpdate struct {
|
||||
|
||||
@@ -81,8 +81,8 @@ type NginxCommonReq struct {
|
||||
|
||||
type NginxAntiLeechUpdate struct {
|
||||
WebsiteID uint `json:"websiteID" validate:"required"`
|
||||
Extends string `json:"extends" validate:"required"`
|
||||
Return string `json:"return" validate:"required"`
|
||||
Extends string `json:"extends"`
|
||||
Return string `json:"return"`
|
||||
Enable bool `json:"enable" `
|
||||
ServerNames []string `json:"serverNames"`
|
||||
Cache bool `json:"cache"`
|
||||
|
||||
@@ -6,6 +6,8 @@ type WebsiteSSLSearch struct {
|
||||
dto.PageInfo
|
||||
AcmeAccountID string `json:"acmeAccountID"`
|
||||
Domain string `json:"domain"`
|
||||
OrderBy string `json:"orderBy" validate:"required,oneof=expire_date"`
|
||||
Order string `json:"order" validate:"required,oneof=null ascending descending"`
|
||||
}
|
||||
|
||||
type WebsiteSSLCreate struct {
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
package response
|
||||
|
||||
type DiskInfo struct {
|
||||
DiskBasicInfo
|
||||
Partitions []DiskBasicInfo `json:"partitions"`
|
||||
}
|
||||
|
||||
type DiskBasicInfo struct {
|
||||
Device string `json:"device"`
|
||||
Size string `json:"size"`
|
||||
Model string `json:"model"`
|
||||
DiskType string `json:"diskType"`
|
||||
IsRemovable bool `json:"isRemovable"`
|
||||
IsSystem bool `json:"isSystem"`
|
||||
Filesystem string `json:"filesystem"`
|
||||
Used string `json:"used"`
|
||||
Avail string `json:"avail"`
|
||||
UsePercent int `json:"usePercent"`
|
||||
MountPoint string `json:"mountPoint"`
|
||||
IsMounted bool `json:"isMounted"`
|
||||
Serial string `json:"serial"`
|
||||
}
|
||||
|
||||
type CompleteDiskInfo struct {
|
||||
Disks []DiskInfo `json:"disks"`
|
||||
UnpartitionedDisks []DiskBasicInfo `json:"unpartitionedDisks"`
|
||||
SystemDisk *DiskInfo `json:"systemDisk"`
|
||||
TotalDisks int `json:"totalDisks"`
|
||||
TotalCapacity int64 `json:"totalCapacity"`
|
||||
}
|
||||
|
||||
type MountInfo struct {
|
||||
Device string `json:"device"`
|
||||
MountPoint string `json:"mountPoint"`
|
||||
Filesystem string `json:"filesystem"`
|
||||
Options string `json:"options"`
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package response
|
||||
|
||||
type ComponentInfo struct {
|
||||
Exists bool `json:"exists"`
|
||||
Version string `json:"version"`
|
||||
Path string `json:"path"`
|
||||
Error string `json:"error"`
|
||||
}
|
||||
@@ -4,3 +4,13 @@ type AppLauncher struct {
|
||||
BaseModel
|
||||
Key string `json:"key"`
|
||||
}
|
||||
|
||||
type QuickJump struct {
|
||||
BaseModel
|
||||
Name string `json:"name"`
|
||||
Title string `json:"title"`
|
||||
Detail string `json:"detail"`
|
||||
Recommend int `json:"recommend"`
|
||||
IsShow bool `json:"isShow"`
|
||||
Router string `json:"router"`
|
||||
}
|
||||
|
||||
+18
-1
@@ -1,14 +1,31 @@
|
||||
package model
|
||||
|
||||
import "time"
|
||||
|
||||
type Clam struct {
|
||||
BaseModel
|
||||
|
||||
Name string `gorm:"not null" json:"name"`
|
||||
Status string `json:"status"`
|
||||
Path string `gorm:"not null" json:"path"`
|
||||
InfectedStrategy string `json:"infectedStrategy"`
|
||||
InfectedDir string `json:"infectedDir"`
|
||||
Spec string `json:"spec"`
|
||||
EntryID int `json:"entryID"`
|
||||
Description string `json:"description"`
|
||||
|
||||
Status string `json:"status"`
|
||||
IsExecuting bool `json:"isExecuting"`
|
||||
}
|
||||
|
||||
type ClamRecord struct {
|
||||
BaseModel
|
||||
|
||||
ClamID uint `json:"clamID"`
|
||||
TaskID string `json:"taskID"`
|
||||
StartTime time.Time `json:"startTime"`
|
||||
ScanTime string `json:"scanTime"`
|
||||
InfectedFiles string `json:"infectedFiles"`
|
||||
TotalError string `json:"totalError"`
|
||||
Status string `json:"status"`
|
||||
Message string `json:"message"`
|
||||
}
|
||||
|
||||
@@ -4,8 +4,6 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
// BackupAccounts ---> SourceAccountIDs
|
||||
// BackupAccounts ---> DownloadAccountID
|
||||
type Cronjob struct {
|
||||
BaseModel
|
||||
|
||||
@@ -40,10 +38,11 @@ type Cronjob struct {
|
||||
IgnoreErr bool `json:"ignoreErr"`
|
||||
RetainCopies uint64 `json:"retainCopies"`
|
||||
|
||||
Status string `json:"status"`
|
||||
EntryIDs string `json:"entryIDs"`
|
||||
Records []JobRecords `json:"records"`
|
||||
Secret string `json:"secret"`
|
||||
IsExecuting bool `json:"isExecuting"`
|
||||
Status string `json:"status"`
|
||||
EntryIDs string `json:"entryIDs"`
|
||||
Records []JobRecords `json:"records"`
|
||||
Secret string `json:"secret"`
|
||||
}
|
||||
|
||||
type JobRecords struct {
|
||||
|
||||
@@ -17,4 +17,5 @@ type McpServer struct {
|
||||
HostIP string `json:"hostIP"`
|
||||
StreamableHttpPath string `json:"streamableHttpPath"`
|
||||
OutputTransport string `json:"outputTransport"`
|
||||
Type string `json:"type"`
|
||||
}
|
||||
|
||||
@@ -15,3 +15,13 @@ type NodeInfo struct {
|
||||
ServerCrt string `json:"serverCrt"`
|
||||
ServerKey string `json:"serverKey"`
|
||||
}
|
||||
|
||||
type LocalConnInfo struct {
|
||||
Addr string `json:"addr"`
|
||||
Port uint `json:"port"`
|
||||
User string `json:"user"`
|
||||
AuthMode string `json:"authMode"`
|
||||
Password string `json:"password"`
|
||||
PrivateKey string `json:"privateKey"`
|
||||
PassPhrase string `json:"passPhrase"`
|
||||
}
|
||||
|
||||
@@ -13,6 +13,10 @@ type ILauncherRepo interface {
|
||||
Create(launcher *model.AppLauncher) error
|
||||
Save(launcher *model.AppLauncher) error
|
||||
Delete(opts ...DBOption) error
|
||||
|
||||
GetQuickJump(opts ...DBOption) (model.QuickJump, error)
|
||||
ListQuickJump(withAll bool) []model.QuickJump
|
||||
UpdateQuicks(quicks []model.QuickJump) error
|
||||
}
|
||||
|
||||
func NewILauncherRepo() ILauncherRepo {
|
||||
@@ -57,3 +61,45 @@ func (u *LauncherRepo) Delete(opts ...DBOption) error {
|
||||
}
|
||||
return db.Delete(&model.AppLauncher{}).Error
|
||||
}
|
||||
|
||||
func (u *LauncherRepo) GetQuickJump(opts ...DBOption) (model.QuickJump, error) {
|
||||
var launcher model.QuickJump
|
||||
db := global.DB
|
||||
for _, opt := range opts {
|
||||
db = opt(db)
|
||||
}
|
||||
err := db.First(&launcher).Error
|
||||
return launcher, err
|
||||
}
|
||||
func (u *LauncherRepo) ListQuickJump(withAll bool) []model.QuickJump {
|
||||
var quicks []model.QuickJump
|
||||
if withAll {
|
||||
_ = global.DB.Find(&quicks).Error
|
||||
} else {
|
||||
_ = global.DB.Where("is_show = ?", true).Find(&quicks).Error
|
||||
}
|
||||
if !withAll && len(quicks) == 0 {
|
||||
return []model.QuickJump{
|
||||
{Name: "Website", Title: "menu.website", Recommend: 10, IsShow: true, Router: "/websites"},
|
||||
{Name: "Database", Title: "menu.database", Recommend: 30, IsShow: true, Router: "/databases"},
|
||||
{Name: "Cronjob", Title: "menu.cronjob", Recommend: 50, IsShow: true, Router: "/cronjobs"},
|
||||
{Name: "AppInstalled", Title: "home.appInstalled", Recommend: 70, IsShow: true, Router: "/apps/installed"},
|
||||
}
|
||||
}
|
||||
|
||||
return quicks
|
||||
}
|
||||
func (u *LauncherRepo) UpdateQuicks(quicks []model.QuickJump) error {
|
||||
tx := global.DB.Begin()
|
||||
for _, item := range quicks {
|
||||
if err := tx.Model(&model.QuickJump{}).Where("id = ?", item.ID).Updates(map[string]interface{}{
|
||||
"is_show": item.IsShow,
|
||||
"detail": item.Detail,
|
||||
}).Error; err != nil {
|
||||
tx.Rollback()
|
||||
return err
|
||||
}
|
||||
}
|
||||
tx.Commit()
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -1,8 +1,13 @@
|
||||
package repo
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/agent/app/model"
|
||||
"github.com/1Panel-dev/1Panel/agent/constant"
|
||||
"github.com/1Panel-dev/1Panel/agent/global"
|
||||
"github.com/google/uuid"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type ClamRepo struct{}
|
||||
@@ -14,6 +19,14 @@ type IClamRepo interface {
|
||||
Delete(opts ...DBOption) error
|
||||
Get(opts ...DBOption) (model.Clam, error)
|
||||
List(opts ...DBOption) ([]model.Clam, error)
|
||||
|
||||
WithByClamID(id uint) DBOption
|
||||
ListRecord(opts ...DBOption) ([]model.ClamRecord, error)
|
||||
RecordFirst(id uint) (model.ClamRecord, error)
|
||||
DeleteRecord(opts ...DBOption) error
|
||||
StartRecords(clamID uint) model.ClamRecord
|
||||
EndRecords(record model.ClamRecord, status, message string)
|
||||
PageRecords(page, size int, opts ...DBOption) (int64, []model.ClamRecord, error)
|
||||
}
|
||||
|
||||
func NewIClamRepo() IClamRepo {
|
||||
@@ -67,3 +80,69 @@ func (u *ClamRepo) Delete(opts ...DBOption) error {
|
||||
}
|
||||
return db.Delete(&model.Clam{}).Error
|
||||
}
|
||||
|
||||
func (c *ClamRepo) WithByClamID(id uint) DBOption {
|
||||
return func(g *gorm.DB) *gorm.DB {
|
||||
return g.Where("clam_id = ?", id)
|
||||
}
|
||||
}
|
||||
|
||||
func (u *ClamRepo) ListRecord(opts ...DBOption) ([]model.ClamRecord, error) {
|
||||
var record []model.ClamRecord
|
||||
db := global.DB
|
||||
for _, opt := range opts {
|
||||
db = opt(db)
|
||||
}
|
||||
err := db.Find(&record).Error
|
||||
return record, err
|
||||
}
|
||||
|
||||
func (u *ClamRepo) RecordFirst(id uint) (model.ClamRecord, error) {
|
||||
var record model.ClamRecord
|
||||
err := global.DB.Where("clam_id = ?", id).Order("created_at desc").First(&record).Error
|
||||
return record, err
|
||||
}
|
||||
|
||||
func (u *ClamRepo) PageRecords(page, size int, opts ...DBOption) (int64, []model.ClamRecord, error) {
|
||||
var records []model.ClamRecord
|
||||
db := global.DB.Model(&model.ClamRecord{})
|
||||
for _, opt := range opts {
|
||||
db = opt(db)
|
||||
}
|
||||
count := int64(0)
|
||||
db = db.Count(&count)
|
||||
err := db.Order("created_at desc").Limit(size).Offset(size * (page - 1)).Find(&records).Error
|
||||
return count, records, err
|
||||
}
|
||||
func (u *ClamRepo) StartRecords(clamID uint) model.ClamRecord {
|
||||
var record model.ClamRecord
|
||||
record.StartTime = time.Now()
|
||||
record.ClamID = clamID
|
||||
record.TaskID = uuid.New().String()
|
||||
record.Status = constant.StatusWaiting
|
||||
if err := global.DB.Create(&record).Error; err != nil {
|
||||
global.LOG.Errorf("create record status failed, err: %v", err)
|
||||
}
|
||||
_ = u.Update(clamID, map[string]interface{}{"is_executing": true})
|
||||
return record
|
||||
}
|
||||
func (u *ClamRepo) EndRecords(record model.ClamRecord, status, message string) {
|
||||
upMap := make(map[string]interface{})
|
||||
upMap["status"] = status
|
||||
upMap["message"] = message
|
||||
upMap["task_id"] = record.TaskID
|
||||
upMap["scan_time"] = record.ScanTime
|
||||
upMap["infected_files"] = record.InfectedFiles
|
||||
upMap["total_error"] = record.TotalError
|
||||
if err := global.DB.Model(&model.ClamRecord{}).Where("id = ?", record.ID).Updates(upMap).Error; err != nil {
|
||||
global.LOG.Errorf("update record status failed, err: %v", err)
|
||||
}
|
||||
_ = u.Update(record.ClamID, map[string]interface{}{"is_executing": false})
|
||||
}
|
||||
func (u *ClamRepo) DeleteRecord(opts ...DBOption) error {
|
||||
db := global.DB
|
||||
for _, opt := range opts {
|
||||
db = opt(db)
|
||||
}
|
||||
return db.Delete(&model.ClamRecord{}).Error
|
||||
}
|
||||
|
||||
@@ -29,9 +29,10 @@ type ICronjobRepo interface {
|
||||
Update(id uint, vars map[string]interface{}) error
|
||||
Delete(opts ...DBOption) error
|
||||
DeleteRecord(opts ...DBOption) error
|
||||
StartRecords(cronjobID uint, targetPath, cronjobType string) model.JobRecords
|
||||
StartRecords(cronjobID uint) model.JobRecords
|
||||
UpdateRecords(id uint, vars map[string]interface{}) error
|
||||
EndRecords(record model.JobRecords, status, message, records string)
|
||||
AddFailedRecord(cronjobID uint, message string)
|
||||
PageRecords(page, size int, opts ...DBOption) (int64, []model.JobRecords, error)
|
||||
}
|
||||
|
||||
@@ -143,7 +144,7 @@ func (c *CronjobRepo) WithByRecordDropID(id int) DBOption {
|
||||
}
|
||||
}
|
||||
|
||||
func (u *CronjobRepo) StartRecords(cronjobID uint, targetPath, cronjobType string) model.JobRecords {
|
||||
func (u *CronjobRepo) StartRecords(cronjobID uint) model.JobRecords {
|
||||
var record model.JobRecords
|
||||
record.StartTime = time.Now()
|
||||
record.CronjobID = cronjobID
|
||||
@@ -152,6 +153,7 @@ func (u *CronjobRepo) StartRecords(cronjobID uint, targetPath, cronjobType strin
|
||||
if err := global.DB.Create(&record).Error; err != nil {
|
||||
global.LOG.Errorf("create record status failed, err: %v", err)
|
||||
}
|
||||
_ = u.Update(cronjobID, map[string]interface{}{"is_executing": true})
|
||||
return record
|
||||
}
|
||||
func (u *CronjobRepo) EndRecords(record model.JobRecords, status, message, records string) {
|
||||
@@ -165,6 +167,17 @@ func (u *CronjobRepo) EndRecords(record model.JobRecords, status, message, recor
|
||||
if err := global.DB.Model(&model.JobRecords{}).Where("id = ?", record.ID).Updates(errMap).Error; err != nil {
|
||||
global.LOG.Errorf("update record status failed, err: %v", err)
|
||||
}
|
||||
_ = u.Update(record.CronjobID, map[string]interface{}{"is_executing": false})
|
||||
}
|
||||
func (u *CronjobRepo) AddFailedRecord(cronjobID uint, message string) {
|
||||
var record model.JobRecords
|
||||
record.StartTime = time.Now()
|
||||
record.CronjobID = cronjobID
|
||||
record.Status = constant.StatusFailed
|
||||
record.Message = message
|
||||
if err := global.DB.Create(&record).Error; err != nil {
|
||||
global.LOG.Errorf("create record status failed, err: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func (u *CronjobRepo) Save(id uint, cronjob model.Cronjob) error {
|
||||
|
||||
@@ -177,7 +177,24 @@ func (a AlertService) UpdateAlert(req dto.AlertUpdate) error {
|
||||
}
|
||||
|
||||
func (a AlertService) DeleteAlert(id uint) error {
|
||||
return alertRepo.Delete(repo.WithByID(id))
|
||||
alertInfo, _ := alertRepo.Get(repo.WithByID(id))
|
||||
if alertInfo.ID == 0 {
|
||||
return buserr.New("ErrRecordNotFound")
|
||||
}
|
||||
err := alertRepo.Delete(repo.WithByID(id))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
alerts, err := a.GetAlerts()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(alerts) > 0 {
|
||||
NewIAlertTaskHelper().InitTask(alertInfo.Type)
|
||||
} else {
|
||||
NewIAlertTaskHelper().StopTask()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a AlertService) GetAlert(id uint) (dto.AlertDTO, error) {
|
||||
|
||||
@@ -377,16 +377,17 @@ func loadLoadInfo(alert dto.AlertDTO) {
|
||||
|
||||
func loadDiskUsage(alert dto.AlertDTO) {
|
||||
newDate, err := alertRepo.GetTaskLog(alert.Type, alert.ID)
|
||||
if err != nil || !isAlertDue(newDate) {
|
||||
if err != nil {
|
||||
global.LOG.Errorf("record not found, err: %v", err)
|
||||
return
|
||||
}
|
||||
if strings.Contains(alert.Project, "all") {
|
||||
err = processAllDisks(alert)
|
||||
} else {
|
||||
err = processSingleDisk(alert)
|
||||
if isAlertDue(newDate) {
|
||||
if strings.Contains(alert.Project, "all") {
|
||||
err = processAllDisks(alert)
|
||||
} else {
|
||||
err = processSingleDisk(alert)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func loadPanelLogin(alert dto.AlertDTO) {
|
||||
@@ -515,12 +516,14 @@ func loadNodeException(alert dto.AlertDTO) {
|
||||
},
|
||||
}
|
||||
newDate, err := alertRepo.GetTaskLog(alert.Type, alert.ID)
|
||||
if err != nil || !isAlertDue(newDate) {
|
||||
if err != nil {
|
||||
global.LOG.Errorf("record not found, err: %v", err)
|
||||
return
|
||||
}
|
||||
sender := NewAlertSender(alert, quotaType)
|
||||
sender.ResourceSend(strconv.Itoa(int(failCount)), params)
|
||||
if isAlertDue(newDate) {
|
||||
sender := NewAlertSender(alert, quotaType)
|
||||
sender.ResourceSend(strconv.Itoa(int(failCount)), params)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -542,12 +545,14 @@ func loadLicenseException(alert dto.AlertDTO) {
|
||||
},
|
||||
}
|
||||
newDate, err := alertRepo.GetTaskLog(alert.Type, alert.ID)
|
||||
if err != nil || !isAlertDue(newDate) {
|
||||
if err != nil {
|
||||
global.LOG.Errorf("record not found, err: %v", err)
|
||||
return
|
||||
}
|
||||
sender := NewAlertSender(alert, quotaType)
|
||||
sender.ResourceSend(strconv.Itoa(int(failCount)), params)
|
||||
if isAlertDue(newDate) {
|
||||
sender := NewAlertSender(alert, quotaType)
|
||||
sender.ResourceSend(strconv.Itoa(int(failCount)), params)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -814,16 +819,11 @@ func processAllDisks(alert dto.AlertDTO) error {
|
||||
global.LOG.Errorf("error getting disk list, err: %v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
var flag bool
|
||||
for _, item := range diskList {
|
||||
if success, err := checkAndCreateDiskAlert(alert, item.Path); err == nil && success {
|
||||
flag = true
|
||||
global.LOG.Infof("disk alert pushed successfully for %s", item.Path)
|
||||
}
|
||||
}
|
||||
if flag {
|
||||
global.LOG.Info("all disk alert push successful")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -833,7 +833,7 @@ func processSingleDisk(alert dto.AlertDTO) error {
|
||||
return err
|
||||
}
|
||||
if success {
|
||||
global.LOG.Info("disk alert push successful")
|
||||
global.LOG.Infof("disk alert pushed successfully for %s", alert.Project)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -1881,6 +1881,10 @@ func ignoreUpdate(installed model.AppInstall) bool {
|
||||
if installed.App.Type == "php" || installed.Status == constant.StatusInstalling {
|
||||
return true
|
||||
}
|
||||
ignores, _ := appIgnoreUpgradeRepo.List(appDetailRepo.WithAppId(installed.AppId), appIgnoreUpgradeRepo.WithScope("all"))
|
||||
if len(ignores) > 0 {
|
||||
return true
|
||||
}
|
||||
if installed.App.Key == constant.AppMysql {
|
||||
majorVersion := getMajorVersion(installed.Version)
|
||||
appDetails, _ := appDetailRepo.GetBy(appDetailRepo.WithAppId(installed.App.ID))
|
||||
@@ -1896,10 +1900,6 @@ func ignoreUpdate(installed model.AppInstall) bool {
|
||||
return true
|
||||
}
|
||||
}
|
||||
ignores, _ := appIgnoreUpgradeRepo.List(appDetailRepo.WithAppId(installed.AppId), appIgnoreUpgradeRepo.WithScope("all"))
|
||||
if len(ignores) > 0 {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
|
||||
@@ -39,6 +39,7 @@ type IBackupService interface {
|
||||
Delete(id uint) error
|
||||
RefreshToken(req dto.OperateByID) error
|
||||
GetLocalDir() (string, error)
|
||||
UploadForRecover(req dto.UploadForRecover) error
|
||||
|
||||
MysqlBackup(db dto.CommonBackup) error
|
||||
PostgresqlBackup(db dto.CommonBackup) error
|
||||
@@ -309,6 +310,16 @@ func (u *BackupService) RefreshToken(req dto.OperateByID) error {
|
||||
return backupRepo.Save(&backup)
|
||||
}
|
||||
|
||||
func (u *BackupService) UploadForRecover(req dto.UploadForRecover) error {
|
||||
fileOp := files.NewFileOp()
|
||||
if !fileOp.Stat(req.TargetDir) {
|
||||
if err := fileOp.CreateDir(req.TargetDir, constant.DirPerm); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return fileOp.Copy(req.FilePath, req.TargetDir)
|
||||
}
|
||||
|
||||
func (u *BackupService) checkBackupConn(backup *model.BackupAccount) (bool, error) {
|
||||
client, err := newClient(backup, false)
|
||||
if err != nil {
|
||||
@@ -445,11 +456,15 @@ func uploadWithMap(taskItem task.Task, accountMap map[string]backupClientHelper,
|
||||
taskItem.LogFailed(i18n.GetMsgWithDetail("LoadBackupFailed", itemBackup.message))
|
||||
continue
|
||||
}
|
||||
name := itemBackup.name
|
||||
if itemBackup.name == "localhost" {
|
||||
name = i18n.GetMsgByKey("Localhost")
|
||||
}
|
||||
taskItem.LogStart(i18n.GetMsgWithMap("UploadFile", map[string]interface{}{
|
||||
"file": path.Join(itemBackup.backupPath, dst),
|
||||
"backup": itemBackup.name,
|
||||
"backup": name,
|
||||
}))
|
||||
for i := 0; i < int(retry); i++ {
|
||||
for i := 0; i < int(retry)+1; i++ {
|
||||
_, err := itemBackup.client.Upload(src, path.Join(itemBackup.backupPath, dst))
|
||||
taskItem.LogWithStatus(i18n.GetMsgByKey("Upload"), err)
|
||||
if err != nil {
|
||||
|
||||
@@ -59,44 +59,11 @@ func (u *BackupService) MysqlRecover(req dto.CommonRecover) error {
|
||||
}
|
||||
|
||||
func (u *BackupService) MysqlRecoverByUpload(req dto.CommonRecover) error {
|
||||
file := req.File
|
||||
fileName := path.Base(req.File)
|
||||
if strings.HasSuffix(fileName, ".tar.gz") {
|
||||
fileNameItem := time.Now().Format(constant.DateTimeSlimLayout)
|
||||
dstDir := fmt.Sprintf("%s/%s", path.Dir(req.File), fileNameItem)
|
||||
fileOp := files.NewFileOp()
|
||||
if !fileOp.Stat(dstDir) {
|
||||
if err := fileOp.CreateDir(dstDir, os.ModePerm); err != nil {
|
||||
return fmt.Errorf("mkdir %s failed, err: %v", dstDir, err)
|
||||
}
|
||||
}
|
||||
if err := fileOp.TarGzExtractPro(req.File, dstDir, ""); err != nil {
|
||||
_ = os.RemoveAll(dstDir)
|
||||
return err
|
||||
}
|
||||
global.LOG.Infof("decompress file %s successful, now start to check test.sql is exist", req.File)
|
||||
hasTestSql := false
|
||||
_ = filepath.Walk(dstDir, func(path string, info os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
if !info.IsDir() && info.Name() == "test.sql" {
|
||||
hasTestSql = true
|
||||
file = path
|
||||
fileName = "test.sql"
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if !hasTestSql {
|
||||
_ = os.RemoveAll(dstDir)
|
||||
return fmt.Errorf("no such file named test.sql in %s", fileName)
|
||||
}
|
||||
defer func() {
|
||||
_ = os.RemoveAll(dstDir)
|
||||
}()
|
||||
recoveFile, err := loadSqlFile(req.File)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
req.File = path.Dir(file) + "/" + fileName
|
||||
req.File = recoveFile
|
||||
if err := handleMysqlRecover(req, nil, false, req.TaskID); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -243,3 +210,55 @@ func doMysqlBackup(db DatabaseHelper, targetDir, fileName string) error {
|
||||
}
|
||||
return cli.Backup(backupInfo)
|
||||
}
|
||||
|
||||
func loadSqlFile(file string) (string, error) {
|
||||
if !strings.HasSuffix(file, ".tar.gz") && !strings.HasSuffix(file, ".zip") {
|
||||
return file, nil
|
||||
}
|
||||
fileName := path.Base(file)
|
||||
fileDir := path.Dir(file)
|
||||
fileNameItem := time.Now().Format(constant.DateTimeSlimLayout)
|
||||
dstDir := fmt.Sprintf("%s/%s", fileDir, fileNameItem)
|
||||
_ = os.Mkdir(dstDir, constant.DirPerm)
|
||||
if strings.HasSuffix(fileName, ".tar.gz") {
|
||||
fileOp := files.NewFileOp()
|
||||
if err := fileOp.TarGzExtractPro(file, dstDir, ""); err != nil {
|
||||
_ = os.RemoveAll(dstDir)
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
if strings.HasSuffix(fileName, ".zip") {
|
||||
archiver, err := files.NewShellArchiver(files.Zip)
|
||||
if err != nil {
|
||||
_ = os.RemoveAll(dstDir)
|
||||
return "", err
|
||||
}
|
||||
if err := archiver.Extract(file, dstDir, ""); err != nil {
|
||||
_ = os.RemoveAll(dstDir)
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
global.LOG.Infof("decompress file %s successful, now start to check test.sql is exist", file)
|
||||
var sqlFiles []string
|
||||
hasTestSql := false
|
||||
_ = filepath.Walk(dstDir, func(path string, info os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
if !info.IsDir() && strings.HasSuffix(info.Name(), ".sql") {
|
||||
sqlFiles = append(sqlFiles, path)
|
||||
if info.Name() == "test.sql" {
|
||||
hasTestSql = true
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if len(sqlFiles) == 1 {
|
||||
return sqlFiles[0], nil
|
||||
}
|
||||
if !hasTestSql {
|
||||
_ = os.RemoveAll(dstDir)
|
||||
return "", fmt.Errorf("no such file named test.sql in %s", fileName)
|
||||
}
|
||||
return "", nil
|
||||
}
|
||||
|
||||
@@ -4,8 +4,6 @@ import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/agent/app/repo"
|
||||
@@ -59,44 +57,11 @@ func (u *BackupService) PostgresqlRecover(req dto.CommonRecover) error {
|
||||
}
|
||||
|
||||
func (u *BackupService) PostgresqlRecoverByUpload(req dto.CommonRecover) error {
|
||||
file := req.File
|
||||
fileName := path.Base(req.File)
|
||||
if strings.HasSuffix(fileName, ".tar.gz") {
|
||||
fileNameItem := time.Now().Format(constant.DateTimeSlimLayout)
|
||||
dstDir := fmt.Sprintf("%s/%s", path.Dir(req.File), fileNameItem)
|
||||
fileOp := files.NewFileOp()
|
||||
if !fileOp.Stat(dstDir) {
|
||||
if err := fileOp.CreateDir(dstDir, os.ModePerm); err != nil {
|
||||
return fmt.Errorf("mkdir %s failed, err: %v", dstDir, err)
|
||||
}
|
||||
}
|
||||
if err := fileOp.TarGzExtractPro(req.File, dstDir, ""); err != nil {
|
||||
_ = os.RemoveAll(dstDir)
|
||||
return err
|
||||
}
|
||||
global.LOG.Infof("decompress file %s successful, now start to check test.sql is exist", req.File)
|
||||
hasTestSql := false
|
||||
_ = filepath.Walk(dstDir, func(path string, info os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
if !info.IsDir() && info.Name() == "test.sql" {
|
||||
hasTestSql = true
|
||||
file = path
|
||||
fileName = "test.sql"
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if !hasTestSql {
|
||||
_ = os.RemoveAll(dstDir)
|
||||
return fmt.Errorf("no such file named test.sql in %s", fileName)
|
||||
}
|
||||
defer func() {
|
||||
_ = os.RemoveAll(dstDir)
|
||||
}()
|
||||
recoveFile, err := loadSqlFile(req.File)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
req.File = path.Dir(file) + "/" + fileName
|
||||
req.File = recoveFile
|
||||
if err := handlePostgresqlRecover(req, nil, false); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
+98
-232
@@ -6,33 +6,25 @@ import (
|
||||
"os"
|
||||
"os/exec"
|
||||
"path"
|
||||
"sort"
|
||||
"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/repo"
|
||||
"github.com/1Panel-dev/1Panel/agent/app/task"
|
||||
"github.com/1Panel-dev/1Panel/agent/buserr"
|
||||
"github.com/1Panel-dev/1Panel/agent/constant"
|
||||
"github.com/1Panel-dev/1Panel/agent/global"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/alert_push"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/clam"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/cmd"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/common"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/systemctl"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/xpack"
|
||||
"github.com/jinzhu/copier"
|
||||
"github.com/robfig/cron/v3"
|
||||
)
|
||||
|
||||
const (
|
||||
clamServiceNameCentOs = "clamd@scan.service"
|
||||
clamServiceNameUbuntu = "clamav-daemon.service"
|
||||
freshClamService = "clamav-freshclam.service"
|
||||
resultDir = "clamav"
|
||||
)
|
||||
|
||||
type ClamService struct {
|
||||
serviceName string
|
||||
}
|
||||
@@ -45,13 +37,13 @@ type IClamService interface {
|
||||
Update(req dto.ClamUpdate) error
|
||||
UpdateStatus(id uint, status string) error
|
||||
Delete(req dto.ClamDelete) error
|
||||
HandleOnce(req dto.OperateByID) error
|
||||
HandleOnce(id uint) error
|
||||
|
||||
LoadFile(req dto.ClamFileReq) (string, error)
|
||||
UpdateFile(req dto.UpdateByNameAndFile) error
|
||||
LoadRecords(req dto.ClamLogSearch) (int64, interface{}, error)
|
||||
CleanRecord(req dto.OperateByID) error
|
||||
|
||||
LoadRecordLog(req dto.ClamLogReq) (string, error)
|
||||
SearchRecords(req dto.ClamLogSearch) (int64, interface{}, error)
|
||||
CleanRecord(id uint) error
|
||||
}
|
||||
|
||||
func NewIClamService() IClamService {
|
||||
@@ -62,22 +54,22 @@ func (c *ClamService) LoadBaseInfo() (dto.ClamBaseInfo, error) {
|
||||
var baseInfo dto.ClamBaseInfo
|
||||
baseInfo.Version = "-"
|
||||
baseInfo.FreshVersion = "-"
|
||||
exist1, _ := systemctl.IsExist(clamServiceNameCentOs)
|
||||
exist1, _ := systemctl.IsExist(constant.ClamServiceNameCentOs)
|
||||
if exist1 {
|
||||
c.serviceName = clamServiceNameCentOs
|
||||
c.serviceName = constant.ClamServiceNameCentOs
|
||||
baseInfo.IsExist = true
|
||||
baseInfo.IsActive, _ = systemctl.IsActive(clamServiceNameCentOs)
|
||||
baseInfo.IsActive, _ = systemctl.IsActive(constant.ClamServiceNameCentOs)
|
||||
}
|
||||
exist2, _ := systemctl.IsExist(clamServiceNameUbuntu)
|
||||
exist2, _ := systemctl.IsExist(constant.ClamServiceNameUbuntu)
|
||||
if exist2 {
|
||||
c.serviceName = clamServiceNameUbuntu
|
||||
c.serviceName = constant.ClamServiceNameUbuntu
|
||||
baseInfo.IsExist = true
|
||||
baseInfo.IsActive, _ = systemctl.IsActive(clamServiceNameUbuntu)
|
||||
baseInfo.IsActive, _ = systemctl.IsActive(constant.ClamServiceNameUbuntu)
|
||||
}
|
||||
freshExist, _ := systemctl.IsExist(freshClamService)
|
||||
freshExist, _ := systemctl.IsExist(constant.FreshClamService)
|
||||
if freshExist {
|
||||
baseInfo.FreshIsExist = true
|
||||
baseInfo.FreshIsActive, _ = systemctl.IsActive(freshClamService)
|
||||
baseInfo.FreshIsActive, _ = systemctl.IsActive(constant.FreshClamService)
|
||||
}
|
||||
if !cmd.Which("clamdscan") {
|
||||
baseInfo.IsActive = false
|
||||
@@ -93,7 +85,7 @@ func (c *ClamService) LoadBaseInfo() (dto.ClamBaseInfo, error) {
|
||||
}
|
||||
}
|
||||
} else {
|
||||
_ = StopAllCronJob(false)
|
||||
_ = clam.CheckClamIsActive(false, clamRepo)
|
||||
}
|
||||
if baseInfo.FreshIsActive {
|
||||
version, err := cmd.RunDefaultWithStdoutBashC("freshclam --version")
|
||||
@@ -117,7 +109,7 @@ func (c *ClamService) Operate(operate string) error {
|
||||
}
|
||||
return nil
|
||||
case "fresh-start", "fresh-restart", "fresh-stop":
|
||||
stdout, err := cmd.RunDefaultWithStdoutBashCf("systemctl %s %s", strings.TrimPrefix(operate, "fresh-"), freshClamService)
|
||||
stdout, err := cmd.RunDefaultWithStdoutBashCf("systemctl %s %s", strings.TrimPrefix(operate, "fresh-"), constant.FreshClamService)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%s the %s failed, err: %s", operate, c.serviceName, stdout)
|
||||
}
|
||||
@@ -128,31 +120,25 @@ func (c *ClamService) Operate(operate string) error {
|
||||
}
|
||||
|
||||
func (c *ClamService) SearchWithPage(req dto.SearchClamWithPage) (int64, interface{}, error) {
|
||||
total, commands, err := clamRepo.Page(req.Page, req.PageSize, repo.WithByLikeName(req.Info), repo.WithOrderRuleBy(req.OrderBy, req.Order))
|
||||
total, clams, err := clamRepo.Page(req.Page, req.PageSize, repo.WithByLikeName(req.Info), repo.WithOrderRuleBy(req.OrderBy, req.Order))
|
||||
if err != nil {
|
||||
return 0, nil, err
|
||||
}
|
||||
var datas []dto.ClamInfo
|
||||
for _, command := range commands {
|
||||
for _, clam := range clams {
|
||||
var item dto.ClamInfo
|
||||
if err := copier.Copy(&item, &command); err != nil {
|
||||
if err := copier.Copy(&item, &clam); err != nil {
|
||||
return 0, nil, buserr.WithDetail("ErrStructTransform", err.Error(), nil)
|
||||
}
|
||||
item.LastHandleDate = "-"
|
||||
datas = append(datas, item)
|
||||
}
|
||||
nyc, _ := time.LoadLocation(common.LoadTimeZoneByCmd())
|
||||
for i := 0; i < len(datas); i++ {
|
||||
logPaths := loadFileByName(datas[i].Name)
|
||||
sort.Slice(logPaths, func(i, j int) bool {
|
||||
return logPaths[i] > logPaths[j]
|
||||
})
|
||||
if len(logPaths) != 0 {
|
||||
t1, err := time.ParseInLocation(constant.DateTimeSlimLayout, logPaths[0], nyc)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
datas[i].LastHandleDate = t1.Format(constant.DateTimeLayout)
|
||||
record, _ := clamRepo.RecordFirst(datas[i].ID)
|
||||
if record.ID != 0 {
|
||||
datas[i].LastRecordStatus = record.Status
|
||||
datas[i].LastRecordTime = record.StartTime.Format(constant.DateTimeLayout)
|
||||
} else {
|
||||
datas[i].LastRecordTime = "-"
|
||||
}
|
||||
alertBase := dto.AlertBase{
|
||||
AlertType: "clams",
|
||||
@@ -174,6 +160,9 @@ func (c *ClamService) Create(req dto.ClamCreate) error {
|
||||
if clam.ID != 0 {
|
||||
return buserr.New("ErrRecordExist")
|
||||
}
|
||||
if cmd.CheckIllegal(req.Path) {
|
||||
return buserr.New("ErrCmdIllegal")
|
||||
}
|
||||
if err := copier.Copy(&clam, &req); err != nil {
|
||||
return buserr.WithDetail("ErrStructTransform", err.Error(), nil)
|
||||
}
|
||||
@@ -181,7 +170,7 @@ func (c *ClamService) Create(req dto.ClamCreate) error {
|
||||
clam.InfectedDir = ""
|
||||
}
|
||||
if len(req.Spec) != 0 {
|
||||
entryID, err := xpack.StartClam(clam, false)
|
||||
entryID, err := xpack.StartClam(&clam, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -209,6 +198,9 @@ func (c *ClamService) Create(req dto.ClamCreate) error {
|
||||
}
|
||||
|
||||
func (c *ClamService) Update(req dto.ClamUpdate) error {
|
||||
if cmd.CheckIllegal(req.Path) {
|
||||
return buserr.New("ErrCmdIllegal")
|
||||
}
|
||||
clam, _ := clamRepo.Get(repo.WithByName(req.Name))
|
||||
if clam.ID == 0 {
|
||||
return buserr.New("ErrRecordNotFound")
|
||||
@@ -231,7 +223,7 @@ func (c *ClamService) Update(req dto.ClamUpdate) error {
|
||||
upMap["entry_id"] = 0
|
||||
}
|
||||
if len(req.Spec) != 0 && clam.Status != constant.StatusDisable {
|
||||
newEntryID, err := xpack.StartClam(clamItem, true)
|
||||
newEntryID, err := xpack.StartClam(&clamItem, true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -274,7 +266,7 @@ func (c *ClamService) UpdateStatus(id uint, status string) error {
|
||||
err error
|
||||
)
|
||||
if status == constant.StatusEnable {
|
||||
entryID, err = xpack.StartClam(clam, true)
|
||||
entryID, err = xpack.StartClam(&clam, true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -292,9 +284,10 @@ func (c *ClamService) Delete(req dto.ClamDelete) error {
|
||||
if clam.ID == 0 {
|
||||
continue
|
||||
}
|
||||
if req.RemoveRecord {
|
||||
_ = os.RemoveAll(path.Join(global.Dir.DataDir, resultDir, clam.Name))
|
||||
if len(clam.Spec) != 0 {
|
||||
global.Cron.Remove(cron.EntryID(clam.EntryID))
|
||||
}
|
||||
_ = c.CleanRecord(clam.ID)
|
||||
if req.RemoveInfected {
|
||||
_ = os.RemoveAll(path.Join(clam.InfectedDir, "1panel-infected", clam.Name))
|
||||
}
|
||||
@@ -309,128 +302,78 @@ func (c *ClamService) Delete(req dto.ClamDelete) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *ClamService) HandleOnce(req dto.OperateByID) error {
|
||||
if cleaned := StopAllCronJob(true); cleaned {
|
||||
func (c *ClamService) HandleOnce(id uint) error {
|
||||
if active := clam.CheckClamIsActive(true, clamRepo); !active {
|
||||
return buserr.New("ErrClamdscanNotFound")
|
||||
}
|
||||
clam, _ := clamRepo.Get(repo.WithByID(req.ID))
|
||||
if clam.ID == 0 {
|
||||
clamItem, _ := clamRepo.Get(repo.WithByID(id))
|
||||
if clamItem.ID == 0 {
|
||||
return buserr.New("ErrRecordNotFound")
|
||||
}
|
||||
if cmd.CheckIllegal(clam.Path) {
|
||||
return buserr.New("ErrCmdIllegal")
|
||||
}
|
||||
timeNow := time.Now().Format(constant.DateTimeSlimLayout)
|
||||
logFile := path.Join(global.Dir.DataDir, resultDir, clam.Name, timeNow)
|
||||
if _, err := os.Stat(path.Dir(logFile)); err != nil {
|
||||
_ = os.MkdirAll(path.Dir(logFile), os.ModePerm)
|
||||
record := clamRepo.StartRecords(clamItem.ID)
|
||||
taskItem, err := task.NewTaskWithOps("clam-"+clamItem.Name, task.TaskScan, task.TaskScopeClam, record.TaskID, clamItem.ID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("new task for exec shell failed, err: %v", err)
|
||||
}
|
||||
clam.AddScanTask(taskItem, clamItem, record.StartTime.Format(constant.DateTimeSlimLayout))
|
||||
go func() {
|
||||
strategy := ""
|
||||
switch clam.InfectedStrategy {
|
||||
case "remove":
|
||||
strategy = "--remove"
|
||||
case "move":
|
||||
dir := path.Join(clam.InfectedDir, "1panel-infected", clam.Name, timeNow)
|
||||
strategy = "--move=" + dir
|
||||
if _, err := os.Stat(dir); err != nil {
|
||||
_ = os.MkdirAll(dir, os.ModePerm)
|
||||
}
|
||||
case "copy":
|
||||
dir := path.Join(clam.InfectedDir, "1panel-infected", clam.Name, timeNow)
|
||||
strategy = "--copy=" + dir
|
||||
if _, err := os.Stat(dir); err != nil {
|
||||
_ = os.MkdirAll(dir, os.ModePerm)
|
||||
}
|
||||
err := taskItem.Execute()
|
||||
taskRepo := repo.NewITaskRepo()
|
||||
taskItem, _ := taskRepo.GetFirst(taskRepo.WithByID(record.TaskID))
|
||||
if len(taskItem.ID) == 0 {
|
||||
record.TaskID = ""
|
||||
}
|
||||
global.LOG.Debugf("clamdscan --fdpass %s %s -l %s", strategy, clam.Path, logFile)
|
||||
stdout, err := cmd.NewCommandMgr(cmd.WithIgnoreExist1(), cmd.WithTimeout(30*time.Minute)).RunWithStdoutBashCf("clamdscan --fdpass %s %s -l %s", strategy, clam.Path, logFile)
|
||||
if err != nil {
|
||||
global.LOG.Errorf("clamdscan failed, stdout: %v, err: %v", stdout, err)
|
||||
clamRepo.EndRecords(record, constant.StatusFailed, err.Error())
|
||||
return
|
||||
}
|
||||
handleAlert(stdout, clam.Name, clam.ID)
|
||||
handleAlert(record.InfectedFiles, clamItem.Name, clamItem.ID)
|
||||
clam.AnalysisFromLog(taskItem.LogFile, &record)
|
||||
clamRepo.EndRecords(record, constant.StatusDone, "")
|
||||
}()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *ClamService) LoadRecords(req dto.ClamLogSearch) (int64, interface{}, error) {
|
||||
func (c *ClamService) SearchRecords(req dto.ClamLogSearch) (int64, interface{}, error) {
|
||||
clam, _ := clamRepo.Get(repo.WithByID(req.ClamID))
|
||||
if clam.ID == 0 {
|
||||
return 0, nil, buserr.New("ErrRecordNotFound")
|
||||
}
|
||||
logPaths := loadFileByName(clam.Name)
|
||||
if len(logPaths) == 0 {
|
||||
return 0, nil, nil
|
||||
}
|
||||
|
||||
var filterFiles []string
|
||||
nyc, _ := time.LoadLocation(common.LoadTimeZoneByCmd())
|
||||
for _, item := range logPaths {
|
||||
t1, err := time.ParseInLocation(constant.DateTimeSlimLayout, item, nyc)
|
||||
if err != nil {
|
||||
continue
|
||||
total, records, err := clamRepo.PageRecords(req.Page, req.PageSize, clamRepo.WithByClamID(req.ClamID), repo.WithByStatus(req.Status), repo.WithByCreatedAt(req.StartTime, req.EndTime))
|
||||
if err != nil {
|
||||
return 0, nil, err
|
||||
}
|
||||
var datas []dto.ClamRecord
|
||||
for _, record := range records {
|
||||
var item dto.ClamRecord
|
||||
if err := copier.Copy(&item, &record); err != nil {
|
||||
return 0, nil, buserr.WithDetail("ErrStructTransform", err.Error(), nil)
|
||||
}
|
||||
if t1.After(req.StartTime) && t1.Before(req.EndTime) {
|
||||
filterFiles = append(filterFiles, item)
|
||||
}
|
||||
}
|
||||
if len(filterFiles) == 0 {
|
||||
return 0, nil, nil
|
||||
}
|
||||
|
||||
sort.Slice(filterFiles, func(i, j int) bool {
|
||||
return filterFiles[i] > filterFiles[j]
|
||||
})
|
||||
|
||||
var records []string
|
||||
total, start, end := len(filterFiles), (req.Page-1)*req.PageSize, req.Page*req.PageSize
|
||||
if start > total {
|
||||
records = make([]string, 0)
|
||||
} else {
|
||||
if end >= total {
|
||||
end = total
|
||||
}
|
||||
records = filterFiles[start:end]
|
||||
}
|
||||
|
||||
var datas []dto.ClamLog
|
||||
for i := 0; i < len(records); i++ {
|
||||
item := loadResultFromLog(path.Join(global.Dir.DataDir, resultDir, clam.Name, records[i]))
|
||||
datas = append(datas, item)
|
||||
}
|
||||
return int64(total), datas, nil
|
||||
}
|
||||
func (c *ClamService) LoadRecordLog(req dto.ClamLogReq) (string, error) {
|
||||
logPath := path.Join(global.Dir.DataDir, resultDir, req.ClamName, req.RecordName)
|
||||
var tail string
|
||||
if req.Tail != "0" {
|
||||
tail = req.Tail
|
||||
} else {
|
||||
tail = "+1"
|
||||
}
|
||||
cmd := exec.Command("tail", "-n", tail, logPath)
|
||||
stdout, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("tail -n %v failed, err: %v", req.Tail, err)
|
||||
}
|
||||
return string(stdout), nil
|
||||
}
|
||||
|
||||
func (c *ClamService) CleanRecord(req dto.OperateByID) error {
|
||||
clam, _ := clamRepo.Get(repo.WithByID(req.ID))
|
||||
if clam.ID == 0 {
|
||||
return buserr.New("ErrRecordNotFound")
|
||||
func (c *ClamService) CleanRecord(id uint) error {
|
||||
record, _ := clamRepo.ListRecord()
|
||||
for _, item := range record {
|
||||
if len(item.TaskID) != 0 {
|
||||
continue
|
||||
}
|
||||
taskItem, _ := taskRepo.GetFirst(taskRepo.WithByID(item.TaskID))
|
||||
if len(taskItem.LogFile) != 0 {
|
||||
_ = os.Remove(taskItem.LogFile)
|
||||
}
|
||||
}
|
||||
pathItem := path.Join(global.Dir.DataDir, resultDir, clam.Name)
|
||||
_ = os.RemoveAll(pathItem)
|
||||
return nil
|
||||
return clamRepo.DeleteRecord(clamRepo.WithByClamID(id))
|
||||
}
|
||||
|
||||
func (c *ClamService) LoadFile(req dto.ClamFileReq) (string, error) {
|
||||
filePath := ""
|
||||
switch req.Name {
|
||||
case "clamd":
|
||||
if c.serviceName == clamServiceNameUbuntu {
|
||||
if c.serviceName == constant.ClamServiceNameUbuntu {
|
||||
filePath = "/etc/clamav/clamd.conf"
|
||||
} else {
|
||||
filePath = "/etc/clamd.d/scan.conf"
|
||||
@@ -440,13 +383,13 @@ func (c *ClamService) LoadFile(req dto.ClamFileReq) (string, error) {
|
||||
if len(filePath) != 0 {
|
||||
break
|
||||
}
|
||||
if c.serviceName == clamServiceNameUbuntu {
|
||||
if c.serviceName == constant.ClamServiceNameUbuntu {
|
||||
filePath = "/var/log/clamav/clamav.log"
|
||||
} else {
|
||||
filePath = "/var/log/clamd.scan"
|
||||
}
|
||||
case "freshclam":
|
||||
if c.serviceName == clamServiceNameUbuntu {
|
||||
if c.serviceName == constant.ClamServiceNameUbuntu {
|
||||
filePath = "/etc/clamav/freshclam.conf"
|
||||
} else {
|
||||
filePath = "/etc/freshclam.conf"
|
||||
@@ -456,7 +399,7 @@ func (c *ClamService) LoadFile(req dto.ClamFileReq) (string, error) {
|
||||
if len(filePath) != 0 {
|
||||
break
|
||||
}
|
||||
if c.serviceName == clamServiceNameUbuntu {
|
||||
if c.serviceName == constant.ClamServiceNameUbuntu {
|
||||
filePath = "/var/log/clamav/freshclam.log"
|
||||
} else {
|
||||
filePath = "/var/log/freshclam.log"
|
||||
@@ -486,15 +429,15 @@ func (c *ClamService) UpdateFile(req dto.UpdateByNameAndFile) error {
|
||||
service := ""
|
||||
switch req.Name {
|
||||
case "clamd":
|
||||
if c.serviceName == clamServiceNameUbuntu {
|
||||
service = clamServiceNameUbuntu
|
||||
if c.serviceName == constant.ClamServiceNameUbuntu {
|
||||
service = constant.ClamServiceNameUbuntu
|
||||
filePath = "/etc/clamav/clamd.conf"
|
||||
} else {
|
||||
service = clamServiceNameCentOs
|
||||
service = constant.ClamServiceNameCentOs
|
||||
filePath = "/etc/clamd.d/scan.conf"
|
||||
}
|
||||
case "freshclam":
|
||||
if c.serviceName == clamServiceNameUbuntu {
|
||||
if c.serviceName == constant.ClamServiceNameUbuntu {
|
||||
filePath = "/etc/clamav/freshclam.conf"
|
||||
} else {
|
||||
filePath = "/etc/freshclam.conf"
|
||||
@@ -516,86 +459,16 @@ func (c *ClamService) UpdateFile(req dto.UpdateByNameAndFile) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func StopAllCronJob(withCheck bool) bool {
|
||||
if withCheck {
|
||||
isActive := false
|
||||
exist1, _ := systemctl.IsExist(clamServiceNameCentOs)
|
||||
if exist1 {
|
||||
isActive, _ = systemctl.IsActive(clamServiceNameCentOs)
|
||||
}
|
||||
exist2, _ := systemctl.IsExist(clamServiceNameUbuntu)
|
||||
if exist2 {
|
||||
isActive, _ = systemctl.IsActive(clamServiceNameUbuntu)
|
||||
}
|
||||
if isActive {
|
||||
return false
|
||||
}
|
||||
}
|
||||
clams, _ := clamRepo.List(repo.WithByStatus(constant.StatusEnable))
|
||||
for i := 0; i < len(clams); i++ {
|
||||
global.Cron.Remove(cron.EntryID(clams[i].EntryID))
|
||||
_ = clamRepo.Update(clams[i].ID, map[string]interface{}{"status": constant.StatusDisable, "entry_id": 0})
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func loadFileByName(name string) []string {
|
||||
var logPaths []string
|
||||
pathItem := path.Join(global.Dir.DataDir, resultDir, name)
|
||||
fileItems, err := os.ReadDir(pathItem)
|
||||
if err != nil {
|
||||
return logPaths
|
||||
}
|
||||
for _, item := range fileItems {
|
||||
if !item.IsDir() {
|
||||
logPaths = append(logPaths, item.Name())
|
||||
}
|
||||
}
|
||||
return logPaths
|
||||
}
|
||||
func loadResultFromLog(pathItem string) dto.ClamLog {
|
||||
var data dto.ClamLog
|
||||
data.Name = path.Base(pathItem)
|
||||
data.Status = constant.StatusWaiting
|
||||
file, err := os.ReadFile(pathItem)
|
||||
if err != nil {
|
||||
return data
|
||||
}
|
||||
lines := strings.Split(string(file), "\n")
|
||||
for _, line := range lines {
|
||||
if strings.Contains(line, "- SCAN SUMMARY -") {
|
||||
data.Status = constant.StatusDone
|
||||
}
|
||||
if data.Status != constant.StatusDone {
|
||||
continue
|
||||
}
|
||||
switch {
|
||||
case strings.HasPrefix(line, "Infected files:"):
|
||||
data.InfectedFiles = strings.TrimPrefix(line, "Infected files:")
|
||||
case strings.HasPrefix(line, "Total errors:"):
|
||||
data.TotalError = strings.TrimPrefix(line, "Total errors:")
|
||||
case strings.HasPrefix(line, "Time:"):
|
||||
if strings.Contains(line, "(") {
|
||||
data.ScanTime = strings.ReplaceAll(strings.Split(line, "(")[1], ")", "")
|
||||
continue
|
||||
}
|
||||
data.ScanTime = strings.TrimPrefix(line, "Time:")
|
||||
case strings.HasPrefix(line, "Start Date:"):
|
||||
data.ScanDate = strings.TrimPrefix(line, "Start Date:")
|
||||
}
|
||||
}
|
||||
return data
|
||||
}
|
||||
func (c *ClamService) loadLogPath(name string) string {
|
||||
confPath := ""
|
||||
if name == "clamd-log" {
|
||||
if c.serviceName == clamServiceNameUbuntu {
|
||||
if c.serviceName == constant.ClamServiceNameUbuntu {
|
||||
confPath = "/etc/clamav/clamd.conf"
|
||||
} else {
|
||||
confPath = "/etc/clamd.d/scan.conf"
|
||||
}
|
||||
} else {
|
||||
if c.serviceName == clamServiceNameUbuntu {
|
||||
if c.serviceName == constant.ClamServiceNameUbuntu {
|
||||
confPath = "/etc/clamav/freshclam.conf"
|
||||
} else {
|
||||
confPath = "/etc/freshclam.conf"
|
||||
@@ -626,23 +499,16 @@ func (c *ClamService) loadLogPath(name string) string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func handleAlert(stdout, clamName string, clamId uint) {
|
||||
if strings.Contains(stdout, "- SCAN SUMMARY -") {
|
||||
lines := strings.Split(stdout, "\n")
|
||||
for _, line := range lines {
|
||||
if strings.HasPrefix(line, "Infected files: ") {
|
||||
infectedFiles, _ := strconv.Atoi(strings.TrimPrefix(line, "Infected files: "))
|
||||
if infectedFiles > 0 {
|
||||
pushAlert := dto.PushAlert{
|
||||
TaskName: clamName,
|
||||
AlertType: "clams",
|
||||
EntryID: clamId,
|
||||
Param: strconv.Itoa(infectedFiles),
|
||||
}
|
||||
_ = alert_push.PushAlert(pushAlert)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
func handleAlert(infectedFiles, clamName string, clamId uint) {
|
||||
itemInfected, _ := strconv.Atoi(strings.TrimSpace(infectedFiles))
|
||||
if itemInfected < 0 {
|
||||
return
|
||||
}
|
||||
pushAlert := dto.PushAlert{
|
||||
TaskName: clamName,
|
||||
AlertType: "clams",
|
||||
EntryID: clamId,
|
||||
Param: strconv.Itoa(itemInfected),
|
||||
}
|
||||
_ = alert_push.PushAlert(pushAlert)
|
||||
}
|
||||
|
||||
@@ -711,12 +711,6 @@ func (u *ContainerService) ContainerUpdate(req dto.ContainerOperate) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(oldContainer.Config.Entrypoint) != 0 {
|
||||
if oldContainer.Config.Entrypoint[0] == "/docker-entrypoint.sh" {
|
||||
oldContainer.Config.Entrypoint = []string{}
|
||||
}
|
||||
}
|
||||
|
||||
taskItem, err := task.NewTaskWithOps(req.Name, task.TaskUpdate, task.TaskScopeContainer, req.TaskID, 1)
|
||||
if err != nil {
|
||||
global.LOG.Errorf("new task for create container failed, err: %v", err)
|
||||
@@ -805,19 +799,10 @@ func (u *ContainerService) ContainerUpgrade(req dto.ContainerUpgrade) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
inspected, err := client.ImageInspect(ctx, req.Image)
|
||||
if err != nil {
|
||||
return fmt.Errorf("inspect image failed, err: %v", err)
|
||||
}
|
||||
if isDynamicImage(inspected) {
|
||||
oldContainer.Config.Entrypoint = nil
|
||||
oldContainer.Config.Cmd = nil
|
||||
}
|
||||
return nil
|
||||
}, nil)
|
||||
|
||||
taskItem.AddSubTask(i18n.GetWithName("ContainerCreate", item), func(t *task.Task) error {
|
||||
oldContainer.Config.Cmd = nil
|
||||
config := oldContainer.Config
|
||||
config.Image = req.Image
|
||||
hostConf := oldContainer.HostConfig
|
||||
@@ -1743,23 +1728,3 @@ func loadContainerPortForInfo(itemPorts []container.Port) []dto.PortHelper {
|
||||
}
|
||||
return exposedPorts
|
||||
}
|
||||
|
||||
func isDynamicImage(inspected image.InspectResponse) bool {
|
||||
if len(inspected.Config.Entrypoint) > 0 {
|
||||
entrypointStr := strings.Join(inspected.Config.Entrypoint, " ")
|
||||
if strings.Contains(entrypointStr, "entrypoint") {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
dirs := []string{"/docker-entrypoint.d", "/docker-entrypoint-initdb.d"}
|
||||
for _, dir := range dirs {
|
||||
for _, layer := range inspected.RootFS.Layers {
|
||||
if strings.Contains(layer, dir) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
"unicode"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/agent/app/dto"
|
||||
"github.com/1Panel-dev/1Panel/agent/buserr"
|
||||
@@ -53,25 +54,27 @@ func (u *ContainerService) PageVolume(req dto.SearchWithPage) (int64, interface{
|
||||
|
||||
nyc, _ := time.LoadLocation(common.LoadTimeZoneByCmd())
|
||||
for _, item := range records {
|
||||
tag := make([]string, 0)
|
||||
for _, val := range item.Labels {
|
||||
tag = append(tag, val)
|
||||
var volume dto.Volume
|
||||
volume.Driver = item.Driver
|
||||
volume.Mountpoint = item.Mountpoint
|
||||
volume.Name = simplifyVolumeName(item.Name)
|
||||
for key, val := range item.Labels {
|
||||
volume.Labels = append(volume.Labels, dto.VolumeOption{Key: key, Value: val})
|
||||
}
|
||||
var createTime time.Time
|
||||
if strings.Contains(item.CreatedAt, "Z") {
|
||||
createTime, _ = time.ParseInLocation("2006-01-02T15:04:05Z", item.CreatedAt, nyc)
|
||||
} else if strings.Contains(item.CreatedAt, "+") {
|
||||
createTime, _ = time.ParseInLocation("2006-01-02T15:04:05+08:00", item.CreatedAt, nyc)
|
||||
} else {
|
||||
createTime, _ = time.ParseInLocation("2006-01-02T15:04:05", item.CreatedAt, nyc)
|
||||
for key, val := range item.Options {
|
||||
volume.Options = append(volume.Options, dto.VolumeOption{Key: key, Value: val})
|
||||
}
|
||||
data = append(data, dto.Volume{
|
||||
CreatedAt: createTime,
|
||||
Name: item.Name,
|
||||
Driver: item.Driver,
|
||||
Mountpoint: item.Mountpoint,
|
||||
Labels: tag,
|
||||
sort.Slice(volume.Options, func(i, j int) bool {
|
||||
return volume.Options[i].Key < volume.Options[j].Key
|
||||
})
|
||||
if strings.Contains(item.CreatedAt, "Z") {
|
||||
volume.CreatedAt, _ = time.ParseInLocation("2006-01-02T15:04:05Z", item.CreatedAt, nyc)
|
||||
} else if strings.Contains(item.CreatedAt, "+") {
|
||||
volume.CreatedAt, _ = time.ParseInLocation("2006-01-02T15:04:05+08:00", item.CreatedAt, nyc)
|
||||
} else {
|
||||
volume.CreatedAt, _ = time.ParseInLocation("2006-01-02T15:04:05", item.CreatedAt, nyc)
|
||||
}
|
||||
data = append(data, volume)
|
||||
}
|
||||
|
||||
return int64(total), data, nil
|
||||
@@ -140,3 +143,16 @@ func (u *ContainerService) CreateVolume(req dto.VolumeCreate) error {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func simplifyVolumeName(name string) string {
|
||||
if len(name) != 64 {
|
||||
return name
|
||||
}
|
||||
|
||||
for _, char := range name {
|
||||
if !unicode.Is(unicode.ASCII_Hex_Digit, char) {
|
||||
return name
|
||||
}
|
||||
}
|
||||
return name[:12]
|
||||
}
|
||||
|
||||
@@ -536,6 +536,9 @@ func (u *CronjobService) HandleOnce(id uint) error {
|
||||
if cronjob.ID == 0 {
|
||||
return buserr.New("ErrRecordNotFound")
|
||||
}
|
||||
if cronjob.IsExecuting {
|
||||
return buserr.New("InExecuting")
|
||||
}
|
||||
u.HandleJob(&cronjob)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -27,7 +27,8 @@ import (
|
||||
func (u *CronjobService) handleApp(cronjob model.Cronjob, startTime time.Time, taskItem *task.Task) error {
|
||||
apps := loadAppsForJob(cronjob)
|
||||
if len(apps) == 0 {
|
||||
return errors.New("no such app in database!")
|
||||
addSkipTask("App", taskItem)
|
||||
return nil
|
||||
}
|
||||
accountMap := NewBackupClientMap(strings.Split(cronjob.SourceAccountIDs, ","))
|
||||
if !accountMap[fmt.Sprintf("%d", cronjob.DownloadAccountID)].isOk {
|
||||
@@ -83,7 +84,8 @@ func (u *CronjobService) handleApp(cronjob model.Cronjob, startTime time.Time, t
|
||||
func (u *CronjobService) handleWebsite(cronjob model.Cronjob, startTime time.Time, taskItem *task.Task) error {
|
||||
webs := loadWebsForJob(cronjob)
|
||||
if len(webs) == 0 {
|
||||
return errors.New("no such website in database!")
|
||||
addSkipTask("Website", taskItem)
|
||||
return nil
|
||||
}
|
||||
accountMap := NewBackupClientMap(strings.Split(cronjob.SourceAccountIDs, ","))
|
||||
if !accountMap[fmt.Sprintf("%d", cronjob.DownloadAccountID)].isOk {
|
||||
@@ -140,7 +142,8 @@ func (u *CronjobService) handleWebsite(cronjob model.Cronjob, startTime time.Tim
|
||||
func (u *CronjobService) handleDatabase(cronjob model.Cronjob, startTime time.Time, taskItem *task.Task) error {
|
||||
dbs := loadDbsForJob(cronjob)
|
||||
if len(dbs) == 0 {
|
||||
return errors.New("no such db in database!")
|
||||
addSkipTask("Database", taskItem)
|
||||
return nil
|
||||
}
|
||||
accountMap := NewBackupClientMap(strings.Split(cronjob.SourceAccountIDs, ","))
|
||||
if !accountMap[fmt.Sprintf("%d", cronjob.DownloadAccountID)].isOk {
|
||||
@@ -372,6 +375,13 @@ type DatabaseHelper struct {
|
||||
Name string
|
||||
}
|
||||
|
||||
func addSkipTask(source string, taskItem *task.Task) {
|
||||
taskItem.AddSubTask(task.GetTaskName(i18n.GetMsgByKey(source), task.TaskBackup, task.TaskScopeCronjob), func(task *task.Task) error {
|
||||
taskItem.Log(i18n.GetMsgByKey("NoSuchResource"))
|
||||
return nil
|
||||
}, nil)
|
||||
}
|
||||
|
||||
func loadDbsForJob(cronjob model.Cronjob) []DatabaseHelper {
|
||||
var dbs []DatabaseHelper
|
||||
if cronjob.DBName == "all" {
|
||||
|
||||
@@ -25,7 +25,12 @@ import (
|
||||
)
|
||||
|
||||
func (u *CronjobService) HandleJob(cronjob *model.Cronjob) {
|
||||
record := cronjobRepo.StartRecords(cronjob.ID, "", cronjob.Type)
|
||||
cronjobItem, _ := cronjobRepo.Get(repo.WithByID(cronjob.ID))
|
||||
if cronjobItem.IsExecuting {
|
||||
cronjobRepo.AddFailedRecord(cronjob.ID, i18n.GetMsgByKey("InExecuting"))
|
||||
return
|
||||
}
|
||||
record := cronjobRepo.StartRecords(cronjob.ID)
|
||||
taskItem, err := task.NewTaskWithOps(fmt.Sprintf("cronjob-%s", cronjob.Name), task.TaskHandle, task.TaskScopeCronjob, record.TaskID, cronjob.ID)
|
||||
if err != nil {
|
||||
global.LOG.Errorf("new task for exec shell failed, err: %v", err)
|
||||
|
||||
@@ -14,6 +14,7 @@ import (
|
||||
"github.com/1Panel-dev/1Panel/agent/app/dto"
|
||||
"github.com/1Panel-dev/1Panel/agent/app/model"
|
||||
"github.com/1Panel-dev/1Panel/agent/app/repo"
|
||||
"github.com/1Panel-dev/1Panel/agent/buserr"
|
||||
"github.com/1Panel-dev/1Panel/agent/constant"
|
||||
"github.com/1Panel-dev/1Panel/agent/global"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/ai_tools/gpu"
|
||||
@@ -38,6 +39,9 @@ type IDashboardService interface {
|
||||
LoadCurrentInfoForNode() *dto.NodeCurrent
|
||||
LoadCurrentInfo(ioOption string, netOption string) *dto.DashboardCurrent
|
||||
|
||||
LoadQuickOptions() []dto.QuickJump
|
||||
ChangeQuick(req dto.ChangeQuicks) error
|
||||
|
||||
LoadAppLauncher(ctx *gin.Context) ([]dto.AppLauncher, error)
|
||||
ChangeShow(req dto.SettingUpdate) error
|
||||
ListLauncherOption(filter string) ([]dto.LauncherOption, error)
|
||||
@@ -140,7 +144,7 @@ func (u *DashboardService) LoadBaseInfo(ioOption string, netOption string) (*dto
|
||||
baseInfo.KernelVersion = hostInfo.KernelVersion
|
||||
ss, _ := json.Marshal(hostInfo)
|
||||
baseInfo.VirtualizationSystem = string(ss)
|
||||
baseInfo.IpV4Addr = GetOutboundIP()
|
||||
baseInfo.IpV4Addr = loadOutboundIP()
|
||||
httpProxy := os.Getenv("http_proxy")
|
||||
if httpProxy == "" {
|
||||
httpProxy = os.Getenv("HTTP_PROXY")
|
||||
@@ -150,30 +154,7 @@ func (u *DashboardService) LoadBaseInfo(ioOption string, netOption string) (*dto
|
||||
}
|
||||
baseInfo.SystemProxy = "noProxy"
|
||||
|
||||
appInstall, err := appInstallRepo.ListBy(context.Background())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
baseInfo.AppInstalledNumber = len(appInstall)
|
||||
postgresqlDbs, err := postgresqlRepo.List()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
mysqlDbs, err := mysqlRepo.List()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
baseInfo.DatabaseNumber = len(mysqlDbs) + len(postgresqlDbs)
|
||||
website, err := websiteRepo.GetBy()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
baseInfo.WebsiteNumber = len(website)
|
||||
cronjobs, err := cronjobRepo.List()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
baseInfo.CronjobNumber = len(cronjobs)
|
||||
loadQuickJump(&baseInfo)
|
||||
|
||||
cpuInfo, err := cpu.Info()
|
||||
if err == nil {
|
||||
@@ -347,6 +328,39 @@ func (u *DashboardService) ChangeShow(req dto.SettingUpdate) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *DashboardService) LoadQuickOptions() []dto.QuickJump {
|
||||
quicks := launcherRepo.ListQuickJump(true)
|
||||
var list []dto.QuickJump
|
||||
for _, quick := range quicks {
|
||||
var item dto.QuickJump
|
||||
_ = copier.Copy(&item, &quick)
|
||||
list = append(list, item)
|
||||
}
|
||||
return list
|
||||
}
|
||||
func (u *DashboardService) ChangeQuick(req dto.ChangeQuicks) error {
|
||||
showCount := 0
|
||||
var quicks []model.QuickJump
|
||||
for _, item := range req.Quicks {
|
||||
var quick model.QuickJump
|
||||
if item.IsShow {
|
||||
showCount++
|
||||
}
|
||||
if err := copier.Copy(&quick, &item); err != nil {
|
||||
return err
|
||||
}
|
||||
quicks = append(quicks, quick)
|
||||
}
|
||||
if showCount == 0 {
|
||||
return buserr.New("ErrMinQuickJump")
|
||||
}
|
||||
if showCount > 4 {
|
||||
return buserr.New("ErrMaxQuickJump")
|
||||
}
|
||||
|
||||
return launcherRepo.UpdateQuicks(quicks)
|
||||
}
|
||||
|
||||
func (u *DashboardService) ListLauncherOption(filter string) ([]dto.LauncherOption, error) {
|
||||
showList, _ := launcherRepo.ListName()
|
||||
var data []dto.LauncherOption
|
||||
@@ -545,7 +559,7 @@ func loadXpuInfo() []dto.XPUInfo {
|
||||
return data
|
||||
}
|
||||
|
||||
func GetOutboundIP() string {
|
||||
func loadOutboundIP() string {
|
||||
conn, err := network.Dial("udp", "8.8.8.8:80")
|
||||
|
||||
if err != nil {
|
||||
@@ -556,3 +570,38 @@ func GetOutboundIP() string {
|
||||
localAddr := conn.LocalAddr().(*network.UDPAddr)
|
||||
return localAddr.IP.String()
|
||||
}
|
||||
|
||||
func loadQuickJump(base *dto.DashboardBase) {
|
||||
website, _ := websiteRepo.GetBy()
|
||||
base.WebsiteNumber = len(website)
|
||||
|
||||
postgresqlDbs, _ := postgresqlRepo.List()
|
||||
mysqlDbs, _ := mysqlRepo.List()
|
||||
base.DatabaseNumber = len(mysqlDbs) + len(postgresqlDbs)
|
||||
|
||||
cronjobs, _ := cronjobRepo.List()
|
||||
base.CronjobNumber = len(cronjobs)
|
||||
|
||||
appInstall, _ := appInstallRepo.ListBy(context.Background())
|
||||
base.AppInstalledNumber = len(appInstall)
|
||||
|
||||
quicks := launcherRepo.ListQuickJump(false)
|
||||
for i := 0; i < len(quicks); i++ {
|
||||
switch quicks[i].Name {
|
||||
case "Website":
|
||||
quicks[i].Detail = fmt.Sprintf("%d", base.WebsiteNumber)
|
||||
case "Database":
|
||||
quicks[i].Detail = fmt.Sprintf("%d", base.DatabaseNumber)
|
||||
case "Cronjob":
|
||||
quicks[i].Detail = fmt.Sprintf("%d", base.CronjobNumber)
|
||||
case "AppInstalled":
|
||||
quicks[i].Detail = fmt.Sprintf("%d", base.AppInstalledNumber)
|
||||
}
|
||||
var item dto.QuickJump
|
||||
_ = copier.Copy(&item, quicks[i])
|
||||
base.QuickJumps = append(base.QuickJumps, item)
|
||||
}
|
||||
sort.Slice(quicks, func(i, j int) bool {
|
||||
return quicks[i].Recommend < quicks[j].Recommend
|
||||
})
|
||||
}
|
||||
|
||||
@@ -0,0 +1,146 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/1Panel-dev/1Panel/agent/app/dto"
|
||||
"github.com/1Panel-dev/1Panel/agent/buserr"
|
||||
"github.com/1Panel-dev/1Panel/agent/global"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/cmd"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/agent/app/dto/request"
|
||||
"github.com/1Panel-dev/1Panel/agent/app/dto/response"
|
||||
)
|
||||
|
||||
type DiskService struct{}
|
||||
|
||||
type IDiskService interface {
|
||||
GetCompleteDiskInfo() (*response.CompleteDiskInfo, error)
|
||||
PartitionDisk(req request.DiskPartitionRequest) (string, error)
|
||||
MountDisk(req request.DiskMountRequest) error
|
||||
UnmountDisk(req request.DiskUnmountRequest) error
|
||||
}
|
||||
|
||||
func NewIDiskService() IDiskService {
|
||||
return &DiskService{}
|
||||
}
|
||||
|
||||
func (s *DiskService) GetCompleteDiskInfo() (*response.CompleteDiskInfo, error) {
|
||||
output, err := cmd.RunDefaultWithStdoutBashC("lsblk -P -o NAME,SIZE,TYPE,MOUNTPOINT,FSTYPE,MODEL,SERIAL,TRAN,ROTA")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to execute lsblk command: %v", err)
|
||||
}
|
||||
|
||||
diskInfos, err := parseLsblkOutput(output)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to parse lsblk output: %v", err)
|
||||
}
|
||||
|
||||
result := organizeDiskInfo(diskInfos)
|
||||
return &result, nil
|
||||
}
|
||||
|
||||
func (s *DiskService) PartitionDisk(req request.DiskPartitionRequest) (string, error) {
|
||||
if !strings.HasPrefix("/dev", req.Device) {
|
||||
req.Device = "/dev/" + req.Device
|
||||
}
|
||||
if !deviceExists(req.Device) {
|
||||
return "", buserr.WithName("DeviceNotFound", req.Device)
|
||||
}
|
||||
|
||||
if isDeviceMounted(req.Device) {
|
||||
return "", buserr.WithName("DeviceIsMounted", req.Device)
|
||||
}
|
||||
|
||||
cmdMgr := cmd.NewCommandMgr(cmd.WithTimeout(10 * time.Second))
|
||||
if err := cmdMgr.RunBashC(fmt.Sprintf("partprobe %s", req.Device)); err != nil {
|
||||
return "", buserr.WithErr("PartitionDiskErr", err)
|
||||
}
|
||||
|
||||
if err := cmdMgr.RunBashC(fmt.Sprintf("parted -s %s mklabel gpt", req.Device)); err != nil {
|
||||
return "", buserr.WithErr("PartitionDiskErr", err)
|
||||
}
|
||||
|
||||
if err := cmdMgr.RunBashC(fmt.Sprintf("parted -s %s mkpart primary 1MiB 100%%", req.Device)); err != nil {
|
||||
return "", buserr.WithErr("PartitionDiskErr", err)
|
||||
}
|
||||
|
||||
if err := cmdMgr.RunBashC(fmt.Sprintf("partprobe %s", req.Device)); err != nil {
|
||||
return "", buserr.WithErr("PartitionDiskErr", err)
|
||||
}
|
||||
partition := req.Device + "1"
|
||||
|
||||
formatReq := dto.DiskFormatRequest{
|
||||
Device: partition,
|
||||
Filesystem: req.Filesystem,
|
||||
Label: req.Label,
|
||||
}
|
||||
if err := formatDisk(formatReq); err != nil {
|
||||
return "", buserr.WithErr("FormatDiskErr", err)
|
||||
}
|
||||
|
||||
if req.MountPoint != "" {
|
||||
mountReq := request.DiskMountRequest{
|
||||
Device: partition,
|
||||
MountPoint: req.MountPoint,
|
||||
Filesystem: req.Filesystem,
|
||||
AutoMount: req.AutoMount,
|
||||
}
|
||||
if err := s.MountDisk(mountReq); err != nil {
|
||||
return "", buserr.WithErr("MountDiskErr", err)
|
||||
}
|
||||
}
|
||||
|
||||
return partition, nil
|
||||
}
|
||||
|
||||
func (s *DiskService) MountDisk(req request.DiskMountRequest) error {
|
||||
if !deviceExists(req.Device) {
|
||||
return buserr.WithName("DeviceNotFound", req.Device)
|
||||
}
|
||||
|
||||
if err := os.MkdirAll(req.MountPoint, 0755); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fileSystem, err := getFilesystemType(req.Device)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if fileSystem == "" {
|
||||
formatReq := dto.DiskFormatRequest{
|
||||
Device: req.Device,
|
||||
Filesystem: req.Filesystem,
|
||||
}
|
||||
if err := formatDisk(formatReq); err != nil {
|
||||
return buserr.WithErr("FormatDiskErr", err)
|
||||
}
|
||||
}
|
||||
|
||||
cmdMgr := cmd.NewCommandMgr(cmd.WithTimeout(1 * time.Minute))
|
||||
if err := cmdMgr.RunBashC(fmt.Sprintf("mount -t %s %s %s", req.Filesystem, req.Device, req.MountPoint)); err != nil {
|
||||
return buserr.WithErr("MountDiskErr", err)
|
||||
}
|
||||
if req.AutoMount {
|
||||
if err := addToFstabWithOptions(req.Device, req.MountPoint, req.Filesystem, ""); err != nil {
|
||||
return buserr.WithErr("MountDiskErr", err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *DiskService) UnmountDisk(req request.DiskUnmountRequest) error {
|
||||
if !isPointMounted(req.MountPoint) {
|
||||
return buserr.New("MountDiskErr")
|
||||
}
|
||||
if err := cmd.RunDefaultBashC(fmt.Sprintf("umount -f %s", req.MountPoint)); err != nil {
|
||||
return buserr.WithErr("MountDiskErr", err)
|
||||
}
|
||||
if err := removeFromFstab(req.MountPoint); err != nil {
|
||||
global.LOG.Errorf("remove %s mountPoint err: %v", req.MountPoint, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,440 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"github.com/1Panel-dev/1Panel/agent/app/dto"
|
||||
"github.com/1Panel-dev/1Panel/agent/app/dto/response"
|
||||
"github.com/1Panel-dev/1Panel/agent/buserr"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/cmd"
|
||||
"os"
|
||||
"os/exec"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func organizeDiskInfo(diskInfos []response.DiskBasicInfo) response.CompleteDiskInfo {
|
||||
var result response.CompleteDiskInfo
|
||||
var systemDisk *response.DiskInfo
|
||||
diskMap := make(map[string]*response.DiskInfo)
|
||||
partitions := make(map[string][]response.DiskBasicInfo)
|
||||
|
||||
for _, info := range diskInfos {
|
||||
isPartition := isPartitionDevice(info.Device)
|
||||
|
||||
if isPartition {
|
||||
parentDevice := getParentDevice(info.Device)
|
||||
partitions[parentDevice] = append(partitions[parentDevice], info)
|
||||
} else {
|
||||
disk := &response.DiskInfo{
|
||||
DiskBasicInfo: info,
|
||||
Partitions: []response.DiskBasicInfo{},
|
||||
}
|
||||
diskMap[info.Device] = disk
|
||||
}
|
||||
}
|
||||
|
||||
for parentDevice, partList := range partitions {
|
||||
if disk, exists := diskMap[parentDevice]; exists {
|
||||
for index, part := range partList {
|
||||
part.Device = fmt.Sprintf("/dev/%s", part.Device)
|
||||
if part.IsSystem {
|
||||
disk.IsSystem = true
|
||||
}
|
||||
partList[index] = part
|
||||
}
|
||||
disk.Partitions = partList
|
||||
}
|
||||
}
|
||||
|
||||
var totalCapacity int64
|
||||
for _, disk := range diskMap {
|
||||
capacity := parseSizeToBytes(disk.Size)
|
||||
totalCapacity += capacity
|
||||
|
||||
if disk.IsSystem {
|
||||
systemDisk = disk
|
||||
} else if len(disk.Partitions) == 0 {
|
||||
result.UnpartitionedDisks = append(result.UnpartitionedDisks, disk.DiskBasicInfo)
|
||||
} else {
|
||||
result.Disks = append(result.Disks, *disk)
|
||||
}
|
||||
}
|
||||
|
||||
result.SystemDisk = systemDisk
|
||||
result.TotalDisks = len(diskMap)
|
||||
result.TotalCapacity = totalCapacity
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func parseLsblkOutput(output string) ([]response.DiskBasicInfo, error) {
|
||||
lines := strings.Split(strings.TrimSpace(output), "\n")
|
||||
if len(lines) == 0 {
|
||||
return nil, fmt.Errorf("invalid lsblk output")
|
||||
}
|
||||
|
||||
var diskInfos []response.DiskBasicInfo
|
||||
lvmMap := make(map[string]response.DiskBasicInfo)
|
||||
var pendingDevices []map[string]string
|
||||
|
||||
for _, line := range lines {
|
||||
line = strings.TrimSpace(line)
|
||||
if line == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
fields := parseKeyValuePairs(line)
|
||||
|
||||
name, ok := fields["NAME"]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
if strings.HasPrefix(name, "loop") ||
|
||||
strings.HasPrefix(name, "dm-") {
|
||||
continue
|
||||
}
|
||||
|
||||
diskType := fields["TYPE"]
|
||||
mountPoint := fields["MOUNTPOINT"]
|
||||
fsType := fields["FSTYPE"]
|
||||
size := fields["SIZE"]
|
||||
|
||||
if diskType == "lvm" {
|
||||
total, used, avail, usePercent, _ := getDiskUsageInfo("/dev/mapper/" + name)
|
||||
if total != "" && fsType != "" {
|
||||
size = total
|
||||
}
|
||||
|
||||
lvmInfo := response.DiskBasicInfo{
|
||||
Device: name,
|
||||
Size: size,
|
||||
Model: fields["MODEL"],
|
||||
DiskType: "LVM",
|
||||
IsRemovable: false,
|
||||
IsSystem: isSystemDisk(mountPoint),
|
||||
Filesystem: fsType,
|
||||
Used: used,
|
||||
Avail: avail,
|
||||
UsePercent: usePercent,
|
||||
MountPoint: mountPoint,
|
||||
IsMounted: mountPoint != "" && mountPoint != "-",
|
||||
Serial: fields["SERIAL"],
|
||||
}
|
||||
lvmMap[name] = lvmInfo
|
||||
} else if diskType == "disk" || diskType == "part" {
|
||||
pendingDevices = append(pendingDevices, fields)
|
||||
}
|
||||
}
|
||||
|
||||
for _, fields := range pendingDevices {
|
||||
name := fields["NAME"]
|
||||
size := fields["SIZE"]
|
||||
mountPoint := fields["MOUNTPOINT"]
|
||||
fsType := fields["FSTYPE"]
|
||||
model := fields["MODEL"]
|
||||
serial := fields["SERIAL"]
|
||||
tran := fields["TRAN"]
|
||||
rota := fields["ROTA"]
|
||||
|
||||
var (
|
||||
used, avail, totalSize string
|
||||
usePercent int
|
||||
)
|
||||
if mountPoint != "" {
|
||||
totalSize, used, avail, usePercent, _ = getDiskUsageInfo("/dev/" + name)
|
||||
if totalSize != "" {
|
||||
size = totalSize
|
||||
}
|
||||
}
|
||||
|
||||
actualMountPoint := mountPoint
|
||||
actualFsType := fsType
|
||||
actualUsed := used
|
||||
actualAvail := avail
|
||||
actualUsePercent := usePercent
|
||||
isMounted := mountPoint != "" && mountPoint != "-"
|
||||
isSystemPartition := isSystemDisk(mountPoint)
|
||||
|
||||
if fsType == "LVM2_member" {
|
||||
for _, lvmInfo := range lvmMap {
|
||||
if lvmInfo.IsMounted {
|
||||
actualMountPoint = lvmInfo.MountPoint
|
||||
actualFsType = lvmInfo.Filesystem
|
||||
actualUsed = lvmInfo.Used
|
||||
size = lvmInfo.Size
|
||||
actualAvail = lvmInfo.Avail
|
||||
actualUsePercent = lvmInfo.UsePercent
|
||||
isMounted = true
|
||||
isSystemPartition = lvmInfo.IsSystem
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
info := response.DiskBasicInfo{
|
||||
Device: name,
|
||||
Size: size,
|
||||
Model: model,
|
||||
DiskType: getDiskType(rota),
|
||||
IsRemovable: tran == "usb",
|
||||
IsSystem: isSystemPartition,
|
||||
Filesystem: actualFsType,
|
||||
Used: actualUsed,
|
||||
Avail: actualAvail,
|
||||
UsePercent: actualUsePercent,
|
||||
MountPoint: actualMountPoint,
|
||||
IsMounted: isMounted,
|
||||
Serial: serial,
|
||||
}
|
||||
|
||||
diskInfos = append(diskInfos, info)
|
||||
}
|
||||
|
||||
return diskInfos, nil
|
||||
}
|
||||
|
||||
func getDiskType(rota string) string {
|
||||
if rota == "0" {
|
||||
return "SSD"
|
||||
} else if rota == "1" {
|
||||
return "HDD"
|
||||
}
|
||||
return "Unknown"
|
||||
}
|
||||
|
||||
var kvRe = regexp.MustCompile(`([A-Za-z0-9_]+)=("([^"\\]|\\.)*"|[^ \t]+)`)
|
||||
|
||||
func parseKeyValuePairs(line string) map[string]string {
|
||||
fields := make(map[string]string)
|
||||
|
||||
matches := kvRe.FindAllStringSubmatch(line, -1)
|
||||
for _, m := range matches {
|
||||
key := m[1]
|
||||
raw := m[2]
|
||||
|
||||
val := raw
|
||||
if len(val) >= 2 && val[0] == '"' && val[len(val)-1] == '"' {
|
||||
if unq, err := strconv.Unquote(val); err == nil {
|
||||
val = unq
|
||||
} else {
|
||||
val = val[1 : len(val)-1]
|
||||
}
|
||||
}
|
||||
fields[key] = val
|
||||
}
|
||||
return fields
|
||||
}
|
||||
|
||||
func isPartitionDevice(device string) bool {
|
||||
if strings.Contains(device, "nvme") {
|
||||
return strings.Contains(device, "p") &&
|
||||
strings.ContainsAny(device[strings.LastIndex(device, "p")+1:], "0123456789")
|
||||
} else if strings.HasPrefix(device, "sd") || strings.HasPrefix(device, "hd") {
|
||||
return len(device) > 3 &&
|
||||
strings.ContainsAny(device[len(device)-1:], "0123456789")
|
||||
} else if strings.HasPrefix(device, "vd") {
|
||||
return len(device) > 3 &&
|
||||
strings.ContainsAny(device[len(device)-1:], "0123456789")
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func getParentDevice(device string) string {
|
||||
if strings.Contains(device, "nvme") {
|
||||
if idx := strings.LastIndex(device, "p"); idx != -1 {
|
||||
return device[:idx]
|
||||
}
|
||||
} else {
|
||||
return strings.TrimRight(device, "0123456789")
|
||||
}
|
||||
|
||||
return device
|
||||
}
|
||||
|
||||
func getDiskUsageInfo(device string) (size, used, avail string, usePercent int, err error) {
|
||||
output, err := cmd.RunDefaultWithStdoutBashC(fmt.Sprintf("df -h %s | tail -1", device))
|
||||
if err != nil {
|
||||
return "", "", "", 0, nil
|
||||
}
|
||||
|
||||
fields := strings.Fields(output)
|
||||
if len(fields) >= 5 {
|
||||
size = fields[1]
|
||||
used = fields[2]
|
||||
avail = fields[3]
|
||||
usePercentStr := strings.TrimSuffix(fields[4], "%")
|
||||
usePercent, _ = strconv.Atoi(usePercentStr)
|
||||
}
|
||||
|
||||
return size, used, avail, usePercent, nil
|
||||
}
|
||||
|
||||
func formatDisk(req dto.DiskFormatRequest) error {
|
||||
var mkfsCmd *exec.Cmd
|
||||
|
||||
switch req.Filesystem {
|
||||
case "ext4":
|
||||
mkfsCmd = exec.Command("mkfs.ext4", "-F", req.Device)
|
||||
case "xfs":
|
||||
if !cmd.Which("mkfs.xfs") {
|
||||
return buserr.New("XfsNotFound")
|
||||
}
|
||||
mkfsCmd = exec.Command("mkfs.xfs", "-f", req.Device)
|
||||
default:
|
||||
return fmt.Errorf("unsupport type: %s", req.Filesystem)
|
||||
}
|
||||
if err := mkfsCmd.Run(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func deviceExists(device string) bool {
|
||||
_, err := os.Stat(device)
|
||||
return err == nil
|
||||
}
|
||||
|
||||
func isDeviceMounted(device string) bool {
|
||||
file, err := os.Open("/proc/mounts")
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
scanner := bufio.NewScanner(file)
|
||||
for scanner.Scan() {
|
||||
fields := strings.Fields(scanner.Text())
|
||||
if len(fields) >= 2 && fields[0] == device {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func isPointMounted(mountPoint string) bool {
|
||||
file, err := os.Open("/proc/mounts")
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
scanner := bufio.NewScanner(file)
|
||||
for scanner.Scan() {
|
||||
fields := strings.Fields(scanner.Text())
|
||||
if len(fields) >= 2 && fields[1] == mountPoint {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func addToFstabWithOptions(device, mountPoint, filesystem, options string) error {
|
||||
if filesystem == "" {
|
||||
fsType, err := getFilesystemType(device)
|
||||
if err != nil {
|
||||
filesystem = "auto"
|
||||
} else {
|
||||
filesystem = fsType
|
||||
}
|
||||
}
|
||||
|
||||
if options == "" {
|
||||
options = "defaults"
|
||||
}
|
||||
|
||||
entry := fmt.Sprintf("%s %s %s %s 0 2\n", device, mountPoint, filesystem, options)
|
||||
|
||||
file, err := os.OpenFile("/etc/fstab", os.O_APPEND|os.O_WRONLY, 0644)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
_, err = file.WriteString(entry)
|
||||
return err
|
||||
}
|
||||
|
||||
func removeFromFstab(mountPoint string) error {
|
||||
file, err := os.Open("/etc/fstab")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
var lines []string
|
||||
scanner := bufio.NewScanner(file)
|
||||
for scanner.Scan() {
|
||||
line := scanner.Text()
|
||||
fields := strings.Fields(line)
|
||||
if len(fields) >= 2 && fields[1] == mountPoint {
|
||||
continue
|
||||
}
|
||||
lines = append(lines, line)
|
||||
}
|
||||
|
||||
return os.WriteFile("/etc/fstab", []byte(strings.Join(lines, "\n")+"\n"), 0644)
|
||||
}
|
||||
|
||||
func getFilesystemType(device string) (string, error) {
|
||||
output, err := cmd.RunDefaultWithStdoutBashC(fmt.Sprintf("blkid -o value -s TYPE %s", device))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return strings.TrimSpace(output), nil
|
||||
}
|
||||
|
||||
func isSystemDisk(mountPoint string) bool {
|
||||
systemMountPoints := []string{
|
||||
"/",
|
||||
"/boot",
|
||||
"/boot/efi",
|
||||
"/usr",
|
||||
"/var",
|
||||
"/home",
|
||||
}
|
||||
|
||||
for _, sysMount := range systemMountPoints {
|
||||
if mountPoint == sysMount {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func parseSizeToBytes(sizeStr string) int64 {
|
||||
if sizeStr == "" || sizeStr == "-" {
|
||||
return 0
|
||||
}
|
||||
|
||||
sizeStr = strings.TrimSpace(sizeStr)
|
||||
if len(sizeStr) < 2 {
|
||||
return 0
|
||||
}
|
||||
|
||||
unit := strings.ToUpper(sizeStr[len(sizeStr)-1:])
|
||||
valueStr := sizeStr[:len(sizeStr)-1]
|
||||
|
||||
value, err := strconv.ParseFloat(valueStr, 64)
|
||||
if err != nil {
|
||||
return 0
|
||||
}
|
||||
|
||||
switch unit {
|
||||
case "K":
|
||||
return int64(value * 1024)
|
||||
case "M":
|
||||
return int64(value * 1024 * 1024)
|
||||
case "G":
|
||||
return int64(value * 1024 * 1024 * 1024)
|
||||
case "T":
|
||||
return int64(value * 1024 * 1024 * 1024 * 1024)
|
||||
default:
|
||||
val, _ := strconv.ParseInt(sizeStr, 10, 64)
|
||||
return val
|
||||
}
|
||||
}
|
||||
@@ -60,7 +60,7 @@ func (u *DockerService) LoadDockerStatus() *dto.DockerStatus {
|
||||
return &data
|
||||
}
|
||||
data.IsExist = true
|
||||
data.IsActive, _ = systemctl.IsActive("docker")
|
||||
data.IsActive = true
|
||||
client, err := docker.NewDockerClient()
|
||||
if err != nil {
|
||||
global.LOG.Errorf("load docker client failed, err: %v", err)
|
||||
|
||||
@@ -580,13 +580,6 @@ func (f *FileService) ReadLogByLine(req request.FileReadByLineReq) (*response.Fi
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if req.Latest && req.Page == 1 && len(logFileRes.Lines) < 1000 && logFileRes.TotalPages > 1 {
|
||||
res, err := files.ReadFileByLine(logFilePath, logFileRes.TotalPages-1, req.PageSize, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
logFileRes.Lines = append(res.Lines, logFileRes.Lines...)
|
||||
}
|
||||
scope = "page"
|
||||
lines = logFileRes.Lines
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@ type FirewallService struct{}
|
||||
type IFirewallService interface {
|
||||
LoadBaseInfo() (dto.FirewallBaseInfo, error)
|
||||
SearchWithPage(search dto.RuleSearch) (int64, interface{}, error)
|
||||
OperateFirewall(operation string) error
|
||||
OperateFirewall(req dto.FirewallOperation) error
|
||||
OperatePortRule(req dto.PortRuleOperate, reload bool) error
|
||||
OperateForwardRule(req dto.ForwardRuleOperate) error
|
||||
OperateAddressRule(req dto.AddrRuleOperate, reload bool) error
|
||||
@@ -177,13 +177,13 @@ func (u *FirewallService) SearchWithPage(req dto.RuleSearch) (int64, interface{}
|
||||
return int64(total), backDatas, nil
|
||||
}
|
||||
|
||||
func (u *FirewallService) OperateFirewall(operation string) error {
|
||||
func (u *FirewallService) OperateFirewall(req dto.FirewallOperation) error {
|
||||
client, err := firewall.NewFirewallClient()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
needRestartDocker := false
|
||||
switch operation {
|
||||
switch req.Operation {
|
||||
case "start":
|
||||
if err := client.Start(); err != nil {
|
||||
return err
|
||||
@@ -208,9 +208,9 @@ func (u *FirewallService) OperateFirewall(operation string) error {
|
||||
case "enablePing":
|
||||
return u.updatePingStatus("1")
|
||||
default:
|
||||
return fmt.Errorf("not supported operation: %s", operation)
|
||||
return fmt.Errorf("not supported operation: %s", req.Operation)
|
||||
}
|
||||
if needRestartDocker {
|
||||
if needRestartDocker && req.WithDockerRestart {
|
||||
if err := restartDocker(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -59,7 +59,11 @@ func (m McpServerService) Page(req request.McpServerSearch) response.McpServersR
|
||||
Environments: make([]request.Environment, 0),
|
||||
Volumes: make([]request.Volume, 0),
|
||||
}
|
||||
project, _ := docker.GetComposeProject(item.Name, path.Join(global.Dir.McpDir, item.Name), []byte(item.DockerCompose), []byte(item.Env), true)
|
||||
project, err := docker.GetComposeProject(item.Name, path.Join(global.Dir.McpDir, item.Name), []byte(item.DockerCompose), []byte(item.Env), true)
|
||||
if err != nil {
|
||||
global.LOG.Errorf("get mcp compose project error: %s", err.Error())
|
||||
continue
|
||||
}
|
||||
for _, service := range project.Services {
|
||||
if service.Environment != nil {
|
||||
for key, value := range service.Environment {
|
||||
@@ -86,11 +90,7 @@ func (m McpServerService) Page(req request.McpServerSearch) response.McpServersR
|
||||
}
|
||||
|
||||
func (m McpServerService) Update(req request.McpServerUpdate) error {
|
||||
go func() {
|
||||
if err := docker.PullImage("supercorp/supergateway:latest"); err != nil {
|
||||
global.LOG.Errorf("docker pull mcp image error: %s", err.Error())
|
||||
}
|
||||
}()
|
||||
go pullImage(req.Type)
|
||||
mcpServer, err := mcpServerRepo.GetFirst(repo.WithByID(req.ID))
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -111,10 +111,14 @@ func (m McpServerService) Update(req request.McpServerUpdate) error {
|
||||
mcpServer.Port = req.Port
|
||||
mcpServer.Command = req.Command
|
||||
mcpServer.BaseURL = req.BaseURL
|
||||
mcpServer.SsePath = req.SsePath
|
||||
mcpServer.HostIP = req.HostIP
|
||||
mcpServer.StreamableHttpPath = req.StreamableHttpPath
|
||||
mcpServer.OutputTransport = req.OutputTransport
|
||||
mcpServer.Type = req.Type
|
||||
if req.OutputTransport == "sse" {
|
||||
mcpServer.SsePath = req.SsePath
|
||||
} else {
|
||||
mcpServer.StreamableHttpPath = req.StreamableHttpPath
|
||||
}
|
||||
if err := handleCreateParams(mcpServer, req.Environments, req.Volumes); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -137,11 +141,7 @@ func (m McpServerService) Update(req request.McpServerUpdate) error {
|
||||
}
|
||||
|
||||
func (m McpServerService) Create(create request.McpServerCreate) error {
|
||||
go func() {
|
||||
if err := docker.PullImage("supercorp/supergateway:latest"); err != nil {
|
||||
global.LOG.Errorf("docker pull mcp image error: %s", err.Error())
|
||||
}
|
||||
}()
|
||||
go pullImage(create.Type)
|
||||
servers, _ := mcpServerRepo.List()
|
||||
for _, server := range servers {
|
||||
if server.Port == create.Port {
|
||||
@@ -166,18 +166,23 @@ func (m McpServerService) Create(create request.McpServerCreate) error {
|
||||
}
|
||||
mcpDir := path.Join(global.Dir.McpDir, create.Name)
|
||||
mcpServer := &model.McpServer{
|
||||
Name: create.Name,
|
||||
ContainerName: create.ContainerName,
|
||||
Port: create.Port,
|
||||
Command: create.Command,
|
||||
Status: constant.StatusStarting,
|
||||
BaseURL: create.BaseURL,
|
||||
SsePath: create.SsePath,
|
||||
Dir: mcpDir,
|
||||
HostIP: create.HostIP,
|
||||
StreamableHttpPath: create.StreamableHttpPath,
|
||||
OutputTransport: create.OutputTransport,
|
||||
Name: create.Name,
|
||||
ContainerName: create.ContainerName,
|
||||
Port: create.Port,
|
||||
Command: create.Command,
|
||||
Status: constant.StatusStarting,
|
||||
BaseURL: create.BaseURL,
|
||||
Dir: mcpDir,
|
||||
HostIP: create.HostIP,
|
||||
OutputTransport: create.OutputTransport,
|
||||
Type: create.Type,
|
||||
}
|
||||
if create.OutputTransport == "sse" {
|
||||
mcpServer.SsePath = create.SsePath
|
||||
} else {
|
||||
mcpServer.StreamableHttpPath = create.StreamableHttpPath
|
||||
}
|
||||
|
||||
if err := handleCreateParams(mcpServer, create.Environments, create.Volumes); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -447,8 +452,14 @@ func addProxy(server *model.McpServer) {
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
location.UpdateDirective("proxy_pass", []string{fmt.Sprintf("http://127.0.0.1:%d%s", server.Port, server.SsePath)})
|
||||
location.ChangePath("^~", server.SsePath)
|
||||
var proxyPath string
|
||||
if server.OutputTransport == "sse" {
|
||||
proxyPath = server.SsePath
|
||||
} else {
|
||||
proxyPath = server.StreamableHttpPath
|
||||
}
|
||||
location.UpdateDirective("proxy_pass", []string{fmt.Sprintf("http://127.0.0.1:%d%s", server.Port, proxyPath)})
|
||||
location.ChangePath("^~", proxyPath)
|
||||
if err = nginx.WriteConfig(config, nginx.IndentedStyle); err != nil {
|
||||
global.LOG.Errorf("write config failed, err: %v", buserr.WithErr("ErrUpdateBuWebsite", err))
|
||||
return
|
||||
@@ -499,8 +510,14 @@ func addMCPProxy(websiteID uint) error {
|
||||
err = errors.New("error")
|
||||
return err
|
||||
}
|
||||
location.UpdateDirective("proxy_pass", []string{fmt.Sprintf("http://127.0.0.1:%d%s", server.Port, server.SsePath)})
|
||||
location.ChangePath("^~", server.SsePath)
|
||||
var proxyPath string
|
||||
if server.OutputTransport == "sse" {
|
||||
proxyPath = server.SsePath
|
||||
} else {
|
||||
proxyPath = server.StreamableHttpPath
|
||||
}
|
||||
location.UpdateDirective("proxy_pass", []string{fmt.Sprintf("http://127.0.0.1:%d%s", server.Port, proxyPath)})
|
||||
location.ChangePath("^~", proxyPath)
|
||||
if err = nginx.WriteConfig(config, nginx.IndentedStyle); err != nil {
|
||||
return buserr.WithErr("ErrUpdateBuWebsite", err)
|
||||
}
|
||||
@@ -590,6 +607,11 @@ func handleCreateParams(mcpServer *model.McpServer, environments []request.Envir
|
||||
}
|
||||
serviceValue["volumes"] = volumeList
|
||||
}
|
||||
if mcpServer.Type == "npx" {
|
||||
serviceValue["image"] = "supercorp/supergateway:latest"
|
||||
} else {
|
||||
serviceValue["image"] = "supercorp/supergateway:uvx"
|
||||
}
|
||||
|
||||
services[mcpServer.Name] = serviceValue
|
||||
composeByte, err := yaml.Marshal(composeMap)
|
||||
@@ -641,7 +663,7 @@ func syncMcpServerContainerStatus(mcpServer *model.McpServer) error {
|
||||
case "restarting":
|
||||
mcpServer.Status = constant.StatusRestarting
|
||||
default:
|
||||
if mcpServer.Status != constant.StatusBuilding {
|
||||
if mcpServer.Status != constant.StatusStarting {
|
||||
mcpServer.Status = constant.StatusStopped
|
||||
}
|
||||
}
|
||||
@@ -656,3 +678,15 @@ func GetWebsiteID() uint {
|
||||
websiteIDUint, _ := strconv.ParseUint(websiteID.Value, 10, 64)
|
||||
return uint(websiteIDUint)
|
||||
}
|
||||
|
||||
func pullImage(imageType string) {
|
||||
if imageType == "npx" {
|
||||
if err := docker.PullImage("supercorp/supergateway:latest"); err != nil {
|
||||
global.LOG.Errorf("docker pull mcp image error: %s", err.Error())
|
||||
}
|
||||
} else {
|
||||
if err := docker.PullImage("supercorp/supergateway:uvx"); err != nil {
|
||||
global.LOG.Errorf("docker pull mcp image error: %s", err.Error())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -241,13 +241,13 @@ func (n NginxService) Build(req request.NginxBuildReq) error {
|
||||
return err
|
||||
}
|
||||
buildTask.AddSubTaskWithOps("", func(t *task.Task) error {
|
||||
cmdMgr := cmd2.NewCommandMgr(cmd.WithTask(*buildTask), cmd.WithTimeout(60*time.Minute))
|
||||
cmdMgr := cmd2.NewCommandMgr(cmd.WithTask(*buildTask), cmd.WithTimeout(120*time.Minute))
|
||||
if err = cmdMgr.RunBashCf("docker compose -f %s build", nginxInstall.GetComposePath()); err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = compose.DownAndUp(nginxInstall.GetComposePath())
|
||||
return err
|
||||
}, nil, 0, 60*time.Minute)
|
||||
}, nil, 0, 120*time.Minute)
|
||||
|
||||
go func() {
|
||||
_ = buildTask.Execute()
|
||||
|
||||
@@ -1,21 +1,26 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"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"
|
||||
"time"
|
||||
)
|
||||
|
||||
type ProcessService struct{}
|
||||
|
||||
type IProcessService interface {
|
||||
StopProcess(req request.ProcessReq) error
|
||||
GetProcessInfoByPID(pid int32) (*websocket.PsProcessData, error)
|
||||
}
|
||||
|
||||
func NewIProcessService() IProcessService {
|
||||
return &ProcessService{}
|
||||
}
|
||||
|
||||
func (p *ProcessService) StopProcess(req request.ProcessReq) error {
|
||||
func (ps *ProcessService) StopProcess(req request.ProcessReq) error {
|
||||
proc, err := process.NewProcess(req.PID)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -25,3 +30,106 @@ func (p *ProcessService) StopProcess(req request.ProcessReq) error {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ps *ProcessService) GetProcessInfoByPID(pid int32) (*websocket.PsProcessData, error) {
|
||||
p, err := process.NewProcess(pid)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("get process info by pid %v: %v", pid, err)
|
||||
}
|
||||
|
||||
exists, err := p.IsRunning()
|
||||
if err != nil || !exists {
|
||||
return nil, fmt.Errorf("process %v is not running", pid)
|
||||
}
|
||||
|
||||
data := &websocket.PsProcessData{
|
||||
PID: pid,
|
||||
}
|
||||
|
||||
if name, err := p.Name(); err == nil {
|
||||
data.Name = name
|
||||
}
|
||||
|
||||
if ppid, err := p.Ppid(); err == nil {
|
||||
data.PPID = ppid
|
||||
}
|
||||
|
||||
if username, err := p.Username(); err == nil {
|
||||
data.Username = username
|
||||
}
|
||||
|
||||
if status, err := p.Status(); err == nil {
|
||||
if len(status) > 0 {
|
||||
data.Status = status[0]
|
||||
}
|
||||
}
|
||||
|
||||
if createTime, err := p.CreateTime(); err == nil {
|
||||
data.StartTime = time.Unix(createTime/1000, 0).Format("2006-01-02 15:04:05")
|
||||
}
|
||||
|
||||
if numThreads, err := p.NumThreads(); err == nil {
|
||||
data.NumThreads = numThreads
|
||||
}
|
||||
|
||||
if connections, err := p.Connections(); err == nil {
|
||||
data.NumConnections = len(connections)
|
||||
|
||||
var connects []websocket.ProcessConnect
|
||||
for _, conn := range connections {
|
||||
pc := websocket.ProcessConnect{
|
||||
Status: conn.Status,
|
||||
Laddr: conn.Laddr,
|
||||
Raddr: conn.Raddr,
|
||||
PID: pid,
|
||||
Name: data.Name,
|
||||
}
|
||||
connects = append(connects, pc)
|
||||
}
|
||||
data.Connects = connects
|
||||
}
|
||||
|
||||
if cpuPercent, err := p.CPUPercent(); err == nil {
|
||||
data.CpuValue = cpuPercent
|
||||
data.CpuPercent = fmt.Sprintf("%.2f%%", cpuPercent)
|
||||
}
|
||||
|
||||
if ioCounters, err := p.IOCounters(); err == nil {
|
||||
data.DiskRead = common.FormatBytes(ioCounters.ReadBytes)
|
||||
data.DiskWrite = common.FormatBytes(ioCounters.WriteBytes)
|
||||
}
|
||||
|
||||
if cmdline, err := p.Cmdline(); err == nil {
|
||||
data.CmdLine = cmdline
|
||||
}
|
||||
|
||||
if memInfo, err := p.MemoryInfo(); err == nil {
|
||||
data.Rss = common.FormatBytes(memInfo.RSS)
|
||||
data.VMS = common.FormatBytes(memInfo.VMS)
|
||||
data.HWM = common.FormatBytes(memInfo.HWM)
|
||||
data.Data = common.FormatBytes(memInfo.Data)
|
||||
data.Stack = common.FormatBytes(memInfo.Stack)
|
||||
data.Locked = common.FormatBytes(memInfo.Locked)
|
||||
data.Swap = common.FormatBytes(memInfo.Swap)
|
||||
data.RssValue = memInfo.RSS
|
||||
} else {
|
||||
data.Rss = "--"
|
||||
data.Data = "--"
|
||||
data.VMS = "--"
|
||||
data.HWM = "--"
|
||||
data.Stack = "--"
|
||||
data.Locked = "--"
|
||||
data.Swap = "--"
|
||||
data.RssValue = 0
|
||||
}
|
||||
|
||||
if envs, err := p.Environ(); err == nil {
|
||||
data.Envs = envs
|
||||
}
|
||||
|
||||
if openFiles, err := p.OpenFiles(); err == nil {
|
||||
data.OpenFiles = openFiles
|
||||
}
|
||||
|
||||
return data, nil
|
||||
}
|
||||
|
||||
@@ -6,7 +6,9 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/agent/app/dto"
|
||||
"github.com/1Panel-dev/1Panel/agent/app/model"
|
||||
"github.com/1Panel-dev/1Panel/agent/buserr"
|
||||
"github.com/1Panel-dev/1Panel/agent/constant"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/encrypt"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/ssh"
|
||||
"github.com/jinzhu/copier"
|
||||
@@ -18,7 +20,6 @@ type ISettingService interface {
|
||||
GetSettingInfo() (*dto.SettingInfo, error)
|
||||
Update(key, value string) error
|
||||
|
||||
GetSSHInfo() (string, error)
|
||||
TestConnByInfo(req dto.SSHConnData) bool
|
||||
SaveConnInfo(req dto.SSHConnData) error
|
||||
GetSystemProxy() (*dto.SystemProxy, error)
|
||||
@@ -55,14 +56,6 @@ func (u *SettingService) Update(key, value string) error {
|
||||
return settingRepo.UpdateOrCreate(key, value)
|
||||
}
|
||||
|
||||
func (u *SettingService) GetSSHInfo() (string, error) {
|
||||
conn, err := settingRepo.GetValueByKey("LocalSSHConn")
|
||||
if err != nil || len(conn) == 0 {
|
||||
return "", err
|
||||
}
|
||||
return encrypt.StringDecrypt(conn)
|
||||
}
|
||||
|
||||
func (u *SettingService) TestConnByInfo(req dto.SSHConnData) bool {
|
||||
if req.AuthMode == "password" && len(req.Password) != 0 {
|
||||
password, err := base64.StdEncoding.DecodeString(req.Password)
|
||||
@@ -121,9 +114,12 @@ func (u *SettingService) SaveConnInfo(req dto.SSHConnData) error {
|
||||
}
|
||||
defer client.Close()
|
||||
|
||||
localConn, _ := json.Marshal(&connInfo)
|
||||
var connItem model.LocalConnInfo
|
||||
_ = copier.Copy(&connItem, &req)
|
||||
localConn, _ := json.Marshal(&connItem)
|
||||
connAfterEncrypt, _ := encrypt.StringEncrypt(string(localConn))
|
||||
_ = settingRepo.Update("LocalSSHConn", connAfterEncrypt)
|
||||
_ = settingRepo.Update("LocalSSHConnShow", constant.StatusEnable)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -140,10 +136,15 @@ func (u *SettingService) GetSystemProxy() (*dto.SystemProxy, error) {
|
||||
|
||||
func (u *SettingService) GetSettingByKey(key string) string {
|
||||
switch key {
|
||||
case "SystemIP":
|
||||
case "LocalSSHConn":
|
||||
value, _ := settingRepo.GetValueByKey(key)
|
||||
if len(value) == 0 {
|
||||
return ""
|
||||
}
|
||||
itemStr, _ := encrypt.StringDecrypt(value)
|
||||
return itemStr
|
||||
default:
|
||||
value, _ := settingRepo.GetValueByKey(key)
|
||||
return value
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"github.com/1Panel-dev/1Panel/agent/app/dto/response"
|
||||
"os/exec"
|
||||
)
|
||||
|
||||
type SystemService struct{}
|
||||
|
||||
type ISystemService interface {
|
||||
IsComponentExist(name string) response.ComponentInfo
|
||||
}
|
||||
|
||||
func NewISystemService() ISystemService {
|
||||
return &SystemService{}
|
||||
}
|
||||
|
||||
func (s *SystemService) IsComponentExist(name string) response.ComponentInfo {
|
||||
info := response.ComponentInfo{}
|
||||
path, err := exec.LookPath(name)
|
||||
if err != nil {
|
||||
info.Exists = false
|
||||
info.Error = err.Error()
|
||||
return info
|
||||
}
|
||||
info.Exists = true
|
||||
info.Path = path
|
||||
return info
|
||||
}
|
||||
@@ -59,7 +59,11 @@ func (w WebsiteSSLService) Page(search request.WebsiteSSLSearch) (int64, []respo
|
||||
result []response.WebsiteSSLDTO
|
||||
opts []repo.DBOption
|
||||
)
|
||||
opts = append(opts, repo.WithOrderBy("created_at desc"))
|
||||
if search.OrderBy != "" && search.Order != "null" {
|
||||
opts = append(opts, repo.WithOrderRuleBy(search.OrderBy, search.Order))
|
||||
} else {
|
||||
opts = append(opts, repo.WithOrderBy("created_at desc"))
|
||||
}
|
||||
if search.Domain != "" {
|
||||
opts = append(opts, websiteSSLRepo.WithByDomain(search.Domain))
|
||||
}
|
||||
|
||||
@@ -67,6 +67,7 @@ const (
|
||||
TaskPush = "TaskPush"
|
||||
TaskClean = "TaskClean"
|
||||
TaskHandle = "TaskHandle"
|
||||
TaskScan = "TaskScan"
|
||||
TaskExec = "TaskExec"
|
||||
)
|
||||
|
||||
@@ -77,6 +78,7 @@ const (
|
||||
TaskScopeRuntime = "Runtime"
|
||||
TaskScopeDatabase = "Database"
|
||||
TaskScopeCronjob = "Cronjob"
|
||||
TaskScopeClam = "Clam"
|
||||
TaskScopeSystem = "System"
|
||||
TaskScopeAppStore = "AppStore"
|
||||
TaskScopeSnapshot = "Snapshot"
|
||||
|
||||
@@ -8,12 +8,31 @@ import (
|
||||
|
||||
// @title 1Panel
|
||||
// @version 2.0
|
||||
// @description 开源Linux面板
|
||||
// @description Top-Rated Web-based Linux Server Management Tool
|
||||
// @termsOfService http://swagger.io/terms/
|
||||
// @license.name Apache 2.0
|
||||
// @license.url http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
// @host localhost
|
||||
// @license.name GPL-3.0
|
||||
// @license.url https://www.gnu.org/licenses/gpl-3.0.html
|
||||
// @BasePath /api/v2
|
||||
// @schemes http https
|
||||
|
||||
// @securityDefinitions.apikey ApiKeyAuth
|
||||
// @description Custom Token Format, Format: md5('1panel' + API-Key + UnixTimestamp).
|
||||
// @description ```
|
||||
// @description eg:
|
||||
// @description curl -X GET "http://{host}:{port}/api/v2/toolbox/device/base" \
|
||||
// @description -H "1Panel-Token: <1panel_token>" \
|
||||
// @description -H "1Panel-Timestamp: <current_unix_timestamp>"
|
||||
// @description ```
|
||||
// @description - `1Panel-Token` is the key for the panel API Key.
|
||||
// @type apiKey
|
||||
// @in Header
|
||||
// @name 1Panel-Token
|
||||
// @securityDefinitions.apikey Timestamp
|
||||
// @type apiKey
|
||||
// @in header
|
||||
// @name 1Panel-Timestamp
|
||||
// @description - `1Panel-Timestamp` is the Unix timestamp of the current time in seconds.
|
||||
|
||||
func main() {
|
||||
if err := cmd.RootCmd.Execute(); err != nil {
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
|
||||
@@ -5,4 +5,8 @@ const (
|
||||
Supervisor = "supervisor"
|
||||
SupervisorConfigPath = "SupervisorConfigPath"
|
||||
SupervisorServiceName = "SupervisorServiceName"
|
||||
|
||||
ClamServiceNameCentOs = "clamd@scan.service"
|
||||
ClamServiceNameUbuntu = "clamav-daemon.service"
|
||||
FreshClamService = "clamav-freshclam.service"
|
||||
)
|
||||
|
||||
+20
-1
@@ -15,6 +15,9 @@ ErrApiConfigIPInvalid: 'The IP used to call the API interface is not in the whit
|
||||
ErrApiConfigDisable: 'This interface prohibits the use of API interface calls: {{ .detail }}'
|
||||
ErrApiConfigKeyTimeInvalid: 'API interface timestamp error: {{ .detail }}'
|
||||
|
||||
ErrMinQuickJump: "Please set at least one quick jump entry!"
|
||||
ErrMaxQuickJump: "You can set up to four quick jump entries!"
|
||||
|
||||
#common
|
||||
ErrUsernameIsExist: 'Username already exists'
|
||||
ErrNameIsExist: 'Name already exists'
|
||||
@@ -34,6 +37,7 @@ ErrGroupIsDefault: 'Default group, cannot be deleted'
|
||||
ErrGroupIsInWebsiteUse: 'The group is being used by another website and cannot be deleted.'
|
||||
|
||||
#backup
|
||||
Localhost: 'Local Machine'
|
||||
ErrBackupInUsed: 'The backup account has been used in the scheduled task and cannot be deleted.'
|
||||
ErrBackupCheck: 'Backup account test connection failed {{ .err }}'
|
||||
ErrBackupLocalDelete: 'Deleting the local server backup account is not supported yet'
|
||||
@@ -230,6 +234,8 @@ Upload: "Upload"
|
||||
IgnoreBackupErr: "Backup failed, error: {{ .detail }}, ignoring this error..."
|
||||
IgnoreUploadErr: "Upload failed, error: {{ .detail }}, ignoring this error..."
|
||||
LoadBackupFailed: "Failed to get backup account connection, error: {{ .detail }}"
|
||||
InExecuting: "The current task is being executed, please do not repeat the execution!"
|
||||
NoSuchResource: "No backup content found in the database, skipping..."
|
||||
|
||||
#toolbox
|
||||
ErrNotExistUser: 'The current user does not exist, please modify and try again!'
|
||||
@@ -348,6 +354,10 @@ TaskIsExecuting: 'Task is running'
|
||||
CustomAppstore: 'Custom application warehouse'
|
||||
TaskExec: 'Execute'
|
||||
|
||||
# task - clam
|
||||
Clamscan: "Scan {{ .name }}"
|
||||
TaskScan: "Scan"
|
||||
|
||||
# task - ai
|
||||
OllamaModelPull: 'Pull Ollama model {{ .name }}'
|
||||
OllamaModelSize: 'Get the size of Ollama model {{ .name }}'
|
||||
@@ -467,4 +477,13 @@ PanelPwdExpirationAlert: "Your 1Panel password will expire in {{ .day }} days. P
|
||||
CommonAlert: "Your 1Panel, {{ .msg }}, please log in to the panel for details."
|
||||
NodeExceptionAlert: "Your 1Panel, {{ .num }} nodes are abnormal, please log in to the panel for details."
|
||||
LicenseExceptionAlert: "Your 1Panel, {{ .num }} licenses are abnormal, please log in to the panel for details."
|
||||
SSHAndPanelLoginAlert: "Your 1Panel, abnormal panel {{ .name }} login from {{ .ip }}, please log in to the panel for details."
|
||||
SSHAndPanelLoginAlert: "Your 1Panel, abnormal panel {{ .name }} login from {{ .ip }}, please log in to the panel for details."
|
||||
|
||||
#disk
|
||||
DeviceNotFound: "Device {{ .name }} not found"
|
||||
DeviceIsMounted: "Device {{ .name }} is mounted, please unmount first"
|
||||
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"
|
||||
+20
-1
@@ -15,6 +15,9 @@ ErrApiConfigIPInvalid: 'API インターフェースの呼び出しに使用さ
|
||||
ErrApiConfigDisable: 'このインターフェースは、API インターフェース呼び出しの使用を禁止しています: {{ .detail }}'
|
||||
ErrApiConfigKeyTimeInvalid: 'API インターフェースのタイムスタンプ エラー: {{ .detail }}'
|
||||
|
||||
ErrMinQuickJump: "少なくとも1つのクイックジャンプエントリを設定してください!"
|
||||
ErrMaxQuickJump: "最大4つのクイックジャンプエントリを設定できます!"
|
||||
|
||||
#common
|
||||
ErrUsernameIsExist: 'ユーザー名は既に存在します'
|
||||
ErrNameIsExist: '名前は既に存在します'
|
||||
@@ -34,6 +37,7 @@ ErrGroupIsDefault: 'デフォルト グループ、削除できません'
|
||||
ErrGroupIsInWebsiteUse: 'グループは別の Web サイトで使用されているため、削除できません。'
|
||||
|
||||
#backup
|
||||
Localhost: 'ローカルマシン'
|
||||
ErrBackupInUsed: 'バックアップ アカウントはスケジュールされたタスクで使用されているため、削除できません。'
|
||||
ErrBackupCheck: 'バックアップ アカウントのテスト接続に失敗しました {{ .err }}'
|
||||
ErrBackupLocalDelete: 'ローカル サーバーのバックアップ アカウントの削除はまだサポートされていません'
|
||||
@@ -230,6 +234,8 @@ Upload: "アップロード"
|
||||
IgnoreBackupErr: "バックアップ失敗、エラー: {{ .detail }}、このエラーを無視します..."
|
||||
IgnoreUploadErr: "アップロード失敗、エラー: {{ .detail }}、このエラーを無視します..."
|
||||
LoadBackupFailed: "バックアップアカウントの接続取得に失敗、エラー: {{ .detail }}"
|
||||
InExecuting: "現在のタスクは実行中です。重複して実行しないでください!"
|
||||
NoSuchResource: "データベースにバックアップ内容が見つかりませんでした。スキップします..."
|
||||
|
||||
#toolbox
|
||||
ErrNotExistUser: '現在のユーザーは存在しません。変更してもう一度お試しください。'
|
||||
@@ -348,6 +354,10 @@ TaskIsExecuting: 'タスクは実行中です'
|
||||
CustomAppstore: 'カスタム アプリケーション ウェアハウス'
|
||||
TaskExec: '実行'
|
||||
|
||||
# task - clam
|
||||
Clamscan: "{{ .name }} をスキャン"
|
||||
TaskScan: "スキャン"
|
||||
|
||||
#task - ai
|
||||
OllamaModelPull: 'Ollama モデル {{ .name }} をプルします'
|
||||
OllamaModelSize: "Ollama モデル {{ .name }} のサイズを取得します"
|
||||
@@ -467,4 +477,13 @@ PanelPwdExpirationAlert: "1Panel のパスワードは {{ .day }} 日後に期
|
||||
CommonAlert: "お使いの1Panel、{{ .msg }}、詳細はパネルにログインしてご確認ください。"
|
||||
NodeExceptionAlert: "お使いの1Panel、{{ .num }}個のノードに異常があります。詳細はパネルにログインしてご確認ください。"
|
||||
LicenseExceptionAlert: "お使いの1Panel、{{ .num }}個のライセンスに異常があります。詳細はパネルにログインしてご確認ください。"
|
||||
SSHAndPanelLoginAlert: "お使いの1Panel、パネル{{ .name }}が{{ .ip }}から異常ログインしました。詳細はパネルにログインしてご確認ください。"
|
||||
SSHAndPanelLoginAlert: "お使いの1Panel、パネル{{ .name }}が{{ .ip }}から異常ログインしました。詳細はパネルにログインしてご確認ください。"
|
||||
|
||||
#disk
|
||||
DeviceNotFound: "デバイス {{ .name }} が見つかりません"
|
||||
DeviceIsMounted: "デバイス {{ .name }} はマウントされています、まずアンマウントしてください"
|
||||
PartitionDiskErr: "パーティションに失敗しました、{{ .err }}"
|
||||
FormatDiskErr: "ディスクのフォーマットに失敗しました、{{ .err }}"
|
||||
MountDiskErr: "ディスクのマウントに失敗しました、{{ .err }}"
|
||||
UnMountDiskErr: "ディスクのアンマウントに失敗しました、{{ .err }}"
|
||||
XfsNotFound: "xfs ファイルシステムが検出されませんでした、最初に xfsprogs をインストールしてください"
|
||||
+21
-2
@@ -15,6 +15,9 @@ ErrApiConfigIPInvalid: 'API 인터페이스를 호출하는 데 사용된 IP가
|
||||
ErrApiConfigDisable: '이 인터페이스는 API 인터페이스 호출 사용을 금지합니다: {{ .detail }}'
|
||||
ErrApiConfigKeyTimeInvalid: 'API 인터페이스 타임스탬프 오류: {{ .detail }}'
|
||||
|
||||
ErrMinQuickJump: "최소 하나의 빠른 점프 항목을 설정해 주세요!"
|
||||
ErrMaxQuickJump: "최대 네 개의 빠른 점프 항목을 설정할 수 있습니다!"
|
||||
|
||||
#흔한
|
||||
ErrUsernameIsExist: '사용자 이름이 이미 존재합니다'
|
||||
ErrNameIsExist: '이름이 이미 존재합니다'
|
||||
@@ -34,6 +37,7 @@ ErrGroupIsDefault: '기본 그룹, 삭제할 수 없습니다'
|
||||
ErrGroupIsInWebsiteUse: '그룹이 다른 웹사이트에서 사용 중이므로 삭제할 수 없습니다.'
|
||||
|
||||
#지원
|
||||
Localhost: '로컬 머신'
|
||||
ErrBackupInUsed: '백업 계정이 예약된 작업에 사용되었으므로 삭제할 수 없습니다.'
|
||||
ErrBackupCheck: '백업 계정 테스트 연결에 실패했습니다 {{ .err }}'
|
||||
ErrBackupLocalDelete: '로컬 서버 백업 계정 삭제는 아직 지원되지 않습니다.'
|
||||
@@ -230,6 +234,8 @@ Upload: "업로드"
|
||||
IgnoreBackupErr: "백업 실패, 오류: {{ .detail }}, 이 오류를 무시합니다..."
|
||||
IgnoreUploadErr: "업로드 실패, 오류: {{ .detail }}, 이 오류를 무시합니다..."
|
||||
LoadBackupFailed: "백업 계정 연결 획득 실패, 오류: {{ .detail }}"
|
||||
InExecuting: "현재 작업이 실행 중입니다. 중복 실행하지 마세요!"
|
||||
NoSuchResource: "데이터베이스에서 백업 내용을 찾을 수 없어 건너뜁니다..."
|
||||
|
||||
#도구상자
|
||||
ErrNotExistUser: '현재 사용자가 존재하지 않습니다. 수정한 후 다시 시도하세요!'
|
||||
@@ -346,7 +352,11 @@ SubTask: '하위 작업'
|
||||
RuntimeExtension: '런타임 환경 확장'
|
||||
TaskIsExecuting: '작업이 실행 중입니다'
|
||||
CustomAppstore: '사용자 정의 애플리케이션 웨어하우스'
|
||||
TaskExec": '실행'
|
||||
TaskExec: '실행'
|
||||
|
||||
# task - clam
|
||||
Clamscan: "{{ .name }} 스캔"
|
||||
TaskScan: "스캔"
|
||||
|
||||
# 작업 - ai
|
||||
OllamaModelPull: 'Ollama 모델 {{ .name }}을(를) 끌어오세요'
|
||||
@@ -467,4 +477,13 @@ PanelPwdExpirationAlert: "1Panel 비밀번호가 {{ .day }}일 후에 만료됩
|
||||
CommonAlert: "귀하의 1Panel, {{ .msg }}. 자세한 내용은 패널에 로그인하여 확인하세요."
|
||||
NodeExceptionAlert: "귀하의 1Panel, {{ .num }}개의 노드에 이상이 있습니다. 자세한 내용은 패널에 로그인하여 확인하세요."
|
||||
LicenseExceptionAlert: "귀하의 1Panel, {{ .num }}개의 라이선스에 이상이 있습니다. 자세한 내용은 패널에 로그인하여 확인하세요."
|
||||
SSHAndPanelLoginAlert: "귀하의 1Panel, 패널 {{ .name }}이(가) {{ .ip }}에서 비정상 로그인했습니다. 자세한 내용은 패널에 로그인하여 확인하세요."
|
||||
SSHAndPanelLoginAlert: "귀하의 1Panel, 패널 {{ .name }}이(가) {{ .ip }}에서 비정상 로그인했습니다. 자세한 내용은 패널에 로그인하여 확인하세요."
|
||||
|
||||
#disk
|
||||
DeviceNotFound: "장치 {{ .name }} 을(를) 찾을 수 없습니다"
|
||||
DeviceIsMounted: "장치 {{ .name }} 이(가) 마운트되었습니다, 먼저 마운트 해제하세요"
|
||||
PartitionDiskErr: "파티션 분할에 실패했습니다, {{ .err }}"
|
||||
FormatDiskErr: "디스크 포맷에 실패했습니다, {{ .err }}"
|
||||
MountDiskErr: "디스크 마운트에 실패했습니다, {{ .err }}"
|
||||
UnMountDiskErr: "디스크 마운트 해제에 실패했습니다, {{ .err }}"
|
||||
XfsNotFound: "xfs 파일 시스템이 감지되지 않았습니다, 먼저 xfsprogs 를 설치하세요"
|
||||
+20
-1
@@ -18,6 +18,9 @@ StartPushSSLToNode: "Mula menolak sijil ke nod"
|
||||
PushSSLToNodeFailed: "Gagal menolak sijil ke nod: {{ .err }}"
|
||||
PushSSLToNodeSuccess: "Berjaya menolak sijil ke nod"
|
||||
|
||||
ErrMinQuickJump: "Sila tetapkan sekurang-kurangnya satu entri lompat pantas!"
|
||||
ErrMaxQuickJump: "Anda boleh menetapkan sehingga empat entri lompat pantas!"
|
||||
|
||||
#biasa
|
||||
ErrUsernameIsExist: 'Nama pengguna sudah wujud'
|
||||
ErrNameIsExist: 'Nama sudah wujud'
|
||||
@@ -37,6 +40,7 @@ ErrGroupIsDefault: 'Kumpulan lalai, tidak boleh dipadamkan'
|
||||
ErrGroupIsInWebsiteUse: 'Kumpulan sedang digunakan oleh tapak web lain dan tidak boleh dipadamkan.'
|
||||
|
||||
#sandaran
|
||||
Localhost: 'Mesin Tempatan'
|
||||
ErrBackupInUsed: 'Akaun sandaran telah digunakan dalam tugas yang dijadualkan dan tidak boleh dipadamkan.'
|
||||
ErrBackupCheck: 'Sambungan ujian akaun sandaran gagal {{ .err }}'
|
||||
ErrBackupLocalDelete: 'Memadam akaun sandaran pelayan tempatan belum disokong lagi'
|
||||
@@ -230,6 +234,8 @@ Upload: "Muat Naik"
|
||||
IgnoreBackupErr: "Sandaran gagal, ralat: {{ .detail }}, abaikan ralat ini..."
|
||||
IgnoreUploadErr: "Muat naik gagal, ralat: {{ .detail }}, abaikan ralat ini..."
|
||||
LoadBackupFailed: "Gagal mendapatkan sambungan akaun sandaran, ralat: {{ .detail }}"
|
||||
InExecuting: "Tugas semasa sedang dilaksanakan, jangan ulangi pelaksanaan!"
|
||||
NoSuchResource: "Tiada kandungan sandaran ditemui dalam pangkalan data, dilangkau..."
|
||||
|
||||
#kotak alat
|
||||
ErrNotExistUser: 'Pengguna semasa tidak wujud, sila ubah suai dan cuba lagi!'
|
||||
@@ -348,6 +354,10 @@ TaskIsExecuting: 'Tugas sedang berjalan'
|
||||
CustomAppstore: 'Gudang aplikasi tersuai'
|
||||
TaskExec: 'Laksanakan'
|
||||
|
||||
# task - clam
|
||||
Clamscan: "Imbas {{ .name }}"
|
||||
TaskScan: "Imbas"
|
||||
|
||||
# tugasan - ai
|
||||
OllamaModelPull: 'Tarik model Ollama {{ .name }}'
|
||||
OllamaModelSize: 'Dapatkan saiz model Ollama {{ .name }}'
|
||||
@@ -467,4 +477,13 @@ PanelPwdExpirationAlert: "Kata laluan 1Panel anda akan tamat dalam {{ .day }} ha
|
||||
CommonAlert: "1Panel anda, {{ .msg }}, sila log masuk ke panel untuk maklumat lanjut."
|
||||
NodeExceptionAlert: "1Panel anda, {{ .num }} nod bermasalah, sila log masuk ke panel untuk maklumat lanjut."
|
||||
LicenseExceptionAlert: "1Panel anda, {{ .num }} lesen bermasalah, sila log masuk ke panel untuk maklumat lanjut."
|
||||
SSHAndPanelLoginAlert: "1Panel anda, log masuk panel {{ .name }} yang tidak normal dari {{ .ip }}, sila log masuk ke panel untuk maklumat lanjut."
|
||||
SSHAndPanelLoginAlert: "1Panel anda, log masuk panel {{ .name }} yang tidak normal dari {{ .ip }}, sila log masuk ke panel untuk maklumat lanjut."
|
||||
|
||||
#disk
|
||||
DeviceNotFound: "Peranti {{ .name }} tidak ditemui"
|
||||
DeviceIsMounted: "Peranti {{ .name }} telah dikaitkan, sila nyahkaitkan terlebih dahulu"
|
||||
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"
|
||||
@@ -18,6 +18,9 @@ StartPushSSLToNode: "Iniciando o envio do certificado para o nó"
|
||||
PushSSLToNodeFailed: "Falha ao enviar o certificado para o nó: {{ .err }}"
|
||||
PushSSLToNodeSuccess: "Certificado enviado com sucesso para o nó"
|
||||
|
||||
ErrMinQuickJump: "Defina pelo menos uma entrada de salto rápido!"
|
||||
ErrMaxQuickJump: "Você pode definir até quatro entradas de salto rápido!"
|
||||
|
||||
#comum
|
||||
ErrUsernameIsExist: 'Nome de usuário já existe'
|
||||
ErrNameIsExist: 'Nome já existe'
|
||||
@@ -37,6 +40,7 @@ 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.'
|
||||
|
||||
#backup
|
||||
Localhost: 'Máquina Local'
|
||||
ErrBackupInUsed: 'A conta de backup foi usada na tarefa agendada e não pode ser excluída.'
|
||||
ErrBackupCheck: 'Falha na conexão de teste da conta de backup {{ .err }}'
|
||||
ErrBackupLocalDelete: 'A exclusão da conta de backup do servidor local ainda não é suportada'
|
||||
@@ -230,6 +234,8 @@ Upload: "Enviar"
|
||||
IgnoreBackupErr: "Backup falhou, erro: {{ .detail }}, ignorando este erro..."
|
||||
IgnoreUploadErr: "Upload falhou, erro: {{ .detail }}, ignorando este erro..."
|
||||
LoadBackupFailed: "Falha ao obter conexão da conta de backup, erro: {{ .detail }}"
|
||||
InExecuting: "A tarefa atual está sendo executada, não repita a execução!"
|
||||
NoSuchResource: "Nenhum conteúdo de backup encontrado no banco de dados, ignorando..."
|
||||
|
||||
#caixa de ferramentas
|
||||
ErrNotExistUser: 'O usuário atual não existe, modifique e tente novamente!'
|
||||
@@ -348,6 +354,10 @@ TaskIsExecuting: 'A tarefa está em execução'
|
||||
CustomAppstore: 'Armazém de aplicativos personalizados'
|
||||
TaskExec: 'Executar'
|
||||
|
||||
# task - clam
|
||||
Clamscan: "Escanear {{ .name }}"
|
||||
TaskScan: "Escanear"
|
||||
|
||||
# tarefa - ai
|
||||
OllamaModelPull: 'Puxar modelo Ollama {{ .name }}'
|
||||
OllamaModelSize: 'Obtenha o tamanho do modelo Ollama {{ .name }}'
|
||||
@@ -467,4 +477,13 @@ PanelPwdExpirationAlert: "A senha do 1Panel expirará em {{ .day }} dias. Acesse
|
||||
CommonAlert: "Seu 1Panel, {{ .msg }}. Para mais detalhes, faça login no painel."
|
||||
NodeExceptionAlert: "Seu 1Panel, {{ .num }} nós estão com problemas. Para mais detalhes, faça login no painel."
|
||||
LicenseExceptionAlert: "Seu 1Panel, {{ .num }} licenças estão com problemas. Para mais detalhes, faça login no painel."
|
||||
SSHAndPanelLoginAlert: "Seu 1Panel, login anormal no painel {{ .name }} a partir de {{ .ip }}. Para mais detalhes, faça login no painel."
|
||||
SSHAndPanelLoginAlert: "Seu 1Panel, login anormal no painel {{ .name }} a partir de {{ .ip }}. Para mais detalhes, faça login no painel."
|
||||
|
||||
#disk
|
||||
DeviceNotFound: "Dispositivo {{ .name }} não encontrado"
|
||||
DeviceIsMounted: "Dispositivo {{ .name }} está montado, por favor desmonte primeiro"
|
||||
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"
|
||||
+20
-1
@@ -18,6 +18,9 @@ StartPushSSLToNode: "Начало отправки сертификата на
|
||||
PushSSLToNodeFailed: "Не удалось отправить сертификат на узел: {{ .err }}"
|
||||
PushSSLToNodeSuccess: "Сертификат успешно отправлен на узел"
|
||||
|
||||
ErrMinQuickJump: "Пожалуйста, установите хотя бы одну запись быстрого перехода!"
|
||||
ErrMaxQuickJump: "Можно установить до четырех записей быстрого перехода!"
|
||||
|
||||
#общий
|
||||
ErrUsernameIsExist: 'Имя пользователя уже существует'
|
||||
ErrNameIsExist: 'Имя уже существует'
|
||||
@@ -37,6 +40,7 @@ ErrGroupIsDefault: 'Группа по умолчанию, не может быт
|
||||
ErrGroupIsInWebsiteUse: 'Группа используется другим веб-сайтом и не может быть удалена.'
|
||||
|
||||
#резервное копирование
|
||||
Localhost: 'Локальная машина'
|
||||
ErrBackupInUsed: 'Учетная запись резервного копирования использовалась в запланированной задаче и не может быть удалена.'
|
||||
ErrBackupCheck: 'Проверка подключения к резервной копии учетной записи не удалась {{ .err }}'
|
||||
ErrBackupLocalDelete: 'Удаление учетной записи резервного копирования локального сервера пока не поддерживается'
|
||||
@@ -230,6 +234,8 @@ Upload: "Загрузить"
|
||||
IgnoreBackupErr: "Ошибка резервного копирования: {{ .detail }}, игнорируем эту ошибку..."
|
||||
IgnoreUploadErr: "Ошибка загрузки: {{ .detail }}, игнорируем эту ошибку..."
|
||||
LoadBackupFailed: "Не удалось получить подключение к учетной записи резервной копии, ошибка: {{ .detail }}"
|
||||
InExecuting: "Текущая задача выполняется, не повторяйте выполнение!"
|
||||
NoSuchResource: "В базе данных не найдено содержимое резервной копии, пропускаем..."
|
||||
|
||||
#ящик для инструментов
|
||||
ErrNotExistUser: 'Текущий пользователь не существует, измените его и повторите попытку!'
|
||||
@@ -348,6 +354,10 @@ TaskIsExecuting: 'Задача выполняется'
|
||||
CustomAppstore: 'Хранилище пользовательских приложений'
|
||||
TaskExec: 'Выполнить'
|
||||
|
||||
# task - clam
|
||||
Clamscan: "Сканировать {{ .name }}"
|
||||
TaskScan: "Сканировать"
|
||||
|
||||
# задача - ай
|
||||
OllamaModelPull: 'Вытянуть модель Ollama {{ .name }}'
|
||||
OllamaModelSize: 'Получить размер модели Ollama {{ .name }}'
|
||||
@@ -467,4 +477,13 @@ PanelPwdExpirationAlert: "Пароль для 1Panel истекает через
|
||||
CommonAlert: "Ваш 1Panel, {{ .msg }}. Подробности смотрите, войдя в панель."
|
||||
NodeExceptionAlert: "Ваш 1Panel, {{ .num }} узлов работают неправильно. Подробности смотрите, войдя в панель."
|
||||
LicenseExceptionAlert: "Ваш 1Panel, {{ .num }} лицензий работают неправильно. Подробности смотрите, войдя в панель."
|
||||
SSHAndPanelLoginAlert: "Ваш 1Panel, обнаружен аномальный вход в панель {{ .name }} с {{ .ip }}. Подробности смотрите, войдя в панель."
|
||||
SSHAndPanelLoginAlert: "Ваш 1Panel, обнаружен аномальный вход в панель {{ .name }} с {{ .ip }}. Подробности смотрите, войдя в панель."
|
||||
|
||||
#disk
|
||||
DeviceNotFound: "Устройство {{ .name }} не найдено"
|
||||
DeviceIsMounted: "Устройство {{ .name }} подключено, сначала отключите"
|
||||
PartitionDiskErr: "Не удалось разделить, {{ .err }}"
|
||||
FormatDiskErr: "Не удалось отформатировать диск, {{ .err }}"
|
||||
MountDiskErr: "Не удалось подключить диск, {{ .err }}"
|
||||
UnMountDiskErr: "Не удалось отключить диск, {{ .err }}"
|
||||
XfsNotFound: "Файловая система xfs не обнаружена, сначала установите xfsprogs"
|
||||
@@ -18,6 +18,9 @@ StartPushSSLToNode: "Sertifika düğüme gönderilmeye başlandı"
|
||||
PushSSLToNodeFailed: "Sertifika düğüme gönderilemedi: {{ .err }}"
|
||||
PushSSLToNodeSuccess: "Sertifika düğüme başarıyla gönderildi"
|
||||
|
||||
ErrMinQuickJump: "Lütfen en az bir hızlı atlama girişi ayarlayın!"
|
||||
ErrMaxQuickJump: "En fazla dört hızlı atlama girişi ayarlayabilirsiniz!"
|
||||
|
||||
#common
|
||||
ErrUsernameIsExist: 'Kullanıcı adı zaten mevcut'
|
||||
ErrNameIsExist: 'İsim zaten mevcut'
|
||||
@@ -37,6 +40,7 @@ ErrGroupIsDefault: 'Varsayılan grup, silinemez'
|
||||
ErrGroupIsInWebsiteUse: 'Grup başka bir web sitesi tarafından kullanılıyor ve silinemez.'
|
||||
|
||||
#backup
|
||||
Localhost: 'Yerel Makine'
|
||||
ErrBackupInUsed: 'Yedekleme hesabı zamanlanmış görevde kullanıldı ve silinemez.'
|
||||
ErrBackupCheck: 'Yedekleme hesabı test bağlantısı başarısız {{ .err }}'
|
||||
ErrBackupLocalDelete: 'Yerel sunucu yedekleme hesabını silme henüz desteklenmiyor'
|
||||
@@ -231,6 +235,8 @@ Upload: "Yükle"
|
||||
IgnoreBackupErr: "Yedekleme başarısız, hata: {{ .detail }}, bu hata yoksayılıyor..."
|
||||
IgnoreUploadErr: "Yükleme başarısız, hata: {{ .detail }}, bu hata yoksayılıyor..."
|
||||
LoadBackupFailed: "Yedek hesap bağlantısı alınamadı, hata: {{ .detail }}"
|
||||
InExecuting: "Mevcut görev yürütülüyor, lütfen tekrar yürütmeyin!"
|
||||
NoSuchResource: "Veritabanında yedek içeriği bulunamadı, atlanıyor..."
|
||||
|
||||
#toolbox
|
||||
ErrNotExistUser: 'Mevcut kullanıcı mevcut değil, lütfen değiştirin ve tekrar deneyin!'
|
||||
@@ -349,6 +355,10 @@ TaskIsExecuting: 'Görev çalışıyor'
|
||||
CustomAppstore: 'Özel uygulama deposu'
|
||||
TaskExec: 'Çalıştır'
|
||||
|
||||
# task - clam
|
||||
Clamscan: "{{ .name }} Tara"
|
||||
TaskScan: "Tara"
|
||||
|
||||
# task - ai
|
||||
OllamaModelPull: 'Ollama modeli {{ .name }} çek'
|
||||
OllamaModelSize: 'Ollama modeli {{ .name }} boyutunu al'
|
||||
@@ -469,3 +479,12 @@ CommonAlert: "1Panel'iniz, {{ .msg }}. Detaylar için panele giriş yapınız."
|
||||
NodeExceptionAlert: "1Panel'iniz, {{ .num }} düğümde sorun var. Detaylar için panele giriş yapınız."
|
||||
LicenseExceptionAlert: "1Panel'iniz, {{ .num }} lisansda sorun var. Detaylar için panele giriş yapınız."
|
||||
SSHAndPanelLoginAlert: "1Panel'iniz, {{ .ip }} adresinden {{ .name }} paneline anormal giriş tespit edildi. Detaylar için panele giriş yapınız."
|
||||
|
||||
#disk
|
||||
DeviceNotFound: "Cihaz {{ .name }} bulunamadı"
|
||||
DeviceIsMounted: "Cihaz {{ .name }} bağlandı, önce çıkarın"
|
||||
PartitionDiskErr: "Bölümleme başarısız, {{ .err }}"
|
||||
FormatDiskErr: "Disk biçimlendirme başarısız, {{ .err }}"
|
||||
MountDiskErr: "Disk bağlama başarısız, {{ .err }}"
|
||||
UnMountDiskErr: "Disk bağının kaldırılması başarısız, {{ .err }}"
|
||||
XfsNotFound: "XFS dosya sistemi algılanmadı, lütfen önce xfsprogs'i yükleyin"
|
||||
|
||||
@@ -15,6 +15,9 @@ ErrApiConfigIPInvalid: '呼叫 API 介面 IP 不在白名單: {{ .detail }}'
|
||||
ErrApiConfigDisable: '此介面禁止使用 API 介面呼叫: {{ .detail }}'
|
||||
ErrApiConfigKeyTimeInvalid: 'API 介面時間戳記錯誤: {{ .detail }}'
|
||||
|
||||
ErrMinQuickJump: "請至少設定一個快速跳轉入口!"
|
||||
ErrMaxQuickJump: "最多可設定四個快速跳轉入口!"
|
||||
|
||||
#common
|
||||
ErrUsernameIsExist: '使用者名稱已存在'
|
||||
ErrNameIsExist: '名稱已存在'
|
||||
@@ -34,6 +37,7 @@ ErrGroupIsDefault: '預設分組,無法刪除'
|
||||
ErrGroupIsInWebsiteUse: '分組正在被其他網站使用,無法刪除'
|
||||
|
||||
#backup
|
||||
Localhost: '本機'
|
||||
ErrBackupInUsed: '此備份帳號已在排程任務中使用,無法刪除'
|
||||
ErrBackupCheck: '備份帳號測試連線失敗{{ .err }}'
|
||||
ErrBackupLocalDelete: '暫時不支援刪除本機伺服器備份帳號'
|
||||
@@ -229,6 +233,8 @@ Upload: "上傳"
|
||||
IgnoreBackupErr: "備份失敗,錯誤:{{ .detail }},忽略本次錯誤..."
|
||||
IgnoreUploadErr: "上傳失敗,錯誤:{{ .detail }},忽略本次錯誤..."
|
||||
LoadBackupFailed: "獲取備份帳號連接失敗,錯誤:{{ .detail }}"
|
||||
InExecuting: "當前任務正在執行中,請勿重複執行!"
|
||||
NoSuchResource: "資料庫中未能查詢到備份內容,跳過..."
|
||||
|
||||
#toolbox
|
||||
ErrNotExistUser: '目前使用者不存在,請修改後重試!'
|
||||
@@ -347,6 +353,10 @@ TaskIsExecuting: '任務正在運作'
|
||||
CustomAppstore: '自訂應用程式倉庫'
|
||||
TaskExec: '執行'
|
||||
|
||||
# task - clam
|
||||
Clamscan: "掃描 {{ .name }}"
|
||||
TaskScan: "掃描"
|
||||
|
||||
# task - ai
|
||||
OllamaModelPull: '拉取 Ollama 模型{{ .name }} '
|
||||
OllamaModelSize: '取得 Ollama 模型{{ .name }} 大小'
|
||||
@@ -467,3 +477,12 @@ CommonAlert: "您的 1Panel 面板,{{ .msg }},詳情請登入面板查看。
|
||||
NodeExceptionAlert: "您的 1Panel 面板,{{ .num }} 個節點存在異常,詳情請登入面板查看。"
|
||||
LicenseExceptionAlert: "您的 1Panel 面板,{{ .num }} 個許可證存在異常,詳情請登入面板查看。"
|
||||
SSHAndPanelLoginAlert: "您的 1Panel 面板,面板{{ .name }}從 {{ .ip }} 登錄異常,詳情請登入面板查看。"
|
||||
|
||||
#disk
|
||||
DeviceNotFound: "設備 {{ .name }} 未找到"
|
||||
DeviceIsMounted: "設備 {{ .name }} 已掛載,請先卸載"
|
||||
PartitionDiskErr: "分區失敗,{{ .err }}"
|
||||
FormatDiskErr: "格式化磁盤失敗,{{ .err }}"
|
||||
MountDiskErr: "掛載磁盤失敗,{{ .err }}"
|
||||
UnMountDiskErr: "卸載磁盤失敗,{{ .err }}"
|
||||
XfsNotFound: "未檢測到 xfs 文件系統,請先安裝 xfsprogs"
|
||||
|
||||
+20
-1
@@ -15,6 +15,9 @@ ErrApiConfigIPInvalid: "调用 API 接口 IP 不在白名单: {{ .detail }}"
|
||||
ErrApiConfigDisable: "此接口禁止使用 API 接口调用: {{ .detail }}"
|
||||
ErrApiConfigKeyTimeInvalid: "API 接口时间戳错误: {{ .detail }}"
|
||||
|
||||
ErrMinQuickJump: "请至少设置一个快速跳转入口!"
|
||||
ErrMaxQuickJump: "最多可设置四个快速跳转入口!"
|
||||
|
||||
#common
|
||||
ErrUsernameIsExist: "用户名已存在"
|
||||
ErrNameIsExist: "名称已存在"
|
||||
@@ -34,6 +37,7 @@ ErrGroupIsDefault: "默认分组,无法删除"
|
||||
ErrGroupIsInWebsiteUse: "分组正在被其他网站使用,无法删除"
|
||||
|
||||
#backup
|
||||
Localhost: '本机'
|
||||
ErrBackupInUsed: "该备份账号已在计划任务中使用,无法删除"
|
||||
ErrBackupCheck: "备份账号测试连接失败 {{ .err }}"
|
||||
ErrBackupLocalDelete: "暂不支持删除本地服务器备份账号"
|
||||
@@ -229,6 +233,8 @@ Upload: "上传"
|
||||
IgnoreBackupErr: "备份失败,错误:{{ .detail }},忽略本次错误..."
|
||||
IgnoreUploadErr: "上传失败,错误:{{ .detail }},忽略本次错误..."
|
||||
LoadBackupFailed: "获取备份账号连接失败,错误:{{ .detail }}"
|
||||
InExecuting: "当前任务正在执行中,请勿重复执行!"
|
||||
NoSuchResource: "数据库中未能查询到备份内容,跳过..."
|
||||
|
||||
#toolbox
|
||||
ErrNotExistUser: "当前用户不存在,请修改后重试!"
|
||||
@@ -348,6 +354,10 @@ TaskIsExecuting: "任务正在运行"
|
||||
CustomAppstore: "自定义应用仓库"
|
||||
TaskExec: "执行"
|
||||
|
||||
# task - clam
|
||||
Clamscan: "扫描 {{ .name }}"
|
||||
TaskScan: "扫描"
|
||||
|
||||
# task - ai
|
||||
OllamaModelPull: "拉取 Ollama 模型 {{ .name }} "
|
||||
OllamaModelSize: "获取 Ollama 模型 {{ .name }} 大小 "
|
||||
@@ -467,4 +477,13 @@ PanelPwdExpirationAlert: "您的 1Panel 面板,面板密码将在 {{ .day }}
|
||||
CommonAlert: "您的 1Panel 面板,{{ .msg }},详情请登录面板查看。"
|
||||
NodeExceptionAlert: "您的 1Panel 面板,{{ .num }} 个节点存在异常,详情请登录面板查看。"
|
||||
LicenseExceptionAlert: "您的 1Panel 面板,{{ .num }} 个许可证存在异常,详情请登录面板查看。"
|
||||
SSHAndPanelLoginAlert: "您的 1Panel 面板,面板{{ .name }}登录{{ .ip }}异常,详情请登录面板查看。"
|
||||
SSHAndPanelLoginAlert: "您的 1Panel 面板,面板{{ .name }}登录{{ .ip }}异常,详情请登录面板查看。"
|
||||
|
||||
#disk
|
||||
DeviceNotFound: "设备 {{ .name }} 未找到"
|
||||
DeviceIsMounted: "设备 {{ .name }} 已挂载,请先卸载"
|
||||
PartitionDiskErr: "分区失败,{{ .err }}"
|
||||
FormatDiskErr: "格式化磁盘失败,{{ .err }}"
|
||||
MountDiskErr: "挂载磁盘失败,{{ .err }}"
|
||||
UnMountDiskErr: "卸载磁盘失败,{{ .err }}"
|
||||
XfsNotFound: "未检测到 xfs 文件系统,请先安装 xfsprogs"
|
||||
+22
-2
@@ -1,16 +1,16 @@
|
||||
package hook
|
||||
|
||||
import (
|
||||
"github.com/1Panel-dev/1Panel/agent/app/service"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/alert_push"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/agent/app/dto"
|
||||
"github.com/1Panel-dev/1Panel/agent/app/model"
|
||||
"github.com/1Panel-dev/1Panel/agent/app/repo"
|
||||
"github.com/1Panel-dev/1Panel/agent/app/service"
|
||||
"github.com/1Panel-dev/1Panel/agent/constant"
|
||||
"github.com/1Panel-dev/1Panel/agent/global"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/alert_push"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/cmd"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/xpack"
|
||||
)
|
||||
@@ -18,6 +18,7 @@ import (
|
||||
func Init() {
|
||||
initGlobalData()
|
||||
handleCronjobStatus()
|
||||
handleClamStatus()
|
||||
handleSnapStatus()
|
||||
handleOllamaModelStatus()
|
||||
|
||||
@@ -68,6 +69,7 @@ func handleSnapStatus() {
|
||||
|
||||
func handleCronjobStatus() {
|
||||
var jobRecords []model.JobRecords
|
||||
_ = global.DB.Model(&model.Cronjob{}).Where("is_executing = ?", true).Updates(map[string]interface{}{"is_executing": false}).Error
|
||||
_ = global.DB.Where("status = ?", constant.StatusWaiting).Find(&jobRecords).Error
|
||||
for _, record := range jobRecords {
|
||||
err := global.DB.Model(&model.JobRecords{}).Where("status = ?", constant.StatusWaiting).
|
||||
@@ -87,6 +89,24 @@ func handleCronjobStatus() {
|
||||
}
|
||||
}
|
||||
|
||||
func handleClamStatus() {
|
||||
var jobRecords []model.ClamRecord
|
||||
_ = global.DB.Model(&model.Clam{}).Where("is_executing = ?", true).Updates(map[string]interface{}{"is_executing": false}).Error
|
||||
_ = global.DB.Where("status = ?", constant.StatusWaiting).Find(&jobRecords).Error
|
||||
for _, record := range jobRecords {
|
||||
err := global.DB.Model(&model.ClamRecord{}).Where("status = ?", constant.StatusWaiting).
|
||||
Updates(map[string]interface{}{
|
||||
"status": constant.StatusFailed,
|
||||
"message": "the task was interrupted due to the restart of the 1panel service",
|
||||
}).Error
|
||||
|
||||
if err != nil {
|
||||
global.LOG.Errorf("Failed to update job ID: %v, Error:%v", record.ID, err)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func handleOllamaModelStatus() {
|
||||
message := "the task was interrupted due to the restart of the 1panel service"
|
||||
_ = global.DB.Model(&model.OllamaModel{}).Where("status = ?", constant.StatusWaiting).Updates(map[string]interface{}{"status": constant.StatusCanceled, "message": message}).Error
|
||||
|
||||
@@ -37,6 +37,10 @@ func InitAgentDB() {
|
||||
migrations.InitCronjobGroup,
|
||||
migrations.AddColumnToAlert,
|
||||
migrations.UpdateWebsiteSSL,
|
||||
migrations.AddQuickJump,
|
||||
migrations.UpdateMcpServerAddType,
|
||||
migrations.InitLocalSSHConn,
|
||||
migrations.InitLocalSSHShow,
|
||||
})
|
||||
if err := m.Migrate(); err != nil {
|
||||
global.LOG.Error(err)
|
||||
|
||||
@@ -1,17 +1,23 @@
|
||||
package migrations
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/user"
|
||||
"path"
|
||||
"time"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/agent/app/dto"
|
||||
"github.com/1Panel-dev/1Panel/agent/app/dto/request"
|
||||
"github.com/1Panel-dev/1Panel/agent/app/model"
|
||||
"github.com/1Panel-dev/1Panel/agent/app/service"
|
||||
"github.com/1Panel-dev/1Panel/agent/constant"
|
||||
"github.com/1Panel-dev/1Panel/agent/global"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/common"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/copier"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/encrypt"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/ssh"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/xpack"
|
||||
|
||||
"github.com/go-gormigrate/gormigrate/v2"
|
||||
@@ -19,7 +25,7 @@ import (
|
||||
)
|
||||
|
||||
var AddTable = &gormigrate.Migration{
|
||||
ID: "20250805-add-table",
|
||||
ID: "20250902-add-table",
|
||||
Migrate: func(tx *gorm.DB) error {
|
||||
return tx.AutoMigrate(
|
||||
&model.AppDetail{},
|
||||
@@ -64,6 +70,7 @@ var AddTable = &gormigrate.Migration{
|
||||
&model.AppIgnoreUpgrade{},
|
||||
&model.McpServer{},
|
||||
&model.RootCert{},
|
||||
&model.ClamRecord{},
|
||||
)
|
||||
},
|
||||
}
|
||||
@@ -471,3 +478,88 @@ var UpdateWebsiteSSL = &gormigrate.Migration{
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
var AddQuickJump = &gormigrate.Migration{
|
||||
ID: "20250901-add-quick-jump",
|
||||
Migrate: func(tx *gorm.DB) error {
|
||||
if err := tx.AutoMigrate(&model.QuickJump{}); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := tx.Create(&model.QuickJump{Name: "Website", Title: "menu.website", Recommend: 10, IsShow: true, Router: "/websites"}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if err := tx.Create(&model.QuickJump{Name: "Database", Title: "home.database", Recommend: 30, IsShow: true, Router: "/databases"}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if err := tx.Create(&model.QuickJump{Name: "Cronjob", Title: "menu.cronjob", Recommend: 50, IsShow: true, Router: "/cronjobs"}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if err := tx.Create(&model.QuickJump{Name: "AppInstalled", Title: "home.appInstalled", Recommend: 70, IsShow: true, Router: "/apps/installed"}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if err := tx.Create(&model.QuickJump{Name: "File", Detail: "/", Title: "home.quickDir", Recommend: 90, IsShow: false, Router: "/hosts/files"}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
var UpdateMcpServerAddType = &gormigrate.Migration{
|
||||
ID: "20250904-update-mcp-server",
|
||||
Migrate: func(tx *gorm.DB) error {
|
||||
if err := tx.AutoMigrate(&model.McpServer{}); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := tx.Model(&model.McpServer{}).Where("1=1").Update("type", "npx").Error; err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
var InitLocalSSHConn = &gormigrate.Migration{
|
||||
ID: "20250905-init-local-ssh",
|
||||
Migrate: func(tx *gorm.DB) error {
|
||||
itemPath := ""
|
||||
currentInfo, _ := user.Current()
|
||||
if len(currentInfo.HomeDir) == 0 {
|
||||
itemPath = "/root/.ssh/id_ed25519_1panel"
|
||||
} else {
|
||||
itemPath = path.Join(currentInfo.HomeDir, ".ssh/id_ed25519_1panel")
|
||||
}
|
||||
if _, err := os.Stat(itemPath); err != nil {
|
||||
_ = service.NewISSHService().CreateRootCert(dto.CreateRootCert{EncryptionMode: "ed25519", Name: "id_ed25519_1panel", Description: "1Panel Terminal"})
|
||||
}
|
||||
privateKey, _ := os.ReadFile(itemPath)
|
||||
connWithKey := ssh.ConnInfo{
|
||||
Addr: "127.0.0.1",
|
||||
User: "root",
|
||||
Port: 22,
|
||||
AuthMode: "key",
|
||||
PrivateKey: privateKey,
|
||||
}
|
||||
if _, err := ssh.NewClient(connWithKey); err != nil {
|
||||
return nil
|
||||
}
|
||||
var conn model.LocalConnInfo
|
||||
_ = copier.Copy(&conn, &connWithKey)
|
||||
conn.PrivateKey = string(privateKey)
|
||||
conn.PassPhrase = ""
|
||||
localConn, _ := json.Marshal(&conn)
|
||||
connAfterEncrypt, _ := encrypt.StringEncrypt(string(localConn))
|
||||
if err := tx.Model(&model.Setting{}).Where("key = ?", "LocalSSHConn").Updates(map[string]interface{}{"value": connAfterEncrypt}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
var InitLocalSSHShow = &gormigrate.Migration{
|
||||
ID: "20250908-init-local-ssh-show",
|
||||
Migrate: func(tx *gorm.DB) error {
|
||||
if err := tx.Create(&model.Setting{Key: "LocalSSHConnShow", Value: constant.StatusEnable}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ func (s *BackupRouter) InitRouter(Router *gin.RouterGroup) {
|
||||
backupRouter.POST("", baseApi.CreateBackup)
|
||||
backupRouter.POST("/del", baseApi.DeleteBackup)
|
||||
backupRouter.POST("/update", baseApi.UpdateBackup)
|
||||
backupRouter.POST("/upload", baseApi.UploadForRecover)
|
||||
|
||||
backupRouter.POST("/backup", baseApi.Backup)
|
||||
backupRouter.POST("/recover", baseApi.Recover)
|
||||
|
||||
@@ -12,6 +12,8 @@ func (s *DashboardRouter) InitRouter(Router *gin.RouterGroup) {
|
||||
baseApi := v2.ApiGroupApp.BaseApi
|
||||
{
|
||||
cmdRouter.GET("/base/os", baseApi.LoadDashboardOsInfo)
|
||||
cmdRouter.GET("/quick/option", baseApi.LoadQuickOption)
|
||||
cmdRouter.POST("/quick/change", baseApi.UpdateQuickJump)
|
||||
cmdRouter.GET("/app/launcher", baseApi.LoadAppLauncher)
|
||||
cmdRouter.POST("/app/launcher/show", baseApi.UpdateAppLauncher)
|
||||
cmdRouter.POST("/app/launcher/option", baseApi.LoadAppLauncherOption)
|
||||
|
||||
@@ -52,5 +52,12 @@ func (s *HostRouter) InitRouter(Router *gin.RouterGroup) {
|
||||
hostRouter.POST("/tool/supervisor/process/file", baseApi.GetProcessFile)
|
||||
|
||||
hostRouter.GET("/terminal", baseApi.WsSSH)
|
||||
|
||||
hostRouter.GET("/disks", baseApi.GetCompleteDiskInfo)
|
||||
hostRouter.POST("/disks/partition", baseApi.PartitionDisk)
|
||||
hostRouter.POST("/disks/mount", baseApi.MountDisk)
|
||||
hostRouter.POST("/disks/unmount", baseApi.UnmountDisk)
|
||||
|
||||
hostRouter.GET("/components/:name", baseApi.CheckComponentExistence)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,5 +14,6 @@ func (f *ProcessRouter) InitRouter(Router *gin.RouterGroup) {
|
||||
{
|
||||
processRouter.GET("/ws", baseApi.ProcessWs)
|
||||
processRouter.POST("/stop", baseApi.StopProcess)
|
||||
processRouter.GET("/:pid", baseApi.GetProcessInfoByPID)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,7 +29,8 @@ func (s *SettingRouter) InitRouter(Router *gin.RouterGroup) {
|
||||
settingRouter.GET("/basedir", baseApi.LoadBaseDir)
|
||||
|
||||
settingRouter.POST("/ssh/check", baseApi.CheckLocalConn)
|
||||
settingRouter.POST("/ssh", baseApi.SaveLocalConnInfo)
|
||||
settingRouter.GET("/ssh/conn", baseApi.LoadLocalConn)
|
||||
settingRouter.POST("/ssh", baseApi.SaveLocalConn)
|
||||
settingRouter.POST("/ssh/check/info", baseApi.CheckLocalConnByInfo)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,7 +45,6 @@ func (s *ToolboxRouter) InitRouter(Router *gin.RouterGroup) {
|
||||
toolboxRouter.POST("/clam/search", baseApi.SearchClam)
|
||||
toolboxRouter.POST("/clam/record/search", baseApi.SearchClamRecord)
|
||||
toolboxRouter.POST("/clam/record/clean", baseApi.CleanClamRecord)
|
||||
toolboxRouter.POST("/clam/record/log", baseApi.LoadClamRecordLog)
|
||||
toolboxRouter.POST("/clam/file/search", baseApi.SearchClamFile)
|
||||
toolboxRouter.POST("/clam/file/update", baseApi.UpdateFile)
|
||||
toolboxRouter.POST("/clam", baseApi.CreateClam)
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
package clam
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/agent/app/model"
|
||||
"github.com/1Panel-dev/1Panel/agent/app/repo"
|
||||
"github.com/1Panel-dev/1Panel/agent/app/task"
|
||||
"github.com/1Panel-dev/1Panel/agent/constant"
|
||||
"github.com/1Panel-dev/1Panel/agent/global"
|
||||
"github.com/1Panel-dev/1Panel/agent/i18n"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/cmd"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/systemctl"
|
||||
"github.com/robfig/cron/v3"
|
||||
)
|
||||
|
||||
func AddScanTask(taskItem *task.Task, clam model.Clam, timeNow string) {
|
||||
taskItem.AddSubTask(i18n.GetWithName("Clamscan", clam.Path), func(t *task.Task) error {
|
||||
strategy := ""
|
||||
switch clam.InfectedStrategy {
|
||||
case "remove":
|
||||
strategy = "--remove"
|
||||
case "move", "copy":
|
||||
dir := path.Join(clam.InfectedDir, "1panel-infected", clam.Name, timeNow)
|
||||
taskItem.Log("infected dir: " + dir)
|
||||
if _, err := os.Stat(dir); err != nil {
|
||||
_ = os.MkdirAll(dir, os.ModePerm)
|
||||
}
|
||||
strategy = fmt.Sprintf("--%s=%s", clam.InfectedStrategy, dir)
|
||||
}
|
||||
taskItem.Logf("clamdscan --fdpass %s %s", strategy, clam.Path)
|
||||
mgr := cmd.NewCommandMgr(cmd.WithIgnoreExist1(), cmd.WithTimeout(10*time.Hour), cmd.WithTask(*taskItem))
|
||||
stdout, err := mgr.RunWithStdoutBashCf("clamdscan --fdpass %s %s", strategy, clam.Path)
|
||||
if err != nil {
|
||||
return fmt.Errorf("clamdscan failed, stdout: %v, err: %v", stdout, err)
|
||||
}
|
||||
return nil
|
||||
}, nil)
|
||||
}
|
||||
|
||||
func CheckClamIsActive(withCheck bool, clamRepo repo.IClamRepo) bool {
|
||||
if withCheck {
|
||||
isActive := false
|
||||
exist1, _ := systemctl.IsExist(constant.ClamServiceNameCentOs)
|
||||
if exist1 {
|
||||
isActive, _ = systemctl.IsActive(constant.ClamServiceNameCentOs)
|
||||
}
|
||||
exist2, _ := systemctl.IsExist(constant.ClamServiceNameUbuntu)
|
||||
if exist2 {
|
||||
isActive, _ = systemctl.IsActive(constant.ClamServiceNameUbuntu)
|
||||
}
|
||||
if isActive {
|
||||
return true
|
||||
}
|
||||
}
|
||||
clams, _ := clamRepo.List(repo.WithByStatus(constant.StatusEnable))
|
||||
for i := 0; i < len(clams); i++ {
|
||||
global.Cron.Remove(cron.EntryID(clams[i].EntryID))
|
||||
_ = clamRepo.Update(clams[i].ID, map[string]interface{}{"status": constant.StatusDisable, "entry_id": 0})
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func AnalysisFromLog(pathItem string, record *model.ClamRecord) {
|
||||
file, err := os.ReadFile(pathItem)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
lines := strings.Split(string(file), "\n")
|
||||
for _, line := range lines {
|
||||
if len(line) < 20 {
|
||||
continue
|
||||
}
|
||||
line = line[20:]
|
||||
switch {
|
||||
case strings.HasPrefix(line, "Infected files: "):
|
||||
record.InfectedFiles = strings.TrimPrefix(line, "Infected files: ")
|
||||
case strings.HasPrefix(line, "Total errors: "):
|
||||
record.TotalError = strings.TrimPrefix(line, "Total errors: ")
|
||||
case strings.HasPrefix(line, "Time: "):
|
||||
record.ScanTime = strings.TrimPrefix(line, "Time: ")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -11,24 +11,48 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
// SMTPConfig holds SMTP connection info
|
||||
type SMTPConfig struct {
|
||||
Host string
|
||||
Port int // 465, 587, 25
|
||||
Port int
|
||||
Username string
|
||||
Password string
|
||||
From string
|
||||
Encryption string // "ssl", "starttls", "none"
|
||||
Encryption string
|
||||
Recipient string
|
||||
}
|
||||
|
||||
// EmailMessage represents an email
|
||||
type EmailMessage struct {
|
||||
Subject string
|
||||
Body string // HTML or plain text
|
||||
Body string
|
||||
IsHTML bool
|
||||
}
|
||||
|
||||
type loginAuth struct {
|
||||
username, password string
|
||||
}
|
||||
|
||||
func LoginAuth(username, password string) smtp.Auth {
|
||||
return &loginAuth{username, password}
|
||||
}
|
||||
|
||||
func (a *loginAuth) Start(server *smtp.ServerInfo) (string, []byte, error) {
|
||||
return "LOGIN", []byte{}, nil
|
||||
}
|
||||
|
||||
func (a *loginAuth) Next(fromServer []byte, more bool) ([]byte, error) {
|
||||
if more {
|
||||
switch string(fromServer) {
|
||||
case "Username:":
|
||||
return []byte(a.username), nil
|
||||
case "Password:":
|
||||
return []byte(a.password), nil
|
||||
default:
|
||||
return nil, fmt.Errorf("unknown server challenge: %s", fromServer)
|
||||
}
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func SendMail(config SMTPConfig, message EmailMessage, transport *http.Transport) error {
|
||||
if err := validateConfig(config); err != nil {
|
||||
return err
|
||||
@@ -147,7 +171,9 @@ func sendWithSSL(config SMTPConfig, addr string, toList []string, msg string, tr
|
||||
return fmt.Errorf("failed to create SMTP client: %w", err)
|
||||
}
|
||||
defer client.Quit()
|
||||
|
||||
if err := tryAuth(client, config.Username, config.Password, config.Host); err != nil {
|
||||
return fmt.Errorf("authentication failed: %w", err)
|
||||
}
|
||||
return sendEmailWithClient(client, config, toList, msg)
|
||||
}
|
||||
|
||||
@@ -173,7 +199,9 @@ func sendWithStartTLS(config SMTPConfig, addr string, toList []string, msg strin
|
||||
if err = client.StartTLS(&tls.Config{ServerName: config.Host}); err != nil {
|
||||
return fmt.Errorf("failed to start TLS: %w", err)
|
||||
}
|
||||
|
||||
if err := tryAuth(client, config.Username, config.Password, config.Host); err != nil {
|
||||
return fmt.Errorf("authentication failed: %w", err)
|
||||
}
|
||||
return sendEmailWithClient(client, config, toList, msg)
|
||||
}
|
||||
|
||||
@@ -199,10 +227,6 @@ func sendPlaintext(config SMTPConfig, addr string, toList []string, msg string,
|
||||
}
|
||||
|
||||
func sendEmailWithClient(client *smtp.Client, config SMTPConfig, toList []string, msg string) error {
|
||||
auth := smtp.PlainAuth("", config.Username, config.Password, config.Host)
|
||||
if err := client.Auth(auth); err != nil {
|
||||
return fmt.Errorf("authentication failed: %w", err)
|
||||
}
|
||||
if err := client.Mail(config.Username); err != nil {
|
||||
return fmt.Errorf("setting sender failed: %w", err)
|
||||
}
|
||||
@@ -223,3 +247,26 @@ func sendEmailWithClient(client *smtp.Client, config SMTPConfig, toList []string
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func tryAuth(client *smtp.Client, username, password, host string) error {
|
||||
ok, authCap := client.Extension("AUTH")
|
||||
if !ok {
|
||||
return fmt.Errorf("server does not support AUTH")
|
||||
}
|
||||
authCap = strings.ToUpper(authCap)
|
||||
if strings.Contains(authCap, "PLAIN") {
|
||||
auth := smtp.PlainAuth("", username, password, host)
|
||||
if err := client.Auth(auth); err != nil {
|
||||
return fmt.Errorf("plain auth failed: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
if strings.Contains(authCap, "LOGIN") {
|
||||
if err := client.Auth(LoginAuth(username, password)); err != nil {
|
||||
return fmt.Errorf("login auth failed: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
return fmt.Errorf("no supported auth mechanism, server supports: %s", authCap)
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package files
|
||||
import (
|
||||
"archive/zip"
|
||||
"bufio"
|
||||
"compress/gzip"
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"encoding/json"
|
||||
@@ -587,7 +588,7 @@ func getFormat(cType CompressType) archiver.CompressedArchive {
|
||||
switch cType {
|
||||
case Tar:
|
||||
format.Archival = archiver.Tar{}
|
||||
case TarGz, Gz:
|
||||
case TarGz, Gz, Tgz:
|
||||
format.Compression = archiver.Gz{}
|
||||
format.Archival = archiver.Tar{}
|
||||
case SdkTarGz:
|
||||
@@ -597,10 +598,10 @@ func getFormat(cType CompressType) archiver.CompressedArchive {
|
||||
format.Archival = archiver.Zip{
|
||||
Compression: zip.Deflate,
|
||||
}
|
||||
case Bz2:
|
||||
case Bz2, TarBz2:
|
||||
format.Compression = archiver.Bz2{}
|
||||
format.Archival = archiver.Tar{}
|
||||
case Xz:
|
||||
case Xz, TarXz:
|
||||
format.Compression = archiver.Xz{}
|
||||
format.Archival = archiver.Tar{}
|
||||
}
|
||||
@@ -680,6 +681,14 @@ func decodeGBK(input string) (string, error) {
|
||||
|
||||
func (f FileOp) decompressWithSDK(srcFile string, dst string, cType CompressType) error {
|
||||
format := getFormat(cType)
|
||||
if cType == Gz {
|
||||
err := f.DecompressGzFile(srcFile, dst)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
handler := func(ctx context.Context, archFile archiver.File) error {
|
||||
info := archFile.FileInfo
|
||||
if isIgnoreFile(archFile.Name()) {
|
||||
@@ -794,6 +803,41 @@ func ZipFile(files []archiver.File, dst afero.File) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f FileOp) DecompressGzFile(srcFile, dst string) error {
|
||||
in, err := f.Fs.Open(srcFile)
|
||||
if err != nil {
|
||||
return fmt.Errorf("open source file failed: %w", err)
|
||||
}
|
||||
defer in.Close()
|
||||
|
||||
gr, err := gzip.NewReader(in)
|
||||
if err != nil {
|
||||
return fmt.Errorf("gzip reader creation failed: %w", err)
|
||||
}
|
||||
defer gr.Close()
|
||||
|
||||
outName := strings.TrimSuffix(filepath.Base(srcFile), ".gz")
|
||||
outPath := filepath.Join(dst, outName)
|
||||
parentDir := filepath.Dir(outPath)
|
||||
if !f.Stat(parentDir) {
|
||||
if err := f.Fs.MkdirAll(parentDir, 0755); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
fw, err := f.Fs.OpenFile(outPath, os.O_CREATE|os.O_RDWR|os.O_TRUNC, 0644)
|
||||
if err != nil {
|
||||
return fmt.Errorf("create output file failed: %w", err)
|
||||
}
|
||||
defer fw.Close()
|
||||
|
||||
if _, err := io.Copy(fw, gr); err != nil {
|
||||
return fmt.Errorf("copy content failed: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f FileOp) TarGzCompressPro(withDir bool, src, dst, secret, exclusionRules string) error {
|
||||
if !f.Stat(path.Dir(dst)) {
|
||||
if err := f.Fs.MkdirAll(path.Dir(dst), constant.FilePerm); err != nil {
|
||||
|
||||
@@ -460,9 +460,12 @@ const (
|
||||
Zip CompressType = "zip"
|
||||
Gz CompressType = "gz"
|
||||
Bz2 CompressType = "bz2"
|
||||
TarBz2 CompressType = "tar.bz2"
|
||||
Tar CompressType = "tar"
|
||||
TarGz CompressType = "tar.gz"
|
||||
Tgz CompressType = "tgz"
|
||||
Xz CompressType = "xz"
|
||||
TarXz CompressType = "tar.xz"
|
||||
SdkZip CompressType = "sdkZip"
|
||||
SdkTarGz CompressType = "sdkTarGz"
|
||||
Rar CompressType = "rar"
|
||||
|
||||
@@ -2,6 +2,7 @@ package files
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
@@ -17,6 +18,9 @@ func NewTarGzArchiver() ShellArchiver {
|
||||
}
|
||||
|
||||
func (t TarGzArchiver) Extract(filePath, dstDir string, secret string) error {
|
||||
if err := os.MkdirAll(dstDir, 0755); err != nil {
|
||||
return fmt.Errorf("failed to create destination dir: %w", err)
|
||||
}
|
||||
var err error
|
||||
commands := ""
|
||||
if len(secret) != 0 {
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/agent/global"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/cmd"
|
||||
@@ -76,17 +77,17 @@ func (f *Fail2ban) Operate(operate string) error {
|
||||
|
||||
func (f *Fail2ban) ReBanIPs(ips []string) error {
|
||||
ipItems, _ := f.ListBanned()
|
||||
stdout, err := cmd.RunDefaultWithStdoutBashCf("fail2ban-client unban --all")
|
||||
stdout, err := cmd.RunDefaultWithStdoutBashCfAndTimeOut("fail2ban-client unban --all", 10*time.Minute)
|
||||
if err != nil {
|
||||
stdout1, err := cmd.RunDefaultWithStdoutBashCf("fail2ban-client set sshd banip %s", strings.Join(ipItems, " "))
|
||||
stdout1, err := cmd.RunDefaultWithStdoutBashCfAndTimeOut("fail2ban-client set sshd banip %s", 10*time.Minute, strings.Join(ipItems, " "))
|
||||
if err != nil {
|
||||
global.LOG.Errorf("rebanip after fail2ban-client unban --all failed, err: %s", stdout1)
|
||||
}
|
||||
return fmt.Errorf("fail2ban-client unban --all failed, err: %s", stdout)
|
||||
}
|
||||
stdout1, err := cmd.RunDefaultWithStdoutBashCf("fail2ban-client set sshd banip %s", strings.Join(ips, " "))
|
||||
stdout, err = cmd.RunDefaultWithStdoutBashCfAndTimeOut("fail2ban-client set sshd banip %s", 10*time.Minute, strings.Join(ips, " "))
|
||||
if err != nil {
|
||||
return fmt.Errorf("handle `fail2ban-client set sshd banip %s` failed, err: %s", strings.Join(ips, " "), stdout1)
|
||||
return fmt.Errorf("handle `fail2ban-client set sshd banip %s` failed, err: %s", strings.Join(ips, " "), stdout)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -3,13 +3,13 @@ package websocket
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/common"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/agent/global"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/common"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/files"
|
||||
"github.com/shirou/gopsutil/v4/host"
|
||||
"github.com/shirou/gopsutil/v4/net"
|
||||
@@ -74,10 +74,10 @@ type PsProcessData struct {
|
||||
Envs []string `json:"envs"`
|
||||
|
||||
OpenFiles []process.OpenFilesStat `json:"openFiles"`
|
||||
Connects []processConnect `json:"connects"`
|
||||
Connects []ProcessConnect `json:"connects"`
|
||||
}
|
||||
|
||||
type processConnect struct {
|
||||
type ProcessConnect struct {
|
||||
Type string `json:"type"`
|
||||
Status string `json:"status"`
|
||||
Laddr net.Addr `json:"localaddr"`
|
||||
@@ -86,7 +86,7 @@ type processConnect struct {
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
type ProcessConnects []processConnect
|
||||
type ProcessConnects []ProcessConnect
|
||||
|
||||
type sshSession struct {
|
||||
Username string `json:"username"`
|
||||
@@ -196,53 +196,15 @@ func getProcessData(processConfig PsProcessConfig) (res []byte, err error) {
|
||||
procData.StartTime = t.Format("2006-1-2 15:04:05")
|
||||
}
|
||||
procData.NumThreads, _ = proc.NumThreads()
|
||||
connections, procErr := proc.Connections()
|
||||
if procErr == nil {
|
||||
procData.NumConnections = len(connections)
|
||||
for _, conn := range connections {
|
||||
if conn.Laddr.IP != "" || conn.Raddr.IP != "" {
|
||||
procData.Connects = append(procData.Connects, processConnect{
|
||||
Status: conn.Status,
|
||||
Laddr: conn.Laddr,
|
||||
Raddr: conn.Raddr,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
procData.CpuValue, _ = proc.CPUPercent()
|
||||
procData.CpuPercent = fmt.Sprintf("%.2f", procData.CpuValue) + "%"
|
||||
menInfo, procErr := proc.MemoryInfo()
|
||||
if procErr == nil {
|
||||
procData.Rss = common.FormatBytes(menInfo.RSS)
|
||||
procData.RssValue = menInfo.RSS
|
||||
procData.Data = common.FormatBytes(menInfo.Data)
|
||||
procData.VMS = common.FormatBytes(menInfo.VMS)
|
||||
procData.HWM = common.FormatBytes(menInfo.HWM)
|
||||
procData.Stack = common.FormatBytes(menInfo.Stack)
|
||||
procData.Locked = common.FormatBytes(menInfo.Locked)
|
||||
procData.Swap = common.FormatBytes(menInfo.Swap)
|
||||
} else {
|
||||
procData.Rss = "--"
|
||||
procData.Data = "--"
|
||||
procData.VMS = "--"
|
||||
procData.HWM = "--"
|
||||
procData.Stack = "--"
|
||||
procData.Locked = "--"
|
||||
procData.Swap = "--"
|
||||
|
||||
if memInfo, err := proc.MemoryInfo(); err == nil {
|
||||
procData.RssValue = memInfo.RSS
|
||||
procData.Rss = common.FormatBytes(memInfo.RSS)
|
||||
} else {
|
||||
procData.RssValue = 0
|
||||
}
|
||||
ioStat, procErr := proc.IOCounters()
|
||||
if procErr == nil {
|
||||
procData.DiskWrite = common.FormatBytes(ioStat.WriteBytes)
|
||||
procData.DiskRead = common.FormatBytes(ioStat.ReadBytes)
|
||||
} else {
|
||||
procData.DiskWrite = "--"
|
||||
procData.DiskRead = "--"
|
||||
}
|
||||
procData.CmdLine, _ = proc.Cmdline()
|
||||
procData.OpenFiles, _ = proc.OpenFiles()
|
||||
procData.Envs, _ = proc.Environ()
|
||||
|
||||
resultMutex.Lock()
|
||||
result = append(result, procData)
|
||||
@@ -333,7 +295,7 @@ var netTypes = [...]string{"tcp", "udp"}
|
||||
|
||||
func getNetConnections(config NetConfig) (res []byte, err error) {
|
||||
var (
|
||||
result []processConnect
|
||||
result []ProcessConnect
|
||||
proc *process.Process
|
||||
)
|
||||
for _, netType := range netTypes {
|
||||
@@ -352,7 +314,7 @@ func getNetConnections(config NetConfig) (res []byte, err error) {
|
||||
if config.Port > 0 && config.Port != conn.Laddr.Port && config.Port != conn.Raddr.Port {
|
||||
continue
|
||||
}
|
||||
result = append(result, processConnect{
|
||||
result = append(result, ProcessConnect{
|
||||
Type: netType,
|
||||
Status: conn.Status,
|
||||
Laddr: conn.Laddr,
|
||||
|
||||
@@ -20,7 +20,7 @@ import (
|
||||
|
||||
func RemoveTamper(website string) {}
|
||||
|
||||
func StartClam(startClam model.Clam, isUpdate bool) (int, error) {
|
||||
func StartClam(startClam *model.Clam, isUpdate bool) (int, error) {
|
||||
return 0, buserr.New("ErrXpackNotFound")
|
||||
}
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@ import (
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /script [post]
|
||||
// @Router /core/script [post]
|
||||
// @x-panel-log {"bodyKeys":["name"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"添加脚本库脚本 [name]","formatEN":"add script [name]"}
|
||||
func (b *BaseApi) CreateScript(c *gin.Context) {
|
||||
var req dto.ScriptOperate
|
||||
@@ -48,7 +48,7 @@ func (b *BaseApi) CreateScript(c *gin.Context) {
|
||||
// @Success 200 {object} dto.PageResult
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /script/search [post]
|
||||
// @Router /core/script/search [post]
|
||||
func (b *BaseApi) SearchScript(c *gin.Context) {
|
||||
var req dto.SearchPageWithGroup
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
@@ -74,7 +74,7 @@ func (b *BaseApi) SearchScript(c *gin.Context) {
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /script/del [post]
|
||||
// @Router /core/script/del [post]
|
||||
// @x-panel-log {"bodyKeys":["ids"],"paramKeys":[],"BeforeFunctions":[{"input_column":"id","input_value":"ids","isList":true,"db":"script_librarys","output_column":"name","output_value":"names"}],"formatZH":"删除脚本库脚本 [names]","formatEN":"delete script [names]"}
|
||||
func (b *BaseApi) DeleteScript(c *gin.Context) {
|
||||
var req dto.OperateByIDs
|
||||
@@ -96,7 +96,7 @@ func (b *BaseApi) DeleteScript(c *gin.Context) {
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /script/sync [post]
|
||||
// @Router /core/script/sync [post]
|
||||
// @x-panel-log {"bodyKeys":[],"paramKeys":[],"BeforeFunctions":[],"formatZH":"同步脚本库脚本","formatEN":"sync scripts"}
|
||||
func (b *BaseApi) SyncScript(c *gin.Context) {
|
||||
var req dto.OperateByTaskID
|
||||
@@ -117,7 +117,7 @@ func (b *BaseApi) SyncScript(c *gin.Context) {
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /script/update [post]
|
||||
// @Router /core/script/update [post]
|
||||
// @x-panel-log {"bodyKeys":["id"],"paramKeys":[],"BeforeFunctions":[{"input_column":"id","input_value":"id","isList":false,"db":"cronjobs","output_column":"name","output_value":"name"}],"formatZH":"更新脚本库脚本 [name]","formatEN":"update script [name]"}
|
||||
func (b *BaseApi) UpdateScript(c *gin.Context) {
|
||||
var req dto.ScriptOperate
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user