Compare commits

..

52 Commits

Author SHA1 Message Date
李佳航
80e7c28ef1 Merge pull request #111 from dromara/dev
Dev
2025-06-27 12:07:06 +08:00
lijiahangmax
154f8d56ac 🐳 修改 docker 配置. 2025-06-27 12:06:39 +08:00
lijiahangmax
156e63bef9 🐳 修改 docker 配置. 2025-06-27 11:41:09 +08:00
lijiahangmax
34d65c0bc5 🎨 格式化代码. 2025-06-27 11:39:08 +08:00
lijiahangmax
decbce5410 🎨 格式化代码. 2025-06-26 01:27:14 +08:00
lijiahangmax
8b795e889a 🎨 格式化代码. 2025-06-26 01:23:13 +08:00
lijiahangmax
b75446f405 ✏️ 修改文档. 2025-06-26 01:07:35 +08:00
lijiahangmax
2b43270896 🔨 修改终端逻辑. 2025-06-26 01:06:36 +08:00
lijiahangmax
9d4c2aaeb4 🎉 优化 terminal 模块逻辑. 2025-06-26 00:59:15 +08:00
lijiahangmax
712b175179 🎉 优化 exec 模块逻辑. 2025-06-26 00:51:21 +08:00
lijiahangmax
10178d998e 🎉 优化 asset 模块逻辑. 2025-06-25 19:16:04 +08:00
lijiahangmax
c603c57ad8 🎉 优化 infra 模块逻辑. 2025-06-25 19:06:08 +08:00
lijiahangmax
f69093de66 🎉 优化系统架构. 2025-06-25 18:59:16 +08:00
lijiahangmax
cec7e21d5a 🎉 优化系统架构. 2025-06-25 18:57:05 +08:00
lijiahangmax
f1ade56e13 升级版本. 2025-06-25 18:48:25 +08:00
lijiahangmax
b182452f30 🎉 重构终端代码. 2025-06-25 14:50:35 +08:00
lijiahangmax
5ed513f472 🎉 重构页面代码. 2025-06-25 14:50:25 +08:00
lijiahangmax
aa8b380289 🎉 重构组件代码. 2025-06-25 14:50:01 +08:00
lijiahangmax
6149010bf4 🎉 重构前端代码. 2025-06-25 14:49:36 +08:00
lijiahangmax
5183b7ccb4 添加 guacd 镜像. 2025-06-25 12:16:33 +08:00
lijiahangmax
f78958532b 🐛 右键菜单保存报错的问题. 2025-06-24 20:44:48 +08:00
lijiahangmax
603dd89be4 🔨 下线会话. 2025-06-24 20:44:14 +08:00
lijiahangmax
1a58e40607 🔨 修改 noredis 配置. 2025-06-22 00:07:13 +08:00
lijiahangmax
84721f2e17 优化连接逻辑. 2025-06-22 00:06:51 +08:00
lijiahangmax
d72ccb1df6 修改防抖时间. 2025-06-13 23:53:01 +08:00
李佳航
6d4952c483 Merge pull request #109 from dromara/dev
Dev
2025-06-06 16:39:28 +08:00
lijiahangmax
7fab68f8c0 升级版本. 2025-06-06 16:27:29 +08:00
lijiahangmax
3a586c47a3 添加更新日期. 2025-06-06 16:18:48 +08:00
lijiahangmax
1767079249 复制主机. 2025-06-06 15:58:49 +08:00
lijiahangmax
fdd3be5a91 🐛 复制主机. 2025-05-30 01:29:28 +08:00
lijiahangmax
2457a015e4 🐛 修复分组无法删除的问题. 2025-05-29 22:15:08 +08:00
李佳航
40a99eb67a Merge pull request #106 from dromara/dev
Dev
2025-05-29 19:14:53 +08:00
lijiahangmax
1fcf239561 ✏️ 修改文档. 2025-05-29 19:07:16 +08:00
lijiahangmax
bbb1bb0db6 🔨 自定义标签名称. 2025-05-28 20:39:28 +08:00
李佳航
f79d89def9 Merge pull request #101 from dromara/dev
Dev
2025-04-06 21:44:08 +08:00
lijiahangmax
fd535f00c8 🔨 优化前端逻辑. 2025-04-06 21:41:41 +08:00
lijiahangmax
2c07551b88 🐛 修复修改主机配置报错. 2025-04-06 21:17:50 +08:00
lijiahangmax
86914321a6 🔖 修改 sql 脚本. 2025-04-06 11:11:39 +08:00
lijiahangmax
f64c15a01b 🔖 升级版本. 2025-04-06 10:56:28 +08:00
李佳航
8265cc3cfd Merge pull request #100 from dromara/dev
Dev
2025-04-03 14:55:55 +08:00
lijiahangmax
84c8bc98af 🔖 修改 sql 脚本. 2025-04-03 14:34:14 +08:00
lijiahangmax
858ca1becc 🔖 升级版本. 2025-04-03 01:43:30 +08:00
lijiahangmax
0e0c9cc628 🔨 添加 SN. 2025-04-03 01:38:37 +08:00
lijiahangmax
cdc3c88507 🔨 优化前端逻辑. 2025-04-02 13:33:21 +08:00
lijiahangmax
10624b42c4 🔨 优化代码逻辑. 2025-03-31 21:00:31 +08:00
lijiahang
6479694b4b 🔨 添加主机配置逻辑. 2025-03-28 16:07:15 +08:00
lijiahang
d2e72aea56 🔨 修改基础查询. 2025-03-28 15:32:08 +08:00
lijiahang
986f0974db 🔨 优化主机逻辑. 2025-03-28 14:34:51 +08:00
lijiahang
6b5c7fd409 Merge remote-tracking branch 'origin/dev' into dev 2025-03-26 15:27:32 +08:00
lijiahang
ca803e4e5a 添加系统架构枚举. 2025-03-26 15:27:16 +08:00
lijiahang
5de22e4b41 🐳 修改 e2e 配置. 2025-03-26 15:26:30 +08:00
lijiahangmax
b8e81ee100 修改文件过滤规则. 2025-03-26 01:42:14 +08:00
779 changed files with 36122 additions and 7678 deletions

View File

@@ -1,5 +1,7 @@
VOLUME_BASE=/data/orion-visor-space/docker-volumes
DEMO_MODE=false
SERVICE_PORT=1081
SPRING_PROFILES_ACTIVE=prod
SECRET_KEY=uQeacXV8b3isvKLK
@@ -14,4 +16,6 @@ MYSQL_ROOT_PASSWORD=Data@123456
REDIS_HOST=redis
REDIS_PASSWORD=Data@123456
DEMO_MODE=false
GUACD_HOST=guacd
GUACD_PORT=4822
GUACD_DRIVE_PATH=/drive

View File

@@ -1,4 +1,4 @@
<div align="center"><img src="https://bjuimg.obs.cn-north-4.myhuaweicloud.com/images/2024/9/11/11e7e78e-2af0-4c68-9811-db8a4c4400f4.png" alt="logo" width="520" /></div>
<div align="center"><img src="https://oss.orionsec.cn/orion-visor/logo_horizontal.png" alt="logo" width="520" /></div>
<p style="margin-top: 12px" align="center"><b>【Dromara】 一款高颜值、现代化的自动化运维&轻量堡垒机平台。</b></p>
<p align="center">
<a target="_blank"
@@ -48,7 +48,7 @@
**`orion-visor`** 提供一站式自动化运维解决方案。
* **资产管理**:支持对资产进行分组,实现对主机、密钥和身份的统一管理和授权。
* **在线终端**:提供在线终端 SSH 服务,支持快捷命令、自定义快捷键和主题风格。
* **在线终端**:提供在线终端 SSH/RDP 等多种协议,支持快捷命令、自定义快捷键和主题风格。
* **文件管理**:支持远程主机 SFTP 大文件的批量上传、下载和在线编辑等操作。
* **批量操作**:支持批量执行主机命令、多主机文件分发等功能。
* **计划任务**:支持配置 cron 表达式,定时执行主机命令。
@@ -64,7 +64,7 @@
* 🎭 演示环境部分功能不可用, 完整功能请本地部署!
* 📛 演示环境请不要随便删除数据!
* 📧 如果演示环境不可用请联系我!
* 📨 **作者已被毕(cai)业(yuan) 寻java高级内推 望京/5号/10号线 有坑位的联系我哦** 微信: `ljh1553488`
* 📨 **作者已被毕(cai)业(yuan) 寻java高级内推 望京/5号/10号线 有坑位的联系我哦(随缘)** 微信: `ljh1553488`
## 快速开始
@@ -87,32 +87,42 @@ docker compose up -d
## 技术栈
* SpringBoot 2.7+
* Mysql 8+
* Redis 6+
* Vue3 3+
* Arco Design 2+
* SpringBoot 2.7.17
* Mysql 8.0+
* Redis 6.0+
* Vue3 3.5+
* Arco Design 2.56+
## 主要功能预览
#### 工作台
![工作台](https://oss.orionsec.cn/orion-visor/screenshot/workplace.png "工作台")
#### 主机终端
![新建连接](https://bjuimg.obs.cn-north-4.myhuaweicloud.com/images/2024/5/29/aa7efb14-f2cc-4a6f-b96b-a47964ed8f79.png "新建连接")
![主机终端](https://bjuimg.obs.cn-north-4.myhuaweicloud.com/images/2024/5/29/66f121de-69b6-49f6-adc4-701a22d481c4.png "主机终端")
![sftp](https://bjuimg.obs.cn-north-4.myhuaweicloud.com/images/2024/5/29/f7a0d141-0ee0-484e-8ddb-24cad9ed2c03.png "sftp")
![主题设置](https://bjuimg.obs.cn-north-4.myhuaweicloud.com/images/2024/5/29/d6f37ab3-62d2-4c5e-a503-e76a1d5ddc8e.png "主题设置")
![新建连接](https://oss.orionsec.cn/orion-visor/screenshot/terminal-hosts.png "新建连接")
![ssh](https://oss.orionsec.cn/orion-visor/screenshot/terminal-ssh.png "ssh")
![sftp](https://oss.orionsec.cn/orion-visor/screenshot/terminal-sftp.png "sftp")
![rdp](https://oss.orionsec.cn/orion-visor/screenshot/terminal-rdp.png "rdp")
![主题设置](https://oss.orionsec.cn/orion-visor/screenshot/terminal-theme.png "主题设置")
#### 主机列表
![主机列表](https://oss.orionsec.cn/orion-visor/screenshot/host-list.png "主机列表")
#### 批量执行
![批量执行](https://bjuimg.obs.cn-north-4.myhuaweicloud.com/images/2024/5/29/3effc2fc-56a5-498d-8dfb-0f4f3b8a4056.png "批量执行")
![批量执行](https://oss.orionsec.cn/orion-visor/screenshot/exec-command.png "批量执行")
![执行日志](https://oss.orionsec.cn/orion-visor/screenshot/exec-log.png "执行日志")
#### 批量上传
![批量上传任务](https://bjuimg.obs.cn-north-4.myhuaweicloud.com/images/2024/5/29/98240fa9-4056-4520-9034-290d1aa47d80.png "批量上传任务")
![批量上传任务](https://oss.orionsec.cn/orion-visor/screenshot/batch-upload.png "批量上传任务")
#### 计划任务
![计划任务详情](https://bjuimg.obs.cn-north-4.myhuaweicloud.com/images/2024/5/29/d5ee6f04-7b2c-42ba-a3b3-642587f40cce.png "计划任务详情")
![计划任务详情](https://oss.orionsec.cn/orion-visor/screenshot/exec-job.png "计划任务详情")
## Star History
@@ -124,7 +134,7 @@ docker compose up -d
## 联系我
<img src="https://bjuimg.obs.cn-north-4.myhuaweicloud.com/images/2024/10/11/cf72c603-3951-4171-95b4-06271dda1c80.jpg" alt="wx" width="628px"/>
<img src="https://oss.orionsec.cn/orion-visor/vx.jpg" alt="vx" width="628px"/>
微信: ljh1553488
QQ群: 755242157
@@ -134,7 +144,7 @@ QQ群: 755242157
## 支持一下
<img src="https://bjuimg.obs.cn-north-4.myhuaweicloud.com/images/2024/5/17/a5351e59-294c-4bec-b767-1a44c362fcbc.jpg" alt="收款码" width="540px"/>
<img src="https://oss.orionsec.cn/orion-visor/pay.jpg" alt="收款码" width="540px"/>
🎁 为了项目能健康持续的发展, 我期望获得相应的资金支持, 你们的支持是我不断更新前进的动力!
@@ -146,6 +156,10 @@ QQ群: 755242157
本项目遵循 [Apache-2.0](https://github.com/dromara/orion-visor/blob/main/LICENSE) 开源许可证。
## Gitee 最有价值开源项目 GVP
## Gitee 最有价值开源项目 GVP
![GVP](https://bjuimg.obs.cn-north-4.myhuaweicloud.com/images/2024/10/24/8dd98b8d-9de5-44e6-86d3-04e27ec66123.jpg "GVP")
![GVP](https://oss.orionsec.cn/orion-visor/gvp.jpg "GVP")
## GitCode 最有影响力的开源项目 G-Star
![GSTAR](https://oss.orionsec.cn/orion-visor/gstar.jpg "GSTAR")

View File

@@ -7,16 +7,16 @@ services:
ports:
- 9200:9200
environment:
- SPRING_PROFILES_ACTIVE=prod
- MYSQL_HOST=mysql
- MYSQL_PORT=3306
- MYSQL_DATABASE=orion_visor
- MYSQL_USER=root
- MYSQL_PASSWORD=Data@123456
- REDIS_HOST=redis
- REDIS_PASSWORD=Data@123456
- SECRET_KEY=uQeacXV8b3isvKLK
- DEMO_MODE=false
SPRING_PROFILES_ACTIVE: prod
MYSQL_HOST: mysql
MYSQL_PORT: 3306
MYSQL_DATABASE: orion_visor
MYSQL_USER: root
MYSQL_PASSWORD: Data@123456
REDIS_HOST: redis
REDIS_PASSWORD: Data@123456
SECRET_KEY: uQeacXV8b3isvKLK
DEMO_MODE: false
volumes:
- /data/orion-visor-space/docker-volumes/service/root-orion:/root/orion
healthcheck:
@@ -39,10 +39,10 @@ services:
ports:
- 3307:3306
environment:
- MYSQL_DATABASE=orion_visor
- MYSQL_USER=orion
- MYSQL_PASSWORD=Data@123456
- MYSQL_ROOT_PASSWORD=Data@123456
MYSQL_DATABASE: orion_visor
MYSQL_USER: orion
MYSQL_PASSWORD: Data@123456
MYSQL_ROOT_PASSWORD: Data@123456
volumes:
- /data/orion-visor-space/docker-volumes/mysql/var-lib-mysql:/var/lib/mysql
- /data/orion-visor-space/docker-volumes/mysql/var-lib-mysql-files:/var/lib/mysql-files
@@ -61,7 +61,7 @@ services:
ports:
- 6380:6379
environment:
- REDIS_PASSWORD=Data@123456
REDIS_PASSWORD: Data@123456
volumes:
- /data/orion-visor-space/docker-volumes/redis/data:/data
command: sh -c "redis-server /usr/local/redis.conf --requirepass $${REDIS_PASSWORD}"
@@ -78,7 +78,7 @@ services:
build:
context: ./docker/e2e
environment:
SERVER: http://service:80
SERVER: http://service:9200
depends_on:
service:
condition: service_healthy

View File

@@ -1,5 +1,6 @@
version: '3.3'
# latest = 2.4.0
services:
ui:
image: registry.cn-hangzhou.aliyuncs.com/orionsec/orion-visor-ui:latest
@@ -18,16 +19,19 @@ services:
ports:
- 9200:9200
environment:
- SPRING_PROFILES_ACTIVE=${SPRING_PROFILES_ACTIVE:-prod}
- MYSQL_HOST=${MYSQL_HOST:-mysql}
- MYSQL_PORT=${MYSQL_PORT:-3306}
- MYSQL_DATABASE=${MYSQL_DATABASE:-orion_visor}
- MYSQL_USER=${MYSQL_USER:-root}
- MYSQL_PASSWORD=${MYSQL_PASSWORD:-Data@123456}
- REDIS_HOST=${REDIS_HOST:-redis}
- REDIS_PASSWORD=${REDIS_PASSWORD:-Data@123456}
- SECRET_KEY=${SECRET_KEY:-uQeacXV8b3isvKLK}
- DEMO_MODE=${DEMO_MODE:-false}
SPRING_PROFILES_ACTIVE: ${SPRING_PROFILES_ACTIVE:-prod}
MYSQL_HOST: ${MYSQL_HOST:-mysql}
MYSQL_PORT: ${MYSQL_PORT:-3306}
MYSQL_DATABASE: ${MYSQL_DATABASE:-orion_visor}
MYSQL_USER: ${MYSQL_USER:-root}
MYSQL_PASSWORD: ${MYSQL_PASSWORD:-Data@123456}
REDIS_HOST: ${REDIS_HOST:-redis}
REDIS_PASSWORD: ${REDIS_PASSWORD:-Data@123456}
GUACD_HOST: ${GUACD_HOST:-guacd}
GUACD_PORT: ${GUACD_PORT:-4822}
GUACD_DRIVE_PATH: ${GUACD_DRIVE_PATH:-/drive}
SECRET_KEY: ${SECRET_KEY:-uQeacXV8b3isvKLK}
DEMO_MODE: ${DEMO_MODE:-false}
volumes:
- ${VOLUME_BASE:-/data/orion-visor-space/docker-volumes}/service/root-orion:/root/orion
restart: unless-stopped
@@ -51,10 +55,10 @@ services:
ports:
- 3307:3306
environment:
- MYSQL_DATABASE=${MYSQL_DATABASE:-orion_visor}
- MYSQL_USER=${MYSQL_USER:-orion}
- MYSQL_PASSWORD=${MYSQL_PASSWORD:-Data@123456}
- MYSQL_ROOT_PASSWORD=${MYSQL_ROOT_PASSWORD:-Data@123456}
MYSQL_DATABASE: ${MYSQL_DATABASE:-orion_visor}
MYSQL_USER: ${MYSQL_USER:-orion}
MYSQL_PASSWORD: ${MYSQL_PASSWORD:-Data@123456}
MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD:-Data@123456}
volumes:
- ${VOLUME_BASE:-/data/orion-visor-space/docker-volumes}/mysql/var-lib-mysql:/var/lib/mysql
- ${VOLUME_BASE:-/data/orion-visor-space/docker-volumes}/mysql/var-lib-mysql-files:/var/lib/mysql-files
@@ -74,7 +78,7 @@ services:
ports:
- 6380:6379
environment:
- REDIS_PASSWORD=${REDIS_PASSWORD:-Data@123456}
REDIS_PASSWORD: ${REDIS_PASSWORD:-Data@123456}
volumes:
- ${VOLUME_BASE:-/data/orion-visor-space/docker-volumes}/redis/data:/data
command: sh -c "redis-server /usr/local/redis.conf --requirepass $${REDIS_PASSWORD}"
@@ -88,12 +92,34 @@ services:
networks:
- orion-visor-net
guacd:
image: registry.cn-hangzhou.aliyuncs.com/orionsec/orion-visor-guacd:latest
ports:
- 4822:4822
environment:
GUACD_LOG_LEVEL: info
GUACD_LOG_FILE: /var/log/guacd.log
volumes:
- ${VOLUME_BASE:-/data/orion-visor-space/docker-volumes}/guacd/drive:${GUACD_DRIVE_PATH:-/drive}
- ${VOLUME_BASE:-/data/orion-visor-space/docker-volumes}/guacd/var-logs:/var/log
- ${VOLUME_BASE:-/data/orion-visor-space/docker-volumes}/guacd/local-guacamole-lib:/usr/local/guacamole/lib
- ${VOLUME_BASE:-/data/orion-visor-space/docker-volumes}/guacd/local-guacamole-extensions:/usr/local/guacamole/extensions
restart: unless-stopped
healthcheck:
test: [ "CMD", "nc", "-vz", "localhost", "4822" ]
interval: 15s
timeout: 5s
retries: 10
start_period: 10s
networks:
- orion-visor-net
adminer:
image: registry.cn-hangzhou.aliyuncs.com/orionsec/orion-visor-adminer:latest
ports:
- 8081:8080
environment:
- ADMINER_DEFAULT_SERVER=${MYSQL_HOST:-mysql}
ADMINER_DEFAULT_SERVER: ${MYSQL_HOST:-mysql}
depends_on:
mysql:
condition: service_healthy

View File

@@ -1,7 +1,16 @@
#/bin/bash
docker compose down
# demo 启动
#!/bin/bash
# 停止并移除现有容器
docker compose down --remove-orphans
if [ "$1" == "demo" ]; then
sed -i 's/\${DEMO_MODE:-false}/true/g' docker-compose.yml
# 设置 DEMO_MODE 环境变量为 true
export DEMO_MODE=true
echo "Starting services for demo mode..."
# 启动指定的服务
docker compose up -d --remove-orphans mysql redis ui service adminer
else
echo "Starting all services..."
# 正常启动所有服务
docker compose up -d --remove-orphans
fi
docker compose up -d --remove-orphans

View File

@@ -1,6 +1,6 @@
#/bin/bash
set -e
version=2.3.6
version=2.4.0
docker build -t orion-visor-adminer:${version} .
docker tag orion-visor-adminer:${version} registry.cn-hangzhou.aliyuncs.com/orionsec/orion-visor-adminer:${version}
docker tag orion-visor-adminer:${version} registry.cn-hangzhou.aliyuncs.com/orionsec/orion-visor-adminer:latest

View File

@@ -2,7 +2,7 @@
# yaml-language-server: $schema=https://linuxsuren.github.io/api-testing/api-testing-schema.json
name: orion-visor
api: |
{{default "http://orion-visor-service:9200" (env "SERVER")}}
{{default "http://service:9200" (env "SERVER")}}
items:
- name: login
request:

10
docker/guacd/Dockerfile Normal file
View File

@@ -0,0 +1,10 @@
FROM guacamole/guacd:1.5.5
USER root
# 系统时区
ARG TZ=Asia/Shanghai
# 设置时区
RUN ln -sf /usr/share/zoneinfo/${TZ} /etc/localtime && \
echo '${TZ}' > /etc/timezone
# 创建所需目录
RUN mkdir -p /home/guacd/drive /usr/share/guacd/drive

6
docker/guacd/build.sh Normal file
View File

@@ -0,0 +1,6 @@
#/bin/bash
set -e
version=2.4.0
docker build -t orion-visor-guacd:${version} .
docker tag orion-visor-guacd:${version} registry.cn-hangzhou.aliyuncs.com/orionsec/orion-visor-guacd:${version}
docker tag orion-visor-guacd:${version} registry.cn-hangzhou.aliyuncs.com/orionsec/orion-visor-guacd:latest

View File

@@ -1,6 +1,6 @@
#/bin/bash
set -e
version=2.3.6
version=2.4.0
cp -r ../../sql ./sql
docker build -t orion-visor-mysql:${version} .
rm -rf ./sql

View File

@@ -1,13 +1,15 @@
#/bin/bash
set -e
version=2.3.6
version=2.4.0
docker push registry.cn-hangzhou.aliyuncs.com/orionsec/orion-visor-adminer:${version}
docker push registry.cn-hangzhou.aliyuncs.com/orionsec/orion-visor-mysql:${version}
docker push registry.cn-hangzhou.aliyuncs.com/orionsec/orion-visor-redis:${version}
docker push registry.cn-hangzhou.aliyuncs.com/orionsec/orion-visor-guacd:${version}
docker push registry.cn-hangzhou.aliyuncs.com/orionsec/orion-visor-service:${version}
docker push registry.cn-hangzhou.aliyuncs.com/orionsec/orion-visor-ui:${version}
docker push registry.cn-hangzhou.aliyuncs.com/orionsec/orion-visor-adminer:latest
docker push registry.cn-hangzhou.aliyuncs.com/orionsec/orion-visor-mysql:latest
docker push registry.cn-hangzhou.aliyuncs.com/orionsec/orion-visor-redis:latest
docker push registry.cn-hangzhou.aliyuncs.com/orionsec/orion-visor-guacd:latest
docker push registry.cn-hangzhou.aliyuncs.com/orionsec/orion-visor-service:latest
docker push registry.cn-hangzhou.aliyuncs.com/orionsec/orion-visor-ui:latest

View File

@@ -1,6 +1,6 @@
#/bin/bash
set -e
version=2.3.6
version=2.4.0
docker build -t orion-visor-redis:${version} .
docker tag orion-visor-redis:${version} registry.cn-hangzhou.aliyuncs.com/orionsec/orion-visor-redis:${version}
docker tag orion-visor-redis:${version} registry.cn-hangzhou.aliyuncs.com/orionsec/orion-visor-redis:latest

View File

@@ -1,6 +1,6 @@
#/bin/bash
set -e
version=2.3.6
version=2.4.0
mv ../../orion-visor-launch/target/orion-visor-launch.jar ./orion-visor-launch.jar
docker build -t orion-visor-service:${version} .
rm -rf ./orion-visor-launch.jar

View File

@@ -1,6 +1,6 @@
#/bin/bash
set -e
version=2.3.6
version=2.4.0
mv ../../orion-visor-ui/dist ./dist
docker build -t orion-visor-ui:${version} .
rm -rf ./orion-visor-launch.jar

View File

@@ -36,7 +36,7 @@ public interface AppConst extends OrionConst {
/**
* 同 ${orion.version} 迭代时候需要手动更改
*/
String VERSION = "2.3.6";
String VERSION = "2.4.0";
/**
* 同 ${spring.application.name}

View File

@@ -51,6 +51,11 @@ public interface Const extends cn.orionsec.kit.lang.constant.Const, FieldConst,
String SYSTEM_USERNAME = "system";
// FIXME KIT
String ADMINISTRATOR = "Administrator";
Long ALL_HOST_ID = -1L;
int BATCH_COUNT = 500;
}

View File

@@ -88,7 +88,7 @@ public enum ErrorCode implements CodeInfo {
EXCEL_PASSWORD_ERROR(905, "文档密码错误"),
PASER_FAILED(906, "解析失败"),
PASER_FAILED(906, "表达式解析失败"),
ENCRYPT_ERROR(907, "数据加密异常"),
@@ -134,9 +134,10 @@ public enum ErrorCode implements CodeInfo {
* 获取 wapper
*
* @param data data
* @param <T> T
* @return HttpWrapper
*/
public HttpWrapper<?> wrapper() {
public <T> HttpWrapper<T> wrapper() {
return HttpWrapper.of(this);
}

View File

@@ -24,6 +24,14 @@ package org.dromara.visor.common.constant;
import cn.orionsec.kit.lang.exception.ApplicationException;
import cn.orionsec.kit.lang.exception.argument.InvalidArgumentException;
import cn.orionsec.kit.lang.utils.Strings;
import org.springframework.validation.BindException;
import org.springframework.web.bind.MissingServletRequestParameterException;
import javax.validation.ConstraintViolationException;
import java.util.Iterator;
import java.util.Optional;
import java.util.Set;
/**
* 错误信息
@@ -52,6 +60,12 @@ public interface ErrorMessage {
String IDENTITY_ABSENT = "主机身份不存在";
String CHECK_IDENTITY_PASSWORD = "请选择类型为[密码]的主机身份";
String KEY_ABSENT_WITH = "主机密钥不存在 {}";
String IDENTITY_ABSENT_WITH = "主机身份不存在 {}";
String CONFIG_ABSENT = "配置不存在";
String CONFIG_PRESENT = "配置已存在";
@@ -90,28 +104,42 @@ public interface ErrorMessage {
String HOST_NOT_ENABLED = "主机未启用";
String CONFIG_NOT_ENABLED = "配置未启用";
String UNABLE_OPERATE_ADMIN_ROLE = "无法操作管理员账号";
String UNSUPPORTED_CHARSET = "不支持的编码 [{}]";
String DECRYPT_ERROR = "数据解密失败";
String PASSWORD_MISSING = "请输入密码";
String BEFORE_PASSWORD_ERROR = "原密码错误";
String DATA_NO_PERMISSION = "数据无权限";
String EXPRESSION_ERROR = "表达式错误";
String ANY_NO_PERMISSION = "{}无权限";
String OPT_NO_PERMISSION = "无操作权限";
String SESSION_PRESENT = "会话已存在";
String SESSION_ABSENT = "会话不存在";
String SESSION_CLOSED = "会话已关闭";
String USER_UNSUPPORTED_OPT = "用户不支持此操作";
String CURRENT_USER_UNSUPPORTED_OPT = "当前" + USER_UNSUPPORTED_OPT;
String PATH_NOT_NORMALIZE = "路径不合法";
String OPERATE_ERROR = "操作失败";
String ENCRYPT_KEY_UNSET = "加密密钥未配置";
String DECRYPT_ERROR = "数据解密失败";
String UNKNOWN_TYPE = "未知类型";
String ERROR_TYPE = "错误的类型";
@@ -146,8 +174,26 @@ public interface ErrorMessage {
String CLIENT_ABORT = "手动中断";
String COMMAND_EXEC_ERROR = "命令执行失败 [{}]";
String COMPRESS_ERROR = "压缩失败";
String DECOMPRESS_ERROR = "解压失败";
String COMPRESS_FILE_ABSENT = "压缩文件不存在";
String UNABLE_DOWNLOAD_FOLDER = "无法下载文件夹";
String VALID_ERROR = "验证失败";
String CONVERT_ERROR = "转换失败";
String PRESENT_MODIFY = "{} 已存在, 请修改后重试";
String ILLEGAL_MODIFY = "{} 不正确, 请修改后重试";
String PLEASE_SELECT_SUFFIX_FILE = "请选择 {} 类型的文件";
/**
* 是否为业务异常
*
@@ -185,4 +231,37 @@ public interface ErrorMessage {
return defaultMsg;
}
/**
* 获取验证错误消息
*
* @param ex ex
* @param defaultMsg defaultMsg
* @return message
*/
static String getValidErrorMessage(Exception ex, String defaultMsg) {
if (ex == null) {
return null;
}
// 参数不存在异常
if (ex instanceof MissingServletRequestParameterException) {
return Strings.format(ErrorMessage.MISSING, ((MissingServletRequestParameterException) ex).getParameterName());
}
// 参数绑定异常
if (ex instanceof BindException) {
return Optional.ofNullable(((BindException) ex)
.getFieldError())
.map(error -> error.getField() + Const.SPACE + error.getDefaultMessage())
.orElse(defaultMsg);
}
// 参数验证异常
if (ex instanceof ConstraintViolationException) {
return Optional.ofNullable(((ConstraintViolationException) ex).getConstraintViolations())
.map(Set::iterator)
.map(Iterator::next)
.map(s -> s.getPropertyPath().toString() + Const.SPACE + s.getMessage())
.orElse(defaultMsg);
}
return getErrorMessage(ex, defaultMsg);
}
}

View File

@@ -55,7 +55,7 @@ public interface ExtraFieldConst extends FieldConst {
String GRANT_NAME = "grantName";
String CHANNEL_ID = "channelId";
String CHANNEL = "channel";
String SESSION_ID = "sessionId";

View File

@@ -103,8 +103,20 @@ public interface FieldConst {
String FILTER = "filter";
String LICENSE = "license";
String SESSION = "session";
String CONNECT = "connect";
String ALL = "all";
String PROPS = "props";
String SENDER = "sender";
String RESULT = "result";
String CONFIG = "config";
}

View File

@@ -52,14 +52,26 @@ public enum EnableStatus {
public static EnableStatus of(Integer value) {
if (value == null) {
return null;
return DISABLED;
}
for (EnableStatus e : values()) {
if (e.value.equals(value)) {
return e;
}
}
return null;
return DISABLED;
}
public static EnableStatus of(String value) {
if (value == null) {
return DISABLED;
}
for (EnableStatus e : values()) {
if (e.name().equals(value)) {
return e;
}
}
return DISABLED;
}
}

View File

@@ -0,0 +1,148 @@
/*
* Copyright (c) 2023 - present Dromara, All rights reserved.
*
* https://visor.dromara.org
* https://visor.dromara.org.cn
* https://visor.orionsec.cn
*
* Members:
* Jiahang Li - ljh1553488six@139.com - author
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.dromara.visor.common.enums;
import cn.orionsec.kit.lang.utils.collect.Lists;
import cn.orionsec.kit.lang.utils.time.DateStream;
import cn.orionsec.kit.lang.utils.time.Dates;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.stream.Collectors;
/**
* 统计区间枚举
*
* @author Jiahang Li
* @version 1.0.0
* @since 2024/12/23 14:02
*/
public enum StatisticsRange {
/**
* 当天
*/
TODAY {
@Override
public Date getRangeEndTime(Date startTime) {
return DateStream.of(startTime)
.dayEnd()
.date();
}
@Override
public List<String> getDateRanges(Date startTime) {
return Lists.singleton(Dates.format(startTime, Dates.YMD));
}
},
/**
* 日视图
*/
DAY {
@Override
public Date getRangeEndTime(Date startTime) {
return DateStream.of(startTime)
.dayEnd()
.date();
}
@Override
public List<String> getDateRanges(Date startTime) {
return Lists.singleton(Dates.format(startTime, Dates.YMD));
}
},
/**
* 周视图
*/
WEEK {
@Override
public Date getRangeEndTime(Date startTime) {
return DateStream.of(startTime)
.addDay(7)
.dayEnd()
.date();
}
@Override
public List<String> getDateRanges(Date startTime) {
return Arrays.stream(Dates.getIncrementDayDates(startTime, 1, 7))
.map(s -> Dates.format(s, Dates.YMD))
.collect(Collectors.toList());
}
},
/**
* 月视图
*/
MONTH {
@Override
public Date getRangeEndTime(Date startTime) {
return DateStream.of(startTime)
.addMonth(1)
.dayEnd()
.date();
}
@Override
public List<String> getDateRanges(Date startTime) {
int monthLastDay = Dates.getMonthLastDay(startTime);
return Arrays.stream(Dates.getIncrementDayDates(startTime, 1, monthLastDay - 1))
.map(s -> Dates.format(s, Dates.YMD))
.collect(Collectors.toList());
}
},
;
/**
* 获取区间结束时间
*
* @param startTime startTime
* @return end
*/
public abstract Date getRangeEndTime(Date startTime);
/**
* 获取时间区间
*
* @param startTime startTime
* @return ranges
*/
public abstract List<String> getDateRanges(Date startTime);
public static StatisticsRange of(String type) {
if (type == null) {
return TODAY;
}
for (StatisticsRange value : values()) {
if (value.name().equals(type)) {
return value;
}
}
return TODAY;
}
}

View File

@@ -44,7 +44,6 @@ public interface GenericsDataModel {
return JSON.toJSONString(this);
}
/**
* 转为 map
*

View File

@@ -0,0 +1,81 @@
/*
* Copyright (c) 2023 - present Dromara, All rights reserved.
*
* https://visor.dromara.org
* https://visor.dromara.org.cn
* https://visor.orionsec.cn
*
* Members:
* Jiahang Li - ljh1553488six@139.com - author
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.dromara.visor.common.mapstruct;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
/**
* json 转换器
*
* @author Jiahang Li
* @version 1.0.0
* @since 2025/3/7 17:43
*/
public class JsonConversion {
private JsonConversion() {
}
/**
* JSONString > JSONObject
*
* @param json json
* @return JSONObject
*/
public static JSONObject stringToJsonObject(String json) {
return json != null ? JSON.parseObject(json) : null;
}
/**
* JSONString > JSONArray.
*
* @param json json
* @return JSONArray
*/
public static JSONArray stringToJsonArray(String json) {
return json != null ? JSON.parseArray(json) : null;
}
/**
* JSONObject > JSONString.
*
* @param jsonObject JSONObject
* @return JSONString
*/
public static String jsonObjectToString(JSONObject jsonObject) {
return jsonObject != null ? jsonObject.toJSONString() : null;
}
/**
* JSONArray > JSONString.
*
* @param jsonArray JSONArray
* @return JSONString
*/
public static String jsonArrayToString(JSONArray jsonArray) {
return jsonArray != null ? jsonArray.toJSONString() : null;
}
}

View File

@@ -0,0 +1,128 @@
/*
* Copyright (c) 2023 - present Dromara, All rights reserved.
*
* https://visor.dromara.org
* https://visor.dromara.org.cn
* https://visor.orionsec.cn
*
* Members:
* Jiahang Li - ljh1553488six@139.com - author
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.dromara.visor.common.mapstruct;
import cn.orionsec.kit.lang.utils.Strings;
import cn.orionsec.kit.lang.utils.collect.Lists;
import org.dromara.visor.common.constant.Const;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
/**
* string 转换器
*
* @author Jiahang Li
* @version 1.0.0
* @since 2025/3/7 17:35
*/
public class StringConversion {
private StringConversion() {
}
/**
* String > List<Integer>
*
* @param str str
* @return list
*/
public static List<Integer> stringToIntegerList(String str) {
if (Strings.isBlank(str)) {
return Lists.newList();
}
return Arrays.stream(str.split(Const.COMMA))
.map(Integer::valueOf)
.collect(Collectors.toList());
}
/**
* String > List<String>
*
* @param str str
* @return list
*/
public static List<Long> stringToLongList(String str) {
if (Strings.isBlank(str)) {
return Lists.newList();
}
return Arrays.stream(str.split(Const.COMMA))
.map(Long::valueOf)
.collect(Collectors.toList());
}
/**
* String > List<String>
*
* @param str str
* @return list
*/
public static List<String> stringToStringList(String str) {
if (Strings.isBlank(str)) {
return Lists.newList();
}
return Lists.of(str.split(Const.COMMA));
}
/**
* List<Integer> String
*
* @param list list
* @return str
*/
public static String integerListToString(List<Integer> list) {
if (list != null) {
return list.stream()
.map(String::valueOf)
.collect(Collectors.joining(Const.COMMA));
}
return null;
}
/**
* List<Long> String
*
* @param list list
* @return str
*/
public static String longListToString(List<Long> list) {
if (list != null) {
return list.stream()
.map(String::valueOf)
.collect(Collectors.joining(Const.COMMA));
}
return null;
}
/**
* List<String> String
*
* @param list list
* @return str
*/
public static String stringListToString(List<String> list) {
return list != null ? String.join(Const.COMMA, list) : null;
}
}

View File

@@ -47,4 +47,11 @@ public interface UpdatePasswordAction extends Serializable {
*/
String getPassword();
/**
* 设置密码
*
* @param password password
*/
void setPassword(String password);
}

View File

@@ -0,0 +1,77 @@
/*
* Copyright (c) 2023 - present Dromara, All rights reserved.
*
* https://visor.dromara.org
* https://visor.dromara.org.cn
* https://visor.orionsec.cn
*
* Members:
* Jiahang Li - ljh1553488six@139.com - author
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.dromara.visor.common.session;
import cn.orionsec.kit.lang.exception.AuthenticationException;
import cn.orionsec.kit.lang.exception.argument.InvalidArgumentException;
import cn.orionsec.kit.lang.utils.Exceptions;
import cn.orionsec.kit.lang.utils.Strings;
import org.dromara.visor.common.constant.Const;
/**
* 连接消息
*
* @author Jiahang Li
* @version 1.0.0
* @since 2024/7/11 16:30
*/
public interface SessionMessage {
String AUTHENTICATION_FAILURE = "身份认证失败. {}";
String SERVER_UNREACHABLE = "无法连接至服务器. {}";
String CONNECTION_TIMEOUT = "连接服务器超时. {}";
/**
* 获取错误信息
*
* @param address address
* @param e e
* @return errorMessage
*/
static String getErrorMessage(String address, Exception e) {
if (e == null) {
return null;
}
String message = e.getMessage();
if (Strings.contains(message, Const.TIMEOUT)) {
// 连接超时
return Strings.format(SessionMessage.CONNECTION_TIMEOUT, address);
} else if (Exceptions.isCausedBy(e, AuthenticationException.class)) {
// 认证失败
return Strings.format(SessionMessage.AUTHENTICATION_FAILURE, address);
} else if (Exceptions.isCausedBy(e, InvalidArgumentException.class)) {
// 参数错误
if (Strings.isBlank(message)) {
return Strings.format(SessionMessage.SERVER_UNREACHABLE, address);
} else {
return message;
}
} else {
// 其他错误
return Strings.format(SessionMessage.SERVER_UNREACHABLE, address);
}
}
}

View File

@@ -0,0 +1,72 @@
/*
* Copyright (c) 2023 - present Dromara, All rights reserved.
*
* https://visor.dromara.org
* https://visor.dromara.org.cn
* https://visor.orionsec.cn
*
* Members:
* Jiahang Li - ljh1553488six@139.com - author
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.dromara.visor.common.session.config;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.SuperBuilder;
/**
* 基础连接配置实现
*
* @author Jiahang Li
* @version 1.0.0
* @since 2025/4/1 16:59
*/
@Data
@SuperBuilder
@NoArgsConstructor
@AllArgsConstructor
@Schema(name = "BaseConnectConfig", description = "基础连接参数")
public class BaseConnectConfig implements IBaseConnectConfig {
@Schema(description = "系统类型")
private String osType;
@Schema(description = "系统架构")
private String archType;
@Schema(description = "hostId")
private Long hostId;
@Schema(description = "hostName")
private String hostName;
@Schema(description = "主机编码")
private String hostCode;
@Schema(description = "主机地址")
private String hostAddress;
@Schema(description = "主机端口")
private Integer hostPort;
@Schema(description = "用户名")
private String username;
@Schema(description = "密码")
private String password;
}

View File

@@ -0,0 +1,72 @@
/*
* Copyright (c) 2023 - present Dromara, All rights reserved.
*
* https://visor.dromara.org
* https://visor.dromara.org.cn
* https://visor.orionsec.cn
*
* Members:
* Jiahang Li - ljh1553488six@139.com - author
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.dromara.visor.common.session.config;
/**
* 基础连接配置定义
*
* @author Jiahang Li
* @version 1.0.0
* @since 2025/6/24 17:11
*/
public interface IBaseConnectConfig {
// -------------------- getter/setter --------------------
String getOsType();
void setOsType(String osType);
String getArchType();
void setArchType(String archType);
Long getHostId();
void setHostId(Long hostId);
String getHostName();
void setHostName(String hostName);
String getHostCode();
void setHostCode(String hostCode);
String getHostAddress();
void setHostAddress(String hostAddress);
Integer getHostPort();
void setHostPort(Integer hostPort);
String getUsername();
void setUsername(String username);
String getPassword();
void setPassword(String password);
}

View File

@@ -0,0 +1,80 @@
/*
* Copyright (c) 2023 - present Dromara, All rights reserved.
*
* https://visor.dromara.org
* https://visor.dromara.org.cn
* https://visor.orionsec.cn
*
* Members:
* Jiahang Li - ljh1553488six@139.com - author
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.dromara.visor.common.session.config;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import lombok.experimental.SuperBuilder;
/**
* RDP 连接参数
*
* @author Jiahang Li
* @version 1.0.0
* @since 2025/4/1 16:57
*/
@Data
@SuperBuilder
@NoArgsConstructor
@AllArgsConstructor
@EqualsAndHashCode(callSuper = true)
@Schema(name = "RdpConnectConfig", description = "RDP 连接参数")
public class RdpConnectConfig extends BaseConnectConfig {
@Schema(description = "低带宽模式")
private Boolean lowBandwidthMode;
@Schema(description = "RDP 版本是否大于8.1")
private Boolean versionGt81;
@Schema(description = "时区")
private String timezone;
@Schema(description = "键盘布局")
private String keyboardLayout;
@Schema(description = "剪切板规范")
private String clipboardNormalize;
@Schema(description = "")
private String domain;
@Schema(description = "预连接id")
private String preConnectionId;
@Schema(description = "预连接数据")
private String preConnectionBlob;
@Schema(description = "远程应用")
private String remoteApp;
@Schema(description = "远程应用路径")
private String remoteAppDir;
@Schema(description = "远程应用参数")
private String remoteAppArgs;
}

View File

@@ -20,54 +20,29 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.dromara.visor.module.asset.entity.dto;
package org.dromara.visor.common.session.config;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import org.dromara.visor.framework.desensitize.core.annotation.Desensitize;
import org.dromara.visor.framework.desensitize.core.annotation.DesensitizeObject;
import lombok.experimental.SuperBuilder;
/**
* 终端连接参数
* SSH 连接参数
*
* @author Jiahang Li
* @version 1.0.0
* @since 2023/12/26 15:47
*/
@Data
@Builder
@SuperBuilder
@NoArgsConstructor
@AllArgsConstructor
@DesensitizeObject
@Schema(name = "TerminalConnectDTO", description = "终端连接参数")
public class TerminalConnectDTO {
@Schema(description = "logId")
private Long logId;
@Schema(description = "连接类型")
private String connectType;
@Schema(description = "hostId")
private Long hostId;
@Schema(description = "hostName")
private String hostName;
@Schema(description = "主机编码")
private String hostCode;
@Schema(description = "主机地址")
private String hostAddress;
@Schema(description = "主机端口")
private Integer hostPort;
@Schema(description = "系统类型")
private String osType;
@EqualsAndHashCode(callSuper = true)
@Schema(name = "SshConnectConfig", description = "SSH 连接参数")
public class SshConnectConfig extends BaseConnectConfig {
@Schema(description = "超时时间")
private Integer timeout;
@@ -81,25 +56,15 @@ public class TerminalConnectDTO {
@Schema(description = "文件内容编码")
private String fileContentCharset;
@Schema(description = "用户名")
private String username;
@Desensitize(toEmpty = true)
@Schema(description = "密码")
private String password;
@Schema(description = "密钥id")
private Long keyId;
@Desensitize(toEmpty = true)
@Schema(description = "公钥文本")
private String publicKey;
@Desensitize(toEmpty = true)
@Schema(description = "私钥文本")
private String privateKey;
@Desensitize(toEmpty = true)
@Schema(description = "私钥密码")
private String privateKeyPassword;

View File

@@ -20,10 +20,8 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.dromara.visor.module.asset.handler.host.jsch;
package org.dromara.visor.common.session.ssh;
import cn.orionsec.kit.lang.exception.AuthenticationException;
import cn.orionsec.kit.lang.exception.argument.InvalidArgumentException;
import cn.orionsec.kit.lang.utils.Exceptions;
import cn.orionsec.kit.lang.utils.Strings;
import cn.orionsec.kit.net.host.SessionHolder;
@@ -31,9 +29,9 @@ import cn.orionsec.kit.net.host.SessionLogger;
import cn.orionsec.kit.net.host.SessionStore;
import lombok.extern.slf4j.Slf4j;
import org.dromara.visor.common.constant.AppConst;
import org.dromara.visor.common.constant.Const;
import org.dromara.visor.common.session.SessionMessage;
import org.dromara.visor.common.session.config.SshConnectConfig;
import org.dromara.visor.common.utils.AesEncryptUtils;
import org.dromara.visor.module.asset.entity.dto.TerminalConnectDTO;
import java.util.Optional;
@@ -47,25 +45,22 @@ import java.util.Optional;
@Slf4j
public class SessionStores {
protected static final ThreadLocal<String> CURRENT_ADDRESS = new ThreadLocal<>();
/**
* 打开 sessionStore
*
* @param conn conn
* @param config config
* @return sessionStore
*/
public static SessionStore openSessionStore(TerminalConnectDTO conn) {
Long hostId = conn.getHostId();
String address = conn.getHostAddress();
String username = conn.getUsername();
public static SessionStore openSessionStore(SshConnectConfig config) {
Long hostId = config.getHostId();
String address = config.getHostAddress();
String username = config.getUsername();
log.info("SessionStores-open-start hostId: {}, address: {}, username: {}", hostId, address, username);
try {
CURRENT_ADDRESS.set(address);
// 创建会话
SessionHolder sessionHolder = SessionHolder.create();
sessionHolder.setLogger(SessionLogger.INFO);
SessionStore session = createSessionStore(conn, sessionHolder);
SessionStore session = createSessionStore(config, sessionHolder);
// 设置版本
session.getSession().setClientVersion("SSH-2.0-ORION_VISOR_V" + AppConst.VERSION);
// 连接
@@ -75,81 +70,48 @@ public class SessionStores {
} catch (Exception e) {
String message = e.getMessage();
log.error("SessionStores-open-error hostId: {}, address: {}, username: {}, message: {}", hostId, address, username, message, e);
throw Exceptions.app(getErrorMessage(e), e);
} finally {
CURRENT_ADDRESS.remove();
throw Exceptions.app(SessionMessage.getErrorMessage(address, e), e);
}
}
/**
* 创建 sessionStore
*
* @param conn conn
* @param config config
* @param sessionHolder sessionHolder
* @return sessionStore
*/
private static SessionStore createSessionStore(TerminalConnectDTO conn, SessionHolder sessionHolder) {
final boolean useKey = conn.getKeyId() != null;
private static SessionStore createSessionStore(SshConnectConfig config, SessionHolder sessionHolder) {
final boolean useKey = config.getKeyId() != null;
// 使用密钥认证
if (useKey) {
// 加载密钥
String publicKey = Optional.ofNullable(conn.getPublicKey())
String publicKey = Optional.ofNullable(config.getPublicKey())
.map(AesEncryptUtils::decryptAsString)
.orElse(null);
String privateKey = Optional.ofNullable(conn.getPrivateKey())
String privateKey = Optional.ofNullable(config.getPrivateKey())
.map(AesEncryptUtils::decryptAsString)
.orElse(null);
String password = Optional.ofNullable(conn.getPrivateKeyPassword())
String password = Optional.ofNullable(config.getPrivateKeyPassword())
.map(AesEncryptUtils::decryptAsString)
.orElse(null);
sessionHolder.addIdentityValue(String.valueOf(conn.getKeyId()),
sessionHolder.addIdentityValue(String.valueOf(config.getKeyId()),
privateKey,
publicKey,
password);
}
// 获取会话
SessionStore session = sessionHolder.getSession(conn.getHostAddress(), conn.getHostPort(), conn.getUsername());
SessionStore session = sessionHolder.getSession(config.getHostAddress(), config.getHostPort(), config.getUsername());
// 使用密码认证
if (!useKey) {
String password = conn.getPassword();
String password = config.getPassword();
if (!Strings.isEmpty(password)) {
session.password(AesEncryptUtils.decryptAsString(password));
}
}
// 超时时间
session.timeout(conn.getTimeout());
session.timeout(config.getTimeout());
return session;
}
/**
* 获取错误信息
*
* @param e e
* @return errorMessage
*/
private static String getErrorMessage(Exception e) {
if (e == null) {
return null;
}
String host = CURRENT_ADDRESS.get();
String message = e.getMessage();
if (Strings.contains(message, Const.TIMEOUT)) {
// 连接超时
return Strings.format(SessionMessage.CONNECTION_TIMEOUT, host);
} else if (Exceptions.isCausedBy(e, AuthenticationException.class)) {
// 认证失败
return Strings.format(SessionMessage.AUTHENTICATION_FAILURE, host);
} else if (Exceptions.isCausedBy(e, InvalidArgumentException.class)) {
// 参数错误
if (Strings.isBlank(message)) {
return Strings.format(SessionMessage.SERVER_UNREACHABLE, host);
} else {
return message;
}
} else {
// 其他错误
return Strings.format(SessionMessage.SERVER_UNREACHABLE, host);
}
}
}

View File

@@ -22,7 +22,7 @@
*/
package org.dromara.visor.common.utils;
import cn.orionsec.kit.lang.constant.Const;
import org.dromara.visor.common.constant.Const;
import java.util.Arrays;
import java.util.List;

View File

@@ -43,6 +43,9 @@ public class SqlUtils {
* @return limit
*/
public static String limit(Number limit) {
if (limit == null) {
return Const.EMPTY;
}
return Const.LIMIT + Const.SPACE + limit;
}
@@ -54,6 +57,9 @@ public class SqlUtils {
* @return limit
*/
public static String limit(Number offset, Number limit) {
if (offset == null) {
return limit(limit);
}
return Const.LIMIT + Const.SPACE + offset + Const.COMMA + limit;
}

View File

@@ -25,6 +25,7 @@ package org.dromara.visor.common.utils;
import cn.orionsec.kit.lang.utils.Arrays1;
import cn.orionsec.kit.lang.utils.io.Files1;
import cn.orionsec.kit.spring.SpringHolder;
import org.dromara.visor.common.constant.Const;
import org.dromara.visor.common.constant.ErrorMessage;
import javax.validation.ConstraintViolation;
@@ -159,4 +160,17 @@ public class Valid extends cn.orionsec.kit.lang.utils.Valid {
return Files1.getPath(path);
}
/**
* 检查后缀
*
* @param file file
* @param suffix suffix
* @return file
*/
public static String checkSuffix(String file, String suffix) {
Valid.notBlank(file);
Valid.isTrue(file.toLowerCase().endsWith(Const.DOT + suffix), ErrorMessage.PLEASE_SELECT_SUFFIX_FILE, suffix);
return file;
}
}

View File

@@ -14,7 +14,7 @@
<url>https://github.com/dromara/orion-visor</url>
<properties>
<revision>2.3.6</revision>
<revision>2.4.0</revision>
<spring.boot.version>2.7.17</spring.boot.version>
<spring.boot.admin.version>2.7.15</spring.boot.admin.version>
<flatten.maven.plugin.version>1.5.0</flatten.maven.plugin.version>
@@ -34,6 +34,7 @@
<mockito.inline.version>4.11.0</mockito.inline.version>
<jedis.mock.version>1.0.7</jedis.mock.version>
<podam.version>7.2.11.RELEASE</podam.version>
<guacd.version>1.5.5</guacd.version>
</properties>
<dependencyManagement>
@@ -312,6 +313,13 @@
<artifactId>podam</artifactId>
<version>${podam.version}</version>
</dependency>
<!-- guacd -->
<dependency>
<groupId>org.apache.guacamole</groupId>
<artifactId>guacamole-common</artifactId>
<version>${guacd.version}</version>
</dependency>
</dependencies>
</dependencyManagement>

View File

@@ -122,4 +122,9 @@ public class OperatorLogModel implements RequestIdentity {
*/
private Date endTime;
/**
* 创建时间
*/
private Date createTime;
}

View File

@@ -31,8 +31,8 @@ import org.springframework.boot.autoconfigure.AutoConfigureOrder;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.data.redis.RedisProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.data.redis.connection.RedisStandaloneConfiguration;
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
import redis.clients.jedis.JedisPoolConfig;
import java.net.InetAddress;
import java.util.function.Supplier;
@@ -68,12 +68,10 @@ public class OrionNoRedisAutoConfiguration {
*/
@Bean
public JedisConnectionFactory jedisConnectionFactory(RedisServer redisServer) {
JedisConnectionFactory factory = new JedisConnectionFactory();
factory.setHostName(redisServer.getHost());
factory.setPort(redisServer.getBindPort());
factory.setUsePool(true);
factory.setPoolConfig(new JedisPoolConfig());
return factory;
RedisStandaloneConfiguration config = new RedisStandaloneConfiguration();
config.setHostName(redisServer.getHost());
config.setPort(redisServer.getBindPort());
return new JedisConnectionFactory(config);
}
/**

View File

@@ -49,6 +49,8 @@ public class CacheBarriers {
public static final GenericsBarrier<Map<?, ?>> MAP = GenericsAnonymousMapBarrier.create(Const.NONE_ID, Const.NONE_ID);
public static final GenericsBarrier<Map<?, ?>> STRING_MAP = GenericsAnonymousMapBarrier.create(Const.NONE_ID.toString(), Const.NONE_ID);
/**
* 创建屏障对象 防止穿透
*

View File

@@ -230,7 +230,7 @@ public class OrionSecurityAutoConfiguration {
return httpSecurity
// 开启跨域
.cors().and()
// 因为不使用session 禁用CSRF
// 因为不使用 session 禁用 CSRF
.csrf().disable()
// 基于 token 机制所以不需要 session
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()

View File

@@ -11,6 +11,11 @@
"description": "是否为演示模式.",
"defaultValue": false
},
{
"name": "orion.prefix",
"type": "java.lang.String",
"description": "项目前缀."
},
{
"name": "orion.api.prefix",
"type": "java.lang.String",

View File

@@ -0,0 +1,50 @@
/*
* Copyright (c) 2023 - present Dromara, All rights reserved.
*
* https://visor.dromara.org
* https://visor.dromara.org.cn
* https://visor.orionsec.cn
*
* Members:
* Jiahang Li - ljh1553488six@139.com - author
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.dromara.visor.framework.websocket.core.constant;
/**
* ws 服务端关闭 code
* <p>
* > 2999 && < 5000
*
* @author Jiahang Li
* @version 1.0.0
* @since 2021/6/16 15:18
*/
public interface CloseCode {
/**
* code
*
* @return code
*/
int getCode();
/**
* reason
*
* @return reason
*/
String getReason();
}

View File

@@ -22,27 +22,36 @@
*/
package org.dromara.visor.framework.websocket.core.constant;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* ws 服务端关闭 code
* ws 关闭码
* <p>
* > 2999 && < 5000
*
* @author Jiahang Li
* @version 1.0.0
* @since 2021/6/16 15:18
* @since 2024/7/31 17:41
*/
public interface WsCloseCode {
@Getter
@AllArgsConstructor
public enum WsCloseCode implements CloseCode {
/**
* code
*
* @return code
* 初始化失败
*/
int getCode();
INIT_ERROR(3000, "init error"),
/**
* reason
*
* @return reason
* 会话已关闭
*/
String getReason();
SESSION_CLOSED(3100, "session closed"),
;
private final int code;
private final String reason;
}

View File

@@ -22,18 +22,23 @@
*/
package org.dromara.visor.framework.websocket.core.utils;
import cn.orionsec.kit.lang.constant.StandardHttpHeader;
import cn.orionsec.kit.lang.utils.Exceptions;
import cn.orionsec.kit.lang.utils.Threads;
import cn.orionsec.kit.lang.utils.io.Streams;
import com.alibaba.fastjson.JSON;
import lombok.extern.slf4j.Slf4j;
import org.dromara.visor.common.constant.Const;
import org.dromara.visor.framework.websocket.core.constant.WsCloseCode;
import org.dromara.visor.framework.websocket.core.constant.CloseCode;
import org.dromara.visor.framework.websocket.core.session.WebSocketSyncSession;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.web.socket.CloseStatus;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketSession;
import java.io.IOException;
import java.util.List;
/**
* websocket 工具类
@@ -132,18 +137,54 @@ public class WebSockets {
}
}
/**
* 设置子协议
*
* @param request request
* @param response response
*/
public static void setSubProtocols(ServerHttpRequest request, ServerHttpResponse response) {
List<String> subProtocols = request.getHeaders().get(StandardHttpHeader.SEC_WEBSOCKET_PROTOCOL);
if (subProtocols != null) {
response.getHeaders().put(StandardHttpHeader.SEC_WEBSOCKET_PROTOCOL, subProtocols);
}
}
/**
* 关闭会话
*
* @param session session
*/
public static void close(WebSocketSession session) {
if (!session.isOpen()) {
return;
}
Streams.close(session);
}
/**
* 关闭会话
*
* @param session session
* @param code code
*/
public static void close(WebSocketSession session, WsCloseCode code) {
public static void close(WebSocketSession session, CloseCode code) {
close(session, code.getCode(), code.getReason());
}
/**
* 关闭会话
*
* @param session session
* @param code code
* @param reason reason
*/
public static void close(WebSocketSession session, int code, String reason) {
if (!session.isOpen()) {
return;
}
try {
session.close(new CloseStatus(code.getCode(), code.getReason()));
session.close(new CloseStatus(code, reason));
} catch (Exception e) {
log.error("websocket close failure", e);
}

View File

@@ -23,13 +23,26 @@
<optional>true</optional>
</dependency>
<!-- starter -->
<!-- spring boot starter -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<!-- modules -->
<!-- common -->
<dependency>
<groupId>org.dromara.visor</groupId>
<artifactId>orion-visor-common</artifactId>
</dependency>
<!-- module common -->
<dependency>
<groupId>org.dromara.visor</groupId>
<artifactId>orion-visor-module-common</artifactId>
<version>${revision}</version>
</dependency>
<!-- module service -->
<dependency>
<groupId>org.dromara.visor</groupId>
<artifactId>orion-visor-module-infra-service</artifactId>
@@ -40,6 +53,16 @@
<artifactId>orion-visor-module-asset-service</artifactId>
<version>${revision}</version>
</dependency>
<dependency>
<groupId>org.dromara.visor</groupId>
<artifactId>orion-visor-module-exec-service</artifactId>
<version>${revision}</version>
</dependency>
<dependency>
<groupId>org.dromara.visor</groupId>
<artifactId>orion-visor-module-terminal-service</artifactId>
<version>${revision}</version>
</dependency>
<!-- framework starter -->
<dependency>
@@ -112,8 +135,12 @@
<!-- 设置构建的 jar 包名 -->
<finalName>${project.artifactId}</finalName>
<resources>
<!-- 应用配置过滤 -->
<resource>
<directory>src/main/resources</directory>
<includes>
<include>*.yaml</include>
</includes>
<filtering>true</filtering>
</resource>
</resources>

View File

@@ -24,6 +24,11 @@ spring:
server:
enabled: false
guacd:
host: ${GUACD_HOST:127.0.0.1}
port: ${GUACD_PORT:4822}
drive-path: ${GUACD_DRIVE_PATH:/home/guacd/drive}
management:
endpoints:
enabled-by-default: false
@@ -33,7 +38,7 @@ management:
mybatis-plus:
configuration:
# sql 日志打印
# 日志打印
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
no:

View File

@@ -41,6 +41,11 @@ spring:
server:
enabled: true
guacd:
host: ${GUACD_HOST:127.0.0.1}
port: ${GUACD_PORT:4822}
drive-path: ${GUACD_DRIVE_PATH:/usr/share/guacd/drive}
management:
endpoints:
enabled-by-default: true

View File

@@ -163,14 +163,16 @@ orion:
version: @revision@
# 是否为演示模式
demo: false
# 前缀
prefix: /orion-visor
api:
# 公共 api 前缀
prefix: /orion-visor/api
prefix: ${orion.prefix}/api
# 是否允许跨域
cors: true
websocket:
# 公共 websocket 前缀
prefix: /orion-visor/keep-alive
prefix: ${orion.prefix}/keep-alive
# 1MB
binary-buffer-size: 1048576
# 1MB
@@ -194,6 +196,12 @@ orion:
asset:
group: "asset - 资产模块"
path: "asset"
exec:
group: "exec - 执行模块"
path: "exec"
terminal:
group: "terminal - 终端模块"
path: "terminal"
logging:
# 全局日志打印
printer:
@@ -206,6 +214,7 @@ orion:
field:
ignore:
- password,beforePassword,newPassword,useNewPassword,publicKey,privateKey,privateKeyPassword
- accessKey,secretKey
- metrics
desensitize:
storage:

View File

@@ -39,9 +39,9 @@ import java.util.function.Function;
*/
public class ReplaceVersion {
private static final String TARGET_VERSION = "2.3.5";
private static final String TARGET_VERSION = "2.3.9";
private static final String REPLACE_VERSION = "2.3.6";
private static final String REPLACE_VERSION = "2.4.0";
private static final String PATH = new File("").getAbsolutePath();
@@ -50,6 +50,7 @@ public class ReplaceVersion {
"docker/adminer/build.sh",
"docker/mysql/build.sh",
"docker/redis/build.sh",
"docker/guacd/build.sh",
"docker/service/build.sh",
"docker/ui/build.sh",
"docker-compose.yml",

View File

@@ -0,0 +1,57 @@
/*
* Copyright (c) 2023 - present Dromara, All rights reserved.
*
* https://visor.dromara.org
* https://visor.dromara.org.cn
* https://visor.orionsec.cn
*
* Members:
* Jiahang Li - ljh1553488six@139.com - author
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.dromara.visor.module.asset.api;
import org.dromara.visor.module.asset.entity.dto.host.HostDTO;
import org.dromara.visor.module.asset.enums.HostTypeEnum;
import java.util.List;
/**
* 资产模块 授权数据对外服务
*
* @author Jiahang Li
* @version 1.0.0
* @since 2024/10/12 16:13
*/
public interface AssetAuthorizedDataApi {
/**
* 获取用户已授权&配置已启用的主机id 查询角色
*
* @param userId userId
* @param type type
* @return hostId
*/
List<Long> getUserAuthorizedEnabledHostId(Long userId, HostTypeEnum type);
/**
* 查询用户已授权并且启用的主机
*
* @param userId userId
* @param type type
* @return group
*/
List<HostDTO> getUserAuthorizedHostList(Long userId, HostTypeEnum type);
}

View File

@@ -20,26 +20,35 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.dromara.visor.module.asset.handler.host.terminal.handler;
package org.dromara.visor.module.asset.api;
import org.dromara.visor.module.asset.handler.host.terminal.model.TerminalBasePayload;
import org.springframework.web.socket.WebSocketSession;
import org.dromara.visor.module.asset.entity.dto.host.HostDTO;
import java.util.List;
/**
* 终端消息处理器
* 主机 对外服务
*
* @author Jiahang Li
* @version 1.0.0
* @since 2023/12/29 18:53
* @since 2024/10/12 16:14
*/
public interface ITerminalHandler<T extends TerminalBasePayload> {
public interface HostApi {
/**
* 处理消息
* 通过 id 查询
*
* @param channel channel
* @param payload payload
* @param id id
* @return row
*/
void handle(WebSocketSession channel, T payload);
HostDTO selectById(Long id);
/**
* 通过 id 查询
*
* @param idList idList
* @return rows
*/
List<HostDTO> selectByIdList(List<Long> idList);
}

View File

@@ -0,0 +1,90 @@
/*
* Copyright (c) 2023 - present Dromara, All rights reserved.
*
* https://visor.dromara.org
* https://visor.dromara.org.cn
* https://visor.orionsec.cn
*
* Members:
* Jiahang Li - ljh1553488six@139.com - author
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.dromara.visor.module.asset.api;
import org.dromara.visor.common.session.config.RdpConnectConfig;
import org.dromara.visor.common.session.config.SshConnectConfig;
import org.dromara.visor.module.asset.entity.dto.host.HostDTO;
/**
* 主机连接 对外服务
*
* @author Jiahang Li
* @version 1.0.0
* @since 2024/10/12 23:53
*/
public interface HostConnectApi {
/**
* 获取 SSH 连接配置
*
* @param hostId hostId
* @return session
*/
SshConnectConfig getSshConnectConfig(Long hostId);
/**
* 使用用户配置获取 SSH 连接配置
*
* @param hostId hostId
* @param userId userId
* @return session
*/
SshConnectConfig getSshConnectConfig(Long hostId, Long userId);
/**
* 使用用户配置获取 SSH 连接配置
*
* @param host host
* @param userId userId
* @return session
*/
SshConnectConfig getSshConnectConfig(HostDTO host, Long userId);
/**
* 获取 RDP 连接配置
*
* @param hostId hostId
* @return session
*/
RdpConnectConfig getRdpConnectConfig(Long hostId);
/**
* 使用用户配置获取 RDP 连接配置
*
* @param hostId hostId
* @param userId userId
* @return session
*/
RdpConnectConfig getRdpConnectConfig(Long hostId, Long userId);
/**
* 使用用户配置获取 RDP 连接配置
*
* @param host host
* @param userId userId
* @return session
*/
RdpConnectConfig getRdpConnectConfig(HostDTO host, Long userId);
}

View File

@@ -0,0 +1,73 @@
/*
* Copyright (c) 2023 - present Dromara, All rights reserved.
*
* https://visor.dromara.org
* https://visor.dromara.org.cn
* https://visor.orionsec.cn
*
* Members:
* Jiahang Li - ljh1553488six@139.com - author
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.dromara.visor.module.asset.entity.dto.host;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
/**
* 主机基本信息 业务响应对象
*
* @author Jiahang Li
* @version 1.0.0
* @since 2023-9-11 14:16
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Schema(name = "HostBaseDTO", description = "主机基本信息 业务响应对象")
public class HostBaseDTO implements Serializable {
private static final long serialVersionUID = 1L;
@Schema(description = "id")
private Long id;
@Schema(description = "主机类型")
private String types;
@Schema(description = "系统类型")
private String osType;
@Schema(description = "系统架构")
private String archType;
@Schema(description = "主机名称")
private String name;
@Schema(description = "主机编码")
private String code;
@Schema(description = "主机地址")
private String address;
@Schema(description = "主机端口")
private Integer port;
}

View File

@@ -0,0 +1,121 @@
/*
* Copyright (c) 2023 - present Dromara, All rights reserved.
*
* https://visor.dromara.org
* https://visor.dromara.org.cn
* https://visor.orionsec.cn
*
* Members:
* Jiahang Li - ljh1553488six@139.com - author
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.dromara.visor.module.asset.entity.dto.host;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
import java.util.Date;
import java.util.Set;
/**
* 主机 业务对象
*
* @author Jiahang Li
* @version 1.0.0
* @since 2023-9-11 14:16
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Schema(name = "HostDTO", description = "主机 业务对象")
public class HostDTO implements Serializable {
private static final long serialVersionUID = 1L;
@Schema(description = "id")
private Long id;
@Schema(description = "主机类型")
private String types;
@Schema(description = "系统类型")
private String osType;
@Schema(description = "系统架构")
private String archType;
@Schema(description = "主机名称")
private String name;
@Schema(description = "主机编码")
private String code;
@Schema(description = "主机地址")
private String address;
@Schema(description = "主机端口")
private Integer port;
@Schema(description = "主机状态")
private String status;
@Schema(description = "描述")
private String description;
@Schema(description = "创建时间")
private Date createTime;
@Schema(description = "修改时间")
private Date updateTime;
@Schema(description = "创建人")
private String creator;
@Schema(description = "修改人")
private String updater;
@Schema(description = "是否收藏")
private Boolean favorite;
@Schema(description = "分组 id")
private Set<Long> groupIdList;
@Schema(description = "别名")
private String alias;
@Schema(description = "颜色")
private String color;
/**
* 转为 base
*
* @return base
*/
public HostBaseDTO toBase() {
return HostBaseDTO.builder()
.id(this.id)
.types(this.types)
.name(this.name)
.code(this.code)
.address(this.address)
.port(this.port)
.build();
}
}

View File

@@ -0,0 +1,106 @@
/*
* Copyright (c) 2023 - present Dromara, All rights reserved.
*
* https://visor.dromara.org
* https://visor.dromara.org.cn
* https://visor.orionsec.cn
*
* Members:
* Jiahang Li - ljh1553488six@139.com - author
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.dromara.visor.module.asset.entity.dto.host;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.dromara.visor.common.handler.data.model.GenericsDataModel;
import org.dromara.visor.common.security.UpdatePasswordAction;
import javax.validation.constraints.*;
/**
* 主机 RDP 配置
*
* @author Jiahang Li
* @version 1.0.0
* @since 2023/9/13 16:18
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Schema(name = "HostRdpConfigDTO", description = "主机 RDP 配置业务对象")
public class HostRdpConfigDTO implements GenericsDataModel, UpdatePasswordAction {
@NotNull
@Min(value = 1)
@Max(value = 65535)
@Schema(description = "主机端口")
private Integer port;
@Size(max = 128)
@Schema(description = "用户名")
private String username;
@NotBlank
@Size(max = 12)
@Schema(description = "认证方式")
private String authType;
@Schema(description = "密码")
private String password;
@Schema(description = "身份id")
private Long identityId;
@Schema(description = "RDP 版本是否大于8.1")
private Boolean versionGt81;
@Schema(description = "时区")
private String timezone;
@Schema(description = "键盘布局")
private String keyboardLayout;
@Schema(description = "剪切板规范")
private String clipboardNormalize;
@Schema(description = "")
private String domain;
@Schema(description = "预连接id")
private String preConnectionId;
@Schema(description = "预连接数据")
private String preConnectionBlob;
@Schema(description = "远程应用")
private String remoteApp;
@Schema(description = "远程应用路径")
private String remoteAppDir;
@Schema(description = "远程应用参数")
private String remoteAppArgs;
@Schema(description = "是否使用新密码 仅参数")
private Boolean useNewPassword;
@Schema(description = "是否已设置密码 仅返回")
private Boolean hasPassword;
}

View File

@@ -20,8 +20,9 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.dromara.visor.module.asset.handler.host.config.model;
package org.dromara.visor.module.asset.entity.dto.host;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
@@ -42,73 +43,58 @@ import javax.validation.constraints.*;
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class HostSshConfigModel implements GenericsDataModel, UpdatePasswordAction {
@Schema(name = "HostSshConfigDTO", description = "主机 SSH 配置业务对象")
public class HostSshConfigDTO implements GenericsDataModel, UpdatePasswordAction {
@NotNull
@Min(value = 1)
@Max(value = 65535)
@Schema(description = "主机端口")
private Integer port;
/**
* 用户名
*/
@Size(max = 128)
@Schema(description = "用户名")
private String username;
/**
* 认证方式
*/
@NotBlank
@Size(max = 12)
@Schema(description = "认证方式")
private String authType;
/**
* 密码
*/
@Schema(description = "密码")
private String password;
/**
* 身份id
*/
@Schema(description = "身份id")
private Long identityId;
/**
* 密钥id
*/
@Schema(description = "密钥id")
private Long keyId;
/**
* 连接超时时间
*/
@NotNull
@Min(value = 1)
@Max(value = 100000)
@Schema(description = "连接超时时间")
private Integer connectTimeout;
/**
* SSH输出编码
*/
@NotBlank
@Size(max = 12)
@Schema(description = "SSH输出编码")
private String charset;
/**
* 文件名称编码
*/
@NotBlank
@Size(max = 12)
@Schema(description = "文件名称编码")
private String fileNameCharset;
/**
* 文件内容编码
*/
@NotBlank
@Size(max = 12)
@Schema(description = "文件内容编码")
private String fileContentCharset;
/**
* 是否使用新密码 仅参数
*/
@Schema(description = "是否使用新密码 仅参数")
private Boolean useNewPassword;
/**
* 是否已设置密码 仅返回
*/
@Schema(description = "是否已设置密码 仅返回")
private Boolean hasPassword;
}

View File

@@ -0,0 +1,71 @@
/*
* Copyright (c) 2023 - present Dromara, All rights reserved.
*
* https://visor.dromara.org
* https://visor.dromara.org.cn
* https://visor.orionsec.cn
*
* Members:
* Jiahang Li - ljh1553488six@139.com - author
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.dromara.visor.module.asset.enums;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* 主机系统架构类型
*
* @author Jiahang Li
* @version 1.0.0
* @since 2024/4/16 21:58
*/
@Getter
@AllArgsConstructor
public enum HostArchTypeEnum {
/**
* X86_64
*/
AMD64,
/**
* arm64
*/
ARM64,
;
public boolean is(String type) {
if (type == null) {
return false;
}
return type.equalsIgnoreCase(this.name());
}
public static HostArchTypeEnum of(String type) {
if (type == null) {
return AMD64;
}
type = type.toUpperCase();
for (HostArchTypeEnum value : values()) {
if (value.name().equals(type) || type.contains(value.name())) {
return value;
}
}
return AMD64;
}
}

View File

@@ -46,40 +46,33 @@ public enum HostOsTypeEnum {
*/
WINDOWS(".cmd"),
/**
* darwin
*/
DARWIN(".sh"),
;
private final String scriptSuffix;
public boolean is(String type) {
if (type == null) {
return false;
}
return type.equalsIgnoreCase(this.name());
}
public static HostOsTypeEnum of(String type) {
if (type == null) {
return LINUX;
}
type = type.toUpperCase();
for (HostOsTypeEnum value : values()) {
if (value.name().equals(type)) {
if (value.name().equals(type) || type.contains(value.name())) {
return value;
}
}
return LINUX;
}
/**
* 是否为 linux 系统
*
* @param type type
* @return isLinux
*/
public static boolean isLinux(String type) {
return LINUX.name().equals(type);
}
/**
* 是否为 windows 系统
*
* @param type type
* @return isWindows
*/
public static boolean isWindows(String type) {
return WINDOWS.name().equals(type);
}
}

View File

@@ -0,0 +1,89 @@
/*
* Copyright (c) 2023 - present Dromara, All rights reserved.
*
* https://visor.dromara.org
* https://visor.dromara.org.cn
* https://visor.orionsec.cn
*
* Members:
* Jiahang Li - ljh1553488six@139.com - author
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.dromara.visor.module.asset.enums;
import com.alibaba.fastjson.JSON;
import lombok.AllArgsConstructor;
import org.dromara.visor.common.constant.Const;
import org.dromara.visor.module.asset.entity.dto.host.HostRdpConfigDTO;
import org.dromara.visor.module.asset.entity.dto.host.HostSshConfigDTO;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
/**
* 主机类型
*
* @author Jiahang Li
* @version 1.0.0
* @since 2024/10/12 18:12
*/
@AllArgsConstructor
public enum HostTypeEnum {
/**
* SSH
*/
SSH(HostSshConfigDTO.class),
/**
* RDP
*/
RDP(HostRdpConfigDTO.class),
;
private final Class<?> clazz;
public static HostTypeEnum of(String type) {
if (type == null) {
return null;
}
for (HostTypeEnum value : values()) {
if (value.name().equals(type)) {
return value;
}
}
return null;
}
public static List<String> split(String types) {
if (types == null) {
return new ArrayList<>();
}
return Arrays.stream(types.split(Const.COMMA))
.map(HostTypeEnum::of)
.filter(Objects::nonNull)
.map(Enum::name)
.collect(Collectors.toList());
}
@SuppressWarnings("unchecked")
public <T> T parse(String config) {
return (T) JSON.parseObject(config, this.clazz);
}
}

View File

@@ -32,6 +32,11 @@
<artifactId>orion-visor-module-asset-provider</artifactId>
<version>${revision}</version>
</dependency>
<dependency>
<groupId>org.dromara.visor</groupId>
<artifactId>orion-visor-module-exec-provider</artifactId>
<version>${revision}</version>
</dependency>
<!-- framework starter -->
<dependency>

View File

@@ -0,0 +1,66 @@
/*
* Copyright (c) 2023 - present Dromara, All rights reserved.
*
* https://visor.dromara.org
* https://visor.dromara.org.cn
* https://visor.orionsec.cn
*
* Members:
* Jiahang Li - ljh1553488six@139.com - author
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.dromara.visor.module.asset.api.impl;
import lombok.extern.slf4j.Slf4j;
import org.dromara.visor.module.asset.api.AssetAuthorizedDataApi;
import org.dromara.visor.module.asset.convert.HostProviderConvert;
import org.dromara.visor.module.asset.entity.dto.host.HostDTO;
import org.dromara.visor.module.asset.entity.vo.AuthorizedHostWrapperVO;
import org.dromara.visor.module.asset.enums.HostTypeEnum;
import org.dromara.visor.module.asset.service.AssetAuthorizedDataService;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.util.List;
import java.util.stream.Collectors;
/**
* 资产模块 授权数据对外服务实现
*
* @author Jiahang Li
* @version 1.0.0
* @since 2024/10/12 18:14
*/
@Slf4j
@Service
public class AssetAuthorizedDataApiImpl implements AssetAuthorizedDataApi {
@Resource
private AssetAuthorizedDataService assetAuthorizedDataService;
@Override
public List<Long> getUserAuthorizedEnabledHostId(Long userId, HostTypeEnum type) {
return assetAuthorizedDataService.getUserAuthorizedEnabledHostId(userId, type.name());
}
@Override
public List<HostDTO> getUserAuthorizedHostList(Long userId, HostTypeEnum type) {
AuthorizedHostWrapperVO wrapper = assetAuthorizedDataService.getUserAuthorizedHost(userId, type.name());
return wrapper.getHostList()
.stream()
.map(HostProviderConvert.MAPPER::to)
.collect(Collectors.toList());
}
}

View File

@@ -0,0 +1,67 @@
/*
* Copyright (c) 2023 - present Dromara, All rights reserved.
*
* https://visor.dromara.org
* https://visor.dromara.org.cn
* https://visor.orionsec.cn
*
* Members:
* Jiahang Li - ljh1553488six@139.com - author
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.dromara.visor.module.asset.api.impl;
import cn.orionsec.kit.lang.utils.collect.Lists;
import lombok.extern.slf4j.Slf4j;
import org.dromara.visor.module.asset.api.HostApi;
import org.dromara.visor.module.asset.convert.HostProviderConvert;
import org.dromara.visor.module.asset.dao.HostDAO;
import org.dromara.visor.module.asset.entity.dto.host.HostDTO;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.util.List;
import java.util.stream.Collectors;
/**
* 主机 对外服务实现类
*
* @author Jiahang Li
* @version 1.0.0
* @since 2024/10/12 18:27
*/
@Slf4j
@Service
public class HostApiImpl implements HostApi {
@Resource
private HostDAO hostDAO;
@Override
public HostDTO selectById(Long id) {
return HostProviderConvert.MAPPER.to(hostDAO.selectById(id));
}
@Override
public List<HostDTO> selectByIdList(List<Long> idList) {
if (Lists.isEmpty(idList)) {
return Lists.empty();
}
return hostDAO.selectBatchIds(idList)
.stream()
.map(HostProviderConvert.MAPPER::to)
.collect(Collectors.toList());
}
}

View File

@@ -0,0 +1,80 @@
/*
* Copyright (c) 2023 - present Dromara, All rights reserved.
*
* https://visor.dromara.org
* https://visor.dromara.org.cn
* https://visor.orionsec.cn
*
* Members:
* Jiahang Li - ljh1553488six@139.com - author
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.dromara.visor.module.asset.api.impl;
import lombok.extern.slf4j.Slf4j;
import org.dromara.visor.common.session.config.RdpConnectConfig;
import org.dromara.visor.common.session.config.SshConnectConfig;
import org.dromara.visor.module.asset.api.HostConnectApi;
import org.dromara.visor.module.asset.convert.HostProviderConvert;
import org.dromara.visor.module.asset.entity.dto.host.HostDTO;
import org.dromara.visor.module.asset.service.HostConnectService;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
/**
* 主机连接 对外服务实现
*
* @author Jiahang Li
* @version 1.0.0
* @since 2024/10/13 0:03
*/
@Slf4j
@Service
public class HostConnectApiImpl implements HostConnectApi {
@Resource
private HostConnectService hostConnectService;
@Override
public SshConnectConfig getSshConnectConfig(Long hostId) {
return hostConnectService.getSshConnectConfig(hostId);
}
@Override
public SshConnectConfig getSshConnectConfig(Long hostId, Long userId) {
return hostConnectService.getSshConnectConfig(hostId, userId);
}
@Override
public SshConnectConfig getSshConnectConfig(HostDTO host, Long userId) {
return hostConnectService.getSshConnectConfig(HostProviderConvert.MAPPER.to(host), userId);
}
@Override
public RdpConnectConfig getRdpConnectConfig(Long hostId) {
return hostConnectService.getRdpConnectConfig(hostId);
}
@Override
public RdpConnectConfig getRdpConnectConfig(Long hostId, Long userId) {
return hostConnectService.getRdpConnectConfig(hostId, userId);
}
@Override
public RdpConnectConfig getRdpConnectConfig(HostDTO host, Long userId) {
return hostConnectService.getRdpConnectConfig(HostProviderConvert.MAPPER.to(host), userId);
}
}

View File

@@ -0,0 +1,78 @@
/*
* Copyright (c) 2023 - present Dromara, All rights reserved.
*
* https://visor.dromara.org
* https://visor.dromara.org.cn
* https://visor.orionsec.cn
*
* Members:
* Jiahang Li - ljh1553488six@139.com - author
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.dromara.visor.module.asset.controller;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.extern.slf4j.Slf4j;
import org.dromara.visor.framework.biz.operator.log.core.annotation.OperatorLog;
import org.dromara.visor.framework.log.core.annotation.IgnoreLog;
import org.dromara.visor.framework.log.core.enums.IgnoreLogMode;
import org.dromara.visor.framework.web.core.annotation.DemoDisableApi;
import org.dromara.visor.framework.web.core.annotation.RestWrapper;
import org.dromara.visor.module.asset.define.operator.HostOperatorType;
import org.dromara.visor.module.asset.entity.request.host.HostConfigQueryRequest;
import org.dromara.visor.module.asset.entity.request.host.HostConfigUpdateRequest;
import org.dromara.visor.module.asset.service.HostConfigService;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
/**
* 主机 api
*
* @author Jiahang Li
* @version 1.0.0
* @since 2023-9-11 14:16
*/
@Tag(name = "asset - 主机配置服务")
@Slf4j
@Validated
@RestWrapper
@RestController
@RequestMapping("/asset/host-config")
public class HostConfigController {
@Resource
private HostConfigService hostConfigService;
@DemoDisableApi
@OperatorLog(HostOperatorType.UPDATE_CONFIG)
@PutMapping("/update")
@Operation(summary = "更新主机配置")
@PreAuthorize("@ss.hasPermission('asset:host:update-config')")
public Integer updateHostConfig(@Validated @RequestBody HostConfigUpdateRequest request) {
return hostConfigService.updateHostConfig(request);
}
@IgnoreLog(IgnoreLogMode.RET)
@PostMapping("/get")
@Operation(summary = "查询主机配置")
@PreAuthorize("@ss.hasPermission('asset:host:query')")
public Object getHostConfig(@Validated @RequestBody HostConfigQueryRequest request) {
return hostConfigService.getHostConfigView(request);
}
}

View File

@@ -35,8 +35,8 @@ import org.dromara.visor.framework.web.core.annotation.DemoDisableApi;
import org.dromara.visor.framework.web.core.annotation.RestWrapper;
import org.dromara.visor.module.asset.define.operator.HostOperatorType;
import org.dromara.visor.module.asset.entity.request.host.*;
import org.dromara.visor.module.asset.entity.vo.HostConfigVO;
import org.dromara.visor.module.asset.entity.vo.HostVO;
import org.dromara.visor.module.asset.service.HostConnectService;
import org.dromara.visor.module.asset.service.HostService;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
@@ -63,6 +63,9 @@ public class HostController {
@Resource
private HostService hostService;
@Resource
private HostConnectService hostConnectService;
@DemoDisableApi
@OperatorLog(HostOperatorType.CREATE)
@PostMapping("/create")
@@ -81,6 +84,17 @@ public class HostController {
return hostService.updateHostById(request);
}
@DemoDisableApi
@OperatorLog(HostOperatorType.CREATE)
@PostMapping("/copy")
@Operation(summary = "复制主机")
@PreAuthorize("@ss.hasPermission('asset:host:create')")
public Long copyHost(@Validated @RequestBody HostUpdateRequest request) {
Long id = request.getId();
request.setId(null);
return hostService.copyHost(id, request);
}
@DemoDisableApi
@OperatorLog(HostOperatorType.UPDATE_STATUS)
@PutMapping("/update-status")
@@ -91,12 +105,12 @@ public class HostController {
}
@DemoDisableApi
@OperatorLog(HostOperatorType.UPDATE_CONFIG)
@PutMapping("/update-config")
@Operation(summary = "更新主机配置")
@PreAuthorize("@ss.hasPermission('asset:host:update-config')")
public Integer updateHostConfig(@Validated @RequestBody HostUpdateConfigRequest request) {
return hostService.updateHostConfig(request);
@OperatorLog(HostOperatorType.UPDATE_SPEC)
@PutMapping("/update-spec")
@Operation(summary = "修改主机规格信息")
@PreAuthorize("@ss.hasPermission('asset:host:update')")
public Integer updateHostSpec(@Validated @RequestBody HostExtraUpdateRequest request) {
return hostService.updateHostSpec(request);
}
@IgnoreLog(IgnoreLogMode.RET)
@@ -108,15 +122,6 @@ public class HostController {
return hostService.getHostById(id);
}
@IgnoreLog(IgnoreLogMode.RET)
@GetMapping("/get-config")
@Operation(summary = "查询主机配置")
@Parameter(name = "id", description = "id", required = true)
@PreAuthorize("@ss.hasPermission('asset:host:query')")
public HostConfigVO getHostConfig(@RequestParam("id") Long id) {
return hostService.getHostConfig(id);
}
@IgnoreLog(IgnoreLogMode.RET)
@GetMapping("/list")
@Operation(summary = "查询主机")
@@ -138,7 +143,7 @@ public class HostController {
@PostMapping("/count")
@Operation(summary = "查询主机数量")
@PreAuthorize("@ss.hasPermission('asset:host:query')")
public Long getHostExportCount(@Validated @RequestBody HostQueryRequest request) {
public Long getHostCount(@Validated @RequestBody HostQueryRequest request) {
return hostService.getHostCount(request);
}
@@ -162,5 +167,14 @@ public class HostController {
return hostService.deleteHostByIdList(idList);
}
@DemoDisableApi
@PostMapping("/test-connect")
@Operation(summary = "测试主机连接")
@PreAuthorize("@ss.hasAnyPermission('asset:host:update', 'asset:host:update-config')")
public Boolean testHostConnect(@Validated @RequestBody HostTestConnectRequest request) {
hostConnectService.testHostConnect(request);
return true;
}
}

View File

@@ -26,11 +26,13 @@ import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.extern.slf4j.Slf4j;
import org.dromara.visor.common.constant.ErrorMessage;
import org.dromara.visor.common.utils.Valid;
import org.dromara.visor.framework.log.core.annotation.IgnoreLog;
import org.dromara.visor.framework.log.core.enums.IgnoreLogMode;
import org.dromara.visor.framework.web.core.annotation.RestWrapper;
import org.dromara.visor.module.asset.entity.request.host.HostExtraQueryRequest;
import org.dromara.visor.module.asset.entity.request.host.HostExtraUpdateRequest;
import org.dromara.visor.module.asset.handler.host.extra.HostExtraItemEnum;
import org.dromara.visor.module.asset.service.HostExtraService;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
@@ -61,20 +63,15 @@ public class HostExtraController {
@Operation(summary = "获取主机拓展信息")
@Parameter(name = "hostId", description = "hostId", required = true)
@Parameter(name = "item", description = "item", required = true)
public Map<String, Object> getHostExtra(@RequestParam("hostId") Long hostId, @RequestParam("item") String item) {
return hostExtraService.getHostExtra(hostId, item);
}
@IgnoreLog(IgnoreLogMode.RET)
@PostMapping("/list")
@Operation(summary = "获取多个主机拓展信息")
public Map<String, Map<String, Object>> getHostExtraList(@Validated @RequestBody HostExtraQueryRequest request) {
return hostExtraService.getHostExtraList(request);
public Map<String, Object> getHostExtraView(@RequestParam("hostId") Long hostId, @RequestParam("item") String item) {
return hostExtraService.getHostExtraView(hostId, item);
}
@PutMapping("/update")
@Operation(summary = "修改主机拓展信息")
public Integer updateHostExtra(@Validated @RequestBody HostExtraUpdateRequest request) {
HostExtraItemEnum item = Valid.valid(HostExtraItemEnum::of, request.getItem());
Valid.isTrue(item.isUserExtra(), ErrorMessage.PARAM_ERROR);
return hostExtraService.updateHostExtra(request);
}

View File

@@ -22,6 +22,7 @@
*/
package org.dromara.visor.module.asset.convert;
import org.dromara.visor.common.mapstruct.StringConversion;
import org.dromara.visor.module.asset.entity.domain.HostDO;
import org.dromara.visor.module.asset.entity.dto.HostCacheDTO;
import org.dromara.visor.module.asset.entity.request.host.HostCreateRequest;
@@ -41,7 +42,7 @@ import java.util.List;
* @version 1.0.0
* @since 2023-9-11 14:16
*/
@Mapper
@Mapper(uses = StringConversion.class)
public interface HostConvert {
HostConvert MAPPER = Mappers.getMapper(HostConvert.class);
@@ -60,6 +61,8 @@ public interface HostConvert {
HostBaseVO toBase(HostDO domain);
HostCreateRequest toCreate(HostUpdateRequest request);
List<HostVO> toList(List<HostDO> domain);
List<HostBaseVO> toBaseList(List<HostDO> domain);

View File

@@ -0,0 +1,53 @@
/*
* Copyright (c) 2023 - present Dromara, All rights reserved.
*
* https://visor.dromara.org
* https://visor.dromara.org.cn
* https://visor.orionsec.cn
*
* Members:
* Jiahang Li - ljh1553488six@139.com - author
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.dromara.visor.module.asset.convert;
import org.dromara.visor.common.mapstruct.StringConversion;
import org.dromara.visor.module.asset.entity.domain.HostDO;
import org.dromara.visor.module.asset.entity.dto.host.HostBaseDTO;
import org.dromara.visor.module.asset.entity.dto.host.HostDTO;
import org.dromara.visor.module.asset.entity.vo.HostVO;
import org.mapstruct.Mapper;
import org.mapstruct.factory.Mappers;
/**
* 主机 对外对象转换器
*
* @author Jiahang Li
* @version 1.0.0
* @since 2023-9-11 14:16
*/
@Mapper(uses = StringConversion.class)
public interface HostProviderConvert {
HostProviderConvert MAPPER = Mappers.getMapper(HostProviderConvert.class);
HostDO to(HostDTO host);
HostDTO to(HostDO domain);
HostDTO to(HostVO vo);
HostBaseDTO toBase(HostDO domain);
}

View File

@@ -0,0 +1,136 @@
/*
* Copyright (c) 2023 - present Dromara, All rights reserved.
*
* https://visor.dromara.org
* https://visor.dromara.org.cn
* https://visor.orionsec.cn
*
* Members:
* Jiahang Li - ljh1553488six@139.com - author
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.dromara.visor.module.asset.dao;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.dromara.visor.framework.mybatis.core.mapper.IMapper;
import org.dromara.visor.framework.mybatis.core.query.Conditions;
import org.dromara.visor.module.asset.entity.domain.HostConfigDO;
import java.util.List;
/**
* 主机配置 Mapper 接口
*
* @author Jiahang Li
* @version 1.0.0
* @since 2025-3-6 10:59
*/
@Mapper
public interface HostConfigDAO extends IMapper<HostConfigDO> {
/**
* 通过 hostId 和 type 查询
*
* @param hostId hostId
* @param type type
* @return config
*/
default HostConfigDO selectByHostIdType(Long hostId, String type) {
return this.of()
.createWrapper()
.eq(HostConfigDO::getHostId, hostId)
.eq(HostConfigDO::getType, type)
.then()
.getOne();
}
/**
* 通过 hostId 查询
*
* @param hostId hostId
* @return config
*/
default List<HostConfigDO> selectByHostId(Long hostId) {
return this.of()
.createWrapper()
.eq(HostConfigDO::getHostId, hostId)
.then()
.list();
}
/**
* 更新配置状态
*
* @param hostId hostId
* @param types types
* @param status status
*/
default void updateConfigStatus(Long hostId, List<String> types, String status) {
HostConfigDO update = HostConfigDO.builder()
.status(status)
.build();
LambdaQueryWrapper<HostConfigDO> condition = this.wrapper()
.eq(HostConfigDO::getHostId, hostId)
.in(HostConfigDO::getType, types)
.ne(HostConfigDO::getStatus, status);
this.update(update, condition);
}
/**
* 更新配置状态
*
* @param hostId hostId
* @param notTypes notTypes
* @param status status
*/
default void updateConfigInvertStatus(Long hostId, List<String> notTypes, String status) {
HostConfigDO update = HostConfigDO.builder()
.status(status)
.build();
LambdaQueryWrapper<HostConfigDO> condition = this.wrapper()
.eq(HostConfigDO::getHostId, hostId)
.notIn(HostConfigDO::getType, notTypes)
.ne(HostConfigDO::getStatus, status);
this.update(update, condition);
}
/**
* 通过 hostId 删除
*
* @param hostIdList hostIdList
* @return effect
*/
default int deleteByHostIdList(List<Long> hostIdList) {
return this.delete(Conditions.in(HostConfigDO::getHostId, hostIdList));
}
/**
* 设置 keyId 为 NULL
*
* @param keyIdList keyIdList
* @return effect
*/
int setKeyIdWithNull(@Param("keyIdList") List<Long> keyIdList);
/**
* 设置 identityId 为 NULL
*
* @param identityIdList identityIdList
* @return effect
*/
int setIdentityIdWithNull(@Param("identityIdList") List<Long> identityIdList);
}

View File

@@ -22,13 +22,10 @@
*/
package org.dromara.visor.module.asset.dao;
import com.baomidou.mybatisplus.core.toolkit.support.SFunction;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.dromara.visor.framework.mybatis.core.mapper.IMapper;
import org.dromara.visor.module.asset.entity.domain.HostDO;
import java.util.Arrays;
import java.util.List;
/**
@@ -41,54 +38,8 @@ import java.util.List;
@Mapper
public interface HostDAO extends IMapper<HostDO> {
List<SFunction<HostDO, ?>> BASE_COLUMN = Arrays.asList(
HostDO::getId,
HostDO::getType,
HostDO::getOsType,
HostDO::getName,
HostDO::getCode,
HostDO::getAddress,
HostDO::getPort,
HostDO::getStatus,
HostDO::getDescription,
HostDO::getCreateTime,
HostDO::getUpdateTime,
HostDO::getCreator,
HostDO::getUpdater
);
/**
* 通过 id 查询基本信息
*
* @param id id
* @return id
*/
default HostDO selectBaseById(Long id) {
return this.of()
.createWrapper()
.select(BASE_COLUMN)
.eq(HostDO::getId, id)
.then()
.getOne();
}
/**
* 通过 id 查询基本信息
*
* @param idList idList
* @return id
*/
default List<HostDO> selectBaseByIdList(List<Long> idList) {
return this.of()
.createWrapper()
.select(BASE_COLUMN)
.in(HostDO::getId, idList)
.then()
.list();
}
/**
* 获取的 hostId
* 通过类型查询 hostId
*
* @param hostIdList hostIdList
* @param type type
@@ -100,26 +51,10 @@ public interface HostDAO extends IMapper<HostDO> {
.createWrapper(true)
.select(HostDO::getId)
.in(HostDO::getId, hostIdList)
.eq(HostDO::getType, type)
.eq(HostDO::getStatus, status)
.like(HostDO::getTypes, type)
.then()
.list(HostDO::getId);
}
/**
* 设置 keyId 为 NULL
*
* @param keyIdList keyIdList
* @return effect
*/
int setKeyIdWithNull(@Param("keyIdList") List<Long> keyIdList);
/**
* 设置 identityId 为 NULL
*
* @param identityIdList identityIdList
* @return effect
*/
int setIdentityIdWithNull(@Param("identityIdList") List<Long> identityIdList);
}

View File

@@ -48,6 +48,8 @@ public class HostOperatorType extends InitializingOperatorTypes {
public static final String UPDATE_CONFIG = "host:update-config";
public static final String UPDATE_SPEC = "host:update-spec";
@Override
public OperatorType[] types() {
return new OperatorType[]{
@@ -55,7 +57,8 @@ public class HostOperatorType extends InitializingOperatorTypes {
new OperatorType(L, UPDATE, "修改主机 <sb>${name}</sb>"),
new OperatorType(H, DELETE, "删除主机 <sb>${count}</sb> 条"),
new OperatorType(M, UPDATE_STATUS, "修改主机状态 <sb>${name}</sb> - <sb>${status}</sb>"),
new OperatorType(M, UPDATE_CONFIG, "修改主机配置 <sb>${name}</sb>"),
new OperatorType(M, UPDATE_CONFIG, "修改主机配置 <sb>${name}</sb> - <sb>${type}</sb>"),
new OperatorType(M, UPDATE_SPEC, "修改主机规格信息 <sb>${name}</sb>"),
};
}

View File

@@ -0,0 +1,69 @@
/*
* Copyright (c) 2023 - present Dromara, All rights reserved.
*
* https://visor.dromara.org
* https://visor.dromara.org.cn
* https://visor.orionsec.cn
*
* Members:
* Jiahang Li - ljh1553488six@139.com - author
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.dromara.visor.module.asset.entity.domain;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import lombok.experimental.SuperBuilder;
import org.dromara.visor.framework.mybatis.core.domain.BaseDO;
/**
* 主机配置 实体对象
*
* @author Jiahang Li
* @version 1.0.0
* @since 2025-3-6 10:59
*/
@Data
@SuperBuilder
@NoArgsConstructor
@AllArgsConstructor
@EqualsAndHashCode(callSuper = true)
@TableName(value = "host_config", autoResultMap = true)
@Schema(name = "HostConfigDO", description = "主机配置 实体对象")
public class HostConfigDO extends BaseDO {
private static final long serialVersionUID = 1L;
@Schema(description = "主机id")
@TableField("host_id")
private Long hostId;
@Schema(description = "配置类型")
@TableField("type")
private String type;
@Schema(description = "配置状态")
@TableField("status")
private String status;
@Schema(description = "配置值")
@TableField("config")
private String config;
}

View File

@@ -50,14 +50,18 @@ public class HostDO extends BaseDO {
private static final long serialVersionUID = 1L;
@Schema(description = "主机类型")
@TableField("type")
private String type;
@Schema(description = "类型")
@TableField("types")
private String types;
@Schema(description = "系统类型")
@TableField("os_type")
private String osType;
@Schema(description = "系统架构")
@TableField("arch_type")
private String archType;
@Schema(description = "主机名称")
@TableField("name")
private String name;
@@ -70,19 +74,11 @@ public class HostDO extends BaseDO {
@TableField("address")
private String address;
@Schema(description = "主机端口")
@TableField("port")
private Integer port;
@Schema(description = "主机状态")
@TableField("status")
private String status;
@Schema(description = "主机配置")
@TableField("config")
private String config;
@Schema(description = "描述")
@Schema(description = "主机描述")
@TableField("description")
private String description;

View File

@@ -49,11 +49,14 @@ public class HostCacheDTO implements LongCacheIdModel, Serializable {
private Long id;
@Schema(description = "主机类型")
private String type;
private String types;
@Schema(description = "系统类型")
private String osType;
@Schema(description = "系统架构")
private String archType;
@Schema(description = "主机名称")
private String name;
@@ -69,7 +72,7 @@ public class HostCacheDTO implements LongCacheIdModel, Serializable {
@Schema(description = "主机状态")
private String status;
@Schema(description = "描述")
@Schema(description = "主机描述")
private String description;
}

View File

@@ -61,10 +61,16 @@ public class HostIdentityCacheDTO implements LongCacheIdModel, Serializable {
@Schema(description = "密钥id")
private Long keyId;
@Schema(description = "描述")
private String description;
@Schema(description = "创建时间 资产页面展示")
/**
* 资产页面展示
*/
@Schema(description = "创建时间")
private Date createTime;
/**
* 资产页面展示
*/
@Schema(description = "修改时间")
private Date updateTime;
}

View File

@@ -0,0 +1,53 @@
/*
* Copyright (c) 2023 - present Dromara, All rights reserved.
*
* https://visor.dromara.org
* https://visor.dromara.org.cn
* https://visor.orionsec.cn
*
* Members:
* Jiahang Li - ljh1553488six@139.com - author
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.dromara.visor.module.asset.entity.request.host;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.*;
import org.dromara.visor.common.entity.BaseQueryRequest;
import javax.validation.constraints.Size;
/**
* 主机配置 查询请求对象
*
* @author Jiahang Li
* @version 1.0.0
* @since 2025-3-6 10:59
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@EqualsAndHashCode(callSuper = true)
@Schema(name = "HostConfigQueryRequest", description = "主机配置 查询请求对象")
public class HostConfigQueryRequest extends BaseQueryRequest {
@Schema(description = "主机id")
private Long hostId;
@Size(max = 12)
@Schema(description = "配置类型")
private String type;
}

View File

@@ -0,0 +1,65 @@
/*
* Copyright (c) 2023 - present Dromara, All rights reserved.
*
* https://visor.dromara.org
* https://visor.dromara.org.cn
* https://visor.orionsec.cn
*
* Members:
* Jiahang Li - ljh1553488six@139.com - author
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.dromara.visor.module.asset.entity.request.host;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
import java.io.Serializable;
/**
* 主机配置 更新请求对象
*
* @author Jiahang Li
* @version 1.0.0
* @since 2025-3-6 10:59
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Schema(name = "HostConfigUpdateRequest", description = "主机配置 更新请求对象")
public class HostConfigUpdateRequest implements Serializable {
private static final long serialVersionUID = 1L;
@NotNull
@Schema(description = "主机id")
private Long hostId;
@NotBlank
@Size(max = 12)
@Schema(description = "配置类型")
private String type;
@NotBlank
@Schema(description = "配置值")
private String config;
}

View File

@@ -28,7 +28,9 @@ import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import javax.validation.constraints.*;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.Size;
import java.io.Serializable;
import java.util.List;
@@ -46,15 +48,20 @@ import java.util.List;
@Schema(name = "HostCreateRequest", description = "主机 创建请求对象")
public class HostCreateRequest implements Serializable {
@NotBlank
@NotEmpty
@Schema(description = "主机类型")
private String type;
private List<String> types;
@NotBlank
@Size(max = 12)
@NotBlank
@Schema(description = "系统类型")
private String osType;
@Size(max = 12)
@NotBlank
@Schema(description = "系统架构")
private String archType;
@NotBlank
@Size(max = 64)
@Schema(description = "主机名称")
@@ -70,11 +77,6 @@ public class HostCreateRequest implements Serializable {
@Schema(description = "主机地址")
private String address;
@Min(value = 1)
@Max(value = 65535)
@Schema(description = "主机端口")
private Integer port;
@NotEmpty
@Schema(description = "主机分组")
private List<Long> groupIdList;

View File

@@ -49,7 +49,6 @@ public class HostExtraUpdateRequest {
@Schema(description = "主机id")
private Long hostId;
@NotNull
@Schema(description = "配置项")
private String item;

View File

@@ -50,6 +50,10 @@ public class HostQueryRequest extends BaseQueryRequest {
@Schema(description = "id")
private Long id;
@Size(max = 8)
@Schema(description = "主机类型")
private String type;
@Size(max = 64)
@Schema(description = "主机名称")
private String name;
@@ -62,14 +66,14 @@ public class HostQueryRequest extends BaseQueryRequest {
@Schema(description = "主机地址")
private String address;
@Size(max = 8)
@Schema(description = "主机类型")
private String type;
@Size(max = 12)
@Schema(description = "系统类型")
private String osType;
@Size(max = 12)
@Schema(description = "系统架构")
private String archType;
@Size(max = 8)
@Schema(description = "主机状态")
private String status;
@@ -81,7 +85,13 @@ public class HostQueryRequest extends BaseQueryRequest {
@Schema(description = "描述")
private String description;
@Schema(description = "是否查询分组信息")
private Boolean queryGroup;
@Schema(description = "是否查询 tag 信息")
private Boolean queryTag;
@Schema(description = "是否查询规格信息")
private Boolean querySpec;
}

View File

@@ -30,28 +30,27 @@ import lombok.NoArgsConstructor;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import java.io.Serializable;
/**
* 主机 更新配置请求对象
* 主机 测试连接请求对象
*
* @author Jiahang Li
* @version 1.0.0
* @since 2023-9-13 14:31
* @since 2023-9-11 14:16
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Schema(name = "HostUpdateConfigRequest", description = "主机 更新配置请求对象")
public class HostUpdateConfigRequest implements Serializable {
@Schema(name = "HostTestConnectRequest", description = "主机 测试连接请求对象")
public class HostTestConnectRequest {
@NotNull
@Schema(description = "id")
private Long id;
@NotBlank
@Schema(description = "配置详情")
private String config;
@Schema(description = "主机类型")
private String type;
}

View File

@@ -28,7 +28,10 @@ import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import javax.validation.constraints.*;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
import java.io.Serializable;
import java.util.List;
@@ -55,6 +58,11 @@ public class HostUpdateRequest implements Serializable {
@Schema(description = "系统类型")
private String osType;
@NotBlank
@Size(max = 12)
@Schema(description = "系统架构")
private String archType;
@NotBlank
@Size(max = 64)
@Schema(description = "主机名称")
@@ -70,11 +78,9 @@ public class HostUpdateRequest implements Serializable {
@Schema(description = "主机地址")
private String address;
@NotNull
@Min(value = 1)
@Max(value = 65535)
@Schema(description = "主机端口")
private Integer port;
@NotEmpty
@Schema(description = "主机类型")
private List<String> types;
@NotEmpty
@Schema(description = "主机分组")

View File

@@ -29,6 +29,7 @@ import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
import java.util.List;
/**
* 主机基本信息 视图响应对象
@@ -50,7 +51,7 @@ public class HostBaseVO implements Serializable {
private Long id;
@Schema(description = "主机类型")
private String type;
private List<String> types;
@Schema(description = "主机名称")
private String name;

View File

@@ -27,6 +27,7 @@ import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.dromara.visor.module.asset.handler.host.extra.model.HostSpecExtraModel;
import org.dromara.visor.module.infra.entity.dto.tag.TagDTO;
import java.io.Serializable;
@@ -54,11 +55,14 @@ public class HostVO implements Serializable {
private Long id;
@Schema(description = "主机类型")
private String type;
private List<String> types;
@Schema(description = "系统类型")
private String osType;
@Schema(description = "系统架构")
private String archType;
@Schema(description = "主机名称")
private String name;
@@ -104,4 +108,7 @@ public class HostVO implements Serializable {
@Schema(description = "颜色")
private String color;
@Schema(description = "规格")
private HostSpecExtraModel spec;
}

View File

@@ -23,13 +23,13 @@
package org.dromara.visor.module.asset.enums;
/**
* 主机认证类型 - ssh
* 主机认证类型
*
* @author Jiahang Li
* @version 1.0.0
* @since 2023/9/21 19:01
*/
public enum HostSshAuthTypeEnum {
public enum HostAuthTypeEnum {
/**
* 密码认证
@@ -48,11 +48,11 @@ public enum HostSshAuthTypeEnum {
;
public static HostSshAuthTypeEnum of(String type) {
public static HostAuthTypeEnum of(String type) {
if (type == null) {
return PASSWORD;
}
for (HostSshAuthTypeEnum value : values()) {
for (HostAuthTypeEnum value : values()) {
if (value.name().equals(type)) {
return value;
}

View File

@@ -29,7 +29,7 @@ package org.dromara.visor.module.asset.enums;
* @version 1.0.0
* @since 2023/12/20 21:41
*/
public enum HostExtraSshAuthTypeEnum {
public enum HostExtraAuthTypeEnum {
/**
* 默认认证方式
@@ -48,11 +48,11 @@ public enum HostExtraSshAuthTypeEnum {
;
public static HostExtraSshAuthTypeEnum of(String type) {
public static HostExtraAuthTypeEnum of(String type) {
if (type == null) {
return DEFAULT;
}
for (HostExtraSshAuthTypeEnum value : values()) {
for (HostExtraAuthTypeEnum value : values()) {
if (value.name().equals(type)) {
return value;
}

View File

@@ -0,0 +1,81 @@
/*
* Copyright (c) 2023 - present Dromara, All rights reserved.
*
* https://visor.dromara.org
* https://visor.dromara.org.cn
* https://visor.orionsec.cn
*
* Members:
* Jiahang Li - ljh1553488six@139.com - author
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.dromara.visor.module.asset.handler.host.config;
import cn.orionsec.kit.lang.utils.Booleans;
import cn.orionsec.kit.lang.utils.Charsets;
import org.dromara.visor.common.constant.ErrorMessage;
import org.dromara.visor.common.handler.data.model.GenericsDataModel;
import org.dromara.visor.common.handler.data.strategy.AbstractGenericsDataStrategy;
import org.dromara.visor.common.security.UpdatePasswordAction;
import org.dromara.visor.common.utils.AesEncryptUtils;
import org.dromara.visor.common.utils.RsaParamDecryptUtils;
import org.dromara.visor.common.utils.Valid;
import org.dromara.visor.module.asset.enums.HostAuthTypeEnum;
/**
* 主机配置策略基类
*
* @author Jiahang Li
* @version 1.0.0
* @since 2025/3/31 19:44
*/
public abstract class AbstractHostConfigStrategy<T extends GenericsDataModel> extends AbstractGenericsDataStrategy<T> {
public AbstractHostConfigStrategy(Class<T> modelClass) {
super(modelClass);
}
/**
* 检查加密密码
*
* @param before before
* @param after after
*/
protected void checkEncryptPassword(String authType, UpdatePasswordAction before, UpdatePasswordAction after) {
// 非密码认证/使用原始密码则直接赋值
if (!HostAuthTypeEnum.PASSWORD.name().equals(authType) || !Booleans.isTrue(after.getUseNewPassword())) {
if (before != null) {
after.setPassword(before.getPassword());
}
return;
}
// 检查新密码
String newPassword = Valid.notBlank(after.getPassword(), ErrorMessage.PASSWORD_MISSING);
// 解密密码
newPassword = RsaParamDecryptUtils.decrypt(newPassword);
Valid.notBlank(newPassword, ErrorMessage.DECRYPT_ERROR);
// 设置密码
after.setPassword(AesEncryptUtils.encryptAsString(newPassword));
}
/**
* 检查编码格式
*
* @param charset charset
*/
protected void validCharset(String charset) {
Valid.isTrue(Charsets.isSupported(charset), ErrorMessage.UNSUPPORTED_CHARSET, charset);
}
}

View File

@@ -20,17 +20,16 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.dromara.visor.module.asset.enums;
package org.dromara.visor.module.asset.handler.host.config;
import lombok.AllArgsConstructor;
import lombok.Getter;
import org.dromara.visor.common.handler.data.GenericsStrategyDefinition;
import org.dromara.visor.common.handler.data.model.GenericsDataModel;
import org.dromara.visor.common.handler.data.strategy.GenericsDataStrategy;
import org.dromara.visor.module.asset.handler.host.config.strategy.HostSshConfigStrategy;
/**
* 主机配置类型枚举
* 主机配置类型策略枚举
*
* @author Jiahang Li
* @version 1.0.0
@@ -38,22 +37,27 @@ import org.dromara.visor.module.asset.handler.host.config.strategy.HostSshConfig
*/
@Getter
@AllArgsConstructor
public enum HostTypeEnum implements GenericsStrategyDefinition {
public enum HostConfigStrategyEnum implements GenericsStrategyDefinition {
/**
* SSH
*/
SSH(HostSshConfigStrategy.class),
/**
* RDP
*/
RDP(HostRdpConfigStrategy.class),
;
private final Class<? extends GenericsDataStrategy<? extends GenericsDataModel>> strategyClass;
public static HostTypeEnum of(String type) {
public static HostConfigStrategyEnum of(String type) {
if (type == null) {
return null;
}
for (HostTypeEnum value : values()) {
for (HostConfigStrategyEnum value : values()) {
if (value.name().equals(type)) {
return value;
}

View File

@@ -0,0 +1,107 @@
/*
* Copyright (c) 2023 - present Dromara, All rights reserved.
*
* https://visor.dromara.org
* https://visor.dromara.org.cn
* https://visor.orionsec.cn
*
* Members:
* Jiahang Li - ljh1553488six@139.com - author
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.dromara.visor.module.asset.handler.host.config;
import cn.orionsec.kit.lang.utils.Strings;
import org.dromara.visor.common.constant.Const;
import org.dromara.visor.common.constant.ErrorMessage;
import org.dromara.visor.common.utils.Valid;
import org.dromara.visor.module.asset.dao.HostIdentityDAO;
import org.dromara.visor.module.asset.entity.domain.HostIdentityDO;
import org.dromara.visor.module.asset.entity.dto.host.HostRdpConfigDTO;
import org.dromara.visor.module.asset.enums.HostAuthTypeEnum;
import org.dromara.visor.module.asset.enums.HostIdentityTypeEnum;
import org.dromara.visor.module.asset.enums.HostTypeEnum;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
/**
* 主机 RDP 配置策略
*
* @author Jiahang Li
* @version 1.0.0
* @since 2023/9/19 14:26
*/
@Component
public class HostRdpConfigStrategy extends AbstractHostConfigStrategy<HostRdpConfigDTO> {
@Resource
private HostIdentityDAO hostIdentityDAO;
public HostRdpConfigStrategy() {
super(HostRdpConfigDTO.class);
}
@Override
public HostRdpConfigDTO getDefault() {
return HostRdpConfigDTO.builder()
.port(3389)
.username(Const.ADMINISTRATOR)
.authType(HostAuthTypeEnum.PASSWORD.name())
.versionGt81(true)
.timezone("Asia/Shanghai")
.keyboardLayout("en-us-qwerty")
.clipboardNormalize("preserve")
.build();
}
@Override
protected void preValid(HostRdpConfigDTO model) {
// 检查主机身份是否存在
Long identityId = model.getIdentityId();
if (identityId != null) {
HostIdentityDO identity = Valid.notNull(hostIdentityDAO.selectById(identityId), ErrorMessage.IDENTITY_ABSENT);
Valid.eq(HostIdentityTypeEnum.PASSWORD.name(), identity.getType(), ErrorMessage.CHECK_IDENTITY_PASSWORD);
}
}
@Override
protected void valid(HostRdpConfigDTO model) {
// 验证填充后的参数
Valid.valid(model);
}
@Override
protected void updateFill(HostRdpConfigDTO beforeModel, HostRdpConfigDTO afterModel) {
// 加密密码
this.checkEncryptPassword(afterModel.getAuthType(), beforeModel, afterModel);
afterModel.setHasPassword(null);
afterModel.setUseNewPassword(null);
}
@Override
public HostRdpConfigDTO parse(String serialModel) {
return HostTypeEnum.RDP.parse(serialModel);
}
@Override
public void toView(HostRdpConfigDTO model) {
if (model == null) {
return;
}
model.setHasPassword(Strings.isNotBlank(model.getPassword()));
model.setPassword(null);
}
}

View File

@@ -20,21 +20,17 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.dromara.visor.module.asset.handler.host.config.strategy;
package org.dromara.visor.module.asset.handler.host.config;
import cn.orionsec.kit.lang.utils.Booleans;
import cn.orionsec.kit.lang.utils.Charsets;
import cn.orionsec.kit.lang.utils.Strings;
import org.dromara.visor.common.constant.Const;
import org.dromara.visor.common.constant.ErrorMessage;
import org.dromara.visor.common.handler.data.strategy.AbstractGenericsDataStrategy;
import org.dromara.visor.common.utils.AesEncryptUtils;
import org.dromara.visor.common.utils.RsaParamDecryptUtils;
import org.dromara.visor.common.utils.Valid;
import org.dromara.visor.module.asset.dao.HostIdentityDAO;
import org.dromara.visor.module.asset.dao.HostKeyDAO;
import org.dromara.visor.module.asset.enums.HostSshAuthTypeEnum;
import org.dromara.visor.module.asset.handler.host.config.model.HostSshConfigModel;
import org.dromara.visor.module.asset.entity.dto.host.HostSshConfigDTO;
import org.dromara.visor.module.asset.enums.HostAuthTypeEnum;
import org.dromara.visor.module.asset.enums.HostTypeEnum;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
@@ -47,7 +43,7 @@ import javax.annotation.Resource;
* @since 2023/9/19 14:26
*/
@Component
public class HostSshConfigStrategy extends AbstractGenericsDataStrategy<HostSshConfigModel> {
public class HostSshConfigStrategy extends AbstractHostConfigStrategy<HostSshConfigDTO> {
@Resource
private HostKeyDAO hostKeyDAO;
@@ -55,17 +51,16 @@ public class HostSshConfigStrategy extends AbstractGenericsDataStrategy<HostSshC
@Resource
private HostIdentityDAO hostIdentityDAO;
private static final String USERNAME = "root";
public HostSshConfigStrategy() {
super(HostSshConfigModel.class);
super(HostSshConfigDTO.class);
}
@Override
public HostSshConfigModel getDefault() {
return HostSshConfigModel.builder()
.username(USERNAME)
.authType(HostSshAuthTypeEnum.PASSWORD.name())
public HostSshConfigDTO getDefault() {
return HostSshConfigDTO.builder()
.port(22)
.username(Const.ROOT)
.authType(HostAuthTypeEnum.PASSWORD.name())
.connectTimeout(Const.MS_S_10)
.charset(Const.UTF_8)
.fileNameCharset(Const.UTF_8)
@@ -74,7 +69,7 @@ public class HostSshConfigStrategy extends AbstractGenericsDataStrategy<HostSshC
}
@Override
protected void preValid(HostSshConfigModel model) {
protected void preValid(HostSshConfigDTO model) {
// 验证编码格式
this.validCharset(model.getCharset());
this.validCharset(model.getFileNameCharset());
@@ -92,21 +87,26 @@ public class HostSshConfigStrategy extends AbstractGenericsDataStrategy<HostSshC
}
@Override
protected void valid(HostSshConfigModel model) {
protected void valid(HostSshConfigDTO model) {
// 验证填充后的参数
Valid.valid(model);
}
@Override
protected void updateFill(HostSshConfigModel beforeModel, HostSshConfigModel afterModel) {
protected void updateFill(HostSshConfigDTO beforeModel, HostSshConfigDTO afterModel) {
// 加密密码
this.checkEncryptPassword(beforeModel, afterModel);
this.checkEncryptPassword(afterModel.getAuthType(), beforeModel, afterModel);
afterModel.setHasPassword(null);
afterModel.setUseNewPassword(null);
}
@Override
public void toView(HostSshConfigModel model) {
public HostSshConfigDTO parse(String serialModel) {
return HostTypeEnum.SSH.parse(serialModel);
}
@Override
public void toView(HostSshConfigDTO model) {
if (model == null) {
return;
}
@@ -114,39 +114,4 @@ public class HostSshConfigStrategy extends AbstractGenericsDataStrategy<HostSshC
model.setPassword(null);
}
/**
* 检查加密密码
*
* @param before before
* @param after after
*/
private void checkEncryptPassword(HostSshConfigModel before, HostSshConfigModel after) {
// 非密码认证则直接赋值
if (!HostSshAuthTypeEnum.PASSWORD.name().equals(after.getAuthType())) {
after.setPassword(before.getPassword());
return;
}
// 使用原始密码
if (!Booleans.isTrue(after.getUseNewPassword())) {
after.setPassword(before.getPassword());
return;
}
// 检查新密码
String newPassword = Valid.notBlank(after.getPassword(), ErrorMessage.PASSWORD_MISSING);
// 解密密码
newPassword = RsaParamDecryptUtils.decrypt(newPassword);
Valid.notBlank(newPassword, ErrorMessage.DECRYPT_ERROR);
// 设置密码
after.setPassword(AesEncryptUtils.encryptAsString(newPassword));
}
/**
* 检查编码格式
*
* @param charset charset
*/
private void validCharset(String charset) {
Valid.isTrue(Charsets.isSupported(charset), ErrorMessage.UNSUPPORTED_CHARSET, charset);
}
}

View File

@@ -20,7 +20,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.dromara.visor.module.asset.enums;
package org.dromara.visor.module.asset.handler.host.extra;
import lombok.AllArgsConstructor;
import lombok.Getter;
@@ -28,10 +28,12 @@ import org.dromara.visor.common.handler.data.GenericsStrategyDefinition;
import org.dromara.visor.common.handler.data.model.GenericsDataModel;
import org.dromara.visor.common.handler.data.strategy.GenericsDataStrategy;
import org.dromara.visor.module.asset.handler.host.extra.strategy.HostLabelExtraStrategy;
import org.dromara.visor.module.asset.handler.host.extra.strategy.HostRdpExtraStrategy;
import org.dromara.visor.module.asset.handler.host.extra.strategy.HostSpecExtraStrategy;
import org.dromara.visor.module.asset.handler.host.extra.strategy.HostSshExtraStrategy;
/**
* 主机额外配置项枚举
* 主机额外配置项策略枚举
*
* @author Jiahang Li
* @version 1.0.0
@@ -41,20 +43,32 @@ import org.dromara.visor.module.asset.handler.host.extra.strategy.HostSshExtraSt
@AllArgsConstructor
public enum HostExtraItemEnum implements GenericsStrategyDefinition {
/**
* SSH 额外配置
*/
SSH(HostSshExtraStrategy.class),
/**
* 标签额外配置
*/
LABEL(HostLabelExtraStrategy.class),
LABEL(HostLabelExtraStrategy.class, true),
/**
* SSH 额外配置
*/
SSH(HostSshExtraStrategy.class, true),
/**
* RDP 额外配置
*/
RDP(HostRdpExtraStrategy.class, true),
/**
* 规格信息配置
*/
SPEC(HostSpecExtraStrategy.class, false),
;
private final Class<? extends GenericsDataStrategy<? extends GenericsDataModel>> strategyClass;
private final boolean userExtra;
public static HostExtraItemEnum of(String item) {
if (item == null) {
return null;

View File

@@ -0,0 +1,59 @@
/*
* Copyright (c) 2023 - present Dromara, All rights reserved.
*
* https://visor.dromara.org
* https://visor.dromara.org.cn
* https://visor.orionsec.cn
*
* Members:
* Jiahang Li - ljh1553488six@139.com - author
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.dromara.visor.module.asset.handler.host.extra.model;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.dromara.visor.common.handler.data.model.GenericsDataModel;
/**
* 主机拓展信息 - rdp 模型
*
* @author Jiahang Li
* @version 1.0.0
* @since 2023/12/20 21:36
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class HostRdpExtraModel implements GenericsDataModel {
/**
* 认证方式
*/
private String authType;
/**
* 主机身份
*/
private Long identityId;
/**
* 低带宽模式
*/
private Boolean lowBandwidthMode;
}

View File

@@ -0,0 +1,141 @@
/*
* Copyright (c) 2023 - present Dromara, All rights reserved.
*
* https://visor.dromara.org
* https://visor.dromara.org.cn
* https://visor.orionsec.cn
*
* Members:
* Jiahang Li - ljh1553488six@139.com - author
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.dromara.visor.module.asset.handler.host.extra.model;
import lombok.*;
import org.dromara.visor.common.handler.data.model.GenericsDataModel;
import java.util.Date;
import java.util.List;
/**
* 主机拓展信息 - 规格模型
*
* @author Jiahang Li
* @version 1.0.0
* @since 2025/3/24 0:34
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class HostSpecExtraModel implements GenericsDataModel {
/**
* sn
*/
private String sn;
/**
* 系统名称
*/
private String osName;
/**
* cpu 核心数
*/
private Integer cpuCore;
/**
* cpu 频率
*/
private Double cpuFrequency;
/**
* cpu 型号
*/
private String cpuModel;
/**
* 内存大小
*/
private Double memorySize;
/**
* 硬盘大小
*/
private Double diskSize;
/**
* 上行带宽
*/
private Integer inBandwidth;
/**
* 下行带宽
*/
private Integer outBandwidth;
/**
* 公网 ip 列表
*/
private List<String> publicIpAddress;
/**
* 内网 ip 列表
*/
private List<String> privateIpAddress;
/**
* 负责人
*/
private String chargePerson;
/**
* 创建时间
*/
private Date createdTime;
/**
* 到期时间
*/
private Date expiredTime;
/**
* 扩展信息
*/
@Singular
private List<HostSpecExtraItem> items;
/**
* 扩展信息项
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public static class HostSpecExtraItem {
/**
* 标签
*/
private String label;
/**
* 值
*/
private String value;
}
}

View File

@@ -53,12 +53,14 @@ public class HostLabelExtraStrategy extends AbstractGenericsDataStrategy<HostLab
@Override
public void updateFill(HostLabelExtraModel beforeModel, HostLabelExtraModel afterModel) {
// 为空则覆盖
if (afterModel.getAlias() == null) {
afterModel.setAlias(beforeModel.getAlias());
}
if (afterModel.getColor() == null) {
afterModel.setColor(beforeModel.getColor());
if (beforeModel != null) {
// 为空则覆盖
if (afterModel.getAlias() == null) {
afterModel.setAlias(beforeModel.getAlias());
}
if (afterModel.getColor() == null) {
afterModel.setColor(beforeModel.getColor());
}
}
}

View File

@@ -0,0 +1,83 @@
/*
* Copyright (c) 2023 - present Dromara, All rights reserved.
*
* https://visor.dromara.org
* https://visor.dromara.org.cn
* https://visor.orionsec.cn
*
* Members:
* Jiahang Li - ljh1553488six@139.com - author
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.dromara.visor.module.asset.handler.host.extra.strategy;
import org.dromara.visor.common.constant.ErrorMessage;
import org.dromara.visor.common.handler.data.strategy.AbstractGenericsDataStrategy;
import org.dromara.visor.common.utils.Valid;
import org.dromara.visor.framework.security.core.utils.SecurityUtils;
import org.dromara.visor.module.asset.dao.HostIdentityDAO;
import org.dromara.visor.module.asset.enums.HostExtraAuthTypeEnum;
import org.dromara.visor.module.asset.handler.host.extra.model.HostRdpExtraModel;
import org.dromara.visor.module.infra.api.DataPermissionApi;
import org.dromara.visor.module.infra.enums.DataPermissionTypeEnum;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
/**
* 主机拓展信息 - rdp 模型处理策略
*
* @author Jiahang Li
* @version 1.0.0
* @since 2023/12/20 22:17
*/
@Component
public class HostRdpExtraStrategy extends AbstractGenericsDataStrategy<HostRdpExtraModel> {
@Resource
private HostIdentityDAO hostIdentityDAO;
@Resource
private DataPermissionApi dataPermissionApi;
public HostRdpExtraStrategy() {
super(HostRdpExtraModel.class);
}
@Override
public HostRdpExtraModel getDefault() {
return HostRdpExtraModel.builder()
.authType(HostExtraAuthTypeEnum.DEFAULT.name())
.build();
}
@Override
public void preValid(HostRdpExtraModel model) {
HostExtraAuthTypeEnum authType = Valid.valid(HostExtraAuthTypeEnum::of, model.getAuthType());
model.setAuthType(authType.name());
Long identityId = model.getIdentityId();
Long userId = SecurityUtils.getLoginUserId();
// 必填验证
if (HostExtraAuthTypeEnum.CUSTOM_IDENTITY.equals(authType)) {
Valid.notNull(identityId);
// 验证主机身份是否存在
Valid.notNull(hostIdentityDAO.selectById(identityId), ErrorMessage.IDENTITY_ABSENT);
// 验证主机身份是否有权限
Valid.isTrue(dataPermissionApi.hasPermission(DataPermissionTypeEnum.HOST_IDENTITY, userId, identityId),
ErrorMessage.ANY_NO_PERMISSION,
DataPermissionTypeEnum.HOST_IDENTITY.getPermissionName());
}
}
}

View File

@@ -20,29 +20,29 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.dromara.visor.module.asset.handler.host.terminal.handler;
package org.dromara.visor.module.asset.handler.host.extra.strategy;
import lombok.extern.slf4j.Slf4j;
import org.dromara.visor.module.asset.handler.host.terminal.model.TerminalBasePayload;
import org.dromara.visor.common.handler.data.strategy.AbstractGenericsDataStrategy;
import org.dromara.visor.module.asset.handler.host.extra.model.HostSpecExtraModel;
import org.springframework.stereotype.Component;
import org.springframework.web.socket.WebSocketSession;
/**
* 关闭处理器
* 主机规格额外信息策略
*
* @author Jiahang Li
* @version 1.0.0
* @since 2023/12/29 15:32
* @since 2025/3/24 0:21
*/
@Slf4j
@Component
public class TerminalCloseHandler extends AbstractTerminalHandler<TerminalBasePayload> {
public class HostSpecExtraStrategy extends AbstractGenericsDataStrategy<HostSpecExtraModel> {
public HostSpecExtraStrategy() {
super(HostSpecExtraModel.class);
}
@Override
public void handle(WebSocketSession channel, TerminalBasePayload payload) {
log.info("TerminalCloseHandler-handle start sessionId: {}", payload.getSessionId());
// 关闭会话
terminalManager.closeSession(channel.getId(), payload.getSessionId());
public HostSpecExtraModel getDefault() {
return new HostSpecExtraModel();
}
}

View File

@@ -28,7 +28,7 @@ import org.dromara.visor.common.utils.Valid;
import org.dromara.visor.framework.security.core.utils.SecurityUtils;
import org.dromara.visor.module.asset.dao.HostIdentityDAO;
import org.dromara.visor.module.asset.dao.HostKeyDAO;
import org.dromara.visor.module.asset.enums.HostExtraSshAuthTypeEnum;
import org.dromara.visor.module.asset.enums.HostExtraAuthTypeEnum;
import org.dromara.visor.module.asset.handler.host.extra.model.HostSshExtraModel;
import org.dromara.visor.module.infra.api.DataPermissionApi;
import org.dromara.visor.module.infra.enums.DataPermissionTypeEnum;
@@ -62,20 +62,20 @@ public class HostSshExtraStrategy extends AbstractGenericsDataStrategy<HostSshEx
@Override
public HostSshExtraModel getDefault() {
return HostSshExtraModel.builder()
.authType(HostExtraSshAuthTypeEnum.DEFAULT.name())
.authType(HostExtraAuthTypeEnum.DEFAULT.name())
.build();
}
@Override
public void preValid(HostSshExtraModel model) {
HostExtraSshAuthTypeEnum authType = Valid.valid(HostExtraSshAuthTypeEnum::of, model.getAuthType());
HostExtraAuthTypeEnum authType = Valid.valid(HostExtraAuthTypeEnum::of, model.getAuthType());
model.setAuthType(authType.name());
Long keyId = model.getKeyId();
Long identityId = model.getIdentityId();
// 必填验证
if (HostExtraSshAuthTypeEnum.CUSTOM_KEY.equals(authType)) {
if (HostExtraAuthTypeEnum.CUSTOM_KEY.equals(authType)) {
Valid.notNull(keyId);
} else if (HostExtraSshAuthTypeEnum.CUSTOM_IDENTITY.equals(authType)) {
} else if (HostExtraAuthTypeEnum.CUSTOM_IDENTITY.equals(authType)) {
Valid.notNull(identityId);
}
// 验证主机密钥是否存在

View File

@@ -1,93 +0,0 @@
/*
* Copyright (c) 2023 - present Dromara, All rights reserved.
*
* https://visor.dromara.org
* https://visor.dromara.org.cn
* https://visor.orionsec.cn
*
* Members:
* Jiahang Li - ljh1553488six@139.com - author
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.dromara.visor.module.asset.handler.host.terminal;
import lombok.extern.slf4j.Slf4j;
import org.dromara.visor.module.asset.define.AssetThreadPools;
import org.dromara.visor.module.asset.handler.host.terminal.enums.InputTypeEnum;
import org.dromara.visor.module.asset.handler.host.terminal.manager.TerminalManager;
import org.springframework.stereotype.Component;
import org.springframework.web.socket.CloseStatus;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.handler.AbstractWebSocketHandler;
import javax.annotation.Resource;
/**
* 终端处理器
*
* @author Jiahang Li
* @version 1.0.0
* @since 2023/12/28 14:33
*/
@Slf4j
@Component
public class TerminalMessageDispatcher extends AbstractWebSocketHandler {
@Resource
private TerminalManager terminalManager;
@Override
protected void handleTextMessage(WebSocketSession session, TextMessage message) {
String payload = message.getPayload();
try {
// 解析类型
InputTypeEnum type = InputTypeEnum.of(payload);
if (type == null) {
return;
}
// 解析并处理消息
if (type.isAsyncExec()) {
// 异步执行
AssetThreadPools.TERMINAL_OPERATOR.execute(() -> {
type.getHandler().handle(session, type.parse(payload));
});
} else {
// 同步执行
type.getHandler().handle(session, type.parse(payload));
}
} catch (Exception e) {
log.error("TerminalDispatchHandler-handleMessage-error id: {}, msg: {}", session.getId(), payload, e);
}
}
@Override
public void afterConnectionEstablished(WebSocketSession session) {
log.info("TerminalMessageDispatcher-afterConnectionEstablished id: {}", session.getId());
}
@Override
public void handleTransportError(WebSocketSession session, Throwable exception) {
log.error("TerminalMessageDispatcher-handleTransportError id: {}", session.getId(), exception);
}
@Override
public void afterConnectionClosed(WebSocketSession session, CloseStatus status) {
String id = session.getId();
log.info("TerminalMessageDispatcher-afterConnectionClosed id: {}, code: {}, reason: {}", id, status.getCode(), status.getReason());
// 关闭会话
terminalManager.closeSession(id);
}
}

View File

@@ -1,125 +0,0 @@
/*
* Copyright (c) 2023 - present Dromara, All rights reserved.
*
* https://visor.dromara.org
* https://visor.dromara.org.cn
* https://visor.orionsec.cn
*
* Members:
* Jiahang Li - ljh1553488six@139.com - author
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.dromara.visor.module.asset.handler.host.terminal.handler;
import org.dromara.visor.common.constant.ErrorMessage;
import org.dromara.visor.framework.biz.operator.log.core.model.OperatorLogModel;
import org.dromara.visor.framework.biz.operator.log.core.service.OperatorLogFrameworkService;
import org.dromara.visor.framework.biz.operator.log.core.utils.OperatorLogs;
import org.dromara.visor.framework.websocket.core.utils.WebSockets;
import org.dromara.visor.module.asset.handler.host.terminal.enums.OutputTypeEnum;
import org.dromara.visor.module.asset.handler.host.terminal.manager.TerminalManager;
import org.dromara.visor.module.asset.handler.host.terminal.model.TerminalBasePayload;
import org.dromara.visor.module.asset.handler.host.terminal.model.TerminalConfig;
import org.dromara.visor.module.asset.handler.host.terminal.session.ITerminalSession;
import org.dromara.visor.module.asset.handler.host.terminal.utils.TerminalUtils;
import org.springframework.web.socket.WebSocketSession;
import javax.annotation.Resource;
import java.util.Map;
/**
* 终端消息处理器 基类
*
* @author Jiahang Li
* @version 1.0.0
* @since 2023/12/29 18:59
*/
public abstract class AbstractTerminalHandler<T extends TerminalBasePayload> implements ITerminalHandler<T> {
@Resource
protected TerminalManager terminalManager;
@Resource
private OperatorLogFrameworkService operatorLogFrameworkService;
/**
* 发送消息
*
* @param channel channel
* @param type type
* @param body body
* @param <E> E
*/
public <E extends TerminalBasePayload> void send(WebSocketSession channel, OutputTypeEnum type, E body) {
body.setType(type.getType());
// 发送消息
this.send(channel, type.format(body));
}
/**
* 发送消息
*
* @param channel channel
* @param message message
*/
protected void send(WebSocketSession channel, String message) {
WebSockets.sendText(channel, message);
}
/**
* 保存操作日志
*
* @param payload payload
* @param channel channel
* @param extra extra
* @param type type
* @param startTime startTime
* @param ex ex
*/
protected void saveOperatorLog(T payload,
WebSocketSession channel,
Map<String, Object> extra,
String type,
long startTime,
Exception ex) {
String channelId = channel.getId();
String sessionId = payload.getSessionId();
// 获取会话并且设置参数
ITerminalSession session = terminalManager.getSession(channelId, sessionId);
if (session != null) {
TerminalConfig config = session.getConfig();
extra.put(OperatorLogs.HOST_ID, config.getHostId());
extra.put(OperatorLogs.HOST_NAME, config.getHostName());
extra.put(OperatorLogs.ADDRESS, config.getAddress());
}
extra.put(OperatorLogs.CHANNEL_ID, channelId);
extra.put(OperatorLogs.SESSION_ID, sessionId);
// 获取日志
OperatorLogModel model = TerminalUtils.getOperatorLogModel(channel, extra, type, startTime, ex);
// 保存
operatorLogFrameworkService.insert(model);
}
/**
* 获取错误信息
*
* @param ex ex
* @return msg
*/
protected String getErrorMessage(Exception ex) {
// 获取错误信息
return ErrorMessage.getErrorMessage(ex, ErrorMessage.OPERATE_ERROR);
}
}

Some files were not shown because too many files have changed in this diff Show More