🔨 抽象执行日志.

This commit is contained in:
lijiahang
2024-04-12 15:23:15 +08:00
parent 40d0ec2025
commit a4c5d25815
42 changed files with 1304 additions and 388 deletions

View File

@@ -7,20 +7,18 @@ SELECT @MODULE_KEY_ID:= id FROM dict_key WHERE key_name = 'operatorLogModule' AN
SELECT @MODULE_KEY_MAX_SORT:= IFNULL(MAX(sort), 0) FROM dict_value where key_id = @MODULE_KEY_ID AND deleted = 0;
-- 类型 key id
SELECT @TYPE_KEY_ID:= id FROM dict_key WHERE key_name = 'operatorLogType' AND deleted = 0;
-- 类型 key 排序
SELECT @TYPE_KEY_MAX_SORT:= IFNULL(MAX(sort), 0) FROM dict_value WHERE key_id = @TYPE_KEY_ID AND deleted = 0;
-- 插入类型
INSERT INTO dict_value
(`key_id`, `key_name`, `value`, `label`, `extra`, `sort`, `create_time`, `update_time`, `creator`, `updater`, `deleted`)
VALUES
(@MODULE_KEY_ID, 'operatorLogModule', '${package.ModuleName}:${typeHyphen}', '$!{table.comment}', '{}', @MODULE_KEY_MAX_SORT + 10, now(), now(), '1', '1', 0),
(@TYPE_KEY_ID, 'operatorLogType', '${typeHyphen}:create', '创建$!{table.comment}', '{}', @TYPE_KEY_MAX_SORT + 10, now(), now(), '1', '1', 0),
(@TYPE_KEY_ID, 'operatorLogType', '${typeHyphen}:update', '更新$!{table.comment}', '{}', @TYPE_KEY_MAX_SORT + 20, now(), now(), '1', '1', 0),
(@TYPE_KEY_ID, 'operatorLogType', '${typeHyphen}:create', '创建$!{table.comment}', '{}', 10, now(), now(), '1', '1', 0),
(@TYPE_KEY_ID, 'operatorLogType', '${typeHyphen}:update', '更新$!{table.comment}', '{}', 20, now(), now(), '1', '1', 0),
#if($meta.enableExport)
(@TYPE_KEY_ID, 'operatorLogType', '${typeHyphen}:delete', '删除$!{table.comment}', '{}', @TYPE_KEY_MAX_SORT + 30, now(), now(), '1', '1', 0),
(@TYPE_KEY_ID, 'operatorLogType', '${typeHyphen}:export', '导出$!{table.comment}', '{}', @TYPE_KEY_MAX_SORT + 40, now(), now(), '1', '1', 0);
(@TYPE_KEY_ID, 'operatorLogType', '${typeHyphen}:delete', '删除$!{table.comment}', '{}', 30, now(), now(), '1', '1', 0),
(@TYPE_KEY_ID, 'operatorLogType', '${typeHyphen}:export', '导出$!{table.comment}', '{}', 40, now(), now(), '1', '1', 0);
#else
(@TYPE_KEY_ID, 'operatorLogType', '${typeHyphen}:delete', '删除$!{table.comment}', '{}', @TYPE_KEY_MAX_SORT + 30, now(), now(), '1', '1', 0);
(@TYPE_KEY_ID, 'operatorLogType', '${typeHyphen}:delete', '删除$!{table.comment}', '{}', 30, now(), now(), '1', '1', 0);
#end
#end

View File

@@ -20,14 +20,5 @@ Authorization: {{token}}
"logId": 1
}
### 中断执行命令
POST {{baseUrl}}/asset/exec-command/interrupt
Content-Type: application/json
Authorization: {{token}}
{
"logId": 7
}
###

View File

@@ -1,24 +1,22 @@
package com.orion.ops.module.asset.controller;
import com.orion.lang.define.wrapper.HttpWrapper;
import com.orion.ops.framework.biz.operator.log.core.annotation.OperatorLog;
import com.orion.ops.framework.biz.operator.log.core.enums.ReturnType;
import com.orion.ops.framework.common.utils.Valid;
import com.orion.ops.framework.web.core.annotation.RestWrapper;
import com.orion.ops.module.asset.define.operator.ExecCommandOperatorType;
import com.orion.ops.module.asset.entity.request.exec.ExecCommandRequest;
import com.orion.ops.module.asset.entity.request.exec.ExecInterruptRequest;
import com.orion.ops.module.asset.entity.request.exec.ReExecCommandRequest;
import com.orion.ops.module.asset.entity.vo.ExecLogVO;
import com.orion.ops.module.asset.enums.ExecSourceEnum;
import com.orion.ops.module.asset.service.ExecCommandService;
import com.orion.ops.module.asset.service.ExecLogService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
@@ -38,14 +36,9 @@ import javax.annotation.Resource;
@SuppressWarnings({"ELValidationInJSP", "SpringElInspection"})
public class ExecCommandController {
private static final String SOURCE = ExecSourceEnum.BATCH.name();
@Resource
private ExecCommandService execCommandService;
@Resource
private ExecLogService execLogService;
@OperatorLog(value = ExecCommandOperatorType.EXEC, ret = ReturnType.IGNORE)
@PostMapping("/exec")
@Operation(summary = "批量执行命令")
@@ -62,24 +55,4 @@ public class ExecCommandController {
return execCommandService.reExecCommand(request.getLogId());
}
@OperatorLog(ExecCommandOperatorType.INTERRUPT_EXEC)
@PutMapping("/interrupt")
@Operation(summary = "中断执行命令")
@PreAuthorize("@ss.hasPermission('asset:exec-command:interrupt')")
public HttpWrapper<?> interruptExecCommand(@RequestBody ExecInterruptRequest request) {
Long logId = Valid.notNull(request.getLogId());
execLogService.interruptExec(logId, SOURCE);
return HttpWrapper.ok();
}
@OperatorLog(ExecCommandOperatorType.INTERRUPT_HOST)
@PutMapping("/interrupt-host")
@Operation(summary = "中断执行主机命令")
@PreAuthorize("@ss.hasPermission('asset:exec-command:interrupt')")
public HttpWrapper<?> interruptHostExecCommand(@RequestBody ExecInterruptRequest request) {
Long hostLogId = Valid.notNull(request.getHostLogId());
execLogService.interruptHostExec(hostLogId, SOURCE);
return HttpWrapper.ok();
}
}

View File

@@ -40,9 +40,19 @@ Authorization: {{token}}
}
### 下载执行日志文件
### 下载批量执行日志文件
GET {{baseUrl}}/asset/exec-command-log/download?id=83
Authorization: {{token}}
### 中断批量执行命令
POST {{baseUrl}}/asset/exec-command-log/interrupt
Content-Type: application/json
Authorization: {{token}}
{
"logId": 7
}
###

View File

@@ -1,13 +1,17 @@
package com.orion.ops.module.asset.controller;
import com.orion.lang.define.wrapper.DataGrid;
import com.orion.lang.define.wrapper.HttpWrapper;
import com.orion.ops.framework.biz.operator.log.core.annotation.OperatorLog;
import com.orion.ops.framework.common.utils.Valid;
import com.orion.ops.framework.common.validator.group.Page;
import com.orion.ops.framework.log.core.annotation.IgnoreLog;
import com.orion.ops.framework.log.core.enums.IgnoreLogMode;
import com.orion.ops.framework.security.core.utils.SecurityUtils;
import com.orion.ops.framework.web.core.annotation.RestWrapper;
import com.orion.ops.module.asset.define.operator.ExecCommandLogOperatorType;
import com.orion.ops.module.asset.define.operator.ExecCommandOperatorType;
import com.orion.ops.module.asset.entity.request.exec.ExecInterruptRequest;
import com.orion.ops.module.asset.entity.request.exec.ExecLogQueryRequest;
import com.orion.ops.module.asset.entity.request.exec.ExecLogTailRequest;
import com.orion.ops.module.asset.entity.vo.ExecHostLogVO;
@@ -156,5 +160,25 @@ public class ExecCommandLogController {
execLogService.downloadLogFile(id, SOURCE, response);
}
@OperatorLog(ExecCommandLogOperatorType.INTERRUPT)
@PutMapping("/interrupt")
@Operation(summary = "中断批量执行命令")
@PreAuthorize("@ss.hasPermission('asset:exec-command-log:interrupt')")
public HttpWrapper<?> interruptExecCommand(@RequestBody ExecInterruptRequest request) {
Long logId = Valid.notNull(request.getLogId());
execLogService.interruptExec(logId, SOURCE);
return HttpWrapper.ok();
}
@OperatorLog(ExecCommandLogOperatorType.INTERRUPT_HOST)
@PutMapping("/interrupt-host")
@Operation(summary = "中断批量执行主机命令")
@PreAuthorize("@ss.hasPermission('asset:exec-command-log:interrupt')")
public HttpWrapper<?> interruptHostExecCommand(@RequestBody ExecInterruptRequest request) {
Long hostLogId = Valid.notNull(request.getHostLogId());
execLogService.interruptHostExec(hostLogId, SOURCE);
return HttpWrapper.ok();
}
}

View File

@@ -20,6 +20,7 @@ import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import java.util.List;
/**
* 计划任务 api
@@ -73,6 +74,14 @@ public class ExecJobController {
return execJobService.getExecJobById(id);
}
@IgnoreLog(IgnoreLogMode.RET)
@GetMapping("/list")
@Operation(summary = "查询全部计划任务")
@PreAuthorize("@ss.hasPermission('asset:exec-job:query')")
public List<ExecJobVO> getExecJobList() {
return execJobService.getExecJobList();
}
@IgnoreLog(IgnoreLogMode.RET)
@PostMapping("/query")
@Operation(summary = "分页查询计划任务")

View File

@@ -30,7 +30,7 @@ DELETE {{baseUrl}}/asset/exec-job-log/batch-delete?idList=1,2,3
Authorization: {{token}}
### 查看执行日志
### 查看计划任务日志
POST {{baseUrl}}/asset/exec-job-log/tail
Content-Type: application/json
Authorization: {{token}}
@@ -40,9 +40,18 @@ Authorization: {{token}}
}
### 下载执行日志文件
### 下载计划任务日志文件
GET {{baseUrl}}/asset/exec-job-log/download?id=83
Authorization: {{token}}
### 中断计划任务命令
POST {{baseUrl}}/asset/exec-command-log/interrupt
Content-Type: application/json
Authorization: {{token}}
{
"logId": 7
}
###

View File

@@ -1,12 +1,16 @@
package com.orion.ops.module.asset.controller;
import com.orion.lang.define.wrapper.DataGrid;
import com.orion.lang.define.wrapper.HttpWrapper;
import com.orion.ops.framework.biz.operator.log.core.annotation.OperatorLog;
import com.orion.ops.framework.common.utils.Valid;
import com.orion.ops.framework.common.validator.group.Page;
import com.orion.ops.framework.log.core.annotation.IgnoreLog;
import com.orion.ops.framework.log.core.enums.IgnoreLogMode;
import com.orion.ops.framework.web.core.annotation.RestWrapper;
import com.orion.ops.module.asset.define.operator.ExecCommandLogOperatorType;
import com.orion.ops.module.asset.define.operator.ExecJobLogOperatorType;
import com.orion.ops.module.asset.entity.request.exec.ExecInterruptRequest;
import com.orion.ops.module.asset.entity.request.exec.ExecLogQueryRequest;
import com.orion.ops.module.asset.entity.request.exec.ExecLogTailRequest;
import com.orion.ops.module.asset.entity.vo.ExecHostLogVO;
@@ -145,5 +149,25 @@ public class ExecJobLogController {
execLogService.downloadLogFile(id, SOURCE, response);
}
@OperatorLog(ExecJobLogOperatorType.INTERRUPT)
@PutMapping("/interrupt")
@Operation(summary = "中断计划任务命令")
@PreAuthorize("@ss.hasPermission('asset:exec-job-log:interrupt')")
public HttpWrapper<?> interruptExecCommand(@RequestBody ExecInterruptRequest request) {
Long logId = Valid.notNull(request.getLogId());
execLogService.interruptExec(logId, SOURCE);
return HttpWrapper.ok();
}
@OperatorLog(ExecJobLogOperatorType.INTERRUPT_HOST)
@PutMapping("/interrupt-host")
@Operation(summary = "中断计划任务主机命令")
@PreAuthorize("@ss.hasPermission('asset:exec-job-log:interrupt')")
public HttpWrapper<?> interruptHostExecCommand(@RequestBody ExecInterruptRequest request) {
Long hostLogId = Valid.notNull(request.getHostLogId());
execLogService.interruptHostExec(hostLogId, SOURCE);
return HttpWrapper.ok();
}
}

View File

@@ -4,8 +4,7 @@ import com.orion.ops.framework.biz.operator.log.core.annotation.Module;
import com.orion.ops.framework.biz.operator.log.core.factory.InitializingOperatorTypes;
import com.orion.ops.framework.biz.operator.log.core.model.OperatorType;
import static com.orion.ops.framework.biz.operator.log.core.enums.OperatorRiskLevel.H;
import static com.orion.ops.framework.biz.operator.log.core.enums.OperatorRiskLevel.L;
import static com.orion.ops.framework.biz.operator.log.core.enums.OperatorRiskLevel.*;
/**
* 批量执行日志 操作记录类型
@@ -25,6 +24,10 @@ public class ExecCommandLogOperatorType extends InitializingOperatorTypes {
public static final String DOWNLOAD = "exec-command-log:download";
public static final String INTERRUPT = "exec-command-log:interrupt";
public static final String INTERRUPT_HOST = "exec-command-log:interrupt-host";
@Override
public OperatorType[] types() {
return new OperatorType[]{
@@ -32,6 +35,8 @@ public class ExecCommandLogOperatorType extends InitializingOperatorTypes {
new OperatorType(H, DELETE_HOST, "删除批量执行主机日志 <sb>${logId}</sb> <sb>${hostName}</sb>"),
new OperatorType(H, CLEAR, "清理批量执行日志 <sb>${count}</sb> 条"),
new OperatorType(L, DOWNLOAD, "下载批量执行日志 <sb>${logId}</sb> <sb>${hostName}</sb>"),
new OperatorType(M, INTERRUPT, "中断批量执行命令"),
new OperatorType(M, INTERRUPT_HOST, "中断批量执行主机命令 <sb>${logId}</sb> <sb>${hostName}</sb>"),
};
}

View File

@@ -18,16 +18,10 @@ public class ExecCommandOperatorType extends InitializingOperatorTypes {
public static final String EXEC = "exec-command:exec";
public static final String INTERRUPT_EXEC = "exec-command:interrupt-exec";
public static final String INTERRUPT_HOST = "exec-command:interrupt-host";
@Override
public OperatorType[] types() {
return new OperatorType[]{
new OperatorType(M, EXEC, "执行主机命令"),
new OperatorType(M, INTERRUPT_EXEC, "中断执行命令"),
new OperatorType(M, INTERRUPT_HOST, "中断主机执行命令 <sb>${logId}</sb> <sb>${hostName}</sb>"),
};
}

View File

@@ -4,8 +4,7 @@ import com.orion.ops.framework.biz.operator.log.core.annotation.Module;
import com.orion.ops.framework.biz.operator.log.core.factory.InitializingOperatorTypes;
import com.orion.ops.framework.biz.operator.log.core.model.OperatorType;
import static com.orion.ops.framework.biz.operator.log.core.enums.OperatorRiskLevel.H;
import static com.orion.ops.framework.biz.operator.log.core.enums.OperatorRiskLevel.L;
import static com.orion.ops.framework.biz.operator.log.core.enums.OperatorRiskLevel.*;
/**
* 计划任务执行日志 操作记录类型
@@ -25,6 +24,10 @@ public class ExecJobLogOperatorType extends InitializingOperatorTypes {
public static final String DOWNLOAD = "exec-job-log:download";
public static final String INTERRUPT = "exec-job-log:interrupt";
public static final String INTERRUPT_HOST = "exec-job-log:interrupt-host";
@Override
public OperatorType[] types() {
return new OperatorType[]{
@@ -32,6 +35,8 @@ public class ExecJobLogOperatorType extends InitializingOperatorTypes {
new OperatorType(H, DELETE_HOST, "删除计划任务执行主机日志 <sb>${logId}</sb> <sb>${hostName}</sb>"),
new OperatorType(H, CLEAR, "清理计划任务执行日志 <sb>${count}</sb> 条"),
new OperatorType(L, DOWNLOAD, "下载计划任务执行日志 <sb>${logId}</sb> <sb>${hostName}</sb>"),
new OperatorType(M, INTERRUPT, "中断计划任务执行命令"),
new OperatorType(M, INTERRUPT_HOST, "中断计划任务执行主机命令 <sb>${logId}</sb> <sb>${hostName}</sb>"),
};
}

View File

@@ -4,6 +4,8 @@ import com.orion.lang.define.wrapper.DataGrid;
import com.orion.ops.module.asset.entity.request.exec.*;
import com.orion.ops.module.asset.entity.vo.ExecJobVO;
import java.util.List;
/**
* 计划任务 服务类
*
@@ -45,6 +47,13 @@ public interface ExecJobService {
*/
ExecJobVO getExecJobById(Long id);
/**
* 查询全部计划任务
*
* @return rows
*/
List<ExecJobVO> getExecJobList();
/**
* 分页查询计划任务
*

View File

@@ -164,6 +164,15 @@ public class ExecJobServiceImpl implements ExecJobService {
return vo;
}
@Override
public List<ExecJobVO> getExecJobList() {
return execJobDAO.of()
.createWrapper()
.select(ExecJobDO::getId, ExecJobDO::getName)
.then()
.list(ExecJobConvert.MAPPER::to);
}
@Override
public DataGrid<ExecJobVO> getExecJobPage(ExecJobQueryRequest request) {
// 条件

View File

@@ -1,105 +1,41 @@
import type { DataGrid, Pagination } from '@/types/global';
import type { TableData } from '@arco-design/web-vue/es/table/interface';
import type { DataGrid } from '@/types/global';
import type {
ExecHostLogQueryResponse,
ExecLogInterruptRequest,
ExecLogQueryRequest,
ExecLogQueryResponse,
ExecLogStatusResponse,
ExecLogTailRequest
} from './exec-log';
import axios from 'axios';
import qs from 'query-string';
/**
* 批量执行日志查询请求
*/
export interface ExecCommandLogQueryRequest extends Pagination {
id?: number;
userId?: number;
description?: string;
command?: string;
status?: string;
startTimeRange?: string[];
}
/**
* 批量执行日志查询响应
*/
export interface ExecCommandLogQueryResponse extends TableData, ExecCommandLogQueryExtraResponse {
id: number;
userId: number;
username: string;
description: string;
command: string;
parameterSchema: string;
timeout: number;
status: string;
startTime: number;
finishTime: number;
hostIdList: Array<number>;
hosts: Array<ExecCommandHostLogQueryResponse>;
}
/**
* 批量执行日志查询响应 拓展
*/
export interface ExecCommandLogQueryExtraResponse {
hosts: Array<ExecCommandHostLogQueryResponse>;
}
/**
* 主机批量执行日志查询响应
*/
export interface ExecCommandHostLogQueryResponse extends TableData {
id: number;
logId: number;
hostId: number;
hostName: string;
hostAddress: string;
status: string;
command: string;
parameter: string;
exitStatus: number;
errorMessage: string;
startTime: number;
finishTime: number;
}
/**
* 批量执行状态查询响应
*/
export interface ExecCommandLogStatusResponse {
logList: Array<ExecCommandLogQueryResponse>;
hostList: Array<ExecCommandHostLogQueryResponse>;
}
/**
* 批量执行日志 tail 请求
*/
export interface ExecCommandLogTailRequest {
execId?: number;
hostExecIdList?: Array<number>;
}
/**
* 分页查询批量执行日志
*/
export function getExecCommandLogPage(request: ExecCommandLogQueryRequest) {
return axios.post<DataGrid<ExecCommandLogQueryResponse>>('/asset/exec-command-log/query', request);
export function getExecCommandLogPage(request: ExecLogQueryRequest) {
return axios.post<DataGrid<ExecLogQueryResponse>>('/asset/exec-command-log/query', request);
}
/**
* 查询批量执行日志
*/
export function getExecCommandLog(id: number) {
return axios.get<ExecCommandLogQueryResponse>('/asset/exec-command-log/get', { params: { id } });
return axios.get<ExecLogQueryResponse>('/asset/exec-command-log/get', { params: { id } });
}
/**
* 查询主机批量执行日志
*/
export function getExecCommandHostLogList(logId: number) {
return axios.get<Array<ExecCommandHostLogQueryResponse>>('/asset/exec-command-log/host-list', { params: { logId } });
return axios.get<Array<ExecHostLogQueryResponse>>('/asset/exec-command-log/host-list', { params: { logId } });
}
/**
* 查询命令执行状态
*/
export function getExecCommandLogStatus(idList: Array<number>) {
return axios.get<ExecCommandLogStatusResponse>('/asset/exec-command-log/status', {
return axios.get<ExecLogStatusResponse>('/asset/exec-command-log/status', {
params: { idList },
paramsSerializer: params => {
return qs.stringify(params, { arrayFormat: 'comma' });
@@ -111,7 +47,7 @@ export function getExecCommandLogStatus(idList: Array<number>) {
* 查询历史批量执行日志
*/
export function getExecCommandLogHistory(limit: number) {
return axios.get<Array<ExecCommandLogQueryResponse>>('/asset/exec-command-log/history', { params: { page: 1, limit } });
return axios.get<Array<ExecLogQueryResponse>>('/asset/exec-command-log/history', { params: { page: 1, limit } });
}
/**
@@ -143,21 +79,21 @@ export function deleteExecCommandHostLog(id: number) {
/**
* 查询批量执行日志数量
*/
export function getExecCommandLogCount(request: ExecCommandLogQueryRequest) {
export function getExecCommandLogCount(request: ExecLogQueryRequest) {
return axios.post<number>('/asset/exec-command-log/query-count', request);
}
/**
* 清空批量执行日志
*/
export function clearExecCommandLog(request: ExecCommandLogQueryRequest) {
export function clearExecCommandLog(request: ExecLogQueryRequest) {
return axios.post<number>('/asset/exec-command-log/clear', request);
}
/**
* 查看批量执行日志
*/
export function getExecCommandLogTailToken(request: ExecCommandLogTailRequest) {
export function getExecCommandLogTailToken(request: ExecLogTailRequest) {
return axios.post<string>('/asset/exec-command-log/tail', request);
}
@@ -167,3 +103,17 @@ export function getExecCommandLogTailToken(request: ExecCommandLogTailRequest) {
export function downloadExecCommandLogFile(id: number) {
return axios.get('/asset/exec-command-log/download', { unwrap: true, params: { id } });
}
/**
* 中断执行命令
*/
export function interruptExecCommand(request: ExecLogInterruptRequest) {
return axios.put('/asset/exec-command-log/interrupt', request);
}
/**
* 中断执行主机命令
*/
export function interruptHostExecCommand(request: ExecLogInterruptRequest) {
return axios.put('/asset/exec-command-log/interrupt-host', request);
}

View File

@@ -1,4 +1,4 @@
import type { ExecCommandLogQueryResponse } from './exec-command-log';
import type { ExecLogQueryResponse } from './exec-log';
import axios from 'axios';
/**
@@ -13,38 +13,16 @@ export interface ExecCommandRequest {
hostIdList?: Array<number>;
}
/**
* 中断命令请求
*/
export interface ExecCommandInterruptRequest {
logId?: number;
hostLogId?: number;
}
/**
* 批量执行命令
*/
export function batchExecCommand(request: ExecCommandRequest) {
return axios.post<ExecCommandLogQueryResponse>('/asset/exec-command/exec', request);
return axios.post<ExecLogQueryResponse>('/asset/exec-command/exec', request);
}
/**
* 重新执行命令
*/
export function reExecCommand(request: ExecCommandRequest) {
return axios.post<ExecCommandLogQueryResponse>('/asset/exec-command/re-exec', request);
}
/**
* 中断执行命令
*/
export function interruptExecCommand(request: ExecCommandInterruptRequest) {
return axios.put('/asset/exec-command/interrupt', request);
}
/**
* 中断执行主机命令
*/
export function interruptHostExecCommand(request: ExecCommandInterruptRequest) {
return axios.put('/asset/exec-command/interrupt-host', request);
return axios.post<ExecLogQueryResponse>('/asset/exec-command/re-exec', request);
}

View File

@@ -1,105 +1,41 @@
import type { DataGrid, Pagination } from '@/types/global';
import type { TableData } from '@arco-design/web-vue/es/table/interface';
import type { DataGrid } from '@/types/global';
import type {
ExecHostLogQueryResponse,
ExecLogQueryRequest,
ExecLogQueryResponse,
ExecLogStatusResponse,
ExecLogTailRequest,
ExecLogInterruptRequest
} from './exec-log';
import axios from 'axios';
import qs from 'query-string';
/**
* 计划任务日志查询请求
*/
export interface ExecJobLogQueryRequest extends Pagination {
id?: number;
userId?: number;
description?: string;
command?: string;
status?: string;
startTimeRange?: string[];
}
/**
* 计划任务日志查询响应
*/
export interface ExecJobLogQueryResponse extends TableData, ExecJobLogQueryExtraResponse {
id: number;
userId: number;
username: string;
description: string;
command: string;
parameterSchema: string;
timeout: number;
status: string;
startTime: number;
finishTime: number;
hostIdList: Array<number>;
hosts: Array<ExecJobHostLogQueryResponse>;
}
/**
* 计划任务日志查询响应 拓展
*/
export interface ExecJobLogQueryExtraResponse {
hosts: Array<ExecJobHostLogQueryResponse>;
}
/**
* 主机计划任务日志查询响应
*/
export interface ExecJobHostLogQueryResponse extends TableData {
id: number;
logId: number;
hostId: number;
hostName: string;
hostAddress: string;
status: string;
command: string;
parameter: string;
exitStatus: number;
errorMessage: string;
startTime: number;
finishTime: number;
}
/**
* 计划任务状态查询响应
*/
export interface ExecJobLogStatusResponse {
logList: Array<ExecJobLogQueryResponse>;
hostList: Array<ExecJobHostLogQueryResponse>;
}
/**
* 计划任务日志 tail 请求
*/
export interface ExecJobLogTailRequest {
execId?: number;
hostExecIdList?: Array<number>;
}
/**
* 分页查询计划任务日志
*/
export function getExecJobLogPage(request: ExecJobLogQueryRequest) {
return axios.post<DataGrid<ExecJobLogQueryResponse>>('/asset/exec-job-log/query', request);
export function getExecJobLogPage(request: ExecLogQueryRequest) {
return axios.post<DataGrid<ExecLogQueryResponse>>('/asset/exec-job-log/query', request);
}
/**
* 查询计划任务日志
*/
export function getExecJobLog(id: number) {
return axios.get<ExecJobLogQueryResponse>('/asset/exec-job-log/get', { params: { id } });
return axios.get<ExecLogQueryResponse>('/asset/exec-job-log/get', { params: { id } });
}
/**
* 查询主机计划任务日志
*/
export function getExecJobHostLogList(logId: number) {
return axios.get<Array<ExecJobHostLogQueryResponse>>('/asset/exec-job-log/host-list', { params: { logId } });
return axios.get<Array<ExecHostLogQueryResponse>>('/asset/exec-job-log/host-list', { params: { logId } });
}
/**
* 查询命令执行状态
*/
export function getExecJobLogStatus(idList: Array<number>) {
return axios.get<ExecJobLogStatusResponse>('/asset/exec-job-log/status', {
return axios.get<ExecLogStatusResponse>('/asset/exec-job-log/status', {
params: { idList },
paramsSerializer: params => {
return qs.stringify(params, { arrayFormat: 'comma' });
@@ -136,21 +72,21 @@ export function deleteExecJobHostLog(id: number) {
/**
* 查询计划任务日志数量
*/
export function getExecJobLogCount(request: ExecJobLogQueryRequest) {
export function getExecJobLogCount(request: ExecLogQueryRequest) {
return axios.post<number>('/asset/exec-job-log/query-count', request);
}
/**
* 清空计划任务日志
*/
export function clearExecJobLog(request: ExecJobLogQueryRequest) {
export function clearExecJobLog(request: ExecLogQueryRequest) {
return axios.post<number>('/asset/exec-job-log/clear', request);
}
/**
* 查看计划任务日志
*/
export function getExecJobLogTailToken(request: ExecJobLogTailRequest) {
export function getExecJobLogTailToken(request: ExecLogTailRequest) {
return axios.post<string>('/asset/exec-job-log/tail', request);
}
@@ -160,3 +96,17 @@ export function getExecJobLogTailToken(request: ExecJobLogTailRequest) {
export function downloadExecJobLogFile(id: number) {
return axios.get('/asset/exec-job-log/download', { unwrap: true, params: { id } });
}
/**
* 中断计划任务执行
*/
export function interruptExecJob(request: ExecLogInterruptRequest) {
return axios.put('/asset/exec-job-log/interrupt', request);
}
/**
* 中断计划任务执行主机
*/
export function interruptHostExecJob(request: ExecLogInterruptRequest) {
return axios.put('/asset/exec-job-log/interrupt-host', request);
}

View File

@@ -89,6 +89,13 @@ export function getExecJob(id: number) {
return axios.get<ExecJobQueryResponse>('/asset/exec-job/get', { params: { id } });
}
/**
* 查询全部计划任务
*/
export function getExecJobList() {
return axios.get<Array<ExecJobQueryResponse>>('/asset/exec-job/list');
}
/**
* 分页查询计划任务
*/

View File

@@ -0,0 +1,83 @@
import type { Pagination } from '@/types/global';
import type { TableData } from '@arco-design/web-vue/es/table/interface';
/**
* 执行日志查询请求
*/
export interface ExecLogQueryRequest extends Pagination {
id?: number;
userId?: number;
sourceId?: number;
description?: string;
command?: string;
status?: string;
startTimeRange?: string[];
}
/**
* 执行日志查询响应
*/
export interface ExecLogQueryResponse extends TableData, ExecLogQueryExtraResponse {
id: number;
userId: number;
username: string;
description: string;
execSeq: number;
command: string;
parameterSchema: string;
timeout: number;
status: string;
startTime: number;
finishTime: number;
hostIdList: Array<number>;
hosts: Array<ExecHostLogQueryResponse>;
}
/**
* 执行日志查询响应 拓展
*/
export interface ExecLogQueryExtraResponse {
hosts: Array<ExecHostLogQueryResponse>;
}
/**
* 主机执行日志查询响应
*/
export interface ExecHostLogQueryResponse extends TableData {
id: number;
logId: number;
hostId: number;
hostName: string;
hostAddress: string;
status: string;
command: string;
parameter: string;
exitStatus: number;
errorMessage: string;
startTime: number;
finishTime: number;
}
/**
* 执行状态查询响应
*/
export interface ExecLogStatusResponse {
logList: Array<ExecLogQueryResponse>;
hostList: Array<ExecHostLogQueryResponse>;
}
/**
* 执行日志 tail 请求
*/
export interface ExecLogTailRequest {
execId?: number;
hostExecIdList?: Array<number>;
}
/**
* 执行中断命令请求
*/
export interface ExecLogInterruptRequest {
logId?: number;
hostLogId?: number;
}

View File

@@ -0,0 +1,36 @@
// 批量执行状态
export const execStatus = {
// 等待中
WAITING: 'WAITING',
// 运行中
RUNNING: 'RUNNING',
// 执行完成
COMPLETED: 'COMPLETED',
// 执行失败
FAILED: 'FAILED',
};
// 主机执行状态
export const execHostStatus = {
// 等待中
WAITING: 'WAITING',
// 运行中
RUNNING: 'RUNNING',
// 执行完成
COMPLETED: 'COMPLETED',
// 执行失败
FAILED: 'FAILED',
// 执行超时
TIMEOUT: 'TIMEOUT',
// 已中断
INTERRUPTED: 'INTERRUPTED',
};
// 执行状态 字典项
export const execStatusKey = 'execStatus';
// 执行状态 字典项
export const execHostStatusKey = 'execHostStatus';
// 加载的字典值
export const dictKeys = [execStatusKey, execHostStatusKey];

View File

@@ -5,7 +5,7 @@ import type { WebLinksAddon } from 'xterm-addon-web-links';
import type { WebglAddon } from 'xterm-addon-webgl';
// appender 配置
export const AppenderOptions: ITerminalOptions & ITerminalInitOnlyOptions = {
export const LogAppenderOptions: ITerminalOptions & ITerminalInitOnlyOptions = {
theme: {
foreground: '#FFFFFF',
background: '#202020',
@@ -97,44 +97,3 @@ export interface ILogAppender {
// 关闭
close(): void;
}
/**
*
*/
export const execStatus = {
// 等待中
WAITING: 'WAITING',
// 运行中
RUNNING: 'RUNNING',
// 执行完成
COMPLETED: 'COMPLETED',
// 执行失败
FAILED: 'FAILED',
};
/**
*
*/
export const execHostStatus = {
// 等待中
WAITING: 'WAITING',
// 运行中
RUNNING: 'RUNNING',
// 执行完成
COMPLETED: 'COMPLETED',
// 执行失败
FAILED: 'FAILED',
// 执行超时
TIMEOUT: 'TIMEOUT',
// 已中断
INTERRUPTED: 'INTERRUPTED',
};
// 执行状态 字典项
export const execStatusKey = 'execStatus';
// 执行状态 字典项
export const execHostStatusKey = 'execHostStatus';
// 加载的字典值
export const dictKeys = [execStatusKey, execHostStatusKey];

View File

@@ -41,14 +41,14 @@
</script>
<script lang="ts" setup>
import type { ExecCommandHostLogQueryResponse } from '@/api/exec/exec-command-log';
import type { ExecHostLogQueryResponse } from '@/api/exec/exec-log';
import { useDictStore } from '@/store';
import { execHostStatusKey } from './const';
import { execHostStatusKey } from '../const';
const props = defineProps<{
visibleBack: boolean;
current: number;
hosts: Array<ExecCommandHostLogQueryResponse>;
hosts: Array<ExecHostLogQueryResponse>;
}>();
const emits = defineEmits(['back', 'selected']);

View File

@@ -23,15 +23,15 @@
</script>
<script lang="ts" setup>
import type { ExecCommandLogQueryResponse } from '@/api/exec/exec-command-log';
import type { ILogAppender } from './const';
import type { ExecLogQueryResponse } from '@/api/exec/exec-log';
import type { ILogAppender } from './appender-const';
import { onUnmounted, ref, nextTick, onMounted } from 'vue';
import { getExecCommandLogStatus } from '@/api/exec/exec-command-log';
import { dictKeys, execHostStatus, execStatus } from './const';
import { dictKeys, execHostStatus, execStatus } from '../const';
import { useDictStore } from '@/store';
import ExecHost from './exec-host.vue';
import LogView from './log-view.vue';
import LogAppender from '@/components/exec/log/panel/log-appender';
import { useDictStore } from '@/store';
const props = defineProps<{
visibleBack: boolean
@@ -43,11 +43,11 @@
const currentHostExecId = ref();
const statusIntervalId = ref();
const finishIntervalId = ref();
const execLog = ref<ExecCommandLogQueryResponse>();
const execLog = ref<ExecLogQueryResponse>();
const appender = ref<ILogAppender>();
// 打开
const open = (record: ExecCommandLogQueryResponse) => {
const open = (record: ExecLogQueryResponse) => {
appender.value = new LogAppender({ execId: record.id });
execLog.value = record;
currentHostExecId.value = record.hosts[0].id;

View File

@@ -1,6 +1,6 @@
import type { ILogAppender, LogAddons, LogAppenderConf, LogDomRef } from './const';
import { AppenderOptions } from './const';
import type { ExecCommandLogTailRequest } from '@/api/exec/exec-command-log';
import type { ILogAppender, LogAddons, LogAppenderConf, LogDomRef } from './appender-const';
import { LogAppenderOptions } from './appender-const';
import type { ExecLogTailRequest } from '@/api/exec/exec-log';
import { getExecCommandLogTailToken } from '@/api/exec/exec-command-log';
import { webSocketBaseUrl } from '@/utils/env';
import { Message } from '@arco-design/web-vue';
@@ -21,7 +21,7 @@ export default class LogAppender implements ILogAppender {
private client?: WebSocket;
private readonly config: ExecCommandLogTailRequest;
private readonly config: ExecLogTailRequest;
private readonly appenderRel: Record<string, LogAppenderConf>;
@@ -29,7 +29,7 @@ export default class LogAppender implements ILogAppender {
private readonly fitAllFn: () => {};
constructor(config: ExecCommandLogTailRequest) {
constructor(config: ExecLogTailRequest) {
this.current = undefined as unknown as LogAppenderConf;
this.config = config;
this.appenderRel = {};
@@ -49,7 +49,7 @@ export default class LogAppender implements ILogAppender {
// 打开 log-view
for (let logDomRef of logDomRefs) {
// 初始化 terminal
const terminal = new Terminal(AppenderOptions);
const terminal = new Terminal(LogAppenderOptions);
// 初始化快捷键
this.initCustomKey(terminal);
// 初始化插件

View File

@@ -160,10 +160,10 @@
</script>
<script lang="ts" setup>
import type { ExecCommandHostLogQueryResponse } from '@/api/exec/exec-command-log';
import type { ILogAppender } from './const';
import type { ExecHostLogQueryResponse } from '@/api/exec/exec-log';
import type { ILogAppender } from './appender-const';
import { ref } from 'vue';
import { execHostStatus, execHostStatusKey } from './const';
import { execHostStatus, execHostStatusKey } from '../const';
import { formatDuration } from '@/utils';
import { useDictStore } from '@/store';
import { downloadExecCommandLogFile } from '@/api/exec/exec-command-log';
@@ -172,7 +172,7 @@
import 'xterm/css/xterm.css';
const props = defineProps<{
host: ExecCommandHostLogQueryResponse;
host: ExecHostLogQueryResponse;
appender: ILogAppender
}>();

View File

@@ -18,14 +18,14 @@
<script lang="ts" setup>
import type { VNodeRef } from 'vue';
import type { ExecCommandHostLogQueryResponse } from '@/api/exec/exec-command-log';
import type { LogDomRef, ILogAppender } from './const';
import type { LogDomRef, ILogAppender } from './appender-const';
import type { ExecHostLogQueryResponse } from '@/api/exec/exec-log';
import { nextTick, ref, watch } from 'vue';
import LogItem from './log-item.vue';
const props = defineProps<{
current: number;
hosts: Array<ExecCommandHostLogQueryResponse>;
hosts: Array<ExecHostLogQueryResponse>;
appender: ILogAppender;
}>();

View File

@@ -69,7 +69,7 @@
position="left"
type="warning"
@ok="interruptedHost(record)">
<a-button v-permission="['asset:exec-command:interrupt']"
<a-button v-permission="['asset:exec-command-log:interrupt']"
type="text"
size="mini"
status="danger"
@@ -101,22 +101,21 @@
</script>
<script lang="ts" setup>
import type { ExecCommandLogQueryResponse, ExecCommandHostLogQueryResponse } from '@/api/exec/exec-command-log';
import type { ExecLogQueryResponse, ExecHostLogQueryResponse } from '@/api/exec/exec-log';
import { deleteExecCommandHostLog } from '@/api/exec/exec-command-log';
import { Message } from '@arco-design/web-vue';
import useLoading from '@/hooks/loading';
import columns from '../types/host-table.columns';
import { execHostStatusKey, execHostStatus } from '../types/const';
import { execHostStatusKey, execHostStatus } from '@/components/exec/log/const';
import { useDictStore } from '@/store';
import { useExpandable } from '@/types/table';
import { dateFormat, formatDuration } from '@/utils';
import { interruptHostExecCommand } from '@/api/exec/exec-command';
import { downloadExecCommandLogFile } from '@/api/exec/exec-command-log';
import { downloadExecCommandLogFile, interruptHostExecCommand } from '@/api/exec/exec-command-log';
import { copy } from '@/hooks/copy';
import { downloadFile } from '@/utils/file';
const props = defineProps<{
row: ExecCommandLogQueryResponse;
row: ExecLogQueryResponse;
}>();
const emits = defineEmits(['viewCommand', 'viewParams', 'refreshHost']);
@@ -132,7 +131,7 @@
};
// 中断执行
const interruptedHost = async (record: ExecCommandHostLogQueryResponse) => {
const interruptedHost = async (record: ExecHostLogQueryResponse) => {
try {
setLoading(true);
// 调用中断接口

View File

@@ -61,11 +61,11 @@
</script>
<script lang="ts" setup>
import type { ExecCommandLogQueryRequest } from '@/api/exec/exec-command-log';
import type { ExecLogQueryRequest } from '@/api/exec/exec-log';
import { ref } from 'vue';
import useLoading from '@/hooks/loading';
import useVisible from '@/hooks/visible';
import { execStatusKey } from '../types/const';
import { execStatusKey } from '@/components/exec/log/const';
import { getExecCommandLogCount, clearExecCommandLog } from '@/api/exec/exec-command-log';
import { Message, Modal } from '@arco-design/web-vue';
import { useDictStore } from '@/store';
@@ -78,9 +78,9 @@
const { toOptions } = useDictStore();
const formRef = ref<any>();
const formModel = ref<ExecCommandLogQueryRequest>({});
const formModel = ref<ExecLogQueryRequest>({});
const defaultForm = (): ExecCommandLogQueryRequest => {
const defaultForm = (): ExecLogQueryRequest => {
return {
id: undefined,
userId: undefined,

View File

@@ -161,7 +161,7 @@
命令
</a-button>
<!-- 日志 -->
<a-button v-permission="['asset:exec-command:exec']"
<a-button v-permission="['asset:exec-command-log:query', 'asset:exec-command:exec']"
type="text"
size="mini"
title="ctrl + 左键新页面打开"
@@ -173,7 +173,7 @@
position="left"
type="warning"
@ok="doInterruptExecCommand(record)">
<a-button v-permission="['asset:exec-command:interrupt']"
<a-button v-permission="['asset:exec-command-log:interrupt']"
type="text"
size="mini"
status="danger"
@@ -207,7 +207,7 @@
<script lang="ts" setup>
import type { TableData } from '@arco-design/web-vue/es/table/interface';
import type { ExecCommandLogQueryRequest, ExecCommandLogQueryResponse } from '@/api/exec/exec-command-log';
import type { ExecLogQueryResponse,ExecLogQueryRequest } from '@/api/exec/exec-log';
import { reactive, ref, onMounted, onUnmounted } from 'vue';
import {
batchDeleteExecCommandLog,
@@ -219,11 +219,12 @@
import { Message } from '@arco-design/web-vue';
import useLoading from '@/hooks/loading';
import columns from '../types/table.columns';
import { execStatus, execStatusKey } from '../types/const';
import { execStatus, execStatusKey } from '@/components/exec/log/const';
import { useExpandable, usePagination, useRowSelection } from '@/types/table';
import { useDictStore } from '@/store';
import { dateFormat, formatDuration } from '@/utils';
import { reExecCommand, interruptExecCommand } from '@/api/exec/exec-command';
import { reExecCommand } from '@/api/exec/exec-command';
import { interruptExecCommand } from '@/api/exec/exec-command-log';
import UserSelector from '@/components/user/user/selector/index.vue';
import ExecCommandHostLogTable from './exec-command-host-log-table.vue';
@@ -238,8 +239,8 @@
const intervalId = ref();
const tableRef = ref();
const selectedKeys = ref<number[]>([]);
const tableRenderData = ref<ExecCommandLogQueryResponse[]>([]);
const formModel = reactive<ExecCommandLogQueryRequest>({
const tableRenderData = ref<ExecLogQueryResponse[]>([]);
const formModel = reactive<ExecLogQueryResponse>({
id: undefined,
userId: undefined,
description: undefined,
@@ -287,7 +288,7 @@
};
// 重新执行命令
const doReExecCommand = async (record: ExecCommandLogQueryResponse) => {
const doReExecCommand = async (record: ExecLogQueryResponse) => {
try {
setLoading(true);
// 调用中断接口
@@ -303,7 +304,7 @@
};
// 中断执行
const doInterruptExecCommand = async (record: ExecCommandLogQueryResponse) => {
const doInterruptExecCommand = async (record: ExecLogQueryResponse) => {
try {
setLoading(true);
// 调用中断接口
@@ -379,7 +380,7 @@
};
// 加载数据
const doFetchTableData = async (request: ExecCommandLogQueryRequest) => {
const doFetchTableData = async (request: ExecLogQueryRequest) => {
try {
setLoading(true);
const { data } = await getExecCommandLogPage(request);

View File

@@ -30,7 +30,7 @@
<script lang="ts" setup>
import { ref, onBeforeMount } from 'vue';
import { useDictStore } from '@/store';
import { dictKeys } from './types/const';
import { dictKeys } from '@/components/exec/log/const';
import { useRouter } from 'vue-router';
import { openNewRoute } from '@/router';
import ExecCommandLogTable from './components/exec-command-log-table.vue';

View File

@@ -1,40 +0,0 @@
/**
* 批量执行状态
*/
export const execStatus = {
// 等待中
WAITING: 'WAITING',
// 运行中
RUNNING: 'RUNNING',
// 执行完成
COMPLETED: 'COMPLETED',
// 执行失败
FAILED: 'FAILED',
};
/**
* 主机执行状态
*/
export const execHostStatus = {
// 等待中
WAITING: 'WAITING',
// 运行中
RUNNING: 'RUNNING',
// 执行完成
COMPLETED: 'COMPLETED',
// 执行失败
FAILED: 'FAILED',
// 执行超时
TIMEOUT: 'TIMEOUT',
// 已中断
INTERRUPTED: 'INTERRUPTED',
};
// 执行状态 字典项
export const execStatusKey = 'execStatus';
// 执行状态 字典项
export const execHostStatusKey = 'execHostStatus';
// 加载的字典值
export const dictKeys = [execStatusKey, execHostStatusKey];

View File

@@ -43,7 +43,7 @@
</script>
<script lang="ts" setup>
import type { ExecCommandLogQueryResponse } from '@/api/exec/exec-command-log';
import type { ExecLogQueryResponse } from '@/api/exec/exec-log';
import type { ExecCommandRequest } from '@/api/exec/exec-command';
import { onMounted, ref } from 'vue';
import { getExecCommandLogHistory } from '@/api/exec/exec-command-log';
@@ -54,7 +54,7 @@
const { loading, setLoading } = useLoading(true);
const historyLogs = ref<Array<ExecCommandLogQueryResponse>>([]);
const historyLogs = ref<Array<ExecLogQueryResponse>>([]);
// 添加
const add = (record: ExecCommandRequest) => {
@@ -67,7 +67,7 @@
parameterSchema: record.parameterSchema,
timeout: record.timeout,
hostIdList: record.hostIdList
} as ExecCommandLogQueryResponse);
} as ExecLogQueryResponse);
} else {
// 存在
const his = historyLogs.value[index];
@@ -78,7 +78,7 @@
parameterSchema: record.parameterSchema,
timeout: record.timeout,
hostIdList: record.hostIdList
} as ExecCommandLogQueryResponse);
} as ExecLogQueryResponse);
}
};

View File

@@ -96,10 +96,10 @@
</script>
<script lang="ts" setup>
import type { ExecTemplateQueryResponse } from '@/api/exec/exec-template';
import type { ExecLogQueryResponse } from '@/api/exec/exec-log';
import type { ExecCommandRequest } from '@/api/exec/exec-command';
import type { TemplateParam } from '@/components/view/exec-editor/const';
import type { ExecTemplateQueryResponse } from '@/api/exec/exec-template';
import type { ExecCommandLogQueryResponse } from '@/api/exec/exec-command-log';
import { ref } from 'vue';
import formRules from '../types/form.rules';
import useLoading from '@/hooks/loading';
@@ -162,7 +162,7 @@
};
// 从执行日志设置
const setWithExecLog = (record: ExecCommandLogQueryResponse) => {
const setWithExecLog = (record: ExecLogQueryResponse) => {
formModel.value = {
...formModel.value,
command: record.command,

View File

@@ -20,11 +20,11 @@
</script>
<script lang="ts" setup>
import type { ExecCommandLogQueryResponse } from '@/api/exec/exec-command-log';
import type { ExecLogQueryResponse } from '@/api/exec/exec-log';
import { nextTick, onMounted, ref } from 'vue';
import useVisible from '@/hooks/visible';
import { useDictStore } from '@/store';
import { dictKeys } from '@/views/exec/exec-command-log/types/const';
import { dictKeys } from '@/components/exec/log/const';
import ExecCommandPanel from './components/exec-command-panel.vue';
import ExecLogPanel from '@/components/exec/log/panel/index.vue';
@@ -33,7 +33,7 @@
const log = ref();
// 打开日志
const openLog = (record: ExecCommandLogQueryResponse) => {
const openLog = (record: ExecLogQueryResponse) => {
setLogVisible(true);
nextTick(() => {
log.value.open(record);

View File

@@ -0,0 +1,59 @@
<template>
<div class="container">
<div class="wrapper">
<exec-log-panel ref="log" :visible-back="false" />
</div>
</div>
</template>
<script lang="ts">
export default {
name: 'execJobLogView'
};
</script>
<script lang="ts" setup>
import { onMounted, ref, nextTick } from 'vue';
import { useRoute } from 'vue-router';
import { getExecCommandLog } from '@/api/exec/exec-command-log';
import ExecLogPanel from '@/components/exec/log/panel/index.vue';
const route = useRoute();
const log = ref();
// 初始化
const init = async (id: number) => {
// 获取执行日志
const { data } = await getExecCommandLog(id);
// 打开日志
await nextTick(() => {
setTimeout(() => {
log.value.open(data);
}, 50);
});
};
onMounted(() => {
const isParam = route.query.id;
if (isParam) {
init(Number.parseInt(isParam as string));
}
});
</script>
<style lang="less" scoped>
.container {
width: 100%;
height: 100%;
position: relative;
padding: 16px;
background: var(--color-fill-2);
.wrapper {
width: 100%;
height: 100%;
position: relative;
}
}
</style>

View File

@@ -0,0 +1,171 @@
<template>
<!-- table -->
<a-table row-key="id"
ref="tableRef"
:loading="loading"
:columns="columns"
:data="row.hosts"
:expandable="expandable"
:scroll="{ y: '100%' }"
:pagination="false"
:bordered="false">
<!-- 执行主机 -->
<template #hostName="{ record }">
<span class="table-cell-value span-blue">
{{ record.hostName }}
</span>
<br>
<span class="table-cell-sub-value usn text-copy"
style="font-size: 12px;"
@click="copy(record.hostAddress)">
{{ record.hostAddress }}
</span>
</template>
<!-- 错误信息 -->
<template #errorMessage="{ record }">
<span class="span-red">
{{ record.errorMessage }}
</span>
</template>
<!-- 执行状态 -->
<template #status="{ record }">
<a-tag :color="getDictValue(execHostStatusKey, record.status, 'color')">
{{ getDictValue(execHostStatusKey, record.status) }}
</a-tag>
</template>
<!-- 执行时间 -->
<template #startTime="{ record }">
<span class="table-cell-value">
{{ (record.startTime && dateFormat(new Date(record.startTime))) || '-' }}
</span>
<br>
<span class="table-cell-sub-value usn" style="font-size: 12px;">
持续时间: {{ formatDuration(record.startTime, record.finishTime) || '-' }}
</span>
</template>
<!-- 操作 -->
<template #handle="{ record }">
<div class="table-handle-wrapper">
<!-- 命令 -->
<a-button type="text"
size="mini"
@click="emits('viewCommand', record.command)">
命令
</a-button>
<!-- 参数 -->
<a-button type="text"
size="mini"
@click="emits('viewParams', record.parameter)">
参数
</a-button>
<!-- 下载 -->
<a-button type="text"
size="mini"
@click="downloadLogFile(record.id)">
下载
</a-button>
<!-- 中断 -->
<a-popconfirm content="确认要中断命令吗, 删除后会中断执行?"
position="left"
type="warning"
@ok="interruptedHost(record)">
<a-button v-permission="['asset:exec-job-log:interrupt']"
type="text"
size="mini"
status="danger"
:disabled="record.status !== execHostStatus.WAITING && record.status !== execHostStatus.RUNNING">
中断
</a-button>
</a-popconfirm>
<!-- 删除 -->
<a-popconfirm content="确认删除这条记录吗?"
position="left"
type="warning"
@ok="deleteRow(record)">
<a-button v-permission="['asset:exec-job-log:delete']"
type="text"
size="mini"
status="danger">
删除
</a-button>
</a-popconfirm>
</div>
</template>
</a-table>
</template>
<script lang="ts">
export default {
name: 'execJobHostLogTable'
};
</script>
<script lang="ts" setup>
import type { ExecLogQueryResponse, ExecHostLogQueryResponse } from '@/api/exec/exec-log';
import { deleteExecJobHostLog } from '@/api/exec/exec-job-log';
import { Message } from '@arco-design/web-vue';
import useLoading from '@/hooks/loading';
import columns from '../../exec-command-log/types/host-table.columns';
import { execHostStatusKey, execHostStatus } from '@/components/exec/log/const';
import { useDictStore } from '@/store';
import { useExpandable } from '@/types/table';
import { dateFormat, formatDuration } from '@/utils';
// import { interruptHostExecJob } from '@/api/exec/exec-job';
import { downloadExecJobLogFile } from '@/api/exec/exec-job-log';
import { copy } from '@/hooks/copy';
import { downloadFile } from '@/utils/file';
const props = defineProps<{
row: ExecLogQueryResponse;
}>();
const emits = defineEmits(['viewCommand', 'viewParams', 'refreshHost']);
const expandable = useExpandable({ width: 90 });
const { loading, setLoading } = useLoading();
const { toOptions, getDictValue } = useDictStore();
// 下载文件
const downloadLogFile = async (id: number) => {
const data = await downloadExecJobLogFile(id);
downloadFile(data);
};
// 中断执行
const interruptedHost = async (record: ExecHostLogQueryResponse) => {
try {
setLoading(true);
// 调用中断接口
// await interruptHostExecJob({
// hostLogId: record.id
// });
Message.success('已中断');
record.status = execHostStatus.INTERRUPTED;
} catch (e) {
} finally {
setLoading(false);
}
};
// 删除当前行
const deleteRow = async ({ id, logId }: {
id: number,
logId: number
}) => {
try {
setLoading(true);
// 调用删除接口
await deleteExecJobHostLog(id);
Message.success('删除成功');
emits('refreshHost', logId);
} catch (e) {
} finally {
setLoading(false);
}
};
</script>
<style lang="less" scoped>
</style>

View File

@@ -0,0 +1,162 @@
<template>
<a-modal v-model:visible="visible"
body-class="modal-form"
title-align="start"
title="清空批量执行日志"
:align-center="false"
:draggable="true"
:mask-closable="false"
:unmount-on-close="true"
ok-text="清空"
:ok-button-props="{ disabled: loading }"
:cancel-button-props="{ disabled: loading }"
:on-before-ok="handlerOk"
@close="handleClose">
<a-spin class="full" :loading="loading">
<a-form :model="formModel"
ref="formRef"
label-align="right"
:label-col-props="{ span: 5 }"
:wrapper-col-props="{ span: 18 }">
<!-- 执行时间 -->
<a-form-item field="startTimeRange" label="执行时间">
<a-range-picker v-model="formModel.startTimeRange"
:time-picker-props="{ defaultValue: ['00:00:00', '23:59:59'] }"
show-time
format="YYYY-MM-DD HH:mm:ss" />
</a-form-item>
<!-- 执行用户 -->
<a-form-item field="userId" label="执行用户">
<user-selector v-model="formModel.userId"
placeholder="请选择执行用户"
allow-clear />
</a-form-item>
<!-- 执行描述 -->
<a-form-item field="description" label="执行描述">
<a-input v-model="formModel.description"
placeholder="请输入执行描述"
allow-clear />
</a-form-item>
<!-- 执行命令 -->
<a-form-item field="command" label="执行命令">
<a-input v-model="formModel.command"
placeholder="请输入执行命令"
allow-clear />
</a-form-item>
<!-- 执行状态 -->
<a-form-item field="status" label="执行状态">
<a-select v-model="formModel.status"
:options="toOptions(execStatusKey)"
placeholder="请选择执行状态" />
</a-form-item>
</a-form>
</a-spin>
</a-modal>
</template>
<script lang="ts">
export default {
name: 'execJobLogClearModal'
};
</script>
<script lang="ts" setup>
import type { ExecLogQueryRequest } from '@/api/exec/exec-log';
import { ref } from 'vue';
import useLoading from '@/hooks/loading';
import useVisible from '@/hooks/visible';
import { execStatusKey } from '@/components/exec/log/const';
import { getExecJobLogCount, clearExecJobLog } from '@/api/exec/exec-job-log';
import { Message, Modal } from '@arco-design/web-vue';
import { useDictStore } from '@/store';
import UserSelector from '@/components/user/user/selector/index.vue';
const emits = defineEmits(['clear']);
const { visible, setVisible } = useVisible();
const { loading, setLoading } = useLoading();
const { toOptions } = useDictStore();
const formRef = ref<any>();
const formModel = ref<ExecLogQueryRequest>({});
const defaultForm = (): ExecLogQueryRequest => {
return {
id: undefined,
userId: undefined,
description: undefined,
command: undefined,
status: undefined,
startTimeRange: undefined
};
};
// 打开
const open = (record: any) => {
renderForm({ ...defaultForm(), ...record });
setVisible(true);
};
// 渲染表单
const renderForm = (record: any) => {
formModel.value = Object.assign({}, record);
};
defineExpose({ open });
// 确定
const handlerOk = async () => {
setLoading(true);
try {
// 获取总数量
const { data } = await getExecJobLogCount(formModel.value);
if (data) {
// 清空
doClear(data);
} else {
// 无数据
Message.warning('当前条件未查询到数据');
}
} catch (e) {
} finally {
setLoading(false);
}
return false;
};
// 执行删除
const doClear = (count: number) => {
Modal.confirm({
title: '删除清空',
content: `确定要删除 ${count} 条数据吗? 确定后将立即删除且无法恢复!`,
onOk: async () => {
setLoading(true);
try {
// 调用删除
await clearExecJobLog(formModel.value);
emits('clear');
// 清空
setVisible(false);
handlerClear();
} catch (e) {
} finally {
setLoading(false);
}
}
});
};
// 关闭
const handleClose = () => {
handlerClear();
};
// 清空
const handlerClear = () => {
setLoading(false);
};
</script>
<style lang="less" scoped>
</style>

View File

@@ -0,0 +1,400 @@
<template>
<!-- 搜索 -->
<a-card class="general-card table-search-card">
<query-header :model="formModel"
label-align="left"
:itemOptions="{ 5: { span: 2 } }"
@submit="fetchTableData"
@reset="fetchTableData"
@keyup.enter="() => fetchTableData()">
<!-- 执行描述 -->
<a-form-item field="description" label="执行描述">
<a-input v-model="formModel.description"
placeholder="请输入执行描述"
allow-clear />
</a-form-item>
<!-- 执行状态 -->
<a-form-item field="status" label="执行状态">
<a-select v-model="formModel.status"
:options="toOptions(execStatusKey)"
placeholder="请选择执行状态"
allow-clear />
</a-form-item>
<!-- 执行用户 -->
<a-form-item field="userId" label="执行用户">
<user-selector v-model="formModel.userId"
placeholder="请选择执行用户"
allow-clear />
</a-form-item>
<!-- 执行命令 -->
<a-form-item field="command" label="执行命令">
<a-input v-model="formModel.command"
placeholder="请输入执行命令"
allow-clear />
</a-form-item>
<!-- id -->
<a-form-item field="id" label="id">
<a-input-number v-model="formModel.id"
placeholder="请输入id"
allow-clear
hide-button />
</a-form-item>
<!-- 执行时间 -->
<a-form-item field="startTimeRange" label="执行时间">
<a-range-picker v-model="formModel.startTimeRange"
:time-picker-props="{ defaultValue: ['00:00:00', '23:59:59'] }"
show-time
format="YYYY-MM-DD HH:mm:ss" />
</a-form-item>
</query-header>
</a-card>
<!-- 表格 -->
<a-card class="general-card table-card">
<template #title>
<!-- 左侧操作 -->
<div class="table-left-bar-handle">
<!-- 标题 -->
<div class="table-title">
计划任务日志列表
</div>
</div>
<!-- 右侧操作 -->
<div class="table-right-bar-handle">
<a-space>
<!-- 清空 -->
<a-button v-permission="['asset:exec-job-log:management:clear']"
status="danger"
@click="openClear">
清空
<template #icon>
<icon-close />
</template>
</a-button>
<!-- 删除 -->
<a-popconfirm :content="`确认删除选中的 ${selectedKeys.length} 条记录吗? 删除后会中断执行!`"
position="br"
type="warning"
@ok="deleteSelectRows">
<a-button v-permission="['asset:exec-job-log:delete']"
type="secondary"
status="danger"
:disabled="selectedKeys.length === 0">
删除
<template #icon>
<icon-delete />
</template>
</a-button>
</a-popconfirm>
</a-space>
</div>
</template>
<!-- table -->
<a-table row-key="id"
ref="tableRef"
:loading="loading"
:columns="columns"
v-model:selected-keys="selectedKeys"
:row-selection="rowSelection"
:expandable="expandable"
:data="tableRenderData"
:pagination="pagination"
:bordered="false"
@page-change="(page) => fetchTableData(page, pagination.pageSize)"
@page-size-change="(size) => fetchTableData(1, size)"
@expand="loadExecHost">
<!-- 展开表格 -->
<template #expand-row="{ record }">
<exec-job-host-log-table :row="record"
@view-command="s => emits('viewCommand', s)"
@view-params="s => emits('viewParams', s)"
@refresh-host="refreshExecHost" />
</template>
<!-- 任务名称 -->
<template #description="{ record }">
<span class="span-blue mr4 usn">
#{{ record.execSeq }}
</span>
<span :title="record.description">
{{ record.description }}
</span>
</template>
<!-- 执行命令 -->
<template #command="{ record }">
<span :title="record.command">
{{ record.command }}
</span>
</template>
<!-- 执行状态 -->
<template #status="{ record }">
<a-tag :color="getDictValue(execStatusKey, record.status, 'color')">
{{ getDictValue(execStatusKey, record.status) }}
</a-tag>
</template>
<!-- 执行时间 -->
<template #startTime="{ record }">
<span class="table-cell-value">
{{ (record.startTime && dateFormat(new Date(record.startTime))) || '-' }}
</span>
<br>
<span class="table-cell-sub-value usn" style="font-size: 12px;">
持续时间: {{ formatDuration(record.startTime, record.finishTime) || '-' }}
</span>
</template>
<!-- 操作 -->
<template #handle="{ record }">
<div class="table-handle-wrapper">
<!-- 命令 -->
<a-button type="text"
size="mini"
@click="emits('viewCommand', record.command)">
命令
</a-button>
<!-- 日志 -->
<a-button v-permission="['asset:exec-job-log:query']"
type="text"
size="mini"
title="ctrl + 左键新页面打开"
@click="(e) => emits('viewLog', record.id, e.ctrlKey)">
日志
</a-button>
<!-- 中断 -->
<a-popconfirm content="确定要中断执行吗?"
position="left"
type="warning"
@ok="doInterruptExecJob(record)">
<a-button v-permission="['asset:exec-job-log:interrupt']"
type="text"
size="mini"
status="danger"
:disabled="record.status !== execStatus.WAITING && record.status !== execStatus.RUNNING">
中断
</a-button>
</a-popconfirm>
<!-- 删除 -->
<a-popconfirm content="确认删除这条记录吗, 删除后会中断执行?"
position="left"
type="warning"
@ok="deleteRow(record)">
<a-button v-permission="['asset:exec-job-log:delete']"
type="text"
size="mini"
status="danger">
删除
</a-button>
</a-popconfirm>
</div>
</template>
</a-table>
</a-card>
</template>
<script lang="ts">
export default {
name: 'execJobLogTable'
};
</script>
<script lang="ts" setup>
import type { TableData } from '@arco-design/web-vue/es/table/interface';
import type { ExecLogQueryRequest, ExecLogQueryResponse } from '@/api/exec/exec-log';
import { reactive, ref, onMounted, onUnmounted } from 'vue';
import {
batchDeleteExecJobLog,
deleteExecJobLog,
getExecJobHostLogList,
getExecJobLogPage,
getExecJobLogStatus
} from '@/api/exec/exec-job-log';
import { Message } from '@arco-design/web-vue';
import useLoading from '@/hooks/loading';
import columns from '../types/table.columns';
import { execStatus, execStatusKey } from '@/components/exec/log/const';
import { useExpandable, usePagination, useRowSelection } from '@/types/table';
import { useDictStore } from '@/store';
import { dateFormat, formatDuration } from '@/utils';
// import { interruptExecJob } from '@/api/exec/exec-job';
import UserSelector from '@/components/user/user/selector/index.vue';
import ExecJobHostLogTable from './exec-job-host-log-table.vue';
const emits = defineEmits(['viewCommand', 'viewParams', 'viewLog', 'openClear']);
const pagination = usePagination();
const rowSelection = useRowSelection();
const expandable = useExpandable();
const { loading, setLoading } = useLoading();
const { toOptions, getDictValue } = useDictStore();
const intervalId = ref();
const tableRef = ref();
const selectedKeys = ref<number[]>([]);
const tableRenderData = ref<ExecLogQueryResponse[]>([]);
const formModel = reactive<ExecLogQueryRequest>({
id: undefined,
userId: undefined,
description: undefined,
command: undefined,
status: undefined,
startTimeRange: undefined,
});
// 打开清理
const openClear = () => {
emits('openClear', { ...formModel, id: undefined });
};
// 删除选中行
const deleteSelectRows = async () => {
try {
setLoading(true);
// 调用删除接口
await batchDeleteExecJobLog(selectedKeys.value);
Message.success(`成功删除 ${selectedKeys.value.length} 条数据`);
selectedKeys.value = [];
// 重新加载数据
fetchTableData();
} catch (e) {
} finally {
setLoading(false);
}
};
// 删除当前行
const deleteRow = async ({ id }: {
id: number
}) => {
try {
setLoading(true);
// 调用删除接口
await deleteExecJobLog(id);
Message.success('删除成功');
// 重新加载数据
fetchTableData();
} catch (e) {
} finally {
setLoading(false);
}
};
// 中断执行
const doInterruptExecJob = async (record: ExecLogQueryResponse) => {
try {
setLoading(true);
// 调用中断接口
// await interruptExecJob({
// logId: record.id
// });
Message.success('已中断');
record.status = execStatus.COMPLETED;
} catch (e) {
} finally {
setLoading(false);
}
};
// 刷新执行主机
const refreshExecHost = (id: number) => {
// 获取到执行主机
const exec = tableRenderData.value.find(s => s.id === id);
if (!exec) {
return;
}
// 加载数据
getExecJobHostLogList(id).then(s => {
exec.hosts = s.data;
});
};
// 加载主机数据
const loadExecHost = async (key: number | string, record: TableData) => {
if (record.hosts?.length) {
return;
}
// 加载数据
const { data } = await getExecJobHostLogList(record.id);
record.hosts = data;
};
// 加载状态
const fetchTaskStatus = async () => {
const unCompleteIdList = tableRenderData.value
.filter(s => s.status === execStatus.WAITING || s.status === execStatus.RUNNING)
.map(s => s.id);
if (!unCompleteIdList.length) {
return;
}
// 加载未完成的状态
const { data: { logList, hostList } } = await getExecJobLogStatus(unCompleteIdList);
// 设置任务状态
logList.forEach(s => {
const tableRow = tableRenderData.value.find(r => r.id === s.id);
if (!tableRow) {
return;
}
tableRow.status = s.status;
tableRow.startTime = s.startTime;
tableRow.finishTime = s.finishTime;
});
// 设置主机状态
hostList.forEach(s => {
const host = tableRenderData.value
.find(r => r.id === s.logId)
?.hosts
?.find(r => r.id === s.id);
if (!host) {
return;
}
host.status = s.status;
host.startTime = s.startTime;
host.finishTime = s.finishTime;
host.exitStatus = s.exitStatus;
host.errorMessage = s.errorMessage;
});
};
// 加载数据
const doFetchTableData = async (request: ExecLogQueryRequest) => {
try {
setLoading(true);
const { data } = await getExecJobLogPage(request);
tableRenderData.value = data.rows;
pagination.total = data.total;
pagination.current = request.page;
pagination.pageSize = request.limit;
selectedKeys.value = [];
tableRef.value.expandAll(false);
} catch (e) {
} finally {
setLoading(false);
}
};
// 切换页码
const fetchTableData = (page = 1, limit = pagination.pageSize, form = formModel) => {
doFetchTableData({ page, limit, ...form });
};
defineExpose({
fetchTableData
});
onMounted(() => {
// 加载数据
fetchTableData();
// 注册状态轮询
intervalId.value = setInterval(fetchTaskStatus, 10000);
});
onUnmounted(() => {
// 卸载状态轮询
clearInterval(intervalId.value);
});
</script>
<style lang="less" scoped>
:deep(.arco-table-cell-fixed-expand) {
width: 100% !important;
}
</style>

View File

@@ -0,0 +1,96 @@
<template>
<div class="layout-container" v-if="render">
<!-- 列表-表格 -->
<exec-job-log-table ref="tableRef"
@view-command="viewCommand"
@view-params="viewParams"
@view-log="viewLog"
@open-clear="openClearModal" />
<!-- 清理模态框 -->
<exec-job-log-clear-modal ref="clearModal"
@clear="clearCallback" />
<!-- 执行日志模态框 -->
<exec-log-panel-modal ref="logModal" />
<!-- json 模态框 -->
<json-editor-modal ref="jsonModal"
:esc-to-close="true" />
<!-- shell 模态框 -->
<shell-editor-modal ref="shellModal"
:footer="false"
:esc-to-close="true" />
</div>
</template>
<script lang="ts">
export default {
name: 'execJobLog'
};
</script>
<script lang="ts" setup>
import { ref, onBeforeMount } from 'vue';
import { useDictStore } from '@/store';
import { dictKeys } from '@/components/exec/log/const';
import { useRouter } from 'vue-router';
import { openNewRoute } from '@/router';
import ExecJobLogTable from './components/exec-job-log-table.vue';
import ExecJobLogClearModal from './components/exec-job-log-clear-modal.vue';
import JsonEditorModal from '@/components/view/json-editor/modal/index.vue';
import ShellEditorModal from '@/components/view/shell-editor/modal/index.vue';
import ExecLogPanelModal from '@/components/exec/log/panel-modal/index.vue';
const router = useRouter();
const render = ref(false);
const tableRef = ref();
const logModal = ref();
const clearModal = ref();
const jsonModal = ref();
const shellModal = ref();
// 打开清理模态框
const openClearModal = (e: any) => {
clearModal.value.open(e);
};
// 查看命令
const viewCommand = (data: string) => {
shellModal.value.open(data, '命令');
};
// 查看参数
const viewParams = (data: string) => {
jsonModal.value.open(JSON.parse(data));
};
// 查看日志
const viewLog = (id: number, newWindow: boolean) => {
if (newWindow) {
// 跳转新页面
openNewRoute({
name: 'execJobLogView',
query: {
id
}
});
} else {
logModal.value.open(id);
}
};
// 清理回调
const clearCallback = () => {
tableRef.value.fetchTableData();
};
onBeforeMount(async () => {
const dictStore = useDictStore();
await dictStore.loadKeys(dictKeys);
render.value = true;
});
</script>
<style lang="less" scoped>
</style>

View File

@@ -0,0 +1,45 @@
import type { TableColumnData } from '@arco-design/web-vue/es/table/interface';
const columns = [
{
title: 'id',
dataIndex: 'id',
slotName: 'id',
width: 70,
align: 'left',
fixed: 'left',
}, {
title: '任务名称',
dataIndex: 'description',
slotName: 'description',
align: 'left',
width: 188,
ellipsis: true,
}, {
title: '执行命令',
dataIndex: 'command',
slotName: 'command',
align: 'left',
ellipsis: true,
}, {
title: '执行状态',
dataIndex: 'status',
slotName: 'status',
align: 'left',
width: 118,
}, {
title: '执行时间',
dataIndex: 'startTime',
slotName: 'startTime',
align: 'left',
width: 190,
}, {
title: '操作',
slotName: 'handle',
width: 218,
align: 'center',
fixed: 'right',
},
] as TableColumnData[];
export default columns;

View File

@@ -11,9 +11,9 @@
:bordered="false">
<!-- 操作模块 -->
<template #module="{ record }">
{{ getDictValue(operatorLogModuleKey, record.module) }}
<span>{{ getDictValue(operatorLogModuleKey, record.module) }}</span>
<icon-oblique-line />
{{ getDictValue(operatorLogTypeKey, record.type) }}
<span>{{ getDictValue(operatorLogTypeKey, record.type) }}</span>
</template>
<!-- 风险等级 -->
<template #riskLevel="{ record }">

View File

@@ -58,9 +58,9 @@
:bordered="false">
<!-- 操作模块 -->
<template #module="{ record }">
{{ getDictValue(operatorLogModuleKey, record.module) }}
<span>{{ getDictValue(operatorLogModuleKey, record.module) }}</span>
<icon-oblique-line />
{{ getDictValue(operatorLogTypeKey, record.type) }}
<span>{{ getDictValue(operatorLogTypeKey, record.type) }}</span>
</template>
<!-- 风险等级 -->
<template #riskLevel="{ record }">