feat: 容器支持自定义控制台交互方式 (#2434)

This commit is contained in:
ssongliu 2023-10-07 14:30:46 +08:00 committed by GitHub
parent dc86cbc1ee
commit bca9777a43
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 77 additions and 22 deletions

View file

@ -222,13 +222,13 @@ func (b *BaseApi) ContainerInfo(c *gin.Context) {
helper.SuccessWithData(c, data) helper.SuccessWithData(c, data)
} }
// @Summary Load container limis // @Summary Load container limits
// @Description 获取容器限制 // @Description 获取容器限制
// @Success 200 {object} dto.ResourceLimit // @Success 200 {object} dto.ResourceLimit
// @Security ApiKeyAuth // @Security ApiKeyAuth
// @Router /containers/limit [get] // @Router /containers/limit [get]
func (b *BaseApi) LoadResouceLimit(c *gin.Context) { func (b *BaseApi) LoadResourceLimit(c *gin.Context) {
data, err := containerService.LoadResouceLimit() data, err := containerService.LoadResourceLimit()
if err != nil { if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err) helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return return

View file

@ -44,6 +44,8 @@ type ContainerOperate struct {
Network string `json:"network"` Network string `json:"network"`
PublishAllPorts bool `json:"publishAllPorts"` PublishAllPorts bool `json:"publishAllPorts"`
ExposedPorts []PortHelper `json:"exposedPorts"` ExposedPorts []PortHelper `json:"exposedPorts"`
Tty bool `json:"tty"`
OpenStdin bool `json:"openStdin"`
Cmd []string `json:"cmd"` Cmd []string `json:"cmd"`
Entrypoint []string `json:"entrypoint"` Entrypoint []string `json:"entrypoint"`
CPUShares int64 `json:"cpuShares"` CPUShares int64 `json:"cpuShares"`
@ -70,7 +72,7 @@ type ContainerListStats struct {
CPUPercent float64 `json:"cpuPercent"` CPUPercent float64 `json:"cpuPercent"`
PercpuUsage int `json:"percpuUsage"` PercpuUsage int `json:"percpuUsage"`
MemroyCache uint64 `json:"memoryCache"` MemoryCache uint64 `json:"memoryCache"`
MemoryUsage uint64 `json:"memoryUsage"` MemoryUsage uint64 `json:"memoryUsage"`
MemoryLimit uint64 `json:"memoryLimit"` MemoryLimit uint64 `json:"memoryLimit"`
MemoryPercent float64 `json:"memoryPercent"` MemoryPercent float64 `json:"memoryPercent"`

View file

@ -51,7 +51,7 @@ type IContainerService interface {
ContainerUpgrade(req dto.ContainerUpgrade) error ContainerUpgrade(req dto.ContainerUpgrade) error
ContainerInfo(req dto.OperationWithName) (*dto.ContainerOperate, error) ContainerInfo(req dto.OperationWithName) (*dto.ContainerOperate, error)
ContainerListStats() ([]dto.ContainerListStats, error) ContainerListStats() ([]dto.ContainerListStats, error)
LoadResouceLimit() (*dto.ResourceLimit, error) LoadResourceLimit() (*dto.ResourceLimit, error)
ContainerLogClean(req dto.OperationWithName) error ContainerLogClean(req dto.OperationWithName) error
ContainerOperation(req dto.ContainerOperation) error ContainerOperation(req dto.ContainerOperation) error
ContainerLogs(wsConn *websocket.Conn, container, since, tail string, follow bool) error ContainerLogs(wsConn *websocket.Conn, container, since, tail string, follow bool) error
@ -288,7 +288,7 @@ func (u *ContainerService) Prune(req dto.ContainerPrune) (dto.ContainerPruneRepo
return report, nil return report, nil
} }
func (u *ContainerService) LoadResouceLimit() (*dto.ResourceLimit, error) { func (u *ContainerService) LoadResourceLimit() (*dto.ResourceLimit, error) {
cpuCounts, err := cpu.Counts(true) cpuCounts, err := cpu.Counts(true)
if err != nil { if err != nil {
return nil, fmt.Errorf("load cpu limit failed, err: %v", err) return nil, fmt.Errorf("load cpu limit failed, err: %v", err)
@ -368,6 +368,8 @@ func (u *ContainerService) ContainerInfo(req dto.OperationWithName) (*dto.Contai
} }
} }
data.Cmd = oldContainer.Config.Cmd data.Cmd = oldContainer.Config.Cmd
data.OpenStdin = oldContainer.Config.OpenStdin
data.Tty = oldContainer.Config.Tty
data.Entrypoint = oldContainer.Config.Entrypoint data.Entrypoint = oldContainer.Config.Entrypoint
data.Env = oldContainer.Config.Env data.Env = oldContainer.Config.Env
data.CPUShares = oldContainer.HostConfig.CPUShares data.CPUShares = oldContainer.HostConfig.CPUShares
@ -441,7 +443,7 @@ func (u *ContainerService) ContainerUpdate(req dto.ContainerOperate) error {
container, err := client.ContainerCreate(ctx, config, hostConf, &networkConf, &v1.Platform{}, req.Name) container, err := client.ContainerCreate(ctx, config, hostConf, &networkConf, &v1.Platform{}, req.Name)
if err != nil { if err != nil {
reCreateAfterUpdate(req.Name, client, oldContainer.Config, oldContainer.HostConfig, oldContainer.NetworkSettings) reCreateAfterUpdate(req.Name, client, oldContainer.Config, oldContainer.HostConfig, oldContainer.NetworkSettings)
return fmt.Errorf("update contianer failed, err: %v", err) return fmt.Errorf("update container failed, err: %v", err)
} }
global.LOG.Infof("update container %s successful! now check if the container is started.", req.Name) global.LOG.Infof("update container %s successful! now check if the container is started.", req.Name)
if err := client.ContainerStart(ctx, container.ID, types.ContainerStartOptions{}); err != nil { if err := client.ContainerStart(ctx, container.ID, types.ContainerStartOptions{}); err != nil {
@ -487,7 +489,7 @@ func (u *ContainerService) ContainerUpgrade(req dto.ContainerUpgrade) error {
container, err := client.ContainerCreate(ctx, config, hostConf, &networkConf, &v1.Platform{}, req.Name) container, err := client.ContainerCreate(ctx, config, hostConf, &networkConf, &v1.Platform{}, req.Name)
if err != nil { if err != nil {
reCreateAfterUpdate(req.Name, client, oldContainer.Config, oldContainer.HostConfig, oldContainer.NetworkSettings) reCreateAfterUpdate(req.Name, client, oldContainer.Config, oldContainer.HostConfig, oldContainer.NetworkSettings)
return fmt.Errorf("upgrade contianer failed, err: %v", err) return fmt.Errorf("upgrade container failed, err: %v", err)
} }
global.LOG.Infof("upgrade container %s successful! now check if the container is started.", req.Name) global.LOG.Infof("upgrade container %s successful! now check if the container is started.", req.Name)
if err := client.ContainerStart(ctx, container.ID, types.ContainerStartOptions{}); err != nil { if err := client.ContainerStart(ctx, container.ID, types.ContainerStartOptions{}); err != nil {
@ -683,14 +685,14 @@ func (u *ContainerService) LoadContainerLogs(req dto.OperationWithNameAndType) s
} }
func stringsToMap(list []string) map[string]string { func stringsToMap(list []string) map[string]string {
var lableMap = make(map[string]string) var labelMap = make(map[string]string)
for _, label := range list { for _, label := range list {
if strings.Contains(label, "=") { if strings.Contains(label, "=") {
sps := strings.SplitN(label, "=", 2) sps := strings.SplitN(label, "=", 2)
lableMap[sps[0]] = sps[1] labelMap[sps[0]] = sps[1]
} }
} }
return lableMap return labelMap
} }
func calculateCPUPercentUnix(stats *types.StatsJSON) float64 { func calculateCPUPercentUnix(stats *types.StatsJSON) float64 {
@ -791,7 +793,7 @@ func loadCpuAndMem(client *client.Client, container string) dto.ContainerListSta
data.CPUPercent = calculateCPUPercentUnix(stats) data.CPUPercent = calculateCPUPercentUnix(stats)
data.PercpuUsage = len(stats.CPUStats.CPUUsage.PercpuUsage) data.PercpuUsage = len(stats.CPUStats.CPUUsage.PercpuUsage)
data.MemroyCache = stats.MemoryStats.Stats["cache"] data.MemoryCache = stats.MemoryStats.Stats["cache"]
data.MemoryUsage = stats.MemoryStats.Usage data.MemoryUsage = stats.MemoryStats.Usage
data.MemoryLimit = stats.MemoryStats.Limit data.MemoryLimit = stats.MemoryStats.Limit
data.MemoryPercent = calculateMemPercentUnix(stats.MemoryStats) data.MemoryPercent = calculateMemPercentUnix(stats.MemoryStats)
@ -849,16 +851,18 @@ func loadConfigInfo(req dto.ContainerOperate, config *container.Config, hostConf
if err != nil { if err != nil {
return err return err
} }
exposeds := make(nat.PortSet) exposed := make(nat.PortSet)
for port := range portMap { for port := range portMap {
exposeds[port] = struct{}{} exposed[port] = struct{}{}
} }
config.Image = req.Image config.Image = req.Image
config.Cmd = req.Cmd config.Cmd = req.Cmd
config.Entrypoint = req.Entrypoint config.Entrypoint = req.Entrypoint
config.Env = req.Env config.Env = req.Env
config.Labels = stringsToMap(req.Labels) config.Labels = stringsToMap(req.Labels)
config.ExposedPorts = exposeds config.ExposedPorts = exposed
config.OpenStdin = req.OpenStdin
config.Tty = req.Tty
if len(req.Network) != 0 { if len(req.Network) != 0 {
networkConf.EndpointsConfig = map[string]*network.EndpointSettings{req.Network: {}} networkConf.EndpointsConfig = map[string]*network.EndpointSettings{req.Network: {}}

View file

@ -26,7 +26,7 @@ func (s *ContainerRouter) InitContainerRouter(Router *gin.RouterGroup) {
baRouter.POST("/list", baseApi.ListContainer) baRouter.POST("/list", baseApi.ListContainer)
baRouter.GET("/list/stats", baseApi.ContainerListStats) baRouter.GET("/list/stats", baseApi.ContainerListStats)
baRouter.GET("/search/log", baseApi.ContainerLogs) baRouter.GET("/search/log", baseApi.ContainerLogs)
baRouter.GET("/limit", baseApi.LoadResouceLimit) baRouter.GET("/limit", baseApi.LoadResourceLimit)
baRouter.POST("/clean/log", baseApi.CleanContainerLog) baRouter.POST("/clean/log", baseApi.CleanContainerLog)
baRouter.POST("/load/log", baseApi.LoadContainerLog) baRouter.POST("/load/log", baseApi.LoadContainerLog)
baRouter.POST("/inspect", baseApi.Inspect) baRouter.POST("/inspect", baseApi.Inspect)

View file

@ -1233,7 +1233,7 @@ const docTemplate = `{
} }
}, },
"/containers/compose/search/log": { "/containers/compose/search/log": {
"post": { "get": {
"security": [ "security": [
{ {
"ApiKeyAuth": [] "ApiKeyAuth": []
@ -2037,7 +2037,7 @@ const docTemplate = `{
} }
], ],
"description": "获取容器限制", "description": "获取容器限制",
"summary": "Load container limis", "summary": "Load container limits",
"responses": { "responses": {
"200": { "200": {
"description": "OK", "description": "OK",
@ -12549,12 +12549,18 @@ const docTemplate = `{
"network": { "network": {
"type": "string" "type": "string"
}, },
"openStdin": {
"type": "boolean"
},
"publishAllPorts": { "publishAllPorts": {
"type": "boolean" "type": "boolean"
}, },
"restartPolicy": { "restartPolicy": {
"type": "string" "type": "string"
}, },
"tty": {
"type": "boolean"
},
"volumes": { "volumes": {
"type": "array", "type": "array",
"items": { "items": {
@ -16649,6 +16655,9 @@ const docTemplate = `{
"type": "object", "type": "object",
"additionalProperties": true "additionalProperties": true
}, },
"port": {
"type": "integer"
},
"resource": { "resource": {
"type": "string" "type": "string"
}, },
@ -16734,6 +16743,9 @@ const docTemplate = `{
"type": "object", "type": "object",
"additionalProperties": true "additionalProperties": true
}, },
"port": {
"type": "integer"
},
"rebuild": { "rebuild": {
"type": "boolean" "type": "boolean"
}, },

View file

@ -1226,7 +1226,7 @@
} }
}, },
"/containers/compose/search/log": { "/containers/compose/search/log": {
"post": { "get": {
"security": [ "security": [
{ {
"ApiKeyAuth": [] "ApiKeyAuth": []
@ -2030,7 +2030,7 @@
} }
], ],
"description": "获取容器限制", "description": "获取容器限制",
"summary": "Load container limis", "summary": "Load container limits",
"responses": { "responses": {
"200": { "200": {
"description": "OK", "description": "OK",
@ -12542,12 +12542,18 @@
"network": { "network": {
"type": "string" "type": "string"
}, },
"openStdin": {
"type": "boolean"
},
"publishAllPorts": { "publishAllPorts": {
"type": "boolean" "type": "boolean"
}, },
"restartPolicy": { "restartPolicy": {
"type": "string" "type": "string"
}, },
"tty": {
"type": "boolean"
},
"volumes": { "volumes": {
"type": "array", "type": "array",
"items": { "items": {
@ -16642,6 +16648,9 @@
"type": "object", "type": "object",
"additionalProperties": true "additionalProperties": true
}, },
"port": {
"type": "integer"
},
"resource": { "resource": {
"type": "string" "type": "string"
}, },
@ -16727,6 +16736,9 @@
"type": "object", "type": "object",
"additionalProperties": true "additionalProperties": true
}, },
"port": {
"type": "integer"
},
"rebuild": { "rebuild": {
"type": "boolean" "type": "boolean"
}, },

View file

@ -403,10 +403,14 @@ definitions:
type: number type: number
network: network:
type: string type: string
openStdin:
type: boolean
publishAllPorts: publishAllPorts:
type: boolean type: boolean
restartPolicy: restartPolicy:
type: string type: string
tty:
type: boolean
volumes: volumes:
items: items:
$ref: '#/definitions/dto.VolumeHelper' $ref: '#/definitions/dto.VolumeHelper'
@ -3152,6 +3156,8 @@ definitions:
params: params:
additionalProperties: true additionalProperties: true
type: object type: object
port:
type: integer
resource: resource:
type: string type: string
source: source:
@ -3208,6 +3214,8 @@ definitions:
params: params:
additionalProperties: true additionalProperties: true
type: object type: object
port:
type: integer
rebuild: rebuild:
type: boolean type: boolean
source: source:
@ -4909,7 +4917,7 @@ paths:
tags: tags:
- Container Compose - Container Compose
/containers/compose/search/log: /containers/compose/search/log:
post: get:
description: docker-compose 日志 description: docker-compose 日志
parameters: parameters:
- description: compose 文件地址 - description: compose 文件地址
@ -5426,7 +5434,7 @@ paths:
$ref: '#/definitions/dto.ResourceLimit' $ref: '#/definitions/dto.ResourceLimit'
security: security:
- ApiKeyAuth: [] - ApiKeyAuth: []
summary: Load container limis summary: Load container limits
/containers/list: /containers/list:
post: post:
consumes: consumes:

View file

@ -27,6 +27,8 @@ export namespace Container {
entrypointStr: string; entrypointStr: string;
memoryItem: number; memoryItem: number;
cmd: Array<string>; cmd: Array<string>;
openStdin: boolean;
tty: boolean;
entrypoint: Array<string>; entrypoint: Array<string>;
publishAllPorts: boolean; publishAllPorts: boolean;
exposedPorts: Array<Port>; exposedPorts: Array<Port>;

View file

@ -544,6 +544,9 @@ const message = {
'The default CPU share for a container is 1024, which can be increased to give the container more CPU time.', 'The default CPU share for a container is 1024, which can be increased to give the container more CPU time.',
command: 'Command', command: 'Command',
console: 'Console Interaction',
tty: 'TTY (-t)',
openStdin: 'OpenStdin (-i)',
custom: 'Custom', custom: 'Custom',
emptyUser: 'When empty, you will log in as default', emptyUser: 'When empty, you will log in as default',
containerTerminal: 'Terminal', containerTerminal: 'Terminal',

View file

@ -529,6 +529,9 @@ const message = {
cpuShareHelper: '容器默認份額為 1024 CPU增大可使當前容器獲得更多的 CPU 時間', cpuShareHelper: '容器默認份額為 1024 CPU增大可使當前容器獲得更多的 CPU 時間',
command: '命令', command: '命令',
console: '控製臺交互',
tty: '偽終端 ( -t )',
openStdin: '標準輸入 ( -i )',
custom: '自定義', custom: '自定義',
containerTerminal: '終端', containerTerminal: '終端',
emptyUser: '為空時將使用容器默認的用戶登錄', emptyUser: '為空時將使用容器默認的用戶登錄',

View file

@ -529,6 +529,9 @@ const message = {
cpuShareHelper: '容器默认份额为 1024 CPU增大可使当前容器获得更多的 CPU 时间', cpuShareHelper: '容器默认份额为 1024 CPU增大可使当前容器获得更多的 CPU 时间',
command: '命令', command: '命令',
console: '控制台交互',
tty: '伪终端 ( -t )',
openStdin: '标准输入 ( -i )',
custom: '自定义', custom: '自定义',
containerTerminal: '终端', containerTerminal: '终端',
emptyUser: '为空时将使用容器默认的用户登录', emptyUser: '为空时将使用容器默认的用户登录',

View file

@ -176,6 +176,12 @@
{{ $t('container.autoRemove') }} {{ $t('container.autoRemove') }}
</el-checkbox> </el-checkbox>
</el-form-item> </el-form-item>
<el-form-item :label="$t('container.console')">
<el-checkbox v-model="dialogData.rowData!.tty">{{ $t('container.tty') }}</el-checkbox>
<el-checkbox v-model="dialogData.rowData!.openStdin">
{{ $t('container.openStdin') }}
</el-checkbox>
</el-form-item>
<el-form-item :label="$t('container.restartPolicy')" prop="restartPolicy"> <el-form-item :label="$t('container.restartPolicy')" prop="restartPolicy">
<el-radio-group v-model="dialogData.rowData!.restartPolicy"> <el-radio-group v-model="dialogData.rowData!.restartPolicy">
<el-radio label="no">{{ $t('container.no') }}</el-radio> <el-radio label="no">{{ $t('container.no') }}</el-radio>