feat: 修改主机额外配置.

This commit is contained in:
lijiahang
2023-12-25 19:03:24 +08:00
parent b2a697f6ba
commit f7cbd7b507
34 changed files with 321 additions and 59 deletions

View File

@@ -8,7 +8,7 @@
:cancel-button-props="{ disabled: loading }" :cancel-button-props="{ disabled: loading }"
:on-before-ok="handlerOk" :on-before-ok="handlerOk"
@cancel="handleClose"> @cancel="handleClose">
<a-spin :loading="loading"> <a-spin class="full" :loading="loading">
<a-form :model="formModel" <a-form :model="formModel"
ref="formRef" ref="formRef"
label-align="right" label-align="right"

View File

@@ -12,7 +12,7 @@
:cancel-button-props="{ disabled: loading }" :cancel-button-props="{ disabled: loading }"
:on-before-ok="handlerOk" :on-before-ok="handlerOk"
@close="handleClose"> @close="handleClose">
<a-spin :loading="loading"> <a-spin class="full" :loading="loading">
<a-form :model="formModel" <a-form :model="formModel"
ref="formRef" ref="formRef"
label-align="right" label-align="right"

View File

@@ -58,7 +58,7 @@ public class HostExtraController {
return hostExtraService.getHostExtraList(request); return hostExtraService.getHostExtraList(request);
} }
@GetMapping("/update") @PutMapping("/update")
@Operation(summary = "修改主机拓展信息") @Operation(summary = "修改主机拓展信息")
public Integer updateHostExtra(@Validated @RequestBody HostExtraUpdateRequest request) { public Integer updateHostExtra(@Validated @RequestBody HostExtraUpdateRequest request) {
return hostExtraService.updateHostExtra(request); return hostExtraService.updateHostExtra(request);

View File

@@ -15,14 +15,14 @@ public enum HostExtraSshAuthTypeEnum {
DEFAULT, DEFAULT,
/** /**
* 秘钥验证 * 自定义秘钥验证
*/ */
KEY, CUSTOM_KEY,
/** /**
* 身份验证 * 自定义身份验证
*/ */
IDENTITY, CUSTOM_IDENTITY,
; ;

View File

@@ -55,9 +55,9 @@ public class HostSshExtraStrategy implements MapDataStrategy<HostSshExtraModel>
Long keyId = model.getKeyId(); Long keyId = model.getKeyId();
Long identityId = model.getIdentityId(); Long identityId = model.getIdentityId();
// 必填验证 // 必填验证
if (HostExtraSshAuthTypeEnum.KEY.equals(authType)) { if (HostExtraSshAuthTypeEnum.CUSTOM_KEY.equals(authType)) {
Valid.notNull(keyId); Valid.notNull(keyId);
} else if (HostExtraSshAuthTypeEnum.IDENTITY.equals(authType)) { } else if (HostExtraSshAuthTypeEnum.CUSTOM_IDENTITY.equals(authType)) {
Valid.notNull(identityId); Valid.notNull(identityId);
} }
// 验证主机秘钥是否存在 // 验证主机秘钥是否存在

View File

@@ -243,7 +243,6 @@ public class AssetAuthorizedDataServiceImpl implements AssetAuthorizedDataServic
List<Long> authorizedGroupIdList) { List<Long> authorizedGroupIdList) {
// 查询主机列表 // 查询主机列表
List<HostVO> hosts = hostService.getHostListByCache(); List<HostVO> hosts = hostService.getHostListByCache();
// 全部数据直接返回 // 全部数据直接返回
if (allData) { if (allData) {
return hosts; return hosts;
@@ -258,6 +257,7 @@ public class AssetAuthorizedDataServiceImpl implements AssetAuthorizedDataServic
.map(dataGroupRel::get) .map(dataGroupRel::get)
.filter(Lists::isNoneEmpty) .filter(Lists::isNoneEmpty)
.flatMap(Collection::stream) .flatMap(Collection::stream)
.distinct()
.map(hostMap::get) .map(hostMap::get)
.filter(Objects::nonNull) .filter(Objects::nonNull)
.collect(Collectors.toList()); .collect(Collectors.toList());

View File

@@ -149,14 +149,17 @@ public class DataPermissionServiceImpl implements DataPermissionService {
List<Long> list = RedisLists.range(cacheKey, Long::valueOf); List<Long> list = RedisLists.range(cacheKey, Long::valueOf);
if (list.isEmpty()) { if (list.isEmpty()) {
LambdaQueryWrapper<DataPermissionDO> wrapper = dataPermissionDAO.lambda() LambdaQueryWrapper<DataPermissionDO> wrapper = dataPermissionDAO.lambda()
.eq(DataPermissionDO::getType, type) .eq(DataPermissionDO::getType, type);
.eq(DataPermissionDO::getUserId, userId);
// 查询用户角色
if (dataType.isToRole()) { if (dataType.isToRole()) {
// 查询用户角色
List<Long> roleIdList = systemUserRoleDAO.selectRoleIdByUserId(userId); List<Long> roleIdList = systemUserRoleDAO.selectRoleIdByUserId(userId);
if (!roleIdList.isEmpty()) { wrapper.and(s -> s.eq(DataPermissionDO::getUserId, userId)
wrapper.or().in(DataPermissionDO::getRoleId, roleIdList); .or()
} .in(!roleIdList.isEmpty(), DataPermissionDO::getRoleId, roleIdList)
);
} else {
// 单用户
wrapper.eq(DataPermissionDO::getUserId, userId);
} }
// 查询数据库 // 查询数据库
list = dataPermissionDAO.of() list = dataPermissionDAO.of()

View File

@@ -26,7 +26,11 @@
UPDATE data_extra UPDATE data_extra
SET value = JSON_REPLACE(value, SET value = JSON_REPLACE(value,
"$.keyId", NULL, "$.keyId", NULL,
"$.authType", IF(JSON_EXTRACT(value, "$.authType") = 'KEY', 'DEFAULT', JSON_EXTRACT(value, "$.authType"))) "$.authType", IF(
JSON_EXTRACT(value, "$.authType") = 'CUSTOM_KEY',
'DEFAULT',
JSON_EXTRACT(value, "$.authType")
))
WHERE deleted = 0 WHERE deleted = 0
AND type = 'HOST' AND type = 'HOST'
AND item = 'ssh' AND item = 'ssh'
@@ -37,7 +41,11 @@
UPDATE data_extra UPDATE data_extra
SET value = JSON_REPLACE(value, SET value = JSON_REPLACE(value,
"$.identityId", NULL, "$.identityId", NULL,
"$.authType", IF(JSON_EXTRACT(value, "$.authType") = 'IDENTITY', 'DEFAULT', JSON_EXTRACT(value, "$.authType"))) "$.authType", IF(
JSON_EXTRACT(value, "$.authType") = 'CUSTOM_IDENTITY',
'DEFAULT',
JSON_EXTRACT(value, "$.authType")
))
WHERE deleted = 0 WHERE deleted = 0
AND type = 'HOST' AND type = 'HOST'
AND item = 'ssh' AND item = 'ssh'

View File

@@ -24,12 +24,12 @@ export function getCurrentAuthorizedHost() {
* 查询当前用户已授权的主机秘钥 * 查询当前用户已授权的主机秘钥
*/ */
export function getCurrentAuthorizedHostKey() { export function getCurrentAuthorizedHostKey() {
return axios.get<HostKeyQueryResponse>('/asset/authorized-data/current-host-key'); return axios.get<Array<HostKeyQueryResponse>>('/asset/authorized-data/current-host-key');
} }
/** /**
* 查询当前用户已授权的主机身份 * 查询当前用户已授权的主机身份
*/ */
export function getCurrentAuthorizedHostIdentity() { export function getCurrentAuthorizedHostIdentity() {
return axios.get<HostIdentityQueryResponse>('/asset/authorized-data/current-host-identity'); return axios.get<Array<HostIdentityQueryResponse>>('/asset/authorized-data/current-host-identity');
} }

View File

@@ -8,9 +8,48 @@ export interface HostAliasUpdateRequest {
name?: string; name?: string;
} }
/**
* 主机拓展信息查询请求
*/
export interface HostExtraQueryRequest {
hostId?: number;
item: string;
items?: Array<string>;
}
/**
* 主机拓展信息更新请求
*/
export interface HostExtraUpdateRequest {
hostId?: number;
item: string;
extra: string;
}
/** /**
* 修改主机别名 * 修改主机别名
*/ */
export function updateHostAlias(request: HostAliasUpdateRequest) { export function updateHostAlias(request: HostAliasUpdateRequest) {
return axios.put('/asset/host-extra/update-alias', request); return axios.put('/asset/host-extra/update-alias', request);
} }
/**
* 获取主机拓展信息
*/
export function getHostExtraItem<T>(params: HostExtraQueryRequest) {
return axios.get<T>('/asset/host-extra/get', { params });
}
/**
* 获取多个主机拓展信息
*/
export function getHostExtraItemList(request: HostExtraQueryRequest) {
return axios.post<Record<string, Record<string, any>>>('/asset/host-extra/list', request);
}
/**
* 修改主机拓展信息
*/
export function updateHostExtra(request: HostExtraUpdateRequest) {
return axios.put('/asset/host-extra/update', request);
}

View File

@@ -94,6 +94,11 @@ body {
border-color: rgb(var(--gray-2)); border-color: rgb(var(--gray-2));
} }
.full {
width: 100%;
height: 100%;
}
.disabled { .disabled {
pointer-events: none; pointer-events: none;
} }

View File

@@ -256,10 +256,10 @@
const localeRef = ref(); const localeRef = ref();
// 打开应用设置 // 打开应用设置
const openAppSetting = inject(openAppSettingKey) as () => void; const openAppSetting = inject<() => void>(openAppSettingKey);
// 注入收缩菜单 // 注入收缩菜单
const toggleDrawerMenu = inject(toggleDrawerMenuKey) as () => void; const toggleDrawerMenu = inject<() => void>(toggleDrawerMenuKey);
// 切换主题 // 切换主题
const handleToggleTheme = () => { const handleToggleTheme = () => {

View File

@@ -20,6 +20,7 @@
const props = defineProps({ const props = defineProps({
modelValue: Number, modelValue: Number,
authorized: Boolean
}); });
const emits = defineEmits(['update:modelValue']); const emits = defineEmits(['update:modelValue']);
@@ -45,7 +46,9 @@
onBeforeMount(async () => { onBeforeMount(async () => {
setLoading(true); setLoading(true);
try { try {
const hostIdentities = await cacheStore.loadHostIdentities(); const hostIdentities = props.authorized
? await cacheStore.loadAuthorizedHostIdentities()
: await cacheStore.loadHostIdentities();
optionData.value = hostIdentities.map(s => { optionData.value = hostIdentities.map(s => {
return { return {
label: `${s.name} (${s.username})`, label: `${s.name} (${s.username})`,

View File

@@ -20,6 +20,7 @@
const props = defineProps({ const props = defineProps({
modelValue: Number, modelValue: Number,
authorized: Boolean
}); });
const emits = defineEmits(['update:modelValue']); const emits = defineEmits(['update:modelValue']);
@@ -45,7 +46,9 @@
onBeforeMount(async () => { onBeforeMount(async () => {
setLoading(true); setLoading(true);
try { try {
const hostKeys = await cacheStore.loadHostKeys(); const hostKeys = props.authorized
? await cacheStore.loadAuthorizedHostKeys()
: await cacheStore.loadHostKeys();
optionData.value = hostKeys.map(s => { optionData.value = hostKeys.map(s => {
return { return {
label: s.name, label: s.name,

View File

@@ -12,7 +12,7 @@
:cancel-button-props="{ disabled: loading }" :cancel-button-props="{ disabled: loading }"
:on-before-ok="handlerOk" :on-before-ok="handlerOk"
@close="handleClose"> @close="handleClose">
<a-spin :loading="loading"> <a-spin class="full" :loading="loading">
<a-form :model="formModel" <a-form :model="formModel"
ref="formRef" ref="formRef"
label-align="right" label-align="right"

View File

@@ -11,10 +11,13 @@ import { getHostIdentityList } from '@/api/asset/host-identity';
import { getHostList } from '@/api/asset/host'; import { getHostList } from '@/api/asset/host';
import { getHostGroupTree } from '@/api/asset/host-group'; import { getHostGroupTree } from '@/api/asset/host-group';
import { getMenuList } from '@/api/system/menu'; import { getMenuList } from '@/api/system/menu';
import { getCurrentAuthorizedHostIdentity, getCurrentAuthorizedHostKey } from '@/api/asset/asset-authorized-data';
export type CacheType = 'users' | 'menus' | 'roles' export type CacheType = 'users' | 'menus' | 'roles'
| 'host' | 'hostGroups' | 'hostKeys' | 'hostIdentities' | 'host' | 'hostGroups' | 'hostKeys' | 'hostIdentities'
| 'dictKeys' | string | 'dictKeys'
| 'authorizedHostKeys' | 'authorizedHostIdentities'
| string
export default defineStore('cache', { export default defineStore('cache', {
state: (): CacheState => ({}), state: (): CacheState => ({}),
@@ -98,5 +101,15 @@ export default defineStore('cache', {
return await this.load(`${type}_Tags`, () => getTagList(type), force); return await this.load(`${type}_Tags`, () => getTagList(type), force);
}, },
// 获取已授权的主机秘钥列表
async loadAuthorizedHostKeys(force = false) {
return await this.load('authorizedHostKeys', getCurrentAuthorizedHostKey, force);
},
// 获取已授权的主机身份列表
async loadAuthorizedHostIdentities(force = false) {
return await this.load('authorizedHostIdentities', getCurrentAuthorizedHostIdentity, force);
},
} }
}); });

View File

@@ -16,6 +16,8 @@ export interface CacheState {
hostKeys?: HostKeyQueryResponse[]; hostKeys?: HostKeyQueryResponse[];
hostIdentities?: HostIdentityQueryResponse[]; hostIdentities?: HostIdentityQueryResponse[];
dictKeys?: DictKeyQueryResponse[]; dictKeys?: DictKeyQueryResponse[];
authorizedHostKeys?: HostKeyQueryResponse[];
authorizedHostIdentities?: HostIdentityQueryResponse[];
[key: string]: unknown; [key: string]: unknown;
} }

View File

@@ -12,7 +12,7 @@
:cancel-button-props="{ disabled: loading }" :cancel-button-props="{ disabled: loading }"
:on-before-ok="handlerOk" :on-before-ok="handlerOk"
@close="handleClose"> @close="handleClose">
<a-spin :loading="loading"> <a-spin class="full" :loading="loading">
<a-form :model="formModel" <a-form :model="formModel"
ref="formRef" ref="formRef"
label-align="right" label-align="right"

View File

@@ -26,9 +26,9 @@
label="用户名" label="用户名"
:rules="usernameRules" :rules="usernameRules"
label-col-flex="60px" label-col-flex="60px"
:help="AuthType.IDENTITY === formModel.authType ? '将使用主机身份的用户名' : undefined"> :help="SshAuthType.IDENTITY === formModel.authType ? '将使用主机身份的用户名' : undefined">
<a-input v-model="formModel.username" <a-input v-model="formModel.username"
:disabled="AuthType.IDENTITY === formModel.authType" :disabled="SshAuthType.IDENTITY === formModel.authType"
placeholder="请输入用户名" /> placeholder="请输入用户名" />
</a-form-item> </a-form-item>
<!-- SSH 端口 --> <!-- SSH 端口 -->
@@ -48,10 +48,10 @@
<a-radio-group type="button" <a-radio-group type="button"
class="auth-type-group usn" class="auth-type-group usn"
v-model="formModel.authType" v-model="formModel.authType"
:options="toOptions(authTypeKey)" /> :options="toOptions(sshAuthTypeKey)" />
</a-form-item> </a-form-item>
<!-- 主机密码 --> <!-- 主机密码 -->
<a-form-item v-if="AuthType.PASSWORD === formModel.authType" <a-form-item v-if="SshAuthType.PASSWORD === formModel.authType"
field="password" field="password"
label="主机密码" label="主机密码"
:rules="passwordRules" :rules="passwordRules"
@@ -68,7 +68,7 @@
unchecked-text="使用原密码" /> unchecked-text="使用原密码" />
</a-form-item> </a-form-item>
<!-- 主机秘钥 --> <!-- 主机秘钥 -->
<a-form-item v-if="AuthType.KEY === formModel.authType" <a-form-item v-if="SshAuthType.KEY === formModel.authType"
field="keyId" field="keyId"
label="主机秘钥" label="主机秘钥"
:hide-asterisk="true" :hide-asterisk="true"
@@ -76,7 +76,7 @@
<host-key-selector v-model="formModel.keyId" /> <host-key-selector v-model="formModel.keyId" />
</a-form-item> </a-form-item>
<!-- 主机身份 --> <!-- 主机身份 -->
<a-form-item v-if="AuthType.IDENTITY === formModel.authType" <a-form-item v-if="SshAuthType.IDENTITY === formModel.authType"
field="identityId" field="identityId"
label="主机身份" label="主机身份"
:hide-asterisk="true" :hide-asterisk="true"
@@ -140,7 +140,7 @@
import type { HostSshConfig } from './types/const'; import type { HostSshConfig } from './types/const';
import { reactive, ref, watch } from 'vue'; import { reactive, ref, watch } from 'vue';
import { updateHostConfigStatus, updateHostConfig } from '@/api/asset/host-config'; import { updateHostConfigStatus, updateHostConfig } from '@/api/asset/host-config';
import { authTypeKey, AuthType } from './types/const'; import { sshAuthTypeKey, SshAuthType } from './types/const';
import rules from './types/form.rules'; import rules from './types/form.rules';
import { Message } from '@arco-design/web-vue'; import { Message } from '@arco-design/web-vue';
import useLoading from '@/hooks/loading'; import useLoading from '@/hooks/loading';
@@ -170,7 +170,7 @@
username: undefined, username: undefined,
port: undefined, port: undefined,
password: undefined, password: undefined,
authType: AuthType.PASSWORD, authType: SshAuthType.PASSWORD,
keyId: undefined, keyId: undefined,
identityId: undefined, identityId: undefined,
connectTimeout: undefined, connectTimeout: undefined,
@@ -195,7 +195,7 @@
cb('用户名长度不能大于128位'); cb('用户名长度不能大于128位');
return; return;
} }
if (formModel.value.authType !== AuthType.IDENTITY && !value) { if (formModel.value.authType !== SshAuthType.IDENTITY && !value) {
cb('请输入用户名'); cb('请输入用户名');
return; return;
} }

View File

@@ -14,8 +14,8 @@ export interface HostSshConfig {
hasPassword?: boolean; hasPassword?: boolean;
} }
// 主机验证方式 // 主机 ssh 验证方式
export const AuthType = { export const SshAuthType = {
// 密码验证 // 密码验证
PASSWORD: 'PASSWORD', PASSWORD: 'PASSWORD',
// 秘钥验证 // 秘钥验证
@@ -25,7 +25,7 @@ export const AuthType = {
}; };
// 主机验证方式 字典项 // 主机验证方式 字典项
export const authTypeKey = 'hostAuthTypeType'; export const sshAuthTypeKey = 'hostSshAuthType';
// 加载的字典值 // 加载的字典值
export const dictKeys = [authTypeKey]; export const dictKeys = [sshAuthTypeKey];

View File

@@ -12,7 +12,7 @@
:cancel-button-props="{ disabled: loading }" :cancel-button-props="{ disabled: loading }"
:on-before-ok="handlerOk" :on-before-ok="handlerOk"
@close="handleClose"> @close="handleClose">
<a-spin :loading="loading"> <a-spin class="full" :loading="loading">
<a-form :model="formModel" <a-form :model="formModel"
ref="formRef" ref="formRef"
label-align="right" label-align="right"

View File

@@ -60,7 +60,7 @@
:max-length="32" :max-length="32"
:disabled="item.loading" :disabled="item.loading"
size="mini" size="mini"
placeholder="主机别名" :placeholder="`${item.name} (${item.code})`"
@blur="saveAlias(item)" @blur="saveAlias(item)"
@pressEnter="saveAlias(item)" @pressEnter="saveAlias(item)"
@change="saveAlias(item)" @change="saveAlias(item)"
@@ -164,11 +164,12 @@
<script lang="ts" setup> <script lang="ts" setup>
import type { HostQueryResponse } from '@/api/asset/host'; import type { HostQueryResponse } from '@/api/asset/host';
import { ref, nextTick, inject } from 'vue';
import useFavorite from '@/hooks/favorite'; import useFavorite from '@/hooks/favorite';
import { dataColor } from '@/utils'; import { dataColor } from '@/utils';
import { tagColor } from '@/views/asset/host-list/types/const'; import { tagColor } from '@/views/asset/host-list/types/const';
import { ref, nextTick } from 'vue';
import { updateHostAlias } from '@/api/asset/host-extra'; import { updateHostAlias } from '@/api/asset/host-extra';
import { sshModalKey } from '../../types/terminal.const';
const props = defineProps<{ const props = defineProps<{
hostList: Array<HostQueryResponse>, hostList: Array<HostQueryResponse>,
@@ -183,7 +184,7 @@
const clickEditAlias = (item: HostQueryResponse) => { const clickEditAlias = (item: HostQueryResponse) => {
item.editable = true; item.editable = true;
if (!item.alias) { if (!item.alias) {
item.alias = `${item.name} (${item.code})`; item.alias = '';
} }
nextTick(() => { nextTick(() => {
aliasNameInput.value?.focus(); aliasNameInput.value?.focus();
@@ -217,9 +218,7 @@
}; };
// 打开配置 // 打开配置
const openSetting = (item: HostQueryResponse) => { const openSetting = inject<(record: HostQueryResponse) => void>(sshModalKey);
console.log('set', item);
};
// 设置收藏 // 设置收藏
const setFavorite = async (item: HostQueryResponse) => { const setFavorite = async (item: HostQueryResponse) => {

View File

@@ -21,6 +21,8 @@
class="list-view-container" class="list-view-container"
:hostList="hostList" :hostList="hostList"
empty-value="暂无连接记录, 快去体验吧!" /> empty-value="暂无连接记录, 快去体验吧!" />
<!-- 修改主机设置模态框 -->
<ssh-extra-modal ref="sshModal" />
</div> </div>
</template> </template>
@@ -31,12 +33,13 @@
</script> </script>
<script lang="ts" setup> <script lang="ts" setup>
import { onMounted, ref, watch } from 'vue'; import { onMounted, provide, ref, watch } from 'vue';
import { NewConnectionType } from '../../types/terminal.const'; import { NewConnectionType, sshModalKey } from '../../types/terminal.const';
import { AuthorizedHostQueryResponse } from '@/api/asset/asset-authorized-data'; import { AuthorizedHostQueryResponse } from '@/api/asset/asset-authorized-data';
import { HostQueryResponse } from '@/api/asset/host'; import { HostQueryResponse } from '@/api/asset/host';
import HostGroupView from './host-group-view.vue'; import HostGroupView from './host-group-view.vue';
import HostListView from './host-list-view.vue'; import HostListView from './host-list-view.vue';
import SshExtraModal from './ssh-extra-modal.vue';
const props = defineProps<{ const props = defineProps<{
hosts: AuthorizedHostQueryResponse, hosts: AuthorizedHostQueryResponse,
@@ -51,6 +54,12 @@
? props.hosts.groupTree[0].key ? props.hosts.groupTree[0].key
: 0 : 0
); );
const sshModal = ref();
// 暴露打开 ssh 配置模态框
provide(sshModalKey, (record: any) => {
sshModal.value?.open(record);
});
// 主机数据处理 // 主机数据处理
const shuffleHosts = () => { const shuffleHosts = () => {

View File

@@ -0,0 +1,146 @@
<template>
<a-modal v-model:visible="visible"
body-class="modal-form"
title-align="start"
:title="title"
:top="80"
: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"
:style="{ width: '460px' }"
:label-col-props="{ span: 6 }"
:wrapper-col-props="{ span: 18 }"
:rules="{}">
<!-- 验证方式 -->
<a-form-item field="authType" label="验证方式">
<a-radio-group type="button"
v-model="formModel.authType"
:options="toOptions(extraSshAuthTypeKey)" />
</a-form-item>
<!-- 用户名 -->
<a-form-item v-if="formModel.authType === ExtraSshAuthType.CUSTOM_KEY"
field="username"
label="用户名">
<a-input v-model="formModel.username" placeholder="请输入用户名" />
</a-form-item>
<!-- 主机秘钥 -->
<a-form-item v-if="formModel.authType === ExtraSshAuthType.CUSTOM_KEY"
field="keyId"
label="主机秘钥"
:rules="{ required: true, message: '请选择主机秘钥' }">
<host-key-selector v-model="formModel.keyId"
:authorized="true" />
</a-form-item>
<!-- 主机身份 -->
<a-form-item v-if="formModel.authType === ExtraSshAuthType.CUSTOM_IDENTITY"
field="identityId"
label="主机身份"
:rules="{ required: true, message: '请选择主机身份' }">
<host-identity-selector v-model="formModel.identityId"
:authorized="true" />
</a-form-item>
</a-form>
</a-spin>
</a-modal>
</template>
<script lang="ts">
export default {
name: 'ssh-extra-modal'
};
</script>
<script lang="ts" setup>
import type { HostQueryResponse } from '@/api/asset/host';
import type { SshExtraModel } from '../../types/terminal.const';
import { ref } from 'vue';
import useLoading from '@/hooks/loading';
import useVisible from '@/hooks/visible';
import { Message } from '@arco-design/web-vue';
import { getHostExtraItem, updateHostExtra } from '@/api/asset/host-extra';
import { ExtraSshAuthType, extraSshAuthTypeKey } from '../../types/terminal.const';
import { useDictStore } from '@/store';
import HostIdentitySelector from '@/components/asset/host-identity/host-identity-selector.vue';
import HostKeySelector from '@/components/asset/host-key/host-key-selector.vue';
const { toOptions } = useDictStore();
const { visible, setVisible } = useVisible();
const { loading, setLoading } = useLoading();
const title = ref<string>();
const hostId = ref<number>();
const formRef = ref();
const formModel = ref<SshExtraModel>({});
// 打开配置
const open = (record: HostQueryResponse) => {
hostId.value = record.id;
title.value = record.alias || `${record.name} (${record.code})`;
renderForm();
setVisible(true);
};
defineExpose({ open });
// 渲染表单
const renderForm = async () => {
try {
setLoading(true);
const { data } = await getHostExtraItem<SshExtraModel>({ hostId: hostId.value, item: 'ssh' });
formModel.value = data;
} catch (e) {
} finally {
setLoading(false);
}
};
// 确定
const handlerOk = async () => {
setLoading(true);
try {
// 验证参数
const error = await formRef.value.validate();
if (error) {
return false;
}
// 修改
await updateHostExtra({
hostId: hostId.value,
item: 'ssh',
extra: JSON.stringify(formModel.value)
});
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

@@ -34,9 +34,9 @@
<script lang="ts" setup> <script lang="ts" setup>
import type { TabItem } from './types/terminal.const'; import type { TabItem } from './types/terminal.const';
import { ref, onBeforeMount } from 'vue'; import { ref, onBeforeMount, onUnmounted } from 'vue';
import { TabType, InnerTabs, dictKeys } from './types/terminal.const'; import { TabType, InnerTabs, dictKeys } from './types/terminal.const';
import { useDictStore, useTerminalStore } from '@/store'; import { useCacheStore, useDictStore, useTerminalStore } from '@/store';
import TerminalHeader from './components/layout/terminal-header.vue'; import TerminalHeader from './components/layout/terminal-header.vue';
import TerminalLeftSidebar from './components/layout/terminal-left-sidebar.vue'; import TerminalLeftSidebar from './components/layout/terminal-left-sidebar.vue';
import TerminalRightSidebar from './components/layout/terminal-right-sidebar.vue'; import TerminalRightSidebar from './components/layout/terminal-right-sidebar.vue';
@@ -46,6 +46,7 @@
const terminalStore = useTerminalStore(); const terminalStore = useTerminalStore();
const dictStore = useDictStore(); const dictStore = useDictStore();
const cacheStore = useCacheStore();
const render = ref(false); const render = ref(false);
const activeKey = ref(InnerTabs.NEW_CONNECTION.key); const activeKey = ref(InnerTabs.NEW_CONNECTION.key);
@@ -93,6 +94,11 @@
await dictStore.loadKeys([...dictKeys]); await dictStore.loadKeys([...dictKeys]);
}); });
// 卸载时清除 cache
onUnmounted(() => {
cacheStore.reset('authorizedHostKeys', 'authorizedHostIdentities');
});
</script> </script>
<style lang="less" scoped> <style lang="less" scoped>

View File

@@ -51,6 +51,27 @@ export const NewConnectionType = {
LATEST: 'latest' LATEST: 'latest'
}; };
// ssh 额外配置
export interface SshExtraModel {
authType?: string;
username?: string;
keyId?: number;
identityId?: number;
}
// 主机额外配置 ssh 认证方式
export const ExtraSshAuthType = {
// 使用默认认证方式
DEFAULT: 'DEFAULT',
// 自定义秘钥
CUSTOM_KEY: 'CUSTOM_KEY',
// 自定义身份
CUSTOM_IDENTITY: 'CUSTOM_IDENTITY',
};
// 打开 sshModal key
export const sshModalKey = Symbol();
// 字体后缀 兜底 // 字体后缀 兜底
export const fontFamilySuffix = ',courier-new, courier, monospace'; export const fontFamilySuffix = ',courier-new, courier, monospace';
@@ -72,9 +93,13 @@ export const cursorStyleKey = 'terminalCursorStyle';
// 终端新建连接类型 // 终端新建连接类型
export const NewConnectionTypeKey = 'terminalNewConnectionType'; export const NewConnectionTypeKey = 'terminalNewConnectionType';
// 终端新建连接类型
export const extraSshAuthTypeKey = 'hostExtraSshAuthType';
// 加载的字典值 // 加载的字典值
export const dictKeys = [ export const dictKeys = [
darkThemeKey, fontFamilyKey, darkThemeKey, fontFamilyKey,
fontSizeKey, fontWeightKey, fontSizeKey, fontWeightKey,
cursorStyleKey, NewConnectionTypeKey cursorStyleKey, NewConnectionTypeKey,
extraSshAuthTypeKey
]; ];

View File

@@ -13,7 +13,7 @@
:cancel-button-props="{ disabled: loading }" :cancel-button-props="{ disabled: loading }"
:on-before-ok="handlerOk" :on-before-ok="handlerOk"
@close="handleClose"> @close="handleClose">
<a-spin :loading="loading"> <a-spin class="full" :loading="loading">
<a-form :model="formModel" <a-form :model="formModel"
ref="formRef" ref="formRef"
label-align="right" label-align="right"

View File

@@ -12,7 +12,7 @@
:cancel-button-props="{ disabled: loading }" :cancel-button-props="{ disabled: loading }"
:on-before-ok="handlerOk" :on-before-ok="handlerOk"
@close="handleClose"> @close="handleClose">
<a-spin :loading="loading"> <a-spin class="full" :loading="loading">
<a-form :model="formModel" <a-form :model="formModel"
ref="formRef" ref="formRef"
label-align="right" label-align="right"

View File

@@ -12,7 +12,7 @@
:cancel-button-props="{ disabled: loading }" :cancel-button-props="{ disabled: loading }"
:on-before-ok="handlerOk" :on-before-ok="handlerOk"
@close="handleClose"> @close="handleClose">
<a-spin :loading="loading"> <a-spin class="full" :loading="loading">
<a-form :model="formModel" <a-form :model="formModel"
ref="formRef" ref="formRef"
label-align="right" label-align="right"

View File

@@ -12,7 +12,7 @@
:cancel-button-props="{ disabled: loading }" :cancel-button-props="{ disabled: loading }"
:on-before-ok="handlerOk" :on-before-ok="handlerOk"
@close="handleClose"> @close="handleClose">
<a-spin :loading="loading"> <a-spin class="full" :loading="loading">
<a-form :model="formModel" <a-form :model="formModel"
ref="formRef" ref="formRef"
label-align="right" label-align="right"

View File

@@ -12,7 +12,7 @@
:cancel-button-props="{ disabled: loading }" :cancel-button-props="{ disabled: loading }"
:on-before-ok="handlerOk" :on-before-ok="handlerOk"
@close="handleClose"> @close="handleClose">
<a-spin :loading="loading"> <a-spin class="full" :loading="loading">
<a-form :model="formModel" <a-form :model="formModel"
ref="formRef" ref="formRef"
label-align="right" label-align="right"

View File

@@ -12,7 +12,7 @@
:cancel-button-props="{ disabled: loading }" :cancel-button-props="{ disabled: loading }"
:on-before-ok="handlerOk" :on-before-ok="handlerOk"
@close="handleClose"> @close="handleClose">
<a-spin :loading="loading"> <a-spin class="full" :loading="loading">
<a-form :model="formModel" <a-form :model="formModel"
ref="formRef" ref="formRef"
label-align="right" label-align="right"

View File

@@ -12,7 +12,7 @@
:cancel-button-props="{ disabled: loading }" :cancel-button-props="{ disabled: loading }"
:on-before-ok="handlerOk" :on-before-ok="handlerOk"
@close="handleClose"> @close="handleClose">
<a-spin :loading="loading"> <a-spin class="full" :loading="loading">
<a-form :model="formModel" <a-form :model="formModel"
ref="formRef" ref="formRef"
label-align="right" label-align="right"

View File

@@ -127,6 +127,7 @@
<!-- 分配角色 --> <!-- 分配角色 -->
<a-button type="text" <a-button type="text"
size="mini" size="mini"
:disabled="record.id === userStore.id"
v-permission="['infra:system-user:grant-role']" v-permission="['infra:system-user:grant-role']"
@click="emits('openGrantRole', record)"> @click="emits('openGrantRole', record)">
分配角色 分配角色