Browse Source

feat: docker 配置界面样式统一 (#1173)

pull/1177/head
ssongliu 2 years ago committed by GitHub
parent
commit
ec843f2396
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 30
      backend/app/api/v1/docker.go
  2. 5
      backend/app/dto/docker.go
  3. 116
      backend/app/service/docker.go
  4. 6
      backend/app/service/firewall.go
  5. 1
      backend/router/ro_container.go
  6. 177
      cmd/server/docs/docs.go
  7. 177
      cmd/server/docs/swagger.json
  8. 118
      cmd/server/docs/swagger.yaml
  9. 7
      frontend/src/api/modules/container.ts
  10. 4
      frontend/src/lang/modules/en.ts
  11. 4
      frontend/src/lang/modules/zh.ts
  12. 26
      frontend/src/views/container/container/create/index.vue
  13. 348
      frontend/src/views/container/setting/index.vue
  14. 147
      frontend/src/views/container/setting/log/index.vue
  15. 86
      frontend/src/views/container/setting/mirror/index.vue
  16. 86
      frontend/src/views/container/setting/registry/index.vue

30
backend/app/api/v1/docker.go

@ -58,13 +58,13 @@ func (b *BaseApi) LoadDaemonJson(c *gin.Context) {
// @Summary Update docker daemon.json // @Summary Update docker daemon.json
// @Description 修改 docker 配置信息 // @Description 修改 docker 配置信息
// @Accept json // @Accept json
// @Param request body dto.DaemonJsonConf true "request" // @Param request body dto.SettingUpdate true "request"
// @Success 200 // @Success 200
// @Security ApiKeyAuth // @Security ApiKeyAuth
// @Router /containers/daemonjson/update [post] // @Router /containers/daemonjson/update [post]
// @x-panel-log {"bodyKeys":[],"paramKeys":[],"BeforeFuntions":[],"formatZH":"更新 docker daemon.json 配置","formatEN":"Updated the docker daemon.json configuration"} // @x-panel-log {"bodyKeys":["key", "value"],"paramKeys":[],"BeforeFuntions":[],"formatZH":"更新 docker daemon.json 配置 [key]=>[value]","formatEN":"Updated the docker daemon.json configuration [key]=>[value]"}
func (b *BaseApi) UpdateDaemonJson(c *gin.Context) { func (b *BaseApi) UpdateDaemonJson(c *gin.Context) {
var req dto.DaemonJsonConf var req dto.SettingUpdate
if err := c.ShouldBindJSON(&req); err != nil { if err := c.ShouldBindJSON(&req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err) helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return return
@ -78,6 +78,30 @@ func (b *BaseApi) UpdateDaemonJson(c *gin.Context) {
helper.SuccessWithData(c, nil) helper.SuccessWithData(c, nil)
} }
// @Tags Container Docker
// @Summary Update docker daemon.json log option
// @Description 修改 docker 日志配置
// @Accept json
// @Param request body dto.LogOption true "request"
// @Success 200
// @Security ApiKeyAuth
// @Router /containers/daemonjson/update [post]
// @x-panel-log {"bodyKeys":[],"paramKeys":[],"BeforeFuntions":[],"formatZH":"更新 docker daemon.json 日志配置","formatEN":"Updated the docker daemon.json log option"}
func (b *BaseApi) UpdateLogOption(c *gin.Context) {
var req dto.LogOption
if err := c.ShouldBindJSON(&req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
if err := dockerService.UpdateLogOption(req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithData(c, nil)
}
// @Tags Container Docker // @Tags Container Docker
// @Summary Update docker daemon.json by upload file // @Summary Update docker daemon.json by upload file
// @Description 上传替换 docker 配置文件 // @Description 上传替换 docker 配置文件

5
backend/app/dto/docker.go

@ -18,6 +18,11 @@ type DaemonJsonConf struct {
LogMaxFile string `json:"logMaxFile"` LogMaxFile string `json:"logMaxFile"`
} }
type LogOption struct {
LogMaxSize string `json:"logMaxSize"`
LogMaxFile string `json:"logMaxFile"`
}
type DockerOperation struct { type DockerOperation struct {
Operation string `json:"operation" validate:"required,oneof=start restart stop"` Operation string `json:"operation" validate:"required,oneof=start restart stop"`
} }

116
backend/app/service/docker.go

@ -4,6 +4,7 @@ import (
"bufio" "bufio"
"context" "context"
"encoding/json" "encoding/json"
"fmt"
"os" "os"
"path" "path"
"strings" "strings"
@ -18,7 +19,8 @@ import (
type DockerService struct{} type DockerService struct{}
type IDockerService interface { type IDockerService interface {
UpdateConf(req dto.DaemonJsonConf) error UpdateConf(req dto.SettingUpdate) error
UpdateLogOption(req dto.LogOption) error
UpdateConfByFile(info dto.DaemonJsonUpdateByFile) error UpdateConfByFile(info dto.DaemonJsonUpdateByFile) error
LoadDockerStatus() string LoadDockerStatus() string
LoadDockerConf() *dto.DaemonJsonConf LoadDockerConf() *dto.DaemonJsonConf
@ -95,6 +97,7 @@ func (u *DockerService) LoadDockerConf() *dto.DaemonJsonConf {
return &data return &data
} }
if err := json.Unmarshal(arr, &conf); err != nil { if err := json.Unmarshal(arr, &conf); err != nil {
fmt.Println(err)
return &data return &data
} }
if _, ok := deamonMap["iptables"]; !ok { if _, ok := deamonMap["iptables"]; !ok {
@ -116,7 +119,7 @@ func (u *DockerService) LoadDockerConf() *dto.DaemonJsonConf {
return &data return &data
} }
func (u *DockerService) UpdateConf(req dto.DaemonJsonConf) error { func (u *DockerService) UpdateConf(req dto.SettingUpdate) error {
if _, err := os.Stat(constant.DaemonJsonPath); err != nil && os.IsNotExist(err) { if _, err := os.Stat(constant.DaemonJsonPath); err != nil && os.IsNotExist(err) {
if err = os.MkdirAll(path.Dir(constant.DaemonJsonPath), os.ModePerm); err != nil { if err = os.MkdirAll(path.Dir(constant.DaemonJsonPath), os.ModePerm); err != nil {
return err return err
@ -131,45 +134,88 @@ func (u *DockerService) UpdateConf(req dto.DaemonJsonConf) error {
deamonMap := make(map[string]interface{}) deamonMap := make(map[string]interface{})
_ = json.Unmarshal(file, &deamonMap) _ = json.Unmarshal(file, &deamonMap)
if len(req.Registries) == 0 { switch req.Key {
delete(deamonMap, "insecure-registries") case "Registries":
} else { if len(req.Value) == 0 {
deamonMap["insecure-registries"] = req.Registries delete(deamonMap, "insecure-registries")
} } else {
if len(req.Mirrors) == 0 { deamonMap["insecure-registries"] = strings.Split(req.Value, ",")
delete(deamonMap, "registry-mirrors") }
} else { case "Mirrors":
deamonMap["registry-mirrors"] = req.Mirrors if len(req.Value) == 0 {
} delete(deamonMap, "registry-mirrors")
} else {
changeLogOption(deamonMap, req.LogMaxFile, req.LogMaxSize) deamonMap["registry-mirrors"] = strings.Split(req.Value, ",")
}
if !req.LiveRestore { case "LogOption":
delete(deamonMap, "live-restore") if req.Value == "disable" {
} else { delete(deamonMap, "log-opts")
deamonMap["live-restore"] = req.LiveRestore }
} case "LiveRestore":
if req.IPTables { if req.Value == "disable" {
delete(deamonMap, "iptables") delete(deamonMap, "live-restore")
} else { } else {
deamonMap["iptables"] = false deamonMap["live-restore"] = true
} }
if opts, ok := deamonMap["exec-opts"]; ok { case "IPtables":
if optsValue, isArray := opts.([]interface{}); isArray { if req.Value == "enable" {
for i := 0; i < len(optsValue); i++ { delete(deamonMap, "iptables")
if opt, isStr := optsValue[i].(string); isStr { } else {
if strings.HasPrefix(opt, "native.cgroupdriver=") { deamonMap["iptables"] = false
optsValue[i] = "native.cgroupdriver=" + req.CgroupDriver }
break case "Dirver":
if opts, ok := deamonMap["exec-opts"]; ok {
if optsValue, isArray := opts.([]interface{}); isArray {
for i := 0; i < len(optsValue); i++ {
if opt, isStr := optsValue[i].(string); isStr {
if strings.HasPrefix(opt, "native.cgroupdriver=") {
optsValue[i] = "native.cgroupdriver=" + req.Value
break
}
} }
} }
} }
} else {
if req.Value == "systemd" {
deamonMap["exec-opts"] = []string{"native.cgroupdriver=systemd"}
}
} }
} else { }
if req.CgroupDriver == "systemd" { if len(deamonMap) == 0 {
deamonMap["exec-opts"] = []string{"native.cgroupdriver=systemd"} _ = os.Remove(constant.DaemonJsonPath)
return nil
}
newJson, err := json.MarshalIndent(deamonMap, "", "\t")
if err != nil {
return err
}
if err := os.WriteFile(constant.DaemonJsonPath, newJson, 0640); err != nil {
return err
}
stdout, err := cmd.Exec("systemctl restart docker")
if err != nil {
return errors.New(string(stdout))
}
return nil
}
func (u *DockerService) UpdateLogOption(req dto.LogOption) error {
if _, err := os.Stat(constant.DaemonJsonPath); err != nil && os.IsNotExist(err) {
if err = os.MkdirAll(path.Dir(constant.DaemonJsonPath), os.ModePerm); err != nil {
return err
} }
_, _ = os.Create(constant.DaemonJsonPath)
}
file, err := os.ReadFile(constant.DaemonJsonPath)
if err != nil {
return err
} }
deamonMap := make(map[string]interface{})
_ = json.Unmarshal(file, &deamonMap)
changeLogOption(deamonMap, req.LogMaxFile, req.LogMaxSize)
if len(deamonMap) == 0 { if len(deamonMap) == 0 {
_ = os.Remove(constant.DaemonJsonPath) _ = os.Remove(constant.DaemonJsonPath)
return nil return nil

6
backend/app/service/firewall.go

@ -379,7 +379,7 @@ func (u *FirewallService) pingStatus() string {
return constant.StatusDisable return constant.StatusDisable
} }
func (u *FirewallService) updatePingStatus(enabel string) error { func (u *FirewallService) updatePingStatus(enable string) error {
lineBytes, err := os.ReadFile(confPath) lineBytes, err := os.ReadFile(confPath)
if err != nil { if err != nil {
return err return err
@ -389,14 +389,14 @@ func (u *FirewallService) updatePingStatus(enabel string) error {
hasLine := false hasLine := false
for _, line := range files { for _, line := range files {
if strings.Contains(line, "net/ipv4/icmp_echo_ignore_all") || strings.HasPrefix(line, "net/ipv4/icmp_echo_ignore_all") { if strings.Contains(line, "net/ipv4/icmp_echo_ignore_all") || strings.HasPrefix(line, "net/ipv4/icmp_echo_ignore_all") {
newFiles = append(newFiles, "net/ipv4/icmp_echo_ignore_all="+enabel) newFiles = append(newFiles, "net/ipv4/icmp_echo_ignore_all="+enable)
hasLine = true hasLine = true
} else { } else {
newFiles = append(newFiles, line) newFiles = append(newFiles, line)
} }
} }
if !hasLine { if !hasLine {
newFiles = append(newFiles, "net/ipv4/icmp_echo_ignore_all="+enabel) newFiles = append(newFiles, "net/ipv4/icmp_echo_ignore_all="+enable)
} }
file, err := os.OpenFile(confPath, os.O_WRONLY|os.O_TRUNC, 0666) file, err := os.OpenFile(confPath, os.O_WRONLY|os.O_TRUNC, 0666)
if err != nil { if err != nil {

1
backend/router/ro_container.go

@ -68,6 +68,7 @@ func (s *ContainerRouter) InitContainerRouter(Router *gin.RouterGroup) {
baRouter.GET("/docker/status", baseApi.LoadDockerStatus) baRouter.GET("/docker/status", baseApi.LoadDockerStatus)
baRouter.POST("/docker/operate", baseApi.OperateDocker) baRouter.POST("/docker/operate", baseApi.OperateDocker)
baRouter.POST("/daemonjson/update", baseApi.UpdateDaemonJson) baRouter.POST("/daemonjson/update", baseApi.UpdateDaemonJson)
baRouter.POST("/logoption/update", baseApi.UpdateLogOption)
baRouter.POST("/daemonjson/update/byfile", baseApi.UpdateDaemonJsonByFile) baRouter.POST("/daemonjson/update/byfile", baseApi.UpdateDaemonJsonByFile)
} }
} }

177
cmd/server/docs/docs.go

@ -1241,14 +1241,14 @@ var doc = `{
"ApiKeyAuth": [] "ApiKeyAuth": []
} }
], ],
"description": "修改 docker 配置信息", "description": "修改 docker 日志配置",
"consumes": [ "consumes": [
"application/json" "application/json"
], ],
"tags": [ "tags": [
"Container Docker" "Container Docker"
], ],
"summary": "Update docker daemon.json", "summary": "Update docker daemon.json log option",
"parameters": [ "parameters": [
{ {
"description": "request", "description": "request",
@ -1256,7 +1256,7 @@ var doc = `{
"in": "body", "in": "body",
"required": true, "required": true,
"schema": { "schema": {
"$ref": "#/definitions/dto.DaemonJsonConf" "$ref": "#/definitions/dto.LogOption"
} }
} }
], ],
@ -1268,8 +1268,8 @@ var doc = `{
"x-panel-log": { "x-panel-log": {
"BeforeFuntions": [], "BeforeFuntions": [],
"bodyKeys": [], "bodyKeys": [],
"formatEN": "Updated the docker daemon.json configuration", "formatEN": "Updated the docker daemon.json log option",
"formatZH": "更新 docker daemon.json 配置", "formatZH": "更新 docker daemon.json 日志配置",
"paramKeys": [] "paramKeys": []
} }
} }
@ -9040,6 +9040,72 @@ var doc = `{
} }
} }
}, },
"/websites/leech": {
"post": {
"security": [
{
"ApiKeyAuth": []
}
],
"description": "获取防盗链配置",
"consumes": [
"application/json"
],
"tags": [
"Website"
],
"summary": "Get AntiLeech conf",
"parameters": [
{
"description": "request",
"name": "request",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/request.NginxCommonReq"
}
}
],
"responses": {
"200": {
"description": ""
}
}
}
},
"/websites/leech/update": {
"post": {
"security": [
{
"ApiKeyAuth": []
}
],
"description": "更新防盗链配置",
"consumes": [
"application/json"
],
"tags": [
"Website"
],
"summary": "Update AntiLeech",
"parameters": [
{
"description": "request",
"name": "request",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/request.NginxAntiLeechUpdate"
}
}
],
"responses": {
"200": {
"description": ""
}
}
}
},
"/websites/list": { "/websites/list": {
"get": { "get": {
"security": [ "security": [
@ -10691,6 +10757,9 @@ var doc = `{
"script": { "script": {
"type": "string" "type": "string"
}, },
"second": {
"type": "integer"
},
"sourceDir": { "sourceDir": {
"type": "string" "type": "string"
}, },
@ -10770,6 +10839,9 @@ var doc = `{
"script": { "script": {
"type": "string" "type": "string"
}, },
"second": {
"type": "integer"
},
"sourceDir": { "sourceDir": {
"type": "string" "type": "string"
}, },
@ -11531,6 +11603,17 @@ var doc = `{
} }
} }
}, },
"dto.LogOption": {
"type": "object",
"properties": {
"logMaxFile": {
"type": "string"
},
"logMaxSize": {
"type": "string"
}
}
},
"dto.Login": { "dto.Login": {
"type": "object", "type": "object",
"properties": { "properties": {
@ -12501,6 +12584,9 @@ var doc = `{
"mfaStatus": { "mfaStatus": {
"type": "string" "type": "string"
}, },
"monitorInterval": {
"type": "string"
},
"monitorStatus": { "monitorStatus": {
"type": "string" "type": "string"
}, },
@ -13634,6 +13720,53 @@ var doc = `{
} }
} }
}, },
"request.NginxAntiLeechUpdate": {
"type": "object",
"required": [
"enable",
"extends",
"return",
"websiteID"
],
"properties": {
"blocked": {
"type": "boolean"
},
"cache": {
"type": "boolean"
},
"cacheTime": {
"type": "integer"
},
"cacheUint": {
"type": "string"
},
"enable": {
"type": "boolean"
},
"extends": {
"type": "string"
},
"logEnable": {
"type": "boolean"
},
"noneRef": {
"type": "boolean"
},
"return": {
"type": "string"
},
"serverNames": {
"type": "array",
"items": {
"type": "string"
}
},
"websiteID": {
"type": "integer"
}
}
},
"request.NginxAuthReq": { "request.NginxAuthReq": {
"type": "object", "type": "object",
"required": [ "required": [
@ -13671,6 +13804,17 @@ var doc = `{
} }
} }
}, },
"request.NginxCommonReq": {
"type": "object",
"required": [
"websiteID"
],
"properties": {
"websiteID": {
"type": "integer"
}
}
},
"request.NginxConfigFileUpdate": { "request.NginxConfigFileUpdate": {
"type": "object", "type": "object",
"required": [ "required": [
@ -14198,9 +14342,15 @@ var doc = `{
"type": "object", "type": "object",
"required": [ "required": [
"id", "id",
"params" "scope"
], ],
"properties": { "properties": {
"disableFunctions": {
"type": "array",
"items": {
"type": "string"
}
},
"id": { "id": {
"type": "integer" "type": "integer"
}, },
@ -14209,6 +14359,12 @@ var doc = `{
"additionalProperties": { "additionalProperties": {
"type": "string" "type": "string"
} }
},
"scope": {
"type": "string"
},
"uploadMaxSize": {
"type": "string"
} }
} }
}, },
@ -14806,11 +14962,20 @@ var doc = `{
"response.PHPConfig": { "response.PHPConfig": {
"type": "object", "type": "object",
"properties": { "properties": {
"disableFunctions": {
"type": "array",
"items": {
"type": "string"
}
},
"params": { "params": {
"type": "object", "type": "object",
"additionalProperties": { "additionalProperties": {
"type": "string" "type": "string"
} }
},
"uploadMaxSize": {
"type": "string"
} }
} }
}, },

177
cmd/server/docs/swagger.json

@ -1227,14 +1227,14 @@
"ApiKeyAuth": [] "ApiKeyAuth": []
} }
], ],
"description": "修改 docker 配置信息", "description": "修改 docker 日志配置",
"consumes": [ "consumes": [
"application/json" "application/json"
], ],
"tags": [ "tags": [
"Container Docker" "Container Docker"
], ],
"summary": "Update docker daemon.json", "summary": "Update docker daemon.json log option",
"parameters": [ "parameters": [
{ {
"description": "request", "description": "request",
@ -1242,7 +1242,7 @@
"in": "body", "in": "body",
"required": true, "required": true,
"schema": { "schema": {
"$ref": "#/definitions/dto.DaemonJsonConf" "$ref": "#/definitions/dto.LogOption"
} }
} }
], ],
@ -1254,8 +1254,8 @@
"x-panel-log": { "x-panel-log": {
"BeforeFuntions": [], "BeforeFuntions": [],
"bodyKeys": [], "bodyKeys": [],
"formatEN": "Updated the docker daemon.json configuration", "formatEN": "Updated the docker daemon.json log option",
"formatZH": "更新 docker daemon.json 配置", "formatZH": "更新 docker daemon.json 日志配置",
"paramKeys": [] "paramKeys": []
} }
} }
@ -9026,6 +9026,72 @@
} }
} }
}, },
"/websites/leech": {
"post": {
"security": [
{
"ApiKeyAuth": []
}
],
"description": "获取防盗链配置",
"consumes": [
"application/json"
],
"tags": [
"Website"
],
"summary": "Get AntiLeech conf",
"parameters": [
{
"description": "request",
"name": "request",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/request.NginxCommonReq"
}
}
],
"responses": {
"200": {
"description": ""
}
}
}
},
"/websites/leech/update": {
"post": {
"security": [
{
"ApiKeyAuth": []
}
],
"description": "更新防盗链配置",
"consumes": [
"application/json"
],
"tags": [
"Website"
],
"summary": "Update AntiLeech",
"parameters": [
{
"description": "request",
"name": "request",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/request.NginxAntiLeechUpdate"
}
}
],
"responses": {
"200": {
"description": ""
}
}
}
},
"/websites/list": { "/websites/list": {
"get": { "get": {
"security": [ "security": [
@ -10677,6 +10743,9 @@
"script": { "script": {
"type": "string" "type": "string"
}, },
"second": {
"type": "integer"
},
"sourceDir": { "sourceDir": {
"type": "string" "type": "string"
}, },
@ -10756,6 +10825,9 @@
"script": { "script": {
"type": "string" "type": "string"
}, },
"second": {
"type": "integer"
},
"sourceDir": { "sourceDir": {
"type": "string" "type": "string"
}, },
@ -11517,6 +11589,17 @@
} }
} }
}, },
"dto.LogOption": {
"type": "object",
"properties": {
"logMaxFile": {
"type": "string"
},
"logMaxSize": {
"type": "string"
}
}
},
"dto.Login": { "dto.Login": {
"type": "object", "type": "object",
"properties": { "properties": {
@ -12487,6 +12570,9 @@
"mfaStatus": { "mfaStatus": {
"type": "string" "type": "string"
}, },
"monitorInterval": {
"type": "string"
},
"monitorStatus": { "monitorStatus": {
"type": "string" "type": "string"
}, },
@ -13620,6 +13706,53 @@
} }
} }
}, },
"request.NginxAntiLeechUpdate": {
"type": "object",
"required": [
"enable",
"extends",
"return",
"websiteID"
],
"properties": {
"blocked": {
"type": "boolean"
},
"cache": {
"type": "boolean"
},
"cacheTime": {
"type": "integer"
},
"cacheUint": {
"type": "string"
},
"enable": {
"type": "boolean"
},
"extends": {
"type": "string"
},
"logEnable": {
"type": "boolean"
},
"noneRef": {
"type": "boolean"
},
"return": {
"type": "string"
},
"serverNames": {
"type": "array",
"items": {
"type": "string"
}
},
"websiteID": {
"type": "integer"
}
}
},
"request.NginxAuthReq": { "request.NginxAuthReq": {
"type": "object", "type": "object",
"required": [ "required": [
@ -13657,6 +13790,17 @@
} }
} }
}, },
"request.NginxCommonReq": {
"type": "object",
"required": [
"websiteID"
],
"properties": {
"websiteID": {
"type": "integer"
}
}
},
"request.NginxConfigFileUpdate": { "request.NginxConfigFileUpdate": {
"type": "object", "type": "object",
"required": [ "required": [
@ -14184,9 +14328,15 @@
"type": "object", "type": "object",
"required": [ "required": [
"id", "id",
"params" "scope"
], ],
"properties": { "properties": {
"disableFunctions": {
"type": "array",
"items": {
"type": "string"
}
},
"id": { "id": {
"type": "integer" "type": "integer"
}, },
@ -14195,6 +14345,12 @@
"additionalProperties": { "additionalProperties": {
"type": "string" "type": "string"
} }
},
"scope": {
"type": "string"
},
"uploadMaxSize": {
"type": "string"
} }
} }
}, },
@ -14792,11 +14948,20 @@
"response.PHPConfig": { "response.PHPConfig": {
"type": "object", "type": "object",
"properties": { "properties": {
"disableFunctions": {
"type": "array",
"items": {
"type": "string"
}
},
"params": { "params": {
"type": "object", "type": "object",
"additionalProperties": { "additionalProperties": {
"type": "string" "type": "string"
} }
},
"uploadMaxSize": {
"type": "string"
} }
} }
}, },

118
cmd/server/docs/swagger.yaml

@ -400,6 +400,8 @@ definitions:
type: integer type: integer
script: script:
type: string type: string
second:
type: integer
sourceDir: sourceDir:
type: string type: string
specType: specType:
@ -454,6 +456,8 @@ definitions:
type: integer type: integer
script: script:
type: string type: string
second:
type: integer
sourceDir: sourceDir:
type: string type: string
specType: specType:
@ -969,6 +973,13 @@ definitions:
type: type:
type: string type: string
type: object type: object
dto.LogOption:
properties:
logMaxFile:
type: string
logMaxSize:
type: string
type: object
dto.Login: dto.Login:
properties: properties:
authMethod: authMethod:
@ -1614,6 +1625,8 @@ definitions:
type: string type: string
mfaStatus: mfaStatus:
type: string type: string
monitorInterval:
type: string
monitorStatus: monitorStatus:
type: string type: string
monitorStoreDays: monitorStoreDays:
@ -2367,6 +2380,38 @@ definitions:
additionalProperties: true additionalProperties: true
type: object type: object
type: object type: object
request.NginxAntiLeechUpdate:
properties:
blocked:
type: boolean
cache:
type: boolean
cacheTime:
type: integer
cacheUint:
type: string
enable:
type: boolean
extends:
type: string
logEnable:
type: boolean
noneRef:
type: boolean
return:
type: string
serverNames:
items:
type: string
type: array
websiteID:
type: integer
required:
- enable
- extends
- return
- websiteID
type: object
request.NginxAuthReq: request.NginxAuthReq:
properties: properties:
websiteID: websiteID:
@ -2392,6 +2437,13 @@ definitions:
- username - username
- websiteID - websiteID
type: object type: object
request.NginxCommonReq:
properties:
websiteID:
type: integer
required:
- websiteID
type: object
request.NginxConfigFileUpdate: request.NginxConfigFileUpdate:
properties: properties:
backup: backup:
@ -2745,15 +2797,23 @@ definitions:
type: object type: object
request.WebsitePHPConfigUpdate: request.WebsitePHPConfigUpdate:
properties: properties:
disableFunctions:
items:
type: string
type: array
id: id:
type: integer type: integer
params: params:
additionalProperties: additionalProperties:
type: string type: string
type: object type: object
scope:
type: string
uploadMaxSize:
type: string
required: required:
- id - id
- params - scope
type: object type: object
request.WebsitePHPFileUpdate: request.WebsitePHPFileUpdate:
properties: properties:
@ -3154,10 +3214,16 @@ definitions:
type: object type: object
response.PHPConfig: response.PHPConfig:
properties: properties:
disableFunctions:
items:
type: string
type: array
params: params:
additionalProperties: additionalProperties:
type: string type: string
type: object type: object
uploadMaxSize:
type: string
type: object type: object
response.WebsiteAcmeAccountDTO: response.WebsiteAcmeAccountDTO:
properties: properties:
@ -4058,27 +4124,27 @@ paths:
post: post:
consumes: consumes:
- application/json - application/json
description: 修改 docker 配置信息 description: 修改 docker 日志配置
parameters: parameters:
- description: request - description: request
in: body in: body
name: request name: request
required: true required: true
schema: schema:
$ref: '#/definitions/dto.DaemonJsonConf' $ref: '#/definitions/dto.LogOption'
responses: responses:
"200": "200":
description: "" description: ""
security: security:
- ApiKeyAuth: [] - ApiKeyAuth: []
summary: Update docker daemon.json summary: Update docker daemon.json log option
tags: tags:
- Container Docker - Container Docker
x-panel-log: x-panel-log:
BeforeFuntions: [] BeforeFuntions: []
bodyKeys: [] bodyKeys: []
formatEN: Updated the docker daemon.json configuration formatEN: Updated the docker daemon.json log option
formatZH: 更新 docker daemon.json 配置 formatZH: 更新 docker daemon.json 日志配置
paramKeys: [] paramKeys: []
/containers/daemonjson/update/byfile: /containers/daemonjson/update/byfile:
post: post:
@ -9016,6 +9082,46 @@ paths:
formatEN: Delete domain [domain] formatEN: Delete domain [domain]
formatZH: 删除域名 [domain] formatZH: 删除域名 [domain]
paramKeys: [] paramKeys: []
/websites/leech:
post:
consumes:
- application/json
description: 获取防盗链配置
parameters:
- description: request
in: body
name: request
required: true
schema:
$ref: '#/definitions/request.NginxCommonReq'
responses:
"200":
description: ""
security:
- ApiKeyAuth: []
summary: Get AntiLeech conf
tags:
- Website
/websites/leech/update:
post:
consumes:
- application/json
description: 更新防盗链配置
parameters:
- description: request
in: body
name: request
required: true
schema:
$ref: '#/definitions/request.NginxAntiLeechUpdate'
responses:
"200":
description: ""
security:
- ApiKeyAuth: []
summary: Update AntiLeech
tags:
- Website
/websites/list: /websites/list:
get: get:
description: 获取网站列表 description: 获取网站列表

7
frontend/src/api/modules/container.ts

@ -148,8 +148,11 @@ export const loadDaemonJsonFile = () => {
export const loadDockerStatus = () => { export const loadDockerStatus = () => {
return http.get<string>(`/containers/docker/status`); return http.get<string>(`/containers/docker/status`);
}; };
export const updateDaemonJson = (params: Container.DaemonJsonConf) => { export const updateDaemonJson = (key: string, value: string) => {
return http.post(`/containers/daemonjson/update`, params); return http.post(`/containers/daemonjson/update`, { key: key, value: value }, 60000);
};
export const updateLogOption = (maxSize: string, maxFile: string) => {
return http.post(`/containers/logoption/update`, { maxSize: maxSize, maxFile: maxFile }, 60000);
}; };
export const updateDaemonJsonByfile = (params: Container.DaemonJsonUpdateByFile) => { export const updateDaemonJsonByfile = (params: Container.DaemonJsonUpdateByFile) => {
return http.post(`/containers/daemonjson/update/byfile`, params); return http.post(`/containers/daemonjson/update/byfile`, params);

4
frontend/src/lang/modules/en.ts

@ -605,9 +605,9 @@ const message = {
'Allows the running container state to be preserved in case of unexpected shutdown or crash of the Docker daemon', 'Allows the running container state to be preserved in case of unexpected shutdown or crash of the Docker daemon',
liveWithSwarmHelper: 'live-restore daemon configuration is incompatible with swarm mode.', liveWithSwarmHelper: 'live-restore daemon configuration is incompatible with swarm mode.',
iptablesDisable: 'Close iptables', iptablesDisable: 'Close iptables',
iptablesHelper1: 'This setting will disable Docker automatic configuration of iptables rules.', iptablesHelper1: 'Automatic configuration of iptables rules for Docker.',
iptablesHelper2: iptablesHelper2:
'This may cause the container to be unable to communicate with external networks. Do you want to continue?', 'Disabling iptables will result in the containers being unable to communicate with external networks.',
daemonJsonPath: 'Conf Path', daemonJsonPath: 'Conf Path',
serviceUnavailable: 'Docker service is not started at present, please click', serviceUnavailable: 'Docker service is not started at present, please click',
startIn: ' to start', startIn: ' to start',

4
frontend/src/lang/modules/zh.ts

@ -616,8 +616,8 @@ const message = {
liveHelper: '允许在 Docker 守护进程发生意外停机或崩溃时保留正在运行的容器状态', liveHelper: '允许在 Docker 守护进程发生意外停机或崩溃时保留正在运行的容器状态',
liveWithSwarmHelper: 'live-restore 守护进程配置与 Swarm 模式不兼容', liveWithSwarmHelper: 'live-restore 守护进程配置与 Swarm 模式不兼容',
iptablesDisable: '关闭 iptables', iptablesDisable: '关闭 iptables',
iptablesHelper1: '该设置将关闭 Docker iptables 规则的自动配置', iptablesHelper1: 'Docker iptables 规则的自动配置',
iptablesHelper2: '这可能会导致容器无法与外部网络通信是否继续', iptablesHelper2: '关闭 iptables 会导致容器无法与外部网络通信',
daemonJsonPath: '配置路径', daemonJsonPath: '配置路径',
serviceUnavailable: '当前未启动 Docker 服务请在', serviceUnavailable: '当前未启动 Docker 服务请在',
startIn: '中开启', startIn: '中开启',

26
frontend/src/views/container/container/create/index.vue

@ -10,7 +10,7 @@
<el-input clearable v-model.trim="form.name" /> <el-input clearable v-model.trim="form.name" />
</el-form-item> </el-form-item>
<el-form-item :label="$t('container.image')" prop="image"> <el-form-item :label="$t('container.image')" prop="image">
<el-select style="width: 100%" allow-create filterable v-model="form.image"> <el-select class="widthClass" allow-create filterable v-model="form.image">
<el-option <el-option
v-for="(item, index) of images" v-for="(item, index) of images"
:key="index" :key="index"
@ -26,7 +26,7 @@
</el-radio-group> </el-radio-group>
</el-form-item> </el-form-item>
<el-form-item v-if="!form.publishAllPorts"> <el-form-item v-if="!form.publishAllPorts">
<el-card style="width: 100%"> <el-card class="widthClass">
<table style="width: 100%" class="tab-table"> <table style="width: 100%" class="tab-table">
<tr v-if="form.exposedPorts.length !== 0"> <tr v-if="form.exposedPorts.length !== 0">
<th scope="col" width="45%" align="left"> <th scope="col" width="45%" align="left">
@ -107,21 +107,21 @@
<el-card style="width: 100%"> <el-card style="width: 100%">
<table style="width: 100%" class="tab-table"> <table style="width: 100%" class="tab-table">
<tr v-if="form.volumes.length !== 0"> <tr v-if="form.volumes.length !== 0">
<th scope="col" width="42%" align="left"> <th scope="col" width="39%" align="left">
<label>{{ $t('container.serverPath') }}</label> <label>{{ $t('container.serverPath') }}</label>
</th> </th>
<th scope="col" width="12%" align="left"> <th scope="col" width="18%" align="left">
<label>{{ $t('container.mode') }}</label> <label>{{ $t('container.mode') }}</label>
</th> </th>
<th scope="col" width="42%" align="left"> <th scope="col" width="39%" align="left">
<label>{{ $t('container.containerDir') }}</label> <label>{{ $t('container.containerDir') }}</label>
</th> </th>
<th align="left"></th> <th align="left"></th>
</tr> </tr>
<tr v-for="(row, index) in form.volumes" :key="index"> <tr v-for="(row, index) in form.volumes" :key="index">
<td width="42%"> <td width="39%">
<el-select <el-select
style="width: 100%" class="widthClass"
allow-create allow-create
clearable clearable
:placeholder="$t('commons.msg.inputOrSelect')" :placeholder="$t('commons.msg.inputOrSelect')"
@ -136,13 +136,13 @@
/> />
</el-select> </el-select>
</td> </td>
<td width="12%"> <td width="18%">
<el-select style="width: 100%" filterable v-model="row.mode"> <el-select class="widthClass" filterable v-model="row.mode">
<el-option value="rw" :label="$t('container.modeRW')" /> <el-option value="rw" :label="$t('container.modeRW')" />
<el-option value="ro" :label="$t('container.modeR')" /> <el-option value="ro" :label="$t('container.modeR')" />
</el-select> </el-select>
</td> </td>
<td width="42%"> <td width="39%">
<el-input v-model="row.containerDir" /> <el-input v-model="row.containerDir" />
</td> </td>
<td> <td>
@ -416,3 +416,9 @@ defineExpose({
acceptParams, acceptParams,
}); });
</script> </script>
<style lang="scss" scoped>
.widthClass {
width: 100%;
}
</style>

348
frontend/src/views/container/setting/index.vue

@ -46,12 +46,31 @@
<el-col :span="10"> <el-col :span="10">
<el-form :model="form" label-position="left" :rules="rules" ref="formRef" label-width="150px"> <el-form :model="form" label-position="left" :rules="rules" ref="formRef" label-width="150px">
<el-form-item :label="$t('container.mirrors')" prop="mirrors"> <el-form-item :label="$t('container.mirrors')" prop="mirrors">
<el-input <div style="width: 100%">
type="textarea" <el-input
:placeholder="$t('container.mirrorHelper')" type="textarea"
:autosize="{ minRows: 3, maxRows: 10 }" :autosize="{ minRows: 3, maxRows: 5 }"
v-model="form.mirrors" disabled
/> v-if="form.mirrors"
v-model="form.mirrors"
style="width: calc(100% - 80px)"
/>
<el-button
v-if="form.mirrors"
style="width: 80px"
@click="onChangeMirrors"
icon="Setting"
>
{{ $t('commons.button.set') }}
</el-button>
</div>
<el-input disabled v-if="!form.mirrors" v-model="unset">
<template #append>
<el-button @click="onChangeMirrors" icon="Setting">
{{ $t('commons.button.set') }}
</el-button>
</template>
</el-input>
<span class="input-help">{{ $t('container.mirrorsHelper') }}</span> <span class="input-help">{{ $t('container.mirrorsHelper') }}</span>
<span class="input-help"> <span class="input-help">
{{ $t('container.mirrorsHelper2') }} {{ $t('container.mirrorsHelper2') }}
@ -66,60 +85,58 @@
</span> </span>
</el-form-item> </el-form-item>
<el-form-item :label="$t('container.registries')" prop="registries"> <el-form-item :label="$t('container.registries')" prop="registries">
<el-input <div style="width: 100%">
type="textarea" <el-input
:placeholder="$t('container.registrieHelper')" v-if="form.registries"
:autosize="{ minRows: 3, maxRows: 10 }" type="textarea"
v-model="form.registries" :autosize="{ minRows: 3, maxRows: 5 }"
/> disabled
v-model="form.registries"
style="width: calc(100% - 80px)"
/>
<el-button
v-if="form.mirrors"
style="width: 80px"
@click="onChangeRegistries"
icon="Setting"
>
{{ $t('commons.button.set') }}
</el-button>
</div>
<el-input disabled v-if="!form.registries" v-model="unset">
<template #append>
<el-button @click="onChangeRegistries" icon="Setting">
{{ $t('commons.button.set') }}
</el-button>
</template>
</el-input>
</el-form-item> </el-form-item>
<el-form-item :label="$t('container.cutLog')" prop="hasLogOption"> <el-form-item :label="$t('container.cutLog')" prop="hasLogOption">
<el-switch v-model="form.logOptionShow"></el-switch> <el-switch v-model="form.logOptionShow" @change="handleLogOption"></el-switch>
</el-form-item> </el-form-item>
<div v-if="form.logOptionShow">
<el-form-item prop="logMaxSize">
<el-input v-model.number="form.logMaxSize">
<template #prepend>{{ $t('container.maxSize') }}</template>
<template #append>
<el-select v-model="form.sizeUnit" style="width: 70px">
<el-option label="b" value="b"></el-option>
<el-option label="k" value="k"></el-option>
<el-option label="m" value="m"></el-option>
<el-option label="g" value="g"></el-option>
<el-option label="t" value="t"></el-option>
</el-select>
</template>
</el-input>
</el-form-item>
<el-form-item prop="logMaxFile">
<el-input v-model.number="form.logMaxFile">
<template #prepend>{{ $t('container.maxFile') }}</template>
</el-input>
</el-form-item>
</div>
<el-form-item label="iptables" prop="iptables"> <el-form-item label="iptables" prop="iptables">
<el-switch v-model="form.iptables" @change="onChangeIptables"></el-switch> <el-switch v-model="form.iptables" @change="handleIptables"></el-switch>
<span class="input-help">{{ $t('container.iptablesHelper1') }}</span>
</el-form-item> </el-form-item>
<el-form-item label="live-restore" prop="liveRestore"> <el-form-item label="live-restore" prop="liveRestore">
<el-switch :disabled="form.isSwarm" v-model="form.liveRestore"></el-switch> <el-switch
:disabled="form.isSwarm"
v-model="form.liveRestore"
@change="handleLive"
></el-switch>
<span class="input-help">{{ $t('container.liveHelper') }}</span> <span class="input-help">{{ $t('container.liveHelper') }}</span>
<span v-if="form.isSwarm" class="input-help"> <span v-if="form.isSwarm" class="input-help">
{{ $t('container.liveWithSwarmHelper') }} {{ $t('container.liveWithSwarmHelper') }}
</span> </span>
</el-form-item> </el-form-item>
<el-form-item label="cgroup-driver" prop="cgroupDriver"> <el-form-item label="cgroup-driver" prop="cgroupDriver">
<el-radio-group v-model="form.cgroupDriver"> <el-radio-group v-model="form.cgroupDriver" @change="handleCgroup">
<el-radio label="cgroupfs">cgroupfs</el-radio> <el-radio label="cgroupfs">cgroupfs</el-radio>
<el-radio label="systemd">systemd</el-radio> <el-radio label="systemd">systemd</el-radio>
</el-radio-group> </el-radio-group>
</el-form-item> </el-form-item>
<el-form-item>
<el-button :disabled="loading" type="primary" @click="onSave(formRef)">
{{ $t('commons.button.save') }}
</el-button>
</el-form-item>
</el-form> </el-form>
</el-col> </el-col>
</el-row> </el-row>
@ -154,21 +171,47 @@
:close-on-press-escape="false" :close-on-press-escape="false"
:show-close="false" :show-close="false"
> >
<span>{{ $t('container.iptablesHelper1') }}</span>
<div style="margin-top: 10px"> <div style="margin-top: 10px">
<span style="color: red; font-weight: 500">{{ $t('container.iptablesHelper2') }}</span> <span style="color: red">{{ $t('container.iptablesHelper2') }}</span>
<div style="margin-top: 10px">
<span style="font-size: 12px">{{ $t('database.restartNowHelper') }}</span>
</div>
<div style="margin-top: 10px">
<span style="font-size: 12px">{{ $t('commons.msg.operateConfirm') }}</span>
<span style="font-size: 12px; color: red; font-weight: 500">'{{ $t('database.restartNow') }}'</span>
</div>
<el-input style="margin-top: 10px" v-model="submitInput"></el-input>
</div> </div>
<template #footer> <template #footer>
<span class="dialog-footer"> <span class="dialog-footer">
<el-button @click="onSaveIptables(true)">{{ $t('commons.button.cancel') }}</el-button> <el-button
<el-button type="primary" @click="onSaveIptables(false)"> @click="
iptablesVisiable = false;
search();
"
>
{{ $t('commons.button.cancel') }}
</el-button>
<el-button
:disabled="submitInput !== $t('database.restartNow')"
type="primary"
@click="onSubmitCloseIPtable"
>
{{ $t('commons.button.confirm') }} {{ $t('commons.button.confirm') }}
</el-button> </el-button>
</span> </span>
</template> </template>
</el-dialog> </el-dialog>
<ConfirmDialog ref="confirmDialogRef" @confirm="onSubmitSave"></ConfirmDialog> <Mirror ref="mirrorRef" @search="search" />
<Registry ref="registriesRef" @search="search" />
<LogOption ref="logOptionRef" @search="search" />
<ConfirmDialog ref="confirmDialogRefIptable" @confirm="onSubmitOpenIPtable" @cancle="search" />
<ConfirmDialog ref="confirmDialogRefLog" @confirm="onSubmitSaveLog" @cancle="search" />
<ConfirmDialog ref="confirmDialogRefLive" @confirm="onSubmitSaveLive" @cancle="search" />
<ConfirmDialog ref="confirmDialogRefCgroup" @confirm="onSubmitSaveCgroup" @cancle="search" />
<ConfirmDialog ref="confirmDialogRefFile" @confirm="onSubmitSaveFile" @cancle="search" />
</div> </div>
</template> </template>
@ -178,6 +221,9 @@ import { onMounted, reactive, ref } from 'vue';
import { Codemirror } from 'vue-codemirror'; import { Codemirror } from 'vue-codemirror';
import { javascript } from '@codemirror/lang-javascript'; import { javascript } from '@codemirror/lang-javascript';
import { oneDark } from '@codemirror/theme-one-dark'; import { oneDark } from '@codemirror/theme-one-dark';
import Mirror from '@/views/container/setting/mirror/index.vue';
import Registry from '@/views/container/setting/registry/index.vue';
import LogOption from '@/views/container/setting/log/index.vue';
import ConfirmDialog from '@/components/confirm-dialog/index.vue'; import ConfirmDialog from '@/components/confirm-dialog/index.vue';
import i18n from '@/lang'; import i18n from '@/lang';
import { import {
@ -190,11 +236,22 @@ import {
import { MsgSuccess } from '@/utils/message'; import { MsgSuccess } from '@/utils/message';
import { checkNumberRange } from '@/global/form-rules'; import { checkNumberRange } from '@/global/form-rules';
const unset = ref(i18n.global.t('setting.unSetting'));
const submitInput = ref();
const loading = ref(false); const loading = ref(false);
const showDaemonJsonAlert = ref(false); const showDaemonJsonAlert = ref(false);
const extensions = [javascript(), oneDark]; const extensions = [javascript(), oneDark];
const confShowType = ref('base'); const confShowType = ref('base');
const logOptionRef = ref();
const confirmDialogRefLog = ref();
const mirrorRef = ref();
const registriesRef = ref();
const confirmDialogRefLive = ref();
const confirmDialogRefCgroup = ref();
const confirmDialogRefIptable = ref();
const form = reactive({ const form = reactive({
isSwarm: false, isSwarm: false,
status: '', status: '',
@ -205,8 +262,7 @@ const form = reactive({
iptables: true, iptables: true,
cgroupDriver: '', cgroupDriver: '',
logOptionShow: false, logOptionShow: false,
logMaxSize: 10, logMaxSize: '',
sizeUnit: 'm',
logMaxFile: 3, logMaxFile: 3,
}); });
const rules = reactive({ const rules = reactive({
@ -220,41 +276,96 @@ const confirmDialogRef = ref();
const iptablesVisiable = ref(); const iptablesVisiable = ref();
const onSave = async (formEl: FormInstance | undefined) => { const onSaveFile = async () => {
if (!formEl) return; let params = {
formEl.validate(async (valid) => { header: i18n.global.t('database.confChange'),
if (!valid) return; operationInfo: i18n.global.t('database.restartNowHelper'),
if (!valid) return; submitInputInfo: i18n.global.t('database.restartNow'),
};
confirmDialogRef.value!.acceptParams(params);
};
const onChangeMirrors = () => {
mirrorRef.value.acceptParams({ mirrors: form.mirrors });
};
const onChangeRegistries = () => {
registriesRef.value.acceptParams({ registries: form.registries });
};
const handleLogOption = async () => {
if (form.logOptionShow) {
logOptionRef.value.acceptParams({ logMaxSize: form.logMaxSize, logMaxFile: form.logMaxFile });
return;
}
let params = {
header: i18n.global.t('database.confChange'),
operationInfo: i18n.global.t('database.restartNowHelper'),
submitInputInfo: i18n.global.t('database.restartNow'),
};
confirmDialogRefLog.value!.acceptParams(params);
};
const onSubmitSaveLog = async () => {
save('LogOption', 'disable');
};
const handleIptables = () => {
if (form.iptables) {
let params = { let params = {
header: i18n.global.t('database.confChange'), header: i18n.global.t('database.confChange'),
operationInfo: i18n.global.t('database.restartNowHelper'), operationInfo: i18n.global.t('database.restartNowHelper'),
submitInputInfo: i18n.global.t('database.restartNow'), submitInputInfo: i18n.global.t('database.restartNow'),
}; };
confirmDialogRef.value!.acceptParams(params); confirmDialogRefIptable.value!.acceptParams(params);
}); return;
} else {
iptablesVisiable.value = true;
}
}; };
const onSaveFile = async () => { const onSubmitCloseIPtable = () => {
save('IPtables', 'disable');
};
const onSubmitOpenIPtable = () => {
save('IPtables', 'enable');
};
const handleLive = async () => {
let params = { let params = {
header: i18n.global.t('database.confChange'), header: i18n.global.t('database.confChange'),
operationInfo: i18n.global.t('database.restartNowHelper'), operationInfo: i18n.global.t('database.restartNowHelper'),
submitInputInfo: i18n.global.t('database.restartNow'), submitInputInfo: i18n.global.t('database.restartNow'),
}; };
confirmDialogRef.value!.acceptParams(params); confirmDialogRefLive.value!.acceptParams(params);
}; };
const onSubmitSaveLive = () => {
const toDoc = () => { save('LiveRestore', form.liveRestore ? 'enable' : 'disable');
window.open('https://1panel.cn/docs/user_manual/containers/setting/', '_blank'); };
const handleCgroup = async () => {
let params = {
header: i18n.global.t('database.confChange'),
operationInfo: i18n.global.t('database.restartNowHelper'),
submitInputInfo: i18n.global.t('database.restartNow'),
};
confirmDialogRefCgroup.value!.acceptParams(params);
};
const onSubmitSaveCgroup = () => {
save('Dirver', form.cgroupDriver);
}; };
const onChangeIptables = () => { const save = async (key: string, value: string) => {
if (!form.iptables) { loading.value = true;
iptablesVisiable.value = true; await updateDaemonJson(key, value)
} .then(() => {
loading.value = false;
search();
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
})
.catch(() => {
search();
loading.value = false;
});
}; };
const onSaveIptables = (status: boolean) => { const toDoc = () => {
form.iptables = status; window.open('https://1panel.cn/docs/user_manual/containers/setting/', '_blank');
iptablesVisiable.value = false;
}; };
const onOperator = async (operation: string) => { const onOperator = async (operation: string) => {
@ -281,53 +392,18 @@ const onOperator = async (operation: string) => {
}); });
}; };
const onSubmitSave = async () => { const onSubmitSaveFile = async () => {
if (confShowType.value === 'all') { let param = { file: dockerConf.value };
let param = { file: dockerConf.value };
loading.value = true;
await updateDaemonJsonByfile(param)
.then(() => {
loading.value = false;
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
})
.catch(() => {
loading.value = false;
});
return;
}
let itemMirrors = form.mirrors.split('\n');
let itemRegistries = form.registries.split('\n');
let param = {
isSwarm: form.isSwarm,
status: form.status,
version: '',
registryMirrors: itemMirrors.filter(function (el) {
return el !== null && el !== '' && el !== undefined;
}),
insecureRegistries: itemRegistries.filter(function (el) {
return el !== null && el !== '' && el !== undefined;
}),
liveRestore: form.liveRestore,
iptables: form.iptables,
cgroupDriver: form.cgroupDriver,
logMaxSize: form.logMaxSize + form.sizeUnit,
logMaxFile: form.logMaxFile + '',
};
if (!form.logOptionShow) {
param.logMaxFile = '';
param.logMaxSize = '';
}
loading.value = true; loading.value = true;
await updateDaemonJson(param) await updateDaemonJsonByfile(param)
.then(() => { .then(() => {
loading.value = false; loading.value = false;
search();
MsgSuccess(i18n.global.t('commons.msg.operationSuccess')); MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
}) })
.catch(() => { .catch(() => {
loading.value = false; loading.value = false;
}); });
return;
}; };
const loadDockerConf = async () => { const loadDockerConf = async () => {
@ -349,49 +425,21 @@ const changeMode = async () => {
}; };
const search = async () => { const search = async () => {
loading.value = true; const res = await loadDaemonJson();
await loadDaemonJson() form.isSwarm = res.data.isSwarm;
.then((res) => { form.status = res.data.status;
loading.value = false; form.version = res.data.version;
form.isSwarm = res.data.isSwarm; form.cgroupDriver = res.data.cgroupDriver || 'cgroupfs';
form.status = res.data.status; form.liveRestore = res.data.liveRestore;
form.version = res.data.version; form.iptables = res.data.iptables;
form.cgroupDriver = res.data.cgroupDriver; form.mirrors = res.data.registryMirrors ? res.data.registryMirrors.join('\n') : '';
form.liveRestore = res.data.liveRestore; form.registries = res.data.insecureRegistries ? res.data.insecureRegistries.join('\n') : '';
form.iptables = res.data.iptables; if (res.data.logMaxFile || res.data.logMaxSize) {
form.mirrors = res.data.registryMirrors ? res.data.registryMirrors.join('\n') : ''; form.logOptionShow = true;
form.registries = res.data.insecureRegistries ? res.data.insecureRegistries.join('\n') : ''; form.logMaxFile = Number(res.data.logMaxFile);
if (res.data.logMaxFile || res.data.logMaxSize) { form.logMaxSize = res.data.logMaxSize;
form.logOptionShow = true; } else {
} form.logOptionShow = false;
form.logMaxFile = Number(res.data.logMaxFile);
form.logMaxSize = loadSize(res.data.logMaxSize);
})
.catch(() => {
loading.value = false;
});
};
const loadSize = (value: string) => {
if (value.indexOf('b') !== -1 || value.indexOf('B') !== -1) {
form.sizeUnit = 'b';
return Number(value.replaceAll('b', '').replaceAll('B', ''));
}
if (value.indexOf('k') !== -1 || value.indexOf('K') !== -1) {
form.sizeUnit = 'k';
return Number(value.replaceAll('k', '').replaceAll('K', ''));
}
if (value.indexOf('m') !== -1 || value.indexOf('M') !== -1) {
form.sizeUnit = 'm';
return Number(value.replaceAll('m', '').replaceAll('M', ''));
}
if (value.indexOf('g') !== -1 || value.indexOf('G') !== -1) {
form.sizeUnit = 'g';
return Number(value.replaceAll('g', '').replaceAll('G', ''));
}
if (value.indexOf('t') !== -1 || value.indexOf('T') !== -1) {
form.sizeUnit = 't';
return Number(value.replaceAll('t', '').replaceAll('T', ''));
} }
}; };

147
frontend/src/views/container/setting/log/index.vue

@ -0,0 +1,147 @@
<template>
<div>
<el-drawer
v-model="drawerVisiable"
:destroy-on-close="true"
:close-on-click-modal="false"
@close="handleClose"
size="30%"
>
<template #header>
<DrawerHeader :header="$t('container.cutLog')" :back="handleClose" />
</template>
<el-form :model="form" ref="formRef" :rules="rules" v-loading="loading" label-position="top">
<el-row type="flex" justify="center">
<el-col :span="22">
<el-form-item prop="logMaxSize" :label="$t('container.maxSize')">
<el-input v-model.number="form.logMaxSize">
<template #append>
<el-select v-model="form.sizeUnit" style="width: 70px">
<el-option label="B" value="B"></el-option>
<el-option label="KB" value="KB"></el-option>
<el-option label="MB" value="MB"></el-option>
<el-option label="GB" value="GB"></el-option>
</el-select>
</template>
</el-input>
</el-form-item>
<el-form-item prop="logMaxFile" :label="$t('container.maxFile')">
<el-input v-model.number="form.logMaxFile" />
</el-form-item>
</el-col>
</el-row>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="handleClose">{{ $t('commons.button.cancel') }}</el-button>
<el-button :disabled="loading" type="primary" @click="onSave(formRef)">
{{ $t('commons.button.confirm') }}
</el-button>
</span>
</template>
</el-drawer>
<ConfirmDialog ref="confirmDialogRef" @confirm="onSubmitSave"></ConfirmDialog>
</div>
</template>
<script lang="ts" setup>
import { reactive, ref } from 'vue';
import { Rules, checkNumberRange } from '@/global/form-rules';
import i18n from '@/lang';
import { MsgSuccess } from '@/utils/message';
import { FormInstance } from 'element-plus';
import { updateLogOption } from '@/api/modules/container';
const loading = ref();
const drawerVisiable = ref();
const confirmDialogRef = ref();
const formRef = ref();
interface DialogProps {
logMaxSize: string;
logMaxFile: number;
}
const form = reactive({
logMaxSize: 10,
logMaxFile: 3,
sizeUnit: 'MB',
});
const rules = reactive({
logMaxSize: [checkNumberRange(1, 1024000), Rules.number],
logMaxFile: [checkNumberRange(1, 100), Rules.number],
});
const emit = defineEmits<{ (e: 'search'): void }>();
const acceptParams = (params: DialogProps): void => {
form.logMaxFile = params.logMaxFile || 3;
if (params.logMaxSize) {
form.logMaxSize = loadSize(params.logMaxSize);
} else {
form.logMaxSize = 10;
form.sizeUnit = 'MB';
}
drawerVisiable.value = true;
};
const onSave = async (formEl: FormInstance | undefined) => {
if (!formEl) return;
formEl.validate(async (valid) => {
if (!valid) return;
let params = {
header: i18n.global.t('database.confChange'),
operationInfo: i18n.global.t('database.restartNowHelper'),
submitInputInfo: i18n.global.t('database.restartNow'),
};
confirmDialogRef.value!.acceptParams(params);
});
};
const onSubmitSave = async () => {
loading.value = true;
await updateLogOption(form.logMaxSize + '', form.logMaxFile + '')
.then(() => {
loading.value = false;
drawerVisiable.value = false;
emit('search');
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
})
.catch(() => {
loading.value = false;
});
};
const loadSize = (value: string) => {
if (value.indexOf('b') !== -1 || value.indexOf('B') !== -1) {
form.sizeUnit = 'B';
return Number(value.replaceAll('b', '').replaceAll('B', ''));
}
if (value.indexOf('k') !== -1 || value.indexOf('K') !== -1) {
form.sizeUnit = 'KB';
return Number(value.replaceAll('k', '').replaceAll('K', ''));
}
if (value.indexOf('m') !== -1 || value.indexOf('M') !== -1) {
form.sizeUnit = 'MB';
return Number(value.replaceAll('m', '').replaceAll('M', ''));
}
if (value.indexOf('g') !== -1 || value.indexOf('G') !== -1) {
form.sizeUnit = 'GB';
return Number(value.replaceAll('g', '').replaceAll('G', ''));
}
};
const handleClose = () => {
emit('search');
drawerVisiable.value = false;
};
defineExpose({
acceptParams,
});
</script>
<style scoped>
.help-ul {
color: #8f959e;
}
</style>

86
frontend/src/views/container/setting/mirror/index.vue

@ -0,0 +1,86 @@
<template>
<div>
<el-drawer v-model="drawerVisiable" :destroy-on-close="true" :close-on-click-modal="false" size="30%">
<template #header>
<DrawerHeader :header="$t('container.mirrors')" :back="handleClose" />
</template>
<el-form label-position="top" @submit.prevent v-loading="loading">
<el-row type="flex" justify="center">
<el-col :span="22">
<el-form-item :label="$t('container.mirrors')">
<el-input
type="textarea"
:placeholder="$t('container.mirrorHelper')"
:autosize="{ minRows: 8, maxRows: 10 }"
v-model="mirrors"
/>
</el-form-item>
</el-col>
</el-row>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="drawerVisiable = false">{{ $t('commons.button.cancel') }}</el-button>
<el-button :disabled="loading" type="primary" @click="onSave">
{{ $t('commons.button.confirm') }}
</el-button>
</span>
</template>
</el-drawer>
<ConfirmDialog ref="confirmDialogRef" @confirm="onSubmit" />
</div>
</template>
<script lang="ts" setup>
import { ref } from 'vue';
import i18n from '@/lang';
import { MsgSuccess } from '@/utils/message';
import ConfirmDialog from '@/components/confirm-dialog/index.vue';
import { updateDaemonJson } from '@/api/modules/container';
const emit = defineEmits<{ (e: 'search'): void }>();
const confirmDialogRef = ref();
const mirrors = ref();
interface DialogProps {
mirrors: string;
}
const drawerVisiable = ref();
const loading = ref();
const acceptParams = (params: DialogProps): void => {
mirrors.value = params.mirrors || params.mirrors.replaceAll(',', '\n');
drawerVisiable.value = true;
};
const onSave = async () => {
let params = {
header: i18n.global.t('database.confChange'),
operationInfo: i18n.global.t('database.restartNowHelper'),
submitInputInfo: i18n.global.t('database.restartNow'),
};
confirmDialogRef.value!.acceptParams(params);
};
const onSubmit = async () => {
loading.value = true;
await updateDaemonJson('Mirrors', mirrors.value.replaceAll('\n', ','))
.then(() => {
loading.value = false;
emit('search');
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
})
.catch(() => {
loading.value = false;
});
};
const handleClose = () => {
drawerVisiable.value = false;
};
defineExpose({
acceptParams,
});
</script>

86
frontend/src/views/container/setting/registry/index.vue

@ -0,0 +1,86 @@
<template>
<div>
<el-drawer v-model="drawerVisiable" :destroy-on-close="true" :close-on-click-modal="false" size="30%">
<template #header>
<DrawerHeader :header="$t('container.registries')" :back="handleClose" />
</template>
<el-form label-position="top" @submit.prevent v-loading="loading">
<el-row type="flex" justify="center">
<el-col :span="22">
<el-form-item :label="$t('container.registries')">
<el-input
type="textarea"
:placeholder="$t('container.registrieHelper')"
:autosize="{ minRows: 8, maxRows: 10 }"
v-model="registries"
/>
</el-form-item>
</el-col>
</el-row>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="drawerVisiable = false">{{ $t('commons.button.cancel') }}</el-button>
<el-button :disabled="loading" type="primary" @click="onSave">
{{ $t('commons.button.confirm') }}
</el-button>
</span>
</template>
</el-drawer>
<ConfirmDialog ref="confirmDialogRef" @confirm="onSubmit" />
</div>
</template>
<script lang="ts" setup>
import { ref } from 'vue';
import i18n from '@/lang';
import { MsgSuccess } from '@/utils/message';
import ConfirmDialog from '@/components/confirm-dialog/index.vue';
import { updateDaemonJson } from '@/api/modules/container';
const emit = defineEmits<{ (e: 'search'): void }>();
const confirmDialogRef = ref();
const registries = ref();
interface DialogProps {
registries: string;
}
const drawerVisiable = ref();
const loading = ref();
const acceptParams = (params: DialogProps): void => {
registries.value = params.registries || params.registries.replaceAll(',', '\n');
drawerVisiable.value = true;
};
const onSave = async () => {
let params = {
header: i18n.global.t('database.confChange'),
operationInfo: i18n.global.t('database.restartNowHelper'),
submitInputInfo: i18n.global.t('database.restartNow'),
};
confirmDialogRef.value!.acceptParams(params);
};
const onSubmit = async () => {
loading.value = true;
await updateDaemonJson('Registries', registries.value.replaceAll('\n', ','))
.then(() => {
loading.value = false;
emit('search');
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
})
.catch(() => {
loading.value = false;
});
};
const handleClose = () => {
drawerVisiable.value = false;
};
defineExpose({
acceptParams,
});
</script>
Loading…
Cancel
Save