🔨 执行日志.
This commit is contained in:
@@ -53,7 +53,8 @@ public class HostSftpLogServiceImpl implements HostSftpLogService {
|
|||||||
vo.setExtra(extra);
|
vo.setExtra(extra);
|
||||||
return vo;
|
return vo;
|
||||||
}).collect(Collectors.toList());
|
}).collect(Collectors.toList());
|
||||||
// 返回 TODO KIT
|
// 返回
|
||||||
|
// TODO KIT
|
||||||
DataGrid<HostSftpLogVO> result = new DataGrid<>(rows, dataGrid.getTotal());
|
DataGrid<HostSftpLogVO> result = new DataGrid<>(rows, dataGrid.getTotal());
|
||||||
result.setPage(dataGrid.getPage());
|
result.setPage(dataGrid.getPage());
|
||||||
result.setLimit(dataGrid.getLimit());
|
result.setLimit(dataGrid.getLimit());
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import type { IDisposable, ITerminalOptions, ITerminalInitOnlyOptions } from 'xterm';
|
import type { IDisposable, ITerminalInitOnlyOptions, ITerminalOptions, Terminal } from 'xterm';
|
||||||
import type { Terminal } from 'xterm';
|
|
||||||
import type { FitAddon } from 'xterm-addon-fit';
|
import type { FitAddon } from 'xterm-addon-fit';
|
||||||
import type { SearchAddon } from 'xterm-addon-search';
|
import type { SearchAddon } from 'xterm-addon-search';
|
||||||
import type { CanvasAddon } from 'xterm-addon-canvas';
|
import type { CanvasAddon } from 'xterm-addon-canvas';
|
||||||
@@ -96,3 +95,44 @@ export interface ILogAppender {
|
|||||||
// 关闭
|
// 关闭
|
||||||
close(): void;
|
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];
|
||||||
@@ -5,7 +5,9 @@
|
|||||||
<div class="panel-header">
|
<div class="panel-header">
|
||||||
<h3>执行主机</h3>
|
<h3>执行主机</h3>
|
||||||
<!-- 操作 -->
|
<!-- 操作 -->
|
||||||
<a-button size="small" @click="emits('back')">
|
<a-button v-if="visibleBack"
|
||||||
|
size="small"
|
||||||
|
@click="emits('back')">
|
||||||
返回
|
返回
|
||||||
</a-button>
|
</a-button>
|
||||||
</div>
|
</div>
|
||||||
@@ -34,16 +36,17 @@
|
|||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
export default {
|
export default {
|
||||||
name: 'logPanelHost'
|
name: 'execHost'
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import type { ExecCommandHostResponse } from '@/api/exec/exec';
|
import type { ExecCommandHostResponse } from '@/api/exec/exec';
|
||||||
import { useDictStore } from '@/store';
|
import { useDictStore } from '@/store';
|
||||||
import { execHostStatusKey } from '@/views/exec/exec-log/types/const';
|
import { execHostStatusKey } from './const';
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
|
visibleBack: boolean;
|
||||||
current: number;
|
current: number;
|
||||||
hosts: Array<ExecCommandHostResponse>;
|
hosts: Array<ExecCommandHostResponse>;
|
||||||
}>();
|
}>();
|
||||||
@@ -1,22 +1,23 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="log-panel-container" v-if="command">
|
<div class="log-panel-container" v-if="command">
|
||||||
<!-- 执行主机 -->
|
<!-- 执行主机 -->
|
||||||
<log-panel-host class="host-container"
|
<exec-host class="exec-host-container"
|
||||||
:current="currentHostExecId"
|
:visibleBack="visibleBack"
|
||||||
:hosts="command.hosts"
|
:current="currentHostExecId"
|
||||||
@selected="selectedHost"
|
:hosts="command.hosts"
|
||||||
@back="emits('back')" />
|
@selected="selectedHost"
|
||||||
|
@back="emits('back')" />
|
||||||
<!-- 日志容器 -->
|
<!-- 日志容器 -->
|
||||||
<log-panel-view ref="logView"
|
<log-view ref="logView"
|
||||||
class="log-container"
|
class="log-view-container"
|
||||||
:current="currentHostExecId"
|
:current="currentHostExecId"
|
||||||
:command="command" />
|
:command="command" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
export default {
|
export default {
|
||||||
name: 'logPanel'
|
name: 'execLogPanel'
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -24,9 +25,13 @@
|
|||||||
import type { ExecCommandResponse } from '@/api/exec/exec';
|
import type { ExecCommandResponse } from '@/api/exec/exec';
|
||||||
import { onUnmounted, ref, nextTick } from 'vue';
|
import { onUnmounted, ref, nextTick } from 'vue';
|
||||||
import { getExecLogStatus } from '@/api/exec/exec-log';
|
import { getExecLogStatus } from '@/api/exec/exec-log';
|
||||||
import { execHostStatus, execStatus } from '@/views/exec/exec-log/types/const';
|
import { execHostStatus, execStatus } from './const';
|
||||||
import LogPanelHost from './log-panel-host.vue';
|
import ExecHost from './exec-host.vue';
|
||||||
import LogPanelView from './log-panel-view.vue';
|
import LogView from './log-view.vue';
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
visibleBack: boolean
|
||||||
|
}>();
|
||||||
|
|
||||||
const emits = defineEmits(['back']);
|
const emits = defineEmits(['back']);
|
||||||
|
|
||||||
@@ -146,20 +151,20 @@
|
|||||||
position: absolute;
|
position: absolute;
|
||||||
}
|
}
|
||||||
|
|
||||||
.host-container, .log-container {
|
.exec-host-container, .log-view-container {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
position: relative;
|
position: relative;
|
||||||
background: var(--color-bg-2);
|
background: var(--color-bg-2);
|
||||||
}
|
}
|
||||||
|
|
||||||
.host-container {
|
.exec-host-container {
|
||||||
width: @host-width;
|
width: @host-width;
|
||||||
padding: 16px;
|
padding: 16px;
|
||||||
margin-right: 16px;
|
margin-right: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.log-container {
|
.log-view-container {
|
||||||
width: calc(100% - @host-real-width);
|
width: calc(100% - @host-real-width);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import type { ILogAppender, LogAddons, LogAppenderConf, LogDomRef } from './appender.const';
|
import type { ILogAppender, LogAddons, LogAppenderConf, LogDomRef } from './const';
|
||||||
import type { ExecTailRequest } from '@/api/exec/exec';
|
import type { ExecTailRequest } from '@/api/exec/exec';
|
||||||
import { AppenderOptions } from './appender.const';
|
import { AppenderOptions } from './const';
|
||||||
import { getExecLogTailToken } from '@/api/exec/exec';
|
import { getExecLogTailToken } from '@/api/exec/exec';
|
||||||
import { webSocketBaseUrl } from '@/utils/env';
|
import { webSocketBaseUrl } from '@/utils/env';
|
||||||
import { Message } from '@arco-design/web-vue';
|
import { Message } from '@arco-design/web-vue';
|
||||||
@@ -155,15 +155,15 @@
|
|||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
export default {
|
export default {
|
||||||
name: 'logPanelAppender'
|
name: 'logItem'
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import type { ExecCommandHostResponse } from '@/api/exec/exec';
|
import type { ExecCommandHostResponse } from '@/api/exec/exec';
|
||||||
import type { ILogAppender } from '@/components/xtrem/log-appender/appender.const';
|
import type { ILogAppender } from './const';
|
||||||
import { ref } from 'vue';
|
import { ref } from 'vue';
|
||||||
import { execHostStatus, execHostStatusKey } from '@/views/exec/exec-log/types/const';
|
import { execHostStatus, execHostStatusKey } from './const';
|
||||||
import { formatDuration } from '@/utils';
|
import { formatDuration } from '@/utils';
|
||||||
import { useDictStore } from '@/store';
|
import { useDictStore } from '@/store';
|
||||||
import { downloadExecLogFile } from '@/api/exec/exec';
|
import { downloadExecLogFile } from '@/api/exec/exec';
|
||||||
@@ -1,30 +1,30 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<template v-if="appender">
|
<template v-if="appender">
|
||||||
<log-panel-appender class="log-view"
|
<log-item class="log-item"
|
||||||
v-show="current === host.id"
|
v-show="current === host.id"
|
||||||
v-for="host in command.hosts"
|
v-for="host in command.hosts"
|
||||||
:key="host.id"
|
:key="host.id"
|
||||||
:ref="addRef as unknown as VNodeRef"
|
:ref="addRef as unknown as VNodeRef"
|
||||||
:host="host"
|
:host="host"
|
||||||
:appender="appender as ILogAppender" />
|
:appender="appender as ILogAppender" />
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
export default {
|
export default {
|
||||||
name: 'logPanelView'
|
name: 'logView'
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import type { VNodeRef } from 'vue';
|
import type { VNodeRef } from 'vue';
|
||||||
import type { ExecCommandResponse } from '@/api/exec/exec';
|
import type { ExecCommandResponse } from '@/api/exec/exec';
|
||||||
import type { LogDomRef, ILogAppender } from '@/components/xtrem/log-appender/appender.const';
|
import type { LogDomRef, ILogAppender } from './const';
|
||||||
import { nextTick, onBeforeMount, ref, watch } from 'vue';
|
import { nextTick, onBeforeMount, ref, watch } from 'vue';
|
||||||
import LogAppender from '@/components/xtrem/log-appender/log-appender';
|
import LogAppender from './log-appender';
|
||||||
import LogPanelAppender from './log-panel-appender.vue';
|
import LogItem from './log-item.vue';
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
current: number;
|
current: number;
|
||||||
@@ -88,7 +88,7 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="less" scoped>
|
<style lang="less" scoped>
|
||||||
.log-view {
|
.log-item {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
position: relative;
|
position: relative;
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div>
|
|
||||||
his
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
export default {
|
|
||||||
name: 'execHistory'
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<script lang="ts" setup>
|
|
||||||
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="less" scoped>
|
|
||||||
|
|
||||||
</style>
|
|
||||||
@@ -220,10 +220,11 @@
|
|||||||
@command-gap: @form-width + @history-width + 32px;
|
@command-gap: @form-width + @history-width + 32px;
|
||||||
|
|
||||||
.exec-container {
|
.exec-container {
|
||||||
|
width: calc(100% - 32px);
|
||||||
|
height: calc(100% - 32px);
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
width: 100%;
|
position: absolute;
|
||||||
height: 100%;
|
|
||||||
|
|
||||||
.exec-form-container {
|
.exec-form-container {
|
||||||
width: @form-width;
|
width: @form-width;
|
||||||
|
|||||||
@@ -4,9 +4,10 @@
|
|||||||
<exec-panel v-show="!logVisible"
|
<exec-panel v-show="!logVisible"
|
||||||
@submit="openLog" />
|
@submit="openLog" />
|
||||||
<!-- 执行日志 -->
|
<!-- 执行日志 -->
|
||||||
<log-panel v-if="logVisible"
|
<exec-log-panel v-if="logVisible"
|
||||||
ref="log"
|
ref="log"
|
||||||
@back="setLogVisible(false)" />
|
:visibleBack="true"
|
||||||
|
@back="setLogVisible(false)" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -23,7 +24,7 @@
|
|||||||
import { useDictStore } from '@/store';
|
import { useDictStore } from '@/store';
|
||||||
import { dictKeys } from '@/views/exec/exec-log/types/const';
|
import { dictKeys } from '@/views/exec/exec-log/types/const';
|
||||||
import ExecPanel from './components/exec-panel.vue';
|
import ExecPanel from './components/exec-panel.vue';
|
||||||
import LogPanel from './components/log-panel.vue';
|
import ExecLogPanel from '@/components/exec/log/panel/index.vue';
|
||||||
|
|
||||||
const { visible: logVisible, setVisible: setLogVisible } = useVisible();
|
const { visible: logVisible, setVisible: setLogVisible } = useVisible();
|
||||||
const { loadKeys } = useDictStore();
|
const { loadKeys } = useDictStore();
|
||||||
@@ -43,44 +44,6 @@
|
|||||||
await loadKeys(dictKeys);
|
await loadKeys(dictKeys);
|
||||||
});
|
});
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
openLog({
|
|
||||||
id: 65,
|
|
||||||
hosts: [
|
|
||||||
{
|
|
||||||
id: 103,
|
|
||||||
hostId: 5,
|
|
||||||
hostName: 'main-55',
|
|
||||||
hostAddress: '192.412.53.2',
|
|
||||||
status: 'INTERRUPTED'
|
|
||||||
}, {
|
|
||||||
id: 76,
|
|
||||||
hostId: 1,
|
|
||||||
hostName: 'main-11',
|
|
||||||
hostAddress: '192.412.53.2',
|
|
||||||
status: 'WAITING'
|
|
||||||
}, {
|
|
||||||
id: 77,
|
|
||||||
hostId: 2,
|
|
||||||
hostName: 'main-22',
|
|
||||||
hostAddress: '192.412.53.2',
|
|
||||||
status: 'RUNNING'
|
|
||||||
}, {
|
|
||||||
id: 78,
|
|
||||||
hostId: 3,
|
|
||||||
hostName: 'main-33',
|
|
||||||
hostAddress: '192.412.53.2',
|
|
||||||
status: 'COMPLETED'
|
|
||||||
}, {
|
|
||||||
id: 79,
|
|
||||||
hostId: 4,
|
|
||||||
hostName: 'main-44',
|
|
||||||
hostAddress: '192.412.53.2',
|
|
||||||
status: 'TIMEOUT'
|
|
||||||
},]
|
|
||||||
} as ExecCommandResponse);
|
|
||||||
});
|
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="less" scoped>
|
<style lang="less" scoped>
|
||||||
|
|||||||
@@ -58,10 +58,11 @@
|
|||||||
@click="emits('viewParams', record.parameter)">
|
@click="emits('viewParams', record.parameter)">
|
||||||
参数
|
参数
|
||||||
</a-button>
|
</a-button>
|
||||||
<!-- 日志 -->
|
<!-- 下载 -->
|
||||||
<a-button type="text"
|
<a-button type="text"
|
||||||
size="mini">
|
size="mini"
|
||||||
日志
|
@click="downloadLogFile(record.id)">
|
||||||
|
下载
|
||||||
</a-button>
|
</a-button>
|
||||||
<!-- 中断 -->
|
<!-- 中断 -->
|
||||||
<a-popconfirm content="确认要中断命令吗, 删除后会中断执行?"
|
<a-popconfirm content="确认要中断命令吗, 删除后会中断执行?"
|
||||||
@@ -109,8 +110,9 @@
|
|||||||
import { useDictStore } from '@/store';
|
import { useDictStore } from '@/store';
|
||||||
import { useExpandable } from '@/types/table';
|
import { useExpandable } from '@/types/table';
|
||||||
import { dateFormat, formatDuration } from '@/utils';
|
import { dateFormat, formatDuration } from '@/utils';
|
||||||
import { interruptHostExec } from '@/api/exec/exec';
|
import { downloadExecLogFile, interruptHostExec } from '@/api/exec/exec';
|
||||||
import { copy } from '@/hooks/copy';
|
import { copy } from '@/hooks/copy';
|
||||||
|
import { downloadFile } from '@/utils/file';
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
row: ExecLogQueryResponse;
|
row: ExecLogQueryResponse;
|
||||||
@@ -122,6 +124,12 @@
|
|||||||
const { loading, setLoading } = useLoading();
|
const { loading, setLoading } = useLoading();
|
||||||
const { toOptions, getDictValue } = useDictStore();
|
const { toOptions, getDictValue } = useDictStore();
|
||||||
|
|
||||||
|
// 下载文件
|
||||||
|
const downloadLogFile = async (id: number) => {
|
||||||
|
const data = await downloadExecLogFile(id);
|
||||||
|
downloadFile(data);
|
||||||
|
};
|
||||||
|
|
||||||
// 中断执行
|
// 中断执行
|
||||||
const interruptedHost = async (record: ExecHostLogQueryResponse) => {
|
const interruptedHost = async (record: ExecHostLogQueryResponse) => {
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -221,7 +221,7 @@
|
|||||||
|
|
||||||
const emits = defineEmits(['viewCommand', 'viewParams', 'viewLog', 'openClear']);
|
const emits = defineEmits(['viewCommand', 'viewParams', 'viewLog', 'openClear']);
|
||||||
|
|
||||||
// TODO 日志 ctrl日志 ctrl重新执行
|
// TODO 日志 ctrl日志
|
||||||
|
|
||||||
const pagination = usePagination();
|
const pagination = usePagination();
|
||||||
const rowSelection = useRowSelection();
|
const rowSelection = useRowSelection();
|
||||||
|
|||||||
@@ -184,9 +184,7 @@
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (parameter.value.length) {
|
formModel.value.parameter = JSON.stringify(parameter.value);
|
||||||
formModel.value.parameter = JSON.stringify(parameter.value);
|
|
||||||
}
|
|
||||||
if (isAddHandle.value) {
|
if (isAddHandle.value) {
|
||||||
// 新增
|
// 新增
|
||||||
await createExecTemplate(formModel.value);
|
await createExecTemplate(formModel.value);
|
||||||
|
|||||||
@@ -79,8 +79,7 @@
|
|||||||
<a-button v-permission="['asset:exec:exec-command']"
|
<a-button v-permission="['asset:exec:exec-command']"
|
||||||
type="text"
|
type="text"
|
||||||
size="mini"
|
size="mini"
|
||||||
title="ctrl + 鼠标左键新页面打开"
|
@click="emits('openExec', record)">
|
||||||
@click="openExec($event, record)">
|
|
||||||
执行
|
执行
|
||||||
</a-button>
|
</a-button>
|
||||||
<!-- 修改 -->
|
<!-- 修改 -->
|
||||||
@@ -136,15 +135,6 @@
|
|||||||
command: undefined,
|
command: undefined,
|
||||||
});
|
});
|
||||||
|
|
||||||
// 打开执行
|
|
||||||
const openExec = (e: any, record: ExecTemplateQueryResponse) => {
|
|
||||||
if (e.ctrlKey) {
|
|
||||||
// TODO 新页面打开
|
|
||||||
} else {
|
|
||||||
emits('openExec', record);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// 删除当前行
|
// 删除当前行
|
||||||
const deleteRow = async ({ id }: {
|
const deleteRow = async ({ id }: {
|
||||||
id: number
|
id: number
|
||||||
|
|||||||
Reference in New Issue
Block a user