feat: 容器适配 IPv6 (#3016)

Refs #2990 #2971
pull/3024/head
ssongliu 2023-11-21 22:14:07 +08:00 committed by GitHub
parent 055216604e
commit a5a707b923
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 938 additions and 61 deletions

View File

@ -83,7 +83,7 @@ func (b *BaseApi) UpdateDaemonJson(c *gin.Context) {
// @Param request body dto.LogOption true "request" // @Param request body dto.LogOption true "request"
// @Success 200 // @Success 200
// @Security ApiKeyAuth // @Security ApiKeyAuth
// @Router /containers/daemonjson/update [post] // @Router /containers/logoption/update [post]
// @x-panel-log {"bodyKeys":[],"paramKeys":[],"BeforeFunctions":[],"formatZH":"更新 docker daemon.json 日志配置","formatEN":"Updated the docker daemon.json log option"} // @x-panel-log {"bodyKeys":[],"paramKeys":[],"BeforeFunctions":[],"formatZH":"更新 docker daemon.json 日志配置","formatEN":"Updated the docker daemon.json log option"}
func (b *BaseApi) UpdateLogOption(c *gin.Context) { func (b *BaseApi) UpdateLogOption(c *gin.Context) {
var req dto.LogOption var req dto.LogOption
@ -99,6 +99,29 @@ func (b *BaseApi) UpdateLogOption(c *gin.Context) {
helper.SuccessWithData(c, nil) helper.SuccessWithData(c, nil)
} }
// @Tags Container Docker
// @Summary Update docker daemon.json ipv6 option
// @Description 修改 docker ipv6 配置
// @Accept json
// @Param request body dto.LogOption true "request"
// @Success 200
// @Security ApiKeyAuth
// @Router /containers/ipv6option/update [post]
// @x-panel-log {"bodyKeys":[],"paramKeys":[],"BeforeFunctions":[],"formatZH":"更新 docker daemon.json ipv6 配置","formatEN":"Updated the docker daemon.json ipv6 option"}
func (b *BaseApi) UpdateIpv6Option(c *gin.Context) {
var req dto.Ipv6Option
if err := helper.CheckBind(&req, c); err != nil {
return
}
if err := dockerService.UpdateIpv6Option(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 配置文件

View File

@ -135,13 +135,21 @@ type Network struct {
Attachable bool `json:"attachable"` Attachable bool `json:"attachable"`
} }
type NetworkCreate struct { type NetworkCreate struct {
Name string `json:"name" validate:"required"` Name string `json:"name" validate:"required"`
Driver string `json:"driver" validate:"required"` Driver string `json:"driver" validate:"required"`
Options []string `json:"options"` Options []string `json:"options"`
Subnet string `json:"subnet"` Ipv4 bool `json:"ipv4"`
Gateway string `json:"gateway"` Subnet string `json:"subnet"`
IPRange string `json:"ipRange"` Gateway string `json:"gateway"`
Labels []string `json:"labels"` IPRange string `json:"ipRange"`
AuxAddress []SettingUpdate `json:"auxAddress"`
Ipv6 bool `json:"ipv6"`
SubnetV6 string `json:"subnetV6"`
GatewayV6 string `json:"gatewayV6"`
IPRangeV6 string `json:"ipRangeV6"`
AuxAddressV6 []SettingUpdate `json:"auxAddressV6"`
Labels []string `json:"labels"`
} }
type Volume struct { type Volume struct {

View File

@ -14,6 +14,11 @@ type DaemonJsonConf struct {
IPTables bool `json:"iptables"` IPTables bool `json:"iptables"`
CgroupDriver string `json:"cgroupDriver"` CgroupDriver string `json:"cgroupDriver"`
Ipv6 bool `json:"ipv6"`
FixedCidrV6 string `json:"fixedCidrV6"`
Ip6Tables bool `json:"ip6Tables"`
Experimental bool `json:"experimental"`
LogMaxSize string `json:"logMaxSize"` LogMaxSize string `json:"logMaxSize"`
LogMaxFile string `json:"logMaxFile"` LogMaxFile string `json:"logMaxFile"`
} }
@ -23,6 +28,12 @@ type LogOption struct {
LogMaxFile string `json:"logMaxFile"` LogMaxFile string `json:"logMaxFile"`
} }
type Ipv6Option struct {
FixedCidrV6 string `json:"fixedCidrV6"`
Ip6Tables bool `json:"ip6Tables" validate:"required"`
Experimental bool `json:"experimental"`
}
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"`
} }

View File

@ -116,29 +116,57 @@ func (u *ContainerService) CreateNetwork(req dto.NetworkCreate) error {
return err return err
} }
var ( var (
ipam network.IPAMConfig ipams []network.IPAMConfig
hasConf bool enableV6 bool
) )
if len(req.Subnet) != 0 { if req.Ipv4 {
ipam.Subnet = req.Subnet var itemIpam network.IPAMConfig
hasConf = true if len(req.AuxAddress) != 0 {
itemIpam.AuxAddress = make(map[string]string)
}
if len(req.Subnet) != 0 {
itemIpam.Subnet = req.Subnet
}
if len(req.Gateway) != 0 {
itemIpam.Gateway = req.Gateway
}
if len(req.IPRange) != 0 {
itemIpam.IPRange = req.IPRange
}
for _, addr := range req.AuxAddress {
itemIpam.AuxAddress[addr.Key] = addr.Value
}
ipams = append(ipams, itemIpam)
} }
if len(req.Gateway) != 0 { if req.Ipv6 {
ipam.Gateway = req.Gateway enableV6 = true
hasConf = true var itemIpam network.IPAMConfig
} if len(req.AuxAddress) != 0 {
if len(req.IPRange) != 0 { itemIpam.AuxAddress = make(map[string]string)
ipam.IPRange = req.IPRange }
hasConf = true if len(req.SubnetV6) != 0 {
itemIpam.Subnet = req.SubnetV6
}
if len(req.GatewayV6) != 0 {
itemIpam.Gateway = req.GatewayV6
}
if len(req.IPRangeV6) != 0 {
itemIpam.IPRange = req.IPRangeV6
}
for _, addr := range req.AuxAddressV6 {
itemIpam.AuxAddress[addr.Key] = addr.Value
}
ipams = append(ipams, itemIpam)
} }
options := types.NetworkCreate{ options := types.NetworkCreate{
Driver: req.Driver, EnableIPv6: enableV6,
Options: stringsToMap(req.Options), Driver: req.Driver,
Labels: stringsToMap(req.Labels), Options: stringsToMap(req.Options),
Labels: stringsToMap(req.Labels),
} }
if hasConf { if len(ipams) != 0 {
options.IPAM = &network.IPAM{Config: []network.IPAMConfig{ipam}} options.IPAM = &network.IPAM{Config: ipams}
} }
if _, err := client.NetworkCreate(context.TODO(), req.Name, options); err != nil { if _, err := client.NetworkCreate(context.TODO(), req.Name, options); err != nil {
return err return err

View File

@ -21,6 +21,7 @@ type DockerService struct{}
type IDockerService interface { type IDockerService interface {
UpdateConf(req dto.SettingUpdate) error UpdateConf(req dto.SettingUpdate) error
UpdateLogOption(req dto.LogOption) error UpdateLogOption(req dto.LogOption) error
UpdateIpv6Option(req dto.Ipv6Option) error
UpdateConfByFile(info dto.DaemonJsonUpdateByFile) error UpdateConfByFile(info dto.DaemonJsonUpdateByFile) error
LoadDockerStatus() string LoadDockerStatus() string
LoadDockerConf() *dto.DaemonJsonConf LoadDockerConf() *dto.DaemonJsonConf
@ -32,13 +33,17 @@ func NewIDockerService() IDockerService {
} }
type daemonJsonItem struct { type daemonJsonItem struct {
Status string `json:"status"` Status string `json:"status"`
Mirrors []string `json:"registry-mirrors"` Mirrors []string `json:"registry-mirrors"`
Registries []string `json:"insecure-registries"` Registries []string `json:"insecure-registries"`
LiveRestore bool `json:"live-restore"` LiveRestore bool `json:"live-restore"`
IPTables bool `json:"iptables"` Ipv6 bool `json:"ipv6"`
ExecOpts []string `json:"exec-opts"` FixedCidrV6 string `json:"fixed-cidr-v6"`
LogOption logOption `json:"log-opts"` Ip6Tables bool `json:"ip6tables"`
Experimental bool `json:"experimental"`
IPTables bool `json:"iptables"`
ExecOpts []string `json:"exec-opts"`
LogOption logOption `json:"log-opts"`
} }
type logOption struct { type logOption struct {
LogMaxSize string `json:"max-size"` LogMaxSize string `json:"max-size"`
@ -110,6 +115,10 @@ func (u *DockerService) LoadDockerConf() *dto.DaemonJsonConf {
break break
} }
} }
data.Ipv6 = conf.Ipv6
data.FixedCidrV6 = conf.FixedCidrV6
data.Ip6Tables = conf.Ip6Tables
data.Experimental = conf.Experimental
data.LogMaxSize = conf.LogOption.LogMaxSize data.LogMaxSize = conf.LogOption.LogMaxSize
data.LogMaxFile = conf.LogOption.LogMaxFile data.LogMaxFile = conf.LogOption.LogMaxFile
data.Mirrors = conf.Mirrors data.Mirrors = conf.Mirrors
@ -149,6 +158,13 @@ func (u *DockerService) UpdateConf(req dto.SettingUpdate) error {
} else { } else {
daemonMap["registry-mirrors"] = strings.Split(req.Value, ",") daemonMap["registry-mirrors"] = strings.Split(req.Value, ",")
} }
case "Ipv6":
if req.Value == "disable" {
delete(daemonMap, "ipv6")
delete(daemonMap, "fixed-cidr-v6")
delete(daemonMap, "ip6tables")
delete(daemonMap, "experimental")
}
case "LogOption": case "LogOption":
if req.Value == "disable" { if req.Value == "disable" {
delete(daemonMap, "log-opts") delete(daemonMap, "log-opts")
@ -237,6 +253,48 @@ func (u *DockerService) UpdateLogOption(req dto.LogOption) error {
return nil return nil
} }
func (u *DockerService) UpdateIpv6Option(req dto.Ipv6Option) 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
}
daemonMap := make(map[string]interface{})
_ = json.Unmarshal(file, &daemonMap)
daemonMap["ipv6"] = true
daemonMap["fixed-cidr-v6"] = req.FixedCidrV6
if req.Ip6Tables {
daemonMap["ip6tables"] = req.Ip6Tables
}
if req.Experimental {
daemonMap["experimental"] = req.Experimental
}
if len(daemonMap) == 0 {
_ = os.Remove(constant.DaemonJsonPath)
return nil
}
newJson, err := json.MarshalIndent(daemonMap, "", "\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) UpdateConfByFile(req dto.DaemonJsonUpdateByFile) error { func (u *DockerService) UpdateConfByFile(req dto.DaemonJsonUpdateByFile) error {
if len(req.File) == 0 { if len(req.File) == 0 {
_ = os.Remove(constant.DaemonJsonPath) _ = os.Remove(constant.DaemonJsonPath)

View File

@ -79,6 +79,7 @@ func (s *ContainerRouter) InitContainerRouter(Router *gin.RouterGroup) {
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("/logoption/update", baseApi.UpdateLogOption)
baRouter.POST("/ipv6option/update", baseApi.UpdateIpv6Option)
baRouter.POST("/daemonjson/update/byfile", baseApi.UpdateDaemonJsonByFile) baRouter.POST("/daemonjson/update/byfile", baseApi.UpdateDaemonJsonByFile)
} }
} }

View File

@ -1414,14 +1414,14 @@ const docTemplate = `{
"ApiKeyAuth": [] "ApiKeyAuth": []
} }
], ],
"description": "修改 docker 日志配置", "description": "修改 docker 配置信息",
"consumes": [ "consumes": [
"application/json" "application/json"
], ],
"tags": [ "tags": [
"Container Docker" "Container Docker"
], ],
"summary": "Update docker daemon.json log option", "summary": "Update docker daemon.json",
"parameters": [ "parameters": [
{ {
"description": "request", "description": "request",
@ -1429,7 +1429,7 @@ const docTemplate = `{
"in": "body", "in": "body",
"required": true, "required": true,
"schema": { "schema": {
"$ref": "#/definitions/dto.LogOption" "$ref": "#/definitions/dto.SettingUpdate"
} }
} }
], ],
@ -1440,9 +1440,12 @@ const docTemplate = `{
}, },
"x-panel-log": { "x-panel-log": {
"BeforeFunctions": [], "BeforeFunctions": [],
"bodyKeys": [], "bodyKeys": [
"formatEN": "Updated the docker daemon.json log option", "key",
"formatZH": "更新 docker daemon.json 日志配置", "value"
],
"formatEN": "Updated the docker daemon.json configuration [key]=\u003e[value]",
"formatZH": "更新 docker daemon.json 配置 [key]=\u003e[value]",
"paramKeys": [] "paramKeys": []
} }
} }
@ -2029,6 +2032,46 @@ const docTemplate = `{
} }
} }
}, },
"/containers/ipv6option/update": {
"post": {
"security": [
{
"ApiKeyAuth": []
}
],
"description": "修改 docker ipv6 配置",
"consumes": [
"application/json"
],
"tags": [
"Container Docker"
],
"summary": "Update docker daemon.json ipv6 option",
"parameters": [
{
"description": "request",
"name": "request",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/dto.LogOption"
}
}
],
"responses": {
"200": {
"description": "OK"
}
},
"x-panel-log": {
"BeforeFunctions": [],
"bodyKeys": [],
"formatEN": "Updated the docker daemon.json ipv6 option",
"formatZH": "更新 docker daemon.json ipv6 配置",
"paramKeys": []
}
}
},
"/containers/limit": { "/containers/limit": {
"get": { "get": {
"security": [ "security": [
@ -2128,6 +2171,46 @@ const docTemplate = `{
} }
} }
}, },
"/containers/logoption/update": {
"post": {
"security": [
{
"ApiKeyAuth": []
}
],
"description": "修改 docker 日志配置",
"consumes": [
"application/json"
],
"tags": [
"Container Docker"
],
"summary": "Update docker daemon.json log option",
"parameters": [
{
"description": "request",
"name": "request",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/dto.LogOption"
}
}
],
"responses": {
"200": {
"description": "OK"
}
},
"x-panel-log": {
"BeforeFunctions": [],
"bodyKeys": [],
"formatEN": "Updated the docker daemon.json log option",
"formatZH": "更新 docker daemon.json 日志配置",
"paramKeys": []
}
}
},
"/containers/network": { "/containers/network": {
"get": { "get": {
"security": [ "security": [
@ -14023,15 +14106,27 @@ const docTemplate = `{
"cgroupDriver": { "cgroupDriver": {
"type": "string" "type": "string"
}, },
"experimental": {
"type": "boolean"
},
"fixedCidrV6": {
"type": "string"
},
"insecureRegistries": { "insecureRegistries": {
"type": "array", "type": "array",
"items": { "items": {
"type": "string" "type": "string"
} }
}, },
"ip6Tables": {
"type": "boolean"
},
"iptables": { "iptables": {
"type": "boolean" "type": "boolean"
}, },
"ipv6": {
"type": "boolean"
},
"isSwarm": { "isSwarm": {
"type": "boolean" "type": "boolean"
}, },
@ -15530,15 +15625,39 @@ const docTemplate = `{
"name" "name"
], ],
"properties": { "properties": {
"auxAddress": {
"type": "array",
"items": {
"$ref": "#/definitions/dto.SettingUpdate"
}
},
"auxAddressV6": {
"type": "array",
"items": {
"$ref": "#/definitions/dto.SettingUpdate"
}
},
"driver": { "driver": {
"type": "string" "type": "string"
}, },
"gateway": { "gateway": {
"type": "string" "type": "string"
}, },
"gatewayV6": {
"type": "string"
},
"ipRange": { "ipRange": {
"type": "string" "type": "string"
}, },
"ipRangeV6": {
"type": "string"
},
"ipv4": {
"type": "boolean"
},
"ipv6": {
"type": "boolean"
},
"labels": { "labels": {
"type": "array", "type": "array",
"items": { "items": {
@ -15556,6 +15675,9 @@ const docTemplate = `{
}, },
"subnet": { "subnet": {
"type": "string" "type": "string"
},
"subnetV6": {
"type": "string"
} }
} }
}, },

View File

@ -1407,14 +1407,14 @@
"ApiKeyAuth": [] "ApiKeyAuth": []
} }
], ],
"description": "修改 docker 日志配置", "description": "修改 docker 配置信息",
"consumes": [ "consumes": [
"application/json" "application/json"
], ],
"tags": [ "tags": [
"Container Docker" "Container Docker"
], ],
"summary": "Update docker daemon.json log option", "summary": "Update docker daemon.json",
"parameters": [ "parameters": [
{ {
"description": "request", "description": "request",
@ -1422,7 +1422,7 @@
"in": "body", "in": "body",
"required": true, "required": true,
"schema": { "schema": {
"$ref": "#/definitions/dto.LogOption" "$ref": "#/definitions/dto.SettingUpdate"
} }
} }
], ],
@ -1433,9 +1433,12 @@
}, },
"x-panel-log": { "x-panel-log": {
"BeforeFunctions": [], "BeforeFunctions": [],
"bodyKeys": [], "bodyKeys": [
"formatEN": "Updated the docker daemon.json log option", "key",
"formatZH": "更新 docker daemon.json 日志配置", "value"
],
"formatEN": "Updated the docker daemon.json configuration [key]=\u003e[value]",
"formatZH": "更新 docker daemon.json 配置 [key]=\u003e[value]",
"paramKeys": [] "paramKeys": []
} }
} }
@ -2022,6 +2025,46 @@
} }
} }
}, },
"/containers/ipv6option/update": {
"post": {
"security": [
{
"ApiKeyAuth": []
}
],
"description": "修改 docker ipv6 配置",
"consumes": [
"application/json"
],
"tags": [
"Container Docker"
],
"summary": "Update docker daemon.json ipv6 option",
"parameters": [
{
"description": "request",
"name": "request",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/dto.LogOption"
}
}
],
"responses": {
"200": {
"description": "OK"
}
},
"x-panel-log": {
"BeforeFunctions": [],
"bodyKeys": [],
"formatEN": "Updated the docker daemon.json ipv6 option",
"formatZH": "更新 docker daemon.json ipv6 配置",
"paramKeys": []
}
}
},
"/containers/limit": { "/containers/limit": {
"get": { "get": {
"security": [ "security": [
@ -2121,6 +2164,46 @@
} }
} }
}, },
"/containers/logoption/update": {
"post": {
"security": [
{
"ApiKeyAuth": []
}
],
"description": "修改 docker 日志配置",
"consumes": [
"application/json"
],
"tags": [
"Container Docker"
],
"summary": "Update docker daemon.json log option",
"parameters": [
{
"description": "request",
"name": "request",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/dto.LogOption"
}
}
],
"responses": {
"200": {
"description": "OK"
}
},
"x-panel-log": {
"BeforeFunctions": [],
"bodyKeys": [],
"formatEN": "Updated the docker daemon.json log option",
"formatZH": "更新 docker daemon.json 日志配置",
"paramKeys": []
}
}
},
"/containers/network": { "/containers/network": {
"get": { "get": {
"security": [ "security": [
@ -14016,15 +14099,27 @@
"cgroupDriver": { "cgroupDriver": {
"type": "string" "type": "string"
}, },
"experimental": {
"type": "boolean"
},
"fixedCidrV6": {
"type": "string"
},
"insecureRegistries": { "insecureRegistries": {
"type": "array", "type": "array",
"items": { "items": {
"type": "string" "type": "string"
} }
}, },
"ip6Tables": {
"type": "boolean"
},
"iptables": { "iptables": {
"type": "boolean" "type": "boolean"
}, },
"ipv6": {
"type": "boolean"
},
"isSwarm": { "isSwarm": {
"type": "boolean" "type": "boolean"
}, },
@ -15523,15 +15618,39 @@
"name" "name"
], ],
"properties": { "properties": {
"auxAddress": {
"type": "array",
"items": {
"$ref": "#/definitions/dto.SettingUpdate"
}
},
"auxAddressV6": {
"type": "array",
"items": {
"$ref": "#/definitions/dto.SettingUpdate"
}
},
"driver": { "driver": {
"type": "string" "type": "string"
}, },
"gateway": { "gateway": {
"type": "string" "type": "string"
}, },
"gatewayV6": {
"type": "string"
},
"ipRange": { "ipRange": {
"type": "string" "type": "string"
}, },
"ipRangeV6": {
"type": "string"
},
"ipv4": {
"type": "boolean"
},
"ipv6": {
"type": "boolean"
},
"labels": { "labels": {
"type": "array", "type": "array",
"items": { "items": {
@ -15549,6 +15668,9 @@
}, },
"subnet": { "subnet": {
"type": "string" "type": "string"
},
"subnetV6": {
"type": "string"
} }
} }
}, },

View File

@ -678,12 +678,20 @@ definitions:
properties: properties:
cgroupDriver: cgroupDriver:
type: string type: string
experimental:
type: boolean
fixedCidrV6:
type: string
insecureRegistries: insecureRegistries:
items: items:
type: string type: string
type: array type: array
ip6Tables:
type: boolean
iptables: iptables:
type: boolean type: boolean
ipv6:
type: boolean
isSwarm: isSwarm:
type: boolean type: boolean
liveRestore: liveRestore:
@ -1694,12 +1702,28 @@ definitions:
type: object type: object
dto.NetworkCreate: dto.NetworkCreate:
properties: properties:
auxAddress:
items:
$ref: '#/definitions/dto.SettingUpdate'
type: array
auxAddressV6:
items:
$ref: '#/definitions/dto.SettingUpdate'
type: array
driver: driver:
type: string type: string
gateway: gateway:
type: string type: string
gatewayV6:
type: string
ipRange: ipRange:
type: string type: string
ipRangeV6:
type: string
ipv4:
type: boolean
ipv6:
type: boolean
labels: labels:
items: items:
type: string type: string
@ -1712,6 +1736,8 @@ definitions:
type: array type: array
subnet: subnet:
type: string type: string
subnetV6:
type: string
required: required:
- driver - driver
- name - name
@ -5426,27 +5452,29 @@ 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.LogOption' $ref: '#/definitions/dto.SettingUpdate'
responses: responses:
"200": "200":
description: OK description: OK
security: security:
- ApiKeyAuth: [] - ApiKeyAuth: []
summary: Update docker daemon.json log option summary: Update docker daemon.json
tags: tags:
- Container Docker - Container Docker
x-panel-log: x-panel-log:
BeforeFunctions: [] BeforeFunctions: []
bodyKeys: [] bodyKeys:
formatEN: Updated the docker daemon.json log option - key
formatZH: 更新 docker daemon.json 日志配置 - value
formatEN: Updated the docker daemon.json configuration [key]=>[value]
formatZH: 更新 docker daemon.json 配置 [key]=>[value]
paramKeys: [] paramKeys: []
/containers/daemonjson/update/byfile: /containers/daemonjson/update/byfile:
post: post:
@ -5820,6 +5848,32 @@ paths:
summary: Container inspect summary: Container inspect
tags: tags:
- Container - Container
/containers/ipv6option/update:
post:
consumes:
- application/json
description: 修改 docker ipv6 配置
parameters:
- description: request
in: body
name: request
required: true
schema:
$ref: '#/definitions/dto.LogOption'
responses:
"200":
description: OK
security:
- ApiKeyAuth: []
summary: Update docker daemon.json ipv6 option
tags:
- Container Docker
x-panel-log:
BeforeFunctions: []
bodyKeys: []
formatEN: Updated the docker daemon.json ipv6 option
formatZH: 更新 docker daemon.json ipv6 配置
paramKeys: []
/containers/limit: /containers/limit:
get: get:
description: 获取容器限制 description: 获取容器限制
@ -5879,6 +5933,32 @@ paths:
summary: Load container log summary: Load container log
tags: tags:
- Container - Container
/containers/logoption/update:
post:
consumes:
- application/json
description: 修改 docker 日志配置
parameters:
- description: request
in: body
name: request
required: true
schema:
$ref: '#/definitions/dto.LogOption'
responses:
"200":
description: OK
security:
- ApiKeyAuth: []
summary: Update docker daemon.json log option
tags:
- Container Docker
x-panel-log:
BeforeFunctions: []
bodyKeys: []
formatEN: Updated the docker daemon.json log option
formatZH: 更新 docker daemon.json 日志配置
paramKeys: []
/containers/network: /containers/network:
get: get:
consumes: consumes:

View File

@ -297,6 +297,12 @@ export namespace Container {
liveRestore: boolean; liveRestore: boolean;
iptables: boolean; iptables: boolean;
cgroupDriver: string; cgroupDriver: string;
ipv6: boolean;
fixedCidrV6: string;
ip6Tables: boolean;
experimental: boolean;
logMaxSize: string; logMaxSize: string;
logMaxFile: string; logMaxFile: string;
} }

View File

@ -179,6 +179,13 @@ export const updateDaemonJson = (key: string, value: string) => {
export const updateLogOption = (maxSize: string, maxFile: string) => { export const updateLogOption = (maxSize: string, maxFile: string) => {
return http.post(`/containers/logoption/update`, { logMaxSize: maxSize, logMaxFile: maxFile }, TimeoutEnum.T_60S); return http.post(`/containers/logoption/update`, { logMaxSize: maxSize, logMaxFile: maxFile }, TimeoutEnum.T_60S);
}; };
export const updateIpv6Option = (fixedCidrV6: string, ip6Tables: boolean, experimental: boolean) => {
return http.post(
`/containers/ipv6option/update`,
{ fixedCidrV6: fixedCidrV6, ip6Tables: ip6Tables, experimental: experimental },
TimeoutEnum.T_60S,
);
};
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);
}; };

View File

@ -190,6 +190,7 @@ const message = {
leechExts: 'Only support letters, numbers and,', leechExts: 'Only support letters, numbers and,',
paramSimple: 'Support lowercase letters and numbers, length 1-128', paramSimple: 'Support lowercase letters and numbers, length 1-128',
filePermission: 'File Permission Error', filePermission: 'File Permission Error',
formatErr: 'Format error, please check and retry',
}, },
res: { res: {
paramError: 'The request failed, please try again later!', paramError: 'The request failed, please try again later!',
@ -658,6 +659,7 @@ const message = {
subnet: 'Subnet', subnet: 'Subnet',
scope: 'IP Scope', scope: 'IP Scope',
gateway: 'Gateway', gateway: 'Gateway',
auxAddress: 'Exclude IP',
volume: 'Volume', volume: 'Volume',
volumeDir: 'Volume dir', volumeDir: 'Volume dir',
@ -716,6 +718,12 @@ const message = {
'The acceleration URL is preferred to perform operations. If this parameter is set to empty, mirror acceleration is disabled.', 'The acceleration URL is preferred to perform operations. If this parameter is set to empty, mirror acceleration is disabled.',
mirrorsHelper2: 'For details, see the official documents, ', mirrorsHelper2: 'For details, see the official documents, ',
registries: 'Insecure registries', registries: 'Insecure registries',
ipv6Helper:
'When enabling IPv6, you need to add an IPv6 container network. Refer to the official documentation for specific configuration steps.',
ipv6CidrHelper: 'IPv6 address pool range for containers',
ipv6TablesHelper: 'Automatic configuration of Docker IPv6 for iptables rules',
experimentalHelper:
'Enabling ip6tables requires this configuration to be turned on; otherwise, ip6tables will be ignored',
cutLog: 'Log option', cutLog: 'Log option',
cutLogHelper1: 'The current configuration will only affect newly created containers.', cutLogHelper1: 'The current configuration will only affect newly created containers.',
cutLogHelper2: 'Existing containers need to be recreated for the configuration to take effect.', cutLogHelper2: 'Existing containers need to be recreated for the configuration to take effect.',

View File

@ -189,6 +189,7 @@ const message = {
leechExts: ',', leechExts: ',',
paramSimple: ', 1-128', paramSimple: ', 1-128',
filePermission: '', filePermission: '',
formatErr: '',
}, },
res: { res: {
paramError: ',!', paramError: ',!',
@ -642,6 +643,7 @@ const message = {
subnet: '', subnet: '',
scope: 'IP ', scope: 'IP ',
gateway: '', gateway: '',
auxAddress: ' IP',
volume: '', volume: '',
volumeDir: '', volumeDir: '',
@ -692,6 +694,10 @@ const message = {
mirrorsHelper: '使 URL ', mirrorsHelper: '使 URL ',
mirrorsHelper2: '', mirrorsHelper2: '',
registries: '', registries: '',
ipv6Helper: ' IPv6 IPv6 ',
ipv6CidrHelper: ' IPv6 ',
ipv6TablesHelper: 'Docker IPv6 iptables ',
experimentalHelper: ' ip6tables ip6tables ',
cutLog: '', cutLog: '',
cutLogHelper1: '', cutLogHelper1: '',
cutLogHelper2: '使', cutLogHelper2: '使',

View File

@ -189,6 +189,7 @@ const message = {
leechExts: ',', leechExts: ',',
paramSimple: ',1-128', paramSimple: ',1-128',
filePermission: '', filePermission: '',
formatErr: '',
}, },
res: { res: {
paramError: ',!', paramError: ',!',
@ -643,6 +644,7 @@ const message = {
subnet: '', subnet: '',
scope: 'IP ', scope: 'IP ',
gateway: '', gateway: '',
auxAddress: ' IP',
volume: '', volume: '',
volumeDir: '', volumeDir: '',
@ -693,6 +695,10 @@ const message = {
mirrorsHelper: '使 URL ', mirrorsHelper: '使 URL ',
mirrorsHelper2: '', mirrorsHelper2: '',
registries: '', registries: '',
ipv6Helper: ' IPv6 IPv6 ',
ipv6CidrHelper: ' IPv6 ',
ipv6TablesHelper: 'Docker IPv6 iptables ',
experimentalHelper: ' ip6tables ip6tables ',
cutLog: '', cutLog: '',
cutLogHelper1: '', cutLogHelper1: '',
cutLogHelper2: '使', cutLogHelper2: '使',

View File

@ -247,6 +247,33 @@ export function checkIpV4V6(value: string): boolean {
} }
} }
export function checkIpV6(value: string): boolean {
if (value === '' || typeof value === 'undefined' || value == null) {
return true;
} else {
const IPv4SegmentFormat = '(?:[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])';
const IPv4AddressFormat = `(${IPv4SegmentFormat}[.]){3}${IPv4SegmentFormat}`;
const IPv6SegmentFormat = '(?:[0-9a-fA-F]{1,4})';
const IPv6AddressRegExp = new RegExp(
'^(' +
`(?:${IPv6SegmentFormat}:){7}(?:${IPv6SegmentFormat}|:)|` +
`(?:${IPv6SegmentFormat}:){6}(?:${IPv4AddressFormat}|:${IPv6SegmentFormat}|:)|` +
`(?:${IPv6SegmentFormat}:){5}(?::${IPv4AddressFormat}|(:${IPv6SegmentFormat}){1,2}|:)|` +
`(?:${IPv6SegmentFormat}:){4}(?:(:${IPv6SegmentFormat}){0,1}:${IPv4AddressFormat}|(:${IPv6SegmentFormat}){1,3}|:)|` +
`(?:${IPv6SegmentFormat}:){3}(?:(:${IPv6SegmentFormat}){0,2}:${IPv4AddressFormat}|(:${IPv6SegmentFormat}){1,4}|:)|` +
`(?:${IPv6SegmentFormat}:){2}(?:(:${IPv6SegmentFormat}){0,3}:${IPv4AddressFormat}|(:${IPv6SegmentFormat}){1,5}|:)|` +
`(?:${IPv6SegmentFormat}:){1}(?:(:${IPv6SegmentFormat}){0,4}:${IPv4AddressFormat}|(:${IPv6SegmentFormat}){1,6}|:)|` +
`(?::((?::${IPv6SegmentFormat}){0,5}:${IPv4AddressFormat}|(?::${IPv6SegmentFormat}){1,7}|:))` +
')(%[0-9a-zA-Z-.:]{1,})?$',
);
if (!IPv6AddressRegExp.test(value) && value !== '') {
return true;
} else {
return false;
}
}
}
export function checkCidr(value: string): boolean { export function checkCidr(value: string): boolean {
if (value === '') { if (value === '') {
return true; return true;

View File

@ -1,5 +1,5 @@
<template> <template>
<el-drawer v-model="drawerVisible" :destroy-on-close="true" :close-on-click-modal="false" size="30%"> <el-drawer v-model="drawerVisible" :destroy-on-close="true" :close-on-click-modal="false" size="50%">
<template #header> <template #header>
<DrawerHeader :header="$t('container.createNetwork')" :back="handleClose" /> <DrawerHeader :header="$t('container.createNetwork')" :back="handleClose" />
</template> </template>
@ -17,6 +17,99 @@
<el-option label="overlay" value="overlay" /> <el-option label="overlay" value="overlay" />
</el-select> </el-select>
</el-form-item> </el-form-item>
<el-checkbox v-model="form.ipv4">IPv4</el-checkbox>
<div v-if="form.ipv4">
<el-row type="flex" justify="center" :gutter="20">
<el-col :span="12">
<el-form-item :label="$t('container.subnet')" prop="subnet">
<el-input placeholder="172.16.10.0/24" clearable v-model.trim="form.subnet" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item :label="$t('container.gateway')" prop="gateway">
<el-input placeholder="172.16.10.12" clearable v-model.trim="form.gateway" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item :label="$t('container.scope')" prop="scope">
<el-input placeholder="172.16.10.0/16" clearable v-model.trim="form.scope" />
</el-form-item>
</el-col>
<el-col :span="12"></el-col>
</el-row>
<el-form-item :label="$t('container.auxAddress')" prop="scopeV6">
<el-table :data="form.auxAddress" v-if="form.auxAddress.length !== 0">
<el-table-column :label="$t('container.label')" min-width="100">
<template #default="{ row }">
<el-input placeholder="my-router" v-model="row.key" />
</template>
</el-table-column>
<el-table-column label="IP" min-width="150">
<template #default="{ row }">
<el-input placeholder="172.16.10.13" v-model="row.value" />
</template>
</el-table-column>
<el-table-column min-width="40">
<template #default="scope">
<el-button link type="primary" @click="handleV4Delete(scope.$index)">
{{ $t('commons.button.delete') }}
</el-button>
</template>
</el-table-column>
</el-table>
<el-button class="mt-2" @click="handleV4Add()">
{{ $t('commons.button.add') }}
</el-button>
</el-form-item>
</div>
<el-checkbox class="mb-4" v-model="form.ipv6">IPv6</el-checkbox>
<div v-if="form.ipv6">
<el-row type="flex" justify="center" :gutter="20">
<el-col :span="12">
<el-form-item :label="$t('container.subnet')" prop="subnetV6">
<el-input placeholder="2408:400e::/48" clearable v-model.trim="form.subnetV6" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item :label="$t('container.gateway')" prop="gatewayV6">
<el-input placeholder="2408:400e::1" clearable v-model.trim="form.gatewayV6" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item :label="$t('container.scope')" prop="scopeV6">
<el-input placeholder="2408:400e::/64" clearable v-model.trim="form.scopeV6" />
</el-form-item>
</el-col>
<el-col :span="12"></el-col>
</el-row>
<el-form-item :label="$t('container.auxAddress')" prop="scopeV6">
<el-table :data="form.auxAddressV6" v-if="form.auxAddressV6.length !== 0">
<el-table-column :label="$t('container.label')" min-width="100">
<template #default="{ row }">
<el-input placeholder="my-router" v-model="row.key" />
</template>
</el-table-column>
<el-table-column label="IP" min-width="150">
<template #default="{ row }">
<el-input placeholder="2408:400e::3" v-model="row.value" />
</template>
</el-table-column>
<el-table-column min-width="40">
<template #default="scope">
<el-button link type="primary" @click="handleV6Delete(scope.$index)">
{{ $t('commons.button.delete') }}
</el-button>
</template>
</el-table-column>
</el-table>
<el-button class="mt-2" @click="handleV6Add()">
{{ $t('commons.button.add') }}
</el-button>
</el-form-item>
</div>
<el-form-item :label="$t('container.option')" prop="optionStr"> <el-form-item :label="$t('container.option')" prop="optionStr">
<el-input <el-input
type="textarea" type="textarea"
@ -25,15 +118,6 @@
v-model="form.optionStr" v-model="form.optionStr"
/> />
</el-form-item> </el-form-item>
<el-form-item :label="$t('container.subnet')" prop="subnet">
<el-input clearable v-model.trim="form.subnet" />
</el-form-item>
<el-form-item :label="$t('container.gateway')" prop="gateway">
<el-input clearable v-model.trim="form.gateway" />
</el-form-item>
<el-form-item :label="$t('container.scope')" prop="scope">
<el-input clearable v-model.trim="form.scope" />
</el-form-item>
<el-form-item :label="$t('container.tag')" prop="labelStr"> <el-form-item :label="$t('container.tag')" prop="labelStr">
<el-input <el-input
type="textarea" type="textarea"
@ -66,6 +150,7 @@ import { ElForm } from 'element-plus';
import { createNetwork } from '@/api/modules/container'; import { createNetwork } from '@/api/modules/container';
import DrawerHeader from '@/components/drawer-header/index.vue'; import DrawerHeader from '@/components/drawer-header/index.vue';
import { MsgSuccess } from '@/utils/message'; import { MsgSuccess } from '@/utils/message';
import { checkIpV6 } from '@/utils/util';
const loading = ref(false); const loading = ref(false);
@ -77,9 +162,16 @@ const form = reactive({
optionStr: '', optionStr: '',
options: [] as Array<string>, options: [] as Array<string>,
driver: '', driver: '',
ipv4: true,
subnet: '', subnet: '',
gateway: '', gateway: '',
scope: '', scope: '',
auxAddress: [],
ipv6: false,
subnetV6: '',
gatewayV6: '',
scopeV6: '',
auxAddressV6: [],
}); });
const acceptParams = (): void => { const acceptParams = (): void => {
@ -88,10 +180,17 @@ const acceptParams = (): void => {
form.labels = []; form.labels = [];
form.optionStr = ''; form.optionStr = '';
form.options = []; form.options = [];
form.driver = ''; form.driver = 'bridge';
form.ipv4 = true;
form.subnet = ''; form.subnet = '';
form.gateway = ''; form.gateway = '';
form.scope = ''; form.scope = '';
form.auxAddress = [];
form.ipv6 = false;
form.subnetV6 = '';
form.gatewayV6 = '';
form.scopeV6 = '';
form.auxAddressV6 = [];
drawerVisible.value = true; drawerVisible.value = true;
}; };
const emit = defineEmits<{ (e: 'search'): void }>(); const emit = defineEmits<{ (e: 'search'): void }>();
@ -103,8 +202,65 @@ const handleClose = () => {
const rules = reactive({ const rules = reactive({
name: [Rules.requiredInput], name: [Rules.requiredInput],
driver: [Rules.requiredSelect], driver: [Rules.requiredSelect],
subnet: [{ validator: checkCidr, trigger: 'blur' }],
gateway: [Rules.ip],
scope: [{ validator: checkCidr, trigger: 'blur' }],
subnetV6: [{ validator: checkFixedCidrV6, trigger: 'blur' }],
gatewayV6: [Rules.ipV6],
scopeV6: [{ validator: checkFixedCidrV6, trigger: 'blur' }],
}); });
function checkCidr(rule: any, value: any, callback: any) {
if (value === '') {
callback();
}
const reg =
/^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)(?:\/([0-9]|[1-2][0-9]|3[0-2]))?$/;
if (!reg.test(value)) {
return callback(new Error(i18n.global.t('commons.rule.formatErr')));
}
callback();
}
function checkFixedCidrV6(rule: any, value: any, callback: any) {
if (value === '') {
callback();
}
if (!form.subnetV6 || form.subnetV6.indexOf('/') === -1) {
return callback(new Error(i18n.global.t('commons.rule.formatErr')));
}
if (checkIpV6(form.subnetV6.split('/')[0])) {
return callback(new Error(i18n.global.t('commons.rule.formatErr')));
}
const reg = /^(?:[1-9]|[1-9][0-9]|1[0-1][0-9]|12[0-8])$/;
if (!reg.test(form.subnetV6.split('/')[1])) {
return callback(new Error(i18n.global.t('commons.rule.formatErr')));
}
callback();
}
const handleV4Add = () => {
let item = {
key: '',
value: '',
};
form.auxAddress.push(item);
};
const handleV4Delete = (index: number) => {
form.auxAddress.splice(index, 1);
};
const handleV6Add = () => {
let item = {
key: '',
value: '',
};
form.auxAddressV6.push(item);
};
const handleV6Delete = (index: number) => {
form.auxAddressV6.splice(index, 1);
};
type FormInstance = InstanceType<typeof ElForm>; type FormInstance = InstanceType<typeof ElForm>;
const formRef = ref<FormInstance>(); const formRef = ref<FormInstance>();
const onSubmit = async (formEl: FormInstance | undefined) => { const onSubmit = async (formEl: FormInstance | undefined) => {

View File

@ -100,6 +100,19 @@
</el-input> </el-input>
</el-form-item> </el-form-item>
<el-form-item label="ipv6" prop="ipv6">
<el-switch v-model="form.ipv6" @change="handleIPv6"></el-switch>
<span class="input-help"></span>
<div v-if="ipv6OptionShow">
<el-tag>{{ $t('container.subnet') }}: {{ form.fixedCidrV6 }}</el-tag>
<div>
<el-button @click="handleIPv6" type="primary" link>
{{ $t('commons.button.view') }}
</el-button>
</div>
</div>
</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" @change="handleLogOption"></el-switch> <el-switch v-model="form.logOptionShow" @change="handleLogOption"></el-switch>
<span class="input-help"></span> <span class="input-help"></span>
@ -206,6 +219,8 @@
<Mirror ref="mirrorRef" @search="search" /> <Mirror ref="mirrorRef" @search="search" />
<Registry ref="registriesRef" @search="search" /> <Registry ref="registriesRef" @search="search" />
<LogOption ref="logOptionRef" @search="search" /> <LogOption ref="logOptionRef" @search="search" />
<Ipv6Option ref="ipv6OptionRef" @search="search" />
<ConfirmDialog ref="confirmDialogRefIpv6" @confirm="onSaveIPv6" @cancel="search" />
<ConfirmDialog ref="confirmDialogRefIptable" @confirm="onSubmitOpenIPtable" @cancel="search" /> <ConfirmDialog ref="confirmDialogRefIptable" @confirm="onSubmitOpenIPtable" @cancel="search" />
<ConfirmDialog ref="confirmDialogRefLog" @confirm="onSubmitSaveLog" @cancel="search" /> <ConfirmDialog ref="confirmDialogRefLog" @confirm="onSubmitSaveLog" @cancel="search" />
<ConfirmDialog ref="confirmDialogRefLive" @confirm="onSubmitSaveLive" @cancel="search" /> <ConfirmDialog ref="confirmDialogRefLive" @confirm="onSubmitSaveLive" @cancel="search" />
@ -224,6 +239,7 @@ import { oneDark } from '@codemirror/theme-one-dark';
import Mirror from '@/views/container/setting/mirror/index.vue'; import Mirror from '@/views/container/setting/mirror/index.vue';
import Registry from '@/views/container/setting/registry/index.vue'; import Registry from '@/views/container/setting/registry/index.vue';
import LogOption from '@/views/container/setting/log/index.vue'; import LogOption from '@/views/container/setting/log/index.vue';
import Ipv6Option from '@/views/container/setting/ipv6/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 {
@ -245,13 +261,16 @@ const extensions = [javascript(), oneDark];
const confShowType = ref('base'); const confShowType = ref('base');
const logOptionRef = ref(); const logOptionRef = ref();
const ipv6OptionRef = ref();
const confirmDialogRefLog = ref(); const confirmDialogRefLog = ref();
const mirrorRef = ref(); const mirrorRef = ref();
const registriesRef = ref(); const registriesRef = ref();
const confirmDialogRefLive = ref(); const confirmDialogRefLive = ref();
const confirmDialogRefCgroup = ref(); const confirmDialogRefCgroup = ref();
const confirmDialogRefIptable = ref(); const confirmDialogRefIptable = ref();
const confirmDialogRefIpv6 = ref();
const logOptionShow = ref(); const logOptionShow = ref();
const ipv6OptionShow = ref();
const form = reactive({ const form = reactive({
isSwarm: false, isSwarm: false,
@ -262,6 +281,12 @@ const form = reactive({
liveRestore: false, liveRestore: false,
iptables: true, iptables: true,
cgroupDriver: '', cgroupDriver: '',
ipv6: false,
fixedCidrV6: '',
ip6Tables: false,
experimental: false,
logOptionShow: false, logOptionShow: false,
logMaxSize: '', logMaxSize: '',
logMaxFile: 3, logMaxFile: 3,
@ -292,6 +317,27 @@ const onChangeMirrors = () => {
const onChangeRegistries = () => { const onChangeRegistries = () => {
registriesRef.value.acceptParams({ registries: form.registries }); registriesRef.value.acceptParams({ registries: form.registries });
}; };
const handleIPv6 = async () => {
if (form.ipv6) {
ipv6OptionRef.value.acceptParams({
fixedCidrV6: form.fixedCidrV6,
ip6Tables: form.ip6Tables,
experimental: form.experimental,
});
return;
}
let params = {
header: i18n.global.t('database.confChange'),
operationInfo: i18n.global.t('database.restartNowHelper'),
submitInputInfo: i18n.global.t('database.restartNow'),
};
confirmDialogRefIpv6.value!.acceptParams(params);
};
const onSaveIPv6 = () => {
save('Ipv6', 'disable');
};
const handleLogOption = async () => { const handleLogOption = async () => {
if (form.logOptionShow) { if (form.logOptionShow) {
logOptionRef.value.acceptParams({ logMaxSize: form.logMaxSize, logMaxFile: form.logMaxFile }); logOptionRef.value.acceptParams({ logMaxSize: form.logMaxSize, logMaxFile: form.logMaxFile });
@ -445,6 +491,11 @@ const search = async () => {
form.logOptionShow = false; form.logOptionShow = false;
logOptionShow.value = false; logOptionShow.value = false;
} }
form.ipv6 = res.data.ipv6;
ipv6OptionShow.value = form.ipv6;
form.fixedCidrV6 = res.data.fixedCidrV6;
form.ip6Tables = res.data.ip6Tables;
form.experimental = res.data.experimental;
}; };
onMounted(() => { onMounted(() => {

View File

@ -0,0 +1,157 @@
<template>
<div>
<el-drawer
v-model="drawerVisible"
:destroy-on-close="true"
:close-on-click-modal="false"
@close="handleClose"
size="30%"
>
<template #header>
<DrawerHeader header="IPv6" :back="handleClose" />
</template>
<el-alert class="common-prompt" :closable="false" type="warning">
<template #default>
<span class="input-help">
{{ $t('container.ipv6Helper') }}
<el-link
style="font-size: 12px; margin-left: 5px"
icon="Position"
@click="toDoc()"
type="primary"
>
{{ $t('firewall.quickJump') }}
</el-link>
</span>
</template>
</el-alert>
<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="fixedCidrV6" :label="$t('container.subnet')">
<el-input v-model="form.fixedCidrV6" />
<span class="input-help">{{ $t('container.ipv6CidrHelper') }}</span>
</el-form-item>
<el-form-item>
<el-checkbox v-model="showMore" :label="$t('app.advanced')" />
</el-form-item>
<div v-if="showMore">
<el-form-item prop="ip6Tables" label="ip6tables">
<el-switch v-model="form.ip6Tables"></el-switch>
<span class="input-help">{{ $t('container.ipv6TablesHelper') }}</span>
</el-form-item>
<el-form-item prop="experimental" label="experimental">
<el-switch v-model="form.experimental"></el-switch>
<span class="input-help">{{ $t('container.experimentalHelper') }}</span>
</el-form-item>
</div>
</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 i18n from '@/lang';
import { MsgSuccess } from '@/utils/message';
import { FormInstance } from 'element-plus';
import { updateIpv6Option } from '@/api/modules/container';
import DrawerHeader from '@/components/drawer-header/index.vue';
import { checkIpV6 } from '@/utils/util';
const loading = ref();
const drawerVisible = ref();
const confirmDialogRef = ref();
const formRef = ref();
const showMore = ref(false);
interface DialogProps {
fixedCidrV6: string;
ip6Tables: boolean;
experimental: boolean;
}
const form = reactive({
fixedCidrV6: '',
ip6Tables: false,
experimental: false,
});
const rules = reactive({
fixedCidrV6: [{ validator: checkFixedCidrV6, trigger: 'blur', required: true }],
});
function checkFixedCidrV6(rule: any, value: any, callback: any) {
if (!form.fixedCidrV6 || form.fixedCidrV6.indexOf('/') === -1) {
return callback(new Error(i18n.global.t('commons.rule.formatErr')));
}
if (checkIpV6(form.fixedCidrV6.split('/')[0])) {
return callback(new Error(i18n.global.t('commons.rule.formatErr')));
}
const reg = /^(?:[1-9]|[1-9][0-9]|1[0-1][0-9]|12[0-8])$/;
if (!reg.test(form.fixedCidrV6.split('/')[1])) {
return callback(new Error(i18n.global.t('commons.rule.formatErr')));
}
callback();
}
const toDoc = () => {
window.open('https://1panel.cn/docs/user_manual/containers/setting/', '_blank', 'noopener,noreferrer');
};
const emit = defineEmits<{ (e: 'search'): void }>();
const acceptParams = (params: DialogProps): void => {
form.fixedCidrV6 = params.fixedCidrV6;
form.ip6Tables = params.ip6Tables;
form.experimental = params.experimental;
drawerVisible.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 updateIpv6Option(form.fixedCidrV6, form.ip6Tables, form.experimental)
.then(() => {
loading.value = false;
drawerVisible.value = false;
emit('search');
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
})
.catch(() => {
loading.value = false;
});
};
const handleClose = () => {
emit('search');
drawerVisible.value = false;
};
defineExpose({
acceptParams,
});
</script>