✨ 添加主机身份类型.
This commit is contained in:
@@ -6,10 +6,17 @@
|
|||||||
|
|
||||||
`2024-04-` `release`
|
`2024-04-` `release`
|
||||||
|
|
||||||
* 🔨 优化 定时删除未引用的 `tag`
|
* 🐞 修复 用户列表用户名显示错误
|
||||||
* 🔨 优化 命令执行日志时间不自增
|
* 🌈 新增 定时删除未引用的 `tag`
|
||||||
|
* 🌈 新增 执行命令时可使用脚本文件执行
|
||||||
|
* 🌈 新增 主机身份添加类型字段
|
||||||
|
* 🔨 优化 文件传输列表进度显示
|
||||||
|
* 🔨 优化 命令执行日志持续时间
|
||||||
* 🔨 优化 tracker 监听文件可配置 `app.tracker`
|
* 🔨 优化 tracker 监听文件可配置 `app.tracker`
|
||||||
* 🔨 优化 sftp 上传文件重复处理可配置 `app.sftp`
|
* 🔨 优化 sftp 上传文件重复处理可配置 `app.sftp`
|
||||||
|
* 🔨 优化 用户状态调整交互逻辑
|
||||||
|
* 🔨 优化 角色状态调整交互逻辑
|
||||||
|
* 🔨 删除 用户锁定状态
|
||||||
|
|
||||||
[如何升级](/update/v1.0.5.md)
|
[如何升级](/update/v1.0.5.md)
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,7 @@
|
|||||||
## 功能排期 ⏳
|
## 功能排期 ⏳
|
||||||
|
|
||||||
* 优化文件传输列表进度显示
|
|
||||||
* 主机身份类型
|
|
||||||
* 终端断开连接后回车重新连接
|
|
||||||
* 使用文件执行命令
|
|
||||||
* 管理员也需要自行授权资产
|
* 管理员也需要自行授权资产
|
||||||
|
* 快捷命令导入
|
||||||
* 文件夹书签
|
* 文件夹书签
|
||||||
* 批量上传
|
* 批量上传
|
||||||
* 站内消息
|
* 站内消息
|
||||||
|
|||||||
@@ -3,9 +3,33 @@
|
|||||||
> sql 脚本 - DDL
|
> sql 脚本 - DDL
|
||||||
|
|
||||||
```sql
|
```sql
|
||||||
|
ALTER TABLE `system_user`
|
||||||
|
MODIFY COLUMN `status` tinyint(0) NULL DEFAULT 1 COMMENT '用户状态 0停用 1启用' AFTER `email`;
|
||||||
|
|
||||||
|
ALTER TABLE `host_identity`
|
||||||
|
ADD COLUMN `type` char(12) NULL COMMENT '类型' AFTER `name`;
|
||||||
|
|
||||||
|
ALTER TABLE `exec_log`
|
||||||
|
ADD COLUMN `script_exec` tinyint(0) NULL DEFAULT 0 COMMENT '是否使用脚本执行' AFTER `timeout`;
|
||||||
|
|
||||||
|
ALTER TABLE `exec_job`
|
||||||
|
ADD COLUMN `script_exec` tinyint(0) NULL DEFAULT 0 COMMENT '是否使用脚本执行' AFTER `timeout`;
|
||||||
|
|
||||||
|
ALTER TABLE `exec_template`
|
||||||
|
ADD COLUMN `script_exec` tinyint(0) NULL DEFAULT 0 COMMENT '是否使用脚本执行' AFTER `timeout`;
|
||||||
|
|
||||||
|
ALTER TABLE `exec_host_log`
|
||||||
|
ADD COLUMN `script_path` varchar(512) NULL COMMENT '脚本路径' AFTER `log_path`;
|
||||||
```
|
```
|
||||||
|
|
||||||
> sql 脚本 - DML
|
> sql 脚本 - DML
|
||||||
|
|
||||||
```sql
|
```sql
|
||||||
|
-- 初始化主机身份类型
|
||||||
|
UPDATE `host_identity` SET type = IF(key_id IS NOT NULL, 'KEY', 'PASSWORD');
|
||||||
|
-- 重新设置用户状态
|
||||||
|
UPDATE `system_user` SET status = 0 WHERE status = 2;
|
||||||
|
DELETE FROM `dict_value` WHERE id = 19;
|
||||||
|
-- 设置主机配置中的 osType
|
||||||
|
UPDATE host_config SET config = JSON_SET(config, '$.osType', 'LINUX') WHERE type = 'ssh' AND deleted = 0;
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -3,22 +3,9 @@
|
|||||||
> sql 脚本 - DDL
|
> sql 脚本 - DDL
|
||||||
|
|
||||||
```sql
|
```sql
|
||||||
ALTER TABLE `exec_log`
|
|
||||||
ADD COLUMN `script_exec` tinyint(0) NULL DEFAULT 0 COMMENT '是否使用脚本执行' AFTER `timeout`;
|
|
||||||
|
|
||||||
ALTER TABLE `exec_job`
|
|
||||||
ADD COLUMN `script_exec` tinyint(0) NULL DEFAULT 0 COMMENT '是否使用脚本执行' AFTER `timeout`;
|
|
||||||
|
|
||||||
ALTER TABLE `exec_template`
|
|
||||||
ADD COLUMN `script_exec` tinyint(0) NULL DEFAULT 0 COMMENT '是否使用脚本执行' AFTER `timeout`;
|
|
||||||
|
|
||||||
ALTER TABLE `exec_host_log`
|
|
||||||
ADD COLUMN `script_path` varchar(512) NULL COMMENT '脚本路径' AFTER `log_path`;
|
|
||||||
```
|
```
|
||||||
|
|
||||||
> sql 脚本 - DML
|
> sql 脚本 - DML
|
||||||
|
|
||||||
```sql
|
```sql
|
||||||
-- 设置主机配置中的 osType
|
|
||||||
UPDATE host_config SET config = JSON_SET(config, '$.osType', 'LINUX') WHERE type = 'ssh' AND deleted = 0;
|
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -31,6 +31,10 @@ public class HostIdentityDO extends BaseDO {
|
|||||||
@TableField("name")
|
@TableField("name")
|
||||||
private String name;
|
private String name;
|
||||||
|
|
||||||
|
@Schema(description = "类型")
|
||||||
|
@TableField("type")
|
||||||
|
private String type;
|
||||||
|
|
||||||
@Schema(description = "用户名")
|
@Schema(description = "用户名")
|
||||||
@TableField("username")
|
@TableField("username")
|
||||||
private String username;
|
private String username;
|
||||||
|
|||||||
@@ -30,6 +30,9 @@ public class HostIdentityCacheDTO implements LongCacheIdModel, Serializable {
|
|||||||
@Schema(description = "名称")
|
@Schema(description = "名称")
|
||||||
private String name;
|
private String name;
|
||||||
|
|
||||||
|
@Schema(description = "类型")
|
||||||
|
private String type;
|
||||||
|
|
||||||
@Schema(description = "用户名")
|
@Schema(description = "用户名")
|
||||||
private String username;
|
private String username;
|
||||||
|
|
||||||
|
|||||||
@@ -29,6 +29,11 @@ public class HostIdentityCreateRequest implements Serializable {
|
|||||||
@Schema(description = "名称")
|
@Schema(description = "名称")
|
||||||
private String name;
|
private String name;
|
||||||
|
|
||||||
|
@NotBlank
|
||||||
|
@Size(max = 12)
|
||||||
|
@Schema(description = "类型")
|
||||||
|
private String type;
|
||||||
|
|
||||||
@NotBlank
|
@NotBlank
|
||||||
@Size(max = 128)
|
@Size(max = 128)
|
||||||
@Schema(description = "用户名")
|
@Schema(description = "用户名")
|
||||||
|
|||||||
@@ -31,6 +31,10 @@ public class HostIdentityQueryRequest extends PageRequest {
|
|||||||
@Schema(description = "名称")
|
@Schema(description = "名称")
|
||||||
private String name;
|
private String name;
|
||||||
|
|
||||||
|
@Size(max = 12)
|
||||||
|
@Schema(description = "类型")
|
||||||
|
private String type;
|
||||||
|
|
||||||
@Size(max = 128)
|
@Size(max = 128)
|
||||||
@Schema(description = "用户名")
|
@Schema(description = "用户名")
|
||||||
private String username;
|
private String username;
|
||||||
|
|||||||
@@ -34,6 +34,11 @@ public class HostIdentityUpdateRequest implements UpdatePasswordAction {
|
|||||||
@Schema(description = "名称")
|
@Schema(description = "名称")
|
||||||
private String name;
|
private String name;
|
||||||
|
|
||||||
|
@NotBlank
|
||||||
|
@Size(max = 12)
|
||||||
|
@Schema(description = "类型")
|
||||||
|
private String type;
|
||||||
|
|
||||||
@NotBlank
|
@NotBlank
|
||||||
@Size(max = 128)
|
@Size(max = 128)
|
||||||
@Schema(description = "用户名")
|
@Schema(description = "用户名")
|
||||||
|
|||||||
@@ -31,6 +31,9 @@ public class HostIdentityVO implements Serializable {
|
|||||||
@Schema(description = "名称")
|
@Schema(description = "名称")
|
||||||
private String name;
|
private String name;
|
||||||
|
|
||||||
|
@Schema(description = "类型")
|
||||||
|
private String type;
|
||||||
|
|
||||||
@Schema(description = "用户名")
|
@Schema(description = "用户名")
|
||||||
private String username;
|
private String username;
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,36 @@
|
|||||||
|
package com.orion.ops.module.asset.enums;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 主机身份类型
|
||||||
|
*
|
||||||
|
* @author Jiahang Li
|
||||||
|
* @version 1.0.0
|
||||||
|
* @since 2023/9/21 19:01
|
||||||
|
*/
|
||||||
|
public enum HostIdentityTypeEnum {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 密码
|
||||||
|
*/
|
||||||
|
PASSWORD,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 秘钥
|
||||||
|
*/
|
||||||
|
KEY,
|
||||||
|
|
||||||
|
;
|
||||||
|
|
||||||
|
public static HostIdentityTypeEnum of(String type) {
|
||||||
|
if (type == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
for (HostIdentityTypeEnum value : values()) {
|
||||||
|
if (value.name().equals(type)) {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -25,6 +25,7 @@ import com.orion.ops.module.asset.entity.request.host.HostIdentityCreateRequest;
|
|||||||
import com.orion.ops.module.asset.entity.request.host.HostIdentityQueryRequest;
|
import com.orion.ops.module.asset.entity.request.host.HostIdentityQueryRequest;
|
||||||
import com.orion.ops.module.asset.entity.request.host.HostIdentityUpdateRequest;
|
import com.orion.ops.module.asset.entity.request.host.HostIdentityUpdateRequest;
|
||||||
import com.orion.ops.module.asset.entity.vo.HostIdentityVO;
|
import com.orion.ops.module.asset.entity.vo.HostIdentityVO;
|
||||||
|
import com.orion.ops.module.asset.enums.HostIdentityTypeEnum;
|
||||||
import com.orion.ops.module.asset.service.HostIdentityService;
|
import com.orion.ops.module.asset.service.HostIdentityService;
|
||||||
import com.orion.ops.module.infra.api.DataExtraApi;
|
import com.orion.ops.module.infra.api.DataExtraApi;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
@@ -64,7 +65,7 @@ public class HostIdentityServiceImpl implements HostIdentityService {
|
|||||||
public Long createHostIdentity(HostIdentityCreateRequest request) {
|
public Long createHostIdentity(HostIdentityCreateRequest request) {
|
||||||
log.info("HostIdentityService-createHostIdentity request: {}", JSON.toJSONString(request));
|
log.info("HostIdentityService-createHostIdentity request: {}", JSON.toJSONString(request));
|
||||||
// 检查秘钥是否存在
|
// 检查秘钥是否存在
|
||||||
this.checkKeyIdPresent(request.getKeyId());
|
this.checkCreateParams(request);
|
||||||
// 转换
|
// 转换
|
||||||
HostIdentityDO record = HostIdentityConvert.MAPPER.to(request);
|
HostIdentityDO record = HostIdentityConvert.MAPPER.to(request);
|
||||||
// 查询数据是否冲突
|
// 查询数据是否冲突
|
||||||
@@ -85,19 +86,25 @@ public class HostIdentityServiceImpl implements HostIdentityService {
|
|||||||
@Override
|
@Override
|
||||||
public Integer updateHostIdentityById(HostIdentityUpdateRequest request) {
|
public Integer updateHostIdentityById(HostIdentityUpdateRequest request) {
|
||||||
log.info("HostIdentityService-updateHostIdentityById request: {}", JSON.toJSONString(request));
|
log.info("HostIdentityService-updateHostIdentityById request: {}", JSON.toJSONString(request));
|
||||||
// 查询
|
// 验证参数
|
||||||
Long id = Valid.notNull(request.getId(), ErrorMessage.ID_MISSING);
|
Long id = Valid.notNull(request.getId(), ErrorMessage.ID_MISSING);
|
||||||
|
HostIdentityTypeEnum type = Valid.valid(HostIdentityTypeEnum::of, request.getType());
|
||||||
|
if (HostIdentityTypeEnum.KEY.equals(type)) {
|
||||||
|
// 秘钥认证
|
||||||
|
this.checkKeyId(request.getKeyId());
|
||||||
|
}
|
||||||
|
// 查询主机身份
|
||||||
HostIdentityDO record = hostIdentityDAO.selectById(id);
|
HostIdentityDO record = hostIdentityDAO.selectById(id);
|
||||||
Valid.notNull(record, ErrorMessage.DATA_ABSENT);
|
Valid.notNull(record, ErrorMessage.DATA_ABSENT);
|
||||||
// 检查秘钥是否存在
|
|
||||||
this.checkKeyIdPresent(request.getKeyId());
|
|
||||||
// 转换
|
// 转换
|
||||||
HostIdentityDO updateRecord = HostIdentityConvert.MAPPER.to(request);
|
HostIdentityDO updateRecord = HostIdentityConvert.MAPPER.to(request);
|
||||||
// 查询数据是否冲突
|
// 查询数据是否冲突
|
||||||
this.checkHostIdentityPresent(updateRecord);
|
this.checkHostIdentityPresent(updateRecord);
|
||||||
// 设置密码
|
if (HostIdentityTypeEnum.PASSWORD.equals(type)) {
|
||||||
String newPassword = PasswordModifier.getEncryptNewPassword(request);
|
// 设置密码
|
||||||
updateRecord.setPassword(newPassword);
|
String newPassword = PasswordModifier.getEncryptNewPassword(request);
|
||||||
|
updateRecord.setPassword(newPassword);
|
||||||
|
}
|
||||||
// 更新
|
// 更新
|
||||||
LambdaUpdateWrapper<HostIdentityDO> wrapper = Wrappers.<HostIdentityDO>lambdaUpdate()
|
LambdaUpdateWrapper<HostIdentityDO> wrapper = Wrappers.<HostIdentityDO>lambdaUpdate()
|
||||||
.set(HostIdentityDO::getKeyId, request.getKeyId())
|
.set(HostIdentityDO::getKeyId, request.getKeyId())
|
||||||
@@ -105,10 +112,7 @@ public class HostIdentityServiceImpl implements HostIdentityService {
|
|||||||
int effect = hostIdentityDAO.update(updateRecord, wrapper);
|
int effect = hostIdentityDAO.update(updateRecord, wrapper);
|
||||||
log.info("HostIdentityService-updateHostIdentityById effect: {}", effect);
|
log.info("HostIdentityService-updateHostIdentityById effect: {}", effect);
|
||||||
// 删除缓存
|
// 删除缓存
|
||||||
if (!record.getName().equals(updateRecord.getName()) ||
|
RedisMaps.delete(HostCacheKeyDefine.HOST_IDENTITY);
|
||||||
!record.getUsername().equals(updateRecord.getUsername())) {
|
|
||||||
RedisMaps.delete(HostCacheKeyDefine.HOST_IDENTITY);
|
|
||||||
}
|
|
||||||
return effect;
|
return effect;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -155,6 +159,7 @@ public class HostIdentityServiceImpl implements HostIdentityService {
|
|||||||
}
|
}
|
||||||
// 设置秘钥名称
|
// 设置秘钥名称
|
||||||
List<Long> keyIdList = dataGrid.stream()
|
List<Long> keyIdList = dataGrid.stream()
|
||||||
|
.filter(s -> HostIdentityTypeEnum.KEY.name().equals(s.getType()))
|
||||||
.map(HostIdentityVO::getKeyId)
|
.map(HostIdentityVO::getKeyId)
|
||||||
.filter(Objects::nonNull)
|
.filter(Objects::nonNull)
|
||||||
.distinct()
|
.distinct()
|
||||||
@@ -212,14 +217,28 @@ public class HostIdentityServiceImpl implements HostIdentityService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 检查秘钥是否存在
|
* 检查创建参数
|
||||||
|
*
|
||||||
|
* @param request request
|
||||||
|
*/
|
||||||
|
private void checkCreateParams(HostIdentityCreateRequest request) {
|
||||||
|
HostIdentityTypeEnum type = Valid.valid(HostIdentityTypeEnum::of, request.getType());
|
||||||
|
if (HostIdentityTypeEnum.PASSWORD.equals(type)) {
|
||||||
|
// 密码认证
|
||||||
|
Valid.notBlank(request.getPassword(), ErrorMessage.PARAM_MISSING);
|
||||||
|
} else if (HostIdentityTypeEnum.KEY.equals(type)) {
|
||||||
|
// 秘钥认证
|
||||||
|
this.checkKeyId(request.getKeyId());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查 keyId 是否存在
|
||||||
*
|
*
|
||||||
* @param keyId keyId
|
* @param keyId keyId
|
||||||
*/
|
*/
|
||||||
private void checkKeyIdPresent(Long keyId) {
|
private void checkKeyId(Long keyId) {
|
||||||
if (keyId == null) {
|
Valid.notNull(keyId, ErrorMessage.PARAM_MISSING);
|
||||||
return;
|
|
||||||
}
|
|
||||||
Valid.notNull(hostKeyDAO.selectById(keyId), ErrorMessage.KEY_ABSENT);
|
Valid.notNull(hostKeyDAO.selectById(keyId), ErrorMessage.KEY_ABSENT);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -234,6 +253,7 @@ public class HostIdentityServiceImpl implements HostIdentityService {
|
|||||||
return hostIdentityDAO.wrapper()
|
return hostIdentityDAO.wrapper()
|
||||||
.eq(HostIdentityDO::getId, request.getId())
|
.eq(HostIdentityDO::getId, request.getId())
|
||||||
.like(HostIdentityDO::getName, request.getName())
|
.like(HostIdentityDO::getName, request.getName())
|
||||||
|
.eq(HostIdentityDO::getType, request.getType())
|
||||||
.like(HostIdentityDO::getUsername, request.getUsername())
|
.like(HostIdentityDO::getUsername, request.getUsername())
|
||||||
.eq(HostIdentityDO::getKeyId, request.getKeyId())
|
.eq(HostIdentityDO::getKeyId, request.getKeyId())
|
||||||
.and(Strings.isNotEmpty(searchValue), c -> c
|
.and(Strings.isNotEmpty(searchValue), c -> c
|
||||||
|
|||||||
@@ -243,52 +243,62 @@ public class HostTerminalServiceImpl implements HostTerminalService {
|
|||||||
private HostTerminalConnectDTO getHostConnectInfo(HostDO host,
|
private HostTerminalConnectDTO getHostConnectInfo(HostDO host,
|
||||||
HostSshConfigModel config,
|
HostSshConfigModel config,
|
||||||
HostSshExtraModel extra) {
|
HostSshExtraModel extra) {
|
||||||
// 获取认证方式
|
|
||||||
HostSshAuthTypeEnum authType = HostSshAuthTypeEnum.of(config.getAuthType());
|
|
||||||
HostExtraSshAuthTypeEnum extraAuthType = Optional.ofNullable(extra)
|
|
||||||
.map(HostSshExtraModel::getAuthType)
|
|
||||||
.map(HostExtraSshAuthTypeEnum::of)
|
|
||||||
.orElse(HostExtraSshAuthTypeEnum.DEFAULT);
|
|
||||||
if (HostExtraSshAuthTypeEnum.CUSTOM_KEY.equals(extraAuthType)) {
|
|
||||||
// 自定义秘钥
|
|
||||||
authType = HostSshAuthTypeEnum.KEY;
|
|
||||||
config.setKeyId(extra.getKeyId());
|
|
||||||
if (extra.getUsername() != null) {
|
|
||||||
config.setUsername(extra.getUsername());
|
|
||||||
}
|
|
||||||
} else if (HostExtraSshAuthTypeEnum.CUSTOM_IDENTITY.equals(extraAuthType)) {
|
|
||||||
// 自定义身份
|
|
||||||
authType = HostSshAuthTypeEnum.IDENTITY;
|
|
||||||
config.setIdentityId(extra.getIdentityId());
|
|
||||||
}
|
|
||||||
Long keyId = null;
|
|
||||||
// 填充认证信息
|
// 填充认证信息
|
||||||
HostTerminalConnectDTO conn = new HostTerminalConnectDTO();
|
HostTerminalConnectDTO conn = new HostTerminalConnectDTO();
|
||||||
conn.setHostId(host.getId());
|
conn.setHostId(host.getId());
|
||||||
conn.setHostName(host.getName());
|
conn.setHostName(host.getName());
|
||||||
conn.setHostAddress(host.getAddress());
|
conn.setHostAddress(host.getAddress());
|
||||||
conn.setPort(config.getPort());
|
conn.setPort(config.getPort());
|
||||||
|
conn.setTimeout(config.getConnectTimeout());
|
||||||
conn.setCharset(config.getCharset());
|
conn.setCharset(config.getCharset());
|
||||||
conn.setFileNameCharset(config.getFileNameCharset());
|
conn.setFileNameCharset(config.getFileNameCharset());
|
||||||
conn.setFileContentCharset(config.getFileContentCharset());
|
conn.setFileContentCharset(config.getFileContentCharset());
|
||||||
conn.setTimeout(config.getConnectTimeout());
|
|
||||||
conn.setUsername(config.getUsername());
|
// 获取自定义认证方式
|
||||||
// 填充身份信息
|
HostExtraSshAuthTypeEnum extraAuthType = Optional.ofNullable(extra)
|
||||||
if (HostSshAuthTypeEnum.PASSWORD.equals(authType)) {
|
.map(HostSshExtraModel::getAuthType)
|
||||||
conn.setPassword(config.getPassword());
|
.map(HostExtraSshAuthTypeEnum::of)
|
||||||
} else if (HostSshAuthTypeEnum.KEY.equals(authType)) {
|
.orElse(null);
|
||||||
// 秘钥认证
|
if (HostExtraSshAuthTypeEnum.CUSTOM_KEY.equals(extraAuthType)) {
|
||||||
keyId = config.getKeyId();
|
// 自定义秘钥
|
||||||
} else if (HostSshAuthTypeEnum.IDENTITY.equals(authType)) {
|
config.setAuthType(HostSshAuthTypeEnum.KEY.name());
|
||||||
|
config.setKeyId(extra.getKeyId());
|
||||||
|
if (extra.getUsername() != null) {
|
||||||
|
config.setUsername(extra.getUsername());
|
||||||
|
}
|
||||||
|
} else if (HostExtraSshAuthTypeEnum.CUSTOM_IDENTITY.equals(extraAuthType)) {
|
||||||
|
// 自定义身份
|
||||||
|
config.setAuthType(HostSshAuthTypeEnum.IDENTITY.name());
|
||||||
|
config.setIdentityId(extra.getIdentityId());
|
||||||
|
}
|
||||||
|
|
||||||
|
// 身份认证
|
||||||
|
HostSshAuthTypeEnum authType = HostSshAuthTypeEnum.of(config.getAuthType());
|
||||||
|
if (HostSshAuthTypeEnum.IDENTITY.equals(authType)) {
|
||||||
// 身份认证
|
// 身份认证
|
||||||
HostIdentityDO identity = hostIdentityDAO.selectById(config.getIdentityId());
|
HostIdentityDO identity = hostIdentityDAO.selectById(config.getIdentityId());
|
||||||
Valid.notNull(identity, ErrorMessage.IDENTITY_ABSENT);
|
Valid.notNull(identity, ErrorMessage.IDENTITY_ABSENT);
|
||||||
keyId = identity.getKeyId();
|
config.setUsername(identity.getUsername());
|
||||||
conn.setUsername(identity.getUsername());
|
HostIdentityTypeEnum identityType = HostIdentityTypeEnum.of(identity.getType());
|
||||||
conn.setPassword(identity.getPassword());
|
if (HostIdentityTypeEnum.PASSWORD.equals(identityType)) {
|
||||||
|
// 密码类型
|
||||||
|
authType = HostSshAuthTypeEnum.PASSWORD;
|
||||||
|
config.setPassword(identity.getPassword());
|
||||||
|
} else if (HostIdentityTypeEnum.KEY.equals(identityType)) {
|
||||||
|
// 秘钥类型
|
||||||
|
authType = HostSshAuthTypeEnum.KEY;
|
||||||
|
config.setKeyId(identity.getKeyId());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// 设置秘钥信息
|
|
||||||
if (keyId != null) {
|
// 填充认证信息
|
||||||
|
conn.setUsername(config.getUsername());
|
||||||
|
if (HostSshAuthTypeEnum.PASSWORD.equals(authType)) {
|
||||||
|
// 密码认证
|
||||||
|
conn.setPassword(config.getPassword());
|
||||||
|
} else if (HostSshAuthTypeEnum.KEY.equals(authType)) {
|
||||||
|
// 秘钥认证
|
||||||
|
Long keyId = config.getKeyId();
|
||||||
HostKeyDO key = hostKeyDAO.selectById(keyId);
|
HostKeyDO key = hostKeyDAO.selectById(keyId);
|
||||||
Valid.notNull(key, ErrorMessage.KEY_ABSENT);
|
Valid.notNull(key, ErrorMessage.KEY_ABSENT);
|
||||||
conn.setKeyId(keyId);
|
conn.setKeyId(keyId);
|
||||||
@@ -296,7 +306,6 @@ public class HostTerminalServiceImpl implements HostTerminalService {
|
|||||||
conn.setPrivateKey(key.getPrivateKey());
|
conn.setPrivateKey(key.getPrivateKey());
|
||||||
conn.setPrivateKeyPassword(key.getPassword());
|
conn.setPrivateKeyPassword(key.getPassword());
|
||||||
}
|
}
|
||||||
// 连接
|
|
||||||
return conn;
|
return conn;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import axios from 'axios';
|
|||||||
*/
|
*/
|
||||||
export interface HostIdentityCreateRequest {
|
export interface HostIdentityCreateRequest {
|
||||||
name?: string;
|
name?: string;
|
||||||
|
type?: string;
|
||||||
username?: string;
|
username?: string;
|
||||||
password?: string;
|
password?: string;
|
||||||
keyId?: number;
|
keyId?: number;
|
||||||
@@ -27,6 +28,7 @@ export interface HostIdentityQueryRequest extends Pagination {
|
|||||||
searchValue?: string;
|
searchValue?: string;
|
||||||
id?: number;
|
id?: number;
|
||||||
name?: string;
|
name?: string;
|
||||||
|
type?: string;
|
||||||
username?: string;
|
username?: string;
|
||||||
keyId?: number;
|
keyId?: number;
|
||||||
}
|
}
|
||||||
@@ -37,6 +39,7 @@ export interface HostIdentityQueryRequest extends Pagination {
|
|||||||
export interface HostIdentityQueryResponse extends TableData {
|
export interface HostIdentityQueryResponse extends TableData {
|
||||||
id: number;
|
id: number;
|
||||||
name: string;
|
name: string;
|
||||||
|
type: string;
|
||||||
username: string;
|
username: string;
|
||||||
password: string;
|
password: string;
|
||||||
keyId: number;
|
keyId: number;
|
||||||
|
|||||||
@@ -57,6 +57,8 @@
|
|||||||
// 定时查询执行状态
|
// 定时查询执行状态
|
||||||
if (record.status === execStatus.WAITING ||
|
if (record.status === execStatus.WAITING ||
|
||||||
record.status === execStatus.RUNNING) {
|
record.status === execStatus.RUNNING) {
|
||||||
|
// 等待一秒后先查询一下状态
|
||||||
|
setTimeout(fetchTaskStatus, 1000);
|
||||||
// 注册状态轮询
|
// 注册状态轮询
|
||||||
statusIntervalId.value = setInterval(fetchTaskStatus, 5000);
|
statusIntervalId.value = setInterval(fetchTaskStatus, 5000);
|
||||||
}
|
}
|
||||||
@@ -92,8 +94,8 @@
|
|||||||
if (hostStatus) {
|
if (hostStatus) {
|
||||||
host.status = hostStatus.status;
|
host.status = hostStatus.status;
|
||||||
host.startTime = hostStatus.startTime;
|
host.startTime = hostStatus.startTime;
|
||||||
// 使用时间
|
// 结束时间绑定了使用时间 如果未完成则使用当前时间
|
||||||
host.finishTime = host.finishTime || Date.now();
|
host.finishTime = hostStatus.finishTime || Date.now();
|
||||||
host.exitStatus = hostStatus.exitStatus;
|
host.exitStatus = hostStatus.exitStatus;
|
||||||
host.errorMessage = hostStatus.errorMessage;
|
host.errorMessage = hostStatus.errorMessage;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,11 +15,24 @@
|
|||||||
:pagination="false"
|
:pagination="false"
|
||||||
:bordered="false"
|
:bordered="false"
|
||||||
@row-click="clickRow">
|
@row-click="clickRow">
|
||||||
|
<!-- 类型 -->
|
||||||
|
<template #type="{ record }">
|
||||||
|
<a-tag :color="getDictValue(identityTypeKey, record.type, 'color')">
|
||||||
|
{{ getDictValue(identityTypeKey, record.type) }}
|
||||||
|
</a-tag>
|
||||||
|
</template>
|
||||||
<!-- 秘钥名称 -->
|
<!-- 秘钥名称 -->
|
||||||
<template #keyId="{ record }">
|
<template #keyId="{ record }">
|
||||||
<a-tag color="arcoblue" v-if="record.keyId">
|
<!-- 有秘钥 -->
|
||||||
{{ hostKeys.find(s => s.id === record.keyId)?.name }}
|
<template v-if="record.keyId && record.type === 'KEY'">
|
||||||
</a-tag>
|
<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>
|
</template>
|
||||||
</a-table>
|
</a-table>
|
||||||
</grant-layout>
|
</grant-layout>
|
||||||
@@ -38,11 +51,12 @@
|
|||||||
import type { HostKeyQueryResponse } from '@/api/asset/host-key';
|
import type { HostKeyQueryResponse } from '@/api/asset/host-key';
|
||||||
import { ref, onMounted } from 'vue';
|
import { ref, onMounted } from 'vue';
|
||||||
import useLoading from '@/hooks/loading';
|
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 { 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';
|
import GrantLayout from './grant-layout.vue';
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
@@ -51,6 +65,7 @@
|
|||||||
|
|
||||||
const cacheStore = useCacheStore();
|
const cacheStore = useCacheStore();
|
||||||
const rowSelection = useRowSelection();
|
const rowSelection = useRowSelection();
|
||||||
|
const { getDictValue } = useDictStore();
|
||||||
const { loading, setLoading } = useLoading();
|
const { loading, setLoading } = useLoading();
|
||||||
|
|
||||||
const selectedKeys = ref<Array<number>>([]);
|
const selectedKeys = ref<Array<number>>([]);
|
||||||
|
|||||||
@@ -26,8 +26,8 @@
|
|||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { onBeforeMount, onUnmounted, ref } from 'vue';
|
import { onBeforeMount, onUnmounted, ref } from 'vue';
|
||||||
import { useCacheStore } from '@/store';
|
import { useCacheStore, useDictStore } from '@/store';
|
||||||
import { GrantTabs } from './types/const';
|
import { GrantTabs, dictKeys } from './types/const';
|
||||||
import { useRoute } from 'vue-router';
|
import { useRoute } from 'vue-router';
|
||||||
|
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
@@ -35,9 +35,10 @@
|
|||||||
|
|
||||||
const activeKey = ref();
|
const activeKey = ref();
|
||||||
|
|
||||||
// 卸载时清除 cache
|
// 加载字典项
|
||||||
onUnmounted(() => {
|
onBeforeMount(async () => {
|
||||||
cacheStore.reset('users', 'roles', 'hosts', 'hostGroups', 'hostKeys', 'hostIdentities');
|
const dictStore = useDictStore();
|
||||||
|
await dictStore.loadKeys(dictKeys);
|
||||||
});
|
});
|
||||||
|
|
||||||
// 跳转到指定页
|
// 跳转到指定页
|
||||||
@@ -48,6 +49,11 @@
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 卸载时清除 cache
|
||||||
|
onUnmounted(() => {
|
||||||
|
cacheStore.reset('users', 'roles', 'hosts', 'hostGroups', 'hostKeys', 'hostIdentities');
|
||||||
|
});
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="less" scoped>
|
<style lang="less" scoped>
|
||||||
|
|||||||
@@ -73,3 +73,9 @@ export const GrantTabs = [
|
|||||||
component: HostIdentityGrant
|
component: HostIdentityGrant
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
// 身份类型 字典项
|
||||||
|
export const identityTypeKey = 'hostIdentityType';
|
||||||
|
|
||||||
|
// 加载的字典值
|
||||||
|
export const dictKeys = [identityTypeKey];
|
||||||
|
|||||||
@@ -14,11 +14,14 @@ export const hostKeyColumns = [
|
|||||||
title: '名称',
|
title: '名称',
|
||||||
dataIndex: 'name',
|
dataIndex: 'name',
|
||||||
slotName: 'name',
|
slotName: 'name',
|
||||||
|
ellipsis: true,
|
||||||
|
tooltip: true
|
||||||
}, {
|
}, {
|
||||||
title: '创建时间',
|
title: '创建时间',
|
||||||
dataIndex: 'createTime',
|
dataIndex: 'createTime',
|
||||||
slotName: 'createTime',
|
slotName: 'createTime',
|
||||||
align: 'center',
|
align: 'center',
|
||||||
|
width: 180,
|
||||||
render: ({ record }) => {
|
render: ({ record }) => {
|
||||||
return dateFormat(new Date(record.createTime));
|
return dateFormat(new Date(record.createTime));
|
||||||
},
|
},
|
||||||
@@ -27,6 +30,7 @@ export const hostKeyColumns = [
|
|||||||
dataIndex: 'updateTime',
|
dataIndex: 'updateTime',
|
||||||
slotName: 'updateTime',
|
slotName: 'updateTime',
|
||||||
align: 'center',
|
align: 'center',
|
||||||
|
width: 180,
|
||||||
render: ({ record }) => {
|
render: ({ record }) => {
|
||||||
return dateFormat(new Date(record.updateTime));
|
return dateFormat(new Date(record.updateTime));
|
||||||
},
|
},
|
||||||
@@ -46,23 +50,23 @@ export const hostIdentityColumns = [
|
|||||||
title: '名称',
|
title: '名称',
|
||||||
dataIndex: 'name',
|
dataIndex: 'name',
|
||||||
slotName: 'name',
|
slotName: 'name',
|
||||||
|
ellipsis: true,
|
||||||
|
tooltip: true
|
||||||
|
}, {
|
||||||
|
title: '类型',
|
||||||
|
dataIndex: 'type',
|
||||||
|
slotName: 'type',
|
||||||
|
width: 98,
|
||||||
}, {
|
}, {
|
||||||
title: '用户名',
|
title: '用户名',
|
||||||
dataIndex: 'username',
|
dataIndex: 'username',
|
||||||
slotName: 'username',
|
slotName: 'username',
|
||||||
|
ellipsis: true,
|
||||||
|
tooltip: true
|
||||||
}, {
|
}, {
|
||||||
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',
|
||||||
|
|||||||
@@ -54,6 +54,13 @@
|
|||||||
<a-form-item field="name" label="名称">
|
<a-form-item field="name" label="名称">
|
||||||
<a-input v-model="formModel.name" placeholder="请输入名称" allow-clear />
|
<a-input v-model="formModel.name" placeholder="请输入名称" allow-clear />
|
||||||
</a-form-item>
|
</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-form-item field="username" label="用户名">
|
||||||
<a-input v-model="formModel.username" placeholder="请输入用户名" allow-clear />
|
<a-input v-model="formModel.username" placeholder="请输入用户名" allow-clear />
|
||||||
@@ -68,6 +75,12 @@
|
|||||||
<template #title="{ record }">
|
<template #title="{ record }">
|
||||||
{{ record.name }}
|
{{ record.name }}
|
||||||
</template>
|
</template>
|
||||||
|
<!-- 类型 -->
|
||||||
|
<template #type="{ record }">
|
||||||
|
<a-tag :color="getDictValue(identityTypeKey, record.type, 'color')">
|
||||||
|
{{ getDictValue(identityTypeKey, record.type) }}
|
||||||
|
</a-tag>
|
||||||
|
</template>
|
||||||
<!-- 用户名 -->
|
<!-- 用户名 -->
|
||||||
<template #username="{ record }">
|
<template #username="{ record }">
|
||||||
<span class="span-blue text-copy" @click="copy(record.username)">
|
<span class="span-blue text-copy" @click="copy(record.username)">
|
||||||
@@ -76,13 +89,14 @@
|
|||||||
</template>
|
</template>
|
||||||
<!-- 秘钥名称 -->
|
<!-- 秘钥名称 -->
|
||||||
<template #keyId="{ record }">
|
<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'])"
|
<a-tooltip v-if="hasAnyPermission(['asset:host-key:detail', 'asset:host-key:update'])"
|
||||||
content="点击查看详情">
|
content="点击查看详情">
|
||||||
<a-tag :checked="true"
|
<a-tag :checked="true"
|
||||||
checkable
|
checkable
|
||||||
@click="emits('openKeyView',{id: record.keyId})">
|
@click="emits('openKeyView', { id: record.keyId })">
|
||||||
{{ record.keyName }}
|
{{ record.keyName }}
|
||||||
</a-tag>
|
</a-tag>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
@@ -91,6 +105,10 @@
|
|||||||
{{ record.keyName }}
|
{{ record.keyName }}
|
||||||
</a-tag>
|
</a-tag>
|
||||||
</template>
|
</template>
|
||||||
|
<!-- 无秘钥 -->
|
||||||
|
<template v-else>
|
||||||
|
<span>-</span>
|
||||||
|
</template>
|
||||||
</template>
|
</template>
|
||||||
<!-- 拓展操作 -->
|
<!-- 拓展操作 -->
|
||||||
<template #extra="{ record }">
|
<template #extra="{ record }">
|
||||||
@@ -151,8 +169,10 @@
|
|||||||
import { deleteHostIdentity, getHostIdentityPage } from '@/api/asset/host-identity';
|
import { deleteHostIdentity, getHostIdentityPage } from '@/api/asset/host-identity';
|
||||||
import { Message, Modal } from '@arco-design/web-vue';
|
import { Message, Modal } from '@arco-design/web-vue';
|
||||||
import usePermission from '@/hooks/permission';
|
import usePermission from '@/hooks/permission';
|
||||||
|
import { useDictStore } from '@/store';
|
||||||
import { copy } from '@/hooks/copy';
|
import { copy } from '@/hooks/copy';
|
||||||
import { GrantKey, GrantRouteName } from '@/views/asset/grant/types/const';
|
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';
|
import HostKeySelector from '@/components/asset/host-key/selector/index.vue';
|
||||||
|
|
||||||
const emits = defineEmits(['openAdd', 'openUpdate', 'openKeyView']);
|
const emits = defineEmits(['openAdd', 'openUpdate', 'openKeyView']);
|
||||||
@@ -161,6 +181,7 @@
|
|||||||
|
|
||||||
const cardColLayout = useColLayout();
|
const cardColLayout = useColLayout();
|
||||||
const pagination = usePagination();
|
const pagination = usePagination();
|
||||||
|
const { toOptions, getDictValue } = useDictStore();
|
||||||
const { loading, setLoading } = useLoading();
|
const { loading, setLoading } = useLoading();
|
||||||
const { hasAnyPermission } = usePermission();
|
const { hasAnyPermission } = usePermission();
|
||||||
|
|
||||||
@@ -168,6 +189,7 @@
|
|||||||
const formModel = reactive<HostIdentityQueryRequest>({
|
const formModel = reactive<HostIdentityQueryRequest>({
|
||||||
searchValue: undefined,
|
searchValue: undefined,
|
||||||
id: undefined,
|
id: undefined,
|
||||||
|
type: undefined,
|
||||||
name: undefined,
|
name: undefined,
|
||||||
username: undefined,
|
username: undefined,
|
||||||
keyId: undefined,
|
keyId: undefined,
|
||||||
|
|||||||
@@ -23,12 +23,20 @@
|
|||||||
<a-form-item field="name" label="名称">
|
<a-form-item field="name" label="名称">
|
||||||
<a-input v-model="formModel.name" placeholder="请输入名称" />
|
<a-input v-model="formModel.name" placeholder="请输入名称" />
|
||||||
</a-form-item>
|
</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-form-item field="username" label="用户名">
|
||||||
<a-input v-model="formModel.username" placeholder="请输入用户名" />
|
<a-input v-model="formModel.username" placeholder="请输入用户名" />
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<!-- 用户密码 -->
|
<!-- 用户密码 -->
|
||||||
<a-form-item field="password"
|
<a-form-item v-if="formModel.type === IdentityType.PASSWORD"
|
||||||
|
field="password"
|
||||||
label="用户密码"
|
label="用户密码"
|
||||||
:rules="passwordRules">
|
:rules="passwordRules">
|
||||||
<a-input-password v-model="formModel.password"
|
<a-input-password v-model="formModel.password"
|
||||||
@@ -42,10 +50,10 @@
|
|||||||
checked-text="使用新密码"
|
checked-text="使用新密码"
|
||||||
unchecked-text="使用原密码" />
|
unchecked-text="使用原密码" />
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<!-- 秘钥id -->
|
<!-- 主机秘钥 -->
|
||||||
<a-form-item field="keyId"
|
<a-form-item v-if="formModel.type === IdentityType.KEY"
|
||||||
label="主机秘钥"
|
field="keyId"
|
||||||
extra="密码和秘钥二选一 优先使用秘钥">
|
label="主机秘钥">
|
||||||
<host-key-selector v-model="formModel.keyId" />
|
<host-key-selector v-model="formModel.keyId" />
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</a-form>
|
</a-form>
|
||||||
@@ -68,8 +76,11 @@
|
|||||||
import formRules from '../types/form.rules';
|
import formRules from '../types/form.rules';
|
||||||
import { createHostIdentity, updateHostIdentity } from '@/api/asset/host-identity';
|
import { createHostIdentity, updateHostIdentity } from '@/api/asset/host-identity';
|
||||||
import { Message } from '@arco-design/web-vue';
|
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';
|
import HostKeySelector from '@/components/asset/host-key/selector/index.vue';
|
||||||
|
|
||||||
|
const { toRadioOptions, getDictValue } = useDictStore();
|
||||||
const { visible, setVisible } = useVisible();
|
const { visible, setVisible } = useVisible();
|
||||||
const { loading, setLoading } = useLoading();
|
const { loading, setLoading } = useLoading();
|
||||||
|
|
||||||
@@ -79,6 +90,7 @@
|
|||||||
const defaultForm = (): HostIdentityUpdateRequest => {
|
const defaultForm = (): HostIdentityUpdateRequest => {
|
||||||
return {
|
return {
|
||||||
id: undefined,
|
id: undefined,
|
||||||
|
type: IdentityType.PASSWORD,
|
||||||
name: undefined,
|
name: undefined,
|
||||||
username: undefined,
|
username: undefined,
|
||||||
password: undefined,
|
password: undefined,
|
||||||
@@ -139,15 +151,6 @@
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (isAddHandle.value) {
|
if (isAddHandle.value) {
|
||||||
if (!formModel.value.password && !formModel.value.keyId) {
|
|
||||||
formRef.value.setFields({
|
|
||||||
password: {
|
|
||||||
status: 'error',
|
|
||||||
message: '创建时密码和秘钥不能同时为空'
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
// 新增
|
// 新增
|
||||||
await createHostIdentity(formModel.value);
|
await createHostIdentity(formModel.value);
|
||||||
Message.success('创建成功');
|
Message.success('创建成功');
|
||||||
|
|||||||
@@ -17,6 +17,13 @@
|
|||||||
<a-form-item field="name" label="名称">
|
<a-form-item field="name" label="名称">
|
||||||
<a-input v-model="formModel.name" placeholder="请输入名称" allow-clear />
|
<a-input v-model="formModel.name" placeholder="请输入名称" allow-clear />
|
||||||
</a-form-item>
|
</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-form-item field="username" label="用户名">
|
||||||
<a-input v-model="formModel.username" placeholder="请输入用户名" allow-clear />
|
<a-input v-model="formModel.username" placeholder="请输入用户名" allow-clear />
|
||||||
@@ -80,6 +87,12 @@
|
|||||||
@page-change="(page) => fetchTableData(page, pagination.pageSize)"
|
@page-change="(page) => fetchTableData(page, pagination.pageSize)"
|
||||||
@page-size-change="(size) => fetchTableData(1, size)"
|
@page-size-change="(size) => fetchTableData(1, size)"
|
||||||
:bordered="false">
|
:bordered="false">
|
||||||
|
<!-- 类型 -->
|
||||||
|
<template #type="{ record }">
|
||||||
|
<a-tag :color="getDictValue(identityTypeKey, record.type, 'color')">
|
||||||
|
{{ getDictValue(identityTypeKey, record.type) }}
|
||||||
|
</a-tag>
|
||||||
|
</template>
|
||||||
<!-- 用户名 -->
|
<!-- 用户名 -->
|
||||||
<template #username="{ record }">
|
<template #username="{ record }">
|
||||||
<span class="span-blue text-copy" @click="copy(record.username)">
|
<span class="span-blue text-copy" @click="copy(record.username)">
|
||||||
@@ -88,13 +101,14 @@
|
|||||||
</template>
|
</template>
|
||||||
<!-- 秘钥名称 -->
|
<!-- 秘钥名称 -->
|
||||||
<template #keyId="{ record }">
|
<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'])"
|
<a-tooltip v-if="hasAnyPermission(['asset:host-key:detail', 'asset:host-key:update'])"
|
||||||
content="点击查看详情">
|
content="点击查看详情">
|
||||||
<a-tag :checked="true"
|
<a-tag :checked="true"
|
||||||
checkable
|
checkable
|
||||||
@click="emits('openKeyView',{id: record.keyId})">
|
@click="emits('openKeyView', { id: record.keyId })">
|
||||||
{{ record.keyName }}
|
{{ record.keyName }}
|
||||||
</a-tag>
|
</a-tag>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
@@ -103,6 +117,10 @@
|
|||||||
{{ record.keyName }}
|
{{ record.keyName }}
|
||||||
</a-tag>
|
</a-tag>
|
||||||
</template>
|
</template>
|
||||||
|
<!-- 无秘钥 -->
|
||||||
|
<template v-else>
|
||||||
|
<span>-</span>
|
||||||
|
</template>
|
||||||
</template>
|
</template>
|
||||||
<!-- 操作 -->
|
<!-- 操作 -->
|
||||||
<template #handle="{ record }">
|
<template #handle="{ record }">
|
||||||
@@ -147,21 +165,24 @@
|
|||||||
import useLoading from '@/hooks/loading';
|
import useLoading from '@/hooks/loading';
|
||||||
import usePermission from '@/hooks/permission';
|
import usePermission from '@/hooks/permission';
|
||||||
import { copy } from '@/hooks/copy';
|
import { copy } from '@/hooks/copy';
|
||||||
|
import { useDictStore } from '@/store';
|
||||||
import { usePagination } from '@/types/table';
|
import { usePagination } from '@/types/table';
|
||||||
import { GrantKey, GrantRouteName } from '@/views/asset/grant/types/const';
|
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';
|
import HostKeySelector from '@/components/asset/host-key/selector/index.vue';
|
||||||
|
|
||||||
const emits = defineEmits(['openAdd', 'openUpdate', 'openKeyView']);
|
const emits = defineEmits(['openAdd', 'openUpdate', 'openKeyView']);
|
||||||
|
|
||||||
const tableRenderData = ref<HostIdentityQueryResponse[]>([]);
|
|
||||||
|
|
||||||
const pagination = usePagination();
|
const pagination = usePagination();
|
||||||
|
const { toOptions, getDictValue } = useDictStore();
|
||||||
const { loading, setLoading } = useLoading();
|
const { loading, setLoading } = useLoading();
|
||||||
const { hasAnyPermission } = usePermission();
|
const { hasAnyPermission } = usePermission();
|
||||||
|
|
||||||
|
const tableRenderData = ref<HostIdentityQueryResponse[]>([]);
|
||||||
const formModel = reactive<HostIdentityQueryRequest>({
|
const formModel = reactive<HostIdentityQueryRequest>({
|
||||||
id: undefined,
|
id: undefined,
|
||||||
name: undefined,
|
name: undefined,
|
||||||
|
type: undefined,
|
||||||
username: undefined,
|
username: undefined,
|
||||||
keyId: undefined,
|
keyId: undefined,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -28,8 +28,9 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { ref, computed, onUnmounted } from 'vue';
|
import { ref, computed, onUnmounted, onBeforeMount } from 'vue';
|
||||||
import { useAppStore, useCacheStore } from '@/store';
|
import { useAppStore, useCacheStore, useDictStore } from '@/store';
|
||||||
|
import { dictKeys } from './types/const';
|
||||||
import HostIdentityCardList from './components/host-identity-card-list.vue';
|
import HostIdentityCardList from './components/host-identity-card-list.vue';
|
||||||
import HostIdentityTable from './components/host-identity-table.vue';
|
import HostIdentityTable from './components/host-identity-table.vue';
|
||||||
import HostIdentityFormModal from './components/host-identity-form-modal.vue';
|
import HostIdentityFormModal from './components/host-identity-form-modal.vue';
|
||||||
@@ -62,6 +63,12 @@
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 加载字典值
|
||||||
|
onBeforeMount(async () => {
|
||||||
|
const dictStore = useDictStore();
|
||||||
|
await dictStore.loadKeys(dictKeys);
|
||||||
|
});
|
||||||
|
|
||||||
// 卸载时清除 cache
|
// 卸载时清除 cache
|
||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
const cacheStore = useCacheStore();
|
const cacheStore = useCacheStore();
|
||||||
|
|||||||
@@ -9,6 +9,10 @@ const fieldConfig = {
|
|||||||
label: 'id',
|
label: 'id',
|
||||||
dataIndex: 'id',
|
dataIndex: 'id',
|
||||||
slotName: 'id',
|
slotName: 'id',
|
||||||
|
}, {
|
||||||
|
label: '类型',
|
||||||
|
dataIndex: 'type',
|
||||||
|
slotName: 'type',
|
||||||
}, {
|
}, {
|
||||||
label: '用户名',
|
label: '用户名',
|
||||||
dataIndex: 'username',
|
dataIndex: 'username',
|
||||||
|
|||||||
@@ -0,0 +1,11 @@
|
|||||||
|
// 身份类型
|
||||||
|
export const IdentityType = {
|
||||||
|
PASSWORD: 'PASSWORD',
|
||||||
|
KEY: 'KEY',
|
||||||
|
};
|
||||||
|
|
||||||
|
// 身份类型 字典项
|
||||||
|
export const identityTypeKey = 'hostIdentityType';
|
||||||
|
|
||||||
|
// 加载的字典值
|
||||||
|
export const dictKeys = [identityTypeKey];
|
||||||
|
|||||||
@@ -8,6 +8,16 @@ export const name = [{
|
|||||||
message: '名称长度不能大于64位'
|
message: '名称长度不能大于64位'
|
||||||
}] as FieldRule[];
|
}] as FieldRule[];
|
||||||
|
|
||||||
|
export const type = [{
|
||||||
|
required: true,
|
||||||
|
message: '请选择类型'
|
||||||
|
}] as FieldRule[];
|
||||||
|
|
||||||
|
export const keyId = [{
|
||||||
|
required: true,
|
||||||
|
message: '请选择秘钥'
|
||||||
|
}] as FieldRule[];
|
||||||
|
|
||||||
export const username = [{
|
export const username = [{
|
||||||
required: true,
|
required: true,
|
||||||
message: '请输入用户名'
|
message: '请输入用户名'
|
||||||
@@ -18,5 +28,7 @@ export const username = [{
|
|||||||
|
|
||||||
export default {
|
export default {
|
||||||
name,
|
name,
|
||||||
|
type,
|
||||||
|
keyId,
|
||||||
username,
|
username,
|
||||||
} as Record<string, FieldRule | FieldRule[]>;
|
} as Record<string, FieldRule | FieldRule[]>;
|
||||||
|
|||||||
@@ -13,6 +13,13 @@ const columns = [
|
|||||||
title: '名称',
|
title: '名称',
|
||||||
dataIndex: 'name',
|
dataIndex: 'name',
|
||||||
slotName: 'name',
|
slotName: 'name',
|
||||||
|
ellipsis: true,
|
||||||
|
tooltip: true
|
||||||
|
}, {
|
||||||
|
title: '类型',
|
||||||
|
dataIndex: 'type',
|
||||||
|
slotName: 'type',
|
||||||
|
width: 138,
|
||||||
}, {
|
}, {
|
||||||
title: '用户名',
|
title: '用户名',
|
||||||
dataIndex: 'username',
|
dataIndex: 'username',
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<a-drawer v-model:visible="visible"
|
<a-drawer v-model:visible="visible"
|
||||||
:title="title"
|
:title="title"
|
||||||
:width="470"
|
:width="520"
|
||||||
:mask-closable="false"
|
:mask-closable="false"
|
||||||
:unmount-on-close="true"
|
:unmount-on-close="true"
|
||||||
:ok-button-props="{ disabled: loading || isViewHandler }"
|
:ok-button-props="{ disabled: loading || isViewHandler }"
|
||||||
@@ -241,7 +241,7 @@
|
|||||||
|
|
||||||
.keygen-alert {
|
.keygen-alert {
|
||||||
margin: 0 0 12px 16px;
|
margin: 0 0 12px 16px;
|
||||||
width: 408px;
|
width: calc(100% - 16px);
|
||||||
}
|
}
|
||||||
|
|
||||||
.password-input {
|
.password-input {
|
||||||
|
|||||||
@@ -13,11 +13,14 @@ const columns = [
|
|||||||
title: '名称',
|
title: '名称',
|
||||||
dataIndex: 'name',
|
dataIndex: 'name',
|
||||||
slotName: 'name',
|
slotName: 'name',
|
||||||
|
ellipsis: true,
|
||||||
|
tooltip: true
|
||||||
}, {
|
}, {
|
||||||
title: '创建时间',
|
title: '创建时间',
|
||||||
dataIndex: 'createTime',
|
dataIndex: 'createTime',
|
||||||
slotName: 'createTime',
|
slotName: 'createTime',
|
||||||
align: 'center',
|
align: 'center',
|
||||||
|
width: 198,
|
||||||
render: ({ record }) => {
|
render: ({ record }) => {
|
||||||
return dateFormat(new Date(record.createTime));
|
return dateFormat(new Date(record.createTime));
|
||||||
},
|
},
|
||||||
@@ -26,6 +29,7 @@ const columns = [
|
|||||||
dataIndex: 'updateTime',
|
dataIndex: 'updateTime',
|
||||||
slotName: 'updateTime',
|
slotName: 'updateTime',
|
||||||
align: 'center',
|
align: 'center',
|
||||||
|
width: 198,
|
||||||
render: ({ record }) => {
|
render: ({ record }) => {
|
||||||
return dateFormat(new Date(record.updateTime));
|
return dateFormat(new Date(record.updateTime));
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -107,7 +107,7 @@
|
|||||||
{{ getDictValue(execJobStatusKey, record.status) }}
|
{{ getDictValue(execJobStatusKey, record.status) }}
|
||||||
</a-tag>
|
</a-tag>
|
||||||
</template>
|
</template>
|
||||||
<!-- 最近任务 -->
|
<!-- 最近执行 -->
|
||||||
<template #recentLog="{ record }">
|
<template #recentLog="{ record }">
|
||||||
<div class="flex-center" v-if="record.recentLogId && record.recentLogStatus">
|
<div class="flex-center" v-if="record.recentLogId && record.recentLogStatus">
|
||||||
<!-- 执行状态 -->
|
<!-- 执行状态 -->
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ const columns = [
|
|||||||
align: 'center',
|
align: 'center',
|
||||||
width: 112,
|
width: 112,
|
||||||
}, {
|
}, {
|
||||||
title: '最近任务',
|
title: '最近执行',
|
||||||
dataIndex: 'recentLog',
|
dataIndex: 'recentLog',
|
||||||
slotName: 'recentLog',
|
slotName: 'recentLog',
|
||||||
align: 'left',
|
align: 'left',
|
||||||
|
|||||||
@@ -245,5 +245,6 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
border-radius: 2px;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
Reference in New Issue
Block a user