mirror of
https://github.com/1Panel-dev/1Panel.git
synced 2025-12-11 12:56:10 +08:00
fix: Fix the issue of container compose log cleanup failure
This commit is contained in:
parent
f9a20ec443
commit
a59d8ff2bd
8 changed files with 151 additions and 18 deletions
|
|
@ -393,6 +393,28 @@ func (b *BaseApi) CleanContainerLog(c *gin.Context) {
|
|||
helper.Success(c)
|
||||
}
|
||||
|
||||
// @Tags Container
|
||||
// @Summary Clean compose log
|
||||
// @Accept json
|
||||
// @Param request body dto.ComposeLogClean true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /containers/compose/clean/log [post]
|
||||
// @x-panel-log {"bodyKeys":["name"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"清理容器编排 [name] 日志","formatEN":"clean compose [name] logs"}
|
||||
func (b *BaseApi) CleanComposeLog(c *gin.Context) {
|
||||
var req dto.ComposeLogClean
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if err := containerService.ComposeLogClean(req); err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
}
|
||||
helper.Success(c)
|
||||
}
|
||||
|
||||
// @Tags Container
|
||||
// @Summary Rename Container
|
||||
// @Accept json
|
||||
|
|
|
|||
|
|
@ -291,6 +291,10 @@ type ComposeUpdate struct {
|
|||
Content string `json:"content" validate:"required"`
|
||||
Env []string `json:"env"`
|
||||
}
|
||||
type ComposeLogClean struct {
|
||||
Name string `json:"name" validate:"required"`
|
||||
Path string `json:"path" validate:"required"`
|
||||
}
|
||||
|
||||
type ContainerLog struct {
|
||||
Container string `json:"container" validate:"required"`
|
||||
|
|
|
|||
|
|
@ -85,6 +85,7 @@ type IContainerService interface {
|
|||
CreateVolume(req dto.VolumeCreate) error
|
||||
TestCompose(req dto.ComposeCreate) (bool, error)
|
||||
ComposeUpdate(req dto.ComposeUpdate) error
|
||||
ComposeLogClean(req dto.ComposeLogClean) error
|
||||
Prune(req dto.ContainerPrune) error
|
||||
|
||||
LoadUsers(req dto.OperationWithName) []string
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import (
|
|||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
|
|
@ -284,6 +285,57 @@ func (u *ContainerService) ComposeUpdate(req dto.ComposeUpdate) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (u *ContainerService) ComposeLogClean(req dto.ComposeLogClean) error {
|
||||
client, err := docker.NewDockerClient()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer client.Close()
|
||||
|
||||
options := container.ListOptions{All: true}
|
||||
options.Filters = filters.NewArgs()
|
||||
options.Filters.Add("label", composeProjectLabel)
|
||||
|
||||
list, err := client.ContainerList(context.Background(), options)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ctx := context.Background()
|
||||
for _, item := range list {
|
||||
if name, ok := item.Labels[composeProjectLabel]; ok {
|
||||
if name != req.Name {
|
||||
continue
|
||||
}
|
||||
containerItem, err := client.ContainerInspect(ctx, item.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := client.ContainerStop(ctx, containerItem.ID, container.StopOptions{}); err != nil {
|
||||
return err
|
||||
}
|
||||
file, err := os.OpenFile(containerItem.LogPath, os.O_RDWR|os.O_CREATE, constant.FilePerm)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer file.Close()
|
||||
if err = file.Truncate(0); err != nil {
|
||||
return err
|
||||
}
|
||||
_, _ = file.Seek(0, 0)
|
||||
|
||||
files, _ := filepath.Glob(fmt.Sprintf("%s.*", containerItem.LogPath))
|
||||
for _, file := range files {
|
||||
_ = os.Remove(file)
|
||||
}
|
||||
}
|
||||
}
|
||||
return u.ComposeOperation(dto.ComposeOperation{
|
||||
Name: req.Name,
|
||||
Path: req.Path,
|
||||
Operation: "restart",
|
||||
})
|
||||
}
|
||||
|
||||
func (u *ContainerService) loadPath(req *dto.ComposeCreate) error {
|
||||
if req.From == "template" || req.From == "edit" {
|
||||
dir := fmt.Sprintf("%s/docker/compose/%s", global.Dir.DataDir, req.Name)
|
||||
|
|
|
|||
|
|
@ -48,6 +48,7 @@ func (s *ContainerRouter) InitRouter(Router *gin.RouterGroup) {
|
|||
baRouter.POST("/compose", baseApi.CreateCompose)
|
||||
baRouter.POST("/compose/test", baseApi.TestCompose)
|
||||
baRouter.POST("/compose/operate", baseApi.OperatorCompose)
|
||||
baRouter.POST("/compose/clean/log", baseApi.CleanComposeLog)
|
||||
baRouter.POST("/compose/update", baseApi.ComposeUpdate)
|
||||
|
||||
baRouter.GET("/template", baseApi.ListComposeTemplate)
|
||||
|
|
|
|||
|
|
@ -39,9 +39,17 @@ export const commitContainer = (params: Container.ContainerCommit) => {
|
|||
export const loadContainerInfo = (name: string) => {
|
||||
return http.post<Container.ContainerHelper>(`/containers/info`, { name: name });
|
||||
};
|
||||
export const cleanComposeLog = (composeName: string, composePath: string, operateNode?: string) => {
|
||||
const params = operateNode ? `?operateNode=${operateNode}` : '';
|
||||
return http.post(
|
||||
`/containers/compose/clean/log${params}`,
|
||||
{ name: composeName, path: composePath },
|
||||
TimeoutEnum.T_60S,
|
||||
);
|
||||
};
|
||||
export const cleanContainerLog = (containerName: string, operateNode?: string) => {
|
||||
const params = operateNode ? `?operateNode=${operateNode}` : '';
|
||||
return http.post(`/containers/clean/log${params}`, { name: containerName });
|
||||
return http.post(`/containers/clean/log${params}`, { name: containerName }, TimeoutEnum.T_60S);
|
||||
};
|
||||
export const containerItemStats = (containerID: string) => {
|
||||
return http.post<Container.ContainerItemStats>(`/containers/item/stats`, { name: containerID }, TimeoutEnum.T_60S);
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@
|
|||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { cleanContainerLog, DownloadFile } from '@/api/modules/container';
|
||||
import { cleanComposeLog, cleanContainerLog, DownloadFile } from '@/api/modules/container';
|
||||
import i18n from '@/lang';
|
||||
import { dateFormatForName } from '@/utils/util';
|
||||
import { computed, nextTick, onMounted, onUnmounted, reactive, ref } from 'vue';
|
||||
|
|
@ -48,6 +48,8 @@ import hightlight from '@/components/log/custom-hightlight/index.vue';
|
|||
import { GlobalStore } from '@/store';
|
||||
const globalStore = GlobalStore();
|
||||
|
||||
const em = defineEmits(['update:loading']);
|
||||
|
||||
const props = defineProps({
|
||||
container: {
|
||||
type: String,
|
||||
|
|
@ -93,6 +95,7 @@ const logSearch = reactive({
|
|||
mode: 'all',
|
||||
tail: props.defaultFollow ? 0 : 100,
|
||||
compose: '',
|
||||
resource: '',
|
||||
});
|
||||
const logHeight = 20;
|
||||
const logCount = computed(() => logs.value.length);
|
||||
|
|
@ -215,6 +218,19 @@ const onClean = async () => {
|
|||
if (props.node && props.node !== '') {
|
||||
currentNode = props.node;
|
||||
}
|
||||
if (logSearch.compose !== '') {
|
||||
em('update:loading', true);
|
||||
await cleanComposeLog(logSearch.resource, logSearch.compose, currentNode)
|
||||
.then(() => {
|
||||
em('update:loading', false);
|
||||
searchLogs();
|
||||
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
|
||||
})
|
||||
.finally(() => {
|
||||
em('update:loading', false);
|
||||
});
|
||||
return;
|
||||
}
|
||||
await cleanContainerLog(logSearch.container, currentNode);
|
||||
searchLogs();
|
||||
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
|
||||
|
|
@ -240,6 +256,7 @@ const resizeObserver = ref<ResizeObserver | null>(null);
|
|||
onMounted(() => {
|
||||
logSearch.container = props.container;
|
||||
logSearch.compose = props.compose;
|
||||
logSearch.resource = props.resource;
|
||||
|
||||
logVisible.value = true;
|
||||
logSearch.tail = 100;
|
||||
|
|
|
|||
|
|
@ -97,13 +97,7 @@
|
|||
>
|
||||
{{ $t('commons.operate.restart') }}
|
||||
</el-button>
|
||||
<el-button
|
||||
:disabled="row.createdBy !== '1Panel'"
|
||||
plain
|
||||
round
|
||||
size="small"
|
||||
@click="onDelete(row)"
|
||||
>
|
||||
<el-button plain round size="small" @click="onDelete(row)">
|
||||
{{ $t('commons.operate.delete') }}
|
||||
</el-button>
|
||||
</div>
|
||||
|
|
@ -174,8 +168,7 @@
|
|||
<el-descriptions-item :label="$t('container.memTotal')">
|
||||
{{ computeSizeForDocker(row.memoryLimit) }}
|
||||
</el-descriptions-item>
|
||||
|
||||
<el-descriptions-item>
|
||||
<el-descriptions-item v-if="row.hasLoadSize">
|
||||
<template #label>
|
||||
{{ $t('container.sizeRw') }}
|
||||
<el-tooltip :content="$t('container.sizeRwHelper')">
|
||||
|
|
@ -184,7 +177,10 @@
|
|||
</template>
|
||||
{{ computeSize2(row.sizeRw) }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item :label="$t('container.sizeRootFs')">
|
||||
<el-descriptions-item
|
||||
:label="$t('container.sizeRootFs')"
|
||||
v-if="row.hasLoadSize"
|
||||
>
|
||||
<template #label>
|
||||
{{ $t('container.sizeRootFs') }}
|
||||
<el-tooltip :content="$t('container.sizeRootFsHelper')">
|
||||
|
|
@ -194,6 +190,17 @@
|
|||
{{ computeSize2(row.sizeRootFs) }}
|
||||
</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
|
||||
<el-button
|
||||
class="mt-2"
|
||||
v-if="!row.hasLoadSize"
|
||||
size="small"
|
||||
link
|
||||
type="primary"
|
||||
@click="loadSize(row)"
|
||||
>
|
||||
{{ $t('container.loadSize') }}
|
||||
</el-button>
|
||||
</template>
|
||||
</el-popover>
|
||||
</div>
|
||||
|
|
@ -252,8 +259,10 @@
|
|||
|
||||
<div v-show="showType === 'log'">
|
||||
<ContainerLog
|
||||
v-model:loading="detailLoading"
|
||||
:key="currentCompose.path"
|
||||
:compose="currentCompose.path"
|
||||
:resource="currentCompose.name"
|
||||
:highlightDiff="450"
|
||||
:defaultFollow="true"
|
||||
/>
|
||||
|
|
@ -282,7 +291,14 @@ import TerminalDialog from '@/views/container/container/terminal/index.vue';
|
|||
import ContainerLogDialog from '@/components/log/container-drawer/index.vue';
|
||||
import CreateDialog from '@/views/container/compose/create/index.vue';
|
||||
import DeleteDialog from '@/views/container/compose/delete/index.vue';
|
||||
import { composeOperator, composeUpdate, containerListStats, inspect, searchCompose } from '@/api/modules/container';
|
||||
import {
|
||||
composeOperator,
|
||||
composeUpdate,
|
||||
containerItemStats,
|
||||
containerListStats,
|
||||
inspect,
|
||||
searchCompose,
|
||||
} from '@/api/modules/container';
|
||||
import DockerStatus from '@/views/container/docker-status/index.vue';
|
||||
import i18n from '@/lang';
|
||||
import { Container } from '@/api/interface/container';
|
||||
|
|
@ -381,11 +397,15 @@ const loadDetail = async (row: Container.ComposeInfo, withRefresh: boolean) => {
|
|||
detailLoading.value = true;
|
||||
currentCompose.value = row;
|
||||
composeContainers.value = row.containers || [];
|
||||
await inspect({ id: currentCompose.value.name, type: 'compose' }).then((res) => {
|
||||
composeContent.value = res.data;
|
||||
detailLoading.value = false;
|
||||
});
|
||||
loadContainerStats();
|
||||
await inspect({ id: currentCompose.value.name, type: 'compose' })
|
||||
.then((res) => {
|
||||
composeContent.value = res.data;
|
||||
detailLoading.value = false;
|
||||
})
|
||||
.finally(() => {
|
||||
loadContainerStats();
|
||||
detailLoading.value = false;
|
||||
});
|
||||
};
|
||||
|
||||
const loadContainerStats = async () => {
|
||||
|
|
@ -440,6 +460,14 @@ const handleComposeOperate = async (operation: 'up' | 'stop' | 'restart', row: a
|
|||
});
|
||||
};
|
||||
|
||||
const loadSize = async (row: any) => {
|
||||
containerItemStats(row.containerID).then((res) => {
|
||||
row.sizeRw = res.data.sizeRw || 0;
|
||||
row.sizeRootFs = res.data.sizeRootFs || 0;
|
||||
row.hasLoadSize = true;
|
||||
});
|
||||
};
|
||||
|
||||
const onSubmitEdit = async () => {
|
||||
const param = {
|
||||
name: currentCompose.value.name,
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue