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
// @Description 修改 docker 配置信息
// @Accept json
// @Param request body dto.DaemonJsonConf true "request"
// @Param request body dto.SettingUpdate 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 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) {
var req dto.DaemonJsonConf
var req dto.SettingUpdate
if err := c.ShouldBindJSON(&req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
@ -78,6 +78,30 @@ func (b *BaseApi) UpdateDaemonJson(c *gin.Context) {
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
// @Summary Update docker daemon.json by upload file
// @Description 上传替换 docker 配置文件

5
backend/app/dto/docker.go

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

116
backend/app/service/docker.go

@ -4,6 +4,7 @@ import (
"bufio"
"context"
"encoding/json"
"fmt"
"os"
"path"
"strings"
@ -18,7 +19,8 @@ import (
type DockerService struct{}
type IDockerService interface {
UpdateConf(req dto.DaemonJsonConf) error
UpdateConf(req dto.SettingUpdate) error
UpdateLogOption(req dto.LogOption) error
UpdateConfByFile(info dto.DaemonJsonUpdateByFile) error
LoadDockerStatus() string
LoadDockerConf() *dto.DaemonJsonConf
@ -95,6 +97,7 @@ func (u *DockerService) LoadDockerConf() *dto.DaemonJsonConf {
return &data
}
if err := json.Unmarshal(arr, &conf); err != nil {
fmt.Println(err)
return &data
}
if _, ok := deamonMap["iptables"]; !ok {
@ -116,7 +119,7 @@ func (u *DockerService) LoadDockerConf() *dto.DaemonJsonConf {
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.MkdirAll(path.Dir(constant.DaemonJsonPath), os.ModePerm); err != nil {
return err
@ -131,45 +134,88 @@ func (u *DockerService) UpdateConf(req dto.DaemonJsonConf) error {
deamonMap := make(map[string]interface{})
_ = json.Unmarshal(file, &deamonMap)
if len(req.Registries) == 0 {
delete(deamonMap, "insecure-registries")
} else {
deamonMap["insecure-registries"] = req.Registries
}
if len(req.Mirrors) == 0 {
delete(deamonMap, "registry-mirrors")
} else {
deamonMap["registry-mirrors"] = req.Mirrors
}
changeLogOption(deamonMap, req.LogMaxFile, req.LogMaxSize)
if !req.LiveRestore {
delete(deamonMap, "live-restore")
} else {
deamonMap["live-restore"] = req.LiveRestore
}
if req.IPTables {
delete(deamonMap, "iptables")
} else {
deamonMap["iptables"] = false
}
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.CgroupDriver
break
switch req.Key {
case "Registries":
if len(req.Value) == 0 {
delete(deamonMap, "insecure-registries")
} else {
deamonMap["insecure-registries"] = strings.Split(req.Value, ",")
}
case "Mirrors":
if len(req.Value) == 0 {
delete(deamonMap, "registry-mirrors")
} else {
deamonMap["registry-mirrors"] = strings.Split(req.Value, ",")
}
case "LogOption":
if req.Value == "disable" {
delete(deamonMap, "log-opts")
}
case "LiveRestore":
if req.Value == "disable" {
delete(deamonMap, "live-restore")
} else {
deamonMap["live-restore"] = true
}
case "IPtables":
if req.Value == "enable" {
delete(deamonMap, "iptables")
} else {
deamonMap["iptables"] = false
}
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" {
deamonMap["exec-opts"] = []string{"native.cgroupdriver=systemd"}
}
if len(deamonMap) == 0 {
_ = 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 {
_ = os.Remove(constant.DaemonJsonPath)
return nil

6
backend/app/service/firewall.go

@ -379,7 +379,7 @@ func (u *FirewallService) pingStatus() string {
return constant.StatusDisable
}
func (u *FirewallService) updatePingStatus(enabel string) error {
func (u *FirewallService) updatePingStatus(enable string) error {
lineBytes, err := os.ReadFile(confPath)
if err != nil {
return err
@ -389,14 +389,14 @@ func (u *FirewallService) updatePingStatus(enabel string) error {
hasLine := false
for _, line := range files {
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
} else {
newFiles = append(newFiles, line)
}
}
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)
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.POST("/docker/operate", baseApi.OperateDocker)
baRouter.POST("/daemonjson/update", baseApi.UpdateDaemonJson)
baRouter.POST("/logoption/update", baseApi.UpdateLogOption)
baRouter.POST("/daemonjson/update/byfile", baseApi.UpdateDaemonJsonByFile)
}
}

177
cmd/server/docs/docs.go

@ -1241,14 +1241,14 @@ var doc = `{
"ApiKeyAuth": []
}
],
"description": "修改 docker 配置信息",
"description": "修改 docker 日志配置",
"consumes": [
"application/json"
],
"tags": [
"Container Docker"
],
"summary": "Update docker daemon.json",
"summary": "Update docker daemon.json log option",
"parameters": [
{
"description": "request",
@ -1256,7 +1256,7 @@ var doc = `{
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/dto.DaemonJsonConf"
"$ref": "#/definitions/dto.LogOption"
}
}
],
@ -1268,8 +1268,8 @@ var doc = `{
"x-panel-log": {
"BeforeFuntions": [],
"bodyKeys": [],
"formatEN": "Updated the docker daemon.json configuration",
"formatZH": "更新 docker daemon.json 配置",
"formatEN": "Updated the docker daemon.json log option",
"formatZH": "更新 docker daemon.json 日志配置",
"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": {
"get": {
"security": [
@ -10691,6 +10757,9 @@ var doc = `{
"script": {
"type": "string"
},
"second": {
"type": "integer"
},
"sourceDir": {
"type": "string"
},
@ -10770,6 +10839,9 @@ var doc = `{
"script": {
"type": "string"
},
"second": {
"type": "integer"
},
"sourceDir": {
"type": "string"
},
@ -11531,6 +11603,17 @@ var doc = `{
}
}
},
"dto.LogOption": {
"type": "object",
"properties": {
"logMaxFile": {
"type": "string"
},
"logMaxSize": {
"type": "string"
}
}
},
"dto.Login": {
"type": "object",
"properties": {
@ -12501,6 +12584,9 @@ var doc = `{
"mfaStatus": {
"type": "string"
},
"monitorInterval": {
"type": "string"
},
"monitorStatus": {
"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": {
"type": "object",
"required": [
@ -13671,6 +13804,17 @@ var doc = `{
}
}
},
"request.NginxCommonReq": {
"type": "object",
"required": [
"websiteID"
],
"properties": {
"websiteID": {
"type": "integer"
}
}
},
"request.NginxConfigFileUpdate": {
"type": "object",
"required": [
@ -14198,9 +14342,15 @@ var doc = `{
"type": "object",
"required": [
"id",
"params"
"scope"
],
"properties": {
"disableFunctions": {
"type": "array",
"items": {
"type": "string"
}
},
"id": {
"type": "integer"
},
@ -14209,6 +14359,12 @@ var doc = `{
"additionalProperties": {
"type": "string"
}
},
"scope": {
"type": "string"
},
"uploadMaxSize": {
"type": "string"
}
}
},
@ -14806,11 +14962,20 @@ var doc = `{
"response.PHPConfig": {
"type": "object",
"properties": {
"disableFunctions": {
"type": "array",
"items": {
"type": "string"
}
},
"params": {
"type": "object",
"additionalProperties": {
"type": "string"
}
},
"uploadMaxSize": {
"type": "string"
}
}
},

177
cmd/server/docs/swagger.json

@ -1227,14 +1227,14 @@
"ApiKeyAuth": []
}
],
"description": "修改 docker 配置信息",
"description": "修改 docker 日志配置",
"consumes": [
"application/json"
],
"tags": [
"Container Docker"
],
"summary": "Update docker daemon.json",
"summary": "Update docker daemon.json log option",
"parameters": [
{
"description": "request",
@ -1242,7 +1242,7 @@
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/dto.DaemonJsonConf"
"$ref": "#/definitions/dto.LogOption"
}
}
],
@ -1254,8 +1254,8 @@
"x-panel-log": {
"BeforeFuntions": [],
"bodyKeys": [],
"formatEN": "Updated the docker daemon.json configuration",
"formatZH": "更新 docker daemon.json 配置",
"formatEN": "Updated the docker daemon.json log option",
"formatZH": "更新 docker daemon.json 日志配置",
"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": {
"get": {
"security": [
@ -10677,6 +10743,9 @@
"script": {
"type": "string"
},
"second": {
"type": "integer"
},
"sourceDir": {
"type": "string"
},
@ -10756,6 +10825,9 @@
"script": {
"type": "string"
},
"second": {
"type": "integer"
},
"sourceDir": {
"type": "string"
},
@ -11517,6 +11589,17 @@
}
}
},
"dto.LogOption": {
"type": "object",
"properties": {
"logMaxFile": {
"type": "string"
},
"logMaxSize": {
"type": "string"
}
}
},
"dto.Login": {
"type": "object",
"properties": {
@ -12487,6 +12570,9 @@
"mfaStatus": {
"type": "string"
},
"monitorInterval": {
"type": "string"
},
"monitorStatus": {
"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": {
"type": "object",
"required": [
@ -13657,6 +13790,17 @@
}
}
},
"request.NginxCommonReq": {
"type": "object",
"required": [
"websiteID"
],
"properties": {
"websiteID": {
"type": "integer"
}
}
},
"request.NginxConfigFileUpdate": {
"type": "object",
"required": [
@ -14184,9 +14328,15 @@
"type": "object",
"required": [
"id",
"params"
"scope"
],
"properties": {
"disableFunctions": {
"type": "array",
"items": {
"type": "string"
}
},
"id": {
"type": "integer"
},
@ -14195,6 +14345,12 @@
"additionalProperties": {
"type": "string"
}
},
"scope": {
"type": "string"
},
"uploadMaxSize": {
"type": "string"
}
}
},
@ -14792,11 +14948,20 @@
"response.PHPConfig": {
"type": "object",
"properties": {
"disableFunctions": {
"type": "array",
"items": {
"type": "string"
}
},
"params": {
"type": "object",
"additionalProperties": {
"type": "string"
}
},
"uploadMaxSize": {
"type": "string"
}
}
},

118
cmd/server/docs/swagger.yaml

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

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

@ -148,8 +148,11 @@ export const loadDaemonJsonFile = () => {
export const loadDockerStatus = () => {
return http.get<string>(`/containers/docker/status`);
};
export const updateDaemonJson = (params: Container.DaemonJsonConf) => {
return http.post(`/containers/daemonjson/update`, params);
export const updateDaemonJson = (key: string, value: string) => {
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) => {
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',
liveWithSwarmHelper: 'live-restore daemon configuration is incompatible with swarm mode.',
iptablesDisable: 'Close iptables',
iptablesHelper1: 'This setting will disable Docker automatic configuration of iptables rules.',
iptablesHelper1: 'Automatic configuration of iptables rules for Docker.',
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',
serviceUnavailable: 'Docker service is not started at present, please click',
startIn: ' to start',

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

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

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

@ -10,7 +10,7 @@
<el-input clearable v-model.trim="form.name" />
</el-form-item>
<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
v-for="(item, index) of images"
:key="index"
@ -26,7 +26,7 @@
</el-radio-group>
</el-form-item>
<el-form-item v-if="!form.publishAllPorts">
<el-card style="width: 100%">
<el-card class="widthClass">
<table style="width: 100%" class="tab-table">
<tr v-if="form.exposedPorts.length !== 0">
<th scope="col" width="45%" align="left">
@ -107,21 +107,21 @@
<el-card style="width: 100%">
<table style="width: 100%" class="tab-table">
<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>
</th>
<th scope="col" width="12%" align="left">
<th scope="col" width="18%" align="left">
<label>{{ $t('container.mode') }}</label>
</th>
<th scope="col" width="42%" align="left">
<th scope="col" width="39%" align="left">
<label>{{ $t('container.containerDir') }}</label>
</th>
<th align="left"></th>
</tr>
<tr v-for="(row, index) in form.volumes" :key="index">
<td width="42%">
<td width="39%">
<el-select
style="width: 100%"
class="widthClass"
allow-create
clearable
:placeholder="$t('commons.msg.inputOrSelect')"
@ -136,13 +136,13 @@
/>
</el-select>
</td>
<td width="12%">
<el-select style="width: 100%" filterable v-model="row.mode">
<td width="18%">
<el-select class="widthClass" filterable v-model="row.mode">
<el-option value="rw" :label="$t('container.modeRW')" />
<el-option value="ro" :label="$t('container.modeR')" />
</el-select>
</td>
<td width="42%">
<td width="39%">
<el-input v-model="row.containerDir" />
</td>
<td>
@ -416,3 +416,9 @@ defineExpose({
acceptParams,
});
</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-form :model="form" label-position="left" :rules="rules" ref="formRef" label-width="150px">
<el-form-item :label="$t('container.mirrors')" prop="mirrors">
<el-input
type="textarea"
:placeholder="$t('container.mirrorHelper')"
:autosize="{ minRows: 3, maxRows: 10 }"
v-model="form.mirrors"
/>
<div style="width: 100%">
<el-input
type="textarea"
:autosize="{ minRows: 3, maxRows: 5 }"
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.mirrorsHelper2') }}
@ -66,60 +85,58 @@
</span>
</el-form-item>
<el-form-item :label="$t('container.registries')" prop="registries">
<el-input
type="textarea"
:placeholder="$t('container.registrieHelper')"
:autosize="{ minRows: 3, maxRows: 10 }"
v-model="form.registries"
/>
<div style="width: 100%">
<el-input
v-if="form.registries"
type="textarea"
: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 :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>
<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-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 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 v-if="form.isSwarm" class="input-help">
{{ $t('container.liveWithSwarmHelper') }}
</span>
</el-form-item>
<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="systemd">systemd</el-radio>
</el-radio-group>
</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-col>
</el-row>
@ -154,21 +171,47 @@
:close-on-press-escape="false"
:show-close="false"
>
<span>{{ $t('container.iptablesHelper1') }}</span>
<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>
<template #footer>
<span class="dialog-footer">
<el-button @click="onSaveIptables(true)">{{ $t('commons.button.cancel') }}</el-button>
<el-button type="primary" @click="onSaveIptables(false)">
<el-button
@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') }}
</el-button>
</span>
</template>
</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>
</template>
@ -178,6 +221,9 @@ import { onMounted, reactive, ref } from 'vue';
import { Codemirror } from 'vue-codemirror';
import { javascript } from '@codemirror/lang-javascript';
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 i18n from '@/lang';
import {
@ -190,11 +236,22 @@ import {
import { MsgSuccess } from '@/utils/message';
import { checkNumberRange } from '@/global/form-rules';
const unset = ref(i18n.global.t('setting.unSetting'));
const submitInput = ref();
const loading = ref(false);
const showDaemonJsonAlert = ref(false);
const extensions = [javascript(), oneDark];
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({
isSwarm: false,
status: '',
@ -205,8 +262,7 @@ const form = reactive({
iptables: true,
cgroupDriver: '',
logOptionShow: false,
logMaxSize: 10,
sizeUnit: 'm',
logMaxSize: '',
logMaxFile: 3,
});
const rules = reactive({
@ -220,41 +276,96 @@ const confirmDialogRef = ref();
const iptablesVisiable = ref();
const onSave = async (formEl: FormInstance | undefined) => {
if (!formEl) return;
formEl.validate(async (valid) => {
if (!valid) return;
if (!valid) return;
const onSaveFile = 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 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 = {
header: i18n.global.t('database.confChange'),
operationInfo: i18n.global.t('database.restartNowHelper'),
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 = {
header: i18n.global.t('database.confChange'),
operationInfo: i18n.global.t('database.restartNowHelper'),
submitInputInfo: i18n.global.t('database.restartNow'),
};
confirmDialogRef.value!.acceptParams(params);
confirmDialogRefLive.value!.acceptParams(params);
};
const toDoc = () => {
window.open('https://1panel.cn/docs/user_manual/containers/setting/', '_blank');
const onSubmitSaveLive = () => {
save('LiveRestore', form.liveRestore ? 'enable' : 'disable');
};
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 = () => {
if (!form.iptables) {
iptablesVisiable.value = true;
}
const save = async (key: string, value: string) => {
loading.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) => {
form.iptables = status;
iptablesVisiable.value = false;
const toDoc = () => {
window.open('https://1panel.cn/docs/user_manual/containers/setting/', '_blank');
};
const onOperator = async (operation: string) => {
@ -281,53 +392,18 @@ const onOperator = async (operation: string) => {
});
};
const onSubmitSave = async () => {
if (confShowType.value === 'all') {
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 = '';
}
const onSubmitSaveFile = async () => {
let param = { file: dockerConf.value };
loading.value = true;
await updateDaemonJson(param)
await updateDaemonJsonByfile(param)
.then(() => {
loading.value = false;
search();
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
})
.catch(() => {
loading.value = false;
});
return;
};
const loadDockerConf = async () => {
@ -349,49 +425,21 @@ const changeMode = async () => {
};
const search = async () => {
loading.value = true;
await loadDaemonJson()
.then((res) => {
loading.value = false;
form.isSwarm = res.data.isSwarm;
form.status = res.data.status;
form.version = res.data.version;
form.cgroupDriver = res.data.cgroupDriver;
form.liveRestore = res.data.liveRestore;
form.iptables = res.data.iptables;
form.mirrors = res.data.registryMirrors ? res.data.registryMirrors.join('\n') : '';
form.registries = res.data.insecureRegistries ? res.data.insecureRegistries.join('\n') : '';
if (res.data.logMaxFile || res.data.logMaxSize) {
form.logOptionShow = true;
}
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', ''));
const res = await loadDaemonJson();
form.isSwarm = res.data.isSwarm;
form.status = res.data.status;
form.version = res.data.version;
form.cgroupDriver = res.data.cgroupDriver || 'cgroupfs';
form.liveRestore = res.data.liveRestore;
form.iptables = res.data.iptables;
form.mirrors = res.data.registryMirrors ? res.data.registryMirrors.join('\n') : '';
form.registries = res.data.insecureRegistries ? res.data.insecureRegistries.join('\n') : '';
if (res.data.logMaxFile || res.data.logMaxSize) {
form.logOptionShow = true;
form.logMaxFile = Number(res.data.logMaxFile);
form.logMaxSize = res.data.logMaxSize;
} else {
form.logOptionShow = false;
}
};

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