添加主机身份类型.

This commit is contained in:
lijiahang
2024-04-17 10:11:36 +08:00
parent bc8e04b908
commit 339d86fc87
32 changed files with 350 additions and 118 deletions

View File

@@ -7,6 +7,7 @@ import axios from 'axios';
*/
export interface HostIdentityCreateRequest {
name?: string;
type?: string;
username?: string;
password?: string;
keyId?: number;
@@ -27,6 +28,7 @@ export interface HostIdentityQueryRequest extends Pagination {
searchValue?: string;
id?: number;
name?: string;
type?: string;
username?: string;
keyId?: number;
}
@@ -37,6 +39,7 @@ export interface HostIdentityQueryRequest extends Pagination {
export interface HostIdentityQueryResponse extends TableData {
id: number;
name: string;
type: string;
username: string;
password: string;
keyId: number;

View File

@@ -57,6 +57,8 @@
// 定时查询执行状态
if (record.status === execStatus.WAITING ||
record.status === execStatus.RUNNING) {
// 等待一秒后先查询一下状态
setTimeout(fetchTaskStatus, 1000);
// 注册状态轮询
statusIntervalId.value = setInterval(fetchTaskStatus, 5000);
}
@@ -92,8 +94,8 @@
if (hostStatus) {
host.status = hostStatus.status;
host.startTime = hostStatus.startTime;
// 使用时间
host.finishTime = host.finishTime || Date.now();
// 结束时间绑定了使用时间 如果未完成则使用当前时间
host.finishTime = hostStatus.finishTime || Date.now();
host.exitStatus = hostStatus.exitStatus;
host.errorMessage = hostStatus.errorMessage;
}

View File

@@ -15,11 +15,24 @@
:pagination="false"
:bordered="false"
@row-click="clickRow">
<!-- 类型 -->
<template #type="{ record }">
<a-tag :color="getDictValue(identityTypeKey, record.type, 'color')">
{{ getDictValue(identityTypeKey, record.type) }}
</a-tag>
</template>
<!-- 秘钥名称 -->
<template #keyId="{ record }">
<a-tag color="arcoblue" v-if="record.keyId">
{{ hostKeys.find(s => s.id === record.keyId)?.name }}
</a-tag>
<!-- 有秘钥 -->
<template v-if="record.keyId && record.type === 'KEY'">
<a-tag color="arcoblue" v-if="record.keyId">
{{ hostKeys.find(s => s.id === record.keyId)?.name }}
</a-tag>
</template>
<!-- 无秘钥 -->
<template v-else>
<span>-</span>
</template>
</template>
</a-table>
</grant-layout>
@@ -38,11 +51,12 @@
import type { HostKeyQueryResponse } from '@/api/asset/host-key';
import { ref, onMounted } from 'vue';
import useLoading from '@/hooks/loading';
import { getAuthorizedHostIdentity, grantHostIdentity } from '@/api/asset/asset-data-grant';
import { Message } from '@arco-design/web-vue';
import { hostIdentityColumns } from '../types/table.columns';
import { useCacheStore } from '@/store';
import { useRowSelection } from '@/types/table';
import { getAuthorizedHostIdentity, grantHostIdentity } from '@/api/asset/asset-data-grant';
import { useCacheStore, useDictStore } from '@/store';
import { hostIdentityColumns } from '../types/table.columns';
import { identityTypeKey } from '../types/const';
import { Message } from '@arco-design/web-vue';
import GrantLayout from './grant-layout.vue';
const props = defineProps<{
@@ -51,6 +65,7 @@
const cacheStore = useCacheStore();
const rowSelection = useRowSelection();
const { getDictValue } = useDictStore();
const { loading, setLoading } = useLoading();
const selectedKeys = ref<Array<number>>([]);

View File

@@ -26,8 +26,8 @@
<script lang="ts" setup>
import { onBeforeMount, onUnmounted, ref } from 'vue';
import { useCacheStore } from '@/store';
import { GrantTabs } from './types/const';
import { useCacheStore, useDictStore } from '@/store';
import { GrantTabs, dictKeys } from './types/const';
import { useRoute } from 'vue-router';
const route = useRoute();
@@ -35,9 +35,10 @@
const activeKey = ref();
// 卸载时清除 cache
onUnmounted(() => {
cacheStore.reset('users', 'roles', 'hosts', 'hostGroups', 'hostKeys', 'hostIdentities');
// 加载字典项
onBeforeMount(async () => {
const dictStore = useDictStore();
await dictStore.loadKeys(dictKeys);
});
// 跳转到指定页
@@ -48,6 +49,11 @@
}
});
// 卸载时清除 cache
onUnmounted(() => {
cacheStore.reset('users', 'roles', 'hosts', 'hostGroups', 'hostKeys', 'hostIdentities');
});
</script>
<style lang="less" scoped>

View File

@@ -73,3 +73,9 @@ export const GrantTabs = [
component: HostIdentityGrant
},
];
// 身份类型 字典项
export const identityTypeKey = 'hostIdentityType';
// 加载的字典值
export const dictKeys = [identityTypeKey];

View File

@@ -14,11 +14,14 @@ export const hostKeyColumns = [
title: '名称',
dataIndex: 'name',
slotName: 'name',
ellipsis: true,
tooltip: true
}, {
title: '创建时间',
dataIndex: 'createTime',
slotName: 'createTime',
align: 'center',
width: 180,
render: ({ record }) => {
return dateFormat(new Date(record.createTime));
},
@@ -27,6 +30,7 @@ export const hostKeyColumns = [
dataIndex: 'updateTime',
slotName: 'updateTime',
align: 'center',
width: 180,
render: ({ record }) => {
return dateFormat(new Date(record.updateTime));
},
@@ -46,23 +50,23 @@ export const hostIdentityColumns = [
title: '名称',
dataIndex: 'name',
slotName: 'name',
ellipsis: true,
tooltip: true
}, {
title: '类型',
dataIndex: 'type',
slotName: 'type',
width: 98,
}, {
title: '用户名',
dataIndex: 'username',
slotName: 'username',
ellipsis: true,
tooltip: true
}, {
title: '主机秘钥',
dataIndex: 'keyId',
slotName: 'keyId',
}, {
title: '创建时间',
dataIndex: 'createTime',
slotName: 'createTime',
align: 'center',
width: 180,
render: ({ record }) => {
return dateFormat(new Date(record.createTime));
},
}, {
title: '修改时间',
dataIndex: 'updateTime',

View File

@@ -54,6 +54,13 @@
<a-form-item field="name" label="名称">
<a-input v-model="formModel.name" placeholder="请输入名称" allow-clear />
</a-form-item>
<!-- 类型 -->
<a-form-item field="type" label="类型">
<a-select v-model="formModel.type"
placeholder="请选择类型"
:options="toOptions(identityTypeKey)"
allow-clear />
</a-form-item>
<!-- 用户名 -->
<a-form-item field="username" label="用户名">
<a-input v-model="formModel.username" placeholder="请输入用户名" allow-clear />
@@ -68,6 +75,12 @@
<template #title="{ record }">
{{ record.name }}
</template>
<!-- 类型 -->
<template #type="{ record }">
<a-tag :color="getDictValue(identityTypeKey, record.type, 'color')">
{{ getDictValue(identityTypeKey, record.type) }}
</a-tag>
</template>
<!-- 用户名 -->
<template #username="{ record }">
<span class="span-blue text-copy" @click="copy(record.username)">
@@ -76,13 +89,14 @@
</template>
<!-- 秘钥名称 -->
<template #keyId="{ record }">
<template v-if="record.keyId">
<!-- 有秘钥 -->
<template v-if="record.keyId && record.type === IdentityType.KEY">
<!-- 可查看详情 -->
<a-tooltip v-if="hasAnyPermission(['asset:host-key:detail', 'asset:host-key:update'])"
content="点击查看详情">
<a-tag :checked="true"
checkable
@click="emits('openKeyView',{id: record.keyId})">
@click="emits('openKeyView', { id: record.keyId })">
{{ record.keyName }}
</a-tag>
</a-tooltip>
@@ -91,6 +105,10 @@
{{ record.keyName }}
</a-tag>
</template>
<!-- 无秘钥 -->
<template v-else>
<span>-</span>
</template>
</template>
<!-- 拓展操作 -->
<template #extra="{ record }">
@@ -151,8 +169,10 @@
import { deleteHostIdentity, getHostIdentityPage } from '@/api/asset/host-identity';
import { Message, Modal } from '@arco-design/web-vue';
import usePermission from '@/hooks/permission';
import { useDictStore } from '@/store';
import { copy } from '@/hooks/copy';
import { GrantKey, GrantRouteName } from '@/views/asset/grant/types/const';
import { IdentityType, identityTypeKey } from '../types/const';
import HostKeySelector from '@/components/asset/host-key/selector/index.vue';
const emits = defineEmits(['openAdd', 'openUpdate', 'openKeyView']);
@@ -161,6 +181,7 @@
const cardColLayout = useColLayout();
const pagination = usePagination();
const { toOptions, getDictValue } = useDictStore();
const { loading, setLoading } = useLoading();
const { hasAnyPermission } = usePermission();
@@ -168,6 +189,7 @@
const formModel = reactive<HostIdentityQueryRequest>({
searchValue: undefined,
id: undefined,
type: undefined,
name: undefined,
username: undefined,
keyId: undefined,

View File

@@ -23,12 +23,20 @@
<a-form-item field="name" label="名称">
<a-input v-model="formModel.name" placeholder="请输入名称" />
</a-form-item>
<!-- 类型 -->
<a-form-item field="type" label="类型">
<a-radio-group v-model="formModel.type"
type="button"
class="usn"
:options="toRadioOptions(identityTypeKey)" />
</a-form-item>
<!-- 用户名 -->
<a-form-item field="username" label="用户名">
<a-input v-model="formModel.username" placeholder="请输入用户名" />
</a-form-item>
<!-- 用户密码 -->
<a-form-item field="password"
<a-form-item v-if="formModel.type === IdentityType.PASSWORD"
field="password"
label="用户密码"
:rules="passwordRules">
<a-input-password v-model="formModel.password"
@@ -42,10 +50,10 @@
checked-text="使用新密码"
unchecked-text="使用原密码" />
</a-form-item>
<!-- 秘钥id -->
<a-form-item field="keyId"
label="主机秘钥"
extra="密码和秘钥二选一 优先使用秘钥">
<!-- 主机秘钥 -->
<a-form-item v-if="formModel.type === IdentityType.KEY"
field="keyId"
label="主机秘钥">
<host-key-selector v-model="formModel.keyId" />
</a-form-item>
</a-form>
@@ -68,8 +76,11 @@
import formRules from '../types/form.rules';
import { createHostIdentity, updateHostIdentity } from '@/api/asset/host-identity';
import { Message } from '@arco-design/web-vue';
import { IdentityType, identityTypeKey } from '../types/const';
import { useDictStore } from '@/store';
import HostKeySelector from '@/components/asset/host-key/selector/index.vue';
const { toRadioOptions, getDictValue } = useDictStore();
const { visible, setVisible } = useVisible();
const { loading, setLoading } = useLoading();
@@ -79,6 +90,7 @@
const defaultForm = (): HostIdentityUpdateRequest => {
return {
id: undefined,
type: IdentityType.PASSWORD,
name: undefined,
username: undefined,
password: undefined,
@@ -139,15 +151,6 @@
return false;
}
if (isAddHandle.value) {
if (!formModel.value.password && !formModel.value.keyId) {
formRef.value.setFields({
password: {
status: 'error',
message: '创建时密码和秘钥不能同时为空'
}
});
return false;
}
// 新增
await createHostIdentity(formModel.value);
Message.success('创建成功');

View File

@@ -17,6 +17,13 @@
<a-form-item field="name" label="名称">
<a-input v-model="formModel.name" placeholder="请输入名称" allow-clear />
</a-form-item>
<!-- 类型 -->
<a-form-item field="type" label="类型">
<a-select v-model="formModel.type"
placeholder="请选择类型"
:options="toOptions(identityTypeKey)"
allow-clear />
</a-form-item>
<!-- 用户名 -->
<a-form-item field="username" label="用户名">
<a-input v-model="formModel.username" placeholder="请输入用户名" allow-clear />
@@ -80,6 +87,12 @@
@page-change="(page) => fetchTableData(page, pagination.pageSize)"
@page-size-change="(size) => fetchTableData(1, size)"
:bordered="false">
<!-- 类型 -->
<template #type="{ record }">
<a-tag :color="getDictValue(identityTypeKey, record.type, 'color')">
{{ getDictValue(identityTypeKey, record.type) }}
</a-tag>
</template>
<!-- 用户名 -->
<template #username="{ record }">
<span class="span-blue text-copy" @click="copy(record.username)">
@@ -88,13 +101,14 @@
</template>
<!-- 秘钥名称 -->
<template #keyId="{ record }">
<template v-if="record.keyId">
<!-- 有秘钥 -->
<template v-if="record.keyId && record.type === IdentityType.KEY">
<!-- 可查看详情 -->
<a-tooltip v-if="hasAnyPermission(['asset:host-key:detail', 'asset:host-key:update'])"
content="点击查看详情">
<a-tag :checked="true"
checkable
@click="emits('openKeyView',{id: record.keyId})">
@click="emits('openKeyView', { id: record.keyId })">
{{ record.keyName }}
</a-tag>
</a-tooltip>
@@ -103,6 +117,10 @@
{{ record.keyName }}
</a-tag>
</template>
<!-- 无秘钥 -->
<template v-else>
<span>-</span>
</template>
</template>
<!-- 操作 -->
<template #handle="{ record }">
@@ -147,21 +165,24 @@
import useLoading from '@/hooks/loading';
import usePermission from '@/hooks/permission';
import { copy } from '@/hooks/copy';
import { useDictStore } from '@/store';
import { usePagination } from '@/types/table';
import { GrantKey, GrantRouteName } from '@/views/asset/grant/types/const';
import { IdentityType, identityTypeKey } from '../types/const';
import HostKeySelector from '@/components/asset/host-key/selector/index.vue';
const emits = defineEmits(['openAdd', 'openUpdate', 'openKeyView']);
const tableRenderData = ref<HostIdentityQueryResponse[]>([]);
const pagination = usePagination();
const { toOptions, getDictValue } = useDictStore();
const { loading, setLoading } = useLoading();
const { hasAnyPermission } = usePermission();
const tableRenderData = ref<HostIdentityQueryResponse[]>([]);
const formModel = reactive<HostIdentityQueryRequest>({
id: undefined,
name: undefined,
type: undefined,
username: undefined,
keyId: undefined,
});

View File

@@ -28,8 +28,9 @@
</script>
<script lang="ts" setup>
import { ref, computed, onUnmounted } from 'vue';
import { useAppStore, useCacheStore } from '@/store';
import { ref, computed, onUnmounted, onBeforeMount } from 'vue';
import { useAppStore, useCacheStore, useDictStore } from '@/store';
import { dictKeys } from './types/const';
import HostIdentityCardList from './components/host-identity-card-list.vue';
import HostIdentityTable from './components/host-identity-table.vue';
import HostIdentityFormModal from './components/host-identity-form-modal.vue';
@@ -62,6 +63,12 @@
}
};
// 加载字典值
onBeforeMount(async () => {
const dictStore = useDictStore();
await dictStore.loadKeys(dictKeys);
});
// 卸载时清除 cache
onUnmounted(() => {
const cacheStore = useCacheStore();

View File

@@ -9,6 +9,10 @@ const fieldConfig = {
label: 'id',
dataIndex: 'id',
slotName: 'id',
}, {
label: '类型',
dataIndex: 'type',
slotName: 'type',
}, {
label: '用户名',
dataIndex: 'username',

View File

@@ -0,0 +1,11 @@
// 身份类型
export const IdentityType = {
PASSWORD: 'PASSWORD',
KEY: 'KEY',
};
// 身份类型 字典项
export const identityTypeKey = 'hostIdentityType';
// 加载的字典值
export const dictKeys = [identityTypeKey];

View File

@@ -8,6 +8,16 @@ export const name = [{
message: '名称长度不能大于64位'
}] as FieldRule[];
export const type = [{
required: true,
message: '请选择类型'
}] as FieldRule[];
export const keyId = [{
required: true,
message: '请选择秘钥'
}] as FieldRule[];
export const username = [{
required: true,
message: '请输入用户名'
@@ -18,5 +28,7 @@ export const username = [{
export default {
name,
type,
keyId,
username,
} as Record<string, FieldRule | FieldRule[]>;

View File

@@ -13,6 +13,13 @@ const columns = [
title: '名称',
dataIndex: 'name',
slotName: 'name',
ellipsis: true,
tooltip: true
}, {
title: '类型',
dataIndex: 'type',
slotName: 'type',
width: 138,
}, {
title: '用户名',
dataIndex: 'username',

View File

@@ -1,7 +1,7 @@
<template>
<a-drawer v-model:visible="visible"
:title="title"
:width="470"
:width="520"
:mask-closable="false"
:unmount-on-close="true"
:ok-button-props="{ disabled: loading || isViewHandler }"
@@ -241,7 +241,7 @@
.keygen-alert {
margin: 0 0 12px 16px;
width: 408px;
width: calc(100% - 16px);
}
.password-input {

View File

@@ -13,11 +13,14 @@ const columns = [
title: '名称',
dataIndex: 'name',
slotName: 'name',
ellipsis: true,
tooltip: true
}, {
title: '创建时间',
dataIndex: 'createTime',
slotName: 'createTime',
align: 'center',
width: 198,
render: ({ record }) => {
return dateFormat(new Date(record.createTime));
},
@@ -26,6 +29,7 @@ const columns = [
dataIndex: 'updateTime',
slotName: 'updateTime',
align: 'center',
width: 198,
render: ({ record }) => {
return dateFormat(new Date(record.updateTime));
},

View File

@@ -107,7 +107,7 @@
{{ getDictValue(execJobStatusKey, record.status) }}
</a-tag>
</template>
<!-- 最近任务 -->
<!-- 最近执行 -->
<template #recentLog="{ record }">
<div class="flex-center" v-if="record.recentLogId && record.recentLogStatus">
<!-- 执行状态 -->

View File

@@ -38,7 +38,7 @@ const columns = [
align: 'center',
width: 112,
}, {
title: '最近任务',
title: '最近执行',
dataIndex: 'recentLog',
slotName: 'recentLog',
align: 'left',

View File

@@ -245,5 +245,6 @@
display: flex;
align-items: center;
justify-content: center;
border-radius: 2px;
}
</style>