🔨 执行命令.
This commit is contained in:
@@ -58,7 +58,7 @@
|
||||
日志
|
||||
</a-button>
|
||||
<!-- 中断 -->
|
||||
<a-popconfirm content="确认要中断命令吗?"
|
||||
<a-popconfirm content="确认要中断命令吗, 删除后会中断执行?"
|
||||
position="left"
|
||||
type="warning"
|
||||
@ok="interruptedHost(record)">
|
||||
|
||||
@@ -0,0 +1,162 @@
|
||||
<template>
|
||||
<a-modal v-model:visible="visible"
|
||||
body-class="modal-form"
|
||||
title-align="start"
|
||||
title="清空执行记录"
|
||||
:align-center="false"
|
||||
:draggable="true"
|
||||
:mask-closable="false"
|
||||
:unmount-on-close="true"
|
||||
ok-text="清空"
|
||||
:ok-button-props="{ disabled: loading }"
|
||||
:cancel-button-props="{ disabled: loading }"
|
||||
:on-before-ok="handlerOk"
|
||||
@close="handleClose">
|
||||
<a-spin class="full" :loading="loading">
|
||||
<a-form :model="formModel"
|
||||
ref="formRef"
|
||||
label-align="right"
|
||||
:label-col-props="{ span: 5 }"
|
||||
:wrapper-col-props="{ span: 18 }">
|
||||
<!-- 执行用户 -->
|
||||
<a-form-item field="userId" label="执行用户">
|
||||
<user-selector v-model="formModel.userId"
|
||||
placeholder="请选择执行用户"
|
||||
allow-clear />
|
||||
</a-form-item>
|
||||
<!-- 执行描述 -->
|
||||
<a-form-item field="description" label="执行描述">
|
||||
<a-input v-model="formModel.description"
|
||||
placeholder="请输入执行描述"
|
||||
allow-clear />
|
||||
</a-form-item>
|
||||
<!-- 执行命令 -->
|
||||
<a-form-item field="command" label="执行命令">
|
||||
<a-input v-model="formModel.command"
|
||||
placeholder="请输入执行命令"
|
||||
allow-clear />
|
||||
</a-form-item>
|
||||
<!-- 执行状态 -->
|
||||
<a-form-item field="status" label="执行状态">
|
||||
<a-select v-model="formModel.status"
|
||||
:options="toOptions(execStatusKey)"
|
||||
placeholder="请选择执行状态" />
|
||||
</a-form-item>
|
||||
<!-- 执行时间 -->
|
||||
<a-form-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>
|
||||
</a-spin>
|
||||
</a-modal>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: 'execLogClearModal'
|
||||
};
|
||||
</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 '../types/const';
|
||||
import { getExecLogCount, clearExecLog } from '@/api/exec/exec-log';
|
||||
import { Message, Modal } from '@arco-design/web-vue';
|
||||
import { useDictStore } from '@/store';
|
||||
import UserSelector from '@/components/user/user/selector/index.vue';
|
||||
|
||||
const emits = defineEmits(['clear']);
|
||||
|
||||
const { visible, setVisible } = useVisible();
|
||||
const { loading, setLoading } = useLoading();
|
||||
const { toOptions } = useDictStore();
|
||||
|
||||
const formRef = ref<any>();
|
||||
const formModel = ref<ExecLogQueryRequest>({});
|
||||
|
||||
const defaultForm = (): ExecLogQueryRequest => {
|
||||
return {
|
||||
id: undefined,
|
||||
userId: undefined,
|
||||
description: undefined,
|
||||
command: undefined,
|
||||
status: undefined,
|
||||
startTimeRange: undefined
|
||||
};
|
||||
};
|
||||
|
||||
// 打开
|
||||
const open = (record: any) => {
|
||||
renderForm({ ...defaultForm(), ...record });
|
||||
setVisible(true);
|
||||
};
|
||||
|
||||
// 渲染表单
|
||||
const renderForm = (record: any) => {
|
||||
formModel.value = Object.assign({}, record);
|
||||
};
|
||||
|
||||
defineExpose({ open });
|
||||
// 确定
|
||||
const handlerOk = async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
// 获取总数量
|
||||
const { data } = await getExecLogCount(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 clearExecLog(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>
|
||||
@@ -61,8 +61,26 @@
|
||||
<!-- 右侧操作 -->
|
||||
<div class="table-right-bar-handle">
|
||||
<a-space>
|
||||
<!-- 执行命令 -->
|
||||
<a-button v-permission="['asset:exec:exec-command']"
|
||||
type="primary"
|
||||
@click="$router.push({ name: 'execCommand' })">
|
||||
执行命令
|
||||
<template #icon>
|
||||
<icon-thunderbolt />
|
||||
</template>
|
||||
</a-button>
|
||||
<!-- 清空 -->
|
||||
<a-button v-permission="['infra:exec-log:clear']"
|
||||
status="danger"
|
||||
@click="openClear">
|
||||
清空
|
||||
<template #icon>
|
||||
<icon-close />
|
||||
</template>
|
||||
</a-button>
|
||||
<!-- 删除 -->
|
||||
<a-popconfirm :content="`确认删除选中的 ${selectedKeys.length} 条记录吗?`"
|
||||
<a-popconfirm :content="`确认删除选中的 ${selectedKeys.length} 条记录吗? 删除后会中断执行!`"
|
||||
position="br"
|
||||
type="warning"
|
||||
@ok="deleteSelectRows">
|
||||
@@ -128,7 +146,7 @@
|
||||
<a-popconfirm content="确定要重新执行吗?"
|
||||
position="left"
|
||||
type="warning"
|
||||
@ok="deleteRow(record)">
|
||||
@ok="doReExecCommand(record)">
|
||||
<a-button v-permission="['asset:exec:exec-command']"
|
||||
type="text"
|
||||
size="mini">
|
||||
@@ -136,14 +154,13 @@
|
||||
</a-button>
|
||||
</a-popconfirm>
|
||||
<!-- 命令 -->
|
||||
<a-button v-permission="['asset:exec:interrupt-exec']"
|
||||
type="text"
|
||||
<a-button type="text"
|
||||
size="mini"
|
||||
@click="emits('viewCommand', record.command)">
|
||||
命令
|
||||
</a-button>
|
||||
<!-- 日志 -->
|
||||
<a-button v-permission="['asset:exec:interrupt-exec']"
|
||||
<a-button v-permission="['asset:exec:exec-command']"
|
||||
type="text"
|
||||
size="mini"
|
||||
@click="emits('viewLog', record.id)">
|
||||
@@ -153,7 +170,7 @@
|
||||
<a-popconfirm content="确定要中断执行吗?"
|
||||
position="left"
|
||||
type="warning"
|
||||
@ok="interruptedExec(record)">
|
||||
@ok="doInterruptExec(record)">
|
||||
<a-button v-permission="['asset:exec:interrupt-exec']"
|
||||
type="text"
|
||||
size="mini"
|
||||
@@ -163,7 +180,7 @@
|
||||
</a-button>
|
||||
</a-popconfirm>
|
||||
<!-- 删除 -->
|
||||
<a-popconfirm content="确认删除这条记录吗?"
|
||||
<a-popconfirm content="确认删除这条记录吗, 删除后会中断执行?"
|
||||
position="left"
|
||||
type="warning"
|
||||
@ok="deleteRow(record)">
|
||||
@@ -188,8 +205,8 @@
|
||||
|
||||
<script lang="ts" setup>
|
||||
import type { ExecLogQueryRequest, ExecLogQueryResponse } from '@/api/exec/exec-log';
|
||||
import { reactive, ref, onMounted } from 'vue';
|
||||
import { batchDeleteExecLog, deleteExecLog, getExecHostLogList, getExecLogPage } from '@/api/exec/exec-log';
|
||||
import { reactive, ref, onMounted, onUnmounted } from 'vue';
|
||||
import { batchDeleteExecLog, deleteExecLog, getExecHostLogList, getExecLogPage, getExecLogStatus } from '@/api/exec/exec-log';
|
||||
import { Message } from '@arco-design/web-vue';
|
||||
import useLoading from '@/hooks/loading';
|
||||
import columns from '../types/table.columns';
|
||||
@@ -197,13 +214,13 @@
|
||||
import { useExpandable, usePagination, useRowSelection } from '@/types/table';
|
||||
import { useDictStore } from '@/store';
|
||||
import { dateFormat, formatDuration } from '@/utils';
|
||||
import { interruptExec } from '@/api/exec/exec';
|
||||
import { interruptExec, reExecCommand } from '@/api/exec/exec';
|
||||
import UserSelector from '@/components/user/user/selector/index.vue';
|
||||
import ExecHostLogTable from './exec-host-log-table.vue';
|
||||
|
||||
const emits = defineEmits(['viewCommand', 'viewParams', 'viewLog']);
|
||||
const emits = defineEmits(['viewCommand', 'viewParams', 'viewLog', 'openClear']);
|
||||
|
||||
// TODO 日志 清理 轮询状态 ctrl日志 ctrl重新执行
|
||||
// TODO 日志 清理 ctrl日志 ctrl重新执行
|
||||
|
||||
const pagination = usePagination();
|
||||
const rowSelection = useRowSelection();
|
||||
@@ -211,6 +228,7 @@
|
||||
const { loading, setLoading } = useLoading();
|
||||
const { toOptions, getDictValue } = useDictStore();
|
||||
|
||||
const intervalId = ref();
|
||||
const tableRef = ref();
|
||||
const selectedKeys = ref<number[]>([]);
|
||||
const tableRenderData = ref<ExecLogQueryResponse[]>([]);
|
||||
@@ -223,6 +241,11 @@
|
||||
startTimeRange: undefined,
|
||||
});
|
||||
|
||||
// 打开清理
|
||||
const openClear = () => {
|
||||
emits('openClear', { ...formModel, id: undefined });
|
||||
};
|
||||
|
||||
// 删除选中行
|
||||
const deleteSelectRows = async () => {
|
||||
try {
|
||||
@@ -256,8 +279,24 @@
|
||||
}
|
||||
};
|
||||
|
||||
// 重新执行命令
|
||||
const doReExecCommand = async (record: ExecLogQueryResponse) => {
|
||||
try {
|
||||
setLoading(true);
|
||||
// 调用中断接口
|
||||
await reExecCommand({
|
||||
logId: record.id
|
||||
});
|
||||
Message.success('已重新执行');
|
||||
fetchTableData();
|
||||
} catch (e) {
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
// 中断执行
|
||||
const interruptedExec = async (record: ExecLogQueryResponse) => {
|
||||
const doInterruptExec = async (record: ExecLogQueryResponse) => {
|
||||
try {
|
||||
setLoading(true);
|
||||
// 调用中断接口
|
||||
@@ -282,6 +321,43 @@
|
||||
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 getExecLogStatus(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 {
|
||||
@@ -304,8 +380,20 @@
|
||||
doFetchTableData({ page, limit, ...form });
|
||||
};
|
||||
|
||||
defineExpose({
|
||||
fetchTableData
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
// 加载数据
|
||||
fetchTableData();
|
||||
// 注册状态轮询
|
||||
intervalId.value = setInterval(fetchTaskStatus, 10000);
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
// 卸载状态轮询
|
||||
clearInterval(intervalId.value);
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
@@ -1,8 +1,13 @@
|
||||
<template>
|
||||
<div class="layout-container" v-if="render">
|
||||
<!-- 列表-表格 -->
|
||||
<exec-log-table @view-command="viewCommand"
|
||||
@view-params="viewParams" />
|
||||
<exec-log-table ref="tableRef"
|
||||
@view-command="viewCommand"
|
||||
@view-params="viewParams"
|
||||
@open-clear="openClearModal" />
|
||||
<!-- 清理模态框 -->
|
||||
<exec-log-clear-modal ref="clearModal"
|
||||
@clear="clearCallback" />
|
||||
<!-- json 模态框 -->
|
||||
<json-editor-modal ref="jsonModal"
|
||||
:esc-to-close="true" />
|
||||
@@ -24,13 +29,21 @@
|
||||
import { useDictStore } from '@/store';
|
||||
import { dictKeys } from './types/const';
|
||||
import ExecLogTable from './components/exec-log-table.vue';
|
||||
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';
|
||||
|
||||
const render = ref(false);
|
||||
const tableRef = 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, '命令');
|
||||
@@ -41,6 +54,11 @@
|
||||
jsonModal.value.open(JSON.parse(data));
|
||||
};
|
||||
|
||||
// 清理回调
|
||||
const clearCallback = () => {
|
||||
tableRef.value.fetchTableData();
|
||||
};
|
||||
|
||||
onBeforeMount(async () => {
|
||||
const dictStore = useDictStore();
|
||||
await dictStore.loadKeys(dictKeys);
|
||||
|
||||
Reference in New Issue
Block a user