Compare commits
23 Commits
v1.0.0-bet
...
v1.0.1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
78bf636cdb | ||
|
|
ba338c15de | ||
|
|
93407460d8 | ||
|
|
554c62abf7 | ||
|
|
a75ead9a58 | ||
|
|
f1ade4e182 | ||
|
|
462e77f936 | ||
|
|
ba955571a3 | ||
|
|
d1e94a49e0 | ||
|
|
b9127967d0 | ||
|
|
0538d2aa26 | ||
|
|
0f8eebf53c | ||
|
|
0f9c3db9cc | ||
|
|
b424dd02db | ||
|
|
f1d14b4a12 | ||
|
|
770f54df96 | ||
|
|
4799936a5f | ||
|
|
55373bb7dc | ||
|
|
7e91ef8386 | ||
|
|
1b459beb84 | ||
|
|
c48e48ab72 | ||
|
|
1c6a38d5d9 | ||
|
|
44dd5a9079 |
10
README.md
10
README.md
@@ -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 * 批量操作: 支持远程主机批量执行命令 以及 批量执行上传文件)
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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 .
|
||||
|
||||
@@ -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 * 批量操作: 支持远程主机批量执行命令 以及 批量执行上传文件)
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
# orion-ops-pro <small>1.0.0-beta.1</small>
|
||||
# orion-ops-pro <small>1.0.1</small>
|
||||
|
||||
> 一款开箱即用的运维平台。
|
||||
|
||||
- 易用 便捷
|
||||
- 友好 易用
|
||||
- 安全 稳定
|
||||
- 智能 高效
|
||||
|
||||
|
||||
@@ -1,8 +1,30 @@
|
||||
> 版本号严格遵循 Semver 规范。
|
||||
|
||||
[//]: # (🌈添加 🔨优化 🐞修复 [如何升级](/about/update.md?id=_100))
|
||||
## 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`
|
||||
|
||||
|
||||
@@ -4,9 +4,11 @@
|
||||
|
||||
## 未开始 ⏳
|
||||
|
||||
* 资产管理表结构优化
|
||||
* 批量执行
|
||||
* 定时执行
|
||||
* 站内消息
|
||||
* 后端配置动态配置
|
||||
* 终端背景图片
|
||||
* 按资产数据 UI 改版
|
||||
* 资产授权 UI 改版
|
||||
* RDP 远程桌面
|
||||
* 接入 config 后端动态配置
|
||||
|
||||
@@ -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);
|
||||
```
|
||||
|
||||
@@ -1,3 +1,14 @@
|
||||
### 连接日志
|
||||
|
||||
在主机终端页面打开的 `SSH` `SFTP` 连接都会记录下来, 这里默认只展示 `SSH` 连接记录, 可以展开条件进行修改。
|
||||
在主机终端页面打开的 `SSH` `SFTP` 连接都会记录下来。
|
||||
|
||||
* 详情: 查看连接详情
|
||||
* 断开: 断开连接
|
||||
* 删除: 删除连接记录
|
||||
* 清理: 根据条件清理数据
|
||||
|
||||
### SFTP 操作日志
|
||||
|
||||
查看用户 SFTP 操作日志, 是从用户操作日志中过滤查询。
|
||||
|
||||
* 删除: 删除操作日志
|
||||
|
||||
@@ -2,8 +2,12 @@
|
||||
|
||||
主机终端页面 支持 SSH, SFTP。
|
||||
打开后默认会进入新建连接页面, 页面的主机数据是用户授权的资产数据。
|
||||
鼠标移入列表内的主机上时, 右侧会出现 `打开 SSH` `打开 SFTP` `连接设置` `收藏` 的按钮。
|
||||
点击 `连接设置` 后, 会弹出模态框, 可以自定义配置连接主机的 密码/秘钥/身份, 仅对自己生效, 不会修改全局配置, `秘钥` `身份` 数据是用户授权的资产数据。
|
||||
鼠标移入列表内的主机上时, 右侧会出现 `打开 SSH` `打开 SFTP` `主机设置` `收藏` 的按钮。
|
||||
|
||||
> 主机设置
|
||||
|
||||
* SSH 配置: 可以自定义配置连接主机的 密码/秘钥/身份, 仅对自己生效, 不会修改全局配置, `秘钥` `身份` 数据是用户授权的资产数据
|
||||
* 标签颜色: 自定义配置标签的颜色, 可以用来区分环境等
|
||||
|
||||
> 顶部状态栏
|
||||
|
||||
|
||||
@@ -25,3 +25,4 @@
|
||||
记录用户在系统内的操作日志。
|
||||
|
||||
* 详情: 查看操作的参数以及留痕信息
|
||||
* 清理: 根据条件清理数据
|
||||
|
||||
@@ -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. 修改前端配置
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
可以在执行命令的第一行设置 `set -e`
|
||||
作用是: 当执行出现意料之外的情况时, 立即退出
|
||||
|
||||
> ##### 5. 在调度任务、应用构建、应用发布 命令执行成功的依据是什么?
|
||||
> ##### 5. 在调度任务、批量执行 命令执行成功的依据是什么?
|
||||
|
||||
是获取命令的 `exitcode` 判断是否为 `0` 如果非0则代表命令执行失败
|
||||
同理, 在命令的最后一行设置 `exit 1` 结果将会是失败, 可以用此来中断后续流程
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -85,4 +85,8 @@ public interface ErrorMessage {
|
||||
|
||||
String FILE_ABSENT = "文件不存在";
|
||||
|
||||
String LOG_ABSENT = "日志不存在";
|
||||
|
||||
String ILLEGAL_STATUS = "当前状态不支持此操作";
|
||||
|
||||
}
|
||||
|
||||
@@ -47,4 +47,12 @@ public interface FieldConst {
|
||||
|
||||
String MOD = "mod";
|
||||
|
||||
String COUNT = "count";
|
||||
|
||||
String LOCATION = "location";
|
||||
|
||||
String USER_AGENT = "userAgent";
|
||||
|
||||
String ERROR_MESSAGE = "errorMessage";
|
||||
|
||||
}
|
||||
|
||||
@@ -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";
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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": "登录失败锁定时间 (分)."
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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:
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -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}}
|
||||
|
||||
|
||||
###
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
@@ -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>"),
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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>"),
|
||||
|
||||
@@ -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;
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
}
|
||||
@@ -64,4 +64,7 @@ public class HostVO implements Serializable {
|
||||
@Schema(description = "别名")
|
||||
private String alias;
|
||||
|
||||
@Schema(description = "颜色")
|
||||
private String color;
|
||||
|
||||
}
|
||||
|
||||
@@ -24,6 +24,11 @@ public enum HostConnectStatusEnum {
|
||||
*/
|
||||
FAILED,
|
||||
|
||||
/**
|
||||
* 强制下线
|
||||
*/
|
||||
FORCE_OFFLINE,
|
||||
|
||||
;
|
||||
|
||||
public static HostConnectStatusEnum of(String type) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
}
|
||||
@@ -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) {
|
||||
}
|
||||
|
||||
}
|
||||
@@ -28,7 +28,7 @@ public enum OutputTypeEnum {
|
||||
/**
|
||||
* 关闭连接
|
||||
*/
|
||||
CLOSE("cl", "${type}|${sessionId}|${msg}"),
|
||||
CLOSE("cl", "${type}|${sessionId}|${forceClose}|${msg}"),
|
||||
|
||||
/**
|
||||
* pong
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -38,4 +38,9 @@ public interface ITerminalSession extends SafeCloseable {
|
||||
*/
|
||||
void keepAlive();
|
||||
|
||||
/**
|
||||
* 强制下线
|
||||
*/
|
||||
void forceOffline();
|
||||
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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())
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
/**
|
||||
* 查询额外配置
|
||||
*
|
||||
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
@@ -11,4 +11,6 @@ public interface DataExtraItems {
|
||||
|
||||
String ALIAS = "alias";
|
||||
|
||||
String COLOR = "color";
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
}
|
||||
@@ -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()));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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());
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
}
|
||||
@@ -14,9 +14,4 @@ Authorization: {{token}}
|
||||
"endTime": ""
|
||||
}
|
||||
|
||||
### 查询登录日志
|
||||
GET {{baseUrl}}/infra/operator-log/login-history?username=admin
|
||||
Content-Type: application/json
|
||||
Authorization: {{token}}
|
||||
|
||||
###
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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}}
|
||||
|
||||
|
||||
###
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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> 条"),
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
/**
|
||||
* 登录
|
||||
*
|
||||
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
/**
|
||||
* 查询额外配置
|
||||
*
|
||||
|
||||
@@ -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);
|
||||
|
||||
/**
|
||||
* 查询用户登录日志
|
||||
*
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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) {
|
||||
// 条件
|
||||
|
||||
@@ -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, "*"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
62
orion-ops-ui/src/api/asset/host-sftp-log.ts
Normal file
62
orion-ops-ui/src/api/asset/host-sftp-log.ts
Normal 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' });
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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';
|
||||
|
||||
/**
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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 } });
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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> -->
|
||||
|
||||
@@ -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 = () => {
|
||||
|
||||
@@ -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']"
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user