🔨 执行模板主机.

This commit is contained in:
lijiahang
2024-04-22 16:27:19 +08:00
parent e057ab858f
commit be87c930e2
17 changed files with 243 additions and 89 deletions

View File

@@ -44,20 +44,13 @@ public class CodeGenerators {
// .color("blue", "gray", "red", "green", "white")
// .valueUseFields()
// .build(),
Template.create("exec_job", "计划任务", "exec")
// Template.create("exec_template_host", "执行模板主机", "exec")
// .disableUnitTest()
// .vue("exec", "exec-template-host")
// .build(),
Template.create("path_bookmark", "路径标签", "host")
.disableUnitTest()
.vue("exec", "exec-job")
.enableDrawerForm()
.dict("execJobStatus", "status")
.comment("计划任务状态")
.field("execJobStatus")
.fields("DISABLED", "ENABLED")
.labels("禁用", "启用")
.values(0, 1)
.build(),
Template.create("exec_job_host", "计划任务主机", "exec")
.disableUnitTest()
.vue("exec", "exec-job-host")
.vue("host", "path-bookmark")
.build(),
};
// jdbc 配置 - 使用配置文件

View File

@@ -66,6 +66,15 @@ public class ExecTemplateController {
return execTemplateService.getExecTemplateById(id);
}
@IgnoreLog(IgnoreLogMode.RET)
@GetMapping("/get-with-authorized")
@Operation(summary = "查询执行模板 (查询认证的主机)")
@Parameter(name = "id", description = "id", required = true)
@PreAuthorize("@ss.hasPermission('asset:exec-template:query')")
public ExecTemplateVO getExecTemplateWithAuthorized(@RequestParam("id") Long id) {
return execTemplateService.getExecTemplateWithAuthorized(id);
}
@IgnoreLog(IgnoreLogMode.RET)
@PostMapping("/query")
@Operation(summary = "分页查询执行模板")

View File

@@ -31,7 +31,4 @@ public class ExecTemplateQueryRequest extends PageRequest {
@Schema(description = "命令")
private String command;
@Schema(description = "是否查询模板主机")
private Boolean queryHost;
}

View File

@@ -39,6 +39,14 @@ public interface ExecTemplateService {
*/
ExecTemplateVO getExecTemplateById(Long id);
/**
* 查询执行模板 (查询认证的主机)
*
* @param id id
* @return row
*/
ExecTemplateVO getExecTemplateWithAuthorized(Long id);
/**
* 分页查询执行模板
*

View File

@@ -3,9 +3,10 @@ package com.orion.ops.module.asset.service.impl;
import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.orion.lang.define.wrapper.DataGrid;
import com.orion.lang.utils.Booleans;
import com.orion.lang.utils.collect.Lists;
import com.orion.ops.framework.common.constant.ErrorMessage;
import com.orion.ops.framework.common.utils.Valid;
import com.orion.ops.framework.security.core.utils.SecurityUtils;
import com.orion.ops.module.asset.convert.ExecTemplateConvert;
import com.orion.ops.module.asset.dao.ExecTemplateDAO;
import com.orion.ops.module.asset.entity.domain.ExecTemplateDO;
@@ -13,7 +14,9 @@ import com.orion.ops.module.asset.entity.request.exec.ExecTemplateCreateRequest;
import com.orion.ops.module.asset.entity.request.exec.ExecTemplateQueryRequest;
import com.orion.ops.module.asset.entity.request.exec.ExecTemplateUpdateRequest;
import com.orion.ops.module.asset.entity.vo.ExecTemplateVO;
import com.orion.ops.module.asset.enums.HostConfigTypeEnum;
import com.orion.ops.module.asset.enums.ScriptExecEnum;
import com.orion.ops.module.asset.service.AssetAuthorizedDataService;
import com.orion.ops.module.asset.service.ExecTemplateHostService;
import com.orion.ops.module.asset.service.ExecTemplateService;
import lombok.extern.slf4j.Slf4j;
@@ -21,9 +24,7 @@ import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
/**
* 执行模板 服务实现类
@@ -42,6 +43,9 @@ public class ExecTemplateServiceImpl implements ExecTemplateService {
@Resource
private ExecTemplateHostService execTemplateHostService;
@Resource
private AssetAuthorizedDataService assetAuthorizedDataService;
@Override
public Long createExecTemplate(ExecTemplateCreateRequest request) {
log.info("ExecTemplateService-createExecTemplate request: {}", JSON.toJSONString(request));
@@ -92,28 +96,33 @@ public class ExecTemplateServiceImpl implements ExecTemplateService {
return template;
}
@Override
public ExecTemplateVO getExecTemplateWithAuthorized(Long id) {
// 查询模板
ExecTemplateDO record = execTemplateDAO.selectById(id);
Valid.notNull(record, ErrorMessage.DATA_ABSENT);
// 转换
ExecTemplateVO template = ExecTemplateConvert.MAPPER.to(record);
// 查询模板主机
Set<Long> hostIdList = execTemplateHostService.getHostByTemplateId(id);
if (Lists.isEmpty(hostIdList)) {
return template;
}
// 过滤认证的主机
List<Long> authorizedHostIdList = assetAuthorizedDataService.getUserAuthorizedHostIdWithEnabledConfig(SecurityUtils.getLoginUserId(), HostConfigTypeEnum.SSH);
hostIdList.removeIf(s -> !authorizedHostIdList.contains(s));
template.setHostIdList(hostIdList);
return template;
}
@Override
public DataGrid<ExecTemplateVO> getExecTemplatePage(ExecTemplateQueryRequest request) {
// 条件
LambdaQueryWrapper<ExecTemplateDO> wrapper = this.buildQueryWrapper(request);
// 查询模板
DataGrid<ExecTemplateVO> templates = execTemplateDAO.of(wrapper)
return execTemplateDAO.of(wrapper)
.page(request)
.dataGrid(ExecTemplateConvert.MAPPER::to);
if (templates.isEmpty()) {
return templates;
}
// 查询模板主机
if (Booleans.isTrue(request.getQueryHost())) {
List<Long> idList = templates.stream()
.map(ExecTemplateVO::getId)
.collect(Collectors.toList());
Map<Long, Set<Long>> templateHosts = execTemplateHostService.getHostByTemplateIdList(idList);
for (ExecTemplateVO template : templates) {
template.setHostIdList(templateHosts.get(template.getId()));
}
}
return templates;
}
@Override

View File

@@ -11,6 +11,7 @@ export interface ExecTemplateCreateRequest {
timeout?: number;
scriptExec?: number;
parameterSchema?: string;
hostIdList?: Array<number>;
}
/**
@@ -43,6 +44,7 @@ export interface ExecTemplateQueryResponse extends TableData {
updateTime: number;
creator: string;
updater: string;
hostIdList?: Array<number>;
}
/**
@@ -66,6 +68,13 @@ export function getExecTemplate(id: number) {
return axios.get<ExecTemplateQueryResponse>('/asset/exec-template/get', { params: { id } });
}
/**
* 查询执行模板
*/
export function getExecTemplateWithAuthorized(id: number) {
return axios.get<ExecTemplateQueryResponse>('/asset/exec-template/get-with-authorized', { params: { id } });
}
/**
* 分页查询执行模板
*/

View File

@@ -1,14 +1,14 @@
<template>
<a-modal v-model:visible="visible"
title-align="start"
title="执行日志"
width="94%"
:top="44"
:body-style="{ padding: '0', height: 'calc(100vh - 138px)', overflow: 'hidden' }"
width="96%"
:top="24"
:body-style="{ padding: '0', height: 'calc(100vh - 48px)', overflow: 'hidden' }"
:align-center="false"
:draggable="true"
:mask-closable="false"
:mask-closable="true"
:unmount-on-close="true"
:hide-title="true"
:footer="false"
@close="handleClose">
<a-spin v-if="visible"

View File

@@ -3,7 +3,7 @@
<div class="container">
<!-- 表头 -->
<div class="host-header">
<h3>执行主机</h3>
<h3 class="usn">执行主机</h3>
<!-- 操作 -->
<a-button v-if="visibleBack"
size="small"

View File

@@ -4,7 +4,7 @@
title="执行模板"
width="86%"
:top="80"
:body-style="{padding: '0 8px'}"
:body-style="{ padding: '0 8px' }"
:align-center="false"
:draggable="true"
:mask-closable="false"
@@ -93,8 +93,8 @@
import useVisible from '@/hooks/visible';
import useLoading from '@/hooks/loading';
import { copy } from '@/hooks/copy';
import columns from './table.columns';
import { getExecTemplatePage } from '@/api/exec/exec-template';
import columns from './table.columns';
const emits = defineEmits(['selected']);

View File

@@ -6,7 +6,16 @@ import { useUserStore } from '@/store';
export default function useUser() {
const router = useRouter();
const userStore = useUserStore();
const logout = async (logoutTo?: string) => {
// 退出登录
const logout = async () => {
await userStore.logout();
Message.success('已退出登录');
await router.push({ name: 'login' });
};
// 退出并重定向
const logoutRedirect = async (logoutTo?: string) => {
await userStore.logout();
const currentRoute = router.currentRoute.value;
Message.success('已退出登录');
@@ -18,7 +27,9 @@ export default function useUser() {
},
});
};
return {
logout,
logoutRedirect,
};
}

View File

@@ -126,6 +126,7 @@
import formRules from '../types/form.rules';
import useLoading from '@/hooks/loading';
import { batchExecCommand } from '@/api/exec/exec-command';
import { getExecTemplateWithAuthorized } from '@/api/exec/exec-template';
import { EnabledStatus } from '@/types/const';
import { Message } from '@arco-design/web-vue';
import ExecEditor from '@/components/view/exec-editor/index.vue';
@@ -170,22 +171,31 @@
};
// 从执行模板设置
const setWithTemplate = (record: ExecTemplateQueryResponse) => {
formModel.value = {
...formModel.value,
command: record.command,
description: record.name,
timeout: record.timeout,
scriptExec: record.scriptExec,
};
parameterSchema.value = record.parameterSchema ? JSON.parse(record.parameterSchema) : [];
if (parameterSchema.value.length) {
parameterFormModel.value = parameterSchema.value.reduce((acc, cur) => ({
...acc,
[cur.name as string]: cur.value
}), {});
} else {
parameterFormModel.value = {};
const setWithTemplate = async ({ id }: ExecTemplateQueryResponse) => {
setLoading(true);
try {
// 查询模板信息
const { data } = await getExecTemplateWithAuthorized(id);
formModel.value = {
...formModel.value,
description: data.name,
command: data.command,
timeout: data.timeout,
scriptExec: data.scriptExec,
hostIdList: data.hostIdList,
};
parameterSchema.value = data.parameterSchema ? JSON.parse(data.parameterSchema) : [];
if (parameterSchema.value.length) {
parameterFormModel.value = parameterSchema.value.reduce((acc, cur) => ({
...acc,
[cur.name as string]: cur.value
}), {});
} else {
parameterFormModel.value = {};
}
} catch (e) {
} finally {
setLoading(false);
}
};

View File

@@ -1,7 +1,7 @@
<template>
<a-drawer v-model:visible="visible"
title="计划任务详情"
width="60%"
width="66%"
:mask-closable="false"
:unmount-on-close="true"
ok-text="关闭"
@@ -146,6 +146,7 @@
}
.command-editor {
height: calc(100vh - 378px);
height: calc(100vh - 384px);
}
</style>

View File

@@ -1,7 +1,8 @@
<template>
<a-drawer v-model:visible="visible"
:title="title"
width="60%"
width="66%"
:esc-to-close="false"
:mask-closable="false"
:unmount-on-close="true"
:ok-button-props="{ disabled: loading }"
@@ -135,6 +136,7 @@
import formRules from '../types/form.rules';
import { jobBuiltinsParams } from '../types/const';
import { createExecJob, getExecJob, updateExecJob } from '@/api/exec/exec-job';
import { getExecTemplateWithAuthorized } from '@/api/exec/exec-template';
import { Message } from '@arco-design/web-vue';
import { EnabledStatus } from '@/types/const';
import { useDictStore } from '@/store';
@@ -195,9 +197,9 @@
id: record.id,
name: record.name,
expression: record.expression,
command: record.command,
timeout: record.timeout,
scriptExec: record.scriptExec,
command: record.command,
parameterSchema: record.parameterSchema,
hostIdList: record.hostIdList,
};
@@ -209,9 +211,24 @@
};
// 通过模板设置
const setWithTemplate = (template: ExecTemplateQueryResponse) => {
formModel.value.command = template.command;
formModel.value.timeout = template.timeout;
const setWithTemplate = async ({ id }: ExecTemplateQueryResponse) => {
setLoading(true);
try {
// 查询模板信息
const { data } = await getExecTemplateWithAuthorized(id);
formModel.value = {
...formModel.value,
name: data.name,
command: data.command,
timeout: data.timeout,
scriptExec: data.scriptExec,
parameterSchema: data.parameterSchema,
hostIdList: data.hostIdList,
};
} catch (e) {
} finally {
setLoading(false);
}
};
defineExpose({ openAdd, openUpdate, setSelectedHost, setWithTemplate });

View File

@@ -1,7 +1,8 @@
<template>
<a-drawer v-model:visible="visible"
title="执行命令"
width="60%"
width="66%"
:esc-to-close="false"
:mask-closable="false"
:unmount-on-close="true"
:ok-button-props="{ disabled: loading }"
@@ -137,6 +138,7 @@
import { Message } from '@arco-design/web-vue';
import { EnabledStatus } from '@/types/const';
import { batchExecCommand } from '@/api/exec/exec-command';
import { getExecTemplateWithAuthorized } from '@/api/exec/exec-template';
import ExecEditor from '@/components/view/exec-editor/index.vue';
const emits = defineEmits(['openHost']);
@@ -151,9 +153,18 @@
const parameterSchema = ref<Array<TemplateParam>>([]);
// 打开
const open = (record: ExecTemplateQueryResponse) => {
renderForm({ ...record });
const open = async (id: number) => {
renderForm({} as ExecTemplateQueryResponse);
setVisible(true);
setLoading(true);
try {
// 查询模板信息
const { data } = await getExecTemplateWithAuthorized(id);
renderForm(data);
} catch (e) {
} finally {
setLoading(false);
}
};
// 渲染表单
@@ -163,7 +174,7 @@
timeout: record.timeout,
scriptExec: record.scriptExec,
command: record.command,
hostIdList: []
hostIdList: record.hostIdList || [],
};
if (record.parameterSchema) {
parameterSchema.value = JSON.parse(record.parameterSchema);
@@ -172,7 +183,6 @@
params[param.name as keyof any] = param.defaultValue;
}
parameterFormModel.value = params;
} else {
parameterSchema.value = [];
parameterFormModel.value = {};

View File

@@ -1,7 +1,8 @@
<template>
<a-drawer v-model:visible="visible"
width="60%"
width="66%"
:title="title"
:esc-to-close="false"
:mask-closable="false"
:unmount-on-close="true"
:ok-button-props="{ disabled: loading }"
@@ -16,7 +17,7 @@
:rules="formRules">
<a-row :gutter="16">
<!-- 模板名称 -->
<a-col :span="12">
<a-col :span="16">
<a-form-item field="name"
label="模板名称"
:hide-asterisk="true">
@@ -26,7 +27,7 @@
</a-form-item>
</a-col>
<!-- 超时时间 -->
<a-col :span="6">
<a-col :span="8">
<a-form-item field="timeout"
label="超时时间"
:hide-asterisk="true">
@@ -41,8 +42,24 @@
</a-input-number>
</a-form-item>
</a-col>
<!-- 默认主机 -->
<a-col :span="16">
<a-form-item field="hostIdList"
label="默认主机"
:hide-asterisk="true">
<div class="selected-host">
<!-- 已选择数量 -->
<span class="usn" v-if="formModel.hostIdList?.length">
已选择<span class="selected-host-count span-blue">{{ formModel.hostIdList?.length }}</span>台主机
</span>
<span class="usn pointer span-blue" @click="openSelectHost">
{{ formModel.hostIdList?.length ? '重新选择' : '选择主机' }}
</span>
</div>
</a-form-item>
</a-col>
<!-- 脚本执行 -->
<a-col :span="6">
<a-col :span="8">
<a-form-item field="scriptExec"
label="脚本执行"
:hide-asterisk="true">
@@ -132,7 +149,7 @@
import useLoading from '@/hooks/loading';
import useVisible from '@/hooks/visible';
import formRules from '../types/form.rules';
import { createExecTemplate, updateExecTemplate } from '@/api/exec/exec-template';
import { createExecTemplate, getExecTemplateWithAuthorized, updateExecTemplate } from '@/api/exec/exec-template';
import { Message } from '@arco-design/web-vue';
import { EnabledStatus } from '@/types/const';
import ExecEditor from '@/components/view/exec-editor/index.vue';
@@ -151,6 +168,7 @@
timeout: 0,
scriptExec: EnabledStatus.DISABLED,
parameterSchema: undefined,
hostIdList: [],
};
};
@@ -158,7 +176,7 @@
const formModel = ref<ExecTemplateUpdateRequest>({});
const parameter = ref<Array<TemplateParam>>([]);
const emits = defineEmits(['added', 'updated']);
const emits = defineEmits(['added', 'updated', 'openHost']);
// 打开新增
const openAdd = () => {
@@ -169,11 +187,20 @@
};
// 打开修改
const openUpdate = (record: any) => {
const openUpdate = async (id: number) => {
title.value = '修改执行模板';
isAddHandle.value = false;
renderForm({ ...defaultForm(), ...record });
renderForm({ ...defaultForm() });
setVisible(true);
setLoading(true);
try {
// 查询模板信息
const { data } = await getExecTemplateWithAuthorized(id);
renderForm({ ...defaultForm(), ...data });
} catch (e) {
} finally {
setLoading(false);
}
};
// 渲染表单
@@ -186,7 +213,17 @@
}
};
defineExpose({ openAdd, openUpdate });
// 设置选中主机
const setSelectedHost = (hosts: Array<number>) => {
formModel.value.hostIdList = hosts;
};
defineExpose({ openAdd, openUpdate, setSelectedHost });
// 打开选择主机
const openSelectHost = () => {
emits('openHost', formModel.value.hostIdList || []);
};
// 添加参数
const addParameter = () => {
@@ -251,6 +288,30 @@
</script>
<style lang="less" scoped>
.selected-host {
width: 100%;
height: 32px;
padding: 0 8px;
border-radius: 2px;
display: flex;
align-items: center;
justify-content: space-between;
background: var(--color-fill-2);
transition: all 0.3s;
&-count {
font-size: 16px;
font-weight: 600;
display: inline-block;
margin: 0 6px;
}
&:hover {
background: var(--color-fill-3);
}
}
.parameter-form-item {
user-select: none;
margin-top: 4px;
@@ -316,7 +377,7 @@
.command-editor {
width: 100%;
height: 62vh;
height: 55vh;
}
</style>

View File

@@ -79,14 +79,14 @@
<a-button v-permission="['asset:exec-command:exec']"
type="text"
size="mini"
@click="emits('openExec', record)">
@click="emits('openExec', record.id)">
执行
</a-button>
<!-- 修改 -->
<a-button v-permission="['asset:exec-template:update']"
type="text"
size="mini"
@click="emits('openUpdate', record)">
@click="emits('openUpdate', record.id)">
修改
</a-button>
<!-- 删除 -->

View File

@@ -2,19 +2,20 @@
<div class="layout-container" v-if="render">
<!-- 列表-表格 -->
<exec-template-table ref="table"
@open-exec="(e) => execModal.open(e)"
@open-add="() => drawer.openAdd()"
@open-update="(e) => drawer.openUpdate(e)" />
@open-update="(e) => drawer.openUpdate(e)"
@open-exec="(e) => execModal.open(e)" />
<!-- 添加修改模态框 -->
<exec-template-form-drawer ref="drawer"
@added="modalAddCallback"
@updated="modalUpdateCallback" />
@updated="modalUpdateCallback"
@open-host="(e) => openHostModal('drawer', e)" />
<!-- 执行模态框 -->
<exec-template-exec-drawer ref="execModal"
@open-host="(e) => hostModal.open(e)" />
@open-host="(e) => openHostModal('exec', e)" />
<!-- 主机模态框 -->
<authorized-host-modal ref="hostModal"
@selected="(e) => execModal.setSelectedHost(e)" />
@selected="hostSelected" />
</div>
</template>
@@ -34,8 +35,9 @@
const render = ref(false);
const table = ref();
const drawer = ref();
const hostModal = ref();
const execModal = ref();
const hostModal = ref();
const lastOpenHostRef = ref();
// 添加回调
const modalAddCallback = () => {
@@ -47,6 +49,23 @@
table.value.updatedCallback();
};
// 打开主机模态框
const openHostModal = (openRef: string, data: any) => {
lastOpenHostRef.value = openRef;
hostModal.value.open(data);
};
// 选中主机
const hostSelected = (data: any) => {
if (lastOpenHostRef.value === 'drawer') {
// 设置选中的主机
drawer.value.setSelectedHost(data);
} else if (lastOpenHostRef.value === 'exec') {
// 设置选中的主机
execModal.value.setSelectedHost(data);
}
};
onBeforeMount(async () => {
render.value = true;
});