feat: 增加任务日志列表 (#5999)

pull/6011/head
zhengkunwang 2024-08-01 17:37:36 +08:00 committed by GitHub
parent 636e149b29
commit bdece10868
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
20 changed files with 302 additions and 50 deletions

View File

@ -66,4 +66,5 @@ var (
favoriteService = service.NewIFavoriteService()
websiteCAService = service.NewIWebsiteCAService()
taskService = service.NewITaskService()
)

32
agent/app/api/v1/task.go Normal file
View File

@ -0,0 +1,32 @@
package v1
import (
"github.com/1Panel-dev/1Panel/agent/app/api/v1/helper"
"github.com/1Panel-dev/1Panel/agent/app/dto"
"github.com/1Panel-dev/1Panel/agent/constant"
"github.com/gin-gonic/gin"
)
// @Tags TaskLog
// @Summary Page task logs
// @Description 获取任务日志列表
// @Accept json
// @Param request body dto.SearchTaskLogReq true "request"
// @Success 200 {object} dto.PageResult
// @Security ApiKeyAuth
// @Router /logs/tasks/search [post]
func (b *BaseApi) PageTasks(c *gin.Context) {
var req dto.SearchTaskLogReq
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}
total, list, err := taskService.Page(req)
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithData(c, dto.PageResult{
Items: list,
Total: total,
})
}

View File

@ -1,6 +1,7 @@
package dto
import (
"github.com/1Panel-dev/1Panel/agent/app/model"
"time"
)
@ -48,3 +49,13 @@ type LoginLog struct {
type CleanLog struct {
LogType string `json:"logType" validate:"required,oneof=login operation"`
}
type SearchTaskLogReq struct {
Status string `json:"status"`
Type string `json:"type"`
PageInfo
}
type TaskDTO struct {
model.Task
}

View File

@ -18,6 +18,7 @@ type ITaskRepo interface {
WithByID(id string) DBOption
WithType(taskType string) DBOption
WithResourceID(id uint) DBOption
WithStatus(status string) DBOption
}
func NewITaskRepo() ITaskRepo {
@ -36,6 +37,12 @@ func (t TaskRepo) WithType(taskType string) DBOption {
}
}
func (t TaskRepo) WithStatus(status string) DBOption {
return func(g *gorm.DB) *gorm.DB {
return g.Where("status = ?", status)
}
}
func (t TaskRepo) WithResourceID(id uint) DBOption {
return func(g *gorm.DB) *gorm.DB {
return g.Where("resource_id = ?", id)

42
agent/app/service/task.go Normal file
View File

@ -0,0 +1,42 @@
package service
import (
"github.com/1Panel-dev/1Panel/agent/app/dto"
"github.com/1Panel-dev/1Panel/agent/app/repo"
)
type TaskLogService struct{}
type ITaskLogService interface {
Page(req dto.SearchTaskLogReq) (int64, []dto.TaskDTO, error)
}
func NewITaskService() ITaskLogService {
return &TaskLogService{}
}
func (u *TaskLogService) Page(req dto.SearchTaskLogReq) (int64, []dto.TaskDTO, error) {
opts := []repo.DBOption{
commonRepo.WithOrderBy("created_at desc"),
}
if req.Status != "" {
opts = append(opts, taskRepo.WithStatus(req.Status))
}
if req.Type != "" {
opts = append(opts, taskRepo.WithType(req.Type))
}
total, tasks, err := taskRepo.Page(
req.Page,
req.PageSize,
opts...,
)
var items []dto.TaskDTO
for _, t := range tasks {
item := dto.TaskDTO{
Task: t,
}
items = append(items, item)
}
return total, items, err
}

View File

@ -14,5 +14,6 @@ func (s *LogRouter) InitRouter(Router *gin.RouterGroup) {
{
operationRouter.GET("/system/files", baseApi.GetSystemFiles)
operationRouter.POST("/system", baseApi.GetSystemLogs)
operationRouter.POST("/tasks/search", baseApi.PageTasks)
}
}

View File

@ -40,4 +40,23 @@ export namespace Log {
export interface CleanLog {
logType: string;
}
export interface SearchTaskReq extends ReqPage {
type: string;
status: string;
}
export interface Task {
id: string;
name: string;
type: string;
logFile: string;
status: string;
errorMsg: string;
operationLogID: number;
resourceID: number;
currentStep: string;
endAt: Date;
createdAt: Date;
}
}

View File

@ -20,3 +20,7 @@ export const getSystemLogs = (name: string) => {
export const cleanLogs = (param: Log.CleanLog) => {
return http.post(`/logs/clean`, param);
};
export const searchTasks = (req: Log.SearchTaskReq) => {
return http.post<ResPage<Log.Task>>(`/logs/tasks/search`, req);
};

View File

@ -131,7 +131,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}/api/v1/containers/compose/search/log?compose=${logSearch.compose}&since=${logSearch.mode}&tail=${logSearch.tail}&follow=${logSearch.isWatch}`,
`${protocol}://${host}/api/v2/containers/compose/search/log?compose=${logSearch.compose}&since=${logSearch.mode}&tail=${logSearch.tail}&follow=${logSearch.isWatch}`,
);
terminalSocket.value.onmessage = (event) => {
logInfo.value += event.data;

View File

@ -1155,6 +1155,9 @@ const message = {
websiteLog: 'Website Logs',
runLog: 'Run Log',
errLog: 'Err Log',
task: 'Task Log',
taskName: 'Task Name',
taskRunning: 'Running',
},
file: {
dir: 'Folder',

View File

@ -1093,6 +1093,9 @@ const message = {
websiteLog: '',
runLog: '',
errLog: '',
task: '',
taskName: '',
taskRunning: '',
},
file: {
dir: '',

View File

@ -1095,6 +1095,9 @@ const message = {
websiteLog: '',
runLog: '',
errLog: '',
task: '',
taskName: '',
taskRunning: '',
},
file: {
dir: '',

View File

@ -67,6 +67,16 @@ const logsRouter = {
requiresAuth: false,
},
},
{
path: 'task',
name: 'Task',
component: () => import('@/views/log/task/index.vue'),
hidden: true,
meta: {
activeMenu: '/logs',
requiresAuth: false,
},
},
],
},
],

View File

@ -53,7 +53,7 @@ const loadDataFromDB = async () => {
export async function loadProductProFromDB() {
const res = await getLicenseStatus();
if (!res.data) {
if (!res || !res.data) {
resetXSetting();
globalStore.isProductPro = false;
} else {

View File

@ -546,7 +546,7 @@ const hideEntrance = () => {
const loadUpgradeStatus = async () => {
const res = await loadUpgradeInfo();
if (res.data.testVersion || res.data.newVersion || res.data.latestVersion) {
if (res && (res.data.testVersion || res.data.newVersion || res.data.latestVersion)) {
globalStore.hasNewVersion = true;
} else {
globalStore.hasNewVersion = false;

View File

@ -2,15 +2,7 @@
<div>
<LayoutContent v-loading="loading" :title="$t('logs.login')">
<template #search>
<el-button class="tag-button no-active" @click="onChangeRoute('OperationLog')">
{{ $t('logs.operation') }}
</el-button>
<el-button class="tag-button" type="primary" @click="onChangeRoute('LoginLog')">
{{ $t('logs.login') }}
</el-button>
<el-button class="tag-button no-active" @click="onChangeRoute('SystemLog')">
{{ $t('logs.system') }}
</el-button>
<LogRouter current="LoginLog" />
</template>
<template #leftToolBar>
<el-button type="primary" plain @click="onClean()">
@ -59,13 +51,12 @@
<script setup lang="ts">
import ConfirmDialog from '@/components/confirm-dialog/index.vue';
import LogRouter from '@/views/log/router/index.vue';
import { dateFormat } from '@/utils/util';
import { cleanLogs, getLoginLogs } from '@/api/modules/log';
import { onMounted, reactive, ref } from '@vue/runtime-core';
import i18n from '@/lang';
import { MsgSuccess } from '@/utils/message';
import { useRouter } from 'vue-router';
const router = useRouter();
const loading = ref();
const data = ref();
@ -98,10 +89,6 @@ const search = async () => {
});
};
const onChangeRoute = async (addr: string) => {
router.push({ name: addr });
};
const onClean = async () => {
let params = {
header: i18n.global.t('logs.deleteLogs'),

View File

@ -2,15 +2,7 @@
<div>
<LayoutContent v-loading="loading" :title="$t('logs.operation')">
<template #search>
<el-button type="primary" class="tag-button" @click="onChangeRoute('OperationLog')">
{{ $t('logs.operation') }}
</el-button>
<el-button class="tag-button no-active" @click="onChangeRoute('LoginLog')">
{{ $t('logs.login') }}
</el-button>
<el-button class="tag-button no-active" @click="onChangeRoute('SystemLog')">
{{ $t('logs.system') }}
</el-button>
<LogRouter current="OperationLog" />
</template>
<template #leftToolBar>
<el-button type="primary" plain @click="onClean()">
@ -96,14 +88,13 @@
<script setup lang="ts">
import ConfirmDialog from '@/components/confirm-dialog/index.vue';
import LogRouter from '@/views/log/router/index.vue';
import { dateFormat } from '@/utils/util';
import { cleanLogs, getOperationLogs } from '@/api/modules/log';
import { onMounted, reactive, ref } from '@vue/runtime-core';
import i18n from '@/lang';
import { MsgSuccess } from '@/utils/message';
import { GlobalStore } from '@/store';
import { useRouter } from 'vue-router';
const router = useRouter();
const loading = ref();
const data = ref();
@ -154,10 +145,6 @@ const onClean = async () => {
confirmDialogRef.value!.acceptParams(params);
};
const onChangeRoute = async (addr: string) => {
router.push({ name: addr });
};
const loadDetail = (log: string) => {
if (log.indexOf('[enable]') !== -1) {
log = log.replace('[enable]', '[' + i18n.global.t('commons.button.enable') + ']');

View File

@ -0,0 +1,50 @@
<template>
<div>
<el-button
class="tag-button"
:class="current != 'OperationLog' ? 'no-active' : ''"
:type="current === 'OperationLog' ? 'primary' : ''"
@click="onChangeRoute('OperationLog')"
>
{{ $t('logs.operation') }}
</el-button>
<el-button
class="tag-button"
:class="current != 'LoginLog' ? 'no-active' : ''"
:type="current === 'LoginLog' ? 'primary' : ''"
@click="onChangeRoute('LoginLog')"
>
{{ $t('logs.login') }}
</el-button>
<el-button
class="tag-button"
:class="current != 'SystemLog' ? 'no-active' : ''"
:type="current === 'SystemLog' ? 'primary' : ''"
@click="onChangeRoute('SystemLog')"
>
{{ $t('logs.system') }}
</el-button>
<el-button
class="tag-button"
:class="current != 'Task' ? 'no-active' : ''"
:type="current === 'Task' ? 'primary' : ''"
@click="onChangeRoute('Task')"
>
{{ $t('logs.task') }}
</el-button>
</div>
</template>
<script setup lang="ts">
import { useRouter } from 'vue-router';
defineProps({
current: {
type: String,
default: 'LoginLog',
},
});
const router = useRouter();
const onChangeRoute = async (addr: string) => {
router.push({ name: addr });
};
</script>

View File

@ -2,15 +2,7 @@
<div>
<LayoutContent v-loading="loading" :title="$t('logs.system')">
<template #search>
<el-button class="tag-button no-active" @click="onChangeRoute('OperationLog')">
{{ $t('logs.operation') }}
</el-button>
<el-button class="tag-button no-active" @click="onChangeRoute('LoginLog')">
{{ $t('logs.login') }}
</el-button>
<el-button class="tag-button" type="primary" @click="onChangeRoute('SystemLog')">
{{ $t('logs.system') }}
</el-button>
<LogRouter current="SystemLog" />
</template>
<template #leftToolBar>
<el-select class="p-w-200 mr-2.5" v-model="logConfig.name" @change="search()">
@ -39,12 +31,11 @@
</template>
<script setup lang="ts">
import { nextTick, onMounted, reactive, ref } from 'vue';
import { useRouter } from 'vue-router';
import { getSystemFiles } from '@/api/modules/log';
import LogFile from '@/components/log-file/index.vue';
import LogRouter from '@/views/log/router/index.vue';
import { nextTick, onMounted, reactive, ref } from 'vue';
import { getSystemFiles } from '@/api/modules/log';
const router = useRouter();
const loading = ref();
const isWatch = ref();
const fileList = ref();
@ -77,10 +68,6 @@ const search = () => {
});
};
const onChangeRoute = async (addr: string) => {
router.push({ name: addr });
};
onMounted(() => {
loadFiles();
});

View File

@ -0,0 +1,105 @@
<template>
<div>
<LayoutContent v-loading="loading" :title="$t('logs.task')">
<template #search>
<LogRouter current="Task" />
</template>
<template #rightToolBar>
<el-select v-model="req.status" @change="search()" clearable class="p-w-200 mr-2.5">
<template #prefix>{{ $t('commons.table.status') }}</template>
<el-option :label="$t('commons.table.all')" value=""></el-option>
<el-option :label="$t('commons.status.success')" value="Success"></el-option>
<el-option :label="$t('commons.status.failed')" value="Failed"></el-option>
<el-option :label="$t('logs.taskRunning')" value="Running"></el-option>
</el-select>
<TableSetting @search="search()" />
</template>
<template #main>
<ComplexTable :pagination-config="paginationConfig" :data="data" @search="search" :heightDiff="370">
<el-table-column :label="$t('logs.taskName')" prop="name" />
<el-table-column :label="$t('commons.table.type')" prop="type" />
<el-table-column :label="$t('commons.table.status')" prop="status">
<template #default="{ row }">
<div v-if="row.status === 'Success'">
<el-tag type="success">{{ $t('commons.status.success') }}</el-tag>
</div>
<div v-else>
<el-tooltip
class="box-item"
effect="dark"
:content="row.errorMsg"
placement="top-start"
>
<el-tag type="danger">{{ $t('commons.status.failed') }}</el-tag>
</el-tooltip>
</div>
</template>
</el-table-column>
<el-table-column :label="$t('website.log')" prop="log">
<template #default="{ row }">
<el-button @click="openTaskLog(row)" link type="primary">
{{ $t('website.check') }}
</el-button>
</template>
</el-table-column>
<el-table-column
prop="createdAt"
:label="$t('commons.table.date')"
:formatter="dateFormat"
show-overflow-tooltip
/>
</ComplexTable>
</template>
</LayoutContent>
<TaskLog ref="taskLogRef" />
</div>
</template>
<script setup lang="ts">
import LogRouter from '@/views/log/router/index.vue';
import { dateFormat } from '@/utils/util';
import { searchTasks } from '@/api/modules/log';
import { onMounted, reactive, ref } from '@vue/runtime-core';
import { Log } from '@/api/interface/log';
import TaskLog from '@/components/task-log/index.vue';
const loading = ref();
const data = ref();
const paginationConfig = reactive({
cacheSizeKey: 'login-log-page-size',
currentPage: 1,
pageSize: 10,
total: 0,
});
const taskLogRef = ref();
const req = reactive({
type: '',
status: '',
page: 1,
pageSize: 10,
});
const search = async () => {
req.page = paginationConfig.currentPage;
req.pageSize = paginationConfig.pageSize;
loading.value = true;
await searchTasks(req)
.then((res) => {
loading.value = false;
data.value = res.data.items;
paginationConfig.total = res.data.total;
})
.catch(() => {
loading.value = false;
});
};
const openTaskLog = (row: Log.Task) => {
taskLogRef.value.openWithTaskID(row.id);
};
onMounted(() => {
search();
});
</script>