mirror of https://github.com/1Panel-dev/1Panel
feat: 增加任务日志列表 (#5999)
parent
636e149b29
commit
bdece10868
|
@ -66,4 +66,5 @@ var (
|
|||
favoriteService = service.NewIFavoriteService()
|
||||
|
||||
websiteCAService = service.NewIWebsiteCAService()
|
||||
taskService = service.NewITaskService()
|
||||
)
|
||||
|
|
|
@ -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,
|
||||
})
|
||||
}
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
};
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -1093,6 +1093,9 @@ const message = {
|
|||
websiteLog: '網站日誌',
|
||||
runLog: '運行日誌',
|
||||
errLog: '錯誤日誌',
|
||||
task: '任務日誌',
|
||||
taskName: '任務名稱',
|
||||
taskRunning: '運行中',
|
||||
},
|
||||
file: {
|
||||
dir: '文件夾',
|
||||
|
|
|
@ -1095,6 +1095,9 @@ const message = {
|
|||
websiteLog: '网站日志',
|
||||
runLog: '运行日志',
|
||||
errLog: '错误日志',
|
||||
task: '任务日志',
|
||||
taskName: '任务名称',
|
||||
taskRunning: '运行中',
|
||||
},
|
||||
file: {
|
||||
dir: '文件夹',
|
||||
|
|
|
@ -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,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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'),
|
||||
|
|
|
@ -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') + ']');
|
||||
|
|
|
@ -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>
|
|
@ -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();
|
||||
});
|
||||
|
|
|
@ -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>
|
Loading…
Reference in New Issue