🔨 命令执行用户.

This commit is contained in:
lijiahangmax
2024-12-13 01:05:01 +08:00
parent 190b78d14a
commit dd973a46fc
21 changed files with 315 additions and 162 deletions

View File

@@ -32,6 +32,14 @@ export interface ExecJobUpdateStatusRequest {
status: number;
}
/**
* 更新计划任务执行用户
*/
export interface ExecJobUpdateExecUserRequest {
id?: number;
userId?: number;
}
/**
* 计划任务查询请求
*/
@@ -66,14 +74,6 @@ export interface ExecJobQueryResponse extends TableData {
hostList: Array<HostQueryResponse>;
}
/**
* 设置计划任务执行用户
*/
export interface ExecJobSetExecUserRequest {
id: number;
userId: number;
}
/**
* 创建计划任务
*/
@@ -95,6 +95,13 @@ export function updateExecJobStatus(request: ExecJobUpdateStatusRequest) {
return axios.put('/asset/exec-job/update-status', request);
}
/**
* 更新计划任务执行用户
*/
export function updateExecJobExecUser(request: ExecJobUpdateExecUserRequest) {
return axios.put<number>('/asset/exec-job/update-exec-user', request);
}
/**
* 查询计划任务
*/
@@ -116,13 +123,6 @@ export function getExecJobPage(request: ExecJobQueryRequest) {
return axios.post<DataGrid<ExecJobQueryResponse>>('/asset/exec-job/query', request);
}
/**
* 设置计划任务执行用户
*/
export function setExecJobExecUser(request: ExecJobSetExecUserRequest) {
return axios.put<number>('/exec/exec-job/set-exec-user', request);
}
/**
* 删除计划任务
*/

View File

@@ -6,7 +6,7 @@ import type { ITerminalInitOnlyOptions, ITerminalOptions, Terminal } from '@xter
export type ExecType = 'BATCH' | 'JOB';
// 批量执行状态
export const execStatus = {
export const ExecStatus = {
// 等待中
WAITING: 'WAITING',
// 运行中
@@ -18,7 +18,7 @@ export const execStatus = {
};
// 主机执行状态
export const execHostStatus = {
export const ExecHostStatus = {
// 等待中
WAITING: 'WAITING',
// 运行中
@@ -33,6 +33,12 @@ export const execHostStatus = {
INTERRUPTED: 'INTERRUPTED',
};
// 执行模式
export const ExecMode = {
MANUAL: 'MANUAL',
JOB: 'JOB'
};
// 执行状态 字典项
export const execStatusKey = 'execStatus';

View File

@@ -29,7 +29,7 @@
import { onUnmounted, ref, nextTick, onMounted } from 'vue';
import { getExecCommandLogStatus } from '@/api/exec/exec-command-log';
import { getExecJobLogStatus } from '@/api/exec/exec-job-log';
import { dictKeys, execHostStatus, execStatus } from '../const';
import { dictKeys, ExecHostStatus, ExecStatus } from '../const';
import { useDictStore } from '@/store';
import ExecHost from './exec-host.vue';
import LogView from './log-view.vue';
@@ -54,8 +54,8 @@
execLog.value = record;
currentHostExecId.value = record.hosts[0].id;
// 定时查询执行状态
if (record.status === execStatus.WAITING ||
record.status === execStatus.RUNNING) {
if (record.status === ExecStatus.WAITING ||
record.status === ExecStatus.RUNNING) {
// 等待一秒后先查询一下状态
setTimeout(pullExecStatus, 1000);
// 注册状态轮询
@@ -100,8 +100,8 @@
}
}
// 已完成跳过
if (execLog.value.status === execStatus.COMPLETED ||
execLog.value.status === execStatus.FAILED) {
if (execLog.value.status === ExecStatus.COMPLETED ||
execLog.value.status === ExecStatus.FAILED) {
closeClient();
}
};
@@ -114,8 +114,8 @@
}
hosts.forEach(s => {
// 未完成自动设置完成时间为当前时间 用于展示使用时间
if (s.status === execHostStatus.WAITING ||
s.status === execHostStatus.RUNNING) {
if (s.status === ExecHostStatus.WAITING ||
s.status === ExecHostStatus.RUNNING) {
if (!s.startTime) {
s.startTime = Date.now();
}

View File

@@ -27,7 +27,7 @@
color="arcoblue"
title="持续时间">
<template #icon>
<icon-loading v-if="host.status === execHostStatus.WAITING || host.status === execHostStatus.RUNNING" />
<icon-loading v-if="host.status === ExecHostStatus.WAITING || host.status === ExecHostStatus.RUNNING" />
<icon-clock-circle v-else />
</template>
<span class="tag-value">{{ formatDuration(host.startTime, host.finishTime) || '0s' }}</span>
@@ -169,7 +169,7 @@
import type { ExecLogQueryResponse, ExecHostLogQueryResponse } from '@/api/exec/exec-log';
import type { ExecType, ILogAppender } 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';

View File

@@ -1,37 +1,60 @@
import type { AppRouteRecordRaw } from '../types';
import { DEFAULT_LAYOUT } from '../base';
import { DEFAULT_LAYOUT, FULL_LAYOUT } from '../base';
const EXEC: AppRouteRecordRaw = {
name: 'execModule',
path: '/exec-module',
component: DEFAULT_LAYOUT,
children: [
{
name: 'execCommand',
path: '/exec-command',
component: () => import('@/views/exec/exec-command/index.vue'),
},
{
name: 'execCommandLog',
path: '/exec-log',
component: () => import('@/views/exec/exec-command-log/index.vue'),
},
{
name: 'batchUpload',
path: '/batch-upload',
component: () => import('@/views/exec/batch-upload/index.vue'),
},
{
name: 'uploadTask',
path: '/upload-task',
component: () => import('@/views/exec/upload-task/index.vue'),
},
{
name: 'execTemplate',
path: '/exec-template',
component: () => import('@/views/exec/exec-template/index.vue'),
},
],
};
const EXEC: Array<AppRouteRecordRaw> = [
{
name: 'execModule',
path: '/exec-module',
component: DEFAULT_LAYOUT,
children: [
{
name: 'execCommand',
path: '/exec-command',
component: () => import('@/views/exec/exec-command/index.vue'),
},
{
name: 'execCommandLog',
path: '/exec-log',
component: () => import('@/views/exec/exec-command-log/index.vue'),
},
{
name: 'execJob',
path: '/exec-job',
component: () => import('@/views/exec/exec-job/index.vue'),
},
{
name: 'execJobLog',
path: '/exec-job-log',
component: () => import('@/views/exec/exec-job-log/index.vue'),
},
{
name: 'batchUpload',
path: '/batch-upload',
component: () => import('@/views/exec/batch-upload/index.vue'),
},
{
name: 'uploadTask',
path: '/upload-task',
component: () => import('@/views/exec/upload-task/index.vue'),
},
{
name: 'execTemplate',
path: '/exec-template',
component: () => import('@/views/exec/exec-template/index.vue'),
},
],
}, {
name: 'execFullModule',
path: '/exec-full-module',
component: FULL_LAYOUT,
children: [
{
name: 'execJobLogView',
path: '/job-log-view',
component: () => import('@/views/exec/exec-job-log-view/index.vue'),
},
],
}
];
export default EXEC;

View File

@@ -1,36 +0,0 @@
import type { AppRouteRecordRaw } from '../types';
import { DEFAULT_LAYOUT, FULL_LAYOUT } from '../base';
const JOB: AppRouteRecordRaw[] = [
{
name: 'jobModule',
path: '/job-module',
component: DEFAULT_LAYOUT,
children: [
{
name: 'execJob',
path: '/exec-job',
component: () => import('@/views/exec/exec-job/index.vue'),
},
{
name: 'execJobLog',
path: '/exec-job-log',
component: () => import('@/views/exec/exec-job-log/index.vue'),
},
],
},
{
name: 'jobFullModule',
path: '/job-full-module',
component: FULL_LAYOUT,
children: [
{
name: 'execJobLogView',
path: '/job-log-view',
component: () => import('@/views/exec/exec-job-log-view/index.vue'),
},
],
}
];
export default JOB;

View File

@@ -73,7 +73,7 @@
type="text"
size="mini"
status="danger"
:disabled="record.status !== execHostStatus.WAITING && record.status !== execHostStatus.RUNNING">
:disabled="record.status !== ExecHostStatus.WAITING && record.status !== ExecHostStatus.RUNNING">
中断
</a-button>
</a-popconfirm>
@@ -106,7 +106,7 @@
import { Message } from '@arco-design/web-vue';
import useLoading from '@/hooks/loading';
import columns from '../types/host-table.columns';
import { execHostStatusKey, execHostStatus } from '@/components/exec/log/const';
import { execHostStatusKey, ExecHostStatus } from '@/components/exec/log/const';
import { useDictStore } from '@/store';
import { useExpandable } from '@/hooks/table';
import { dateFormat, formatDuration } from '@/utils';
@@ -139,7 +139,7 @@
hostLogId: record.id
});
Message.success('已中断');
record.status = execHostStatus.INTERRUPTED;
record.status = ExecHostStatus.INTERRUPTED;
} catch (e) {
} finally {
setLoading(false);

View File

@@ -177,7 +177,7 @@
type="text"
size="mini"
status="danger"
:disabled="record.status !== execStatus.WAITING && record.status !== execStatus.RUNNING">
:disabled="record.status !== ExecStatus.WAITING && record.status !== ExecStatus.RUNNING">
中断
</a-button>
</a-popconfirm>
@@ -219,7 +219,7 @@
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 { ExecStatus, execStatusKey } from '@/components/exec/log/const';
import { useExpandable, useTablePagination, useRowSelection } from '@/hooks/table';
import { useDictStore } from '@/store';
import { dateFormat, formatDuration } from '@/utils';
@@ -312,7 +312,7 @@
logId: record.id
});
Message.success('已中断');
record.status = execStatus.COMPLETED;
record.status = ExecStatus.COMPLETED;
} catch (e) {
} finally {
setLoading(false);
@@ -345,7 +345,7 @@
// 加载状态
const pullExecStatus = async () => {
const unCompleteIdList = tableRenderData.value
.filter(s => s.status === execStatus.WAITING || s.status === execStatus.RUNNING)
.filter(s => s.status === ExecStatus.WAITING || s.status === ExecStatus.RUNNING)
.map(s => s.id);
if (!unCompleteIdList.length) {
return;

View File

@@ -73,7 +73,7 @@
type="text"
size="mini"
status="danger"
:disabled="record.status !== execHostStatus.WAITING && record.status !== execHostStatus.RUNNING">
:disabled="record.status !== ExecHostStatus.WAITING && record.status !== ExecHostStatus.RUNNING">
中断
</a-button>
</a-popconfirm>
@@ -103,7 +103,7 @@
<script lang="ts" setup>
import type { ExecLogQueryResponse, ExecHostLogQueryResponse } from '@/api/exec/exec-log';
import { deleteExecJobHostLog, interruptHostExecJob } from '@/api/exec/exec-job-log';
import { execHostStatusKey, execHostStatus } from '@/components/exec/log/const';
import { execHostStatusKey, ExecHostStatus } from '@/components/exec/log/const';
import { useDictStore } from '@/store';
import useLoading from '@/hooks/loading';
import columns from '@/views/exec/exec-command-log/types/host-table.columns';
@@ -139,7 +139,7 @@
hostLogId: record.id
});
Message.success('已中断');
record.status = execHostStatus.INTERRUPTED;
record.status = ExecHostStatus.INTERRUPTED;
} catch (e) {
} finally {
setLoading(false);

View File

@@ -118,6 +118,28 @@
{{ record.command }}
</span>
</template>
<!-- 执行用户 -->
<template #username="{ record }">
<div class="flex-center">
<!-- 执行用户 -->
<span :title="record.username">
{{ record.username }}
</span>
<!-- 手动触发 -->
<a-tooltip v-if="ExecMode.MANUAL === record.execMode"
position="top"
content="手动触发"
mini>
<a-tag class="ml8"
style="width: 28px"
color="pinkpurple">
<template #icon>
<icon-user />
</template>
</a-tag>
</a-tooltip>
</div>
</template>
<!-- 执行状态 -->
<template #status="{ record }">
<a-tag :color="getDictValue(execStatusKey, record.status, 'color')">
@@ -160,7 +182,7 @@
type="text"
size="mini"
status="danger"
:disabled="record.status !== execStatus.WAITING && record.status !== execStatus.RUNNING">
:disabled="record.status !== ExecStatus.WAITING && record.status !== ExecStatus.RUNNING">
中断
</a-button>
</a-popconfirm>
@@ -202,7 +224,7 @@
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 { ExecStatus, execStatusKey, ExecMode } from '@/components/exec/log/const';
import { useExpandable, useTablePagination, useRowSelection } from '@/hooks/table';
import { useDictStore } from '@/store';
import { dateFormat, formatDuration } from '@/utils';
@@ -267,7 +289,6 @@
}
};
// 中断执行
const doInterruptExecJob = async (record: ExecLogQueryResponse) => {
try {
@@ -277,7 +298,7 @@
logId: record.id
});
Message.success('已中断');
record.status = execStatus.COMPLETED;
record.status = ExecStatus.COMPLETED;
} catch (e) {
} finally {
setLoading(false);
@@ -310,7 +331,7 @@
// 加载状态
const pullJobStatus = async () => {
const unCompleteIdList = tableRenderData.value
.filter(s => s.status === execStatus.WAITING || s.status === execStatus.RUNNING)
.filter(s => s.status === ExecStatus.WAITING || s.status === ExecStatus.RUNNING)
.map(s => s.id);
if (!unCompleteIdList.length) {
return;

View File

@@ -22,6 +22,13 @@ const columns = [
align: 'left',
minWidth: 238,
ellipsis: true,
}, {
title: '执行用户',
dataIndex: 'username',
slotName: 'username',
align: 'left',
width: 128,
ellipsis: true,
}, {
title: '执行状态',
dataIndex: 'status',

View File

@@ -146,13 +146,6 @@
@click="emits('openDetail', record.id)">
详情
</a-button>
<!-- 修改 -->
<a-button v-permission="['asset:exec-job:update']"
type="text"
size="mini"
@click="emits('openUpdate', record.id)">
修改
</a-button>
<!-- 手动触发 -->
<a-popconfirm content="确认要手动触发吗?"
position="left"
@@ -176,6 +169,24 @@
删除
</a-button>
</a-popconfirm>
<!-- 更多 -->
<a-dropdown trigger="hover" :popup-max-height="false">
<a-button type="text" size="mini">
更多
</a-button>
<template #content>
<!-- 修改任务 -->
<a-doption v-permission="['asset:exec-job:update']"
@click="emits('openUpdate', record.id)">
<span class="more-doption normal">修改任务</span>
</a-doption>
<!-- 修改执行用户 -->
<a-doption v-permission="['asset:exec-job:update-exec-user']"
@click="emits('updateExecUser', record)">
<span class="more-doption normal">修改执行用户</span>
</a-doption>
</template>
</a-dropdown>
</div>
</template>
</a-table>
@@ -202,7 +213,7 @@
import { copy } from '@/hooks/copy';
import { dateFormat } from '@/utils';
const emits = defineEmits(['openAdd', 'openUpdate', 'openDetail', 'testCron']);
const emits = defineEmits(['openAdd', 'openUpdate', 'openDetail', 'updateExecUser', 'testCron']);
const pagination = useTablePagination();
const rowSelection = useRowSelection();

View File

@@ -0,0 +1,101 @@
<template>
<a-modal v-model:visible="visible"
modal-class="modal-form-large"
title-align="start"
title="修改执行用户"
:align-center="false"
:draggable="true"
:mask-closable="false"
:unmount-on-close="true"
: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"
label-align="right"
:auto-label-width="true">
<!-- 执行用户 -->
<a-form-item field="userId" label="执行用户">
<user-selector v-model="formModel.userId"
placeholder="请选择执行用户"
allow-clear />
</a-form-item>
</a-form>
</a-spin>
</a-modal>
</template>
<script lang="ts">
export default {
name: 'execUserUpdateModal'
};
</script>
<script lang="ts" setup>
import type { ExecJobUpdateExecUserRequest, ExecJobQueryResponse } from '@/api/exec/exec-job';
import { ref } from 'vue';
import useLoading from '@/hooks/loading';
import useVisible from '@/hooks/visible';
import { Message } from '@arco-design/web-vue';
import { useCacheStore, useDictStore } from '@/store';
import { updateExecJobExecUser } from '@/api/exec/exec-job';
import UserSelector from '@/components/user/user/selector/index.vue';
const { toOptions } = useDictStore();
const { loadUsers } = useCacheStore();
const { visible, setVisible } = useVisible();
const { loading, setLoading } = useLoading();
const formModel = ref<ExecJobUpdateExecUserRequest>({});
const job = ref<ExecJobQueryResponse>();
// 打开
const open = (record: ExecJobQueryResponse) => {
formModel.value = { id: record.id, userId: record.execUserId };
job.value = record;
setVisible(true);
};
defineExpose({ open });
// 确定
const handlerOk = async () => {
const execUserId = formModel.value.userId;
if (!execUserId) {
Message.error('请选择执行用户');
return false;
}
setLoading(true);
try {
// 更新
await updateExecJobExecUser(formModel.value);
if (job.value) {
job.value.execUserId = execUserId;
job.value.execUsername = (await loadUsers()).find(s => s.id === execUserId)?.username || '';
}
Message.success('修改成功');
// 清空
handlerClear();
} catch (e) {
return false;
} finally {
setLoading(false);
}
};
// 关闭
const handleClose = () => {
handlerClear();
};
// 清空
const handlerClear = () => {
setLoading(false);
};
</script>
<style lang="less" scoped>
</style>

View File

@@ -5,6 +5,7 @@
@open-add="() => drawer.openAdd()"
@open-update="(e) => drawer.openUpdate(e)"
@open-detail="(e) => detail.open(e)"
@update-exec-user="(e) => execUserModal.open(e)"
@test-cron="openNextCron" />
<!-- 添加修改模态框 -->
<exec-job-form-drawer ref="drawer"
@@ -16,6 +17,8 @@
@gen-cron="(e) => genModal.open(e)" />
<!-- 任务详情模态框 -->
<exec-job-detail-drawer ref="detail" />
<!-- 修改执行用户模态框 -->
<exec-user-update-modal ref="execUserModal" />
<!-- cron 执行时间模态框 -->
<cron-next-modal ref="nextCron" />
<!-- cron 生成模态框 -->
@@ -43,6 +46,7 @@
import { CronNextTimes, dictKeys } from './types/const';
import ExecJobTable from './components/exec-job-table.vue';
import ExecJobFormDrawer from './components/exec-job-form-drawer.vue';
import ExecUserUpdateModal from './components/exec-user-update-modal.vue';
import ExecJobDetailDrawer from './components/exec-job-detail-drawer.vue';
import AuthorizedHostModal from '@/components/asset/host/authorized-host-modal/index.vue';
import ExecTemplateModal from '@/components/exec/template/modal/index.vue';
@@ -53,6 +57,7 @@
const table = ref();
const drawer = ref();
const detail = ref();
const execUserModal = ref();
const nextCron = ref();
const genModal = ref();
const templateModal = ref();

View File

@@ -32,6 +32,14 @@ const columns = [
minWidth: 238,
ellipsis: true,
tooltip: true,
}, {
title: '执行用户',
dataIndex: 'execUsername',
slotName: 'execUsername',
align: 'left',
ellipsis: true,
tooltip: true,
width: 124,
}, {
title: '任务状态',
dataIndex: 'status',