🔨 执行命令.
This commit is contained in:
@@ -44,6 +44,10 @@ public class ExecHostLogDO extends BaseDO {
|
|||||||
@TableField("host_name")
|
@TableField("host_name")
|
||||||
private String hostName;
|
private String hostName;
|
||||||
|
|
||||||
|
@Schema(description = "主机地址")
|
||||||
|
@TableField("host_address")
|
||||||
|
private String hostAddress;
|
||||||
|
|
||||||
@Schema(description = "执行状态")
|
@Schema(description = "执行状态")
|
||||||
@TableField("status")
|
@TableField("status")
|
||||||
private String status;
|
private String status;
|
||||||
|
|||||||
@@ -28,4 +28,13 @@ public class ExecCommandHostVO implements Serializable {
|
|||||||
@Schema(description = "hostId")
|
@Schema(description = "hostId")
|
||||||
private Long hostId;
|
private Long hostId;
|
||||||
|
|
||||||
|
@Schema(description = "主机名称")
|
||||||
|
private String hostName;
|
||||||
|
|
||||||
|
@Schema(description = "主机地址")
|
||||||
|
private String hostAddress;
|
||||||
|
|
||||||
|
@Schema(description = "执行状态")
|
||||||
|
private String status;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,6 +37,9 @@ public class ExecHostLogVO implements Serializable {
|
|||||||
@Schema(description = "主机名称")
|
@Schema(description = "主机名称")
|
||||||
private String hostName;
|
private String hostName;
|
||||||
|
|
||||||
|
@Schema(description = "主机地址")
|
||||||
|
private String hostAddress;
|
||||||
|
|
||||||
@Schema(description = "执行状态")
|
@Schema(description = "执行状态")
|
||||||
private String status;
|
private String status;
|
||||||
|
|
||||||
|
|||||||
@@ -65,6 +65,8 @@ public class ExecServiceImpl implements ExecService {
|
|||||||
private static final ReplacementFormatter FORMATTER = ReplacementFormatters.create("@{{ ", " }}")
|
private static final ReplacementFormatter FORMATTER = ReplacementFormatters.create("@{{ ", " }}")
|
||||||
.noMatchStrategy(NoMatchStrategy.KEEP);
|
.noMatchStrategy(NoMatchStrategy.KEEP);
|
||||||
|
|
||||||
|
private static final int DESC_OMIT = 60;
|
||||||
|
|
||||||
@Resource
|
@Resource
|
||||||
private FileClient logsFileClient;
|
private FileClient logsFileClient;
|
||||||
|
|
||||||
@@ -102,7 +104,13 @@ public class ExecServiceImpl implements ExecService {
|
|||||||
.userId(userId)
|
.userId(userId)
|
||||||
.username(user.getUsername())
|
.username(user.getUsername())
|
||||||
.source(ExecSourceEnum.BATCH.name())
|
.source(ExecSourceEnum.BATCH.name())
|
||||||
.description(Strings.ifBlank(request.getDescription(), Strings.retain(command, 60) + Const.OMIT))
|
.description(Strings.ifBlank(request.getDescription(), () -> {
|
||||||
|
if (command.length() < DESC_OMIT + 3) {
|
||||||
|
return command;
|
||||||
|
} else {
|
||||||
|
return command.substring(0, DESC_OMIT) + Const.OMIT;
|
||||||
|
}
|
||||||
|
}))
|
||||||
.command(command)
|
.command(command)
|
||||||
.parameterSchema(request.getParameterSchema())
|
.parameterSchema(request.getParameterSchema())
|
||||||
.timeout(request.getTimeout())
|
.timeout(request.getTimeout())
|
||||||
@@ -120,6 +128,7 @@ public class ExecServiceImpl implements ExecService {
|
|||||||
.logId(execId)
|
.logId(execId)
|
||||||
.hostId(s.getId())
|
.hostId(s.getId())
|
||||||
.hostName(s.getName())
|
.hostName(s.getName())
|
||||||
|
.hostAddress(s.getAddress())
|
||||||
.status(ExecHostStatusEnum.WAITING.name())
|
.status(ExecHostStatusEnum.WAITING.name())
|
||||||
.command(FORMATTER.format(command, parameter))
|
.command(FORMATTER.format(command, parameter))
|
||||||
.parameter(parameter)
|
.parameter(parameter)
|
||||||
@@ -136,6 +145,9 @@ public class ExecServiceImpl implements ExecService {
|
|||||||
.map(s -> ExecCommandHostVO.builder()
|
.map(s -> ExecCommandHostVO.builder()
|
||||||
.id(s.getId())
|
.id(s.getId())
|
||||||
.hostId(s.getHostId())
|
.hostId(s.getHostId())
|
||||||
|
.hostName(s.getHostName())
|
||||||
|
.hostAddress(s.getHostAddress())
|
||||||
|
.status(s.getStatus())
|
||||||
.build())
|
.build())
|
||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
return ExecCommandVO.builder()
|
return ExecCommandVO.builder()
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
<result column="log_id" property="logId"/>
|
<result column="log_id" property="logId"/>
|
||||||
<result column="host_id" property="hostId"/>
|
<result column="host_id" property="hostId"/>
|
||||||
<result column="host_name" property="hostName"/>
|
<result column="host_name" property="hostName"/>
|
||||||
|
<result column="host_address" property="hostAddress"/>
|
||||||
<result column="status" property="status"/>
|
<result column="status" property="status"/>
|
||||||
<result column="command" property="command"/>
|
<result column="command" property="command"/>
|
||||||
<result column="parameter" property="parameter"/>
|
<result column="parameter" property="parameter"/>
|
||||||
@@ -25,7 +26,7 @@
|
|||||||
|
|
||||||
<!-- 通用查询结果列 -->
|
<!-- 通用查询结果列 -->
|
||||||
<sql id="Base_Column_List">
|
<sql id="Base_Column_List">
|
||||||
id, log_id, host_id, host_name, status, command, parameter, exit_status, log_path, error_message, start_time, finish_time, create_time, update_time, creator, updater, deleted
|
id, log_id, host_id, host_name, host_address, status, command, parameter, exit_status, log_path, error_message, start_time, finish_time, create_time, update_time, creator, updater, deleted
|
||||||
</sql>
|
</sql>
|
||||||
|
|
||||||
</mapper>
|
</mapper>
|
||||||
|
|||||||
@@ -47,6 +47,7 @@ export interface ExecHostLogQueryResponse extends TableData {
|
|||||||
logId: number;
|
logId: number;
|
||||||
hostId: number;
|
hostId: number;
|
||||||
hostName: string;
|
hostName: string;
|
||||||
|
hostAddress: string;
|
||||||
status: string;
|
status: string;
|
||||||
command: string;
|
command: string;
|
||||||
parameter: string;
|
parameter: string;
|
||||||
|
|||||||
@@ -25,10 +25,22 @@ export interface ExecInterruptRequest {
|
|||||||
*/
|
*/
|
||||||
export interface ExecCommandResponse {
|
export interface ExecCommandResponse {
|
||||||
id: number;
|
id: number;
|
||||||
hosts: {
|
hosts: Array<ExecCommandHostResponse>;
|
||||||
id: number;
|
}
|
||||||
hostId: number;
|
|
||||||
};
|
/**
|
||||||
|
* 执行命令主机响应
|
||||||
|
*/
|
||||||
|
export interface ExecCommandHostResponse {
|
||||||
|
id: number;
|
||||||
|
hostId: number;
|
||||||
|
hostName: string;
|
||||||
|
hostAddress: string;
|
||||||
|
status: string;
|
||||||
|
exitStatus: number;
|
||||||
|
errorMessage: string;
|
||||||
|
startTime: number;
|
||||||
|
finishTime: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -18,7 +18,7 @@
|
|||||||
</template>
|
</template>
|
||||||
<!-- 名称 -->
|
<!-- 名称 -->
|
||||||
<template #name="{ record }">
|
<template #name="{ record }">
|
||||||
{{ record.alias || `${record.name} (${record.code})` }}
|
{{ record.name }}
|
||||||
</template>
|
</template>
|
||||||
<!-- 编码 -->
|
<!-- 编码 -->
|
||||||
<template #code="{ record }">
|
<template #code="{ record }">
|
||||||
|
|||||||
@@ -145,6 +145,8 @@
|
|||||||
// 加载主机列表
|
// 加载主机列表
|
||||||
const { data } = await getCurrentAuthorizedHost('ssh');
|
const { data } = await getCurrentAuthorizedHost('ssh');
|
||||||
hosts.value = data;
|
hosts.value = data;
|
||||||
|
// 禁用别名
|
||||||
|
data.hostList.forEach(s => s.alias = undefined as unknown as string);
|
||||||
// 设置主机搜索选项
|
// 设置主机搜索选项
|
||||||
filterOptions.value = getAuthorizedHostOptions(data.hostList);
|
filterOptions.value = getAuthorizedHostOptions(data.hostList);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -171,7 +173,6 @@
|
|||||||
: list.filter(item => {
|
: list.filter(item => {
|
||||||
return (item.name as string)?.toLowerCase().indexOf(filterVal) > -1
|
return (item.name as string)?.toLowerCase().indexOf(filterVal) > -1
|
||||||
|| (item.code as string)?.toLowerCase().indexOf(filterVal) > -1
|
|| (item.code as string)?.toLowerCase().indexOf(filterVal) > -1
|
||||||
|| (item.alias as string)?.toLowerCase().indexOf(filterVal) > -1
|
|
||||||
|| (item.address as string)?.toLowerCase().indexOf(filterVal) > -1;
|
|| (item.address as string)?.toLowerCase().indexOf(filterVal) > -1;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -70,11 +70,9 @@
|
|||||||
</template>
|
</template>
|
||||||
<!-- 用户名 -->
|
<!-- 用户名 -->
|
||||||
<template #username="{ record }">
|
<template #username="{ record }">
|
||||||
<a-tooltip content="点击复制">
|
<span class="span-blue text-copy" @click="copy(record.username)">
|
||||||
<span class="pointer span-blue" @click="copy(record.username)">
|
{{ record.username }}
|
||||||
<icon-copy /> {{ record.username }}
|
</span>
|
||||||
</span>
|
|
||||||
</a-tooltip>
|
|
||||||
</template>
|
</template>
|
||||||
<!-- 秘钥名称 -->
|
<!-- 秘钥名称 -->
|
||||||
<template #keyId="{ record }">
|
<template #keyId="{ record }">
|
||||||
|
|||||||
@@ -82,11 +82,9 @@
|
|||||||
:bordered="false">
|
:bordered="false">
|
||||||
<!-- 用户名 -->
|
<!-- 用户名 -->
|
||||||
<template #username="{ record }">
|
<template #username="{ record }">
|
||||||
<a-tooltip content="点击复制">
|
<span class="span-blue text-copy" @click="copy(record.username)">
|
||||||
<span class="pointer span-blue" @click="copy(record.username)">
|
{{ record.username }}
|
||||||
<icon-copy /> {{ record.username }}
|
</span>
|
||||||
</span>
|
|
||||||
</a-tooltip>
|
|
||||||
</template>
|
</template>
|
||||||
<!-- 秘钥名称 -->
|
<!-- 秘钥名称 -->
|
||||||
<template #keyId="{ record }">
|
<template #keyId="{ record }">
|
||||||
|
|||||||
@@ -21,15 +21,6 @@ const columns = [
|
|||||||
title: '主机秘钥',
|
title: '主机秘钥',
|
||||||
dataIndex: 'keyId',
|
dataIndex: 'keyId',
|
||||||
slotName: 'keyId',
|
slotName: 'keyId',
|
||||||
}, {
|
|
||||||
title: '创建时间',
|
|
||||||
dataIndex: 'createTime',
|
|
||||||
slotName: 'createTime',
|
|
||||||
align: 'center',
|
|
||||||
width: 180,
|
|
||||||
render: ({ record }) => {
|
|
||||||
return dateFormat(new Date(record.createTime));
|
|
||||||
},
|
|
||||||
}, {
|
}, {
|
||||||
title: '修改时间',
|
title: '修改时间',
|
||||||
dataIndex: 'updateTime',
|
dataIndex: 'updateTime',
|
||||||
|
|||||||
@@ -88,15 +88,13 @@
|
|||||||
</template>
|
</template>
|
||||||
<!-- 编码 -->
|
<!-- 编码 -->
|
||||||
<template #code="{ record }">
|
<template #code="{ record }">
|
||||||
<a-tag>{{ record.code }}</a-tag>
|
{{ record.code }}
|
||||||
</template>
|
</template>
|
||||||
<!-- 地址 -->
|
<!-- 地址 -->
|
||||||
<template #address="{ record }">
|
<template #address="{ record }">
|
||||||
<a-tooltip content="点击复制">
|
<span class="span-blue text-copy" @click="copy(record.address)">
|
||||||
<span class="pointer span-blue" @click="copy(record.address)">
|
{{ record.address }}
|
||||||
<icon-copy /> {{ record.address }}
|
</span>
|
||||||
</span>
|
|
||||||
</a-tooltip>
|
|
||||||
</template>
|
</template>
|
||||||
<!-- 标签 -->
|
<!-- 标签 -->
|
||||||
<template #tags="{ record }">
|
<template #tags="{ record }">
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<!-- 表头 -->
|
<!-- 表头 -->
|
||||||
<div class="exec-header">
|
<div class="panel-header">
|
||||||
<h3>执行命令</h3>
|
<h3>执行命令</h3>
|
||||||
<span class="span-blue usn pointer" @click="openTemplate">从模板中选择</span>
|
<span class="span-blue usn pointer" @click="openTemplate">从模板中选择</span>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="exec-form-container">
|
<div class="container">
|
||||||
<!-- 表头 -->
|
<!-- 表头 -->
|
||||||
<div class="exec-header">
|
<div class="panel-header">
|
||||||
<h3>执行参数</h3>
|
<h3>执行参数</h3>
|
||||||
<!-- 操作 -->
|
<!-- 操作 -->
|
||||||
<a-button-group size="small">
|
<a-button-group size="small">
|
||||||
|
|||||||
@@ -1,8 +1,11 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<!-- 表头 -->
|
<!-- 表头 -->
|
||||||
<div class="exec-header">
|
<div class="panel-header">
|
||||||
<h3>执行历史</h3>
|
<h3>执行历史</h3>
|
||||||
|
<span class="history-help">
|
||||||
|
展示最近 {{ historyCount }} 条执行记录
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="!historyLogs.length" class="flex-center mt16">
|
<div v-if="!historyLogs.length" class="flex-center mt16">
|
||||||
<a-empty description="无执行记录" />
|
<a-empty description="无执行记录" />
|
||||||
@@ -34,6 +37,7 @@
|
|||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import type { ExecLogQueryResponse } from '@/api/exec/exec-log';
|
import type { ExecLogQueryResponse } from '@/api/exec/exec-log';
|
||||||
|
import type { ExecCommandRequest } from '@/api/exec/exec';
|
||||||
import { onMounted, ref } from 'vue';
|
import { onMounted, ref } from 'vue';
|
||||||
import { getExecLogHistory } from '@/api/exec/exec-log';
|
import { getExecLogHistory } from '@/api/exec/exec-log';
|
||||||
import { historyCount } from '../types/const';
|
import { historyCount } from '../types/const';
|
||||||
@@ -42,6 +46,34 @@
|
|||||||
|
|
||||||
const historyLogs = ref<Array<ExecLogQueryResponse>>([]);
|
const historyLogs = ref<Array<ExecLogQueryResponse>>([]);
|
||||||
|
|
||||||
|
// 添加
|
||||||
|
const add = (record: ExecCommandRequest) => {
|
||||||
|
const index = historyLogs.value.findIndex(s => s.description === record.description);
|
||||||
|
if (index === -1) {
|
||||||
|
// 不存在
|
||||||
|
historyLogs.value.unshift({
|
||||||
|
description: record.description,
|
||||||
|
command: record.command,
|
||||||
|
parameterSchema: record.parameterSchema,
|
||||||
|
timeout: record.timeout,
|
||||||
|
hostIdList: record.hostIdList
|
||||||
|
} as ExecLogQueryResponse);
|
||||||
|
} else {
|
||||||
|
// 存在
|
||||||
|
const his = historyLogs.value[index];
|
||||||
|
historyLogs.value.splice(index, 1);
|
||||||
|
historyLogs.value.unshift({
|
||||||
|
...his,
|
||||||
|
command: record.command,
|
||||||
|
parameterSchema: record.parameterSchema,
|
||||||
|
timeout: record.timeout,
|
||||||
|
hostIdList: record.hostIdList
|
||||||
|
} as ExecLogQueryResponse);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
defineExpose({ add });
|
||||||
|
|
||||||
// 加载执行记录
|
// 加载执行记录
|
||||||
const fetchExecHistory = async () => {
|
const fetchExecHistory = async () => {
|
||||||
const { data } = await getExecLogHistory(historyCount);
|
const { data } = await getExecLogHistory(historyCount);
|
||||||
@@ -57,7 +89,7 @@
|
|||||||
.exec-history-rows {
|
.exec-history-rows {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
width: calc(100% - 32px);
|
width: calc(100% - 32px);
|
||||||
height: calc(100% - 60px);
|
height: calc(100% - 64px);
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
|
|
||||||
&::-webkit-scrollbar-track {
|
&::-webkit-scrollbar-track {
|
||||||
@@ -67,6 +99,7 @@
|
|||||||
|
|
||||||
.exec-history {
|
.exec-history {
|
||||||
padding: 8px;
|
padding: 8px;
|
||||||
|
border-radius: 2px;
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@@ -84,10 +117,11 @@
|
|||||||
width: 24px;
|
width: 24px;
|
||||||
height: 24px;
|
height: 24px;
|
||||||
border-radius: 2px;
|
border-radius: 2px;
|
||||||
|
margin-right: 12px;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
color: var(--color-bg-2);
|
color: #FFF;
|
||||||
background: rgb(var(--arcoblue-6));
|
background: rgb(var(--arcoblue-6));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -101,4 +135,8 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.history-help {
|
||||||
|
color: var(--color-text-3);
|
||||||
|
}
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -77,6 +77,7 @@
|
|||||||
</exec-panel-editor>
|
</exec-panel-editor>
|
||||||
<!-- 执行历史 -->
|
<!-- 执行历史 -->
|
||||||
<exec-panel-history class="exec-history-container"
|
<exec-panel-history class="exec-history-container"
|
||||||
|
ref="historyRef"
|
||||||
@selected="setWithExecLog" />
|
@selected="setWithExecLog" />
|
||||||
<!-- 主机模态框 -->
|
<!-- 主机模态框 -->
|
||||||
<authorized-host-modal ref="hostModal"
|
<authorized-host-modal ref="hostModal"
|
||||||
@@ -106,16 +107,18 @@
|
|||||||
import ExecPanelHistory from './exec-panel-history.vue';
|
import ExecPanelHistory from './exec-panel-history.vue';
|
||||||
import ExecPanelEditor from './exec-panel-editor.vue';
|
import ExecPanelEditor from './exec-panel-editor.vue';
|
||||||
|
|
||||||
|
const emits = defineEmits(['submit']);
|
||||||
|
|
||||||
const defaultForm = (): ExecCommandRequest => {
|
const defaultForm = (): ExecCommandRequest => {
|
||||||
return {
|
return {
|
||||||
timeout: 0,
|
command: '',
|
||||||
command: ''
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const { loading, setLoading } = useLoading();
|
const { loading, setLoading } = useLoading();
|
||||||
|
|
||||||
const hostModal = ref<any>();
|
const hostModal = ref<any>();
|
||||||
|
const historyRef = ref<any>();
|
||||||
const formRef = ref<any>();
|
const formRef = ref<any>();
|
||||||
const parameterFormRef = ref<any>();
|
const parameterFormRef = ref<any>();
|
||||||
const formModel = ref<ExecCommandRequest>({ ...defaultForm() });
|
const formModel = ref<ExecCommandRequest>({ ...defaultForm() });
|
||||||
@@ -186,11 +189,15 @@
|
|||||||
ps.value = parameterFormModel.value[ps.name as string];
|
ps.value = parameterFormModel.value[ps.name as string];
|
||||||
}
|
}
|
||||||
// 执行命令
|
// 执行命令
|
||||||
const { data } = await batchExecCommand({
|
const request = {
|
||||||
...formModel.value,
|
...formModel.value,
|
||||||
parameterSchema: JSON.stringify(parameterSchema.value),
|
parameterSchema: JSON.stringify(parameterSchema.value),
|
||||||
});
|
};
|
||||||
// TODO log history
|
const { data } = await batchExecCommand(request);
|
||||||
|
// 设置历史命令
|
||||||
|
historyRef.value.add(request);
|
||||||
|
Message.success('已开始执行');
|
||||||
|
emits('submit', data);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return false;
|
return false;
|
||||||
} finally {
|
} finally {
|
||||||
@@ -224,6 +231,7 @@
|
|||||||
|
|
||||||
.exec-command-container {
|
.exec-command-container {
|
||||||
width: calc(100% - @command-gap);
|
width: calc(100% - @command-gap);
|
||||||
|
margin: 0 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.exec-history-container {
|
.exec-history-container {
|
||||||
@@ -239,30 +247,13 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
:deep(.exec-header) {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: flex-start;
|
|
||||||
height: 28px;
|
|
||||||
margin-bottom: 4px;
|
|
||||||
|
|
||||||
h3, > span {
|
|
||||||
margin: 0;
|
|
||||||
overflow: hidden;
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
h3 {
|
|
||||||
color: var(--color-text-1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.exec-form-container {
|
.exec-form-container {
|
||||||
.selected-host {
|
.selected-host {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
|
color: var(--color-text-2);
|
||||||
|
|
||||||
&-count {
|
&-count {
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
|
|||||||
@@ -0,0 +1,118 @@
|
|||||||
|
<template>
|
||||||
|
<!-- 执行主机 -->
|
||||||
|
<div class="container">
|
||||||
|
<!-- 表头 -->
|
||||||
|
<div class="panel-header">
|
||||||
|
<h3>执行主机</h3>
|
||||||
|
<!-- 操作 -->
|
||||||
|
<a-button size="small" @click="emits('back')">
|
||||||
|
返回
|
||||||
|
</a-button>
|
||||||
|
</div>
|
||||||
|
<!-- 主机列表 -->
|
||||||
|
<div class="exec-host-container">
|
||||||
|
<div v-for="item in hosts"
|
||||||
|
:key="item.id"
|
||||||
|
class="exec-host-item"
|
||||||
|
:class="[ current === item.hostId ? 'exec-host-item-selected' : '' ]"
|
||||||
|
@click="emits('selected', item.hostId)">
|
||||||
|
<!-- 主机名称 -->
|
||||||
|
<div class="exec-host-item-name">
|
||||||
|
<span class="host-name">{{ item.hostName }}</span>
|
||||||
|
<span class="host-address">{{ item.hostAddress }}</span>
|
||||||
|
</div>
|
||||||
|
<!-- 状态 -->
|
||||||
|
<div class="exec-host-item-status">
|
||||||
|
<a-tag :color="getDictValue(execHostStatusKey, item.status, 'execColor')">
|
||||||
|
{{ getDictValue(execHostStatusKey, item.status) }}
|
||||||
|
</a-tag>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
export default {
|
||||||
|
name: 'logPanelHost'
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import type { ExecCommandHostResponse } from '@/api/exec/exec';
|
||||||
|
import { useDictStore } from '@/store';
|
||||||
|
import { execHostStatusKey } from '@/views/exec/exec-log/types/const';
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
current: number;
|
||||||
|
hosts: Array<ExecCommandHostResponse>;
|
||||||
|
}>();
|
||||||
|
const emits = defineEmits(['back', 'selected']);
|
||||||
|
|
||||||
|
const { getDictValue } = useDictStore();
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="less" scoped>
|
||||||
|
.exec-host-container {
|
||||||
|
margin-top: 4px;
|
||||||
|
position: absolute;
|
||||||
|
width: calc(100% - 32px);
|
||||||
|
height: calc(100% - 68px);
|
||||||
|
overflow: auto;
|
||||||
|
|
||||||
|
&::-webkit-scrollbar-track {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.exec-host-item-selected {
|
||||||
|
background: var(--color-fill-3) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.exec-host-item {
|
||||||
|
height: 46px;
|
||||||
|
padding: 8px;
|
||||||
|
border-radius: 2px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
background: var(--color-fill-2);
|
||||||
|
transition: all .2s;
|
||||||
|
user-select: none;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: var(--color-fill-3);
|
||||||
|
}
|
||||||
|
|
||||||
|
&-name {
|
||||||
|
width: calc(100% - 72px);
|
||||||
|
margin-right: 12px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
color: var(--color-text-2);
|
||||||
|
|
||||||
|
.host-name, .host-address {
|
||||||
|
display: block;
|
||||||
|
overflow: hidden;
|
||||||
|
white-space: nowrap;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
|
.host-address {
|
||||||
|
margin-top: 2px;
|
||||||
|
font-size: 10px;
|
||||||
|
color: var(--color-text-3);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&-status {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,128 @@
|
|||||||
|
<template>
|
||||||
|
<div class="log-panel-container">
|
||||||
|
<!-- 执行主机 -->
|
||||||
|
<log-panel-host class="host-container"
|
||||||
|
:current="currentHostId"
|
||||||
|
:hosts="command.hosts"
|
||||||
|
@selected="selectedHost"
|
||||||
|
@back="back" />
|
||||||
|
<!-- 日志容器 -->
|
||||||
|
<div class="log-container">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
export default {
|
||||||
|
name: 'logPanel'
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import type { ExecCommandResponse } from '@/api/exec/exec';
|
||||||
|
import { ref } from 'vue';
|
||||||
|
import 'xterm/css/xterm.css';
|
||||||
|
import LogPanelHost from './log-panel-host.vue';
|
||||||
|
|
||||||
|
const emits = defineEmits(['back']);
|
||||||
|
|
||||||
|
const currentHostId = ref(1);
|
||||||
|
const command = ref<ExecCommandResponse>({
|
||||||
|
id: 50,
|
||||||
|
hosts: [{
|
||||||
|
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: 'FAILED'
|
||||||
|
}, {
|
||||||
|
id: 80,
|
||||||
|
hostId: 5,
|
||||||
|
hostName: 'main-55',
|
||||||
|
hostAddress: '192.412.53.2',
|
||||||
|
status: 'INTERRUPTED'
|
||||||
|
}]
|
||||||
|
} as ExecCommandResponse);
|
||||||
|
|
||||||
|
// 打开
|
||||||
|
const open = (record: ExecCommandResponse) => {
|
||||||
|
command.value = record;
|
||||||
|
currentHostId.value = record.hosts[0].hostId;
|
||||||
|
// 打开日志
|
||||||
|
openLog();
|
||||||
|
};
|
||||||
|
|
||||||
|
defineExpose({ open });
|
||||||
|
|
||||||
|
// 打开日志
|
||||||
|
const openLog = () => {
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
// 选中主机
|
||||||
|
const selectedHost = (hostId: number) => {
|
||||||
|
currentHostId.value = hostId;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 返回
|
||||||
|
const back = () => {
|
||||||
|
emits('back');
|
||||||
|
// 清理
|
||||||
|
clear();
|
||||||
|
};
|
||||||
|
|
||||||
|
// 清理
|
||||||
|
const clear = () => {
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
// TODO pull status
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="less" scoped>
|
||||||
|
@host-width: 420px;
|
||||||
|
@host-real-width: @host-width + 16px;
|
||||||
|
|
||||||
|
.log-panel-container {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.host-container, .log-container {
|
||||||
|
height: 100%;
|
||||||
|
padding: 16px;
|
||||||
|
border-radius: 4px;
|
||||||
|
position: relative;
|
||||||
|
background: var(--color-bg-2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.host-container {
|
||||||
|
width: @host-width;
|
||||||
|
margin-right: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.log-container {
|
||||||
|
width: calc(100% - @host-real-width);
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
||||||
@@ -1,7 +1,12 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="layout-container full">
|
<div class="layout-container full">
|
||||||
<!-- 执行面板 -->
|
<!-- 执行面板 -->
|
||||||
<exec-panel />
|
<exec-panel v-show="!logVisible"
|
||||||
|
@submit="openLog" />
|
||||||
|
<!-- 执行日志 -->
|
||||||
|
<log-panel v-if="logVisible"
|
||||||
|
ref="log"
|
||||||
|
@back="setLogVisible(false)" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -12,10 +17,51 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
|
import type { ExecCommandResponse } from '@/api/exec/exec';
|
||||||
|
import { onMounted, ref } from 'vue';
|
||||||
|
import useVisible from '@/hooks/visible';
|
||||||
|
import { useDictStore } from '@/store';
|
||||||
|
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';
|
||||||
|
|
||||||
|
const { visible: logVisible, setVisible: setLogVisible } = useVisible(true);
|
||||||
|
const { loadKeys } = useDictStore();
|
||||||
|
|
||||||
|
const log = ref();
|
||||||
|
|
||||||
|
// 打开日志
|
||||||
|
const openLog = (record: ExecCommandResponse) => {
|
||||||
|
setLogVisible(true);
|
||||||
|
log.value.open(record);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 加载字典值
|
||||||
|
onMounted(async () => {
|
||||||
|
await loadKeys(dictKeys);
|
||||||
|
});
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="less" scoped>
|
<style lang="less" scoped>
|
||||||
|
|
||||||
|
:deep(.panel-header) {
|
||||||
|
width: 100%;
|
||||||
|
height: 28px;
|
||||||
|
margin-bottom: 4px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: flex-start;
|
||||||
|
|
||||||
|
h3, > span {
|
||||||
|
margin: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
h3 {
|
||||||
|
color: var(--color-text-1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -11,9 +11,15 @@
|
|||||||
:bordered="false">
|
:bordered="false">
|
||||||
<!-- 执行主机 -->
|
<!-- 执行主机 -->
|
||||||
<template #hostName="{ record }">
|
<template #hostName="{ record }">
|
||||||
<span class="span-blue">
|
<span class="table-cell-value span-blue">
|
||||||
{{ record.hostName }}
|
{{ record.hostName }}
|
||||||
</span>
|
</span>
|
||||||
|
<br>
|
||||||
|
<span class="table-cell-sub-value usn text-copy"
|
||||||
|
style="font-size: 12px;"
|
||||||
|
@click="copy(record.hostAddress)">
|
||||||
|
{{ record.hostAddress }}
|
||||||
|
</span>
|
||||||
</template>
|
</template>
|
||||||
<!-- 错误信息 -->
|
<!-- 错误信息 -->
|
||||||
<template #errorMessage="{ record }">
|
<template #errorMessage="{ record }">
|
||||||
@@ -104,6 +110,7 @@
|
|||||||
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 { interruptHostExec } from '@/api/exec/exec';
|
||||||
|
import { copy } from '@/hooks/copy';
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
row: ExecLogQueryResponse;
|
row: ExecLogQueryResponse;
|
||||||
|
|||||||
@@ -6,6 +6,13 @@
|
|||||||
@submit="fetchTableData"
|
@submit="fetchTableData"
|
||||||
@reset="fetchTableData"
|
@reset="fetchTableData"
|
||||||
@keyup.enter="() => fetchTableData()">
|
@keyup.enter="() => fetchTableData()">
|
||||||
|
<!-- 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="keyName" label="配置项">
|
<a-form-item field="keyName" label="配置项">
|
||||||
<a-input v-model="formModel.keyName"
|
<a-input v-model="formModel.keyName"
|
||||||
@@ -140,13 +147,13 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import type { DictKeyQueryRequest, DictKeyQueryResponse } from '@/api/system/dict-key';
|
import type { DictKeyQueryRequest, DictKeyQueryResponse } from '@/api/system/dict-key';
|
||||||
import { reactive, ref, onMounted } from 'vue';
|
import { reactive, ref, onMounted } from 'vue';
|
||||||
import { batchDeleteDictKey, deleteDictKey, getDictKeyPage, refreshCache } from '@/api/system/dict-key';
|
import { deleteDictKey, getDictKeyPage, refreshCache } from '@/api/system/dict-key';
|
||||||
import { Message } from '@arco-design/web-vue';
|
import { Message } from '@arco-design/web-vue';
|
||||||
import useLoading from '@/hooks/loading';
|
import useLoading from '@/hooks/loading';
|
||||||
import columns from '../types/table.columns';
|
import columns from '../types/table.columns';
|
||||||
import { usePagination } from '@/types/table';
|
import { usePagination } from '@/types/table';
|
||||||
import { dictValueTypeKey } from '../types/const';
|
import { dictValueTypeKey } from '../types/const';
|
||||||
import useCopy from '@/hooks/copy';
|
import { copy } from '@/hooks/copy';
|
||||||
import { useCacheStore, useDictStore } from '@/store';
|
import { useCacheStore, useDictStore } from '@/store';
|
||||||
import { getDictValueList } from '@/api/system/dict-value';
|
import { getDictValueList } from '@/api/system/dict-value';
|
||||||
|
|
||||||
@@ -154,7 +161,6 @@
|
|||||||
const emits = defineEmits(['openAdd', 'openUpdate', 'openView']);
|
const emits = defineEmits(['openAdd', 'openUpdate', 'openView']);
|
||||||
|
|
||||||
const pagination = usePagination();
|
const pagination = usePagination();
|
||||||
const { copy } = useCopy();
|
|
||||||
const { loading, setLoading } = useLoading();
|
const { loading, setLoading } = useLoading();
|
||||||
const { toOptions, getDictValue } = useDictStore();
|
const { toOptions, getDictValue } = useDictStore();
|
||||||
const cacheStore = useCacheStore();
|
const cacheStore = useCacheStore();
|
||||||
|
|||||||
Reference in New Issue
Block a user