优化执行逻辑.

This commit is contained in:
lijiahang
2024-03-20 20:52:40 +08:00
parent b8bd1d4b22
commit 386e440f02
21 changed files with 196 additions and 159 deletions

View File

@@ -9,7 +9,7 @@ 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.ExecLogTailRequest;
import com.orion.ops.module.asset.entity.request.exec.ReExecCommandRequest;
import com.orion.ops.module.asset.entity.vo.ExecCommandVO;
import com.orion.ops.module.asset.entity.vo.ExecLogVO;
import com.orion.ops.module.asset.service.ExecService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
@@ -44,7 +44,7 @@ public class ExecController {
@PostMapping("/exec-command")
@Operation(summary = "批量执行命令")
@PreAuthorize("@ss.hasPermission('asset:exec:exec-command')")
public ExecCommandVO execCommand(@Validated @RequestBody ExecCommandRequest request) {
public ExecLogVO execCommand(@Validated @RequestBody ExecCommandRequest request) {
return execService.execCommand(request);
}
@@ -52,7 +52,7 @@ public class ExecController {
@PostMapping("/re-exec-command")
@Operation(summary = "重新执行命令")
@PreAuthorize("@ss.hasPermission('asset:exec:exec-command')")
public ExecCommandVO reExecCommand(@Validated @RequestBody ReExecCommandRequest request) {
public ExecLogVO reExecCommand(@Validated @RequestBody ReExecCommandRequest request) {
return execService.reExecCommand(request.getLogId());
}

View File

@@ -57,6 +57,14 @@ public class ExecLogController {
return execLogService.getExecLogPage(request);
}
@IgnoreLog(IgnoreLogMode.RET)
@GetMapping("/get")
@Operation(summary = "查询执行日志")
@PreAuthorize("@ss.hasPermission('asset:exec-log:query')")
public ExecLogVO getExecLog(@RequestParam("id") Long id) {
return execLogService.getExecLog(id, ExecSourceEnum.BATCH.name());
}
@IgnoreLog(IgnoreLogMode.RET)
@GetMapping("/host-list")
@Operation(summary = "查询全部执行主机日志")

View File

@@ -1,40 +0,0 @@
package com.orion.ops.module.asset.entity.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
/**
* 命令执行主机 视图响应对象
*
* @author Jiahang Li
* @version 1.0.0
* @since 2024/3/11 14:57
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Schema(name = "ExecCommandHostVO", description = "命令执行主机 视图响应对象")
public class ExecCommandHostVO implements Serializable {
@Schema(description = "id")
private Long id;
@Schema(description = "hostId")
private Long hostId;
@Schema(description = "主机名称")
private String hostName;
@Schema(description = "主机地址")
private String hostAddress;
@Schema(description = "执行状态")
private String status;
}

View File

@@ -1,35 +0,0 @@
package com.orion.ops.module.asset.entity.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
import java.util.List;
/**
* 命令执行 视图响应对象
*
* @author Jiahang Li
* @version 1.0.0
* @since 2024/3/11 14:57
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Schema(name = "ExecCommandVO", description = "命令执行 视图响应对象")
public class ExecCommandVO implements Serializable {
@Schema(description = "id")
private Long id;
@Schema(description = "执行状态")
private String status;
@Schema(description = "主机 id 映射")
private List<ExecCommandHostVO> hosts;
}

View File

@@ -59,4 +59,7 @@ public class ExecLogVO implements Serializable {
@Schema(description = "执行主机id")
private List<Long> hostIdList;
@Schema(description = "执行主机")
private List<ExecHostLogVO> hosts;
}

View File

@@ -24,6 +24,15 @@ public interface ExecLogService {
*/
DataGrid<ExecLogVO> getExecLogPage(ExecLogQueryRequest request);
/**
* 获取执行日志
*
* @param id id
* @param source source
* @return row
*/
ExecLogVO getExecLog(Long id, String source);
/**
* 获取执行历史
*

View File

@@ -3,7 +3,7 @@ package com.orion.ops.module.asset.service;
import com.orion.ops.module.asset.entity.dto.ExecLogTailDTO;
import com.orion.ops.module.asset.entity.request.exec.ExecCommandRequest;
import com.orion.ops.module.asset.entity.request.exec.ExecLogTailRequest;
import com.orion.ops.module.asset.entity.vo.ExecCommandVO;
import com.orion.ops.module.asset.entity.vo.ExecLogVO;
import javax.servlet.http.HttpServletResponse;
@@ -22,7 +22,7 @@ public interface ExecService {
* @param request request
* @return result
*/
ExecCommandVO execCommand(ExecCommandRequest request);
ExecLogVO execCommand(ExecCommandRequest request);
/**
* 重新执行命令
@@ -30,7 +30,7 @@ public interface ExecService {
* @param id id
* @return result
*/
ExecCommandVO reExecCommand(Long id);
ExecLogVO reExecCommand(Long id);
/**
* 中断命令执行

View File

@@ -65,6 +65,24 @@ public class ExecLogServiceImpl implements ExecLogService {
.dataGrid(ExecLogConvert.MAPPER::to);
}
@Override
public ExecLogVO getExecLog(Long id, String source) {
// 查询执行日志
ExecLogDO row = execLogDAO.of()
.createValidateWrapper()
.eq(ExecLogDO::getId, id)
.eq(ExecLogDO::getSource, source)
.then()
.getOne();
Valid.notNull(row, ErrorMessage.LOG_ABSENT);
// 查询执行主机
List<ExecHostLogDO> hosts = execHostLogDAO.selectByLogId(id);
// 返回
ExecLogVO vo = ExecLogConvert.MAPPER.to(row);
vo.setHosts(ExecHostLogConvert.MAPPER.to(hosts));
return vo;
}
@Override
public List<ExecLogVO> getExecHistory(ExecLogQueryRequest request) {
// 查询执行记录

View File

@@ -23,6 +23,8 @@ import com.orion.ops.framework.common.security.LoginUser;
import com.orion.ops.framework.common.utils.Valid;
import com.orion.ops.framework.redis.core.utils.RedisStrings;
import com.orion.ops.framework.security.core.utils.SecurityUtils;
import com.orion.ops.module.asset.convert.ExecHostLogConvert;
import com.orion.ops.module.asset.convert.ExecLogConvert;
import com.orion.ops.module.asset.dao.ExecHostLogDAO;
import com.orion.ops.module.asset.dao.ExecLogDAO;
import com.orion.ops.module.asset.dao.HostDAO;
@@ -35,8 +37,8 @@ import com.orion.ops.module.asset.entity.dto.ExecLogTailDTO;
import com.orion.ops.module.asset.entity.dto.ExecParameterSchemaDTO;
import com.orion.ops.module.asset.entity.request.exec.ExecCommandRequest;
import com.orion.ops.module.asset.entity.request.exec.ExecLogTailRequest;
import com.orion.ops.module.asset.entity.vo.ExecCommandHostVO;
import com.orion.ops.module.asset.entity.vo.ExecCommandVO;
import com.orion.ops.module.asset.entity.vo.ExecHostLogVO;
import com.orion.ops.module.asset.entity.vo.ExecLogVO;
import com.orion.ops.module.asset.entity.vo.HostConfigVO;
import com.orion.ops.module.asset.enums.ExecHostStatusEnum;
import com.orion.ops.module.asset.enums.ExecSourceEnum;
@@ -102,7 +104,7 @@ public class ExecServiceImpl implements ExecService {
@Override
@Transactional(rollbackFor = Exception.class)
public ExecCommandVO execCommand(ExecCommandRequest request) {
public ExecLogVO execCommand(ExecCommandRequest request) {
log.info("ExecService.startExecCommand start params: {}", JSON.toJSONString(request));
LoginUser user = Objects.requireNonNull(SecurityUtils.getLoginUser());
Long userId = user.getId();
@@ -156,25 +158,15 @@ public class ExecServiceImpl implements ExecService {
// 开始执行
this.startExec(execLog, execHostLogs);
// 返回
List<ExecCommandHostVO> hostResult = execHostLogs.stream()
.map(s -> ExecCommandHostVO.builder()
.id(s.getId())
.hostId(s.getHostId())
.hostName(s.getHostName())
.hostAddress(s.getHostAddress())
.status(s.getStatus())
.build())
.collect(Collectors.toList());
return ExecCommandVO.builder()
.id(execId)
.status(execLog.getStatus())
.hosts(hostResult)
.build();
ExecLogVO result = ExecLogConvert.MAPPER.to(execLog);
List<ExecHostLogVO> resultHosts = ExecHostLogConvert.MAPPER.to(execHostLogs);
result.setHosts(resultHosts);
return result;
}
@Override
@Transactional(rollbackFor = Exception.class)
public ExecCommandVO reExecCommand(Long logId) {
public ExecLogVO reExecCommand(Long logId) {
log.info("ExecService.reExecCommand start logId: {}", logId);
// 获取执行记录
ExecLogDO execLog = execLogDAO.selectById(logId);

View File

@@ -30,6 +30,7 @@ export interface ExecLogQueryResponse extends TableData, ExecLogQueryExtraRespon
startTime: number;
finishTime: number;
hostIdList: Array<number>;
hosts: Array<ExecHostLogQueryResponse>;
}
/**
@@ -72,6 +73,13 @@ export function getExecLogPage(request: ExecLogQueryRequest) {
return axios.post<DataGrid<ExecLogQueryResponse>>('/asset/exec-log/query', request);
}
/**
* 查询执行记录
*/
export function getExecLog(id: number) {
return axios.get<ExecLogQueryResponse>('/asset/exec-log/query', { params: { id } });
}
/**
* 查询主机执行记录
*/

View File

@@ -1,3 +1,4 @@
import type { ExecLogQueryResponse } from './exec-log';
import axios from 'axios';
/**
@@ -28,44 +29,18 @@ export interface ExecTailRequest {
hostExecIdList?: Array<number>;
}
/**
* 执行命令响应
*/
export interface ExecCommandResponse {
id: number;
status: string;
startTime: number;
finishTime: number;
hosts: Array<ExecCommandHostResponse>;
}
/**
* 执行命令主机响应
*/
export interface ExecCommandHostResponse {
id: number;
hostId: number;
hostName: string;
hostAddress: string;
status: string;
exitStatus: number;
errorMessage: string;
startTime: number;
finishTime: number;
}
/**
* 批量执行命令
*/
export function batchExecCommand(request: ExecCommandRequest) {
return axios.post<ExecCommandResponse>('/asset/exec/exec-command', request);
return axios.post<ExecLogQueryResponse>('/asset/exec/exec-command', request);
}
/**
* 重新执行命令
*/
export function reExecCommand(request: ExecCommandRequest) {
return axios.post<ExecCommandResponse>('/asset/exec/re-exec-command', request);
return axios.post<ExecLogQueryResponse>('/asset/exec/re-exec-command', request);
}
/**

View File

@@ -0,0 +1,77 @@
<template>
<a-modal v-model:visible="visible"
title-align="start"
title="执行日志"
width="94%"
:top="80"
:body-style="{ padding: '0' }"
:align-center="false"
:draggable="true"
:mask-closable="false"
:unmount-on-close="true"
:footer="false"
@close="handleClose">
<a-spin class="modal-body" :loading="loading">
<!-- 日志面板 -->
<exec-log-panel ref="log" :visible-back="false" />
</a-spin>
</a-modal>
</template>
<script lang="ts">
export default {
name: 'execLogPanelModal'
};
</script>
<script lang="ts" setup>
import useVisible from '@/hooks/visible';
import useLoading from '@/hooks/loading';
import { nextTick, ref } from 'vue';
import { getExecLog } from '@/api/exec/exec-log';
import ExecLogPanel from '../panel/index.vue';
const { visible, setVisible } = useVisible();
const { loading, setLoading } = useLoading();
const log = ref();
// TODO 测试卸载
// 打开
const open = async (id: number) => {
setVisible(true);
setLoading(true);
try {
// 获取执行日志
const { data } = await getExecLog(id);
// 打开日志
nextTick(() => {
log.value.open(data);
});
} catch (e) {
} finally {
setVisible(false);
setLoading(false);
}
};
// 关闭回调
const handleClose = () => {
handleClear();
};
// 清空
const handleClear = () => {
setLoading(false);
setVisible(false);
};
</script>
<style lang="less" scoped>
.modal-body {
width: 100%;
height: calc(100vh - 140px);
}
</style>

View File

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

View File

@@ -22,7 +22,7 @@
</script>
<script lang="ts" setup>
import type { ExecCommandResponse } from '@/api/exec/exec';
import type { ExecLogQueryResponse } from '@/api/exec/exec-log';
import { onUnmounted, ref, nextTick } from 'vue';
import { getExecLogStatus } from '@/api/exec/exec-log';
import { execHostStatus, execStatus } from './const';
@@ -39,16 +39,20 @@
const currentHostExecId = ref();
const statusIntervalId = ref();
const finishIntervalId = ref();
const command = ref<ExecCommandResponse>();
const command = ref<ExecLogQueryResponse>();
// 打开
const open = (record: ExecCommandResponse) => {
const open = (record: ExecLogQueryResponse) => {
command.value = record;
currentHostExecId.value = record.hosts[0].id;
// 注册状态轮询
statusIntervalId.value = setInterval(fetchTaskStatus, 5000);
// 注册完成时间轮询
finishIntervalId.value = setInterval(setTaskFinishTime, 1000);
// 定时查询执行状态
if (record.status === execStatus.WAITING ||
record.status === execStatus.RUNNING) {
// 注册状态轮询
statusIntervalId.value = setInterval(fetchTaskStatus, 5000);
// 注册完成时间轮询
finishIntervalId.value = setInterval(setTaskFinishTime, 1000);
}
// 打开日志
nextTick(() => {
logView.value?.open();

View File

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

View File

@@ -20,7 +20,7 @@
<script lang="ts" setup>
import type { VNodeRef } from 'vue';
import type { ExecCommandResponse } from '@/api/exec/exec';
import type { ExecLogQueryResponse } from '@/api/exec/exec-log';
import type { LogDomRef, ILogAppender } from './const';
import { nextTick, onBeforeMount, ref, watch } from 'vue';
import LogAppender from './log-appender';
@@ -28,7 +28,7 @@
const props = defineProps<{
current: number;
command: ExecCommandResponse;
command: ExecLogQueryResponse;
}>();
const logRefs = ref<Array<LogDomRef>>([]);

View File

@@ -18,7 +18,7 @@
</script>
<script lang="ts" setup>
import type { ExecCommandResponse } from '@/api/exec/exec';
import type { ExecLogQueryResponse } from '@/api/exec/exec-log';
import { nextTick, onMounted, ref } from 'vue';
import useVisible from '@/hooks/visible';
import { useDictStore } from '@/store';
@@ -32,7 +32,7 @@
const log = ref();
// 打开日志
const openLog = (record: ExecCommandResponse) => {
const openLog = (record: ExecLogQueryResponse) => {
setLogVisible(true);
nextTick(() => {
log.value.open(record);

View File

@@ -163,7 +163,7 @@
<a-button v-permission="['asset:exec:exec-command']"
type="text"
size="mini"
@click="emits('viewLog', record.id)">
@click="() => emits('viewLog', record.id, $event.ctrlKey)">
日志
</a-button>
<!-- 中断 -->
@@ -221,8 +221,6 @@
const emits = defineEmits(['viewCommand', 'viewParams', 'viewLog', 'openClear']);
// TODO 日志 ctrl日志
const pagination = usePagination();
const rowSelection = useRowSelection();
const expandable = useExpandable();

View File

@@ -4,10 +4,13 @@
<exec-log-table ref="tableRef"
@view-command="viewCommand"
@view-params="viewParams"
@view-log="viewLog"
@open-clear="openClearModal" />
<!-- 清理模态框 -->
<exec-log-clear-modal ref="clearModal"
@clear="clearCallback" />
<!-- 执行日志模态框 -->
<exec-log-panel-modal ref="logModal" />
<!-- json 模态框 -->
<json-editor-modal ref="jsonModal"
:esc-to-close="true" />
@@ -32,9 +35,11 @@
import ExecLogClearModal from './components/exec-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 render = ref(false);
const tableRef = ref();
const logModal = ref();
const clearModal = ref();
const jsonModal = ref();
const shellModal = ref();
@@ -54,6 +59,15 @@
jsonModal.value.open(JSON.parse(data));
};
// 查看日志
const viewLog = (id: number, newWindow: boolean) => {
if (newWindow) {
// TODO openLog
} else {
logModal.value.open(id);
}
};
// 清理回调
const clearCallback = () => {
tableRef.value.fetchTableData();

View File

@@ -79,9 +79,6 @@
</a-form-item>
</a-form>
</a-spin>
<!-- 主机模态框 -->
<authorized-host-modal ref="hostModal"
@selected="setSelectedHost" />
</a-drawer>
</template>
@@ -102,14 +99,14 @@
import { Message } from '@arco-design/web-vue';
import { batchExecCommand } from '@/api/exec/exec';
import ExecEditor from '@/components/view/exec-editor/index.vue';
import AuthorizedHostModal from '@/components/asset/host/authorized-host-modal/index.vue';
const emits = defineEmits(['openHost']);
const { visible, setVisible } = useVisible();
const { loading, setLoading } = useLoading();
const formRef = ref<any>();
const parameterFormRef = ref<any>();
const hostModal = ref<any>();
const formModel = ref<ExecCommandRequest>({});
const parameterFormModel = ref<Record<string, any>>({});
const parameterSchema = ref<Array<TemplateParam>>([]);
@@ -142,18 +139,18 @@
}
};
defineExpose({ open });
// 打开选择主机
const openSelectHost = () => {
hostModal.value.open(formModel.value.hostIdList);
};
// 设置选中主机
const setSelectedHost = (hosts: Array<number>) => {
formModel.value.hostIdList = hosts;
};
defineExpose({ open, setSelectedHost });
// 打开选择主机
const openSelectHost = () => {
emits('openHost', formModel.value.hostIdList);
};
// 确定
const handlerOk = async () => {
setLoading(true);

View File

@@ -2,15 +2,19 @@
<div class="layout-container" v-if="render">
<!-- 列表-表格 -->
<exec-template-table ref="table"
@open-exec="e => execModal.open(e)"
@open-exec="(e) => execModal.open(e)"
@openAdd="() => drawer.openAdd()"
@openUpdate="(e) => drawer.openUpdate(e)" />
<!-- 添加修改模态框 -->
<exec-template-form-drawer ref="drawer"
@added="modalAddCallback"
@updated="modalUpdateCallback" />
<!-- 主机模态框 -->
<authorized-host-modal ref="hostModal"
@selected="(e) => execModal.setSelectedHost(e)" />
<!-- 执行模态框 -->
<exec-template-exec-drawer ref="execModal" />
<exec-template-exec-drawer ref="execModal"
@open-host="(e) => hostModal.open(e)" />
</div>
</template>
@@ -25,10 +29,15 @@
import ExecTemplateTable from './components/exec-template-table.vue';
import ExecTemplateFormDrawer from './components/exec-template-form-drawer.vue';
import ExecTemplateExecDrawer from './components/exec-template-exec-drawer.vue';
import AuthorizedHostModal from '@/components/asset/host/authorized-host-modal/index.vue';
// TODO TEST 选择主机
// TODO openAdd openUpdate 脊柱
const render = ref(false);
const table = ref();
const drawer = ref();
const hostModal = ref();
const execModal = ref();
// 添加回调