Browse Source

fix: 容器日志接口文档修改 (#1372)

pull/1377/head
ssongliu 1 year ago committed by GitHub
parent
commit
20dbd6c181
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 9
      backend/app/api/v1/container.go
  2. 7
      backend/app/service/container.go
  3. 72
      cmd/server/docs/docs.go
  4. 72
      cmd/server/docs/swagger.json
  5. 56
      cmd/server/docs/swagger.yaml
  6. 4
      frontend/src/api/interface/container.ts
  7. 3
      frontend/src/api/modules/container.ts
  8. 48
      frontend/src/components/container-log/index.vue
  9. 6
      frontend/src/views/container/container/log/index.vue

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

@ -307,6 +307,15 @@ func (b *BaseApi) Inspect(c *gin.Context) {
helper.SuccessWithData(c, result)
}
// @Tags Container
// @Summary Container logs
// @Description 容器日志
// @Param container query string false "容器名称"
// @Param since query string false "时间筛选"
// @Param follow query string false "是否追踪"
// @Param tail query string false "显示行号"
// @Security ApiKeyAuth
// @Router /containers/search/log [post]
func (b *BaseApi) ContainerLogs(c *gin.Context) {
wsConn, err := upGrader.Upgrade(c.Writer, c.Request, nil)
if err != nil {

7
backend/app/service/container.go

@ -1,7 +1,6 @@
package service
import (
"bufio"
"context"
"encoding/json"
"fmt"
@ -354,9 +353,9 @@ func (u *ContainerService) ContainerLogs(wsConn *websocket.Conn, container, sinc
return err
}
reader := bufio.NewReader(stdout)
buffer := make([]byte, 1024)
for {
bytes, err := reader.ReadBytes('\n')
n, err := stdout.Read(buffer)
if err != nil {
if err == io.EOF {
break
@ -364,7 +363,7 @@ func (u *ContainerService) ContainerLogs(wsConn *websocket.Conn, container, sinc
global.LOG.Errorf("read bytes from container log failed, err: %v", err)
continue
}
if err = wsConn.WriteMessage(websocket.TextMessage, bytes); err != nil {
if err = wsConn.WriteMessage(websocket.TextMessage, buffer[:n]); err != nil {
global.LOG.Errorf("send message with container log to ws failed, err: %v", err)
break
}

72
cmd/server/docs/docs.go

@ -490,8 +490,8 @@ var doc = `{
"installId",
"operate"
],
"formatEN": "[appKey] App [appName] [operate]",
"formatZH": "[appKey] 应用 [appName] [operate]",
"formatEN": "[operate] App [appKey][appName]",
"formatZH": "[operate] 应用 [appKey][appName]",
"paramKeys": []
}
}
@ -2327,32 +2327,37 @@ var doc = `{
}
],
"description": "容器日志",
"consumes": [
"application/json"
],
"tags": [
"Container"
],
"summary": "Container logs",
"parameters": [
{
"description": "request",
"name": "request",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/dto.ContainerLog"
}
"type": "string",
"description": "容器名称",
"name": "container",
"in": "query"
},
{
"type": "string",
"description": "时间筛选",
"name": "since",
"in": "query"
},
{
"type": "string",
"description": "是否追踪",
"name": "follow",
"in": "query"
},
{
"type": "string",
"description": "显示行号",
"name": "tail",
"in": "query"
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"type": "string"
}
}
}
"responses": {}
}
},
"/containers/stats/:id": {
@ -10635,21 +10640,6 @@ var doc = `{
}
}
},
"dto.ContainerLog": {
"type": "object",
"required": [
"containerID",
"mode"
],
"properties": {
"containerID": {
"type": "string"
},
"mode": {
"type": "string"
}
}
},
"dto.ContainerOperation": {
"type": "object",
"required": [
@ -10825,8 +10815,8 @@ var doc = `{
},
"week": {
"type": "integer",
"maximum": 7,
"minimum": 1
"maximum": 6,
"minimum": 0
}
}
},
@ -10904,8 +10894,8 @@ var doc = `{
},
"week": {
"type": "integer",
"maximum": 7,
"minimum": 1
"maximum": 6,
"minimum": 0
}
}
},
@ -12347,10 +12337,10 @@ var doc = `{
"dto.SSHHistory": {
"type": "object",
"properties": {
"Area": {
"address": {
"type": "string"
},
"address": {
"area": {
"type": "string"
},
"authMode": {

72
cmd/server/docs/swagger.json

@ -476,8 +476,8 @@
"installId",
"operate"
],
"formatEN": "[appKey] App [appName] [operate]",
"formatZH": "[appKey] 应用 [appName] [operate]",
"formatEN": "[operate] App [appKey][appName]",
"formatZH": "[operate] 应用 [appKey][appName]",
"paramKeys": []
}
}
@ -2313,32 +2313,37 @@
}
],
"description": "容器日志",
"consumes": [
"application/json"
],
"tags": [
"Container"
],
"summary": "Container logs",
"parameters": [
{
"description": "request",
"name": "request",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/dto.ContainerLog"
}
"type": "string",
"description": "容器名称",
"name": "container",
"in": "query"
},
{
"type": "string",
"description": "时间筛选",
"name": "since",
"in": "query"
},
{
"type": "string",
"description": "是否追踪",
"name": "follow",
"in": "query"
},
{
"type": "string",
"description": "显示行号",
"name": "tail",
"in": "query"
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"type": "string"
}
}
}
"responses": {}
}
},
"/containers/stats/:id": {
@ -10621,21 +10626,6 @@
}
}
},
"dto.ContainerLog": {
"type": "object",
"required": [
"containerID",
"mode"
],
"properties": {
"containerID": {
"type": "string"
},
"mode": {
"type": "string"
}
}
},
"dto.ContainerOperation": {
"type": "object",
"required": [
@ -10811,8 +10801,8 @@
},
"week": {
"type": "integer",
"maximum": 7,
"minimum": 1
"maximum": 6,
"minimum": 0
}
}
},
@ -10890,8 +10880,8 @@
},
"week": {
"type": "integer",
"maximum": 7,
"minimum": 1
"maximum": 6,
"minimum": 0
}
}
},
@ -12333,10 +12323,10 @@
"dto.SSHHistory": {
"type": "object",
"properties": {
"Area": {
"address": {
"type": "string"
},
"address": {
"area": {
"type": "string"
},
"authMode": {

56
cmd/server/docs/swagger.yaml

@ -290,16 +290,6 @@ definitions:
$ref: '#/definitions/dto.VolumeHelper'
type: array
type: object
dto.ContainerLog:
properties:
containerID:
type: string
mode:
type: string
required:
- containerID
- mode
type: object
dto.ContainerOperation:
properties:
name:
@ -415,8 +405,8 @@ definitions:
website:
type: string
week:
maximum: 7
minimum: 1
maximum: 6
minimum: 0
type: integer
required:
- name
@ -469,8 +459,8 @@ definitions:
website:
type: string
week:
maximum: 7
minimum: 1
maximum: 6
minimum: 0
type: integer
required:
- id
@ -1437,10 +1427,10 @@ definitions:
type: object
dto.SSHHistory:
properties:
Area:
type: string
address:
type: string
area:
type: string
authMode:
type: string
date:
@ -3670,8 +3660,8 @@ paths:
bodyKeys:
- installId
- operate
formatEN: '[appKey] App [appName] [operate]'
formatZH: '[appKey] 应用 [appName] [operate]'
formatEN: '[operate] App [appKey][appName]'
formatZH: '[operate] 应用 [appKey][appName]'
paramKeys: []
/apps/installed/params/:appInstallId:
get:
@ -4836,21 +4826,25 @@ paths:
- Container
/containers/search/log:
post:
consumes:
- application/json
description: 容器日志
parameters:
- description: request
in: body
name: request
required: true
schema:
$ref: '#/definitions/dto.ContainerLog'
responses:
"200":
description: OK
schema:
type: string
- description: 容器名称
in: query
name: container
type: string
- description: 时间筛选
in: query
name: since
type: string
- description: 是否追踪
in: query
name: follow
type: string
- description: 显示行号
in: query
name: tail
type: string
responses: {}
security:
- ApiKeyAuth: []
summary: Container logs

4
frontend/src/api/interface/container.ts

@ -61,10 +61,6 @@ export namespace Container {
networkTX: number;
shotTime: Date;
}
export interface ContainerLogSearch {
containerID: string;
mode: string;
}
export interface ContainerInspect {
id: string;
type: string;

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

@ -8,9 +8,6 @@ export const searchContainer = (params: Container.ContainerSearch) => {
export const createContainer = (params: Container.ContainerCreate) => {
return http.post(`/containers`, params, 3000000);
};
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 });
};

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

@ -2,8 +2,19 @@
<div>
<div>
<el-select @change="searchLogs" style="width: 10%; float: left" v-model="logSearch.mode">
<template #prefix>{{ $t('container.fetch') }}</template>
<el-option v-for="item in timeOptions" :key="item.label" :value="item.value" :label="item.label" />
</el-select>
<el-input
@change="searchLogs"
class="margin-button"
style="width: 10%; float: left"
v-model.number="logSearch.tail"
>
<template #prefix>
<div style="margin-left: 2px">{{ $t('container.lines') }}</div>
</template>
</el-input>
<div class="margin-button" style="float: left">
<el-checkbox border v-model="logSearch.isWatch">{{ $t('commons.button.watch') }}</el-checkbox>
</div>
@ -34,14 +45,14 @@
</template>
<script lang="ts" setup>
import { cleanContainerLog, logContainer } from '@/api/modules/container';
import { cleanContainerLog } from '@/api/modules/container';
import i18n from '@/lang';
import { dateFormatForName } from '@/utils/util';
import { nextTick, onBeforeUnmount, reactive, ref, shallowRef } from 'vue';
import { 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';
import { MsgError, MsgSuccess } from '@/utils/message';
const extensions = [javascript(), oneDark];
@ -50,14 +61,15 @@ const view = shallowRef();
const handleReady = (payload) => {
view.value = payload.view;
};
const terminalSocket = ref<WebSocket>();
const logSearch = reactive({
isWatch: false,
container: '',
containerID: '',
mode: 'all',
tail: 100,
});
let timer: NodeJS.Timer | null = null;
const timeOptions = ref([
{ label: i18n.global.t('container.all'), value: 'all' },
@ -80,15 +92,26 @@ const timeOptions = ref([
]);
const searchLogs = async () => {
const res = await logContainer(logSearch);
logInfo.value = res.data || '';
nextTick(() => {
if (!Number(logSearch.tail) || Number(logSearch.tail) <= 0) {
MsgError(i18n.global.t('container.linesHelper'));
return;
}
terminalSocket.value?.close();
logInfo.value = '';
const href = window.location.href;
const protocol = href.split('//')[0] === 'http:' ? 'ws' : 'wss';
const host = href.split('//')[1].split('/')[0];
terminalSocket.value = new WebSocket(
`${protocol}://${host}/api/v1/containers/search/log?container=${logSearch.containerID}&since=${logSearch.mode}&tail=${logSearch.tail}&follow=${logSearch.isWatch}`,
);
terminalSocket.value.onmessage = (event) => {
logInfo.value += event.data;
const state = view.value.state;
view.value.dispatch({
selection: { anchor: state.doc.length, head: state.doc.length },
scrollIntoView: true,
});
});
};
};
const onDownload = async () => {
@ -108,15 +131,11 @@ interface DialogProps {
const acceptParams = (props: DialogProps): void => {
logSearch.containerID = props.containerID;
logSearch.tail = 100;
logSearch.mode = 'all';
logSearch.isWatch = false;
logSearch.container = props.container;
searchLogs();
timer = setInterval(() => {
if (logSearch.isWatch) {
searchLogs();
}
}, 1000 * 5);
};
const onClean = async () => {
@ -132,8 +151,7 @@ const onClean = async () => {
};
onBeforeUnmount(() => {
clearInterval(Number(timer));
timer = null;
terminalSocket.value?.close();
});
defineExpose({

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

@ -136,7 +136,7 @@ watch(logVisiable, (val) => {
});
const searchLogs = async () => {
if (!Number(logSearch.tail) || Number(logSearch.tail) <= 0) {
MsgError(global.i18n.$t('container.linesHelper'));
MsgError(i18n.global.t('container.linesHelper'));
return;
}
terminalSocket.value?.close();
@ -145,7 +145,7 @@ const searchLogs = async () => {
const protocol = href.split('//')[0] === 'http:' ? 'ws' : 'wss';
const host = href.split('//')[1].split('/')[0];
terminalSocket.value = new WebSocket(
`${protocol}://${host}/containers/search/log?container=${logSearch.containerID}&since=${logSearch.mode}&tail=${logSearch.tail}&follow=${logSearch.isWatch}`,
`${protocol}://${host}/api/v1/containers/search/log?container=${logSearch.containerID}&since=${logSearch.mode}&tail=${logSearch.tail}&follow=${logSearch.isWatch}`,
);
terminalSocket.value.onmessage = (event) => {
logInfo.value += event.data;
@ -188,7 +188,7 @@ const acceptParams = (props: DialogProps): void => {
logVisiable.value = true;
logSearch.containerID = props.containerID;
logSearch.tail = 100;
logSearch.mode = '10m';
logSearch.mode = 'all';
logSearch.isWatch = false;
logSearch.container = props.container;
searchLogs();

Loading…
Cancel
Save