Compare commits

...
Author SHA1 Message Date
蘭 725d03b41e fix: Fix the issue of generating Swagger documents with actual parameters (#10379) 2025-09-15 16:36:41 +08:00
CityFun 7e8581a7de feat: The certificate list supports sorting by expiration date. (#10372)
Refs https://github.com/1Panel-dev/1Panel/issues/10371
2025-09-15 05:37:27 +00:00
蘭 0ba83d1353 ref: Update swagger api docs (#10351) 2025-09-12 08:24:24 +00:00
CityFun a302e100ec fix: Fixed issue with add large black ip to fail2ban failed (#10348)
Refs https://github.com/1Panel-dev/1Panel/issues/10346
2025-09-12 08:20:25 +00:00
蘭 4f58fd492d ref: Update swagger api docs (#10343)
#10342
2025-09-11 14:03:31 +00:00
CityFun 0b06a4fac6 feat: Adapt for directly mounting an unpartitioned disk (#10341) 2025-09-11 09:05:30 +00:00
CityFun 4e1a7bf42c feat: optimization disk zie display (#10339) 2025-09-11 15:12:45 +08:00
wanghe-fit2cloud ef2fb1c62e chore: Modify prompt msg 2025-09-11 14:02:13 +08:00
蘭 f5fcbcd87a fix: Fix the decompression issue of TGZ files (#10338) 2025-09-11 05:41:29 +00:00
CityFun 986bffaf2b feat: add process detail for supervisor process (#10336)
Refs https://github.com/1Panel-dev/1Panel/issues/5172
2025-09-11 03:47:30 +00:00
CityFun 766a2a0546 feat: optimization disk zie display (#10337) 2025-09-11 11:46:16 +08:00
蘭 291b594ad9 fix: Fix file decompression issue (#10333) 2025-09-10 14:23:25 +00:00
CityFun 7742e3da38 fix: Fixed issue with mcp externalUrl config failed (#10331)
Refs https://github.com/1Panel-dev/1Panel/issues/10323
2025-09-10 09:06:51 +00:00
ssongliu c5f70eae4c feat: Add debounce effect to file selection (#10330) 2025-09-10 09:04:52 +00:00
CityFun 8bdf02c4c0 perf: Optimize process management performance (#10329) 2025-09-10 09:02:53 +00:00
蘭 8abe132c25 style: Optimize log background color (#10327)
#10203
2025-09-10 09:00:52 +00:00
CityFun 3a447b02a1 feat: Adjust hard drive mounting logic (#10326) 2025-09-10 05:50:51 +00:00
ssongliu aa718eef88 fix: Fix the issue of terminal key connection exception (#10325) 2025-09-10 03:32:51 +00:00
CityFun 60b1c9ba32 feat: Adjust disk management menu order (#10324) 2025-09-10 03:30:52 +00:00
蘭 9b50581fd4 fix: Fix alert issue remove some alert logs (#10322) 2025-09-10 03:28:50 +00:00
蘭 ee33fdab89 fix: Fix file decompression issue (#10320) 2025-09-09 11:14:44 +00:00
CityFun c8f19467ba fix: Fixed issue with push node not work when create ssl (#10319) 2025-09-09 09:56:45 +00:00
ssongliu 1fc0f1c71b fix: Fix the issue of terminal key connection exception (#10318) 2025-09-09 09:54:45 +00:00
CityFun 2258ba108d feat: add rsync check for file exchange (#10317) 2025-09-09 08:38:44 +00:00
ssongliu d21f4ffae6 style: Add fullscreen support for some drawers (#10316)
Refs #10312
2025-09-09 08:02:43 +00:00
ssongliu 4bd9c4c0fc fix: Fix snapshot interface timeout configuration issue (#10315) 2025-09-09 07:18:43 +00:00
CityFun 29165f0f6f perf: Optimize the display of HTML-type logs (#10311)
Refs https://github.com/1Panel-dev/1Panel/issues/10303
2025-09-09 06:52:44 +00:00
ssongliu f75b786916 fix: Add backward compatibility for overview page api (#10310) 2025-09-09 06:50:44 +00:00
CityFun 011914339e feat: Extend Openresty build time (#10309)
Refs https://github.com/1Panel-dev/1Panel/issues/10289
2025-09-09 06:48:43 +00:00
ssongliu 08a07214ef fix: Optimize backup logic for cronjob (#10308) 2025-09-09 04:20:43 +00:00
ssongliu f682f07dbc fix: Optimize duplicate execution error messages for cronjob (#10307) 2025-09-09 03:44:43 +00:00
CityFun 50e99f1f4c fix: Fixed issue with can not disable cache in antiLeech (#10306) 2025-09-09 03:22:43 +00:00
蘭 feabc488fe feat: SMTP supports login authentication (#10304) 2025-09-09 03:20:44 +00:00
wanghe-fit2cloud 72d3f5071b chore: Upgrade Node version 2025-09-09 11:19:05 +08:00
CityFun 27dc662430 fix: Fixed issue with build frontend failed (#10305) 2025-09-09 03:00:43 +00:00
ssongliu 80e233c4a6 fix: Fix terminal connection exception issues (#10301) 2025-09-08 14:08:42 +00:00
ssongliu a099308a82 fix: Solve the problem of internationalization of operation logs (#10300)
Refs #10273
2025-09-08 10:02:34 +00:00
ssongliu 6f0673f184 fix: Fix the problem of uploaded backup files being deleted after res… (#10299)
…toration
2025-09-08 09:57:46 +00:00
CityFun b428e699db feat: Disk Management Compatible with Lower Version Operating Systems (#10298) 2025-09-08 17:04:07 +08:00
蘭 f4334eadc0 ref: The default display quantity for the list is 20 (#10297) 2025-09-08 17:03:50 +08:00
ssongliu a2a6503030 fix: Refactor terminal connection logic (#10283)
Refs #9617
2025-09-05 09:39:36 +00:00
CityFunandwanghe-fit2cloud 9c22005b79 feat: Add disk management (#10282)
Co-authored-by: wanghe-fit2cloud <wanghe@fit2cloud.com>
2025-09-05 17:15:58 +08:00
蘭 7448953b7d fix: Fix alert issue (#10281) 2025-09-05 08:41:35 +00:00
蘭 18214095ab fix: Fix the issue where the task did not stop after deleting the alert (#10280) 2025-09-05 07:55:36 +00:00
ssongliu bcfc78e510 fix: Optimize the trigger method for table selection (#10268)
Refs #10262
2025-09-04 10:23:32 +00:00
ssongliu cc6b506176 fix: Solve the problem of abnormal status judgment in docker (#10266)
Refs #10258
2025-09-04 09:15:32 +00:00
ssongliu c61828fb39 fix: Adjust the display logic of the clam scan report (#10264)
Refs #10251
2025-09-04 09:13:32 +00:00
CityFun 23876ed186 feat: Mcp Server support uvx (#10261)
Refs https://github.com/1Panel-dev/1Panel/issues/10118
2025-09-04 07:21:33 +00:00
KOMATA ac4962cf19 fix: Remove duplicate domains in the domain list initialization (#10260) 2025-09-04 15:20:38 +08:00
CityFun d9e7c8d880 fix: Resolve the issue where the page does not display correctly after enabling browser caching. (#10257)
Refs https://github.com/1Panel-dev/1Panel/issues/10245
2025-09-04 07:19:31 +00:00
ssongliu ea25b800d2 fix: Add Docker restart option to firewall operations (#10253)
Refs #10247
2025-09-03 09:29:30 +00:00
CityFun 7ba06e73d7 feat: support closing log dialog with ESC key and outside click (#10252)
Refs https://github.com/1Panel-dev/1Panel/issues/10240
2025-09-03 09:27:30 +00:00
CityFun 39cf26628f feat: Optimize log file reading (#10249) 2025-09-03 09:25:30 +00:00
ssongliu ea7f324a9d style: Optimize version update changelog display (#10248)
Refs #10242
2025-09-03 06:49:28 +00:00
ssongliu 789d451ea2 feat: Add ZIP support for database backup uploads (#10246)
Refs #7179
2025-09-03 05:59:28 +00:00
CityFun 7313f3d591 fix: Resolve the issue of still receiving update prompts after ignoring the application. (#10244)
Refs https://github.com/1Panel-dev/1Panel/issues/10241
2025-09-03 05:57:28 +00:00
ssongliu 1662546bef fix: Fix container image upgrade command loss issue (#10239)
Refs #10227
2025-09-02 10:48:10 +00:00
ssongliu 53f75086ec feat: Optimize quick navigation for dashboard (#10238)
Refs #5167
2025-09-02 09:58:10 +00:00
ssongliu 81fcc61ec9 feat: Add local file selection for import/restore operations (#10234) 2025-09-02 17:55:46 +08:00
ssongliu 324a47966b fix: Optimize duplicate execution prevention for cronjob (#10233)
Refs #4973
2025-09-02 09:54:10 +00:00
ssongliu 5a0c50b9a2 style: Optimize volume list display (#10231)
Refs #6601
2025-09-02 09:48:10 +00:00
ssongliu 66465beb39 fix: Optimize tamper-protected File deletion warning messages (#10230) 2025-09-02 17:45:57 +08:00
238 changed files with 15702 additions and 4976 deletions
+1 -1
View File
@@ -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
+1 -1
View File
@@ -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
+1 -1
View File
@@ -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
+1 -1
View File
@@ -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 {
+26 -4
View File
@@ -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
+4 -27
View File
@@ -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
}
+1 -1
View File
@@ -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")
+32
View File
@@ -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
+1 -1
View File
@@ -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 {
+99
View File
@@ -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)
}
+3
View File
@@ -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()
)
+2 -2
View File
@@ -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 {
+1 -1
View File
@@ -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
}
+4 -4
View File
@@ -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 {
+7 -3
View File
@@ -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
}
+1 -1
View File
@@ -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
+1 -1
View File
@@ -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 {
+8 -8
View File
@@ -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 {
+1 -1
View File
@@ -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
+21
View File
@@ -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)
}
+6 -6
View File
@@ -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
View File
@@ -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
+1 -1
View File
@@ -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
+1 -1
View File
@@ -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
+26
View File
@@ -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)
}
+3 -3
View File
@@ -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
+5
View File
@@ -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
View File
@@ -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"`
}
+10 -6
View File
@@ -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"`
+15
View File
@@ -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"`
+25
View File
@@ -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"`
}
+2 -1
View File
@@ -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 {
+20
View File
@@ -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"`
}
+1
View File
@@ -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 {
+2 -2
View File
@@ -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"`
+2
View File
@@ -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 {
+37
View File
@@ -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"`
}
+8
View File
@@ -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"`
}
+10
View File
@@ -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
View File
@@ -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"`
}
+5 -6
View File
@@ -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 {
+1
View File
@@ -17,4 +17,5 @@ type McpServer struct {
HostIP string `json:"hostIP"`
StreamableHttpPath string `json:"streamableHttpPath"`
OutputTransport string `json:"outputTransport"`
Type string `json:"type"`
}
+10
View File
@@ -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"`
}
+46
View File
@@ -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
}
+79
View File
@@ -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
}
+15 -2
View File
@@ -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 {
+18 -1
View File
@@ -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) {
+19 -19
View File
@@ -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
}
+4 -4
View File
@@ -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
}
+17 -2
View File
@@ -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 {
+56 -37
View File
@@ -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 -39
View File
@@ -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
View File
@@ -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)
}
-35
View File
@@ -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
}
+32 -16
View File
@@ -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]
}
+3
View File
@@ -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
}
+13 -3
View File
@@ -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" {
+6 -1
View File
@@ -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)
+75 -26
View File
@@ -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
})
}
+146
View File
@@ -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
}
+440
View File
@@ -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
}
}
+1 -1
View File
@@ -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)
-7
View File
@@ -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
}
+5 -5
View File
@@ -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
}
+63 -29
View File
@@ -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())
}
}
}
+2 -2
View File
@@ -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()
+109 -1
View File
@@ -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
}
+14 -13
View File
@@ -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 ""
}
}
+29
View File
@@ -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
}
+5 -1
View File
@@ -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))
}
+2
View File
@@ -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"
+23 -4
View File
@@ -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)
+4
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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"
+20 -1
View File
@@ -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
View File
@@ -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"
+19
View File
@@ -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"
+19
View File
@@ -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
View File
@@ -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
View File
@@ -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
+4
View File
@@ -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)
+93 -1
View File
@@ -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
},
}
+1
View File
@@ -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)
+2
View File
@@ -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)
+7
View File
@@ -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)
}
}
+1
View File
@@ -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)
}
}
+2 -1
View File
@@ -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)
}
}
-1
View File
@@ -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)
+88
View File
@@ -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: ")
}
}
}
+58 -11
View File
@@ -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)
}
+47 -3
View File
@@ -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 {
+3
View File
@@ -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"
+4
View File
@@ -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 {
+5 -4
View File
@@ -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
}
+10 -48
View File
@@ -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,
+1 -1
View File
@@ -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")
}
+5 -5
View File
@@ -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