Compare commits

...

23 Commits

Author SHA1 Message Date
lijiahangmax
78bf636cdb Merge pull request #2 from lijiahangmax/dev
merge v1.0.1
2024-03-06 00:06:04 +08:00
lijiahangmax
ba338c15de 🔖 升级版本. 2024-03-06 00:03:13 +08:00
lijiahang
93407460d8 登录配置化. 2024-03-05 19:31:00 +08:00
lijiahang
554c62abf7 SFTP 操作日志. 2024-03-05 18:07:26 +08:00
lijiahangmax
a75ead9a58 sftp 操作日志. 2024-03-05 00:02:06 +08:00
lijiahangmax
f1ade4e182 💄 优化表格样式. 2024-03-04 22:27:39 +08:00
lijiahang
462e77f936 Merge remote-tracking branch 'origin/dev' into dev
# Conflicts:
#	orion-ops-ui/src/views/user/operator-log/components/operator-log-table.vue
2024-03-04 19:11:54 +08:00
lijiahang
ba955571a3 清空操作日志. 2024-03-04 19:10:55 +08:00
lijiahang
d1e94a49e0 清空操作日志. 2024-03-04 19:04:51 +08:00
lijiahang
b9127967d0 清空主机连接日志. 2024-03-04 18:28:07 +08:00
lijiahang
0538d2aa26 清空主机连接日志. 2024-03-04 15:14:45 +08:00
lijiahang
0f8eebf53c 🎨 修改前端代码样式. 2024-03-04 12:28:55 +08:00
lijiahangmax
0f9c3db9cc 💄 连接日志表格样式更新. 2024-03-03 23:28:17 +08:00
lijiahangmax
b424dd02db 强制下线终端. 2024-03-03 00:24:00 +08:00
lijiahangmax
f1d14b4a12 📝 修改文档. 2024-03-02 13:22:22 +08:00
lijiahangmax
770f54df96 Merge pull request #1 from lijiahangmax/dev
v1.0.0
2024-03-01 17:26:29 +08:00
lijiahang
4799936a5f 🐛 终端标签页颜色错误. 2024-03-01 17:24:43 +08:00
lijiahang
55373bb7dc 终端颜色配置. 2024-03-01 15:56:00 +08:00
lijiahang
7e91ef8386 🚀 修订版本号. 2024-03-01 13:54:09 +08:00
lijiahang
1b459beb84 终端颜色配置. 2024-03-01 12:32:06 +08:00
lijiahangmax
c48e48ab72 💄 修改主机配置模态框样式. 2024-03-01 00:35:55 +08:00
lijiahangmax
1c6a38d5d9 优化别名逻辑. 2024-02-29 23:25:00 +08:00
lijiahang
44dd5a9079 添加 tab 配色. 2024-02-29 19:15:08 +08:00
156 changed files with 4160 additions and 1231 deletions

View File

@@ -2,7 +2,7 @@
<img style="margin-right: 8px;" src="https://bjuimg.obs.cn-north-4.myhuaweicloud.com/images/2024/2/27/8c687ef1-5711-4a93-9db0-79c010af7902.png" width="32px" height="32px"/> orion-ops-pro 是什么
</h1>
`orion-ops-pro` 一款开箱即用的运维平台, 提供了资产管理、资产授权、Web终端、WebSftp、角色管理、系统管理等功能。为运维团队提供轻量化的运维治理平台。它是根据 `orion-ops`
`orion-ops-pro` 一款开箱即用的一站式智能运维平台, 提供了资产管理、资产授权、Web终端、WebSftp、角色管理、系统管理等功能。致力于简化运维团队的治理工作。它是根据 `orion-ops`
的产品思路完全重构的一套系统, 重新设计了架构并优化交互逻辑, 操作更快捷友好。
<p style="text-align: left">
@@ -28,7 +28,7 @@
<br/>
当前版本: **1.0.0-beta.1**
当前版本: **1.0.1**
github: https://github.com/lijiahangmax/orion-ops-pro
gitee: https://gitee.com/lijiahangmax/orion-ops-pro
文档: https://lijiahangmax.gitee.io/orion-ops-pro/#/
@@ -40,12 +40,12 @@ demo: http://101.43.254.243:1081/#/
## 特性
* 易用便捷: 极简配置, 开箱即用, 并兼容 Docker 部署方式。
* 易用便捷: 极简配置, 开箱即用, 支持 Docker 部署方式。
* 资产管理: 支持灵活配置主机分组, 统一管理主机、秘钥和身份。
* 资产授权: 可将资产数据授权给指定角色和用户。
* 权限控制: 全面管理用户角色, 支持动态菜单配置和强制下线等功能。
* 在线终端: 提供便捷的在线 Web 终端服务, 支持自定义快捷键和主题风格。
* 文件管理: 实现远程主机文件的批量上传、下载和在线编辑等操作。
* 在线终端: 提供便捷的在线 Web 终端服务, 支持快捷命令、自定义快捷键和主题风格。
* 文件管理: 实现远程主机文件的批量上传、下载和在线编辑等操作。
* 可扩展性: 前后端代码规范统一, 代码质量高、健壮且易于阅读和扩展。
[comment]: <> ( FIXME * 批量操作: 支持远程主机批量执行命令 以及 批量执行上传文件)

View File

@@ -1,7 +1,7 @@
version: '3.3'
services:
orion-ops-pro:
image: registry.cn-hangzhou.aliyuncs.com/lijiahangmax/orion-ops-pro:1.0.0-beta.1
image: registry.cn-hangzhou.aliyuncs.com/lijiahangmax/orion-ops-pro:1.0.1
ports:
- 1081:80
environment:

View File

@@ -1,3 +1,3 @@
mv ../../orion-ops-launch/target/orion-ops-launch.jar ./
mv ../../orion-ops-ui/dist ./dist
docker build -t orion-ops-pro:1.0.0-beta.1 .
docker build -t orion-ops-pro:1.0.1 .

View File

@@ -2,7 +2,7 @@
<img style="margin-right: 8px;" src="./assert/logo.svg" width="32px" height="32px"/> orion-ops-pro 是什么
</h1>
`orion-ops-pro` 一款开箱即用的运维平台, 提供了资产管理、资产授权、Web终端、WebSftp、角色管理、系统管理等功能。为运维团队提供轻量化的运维治理平台。它是根据 `orion-ops`
`orion-ops-pro` 一款开箱即用的一站式智能运维平台, 提供了资产管理、资产授权、Web终端、WebSftp、角色管理、系统管理等功能。致力于简化运维团队的治理工作。它是根据 `orion-ops`
的产品思路完全重构的一套系统, 重新设计了架构并优化交互逻辑, 操作更快捷友好。
<p style="text-align: left">
@@ -28,7 +28,7 @@
<br/>
当前版本: **1.0.0-beta.1**
当前版本: **1.0.1**
github: https://github.com/lijiahangmax/orion-ops-pro
gitee: https://gitee.com/lijiahangmax/orion-ops-pro
文档: https://lijiahangmax.gitee.io/orion-ops-pro/#/
@@ -40,12 +40,12 @@ demo: http://101.43.254.243:1081/#/
## 特性
* 易用便捷: 极简配置, 开箱即用, 并兼容 Docker 部署方式。
* 易用便捷: 极简配置, 开箱即用, 支持 Docker 部署方式。
* 资产管理: 支持灵活配置主机分组, 统一管理主机、秘钥和身份。
* 资产授权: 可将资产数据授权给指定角色和用户。
* 权限控制: 全面管理用户角色, 支持动态菜单配置和强制下线等功能。
* 在线终端: 提供便捷的在线 Web 终端服务, 支持自定义快捷键和主题风格。
* 文件管理: 实现远程主机文件的批量上传、下载和在线编辑等操作。
* 在线终端: 提供便捷的在线 Web 终端服务, 支持快捷命令、自定义快捷键和主题风格。
* 文件管理: 实现远程主机文件的批量上传、下载和在线编辑等操作。
* 可扩展性: 前后端代码规范统一, 代码质量高、健壮且易于阅读和扩展。
[comment]: <> ( FIXME * 批量操作: 支持远程主机批量执行命令 以及 批量执行上传文件)

View File

@@ -1,8 +1,8 @@
# orion-ops-pro <small>1.0.0-beta.1</small>
# orion-ops-pro <small>1.0.1</small>
> 一款开箱即用的运维平台。
- 易用 便捷
- 友好 易用
- 安全 稳定
- 智能 高效

View File

@@ -1,8 +1,30 @@
> 版本号严格遵循 Semver 规范。
[//]: # (🌈添加 🔨优化 🐞修复 [如何升级]&#40;/about/update.md?id=_100&#41;)
## v1.0.1
## 1.0.0-beta.1
`2024-03-06` `release`
🐞 修复 用户操作日志条件重置后类型框数据不正常的问题
🩰 修改 主机连接日志 UI
🌈 新增 SFTP 使用日志列表
🌈 新增 主机连接日志强制下线会话
🌈 新增 主机连接日志删除/清理
🌈 新增 用户操作日志日志删除/清理
🌈 新增 用户操作日志日志删除/清理
🔨 优化 用户锁定次数/时间可配置
[如何升级](/about/update.md?id=_v101)
## v1.0.0
`2024-03-01` `release`
🌈 新增 用户自定义终端标签颜色
🔨 拓展数据模块添加缓存
[如何升级](/about/update.md?id=_v100)
## v1.0.0-beta.1
`2024-02-28` `preview`

View File

@@ -4,9 +4,11 @@
## 未开始 ⏳
* 资产管理表结构优化
* 批量执行
* 定时执行
* 站内消息
* 后端配置动态配置
* 终端背景图片
* 资产数据 UI 改版
* 资产授权 UI 改版
* RDP 远程桌面
* 接入 config 后端动态配置

View File

@@ -1,9 +1,62 @@
⚡ 注意: 应用不支持跨版本升级, 可以进行多次升级
## 1.0.0-beta.1
## v1.0.1
> sql 脚本
```sql
DROP TABLE IF EXISTS `command_template`;
ALTER TABLE `operator_log` ADD INDEX `idx_type`(`type`);
-- 菜单配置
DELETE FROM `system_menu` WHERE id IN (148, 149);
INSERT INTO `system_menu` VALUES (148, 152, '连接日志', NULL, 2, 10, 1, 1, 1, 0, 'IconLink', NULL, 'assetAuditConnectLog', '2023-12-26 22:53:07', '2024-03-05 23:31:23', NULL, '1', 0);
INSERT INTO `system_menu` VALUES (149, 148, '查询连接日志', 'asset:host-connect-log:management:query', 3, 10, 1, 1, 1, 0, NULL, NULL, NULL, '2023-12-26 22:53:08', '2024-03-04 13:40:42', NULL, '1', 0);
INSERT INTO `system_menu` VALUES (152, 0, '运维审计', NULL, 1, 410, 1, 1, 1, 0, 'IconSafe', NULL, 'assetAudit', '2024-01-04 17:54:56', '2024-03-05 23:31:10', '1', '1', 0);
INSERT INTO `system_menu` VALUES (153, 148, '删除连接日志', 'asset:host-connect-log:management:delete', 3, 20, 1, 1, 1, 0, NULL, NULL, NULL, '2024-03-04 13:39:46', '2024-03-04 13:40:29', '1', '1', 0);
INSERT INTO `system_menu` VALUES (154, 148, '清空连接日志', 'asset:host-connect-log:management:clear', 3, 30, 1, 1, 1, 0, NULL, NULL, NULL, '2024-03-04 13:40:05', '2024-03-04 13:40:34', '1', '1', 0);
INSERT INTO `system_menu` VALUES (155, 148, '强制断开连接', 'asset:host-connect-log:management:force-offline', 3, 40, 1, 1, 1, 0, NULL, NULL, NULL, '2024-03-04 13:41:02', '2024-03-05 23:32:01', '1', '1', 0);
INSERT INTO `system_menu` VALUES (156, 122, '删除操作日志', 'infra:operator-log:delete', 3, 20, 1, 1, 1, 0, NULL, NULL, NULL, '2024-03-04 17:06:55', '2024-03-04 17:08:22', '1', '1', 0);
INSERT INTO `system_menu` VALUES (157, 122, '清空操作日志', 'infra:operator-log:clear', 3, 30, 1, 1, 1, 0, NULL, NULL, NULL, '2024-03-04 17:07:25', '2024-03-04 17:08:27', '1', '1', 0);
INSERT INTO `system_menu` VALUES (158, 152, 'SFTP 操作日志', NULL, 2, 20, 1, 1, 1, 0, 'IconFile', NULL, 'assetAuditSftpLog', '2024-03-05 15:30:13', '2024-03-05 23:31:32', '1', '1', 0);
INSERT INTO `system_menu` VALUES (159, 158, '查询 SFTP 操作日志', 'asset:host-sftp-log:management:query', 3, 10, 1, 1, 1, 0, NULL, NULL, NULL, '2024-03-05 15:31:02', '2024-03-05 15:57:20', '1', '1', 0);
INSERT INTO `system_menu` VALUES (160, 158, '删除 SFTP 操作日志', 'asset:host-sftp-log:management:delete', 3, 20, 1, 1, 1, 0, NULL, NULL, NULL, '2024-03-05 15:31:17', '2024-03-05 15:57:30', '1', '1', 0);
-- 字典配置项
INSERT INTO `dict_key` VALUES (33, 'sftpOperatorType', 'STRING', '[]', 'SFTP 操作类型', '2024-03-05 16:49:54', '2024-03-05 16:49:54', '1', '1', 0);
-- 字典配置值
INSERT INTO `dict_value` VALUES (214, 28, 'hostConnectStatus', 'FORCE_OFFLINE', '强制下线', '{\"color\": \"rgb(var(--red-6))\"}', 40, '2024-03-04 12:51:13', '2024-03-04 12:51:13', '1', '1', 0);
INSERT INTO `dict_value` VALUES (215, 1, 'operatorLogModule', 'asset:host-connect-log', '主机连接日志', '{}', 2060, '2024-03-04 13:43:33', '2024-03-04 13:43:33', '1', '1', 0);
INSERT INTO `dict_value` VALUES (216, 2, 'operatorLogType', 'host-connect-log:delete', '删除记录', '{}', 10, '2024-03-04 13:44:34', '2024-03-04 13:44:34', '1', '1', 0);
INSERT INTO `dict_value` VALUES (217, 2, 'operatorLogType', 'host-connect-log:clear', '清空记录', '{}', 20, '2024-03-04 13:45:07', '2024-03-04 14:22:08', '1', '1', 0);
INSERT INTO `dict_value` VALUES (218, 2, 'operatorLogType', 'host-connect-log:force-offline', '强制下线', '{}', 30, '2024-03-04 13:45:36', '2024-03-04 13:45:36', '1', '1', 0);
INSERT INTO `dict_value` VALUES (219, 1, 'operatorLogModule', 'infra:operator-log', '操作日志', '{}', 1060, '2024-03-04 16:32:11', '2024-03-04 16:32:11', '1', '1', 0);
INSERT INTO `dict_value` VALUES (220, 2, 'operatorLogType', 'operator-log:delete', '删除操作日志', '{}', 10, '2024-03-04 16:33:11', '2024-03-04 16:33:44', '1', '1', 0);
INSERT INTO `dict_value` VALUES (221, 2, 'operatorLogType', 'operator-log:clear', '清空操作日志', '{}', 20, '2024-03-04 16:33:31', '2024-03-04 16:33:31', '1', '1', 0);
INSERT INTO `dict_value` VALUES (222, 2, 'operatorLogType', 'host-terminal:delete-sftp-log', '删除SFTP操作日志', '{}', 15, '2024-03-05 15:28:00', '2024-03-05 17:40:47', '1', '1', 0);
INSERT INTO `dict_value` VALUES (223, 33, 'sftpOperatorType', 'host-terminal:sftp-mkdir', '创建文件夹', '{}', 10, '2024-03-05 16:50:17', '2024-03-05 16:50:17', '1', '1', 0);
INSERT INTO `dict_value` VALUES (224, 33, 'sftpOperatorType', 'host-terminal:sftp-touch', '创建文件', '{}', 20, '2024-03-05 16:50:27', '2024-03-05 16:50:27', '1', '1', 0);
INSERT INTO `dict_value` VALUES (225, 33, 'sftpOperatorType', 'host-terminal:sftp-move', '移动文件', '{}', 30, '2024-03-05 16:50:41', '2024-03-05 16:50:41', '1', '1', 0);
INSERT INTO `dict_value` VALUES (226, 33, 'sftpOperatorType', 'host-terminal:sftp-remove', '删除文件', '{}', 40, '2024-03-05 16:50:53', '2024-03-05 16:50:53', '1', '1', 0);
INSERT INTO `dict_value` VALUES (227, 33, 'sftpOperatorType', 'host-terminal:sftp-truncate', '截断文件', '{}', 50, '2024-03-05 16:51:04', '2024-03-05 16:51:04', '1', '1', 0);
INSERT INTO `dict_value` VALUES (228, 33, 'sftpOperatorType', 'host-terminal:sftp-chmod', '文件提权', '{}', 60, '2024-03-05 16:51:15', '2024-03-05 16:51:15', '1', '1', 0);
INSERT INTO `dict_value` VALUES (229, 33, 'sftpOperatorType', 'host-terminal:sftp-set-content', '修改文件内容', '{}', 70, '2024-03-05 16:51:30', '2024-03-05 16:51:48', '1', '1', 0);
INSERT INTO `dict_value` VALUES (230, 33, 'sftpOperatorType', 'host-terminal:sftp-upload', '上传文件', '{}', 80, '2024-03-05 16:52:06', '2024-03-05 16:52:06', '1', '1', 0);
INSERT INTO `dict_value` VALUES (231, 33, 'sftpOperatorType', 'host-terminal:sftp-download', '下载文件', '{}', 90, '2024-03-05 16:52:18', '2024-03-05 16:52:18', '1', '1', 0);
```
## v1.0.0
> sql 脚本
```sql
INSERT INTO `dict_key` VALUES (32, 'terminalTabColor', 'COLOR', '[]', '终端标签页颜色', '2024-03-01 15:01:44', '2024-03-01 15:01:44', '1', '1', 0);
INSERT INTO `dict_value` VALUES (203, 32, 'terminalTabColor', 'rgb(var(--red-6))', '红色', '{}', 10, '2024-03-01 15:07:41', '2024-03-01 15:07:41', '1', '1', 0);
INSERT INTO `dict_value` VALUES (204, 32, 'terminalTabColor', 'rgb(var(--orange-6))', '橙色', '{}', 20, '2024-03-01 15:07:55', '2024-03-01 15:07:55', '1', '1', 0);
INSERT INTO `dict_value` VALUES (205, 32, 'terminalTabColor', 'rgb(var(--yellow-6))', '黄色', '{}', 30, '2024-03-01 15:08:13', '2024-03-01 15:08:13', '1', '1', 0);
INSERT INTO `dict_value` VALUES (206, 32, 'terminalTabColor', 'rgb(var(--green-6))', '绿色', '{}', 40, '2024-03-01 15:08:23', '2024-03-01 15:08:23', '1', '1', 0);
INSERT INTO `dict_value` VALUES (207, 32, 'terminalTabColor', 'rgb(var(--cyan-6))', '青色', '{}', 50, '2024-03-01 15:08:46', '2024-03-01 15:08:46', '1', '1', 0);
INSERT INTO `dict_value` VALUES (208, 32, 'terminalTabColor', 'rgb(var(--blue-6))', '浅蓝', '{}', 60, '2024-03-01 15:11:01', '2024-03-01 15:11:01', '1', '1', 0);
INSERT INTO `dict_value` VALUES (209, 32, 'terminalTabColor', 'rgb(var(--arcoblue-6))', '蓝色', '{}', 70, '2024-03-01 15:11:11', '2024-03-01 15:11:11', '1', '1', 0);
INSERT INTO `dict_value` VALUES (210, 32, 'terminalTabColor', 'rgb(var(--purple-6))', '紫色', '{}', 80, '2024-03-01 15:11:20', '2024-03-01 15:11:20', '1', '1', 0);
INSERT INTO `dict_value` VALUES (211, 32, 'terminalTabColor', 'rgb(var(--pinkpurple-6))', '粉紫', '{}', 90, '2024-03-01 15:11:41', '2024-03-01 15:11:41', '1', '1', 0);
INSERT INTO `dict_value` VALUES (213, 32, 'terminalTabColor', 'rgb(var(--gray-6))', '灰色', '{}', 100, '2024-03-01 15:12:01', '2024-03-01 15:39:34', '1', '1', 0);
```

View File

@@ -1,3 +1,14 @@
### 连接日志
在主机终端页面打开的 `SSH` `SFTP` 连接都会记录下来, 这里默认只展示 `SSH` 连接记录, 可以展开条件进行修改。
在主机终端页面打开的 `SSH` `SFTP` 连接都会记录下来
* 详情: 查看连接详情
* 断开: 断开连接
* 删除: 删除连接记录
* 清理: 根据条件清理数据
### SFTP 操作日志
查看用户 SFTP 操作日志, 是从用户操作日志中过滤查询。
* 删除: 删除操作日志

View File

@@ -2,8 +2,12 @@
主机终端页面 支持 SSH, SFTP。
打开后默认会进入新建连接页面, 页面的主机数据是用户授权的资产数据。
鼠标移入列表内的主机上时, 右侧会出现 `打开 SSH` `打开 SFTP` `连接设置` `收藏` 的按钮。
点击 `连接设置` 后, 会弹出模态框, 可以自定义配置连接主机的 密码/秘钥/身份, 仅对自己生效, 不会修改全局配置, `秘钥` `身份` 数据是用户授权的资产数据。
鼠标移入列表内的主机上时, 右侧会出现 `打开 SSH` `打开 SFTP` `主机设置` `收藏` 的按钮。
> 主机设置
* SSH 配置: 可以自定义配置连接主机的 密码/秘钥/身份, 仅对自己生效, 不会修改全局配置, `秘钥` `身份` 数据是用户授权的资产数据
* 标签颜色: 自定义配置标签的颜色, 可以用来区分环境等
> 顶部状态栏

View File

@@ -25,3 +25,4 @@
记录用户在系统内的操作日志。
* 详情: 查看操作的参数以及留痕信息
* 清理: 根据条件清理数据

View File

@@ -39,6 +39,8 @@ orion-ops-pro/orion-ops-launch/src/main/resources/application-prod.yaml
cd orion-ops-pro
# 编译
mvn -U clean install -DskipTests
# 启动
com.orion.ops.launch.LaunchApplication
```
4. 修改前端配置

View File

@@ -17,7 +17,7 @@
可以在执行命令的第一行设置 `set -e`
作用是: 当执行出现意料之外的情况时, 立即退出
> ##### 5. 在调度任务、应用构建、应用发布 命令执行成功的依据是什么?
> ##### 5. 在调度任务、批量执行 命令执行成功的依据是什么?
是获取命令的 `exitcode` 判断是否为 `0` 如果非0则代表命令执行失败
同理, 在命令的最后一行设置 `exit 1` 结果将会是失败, 可以用此来中断后续流程

View File

@@ -14,7 +14,7 @@
<url>https://github.com/lijiahangmax/orion-ops-pro</url>
<properties>
<revision>1.0.0-beta.1</revision>
<revision>1.0.1</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>

View File

@@ -85,4 +85,8 @@ public interface ErrorMessage {
String FILE_ABSENT = "文件不存在";
String LOG_ABSENT = "日志不存在";
String ILLEGAL_STATUS = "当前状态不支持此操作";
}

View File

@@ -47,4 +47,12 @@ public interface FieldConst {
String MOD = "mod";
String COUNT = "count";
String LOCATION = "location";
String USER_AGENT = "userAgent";
String ERROR_MESSAGE = "errorMessage";
}

View File

@@ -12,7 +12,7 @@ public interface OrionOpsProConst {
/**
* 同 ${orion.version} 迭代时候需要手动更改
*/
String VERSION = "1.0.0-beta.1";
String VERSION = "1.0.1";
String GITHUB = "https://github.com/lijiahangmax/orion-ops-pro";

View File

@@ -14,6 +14,7 @@ import org.hibernate.validator.constraints.Range;
* @since 2023/7/12 23:14
*/
@Data
@Schema(description = "公共页码请求")
public class PageRequest implements IPageRequest {
@Range(min = 1, max = 10000, groups = Page.class)

View File

@@ -45,7 +45,9 @@
show-time
allow-clear />
#else
<a-input v-model="formModel.${field.propertyName}" placeholder="请输入${field.comment}" allow-clear />
<a-input v-model="formModel.${field.propertyName}"
placeholder="请输入${field.comment}"
allow-clear />
#end
#end
</a-form-item>

View File

@@ -35,7 +35,9 @@
placeholder="请选择${field.comment}"
show-time />
#else
<a-input v-model="formModel.${field.propertyName}" placeholder="请输入${field.comment}" allow-clear/>
<a-input v-model="formModel.${field.propertyName}"
placeholder="请输入${field.comment}"
allow-clear/>
#end
#end
</a-form-item>

View File

@@ -39,7 +39,9 @@
placeholder="请选择${field.comment}"
show-time />
#else
<a-input v-model="formModel.${field.propertyName}" placeholder="请输入${field.comment}" allow-clear />
<a-input v-model="formModel.${field.propertyName}"
placeholder="请输入${field.comment}"
allow-clear />
#end
#end
</a-form-item>

View File

@@ -2,10 +2,10 @@
<!-- 搜索 -->
<a-card class="general-card table-search-card">
<query-header :model="formModel"
label-align="left"
@submit="fetchTableData"
@reset="fetchTableData"
@keyup.enter="() => fetchTableData()">
label-align="left"
@submit="fetchTableData"
@reset="fetchTableData"
@keyup.enter="() => fetchTableData()">
#foreach($field in ${table.fields})
<!-- $field.comment -->
<a-form-item field="${field.propertyName}" label="${field.comment}" label-col-flex="50px">
@@ -27,7 +27,9 @@
show-time
allow-clear />
#else
<a-input v-model="formModel.${field.propertyName}" placeholder="请输入${field.comment}" allow-clear />
<a-input v-model="formModel.${field.propertyName}"
placeholder="请输入${field.comment}"
allow-clear />
#end
#end
</a-form-item>
@@ -48,8 +50,8 @@
<div class="table-right-bar-handle">
<a-space>
<!-- 新增 -->
<a-button type="primary"
v-permission="['${package.ModuleName}:${typeHyphen}:create']"
<a-button v-permission="['${package.ModuleName}:${typeHyphen}:create']"
type="primary"
@click="emits('openAdd')">
新增
<template #icon>
@@ -58,9 +60,9 @@
</a-button>
#if($vue.enableRowSelection)
<!-- 删除 -->
<a-popconfirm position="br"
<a-popconfirm :content="`确认删除选中的 ${selectedKeys.length} 条记录吗?`"
position="br"
type="warning"
:content="`确认删除选中的${selectedKeys.length}条记录吗?`"
@ok="deleteSelectRows">
<a-button v-permission="['${package.ModuleName}:${typeHyphen}:delete']"
type="secondary"
@@ -78,9 +80,7 @@
</template>
<!-- table -->
<a-table row-key="id"
class="table-wrapper-8"
ref="tableRef"
label-align="left"
:loading="loading"
:columns="columns"
#if($vue.enableRowSelection)
@@ -104,9 +104,9 @@
<template #handle="{ record }">
<div class="table-handle-wrapper">
<!-- 修改 -->
<a-button type="text"
<a-button v-permission="['${package.ModuleName}:${typeHyphen}:update']"
type="text"
size="mini"
v-permission="['${package.ModuleName}:${typeHyphen}:update']"
@click="emits('openUpdate', record)">
修改
</a-button>
@@ -184,7 +184,7 @@
setLoading(true);
// 调用删除接口
await batchDelete${vue.featureEntity}(selectedKeys.value);
Message.success(`成功删除${selectedKeys.value.length}条数据`);
Message.success(`成功删除 ${selectedKeys.value.length} 条数据`);
selectedKeys.value = [];
// 重新加载数据
fetchTableData();

View File

@@ -10,6 +10,7 @@ import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ScanOptions;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
/**
@@ -165,6 +166,18 @@ public class RedisUtils {
}
}
/**
* 设置过期时间
*
* @param key key
* @param timeout timeout
* @param unit unit
*/
public static void setExpire(String key, long timeout, TimeUnit unit) {
// 设置过期时间
redisTemplate.expire(key, timeout, unit);
}
public static void setRedisTemplate(RedisTemplate<String, String> redisTemplate) {
if (RedisUtils.redisTemplate != null) {
// unmodified

View File

@@ -84,6 +84,7 @@ public class WebSockets {
try {
Threads.sleep(delay);
session.sendMessage(new TextMessage(message));
log.info("消息重发成功");
Threads.sleep(delay);
} catch (Exception ex) {
throw Exceptions.ioRuntime(ex);

View File

@@ -0,0 +1,36 @@
{
"groups": [
{
"name": "app.authentication",
"type": "com.orion.ops.module.infra.config.AppAuthenticationConfig",
"sourceType": "com.orion.ops.module.infra.config.AppAuthenticationConfig"
}
],
"properties": [
{
"name": "app.authentication.allowMultiDevice",
"type": "java.lang.Boolean",
"description": "是否允许多端登录."
},
{
"name": "app.authentication.allowRefresh",
"type": "java.lang.Boolean",
"description": "是否允许凭证续签."
},
{
"name": "app.authentication.maxRefreshCount",
"type": "java.lang.Integer",
"description": "凭证续签最大次数."
},
{
"name": "app.authentication.loginFailedLockCount",
"type": "java.lang.Integer",
"description": "登录失败锁定次数."
},
{
"name": "app.authentication.loginFailedLockTime",
"type": "java.lang.Integer",
"description": "登录失败锁定时间 (分)."
}
]
}

View File

@@ -118,6 +118,21 @@ logging:
level:
com.orion.ops.launch.controller.BootstrapController: INFO
# 应用配置
app:
authentication:
# 是否允许多端登录
allow-multi-device: true
# 是否允许凭证续签
allow-refresh: true
# 凭证续签最大次数
max-refresh-count: 3
# 登录失败锁定次数
login-failed-lock-count: 5
# 登录失败锁定时间 (分)
login-failed-lock-time: 30
# orion framework config
orion:
# 版本
version: @revision@
@@ -151,7 +166,6 @@ orion:
asset:
group: "asset - 资产模块"
path: "asset"
logging:
# 全局日志打印
printer:

View File

@@ -1,22 +1,23 @@
package com.orion.ops.module.asset.controller;
import com.orion.lang.define.wrapper.DataGrid;
import com.orion.ops.framework.biz.operator.log.core.annotation.OperatorLog;
import com.orion.ops.framework.common.validator.group.Id;
import com.orion.ops.framework.common.validator.group.Page;
import com.orion.ops.framework.log.core.annotation.IgnoreLog;
import com.orion.ops.framework.log.core.enums.IgnoreLogMode;
import com.orion.ops.framework.web.core.annotation.RestWrapper;
import com.orion.ops.module.asset.define.operator.HostConnectLogOperatorType;
import com.orion.ops.module.asset.entity.request.host.HostConnectLogQueryRequest;
import com.orion.ops.module.asset.entity.vo.HostConnectLogVO;
import com.orion.ops.module.asset.service.HostConnectLogService;
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.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import java.util.List;
@@ -55,5 +56,36 @@ public class HostConnectLogController {
return hostConnectLogService.getLatestConnectHostId(request);
}
@OperatorLog(HostConnectLogOperatorType.DELETE)
@DeleteMapping("/delete")
@Operation(summary = "删除主机连接日志")
@Parameter(name = "idList", description = "idList", required = true)
@PreAuthorize("@ss.hasPermission('asset:host-connect-log:management:delete')")
public Integer deleteHostConnectLog(@RequestParam("idList") List<Long> idList) {
return hostConnectLogService.deleteHostConnectLog(idList);
}
@PostMapping("/query-count")
@Operation(summary = "查询主机连接日志数量")
public Long getHostConnectLogCount(@RequestBody HostConnectLogQueryRequest request) {
return hostConnectLogService.getHostConnectLogCount(request);
}
@OperatorLog(HostConnectLogOperatorType.CLEAR)
@PostMapping("/clear")
@Operation(summary = "清空主机连接日志")
@PreAuthorize("@ss.hasPermission('asset:host-connect-log:management:clear')")
public Integer clearHostConnectLog(@RequestBody HostConnectLogQueryRequest request) {
return hostConnectLogService.clearHostConnectLog(request);
}
@OperatorLog(HostConnectLogOperatorType.FORCE_OFFLINE)
@PutMapping("/force-offline")
@Operation(summary = "强制断开主机连接")
@PreAuthorize("@ss.hasPermission('asset:host-connect-log:management:force-offline')")
public Integer forceOffline(@Validated(Id.class) @RequestBody HostConnectLogQueryRequest request) {
return hostConnectLogService.forceOffline(request);
}
}

View File

@@ -0,0 +1,17 @@
### 分页查询 SFTP 操作日志
POST {{baseUrl}}/asset/host-sftp-log/query
Content-Type: application/json
Authorization: {{token}}
{
"page": 1,
"limit": 10
}
### 删除 SFTP 操作日志
DELETE {{baseUrl}}/asset/host-sftp-log/delete?idList=1,2,3
Authorization: {{token}}
###

View File

@@ -0,0 +1,61 @@
package com.orion.ops.module.asset.controller;
import com.orion.lang.define.wrapper.DataGrid;
import com.orion.ops.framework.biz.operator.log.core.annotation.OperatorLog;
import com.orion.ops.framework.common.validator.group.Page;
import com.orion.ops.framework.log.core.annotation.IgnoreLog;
import com.orion.ops.framework.log.core.enums.IgnoreLogMode;
import com.orion.ops.framework.web.core.annotation.RestWrapper;
import com.orion.ops.module.asset.define.operator.HostTerminalOperatorType;
import com.orion.ops.module.asset.entity.request.host.HostSftpLogQueryRequest;
import com.orion.ops.module.asset.entity.vo.HostSftpLogVO;
import com.orion.ops.module.asset.service.HostSftpLogService;
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.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import java.util.List;
/**
* SFTP 操作日志服务 api
*
* @author Jiahang Li
* @version 1.0.0
* @since 2023-12-26 22:09
*/
@Tag(name = "asset - SFTP 操作日志服务")
@Slf4j
@Validated
@RestWrapper
@RestController
@RequestMapping("/asset/host-sftp-log")
@SuppressWarnings({"ELValidationInJSP", "SpringElInspection"})
public class HostSftpLogController {
@Resource
private HostSftpLogService hostSftpLogService;
@IgnoreLog(IgnoreLogMode.RET)
@PostMapping("/query")
@Operation(summary = "分页查询 SFTP 操作日志")
@PreAuthorize("@ss.hasAnyPermission('infra:operator-log:query', 'asset:host-sftp-log:management:query')")
public DataGrid<HostSftpLogVO> getHostSftpLogPage(@Validated(Page.class) @RequestBody HostSftpLogQueryRequest request) {
return hostSftpLogService.getHostSftpLogPage(request);
}
@OperatorLog(HostTerminalOperatorType.DELETE_SFTP_LOG)
@DeleteMapping("/delete")
@Operation(summary = "删除 SFTP 操作日志")
@Parameter(name = "idList", description = "idList", required = true)
@PreAuthorize("@ss.hasAnyPermission('infra:operator-log:delete', 'asset:host-sftp-log:management:delete')")
public Integer deleteHostSftpLog(@RequestParam("idList") List<Long> idList) {
return hostSftpLogService.deleteHostSftpLog(idList);
}
}

View File

@@ -0,0 +1,24 @@
package com.orion.ops.module.asset.convert;
import com.orion.ops.module.asset.entity.vo.HostSftpLogVO;
import com.orion.ops.module.infra.entity.dto.operator.OperatorLogDTO;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.factory.Mappers;
/**
* SFTP 操作日志 内部对象转换器
*
* @author Jiahang Li
* @version 1.0.0
* @since 2023-12-26 22:09
*/
@Mapper
public interface HostSftpLogConvert {
HostSftpLogConvert MAPPER = Mappers.getMapper(HostSftpLogConvert.class);
@Mapping(target = "extra", ignore = true)
HostSftpLogVO to(OperatorLogDTO request);
}

View File

@@ -0,0 +1,34 @@
package com.orion.ops.module.asset.define.operator;
import com.orion.ops.framework.biz.operator.log.core.annotation.Module;
import com.orion.ops.framework.biz.operator.log.core.factory.InitializingOperatorTypes;
import com.orion.ops.framework.biz.operator.log.core.model.OperatorType;
import static com.orion.ops.framework.biz.operator.log.core.enums.OperatorRiskLevel.*;
/**
* 主机连接日志 操作日志类型
*
* @author Jiahang Li
* @version 1.0.0
* @since 2024/3/2 14:37
*/
@Module("asset:host-connect-log")
public class HostConnectLogOperatorType extends InitializingOperatorTypes {
public static final String DELETE = "host-connect-log:delete";
public static final String CLEAR = "host-connect-log:clear";
public static final String FORCE_OFFLINE = "host-connect-log:force-offline";
@Override
public OperatorType[] types() {
return new OperatorType[]{
new OperatorType(H, DELETE, "删除主机连接记录 <sb>${count}</sb> 条"),
new OperatorType(H, CLEAR, "清空主机连接记录 <sb>${count}</sb> 条"),
new OperatorType(M, FORCE_OFFLINE, "强制下线主机连接 <sb>${hostName}</sb>"),
};
}
}

View File

@@ -1,9 +1,12 @@
package com.orion.ops.module.asset.define.operator;
import com.orion.lang.utils.collect.Lists;
import com.orion.ops.framework.biz.operator.log.core.annotation.Module;
import com.orion.ops.framework.biz.operator.log.core.factory.InitializingOperatorTypes;
import com.orion.ops.framework.biz.operator.log.core.model.OperatorType;
import java.util.List;
import static com.orion.ops.framework.biz.operator.log.core.enums.OperatorRiskLevel.*;
/**
@@ -18,6 +21,8 @@ public class HostTerminalOperatorType extends InitializingOperatorTypes {
public static final String CONNECT = "host-terminal:connect";
public static final String DELETE_SFTP_LOG = "host-terminal:delete-sftp-log";
public static final String SFTP_MKDIR = "host-terminal:sftp-mkdir";
public static final String SFTP_TOUCH = "host-terminal:sftp-touch";
@@ -36,10 +41,23 @@ public class HostTerminalOperatorType extends InitializingOperatorTypes {
public static final String SFTP_DOWNLOAD = "host-terminal:sftp-download";
public static final List<String> SFTP_TYPES = Lists.of(
SFTP_MKDIR,
SFTP_TOUCH,
SFTP_MOVE,
SFTP_REMOVE,
SFTP_TRUNCATE,
SFTP_CHMOD,
SFTP_SET_CONTENT,
SFTP_UPLOAD,
SFTP_DOWNLOAD
);
@Override
public OperatorType[] types() {
return new OperatorType[]{
new OperatorType(L, CONNECT, "连接主机 ${connectType} <sb>${hostName}</sb>"),
new OperatorType(H, DELETE_SFTP_LOG, "删除 SFTP 操作日志 <sb>${count}</sb> 条"),
new OperatorType(L, SFTP_MKDIR, "创建文件夹 ${hostName} <sb>${path}</sb>"),
new OperatorType(L, SFTP_TOUCH, "创建文件 ${hostName} <sb>${path}</sb>"),
new OperatorType(M, SFTP_MOVE, "移动文件 ${hostName} <sb>${path}</sb> 至 <sb>${target}</sb>"),

View File

@@ -0,0 +1,53 @@
package com.orion.ops.module.asset.entity.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* 主机连接日志推展信息对象
*
* @author Jiahang Li
* @version 1.0.0
* @since 2024-3-12 23:31
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Schema(name = "HostConnectLogExtraDTO", description = "主机连接日志推展信息对象")
public class HostConnectLogExtraDTO {
@Schema(description = "hostId")
private Long hostId;
@Schema(description = "主机名称")
private String hostName;
@Schema(description = "连接类型")
private String connectType;
@Schema(description = "traceId")
private String traceId;
@Schema(description = "channelId")
private String channelId;
@Schema(description = "sessionId")
private String sessionId;
@Schema(description = "请求地址")
private String address;
@Schema(description = "请求位置")
private String location;
@Schema(description = "ua")
private String userAgent;
@Schema(description = "错误信息")
private String errorMessage;
}

View File

@@ -23,6 +23,9 @@ import java.util.Date;
@Schema(name = "HostConnectLogQueryRequest", description = "主机连接日志 查询请求对象")
public class HostConnectLogQueryRequest extends PageRequest {
@Schema(description = "id")
private Long id;
@Schema(description = "用户id")
private Long userId;

View File

@@ -0,0 +1,43 @@
package com.orion.ops.module.asset.entity.request.host;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.orion.ops.framework.common.entity.PageRequest;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.*;
import javax.validation.constraints.Size;
import java.util.Date;
/**
* SFTP 操作日志 查询请求对象
*
* @author Jiahang Li
* @version 1.0.0
* @since 2024-3-4 22:59
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@EqualsAndHashCode(callSuper = true)
@Schema(name = "HostSftpLogQueryRequest", description = "SFTP 操作日志 查询请求对象")
public class HostSftpLogQueryRequest extends PageRequest {
@Schema(description = "用户id")
private Long userId;
@Schema(description = "hostId")
private Long hostId;
@Size(max = 64)
@Schema(description = "操作类型")
private String type;
@Schema(description = "操作结果 0失败 1成功")
private Integer result;
@Schema(description = "开始时间-区间")
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private Date[] startTimeRange;
}

View File

@@ -1,5 +1,6 @@
package com.orion.ops.module.asset.entity.vo;
import com.orion.ops.module.asset.entity.dto.HostConnectLogExtraDTO;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Builder;
@@ -46,9 +47,6 @@ public class HostConnectLogVO implements Serializable {
@Schema(description = "类型")
private String type;
@Schema(description = "token")
private String token;
@Schema(description = "状态")
private String status;
@@ -59,18 +57,6 @@ public class HostConnectLogVO implements Serializable {
private Date endTime;
@Schema(description = "额外信息")
private String extraInfo;
@Schema(description = "创建时间")
private Date createTime;
@Schema(description = "修改时间")
private Date updateTime;
@Schema(description = "创建人")
private String creator;
@Schema(description = "修改人")
private String updater;
private HostConnectLogExtraDTO extra;
}

View File

@@ -0,0 +1,71 @@
package com.orion.ops.module.asset.entity.vo;
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.Map;
/**
* SFTP 操作日志 实体对象
*
* @author Jiahang Li
* @version 1.0.0
* @since 2023-10-10 17:08
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Schema(name = "HostSftpLogVO", description = "SFTP 操作日志 实体对象")
public class HostSftpLogVO implements Serializable {
private static final long serialVersionUID = 1L;
@Schema(description = "id")
private Long id;
@Schema(description = "用户id")
private Long userId;
@Schema(description = "用户名")
private String username;
@Schema(description = "主机id")
private Long hostId;
@Schema(description = "主机名称")
private String hostName;
@Schema(description = "主机地址")
private String hostAddress;
@Schema(description = "操作文件")
private String[] paths;
@Schema(description = "请求ip")
private String address;
@Schema(description = "请求地址")
private String location;
@Schema(description = "userAgent")
private String userAgent;
@Schema(description = "操作类型")
private String type;
@Schema(description = "参数")
private Map<String, Object> extra;
@Schema(description = "操作结果 0失败 1成功")
private Integer result;
@Schema(description = "开始时间")
private Date startTime;
}

View File

@@ -64,4 +64,7 @@ public class HostVO implements Serializable {
@Schema(description = "别名")
private String alias;
@Schema(description = "颜色")
private String color;
}

View File

@@ -24,6 +24,11 @@ public enum HostConnectStatusEnum {
*/
FAILED,
/**
* 强制下线
*/
FORCE_OFFLINE,
;
public static HostConnectStatusEnum of(String type) {

View File

@@ -3,7 +3,9 @@ package com.orion.ops.module.asset.enums;
import com.orion.ops.framework.common.handler.data.GenericsDataDefinition;
import com.orion.ops.framework.common.handler.data.model.GenericsDataModel;
import com.orion.ops.framework.common.handler.data.strategy.MapDataStrategy;
import com.orion.ops.module.asset.handler.host.extra.model.HostColorExtraModel;
import com.orion.ops.module.asset.handler.host.extra.model.HostSshExtraModel;
import com.orion.ops.module.asset.handler.host.extra.strategy.HostColorExtraStrategy;
import com.orion.ops.module.asset.handler.host.extra.strategy.HostSshExtraStrategy;
import lombok.AllArgsConstructor;
import lombok.Getter;
@@ -24,6 +26,11 @@ public enum HostExtraItemEnum implements GenericsDataDefinition {
*/
SSH("ssh", HostSshExtraModel.class, HostSshExtraStrategy.class),
/**
* 颜色额外配置
*/
COLOR("color", HostColorExtraModel.class, HostColorExtraStrategy.class),
;
private final String item;

View File

@@ -0,0 +1,27 @@
package com.orion.ops.module.asset.handler.host.extra.model;
import com.orion.ops.framework.common.handler.data.model.GenericsDataModel;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* 主机拓展信息 - color 模型
*
* @author Jiahang Li
* @version 1.0.0
* @since 2024/2/29 23:16
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Schema(name = "HostExtraSshModel", description = "主机拓展信息 - color 模型")
public class HostColorExtraModel implements GenericsDataModel {
@Schema(description = "颜色")
private String color;
}

View File

@@ -0,0 +1,37 @@
package com.orion.ops.module.asset.handler.host.extra.strategy;
import com.orion.ops.framework.common.handler.data.strategy.MapDataStrategy;
import com.orion.ops.module.asset.handler.host.extra.model.HostColorExtraModel;
import org.springframework.stereotype.Component;
/**
* 主机拓展信息 - 颜色 模型处理策略
*
* @author Jiahang Li
* @version 1.0.0
* @since 2024/2/29 23:16
*/
@Component
public class HostColorExtraStrategy implements MapDataStrategy<HostColorExtraModel> {
@Override
public HostColorExtraModel getDefault() {
return HostColorExtraModel.builder()
// 默认透明
.color("")
.build();
}
@Override
public void updateFill(HostColorExtraModel beforeModel, HostColorExtraModel afterModel) {
}
@Override
public void preValid(HostColorExtraModel model) {
}
@Override
public void valid(HostColorExtraModel model) {
}
}

View File

@@ -28,7 +28,7 @@ public enum OutputTypeEnum {
/**
* 关闭连接
*/
CLOSE("cl", "${type}|${sessionId}|${msg}"),
CLOSE("cl", "${type}|${sessionId}|${forceClose}|${msg}"),
/**
* pong

View File

@@ -2,7 +2,6 @@ package com.orion.ops.module.asset.handler.host.terminal.handler;
import com.orion.lang.utils.collect.Maps;
import com.orion.ops.framework.biz.operator.log.core.utils.OperatorLogs;
import com.orion.ops.framework.common.constant.Const;
import com.orion.ops.framework.common.enums.BooleanBit;
import com.orion.ops.module.asset.define.operator.HostTerminalOperatorType;
import com.orion.ops.module.asset.handler.host.terminal.enums.OutputTypeEnum;
@@ -54,7 +53,7 @@ public class SftpRemoveHandler extends AbstractTerminalHandler<SftpBaseRequest>
.build());
// 保存操作日志
Map<String, Object> extra = Maps.newMap();
extra.put(OperatorLogs.PATH, String.join(Const.COMMA, paths));
extra.put(OperatorLogs.PATH, payload.getPath());
this.saveOperatorLog(payload, channel,
extra, HostTerminalOperatorType.SFTP_REMOVE,
startTime, ex);

View File

@@ -173,7 +173,7 @@ public class TerminalCheckHandler extends AbstractTerminalHandler<TerminalCheckR
String username = WebSockets.getAttr(channel, ExtraFieldConst.USERNAME);
// 额外参数
Map<String, Object> extra = Maps.newMap();
extra.put(OperatorLogs.ID, hostId);
extra.put(OperatorLogs.HOST_ID, hostId);
extra.put(OperatorLogs.HOST_NAME, hostName);
extra.put(OperatorLogs.CONNECT_TYPE, connectType.name());
extra.put(OperatorLogs.CHANNEL_ID, channel.getId());
@@ -194,6 +194,13 @@ public class TerminalCheckHandler extends AbstractTerminalHandler<TerminalCheckR
.token(sessionId)
.extra(extra)
.build();
// 填充其他信息
extra.put(OperatorLogs.TRACE_ID, logModel.getTraceId());
extra.put(OperatorLogs.ADDRESS, logModel.getAddress());
extra.put(OperatorLogs.LOCATION, logModel.getLocation());
extra.put(OperatorLogs.USER_AGENT, logModel.getUserAgent());
extra.put(OperatorLogs.ERROR_MESSAGE, logModel.getErrorMessage());
// 记录连接日志
hostConnectLogService.create(connectType, connectLog);
}

View File

@@ -5,9 +5,11 @@ import com.orion.lang.exception.ConnectionRuntimeException;
import com.orion.lang.exception.TimeoutException;
import com.orion.lang.exception.argument.InvalidArgumentException;
import com.orion.lang.utils.Exceptions;
import com.orion.lang.utils.collect.Maps;
import com.orion.lang.utils.io.Streams;
import com.orion.net.host.SessionStore;
import com.orion.ops.framework.common.constant.ErrorMessage;
import com.orion.ops.framework.common.constant.ExtraFieldConst;
import com.orion.ops.framework.common.enums.BooleanBit;
import com.orion.ops.framework.websocket.core.utils.WebSockets;
import com.orion.ops.module.asset.entity.dto.HostTerminalConnectDTO;
@@ -28,6 +30,7 @@ import org.springframework.stereotype.Component;
import org.springframework.web.socket.WebSocketSession;
import javax.annotation.Resource;
import java.util.Map;
/**
* 连接主机处理器
@@ -74,7 +77,9 @@ public class TerminalConnectHandler extends AbstractTerminalHandler<TerminalConn
} catch (Exception e) {
ex = e;
// 修改连接状态为失败
hostConnectLogService.updateStatusByToken(sessionId, HostConnectStatusEnum.FAILED);
Map<String, Object> extra = Maps.newMap(4);
extra.put(ExtraFieldConst.ERROR_MESSAGE, this.getConnectErrorMessage(e));
hostConnectLogService.updateStatusByToken(sessionId, HostConnectStatusEnum.FAILED, extra);
}
// 返回连接状态
this.send(channel,

View File

@@ -23,6 +23,9 @@ import lombok.experimental.SuperBuilder;
@Schema(name = "TerminalCloseResponse", description = "主机连接关闭响应 实体对象")
public class TerminalCloseResponse extends TerminalBasePayload {
@Schema(description = "是否为强制关闭")
private Integer forceClose;
@Schema(description = "关闭信息")
private String msg;

View File

@@ -38,4 +38,9 @@ public interface ITerminalSession extends SafeCloseable {
*/
void keepAlive();
/**
* 强制下线
*/
void forceOffline();
}

View File

@@ -4,6 +4,7 @@ import com.orion.lang.utils.io.Streams;
import com.orion.net.host.SessionStore;
import com.orion.net.host.ssh.shell.ShellExecutor;
import com.orion.ops.framework.common.constant.Const;
import com.orion.ops.framework.common.enums.BooleanBit;
import com.orion.ops.framework.websocket.core.utils.WebSockets;
import com.orion.ops.module.asset.define.AssetThreadPools;
import com.orion.ops.module.asset.handler.host.terminal.constant.TerminalMessage;
@@ -53,7 +54,7 @@ public class SshSession extends TerminalSession implements ISshSession {
executor.size(cols, rows);
executor.terminalType(terminalType);
executor.streamHandler(this::streamHandler);
executor.callback(this::eofCallback);
executor.callback(this::close);
executor.connect();
// 开始监听输出
AssetThreadPools.TERMINAL_STDOUT.execute(executor);
@@ -122,20 +123,4 @@ public class SshSession extends TerminalSession implements ISshSession {
}
}
/**
* eof 回调
*/
private void eofCallback() {
log.info("terminal eof回调 {}, forClose: {}", sessionId, this.close);
// 发送关闭信息
TerminalCloseResponse resp = TerminalCloseResponse.builder()
.type(OutputTypeEnum.CLOSE.getType())
.sessionId(this.sessionId)
.msg(TerminalMessage.CLOSED_CONNECTION)
.build();
WebSockets.sendText(channel, OutputTypeEnum.CLOSE.format(resp));
// 需要调用关闭 - 可能是 logout 需要手动触发
this.close();
}
}

View File

@@ -1,7 +1,12 @@
package com.orion.ops.module.asset.handler.host.terminal.session;
import com.orion.ops.framework.common.enums.BooleanBit;
import com.orion.ops.framework.websocket.core.utils.WebSockets;
import com.orion.ops.module.asset.enums.HostConnectStatusEnum;
import com.orion.ops.module.asset.handler.host.terminal.constant.TerminalMessage;
import com.orion.ops.module.asset.handler.host.terminal.enums.OutputTypeEnum;
import com.orion.ops.module.asset.handler.host.terminal.model.TerminalConfig;
import com.orion.ops.module.asset.handler.host.terminal.model.response.TerminalCloseResponse;
import com.orion.ops.module.asset.service.HostConnectLogService;
import com.orion.spring.SpringHolder;
import lombok.Getter;
@@ -28,6 +33,8 @@ public abstract class TerminalSession implements ITerminalSession {
protected volatile boolean close;
protected volatile boolean forceOffline;
public TerminalSession(String sessionId, WebSocketSession channel, TerminalConfig config) {
this.sessionId = sessionId;
this.channel = channel;
@@ -39,11 +46,48 @@ public abstract class TerminalSession implements ITerminalSession {
*/
protected abstract void releaseResource();
/**
* 发送关闭消息
*/
protected void sendCloseMessage() {
log.info("TerminalSession close {}, forClose: {}, forceOffline: {}", sessionId, this.close, this.forceOffline);
// 发送关闭信息
TerminalCloseResponse resp = TerminalCloseResponse.builder()
.type(OutputTypeEnum.CLOSE.getType())
.sessionId(this.sessionId)
.forceClose(BooleanBit.of(this.forceOffline).getValue())
.msg(this.forceOffline ? TerminalMessage.FORCED_OFFLINE : TerminalMessage.CLOSED_CONNECTION)
.build();
WebSockets.sendText(channel, OutputTypeEnum.CLOSE.format(resp));
}
@Override
public void close() {
log.info("terminal close {}", sessionId);
// 检查并且关闭
if (this.checkAndClose()) {
// 修改状态
SpringHolder.getBean(HostConnectLogService.class)
.updateStatusByToken(sessionId, HostConnectStatusEnum.COMPLETE, null);
}
}
@Override
public void forceOffline() {
log.info("terminal forceOffline {}", sessionId);
this.forceOffline = true;
// 关闭
this.checkAndClose();
}
/**
* 检查并且关闭会话
*
* @return close
*/
private boolean checkAndClose() {
if (close) {
return;
return false;
}
this.close = true;
// 释放资源
@@ -52,8 +96,13 @@ public abstract class TerminalSession implements ITerminalSession {
} catch (Exception e) {
log.error("terminal release error {}", sessionId, e);
}
// 修改状态
SpringHolder.getBean(HostConnectLogService.class).updateStatusByToken(sessionId, HostConnectStatusEnum.COMPLETE);
// 发送关闭信息
try {
this.sendCloseMessage();
} catch (Exception e) {
log.error("terminal send close error {}", sessionId, e);
}
return true;
}
@Override

View File

@@ -8,6 +8,7 @@ import com.orion.ops.module.asset.enums.HostConnectStatusEnum;
import com.orion.ops.module.asset.enums.HostConnectTypeEnum;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Future;
/**
@@ -40,8 +41,10 @@ public interface HostConnectLogService {
*
* @param token token
* @param status status
* @param extra extra
* @return effect
*/
void updateStatusByToken(String token, HostConnectStatusEnum status);
Integer updateStatusByToken(String token, HostConnectStatusEnum status, Map<String, Object> extra);
/**
* 查询用户最近连接的主机
@@ -60,4 +63,36 @@ public interface HostConnectLogService {
*/
Future<List<Long>> getLatestConnectHostIdAsync(HostConnectTypeEnum type, Long userId);
/**
* 删除主机连接日志
*
* @param idList idList
* @return effect
*/
Integer deleteHostConnectLog(List<Long> idList);
/**
* 获取主机连接日志数量
*
* @param request request
* @return count
*/
Long getHostConnectLogCount(HostConnectLogQueryRequest request);
/**
* 清空主机连接日志
*
* @param request request
* @return effect
*/
Integer clearHostConnectLog(HostConnectLogQueryRequest request);
/**
* 强制断开主机连接
*
* @param request request
* @return effect
*/
Integer forceOffline(HostConnectLogQueryRequest request);
}

View File

@@ -0,0 +1,34 @@
package com.orion.ops.module.asset.service;
import com.orion.lang.define.wrapper.DataGrid;
import com.orion.ops.module.asset.entity.request.host.HostSftpLogQueryRequest;
import com.orion.ops.module.asset.entity.vo.HostSftpLogVO;
import java.util.List;
/**
* SFTP 操作日志 服务类
*
* @author Jiahang Li
* @version 1.0.0
* @since 2023-12-26 22:09
*/
public interface HostSftpLogService {
/**
* 分页查询 SFTP 操作日志
*
* @param request request
* @return rows
*/
DataGrid<HostSftpLogVO> getHostSftpLogPage(HostSftpLogQueryRequest request);
/**
* 删除 SFTP 操作日志
*
* @param idList idList
* @return effect
*/
Integer deleteHostSftpLog(List<Long> idList);
}

View File

@@ -1,6 +1,8 @@
package com.orion.ops.module.asset.service.impl;
import com.alibaba.fastjson.JSON;
import com.orion.lang.function.Functions;
import com.orion.lang.utils.Refs;
import com.orion.lang.utils.collect.Lists;
import com.orion.lang.utils.collect.Maps;
import com.orion.ops.framework.common.constant.Const;
@@ -10,8 +12,10 @@ import com.orion.ops.module.asset.convert.HostGroupConvert;
import com.orion.ops.module.asset.entity.request.asset.AssetAuthorizedDataQueryRequest;
import com.orion.ops.module.asset.entity.vo.*;
import com.orion.ops.module.asset.enums.HostConnectTypeEnum;
import com.orion.ops.module.asset.handler.host.extra.model.HostColorExtraModel;
import com.orion.ops.module.asset.service.*;
import com.orion.ops.module.infra.api.*;
import com.orion.ops.module.infra.constant.DataExtraItems;
import com.orion.ops.module.infra.entity.dto.data.DataGroupDTO;
import com.orion.ops.module.infra.entity.dto.tag.TagDTO;
import com.orion.ops.module.infra.enums.*;
@@ -67,7 +71,7 @@ public class AssetAuthorizedDataServiceImpl implements AssetAuthorizedDataServic
private TagRelApi tagRelApi;
@Resource
private DataAliasApi dataAliasApi;
private DataExtraApi dataExtraApi;
@Override
public List<Long> getAuthorizedDataRelId(DataPermissionTypeEnum type, AssetAuthorizedDataQueryRequest request) {
@@ -179,8 +183,10 @@ public class AssetAuthorizedDataServiceImpl implements AssetAuthorizedDataServic
Future<List<Long>> favoriteResult = favoriteApi.getFavoriteRelIdListAsync(FavoriteTypeEnum.HOST, userId);
// 查询最近连接的主机
Future<List<Long>> latestConnectHostIdList = hostConnectLogService.getLatestConnectHostIdAsync(HostConnectTypeEnum.SSH, userId);
// 查询数据别名
Future<Map<Long, String>> dataAliasResult = dataAliasApi.getDataAliasAsync(userId, DataExtraTypeEnum.HOST);
// 查询别名
Future<Map<Long, String>> dataAliasResult = dataExtraApi.getExtraItemValuesByCacheAsync(userId, DataExtraTypeEnum.HOST, DataExtraItems.ALIAS);
// 查询颜色
Future<Map<Long, String>> dataColorResult = dataExtraApi.getExtraItemValuesByCacheAsync(userId, DataExtraTypeEnum.HOST, DataExtraItems.COLOR);
// 查询分组
List<DataGroupDTO> dataGroup = dataGroupApi.getDataGroupList(DataGroupTypeEnum.HOST);
// 查询分组引用
@@ -207,7 +213,8 @@ public class AssetAuthorizedDataServiceImpl implements AssetAuthorizedDataServic
// 设置主机拓展信息
this.getAuthorizedHostExtra(wrapper.getHostList(),
favoriteResult.get(),
dataAliasResult.get());
dataAliasResult.get(),
dataColorResult.get());
// 设置最近连接的主机
wrapper.setLatestHosts(new LinkedHashSet<>(latestConnectHostIdList.get()));
return wrapper;
@@ -292,10 +299,12 @@ public class AssetAuthorizedDataServiceImpl implements AssetAuthorizedDataServic
* @param hosts hosts
* @param favorite favorite
* @param aliasMap aliasMap
* @param colorMap colorMap
*/
private void getAuthorizedHostExtra(List<HostVO> hosts,
List<Long> favorite,
Map<Long, String> aliasMap) {
Map<Long, String> aliasMap,
Map<Long, String> colorMap) {
if (Lists.isEmpty(hosts)) {
return;
}
@@ -313,7 +322,21 @@ public class AssetAuthorizedDataServiceImpl implements AssetAuthorizedDataServic
}
// 设置主机别名
if (!Maps.isEmpty(aliasMap)) {
hosts.forEach(s -> s.setAlias(aliasMap.get(s.getId())));
hosts.forEach(s -> {
String alias = aliasMap.get(s.getId());
if (alias != null) {
s.setAlias(Refs.unrefToString(alias));
}
});
}
// 设置主机颜色
if (!Maps.isEmpty(colorMap)) {
hosts.forEach(s -> {
HostColorExtraModel color = JSON.parseObject(colorMap.get(s.getId()), HostColorExtraModel.class);
if (color != null) {
s.setColor(color.getColor());
}
});
}
}

View File

@@ -5,16 +5,21 @@ import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.orion.lang.constant.Const;
import com.orion.lang.define.wrapper.DataGrid;
import com.orion.lang.utils.Arrays1;
import com.orion.ops.framework.mybatis.core.query.Conditions;
import com.orion.lang.utils.Valid;
import com.orion.ops.framework.biz.operator.log.core.utils.OperatorLogs;
import com.orion.ops.framework.common.constant.ErrorMessage;
import com.orion.ops.framework.security.core.utils.SecurityUtils;
import com.orion.ops.module.asset.convert.HostConnectLogConvert;
import com.orion.ops.module.asset.dao.HostConnectLogDAO;
import com.orion.ops.module.asset.entity.domain.HostConnectLogDO;
import com.orion.ops.module.asset.entity.dto.HostConnectLogExtraDTO;
import com.orion.ops.module.asset.entity.request.host.HostConnectLogCreateRequest;
import com.orion.ops.module.asset.entity.request.host.HostConnectLogQueryRequest;
import com.orion.ops.module.asset.entity.vo.HostConnectLogVO;
import com.orion.ops.module.asset.enums.HostConnectStatusEnum;
import com.orion.ops.module.asset.enums.HostConnectTypeEnum;
import com.orion.ops.module.asset.handler.host.terminal.manager.TerminalManager;
import com.orion.ops.module.asset.handler.host.terminal.session.ITerminalSession;
import com.orion.ops.module.asset.service.HostConnectLogService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Async;
@@ -23,6 +28,7 @@ import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Future;
@@ -40,6 +46,9 @@ public class HostConnectLogServiceImpl implements HostConnectLogService {
@Resource
private HostConnectLogDAO hostConnectLogDAO;
@Resource
private TerminalManager terminalManager;
@Override
public void create(HostConnectTypeEnum type, HostConnectLogCreateRequest request) {
HostConnectLogDO record = HostConnectLogConvert.MAPPER.to(request);
@@ -62,16 +71,54 @@ public class HostConnectLogServiceImpl implements HostConnectLogService {
// 查询
return hostConnectLogDAO.of(wrapper)
.page(request)
.dataGrid(HostConnectLogConvert.MAPPER::to);
.dataGrid(s -> {
HostConnectLogVO vo = HostConnectLogConvert.MAPPER.to(s);
vo.setExtra(JSON.parseObject(s.getExtraInfo(), HostConnectLogExtraDTO.class));
return vo;
});
}
@Override
public void updateStatusByToken(String token, HostConnectStatusEnum status) {
log.info("HostConnectLogService-updateStatusByToken token: {}, status: {}", token, status);
public Integer updateStatusByToken(String token, HostConnectStatusEnum status, Map<String, Object> partial) {
log.info("HostConnectLogService-updateStatusByToken start token: {}, status: {}", token, status);
// 查询
HostConnectLogDO record = hostConnectLogDAO.of()
.createWrapper()
.eq(HostConnectLogDO::getToken, token)
.orderByDesc(HostConnectLogDO::getId)
.then()
.getOne();
if (record == null) {
log.info("HostConnectLogService-updateStatusByToken no record token: {}", token);
return Const.N_0;
}
return this.updateStatus(record, status, partial);
}
/**
* 更新状态
*
* @param record record
* @param status status
* @param partial partial
* @return effect
*/
private int updateStatus(HostConnectLogDO record, HostConnectStatusEnum status, Map<String, Object> partial) {
// 更新
HostConnectLogDO update = new HostConnectLogDO();
update.setId(record.getId());
update.setStatus(status.name());
update.setEndTime(new Date());
hostConnectLogDAO.update(update, Conditions.eq(HostConnectLogDO::getToken, token));
if (partial != null) {
Map<String, Object> extra = JSON.parseObject(record.getExtraInfo());
if (extra == null) {
extra = partial;
} else {
extra.putAll(partial);
}
update.setExtraInfo(JSON.toJSONString(extra));
}
return hostConnectLogDAO.updateById(update);
}
@Override
@@ -86,6 +133,53 @@ public class HostConnectLogServiceImpl implements HostConnectLogService {
return CompletableFuture.completedFuture(hostIdList);
}
@Override
public Integer deleteHostConnectLog(List<Long> idList) {
log.info("HostConnectLogService.deleteHostConnectLog start {}", JSON.toJSONString(idList));
int effect = hostConnectLogDAO.deleteBatchIds(idList);
log.info("HostConnectLogService.deleteHostConnectLog finish {}", effect);
// 设置日志参数
OperatorLogs.add(OperatorLogs.COUNT, effect);
return effect;
}
@Override
public Long getHostConnectLogCount(HostConnectLogQueryRequest request) {
return hostConnectLogDAO.selectCount(this.buildQueryWrapper(request));
}
@Override
public Integer clearHostConnectLog(HostConnectLogQueryRequest request) {
log.info("HostConnectLogService.clearHostConnectLog start {}", JSON.toJSONString(request));
// 删除
LambdaQueryWrapper<HostConnectLogDO> wrapper = this.buildQueryWrapper(request);
int effect = hostConnectLogDAO.delete(wrapper);
log.info("HostConnectLogService.clearHostConnectLog finish {}", effect);
// 设置日志参数
OperatorLogs.add(OperatorLogs.COUNT, effect);
return effect;
}
@Override
public Integer forceOffline(HostConnectLogQueryRequest request) {
Long id = request.getId();
// 查询数据是否存在
HostConnectLogDO record = hostConnectLogDAO.selectById(id);
Valid.notNull(record, ErrorMessage.LOG_ABSENT);
Valid.eq(record.getStatus(), HostConnectStatusEnum.CONNECTING.name(), ErrorMessage.ILLEGAL_STATUS);
// 设置日志参数
OperatorLogs.add(OperatorLogs.HOST_NAME, record.getHostName());
// 获取会话
HostConnectLogExtraDTO extra = JSON.parseObject(record.getExtraInfo(), HostConnectLogExtraDTO.class);
ITerminalSession session = terminalManager.getSession(extra.getChannelId(), extra.getSessionId());
if (session != null) {
// 关闭会话
session.forceOffline();
}
// 更新状态
return this.updateStatus(record, HostConnectStatusEnum.FORCE_OFFLINE, null);
}
/**
* 构建查询 wrapper
*
@@ -94,6 +188,7 @@ public class HostConnectLogServiceImpl implements HostConnectLogService {
*/
private LambdaQueryWrapper<HostConnectLogDO> buildQueryWrapper(HostConnectLogQueryRequest request) {
return hostConnectLogDAO.wrapper()
.eq(HostConnectLogDO::getId, request.getId())
.eq(HostConnectLogDO::getUserId, request.getUserId())
.eq(HostConnectLogDO::getHostId, request.getHostId())
.like(HostConnectLogDO::getHostAddress, request.getHostAddress())

View File

@@ -1,6 +1,7 @@
package com.orion.ops.module.asset.service.impl;
import com.orion.lang.function.Functions;
import com.orion.lang.utils.Refs;
import com.orion.lang.utils.collect.Maps;
import com.orion.ops.framework.common.constant.ErrorMessage;
import com.orion.ops.framework.common.handler.data.model.GenericsDataModel;
@@ -12,9 +13,8 @@ import com.orion.ops.module.asset.entity.request.host.HostExtraQueryRequest;
import com.orion.ops.module.asset.entity.request.host.HostExtraUpdateRequest;
import com.orion.ops.module.asset.enums.HostExtraItemEnum;
import com.orion.ops.module.asset.service.HostExtraService;
import com.orion.ops.module.infra.api.DataAliasApi;
import com.orion.ops.module.infra.api.DataExtraApi;
import com.orion.ops.module.infra.entity.dto.data.DataAliasUpdateDTO;
import com.orion.ops.module.infra.constant.DataExtraItems;
import com.orion.ops.module.infra.entity.dto.data.DataExtraDTO;
import com.orion.ops.module.infra.entity.dto.data.DataExtraQueryDTO;
import com.orion.ops.module.infra.entity.dto.data.DataExtraSetDTO;
@@ -36,20 +36,18 @@ import java.util.stream.Collectors;
@Service
public class HostExtraServiceImpl implements HostExtraService {
@Resource
private DataAliasApi dataAliasApi;
@Resource
private DataExtraApi dataExtraApi;
@Override
public Integer updateHostAlias(HostAliasUpdateRequest request) {
DataAliasUpdateDTO update = DataAliasUpdateDTO.builder()
DataExtraSetDTO update = DataExtraSetDTO.builder()
.userId(SecurityUtils.getLoginUserId())
.item(DataExtraItems.ALIAS)
.relId(request.getId())
.alias(request.getName())
.value(Refs.json(request.getName()))
.build();
return dataAliasApi.updateDataAlias(update, DataExtraTypeEnum.HOST);
return dataExtraApi.setExtraItem(update, DataExtraTypeEnum.HOST);
}
@Override

View File

@@ -0,0 +1,107 @@
package com.orion.ops.module.asset.service.impl;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.orion.lang.define.wrapper.DataGrid;
import com.orion.lang.utils.Arrays1;
import com.orion.lang.utils.Strings;
import com.orion.ops.framework.biz.operator.log.core.utils.OperatorLogs;
import com.orion.ops.framework.common.constant.ExtraFieldConst;
import com.orion.ops.module.asset.convert.HostSftpLogConvert;
import com.orion.ops.module.asset.define.operator.HostTerminalOperatorType;
import com.orion.ops.module.asset.entity.request.host.HostSftpLogQueryRequest;
import com.orion.ops.module.asset.entity.vo.HostSftpLogVO;
import com.orion.ops.module.asset.service.HostSftpLogService;
import com.orion.ops.module.infra.api.OperatorLogApi;
import com.orion.ops.module.infra.entity.dto.operator.OperatorLogDTO;
import com.orion.ops.module.infra.entity.dto.operator.OperatorLogQueryDTO;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.util.List;
import java.util.stream.Collectors;
/**
* SFTP 操作日志 服务实现类
*
* @author Jiahang Li
* @version 1.0.0
* @since 2024/3/4 23:35
*/
@Slf4j
@Service
public class HostSftpLogServiceImpl implements HostSftpLogService {
@Resource
private OperatorLogApi operatorLogApi;
@Override
public DataGrid<HostSftpLogVO> getHostSftpLogPage(HostSftpLogQueryRequest request) {
// 查询
OperatorLogQueryDTO query = this.buildQueryInfo(request);
DataGrid<OperatorLogDTO> dataGrid = operatorLogApi.getOperatorLogPage(query);
// 转换
List<HostSftpLogVO> rows = dataGrid.stream()
.map(s -> {
JSONObject extra = JSON.parseObject(s.getExtra());
HostSftpLogVO vo = HostSftpLogConvert.MAPPER.to(s);
vo.setHostId(extra.getLong(ExtraFieldConst.HOST_ID));
vo.setHostName(extra.getString(ExtraFieldConst.HOST_NAME));
vo.setHostAddress(extra.getString(ExtraFieldConst.ADDRESS));
vo.setPaths(extra.getString(ExtraFieldConst.PATH).split("\\|"));
vo.setExtra(extra);
return vo;
}).collect(Collectors.toList());
// 返回
DataGrid<HostSftpLogVO> result = new DataGrid<>();
result.setRows(rows);
result.setPage(dataGrid.getPage());
result.setLimit(dataGrid.getLimit());
result.setSize(dataGrid.getSize());
result.setTotal(dataGrid.getTotal());
return result;
}
@Override
public Integer deleteHostSftpLog(List<Long> idList) {
log.info("HostSftpLogService.deleteSftpLog start {}", JSON.toJSONString(idList));
Integer effect = operatorLogApi.deleteOperatorLog(idList);
log.info("HostSftpLogService.deleteSftpLog finish {}", effect);
// 设置日志参数
OperatorLogs.add(OperatorLogs.COUNT, effect);
return effect;
}
/**
* 构建查询对象
*
* @param request request
* @return query
*/
private OperatorLogQueryDTO buildQueryInfo(HostSftpLogQueryRequest request) {
Long hostId = request.getHostId();
String type = request.getType();
// 构建参数
OperatorLogQueryDTO query = OperatorLogQueryDTO.builder()
.userId(request.getUserId())
.result(request.getResult())
.startTimeStart(Arrays1.getIfPresent(request.getStartTimeRange(), 0))
.startTimeEnd(Arrays1.getIfPresent(request.getStartTimeRange(), 1))
.build();
query.setPage(request.getPage());
query.setLimit(request.getLimit());
if (Strings.isBlank(type)) {
// 查询全部 SFTP 类型
query.setTypeList(HostTerminalOperatorType.SFTP_TYPES);
} else {
query.setType(type);
}
// 模糊查询
if (hostId != null) {
query.setExtra("\"hostId\": " + hostId + ",");
}
return query;
}
}

View File

@@ -1,55 +0,0 @@
package com.orion.ops.module.infra.api;
import com.orion.ops.module.infra.entity.dto.data.DataAliasUpdateDTO;
import com.orion.ops.module.infra.enums.DataExtraTypeEnum;
import java.util.Map;
import java.util.concurrent.Future;
/**
* 数据别名 对外服务类
*
* @author Jiahang Li
* @version 1.0.0
* @since 2023-12-18 17:37
*/
public interface DataAliasApi {
/**
* 更新数据别名
*
* @param dto dto
* @param type type
* @return effect
*/
Integer updateDataAlias(DataAliasUpdateDTO dto, DataExtraTypeEnum type);
/**
* 查询数据别名
*
* @param userId userId
* @param type type
* @param relId relId
* @return aliasName
*/
String getDataAlias(Long userId, DataExtraTypeEnum type, Long relId);
/**
* 查询数据别名
*
* @param userId userId
* @param type type
* @return relId:aliasName
*/
Map<Long, String> getDataAlias(Long userId, DataExtraTypeEnum type);
/**
* 异步查询数据别名
*
* @param userId userId
* @param type type
* @return relId:aliasName
*/
Future<Map<Long, String>> getDataAliasAsync(Long userId, DataExtraTypeEnum type);
}

View File

@@ -7,6 +7,7 @@ import com.orion.ops.module.infra.enums.DataExtraTypeEnum;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Future;
/**
* 数据拓展信息 对外服务类
@@ -69,6 +70,37 @@ public interface DataExtraApi {
*/
Map<Long, String> getExtraItemValues(DataExtraQueryDTO dto, DataExtraTypeEnum type);
/**
* 查询额外配置项 (查询缓存)
*
* @param userId userId
* @param type type
* @param item item
* @param relId relId
* @return value
*/
String getExtraItemValueByCache(Long userId, DataExtraTypeEnum type, String item, Long relId);
/**
* 查询额外配置项 (查询缓存)
*
* @param userId userId
* @param type type
* @param item item
* @return relId:value
*/
Map<Long, String> getExtraItemValuesByCache(Long userId, DataExtraTypeEnum type, String item);
/**
* 异步查询额外配置项 (查询缓存)
*
* @param userId userId
* @param type type
* @param item item
* @return value
*/
Future<Map<Long, String>> getExtraItemValuesByCacheAsync(Long userId, DataExtraTypeEnum type, String item);
/**
* 查询额外配置
*

View File

@@ -0,0 +1,34 @@
package com.orion.ops.module.infra.api;
import com.orion.lang.define.wrapper.DataGrid;
import com.orion.ops.module.infra.entity.dto.operator.OperatorLogDTO;
import com.orion.ops.module.infra.entity.dto.operator.OperatorLogQueryDTO;
import java.util.List;
/**
* 操作日志服务
*
* @author Jiahang Li
* @version 1.0.0
* @since 2024/3/4 23:11
*/
public interface OperatorLogApi {
/**
* 分页查询操作日志
*
* @param request request
* @return rows
*/
DataGrid<OperatorLogDTO> getOperatorLogPage(OperatorLogQueryDTO request);
/**
* 删除操作日志
*
* @param idList idList
* @return effect
*/
Integer deleteOperatorLog(List<Long> idList);
}

View File

@@ -11,4 +11,6 @@ public interface DataExtraItems {
String ALIAS = "alias";
String COLOR = "color";
}

View File

@@ -1,42 +0,0 @@
package com.orion.ops.module.infra.entity.dto.data;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
import java.io.Serializable;
/**
* 数据别名 更新请求业务对象
*
* @author Jiahang Li
* @version 1.0.0
* @since 2023-12-18 17:37
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Schema(name = "DataAliasUpdateDTO", description = "数据别名 创建请求业务对象")
public class DataAliasUpdateDTO implements Serializable {
private static final long serialVersionUID = 1L;
@NotNull
@Schema(description = "用户id")
private Long userId;
@NotNull
@Schema(description = "数据id")
private Long relId;
@NotNull
@Size(max = 32)
@Schema(description = "别名")
private String alias;
}

View File

@@ -0,0 +1,85 @@
package com.orion.ops.module.infra.entity.dto.operator;
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;
/**
* 操作日志 业务对象
*
* @author Jiahang Li
* @version 1.0.0
* @since 2023-10-10 17:08
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Schema(name = "OperatorLogDTO", description = "操作日志 业务对象")
public class OperatorLogDTO implements Serializable {
private static final long serialVersionUID = 1L;
@Schema(description = "id")
private Long id;
@Schema(description = "用户id")
private Long userId;
@Schema(description = "用户名")
private String username;
@Schema(description = "traceId")
private String traceId;
@Schema(description = "请求ip")
private String address;
@Schema(description = "请求地址")
private String location;
@Schema(description = "userAgent")
private String userAgent;
@Schema(description = "风险等级")
private String riskLevel;
@Schema(description = "模块")
private String module;
@Schema(description = "操作类型")
private String type;
@Schema(description = "日志")
private String logInfo;
@Schema(description = "参数")
private String extra;
@Schema(description = "操作结果 0失败 1成功")
private Integer result;
@Schema(description = "错误信息")
private String errorMessage;
@Schema(description = "返回值")
private String returnValue;
@Schema(description = "操作时间")
private Integer duration;
@Schema(description = "开始时间")
private Date startTime;
@Schema(description = "结束时间")
private Date endTime;
@Schema(description = "创建时间")
private Date createTime;
}

View File

@@ -0,0 +1,61 @@
package com.orion.ops.module.infra.entity.dto.operator;
import com.orion.ops.framework.common.entity.PageRequest;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.*;
import javax.validation.constraints.Size;
import java.util.Date;
import java.util.List;
/**
* 操作日志 查询对象
*
* @author Jiahang Li
* @version 1.0.0
* @since 2023-10-10 17:08
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@EqualsAndHashCode(callSuper = true)
@Schema(name = "OperatorLogQueryDTO", description = "操作日志 查询对象")
public class OperatorLogQueryDTO extends PageRequest {
private static final long serialVersionUID = 1L;
@Schema(description = "id")
private Long id;
@Schema(description = "用户id")
private Long userId;
@Size(max = 32)
@Schema(description = "模块")
private String module;
@Size(max = 1)
@Schema(description = "风险等级")
private String riskLevel;
@Size(max = 64)
@Schema(description = "操作类型")
private String type;
@Schema(description = "操作类型")
private List<String> typeList;
@Schema(description = "参数")
private String extra;
@Schema(description = "操作结果 0失败 1成功")
private Integer result;
@Schema(description = "开始时间区间 - 开始")
private Date startTimeStart;
@Schema(description = "开始时间区间 - 结束")
private Date startTimeEnd;
}

View File

@@ -1,63 +0,0 @@
package com.orion.ops.module.infra.api.impl;
import com.orion.ops.framework.common.utils.Valid;
import com.orion.ops.module.infra.api.DataAliasApi;
import com.orion.ops.module.infra.entity.dto.data.DataAliasUpdateDTO;
import com.orion.ops.module.infra.entity.request.data.DataAliasUpdateRequest;
import com.orion.ops.module.infra.enums.DataExtraTypeEnum;
import com.orion.ops.module.infra.service.DataAliasService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Future;
/**
* 数据别名 对外服务实现类
*
* @author Jiahang Li
* @version 1.0.0
* @since 2023-12-18 17:37
*/
@Slf4j
@Service
public class DataAliasApiImpl implements DataAliasApi {
@Resource
private DataAliasService dataAliasService;
@Override
public Integer updateDataAlias(DataAliasUpdateDTO dto, DataExtraTypeEnum type) {
Valid.valid(dto);
DataAliasUpdateRequest update = DataAliasUpdateRequest.builder()
.userId(dto.getUserId())
.type(type.name())
.relId(dto.getRelId())
.alias(dto.getAlias())
.build();
return dataAliasService.updateDataAlias(update);
}
@Override
public String getDataAlias(Long userId, DataExtraTypeEnum type, Long relId) {
Valid.allNotNull(userId, relId);
return dataAliasService.getDataAlias(userId, type.name(), relId);
}
@Override
public Map<Long, String> getDataAlias(Long userId, DataExtraTypeEnum type) {
Valid.notNull(userId);
return dataAliasService.getDataAlias(userId, type.name());
}
@Override
@Async("asyncExecutor")
public Future<Map<Long, String>> getDataAliasAsync(Long userId, DataExtraTypeEnum type) {
Valid.notNull(userId);
return CompletableFuture.completedFuture(dataAliasService.getDataAlias(userId, type.name()));
}
}

View File

@@ -18,6 +18,8 @@ import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Future;
import java.util.stream.Collectors;
/**
@@ -83,6 +85,23 @@ public class DataExtraApiImpl implements DataExtraApi {
return dataExtraService.getExtraItemValues(request);
}
@Override
public String getExtraItemValueByCache(Long userId, DataExtraTypeEnum type, String item, Long relId) {
Valid.allNotNull(userId, type, item, relId);
return dataExtraService.getExtraItemValueByCache(userId, type.name(), item, relId);
}
@Override
public Map<Long, String> getExtraItemValuesByCache(Long userId, DataExtraTypeEnum type, String item) {
Valid.allNotNull(userId, type, item);
return dataExtraService.getExtraItemValuesByCache(userId, type.name(), item);
}
@Override
public Future<Map<Long, String>> getExtraItemValuesByCacheAsync(Long userId, DataExtraTypeEnum type, String item) {
return CompletableFuture.completedFuture(this.getExtraItemValuesByCache(userId, type, item));
}
@Override
public DataExtraDTO getExtraItem(DataExtraQueryDTO dto, DataExtraTypeEnum type) {
Valid.allNotNull(dto.getUserId(), dto.getRelId(), dto.getItem());

View File

@@ -0,0 +1,67 @@
package com.orion.ops.module.infra.api.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.orion.lang.define.wrapper.DataGrid;
import com.orion.ops.framework.common.utils.Valid;
import com.orion.ops.module.infra.api.OperatorLogApi;
import com.orion.ops.module.infra.convert.OperatorLogProviderConvert;
import com.orion.ops.module.infra.dao.OperatorLogDAO;
import com.orion.ops.module.infra.entity.domain.OperatorLogDO;
import com.orion.ops.module.infra.entity.dto.operator.OperatorLogDTO;
import com.orion.ops.module.infra.entity.dto.operator.OperatorLogQueryDTO;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.util.List;
/**
* 操作日志服务实现
*
* @author Jiahang Li
* @version 1.0.0
* @since 2024/3/4 23:18
*/
@Slf4j
@Service
public class OperatorLogApiImpl implements OperatorLogApi {
@Resource
private OperatorLogDAO operatorLogDAO;
@Override
public DataGrid<OperatorLogDTO> getOperatorLogPage(OperatorLogQueryDTO request) {
Valid.valid(request);
return operatorLogDAO.of()
.page(request)
.wrapper(this.buildQueryWrapper(request))
.dataGrid(OperatorLogProviderConvert.MAPPER::to);
}
@Override
public Integer deleteOperatorLog(List<Long> idList) {
return operatorLogDAO.deleteBatchIds(idList);
}
/**
* 构建查询 wrapper
*
* @param request request
* @return wrapper
*/
private LambdaQueryWrapper<OperatorLogDO> buildQueryWrapper(OperatorLogQueryDTO request) {
return operatorLogDAO.wrapper()
.eq(OperatorLogDO::getId, request.getId())
.eq(OperatorLogDO::getUserId, request.getUserId())
.eq(OperatorLogDO::getRiskLevel, request.getRiskLevel())
.eq(OperatorLogDO::getModule, request.getModule())
.eq(OperatorLogDO::getType, request.getType())
.in(OperatorLogDO::getType, request.getTypeList())
.eq(OperatorLogDO::getResult, request.getResult())
.like(OperatorLogDO::getExtra, request.getExtra())
.ge(OperatorLogDO::getStartTime, request.getStartTimeStart())
.le(OperatorLogDO::getStartTime, request.getStartTimeEnd())
.orderByDesc(OperatorLogDO::getId);
}
}

View File

@@ -0,0 +1,44 @@
package com.orion.ops.module.infra.config;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
/**
* 应用认证配置
*
* @author Jiahang Li
* @version 1.0.0
* @since 2024/3/5 18:26
*/
@Data
@Component
@ConfigurationProperties("app.authentication")
public class AppAuthenticationConfig {
/**
* 是否允许多端登录
*/
private Boolean allowMultiDevice;
/**
* 是否允许凭证续签
*/
private Boolean allowRefresh;
/**
* 凭证续签最大次数
*/
private Integer maxRefreshCount;
/**
* 登录失败锁定次数
*/
private Integer loginFailedLockCount;
/**
* 登录失败锁定时间 (分)
*/
private Integer loginFailedLockTime;
}

View File

@@ -14,9 +14,4 @@ Authorization: {{token}}
"endTime": ""
}
### 查询登录日志
GET {{baseUrl}}/infra/operator-log/login-history?username=admin
Content-Type: application/json
Authorization: {{token}}
###

View File

@@ -1,16 +1,17 @@
package com.orion.ops.module.infra.controller;
import com.orion.lang.define.wrapper.DataGrid;
import com.orion.ops.framework.biz.operator.log.core.annotation.OperatorLog;
import com.orion.ops.framework.common.validator.group.Page;
import com.orion.ops.framework.log.core.annotation.IgnoreLog;
import com.orion.ops.framework.log.core.enums.IgnoreLogMode;
import com.orion.ops.framework.security.core.utils.SecurityUtils;
import com.orion.ops.framework.web.core.annotation.RestWrapper;
import com.orion.ops.module.infra.define.operator.OperatorLogOperatorType;
import com.orion.ops.module.infra.entity.request.operator.OperatorLogQueryRequest;
import com.orion.ops.module.infra.entity.vo.LoginHistoryVO;
import com.orion.ops.module.infra.entity.vo.OperatorLogVO;
import com.orion.ops.module.infra.service.OperatorLogService;
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.springframework.security.access.prepost.PreAuthorize;
@@ -47,12 +48,27 @@ public class OperatorLogController {
return operatorLogService.getOperatorLogPage(request);
}
@IgnoreLog(IgnoreLogMode.RET)
@GetMapping("/login-history")
@Operation(summary = "查询用户登录日志")
@PreAuthorize("@ss.hasPermission('infra:system-user:login-history')")
public List<LoginHistoryVO> getLoginHistory(@RequestParam("username") String username) {
return operatorLogService.getLoginHistory(username);
@OperatorLog(OperatorLogOperatorType.DELETE)
@DeleteMapping("/delete")
@Operation(summary = "删除操作日志")
@Parameter(name = "idList", description = "idList", required = true)
@PreAuthorize("@ss.hasPermission('infra:operator-log:delete')")
public Integer deleteOperatorLog(@RequestParam("idList") List<Long> idList) {
return operatorLogService.deleteOperatorLog(idList);
}
@PostMapping("/query-count")
@Operation(summary = "查询操作日志数量")
public Long getOperatorLogCount(@RequestBody OperatorLogQueryRequest request) {
return operatorLogService.getOperatorLogCount(request);
}
@OperatorLog(OperatorLogOperatorType.CLEAR)
@PostMapping("/clear")
@Operation(summary = "清空操作日志")
@PreAuthorize("@ss.hasPermission('infra:operator-log:clear')")
public Integer clearOperatorLog(@RequestBody OperatorLogQueryRequest request) {
return operatorLogService.clearOperatorLog(request);
}
}

View File

@@ -67,4 +67,10 @@ DELETE {{baseUrl}}/infra/system-user/delete?id=1
Authorization: {{token}}
### 查询登录日志
GET {{baseUrl}}/infra/system-user/login-history?username=admin
Content-Type: application/json
Authorization: {{token}}
###

View File

@@ -10,8 +10,10 @@ import com.orion.ops.framework.log.core.enums.IgnoreLogMode;
import com.orion.ops.framework.web.core.annotation.RestWrapper;
import com.orion.ops.module.infra.define.operator.SystemUserOperatorType;
import com.orion.ops.module.infra.entity.request.user.*;
import com.orion.ops.module.infra.entity.vo.LoginHistoryVO;
import com.orion.ops.module.infra.entity.vo.SystemUserVO;
import com.orion.ops.module.infra.entity.vo.UserSessionVO;
import com.orion.ops.module.infra.service.OperatorLogService;
import com.orion.ops.module.infra.service.SystemUserManagementService;
import com.orion.ops.module.infra.service.SystemUserRoleService;
import com.orion.ops.module.infra.service.SystemUserService;
@@ -51,6 +53,9 @@ public class SystemUserController {
@Resource
private SystemUserManagementService systemUserManagementService;
@Resource
private OperatorLogService operatorLogService;
@OperatorLog(SystemUserOperatorType.CREATE)
@PostMapping("/create")
@Operation(summary = "创建用户")
@@ -159,5 +164,12 @@ public class SystemUserController {
return HttpWrapper.ok();
}
}
@IgnoreLog(IgnoreLogMode.RET)
@GetMapping("/login-history")
@Operation(summary = "查询用户登录日志")
@PreAuthorize("@ss.hasPermission('infra:system-user:login-history')")
public List<LoginHistoryVO> getLoginHistory(@RequestParam("username") String username) {
return operatorLogService.getLoginHistory(username);
}
}

View File

@@ -0,0 +1,22 @@
package com.orion.ops.module.infra.convert;
import com.orion.ops.module.infra.entity.domain.OperatorLogDO;
import com.orion.ops.module.infra.entity.dto.operator.OperatorLogDTO;
import org.mapstruct.Mapper;
import org.mapstruct.factory.Mappers;
/**
* 操作日志 对外对象转换器
*
* @author Jiahang Li
* @version 1.0.0
* @since 2023-10-10 17:08
*/
@Mapper
public interface OperatorLogProviderConvert {
OperatorLogProviderConvert MAPPER = Mappers.getMapper(OperatorLogProviderConvert.class);
OperatorLogDTO to(OperatorLogDO domain);
}

View File

@@ -15,9 +15,9 @@ import java.util.concurrent.TimeUnit;
*/
public interface DataExtraCacheKeyDefine {
CacheKeyDefine DATA_ALIAS = new CacheKeyBuilder()
.key("data:alias:{}:{}")
.desc("数据别名 ${userId} ${type}")
CacheKeyDefine DATA_EXTRA = new CacheKeyBuilder()
.key("data:extra:{}:{}:{}")
.desc("数据拓展信息 ${userId} ${type} ${item}")
.type(String.class)
.struct(RedisCacheStruct.HASH)
.timeout(1, TimeUnit.DAYS)

View File

@@ -38,7 +38,6 @@ public interface UserCacheKeyDefine {
.desc("用户登录失败次数 ${username}")
.type(Integer.class)
.struct(RedisCacheStruct.STRING)
.timeout(3, TimeUnit.DAYS)
.build();
CacheKeyDefine LOGIN_TOKEN = new CacheKeyBuilder()

View File

@@ -0,0 +1,31 @@
package com.orion.ops.module.infra.define.operator;
import com.orion.ops.framework.biz.operator.log.core.annotation.Module;
import com.orion.ops.framework.biz.operator.log.core.factory.InitializingOperatorTypes;
import com.orion.ops.framework.biz.operator.log.core.model.OperatorType;
import static com.orion.ops.framework.biz.operator.log.core.enums.OperatorRiskLevel.H;
/**
* 操作日志 操作日志类型
*
* @author Jiahang Li
* @version 1.0.0
* @since 2024-3-4 16:20
*/
@Module("infra:operator-log")
public class OperatorLogOperatorType extends InitializingOperatorTypes {
public static final String DELETE = "operator-log:delete";
public static final String CLEAR = "operator-log:clear";
@Override
public OperatorType[] types() {
return new OperatorType[]{
new OperatorType(H, DELETE, "删除操作日志 <sb>${count}</sb> 条"),
new OperatorType(H, CLEAR, "清空操作日志 <sb>${count}</sb> 条"),
};
}
}

View File

@@ -1,47 +0,0 @@
package com.orion.ops.module.infra.entity.request.data;
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 2023-12-18 17:37
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Schema(name = "DataAliasUpdateRequest", description = "数据别名 更新请求对象")
public class DataAliasUpdateRequest implements Serializable {
private static final long serialVersionUID = 1L;
@NotNull
@Schema(description = "用户id")
private Long userId;
@NotNull
@Schema(description = "数据id")
private Long relId;
@NotBlank
@Size(max = 32)
@Schema(description = "数据类型")
private String type;
@Size(max = 32)
@Schema(description = "别名")
private String alias;
}

View File

@@ -16,16 +16,6 @@ import javax.servlet.http.HttpServletRequest;
*/
public interface AuthenticationService {
// TODO 配置化
// 允许多端登录
boolean allowMultiDevice = true;
// 允许凭证续签
boolean allowRefresh = true;
// 凭证续签最大次数
int maxRefreshCount = 3;
// 失败锁定次数
int maxFailedLoginCount = 5;
/**
* 登录
*

View File

@@ -1,43 +0,0 @@
package com.orion.ops.module.infra.service;
import com.orion.ops.module.infra.entity.request.data.DataAliasUpdateRequest;
import java.util.Map;
/**
* 数据别名 服务类
*
* @author Jiahang Li
* @version 1.0.0
* @since 2023-12-18 17:37
*/
public interface DataAliasService {
/**
* 更新数据别名
*
* @param request request
* @return effect
*/
Integer updateDataAlias(DataAliasUpdateRequest request);
/**
* 查询数据别名
*
* @param userId userId
* @param type type
* @param relId relId
* @return aliasName
*/
String getDataAlias(Long userId, String type, Long relId);
/**
* 查询数据别名
*
* @param userId userId
* @param type type
* @return relId:aliasName
*/
Map<Long, String> getDataAlias(Long userId, String type);
}

View File

@@ -64,6 +64,27 @@ public interface DataExtraService {
*/
Map<Long, String> getExtraItemValues(DataExtraQueryRequest request);
/**
* 查询额外配置项 (查询缓存)
*
* @param userId userId
* @param type type
* @param item item
* @param relId relId
* @return value
*/
String getExtraItemValueByCache(Long userId, String type, String item, Long relId);
/**
* 查询额外配置项 (查询缓存)
*
* @param userId userId
* @param type type
* @param item item
* @return relId:value
*/
Map<Long, String> getExtraItemValuesByCache(Long userId, String type, String item);
/**
* 查询额外配置
*

View File

@@ -32,6 +32,30 @@ public interface OperatorLogService {
*/
DataGrid<OperatorLogVO> getOperatorLogPage(OperatorLogQueryRequest request);
/**
* 删除操作日志
*
* @param idList idList
* @return effect
*/
Integer deleteOperatorLog(List<Long> idList);
/**
* 查询操作日志数量
*
* @param request request
* @return count
*/
Long getOperatorLogCount(OperatorLogQueryRequest request);
/**
* 清空操作日志
*
* @param request request
* @return effect
*/
Integer clearOperatorLog(OperatorLogQueryRequest request);
/**
* 查询用户登录日志
*

View File

@@ -16,6 +16,7 @@ import com.orion.ops.framework.common.utils.Valid;
import com.orion.ops.framework.redis.core.utils.RedisStrings;
import com.orion.ops.framework.redis.core.utils.RedisUtils;
import com.orion.ops.framework.security.core.utils.SecurityUtils;
import com.orion.ops.module.infra.config.AppAuthenticationConfig;
import com.orion.ops.module.infra.convert.SystemUserConvert;
import com.orion.ops.module.infra.dao.SystemUserDAO;
import com.orion.ops.module.infra.dao.SystemUserRoleDAO;
@@ -39,6 +40,7 @@ import java.util.Date;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
/**
@@ -51,6 +53,9 @@ import java.util.stream.Collectors;
@Service
public class AuthenticationServiceImpl implements AuthenticationService {
@Resource
private AppAuthenticationConfig appAuthenticationConfig;
@Resource
private SystemUserDAO systemUserDAO;
@@ -95,7 +100,7 @@ public class AuthenticationServiceImpl implements AuthenticationService {
String userAgent = Servlets.getUserAgent(servletRequest);
long current = System.currentTimeMillis();
// 不允许多端登录
if (!allowMultiDevice) {
if (!appAuthenticationConfig.getAllowMultiDevice()) {
// 无效化其他缓存
this.invalidOtherDeviceToken(user.getId(), current, remoteAddr, location, userAgent);
}
@@ -157,7 +162,7 @@ public class AuthenticationServiceImpl implements AuthenticationService {
return JSON.parseObject(loginCache, LoginTokenDTO.class);
}
// loginToken 不存在 需要查询 refreshToken
if (!allowRefresh) {
if (!appAuthenticationConfig.getAllowRefresh()) {
return null;
}
String refreshKey = UserCacheKeyDefine.LOGIN_REFRESH.format(pair.getKey(), pair.getValue());
@@ -172,7 +177,7 @@ public class AuthenticationServiceImpl implements AuthenticationService {
refresh.setRefreshCount(refreshCount);
// 设置登录缓存
RedisStrings.setJson(loginKey, UserCacheKeyDefine.LOGIN_TOKEN, refresh);
if (refreshCount < maxRefreshCount) {
if (refreshCount < appAuthenticationConfig.getMaxRefreshCount()) {
// 小于续签最大次数 则再次设置 refreshToken
RedisStrings.setJson(refreshKey, UserCacheKeyDefine.LOGIN_REFRESH, refresh);
} else {
@@ -214,7 +219,8 @@ public class AuthenticationServiceImpl implements AuthenticationService {
// 检查登录失败次数
String failedCountKey = UserCacheKeyDefine.LOGIN_FAILED_COUNT.format(request.getUsername());
String failedCount = redisTemplate.opsForValue().get(failedCountKey);
if (failedCount != null && Integer.parseInt(failedCount) >= maxFailedLoginCount) {
if (failedCount != null
&& Integer.parseInt(failedCount) >= appAuthenticationConfig.getLoginFailedLockCount()) {
throw Exceptions.argument(ErrorMessage.MAX_LOGIN_FAILED);
}
}
@@ -235,23 +241,23 @@ public class AuthenticationServiceImpl implements AuthenticationService {
// 刷新登录失败缓存
String failedCountKey = UserCacheKeyDefine.LOGIN_FAILED_COUNT.format(request.getUsername());
Long failedLoginCount = redisTemplate.opsForValue().increment(failedCountKey);
RedisUtils.setExpire(failedCountKey, UserCacheKeyDefine.LOGIN_FAILED_COUNT);
// 锁定用户
if (failedLoginCount >= maxFailedLoginCount) {
// 更新用户表
SystemUserDO updateUser = new SystemUserDO();
updateUser.setId(user.getId());
updateUser.setStatus(UserStatusEnum.LOCKED.getStatus());
systemUserDAO.updateById(updateUser);
// 修改缓存状态
String userInfoKey = UserCacheKeyDefine.USER_INFO.format(user.getId());
String userInfoCache = redisTemplate.opsForValue().get(userInfoKey);
if (userInfoCache != null) {
LoginUser loginUser = JSON.parseObject(userInfoCache, LoginUser.class);
loginUser.setStatus(UserStatusEnum.LOCKED.getStatus());
RedisStrings.setJson(userInfoKey, UserCacheKeyDefine.USER_INFO, loginUser);
}
}
RedisUtils.setExpire(failedCountKey, appAuthenticationConfig.getLoginFailedLockTime(), TimeUnit.MINUTES);
// // 锁定用户
// if (failedLoginCount >= appAuthenticationConfig.getLoginFailedLockCount()) {
// // 更新用户表
// SystemUserDO updateUser = new SystemUserDO();
// updateUser.setId(user.getId());
// updateUser.setStatus(UserStatusEnum.LOCKED.getStatus());
// systemUserDAO.updateById(updateUser);
// // 修改缓存状态
// String userInfoKey = UserCacheKeyDefine.USER_INFO.format(user.getId());
// String userInfoCache = redisTemplate.opsForValue().get(userInfoKey);
// if (userInfoCache != null) {
// LoginUser loginUser = JSON.parseObject(userInfoCache, LoginUser.class);
// loginUser.setStatus(UserStatusEnum.LOCKED.getStatus());
// RedisStrings.setJson(userInfoKey, UserCacheKeyDefine.USER_INFO, loginUser);
// }
// }
return false;
}
@@ -337,7 +343,7 @@ public class AuthenticationServiceImpl implements AuthenticationService {
}
}
// 删除续签信息
if (allowRefresh) {
if (appAuthenticationConfig.getAllowRefresh()) {
RedisUtils.scanKeysDelete(UserCacheKeyDefine.LOGIN_REFRESH.format(id, "*"));
}
}
@@ -365,7 +371,7 @@ public class AuthenticationServiceImpl implements AuthenticationService {
.build();
RedisStrings.setJson(loginKey, UserCacheKeyDefine.LOGIN_TOKEN, loginValue);
// 生成 refreshToken
if (allowRefresh) {
if (appAuthenticationConfig.getAllowRefresh()) {
String refreshKey = UserCacheKeyDefine.LOGIN_REFRESH.format(id, loginTime);
RedisStrings.setJson(refreshKey, UserCacheKeyDefine.LOGIN_REFRESH, loginValue);
}

View File

@@ -1,82 +0,0 @@
package com.orion.ops.module.infra.service.impl;
import com.orion.lang.utils.Refs;
import com.orion.lang.utils.collect.Maps;
import com.orion.ops.framework.redis.core.utils.RedisMaps;
import com.orion.ops.framework.redis.core.utils.barrier.CacheBarriers;
import com.orion.ops.module.infra.constant.DataExtraItems;
import com.orion.ops.module.infra.define.cache.DataExtraCacheKeyDefine;
import com.orion.ops.module.infra.entity.request.data.DataAliasUpdateRequest;
import com.orion.ops.module.infra.entity.request.data.DataExtraQueryRequest;
import com.orion.ops.module.infra.entity.request.data.DataExtraSetRequest;
import com.orion.ops.module.infra.service.DataAliasService;
import com.orion.ops.module.infra.service.DataExtraService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.util.Map;
import java.util.function.Function;
/**
* 数据别名 服务实现类
*
* @author Jiahang Li
* @version 1.0.0
* @since 2023-12-18 17:37
*/
@Slf4j
@Service
public class DataAliasServiceImpl implements DataAliasService {
@Resource
private DataExtraService dataExtraService;
@Override
public Integer updateDataAlias(DataAliasUpdateRequest request) {
Long userId = request.getUserId();
String type = request.getType();
// 更新
DataExtraSetRequest update = new DataExtraSetRequest();
update.setUserId(userId);
update.setRelId(request.getRelId());
update.setType(type);
update.setItem(DataExtraItems.ALIAS);
update.setValue(Refs.json(request.getAlias()));
Integer effect = dataExtraService.setExtraItem(update);
// 删除缓存
RedisMaps.delete(DataExtraCacheKeyDefine.DATA_ALIAS.format(userId, type));
return effect;
}
@Override
public String getDataAlias(Long userId, String type, Long relId) {
return this.getDataAlias(userId, type).get(relId);
}
@Override
public Map<Long, String> getDataAlias(Long userId, String type) {
// 查询缓存
String key = DataExtraCacheKeyDefine.DATA_ALIAS.format(userId, type);
Map<String, String> entities = RedisMaps.entities(key);
if (Maps.isEmpty(entities)) {
// 查询数据库
DataExtraQueryRequest request = DataExtraQueryRequest.builder()
.userId(userId)
.type(type)
.item(DataExtraItems.ALIAS)
.build();
Map<Long, String> extras = dataExtraService.getExtraItemValues(request);
entities = Maps.map(extras, String::valueOf, Refs::unrefToString);
// 设置屏障 防止穿透
CacheBarriers.MAP.check(entities);
// 设置缓存
RedisMaps.putAll(key, DataExtraCacheKeyDefine.DATA_ALIAS, entities);
}
// 删除屏障
CacheBarriers.MAP.remove(entities);
// 转换
return Maps.map(entities, Long::valueOf, Function.identity());
}
}

View File

@@ -2,9 +2,14 @@ package com.orion.ops.module.infra.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.orion.lang.function.Functions;
import com.orion.lang.utils.collect.Lists;
import com.orion.lang.utils.collect.Maps;
import com.orion.ops.framework.common.constant.Const;
import com.orion.ops.framework.mybatis.core.query.ThenLambdaWrapper;
import com.orion.ops.framework.redis.core.utils.RedisMaps;
import com.orion.ops.framework.redis.core.utils.barrier.CacheBarriers;
import com.orion.ops.module.infra.dao.DataExtraDAO;
import com.orion.ops.module.infra.define.cache.DataExtraCacheKeyDefine;
import com.orion.ops.module.infra.entity.domain.DataExtraDO;
import com.orion.ops.module.infra.entity.request.data.DataExtraQueryRequest;
import com.orion.ops.module.infra.entity.request.data.DataExtraSetRequest;
@@ -15,6 +20,7 @@ import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;
/**
@@ -62,15 +68,29 @@ public class DataExtraServiceImpl implements DataExtraService {
insert.setItem(request.getItem());
insert.setValue(request.getValue());
dataExtraDAO.insert(insert);
// 删除缓存
RedisMaps.delete(DataExtraCacheKeyDefine.DATA_EXTRA.format(request.getUserId(), request.getType(), request.getItem()));
return insert.getId();
}
@Override
public Integer updateExtraValue(Long id, String value) {
// 查询数据
DataExtraDO data = this.getCacheSelectWrapper()
.eq(DataExtraDO::getId, id)
.then()
.get();
if (data == null) {
return Const.N_0;
}
DataExtraDO update = new DataExtraDO();
update.setId(id);
update.setValue(value);
return dataExtraDAO.updateById(update);
// 更新
int effect = dataExtraDAO.updateById(update);
// 删除缓存
RedisMaps.delete(DataExtraCacheKeyDefine.DATA_EXTRA.format(data.getUserId(), data.getType(), data.getItem()));
return effect;
}
@Override
@@ -78,8 +98,16 @@ public class DataExtraServiceImpl implements DataExtraService {
if (Maps.isEmpty(map)) {
return;
}
// 查询数据
List<DataExtraDO> list = this.getCacheSelectWrapper()
.in(DataExtraDO::getId, map.keySet())
.then()
.list();
if (list.isEmpty()) {
return;
}
// 批量更新
List<DataExtraDO> list = map.entrySet()
List<DataExtraDO> update = map.entrySet()
.stream()
.map(s -> {
DataExtraDO extra = new DataExtraDO();
@@ -87,7 +115,9 @@ public class DataExtraServiceImpl implements DataExtraService {
extra.setValue(s.getValue());
return extra;
}).collect(Collectors.toList());
dataExtraDAO.updateBatch(list);
dataExtraDAO.updateBatch(update);
// 删除缓存
this.deleteCache(list);
}
@Override
@@ -110,6 +140,36 @@ public class DataExtraServiceImpl implements DataExtraService {
);
}
@Override
public String getExtraItemValueByCache(Long userId, String type, String item, Long relId) {
return this.getExtraItemValuesByCache(userId, type, item).get(relId);
}
@Override
public Map<Long, String> getExtraItemValuesByCache(Long userId, String type, String item) {
// 查询缓存
String key = DataExtraCacheKeyDefine.DATA_EXTRA.format(userId, type, item);
Map<String, String> entities = RedisMaps.entities(key);
if (Maps.isEmpty(entities)) {
// 查询数据库
DataExtraQueryRequest request = DataExtraQueryRequest.builder()
.userId(userId)
.type(type)
.item(item)
.build();
Map<Long, String> extras = this.getExtraItemValues(request);
entities = Maps.map(extras, String::valueOf, String::valueOf);
// 设置屏障 防止穿透
CacheBarriers.MAP.check(entities);
// 设置缓存
RedisMaps.putAll(key, DataExtraCacheKeyDefine.DATA_EXTRA, entities);
}
// 删除屏障
CacheBarriers.MAP.remove(entities);
// 转换
return Maps.map(entities, Long::valueOf, Function.identity());
}
@Override
public DataExtraDO getExtraItem(DataExtraQueryRequest request) {
return dataExtraDAO.of()
@@ -126,12 +186,62 @@ public class DataExtraServiceImpl implements DataExtraService {
@Override
public Integer deleteByUserId(Long userId) {
return dataExtraDAO.deleteByUserId(userId);
List<DataExtraDO> list = this.getCacheSelectWrapper()
.eq(DataExtraDO::getUserId, userId)
.then()
.list();
if (list.isEmpty()) {
return Const.N_0;
}
// 删除数据
int effect = dataExtraDAO.deleteByUserId(userId);
// 删除缓存
this.deleteCache(list);
return effect;
}
@Override
public Integer deleteByRelId(String type, Long relId) {
return dataExtraDAO.deleteByRelId(type, relId);
List<DataExtraDO> list = this.getCacheSelectWrapper()
.eq(DataExtraDO::getType, type)
.eq(DataExtraDO::getRelId, relId)
.then()
.list();
if (list.isEmpty()) {
return Const.N_0;
}
// 删除数据
int effect = dataExtraDAO.deleteByRelId(type, relId);
// 删除缓存
this.deleteCache(list);
return effect;
}
/**
* 获取查询缓存参数 wrapper 不查询 longtext 加速查询
*
* @return wrapper
*/
private ThenLambdaWrapper<DataExtraDO> getCacheSelectWrapper() {
return dataExtraDAO.of()
.createWrapper()
.select(DataExtraDO::getId, DataExtraDO::getUserId, DataExtraDO::getType, DataExtraDO::getItem);
}
/**
* 删除缓存
*
* @param list list
*/
private void deleteCache(List<DataExtraDO> list) {
if (Lists.isEmpty(list)) {
return;
}
List<String> keys = list.stream()
.map(s -> DataExtraCacheKeyDefine.DATA_EXTRA.format(s.getUserId(), s.getType(), s.getItem()))
.distinct()
.collect(Collectors.toList());
RedisMaps.delete(keys);
}
/**

View File

@@ -1,9 +1,11 @@
package com.orion.ops.module.infra.service.impl;
import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.orion.lang.define.wrapper.DataGrid;
import com.orion.lang.utils.Arrays1;
import com.orion.ops.framework.biz.operator.log.core.model.OperatorLogModel;
import com.orion.ops.framework.biz.operator.log.core.utils.OperatorLogs;
import com.orion.ops.framework.common.constant.Const;
import com.orion.ops.module.infra.convert.OperatorLogConvert;
import com.orion.ops.module.infra.dao.OperatorLogDAO;
@@ -51,6 +53,33 @@ public class OperatorLogServiceImpl implements OperatorLogService {
.dataGrid(OperatorLogConvert.MAPPER::to);
}
@Override
public Integer deleteOperatorLog(List<Long> idList) {
log.info("OperatorLogService.deleteOperatorLog start {}", JSON.toJSONString(idList));
int effect = operatorLogDAO.deleteBatchIds(idList);
log.info("OperatorLogService.deleteOperatorLog finish {}", effect);
// 设置日志参数
OperatorLogs.add(OperatorLogs.COUNT, effect);
return effect;
}
@Override
public Long getOperatorLogCount(OperatorLogQueryRequest request) {
return operatorLogDAO.selectCount(this.buildQueryWrapper(request));
}
@Override
public Integer clearOperatorLog(OperatorLogQueryRequest request) {
log.info("OperatorLogService.clearOperatorLog start {}", JSON.toJSONString(request));
// 删除
LambdaQueryWrapper<OperatorLogDO> wrapper = this.buildQueryWrapper(request);
int effect = operatorLogDAO.delete(wrapper);
log.info("OperatorLogService.clearOperatorLog finish {}", effect);
// 设置日志参数
OperatorLogs.add(OperatorLogs.COUNT, effect);
return effect;
}
@Override
public List<LoginHistoryVO> getLoginHistory(String username) {
// 条件

View File

@@ -14,6 +14,7 @@ import com.orion.ops.framework.redis.core.utils.RedisStrings;
import com.orion.ops.framework.redis.core.utils.RedisUtils;
import com.orion.ops.framework.redis.core.utils.barrier.CacheBarriers;
import com.orion.ops.framework.security.core.utils.SecurityUtils;
import com.orion.ops.module.infra.config.AppAuthenticationConfig;
import com.orion.ops.module.infra.convert.SystemUserConvert;
import com.orion.ops.module.infra.dao.OperatorLogDAO;
import com.orion.ops.module.infra.dao.SystemRoleDAO;
@@ -49,6 +50,9 @@ import java.util.stream.Collectors;
@Service
public class SystemUserServiceImpl implements SystemUserService {
@Resource
private AppAuthenticationConfig appAuthenticationConfig;
@Resource
private SystemUserDAO systemUserDAO;
@@ -274,7 +278,7 @@ public class SystemUserServiceImpl implements SystemUserService {
// 删除登录缓存
RedisUtils.scanKeysDelete(UserCacheKeyDefine.LOGIN_TOKEN.format(id, "*"));
// 删除续签信息
if (AuthenticationService.allowRefresh) {
if (appAuthenticationConfig.getAllowRefresh()) {
RedisUtils.scanKeysDelete(UserCacheKeyDefine.LOGIN_REFRESH.format(id, "*"));
}
}

View File

@@ -1,4 +1,4 @@
VITE_API_BASE_URL= 'http://127.0.0.1:9200/orion/api'
VITE_WS_BASE_URL= 'ws://127.0.0.1:9200/orion/keep-alive'
VITE_APP_VERSION= '1.0.0-beta.1'
VITE_APP_VERSION= '1.0.1'
VITE_SFTP_PREVIEW_MB= 2

View File

@@ -1,4 +1,4 @@
VITE_API_BASE_URL= '/orion/api'
VITE_WS_BASE_URL= '/orion/keep-alive'
VITE_APP_VERSION= '1.0.0-beta.1'
VITE_APP_VERSION= '1.0.1'
VITE_SFTP_PREVIEW_MB= 2

View File

@@ -1,7 +1,7 @@
{
"name": "orion-ops-pro-ui",
"description": "Orion Ops Pro for Vue",
"version": "1.0.0-beta.1",
"version": "1.0.1",
"private": true,
"author": "Jiahang Li",
"license": "Apache 2.0",

View File

@@ -1,11 +1,13 @@
import type { DataGrid, Pagination } from '@/types/global';
import type { TableData } from '@arco-design/web-vue/es/table/interface';
import axios from 'axios';
import qs from 'query-string';
/**
* 主机连接日志查询请求
*/
export interface HostConnectLogQueryRequest extends Pagination {
id?: number;
userId?: number;
hostId?: number;
hostAddress?: string;
@@ -21,6 +23,7 @@ export interface HostConnectLogQueryRequest extends Pagination {
export interface HostConnectLogQueryResponse extends TableData {
id: number;
userId: number;
username: number;
hostId: number;
hostName: string;
hostAddress: string;
@@ -29,11 +32,20 @@ export interface HostConnectLogQueryResponse extends TableData {
status: string;
startTime: number;
endTime: number;
extraInfo: string;
createTime: number;
updateTime: number;
creator: string;
updater: string;
extra: HostConnectLogExtra;
}
/**
* 主机连接日志拓展对象
*/
export interface HostConnectLogExtra {
traceId: string;
channelId: string;
sessionId: string;
address: string;
location: string;
userAgent: string;
errorMessage: string;
}
/**
@@ -52,3 +64,36 @@ export function getLatestConnectHostId(type: string, limit: number) {
limit
});
}
/**
* 删除主机连接日志
*/
export function deleteHostConnectLog(idList: Array<number>) {
return axios.delete('/asset/host-connect-log/delete', {
params: { idList },
paramsSerializer: params => {
return qs.stringify(params, { arrayFormat: 'comma' });
}
});
}
/**
* 查询主机连接日志数量
*/
export function getHostConnectLogCount(request: HostConnectLogQueryRequest) {
return axios.post<number>('/asset/host-connect-log/query-count', request);
}
/**
* 清空主机连接日志
*/
export function clearHostConnectLog(request: HostConnectLogQueryRequest) {
return axios.post<number>('/asset/host-connect-log/clear', request);
}
/**
* 强制断开主机连接
*/
export function hostForceOffline(request: HostConnectLogQueryRequest) {
return axios.put('/asset/host-connect-log/force-offline', request);
}

View File

@@ -0,0 +1,62 @@
import type { DataGrid, Pagination } from '@/types/global';
import type { TableData } from '@arco-design/web-vue/es/table/interface';
import axios from 'axios';
import qs from 'query-string';
/**
* SFTP 操作日志 查询请求
*/
export interface HostSftpLogQueryRequest extends Pagination {
userId?: number;
hostId?: number;
type?: string;
result?: number;
startTimeRange?: string[];
}
/**
* SFTP 操作日志 查询响应
*/
export interface HostSftpLogQueryResponse extends TableData {
id: number;
userId: number;
username: number;
hostId: number;
hostName: string;
hostAddress: string;
address: string;
location: string;
userAgent: string;
paths: string[];
type: string;
result: string;
startTime: number;
extra: HostSftpLogExtra;
}
/**
* SFTP 操作日志 拓展对象
*/
export interface HostSftpLogExtra {
mod: number;
target: string;
}
/**
* 分页查询 SFTP 操作日志
*/
export function getHostSftpLogPage(request: HostSftpLogQueryRequest) {
return axios.post<DataGrid<HostSftpLogQueryResponse>>('/asset/host-sftp-log/query', request);
}
/**
* 删除 SFTP 操作日志
*/
export function deleteHostSftpLog(idList: Array<number>) {
return axios.delete('/asset/host-sftp-log/delete', {
params: { idList },
paramsSerializer: params => {
return qs.stringify(params, { arrayFormat: 'comma' });
}
});
}

View File

@@ -36,7 +36,7 @@ export interface HostQueryRequest extends Pagination {
/**
* 主机查询响应
*/
export interface HostQueryResponse extends TableData {
export interface HostQueryResponse extends TableData, HostQueryResponseExtra {
id: number;
name: string;
code: string;
@@ -47,9 +47,15 @@ export interface HostQueryResponse extends TableData {
updater: string;
favorite: boolean;
alias: string;
color: string;
tags: Array<{ id: number, name: string }>;
groupIdList: Array<number>;
}
/**
* 主机操作拓展
*/
export interface HostQueryResponseExtra {
editable: boolean;
loading: boolean;
modCount: number;

View File

@@ -1,6 +1,6 @@
import type { DataGrid } from '@/types/global';
import type { LoginHistoryQueryResponse, OperatorLogQueryRequest, OperatorLogQueryResponse } from './operator-log';
import type { UserQueryResponse, UserSessionOfflineRequest, UserSessionQueryResponse, UserUpdateRequest } from './user';
import type { OperatorLogQueryRequest, OperatorLogQueryResponse } from './operator-log';
import type { LoginHistoryQueryResponse, UserQueryResponse, UserSessionOfflineRequest, UserSessionQueryResponse, UserUpdateRequest } from './user';
import axios from 'axios';
/**

View File

@@ -1,5 +1,6 @@
import type { DataGrid, Pagination } from '@/types/global';
import axios from 'axios';
import qs from 'query-string';
/**
* 操作日志查询参数
@@ -40,19 +41,6 @@ export interface OperatorLogQueryResponse {
createTime: number;
}
/**
* 登录日志查询响应
*/
export interface LoginHistoryQueryResponse {
id: number;
address: string;
location: string;
userAgent: string;
result: number;
errorMessage: string;
createTime: number;
}
/**
* 分页操作日志
*/
@@ -61,8 +49,27 @@ export function getOperatorLogPage(request: OperatorLogQueryRequest) {
}
/**
* 查询登录日志
* 删除操作日志
*/
export function getLoginHistory(username: string) {
return axios.get<LoginHistoryQueryResponse[]>('/infra/operator-log/login-history', { params: { username } });
export function deleteOperatorLog(idList: Array<number>) {
return axios.delete('/infra/operator-log/delete', {
params: { idList },
paramsSerializer: params => {
return qs.stringify(params, { arrayFormat: 'comma' });
}
});
}
/**
* 查询操作日志数量
*/
export function getOperatorLogCount(request: OperatorLogQueryRequest) {
return axios.post<number>('/infra/operator-log/query-count', request);
}
/**
* 清空操作日志
*/
export function clearOperatorLog(request: OperatorLogQueryRequest) {
return axios.post<number>('/infra/operator-log/clear', request);
}

View File

@@ -77,6 +77,19 @@ export interface UserSessionOfflineRequest {
timestamp: number;
}
/**
* 登录日志查询响应
*/
export interface LoginHistoryQueryResponse {
id: number;
address: string;
location: string;
userAgent: string;
result: number;
errorMessage: string;
createTime: number;
}
/**
* 创建用户
*/
@@ -160,3 +173,10 @@ export function getUserSessionList(id: number) {
export function offlineUserSession(request: UserSessionOfflineRequest) {
return axios.put('/infra/system-user/session/offline', request);
}
/**
* 查询登录日志
*/
export function getLoginHistory(username: string) {
return axios.get<LoginHistoryQueryResponse[]>('/infra/system-user/login-history', { params: { username } });
}

View File

@@ -230,6 +230,20 @@ body {
margin-bottom: 16px;
}
.text-ellipsis {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.text-copy {
&:hover {
cursor: pointer;
text-decoration: underline;
color: rgb(var(--arcoblue-6))
}
}
.copy-left, .copy-right {
color: rgb(var(--arcoblue-6));
cursor: pointer;

View File

@@ -1,6 +1,6 @@
<template>
<a-layout-footer class="footer">
<a-space direction="vertical" size="medium">
<a-space direction="vertical" size="small">
<a-space size="large">
<!-- <a-link target="_blank" href="https://github.com/lijiahangmax/orion-ops-pro">官网</a-link> -->
<!-- <a-link target="_blank" href="https://github.com/lijiahangmax/orion-ops-pro">教程</a-link> -->

View File

@@ -160,7 +160,7 @@
</li>
<!-- 用户信息 -->
<li>
<a-dropdown trigger="click">
<a-dropdown trigger="click" position="br">
<!-- 头像 -->
<a-avatar draggable="false"
:size="32"
@@ -256,10 +256,10 @@
const localeRef = ref();
// 打开应用设置
const openAppSetting = inject<() => void>(openAppSettingKey);
const openAppSetting = inject(openAppSettingKey) as () => void;
// 注入收缩菜单
const toggleDrawerMenu = inject<() => void>(toggleDrawerMenuKey);
const toggleDrawerMenu = inject(toggleDrawerMenuKey) as () => void;
// 切换主题
const handleToggleTheme = () => {

View File

@@ -58,10 +58,11 @@
position="left"
type="warning"
@ok="deleteNode(node.key)">
<span v-permission="['asset:host-group:delete']"
class="tree-icon" title="删除">
<icon-delete />
</span>
<span v-permission="['asset:host-group:delete']"
class="tree-icon"
title="删除">
<icon-delete />
</span>
</a-popconfirm>
<!-- 新增 -->
<span v-permission="['asset:host-group:create']"

View File

@@ -29,9 +29,7 @@
</template>
<!-- table -->
<a-table row-key="id"
class="table-wrapper-8"
ref="tableRef"
label-align="left"
:loading="loading"
:columns="columns"
:data="tableRenderData"

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