添加修改主机配置页面.

This commit is contained in:
lijiahang
2023-09-22 11:50:56 +08:00
parent 9398f0f4fd
commit fdc3bc6147
27 changed files with 366 additions and 46 deletions

View File

@@ -1,5 +1,4 @@
import axios from 'axios';
import qs from 'query-string';
import { DataGrid, Pagination } from '@/types/global';
/**
@@ -71,7 +70,7 @@ export function getHostIdentity(id: number) {
* 查询主机身份
*/
export function getHostIdentityList() {
return axios.post<Array<HostIdentityQueryResponse>>('/asset/host-identity/list');
return axios.get<Array<HostIdentityQueryResponse>>('/asset/host-identity/list');
}
/**

View File

@@ -67,7 +67,7 @@ export function getHostKey(id: number) {
* 查询主机秘钥
*/
export function getHostKeyList() {
return axios.post<Array<HostKeyQueryResponse>>('/asset/host-key/list');
return axios.get<Array<HostKeyQueryResponse>>('/asset/host-key/list');
}
/**

View File

@@ -0,0 +1,52 @@
<template>
<a-select v-model:model-value="value"
:options="optionData()"
placeholder="请选择主机身份"
allow-clear />
</template>
<script lang="ts">
export default {
name: 'host-identity-selector'
};
</script>
<script lang="ts" setup>
import { computed } from 'vue';
import { useCacheStore } from '@/store';
import { SelectOptionData } from '@arco-design/web-vue';
const props = defineProps({
modelValue: Number,
});
const emits = defineEmits(['update:modelValue']);
const value = computed({
get() {
return props.modelValue;
},
set(e) {
if (e) {
emits('update:modelValue', e);
} else {
emits('update:modelValue', null);
}
}
});
// 选项数据
const cacheStore = useCacheStore();
const optionData = (): SelectOptionData[] => {
return cacheStore.hostIdentities.map(s => {
return {
label: `${s.name} (${s.username})`,
value: s.id,
};
});
};
</script>
<style scoped>
</style>

View File

@@ -17,6 +17,7 @@ export default function useFavorite(type: FavoriteType) {
record[cancelField] = true;
Message.success('已收藏');
}
} catch (e) {
} finally {
loading.close();
}

View File

@@ -1,7 +1,7 @@
import { defineStore } from 'pinia';
import { CacheState } from './types';
export type CacheType = 'menus' | 'roles' | 'tags' | 'hostKeys'
export type CacheType = 'menus' | 'roles' | 'tags' | 'hostKeys' | 'hostIdentities'
const useCacheStore = defineStore('cache', {
state: (): CacheState => ({
@@ -9,6 +9,7 @@ const useCacheStore = defineStore('cache', {
roles: [],
tags: [],
hostKeys: [],
hostIdentities: [],
}),
getters: {},

View File

@@ -2,12 +2,14 @@ import { MenuQueryResponse } from '@/api/system/menu';
import { RoleQueryResponse } from '@/api/user/role';
import { TagQueryResponse } from '@/api/meta/tag';
import { HostKeyQueryResponse } from '@/api/asset/host-key';
import { HostIdentityQueryResponse } from '@/api/asset/host-identity';
export interface CacheState {
menus: MenuQueryResponse[];
roles: RoleQueryResponse[];
tags: TagQueryResponse[];
hostKeys: HostKeyQueryResponse[];
hostIdentities: HostIdentityQueryResponse[];
[key: string]: unknown;
}

View File

@@ -70,6 +70,7 @@ const useUserStore = defineStore('user', {
async logout() {
try {
await userLogout();
} catch (e) {
} finally {
// 登出回调
this.logoutCallBack();

View File

@@ -109,7 +109,9 @@
// 渲染表单
const renderForm = (record: any) => {
Object.keys(formModel).forEach(k => {
formModel[k] = record[k];
if (record.hasOwnProperty(k)) {
formModel[k] = record[k];
}
});
};

View File

@@ -156,6 +156,7 @@
Message.success('删除成功');
// 重新加载数据
await fetchTableData();
} catch (e) {
} finally {
setLoading(false);
}
@@ -184,6 +185,7 @@
pagination.total = data.total;
pagination.current = request.page;
pagination.pageSize = request.limit;
} catch (e) {
} finally {
setLoading(false);
}
@@ -197,8 +199,11 @@
// 获取主机秘钥列表
const fetchHostKeyList = async () => {
const { data } = await getHostKeyList();
cacheStore.set('hostKeys', data);
try {
const { data } = await getHostKeyList();
cacheStore.set('hostKeys', data);
} catch (e) {
}
};
fetchHostKeyList();

View File

@@ -163,7 +163,9 @@
// 渲染表单
const renderForm = (record: any) => {
Object.keys(formModel).forEach(k => {
formModel[k] = record[k];
if (record.hasOwnProperty(k)) {
formModel[k] = record[k];
}
});
};

View File

@@ -124,6 +124,7 @@
Message.success('删除成功');
// 重新加载数据
await fetchTableData();
} catch (e) {
} finally {
setLoading(false);
}
@@ -152,6 +153,7 @@
pagination.total = data.total;
pagination.current = request.page;
pagination.pageSize = request.limit;
} catch (e) {
} finally {
setLoading(false);
}

View File

@@ -35,9 +35,13 @@
import { Message } from '@arco-design/web-vue';
import { getHostConfigAll } from '@/api/asset/host';
import HostConfigSshForm from './host-config-ssh-form.vue';
import { useCacheStore } from '@/store';
import { getHostKeyList } from '@/api/asset/host-key';
import { getHostIdentityList } from '@/api/asset/host-identity';
const { visible, setVisible } = useVisible();
const { loading, setLoading } = useLoading();
const cacheStore = useCacheStore();
const record = ref();
const config = ref<Record<string, any>>({});
@@ -61,23 +65,34 @@
}
};
const handleOk = () => {
console.log('ok');
setLoading(true);
setTimeout(() => {
setVisible(false);
setLoading(false);
}, 1000);
};
// 关闭
const handleCancel = () => {
console.log('cancel');
setLoading(false);
setVisible(false);
};
defineExpose({ open });
// 加载主机秘钥
const fetchHostKeys = async () => {
try {
const { data } = await getHostKeyList();
cacheStore.set('hostKeys', data);
} catch (e) {
}
};
fetchHostKeys();
// 加载主机身份
const fetchHostIdentities = async () => {
try {
const { data } = await getHostIdentityList();
cacheStore.set('hostIdentities', data);
} catch (e) {
}
};
fetchHostIdentities();
</script>
<style lang="less" scoped>

View File

@@ -20,26 +20,72 @@
label-align="right"
:label-col-props="{ span: 6 }"
:wrapper-col-props="{ span: 18 }"
:rules="{}">
:rules="sshRules">
<!-- 用户名 -->
<a-form-item field="username" label="用户名" label-col-flex="60px">
<a-input v-model="formModel.username" placeholder="请输入用户名" />
<a-form-item field="username"
label="用户名"
:rules="usernameRules"
label-col-flex="60px"
:help="AuthTypeEnum.IDENTITY.value === formModel.authType ? '将使用主机身份的用户名' : null">
<a-input v-model="formModel.username"
:disabled="AuthTypeEnum.IDENTITY.value === formModel.authType"
placeholder="请输入用户名" />
</a-form-item>
<!-- SSH 端口 -->
<a-form-item field="port" label="SSH端口" label-col-flex="60px">
<a-form-item field="port"
label="SSH端口"
:hide-asterisk="true"
label-col-flex="60px">
<a-input-number v-model="formModel.port" placeholder="请输入SSH端口" />
</a-form-item>
<!-- 主机密码 -->
<a-form-item field="password" label="主机密码" label-col-flex="60px">
<a-input-password v-model="formModel.password" placeholder="主机密码 / 身份二选一" />
<a-button type="text" class="password-choose-text">选择</a-button>
<!-- 验证方式 -->
<a-form-item field="authType"
label="验证方式"
:hide-asterisk="true"
label-col-flex="60px">
<a-radio-group type="button"
class="auth-type-group"
v-model="formModel.authType"
:options="toOptions(AuthTypeEnum)" />
</a-form-item>
<!-- 身份验证 -->
<a-form-item field="identityId" label="身份验证" label-col-flex="60px">
<a-input v-model="formModel.identityId" placeholder="" />
<!-- 主机密码 -->
<a-form-item v-if="AuthTypeEnum.PASSWORD.value === formModel.authType"
field="password"
label="主机密码"
:rules="passwordRules"
label-col-flex="60px">
<a-input-password v-model="formModel.password"
:disabled="!formModel.useNewPassword && formModel.hasPassword"
placeholder="主机密码" />
<a-switch v-if="formModel.hasPassword"
v-model="formModel.useNewPassword"
class="password-switch"
type="round"
size="large"
checked-text="使用新密码"
unchecked-text="使用原密码" />
</a-form-item>
<!-- 主机秘钥 -->
<a-form-item v-if="AuthTypeEnum.KEY.value === formModel.authType"
field="keyId"
label="主机秘钥"
:hide-asterisk="true"
label-col-flex="60px">
<host-key-selector v-model="formModel.keyId" />
</a-form-item>
<!-- 主机身份 -->
<a-form-item v-if="AuthTypeEnum.IDENTITY.value === formModel.authType"
field="identityId"
label="主机身份"
:hide-asterisk="true"
label-col-flex="60px">
<host-identity-selector v-model="formModel.identityId" />
</a-form-item>
<!-- 用户名 -->
<a-form-item field="connectTimeout" label="连接超时时间" label-col-flex="86px">
<a-form-item field="connectTimeout"
label="连接超时时间"
:hide-asterisk="true"
label-col-flex="86px">
<a-input-number v-model="formModel.connectTimeout" placeholder="请输入连接超时时间">
<template #suffix>
ms
@@ -47,15 +93,26 @@
</a-input-number>
</a-form-item>
<!-- 用户名 -->
<a-form-item field="charset" label="SSH输出编码" label-col-flex="86px">
<a-form-item field="charset"
label="SSH输出编码"
:hide-asterisk="true"
label-col-flex="86px">
<a-input v-model="formModel.charset" placeholder="请输入 SSH 输出编码" />
</a-form-item>
<!-- 文件名称编码 -->
<a-form-item field="fileNameCharset" label="文件名称编码" label-col-flex="86px">
<a-form-item field="fileNameCharset"
label="文件名称编码"
:hide-asterisk="true"
label-col-flex="86px">
<a-input v-model="formModel.fileNameCharset" placeholder="请输入 SFTP 文件名称编码" />
</a-form-item>
<!-- 文件内容编码 -->
<a-form-item field="fileContentCharset" label="文件内容编码" label-col-flex="86px">
<a-form-item field="fileContentCharset"
label="文件内容编码"
:hide-asterisk="true"
label-col-flex="86px">
<a-input v-model="formModel.fileContentCharset" placeholder="请输入 SFTP 文件内容编码" />
</a-form-item>
</a-form>
@@ -80,6 +137,12 @@
import { reactive, ref, watch } from 'vue';
import useLoading from '@/hooks/loading';
import { updateHostConfigStatus, updateHostConfig } from '@/api/asset/host';
import { HostSshConfig, AuthTypeEnum } from '@/views/asset/host/types/host-config.types';
import { sshRules } from '@/views/asset/host/types/host-config.rules';
import HostKeySelector from '@/components/asset/host-key/host-key-selector.vue';
import HostIdentitySelector from '@/components/asset/host-identity/host-identity-selector.vue';
import { toOptions } from '@/utils/enum';
import { FieldRule, Message } from '@arco-design/web-vue';
const { loading, setLoading } = useLoading();
@@ -95,15 +158,19 @@
});
const formRef = ref<any>();
const formModel = reactive<Record<string, any>>({
const formModel = reactive<HostSshConfig & Record<string, any>>({
username: undefined,
port: undefined,
password: undefined,
authType: AuthTypeEnum.PASSWORD.value,
keyId: undefined,
identityId: undefined,
connectTimeout: undefined,
charset: undefined,
fileNameCharset: undefined,
fileContentCharset: undefined,
useNewPassword: false,
hasPassword: false,
});
// 监听数据变化
@@ -113,6 +180,34 @@
resetConfig();
});
// 用户名验证
const usernameRules = [{
validator: (value, cb) => {
if (value && value.length > 128) {
cb('用户名长度不能大于128位');
return;
}
if (formModel.authType !== AuthTypeEnum.IDENTITY.value && !value) {
cb('请输入用户名');
return;
}
}
}] as FieldRule[];
// 密码验证
const passwordRules = [{
validator: (value, cb) => {
if (value && value.length > 256) {
cb('密码长度不能大于256位');
return;
}
if (formModel.useNewPassword && !value) {
cb('请输入密码');
return;
}
}
}] as FieldRule[];
// 修改状态
const updateStatus = (e: number) => {
setLoading(true);
@@ -133,8 +228,12 @@
// 重置配置
const resetConfig = () => {
Object.keys(formModel).forEach(k => {
formModel[k] = props.content?.config[k];
if (props.content?.config?.hasOwnProperty(k)) {
formModel[k] = props.content?.config[k];
}
});
// 使用新密码默认为不包含密码
formModel.useNewPassword = !formModel.hasPassword;
};
// 保存配置
@@ -153,8 +252,10 @@
});
config.value.version = data;
setLoading(false);
Message.success('修改成功');
// 回调 props
emits('submitted', { ...formModel });
} catch (e) {
} finally {
setLoading(false);
}
@@ -173,14 +274,20 @@
width: 100%;
}
.password-choose-text {
margin-left: 6px;
padding: 0 4px 0 4px;
}
.config-button-group {
display: flex;
align-items: center;
justify-content: flex-end;
}
.auth-type-group {
width: 100%;
display: flex;
justify-content: space-between;
}
.password-switch {
width: 148px;
margin-left: 8px;
}
</style>

View File

@@ -101,7 +101,9 @@
// 渲染表单
const renderForm = (record: any) => {
Object.keys(formModel).forEach(k => {
formModel[k] = record[k];
if (record.hasOwnProperty(k)) {
formModel[k] = record[k];
}
});
};

View File

@@ -197,6 +197,7 @@
Message.success('删除成功');
// 重新加载数据
await fetchTableData();
} catch (e) {
} finally {
setLoading(false);
}
@@ -225,6 +226,7 @@
pagination.total = data.total;
pagination.current = request.page;
pagination.pageSize = request.limit;
} catch (e) {
} finally {
setLoading(false);
}

View File

@@ -35,6 +35,8 @@
onUnmounted(() => {
const cacheStore = useCacheStore();
cacheStore.set('tags', []);
cacheStore.set('hostKeys', []);
cacheStore.set('hostIdentities', []);
});
</script>

View File

@@ -0,0 +1,71 @@
import { FieldRule } from '@arco-design/web-vue';
export const port = [{
required: true,
message: '请输入SSH端口'
}, {
type: 'number',
min: 1,
max: 65535,
message: '输入的端口不合法'
}] as FieldRule[];
export const authType = [{
required: true,
message: '请选择认证方式'
}] as FieldRule[];
export const keyId = [{
required: true,
message: '请选择主机秘钥'
}] as FieldRule[];
export const identityId = [{
required: true,
message: '请选择主机身份'
}] as FieldRule[];
export const connectTimeout = [{
required: true,
message: '请输入连接超时时间'
}, {
type: 'number',
min: 0,
max: 100000,
message: '连接超时时间需要在 0 - 100000 之间'
}] as FieldRule[];
export const charset = [{
required: true,
message: '请输入SSH输出编码'
}, {
maxLength: 12,
message: 'SSH输出编码长度不能超过12位'
}] as FieldRule[];
export const fileNameCharset = [{
required: true,
message: '请输入文件名称编码'
}, {
maxLength: 12,
message: '文件名称编码长度不能超过12位'
}] as FieldRule[];
export const fileContentCharset = [{
required: true,
message: '请输入SSH输出编码'
}, {
maxLength: 12,
message: '文件内容编码长度不能超过12位'
}] as FieldRule[];
export const sshRules = {
port,
authType,
keyId,
identityId,
connectTimeout,
charset,
fileNameCharset,
fileContentCharset,
} as Record<string, FieldRule | FieldRule[]>;

View File

@@ -0,0 +1,33 @@
/**
* 验证方式
*/
export const AuthTypeEnum = {
PASSWORD: {
value: 'PASSWORD',
label: '密码验证',
},
KEY: {
value: 'KEY',
label: '秘钥验证',
},
IDENTITY: {
value: 'IDENTITY',
label: '身份验证',
},
};
/**
* 主机 SSH 配置
*/
export interface HostSshConfig {
port?: number;
username?: string;
password?: string;
authType?: string;
identityId?: number;
keyId?: number;
connectTimeout?: number;
charset?: string;
fileNameCharset?: string;
fileContentCharset?: string;
}

View File

@@ -186,7 +186,9 @@
// 渲染表单
const renderForm = (record: any) => {
Object.keys(formModel).forEach(k => {
formModel[k] = record[k];
if (record.hasOwnProperty(k)) {
formModel[k] = record[k];
}
});
};

View File

@@ -235,6 +235,7 @@
}
}
Message.success('删除成功');
} catch (e) {
} finally {
setFetchLoading(false);
}
@@ -261,6 +262,7 @@
const { data } = await getMenuList(formModel);
tableRenderData.value = data as MenuQueryResponse[];
cacheStore.set('menus', tableRenderData.value);
} catch (e) {
} finally {
setFetchLoading(false);
}
@@ -284,6 +286,7 @@
setFetchLoading(true);
await initCache();
Message.success('刷新成功');
} catch (e) {
} finally {
setFetchLoading(false);
}

View File

@@ -85,7 +85,9 @@
// 渲染表单
const renderForm = (record: any) => {
Object.keys(formModel).forEach(k => {
formModel[k] = record[k];
if (record.hasOwnProperty(k)) {
formModel[k] = record[k];
}
});
};

View File

@@ -79,6 +79,7 @@
// 获取角色菜单
const { data: roleMenuIdList } = await getRoleMenuId(record.id);
tree.value.init(roleMenuIdList);
} catch (e) {
} finally {
setLoading(false);
}

View File

@@ -159,6 +159,7 @@
Message.success(`${toggleStatus.label}成功`);
// 修改行状态
record.status = toggleStatus.value;
} catch (e) {
} finally {
setLoading(false);
}
@@ -173,6 +174,7 @@
Message.success('删除成功');
// 重新加载数据
await fetchTableData();
} catch (e) {
} finally {
setLoading(false);
}
@@ -201,6 +203,7 @@
pagination.total = data.total;
pagination.current = request.page;
pagination.pageSize = request.limit;
} catch (e) {
} finally {
setLoading(false);
}

View File

@@ -103,7 +103,9 @@
// 渲染表单
const renderForm = (record: any) => {
Object.keys(formModel).forEach(k => {
formModel[k] = record[k];
if (record.hasOwnProperty(k)) {
formModel[k] = record[k];
}
});
};

View File

@@ -81,7 +81,9 @@
// 渲染表单
const renderForm = (record: any) => {
Object.keys(formModel).forEach(k => {
formModel[k] = record[k];
if (record.hasOwnProperty(k)) {
formModel[k] = record[k];
}
});
};
@@ -97,6 +99,7 @@
// 加载用户角色
const { data: roleIdList } = await getUserRoleIdList(formModel.id);
formModel.roleIdList = roleIdList;
} catch (e) {
} finally {
setRoleLoading(false);
}

View File

@@ -75,7 +75,9 @@
// 渲染表单
const renderForm = (record: any) => {
Object.keys(formModel).forEach(k => {
formModel[k] = record[k];
if (record.hasOwnProperty(k)) {
formModel[k] = record[k];
}
});
};

View File

@@ -187,6 +187,7 @@
Message.success('删除成功');
// 重新加载数据
await fetchTableData();
} catch (e) {
} finally {
setLoading(false);
}
@@ -206,6 +207,7 @@
});
Message.success(`${newStatus.label}成功`);
record.status = newStatus.value;
} catch (e) {
} finally {
setLoading(false);
}
@@ -234,6 +236,7 @@
pagination.total = data.total;
pagination.current = request.page;
pagination.pageSize = request.limit;
} catch (e) {
} finally {
setLoading(false);
}