Merge remote-tracking branch 'origin/dev' into dev
This commit is contained in:
@@ -13,6 +13,4 @@ public interface FilterOrderConst {
|
||||
|
||||
int CORS_FILTER = Integer.MIN_VALUE + 2000;
|
||||
|
||||
int MYBATIS_CACHE_CLEAR_FILTER = Integer.MIN_VALUE + 100000;
|
||||
|
||||
}
|
||||
|
||||
@@ -4,17 +4,13 @@ import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
|
||||
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
|
||||
import com.baomidou.mybatisplus.extension.plugins.inner.OptimisticLockerInnerInterceptor;
|
||||
import com.orion.ops.framework.common.constant.AutoConfigureOrderConst;
|
||||
import com.orion.ops.framework.common.constant.FilterOrderConst;
|
||||
import com.orion.ops.framework.common.web.filter.FilterCreator;
|
||||
import com.orion.ops.framework.common.security.SecurityHolder;
|
||||
import com.orion.ops.framework.mybatis.core.cache.CacheClearFilter;
|
||||
import com.orion.ops.framework.mybatis.core.handler.FieldFillHandler;
|
||||
import com.orion.ops.framework.mybatis.core.utils.DomainFillUtils;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.mybatis.spring.annotation.MapperScan;
|
||||
import org.springframework.boot.autoconfigure.AutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.AutoConfigureOrder;
|
||||
import org.springframework.boot.web.servlet.FilterRegistrationBean;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
|
||||
/**
|
||||
@@ -51,12 +47,4 @@ public class OrionMybatisAutoConfiguration {
|
||||
return interceptor;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return mybatis 缓存清理过滤器
|
||||
*/
|
||||
@Bean
|
||||
public FilterRegistrationBean<CacheClearFilter> rowCacheClearFilterBean() {
|
||||
return FilterCreator.create(new CacheClearFilter(), FilterOrderConst.MYBATIS_CACHE_CLEAR_FILTER);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,31 +0,0 @@
|
||||
package com.orion.ops.framework.mybatis.core.cache;
|
||||
|
||||
import org.springframework.web.filter.OncePerRequestFilter;
|
||||
|
||||
import javax.servlet.FilterChain;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* mybatis 缓存清理过滤器
|
||||
*
|
||||
* @author Jiahang Li
|
||||
* @version 1.0.0
|
||||
* @since 2023/6/25 15:14
|
||||
*/
|
||||
public class CacheClearFilter extends OncePerRequestFilter {
|
||||
|
||||
@Override
|
||||
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
|
||||
try {
|
||||
// 执行请求
|
||||
filterChain.doFilter(request, response);
|
||||
} finally {
|
||||
// 清理缓存
|
||||
CacheHolder.remove();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,61 +0,0 @@
|
||||
package com.orion.ops.framework.mybatis.core.cache;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.orion.lang.define.collect.MultiHashMap;
|
||||
import com.orion.lang.define.wrapper.Ref;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 缓存行持有者
|
||||
*
|
||||
* @author Jiahang Li
|
||||
* @version 1.0.0
|
||||
* @since 2023/6/25 14:21
|
||||
*/
|
||||
public class CacheHolder {
|
||||
|
||||
private CacheHolder() {
|
||||
}
|
||||
|
||||
/**
|
||||
* 缓存
|
||||
* <p>
|
||||
* key: mapper
|
||||
* value: id > row
|
||||
*/
|
||||
private static final ThreadLocal<MultiHashMap<BaseMapper<?>, Serializable, Ref<?>>> HOLDER = ThreadLocal.withInitial(MultiHashMap::new);
|
||||
|
||||
/**
|
||||
* 获取缓存
|
||||
*
|
||||
* @param mapper mapper
|
||||
* @param id id
|
||||
* @param <T> domain
|
||||
* @return cacheWrapper
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public static <T> Ref<T> get(BaseMapper<?> mapper, Serializable id) {
|
||||
return (Ref<T>) HOLDER.get().get(mapper, id);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置缓存
|
||||
*
|
||||
* @param mapper mapper
|
||||
* @param id id
|
||||
* @param row row
|
||||
* @param <T> domainClass
|
||||
*/
|
||||
public static <T> void set(BaseMapper<T> mapper, Serializable id, T row) {
|
||||
HOLDER.get().put(mapper, id, new Ref<>(row));
|
||||
}
|
||||
|
||||
/**
|
||||
* 清空缓存
|
||||
*/
|
||||
public static void remove() {
|
||||
HOLDER.remove();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -3,11 +3,9 @@ package com.orion.ops.framework.mybatis.core.mapper;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.baomidou.mybatisplus.extension.toolkit.Db;
|
||||
import com.orion.ops.framework.mybatis.core.query.CacheQuery;
|
||||
import com.orion.ops.framework.mybatis.core.query.Conditions;
|
||||
import com.orion.ops.framework.mybatis.core.query.DataQuery;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Collection;
|
||||
|
||||
/**
|
||||
@@ -56,25 +54,6 @@ public interface IMapper<T> extends BaseMapper<T> {
|
||||
return DataQuery.of(this, wrapper);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 CacheQuery 对象
|
||||
*
|
||||
* @return CacheQuery
|
||||
*/
|
||||
default CacheQuery<T> cache() {
|
||||
return CacheQuery.of(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 CacheQuery 对象
|
||||
*
|
||||
* @param id id
|
||||
* @return CacheQuery
|
||||
*/
|
||||
default CacheQuery<T> cache(Serializable id) {
|
||||
return CacheQuery.of(this, id);
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量插入
|
||||
*
|
||||
|
||||
@@ -1,95 +0,0 @@
|
||||
package com.orion.ops.framework.mybatis.core.query;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.orion.lang.define.wrapper.Ref;
|
||||
import com.orion.lang.utils.Valid;
|
||||
import com.orion.ops.framework.mybatis.core.cache.CacheHolder;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Function;
|
||||
|
||||
/**
|
||||
* 缓存查询器
|
||||
* <p>
|
||||
* 查询会存入缓存
|
||||
*
|
||||
* @author Jiahang Li
|
||||
* @version 1.0.0
|
||||
* @since 2023/6/25 13:05
|
||||
*/
|
||||
public class CacheQuery<T> {
|
||||
|
||||
private final BaseMapper<T> dao;
|
||||
|
||||
private Serializable id;
|
||||
|
||||
private boolean force;
|
||||
|
||||
private CacheQuery(BaseMapper<T> dao, Serializable id) {
|
||||
this.dao = dao;
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public static <T> CacheQuery<T> of(BaseMapper<T> dao) {
|
||||
Valid.notNull(dao, "dao is null");
|
||||
return new CacheQuery<>(dao, null);
|
||||
}
|
||||
|
||||
public static <T> CacheQuery<T> of(BaseMapper<T> dao, Serializable id) {
|
||||
Valid.notNull(dao, "dao is null");
|
||||
Valid.notNull(id, "id is null");
|
||||
return new CacheQuery<>(dao, id);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置 id
|
||||
*
|
||||
* @param id id
|
||||
* @return this
|
||||
*/
|
||||
public CacheQuery<T> id(Serializable id) {
|
||||
this.id = id;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 强制查询
|
||||
*
|
||||
* @param id id
|
||||
* @return this
|
||||
*/
|
||||
public CacheQuery<T> force(Serializable id) {
|
||||
this.force = true;
|
||||
this.id = id;
|
||||
return this;
|
||||
}
|
||||
|
||||
public <R> Optional<R> optional(Function<T, R> mapper) {
|
||||
Valid.notNull(mapper, "convert function is null");
|
||||
return Optional.ofNullable(this.get())
|
||||
.map(mapper);
|
||||
}
|
||||
|
||||
public Optional<T> optional() {
|
||||
return Optional.ofNullable(this.get());
|
||||
}
|
||||
|
||||
public T get() {
|
||||
// 不查询缓存
|
||||
if (!force) {
|
||||
// 从缓存中获取
|
||||
Ref<T> ref = CacheHolder.get(dao, id);
|
||||
// 命中直接返回
|
||||
if (ref != null) {
|
||||
return ref.get();
|
||||
}
|
||||
}
|
||||
// 查询
|
||||
T row = dao.selectById(id);
|
||||
// 设置缓存
|
||||
CacheHolder.set(dao, id, row);
|
||||
return row;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -20,14 +20,5 @@ Authorization: {{token}}
|
||||
"logId": 1
|
||||
}
|
||||
|
||||
### 中断执行命令
|
||||
POST {{baseUrl}}/asset/exec-command/interrupt
|
||||
Content-Type: application/json
|
||||
Authorization: {{token}}
|
||||
|
||||
{
|
||||
"logId": 7
|
||||
}
|
||||
|
||||
|
||||
###
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
###
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -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 = "分页查询计划任务")
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
###
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -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>"),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -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>"),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -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>"),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
/**
|
||||
* 分页查询计划任务
|
||||
*
|
||||
|
||||
@@ -221,6 +221,7 @@ public class ExecCommandServiceImpl implements ExecCommandService {
|
||||
// 添加内置参数
|
||||
params.put("userId", request.getUserId());
|
||||
params.put("username", request.getUsername());
|
||||
params.put("source", request.getSourceId());
|
||||
params.put("sourceId", request.getSourceId());
|
||||
params.put("seq", request.getExecSeq());
|
||||
params.put("execId", execId);
|
||||
|
||||
@@ -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) {
|
||||
// 条件
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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');
|
||||
}
|
||||
|
||||
/**
|
||||
* 分页查询计划任务
|
||||
*/
|
||||
|
||||
83
orion-ops-ui/src/api/exec/exec-log.ts
Normal file
83
orion-ops-ui/src/api/exec/exec-log.ts
Normal 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;
|
||||
}
|
||||
80
orion-ops-ui/src/components/exec/job/selector/index.vue
Normal file
80
orion-ops-ui/src/components/exec/job/selector/index.vue
Normal file
@@ -0,0 +1,80 @@
|
||||
<template>
|
||||
<a-select v-model:model-value="value as any"
|
||||
:options="optionData"
|
||||
:allow-search="true"
|
||||
:loading="loading"
|
||||
:disabled="loading"
|
||||
:filter-option="labelFilter"
|
||||
:allow-create="allowCreate"
|
||||
placeholder="请选择计划任务" />
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: 'execJobSelector'
|
||||
};
|
||||
</script>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import type { SelectOptionData } from '@arco-design/web-vue';
|
||||
import { computed, onBeforeMount, ref } from 'vue';
|
||||
import { useCacheStore } from '@/store';
|
||||
import { labelFilter } from '@/types/form';
|
||||
import useLoading from '@/hooks/loading';
|
||||
|
||||
const props = withDefaults(defineProps<Partial<{
|
||||
modelValue: number;
|
||||
name: string;
|
||||
allowCreate: boolean;
|
||||
}>>(), {
|
||||
allowCreate: false,
|
||||
});
|
||||
|
||||
const emits = defineEmits(['update:modelValue', 'update:name']);
|
||||
|
||||
const { loading, setLoading } = useLoading();
|
||||
const cacheStore = useCacheStore();
|
||||
|
||||
const value = computed({
|
||||
get() {
|
||||
if (props.allowCreate) {
|
||||
return props.modelValue || props.name;
|
||||
} else {
|
||||
return props.modelValue;
|
||||
}
|
||||
},
|
||||
set(e) {
|
||||
if (typeof e === 'string') {
|
||||
emits('update:modelValue', null);
|
||||
emits('update:name', e);
|
||||
} else {
|
||||
// 已有的值
|
||||
emits('update:modelValue', e);
|
||||
emits('update:name', null);
|
||||
}
|
||||
}
|
||||
});
|
||||
const optionData = ref<Array<SelectOptionData>>([]);
|
||||
|
||||
// 初始化选项
|
||||
onBeforeMount(async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
const dictKeys = await cacheStore.loadExecJobs();
|
||||
optionData.value = dictKeys.map(s => {
|
||||
return {
|
||||
label: s.name,
|
||||
value: s.id,
|
||||
};
|
||||
});
|
||||
} catch (e) {
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
|
||||
</style>
|
||||
39
orion-ops-ui/src/components/exec/log/const.ts
Normal file
39
orion-ops-ui/src/components/exec/log/const.ts
Normal file
@@ -0,0 +1,39 @@
|
||||
// 执行类型
|
||||
export type ExecType = 'BATCH' | 'JOB';
|
||||
|
||||
// 批量执行状态
|
||||
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];
|
||||
@@ -16,7 +16,9 @@
|
||||
:loading="loading">
|
||||
<div class="panel-wrapper">
|
||||
<!-- 日志面板 -->
|
||||
<exec-log-panel ref="log" :visible-back="false" />
|
||||
<exec-log-panel ref="log"
|
||||
:type="type"
|
||||
:visible-back="false" />
|
||||
</div>
|
||||
</a-spin>
|
||||
</a-modal>
|
||||
@@ -29,12 +31,18 @@
|
||||
</script>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import type { ExecType } from '../const';
|
||||
import useVisible from '@/hooks/visible';
|
||||
import useLoading from '@/hooks/loading';
|
||||
import { nextTick, ref } from 'vue';
|
||||
import { getExecCommandLog } from '@/api/exec/exec-command-log';
|
||||
import { getExecJobLog } from '@/api/exec/exec-job-log';
|
||||
import ExecLogPanel from '../panel/index.vue';
|
||||
|
||||
const props = defineProps<{
|
||||
type: ExecType;
|
||||
}>();
|
||||
|
||||
const { visible, setVisible } = useVisible();
|
||||
const { loading, setLoading } = useLoading();
|
||||
|
||||
@@ -46,7 +54,15 @@
|
||||
setLoading(true);
|
||||
try {
|
||||
// 获取执行日志
|
||||
const { data } = await getExecCommandLog(id);
|
||||
let detailGetter;
|
||||
if (props.type === 'BATCH') {
|
||||
// 批量执行日志
|
||||
detailGetter = getExecCommandLog(id);
|
||||
} else {
|
||||
// 计划任务日志
|
||||
detailGetter = getExecJobLog(id);
|
||||
}
|
||||
const { data } = await detailGetter;
|
||||
// 打开日志
|
||||
await nextTick(() => {
|
||||
setTimeout(() => {
|
||||
|
||||
@@ -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];
|
||||
@@ -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']);
|
||||
|
||||
|
||||
@@ -10,8 +10,9 @@
|
||||
<!-- 日志容器 -->
|
||||
<log-view ref="logViewRef"
|
||||
class="log-view-container"
|
||||
:type="type"
|
||||
:current="currentHostExecId"
|
||||
:hosts="execLog.hosts"
|
||||
:exec-log="execLog"
|
||||
:appender="appender" />
|
||||
</div>
|
||||
</template>
|
||||
@@ -23,18 +24,21 @@
|
||||
</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 type { ExecType } from '../const';
|
||||
import { onUnmounted, ref, nextTick, onMounted } from 'vue';
|
||||
import { getExecCommandLogStatus } from '@/api/exec/exec-command-log';
|
||||
import { dictKeys, execHostStatus, execStatus } from './const';
|
||||
import { getExecJobLogStatus } from '@/api/exec/exec-job-log';
|
||||
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';
|
||||
import LogAppender from './log-appender';
|
||||
|
||||
const props = defineProps<{
|
||||
visibleBack: boolean
|
||||
visibleBack: boolean;
|
||||
type: ExecType;
|
||||
}>();
|
||||
|
||||
const emits = defineEmits(['back']);
|
||||
@@ -43,12 +47,12 @@
|
||||
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) => {
|
||||
appender.value = new LogAppender({ execId: record.id });
|
||||
const open = (record: ExecLogQueryResponse) => {
|
||||
appender.value = new LogAppender(props.type, { execId: record.id });
|
||||
execLog.value = record;
|
||||
currentHostExecId.value = record.hosts[0].id;
|
||||
// 定时查询执行状态
|
||||
@@ -71,7 +75,15 @@
|
||||
return;
|
||||
}
|
||||
// 加载状态
|
||||
const { data: { logList, hostList } } = await getExecCommandLogStatus([execLog.value.id]);
|
||||
let statusGetter;
|
||||
if (props.type === 'BATCH') {
|
||||
// 批量执行日志状态
|
||||
statusGetter = getExecCommandLogStatus([execLog.value.id]);
|
||||
} else {
|
||||
// 计划任务日志状态
|
||||
statusGetter = getExecJobLogStatus([execLog.value.id]);
|
||||
}
|
||||
const { data: { logList, hostList } } = await statusGetter;
|
||||
if (logList.length) {
|
||||
execLog.value.status = logList[0].status;
|
||||
execLog.value.startTime = logList[0].startTime;
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
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 { ExecType } from '../const';
|
||||
import type { ExecLogTailRequest } from '@/api/exec/exec-log';
|
||||
import { getExecCommandLogTailToken } from '@/api/exec/exec-command-log';
|
||||
import { getExecJobLogTailToken } from '@/api/exec/exec-job-log';
|
||||
import { webSocketBaseUrl } from '@/utils/env';
|
||||
import { Message } from '@arco-design/web-vue';
|
||||
import { createWebSocket } from '@/utils';
|
||||
@@ -21,7 +23,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,8 +31,11 @@ export default class LogAppender implements ILogAppender {
|
||||
|
||||
private readonly fitAllFn: () => {};
|
||||
|
||||
constructor(config: ExecCommandLogTailRequest) {
|
||||
private readonly type: ExecType;
|
||||
|
||||
constructor(type: ExecType, config: ExecLogTailRequest) {
|
||||
this.current = undefined as unknown as LogAppenderConf;
|
||||
this.type = type;
|
||||
this.config = config;
|
||||
this.appenderRel = {};
|
||||
this.fitAllFn = useDebounceFn(this.fitAll).bind(this);
|
||||
@@ -49,7 +54,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);
|
||||
// 初始化插件
|
||||
@@ -141,8 +146,16 @@ export default class LogAppender implements ILogAppender {
|
||||
|
||||
// 初始化 client
|
||||
async openClient() {
|
||||
let tokenMaker;
|
||||
// 获取 token
|
||||
const { data } = await getExecCommandLogTailToken(this.config);
|
||||
if (this.type === 'BATCH') {
|
||||
// 获取批量执行日志 token
|
||||
tokenMaker = getExecCommandLogTailToken(this.config);
|
||||
} else {
|
||||
// 获取计划任务日志 token
|
||||
tokenMaker = getExecJobLogTailToken(this.config);
|
||||
}
|
||||
const { data } = await tokenMaker;
|
||||
// 打开会话
|
||||
try {
|
||||
this.client = await createWebSocket(`${webSocketBaseUrl}/exec/log/${data}`);
|
||||
|
||||
@@ -4,6 +4,10 @@
|
||||
<div class="log-header">
|
||||
<!-- 左侧信息 -->
|
||||
<a-space class="log-header-left" :size="12">
|
||||
<!-- 执行序列 -->
|
||||
<a-tag v-if="execLog.execSeq" color="green">
|
||||
#{{ execLog.execSeq }}
|
||||
</a-tag>
|
||||
<!-- 状态 -->
|
||||
<a-tag :color="getDictValue(execHostStatusKey, host.status, 'color')">
|
||||
{{ getDictValue(execHostStatusKey, host.status) }}
|
||||
@@ -160,20 +164,24 @@
|
||||
</script>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import type { ExecCommandHostLogQueryResponse } from '@/api/exec/exec-command-log';
|
||||
import type { ILogAppender } from './const';
|
||||
import type { ExecLogQueryResponse, ExecHostLogQueryResponse } from '@/api/exec/exec-log';
|
||||
import type { ILogAppender } from './appender-const';
|
||||
import type { ExecType } from '../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';
|
||||
import { downloadExecJobLogFile } from '@/api/exec/exec-job-log';
|
||||
import { downloadFile } from '@/utils/file';
|
||||
import XtermSearchModal from '@/components/xtrem/search-modal/index.vue';
|
||||
import 'xterm/css/xterm.css';
|
||||
|
||||
const props = defineProps<{
|
||||
host: ExecCommandHostLogQueryResponse;
|
||||
appender: ILogAppender
|
||||
type: ExecType;
|
||||
execLog: ExecLogQueryResponse;
|
||||
host: ExecHostLogQueryResponse;
|
||||
appender: ILogAppender;
|
||||
}>();
|
||||
|
||||
const { getDictValue } = useDictStore();
|
||||
@@ -204,7 +212,17 @@
|
||||
|
||||
// 下载文件
|
||||
const downloadLogFile = async (id: number) => {
|
||||
const data = await downloadExecCommandLogFile(id);
|
||||
// 获取日志文件
|
||||
let fileGetter;
|
||||
if (props.type === 'BATCH') {
|
||||
// 下载批量执行日志
|
||||
fileGetter = downloadExecCommandLogFile(id);
|
||||
} else {
|
||||
// 下载计划任务日志
|
||||
fileGetter = downloadExecJobLogFile(id);
|
||||
}
|
||||
// 瞎子啊
|
||||
const data = await fileGetter;
|
||||
downloadFile(data);
|
||||
};
|
||||
|
||||
|
||||
@@ -2,10 +2,12 @@
|
||||
<div class="container">
|
||||
<log-item class="log-item"
|
||||
v-show="current === host.id"
|
||||
v-for="host in hosts"
|
||||
v-for="host in execLog.hosts"
|
||||
:key="host.id"
|
||||
:ref="addRef as unknown as VNodeRef"
|
||||
:type="type"
|
||||
:host="host"
|
||||
:exec-log="execLog"
|
||||
:appender="appender" />
|
||||
</div>
|
||||
</template>
|
||||
@@ -18,15 +20,17 @@
|
||||
|
||||
<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 { ExecLogQueryResponse } from '@/api/exec/exec-log';
|
||||
import type { ExecType } from '../const';
|
||||
import { nextTick, ref, watch } from 'vue';
|
||||
import LogItem from './log-item.vue';
|
||||
|
||||
const props = defineProps<{
|
||||
current: number;
|
||||
hosts: Array<ExecCommandHostLogQueryResponse>;
|
||||
execLog: ExecLogQueryResponse;
|
||||
appender: ILogAppender;
|
||||
type: ExecType;
|
||||
}>();
|
||||
|
||||
const logRefs = ref<Array<LogDomRef>>([]);
|
||||
|
||||
@@ -13,12 +13,14 @@ import { getHostGroupTree } from '@/api/asset/host-group';
|
||||
import { getMenuList } from '@/api/system/menu';
|
||||
import { getCurrentAuthorizedHostIdentity, getCurrentAuthorizedHostKey } from '@/api/asset/asset-authorized-data';
|
||||
import { getCommandSnippetGroupList } from '@/api/asset/command-snippet-group';
|
||||
import { getExecJobList } from '@/api/exec/exec-job';
|
||||
|
||||
export type CacheType = 'users' | 'menus' | 'roles'
|
||||
| 'hosts' | 'hostGroups' | 'hostKeys' | 'hostIdentities'
|
||||
| 'dictKeys'
|
||||
| 'authorizedHostKeys' | 'authorizedHostIdentities'
|
||||
| 'commandSnippetGroups'
|
||||
| 'execJob'
|
||||
| string
|
||||
|
||||
export default defineStore('cache', {
|
||||
@@ -118,5 +120,10 @@ export default defineStore('cache', {
|
||||
return await this.load('commandSnippetGroups', getCommandSnippetGroupList, force);
|
||||
},
|
||||
|
||||
// 获取执行计划列表
|
||||
async loadExecJobs(force = false) {
|
||||
return await this.load('execJob', getExecJobList, force);
|
||||
},
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
<template>
|
||||
<div class="container">
|
||||
<div class="wrapper">
|
||||
<exec-log-panel ref="log" :visible-back="false" />
|
||||
<exec-log-panel ref="log"
|
||||
type="BATCH"
|
||||
:visible-back="false" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -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);
|
||||
// 调用中断接口
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -10,7 +10,8 @@
|
||||
<exec-command-log-clear-modal ref="clearModal"
|
||||
@clear="clearCallback" />
|
||||
<!-- 执行日志模态框 -->
|
||||
<exec-log-panel-modal ref="logModal" />
|
||||
<exec-log-panel-modal ref="logModal"
|
||||
type="BATCH" />
|
||||
<!-- json 模态框 -->
|
||||
<json-editor-modal ref="jsonModal"
|
||||
:esc-to-close="true" />
|
||||
@@ -30,7 +31,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';
|
||||
|
||||
@@ -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];
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<h3>执行命令</h3>
|
||||
<span v-permission="['asset:exec-template:query']"
|
||||
class="span-blue usn pointer"
|
||||
@click="openTemplate">
|
||||
@click="emits('openTemplate')">
|
||||
从模板中选择
|
||||
</span>
|
||||
</div>
|
||||
@@ -17,9 +17,6 @@
|
||||
<div v-pre class="editor-help">
|
||||
使用 @{{ xxx }} 来替换参数, 输入_可以获取全部变量
|
||||
</div>
|
||||
<!-- 命令模板模态框 -->
|
||||
<exec-template-modal ref="templateModal"
|
||||
@selected="s => emits('selected', s)" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -30,17 +27,8 @@
|
||||
</script>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue';
|
||||
import ExecTemplateModal from '@/components/exec/template/modal/index.vue';
|
||||
|
||||
const emits = defineEmits(['selected']);
|
||||
|
||||
const templateModal = ref<any>();
|
||||
|
||||
// 打开模板
|
||||
const openTemplate = () => {
|
||||
templateModal.value.open();
|
||||
};
|
||||
const emits = defineEmits(['openTemplate']);
|
||||
|
||||
</script>
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
<template #form>
|
||||
<a-form :model="formModel"
|
||||
ref="formRef"
|
||||
class="form-wrapper"
|
||||
label-align="right"
|
||||
:rules="formRules">
|
||||
<!-- 执行主机 -->
|
||||
@@ -70,7 +71,7 @@
|
||||
</exec-command-panel-form>
|
||||
<!-- 执行命令 -->
|
||||
<exec-command-panel-editor class="exec-command-container"
|
||||
@selected="setWithTemplate">
|
||||
@open-template="() => templateModal.open()">
|
||||
<exec-editor v-model="formModel.command"
|
||||
theme="vs-dark"
|
||||
:parameter="parameterSchema" />
|
||||
@@ -79,6 +80,9 @@
|
||||
<exec-command-panel-history class="exec-history-container"
|
||||
ref="historyRef"
|
||||
@selected="setWithExecLog" />
|
||||
<!-- 命令模板模态框 -->
|
||||
<exec-template-modal ref="templateModal"
|
||||
@selected="setWithTemplate" />
|
||||
<!-- 主机模态框 -->
|
||||
<authorized-host-modal ref="hostModal"
|
||||
@selected="setSelectedHost" />
|
||||
@@ -92,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';
|
||||
@@ -106,6 +110,7 @@
|
||||
import ExecCommandPanelForm from './exec-command-panel-form.vue';
|
||||
import ExecCommandPanelHistory from './exec-command-panel-history.vue';
|
||||
import ExecCommandPanelEditor from './exec-command-panel-editor.vue';
|
||||
import ExecTemplateModal from '@/components/exec/template/modal/index.vue';
|
||||
|
||||
const emits = defineEmits(['submit']);
|
||||
|
||||
@@ -121,6 +126,7 @@
|
||||
const hostModal = ref<any>();
|
||||
const historyRef = ref<any>();
|
||||
const formRef = ref<any>();
|
||||
const templateModal = ref<any>();
|
||||
const parameterFormRef = ref<any>();
|
||||
const formModel = ref<ExecCommandRequest>({ ...defaultForm() });
|
||||
const parameterFormModel = ref<Record<string, any>>({});
|
||||
@@ -156,7 +162,7 @@
|
||||
};
|
||||
|
||||
// 从执行日志设置
|
||||
const setWithExecLog = (record: ExecCommandLogQueryResponse) => {
|
||||
const setWithExecLog = (record: ExecLogQueryResponse) => {
|
||||
formModel.value = {
|
||||
...formModel.value,
|
||||
command: record.command,
|
||||
@@ -257,6 +263,11 @@
|
||||
}
|
||||
|
||||
.exec-form-container {
|
||||
|
||||
.form-wrapper {
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
.selected-host {
|
||||
width: 100%;
|
||||
height: 32px;
|
||||
@@ -285,7 +296,7 @@
|
||||
:deep(.panel-header) {
|
||||
width: 100%;
|
||||
height: 28px;
|
||||
margin-bottom: 12px;
|
||||
margin-bottom: 4px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: flex-start;
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
<!-- 执行日志 -->
|
||||
<div v-if="logVisible" class="panel-wrapper">
|
||||
<exec-log-panel ref="log"
|
||||
type="BATCH"
|
||||
:visibleBack="true"
|
||||
@back="setLogVisible(false)" />
|
||||
</div>
|
||||
@@ -20,11 +21,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 +34,7 @@
|
||||
const log = ref();
|
||||
|
||||
// 打开日志
|
||||
const openLog = (record: ExecCommandLogQueryResponse) => {
|
||||
const openLog = (record: ExecLogQueryResponse) => {
|
||||
setLogVisible(true);
|
||||
nextTick(() => {
|
||||
log.value.open(record);
|
||||
|
||||
61
orion-ops-ui/src/views/exec/exec-job-log-view/index.vue
Normal file
61
orion-ops-ui/src/views/exec/exec-job-log-view/index.vue
Normal file
@@ -0,0 +1,61 @@
|
||||
<template>
|
||||
<div class="container">
|
||||
<div class="wrapper">
|
||||
<exec-log-panel ref="log"
|
||||
type="JOB"
|
||||
: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>
|
||||
@@ -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 { interruptHostExecJob } from '@/api/exec/exec-job-log';
|
||||
import { execHostStatusKey, execHostStatus } from '@/components/exec/log/const';
|
||||
import { useDictStore } from '@/store';
|
||||
import useLoading from '@/hooks/loading';
|
||||
import columns from '../../exec-command-log/types/host-table.columns';
|
||||
import { useExpandable } from '@/types/table';
|
||||
import { dateFormat, formatDuration } from '@/utils';
|
||||
import { downloadExecJobLogFile } from '@/api/exec/exec-job-log';
|
||||
import { copy } from '@/hooks/copy';
|
||||
import { downloadFile } from '@/utils/file';
|
||||
import { Message } from '@arco-design/web-vue';
|
||||
|
||||
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>
|
||||
@@ -0,0 +1,158 @@
|
||||
<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="sourceId" label="计划任务">
|
||||
<exec-job-selector v-model:model-value="formModel.sourceId"
|
||||
v-model:name="formModel.description"
|
||||
allow-create
|
||||
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 ExecJobSelector from '@/components/exec/job/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,
|
||||
sourceId: 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>
|
||||
@@ -0,0 +1,392 @@
|
||||
<template>
|
||||
<!-- 搜索 -->
|
||||
<a-card class="general-card table-search-card">
|
||||
<query-header :model="formModel"
|
||||
label-align="left"
|
||||
:itemOptions="{ 4: { 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="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-log';
|
||||
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,
|
||||
description: undefined,
|
||||
command: undefined,
|
||||
status: undefined,
|
||||
startTimeRange: undefined,
|
||||
});
|
||||
|
||||
// 打开清理
|
||||
const openClear = () => {
|
||||
emits('openClear', { ...formModel, id: undefined, description: 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>
|
||||
97
orion-ops-ui/src/views/exec/exec-job-log/index.vue
Normal file
97
orion-ops-ui/src/views/exec/exec-job-log/index.vue
Normal file
@@ -0,0 +1,97 @@
|
||||
<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"
|
||||
type="JOB" />
|
||||
<!-- 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>
|
||||
@@ -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;
|
||||
@@ -69,11 +69,20 @@
|
||||
</a-col>
|
||||
<!-- 执行命令 -->
|
||||
<a-col :span="24">
|
||||
<a-form-item field="command"
|
||||
<a-form-item class="command-item"
|
||||
field="command"
|
||||
label="执行命令"
|
||||
:hide-label="true"
|
||||
:wrapper-col-props="{ span: 24 }"
|
||||
:help="'使用 @{{ xxx }} 来替换参数, 输入_可以获取全部变量'">
|
||||
<template #extra>
|
||||
<span v-permission="['asset:exec-template:query']"
|
||||
class="span-blue usn pointer"
|
||||
@click="emits('openTemplate')">
|
||||
从模板中选择
|
||||
</span>
|
||||
</template>
|
||||
<!-- 命令框 -->
|
||||
<exec-editor v-model="formModel.command"
|
||||
container-class="command-editor"
|
||||
theme="vs-dark"
|
||||
@@ -94,6 +103,7 @@
|
||||
|
||||
<script lang="ts" setup>
|
||||
import type { ExecJobUpdateRequest } from '@/api/exec/exec-job';
|
||||
import type { ExecTemplateQueryResponse } from '@/api/exec/exec-template';
|
||||
import { onUnmounted, ref } from 'vue';
|
||||
import useLoading from '@/hooks/loading';
|
||||
import useVisible from '@/hooks/visible';
|
||||
@@ -104,7 +114,7 @@
|
||||
import { useDictStore } from '@/store';
|
||||
import ExecEditor from '@/components/view/exec-editor/index.vue';
|
||||
|
||||
const emits = defineEmits(['added', 'updated', 'openHost', 'testCron']);
|
||||
const emits = defineEmits(['added', 'updated', 'openHost', 'openTemplate', 'testCron']);
|
||||
|
||||
const { visible, setVisible } = useVisible();
|
||||
const { loading, setLoading } = useLoading();
|
||||
@@ -170,7 +180,13 @@
|
||||
formModel.value.hostIdList = hosts;
|
||||
};
|
||||
|
||||
defineExpose({ openAdd, openUpdate, setSelectedHost });
|
||||
// 通过模板设置
|
||||
const setWithTemplate = (template: ExecTemplateQueryResponse) => {
|
||||
formModel.value.command = template.command;
|
||||
formModel.value.timeout = template.timeout;
|
||||
};
|
||||
|
||||
defineExpose({ openAdd, openUpdate, setSelectedHost, setWithTemplate });
|
||||
|
||||
// 打开选择主机
|
||||
const openSelectHost = () => {
|
||||
@@ -249,6 +265,14 @@
|
||||
}
|
||||
}
|
||||
|
||||
.command-item {
|
||||
:deep(.arco-form-item-extra) {
|
||||
margin-top: -18px;
|
||||
width: 100%;
|
||||
text-align: end;
|
||||
}
|
||||
}
|
||||
|
||||
.command-editor {
|
||||
width: 100%;
|
||||
height: calc(100vh - 264px);
|
||||
|
||||
@@ -11,11 +11,15 @@
|
||||
@added="modalAddCallback"
|
||||
@updated="modalUpdateCallback"
|
||||
@open-host="(e) => hostModal.open(e)"
|
||||
@open-template="() => templateModal.open()"
|
||||
@test-cron="openNextCron" />
|
||||
<!-- 详情模态框 -->
|
||||
<!-- 任务详情模态框 -->
|
||||
<exec-job-detail-drawer ref="detail" />
|
||||
<!-- cron 执行时间模态框 -->
|
||||
<next-cron-modal ref="nextCron" />
|
||||
<!-- 执行模板模态框 -->
|
||||
<exec-template-modal ref="templateModal"
|
||||
@selected="(e) => drawer.setWithTemplate(e)" />
|
||||
<!-- 主机模态框 -->
|
||||
<authorized-host-modal ref="hostModal"
|
||||
@selected="(e) => drawer.setSelectedHost(e)" />
|
||||
@@ -37,12 +41,14 @@
|
||||
import ExecJobDetailDrawer from './components/exec-job-detail-drawer.vue';
|
||||
import AuthorizedHostModal from '@/components/asset/host/authorized-host-modal/index.vue';
|
||||
import NextCronModal from '@/components/meta/expression/next-cron-modal/index.vue';
|
||||
import ExecTemplateModal from '@/components/exec/template/modal/index.vue';
|
||||
|
||||
const render = ref(false);
|
||||
const table = ref();
|
||||
const drawer = ref();
|
||||
const detail = ref();
|
||||
const nextCron = ref();
|
||||
const templateModal = ref();
|
||||
const hostModal = ref();
|
||||
|
||||
// 添加回调
|
||||
|
||||
@@ -88,6 +88,7 @@
|
||||
</script>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import type { DictKeyQueryResponse } from '@/api/system/dict-key';
|
||||
import type { DictValueUpdateRequest } from '@/api/system/dict-value';
|
||||
import type { ExtraParamType } from '../../dict-key/types/const';
|
||||
import { ref } from 'vue';
|
||||
@@ -97,7 +98,6 @@
|
||||
import { createDictValue, updateDictValue } from '@/api/system/dict-value';
|
||||
import { Message } from '@arco-design/web-vue';
|
||||
import { ValueType, sortStep } from '../../dict-key/types/const';
|
||||
import { DictKeyQueryResponse } from '@/api/system/dict-key';
|
||||
import { useCacheStore } from '@/store';
|
||||
import DictKeySelector from '@/components/system/dict-key/selector/index.vue';
|
||||
|
||||
|
||||
@@ -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 }">
|
||||
|
||||
@@ -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 }">
|
||||
|
||||
Reference in New Issue
Block a user