Compare commits

..
82 Commits
Author SHA1 Message Date
ssongliu b3a55ea44d fix: Fix menu display exception issue (#9883) 2025-08-06 22:00:31 +08:00
ssongliu 270411f7fd fix: Fix menu display exception issue (#9882) 2025-08-06 16:44:54 +08:00
KOMATA 3c01198e61 fix: Improve config handling for proxy configuration in GetProxies (#9879) 2025-08-06 16:02:27 +08:00
ssongliu 483b47ad63 fix: Fix node ip switch exception issue (#9874) 2025-08-06 12:12:19 +08:00
ssongliu b10de66ed3 style: Update list styling for cronjob (#9872) 2025-08-06 10:30:37 +08:00
ssongliu 305eff048c fix: Fix the problem of initializing planned task groups (#9870) 2025-08-05 23:15:03 +08:00
ssongliu da7968d364 fix: Fix firewall routing jump exception issue (#9869) 2025-08-05 22:10:12 +08:00
ssongliu d3e2c8e9d3 fix: Add remote database connection timeout record (#9868) 2025-08-05 11:15:00 +00:00
ssongliu e3a9f2dfb7 fix: Fix PostgreSQL database recovery failures (#9867) 2025-08-05 18:21:18 +08:00
CityFun 3c967f8906 fix: Fix the issue where the list keeps loading when autostart = false is set in the Supervisor configuration. (#9866) 2025-08-05 10:20:59 +00:00
ssongliu 39bb7acdba fix: Add non-standard log filtering for SSH (#9865) 2025-08-05 17:54:41 +08:00
蘭 a30066989b fix: Reset data before copying files (#9864) 2025-08-05 08:44:59 +00:00
CityFun 0c30dfa092 style: Adjust the style of task logs (#9863) 2025-08-05 16:37:04 +08:00
ssongliu 7a44493d13 fix: Fix menu name display issue (#9862) 2025-08-05 08:35:00 +00:00
ssongliu 9b726d5d22 fix: Fix menu name display issue (#9861) 2025-08-05 16:24:06 +08:00
ssongliu 78e3d9e5fb fix: Enhance archive extraction command compatibility (#9860)
Refs #9856
2025-08-05 08:14:59 +00:00
CityFun d74b41a5f2 style: Adjust the column width of the PHP runtime status (#9858) 2025-08-05 07:56:59 +00:00
CityFun f122afdbec feat: Add master node IP validation when installing a cluster slave node (#9857)
#9840
2025-08-05 07:55:00 +00:00
ssongliu 37308b5c42 fix: Add custom timeout support for remote database (#9859) 2025-08-05 15:54:48 +08:00
ssongliu 9c5a6e3925 style: Adjust menu tab styling (#9855) 2025-08-05 06:52:59 +00:00
ssongliu 58b95ef2ba fix: Fix PostgreSQL database backup/restore failures (#9854) 2025-08-05 14:30:34 +08:00
CityFun f617c9d5dd fix: Fix the issue where viewing logs during certificate application results in an error. (#9853) 2025-08-05 14:30:18 +08:00
CityFun c661cc37b3 feat: Delete app while ignoring it during synchronization. (#9852) 2025-08-05 14:29:50 +08:00
ssongliu 7c49f6c79b fix: Fix redis configuration modification issues (#9848)
Refs #9832
2025-08-05 03:26:59 +00:00
蘭 12b9356c17 ref: Optimize code (#9845) 2025-08-05 02:46:59 +00:00
g0l4 d3a34293f7 fix: Move inline editorconfig comments (#9839) 2025-08-05 10:38:50 +08:00
ssongliu c131061f81 fix: Fix grouping exception issue for cronjob (#9841) 2025-08-04 22:20:06 +08:00
CityFun 6a5e1ebaa5 feat: Add OpenResty upgrade notification. (#9837) 2025-08-04 22:19:28 +08:00
ssongliu a07d016951 fix: Fix menu tab display issues (#9838) 2025-08-04 21:32:21 +08:00
CityFun 9d343a90db feat: add DenySpider Config for waf (#9835) 2025-08-04 10:06:31 +00:00
蘭 4c813be09d style: file code editor style (#9834)
refs #9781
2025-08-04 10:04:29 +00:00
CityFun d48166f546 fix: Add autostart configuration for supervisor (#9831) 2025-08-04 14:20:53 +08:00
ssongliu 0f138c1b7c fix: Optimize SSH key synchronization (#9833) 2025-08-04 06:18:29 +00:00
ssongliu 8542323c0b fix: Add custom timeout support for remote database (#9830) 2025-08-04 13:40:35 +08:00
CityFun 00f103c66a fix: Fixed issue with where runtime website fails to start after being stopped (#9827) 2025-08-04 13:40:21 +08:00
ssongliu 430c75e7ed fix: Fix redis cluster connection info modification issue (#9799)
Refs #9798
2025-08-01 10:47:23 +00:00
hang 67ffb5e938 feat: support autocomplete for login form fields to enhance password manager compatibility (#9794) 2025-08-01 16:28:20 +08:00
ssongliu 9c292bebf0 fix: Optimize container cleanup mechanism (#9795)
Refs #7444

涉及到容器、镜像、网络、存储卷、构建缓存清理
2025-08-01 08:07:24 +00:00
ssongliu ba4307c1dc fix: Optimize backup file upload display (#9797) 2025-08-01 08:03:23 +00:00
蘭 b8b2041a0c fix: Fix Docker proxy error issue (#9790)
refs #9780
2025-08-01 08:01:24 +00:00
蘭 c45bb60b50 fix: Fix file copy overwrite error (#9796) 2025-08-01 16:00:11 +08:00
ssongliu fba46be47a fix: Fix overview page refresh issue (#9785)
Refs #9756 #9769
2025-08-01 02:47:22 +00:00
ssongliu be0a460935 feat: Optimize SSH key management (#9782) 2025-08-01 02:43:20 +00:00
CityFun c0a041d024 feat: upgrade github.com/docker/docker version to v28.3.3 (#9779) 2025-07-31 19:13:41 +08:00
ssongliu 2ce5ad82ce fix: Fix SSH SDK timeout and disconnection issues (#9776) 2025-07-31 17:22:19 +08:00
CityFun 50dcb6c7e4 fix: Fix the issue where certificate deletion fails due to network issues (#9775) 2025-07-31 17:21:33 +08:00
CityFun 017424bf7b fix: Fixed issue where syncing remote apps causes local app tags to be lost (#9774) 2025-07-31 17:21:08 +08:00
CityFun c7e7407f7c fix: Fix the issue where updating the image in the Compose file during upgrade has no effect (#9772) 2025-07-31 17:20:55 +08:00
CityFun cab42b3c7d feat: Mcp Server support streamableHttp (#9761)
Refs https://github.com/1Panel-dev/1Panel/issues/8482
2025-07-30 10:54:51 +00:00
CityFun 78f280c88f feat: Support Enter key to confirm in the delete application dialog (#9758)
Refs https://github.com/1Panel-dev/1Panel/issues/9754
2025-07-30 10:52:52 +00:00
CityFun 22f5a975c3 perf: Optimize website log reading (#9755)
Refs https://github.com/1Panel-dev/1Panel/issues/9383
2025-07-30 10:50:52 +00:00
CityFun 90ba036e39 feat: Restrict deletion of certificates that are being applied for. (#9753)
Refs https://github.com/1Panel-dev/1Panel/issues/9737#issuecomment-3134445730
2025-07-30 10:48:52 +00:00
CityFun d1e18e43ee prompt: Optimize the timeout prompt when pulling application during installation (#9757) 2025-07-30 18:48:43 +08:00
ssongliu a801140a82 feat: support group management for cronjob (#9735)
Refs #9258
2025-07-30 04:08:50 +00:00
CityFun 951ab2502e fix: Resolve the issue of ignoring MySQL failures (#9734)
Refs https://github.com/1Panel-dev/1Panel/issues/9718
2025-07-30 04:06:51 +00:00
CityFun 0316cb0662 feat: Add HTTPS fallback prevention configuration in OpenResty (#9703) 2025-07-30 04:04:51 +00:00
CityFun 04b9cbd87a fix: Fixed issue with recover postgres-cluster database failed (#9732) 2025-07-29 16:19:39 +08:00
ssongliu 4565c5b300 fix: Fix fatabase backup cronjobimport failures (#9731) 2025-07-29 16:03:55 +08:00
ssongliu bf9a9bf17a fix: Fix execution refresh issues for cronjob (#9728) 2025-07-29 14:51:34 +08:00
ssongliu 70df4dc6c4 fix: Fix import failures for cronjob (#9727) 2025-07-29 14:32:25 +08:00
ssongliu ed3e738fff fix: Fix import issues of shell cronjob (#9726) 2025-07-29 04:09:49 +00:00
ssongliu bc376ec495 fix: script-type scheduled task import/export issues (#9713) 2025-07-28 19:04:35 +08:00
Snrat 40314e9079 Fix Issue with Podman Unable to Change PHP Runtime in Website Page (#9711)
* Update container.go

* Update website.go
2025-07-28 18:34:05 +08:00
ssongliu 82f978c683 fix: Add file upload restrictions for cronjob (#9710) 2025-07-28 18:33:15 +08:00
CityFun ed288368ea fix: Fixed issue with recover mysql-cluster with backup files (#9707) 2025-07-28 18:01:05 +08:00
sheng.wang db8fa144eb fix: Fix crash when adding custom spec if specs is null (#9708) 2025-07-28 18:00:35 +08:00
ssongliu 57b76d97ec fix: Optimize task log tracking mechanism (#9704) 2025-07-28 17:12:42 +08:00
蘭 28d8210ce6 fix: add nil checks for transport.DialContext (#9702) 2025-07-28 17:02:46 +08:00
ssongliu 80ce60c56c fix: Fix port forwarding error issues (#9700)
Refs #9658
2025-07-28 08:29:40 +00:00
Snrat 2758ab07dc Fix Issue with Podman Unable to Create PHP Runtime Website (#9697)
* Update website.go

* Update container.go
2025-07-28 15:38:27 +08:00
CityFun 0828804de2 fix: Fix the issue where the host IP is shown as 127.0.0.1 after enabling 'allowPort' in the application parameter settings. (#9699) 2025-07-28 15:36:25 +08:00
ssongliu 134b17d2f0 fix: Resolve certificate validate failure Issues (#9698) 2025-07-28 15:36:10 +08:00
ssongliu 3af254cdda fix: Add lightweight object storage compatibility for COS backup acco… (#9673)
Refs #9494
2025-07-25 11:07:36 +00:00
蘭 f3d3d54722 feat: Customize login page buttons and link colors (#9671)
refs  #9061
2025-07-25 11:05:37 +00:00
CityFun fa419809f6 fix: Fixed issue with create website with openresty defaultHttpsPort (#9672) 2025-07-25 19:04:19 +08:00
CityFun da606826c6 fix: Fixed Changing the runtime container name may cause it to fail to start (#9670)
Refs https://github.com/1Panel-dev/1Panel/issues/9666
2025-07-25 11:03:35 +00:00
ssongliu b3d9cc156a fix: Optimize filename generation for directory backup (#9669)
Refs #9433
2025-07-25 09:21:35 +00:00
ssongliu ba03d26b2c fix: Optimize cronjob report loading (#9665)
Refs #9412
2025-07-25 08:35:36 +00:00
ssongliu 40f02889fe feat: Add import/export support for cronjob (#9663)
Refs #5157
2025-07-25 07:15:35 +00:00
CityFun 4f8fb26d89 feat: Adapt default website domain to non-standard OpenResty port during creation (#9664) 2025-07-25 15:15:33 +08:00
ssongliu 0ca11e2b88 fix: Fix node switching style issues (#9641) 2025-07-24 11:30:08 +08:00
ssongliu 0daf0d690c fix: Resolve deleted node status display issue (#9633) 2025-07-24 10:23:45 +08:00
227 changed files with 4167 additions and 1220 deletions
+2 -2
View File
@@ -466,12 +466,12 @@ func (b *BaseApi) RecoverByUpload(c *gin.Context) {
}
switch req.Type {
case "mysql", "mariadb":
case "mysql", "mariadb", constant.AppMysqlCluster:
if err := backupService.MysqlRecoverByUpload(req); err != nil {
helper.InternalServer(c, err)
return
}
case constant.AppPostgresql:
case constant.AppPostgresql, constant.AppPostgresqlCluster:
if err := backupService.PostgresqlRecoverByUpload(req); err != nil {
helper.InternalServer(c, err)
return
+3 -4
View File
@@ -315,7 +315,7 @@ func (b *BaseApi) ContainerUpgrade(c *gin.Context) {
// @Summary Clean container
// @Accept json
// @Param request body dto.ContainerPrune true "request"
// @Success 200 {object} dto.ContainerPruneReport
// @Success 200
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /containers/prune [post]
@@ -326,12 +326,11 @@ func (b *BaseApi) ContainerPrune(c *gin.Context) {
return
}
report, err := containerService.Prune(req)
if err != nil {
if err := containerService.Prune(req); err != nil {
helper.InternalServer(c, err)
return
}
helper.SuccessWithData(c, report)
helper.Success(c)
}
// @Tags Container
+67
View File
@@ -1,6 +1,8 @@
package v2
import (
"net/http"
"strings"
"time"
"github.com/1Panel-dev/1Panel/agent/app/api/v2/helper"
@@ -53,6 +55,49 @@ func (b *BaseApi) LoadCronjobInfo(c *gin.Context) {
helper.SuccessWithData(c, data)
}
// @Tags Cronjob
// @Summary Export cronjob list
// @Accept json
// @Param request body dto.OperateByIDs true "request"
// @Success 200
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /cronjobs/export [post]
func (b *BaseApi) ExportCronjob(c *gin.Context) {
var req dto.OperateByIDs
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}
content, err := cronjobService.Export(req)
if err != nil {
helper.InternalServer(c, err)
return
}
http.ServeContent(c.Writer, c.Request, "", time.Now(), strings.NewReader(content))
}
// @Tags Cronjob
// @Summary Import cronjob list
// @Accept json
// @Param request body dto.CronjobImport true "request"
// @Success 200
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /cronjobs/import [post]
func (b *BaseApi) ImportCronjob(c *gin.Context) {
var req dto.CronjobImport
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}
if err := cronjobService.Import(req.Cronjobs); err != nil {
helper.InternalServer(c, err)
return
}
helper.Success(c)
}
// @Tags Cronjob
// @Summary Load script options
// @Success 200 {array} dto.ScriptOptions
@@ -225,6 +270,28 @@ func (b *BaseApi) UpdateCronjob(c *gin.Context) {
helper.Success(c)
}
// @Tags Cronjob
// @Summary Update cronjob group
// @Accept json
// @Param request body dto.ChangeGroup true "request"
// @Success 200
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /cronjobs/group/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 cronjob group [name]"}
func (b *BaseApi) UpdateCronjobGroup(c *gin.Context) {
var req dto.ChangeGroup
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}
if err := cronjobService.UpdateGroup(req); err != nil {
helper.InternalServer(c, err)
return
}
helper.Success(c)
}
// @Tags Cronjob
// @Summary Update cronjob status
// @Accept json
+3 -4
View File
@@ -138,7 +138,7 @@ func (b *BaseApi) ImagePush(c *gin.Context) {
// @Summary Delete image
// @Accept json
// @Param request body dto.BatchDelete true "request"
// @Success 200 {object} dto.ContainerPruneReport
// @Success 200
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /containers/image/remove [post]
@@ -149,13 +149,12 @@ func (b *BaseApi) ImageRemove(c *gin.Context) {
return
}
data, err := imageService.ImageRemove(req)
if err != nil {
if err := imageService.ImageRemove(req); err != nil {
helper.InternalServer(c, err)
return
}
helper.SuccessWithData(c, data)
helper.Success(c)
}
// @Tags Container Image
+36
View File
@@ -157,3 +157,39 @@ func (b *BaseApi) GetNginxModules(c *gin.Context) {
}
helper.SuccessWithData(c, modules)
}
// @Tags OpenResty
// @Summary Operate default HTTPs
// @Accept json
// @Param request body request.NginxOperateReq true "request"
// @Success 200
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /openresty/https [post]
func (b *BaseApi) OperateDefaultHTTPs(c *gin.Context) {
var req request.NginxOperateReq
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}
if err := nginxService.OperateDefaultHTTPs(req); err != nil {
helper.InternalServer(c, err)
return
}
helper.Success(c)
}
// @Tags OpenResty
// @Summary Get default HTTPs status
// @Success 200 {object} response.NginxConfigRes
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /openresty/https [get]
func (b *BaseApi) GetDefaultHTTPsStatus(c *gin.Context) {
res, err := nginxService.GetDefaultHttpsStatus()
if err != nil {
helper.InternalServer(c, err)
return
}
helper.SuccessWithData(c, res)
}
+1 -1
View File
@@ -113,7 +113,7 @@ func loadLocalConn() (*ssh.SSHClient, error) {
itemPath = path.Join(currentInfo.HomeDir, ".ssh/id_ed25519_1panel")
}
if _, err := os.Stat(itemPath); err != nil {
_ = sshService.GenerateSSH(dto.GenerateSSH{EncryptionMode: "ed25519", Name: "_1panel"})
_ = sshService.CreateRootCert(dto.CreateRootCert{EncryptionMode: "ed25519", Name: "1panel", Description: "1Panel Terminal"})
}
privateKey, _ := os.ReadFile(itemPath)
+78 -12
View File
@@ -1,6 +1,8 @@
package v2
import (
"encoding/base64"
"github.com/1Panel-dev/1Panel/agent/app/api/v2/helper"
"github.com/1Panel-dev/1Panel/agent/app/dto"
"github.com/gin-gonic/gin"
@@ -90,19 +92,58 @@ func (b *BaseApi) UpdateSSHByfile(c *gin.Context) {
// @Tags SSH
// @Summary Generate host SSH secret
// @Accept json
// @Param request body dto.GenerateSSH true "request"
// @Param request body dto.CreateRootCert true "request"
// @Success 200
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /hosts/ssh/generate [post]
// @Router /hosts/ssh/cert [post]
// @x-panel-log {"bodyKeys":[],"paramKeys":[],"BeforeFunctions":[],"formatZH":"生成 SSH 密钥 ","formatEN":"generate SSH secret"}
func (b *BaseApi) GenerateSSH(c *gin.Context) {
var req dto.GenerateSSH
func (b *BaseApi) CreateRootCert(c *gin.Context) {
var req dto.CreateRootCert
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}
if len(req.PassPhrase) != 0 {
passPhrase, err := base64.StdEncoding.DecodeString(req.PassPhrase)
if err != nil {
helper.BadRequest(c, err)
return
}
req.PassPhrase = string(passPhrase)
}
if len(req.PrivateKey) != 0 {
privateKey, err := base64.StdEncoding.DecodeString(req.PrivateKey)
if err != nil {
helper.BadRequest(c, err)
return
}
req.PrivateKey = string(privateKey)
}
if len(req.PublicKey) != 0 {
publicKey, err := base64.StdEncoding.DecodeString(req.PublicKey)
if err != nil {
helper.BadRequest(c, err)
return
}
req.PublicKey = string(publicKey)
}
if err := sshService.GenerateSSH(req); err != nil {
if err := sshService.CreateRootCert(req); err != nil {
helper.InternalServer(c, err)
return
}
helper.Success(c)
}
// @Tags SSH
// @Summary Sycn host SSH secret
// @Success 200
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /hosts/ssh/cert/sync [post]
// @x-panel-log {"bodyKeys":[],"paramKeys":[],"BeforeFunctions":[],"formatZH":"同步 SSH 密钥 ","formatEN":"sync SSH secret"}
func (b *BaseApi) SyncRootCert(c *gin.Context) {
if err := sshService.SyncRootCert(); err != nil {
helper.InternalServer(c, err)
return
}
@@ -112,23 +153,48 @@ func (b *BaseApi) GenerateSSH(c *gin.Context) {
// @Tags SSH
// @Summary Load host SSH secret
// @Accept json
// @Param request body dto.GenerateLoad true "request"
// @Success 200 {string} secret
// @Param request body dto.SearchWithPage true "request"
// @Success 200 {object} dto.PageResult
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /hosts/ssh/secret [post]
func (b *BaseApi) LoadSSHSecret(c *gin.Context) {
var req dto.GenerateLoad
// @Router /hosts/ssh/cert/search [post]
func (b *BaseApi) SearchRootCert(c *gin.Context) {
var req dto.SearchWithPage
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}
data, err := sshService.LoadSSHSecret(req.EncryptionMode)
total, data, err := sshService.SearchRootCerts(req)
if err != nil {
helper.InternalServer(c, err)
return
}
helper.SuccessWithData(c, data)
helper.SuccessWithData(c, dto.PageResult{
Total: total,
Items: data,
})
}
// @Tags SSH
// @Summary Delete host SSH secret
// @Accept json
// @Param request body dto.ForceDelete true "request"
// @Success 200
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /hosts/ssh/cert/delete [post]
// @x-panel-log {"bodyKeys":[],"paramKeys":[],"BeforeFunctions":[],"formatZH":"删除 SSH 密钥 ","formatEN":"delete SSH secret"}
func (b *BaseApi) DeleteRootCert(c *gin.Context) {
var req dto.ForceDelete
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}
if err := sshService.DeleteRootCerts(req); err != nil {
helper.InternalServer(c, err)
return
}
helper.Success(c)
}
// @Tags SSH
+8
View File
@@ -28,6 +28,9 @@ type OperationWithName struct {
type OperateByID struct {
ID uint `json:"id" validate:"required"`
}
type OperateByIDs struct {
IDs []uint `json:"ids"`
}
type Operate struct {
Operation string `json:"operation" validate:"required"`
@@ -76,3 +79,8 @@ type ForceDelete struct {
IDs []uint `json:"ids"`
ForceDelete bool `json:"forceDelete"`
}
type ChangeGroup struct {
ID uint `json:"id" validate:"required"`
GroupID uint `json:"groupID" validate:"required"`
}
+4 -7
View File
@@ -174,15 +174,11 @@ type ContainerCommit struct {
}
type ContainerPrune struct {
TaskID string `json:"taskID"`
PruneType string `json:"pruneType" validate:"required,oneof=container image volume network buildcache"`
WithTagAll bool `json:"withTagAll"`
}
type ContainerPruneReport struct {
DeletedNumber int `json:"deletedNumber"`
SpaceReclaimed int `json:"spaceReclaimed"`
}
type Network struct {
ID string `json:"id"`
Name string `json:"name"`
@@ -227,8 +223,9 @@ type VolumeCreate struct {
}
type BatchDelete struct {
Force bool `json:"force"`
Names []string `json:"names" validate:"required"`
TaskID string `json:"taskID"`
Force bool `json:"force"`
Names []string `json:"names" validate:"required"`
}
type ComposeInfo struct {
+58 -3
View File
@@ -6,9 +6,10 @@ import (
type PageCronjob struct {
PageInfo
Info string `json:"info"`
OrderBy string `json:"orderBy" validate:"required,oneof=name status createdAt"`
Order string `json:"order" validate:"required,oneof=null ascending descending"`
Info string `json:"info"`
GroupIDs []uint `json:"groupIDs"`
OrderBy string `json:"orderBy" validate:"required,oneof=name status createdAt"`
Order string `json:"order" validate:"required,oneof=null ascending descending"`
}
type CronjobSpec struct {
@@ -19,6 +20,7 @@ type CronjobOperate struct {
ID uint `json:"id"`
Name string `json:"name" validate:"required"`
Type string `json:"type" validate:"required"`
GroupID uint `json:"groupID"`
SpecCustom bool `json:"specCustom"`
Spec string `json:"spec" validate:"required"`
@@ -85,6 +87,7 @@ type CronjobInfo struct {
ID uint `json:"id"`
Name string `json:"name"`
Type string `json:"type"`
GroupID uint `json:"groupID"`
SpecCustom bool `json:"specCustom"`
Spec string `json:"spec"`
@@ -123,6 +126,58 @@ type CronjobInfo struct {
AlertCount uint `json:"alertCount"`
}
type CronjobImport struct {
Cronjobs []CronjobTrans `json:"cronjobs"`
}
type CronjobTrans struct {
Name string `json:"name"`
Type string `json:"type"`
GroupID uint `json:"groupID"`
SpecCustom bool `json:"specCustom"`
Spec string `json:"spec"`
Executor string `json:"executor"`
ScriptMode string `json:"scriptMode"`
Script string `json:"script"`
Command string `json:"command"`
ContainerName string `json:"containerName"`
User string `json:"user"`
URL string `json:"url"`
ScriptName string `json:"scriptName"`
Apps []TransHelper `json:"apps"`
Websites []string `json:"websites"`
DBType string `json:"dbType"`
DBNames []TransHelper `json:"dbName"`
ExclusionRules string `json:"exclusionRules"`
IsDir bool `json:"isDir"`
SourceDir string `json:"sourceDir"`
RetainCopies uint64 `json:"retainCopies"`
RetryTimes uint `json:"retryTimes"`
Timeout uint `json:"timeout"`
IgnoreErr bool `json:"ignoreErr"`
SnapshotRule SnapshotTransHelper `json:"snapshotRule"`
Secret string `json:"secret"`
SourceAccounts []string `json:"sourceAccounts"`
DownloadAccount string `json:"downloadAccount"`
AlertCount uint `json:"alertCount"`
AlertTitle string `json:"alertTitle"`
AlertMethod string `json:"alertMethod"`
}
type TransHelper struct {
Name string `json:"name"`
DetailName string `json:"detailName"`
}
type SnapshotTransHelper struct {
WithImage bool `json:"withImage"`
IgnoreApps []TransHelper `json:"ignoreApps"`
}
type ScriptOptions struct {
ID uint `json:"id"`
Name string `json:"name"`
+5 -2
View File
@@ -4,14 +4,14 @@ import "time"
// common
type DBConfUpdateByFile struct {
Type string `json:"type" validate:"required,oneof=mysql mariadb postgresql redis mysql-cluster postgresql-cluster"`
Type string `json:"type" validate:"required,oneof=mysql mariadb postgresql redis mysql-cluster postgresql-cluster redis-cluster"`
Database string `json:"database" validate:"required"`
File string `json:"file"`
}
type ChangeDBInfo struct {
ID uint `json:"id"`
From string `json:"from" validate:"required,oneof=local remote"`
Type string `json:"type" validate:"required,oneof=mysql mariadb postgresql mysql-cluster postgresql-cluster"`
Type string `json:"type" validate:"required,oneof=mysql mariadb postgresql redis mysql-cluster postgresql-cluster redis-cluster"`
Database string `json:"database" validate:"required"`
Value string `json:"value" validate:"required"`
}
@@ -260,6 +260,7 @@ type DatabaseInfo struct {
ClientCert string `json:"clientCert"`
SkipVerify bool `json:"skipVerify"`
Timeout uint `json:"timeout"`
Description string `json:"description"`
}
@@ -295,6 +296,7 @@ type DatabaseCreate struct {
ClientCert string `json:"clientCert"`
SkipVerify bool `json:"skipVerify"`
Timeout uint `json:"timeout"`
Description string `json:"description"`
}
@@ -313,6 +315,7 @@ type DatabaseUpdate struct {
ClientCert string `json:"clientCert"`
SkipVerify bool `json:"skipVerify"`
Timeout uint `json:"timeout"`
Description string `json:"description"`
}
+3 -9
View File
@@ -60,26 +60,20 @@ type PostgresqlPrivileges struct {
type PostgresqlLoadDB struct {
From string `json:"from" validate:"required,oneof=local remote"`
Type string `json:"type" validate:"required,oneof=postgresql"`
Type string `json:"type" validate:"required,oneof=postgresql postgresql-cluster"`
Database string `json:"database" validate:"required"`
}
type PostgresqlDBDeleteCheck struct {
ID uint `json:"id" validate:"required"`
Type string `json:"type" validate:"required,oneof=postgresql"`
Type string `json:"type" validate:"required,oneof=postgresql postgresql-cluster"`
Database string `json:"database" validate:"required"`
}
type PostgresqlDBDelete struct {
ID uint `json:"id" validate:"required"`
Type string `json:"type" validate:"required,oneof=postgresql"`
Type string `json:"type" validate:"required,oneof=postgresql postgresql-cluster"`
Database string `json:"database" validate:"required"`
ForceDelete bool `json:"forceDelete"`
DeleteBackup bool `json:"deleteBackup"`
}
type PostgresqlConfUpdateByFile struct {
Type string `json:"type" validate:"required,oneof=postgresql mariadb"`
Database string `json:"database" validate:"required"`
File string `json:"file"`
}
+1
View File
@@ -33,6 +33,7 @@ type SupervisorProcessConfig struct {
Dir string `json:"dir"`
Numprocs string `json:"numprocs"`
AutoRestart string `json:"autoRestart"`
AutoStart string `json:"autoStart"`
}
type SupervisorProcessFileReq struct {
+11 -9
View File
@@ -9,15 +9,17 @@ type McpServerSearch struct {
}
type McpServerCreate struct {
Name string `json:"name" validate:"required"`
Command string `json:"command" validate:"required"`
Environments []Environment `json:"environments"`
Volumes []Volume `json:"volumes"`
Port int `json:"port" validate:"required"`
ContainerName string `json:"containerName"`
BaseURL string `json:"baseUrl"`
SsePath string `json:"ssePath"`
HostIP string `json:"hostIP"`
Name string `json:"name" validate:"required"`
Command string `json:"command" validate:"required"`
Environments []Environment `json:"environments"`
Volumes []Volume `json:"volumes"`
Port int `json:"port" validate:"required"`
ContainerName string `json:"containerName"`
BaseURL string `json:"baseUrl"`
SsePath string `json:"ssePath"`
HostIP string `json:"hostIP"`
StreamableHttpPath string `json:"streamableHttpPath"`
OutputTransport string `json:"outputTransport" validate:"required"`
}
type McpServerUpdate struct {
+4
View File
@@ -126,3 +126,7 @@ type NginxModuleUpdate struct {
Enable bool `json:"enable"`
Params string `json:"params"`
}
type NginxOperateReq struct {
Operate string `json:"operate" validate:"required,oneof=enable disable"`
}
+1 -1
View File
@@ -6,5 +6,5 @@ type AppIgnoreUpgradeDTO struct {
AppDetailID uint `json:"appDetailID"`
Scope string `json:"scope"`
Version string `json:"version"`
Icon string `json:"icon"`
Name string `json:"name"`
}
+8 -6
View File
@@ -1,8 +1,9 @@
package response
import (
"github.com/1Panel-dev/1Panel/agent/utils/files"
"time"
"github.com/1Panel-dev/1Panel/agent/utils/files"
)
type FileInfo struct {
@@ -37,11 +38,12 @@ type FileWgetRes struct {
}
type FileLineContent struct {
Content string `json:"content"`
End bool `json:"end"`
Path string `json:"path"`
Total int `json:"total"`
Lines []string `json:"lines"`
End bool `json:"end"`
Path string `json:"path"`
Total int `json:"total"`
TaskStatus string `json:"taskStatus"`
Lines []string `json:"lines"`
Scope string `json:"scope"`
}
type FileExist struct {
+1
View File
@@ -31,6 +31,7 @@ type SupervisorProcessConfig struct {
Msg string `json:"msg"`
Status []ProcessStatus `json:"status"`
AutoRestart string `json:"autoRestart"`
AutoStart string `json:"autoStart"`
}
type ProcessStatus struct {
+4
View File
@@ -80,3 +80,7 @@ type NginxBuildConfig struct {
Mirror string `json:"mirror"`
Modules []NginxModule `json:"modules"`
}
type NginxConfigRes struct {
Https bool `json:"https"`
}
+18 -3
View File
@@ -19,12 +19,27 @@ type SSHInfo struct {
PubkeyAuthentication string `json:"pubkeyAuthentication"`
PermitRootLogin string `json:"permitRootLogin"`
UseDNS string `json:"useDNS"`
CurrentUser string `json:"currentUser"`
}
type GenerateSSH struct {
EncryptionMode string `json:"encryptionMode" validate:"required,oneof=rsa ed25519 ecdsa dsa"`
Password string `json:"password"`
type CreateRootCert struct {
Name string `json:"name"`
Mode string `json:"mode"`
EncryptionMode string `json:"encryptionMode" validate:"required,oneof=rsa ed25519 ecdsa dsa"`
PassPhrase string `json:"passPhrase"`
PublicKey string `json:"publicKey"`
PrivateKey string `json:"privateKey"`
Description string `json:"description"`
}
type RootCert struct {
ID uint `json:"id"`
CreatedAt time.Time `json:"createdAt"`
Name string `json:"name"`
EncryptionMode string `json:"encryptionMode"`
PassPhrase string `json:"passPhrase"`
PublicKey string `json:"publicKey"`
PrivateKey string `json:"privateKey"`
Description string `json:"description"`
}
type GenerateLoad struct {
+2 -2
View File
@@ -18,7 +18,7 @@ type AlertTask struct {
Type string `gorm:"type:varchar(64);not null" json:"type"`
Quota string `gorm:"type:varchar(64)" json:"quota"`
QuotaType string `gorm:"type:varchar(64)" json:"quotaType"`
Method string `gorm:"type:varchar(64);not null;default:'sms" json:"method"`
Method string `gorm:"type:varchar(64);not null;default:'sms'" json:"method"`
}
type AlertLog struct {
@@ -33,7 +33,7 @@ type AlertLog struct {
Message string `gorm:"type:varchar(256);" json:"message"`
RecordId uint `gorm:"type:integer;" json:"recordId"`
LicenseId string `gorm:"type:varchar(256);not null;" json:"licenseId" `
Method string `gorm:"type:varchar(64);not null;default:'sms" json:"method"`
Method string `gorm:"type:varchar(64);not null;default:'sms'" json:"method"`
}
type AlertConfig struct {
+1
View File
@@ -11,6 +11,7 @@ type Cronjob struct {
Name string `gorm:"not null" json:"name"`
Type string `gorm:"not null" json:"type"`
GroupID uint `json:"groupID"`
SpecCustom bool `json:"specCustom"`
Spec string `gorm:"not null" json:"spec"`
+1
View File
@@ -18,5 +18,6 @@ type Database struct {
ClientCert string `json:"clientCert"`
SkipVerify bool `json:"skipVerify"`
Timeout uint `json:"timeout"`
Description string `json:"description"`
}
+15 -13
View File
@@ -2,17 +2,19 @@ package model
type McpServer struct {
BaseModel
Name string `json:"name"`
DockerCompose string `json:"dockerCompose"`
Command string `json:"command"`
ContainerName string `json:"containerName"`
Message string `json:"message"`
Port int `json:"port"`
Status string `json:"status"`
Env string `json:"env"`
BaseURL string `json:"baseUrl"`
SsePath string `json:"ssePath"`
WebsiteID int `json:"websiteID"`
Dir string `json:"dir"`
HostIP string `json:"hostIP"`
Name string `json:"name"`
DockerCompose string `json:"dockerCompose"`
Command string `json:"command"`
ContainerName string `json:"containerName"`
Message string `json:"message"`
Port int `json:"port"`
Status string `json:"status"`
Env string `json:"env"`
BaseURL string `json:"baseUrl"`
SsePath string `json:"ssePath"`
WebsiteID int `json:"websiteID"`
Dir string `json:"dir"`
HostIP string `json:"hostIP"`
StreamableHttpPath string `json:"streamableHttpPath"`
OutputTransport string `json:"outputTransport"`
}
+11
View File
@@ -0,0 +1,11 @@
package model
type RootCert struct {
BaseModel
Name string `json:"name" gorm:"not null;"`
EncryptionMode string `json:"encryptionMode"`
PassPhrase string `json:"passPhrase"`
PublicKeyPath string `json:"publicKeyPath"`
PrivateKeyPath string `json:"privateKeyPath"`
Description string `json:"description"`
}
+7
View File
@@ -11,6 +11,7 @@ type AppIgnoreUpgradeRepo struct {
type IAppIgnoreUpgradeRepo interface {
WithScope(scope string) DBOption
WithAppID(appID uint) DBOption
List(opts ...DBOption) ([]model.AppIgnoreUpgrade, error)
Create(appIgnoreUpgrade *model.AppIgnoreUpgrade) error
Delete(opts ...DBOption) error
@@ -26,6 +27,12 @@ func (a AppIgnoreUpgradeRepo) WithScope(scope string) DBOption {
}
}
func (a AppIgnoreUpgradeRepo) WithAppID(appID uint) DBOption {
return func(g *gorm.DB) *gorm.DB {
return g.Where("app_id = ?", appID)
}
}
func (a AppIgnoreUpgradeRepo) List(opts ...DBOption) ([]model.AppIgnoreUpgrade, error) {
var appIgnoreUpgradeList []model.AppIgnoreUpgrade
err := getDb(opts...).Find(&appIgnoreUpgradeList).Error
+21
View File
@@ -36,6 +36,7 @@ type IAppInstallRepo interface {
Page(page, size int, opts ...DBOption) (int64, []model.AppInstall, error)
BatchUpdateBy(maps map[string]interface{}, opts ...DBOption) error
LoadBaseInfo(key string, name string) (*RootInfo, error)
LoadInstallAppByKeyAndName(key string, name string) (*model.AppInstall, error)
GetFirstByCtx(ctx context.Context, opts ...DBOption) (model.AppInstall, error)
}
@@ -246,3 +247,23 @@ func (a *AppInstallRepo) LoadBaseInfo(key string, name string) (*RootInfo, error
info.Status = appInstall.Status
return &info, nil
}
func (a *AppInstallRepo) LoadInstallAppByKeyAndName(key string, name string) (*model.AppInstall, error) {
var (
app model.App
appInstall model.AppInstall
)
if err := global.DB.Where("key = ?", key).First(&app).Error; err != nil {
return nil, err
}
if len(name) == 0 {
if err := global.DB.Where("app_id = ?", app.ID).First(&appInstall).Error; err != nil {
return nil, err
}
} else {
if err := global.DB.Where("app_id = ? AND name = ?", app.ID, name).First(&appInstall).Error; err != nil {
return nil, err
}
}
return &appInstall, nil
}
+17
View File
@@ -17,8 +17,10 @@ type IAppTagRepo interface {
GetByAppId(appId uint) ([]model.AppTag, error)
GetByTagIds(tagIds []uint) ([]model.AppTag, error)
DeleteBy(ctx context.Context, opts ...DBOption) error
GetFirst(opts ...DBOption) (*model.AppTag, error)
WithByTagID(tagID uint) DBOption
WithByAppID(appId uint) DBOption
}
func NewIAppTagRepo() IAppTagRepo {
@@ -31,6 +33,12 @@ func (a AppTagRepo) WithByTagID(tagID uint) DBOption {
}
}
func (a AppTagRepo) WithByAppID(appId uint) DBOption {
return func(g *gorm.DB) *gorm.DB {
return g.Where("app_id = ?", appId)
}
}
func (a AppTagRepo) BatchCreate(ctx context.Context, tags []*model.AppTag) error {
return getTx(ctx).Create(&tags).Error
}
@@ -62,3 +70,12 @@ func (a AppTagRepo) GetByTagIds(tagIds []uint) ([]model.AppTag, error) {
func (a AppTagRepo) DeleteBy(ctx context.Context, opts ...DBOption) error {
return getTx(ctx, opts...).Delete(&model.AppTag{}).Error
}
func (a AppTagRepo) GetFirst(opts ...DBOption) (*model.AppTag, error) {
var appTag model.AppTag
if err := getDb(opts...).First(&appTag).Error; err != nil {
return nil, err
}
return &appTag, nil
}
+9
View File
@@ -104,6 +104,15 @@ func WithByDate(startTime, endTime time.Time) DBOption {
}
}
func WithByGroups(groupIDs []uint) DBOption {
return func(g *gorm.DB) *gorm.DB {
if len(groupIDs) == 0 {
return g
}
return g.Where("group_id in (?)", groupIDs)
}
}
func WithByCreatedAt(startTime, endTime time.Time) DBOption {
return func(g *gorm.DB) *gorm.DB {
return g.Where("created_at > ? AND created_at < ?", startTime, endTime)
+88
View File
@@ -3,6 +3,7 @@ package repo
import (
"github.com/1Panel-dev/1Panel/agent/app/model"
"github.com/1Panel-dev/1Panel/agent/global"
"github.com/1Panel-dev/1Panel/agent/utils/encrypt"
)
type HostRepo struct{}
@@ -13,6 +14,14 @@ type IHostRepo interface {
SaveFirewallRecord(firewall *model.Firewall) error
DeleteFirewallRecordByID(id uint) error
DeleteFirewallRecord(fType, port, protocol, address, strategy string) error
SyncCert(data []model.RootCert) error
GetCert(opts ...DBOption) (model.RootCert, error)
PageCert(limit, offset int, opts ...DBOption) (int64, []model.RootCert, error)
ListCert(opts ...DBOption) ([]model.RootCert, error)
CreateCert(cert *model.RootCert) error
UpdateCert(id uint, vars map[string]interface{}) error
DeleteCert(opts ...DBOption) error
}
func NewIHostRepo() IHostRepo {
@@ -63,3 +72,82 @@ func (h *HostRepo) DeleteFirewallRecordByID(id uint) error {
func (h *HostRepo) DeleteFirewallRecord(fType, port, protocol, address, strategy string) error {
return global.DB.Where("type = ? AND port = ? AND protocol = ? AND address = ? AND strategy = ?", fType, port, protocol, address, strategy).Delete(&model.Firewall{}).Error
}
func (u *HostRepo) GetCert(opts ...DBOption) (model.RootCert, error) {
var cert model.RootCert
db := global.DB
for _, opt := range opts {
db = opt(db)
}
err := db.First(&cert).Error
return cert, err
}
func (u *HostRepo) PageCert(page, size int, opts ...DBOption) (int64, []model.RootCert, error) {
var ops []model.RootCert
db := global.DB.Model(&model.RootCert{})
for _, opt := range opts {
db = opt(db)
}
count := int64(0)
db = db.Count(&count)
err := db.Limit(size).Offset(size * (page - 1)).Find(&ops).Error
return count, ops, err
}
func (u *HostRepo) ListCert(opts ...DBOption) ([]model.RootCert, error) {
var ops []model.RootCert
db := global.DB.Model(&model.RootCert{})
for _, opt := range opts {
db = opt(db)
}
count := int64(0)
db = db.Count(&count)
err := db.Find(&ops).Error
return ops, err
}
func (u *HostRepo) CreateCert(cert *model.RootCert) error {
return global.DB.Create(cert).Error
}
func (u *HostRepo) UpdateCert(id uint, vars map[string]interface{}) error {
return global.DB.Model(&model.RootCert{}).Where("id = ?", id).Updates(vars).Error
}
func (u *HostRepo) DeleteCert(opts ...DBOption) error {
db := global.DB
for _, opt := range opts {
db = opt(db)
}
return db.Delete(&model.RootCert{}).Error
}
func (u *HostRepo) SyncCert(data []model.RootCert) error {
tx := global.DB.Begin()
var oldCerts []model.RootCert
_ = tx.Where("1 = ?", 1).Find(&oldCerts).Error
oldCertsMap := make(map[string]uint)
for _, item := range oldCerts {
oldCertsMap[item.Name] = item.ID
}
for _, item := range data {
if _, ok := oldCertsMap[item.Name]; ok {
delete(oldCertsMap, item.Name)
continue
}
item.PassPhrase, _ = encrypt.StringEncrypt("<UN-SET>")
if err := tx.Model(model.RootCert{}).Create(&item).Error; err != nil {
tx.Rollback()
return err
}
}
for _, val := range oldCertsMap {
if err := tx.Where("id = ?", val).Delete(&model.RootCert{}).Error; err != nil {
tx.Rollback()
return err
}
}
tx.Commit()
return nil
}
+9
View File
@@ -19,6 +19,7 @@ type ITagRepo interface {
DeleteByID(ctx context.Context, id uint) error
Create(ctx context.Context, tag *model.Tag) error
Save(ctx context.Context, tag *model.Tag) error
GetByKey(key string) (*model.Tag, error)
}
func NewITagRepo() ITagRepo {
@@ -41,6 +42,14 @@ func (t TagRepo) All() ([]model.Tag, error) {
return tags, nil
}
func (t TagRepo) GetByKey(key string) (*model.Tag, error) {
var tag model.Tag
if err := getDb().Where("key = ?", key).First(&tag).Error; err != nil {
return nil, err
}
return &tag, nil
}
func (t TagRepo) GetByIds(ids []uint) ([]model.Tag, error) {
var tags []model.Tag
if err := getDb().Where("id in (?)", ids).Find(&tags).Error; err != nil {
+13 -24
View File
@@ -928,18 +928,11 @@ func (a AppService) SyncAppListFromRemote(taskID string) (err error) {
return err
}
var (
tags []*model.Tag
appTags []*model.AppTag
oldAppIds []uint
)
for _, tag := range list.Extra.Tags {
translations, _ := json.Marshal(tag.Locales)
tags = append(tags, &model.Tag{
Name: tag.Name,
Translations: string(translations),
Sort: tag.Sort,
Key: tag.Key,
})
if err = SyncTags(list.Extra); err != nil {
return err
}
deleteCustomApp()
oldApps, err := appRepo.GetBy(appRepo.WithNotLocal())
@@ -1015,6 +1008,7 @@ func (a AppService) SyncAppListFromRemote(taskID string) (err error) {
}
t.LogSuccess(i18n.GetMsgByKey("SyncAppDetail"))
tags, _ := tagRepo.All()
var (
addAppArray []model.App
updateAppArray []model.App
@@ -1061,16 +1055,8 @@ func (a AppService) SyncAppListFromRemote(taskID string) (err error) {
return
}
}
if err = tagRepo.DeleteAll(ctx); err != nil {
return
}
if len(tags) > 0 {
if err = tagRepo.BatchCreate(ctx, tags); err != nil {
return
}
for _, tag := range tags {
tagMap[tag.Key] = tag.ID
}
for _, tag := range tags {
tagMap[tag.Key] = tag.ID
}
for _, update := range updateAppArray {
if err = appRepo.Save(ctx, &update); err != nil {
@@ -1088,10 +1074,13 @@ func (a AppService) SyncAppListFromRemote(taskID string) (err error) {
for _, tag := range app.TagsKey {
tagId, ok := tagMap[tag]
if ok {
appTags = append(appTags, &model.AppTag{
AppId: app.ID,
TagId: tagId,
})
exist, _ := appTagRepo.GetFirst(appTagRepo.WithByTagID(tagId), appTagRepo.WithByAppID(app.ID))
if exist != nil {
appTags = append(appTags, &model.AppTag{
AppId: app.ID,
TagId: tagId,
})
}
}
}
for _, d := range app.Details {
@@ -1134,7 +1123,7 @@ func (a AppService) SyncAppListFromRemote(taskID string) (err error) {
}
if len(oldAppIds) > 0 {
if err = appTagRepo.DeleteByAppIds(ctx, oldAppIds); err != nil {
if err = appTagRepo.DeleteByAppIds(ctx, deleteIds); err != nil {
return
}
}
+1 -1
View File
@@ -40,7 +40,7 @@ func (a AppIgnoreUpgradeService) List() ([]response.AppIgnoreUpgradeDTO, error)
_ = appIgnoreUpgradeRepo.Delete(repo.WithByID(ignore.ID))
continue
}
dto.Icon = app.Icon
dto.Name = app.Name
if ignore.Scope == "version" {
appDetail, err := appDetailRepo.GetFirst(repo.WithByID(ignore.AppDetailID))
if errors.Is(err, gorm.ErrRecordNotFound) {
+1 -1
View File
@@ -672,7 +672,7 @@ func (a *AppInstallService) GetDefaultConfigByKey(key, name string) (string, err
if key == constant.AppMysql || key == constant.AppMariaDB || key == constant.AppMysqlCluster {
filePath = path.Join(filePath, "my.cnf")
}
if key == constant.AppRedis {
if key == constant.AppRedis || key == constant.AppRedisCluster {
filePath = path.Join(filePath, "redis.conf")
}
if key == constant.AppOpenresty {
+73 -6
View File
@@ -440,6 +440,11 @@ func deleteAppInstall(deleteReq request.AppInstallDelete) error {
_ = op.DeleteDir(parentDir)
}
tx.Commit()
existApps, _ := appInstallRepo.ListBy(context.Background(), appInstallRepo.WithAppId(install.AppId))
if len(existApps) == 0 {
_ = appIgnoreUpgradeRepo.Delete(appIgnoreUpgradeRepo.WithAppID(install.AppId))
}
return nil
}
uninstallTask.AddSubTask(task.GetTaskName(install.Name, task.TaskUninstall, task.TaskScopeApp), uninstall, nil)
@@ -650,7 +655,11 @@ func upgradeInstall(req request.AppInstallUpgrade) error {
}
dockerCLi, _ := docker.NewClient()
if req.PullImage {
images, err := composeV2.GetDockerComposeImagesV2(content, []byte(detail.DockerCompose))
composeContent := []byte(detail.DockerCompose)
if req.DockerCompose != "" {
composeContent = []byte(req.DockerCompose)
}
images, err := composeV2.GetDockerComposeImagesV2(content, composeContent)
if err != nil {
return err
}
@@ -1028,7 +1037,6 @@ func upApp(task *task.Task, appInstall *model.AppInstall, pullImages bool) error
errMsg string
)
if pullImages && appInstall.App.Type != "php" {
//projectName := strings.ToLower(appInstall.Name)
envByte, err := files.NewFileOp().GetContent(appInstall.GetEnvPath())
if err != nil {
return err
@@ -1058,8 +1066,8 @@ func upApp(task *task.Task, appInstall *model.AppInstall, pullImages bool) error
if strings.Contains(errOur, "no such host") {
errMsg = i18n.GetMsgByKey("ErrNoSuchHost") + ":"
}
if strings.Contains(errOur, "timeout") {
errMsg = i18n.GetMsgByKey("ErrImagePullTimeOut") + ":"
if strings.Contains(errOur, "Error response from daemon") {
errMsg = i18n.GetMsgByKey("PullImageTimeout") + ":"
}
}
appInstall.Message = errMsg + errOur
@@ -1764,6 +1772,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))
@@ -1774,8 +1786,7 @@ func ignoreUpdate(installed model.AppInstall) bool {
}
return true
}
ignores, _ := appIgnoreUpgradeRepo.List(appDetailRepo.WithAppId(installed.AppId), appIgnoreUpgradeRepo.WithScope("all"))
return len(ignores) > 0
return false
}
func RequestDownloadCallBack(downloadCallBackUrl string) {
@@ -1899,3 +1910,59 @@ func handleSSLConfig(appInstall *model.AppInstall, hasDefaultWebsite bool) error
}
return nil
}
func SyncTags(remoteProperties dto.ExtraProperties) error {
tx, ctx := getTxAndContext()
defer tx.Rollback()
localTags, _ := tagRepo.All()
localTagsMap := make(map[string]*model.Tag)
for i := range localTags {
localTagsMap[localTags[i].Key] = &localTags[i]
}
var err error
remoteTagsMap := make(map[string]*dto.Tag)
for i := range remoteProperties.Tags {
remoteTagsMap[remoteProperties.Tags[i].Key] = &remoteProperties.Tags[i]
}
for key, localTag := range localTagsMap {
if _, exists := remoteTagsMap[key]; !exists {
_ = tagRepo.DeleteByID(ctx, localTag.ID)
}
}
for _, remoteTag := range remoteProperties.Tags {
translations, _ := json.Marshal(remoteTag.Locales)
if existTag, exists := localTagsMap[remoteTag.Key]; exists {
if needsUpdate(existTag, remoteTag, string(translations)) {
existTag.Name = remoteTag.Name
existTag.Sort = remoteTag.Sort
existTag.Translations = string(translations)
if err = tagRepo.Save(ctx, existTag); err != nil {
return err
}
}
} else {
newTag := &model.Tag{
Key: remoteTag.Key,
Name: remoteTag.Name,
Sort: remoteTag.Sort,
Translations: string(translations),
}
if err = tagRepo.Create(ctx, newTag); err != nil {
return err
}
}
}
tx.Commit()
return nil
}
func needsUpdate(localTag *model.Tag, remoteTag dto.Tag, translations string) bool {
return localTag.Name != remoteTag.Name ||
localTag.Sort != remoteTag.Sort ||
localTag.Translations != translations
}
+2 -2
View File
@@ -119,7 +119,7 @@ func handlePostgresqlBackup(db DatabaseHelper, parentTask *task.Task, targetDir,
}
itemHandler := func() error { return doPostgresqlgBackup(db, targetDir, fileName) }
backupTask.AddSubTask(task.GetTaskName(itemName, task.TaskBackup, task.TaskScopeWebsite), func(task *task.Task) error { return itemHandler() }, nil)
backupTask.AddSubTask(task.GetTaskName(itemName, task.TaskBackup, task.TaskScopeDatabase), func(task *task.Task) error { return itemHandler() }, nil)
if parentTask != nil {
return itemHandler()
}
@@ -137,7 +137,7 @@ func handlePostgresqlRecover(req dto.CommonRecover, parentTask *task.Task, isRol
}
itemTask = parentTask
if parentTask == nil {
itemTask, err = task.NewTaskWithOps("Redis", task.TaskRecover, task.TaskScopeDatabase, req.TaskID, dbInfo.ID)
itemTask, err = task.NewTaskWithOps(req.Name, task.TaskRecover, task.TaskScopeDatabase, req.TaskID, dbInfo.ID)
if err != nil {
return err
}
+104 -51
View File
@@ -82,7 +82,7 @@ type IContainerService interface {
CreateVolume(req dto.VolumeCreate) error
TestCompose(req dto.ComposeCreate) (bool, error)
ComposeUpdate(req dto.ComposeUpdate) error
Prune(req dto.ContainerPrune) (dto.ContainerPruneReport, error)
Prune(req dto.ContainerPrune) error
LoadUsers(req dto.OperationWithName) []string
@@ -417,66 +417,93 @@ func (u *ContainerService) Inspect(req dto.InspectReq) (string, error) {
return string(bytes), nil
}
func (u *ContainerService) Prune(req dto.ContainerPrune) (dto.ContainerPruneReport, error) {
report := dto.ContainerPruneReport{}
func (u *ContainerService) Prune(req dto.ContainerPrune) error {
client, err := docker.NewDockerClient()
if err != nil {
return report, err
return err
}
defer client.Close()
pruneFilters := filters.NewArgs()
if req.WithTagAll {
pruneFilters.Add("dangling", "false")
if req.PruneType != "image" {
pruneFilters.Add("until", "24h")
}
}
name := ""
switch req.PruneType {
case "container":
rep, err := client.ContainersPrune(context.Background(), pruneFilters)
if err != nil {
return report, err
}
report.DeletedNumber = len(rep.ContainersDeleted)
report.SpaceReclaimed = int(rep.SpaceReclaimed)
name = "Container"
case "image":
rep, err := client.ImagesPrune(context.Background(), pruneFilters)
if err != nil {
return report, err
}
report.DeletedNumber = len(rep.ImagesDeleted)
report.SpaceReclaimed = int(rep.SpaceReclaimed)
case "network":
rep, err := client.NetworksPrune(context.Background(), pruneFilters)
if err != nil {
return report, err
}
report.DeletedNumber = len(rep.NetworksDeleted)
name = "Image"
case "volume":
versions, err := client.ServerVersion(context.Background())
if err != nil {
return report, err
}
if common.ComparePanelVersion(versions.APIVersion, "1.42") {
pruneFilters.Add("all", "true")
}
rep, err := client.VolumesPrune(context.Background(), pruneFilters)
if err != nil {
return report, err
}
report.DeletedNumber = len(rep.VolumesDeleted)
report.SpaceReclaimed = int(rep.SpaceReclaimed)
name = "Volume"
case "buildcache":
opts := build.CachePruneOptions{}
opts.All = true
rep, err := client.BuildCachePrune(context.Background(), opts)
if err != nil {
return report, err
}
report.DeletedNumber = len(rep.CachesDeleted)
report.SpaceReclaimed = int(rep.SpaceReclaimed)
name = "BuildCache"
case "network":
name = "Network"
}
return report, nil
taskItem, err := task.NewTaskWithOps(i18n.GetMsgByKey(name), task.TaskClean, task.TaskScopeContainer, req.TaskID, 1)
if err != nil {
global.LOG.Errorf("new task for create container failed, err: %v", err)
return err
}
taskItem.AddSubTask(i18n.GetMsgByKey("TaskClean"), func(t *task.Task) error {
pruneFilters := filters.NewArgs()
if req.WithTagAll {
pruneFilters.Add("dangling", "false")
if req.PruneType != "image" {
pruneFilters.Add("until", "24h")
}
}
DeletedNumber := 0
SpaceReclaimed := 0
switch req.PruneType {
case "container":
rep, err := client.ContainersPrune(context.Background(), pruneFilters)
if err != nil {
return err
}
DeletedNumber = len(rep.ContainersDeleted)
SpaceReclaimed = int(rep.SpaceReclaimed)
case "image":
rep, err := client.ImagesPrune(context.Background(), pruneFilters)
if err != nil {
return err
}
DeletedNumber = len(rep.ImagesDeleted)
SpaceReclaimed = int(rep.SpaceReclaimed)
case "network":
rep, err := client.NetworksPrune(context.Background(), pruneFilters)
if err != nil {
return err
}
DeletedNumber = len(rep.NetworksDeleted)
case "volume":
versions, err := client.ServerVersion(context.Background())
if err != nil {
return err
}
if common.ComparePanelVersion(versions.APIVersion, "1.42") {
pruneFilters.Add("all", "true")
}
rep, err := client.VolumesPrune(context.Background(), pruneFilters)
if err != nil {
return err
}
DeletedNumber = len(rep.VolumesDeleted)
SpaceReclaimed = int(rep.SpaceReclaimed)
case "buildcache":
opts := build.CachePruneOptions{}
opts.All = true
rep, err := client.BuildCachePrune(context.Background(), opts)
if err != nil {
return err
}
DeletedNumber = len(rep.CachesDeleted)
SpaceReclaimed = int(rep.SpaceReclaimed)
}
taskItem.Log(i18n.GetMsgWithMap("PruneHelper", map[string]interface{}{"name": i18n.GetMsgByKey(name), "count": DeletedNumber, "size": common.LoadSizeUnit2F(float64(SpaceReclaimed))}))
return nil
}, nil)
go func() {
_ = taskItem.Execute()
}()
return nil
}
func (u *ContainerService) LoadResourceLimit() (*dto.ResourceLimit, error) {
@@ -1240,6 +1267,32 @@ func checkImageExist(client *client.Client, imageItem string) bool {
return false
}
func checkImageLike(client *client.Client, imageName string) bool {
if client == nil {
var err error
client, err = docker.NewDockerClient()
if err != nil {
return false
}
}
images, err := client.ImageList(context.Background(), image.ListOptions{})
if err != nil {
return false
}
for _, img := range images {
for _, tag := range img.RepoTags {
parts := strings.Split(tag, "/")
imageNameWithTag := parts[len(parts)-1]
if imageNameWithTag == imageName {
return true
}
}
}
return false
}
func pullImages(task *task.Task, client *client.Client, imageName string) error {
dockerCli := docker.NewClientWithExist(client)
options := image.PullOptions{}
+309 -1
View File
@@ -1,6 +1,7 @@
package service
import (
"context"
"encoding/json"
"fmt"
"os"
@@ -15,6 +16,7 @@ import (
"github.com/1Panel-dev/1Panel/agent/buserr"
"github.com/1Panel-dev/1Panel/agent/constant"
"github.com/1Panel-dev/1Panel/agent/global"
"github.com/1Panel-dev/1Panel/agent/utils/docker"
"github.com/jinzhu/copier"
"github.com/pkg/errors"
"github.com/robfig/cron/v3"
@@ -30,11 +32,14 @@ type ICronjobService interface {
HandleOnce(id uint) error
Update(id uint, req dto.CronjobOperate) error
UpdateStatus(id uint, status string) error
UpdateGroup(req dto.ChangeGroup) error
Delete(req dto.CronjobBatchDelete) error
Download(down dto.CronjobDownload) (string, error)
StartJob(cronjob *model.Cronjob, isUpdate bool) (string, error)
CleanRecord(req dto.CronjobClean) error
Export(req dto.OperateByIDs) (string, error)
Import(req []dto.CronjobTrans) error
LoadScriptOptions() []dto.ScriptOptions
LoadInfo(req dto.OperateByID) (*dto.CronjobOperate, error)
@@ -46,7 +51,11 @@ func NewICronjobService() ICronjobService {
}
func (u *CronjobService) SearchWithPage(search dto.PageCronjob) (int64, interface{}, error) {
total, cronjobs, err := cronjobRepo.Page(search.Page, search.PageSize, repo.WithByLikeName(search.Info), repo.WithOrderRuleBy(search.OrderBy, search.Order))
total, cronjobs, err := cronjobRepo.Page(search.Page,
search.PageSize,
repo.WithByGroups(search.GroupIDs),
repo.WithByLikeName(search.Info),
repo.WithOrderRuleBy(search.OrderBy, search.Order))
var dtoCronjobs []dto.CronjobInfo
for _, cronjob := range cronjobs {
var item dto.CronjobInfo
@@ -102,6 +111,292 @@ func (u *CronjobService) LoadInfo(req dto.OperateByID) (*dto.CronjobOperate, err
return &item, err
}
func (u *CronjobService) Export(req dto.OperateByIDs) (string, error) {
cronjobs, err := cronjobRepo.List(repo.WithByIDs(req.IDs))
if err != nil {
return "", err
}
var data []dto.CronjobTrans
for _, cronjob := range cronjobs {
item := dto.CronjobTrans{
Name: cronjob.Name,
Type: cronjob.Type,
GroupID: cronjob.GroupID,
SpecCustom: cronjob.SpecCustom,
Spec: cronjob.Spec,
Executor: cronjob.Executor,
ScriptMode: cronjob.ScriptMode,
Script: cronjob.Script,
Command: cronjob.Command,
ContainerName: cronjob.ContainerName,
User: cronjob.User,
URL: cronjob.URL,
DBType: cronjob.DBType,
ExclusionRules: cronjob.ExclusionRules,
IsDir: cronjob.IsDir,
SourceDir: cronjob.SourceDir,
RetainCopies: cronjob.RetainCopies,
RetryTimes: cronjob.RetryTimes,
Timeout: cronjob.Timeout,
IgnoreErr: cronjob.IgnoreErr,
Secret: cronjob.Secret,
}
switch cronjob.Type {
case "app":
if cronjob.AppID == "all" {
break
}
apps := loadAppsForJob(cronjob)
for _, app := range apps {
item.Apps = append(item.Apps, dto.TransHelper{Name: app.App.Key, DetailName: app.Name})
}
case "website", "cutWebsiteLog":
if cronjob.Website == "all" {
break
}
websites := loadWebsForJob(cronjob)
for _, website := range websites {
item.Websites = append(item.Websites, website.Alias)
}
case "database":
if cronjob.DBName == "all" {
break
}
databases := loadDbsForJob(cronjob)
for _, db := range databases {
item.DBNames = append(item.DBNames, dto.TransHelper{Name: db.Database, DetailName: db.Name})
}
case "shell":
if cronjob.ScriptMode == "library" {
script, err := scriptRepo.Get(repo.WithByID(cronjob.ScriptID))
if err != nil {
return "", err
}
item.ScriptName = script.Name
}
case "snapshot":
if len(cronjob.SnapshotRule) == 0 {
break
}
var snapRule dto.SnapshotRule
if err := json.Unmarshal([]byte(cronjob.SnapshotRule), &snapRule); err != nil {
return "", err
}
item.SnapshotRule.WithImage = snapRule.WithImage
if len(snapRule.IgnoreAppIDs) != 0 {
ignoreApps, _ := appInstallRepo.ListBy(context.Background(), repo.WithByIDs(snapRule.IgnoreAppIDs))
for _, app := range ignoreApps {
item.SnapshotRule.IgnoreApps = append(item.SnapshotRule.IgnoreApps, dto.TransHelper{Name: app.App.Key, DetailName: app.Name})
}
}
}
item.SourceAccounts, item.DownloadAccount, _ = loadBackupNamesByID(cronjob.SourceAccountIDs, cronjob.DownloadAccountID)
alertInfo, _ := alertRepo.Get(alertRepo.WithByType(cronjob.Type), alertRepo.WithByProject(strconv.Itoa(int(cronjob.ID))), repo.WithByStatus(constant.AlertEnable))
if alertInfo.SendCount != 0 {
item.AlertCount = alertInfo.SendCount
item.AlertTitle = alertInfo.Title
item.AlertMethod = alertInfo.Method
} else {
item.AlertCount = 0
}
data = append(data, item)
}
itemJson, err := json.Marshal(&data)
if err != nil {
return "", err
}
return string(itemJson), nil
}
func (u *CronjobService) Import(req []dto.CronjobTrans) error {
for _, item := range req {
cronjobItem, _ := cronjobRepo.Get(repo.WithByName(item.Name))
if cronjobItem.ID != 0 {
continue
}
cronjob := model.Cronjob{
Name: item.Name,
Type: item.Type,
GroupID: item.GroupID,
SpecCustom: item.SpecCustom,
Spec: item.Spec,
Executor: item.Executor,
ScriptMode: item.ScriptMode,
Command: item.Command,
ContainerName: item.ContainerName,
User: item.User,
URL: item.URL,
DBType: item.DBType,
ExclusionRules: item.ExclusionRules,
IsDir: item.IsDir,
RetainCopies: item.RetainCopies,
RetryTimes: item.RetryTimes,
Timeout: item.Timeout,
IgnoreErr: item.IgnoreErr,
Secret: item.Secret,
}
hasNotFound := false
switch item.Type {
case "app":
if len(item.Apps) == 0 {
cronjob.AppID = "all"
break
}
var appIDs []string
for _, app := range item.Apps {
appItem, err := appInstallRepo.LoadInstallAppByKeyAndName(app.Name, app.DetailName)
if err != nil {
hasNotFound = true
continue
}
appIDs = append(appIDs, fmt.Sprintf("%v", appItem.ID))
}
cronjob.AppID = strings.Join(appIDs, ",")
case "website", "cutWebsiteLog":
if len(item.Websites) == 0 {
cronjob.Website = "all"
break
}
var webIDs []string
for _, web := range item.Websites {
webItem, err := websiteRepo.GetFirst(websiteRepo.WithAlias(web))
if err != nil {
hasNotFound = true
continue
}
webIDs = append(webIDs, fmt.Sprintf("%v", webItem.ID))
}
cronjob.Website = strings.Join(webIDs, ",")
case "database":
if len(item.DBNames) == 0 {
cronjob.DBName = "all"
break
}
var dbIDs []string
if strings.Contains(cronjob.DBType, "postgresql") {
for _, db := range item.DBNames {
dbItem, err := postgresqlRepo.Get(postgresqlRepo.WithByPostgresqlName(db.Name), repo.WithByName(db.DetailName))
if err != nil {
hasNotFound = true
continue
}
dbIDs = append(dbIDs, fmt.Sprintf("%v", dbItem.ID))
}
} else {
for _, db := range item.DBNames {
dbItem, err := mysqlRepo.Get(mysqlRepo.WithByMysqlName(db.Name), repo.WithByName(db.DetailName))
if err != nil {
hasNotFound = true
continue
}
dbIDs = append(dbIDs, fmt.Sprintf("%v", dbItem.ID))
}
}
cronjob.DBName = strings.Join(dbIDs, ",")
case "shell":
if len(item.ContainerName) != 0 {
cronjob.Script = item.Script
client, err := docker.NewDockerClient()
if err != nil {
hasNotFound = true
break
}
defer client.Close()
if _, err := client.ContainerStats(context.Background(), item.ContainerName, false); err != nil {
hasNotFound = true
break
}
}
switch item.ScriptMode {
case "library":
library, _ := scriptRepo.Get(repo.WithByName(item.ScriptName))
if library.ID == 0 {
hasNotFound = true
break
}
cronjob.ScriptID = library.ID
case "select":
if _, err := os.Stat(item.Script); err != nil {
hasNotFound = true
break
}
cronjob.Script = item.Script
case "input":
cronjob.Script = item.Script
}
case "directory":
if item.IsDir {
if _, err := os.Stat(item.SourceDir); err != nil {
hasNotFound = true
break
}
cronjob.SourceDir = item.SourceDir
} else {
fileList := strings.Split(item.SourceDir, ",")
var newFiles []string
for _, item := range fileList {
if len(item) == 0 {
continue
}
if _, err := os.Stat(item); err != nil {
hasNotFound = true
continue
}
newFiles = append(newFiles, item)
}
cronjob.SourceDir = strings.Join(newFiles, ",")
}
case "snapshot":
if len(item.SnapshotRule.IgnoreApps) == 0 && !item.SnapshotRule.WithImage {
break
}
var itemRules dto.SnapshotRule
itemRules.WithImage = item.SnapshotRule.WithImage
for _, app := range item.SnapshotRule.IgnoreApps {
appItem, err := appInstallRepo.LoadInstallAppByKeyAndName(app.Name, app.DetailName)
if err != nil {
hasNotFound = true
continue
}
itemRules.IgnoreAppIDs = append(itemRules.IgnoreAppIDs, appItem.ID)
}
itemRulesStr, _ := json.Marshal(itemRules)
cronjob.SnapshotRule = string(itemRulesStr)
}
var acIDs []string
for _, ac := range item.SourceAccounts {
backup, err := backupRepo.Get(repo.WithByName(ac))
if err != nil {
hasNotFound = true
continue
}
if ac == item.DownloadAccount {
cronjob.DownloadAccountID = backup.ID
}
acIDs = append(acIDs, fmt.Sprintf("%v", backup.ID))
}
cronjob.SourceAccountIDs = strings.Join(acIDs, ",")
if hasNotFound {
cronjob.Status = constant.StatusPending
} else {
cronjob.Status = constant.StatusDisable
}
_ = cronjobRepo.Create(&cronjob)
if item.AlertCount != 0 {
createAlert := dto.AlertCreate{
Title: item.AlertTitle,
SendCount: item.AlertCount,
Method: item.AlertMethod,
Type: cronjob.Type,
Project: strconv.Itoa(int(cronjob.ID)),
Status: constant.AlertEnable,
}
_ = NewIAlertService().CreateAlert(createAlert)
}
}
return nil
}
func (u *CronjobService) LoadScriptOptions() []dto.ScriptOptions {
scripts, err := scriptRepo.List()
if err != nil {
@@ -411,7 +706,11 @@ func (u *CronjobService) Update(id uint, req dto.CronjobOperate) error {
}
}
if cronModel.Status == constant.StatusPending {
upMap["status"] = constant.StatusEnable
}
upMap["name"] = req.Name
upMap["group_id"] = req.GroupID
upMap["spec_custom"] = req.SpecCustom
upMap["spec"] = spec
upMap["script"] = req.Script
@@ -428,6 +727,7 @@ func (u *CronjobService) Update(id uint, req dto.CronjobOperate) error {
upMap["db_type"] = req.DBType
upMap["db_name"] = req.DBName
upMap["url"] = req.URL
upMap["is_dir"] = req.IsDir
upMap["source_dir"] = req.SourceDir
upMap["snapshot_rule"] = cronjob.SnapshotRule
@@ -482,6 +782,14 @@ func (u *CronjobService) UpdateStatus(id uint, status string) error {
return cronjobRepo.Update(cronjob.ID, map[string]interface{}{"status": status, "entry_ids": entryIDs})
}
func (u *CronjobService) UpdateGroup(req dto.ChangeGroup) error {
cronjob, _ := cronjobRepo.Get(repo.WithByID(req.ID))
if cronjob.ID == 0 {
return buserr.New("ErrRecordNotFound")
}
return cronjobRepo.Update(cronjob.ID, map[string]interface{}{"group_id": req.GroupID})
}
func (u *CronjobService) AddCronJob(cronjob *model.Cronjob) (int, error) {
addFunc := func() {
u.HandleJob(cronjob)
+34 -17
View File
@@ -6,6 +6,7 @@ import (
"fmt"
"os"
"path"
"path/filepath"
"strconv"
"strings"
"time"
@@ -24,22 +25,7 @@ import (
)
func (u *CronjobService) handleApp(cronjob model.Cronjob, startTime time.Time, taskItem *task.Task) error {
var apps []model.AppInstall
if cronjob.AppID == "all" {
apps, _ = appInstallRepo.ListBy(context.Background())
} else {
appIds := strings.Split(cronjob.AppID, ",")
var idItems []uint
for i := 0; i < len(appIds); i++ {
itemID, _ := strconv.Atoi(appIds[i])
idItems = append(idItems, uint(itemID))
}
appItems, err := appInstallRepo.ListBy(context.Background(), repo.WithByIDs(idItems))
if err != nil {
return err
}
apps = appItems
}
apps := loadAppsForJob(cronjob)
if len(apps) == 0 {
return errors.New("no such app in database!")
}
@@ -214,7 +200,10 @@ func (u *CronjobService) handleDirectory(cronjob model.Cronjob, startTime time.T
if err != nil {
return err
}
fileName := fmt.Sprintf("%s.tar.gz", startTime.Format(constant.DateTimeSlimLayout)+common.RandStrAndNum(5))
fileName := fmt.Sprintf("%s.tar.gz", startTime.Format(constant.DateTimeSlimLayout)+common.RandStrAndNum(2))
if cronjob.IsDir || len(strings.Split(cronjob.SourceDir, ",")) == 1 {
fileName = loadFileName(cronjob.SourceDir)
}
backupDir := path.Join(global.Dir.LocalBackupDir, fmt.Sprintf("tmp/%s/%s", cronjob.Type, cronjob.Name))
fileOp := files.NewFileOp()
@@ -344,6 +333,23 @@ func (u *CronjobService) handleSnapshot(cronjob model.Cronjob, jobRecord model.J
return nil
}
func loadAppsForJob(cronjob model.Cronjob) []model.AppInstall {
var apps []model.AppInstall
if cronjob.AppID == "all" {
apps, _ = appInstallRepo.ListBy(context.Background())
} else {
appIds := strings.Split(cronjob.AppID, ",")
var idItems []uint
for i := 0; i < len(appIds); i++ {
itemID, _ := strconv.Atoi(appIds[i])
idItems = append(idItems, uint(itemID))
}
appItems, _ := appInstallRepo.ListBy(context.Background(), repo.WithByIDs(idItems))
apps = appItems
}
return apps
}
type DatabaseHelper struct {
ID uint
DBType string
@@ -546,3 +552,14 @@ func loadSnapWithRule(cronjob model.Cronjob) (dto.SnapshotData, error) {
}
return itemData, nil
}
func loadFileName(src string) string {
dirs := strings.Split(filepath.ToSlash(src), "/")
var keyPart string
if len(dirs) >= 3 {
keyPart = filepath.Join(dirs[len(dirs)-3], dirs[len(dirs)-2], dirs[len(dirs)-1])
}
cleanName := strings.ReplaceAll(keyPart, string(filepath.Separator), "_")
timestamp := time.Now().Format(constant.DateTimeSlimLayout)
return fmt.Sprintf("%s_%s_%s.tar.gz", cleanName, timestamp, common.RandStrAndNum(2))
}
+20 -12
View File
@@ -6,19 +6,17 @@ import (
"os"
"path"
"github.com/1Panel-dev/1Panel/agent/app/repo"
"github.com/1Panel-dev/1Panel/agent/utils/postgresql"
pgclient "github.com/1Panel-dev/1Panel/agent/utils/postgresql/client"
redisclient "github.com/1Panel-dev/1Panel/agent/utils/redis"
"github.com/1Panel-dev/1Panel/agent/app/dto"
"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/encrypt"
"github.com/1Panel-dev/1Panel/agent/utils/mysql"
"github.com/1Panel-dev/1Panel/agent/utils/mysql/client"
"github.com/1Panel-dev/1Panel/agent/utils/postgresql"
pgclient "github.com/1Panel-dev/1Panel/agent/utils/postgresql/client"
redisclient "github.com/1Panel-dev/1Panel/agent/utils/redis"
"github.com/jinzhu/copier"
"github.com/pkg/errors"
)
@@ -118,6 +116,9 @@ func (u *DatabaseService) LoadItems(dbType string) ([]dto.DatabaseItem, error) {
}
func (u *DatabaseService) CheckDatabase(req dto.DatabaseCreate) bool {
if req.Timeout == 0 {
req.Timeout = 30
}
switch req.Type {
case constant.AppPostgresql:
_, err := postgresql.NewPostgresqlClient(pgclient.DBInfo{
@@ -126,7 +127,7 @@ func (u *DatabaseService) CheckDatabase(req dto.DatabaseCreate) bool {
Port: req.Port,
Username: req.Username,
Password: req.Password,
Timeout: 6,
Timeout: req.Timeout,
})
return err == nil
case constant.AppRedis:
@@ -134,6 +135,7 @@ func (u *DatabaseService) CheckDatabase(req dto.DatabaseCreate) bool {
Address: req.Address,
Port: req.Port,
Password: req.Password,
Timeout: req.Timeout,
})
return err == nil
case "mysql", "mariadb":
@@ -149,7 +151,7 @@ func (u *DatabaseService) CheckDatabase(req dto.DatabaseCreate) bool {
ClientKey: req.ClientKey,
ClientCert: req.ClientCert,
SkipVerify: req.SkipVerify,
Timeout: 6,
Timeout: req.Timeout,
})
return err == nil
}
@@ -158,6 +160,9 @@ func (u *DatabaseService) CheckDatabase(req dto.DatabaseCreate) bool {
}
func (u *DatabaseService) Create(req dto.DatabaseCreate) error {
if req.Timeout == 0 {
req.Timeout = 30
}
db, _ := databaseRepo.Get(repo.WithByName(req.Name))
if db.ID != 0 {
if db.From == "local" {
@@ -173,7 +178,7 @@ func (u *DatabaseService) Create(req dto.DatabaseCreate) error {
Port: req.Port,
Username: req.Username,
Password: req.Password,
Timeout: 6,
Timeout: req.Timeout,
}); err != nil {
return err
}
@@ -182,6 +187,7 @@ func (u *DatabaseService) Create(req dto.DatabaseCreate) error {
Address: req.Address,
Port: req.Port,
Password: req.Password,
Timeout: req.Timeout,
}); err != nil {
return err
}
@@ -198,7 +204,7 @@ func (u *DatabaseService) Create(req dto.DatabaseCreate) error {
ClientKey: req.ClientKey,
ClientCert: req.ClientCert,
SkipVerify: req.SkipVerify,
Timeout: 6,
Timeout: req.Timeout,
}); err != nil {
return err
}
@@ -273,7 +279,7 @@ func (u *DatabaseService) Update(req dto.DatabaseUpdate) error {
Port: req.Port,
Username: req.Username,
Password: req.Password,
Timeout: 300,
Timeout: req.Timeout,
}); err != nil {
return err
}
@@ -282,6 +288,7 @@ func (u *DatabaseService) Update(req dto.DatabaseUpdate) error {
Address: req.Address,
Port: req.Port,
Password: req.Password,
Timeout: req.Timeout,
}); err != nil {
return err
}
@@ -298,7 +305,7 @@ func (u *DatabaseService) Update(req dto.DatabaseUpdate) error {
ClientKey: req.ClientKey,
ClientCert: req.ClientCert,
SkipVerify: req.SkipVerify,
Timeout: 300,
Timeout: req.Timeout,
}); err != nil {
return err
}
@@ -318,6 +325,7 @@ func (u *DatabaseService) Update(req dto.DatabaseUpdate) error {
upMap["port"] = req.Port
upMap["username"] = req.Username
upMap["password"] = pass
upMap["timeout"] = req.Timeout
upMap["description"] = req.Description
upMap["ssl"] = req.SSL
upMap["client_key"] = req.ClientKey
+3 -3
View File
@@ -74,11 +74,11 @@ func (u *DBCommonService) UpdateConfByFile(req dto.DBConfUpdateByFile) error {
}
path := ""
switch req.Type {
case constant.AppMariaDB, constant.AppMysql:
case constant.AppMariaDB, constant.AppMysql, constant.AppMysqlCluster:
path = fmt.Sprintf("%s/%s/%s/conf/my.cnf", global.Dir.AppInstallDir, req.Type, app.Name)
case constant.AppPostgresql:
case constant.AppPostgresql, constant.AppPostgresqlCluster:
path = fmt.Sprintf("%s/%s/%s/data/postgresql.conf", global.Dir.AppInstallDir, req.Type, app.Name)
case constant.AppRedis:
case constant.AppRedis, constant.AppRedisCluster:
path = fmt.Sprintf("%s/%s/%s/conf/redis.conf", global.Dir.AppInstallDir, req.Type, app.Name)
}
file, err := os.OpenFile(path, os.O_WRONLY|os.O_TRUNC, 0640)
+5 -1
View File
@@ -53,7 +53,7 @@ func (u *RedisService) UpdateConf(req dto.RedisConfUpdate) error {
if err := confSet(redisInfo.Name, req.DBType, "", confs); err != nil {
return err
}
if _, err := compose.Restart(fmt.Sprintf("%s/redis/%s/docker-compose.yml", global.Dir.AppInstallDir, redisInfo.Name)); err != nil {
if _, err := compose.Restart(fmt.Sprintf("%s/%s/%s/docker-compose.yml", global.Dir.AppInstallDir, req.DBType, redisInfo.Name)); err != nil {
return err
}
@@ -244,6 +244,10 @@ func confSet(redisName string, redisType string, updateType string, changeConf [
}
newFiles = append(newFiles, files[i])
}
if startIndex == 0 {
newFiles = append(newFiles, "# Redis configuration rewrite by 1Panel")
startIndex = len(newFiles) - 1
}
endIndex = endIndex - emptyLine
for _, item := range changeConf {
if item.key == "save" {
+66 -24
View File
@@ -4,10 +4,6 @@ import (
"bufio"
"context"
"fmt"
"github.com/1Panel-dev/1Panel/agent/app/dto"
"github.com/jinzhu/copier"
"golang.org/x/text/encoding"
"golang.org/x/text/encoding/simplifiedchinese"
"io"
"io/fs"
"os"
@@ -20,6 +16,11 @@ import (
"time"
"unicode/utf8"
"github.com/1Panel-dev/1Panel/agent/app/dto"
"github.com/jinzhu/copier"
"golang.org/x/text/encoding"
"golang.org/x/text/encoding/simplifiedchinese"
"github.com/1Panel-dev/1Panel/agent/app/repo"
"github.com/1Panel-dev/1Panel/agent/app/dto/request"
@@ -97,19 +98,25 @@ func (f *FileService) SearchUploadWithPage(req request.SearchUploadWithPage) (in
files []response.UploadInfo
backData []response.UploadInfo
)
_ = filepath.Walk(req.Path, func(path string, info os.FileInfo, err error) error {
fileList, err := os.ReadDir(req.Path)
if err != nil {
return 0, files, nil
}
for _, item := range fileList {
if item.IsDir() {
continue
}
fileItem, err := item.Info()
if err != nil {
return nil
continue
}
if !info.IsDir() {
files = append(files, response.UploadInfo{
CreatedAt: info.ModTime().Format(constant.DateTimeLayout),
Size: int(info.Size()),
Name: info.Name(),
})
}
return nil
})
files = append(files, response.UploadInfo{
CreatedAt: fileItem.ModTime().Format(constant.DateTimeLayout),
Size: int(fileItem.Size()),
Name: item.Name(),
})
}
total, start, end := len(files), (req.Page-1)*req.PageSize, req.Page*req.PageSize
if start > total {
backData = make([]response.UploadInfo, 0)
@@ -416,6 +423,14 @@ func (f *FileService) MvFile(m request.FileMove) error {
global.LOG.Errorf("copy file [%s] to [%s] failed, err: %s", src, m.NewPath, err.Error())
}
}
if len(m.CoverPaths) > 0 {
for _, src := range m.CoverPaths {
if err := fo.CopyAndReName(src, m.NewPath, "", true); err != nil {
errs = append(errs, err)
global.LOG.Errorf("copy file [%s] to [%s] failed, err: %s", src, m.NewPath, err.Error())
}
}
}
}
var errString string
@@ -478,6 +493,7 @@ func (f *FileService) DepthDirSize(req request.DirSizeReq) ([]response.DepthDirS
func (f *FileService) ReadLogByLine(req request.FileReadByLineReq) (*response.FileLineContent, error) {
logFilePath := ""
taskStatus := ""
switch req.Type {
case constant.TypeWebsite:
website, err := websiteRepo.GetFirst(repo.WithByID(req.ID))
@@ -533,29 +549,55 @@ func (f *FileService) ReadLogByLine(req request.FileReadByLineReq) (*response.Fi
return nil, err
}
logFilePath = taskModel.LogFile
taskStatus = taskModel.Status
case "mysql-slow-logs":
logFilePath = path.Join(global.Dir.DataDir, fmt.Sprintf("apps/mysql/%s/data/1Panel-slow.log", req.Name))
case "mariadb-slow-logs":
logFilePath = path.Join(global.Dir.DataDir, fmt.Sprintf("apps/mariadb/%s/db/data/1Panel-slow.log", req.Name))
}
lines, isEndOfFile, total, err := files.ReadFileByLine(logFilePath, req.Page, req.PageSize, req.Latest)
file, err := os.Open(logFilePath)
if err != nil {
return nil, err
}
if req.Latest && req.Page == 1 && len(lines) < 1000 && total > 1 {
preLines, _, _, err := files.ReadFileByLine(logFilePath, total-1, req.PageSize, false)
defer file.Close()
stat, err := file.Stat()
if err != nil {
return nil, err
}
var (
lines []string
isEndOfFile bool
total int
scope string
)
if stat.Size() > 500*1024*1024 {
lines, err = files.TailFromEnd(logFilePath, req.PageSize)
isEndOfFile = true
scope = "tail"
} else {
lines, isEndOfFile, total, err = files.ReadFileByLine(logFilePath, req.Page, req.PageSize, req.Latest)
if err != nil {
return nil, err
}
lines = append(preLines, lines...)
if req.Latest && req.Page == 1 && len(lines) < 1000 && total > 1 {
preLines, _, _, err := files.ReadFileByLine(logFilePath, total-1, req.PageSize, false)
if err != nil {
return nil, err
}
lines = append(preLines, lines...)
}
scope = "page"
}
res := &response.FileLineContent{
Content: strings.Join(lines, "\n"),
End: isEndOfFile,
Path: logFilePath,
Total: total,
Lines: lines,
End: isEndOfFile,
Path: logFilePath,
Total: total,
TaskStatus: taskStatus,
Lines: lines,
Scope: scope,
}
return res, nil
}
+7 -1
View File
@@ -323,6 +323,7 @@ func handleProcess(supervisordDir string, req request.SupervisorProcessConfig, c
_, _ = section.NewKey("command", strings.TrimSpace(req.Command))
_, _ = section.NewKey("directory", req.Dir)
_, _ = section.NewKey("autorestart", req.AutoRestart)
_, _ = section.NewKey("autostart", req.AutoStart)
_, _ = section.NewKey("startsecs", "3")
_, _ = section.NewKey("stdout_logfile", outLog)
_, _ = section.NewKey("stderr_logfile", errLog)
@@ -360,6 +361,8 @@ func handleProcess(supervisordDir string, req request.SupervisorProcessConfig, c
numprocsKey.SetValue(req.Numprocs)
autoRestart := section.Key("autorestart")
autoRestart.SetValue(req.AutoRestart)
autoStart := section.Key("autostart")
autoStart.SetValue(req.AutoStart)
if err = configFile.SaveTo(iniPath); err != nil {
return err
@@ -429,6 +432,9 @@ func handleProcessConfig(configDir, containerName string) ([]response.Supervisor
if autoRestart, _ := section.GetKey("autorestart"); autoRestart != nil {
config.AutoRestart = autoRestart.Value()
}
if autoStart, _ := section.GetKey("autostart"); autoStart != nil {
config.AutoStart = autoStart.Value()
}
_ = getProcessStatus(&config, containerName)
result = append(result, config)
}
@@ -609,7 +615,7 @@ func getProcessStatus(config *response.SupervisorProcessConfig, containerName st
for _, line := range lines {
line = strings.TrimPrefix(line, "stdout:")
fields := strings.Fields(line)
if len(fields) >= 5 {
if len(fields) >= 4 {
status := response.ProcessStatus{
Name: fields[0],
Status: fields[1],
+35 -24
View File
@@ -21,6 +21,7 @@ import (
"github.com/1Panel-dev/1Panel/agent/constant"
"github.com/1Panel-dev/1Panel/agent/global"
"github.com/1Panel-dev/1Panel/agent/i18n"
"github.com/1Panel-dev/1Panel/agent/utils/common"
"github.com/1Panel-dev/1Panel/agent/utils/docker"
"github.com/docker/docker/api/types/build"
"github.com/docker/docker/api/types/container"
@@ -41,7 +42,7 @@ type IImageService interface {
ImageLoad(req dto.ImageLoad) error
ImageSave(req dto.ImageSave) error
ImagePush(req dto.ImagePush) error
ImageRemove(req dto.BatchDelete) (dto.ContainerPruneReport, error)
ImageRemove(req dto.BatchDelete) error
ImageTag(req dto.ImageTag) error
}
@@ -386,34 +387,44 @@ func (u *ImageService) ImagePush(req dto.ImagePush) error {
return nil
}
func (u *ImageService) ImageRemove(req dto.BatchDelete) (dto.ContainerPruneReport, error) {
report := dto.ContainerPruneReport{}
func (u *ImageService) ImageRemove(req dto.BatchDelete) error {
client, err := docker.NewDockerClient()
if err != nil {
return report, err
return err
}
defer client.Close()
for _, id := range req.Names {
imageItem, _, err := client.ImageInspectWithRaw(context.TODO(), id)
if err != nil {
return report, err
}
if _, err := client.ImageRemove(context.TODO(), id, image.RemoveOptions{Force: req.Force, PruneChildren: true}); err != nil {
if strings.Contains(err.Error(), "image is being used") || strings.Contains(err.Error(), "is using") {
if strings.Contains(id, "sha256:") {
return report, buserr.New("ErrObjectInUsed")
}
return report, buserr.WithDetail("ErrInUsed", id, nil)
}
if strings.Contains(err.Error(), "image has dependent") {
return report, buserr.New("ErrObjectBeDependent")
}
return report, err
}
report.DeletedNumber++
report.SpaceReclaimed += int(imageItem.Size)
taskItem, err := task.NewTaskWithOps(task.TaskScopeImage, task.TaskDelete, task.TaskScopeContainer, req.TaskID, 1)
if err != nil {
global.LOG.Errorf("new task for create container failed, err: %v", err)
return err
}
return report, nil
for _, id := range req.Names {
taskItem.AddSubTask(i18n.GetMsgByKey("TaskDelete")+id, func(t *task.Task) error {
imageItem, err := client.ImageInspect(context.TODO(), id)
if err != nil {
return err
}
if _, err := client.ImageRemove(context.TODO(), id, image.RemoveOptions{Force: req.Force, PruneChildren: true}); err != nil {
if strings.Contains(err.Error(), "image is being used") || strings.Contains(err.Error(), "is using") {
if strings.Contains(id, "sha256:") {
return buserr.New("ErrObjectInUsed")
}
return buserr.WithDetail("ErrInUsed", id, nil)
}
if strings.Contains(err.Error(), "image has dependent") {
return buserr.New("ErrObjectBeDependent")
}
return err
}
taskItem.Log(i18n.GetMsgWithMap("ImageRemoveHelper", map[string]interface{}{"name": id, "size": common.LoadSizeUnit2F(float64(imageItem.Size))}))
return nil
}, nil)
}
go func() {
_ = taskItem.Execute()
}()
return nil
}
func formatFileSize(fileSize int64) (size string) {
+25 -9
View File
@@ -86,6 +86,11 @@ 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())
}
}()
mcpServer, err := mcpServerRepo.GetFirst(repo.WithByID(req.ID))
if err != nil {
return err
@@ -108,6 +113,8 @@ func (m McpServerService) Update(req request.McpServerUpdate) error {
mcpServer.BaseURL = req.BaseURL
mcpServer.SsePath = req.SsePath
mcpServer.HostIP = req.HostIP
mcpServer.StreamableHttpPath = req.StreamableHttpPath
mcpServer.OutputTransport = req.OutputTransport
if err := handleCreateParams(mcpServer, req.Environments, req.Volumes); err != nil {
return err
}
@@ -130,6 +137,11 @@ 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())
}
}()
servers, _ := mcpServerRepo.List()
for _, server := range servers {
if server.Port == create.Port {
@@ -154,15 +166,17 @@ 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,
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,
}
if err := handleCreateParams(mcpServer, create.Environments, create.Volumes); err != nil {
return err
@@ -524,6 +538,8 @@ func handleEnv(mcpServer *model.McpServer) gotenv.Env {
env["BASE_URL"] = mcpServer.BaseURL
env["SSE_PATH"] = mcpServer.SsePath
env["HOST_IP"] = mcpServer.HostIP
env["STREAMABLE_HTTP_PATH"] = mcpServer.StreamableHttpPath
env["OUTPUT_TRANSPORT"] = mcpServer.OutputTransport
envStr, _ := gotenv.Marshal(env)
mcpServer.Env = envStr
return env
+72
View File
@@ -4,6 +4,8 @@ import (
"bufio"
"encoding/json"
"fmt"
"github.com/1Panel-dev/1Panel/agent/utils/nginx"
"github.com/1Panel-dev/1Panel/agent/utils/nginx/parser"
"io"
"net/http"
"os"
@@ -42,6 +44,9 @@ type INginxService interface {
Build(req request.NginxBuildReq) error
GetModules() (*response.NginxBuildConfig, error)
UpdateModule(req request.NginxModuleUpdate) error
OperateDefaultHTTPs(req request.NginxOperateReq) error
GetDefaultHttpsStatus() (*response.NginxConfigRes, error)
}
func NewINginxService() INginxService {
@@ -348,3 +353,70 @@ func (n NginxService) UpdateModule(req request.NginxModuleUpdate) error {
}
return fileOp.SaveFileWithByte(moduleConfigPath, moduleByte, constant.DirPerm)
}
func (n NginxService) OperateDefaultHTTPs(req request.NginxOperateReq) error {
appInstall, err := getAppInstallByKey(constant.AppOpenresty)
if err != nil {
return err
}
websites, _ := websiteRepo.List()
hasDefaultWebsite := false
for _, website := range websites {
if website.DefaultServer {
hasDefaultWebsite = true
break
}
}
defaultConfigPath := path.Join(appInstall.GetPath(), "conf", "default", "00.default.conf")
content, err := os.ReadFile(defaultConfigPath)
if err != nil {
return err
}
if req.Operate == "enable" {
if err := handleSSLConfig(&appInstall, hasDefaultWebsite); err != nil {
return err
}
} else if req.Operate == "disable" {
defaultConfig, err := parser.NewStringParser(string(content)).Parse()
if err != nil {
return err
}
defaultConfig.FilePath = defaultConfigPath
defaultServer := defaultConfig.FindServers()[0]
defaultServer.RemoveListen(fmt.Sprintf("%d", appInstall.HttpsPort))
defaultServer.RemoveListen(fmt.Sprintf("[::]:%d", appInstall.HttpsPort))
defaultServer.RemoveDirective("include", []string{"/usr/local/openresty/nginx/conf/ssl/root_ssl.conf"})
defaultServer.RemoveDirective("http2", []string{"on"})
if err = nginx.WriteConfig(defaultConfig, nginx.IndentedStyle); err != nil {
return err
}
}
return nginxCheckAndReload(string(content), defaultConfigPath, appInstall.ContainerName)
}
func (n NginxService) GetDefaultHttpsStatus() (*response.NginxConfigRes, error) {
appInstall, err := getAppInstallByKey(constant.AppOpenresty)
if err != nil {
return nil, err
}
defaultConfigPath := path.Join(appInstall.GetPath(), "conf", "default", "00.default.conf")
content, err := os.ReadFile(defaultConfigPath)
if err != nil {
return nil, err
}
defaultConfig, err := parser.NewStringParser(string(content)).Parse()
if err != nil {
return nil, err
}
defaultConfig.FilePath = defaultConfigPath
defaultServer := defaultConfig.FindServers()[0]
res := &response.NginxConfigRes{}
for _, directive := range defaultServer.GetDirectives() {
if directive.GetName() == "include" && directive.GetParameters()[0] == "/usr/local/openresty/nginx/conf/ssl/root_ssl.conf" {
return &response.NginxConfigRes{
Https: true,
}, nil
}
}
return res, nil
}
+1
View File
@@ -450,6 +450,7 @@ func (r *RuntimeService) Update(req request.RuntimeUpdate) error {
if err := checkContainerName(containerName.(string)); err != nil {
return err
}
runtime.ContainerName = containerName.(string)
}
projectDir := path.Join(global.Dir.RuntimeDir, runtime.Type, runtime.Name)
+184 -52
View File
@@ -1,24 +1,30 @@
package service
import (
"bytes"
"encoding/base64"
"fmt"
"os"
"os/user"
"path"
"sort"
"strconv"
"strings"
"time"
"github.com/1Panel-dev/1Panel/agent/utils/copier"
"github.com/1Panel-dev/1Panel/agent/utils/encrypt"
"github.com/1Panel-dev/1Panel/agent/utils/geo"
"github.com/gin-gonic/gin"
"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/cmd"
"github.com/1Panel-dev/1Panel/agent/utils/common"
"github.com/1Panel-dev/1Panel/agent/utils/files"
"github.com/1Panel-dev/1Panel/agent/utils/systemctl"
"github.com/pkg/errors"
)
@@ -32,11 +38,13 @@ type ISSHService interface {
OperateSSH(operation string) error
UpdateByFile(value string) error
Update(req dto.SSHUpdate) error
GenerateSSH(req dto.GenerateSSH) error
LoadSSHSecret(mode string) (string, error)
LoadLog(ctx *gin.Context, req dto.SearchSSHLog) (*dto.SSHLog, error)
LoadSSHConf() (string, error)
SyncRootCert() error
CreateRootCert(req dto.CreateRootCert) error
SearchRootCerts(req dto.SearchWithPage) (int64, interface{}, error)
DeleteRootCerts(req dto.ForceDelete) error
}
func NewISSHService() ISSHService {
@@ -110,6 +118,14 @@ func (u *SSHService) GetSSHInfo() (*dto.SSHInfo, error) {
data.UseDNS = strings.ReplaceAll(line, "UseDNS ", "")
}
}
currentUser, err := user.Current()
if err != nil || len(currentUser.Name) == 0 {
data.CurrentUser = "root"
} else {
data.CurrentUser = currentUser.Name
}
return &data, nil
}
@@ -214,70 +230,163 @@ func (u *SSHService) UpdateByFile(value string) error {
return nil
}
func (u *SSHService) GenerateSSH(req dto.GenerateSSH) error {
if cmd.CheckIllegal(req.EncryptionMode, req.Password) {
func (u *SSHService) SyncRootCert() error {
currentUser, err := user.Current()
if err != nil {
return fmt.Errorf("load current user failed, err: %v", err)
}
sshDir := fmt.Sprintf("%s/.ssh", currentUser.HomeDir)
fileList, err := os.ReadDir(sshDir)
if err != nil {
return err
}
var rootCerts []model.RootCert
fileMap := make(map[string]bool)
for _, item := range fileList {
if !item.IsDir() {
fileMap[item.Name()] = true
}
}
for item := range fileMap {
if !strings.HasSuffix(item, ".pub") {
continue
}
if !fileMap[strings.TrimSuffix(item, ".pub")] {
continue
}
cert := model.RootCert{Name: strings.TrimSuffix(item, ".pub"), PublicKeyPath: path.Join(sshDir, item), PrivateKeyPath: path.Join(sshDir, strings.TrimSuffix(item, ".pub"))}
pubItem, err := os.ReadFile(path.Join(sshDir, item))
if err != nil {
global.LOG.Errorf("read pubic key of %s for sync failed, err: %v", item, err)
continue
}
cert.EncryptionMode = loadEncryptioMode(string(pubItem))
rootCerts = append(rootCerts, cert)
}
return hostRepo.SyncCert(rootCerts)
}
func (u *SSHService) CreateRootCert(req dto.CreateRootCert) error {
if cmd.CheckIllegal(req.EncryptionMode, req.PassPhrase) {
return buserr.New("ErrCmdIllegal")
}
certItem, _ := hostRepo.GetCert(repo.WithByName(req.Name))
if certItem.ID != 0 {
return buserr.New("ErrRecordExist")
}
currentUser, err := user.Current()
if err != nil {
return fmt.Errorf("load current user failed, err: %v", err)
}
secretFile := fmt.Sprintf("%s/.ssh/id_item_%s", currentUser.HomeDir, req.EncryptionMode)
secretPubFile := fmt.Sprintf("%s/.ssh/id_item_%s.pub", currentUser.HomeDir, req.EncryptionMode)
var cert model.RootCert
if err := copier.Copy(&cert, req); err != nil {
return err
}
privatePath := fmt.Sprintf("%s/.ssh/%s", currentUser.HomeDir, req.Name)
publicPath := fmt.Sprintf("%s/.ssh/%s.pub", currentUser.HomeDir, req.Name)
authFilePath := currentUser.HomeDir + "/.ssh/authorized_keys"
command := fmt.Sprintf("ssh-keygen -t %s -f %s/.ssh/id_item_%s | echo y", req.EncryptionMode, currentUser.HomeDir, req.EncryptionMode)
if len(req.Password) != 0 {
command = fmt.Sprintf("ssh-keygen -t %s -P %s -f %s/.ssh/id_item_%s | echo y", req.EncryptionMode, req.Password, currentUser.HomeDir, req.EncryptionMode)
if req.Mode == "input" || req.Mode == "import" {
if err := os.WriteFile(privatePath, []byte(req.PrivateKey), constant.FilePerm); err != nil {
return err
}
if err := os.WriteFile(publicPath, []byte(req.PublicKey), constant.FilePerm); err != nil {
return err
}
} else {
command := fmt.Sprintf("ssh-keygen -t %s -f %s/.ssh/%s -N ''", req.EncryptionMode, currentUser.HomeDir, req.Name)
if len(req.PassPhrase) != 0 {
command = fmt.Sprintf("ssh-keygen -t %s -P %s -f %s/.ssh/%s | echo y", req.EncryptionMode, req.PassPhrase, currentUser.HomeDir, req.Name)
}
stdout, err := cmd.RunDefaultWithStdoutBashC(command)
if err != nil {
return fmt.Errorf("generate failed, err: %v, message: %s", err, stdout)
}
}
stdout, err := cmd.RunDefaultWithStdoutBashC(command)
stdout, err := cmd.RunDefaultWithStdoutBashCf("cat %s >> %s", publicPath, authFilePath)
if err != nil {
return fmt.Errorf("generate failed, err: %v, message: %s", err, stdout)
}
defer func() {
_ = os.Remove(secretFile)
}()
defer func() {
_ = os.Remove(secretPubFile)
}()
if _, err := os.Stat(authFilePath); err != nil && errors.Is(err, os.ErrNotExist) {
authFile, err := os.Create(authFilePath)
if err != nil {
cert.PrivateKeyPath = privatePath
cert.PublicKeyPath = publicPath
if len(cert.PassPhrase) != 0 {
cert.PassPhrase, _ = encrypt.StringEncrypt(cert.PassPhrase)
}
return hostRepo.CreateCert(&cert)
}
func (u *SSHService) SearchRootCerts(req dto.SearchWithPage) (int64, interface{}, error) {
total, records, err := hostRepo.PageCert(req.Page, req.PageSize)
if err != nil {
return 0, nil, err
}
var datas []dto.RootCert
for i := 0; i < len(records); i++ {
publicItem, err := os.ReadFile(records[i].PublicKeyPath)
var publicBase64 string
if err == nil && len(publicItem) != 0 {
publicBase64 = base64.StdEncoding.EncodeToString(publicItem)
}
privateItem, _ := os.ReadFile(records[i].PrivateKeyPath)
var privateBase64 string
if err == nil && len(publicItem) != 0 {
privateBase64 = base64.StdEncoding.EncodeToString(privateItem)
}
passPhrase, _ := encrypt.StringDecryptWithBase64(records[i].PassPhrase)
datas = append(datas, dto.RootCert{
ID: records[i].ID,
CreatedAt: records[i].CreatedAt,
Name: records[i].Name,
EncryptionMode: records[i].EncryptionMode,
PassPhrase: passPhrase,
PublicKey: publicBase64,
PrivateKey: privateBase64,
Description: records[i].Description,
})
}
return total, datas, err
}
func (u *SSHService) DeleteRootCerts(req dto.ForceDelete) error {
currentUser, err := user.Current()
if err != nil && !req.ForceDelete {
return fmt.Errorf("load current user failed, err: %v", err)
}
authFilePath := currentUser.HomeDir + "/.ssh/authorized_keys"
authItem, err := os.ReadFile(authFilePath)
if err != nil && !req.ForceDelete {
return err
}
for _, id := range req.IDs {
cert, _ := hostRepo.GetCert(repo.WithByID(id))
if cert.ID == 0 {
if !req.ForceDelete {
return buserr.New("ErrRecordNotFound")
} else {
continue
}
}
publicItem, err := os.ReadFile(cert.PublicKeyPath)
if err != nil && !req.ForceDelete {
return err
}
newFile := bytes.ReplaceAll(authItem, publicItem, nil)
if err := os.WriteFile(authFilePath, newFile, constant.FilePerm); err != nil && !req.ForceDelete {
return fmt.Errorf("refresh authorized_keys failed, err: %v", err)
}
_ = os.Remove(cert.PublicKeyPath)
_ = os.Remove(cert.PrivateKeyPath)
if err := hostRepo.DeleteCert(repo.WithByID(id)); err != nil && !req.ForceDelete {
return err
}
defer authFile.Close()
}
stdout1, err := cmd.RunDefaultWithStdoutBashCf("cat %s >> %s/.ssh/authorized_keys", secretPubFile, currentUser.HomeDir)
if err != nil {
return fmt.Errorf("generate failed, err: %v, message: %s", err, stdout1)
}
fileOp := files.NewFileOp()
if err := fileOp.Rename(secretFile, fmt.Sprintf("%s/.ssh/id_%s%s", currentUser.HomeDir, req.EncryptionMode, req.Name)); err != nil {
return err
}
if err := fileOp.Rename(secretPubFile, fmt.Sprintf("%s/.ssh/id_%s%s.pub", currentUser.HomeDir, req.EncryptionMode, req.Name)); err != nil {
return err
}
return nil
}
func (u *SSHService) LoadSSHSecret(mode string) (string, error) {
currentUser, err := user.Current()
if err != nil {
return "", fmt.Errorf("load current user failed, err: %v", err)
}
homeDir := currentUser.HomeDir
if _, err := os.Stat(fmt.Sprintf("%s/.ssh/id_%s", homeDir, mode)); err != nil {
return "", nil
}
file, err := os.ReadFile(fmt.Sprintf("%s/.ssh/id_%s", homeDir, mode))
return string(file), err
}
type sshFileItem struct {
Name string
Year int
@@ -433,7 +542,7 @@ func loadSSHData(ctx *gin.Context, command string, showCountFrom, showCountTo, c
switch {
case strings.Contains(lines[i], "Failed password for"):
itemData = loadFailedSecureDatas(lines[i])
if len(itemData.Address) != 0 {
if checkIsStandard(itemData) {
if successCount+failedCount >= showCountFrom && successCount+failedCount < showCountTo {
itemData.Area, _ = geo.GetIPLocation(getLoc, itemData.Address, common.GetLang(ctx))
itemData.Date = loadDate(currentYear, itemData.DateStr, nyc)
@@ -443,7 +552,7 @@ func loadSSHData(ctx *gin.Context, command string, showCountFrom, showCountTo, c
}
case strings.Contains(lines[i], "Connection closed by authenticating user"):
itemData = loadFailedAuthDatas(lines[i])
if len(itemData.Address) != 0 {
if checkIsStandard(itemData) {
if successCount+failedCount >= showCountFrom && successCount+failedCount < showCountTo {
itemData.Area, _ = geo.GetIPLocation(getLoc, itemData.Address, common.GetLang(ctx))
itemData.Date = loadDate(currentYear, itemData.DateStr, nyc)
@@ -453,7 +562,7 @@ func loadSSHData(ctx *gin.Context, command string, showCountFrom, showCountTo, c
}
case strings.Contains(lines[i], "Accepted "):
itemData = loadSuccessDatas(lines[i])
if len(itemData.Address) != 0 {
if checkIsStandard(itemData) {
if successCount+failedCount >= showCountFrom && successCount+failedCount < showCountTo {
itemData.Area, _ = geo.GetIPLocation(getLoc, itemData.Address, common.GetLang(ctx))
itemData.Date = loadDate(currentYear, itemData.DateStr, nyc)
@@ -481,7 +590,6 @@ func loadSuccessDatas(line string) dto.SSHHistory {
data.Status = constant.StatusSuccess
return data
}
func loadFailedAuthDatas(line string) dto.SSHHistory {
var data dto.SSHHistory
parts := strings.Fields(line)
@@ -531,6 +639,14 @@ func loadFailedSecureDatas(line string) dto.SSHHistory {
return data
}
func checkIsStandard(item dto.SSHHistory) bool {
if len(item.Address) == 0 {
return false
}
portItem, _ := strconv.Atoi(item.Port)
return portItem != 0
}
func handleGunzip(path string) error {
if _, err := cmd.RunDefaultWithStdoutBashCf("gunzip %s", path); err != nil {
return err
@@ -576,3 +692,19 @@ func analyzeDateStr(parts []string) (int, string) {
}
return 2, fmt.Sprintf("%s %s %s", parts[0], parts[1], parts[2])
}
func loadEncryptioMode(content string) string {
if strings.HasPrefix(content, "ssh-rsa") {
return "rsa"
}
if strings.HasPrefix(content, "ssh-ed25519") {
return "ed25519"
}
if strings.HasPrefix(content, "ssh-ecdsa") {
return "ecdsa"
}
if strings.HasPrefix(content, "ssh-dsa") {
return "dsa"
}
return ""
}
+14 -8
View File
@@ -257,7 +257,7 @@ func (w WebsiteService) CreateWebsite(create request.WebsiteCreate) (err error)
var (
domains []model.WebsiteDomain
)
domains, _, _, err = getWebsiteDomains(create.Domains, defaultHttpPort, 0)
domains, _, _, err = getWebsiteDomains(create.Domains, defaultHttpPort, nginxInstall.HttpsPort, 0)
if err != nil {
return err
}
@@ -388,7 +388,7 @@ func (w WebsiteService) CreateWebsite(create request.WebsiteCreate) (err error)
switch runtime.Type {
case constant.RuntimePHP:
if runtime.Resource == constant.ResourceAppstore {
if !checkImageExist(nil, runtime.Image) {
if !checkImageLike(nil, runtime.Image) {
return buserr.WithName("ErrImageNotExist", runtime.Name)
}
website.Proxy = fmt.Sprintf("127.0.0.1:%s", runtime.Port)
@@ -680,7 +680,7 @@ func (w WebsiteService) CreateWebsiteDomain(create request.WebsiteDomainCreate)
domainModels []model.WebsiteDomain
addPorts []int
)
httpPort, _, err := getAppInstallPort(constant.AppOpenresty)
httpPort, httpsPort, err := getAppInstallPort(constant.AppOpenresty)
if err != nil {
return nil, err
}
@@ -689,7 +689,7 @@ func (w WebsiteService) CreateWebsiteDomain(create request.WebsiteDomainCreate)
return nil, err
}
domainModels, addPorts, _, err = getWebsiteDomains(create.Domains, httpPort, create.WebsiteID)
domainModels, addPorts, _, err = getWebsiteDomains(create.Domains, httpPort, httpsPort, create.WebsiteID)
if err != nil {
return nil, err
}
@@ -1354,7 +1354,7 @@ func (w WebsiteService) ChangePHPVersion(req request.WebsitePHPVersionReq) error
return err
}
defer client.Close()
if !checkImageExist(client, oldRuntime.Image) {
if !checkImageLike(client, oldRuntime.Image) {
return buserr.WithName("ErrImageNotExist", oldRuntime.Name)
}
}
@@ -1787,9 +1787,15 @@ func (w WebsiteService) GetProxies(id uint) (res []request.WebsiteProxyConfig, e
}
directives := config.GetDirectives()
location, ok := directives[0].(*components.Location)
if !ok {
err = errors.New("error")
var location *components.Location
for _, directive := range directives {
if loc, ok := directive.(*components.Location); ok {
location = loc
break
}
}
if location == nil {
err = errors.New("invalid proxy config, no location found")
return
}
proxyConfig.ProxyPass = location.ProxyPass
+31 -13
View File
@@ -185,6 +185,8 @@ func (w WebsiteSSLService) Create(create request.WebsiteSSLCreate) (request.Webs
return res, err
}
create.ID = websiteSSL.ID
logFile, _ := os.OpenFile(path.Join(global.Dir.SSLLogDir, fmt.Sprintf("%s-ssl-%d.log", websiteSSL.PrimaryDomain, websiteSSL.ID)), os.O_CREATE|os.O_WRONLY|os.O_TRUNC, constant.FilePerm)
logFile.Close()
go func() {
if create.Provider != constant.DnsManual {
if err = w.ObtainSSL(request.WebsiteSSLApply{
@@ -442,12 +444,15 @@ func (w WebsiteSSLService) GetWebsiteSSL(websiteId uint) (response.WebsiteSSLDTO
}
func (w WebsiteSSLService) Delete(ids []uint) error {
var names []string
var (
websiteSSLS []string
applySSLS []string
)
for _, id := range ids {
if websites, _ := websiteRepo.GetBy(websiteRepo.WithWebsiteSSLID(id)); len(websites) > 0 {
oldSSL, _ := websiteSSLRepo.GetFirst(repo.WithByID(id))
if oldSSL.ID > 0 {
names = append(names, oldSSL.PrimaryDomain)
websiteSSLS = append(websiteSSLS, oldSSL.PrimaryDomain)
}
continue
}
@@ -463,23 +468,36 @@ func (w WebsiteSSLService) Delete(ids []uint) error {
if err != nil {
return err
}
if websiteSSL.Status == constant.SSLApply {
applySSLS = append(applySSLS, websiteSSL.PrimaryDomain)
continue
}
if websiteSSL.Provider != constant.Manual && websiteSSL.Provider != constant.SelfSigned {
acmeAccount, err := websiteAcmeRepo.GetFirst(repo.WithByID(websiteSSL.AcmeAccountID))
if err != nil {
return err
}
client, err := ssl.NewAcmeClient(acmeAccount, getSystemProxy(acmeAccount.UseProxy))
if err != nil {
return err
}
go func() {
_ = client.RevokeSSL([]byte(websiteSSL.Pem))
acmeAccount, err := websiteAcmeRepo.GetFirst(repo.WithByID(websiteSSL.AcmeAccountID))
if err != nil {
global.LOG.Errorf("Failed to get acme account for SSL revoke, err: %v", err)
return
}
client, err := ssl.NewAcmeClient(acmeAccount, getSystemProxy(acmeAccount.UseProxy))
if err != nil {
global.LOG.Errorf("Failed to create ACME client for SSL revoke, err: %v", err)
return
}
err = client.RevokeSSL([]byte(websiteSSL.Pem))
if err != nil {
global.LOG.Errorf("Failed to revoke SSL for domain %s, err: %v", websiteSSL.PrimaryDomain, err)
return
}
}()
}
_ = websiteSSLRepo.DeleteBy(repo.WithByID(id))
}
if len(names) > 0 {
return buserr.WithName("ErrSSLCannotDelete", strings.Join(names, ","))
if len(websiteSSLS) > 0 {
return buserr.WithName("ErrSSLCannotDelete", strings.Join(websiteSSLS, ","))
}
if len(applySSLS) > 0 {
return buserr.WithName("ErrApplySSLCanNotDelete", strings.Join(applySSLS, ","))
}
return nil
}
+5 -5
View File
@@ -955,7 +955,7 @@ func opWebsite(website *model.Website, operate string) error {
}
server.UpdatePHPProxy([]string{website.Proxy}, localPath)
} else {
proxy := fmt.Sprintf("http://127.0.0.1:%s", runtime.Port)
proxy := fmt.Sprintf("http://%s", website.Proxy)
server.UpdateRootProxy([]string{proxy})
}
}
@@ -1031,7 +1031,7 @@ func chownRootDir(path string) error {
return nil
}
func getWebsiteDomains(domains []request.WebsiteDomain, defaultPort int, websiteID uint) (domainModels []model.WebsiteDomain, addPorts []int, addDomains []string, err error) {
func getWebsiteDomains(domains []request.WebsiteDomain, defaultHTTPPort, defaultHTTPsPort int, websiteID uint) (domainModels []model.WebsiteDomain, addPorts []int, addDomains []string, err error) {
var (
ports = make(map[int]struct{})
existPort = make(map[int]struct{})
@@ -1056,7 +1056,7 @@ func getWebsiteDomains(domains []request.WebsiteDomain, defaultPort int, website
domainModel.Domain = strings.ToLower(domainModel.Domain)
domainModel.Port = domain.Port
if domain.Port == 0 {
domain.Port = defaultPort
domain.Port = defaultHTTPPort
}
domainModel.SSL = domain.SSL
domainModel.WebsiteID = websiteID
@@ -1077,7 +1077,7 @@ func getWebsiteDomains(domains []request.WebsiteDomain, defaultPort int, website
}
for port := range ports {
if port == defaultPort {
if port == defaultHTTPPort || port == defaultHTTPsPort {
addPorts = append(addPorts, port)
continue
}
@@ -1098,7 +1098,7 @@ func getWebsiteDomains(domains []request.WebsiteDomain, defaultPort int, website
err = buserr.WithMap("ErrPortExist", errMap, nil)
return
}
if port != 443 && common.ScanPort(port) {
if port != defaultHTTPsPort && common.ScanPort(port) {
err = buserr.WithDetail("ErrPortInUsed", port, nil)
return
}
+1
View File
@@ -65,6 +65,7 @@ const (
TaskPull = "TaskPull"
TaskCommit = "TaskCommit"
TaskPush = "TaskPush"
TaskClean = "TaskClean"
TaskHandle = "TaskHandle"
)
+2
View File
@@ -7,9 +7,11 @@ services:
- "${HOST_IP}:${PANEL_APP_PORT_HTTP}:${PANEL_APP_PORT_HTTP}"
command: [
"--stdio", "${COMMAND}",
"--outputTransport","${OUTPUT_TRANSPORT}",
"--port", "${PANEL_APP_PORT_HTTP}",
"--baseUrl", "${BASE_URL}",
"--ssePath", "${SSE_PATH}",
"--streamableHttpPath", "${STREAMABLE_HTTP_PATH}",
"--messagePath", "${SSE_PATH}/messages"
]
networks:
+1
View File
@@ -5,6 +5,7 @@ const (
StatusCanceled = "Canceled"
StatusDone = "Done"
StatusWaiting = "Waiting"
StatusPending = "Pending"
StatusSuccess = "Success"
StatusFailed = "Failed"
StatusUploading = "Uploading"
+1 -1
View File
@@ -8,7 +8,7 @@ require (
github.com/compose-spec/compose-go/v2 v2.6.4
github.com/creack/pty v1.1.24
github.com/docker/compose/v2 v2.37.0
github.com/docker/docker v28.2.2+incompatible
github.com/docker/docker v28.3.3+incompatible
github.com/docker/go-connections v0.5.0
github.com/fsnotify/fsnotify v1.9.0
github.com/gin-gonic/gin v1.10.0
+2
View File
@@ -202,6 +202,8 @@ github.com/docker/distribution v2.8.3+incompatible h1:AtKxIZ36LoNK51+Z6RpzLpddBi
github.com/docker/distribution v2.8.3+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
github.com/docker/docker v28.2.2+incompatible h1:CjwRSksz8Yo4+RmQ339Dp/D2tGO5JxwYeqtMOEe0LDw=
github.com/docker/docker v28.2.2+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
github.com/docker/docker v28.3.3+incompatible h1:Dypm25kh4rmk49v1eiVbsAtpAsYURjYkaKubwuBdxEI=
github.com/docker/docker v28.3.3+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
github.com/docker/docker-credential-helpers v0.9.3 h1:gAm/VtF9wgqJMoxzT3Gj5p4AqIjCBS4wrsOh9yRqcz8=
github.com/docker/docker-credential-helpers v0.9.3/go.mod h1:x+4Gbw9aGmChi3qTLZj8Dfn0TD20M/fuWy0E5+WDeCo=
github.com/docker/go v1.5.1-1.0.20160303222718-d30aec9fd63c h1:lzqkGL9b3znc+ZUgi7FlLnqjQhcXxkNM/quxIjBVMD0=
+8 -1
View File
@@ -62,7 +62,6 @@ ErrHttpReqTimeOut: 'Request timed out {{ .err }}'
ErrHttpReqFailed: 'Request failed {{ .err }}'
ErrNoSuchHost: 'Unable to find the requested server {{ .err }}'
ErrHttpReqNotFound: 'The requested resource {{ .err }} could not be found'
ErrImagePullTimeOut: 'Image pull timeout'
ErrContainerNotFound: '{{ .name }} container does not exist'
ErrContainerMsg: '{{ .name }} container is abnormal. Please check the log on the container page for details'
ErrAppBackup: '{{ .name }} application backup failed {{ .err }}'
@@ -156,6 +155,7 @@ ExecShellSuccess: 'Script execution successful'
StartUpdateSystemSSL: 'Start updating system certificate'
UpdateSystemSSLSuccess: 'Update system certificate successfully'
ErrWildcardDomain: 'Unable to apply for wildcard domain name certificate in HTTP mode'
ErrApplySSLCanNotDelete: "The certificate {{.name}} being applied for cannot be deleted, please try again later."
#mysql
ErrUserIsExist: 'The current user already exists, please re-enter'
@@ -173,6 +173,11 @@ ErrObjectInUsed: 'The object is in use and cannot be deleted'
ErrObjectBeDependent: 'This image depends on other images and cannot be deleted'
ErrPortRules: 'Port number does not match, please re-enter!'
ErrPgImagePull: 'Image pull timed out, please configure image acceleration or manually pull the {{ .name }} image and try again'
PruneHelper: "This cleanup removed {{ .name }} {{ .count }} items, freeing {{ .size }} disk space"
ImageRemoveHelper: "Deleted image {{ .name }}, freeing {{ .size }} disk space"
BuildCache: "Build cache"
Volume: "Storage volume"
Network: "Network"
#runtime
ErrFileNotExist: '{{ .detail }} file does not exist! Please check the integrity of the source file!'
@@ -266,6 +271,7 @@ allow: 'allow'
deny: 'deny'
OpenrestyNotFound: 'Openresty is not installed'
remoteIpIsNull: "IP list is empty"
OpenrestyVersionErr: "Openresty version is too low, please upgrade Openresty to 1.27.1.2-2-2-focal"
#task
TaskStart: '{{ .name }} task starts [START]'
@@ -290,6 +296,7 @@ TaskPull: 'Pull'
TaskCommit: 'Commit'
TaskBuild: 'Build'
TaskPush: 'Push'
TaskClean: "Cleanup"
TaskHandle: 'Execute'
Website: 'Website'
App: 'Application'
+8 -1
View File
@@ -62,7 +62,6 @@ ErrHttpReqTimeOut: 'リクエストがタイムアウトしました {{ .err }}'
ErrHttpReqFailed: 'リクエストが失敗しました {{ .err }}'
ErrNoSuchHost: '要求されたサーバー {{ .err }} が見つかりません'
ErrHttpReqNotFound: '要求されたリソース {{ .err }} が見つかりませんでした'
ErrImagePullTimeOut: 'イメージ プル タイムアウト'
ErrContainerNotFound: '{{ .name }} コンテナが存在しません'
ErrContainerMsg: '{{ .name }} コンテナが異常です。詳細についてはコンテナページのログを確認してください。'
ErrAppBackup: '{{ .name }} アプリケーションのバックアップに失敗しました。エラー {{ .err }}'
@@ -156,6 +155,7 @@ ExecShellSuccess: 'スクリプトの実行が成功しました'
StartUpdateSystemSSL: 'システム証明書の更新を開始します'
UpdateSystemSSLSuccess: 'システム証明書を正常に更新しました'
ErrWildcardDomain: 'HTTP モードでワイルドカード ドメイン名証明書を申請できません'
ErrApplySSLCanNotDelete: "申請中の証明書 {{.name}} は削除できません。しばらくしてからもう一度お試しください。"
#mysql
ErrUserIsExist: '現在のユーザーは既に存在します。再入力してください'
@@ -173,6 +173,11 @@ ErrObjectInUsed: 'オブジェクトは使用中のため削除できません'
ErrObjectBeDependent: 'このイメージは他のイメージに依存しているため、削除できません'
ErrPortRules: 'ポート番号が一致しません。再入力してください。'
ErrPgImagePull: 'イメージのプルがタイムアウトしました。イメージのアクセラレーションを設定するか、{{ .name }} イメージを手動でプルして再試行してください'
PruneHelper: "今回のクリーンアップで{{ .name }} {{ .count }}個を削除し、{{ .size }}のディスク領域を解放しました"
ImageRemoveHelper: "イメージ{{ .name }}を削除し、{{ .size }}のディスク領域を解放しました"
BuildCache: "ビルドキャッシュ"
Volume: "ストレージボリューム"
Network: "ネットワーク"
#runtime
ErrFileNotExist: '{{ .detail }} ファイルが存在しません。ソース ファイルの整合性を確認してください。'
@@ -266,6 +271,7 @@ allow: '許可'
deny: '拒否'
OpenrestyNotFound: 'Openresty がインストールされていません'
remoteIpIsNull: "IPリストが空です"
OpenrestyVersionErr: "Openrestyのバージョンが低すぎます。Openrestyを1.27.1.2-2-2-focalにアップグレードしてください"
#task
TaskStart: '{{ .name }} タスクが開始されました [START]'
@@ -290,6 +296,7 @@ TaskPull: 'プル'
TaskCommit: 'コミット'
TaskBuild: 'ビルド'
TaskPush: 'プッシュ'
TaskClean: "クリーンアップ"
TaskHandle: '実行'
Website: 'ウェブサイト'
App: 'アプリケーション'
+8 -1
View File
@@ -62,7 +62,6 @@ ErrHttpReqTimeOut: '요청 시간이 초과되었습니다 {{ .err }}'
ErrHttpReqFailed: '요청이 실패했습니다 {{ .err }}'
ErrNoSuchHost: '요청한 서버 {{ .err }}을 찾을 수 없습니다'
ErrHttpReqNotFound: '요청한 리소스 {{ .err }}을 찾을 수 없습니다.'
ErrImagePullTimeOut: '이미지 풀링 시간 초과'
ErrContainerNotFound: '{{ .name }} 컨테이너가 존재하지 않습니다'
ErrContainerMsg: '{{ .name }} 컨테이너가 비정상입니다. 자세한 내용은 컨테이너 페이지의 로그를 확인하세요.'
ErrAppBackup: '{{ .name }} 애플리케이션 백업에 실패했습니다 {{ .err }}'
@@ -156,6 +155,7 @@ ExecShellSuccess: '스크립트 실행 성공'
StartUpdateSystemSSL: '시스템 인증서 업데이트 시작'
UpdateSystemSSLSuccess: '시스템 인증서 업데이트가 성공적으로 완료되었습니다.'
ErrWildcardDomain: 'HTTP 모드에서 와일드카드 도메인 이름 인증서를 신청할 수 없습니다'
ErrApplySSLCanNotDelete: "신청 중인 인증서 {{.name}}는 삭제할 수 없습니다. 나중에 다시 시도해 주세요."
#마이SQL
ErrUserIsExist: '현재 사용자가 이미 존재합니다. 다시 입력하세요'
@@ -173,6 +173,11 @@ ErrObjectInUsed: '개체가 사용 중이므로 삭제할 수 없습니다'
ErrObjectBeDependent: '이 이미지는 다른 이미지에 의존하므로 삭제할 수 없습니다'
ErrPortRules: '포트 번호가 일치하지 않습니다. 다시 입력하세요!'
ErrPgImagePull: '이미지 풀링 시간이 초과되었습니다. 이미지 가속을 구성하거나 {{ .name }} 이미지를 수동으로 풀링한 다음 다시 시도하세요.'
PruneHelper: "이번 정리에서 {{ .name }} {{ .count }}개를 제거하여 {{ .size }} 디스크 공간을 확보했습니다"
ImageRemoveHelper: "이미지 {{ .name }} 삭제, {{ .size }} 디스크 공간 확보"
BuildCache: "빌드 캐시"
Volume: "스토리지 볼륨"
Network: "네트워크"
#실행 시간
ErrFileNotExist: '{{ .detail }} 파일이 존재하지 않습니다! 소스 파일의 무결성을 확인하세요!'
@@ -266,6 +271,7 @@ allow: '허용하다'
deny: '거부하다'
OpenrestyNotFound: 'Openresty가 설치되지 않았습니다'
remoteIpIsNull: "IP 목록이 비어 있습니다"
OpenrestyVersionErr: "Openresty 버전이 너무 낮습니다. Openresty를 1.27.1.2-2-2-focal로 업그레이드하세요"
#일
TaskStart: '[START] {{ .name }} 작업이 시작됩니다.'
@@ -290,6 +296,7 @@ TaskPull: '당기기'
TaskCommit: '커밋'
TaskBuild: '빌드'
TaskPush: '푸시'
TaskClean: "정리"
TaskHandle: '실행'
Website: '웹사이트'
App: '애플리케이션'
+8 -1
View File
@@ -62,7 +62,6 @@ ErrHttpReqTimeOut: 'Permintaan tamat masa {{ .err }}'
ErrHttpReqFailed: 'Permintaan gagal {{ .err }}'
ErrNoSuchHost: 'Tidak dapat mencari pelayan yang diminta {{ .err }}'
ErrHttpReqNotFound: 'Sumber yang diminta {{ .err }} tidak dapat ditemui'
ErrImagePullTimeOut: 'Tamat masa tarik imej'
ErrContainerNotFound: 'Bekas {{ .name }} tidak wujud'
ErrContainerMsg: Bekas '{{ .name }} adalah tidak normal. Sila semak log pada halaman kontena untuk butiran'
ErrAppBackup: '{{ .name }} sandaran aplikasi gagal {{ .err }}'
@@ -155,6 +154,7 @@ ExecShellSuccess: 'Pelaksanaan skrip berjaya'
StartUpdateSystemSSL: 'Mula mengemas kini sijil sistem'
UpdateSystemSSLSuccess: 'Berjaya kemas kini sijil sistem'
ErrWildcardDomain: 'Tidak dapat memohon sijil nama domain kad bebas dalam mod HTTP'
ErrApplySSLCanNotDelete: "Sertifikat {{.name}} yang sedang diajukan tidak dapat dihapus, sila cuba lagi kemudian."
#mysql
ErrUserIsExist: 'Pengguna semasa sudah wujud, sila masukkan semula'
@@ -172,6 +172,11 @@ ErrObjectInUsed: 'Objek sedang digunakan dan tidak boleh dipadamkan'
ErrObjectBeDependent: 'Imej ini bergantung pada imej lain dan tidak boleh dipadamkan'
ErrPortRules: 'Nombor port tidak sepadan, sila masukkan semula!'
ErrPgImagePull: 'Tarikh imej tamat masa, sila konfigurasikan pecutan imej atau tarik imej {{ .name }} secara manual dan cuba lagi'
PruneHelper: "Pembersihan ini membuang {{ .name }} {{ .count }} item, membebaskan {{ .size }} ruang cakera"
ImageRemoveHelper: "Padam imej {{ .name }}, membebaskan {{ .size }} ruang cakera"
BuildCache: "Cache binaan"
Volume: "Jilid storan"
Network: "Rangkaian"
#masa berjalan
ErrFileNotExist: 'Fail {{ .detail }} tidak wujud! Sila semak integriti fail sumber!'
@@ -265,6 +270,7 @@ allow: 'membenarkan'
deny: 'menafikan'
OpenrestyNotFound: 'Openresty tidak dipasang'
remoteIpIsNull: "Senarai IP kosong"
OpenrestyVersionErr: "Versi Openresty terlalu rendah, sila naik taraf Openresty ke 1.27.1.2-2-2-focal"
#tugas
TaskStart: '{{ .name }} Task mula [MULA]'
@@ -289,6 +295,7 @@ TaskPull: 'Tarik'
TaskCommit: 'Komit'
TaskBuild: 'Bina'
TaskPush: 'Tolak'
TaskClean: "Pembersihan"
TaskHandle: 'Laksanakan'
Website: 'Laman web'
App: 'Aplikasi'
+8 -1
View File
@@ -62,7 +62,6 @@ ErrHttpReqTimeOut: 'Tempo limite da solicitação esgotado {{ .err }}'
ErrHttpReqFailed: 'Falha na solicitação {{ .err }}'
ErrNoSuchHost: 'Não foi possível encontrar o servidor solicitado {{ .err }}'
ErrHttpReqNotFound: 'O recurso solicitado {{ .err }} não pôde ser encontrado'
ErrImagePullTimeOut: 'Tempo limite de extração de imagem'
ErrContainerNotFound: '{{ .name }} container não existe'
ErrContainerMsg: 'O contêiner {{ .name }} está anormal. Por favor, verifique o log na página do contêiner para obter detalhes.'
ErrAppBackup: '{{ .name }} falha no backup do aplicativo {{ .err }}'
@@ -156,6 +155,7 @@ ExecShellSuccess: 'Execução do script bem-sucedida'
StartUpdateSystemSSL: 'Iniciar atualização do certificado do sistema'
UpdateSystemSSLSuccess: 'Atualizar certificado do sistema com sucesso'
ErrWildcardDomain: 'Não é possível solicitar certificado de nome de domínio curinga no modo HTTP'
ErrApplySSLCanNotDelete: "O certificado {{.name}} que está sendo solicitado não pode ser excluído, tente novamente mais tarde."
#mysql
ErrUserIsExist: 'O usuário atual já existe, digite novamente'
@@ -173,6 +173,11 @@ ErrObjectInUsed: 'O objeto está em uso e não pode ser excluído'
ErrObjectBeDependent: 'Esta imagem depende de outras imagens e não pode ser excluída'
ErrPortRules: 'O número da porta não corresponde, digite novamente!'
ErrPgImagePull: 'Tempo limite para extração de imagem. Configure a aceleração de imagem ou extraia manualmente a imagem {{ .name }} e tente novamente'
PruneHelper: "Esta limpeza removeu {{ .name }} {{ .count }} itens, liberando {{ .size }} de espaço em disco"
ImageRemoveHelper: "Excluída a imagem {{ .name }}, liberando {{ .size }} de espaço em disco"
BuildCache: "Cache de construção"
Volume: "Volume de armazenamento"
Network: "Rede"
#tempo de execução
ErrFileNotExist: 'O arquivo {{ .detail }} não existe! Verifique a integridade do arquivo de origem!'
@@ -266,6 +271,7 @@ allow: 'permitir'
deny: 'negar'
OpenrestyNotFound: 'Openresty não está instalado'
remoteIpIsNull: "A lista de IP está vazia"
OpenrestyVersionErr: "A versão do Openresty é muito baixa, por favor atualize o Openresty para 1.27.1.2-2-2-focal"
#tarefa
TaskStart: '{{ .name }} A tarefa inicia [START]'
@@ -290,6 +296,7 @@ TaskPull: 'Puxar'
TaskCommit: 'Commit'
TaskBuild: 'Construir'
TaskPush: 'Empurrar'
TaskClean: "Limpeza"
TaskHandle: 'Executar'
Website: 'Site'
App: 'Aplicativo'
+8 -1
View File
@@ -62,7 +62,6 @@ ErrHttpReqTimeOut: 'Истекло время ожидания запроса {{
ErrHttpReqFailed: 'Запрос не выполнен {{ .err }}'
ErrNoSuchHost: 'Не удалось найти запрошенный сервер {{ .err }}'
ErrHttpReqNotFound: 'Запрошенный ресурс {{ .err }} не найден'
ErrImagePullTimeOut: 'Истекло время ожидания извлечения изображения'
ErrContainerNotFound: 'Контейнер {{ .name }} не существует'
ErrContainerMsg: 'Контейнер {{ .name }} ненормален. Подробности смотрите в журнале на странице контейнера.'
ErrAppBackup: '{{ .name }} резервное копирование приложения не удалось {{ .err }}'
@@ -156,6 +155,7 @@ ExecShellSuccess: 'Скрипт выполнен успешно'
StartUpdateSystemSSL: 'Начать обновление системного сертификата'
UpdateSystemSSLSuccess: 'Сертификат системы обновлен успешно'
ErrWildcardDomain: 'Невозможно подать заявку на получение подстановочного сертификата доменного имени в режиме HTTP'
ErrApplySSLCanNotDelete: "Нельзя удалить сертификат {{.name}}, который находится в процессе выпуска. Пожалуйста, попробуйте позже."
#mysql
ErrUserIsExist: 'Текущий пользователь уже существует, пожалуйста, введите еще раз'
@@ -173,6 +173,11 @@ ErrObjectInUsed: 'Объект используется и не может бы
ErrObjectBeDependent: 'Это изображение зависит от других изображений и не может быть удалено'
ErrPortRules: 'Номер порта не совпадает, введите заново!'
ErrPgImagePull: 'Время извлечения изображения истекло. Настройте ускорение изображения или вручную извлеките изображение {{ .name }} и повторите попытку'
PruneHelper: "Очистка удалила {{ .name }} в количестве {{ .count }}, освободив {{ .size }} дискового пространства"
ImageRemoveHelper: "Удалён образ {{ .name }}, освобождено {{ .size }} дискового пространства"
BuildCache: "Кэш сборки"
Volume: "Том хранилища"
Network: "Сеть"
#время выполнения
ErrFileNotExist: 'Файл {{ .detail }} не существует! Проверьте целостность исходного файла!'
@@ -266,6 +271,7 @@ allow: 'разрешить'
deny: 'отрицать'
OpenrestyNotFound: 'Openresty не установлен'
remoteIpIsNull: "Список IP пуст"
OpenrestyVersionErr: "Версия Openresty слишком низкая, пожалуйста, обновите Openresty до 1.27.1.2-2-2-focal"
#задача
TaskStart: '{{ .name }} Задача начинается [START]'
@@ -290,6 +296,7 @@ TaskPull: 'Вытянуть'
TaskCommit: 'Kоммит'
ЗадачаСборка: 'Сборка'
TaskPush: 'Push'
TaskClean: "Очистка"
TaskHandle: 'Выполнить'
Website: 'Be6-сайт'
App: 'Приложение'
+8 -1
View File
@@ -62,7 +62,6 @@ ErrHttpReqTimeOut: 'İstek zaman aşımı {{ .err }}'
ErrHttpReqFailed: 'İstek başarısız {{ .err }}'
ErrNoSuchHost: 'İstenen sunucu bulunamıyor {{ .err }}'
ErrHttpReqNotFound: 'İstenen kaynak {{ .err }} bulunamadı'
ErrImagePullTimeOut: 'Image çekme zaman aşımı'
ErrContainerNotFound: '{{ .name }} konteyneri mevcut değil'
ErrContainerMsg: '{{ .name }} konteyneri anormal. Lütfen ayrıntılar için konteyner sayfasındaki günlüğü kontrol edin'
ErrAppBackup: '{{ .name }} uygulama yedeklemesi başarısız {{ .err }}'
@@ -156,6 +155,7 @@ ExecShellSuccess: 'Betik yürütme başarılı'
StartUpdateSystemSSL: 'Sistem sertifikası güncellemeyi başlat'
UpdateSystemSSLSuccess: 'Sistem sertifikası güncelleme başarılı'
ErrWildcardDomain: 'HTTP modunda joker alan adı sertifikası başvurusu yapılamıyor'
ErrApplySSLCanNotDelete: "İşlemde olan sertifika {{.name}} silinemez, lütfen daha sonra tekrar deneyin."
#mysql
ErrUserIsExist: 'Mevcut kullanıcı zaten mevcut, lütfen yeniden girin'
@@ -173,6 +173,11 @@ ErrObjectInUsed: 'Nesne kullanımda ve silinemez'
ErrObjectBeDependent: 'Bu image diğer imagelere bağlı ve silinemez'
ErrPortRules: 'Port numarası eşleşmiyor, lütfen yeniden girin!'
ErrPgImagePull: 'Image çekme zaman aşımı, lütfen image hızlandırma yapılandırın veya manuel olarak {{ .name }} imageını çekin ve tekrar deneyin'
PruneHelper: "Bu temizleme {{ .name }} {{ .count }} öğe kaldırdı, {{ .size }} disk alanı boşalttı"
ImageRemoveHelper: "{{ .name }} imajı silindi, {{ .size }} disk alanı boşalttı"
BuildCache: "Derleme önbelleği"
Volume: "Depolama hacmi"
Network: "Ağ"
#runtime
ErrFileNotExist: '{{ .detail }} dosyası mevcut değil! Lütfen kaynak dosyanın bütünlüğünü kontrol edin!'
@@ -264,6 +269,7 @@ allow: 'izin ver'
deny: 'reddet'
OpenrestyNotFound: 'Openresty yüklü değil'
remoteIpIsNull: "IP listesi boş"
OpenrestyVersionErr: "Openresty sürümü çok düşük, lütfen Openresty'i 1.27.1.2-2-2-focal olarak güncelleyin"
#task
TaskStart: '{{ .name }} görevi başlıyor [BAŞLAT]'
@@ -288,6 +294,7 @@ TaskPull: 'Çek'
TaskCommit: 'işleme'
TaskBuild: 'Yapı'
TaskPush: 'Gönder'
TaskClean: "Temizleme"
TaskHandle: 'Yürüt'
Website: 'Web Sitesi'
App: 'Uygulama'
+8 -1
View File
@@ -62,7 +62,6 @@ ErrHttpReqTimeOut: '請求逾時{{ .err }}'
ErrHttpReqFailed: '請求失敗{{ .err }}'
ErrNoSuchHost: '無法找到請求的伺服器{{ .err }}'
ErrHttpReqNotFound: '無法找到要求的資源{{ .err }}'
ErrImagePullTimeOut: '鏡像拉取逾時'
ErrContainerNotFound: '{{ .name }} 容器不存在'
ErrContainerMsg: '{{ .name }} 容器異常,請在容器頁面上查看日誌'
ErrAppBackup: '{{ .name }} 應用備份失敗 {{ .err }}'
@@ -155,6 +154,7 @@ ExecShellSuccess: '腳本執行成功'
StartUpdateSystemSSL: '開始更新系統憑證'
UpdateSystemSSLSuccess: '更新系統憑證成功'
ErrWildcardDomain: 'HTTP 模式無法申請泛網域憑證'
ErrApplySSLCanNotDelete: "正在申請的證書 {{.name}} 無法刪除,請稍後再試"
#mysql
ErrUserIsExist: '目前使用者已存在,請重新輸入'
@@ -172,6 +172,11 @@ ErrObjectInUsed: '該物件正被使用,無法刪除'
ErrObjectBeDependent: '此鏡像依賴其他鏡像,無法刪除'
ErrPortRules: '連接埠數目不匹配,請重新輸入!'
ErrPgImagePull: '鏡像拉取逾時,請配置鏡像加速或手動拉取{{ .name }} 鏡像後重試'
PruneHelper: "本次清理 {{ .name }} {{ .count }} 個,釋放磁盤空間 {{ .size }}"
ImageRemoveHelper: "刪除鏡像 {{ .name }} ,釋放磁盤空間 {{ .size }}"
BuildCache: "構建緩存"
Volume: "存儲卷"
Network: "網絡"
#runtime
ErrFileNotExist: '{{ .detail }} 檔案不存在!請檢查來源檔案完整性!'
@@ -265,6 +270,7 @@ allow: '允許'
deny: '禁止'
OpenrestyNotFound: 'Openresty 未安裝'
remoteIpIsNull: "IP 列表為空"
OpenrestyVersionErr: "Openresty 版本過低,請升級 Openresty 至 1.27.1.2-2-2-focal"
#task
TaskStart: '{{ .name }} 任務開始[START]'
@@ -289,6 +295,7 @@ TaskPull: '拉取'
TaskCommit: '制作'
TaskBuild: '建置'
TaskPush: '推送'
TaskClean: "清理"
TaskHandle: '執行'
Website: '網站'
App: '應用程式'
+8 -1
View File
@@ -62,7 +62,6 @@ ErrHttpReqTimeOut: "请求超时 {{ .err }}"
ErrHttpReqFailed: "请求失败 {{ .err }}"
ErrNoSuchHost: "无法找到请求的服务器 {{ .err }}"
ErrHttpReqNotFound: "无法找到请求的资源 {{ .err }}"
ErrImagePullTimeOut: '镜像拉取超时'
ErrContainerNotFound: '{{ .name }} 容器不存在'
ErrContainerMsg: '{{ .name }} 容器异常,具体请在容器页面查看日志'
ErrAppBackup: '{{ .name }} 应用备份失败 {{ .err }}'
@@ -155,6 +154,7 @@ ExecShellSuccess: "脚本执行成功"
StartUpdateSystemSSL: "开始更新系统证书"
UpdateSystemSSLSuccess: "更新系统证书成功"
ErrWildcardDomain: "HTTP 模式无法申请泛域名证书"
ErrApplySSLCanNotDelete: "正在申请的证书{{.name}}无法删除,请稍后再试"
#mysql
ErrUserIsExist: "当前用户已存在,请重新输入"
@@ -172,6 +172,11 @@ ErrObjectInUsed: "该对象正被使用,无法删除"
ErrObjectBeDependent: "该镜像依赖于其他镜像,无法删除"
ErrPortRules: "端口数目不匹配,请重新输入!"
ErrPgImagePull: "镜像拉取超时,请配置镜像加速或手动拉取 {{ .name }} 镜像后重试"
PruneHelper: "本次清理 {{ .name }} {{ .count }} 个,释放磁盘空间 {{ .size }}"
ImageRemoveHelper: "删除镜像 {{ .name }} ,释放磁盘空间 {{ .size }}"
BuildCache: "构建缓存"
Volume: "存储卷"
Network: "网络"
#runtime
ErrFileNotExist: "{{ .detail }} 文件不存在!请检查源文件完整性!"
@@ -265,6 +270,7 @@ allow: "允许"
deny: "禁止"
OpenrestyNotFound: "Openresty 未安装"
remoteIpIsNull: "IP 列表为空"
OpenrestyVersionErr: "Openresty 版本过低,请升级 Openresty 至 1.27.1.2-2-2-focal"
#task
TaskStart: "{{ .name }} 任务开始 [START]"
@@ -289,6 +295,7 @@ TaskPull: "拉取"
TaskCommit: "制作"
TaskBuild: "构建"
TaskPush: "推送"
TaskClean: "清理"
TaskHandle: "执行"
Website: "网站"
App: "应用"
+2
View File
@@ -33,6 +33,8 @@ func InitAgentDB() {
migrations.InitAlertConfig,
migrations.AddMethodToAlertLog,
migrations.AddMethodToAlertTask,
migrations.UpdateMcpServer,
migrations.InitCronjobGroup,
})
if err := m.Migrate(); err != nil {
global.LOG.Error(err)
+28 -1
View File
@@ -19,7 +19,7 @@ import (
)
var AddTable = &gormigrate.Migration{
ID: "20250507-add-table",
ID: "20250805-add-table",
Migrate: func(tx *gorm.DB) error {
return tx.AutoMigrate(
&model.AppDetail{},
@@ -63,6 +63,7 @@ var AddTable = &gormigrate.Migration{
&model.Group{},
&model.AppIgnoreUpgrade{},
&model.McpServer{},
&model.RootCert{},
)
},
}
@@ -419,3 +420,29 @@ var AddMethodToAlertTask = &gormigrate.Migration{
return nil
},
}
var UpdateMcpServer = &gormigrate.Migration{
ID: "20250729-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("output_transport", "sse").Error; err != nil {
return err
}
return nil
},
}
var InitCronjobGroup = &gormigrate.Migration{
ID: "20250805-init-cronjob-group",
Migrate: func(tx *gorm.DB) error {
if err := tx.AutoMigrate(&model.Cronjob{}); err != nil {
return err
}
if err := tx.Model(&model.Cronjob{}).Where("1=1").Updates(map[string]interface{}{"group_id": 0}).Error; err != nil {
return err
}
return nil
},
}
+20 -8
View File
@@ -1,13 +1,15 @@
package middleware
import (
"errors"
"fmt"
"net"
"net/http"
"strings"
"github.com/1Panel-dev/1Panel/agent/app/api/v2/helper"
"github.com/1Panel-dev/1Panel/agent/global"
"github.com/1Panel-dev/1Panel/agent/utils/cmd"
"github.com/1Panel-dev/1Panel/agent/utils/xpack"
"github.com/gin-gonic/gin"
)
@@ -17,13 +19,8 @@ func Certificate() gin.HandlerFunc {
c.Next()
return
}
if !c.Request.TLS.HandshakeComplete || len(c.Request.TLS.PeerCertificates) == 0 {
helper.InternalServer(c, errors.New("no such tls peer certificates"))
return
}
cert := c.Request.TLS.PeerCertificates[0]
if cert.Subject.CommonName != "panel_client" {
helper.InternalServer(c, fmt.Errorf("err certificate"))
if !xpack.ValidateCertificate(c) {
CloseDirectly(c)
return
}
conn := c.Request.Header.Get("Connection")
@@ -40,3 +37,18 @@ func Certificate() gin.HandlerFunc {
c.Next()
}
}
func CloseDirectly(c *gin.Context) {
hijacker, ok := c.Writer.(http.Hijacker)
if !ok {
c.AbortWithStatus(http.StatusForbidden)
return
}
conn, _, err := hijacker.Hijack()
if err != nil {
c.AbortWithStatus(http.StatusForbidden)
return
}
_ = conn.(*net.TCPConn).SetLinger(0)
conn.Close()
}
+3
View File
@@ -13,10 +13,13 @@ func (s *CronjobRouter) InitRouter(Router *gin.RouterGroup) {
{
cmdRouter.POST("", baseApi.CreateCronjob)
cmdRouter.POST("/next", baseApi.LoadNextHandle)
cmdRouter.POST("/import", baseApi.ImportCronjob)
cmdRouter.POST("/export", baseApi.ExportCronjob)
cmdRouter.POST("/load/info", baseApi.LoadCronjobInfo)
cmdRouter.GET("/script/options", baseApi.LoadScriptOptions)
cmdRouter.POST("/del", baseApi.DeleteCronjob)
cmdRouter.POST("/update", baseApi.UpdateCronjob)
cmdRouter.POST("/group/update", baseApi.UpdateCronjobGroup)
cmdRouter.POST("/status", baseApi.UpdateCronjobStatus)
cmdRouter.POST("/handle", baseApi.HandleOnce)
cmdRouter.POST("/download", baseApi.TargetDownload)
+5 -2
View File
@@ -32,12 +32,15 @@ func (s *HostRouter) InitRouter(Router *gin.RouterGroup) {
hostRouter.GET("/ssh/conf", baseApi.LoadSSHConf)
hostRouter.POST("/ssh/search", baseApi.GetSSHInfo)
hostRouter.POST("/ssh/update", baseApi.UpdateSSH)
hostRouter.POST("/ssh/generate", baseApi.GenerateSSH)
hostRouter.POST("/ssh/secret", baseApi.LoadSSHSecret)
hostRouter.POST("/ssh/log", baseApi.LoadSSHLogs)
hostRouter.POST("/ssh/conffile/update", baseApi.UpdateSSHByfile)
hostRouter.POST("/ssh/operate", baseApi.OperateSSH)
hostRouter.POST("/ssh/cert", baseApi.CreateRootCert)
hostRouter.POST("/ssh/cert/sync", baseApi.SyncRootCert)
hostRouter.POST("/ssh/cert/search", baseApi.SearchRootCert)
hostRouter.POST("/ssh/cert/delete", baseApi.DeleteRootCert)
hostRouter.POST("/tool", baseApi.GetToolStatus)
hostRouter.POST("/tool/init", baseApi.InitToolConfig)
hostRouter.POST("/tool/operate", baseApi.OperateTool)
+2
View File
@@ -21,5 +21,7 @@ func (a *NginxRouter) InitRouter(Router *gin.RouterGroup) {
groupRouter.POST("/build", baseApi.BuildNginx)
groupRouter.POST("/modules/update", baseApi.UpdateNginxModule)
groupRouter.GET("/modules", baseApi.GetNginxModules)
groupRouter.POST("/https", baseApi.OperateDefaultHTTPs)
groupRouter.GET("/https", baseApi.GetDefaultHTTPsStatus)
}
}
+10 -1
View File
@@ -2,6 +2,7 @@ package server
import (
"crypto/tls"
"crypto/x509"
"fmt"
"net"
"net/http"
@@ -78,9 +79,17 @@ func Start() {
fmt.Printf("failed to load X.509 key pair: %s\n", err)
return
}
server.TLSConfig = &tls.Config{
Certificates: []tls.Certificate{tlsCert},
ClientAuth: tls.RequireAnyClientCert,
ClientAuth: tls.RequireAndVerifyClientCert,
}
caItem, _ := settingRepo.GetValueByKey("RootCrt")
if len(caItem) != 0 {
caCertPool := x509.NewCertPool()
rootCrt, _ := encrypt.StringDecrypt(caItem)
caCertPool.AppendCertsFromPEM([]byte(rootCrt))
server.TLSConfig.ClientCAs = caCertPool
}
business.Init()
global.LOG.Infof("listen at https://0.0.0.0:%s", global.CONF.Base.Port)
+12
View File
@@ -353,3 +353,15 @@ func logProcess(progress map[string]interface{}, task *task.Task) {
}
_ = setLog(id, progressStr, task)
}
func PullImage(imageName string) error {
cli, err := NewDockerClient()
if err != nil {
return err
}
defer cli.Close()
if _, err := cli.ImagePull(context.Background(), imageName, image.PullOptions{}); err != nil {
return err
}
return nil
}
+3 -3
View File
@@ -132,7 +132,7 @@ func isValidHeader(key, value string) bool {
func sendWithSSL(config SMTPConfig, addr string, toList []string, msg string, transport *http.Transport) error {
var err error
var conn net.Conn
if transport != nil {
if transport != nil && transport.DialContext != nil {
conn, err = transport.DialContext(context.Background(), "tcp", addr)
} else {
conn, err = net.Dial("tcp", addr)
@@ -163,7 +163,7 @@ func sendWithSSL(config SMTPConfig, addr string, toList []string, msg string, tr
func sendWithStartTLS(config SMTPConfig, addr string, toList []string, msg string, transport *http.Transport) error {
var err error
var conn net.Conn
if transport != nil {
if transport != nil && transport.DialContext != nil {
conn, err = transport.DialContext(context.Background(), "tcp", addr)
} else {
conn, err = net.Dial("tcp", addr)
@@ -192,7 +192,7 @@ func sendWithStartTLS(config SMTPConfig, addr string, toList []string, msg strin
func sendPlaintext(config SMTPConfig, addr string, toList []string, msg string, transport *http.Transport) error {
var err error
var conn net.Conn
if transport != nil {
if transport != nil && transport.DialContext != nil {
conn, err = transport.DialContext(context.Background(), "tcp", addr)
} else {
conn, err = net.Dial("tcp", addr)
+60
View File
@@ -101,6 +101,66 @@ func countLines(path string) (int, error) {
}
}
func TailFromEnd(filename string, lines int) ([]string, error) {
file, err := os.Open(filename)
if err != nil {
return nil, err
}
defer file.Close()
stat, err := file.Stat()
if err != nil {
return nil, err
}
fileSize := stat.Size()
bufSize := int64(4096)
var result []string
var leftover string
for offset := fileSize; offset > 0 && len(result) < lines; {
readSize := bufSize
if offset < bufSize {
readSize = offset
}
offset -= readSize
buf := make([]byte, readSize)
_, err := file.ReadAt(buf, offset)
if err != nil && err != io.EOF {
return nil, err
}
data := string(buf) + leftover
linesInChunk := strings.Split(data, "\n")
if offset > 0 {
leftover = linesInChunk[0]
linesInChunk = linesInChunk[1:]
} else {
leftover = ""
}
for i := len(linesInChunk) - 1; i >= 0; i-- {
if len(result) < lines {
if !(i == len(linesInChunk)-1 && linesInChunk[i] == "" && len(result) == 0) {
result = append([]string{linesInChunk[i]}, result...)
}
}
}
}
if leftover != "" && len(result) < lines {
result = append([]string{leftover}, result...)
}
if len(result) > lines {
result = result[len(result)-lines:]
}
return result, nil
}
func ReadFileByLine(filename string, page, pageSize int, latest bool) (lines []string, isEndOfFile bool, total int, err error) {
if !NewFileOp().Stat(filename) {
return
+3 -13
View File
@@ -4,6 +4,7 @@ import (
"fmt"
"regexp"
"strings"
"time"
"github.com/1Panel-dev/1Panel/agent/app/model"
"github.com/1Panel-dev/1Panel/agent/global"
@@ -38,20 +39,9 @@ func NewIptables() (*Iptables, error) {
return iptables, nil
}
func (iptables *Iptables) run(rule string) error {
stdout, err := cmd.RunDefaultWithStdoutBashCf("%s iptables -t nat %s", iptables.CmdStr, rule)
if err != nil {
return fmt.Errorf("%s, %s", err, stdout)
}
if stdout != "" {
return fmt.Errorf("iptables error: %s", stdout)
}
return nil
}
func (iptables *Iptables) outf(tab, rule string, a ...any) (stdout string, err error) {
stdout, err = cmd.RunDefaultWithStdoutBashCf("%s iptables -t %s %s", iptables.CmdStr, tab, fmt.Sprintf(rule, a...))
cmdMgr := cmd.NewCommandMgr(cmd.WithIgnoreExist1(), cmd.WithTimeout(20*time.Second))
stdout, err = cmdMgr.RunWithStdoutBashCf("%s iptables -t %s %s", iptables.CmdStr, tab, fmt.Sprintf(rule, a...))
if err != nil && stdout != "" {
global.LOG.Errorf("iptables failed, err: %s", stdout)
}
+4 -1
View File
@@ -5,6 +5,7 @@ import (
"strings"
"github.com/1Panel-dev/1Panel/agent/buserr"
"github.com/1Panel-dev/1Panel/agent/global"
"github.com/1Panel-dev/1Panel/agent/utils/cmd"
)
@@ -99,7 +100,9 @@ func (f *Ufw) ListPort() ([]FireInfo, error) {
}
func (f *Ufw) ListForward() ([]FireInfo, error) {
_ = f.EnableForward()
if err := f.EnableForward(); err != nil {
global.LOG.Errorf("init port forward failed, err: %v", err)
}
iptables, err := NewIptables()
if err != nil {
return nil, err
+13 -3
View File
@@ -234,7 +234,10 @@ func (r *Local) Backup(info BackupInfo) error {
dumpCmd = "mariadb-dump"
}
global.LOG.Infof("start to %s | gzip > %s.gzip", dumpCmd, info.TargetDir+"/"+info.FileName)
cmd := exec.Command("docker", "exec", r.ContainerName, dumpCmd, "--routines", "-uroot", "-p"+r.Password, "--default-character-set="+info.Format, info.Name)
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(info.Timeout*uint(time.Second)))
defer cancel()
cmd := exec.CommandContext(ctx, "docker", "exec", r.ContainerName, dumpCmd, "--routines", "-uroot", "-p"+r.Password, "--default-character-set="+info.Format, info.Name)
var stderr bytes.Buffer
cmd.Stderr = &stderr
@@ -253,7 +256,14 @@ func (r *Local) Backup(info BackupInfo) error {
func (r *Local) Recover(info RecoverInfo) error {
fi, _ := os.Open(info.SourceFile)
defer fi.Close()
cmd := exec.Command("docker", "exec", "-i", r.ContainerName, r.Type, "-uroot", "-p"+r.Password, "--default-character-set="+info.Format, info.Name)
mysqlCli := r.Type
if mysqlCli == "mysql-cluster" {
mysqlCli = "mysql"
}
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(info.Timeout*uint(time.Second)))
defer cancel()
cmd := exec.CommandContext(ctx, "docker", "exec", "-i", r.ContainerName, mysqlCli, "-uroot", "-p"+r.Password, "--default-character-set="+info.Format, info.Name)
if strings.HasSuffix(info.SourceFile, ".gz") {
gzipFile, err := os.Open(info.SourceFile)
if err != nil {
@@ -271,7 +281,7 @@ func (r *Local) Recover(info RecoverInfo) error {
}
stdout, err := cmd.CombinedOutput()
stdStr := strings.ReplaceAll(string(stdout), "mysql: [Warning] Using a password on the command line interface can be insecure.\n", "")
if err != nil || strings.HasPrefix(string(stdStr), "ERROR ") {
if err != nil || strings.HasPrefix(stdStr, "ERROR ") {
return errors.New(stdStr)
}
+15 -2
View File
@@ -136,7 +136,15 @@ func (r *Local) Backup(info BackupInfo) error {
}
defer outfile.Close()
global.LOG.Infof("start to pg_dump | gzip > %s.gzip", info.TargetDir+"/"+info.FileName)
cmd := exec.Command("docker", "exec", "-e", fmt.Sprintf("PGPASSWORD=%s", r.Password), r.ContainerName, "pg_dump", "-F", "c", "-U", r.Username, "-d", info.Name)
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(info.Timeout*uint(time.Second)))
defer cancel()
cmd := exec.CommandContext(
ctx,
"docker", "exec", "-i", r.ContainerName,
"sh", "-c",
fmt.Sprintf("PGPASSWORD=%s pg_dump -F c -U %s -d %s", r.Password, r.Username, info.Name),
)
var stderr bytes.Buffer
cmd.Stderr = &stderr
@@ -155,7 +163,12 @@ func (r *Local) Backup(info BackupInfo) error {
func (r *Local) Recover(info RecoverInfo) error {
fi, _ := os.Open(info.SourceFile)
defer fi.Close()
cmd := exec.Command("docker", "exec", "-i", r.ContainerName, "pg_restore", "-F", "c", "-c", "-U", r.Username, "-d", info.Name)
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(info.Timeout*uint(time.Second)))
defer cancel()
cmd := exec.CommandContext(ctx, "docker", "exec", "-i", r.ContainerName, "sh", "-c",
fmt.Sprintf("PGPASSWORD=%s pg_restore -F c -c --if-exists --no-owner -U %s -d %s", r.Password, r.Username, info.Name),
)
if strings.HasSuffix(info.SourceFile, ".gz") {
gzipFile, err := os.Open(info.SourceFile)
if err != nil {
+1 -1
View File
@@ -177,7 +177,7 @@ func (r *Remote) Recover(info RecoverInfo) error {
}()
}
recoverCommand := exec.Command("bash", "-c",
fmt.Sprintf("docker run --rm --net=host -i %s /bin/bash -c 'PGPASSWORD=\"%s\" pg_restore -h %s -p %d --verbose --clean --no-privileges --no-owner -Fc -U %s -d %s --role=%s' < %s",
fmt.Sprintf("docker run --rm --net=host -i %s /bin/bash -c 'PGPASSWORD=\"%s\" pg_restore -h %s -p %d --verbose --clean --no-privileges --no-owner -Fc -c --if-exists --no-owner -U %s -d %s --role=%s' < %s",
imageTag, r.Password, r.Address, r.Port, r.User, info.Name, info.Username, fileName))
pipe, _ := recoverCommand.StdoutPipe()
stderrPipe, _ := recoverCommand.StderrPipe()
+6 -3
View File
@@ -2,6 +2,7 @@ package redis
import (
"fmt"
"time"
"github.com/1Panel-dev/1Panel/agent/global"
"github.com/go-redis/redis"
@@ -11,13 +12,15 @@ type DBInfo struct {
Address string `json:"address"`
Port uint `json:"port"`
Password string `json:"password"`
Timeout uint `json:"timeout"`
}
func NewRedisClient(conn DBInfo) (*redis.Client, error) {
client := redis.NewClient(&redis.Options{
Addr: fmt.Sprintf("%s:%v", conn.Address, conn.Port),
Password: conn.Password,
DB: 0,
Addr: fmt.Sprintf("%s:%v", conn.Address, conn.Port),
Password: conn.Password,
DB: 0,
DialTimeout: time.Duration(conn.Timeout) * time.Second,
})
if _, err := client.Ping().Result(); err != nil {
-4
View File
@@ -130,10 +130,6 @@ func loadVersion(isLatest bool, currentVersion, mode string) string {
return ""
}
num, _ := strconv.Atoi(versionPart[1])
if num == 0 {
global.LOG.Errorf("current version is error format: %s", currentVersion)
return ""
}
if num >= 10 {
if version, ok := versionMap[currentVersion[0:5]]; ok {
return checkVersion(version, currentVersion)
+5
View File
@@ -15,6 +15,7 @@ import (
"github.com/1Panel-dev/1Panel/agent/buserr"
"github.com/1Panel-dev/1Panel/agent/global"
"github.com/1Panel-dev/1Panel/agent/utils/cmd"
"github.com/gin-gonic/gin"
)
func RemoveTamper(website string) {}
@@ -72,3 +73,7 @@ func LoadRequestTransport() *http.Transport {
IdleConnTimeout: 15 * time.Second,
}
}
func ValidateCertificate(c *gin.Context) bool {
return true
}
-2
View File
@@ -116,8 +116,6 @@ func (u *GroupService) Delete(id uint) error {
if err := xpack.UpdateGroup("node", id, defaultGroup.ID); err != nil {
return err
}
default:
return buserr.New("ErrNotSupportType")
}
if err != nil {
return err
+15 -10
View File
@@ -189,7 +189,11 @@ func (u *SettingService) UpdateBindInfo(req dto.BindInfo) error {
}
func (u *SettingService) UpdateProxy(req dto.ProxyUpdate) error {
if err := settingRepo.Update("ProxyUrl", req.ProxyUrl); err != nil {
proxyUrl := req.ProxyUrl
if req.ProxyType == "https" || req.ProxyType == "http" {
proxyUrl = req.ProxyType + "://" + req.ProxyUrl
}
if err := settingRepo.Update("ProxyUrl", proxyUrl); err != nil {
return err
}
if err := settingRepo.Update("ProxyType", req.ProxyType); err != nil {
@@ -675,16 +679,17 @@ func (u *SettingService) GetAppstoreConfig() (*dto.AppstoreConfig, error) {
}
func loadDockerProxy(req dto.ProxyUpdate) string {
if len(req.ProxyType) == 0 || req.ProxyType == "close" || !req.ProxyDocker {
if req.ProxyType == "" || req.ProxyType == "close" || !req.ProxyDocker {
return ""
}
proxyPasswd := ""
if len(req.ProxyUser) != 0 {
proxyPasswd = req.ProxyPasswd + "@"
var account string
if req.ProxyUser != "" {
account = req.ProxyUser
if req.ProxyPasswd != "" {
account += ":" + req.ProxyPasswd
}
account += "@"
}
proxyUrl := req.ProxyType + "://" + req.ProxyUser + ":" + proxyPasswd + req.ProxyUrl + ":" + req.ProxyPort
if req.ProxyType == "http" || req.ProxyType == "https" {
req.ProxyUrl = req.ProxyType + "://" + req.ProxyUrl
}
return proxyUrl
return fmt.Sprintf("%s://%s%s:%s", req.ProxyType, account, req.ProxyUrl, req.ProxyPort)
}
+1
View File
@@ -22,6 +22,7 @@ func Init() {
migrations.UpdateOnedrive,
migrations.AddClusterMenu,
migrations.DeleteXpackHideMenu,
migrations.AddCronjobGroup,
})
if err := m.Migrate(); err != nil {
global.LOG.Error(err)
+10
View File
@@ -531,3 +531,13 @@ var DeleteXpackHideMenu = &gormigrate.Migration{
return tx.Model(&model.Setting{}).Where("key = ?", "HideMenu").Update("value", string(updatedJSON)).Error
},
}
var AddCronjobGroup = &gormigrate.Migration{
ID: "20250729-add-cronjob-group",
Migrate: func(tx *gorm.DB) error {
if err := tx.Create(&model.Group{Name: "Default", Type: "cronjob", IsDefault: true}).Error; err != nil {
return err
}
return nil
},
}
+1 -1
View File
@@ -138,7 +138,7 @@ func HandleUnTar(sourceFile, targetDir string, secret string) error {
commands = fmt.Sprintf("%s tar -zxvf - -C %s", extraCmd, targetDir+" > /dev/null 2>&1")
global.LOG.Debug(strings.ReplaceAll(commands, fmt.Sprintf(" '%s' ", secret), " ****** "))
} else {
commands = fmt.Sprintf("tar zxvfC %s %s", sourceFile, targetDir)
commands = fmt.Sprintf("tar zxvf '%s' -C '%s'", sourceFile, targetDir)
global.LOG.Debug(commands)
}
+22 -1
View File
@@ -2,6 +2,7 @@ package ssh
import (
"fmt"
"net"
"strings"
"time"
@@ -51,11 +52,14 @@ func NewClient(c ConnInfo) (*SSHClient, error) {
if strings.Contains(c.Addr, ":") {
proto = "tcp6"
}
client, err := gossh.Dial(proto, addr, config)
client, err := DialWithTimeout(proto, addr, config)
if nil != err {
return nil, err
}
sshClient := &SSHClient{Client: client}
if c.User == "root" {
return sshClient, nil
}
if _, err := sshClient.Run("sudo -n ls"); err == nil {
sshClient.SudoItem = "sudo"
}
@@ -204,3 +208,20 @@ func (c *SSHClient) RunWithStreamOutput(command string, outputCallback func(stri
return err
}
func DialWithTimeout(network, addr string, config *gossh.ClientConfig) (*gossh.Client, error) {
conn, err := net.DialTimeout(network, addr, config.Timeout)
if err != nil {
return nil, err
}
_ = conn.SetDeadline(time.Now().Add(config.Timeout))
c, chans, reqs, err := gossh.NewClientConn(conn, addr, config)
if err != nil {
return nil, err
}
if err := conn.SetDeadline(time.Time{}); err != nil {
conn.Close()
return nil, fmt.Errorf("clear deadline failed: %v", err)
}
return gossh.NewClient(c, chans, reqs), nil
}
+20 -10
View File
@@ -2,14 +2,24 @@
root = true
[*] # 表示所有文件适用
charset = utf-8 # 设置文件字符集为 utf-8
end_of_line = lf # 控制换行类型(lf | cr | crlf)
insert_final_newline = true # 始终在文件末尾插入一个新行
indent_style = space # 缩进风格(tab | space)
indent_size = 4 # 缩进大小
max_line_length = 120 # 最大行长度
# 表示所有文件适用
[*]
# 设置文件字符集为 utf-8
charset = utf-8
# 控制换行类型(lf | cr | crlf)
end_of_line = lf
# 始终在文件末尾插入一个新行
insert_final_newline = true
# 缩进风格(tab | space)
indent_style = space
# 缩进大小
indent_size = 4
# 最大行长度
max_line_length = 120
[*.md] # 表示仅 md 文件适用以下规则
max_line_length = off # 关闭最大行长度限制
trim_trailing_whitespace = false # 关闭末尾空格修剪
# 表示仅 md 文件适用以下规则
[*.md]
# 关闭最大行长度限制
max_line_length = off
# 关闭末尾空格修剪
trim_trailing_whitespace = false
+2 -1
View File
@@ -7,6 +7,7 @@ import { GlobalStore } from '@/store';
import { MsgError } from '@/utils/message';
import { Base64 } from 'js-base64';
import i18n from '@/lang';
import { changeToLocal } from '@/utils/node';
const globalStore = GlobalStore();
@@ -66,7 +67,7 @@ class RequestHttp {
return Promise.reject(data);
}
if (data.code == ResultEnum.NodeUnBind) {
globalStore.currentNode = 'local';
changeToLocal();
window.location.reload();
return;
}
+2
View File
@@ -137,6 +137,8 @@ export namespace AI {
hostIP: string;
protocol: string;
url: string;
outputTransport: string;
streamableHttpPath: string;
}
export interface McpServerSearch extends ReqPage {
+1 -1
View File
@@ -282,7 +282,7 @@ export namespace App {
name: string;
detailID: number;
version: string;
icon: string;
scope: string;
}
export interface AppUpdateVersionReq {
+54
View File
@@ -1,10 +1,17 @@
import { ReqPage } from '.';
export namespace Cronjob {
export interface Search extends ReqPage {
info: string;
groupIDs: Array<number>;
orderBy?: string;
order?: string;
}
export interface CronjobInfo {
id: number;
name: string;
type: string;
groupID: number;
specCustom: boolean;
spec: string;
specs: Array<string>;
@@ -64,6 +71,7 @@ export namespace Cronjob {
export interface CronjobOperate {
id: number;
name: string;
groupID: number;
type: string;
specCustom: boolean;
spec: string;
@@ -101,6 +109,52 @@ export namespace Cronjob {
alertTitle: string;
alertMethod: string;
}
export interface CronjobTrans {
name: string;
type: string;
specCustom: boolean;
spec: string;
group: string;
executor: string;
scriptMode: string;
script: string;
command: string;
containerName: string;
user: string;
url: string;
scriptName: string;
apps: Array<TransHelper>;
websites: Array<string>;
dbType: string;
dbNames: Array<TransHelper>;
exclusionRules: string;
isDir: boolean;
sourceDir: string;
retainCopies: number;
retryTimes: number;
timeout: number;
ignoreErr: boolean;
snapshotRule: string;
secret: string;
sourceAccounts: Array<string>;
downloadAccount: string;
alertCount: number;
}
export interface TransHelper {
name: string;
detailName: string;
}
export interface snapshotTransHelper {
withImage: boolean;
ignoreApps: Array<TransHelper>;
}
export interface snapshotRule {
withImage: boolean;
ignoreAppIDs: Array<Number>;
+4
View File
@@ -221,6 +221,7 @@ export namespace Database {
// redis
export interface RedisConfUpdate {
dbType: string;
database: string;
timeout: string;
maxclients: string;
@@ -283,6 +284,7 @@ export namespace Database {
clientCert: string;
skipVerify: boolean;
timeout: number;
description: string;
}
export interface SearchDatabasePage {
@@ -322,6 +324,7 @@ export namespace Database {
clientCert: string;
skipVerify: boolean;
timeout: number;
description: string;
}
export interface DatabaseUpdate {
@@ -338,6 +341,7 @@ export namespace Database {
clientCert: string;
skipVerify: boolean;
timeout: number;
description: string;
}
export interface DatabaseDelete {
+1
View File
@@ -43,6 +43,7 @@ export namespace HostTool {
numprocs: string;
status?: ProcessStatus[];
autoRestart: string;
autoStart: string;
}
export interface ProcessStatus {
+18 -2
View File
@@ -161,15 +161,31 @@ export namespace Host {
primaryKey: string;
permitRootLogin: string;
useDNS: string;
currentUser: string;
}
export interface SSHUpdate {
key: string;
oldValue: string;
newValue: string;
}
export interface SSHGenerate {
export interface RootCert {
name: string;
mode: string;
encryptionMode: string;
password: string;
passPhrase: string;
privateKey: string;
publicKey: string;
description: string;
}
export interface RootCertInfo {
id: number;
createAt: Date;
name: string;
encryptionMode: string;
passPhrase: string;
description: string;
publicKey: string;
privateKey: string;
}
export interface searchSSHLog extends ReqPage {
info: string;
+8
View File
@@ -50,4 +50,12 @@ export namespace Nginx {
export interface NginxModuleUpdate extends NginxModule {
operate: string;
}
export interface NginxHttpsStatus {
https: boolean;
}
export interface NginxOperateReq {
operate: string;
}
}
+2 -2
View File
@@ -53,7 +53,7 @@ export const containerOperator = (params: Container.ContainerOperate) => {
return http.post(`/containers/operate`, params);
};
export const containerPrune = (params: Container.ContainerPrune) => {
return http.post<Container.ContainerPruneReport>(`/containers/prune`, params);
return http.post(`/containers/prune`, params);
};
export const inspect = (params: Container.ContainerInspect) => {
return http.post<string>(`/containers/inspect`, params);
@@ -95,7 +95,7 @@ export const imageTag = (params: Container.ImageTag) => {
return http.post(`/containers/image/tag`, params);
};
export const imageRemove = (params: Container.BatchDelete) => {
return http.post<Container.ContainerPruneReport>(`/containers/image/remove`, params);
return http.post(`/containers/image/remove`, params);
};
// network

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