Browse Source

feat: 增加容器日志清理功能 (#1111)

pull/1116/head
ssongliu 2 years ago committed by GitHub
parent
commit
5ac9298db0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 26
      backend/app/api/v1/container.go
  2. 2
      backend/app/api/v1/setting.go
  3. 23
      backend/app/service/container.go
  4. 1
      backend/router/ro_container.go
  5. 154
      cmd/server/docs/docs.go
  6. 154
      cmd/server/docs/swagger.json
  7. 101
      cmd/server/docs/swagger.yaml
  8. 3
      frontend/src/api/modules/container.ts
  9. 28
      frontend/src/components/container-log/index.vue
  10. 1
      frontend/src/lang/modules/en.ts
  11. 1
      frontend/src/lang/modules/zh.ts
  12. 29
      frontend/src/views/container/container/log/index.vue

26
backend/app/api/v1/container.go

@ -179,6 +179,32 @@ func (b *BaseApi) ContainerCreate(c *gin.Context) {
helper.SuccessWithData(c, nil)
}
// @Tags Container
// @Summary Clean container log
// @Description 清理容器日志
// @Accept json
// @Param request body dto.OperationWithName true "request"
// @Success 200
// @Security ApiKeyAuth
// @Router /containers/clean/log [post]
// @x-panel-log {"bodyKeys":["name"],"paramKeys":[],"BeforeFuntions":[],"formatZH":"清理容器 [name] 日志","formatEN":"clean container [name] logs"}
func (b *BaseApi) CleanContainerLog(c *gin.Context) {
var req dto.OperationWithName
if err := c.ShouldBindJSON(&req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
if err := global.VALID.Struct(req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
if err := containerService.ContainerLogClean(req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithData(c, nil)
}
// @Tags Container
// @Summary Operate Container
// @Description 容器操作

2
backend/app/api/v1/setting.go

@ -189,7 +189,7 @@ func (b *BaseApi) HandlePasswordExpired(c *gin.Context) {
// @Tags System Setting
// @Summary Load time zone options
// @Description 加载系统可用时区
// @Success 200 {string}
// @Success 200
// @Security ApiKeyAuth
// @Router /settings/time/option [get]
func (b *BaseApi) LoadTimeZone(c *gin.Context) {

23
backend/app/service/container.go

@ -6,6 +6,7 @@ import (
"errors"
"fmt"
"io"
"os"
"os/exec"
"sort"
"strconv"
@ -39,6 +40,7 @@ type IContainerService interface {
CreateCompose(req dto.ComposeCreate) (string, error)
ComposeOperation(req dto.ComposeOperation) error
ContainerCreate(req dto.ContainerCreate) error
ContainerLogClean(req dto.OperationWithName) error
ContainerOperation(req dto.ContainerOperation) error
ContainerLogs(param dto.ContainerLog) (string, error)
ContainerStats(id string) (*dto.ContainterStats, error)
@ -263,6 +265,27 @@ func (u *ContainerService) ContainerOperation(req dto.ContainerOperation) error
return err
}
func (u *ContainerService) ContainerLogClean(req dto.OperationWithName) error {
client, err := docker.NewDockerClient()
if err != nil {
return err
}
container, err := client.ContainerInspect(context.Background(), req.Name)
if err != nil {
return err
}
file, err := os.OpenFile(container.LogPath, os.O_RDWR|os.O_CREATE, 0666)
if err != nil {
return err
}
defer file.Close()
if err = file.Truncate(0); err != nil {
return err
}
_, _ = file.Seek(0, 0)
return nil
}
func (u *ContainerService) ContainerLogs(req dto.ContainerLog) (string, error) {
cmd := exec.Command("docker", "logs", req.ContainerID)
if req.Mode != "all" {

1
backend/router/ro_container.go

@ -21,6 +21,7 @@ func (s *ContainerRouter) InitContainerRouter(Router *gin.RouterGroup) {
baRouter.POST("", baseApi.ContainerCreate)
baRouter.POST("/search", baseApi.SearchContainer)
baRouter.POST("/search/log", baseApi.ContainerLogs)
baRouter.POST("/clean/log", baseApi.CleanContainerLog)
baRouter.POST("/inspect", baseApi.Inspect)
baRouter.POST("/operate", baseApi.ContainerOperation)

154
cmd/server/docs/docs.go

@ -937,6 +937,48 @@ var doc = `{
}
}
},
"/containers/clean/log": {
"post": {
"security": [
{
"ApiKeyAuth": []
}
],
"description": "清理容器日志",
"consumes": [
"application/json"
],
"tags": [
"Container"
],
"summary": "Clean container log",
"parameters": [
{
"description": "request",
"name": "request",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/dto.OperationWithName"
}
}
],
"responses": {
"200": {
"description": ""
}
},
"x-panel-log": {
"BeforeFuntions": [],
"bodyKeys": [
"name"
],
"formatEN": "clean container [name] logs",
"formatZH": "清理容器 [name] 日志",
"paramKeys": []
}
}
},
"/containers/compose": {
"post": {
"security": [
@ -7750,6 +7792,25 @@ var doc = `{
}
}
},
"/settings/time/option": {
"get": {
"security": [
{
"ApiKeyAuth": []
}
],
"description": "加载系统可用时区",
"tags": [
"System Setting"
],
"summary": "Load time zone options",
"responses": {
"200": {
"description": ""
}
}
}
},
"/settings/time/sync": {
"post": {
"security": [
@ -7758,10 +7819,24 @@ var doc = `{
}
],
"description": "系统时间同步",
"consumes": [
"application/json"
],
"tags": [
"System Setting"
],
"summary": "Sync system time",
"parameters": [
{
"description": "request",
"name": "request",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/dto.SyncTimeZone"
}
}
],
"responses": {
"200": {
"description": "OK",
@ -7772,9 +7847,12 @@ var doc = `{
},
"x-panel-log": {
"BeforeFuntions": [],
"bodyKeys": [],
"formatEN": "sync system time",
"formatZH": "系统时间同步",
"bodyKeys": [
"ntpSite",
"timeZone"
],
"formatEN": "sync system time [ntpSite]-[timeZone]",
"formatZH": "系统时间同步[ntpSite]-[timeZone]",
"paramKeys": []
}
}
@ -10689,6 +10767,12 @@ var doc = `{
"liveRestore": {
"type": "boolean"
},
"logMaxFile": {
"type": "string"
},
"logMaxSize": {
"type": "string"
},
"registryMirrors": {
"type": "array",
"items": {
@ -10898,12 +10982,6 @@ var doc = `{
"restart",
"stop"
]
},
"stopService": {
"type": "boolean"
},
"stopSocket": {
"type": "boolean"
}
}
},
@ -11728,6 +11806,17 @@ var doc = `{
}
}
},
"dto.OperationWithName": {
"type": "object",
"required": [
"name"
],
"properties": {
"name": {
"type": "string"
}
}
},
"dto.PageContainer": {
"type": "object",
"required": [
@ -12292,12 +12381,18 @@ var doc = `{
"dto.SettingInfo": {
"type": "object",
"properties": {
"allowIPs": {
"type": "string"
},
"appStoreLastModified": {
"type": "string"
},
"appStoreVersion": {
"type": "string"
},
"bindDomain": {
"type": "string"
},
"complexityVerification": {
"type": "string"
},
@ -12337,6 +12432,9 @@ var doc = `{
"monitorStoreDays": {
"type": "string"
},
"ntpSite": {
"type": "string"
},
"panelName": {
"type": "string"
},
@ -12364,6 +12462,9 @@ var doc = `{
"theme": {
"type": "string"
},
"timeZone": {
"type": "string"
},
"userName": {
"type": "string"
},
@ -12444,6 +12545,17 @@ var doc = `{
}
}
},
"dto.SyncTimeZone": {
"type": "object",
"properties": {
"ntpSite": {
"type": "string"
},
"timeZone": {
"type": "string"
}
}
},
"dto.UpdateDescription": {
"type": "object",
"required": [
@ -12968,6 +13080,12 @@ var doc = `{
"cpuQuota": {
"type": "number"
},
"dockerCompose": {
"type": "string"
},
"editCompose": {
"type": "boolean"
},
"memoryLimit": {
"type": "number"
},
@ -13071,6 +13189,12 @@ var doc = `{
"cpuQuota": {
"type": "number"
},
"dockerCompose": {
"type": "string"
},
"editCompose": {
"type": "boolean"
},
"installId": {
"type": "integer"
},
@ -13417,6 +13541,12 @@ var doc = `{
"cpuQuota": {
"type": "number"
},
"dockerCompose": {
"type": "string"
},
"editCompose": {
"type": "boolean"
},
"memoryLimit": {
"type": "number"
},
@ -14323,6 +14453,9 @@ var doc = `{
"id": {
"type": "integer"
},
"installed": {
"type": "boolean"
},
"key": {
"type": "string"
},
@ -14388,6 +14521,9 @@ var doc = `{
"createdAt": {
"type": "string"
},
"dockerCompose": {
"type": "string"
},
"downloadCallBackUrl": {
"type": "string"
},

154
cmd/server/docs/swagger.json

@ -923,6 +923,48 @@
}
}
},
"/containers/clean/log": {
"post": {
"security": [
{
"ApiKeyAuth": []
}
],
"description": "清理容器日志",
"consumes": [
"application/json"
],
"tags": [
"Container"
],
"summary": "Clean container log",
"parameters": [
{
"description": "request",
"name": "request",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/dto.OperationWithName"
}
}
],
"responses": {
"200": {
"description": ""
}
},
"x-panel-log": {
"BeforeFuntions": [],
"bodyKeys": [
"name"
],
"formatEN": "clean container [name] logs",
"formatZH": "清理容器 [name] 日志",
"paramKeys": []
}
}
},
"/containers/compose": {
"post": {
"security": [
@ -7736,6 +7778,25 @@
}
}
},
"/settings/time/option": {
"get": {
"security": [
{
"ApiKeyAuth": []
}
],
"description": "加载系统可用时区",
"tags": [
"System Setting"
],
"summary": "Load time zone options",
"responses": {
"200": {
"description": ""
}
}
}
},
"/settings/time/sync": {
"post": {
"security": [
@ -7744,10 +7805,24 @@
}
],
"description": "系统时间同步",
"consumes": [
"application/json"
],
"tags": [
"System Setting"
],
"summary": "Sync system time",
"parameters": [
{
"description": "request",
"name": "request",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/dto.SyncTimeZone"
}
}
],
"responses": {
"200": {
"description": "OK",
@ -7758,9 +7833,12 @@
},
"x-panel-log": {
"BeforeFuntions": [],
"bodyKeys": [],
"formatEN": "sync system time",
"formatZH": "系统时间同步",
"bodyKeys": [
"ntpSite",
"timeZone"
],
"formatEN": "sync system time [ntpSite]-[timeZone]",
"formatZH": "系统时间同步[ntpSite]-[timeZone]",
"paramKeys": []
}
}
@ -10675,6 +10753,12 @@
"liveRestore": {
"type": "boolean"
},
"logMaxFile": {
"type": "string"
},
"logMaxSize": {
"type": "string"
},
"registryMirrors": {
"type": "array",
"items": {
@ -10884,12 +10968,6 @@
"restart",
"stop"
]
},
"stopService": {
"type": "boolean"
},
"stopSocket": {
"type": "boolean"
}
}
},
@ -11714,6 +11792,17 @@
}
}
},
"dto.OperationWithName": {
"type": "object",
"required": [
"name"
],
"properties": {
"name": {
"type": "string"
}
}
},
"dto.PageContainer": {
"type": "object",
"required": [
@ -12278,12 +12367,18 @@
"dto.SettingInfo": {
"type": "object",
"properties": {
"allowIPs": {
"type": "string"
},
"appStoreLastModified": {
"type": "string"
},
"appStoreVersion": {
"type": "string"
},
"bindDomain": {
"type": "string"
},
"complexityVerification": {
"type": "string"
},
@ -12323,6 +12418,9 @@
"monitorStoreDays": {
"type": "string"
},
"ntpSite": {
"type": "string"
},
"panelName": {
"type": "string"
},
@ -12350,6 +12448,9 @@
"theme": {
"type": "string"
},
"timeZone": {
"type": "string"
},
"userName": {
"type": "string"
},
@ -12430,6 +12531,17 @@
}
}
},
"dto.SyncTimeZone": {
"type": "object",
"properties": {
"ntpSite": {
"type": "string"
},
"timeZone": {
"type": "string"
}
}
},
"dto.UpdateDescription": {
"type": "object",
"required": [
@ -12954,6 +13066,12 @@
"cpuQuota": {
"type": "number"
},
"dockerCompose": {
"type": "string"
},
"editCompose": {
"type": "boolean"
},
"memoryLimit": {
"type": "number"
},
@ -13057,6 +13175,12 @@
"cpuQuota": {
"type": "number"
},
"dockerCompose": {
"type": "string"
},
"editCompose": {
"type": "boolean"
},
"installId": {
"type": "integer"
},
@ -13403,6 +13527,12 @@
"cpuQuota": {
"type": "number"
},
"dockerCompose": {
"type": "string"
},
"editCompose": {
"type": "boolean"
},
"memoryLimit": {
"type": "number"
},
@ -14309,6 +14439,9 @@
"id": {
"type": "integer"
},
"installed": {
"type": "boolean"
},
"key": {
"type": "string"
},
@ -14374,6 +14507,9 @@
"createdAt": {
"type": "string"
},
"dockerCompose": {
"type": "string"
},
"downloadCallBackUrl": {
"type": "string"
},

101
cmd/server/docs/swagger.yaml

@ -485,6 +485,10 @@ definitions:
type: boolean
liveRestore:
type: boolean
logMaxFile:
type: string
logMaxSize:
type: string
registryMirrors:
items:
type: string
@ -622,10 +626,6 @@ definitions:
- restart
- stop
type: string
stopService:
type: boolean
stopSocket:
type: boolean
required:
- operation
type: object
@ -1180,6 +1180,13 @@ definitions:
required:
- id
type: object
dto.OperationWithName:
properties:
name:
type: string
required:
- name
type: object
dto.PageContainer:
properties:
filters:
@ -1556,10 +1563,14 @@ definitions:
type: object
dto.SettingInfo:
properties:
allowIPs:
type: string
appStoreLastModified:
type: string
appStoreVersion:
type: string
bindDomain:
type: string
complexityVerification:
type: string
dingVars:
@ -1586,6 +1597,8 @@ definitions:
type: string
monitorStoreDays:
type: string
ntpSite:
type: string
panelName:
type: string
port:
@ -1604,6 +1617,8 @@ definitions:
type: string
theme:
type: string
timeZone:
type: string
userName:
type: string
weChatVars:
@ -1658,6 +1673,13 @@ definitions:
required:
- id
type: object
dto.SyncTimeZone:
properties:
ntpSite:
type: string
timeZone:
type: string
type: object
dto.UpdateDescription:
properties:
description:
@ -2000,6 +2022,10 @@ definitions:
type: string
cpuQuota:
type: number
dockerCompose:
type: string
editCompose:
type: boolean
memoryLimit:
type: number
memoryUnit:
@ -2069,6 +2095,10 @@ definitions:
type: string
cpuQuota:
type: number
dockerCompose:
type: string
editCompose:
type: boolean
installId:
type: integer
memoryLimit:
@ -2304,6 +2334,10 @@ definitions:
type: string
cpuQuota:
type: number
dockerCompose:
type: string
editCompose:
type: boolean
memoryLimit:
type: number
memoryUnit:
@ -2915,6 +2949,8 @@ definitions:
type: string
id:
type: integer
installed:
type: boolean
key:
type: string
lastModified:
@ -2958,6 +2994,8 @@ definitions:
type: integer
createdAt:
type: string
dockerCompose:
type: string
downloadCallBackUrl:
type: string
downloadUrl:
@ -3809,6 +3847,33 @@ paths:
formatEN: create container [name][image]
formatZH: 创建容器 [name][image]
paramKeys: []
/containers/clean/log:
post:
consumes:
- application/json
description: 清理容器日志
parameters:
- description: request
in: body
name: request
required: true
schema:
$ref: '#/definitions/dto.OperationWithName'
responses:
"200":
description: ""
security:
- ApiKeyAuth: []
summary: Clean container log
tags:
- Container
x-panel-log:
BeforeFuntions: []
bodyKeys:
- name
formatEN: clean container [name] logs
formatZH: 清理容器 [name] 日志
paramKeys: []
/containers/compose:
post:
consumes:
@ -8139,9 +8204,29 @@ paths:
formatEN: update system ssl => [ssl]
formatZH: 修改系统 ssl => [ssl]
paramKeys: []
/settings/time/option:
get:
description: 加载系统可用时区
responses:
"200":
description: ""
security:
- ApiKeyAuth: []
summary: Load time zone options
tags:
- System Setting
/settings/time/sync:
post:
consumes:
- application/json
description: 系统时间同步
parameters:
- description: request
in: body
name: request
required: true
schema:
$ref: '#/definitions/dto.SyncTimeZone'
responses:
"200":
description: OK
@ -8154,9 +8239,11 @@ paths:
- System Setting
x-panel-log:
BeforeFuntions: []
bodyKeys: []
formatEN: sync system time
formatZH: 系统时间同步
bodyKeys:
- ntpSite
- timeZone
formatEN: sync system time [ntpSite]-[timeZone]
formatZH: 系统时间同步[ntpSite]-[timeZone]
paramKeys: []
/settings/update:
post:

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

@ -11,6 +11,9 @@ export const createContainer = (params: Container.ContainerCreate) => {
export const logContainer = (params: Container.ContainerLogSearch) => {
return http.post<string>(`/containers/search/log`, params, 400000);
};
export const cleanContainerLog = (containerName: string) => {
return http.post(`/containers/clean/log`, { name: containerName });
};
export const ContainerStats = (id: string) => {
return http.get<Container.ContainerStats>(`/containers/stats/${id}`);
};

28
frontend/src/components/container-log/index.vue

@ -4,12 +4,15 @@
<el-select @change="searchLogs" style="width: 10%; float: left" v-model="logSearch.mode">
<el-option v-for="item in timeOptions" :key="item.label" :value="item.value" :label="item.label" />
</el-select>
<div style="margin-left: 20px; float: left">
<div class="margin-button" style="float: left">
<el-checkbox border v-model="logSearch.isWatch">{{ $t('commons.button.watch') }}</el-checkbox>
</div>
<el-button style="margin-left: 20px" @click="onDownload" icon="Download">
<el-button class="margin-button" @click="onDownload" icon="Download">
{{ $t('file.download') }}
</el-button>
<el-button class="margin-button" @click="onClean" icon="Delete">
{{ $t('commons.button.clean') }}
</el-button>
</div>
<codemirror
@ -31,13 +34,14 @@
</template>
<script lang="ts" setup>
import { logContainer } from '@/api/modules/container';
import { cleanContainerLog, logContainer } from '@/api/modules/container';
import i18n from '@/lang';
import { dateFormatForName } from '@/utils/util';
import { nextTick, onBeforeUnmount, reactive, ref, shallowRef } from 'vue';
import { Codemirror } from 'vue-codemirror';
import { javascript } from '@codemirror/lang-javascript';
import { oneDark } from '@codemirror/theme-one-dark';
import { MsgSuccess } from '@/utils/message';
const extensions = [javascript(), oneDark];
@ -115,6 +119,18 @@ const acceptParams = (props: DialogProps): void => {
}, 1000 * 5);
};
const onClean = async () => {
ElMessageBox.confirm(i18n.global.t('commons.msg.clean'), i18n.global.t('container.cleanLog'), {
confirmButtonText: i18n.global.t('commons.button.confirm'),
cancelButtonText: i18n.global.t('commons.button.cancel'),
type: 'info',
}).then(async () => {
await cleanContainerLog(logSearch.container);
searchLogs();
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
});
};
onBeforeUnmount(() => {
clearInterval(Number(timer));
timer = null;
@ -124,3 +140,9 @@ defineExpose({
acceptParams,
});
</script>
<style scoped lang="scss">
.margin-button {
margin-left: 20px;
}
</style>

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

@ -444,6 +444,7 @@ const message = {
last4Hour: 'Last 4 Hours',
lastHour: 'Last Hour',
last10Min: 'Last 10 Minutes',
cleanLog: 'Clean log',
newName: 'New name',
source: 'Resource rate',

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

@ -463,6 +463,7 @@ const message = {
last4Hour: '最近 4 小时',
lastHour: '最近 1 小时',
last10Min: '最近 10 分钟',
cleanLog: '清空日志',
newName: '新名称',
source: '资源使用率',

29
frontend/src/views/container/container/log/index.vue

@ -8,12 +8,15 @@
<el-select @change="searchLogs" style="width: 30%; float: left" v-model="logSearch.mode">
<el-option v-for="item in timeOptions" :key="item.label" :value="item.value" :label="item.label" />
</el-select>
<div style="margin-left: 20px; float: left">
<div class="margin-button" style="float: left">
<el-checkbox border v-model="logSearch.isWatch">{{ $t('commons.button.watch') }}</el-checkbox>
</div>
<el-button style="margin-left: 20px" @click="onDownload" icon="Download">
<el-button class="margin-button" @click="onDownload" icon="Download">
{{ $t('file.download') }}
</el-button>
<el-button class="margin-button" @click="onClean" icon="Delete">
{{ $t('commons.button.clean') }}
</el-button>
</div>
<codemirror
@ -41,7 +44,7 @@
</template>
<script lang="ts" setup>
import { logContainer } from '@/api/modules/container';
import { cleanContainerLog, logContainer } from '@/api/modules/container';
import i18n from '@/lang';
import { dateFormatForName } from '@/utils/util';
import { nextTick, onBeforeUnmount, reactive, ref, shallowRef } from 'vue';
@ -49,6 +52,8 @@ import { Codemirror } from 'vue-codemirror';
import { javascript } from '@codemirror/lang-javascript';
import { oneDark } from '@codemirror/theme-one-dark';
import DrawerHeader from '@/components/drawer-header/index.vue';
import { ElMessageBox } from 'element-plus';
import { MsgSuccess } from '@/utils/message';
const extensions = [javascript(), oneDark];
@ -116,6 +121,18 @@ const onDownload = async () => {
a.dispatchEvent(event);
};
const onClean = async () => {
ElMessageBox.confirm(i18n.global.t('commons.msg.clean'), i18n.global.t('container.cleanLog'), {
confirmButtonText: i18n.global.t('commons.button.confirm'),
cancelButtonText: i18n.global.t('commons.button.cancel'),
type: 'info',
}).then(async () => {
await cleanContainerLog(logSearch.container);
searchLogs();
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
});
};
interface DialogProps {
container: string;
containerID: string;
@ -144,3 +161,9 @@ defineExpose({
acceptParams,
});
</script>
<style scoped lang="scss">
.margin-button {
margin-left: 20px;
}
</style>

Loading…
Cancel
Save