Compare commits

..

23 Commits

Author SHA1 Message Date
李佳航
c793ebf394 Merge pull request #79 from dromara/dev
Dev
2025-01-07 09:58:05 +08:00
lijiahangmax
45b3e0746a ✏️ 修改文档. 2025-01-07 00:08:33 +08:00
lijiahang
aaf18d79bf 🔨 修改构建错误. 2025-01-06 15:25:15 +08:00
lijiahangmax
4e3f548ac8 🔖 升级版本. 2025-01-05 18:33:38 +08:00
lijiahangmax
cbe1eeec74 🐛 修复刷新页面无参数的问题. 2024-12-30 22:50:11 +08:00
lijiahang
0557c41454 🎨 优化主机分组授权显示. 2024-12-30 15:54:07 +08:00
lijiahangmax
c2311f0682 优化缓存策略. 2024-12-29 23:58:44 +08:00
lijiahangmax
3b89e9bf29 🔨 优化会话连接逻辑. 2024-12-29 15:14:19 +08:00
lijiahangmax
6b14c2ef9c 🎨 优化策略定义逻辑. 2024-12-29 13:11:09 +08:00
lijiahangmax
6606d2ca76 🔨 工作台. 2024-12-28 23:28:23 +08:00
lijiahang
1846a496c1 🔨 资产模块统计. 2024-12-27 14:44:59 +08:00
lijiahang
7996ae5b63 🔨 基础模块统计. 2024-12-27 13:54:45 +08:00
lijiahang
cb5657c685 📝 工作台前端. 2024-12-27 11:19:52 +08:00
lijiahangmax
95759adf91 🐛 终端设置无法滚动的问题. 2024-12-27 01:11:18 +08:00
lijiahangmax
4d08a18548 🔨 添加连接操作. 2024-12-26 22:19:30 +08:00
lijiahang
8f1e976c2f 🔨 添加执行用户条件. 2024-12-26 14:03:47 +08:00
lijiahang
5ee18436b8 Merge remote-tracking branch 'origin/dev' into dev 2024-12-26 13:52:06 +08:00
lijiahang
00ea709f9e 🎨 修改代码规范. 2024-12-26 13:51:41 +08:00
lijiahangmax
4873d2cc91 📝 修改文档. 2024-12-25 20:48:39 +08:00
lijiahangmax
4c306fb4f8 🔨 修改为 latest 版本. 2024-12-25 20:19:32 +08:00
lijiahang
9017ada3aa 📝 修改文档. 2024-12-24 10:55:18 +08:00
李佳航
00c4484184 Merge pull request #75 from dromara/dev
 修改 E2E 测试样例.
2024-12-24 10:35:23 +08:00
lijiahang
5a6b1bfafd 修改 E2E 测试样例. 2024-12-24 10:34:26 +08:00
170 changed files with 3438 additions and 883 deletions

View File

@@ -3,7 +3,7 @@
<p align="center">
<a target="_blank"
style="text-decoration: none !important;"
href="https://app.codacy.com/gh/orionsec/orion-visor/dashboard?utm_source=gh&utm_medium=referral&utm_content=&utm_campaign=Badge_grade">
href="https://app.codacy.com/gh/lijiahangmax/orion-visor/dashboard?utm_source=gh&utm_medium=referral&utm_content=&utm_campaign=Badge_grade">
<img src="https://app.codacy.com/project/badge/Grade/49eaab3a9a474af3b87e1d21ffec71c4" alt="quality" />
</a>
<a target="_blank"
@@ -36,6 +36,11 @@
href="https://github.com/dromara/orion-visor">
<img src="https://img.shields.io/github/forks/dromara/orion-visor" alt="star" />
</a>
<a target="_blank"
style="text-decoration: none !important;"
href="https://gitcode.com/dromara/orion-visor">
<img src="https://gitcode.com/dromara/orion-visor/star/badge.svg" alt="star"/>
</a>
</p>
------------------------------
@@ -54,7 +59,7 @@
* 🔗 演示地址: [https://dv.orionsec.cn/](https://dv.orionsec.cn/)
* 🔏 演示账号: admin/admin
* ⭐ 体验后可以点一下 `star`
这对我很重要! [github](https://github.com/dromara/orion-visor) [gitee](https://gitee.com/dromara/orion-visor) [gitcode](https://gitcode.com/dromara/orion-visor/overview)
这对我很重要! [github](https://github.com/dromara/orion-visor) [gitee](https://gitee.com/dromara/orion-visor) [gitcode](https://gitcode.com/dromara/orion-visor)
* 🌈 如果本项目对你有帮助请帮忙推广一下 让更多的人知道此项目!
* 🎭 演示环境部分功能不可用, 完整功能请本地部署!
* 📛 演示环境请不要随便删除数据!
@@ -140,6 +145,6 @@ QQ群: 755242157
本项目遵循 [Apache-2.0](https://github.com/dromara/orion-visor/blob/main/LICENSE) 开源许可证。
## Gite 最有价值开源项目 GVP
## Gitee 最有价值开源项目 GVP
![GVP](https://bjuimg.obs.cn-north-4.myhuaweicloud.com/images/2024/10/24/8dd98b8d-9de5-44e6-86d3-04e27ec66123.jpg "GVP")

View File

@@ -1,7 +1,7 @@
version: '3.3'
services:
service:
image: registry.cn-hangzhou.aliyuncs.com/orionsec/orion-visor-service:2.2.2
image: registry.cn-hangzhou.aliyuncs.com/orionsec/orion-visor-service:latest
privileged: true
ports:
- 1081:80
@@ -32,7 +32,7 @@ services:
- mysql
- redis
mysql:
image: registry.cn-hangzhou.aliyuncs.com/orionsec/orion-visor-mysql:2.2.2
image: registry.cn-hangzhou.aliyuncs.com/orionsec/orion-visor-mysql:latest
privileged: true
ports:
- 3307:3306
@@ -52,7 +52,7 @@ services:
retries: 10
start_period: 3s
redis:
image: registry.cn-hangzhou.aliyuncs.com/orionsec/orion-visor-redis:2.2.2
image: registry.cn-hangzhou.aliyuncs.com/orionsec/orion-visor-redis:latest
privileged: true
ports:
- 6380:6379

View File

@@ -1,7 +1,7 @@
version: '3.3'
services:
service:
image: registry.cn-hangzhou.aliyuncs.com/orionsec/orion-visor-service:2.2.2
image: registry.cn-hangzhou.aliyuncs.com/orionsec/orion-visor-service:latest
privileged: true
ports:
- ${SERVICE_PORT:-1081}:80
@@ -32,7 +32,7 @@ services:
- mysql
- redis
mysql:
image: registry.cn-hangzhou.aliyuncs.com/orionsec/orion-visor-mysql:2.2.2
image: registry.cn-hangzhou.aliyuncs.com/orionsec/orion-visor-mysql:latest
privileged: true
ports:
- 3307:3306
@@ -52,7 +52,7 @@ services:
retries: 15
start_period: 3s
redis:
image: registry.cn-hangzhou.aliyuncs.com/orionsec/orion-visor-redis:2.2.2
image: registry.cn-hangzhou.aliyuncs.com/orionsec/orion-visor-redis:latest
privileged: true
ports:
- 6380:6379
@@ -68,7 +68,7 @@ services:
retries: 15
start_period: 3s
adminer:
image: registry.cn-hangzhou.aliyuncs.com/orionsec/orion-visor-adminer:2.2.2
image: registry.cn-hangzhou.aliyuncs.com/orionsec/orion-visor-adminer:latest
ports:
- 8081:8080
depends_on:

View File

@@ -1,4 +1,5 @@
#/bin/bash
version=2.2.2
version=2.2.3
docker build -t orion-visor-adminer:${version} .
docker tag orion-visor-adminer:${version} registry.cn-hangzhou.aliyuncs.com/orionsec/orion-visor-adminer:${version}
docker tag orion-visor-adminer:${version} registry.cn-hangzhou.aliyuncs.com/orionsec/orion-visor-adminer:latest

View File

@@ -15,9 +15,9 @@ items:
expect:
bodyFieldsExpect:
code: 200
- name: userPermission
- name: user
request:
api: /orion-visor/api/infra/user-permission/user
api: /orion-visor/api/infra/user-aggregate/user
header:
Authorization: Bearer {{.login.data.token}}
expect:
@@ -26,7 +26,7 @@ items:
msg: "success"
- name: menu
request:
api: /orion-visor/api/infra/user-permission/menu
api: /orion-visor/api/infra/user-aggregate/menu
header:
Authorization: Bearer {{.login.data.token}}
expect:

View File

@@ -1,6 +1,7 @@
#/bin/bash
version=2.2.2
version=2.2.3
cp -r ../../sql ./sql
docker build -t orion-visor-mysql:${version} .
rm -rf ./sql
docker tag orion-visor-mysql:${version} registry.cn-hangzhou.aliyuncs.com/orionsec/orion-visor-mysql:${version}
docker tag orion-visor-mysql:${version} registry.cn-hangzhou.aliyuncs.com/orionsec/orion-visor-mysql:latest

View File

@@ -1,6 +1,10 @@
#/bin/bash
version=2.2.2
version=2.2.3
docker push registry.cn-hangzhou.aliyuncs.com/orionsec/orion-visor-adminer:${version}
docker push registry.cn-hangzhou.aliyuncs.com/orionsec/orion-visor-mysql:${version}
docker push registry.cn-hangzhou.aliyuncs.com/orionsec/orion-visor-redis:${version}
docker push registry.cn-hangzhou.aliyuncs.com/orionsec/orion-visor-service:${version}
docker push registry.cn-hangzhou.aliyuncs.com/orionsec/orion-visor-adminer:latest
docker push registry.cn-hangzhou.aliyuncs.com/orionsec/orion-visor-mysql:latest
docker push registry.cn-hangzhou.aliyuncs.com/orionsec/orion-visor-redis:latest
docker push registry.cn-hangzhou.aliyuncs.com/orionsec/orion-visor-service:latest

View File

@@ -1,4 +1,5 @@
#/bin/bash
version=2.2.2
version=2.2.3
docker build -t orion-visor-redis:${version} .
docker tag orion-visor-redis:${version} registry.cn-hangzhou.aliyuncs.com/orionsec/orion-visor-redis:${version}
docker tag orion-visor-redis:${version} registry.cn-hangzhou.aliyuncs.com/orionsec/orion-visor-redis:latest

View File

@@ -1,8 +1,9 @@
#/bin/bash
version=2.2.2
version=2.2.3
mv ../../orion-visor-launch/target/orion-visor-launch.jar ./orion-visor-launch.jar
mv ../../orion-visor-ui/dist ./dist
docker build --no-cache -t orion-visor-service:${version} .
rm -rf ./orion-visor-launch.jar
rm -rf ./dist
docker tag orion-visor-service:${version} registry.cn-hangzhou.aliyuncs.com/orionsec/orion-visor-service:${version}
docker tag orion-visor-service:${version} registry.cn-hangzhou.aliyuncs.com/orionsec/orion-visor-service:latest

View File

@@ -14,7 +14,7 @@
<url>https://github.com/dromara/orion-visor</url>
<properties>
<revision>2.2.2</revision>
<revision>2.2.3</revision>
<spring.boot.version>2.7.17</spring.boot.version>
<spring.boot.admin.version>2.7.15</spring.boot.admin.version>
<flatten.maven.plugin.version>1.5.0</flatten.maven.plugin.version>

View File

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

View File

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

View File

@@ -0,0 +1,50 @@
/*
* Copyright (c) 2023 - present Dromara, All rights reserved.
*
* https://visor.dromara.org
* https://visor.dromara.org.cn
* https://visor.orionsec.cn
*
* Members:
* Jiahang Li - ljh1553488six@139.com - author
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.dromara.visor.framework.common.entity.chart;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.Map;
/**
* 柱状图图单系列数据
*
* @author Jiahang Li
* @version 1.0.0
* @since 2024/12/23 13:42
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Schema(name = "BarSingleChartData", description = "柱状图图单系列数据")
public class BarSingleChartData {
@Schema(description = "数据")
private Map<String, Integer> data;
}

View File

@@ -0,0 +1,54 @@
/*
* Copyright (c) 2023 - present Dromara, All rights reserved.
*
* https://visor.dromara.org
* https://visor.dromara.org.cn
* https://visor.orionsec.cn
*
* Members:
* Jiahang Li - ljh1553488six@139.com - author
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.dromara.visor.framework.common.entity.chart;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.List;
import java.util.Map;
/**
* 折线图多系列数据
*
* @author Jiahang Li
* @version 1.0.0
* @since 2024/12/23 13:41
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Schema(name = "LineChartData", description = "折线图多系列数据")
public class LineChartData {
@Schema(description = "x轴")
private List<String> x;
@Schema(description = "数据")
private Map<String, List<Integer>> data;
}

View File

@@ -0,0 +1,53 @@
/*
* Copyright (c) 2023 - present Dromara, All rights reserved.
*
* https://visor.dromara.org
* https://visor.dromara.org.cn
* https://visor.orionsec.cn
*
* Members:
* Jiahang Li - ljh1553488six@139.com - author
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.dromara.visor.framework.common.entity.chart;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.List;
/**
* 折线图单系列数据
*
* @author Jiahang Li
* @version 1.0.0
* @since 2024/12/23 13:42
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Schema(name = "LineSingleChartData", description = "折线图单系列数据")
public class LineSingleChartData {
@Schema(description = "x轴")
private List<String> x;
@Schema(description = "数据")
private List<Integer> data;
}

View File

@@ -0,0 +1,50 @@
/*
* Copyright (c) 2023 - present Dromara, All rights reserved.
*
* https://visor.dromara.org
* https://visor.dromara.org.cn
* https://visor.orionsec.cn
*
* Members:
* Jiahang Li - ljh1553488six@139.com - author
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.dromara.visor.framework.common.entity.chart;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.Map;
/**
* 饼图数据
*
* @author Jiahang Li
* @version 1.0.0
* @since 2024/12/23 13:42
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Schema(name = "PieChartData", description = "饼图数据")
public class PieChartData {
@Schema(description = "数据")
private Map<String, Integer> data;
}

View File

@@ -34,7 +34,7 @@ import org.dromara.visor.framework.common.handler.data.strategy.GenericsDataStra
* @since 2023/12/21 0:07
*/
@SuppressWarnings("unchecked")
public interface GenericsDataDefinition {
public interface GenericsStrategyDefinition {
/**
* 获取数据处理策略
@@ -54,12 +54,32 @@ public interface GenericsDataDefinition {
return (S) SpringHolder.getBean(this.getStrategyClass());
}
/**
* 获取默认值
*
* @param <M> model
* @return model
*/
default <M extends GenericsDataModel> M getDefault() {
return (M) this.getStrategy().getDefault();
}
/**
* 执行完整验证链
*
* @param beforeModel beforeModel
* @param afterModel afterModel
*/
default void doValid(GenericsDataModel beforeModel, GenericsDataModel afterModel) {
this.getStrategy().doValid(beforeModel, afterModel);
}
/**
* 反序列化对象
*
* @param serialModel serialModel
* @param <M> Model
* @return object
* @param <M> model
* @return model
*/
default <M extends GenericsDataModel> M parse(String serialModel) {
return (M) this.getStrategy().parse(serialModel);
@@ -69,14 +89,11 @@ public interface GenericsDataDefinition {
* 转为视图对象
*
* @param serialModel serialModel
* @param <M> Model
* @param <M> model
* @return viewModel
*/
default <M extends GenericsDataModel> M toView(String serialModel) {
GenericsDataStrategy<GenericsDataModel> strategy = this.getStrategy();
GenericsDataModel model = strategy.parse(serialModel);
strategy.toView(model);
return (M) model;
return (M) this.getStrategy().toView(serialModel);
}
}

View File

@@ -84,4 +84,13 @@ public abstract class AbstractGenericsDataStrategy<M extends GenericsDataModel>
public void toView(M model) {
}
@Override
public M toView(String serialModel) {
// 解析
M parse = this.parse(serialModel);
// 转为视图对象
this.toView(parse);
return parse;
}
}

View File

@@ -65,4 +65,12 @@ public interface GenericsDataStrategy<M extends GenericsDataModel> {
*/
void toView(M model);
/**
* 转为视图配置
*
* @param serialModel serialModel
* @return model
*/
M toView(String serialModel);
}

View File

@@ -51,7 +51,7 @@ public class OrionDataSourceAutoConfiguration {
* @return druid 广告过滤器
*/
@Bean
@ConditionalOnProperty(name = "spring.datasource.druid.web-stat-filter.enabled", havingValue = "true")
@ConditionalOnProperty(value = "spring.datasource.druid.web-stat-filter.enabled", havingValue = "true")
public FilterRegistrationBean<DruidAdRemoveFilter> druidAdRemoveFilterFilter(DruidStatProperties properties) {
// 获取 druid web 监控页面的参数
DruidStatProperties.StatViewServlet config = properties.getStatViewServlet();

View File

@@ -170,8 +170,8 @@
// 调用删除接口
await delete${vue.featureEntity}(id);
Message.success('删除成功');
// 重新加载数据
fetchCardData();
// 重新加载
reload();
} catch (e) {
} finally {
setLoading(false);
@@ -182,6 +182,7 @@
// 重新加载
const reload = () => {
// 重新加载数据
fetchCardData();
};

View File

@@ -189,8 +189,8 @@
await batchDelete${vue.featureEntity}(selectedKeys.value);
Message.success(`成功删除 ${selectedKeys.value.length} 条数据`);
selectedKeys.value = [];
// 重新加载数据
fetchTableData();
// 重新加载
reload();
} catch (e) {
} finally {
setLoading(false);
@@ -207,8 +207,8 @@
// 调用删除接口
await delete${vue.featureEntity}(id);
Message.success('删除成功');
// 重新加载数据
fetchTableData();
// 重新加载
reload();
} catch (e) {
} finally {
setLoading(false);
@@ -217,6 +217,7 @@
// 重新加载
const reload = () => {
// 重新加载数据
fetchTableData();
};

View File

@@ -61,7 +61,7 @@ import java.util.Optional;
*/
@ConditionalOnClass({OpenAPI.class})
@EnableConfigurationProperties(SwaggerConfig.class)
@ConditionalOnProperty(name = "springdoc.api-docs.enabled", havingValue = "true")
@ConditionalOnProperty(value = "springdoc.api-docs.enabled", havingValue = "true")
@AutoConfiguration
@AutoConfigureOrder(AutoConfigureOrderConst.FRAMEWORK_SWAGGER)
public class OrionSwaggerAutoConfiguration {

View File

@@ -39,9 +39,9 @@ import java.util.function.Function;
*/
public class ReplaceVersion {
private static final String TARGET_VERSION = "2.2.1";
private static final String TARGET_VERSION = "2.2.2";
private static final String REPLACE_VERSION = "2.2.2";
private static final String REPLACE_VERSION = "2.2.3";
private static final String PATH = new File("").getAbsolutePath();

View File

@@ -0,0 +1,65 @@
/*
* Copyright (c) 2023 - present Dromara, All rights reserved.
*
* https://visor.dromara.org
* https://visor.dromara.org.cn
* https://visor.orionsec.cn
*
* Members:
* Jiahang Li - ljh1553488six@139.com - author
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.dromara.visor.module.asset.controller;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.extern.slf4j.Slf4j;
import org.dromara.visor.framework.log.core.annotation.IgnoreLog;
import org.dromara.visor.framework.log.core.enums.IgnoreLogMode;
import org.dromara.visor.framework.web.core.annotation.RestWrapper;
import org.dromara.visor.module.asset.entity.vo.AssetWorkplaceStatisticsVO;
import org.dromara.visor.module.asset.service.AssetStatisticsService;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
/**
* asset - 统计服务
*
* @author Jiahang Li
* @version 1.0.0
* @since 2024/12/23 16:07
*/
@Tag(name = "asset - 统计服务")
@Slf4j
@Validated
@RestWrapper
@RestController
@RequestMapping("/asset/statistics")
public class AssetStatisticsController {
@Resource
private AssetStatisticsService assetStatisticsService;
@IgnoreLog(IgnoreLogMode.RET)
@GetMapping("/get-workplace")
@Operation(summary = "查询工作台统计信息")
public AssetWorkplaceStatisticsVO getWorkplaceStatisticsData() {
return assetStatisticsService.getWorkplaceStatisticsData();
}
}

View File

@@ -26,7 +26,9 @@ import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.dromara.visor.framework.mybatis.core.mapper.IMapper;
import org.dromara.visor.module.asset.entity.domain.ExecLogDO;
import org.dromara.visor.module.asset.entity.po.ExecLogCountPO;
import java.util.Date;
import java.util.List;
/**
@@ -67,4 +69,18 @@ public interface ExecLogDAO extends IMapper<ExecLogDO> {
@Param("userId") Long userId,
@Param("limit") Integer limit);
/**
* 获取执行日志统计
*
* @param userId userId
* @param source source
* @param startTime startTime
* @param endTime endTime
* @return count
*/
List<ExecLogCountPO> selectExecLogCount(@Param("userId") Long userId,
@Param("source") String source,
@Param("startTime") Date startTime,
@Param("endTime") Date endTime);
}

View File

@@ -26,7 +26,9 @@ import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.dromara.visor.framework.mybatis.core.mapper.IMapper;
import org.dromara.visor.module.asset.entity.domain.TerminalConnectLogDO;
import org.dromara.visor.module.asset.entity.po.TerminalConnectLogCountPO;
import java.util.Date;
import java.util.List;
/**
@@ -49,4 +51,16 @@ public interface TerminalConnectLogDAO extends IMapper<TerminalConnectLogDO> {
*/
List<Long> selectLatestConnectHostId(@Param("userId") Long userId, @Param("type") String type, @Param("limit") Integer limit);
/**
* 查询终端连接日志用户数量
*
* @param userId userId
* @param startTime startTime
* @param endTime endTime
* @return rows
*/
List<TerminalConnectLogCountPO> selectConnectLogUserCount(@Param("userId") Long userId,
@Param("startTime") Date startTime,
@Param("endTime") Date endTime);
}

View File

@@ -0,0 +1,49 @@
/*
* Copyright (c) 2023 - present Dromara, All rights reserved.
*
* https://visor.dromara.org
* https://visor.dromara.org.cn
* https://visor.orionsec.cn
*
* Members:
* Jiahang Li - ljh1553488six@139.com - author
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.dromara.visor.module.asset.define.cache;
import cn.orionsec.kit.lang.define.cache.key.CacheKeyBuilder;
import cn.orionsec.kit.lang.define.cache.key.CacheKeyDefine;
import cn.orionsec.kit.lang.define.cache.key.struct.RedisCacheStruct;
import org.dromara.visor.module.asset.entity.vo.AssetWorkplaceStatisticsVO;
import java.util.concurrent.TimeUnit;
/**
* 资产模块统计缓存 key
*
* @author Jiahang Li
* @version 1.0.0
* @since 2024/12/23 16:10
*/
public interface AssetStatisticsCacheKeyDefine {
CacheKeyDefine WORKPLACE_DATA = new CacheKeyBuilder()
.key("data:statistics:asset-workplace:{}:{}")
.desc("资产模块工作台统计 ${userId} ${time}")
.type(AssetWorkplaceStatisticsVO.class)
.struct(RedisCacheStruct.STRING)
.timeout(10, TimeUnit.MINUTES)
.build();
}

View File

@@ -0,0 +1,55 @@
/*
* Copyright (c) 2023 - present Dromara, All rights reserved.
*
* https://visor.dromara.org
* https://visor.dromara.org.cn
* https://visor.orionsec.cn
*
* Members:
* Jiahang Li - ljh1553488six@139.com - author
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.dromara.visor.module.asset.entity.po;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
/**
* 执行日志数量
*
* @author Jiahang Li
* @version 1.0.0
* @since 2024/12/23 21:35
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Schema(name = "ExecLogCountPO", description = "执行日志数量")
public class ExecLogCountPO implements Serializable {
private static final long serialVersionUID = 1L;
@Schema(description = "执行日期")
private String execDate;
@Schema(description = "数量")
private Integer count;
}

View File

@@ -0,0 +1,58 @@
/*
* Copyright (c) 2023 - present Dromara, All rights reserved.
*
* https://visor.dromara.org
* https://visor.dromara.org.cn
* https://visor.orionsec.cn
*
* Members:
* Jiahang Li - ljh1553488six@139.com - author
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.dromara.visor.module.asset.entity.po;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
/**
* 终端连接日志数量
*
* @author Jiahang Li
* @version 1.0.0
* @since 2024/12/23 22:31
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Schema(name = "TerminalConnectLogCountPO", description = "终端连接日志数量")
public class TerminalConnectLogCountPO implements Serializable {
private static final long serialVersionUID = 1L;
@Schema(description = "连接日期")
private String connectDate;
@Schema(description = "类型")
private String type;
@Schema(description = "数量")
private Integer count;
}

View File

@@ -56,6 +56,9 @@ public class ExecJobQueryRequest extends PageRequest {
@Schema(description = "任务状态")
private Integer status;
@Schema(description = "执行用户id")
private Long execUserId;
@Schema(description = "是否查询最近执行任务")
private Boolean queryRecentLog;

View File

@@ -0,0 +1,75 @@
/*
* Copyright (c) 2023 - present Dromara, All rights reserved.
*
* https://visor.dromara.org
* https://visor.dromara.org.cn
* https://visor.orionsec.cn
*
* Members:
* Jiahang Li - ljh1553488six@139.com - author
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.dromara.visor.module.asset.entity.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.dromara.visor.framework.common.entity.chart.LineSingleChartData;
import java.util.List;
/**
* 资产模块工作台统计响应
*
* @author Jiahang Li
* @version 1.0.0
* @since 2024/12/26 15:32
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Schema(name = "AssetWorkplaceStatisticsVO", description = "资产模块工作台统计响应")
public class AssetWorkplaceStatisticsVO {
@Schema(description = "执行的计划任务数量")
private Integer execJobCount;
@Schema(description = "今日批量执行数量")
private Integer todayExecCommandCount;
@Schema(description = "7日批量执行次数")
private Integer weekExecCommandCount;
@Schema(description = "今日连接终端次数")
private Integer todayTerminalConnectCount;
@Schema(description = "7日连接终端次数")
private Integer weekTerminalConnectCount;
@Schema(description = "批量执行数量图表")
private LineSingleChartData execCommandChart;
@Schema(description = "连接终端次数图表")
private LineSingleChartData terminalConnectChart;
@Schema(description = "连接终端记录")
private List<TerminalConnectLogVO> terminalConnectList;
@Schema(description = "批量执行记录")
private List<ExecLogVO> execLogList;
}

View File

@@ -24,7 +24,7 @@ package org.dromara.visor.module.asset.enums;
import lombok.AllArgsConstructor;
import lombok.Getter;
import org.dromara.visor.framework.common.handler.data.GenericsDataDefinition;
import org.dromara.visor.framework.common.handler.data.GenericsStrategyDefinition;
import org.dromara.visor.framework.common.handler.data.model.GenericsDataModel;
import org.dromara.visor.framework.common.handler.data.strategy.GenericsDataStrategy;
import org.dromara.visor.module.asset.handler.host.extra.strategy.HostLabelExtraStrategy;
@@ -39,7 +39,7 @@ import org.dromara.visor.module.asset.handler.host.extra.strategy.HostSshExtraSt
*/
@Getter
@AllArgsConstructor
public enum HostExtraItemEnum implements GenericsDataDefinition {
public enum HostExtraItemEnum implements GenericsStrategyDefinition {
/**
* SSH 额外配置

View File

@@ -24,7 +24,7 @@ package org.dromara.visor.module.asset.enums;
import lombok.AllArgsConstructor;
import lombok.Getter;
import org.dromara.visor.framework.common.handler.data.GenericsDataDefinition;
import org.dromara.visor.framework.common.handler.data.GenericsStrategyDefinition;
import org.dromara.visor.framework.common.handler.data.model.GenericsDataModel;
import org.dromara.visor.framework.common.handler.data.strategy.GenericsDataStrategy;
import org.dromara.visor.module.asset.handler.host.config.strategy.HostSshConfigStrategy;
@@ -38,7 +38,7 @@ import org.dromara.visor.module.asset.handler.host.config.strategy.HostSshConfig
*/
@Getter
@AllArgsConstructor
public enum HostTypeEnum implements GenericsDataDefinition {
public enum HostTypeEnum implements GenericsStrategyDefinition {
/**
* SSH

View File

@@ -0,0 +1,62 @@
/*
* Copyright (c) 2023 - present Dromara, All rights reserved.
*
* https://visor.dromara.org
* https://visor.dromara.org.cn
* https://visor.orionsec.cn
*
* Members:
* Jiahang Li - ljh1553488six@139.com - author
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.dromara.visor.module.asset.service;
import org.dromara.visor.framework.common.entity.chart.LineSingleChartData;
import org.dromara.visor.module.asset.entity.vo.AssetWorkplaceStatisticsVO;
import org.dromara.visor.module.asset.enums.ExecSourceEnum;
/**
* 资产模块统计服务
*
* @author Jiahang Li
* @version 1.0.0
* @since 2024/12/23 22:24
*/
public interface AssetStatisticsService {
/**
* 查询工作台统计信息
*
* @return data
*/
AssetWorkplaceStatisticsVO getWorkplaceStatisticsData();
/**
* 获取用户终端连接日志数量图表
*
* @param userId userId
* @return data
*/
LineSingleChartData getTerminalConnectCountChart(Long userId);
/**
* 获取用户执行日志数量图表
*
* @param userId userId
* @param source source
* @return chart
*/
LineSingleChartData getUserExecLogCountChart(Long userId, ExecSourceEnum source);
}

View File

@@ -36,6 +36,7 @@ import java.util.Map;
* @version 1.0.0
* @since 2023-9-11 14:16
*/
// TODO 待优化
public interface HostConfigService {
/**

View File

@@ -0,0 +1,178 @@
/*
* Copyright (c) 2023 - present Dromara, All rights reserved.
*
* https://visor.dromara.org
* https://visor.dromara.org.cn
* https://visor.orionsec.cn
*
* Members:
* Jiahang Li - ljh1553488six@139.com - author
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.dromara.visor.module.asset.service.impl;
import cn.orionsec.kit.lang.utils.collect.Lists;
import cn.orionsec.kit.lang.utils.time.Dates;
import org.dromara.visor.framework.common.entity.StatisticsRange;
import org.dromara.visor.framework.common.entity.chart.LineSingleChartData;
import org.dromara.visor.framework.redis.core.utils.RedisStrings;
import org.dromara.visor.framework.security.core.utils.SecurityUtils;
import org.dromara.visor.module.asset.convert.ExecLogConvert;
import org.dromara.visor.module.asset.convert.TerminalConnectLogConvert;
import org.dromara.visor.module.asset.dao.ExecJobDAO;
import org.dromara.visor.module.asset.dao.ExecLogDAO;
import org.dromara.visor.module.asset.dao.TerminalConnectLogDAO;
import org.dromara.visor.module.asset.define.cache.AssetStatisticsCacheKeyDefine;
import org.dromara.visor.module.asset.entity.domain.ExecJobDO;
import org.dromara.visor.module.asset.entity.domain.ExecLogDO;
import org.dromara.visor.module.asset.entity.domain.TerminalConnectLogDO;
import org.dromara.visor.module.asset.entity.po.ExecLogCountPO;
import org.dromara.visor.module.asset.entity.po.TerminalConnectLogCountPO;
import org.dromara.visor.module.asset.entity.vo.AssetWorkplaceStatisticsVO;
import org.dromara.visor.module.asset.entity.vo.ExecLogVO;
import org.dromara.visor.module.asset.entity.vo.TerminalConnectLogVO;
import org.dromara.visor.module.asset.enums.ExecSourceEnum;
import org.dromara.visor.module.asset.service.AssetStatisticsService;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
/**
* 资产模块统计服务实现
*
* @author Jiahang Li
* @version 1.0.0
* @since 2024/12/23 20:47
*/
@Service
public class AssetStatisticsServiceImpl implements AssetStatisticsService {
@Resource
private ExecJobDAO execJobDAO;
@Resource
private ExecLogDAO execLogDAO;
@Resource
private TerminalConnectLogDAO terminalConnectLogDAO;
@Override
public AssetWorkplaceStatisticsVO getWorkplaceStatisticsData() {
Long userId = SecurityUtils.getLoginUserId();
// 读取缓存
String cacheKey = AssetStatisticsCacheKeyDefine.WORKPLACE_DATA.format(userId, Dates.current(Dates.YMD2));
AssetWorkplaceStatisticsVO data = RedisStrings.getJson(cacheKey, AssetStatisticsCacheKeyDefine.WORKPLACE_DATA);
if (data == null) {
// 查询执行的计划任务数量
int execJobCount = execJobDAO.of()
.createWrapper()
.eq(ExecJobDO::getExecUserId, userId)
.then()
.count()
.intValue();
// 查询批量执行次数图表
LineSingleChartData execLogCountChart = this.getUserExecLogCountChart(userId, ExecSourceEnum.BATCH);
List<Integer> execLogCountData = execLogCountChart.getData();
int execLogCount = execLogCountData.stream()
.mapToInt(Integer::intValue)
.sum();
// 查询终端连接次数图表
LineSingleChartData terminalConnectCountChart = this.getTerminalConnectCountChart(userId);
List<Integer> terminalConnectCountData = terminalConnectCountChart.getData();
int terminalConnectCount = terminalConnectCountData.stream()
.mapToInt(Integer::intValue)
.sum();
data = AssetWorkplaceStatisticsVO.builder()
.execJobCount(execJobCount)
.todayExecCommandCount(Lists.last(execLogCountData))
.weekExecCommandCount(execLogCount)
.todayTerminalConnectCount(Lists.last(terminalConnectCountData))
.weekTerminalConnectCount(terminalConnectCount)
.execCommandChart(execLogCountChart)
.terminalConnectChart(terminalConnectCountChart)
.build();
// 设置缓存
RedisStrings.setJson(cacheKey, AssetStatisticsCacheKeyDefine.WORKPLACE_DATA, data);
}
// 查询命令执行记录
List<ExecLogVO> execLogList = execLogDAO.of()
.createWrapper()
.eq(ExecLogDO::getUserId, userId)
.eq(ExecLogDO::getSource, ExecSourceEnum.BATCH.name())
.orderByDesc(ExecLogDO::getId)
.then()
.limit(10)
.list(ExecLogConvert.MAPPER::to);
data.setExecLogList(execLogList);
// 查询终端连接记录
List<TerminalConnectLogVO> connectList = terminalConnectLogDAO.of()
.createWrapper()
.eq(TerminalConnectLogDO::getUserId, userId)
.orderByDesc(TerminalConnectLogDO::getId)
.then()
.limit(10)
.list(TerminalConnectLogConvert.MAPPER::to);
data.setTerminalConnectList(connectList);
return data;
}
@Override
public LineSingleChartData getUserExecLogCountChart(Long userId, ExecSourceEnum source) {
Date endTime = new Date();
Date startTime = Dates.stream()
.clearHms()
.addDay(-6)
.get();
List<String> rangeDays = StatisticsRange.WEEK.getDateRanges(startTime);
// 查询连接数量
Map<String, Integer> countMap = execLogDAO.selectExecLogCount(userId, source.name(), startTime, endTime)
.stream()
.collect(Collectors.toMap(ExecLogCountPO::getExecDate, ExecLogCountPO::getCount));
// 构建每天的数据
List<Integer> data = rangeDays.stream()
.map(s -> countMap.getOrDefault(s, 0))
.collect(Collectors.toList());
return LineSingleChartData.builder()
.x(rangeDays)
.data(data)
.build();
}
@Override
public LineSingleChartData getTerminalConnectCountChart(Long userId) {
Date endTime = new Date();
Date startTime = Dates.stream()
.clearHms()
.addDay(-6)
.get();
List<String> rangeDays = StatisticsRange.WEEK.getDateRanges(startTime);
// 查询连接数量
Map<String, Integer> countMap = terminalConnectLogDAO.selectConnectLogUserCount(userId, startTime, endTime)
.stream()
.collect(Collectors.toMap(TerminalConnectLogCountPO::getConnectDate, TerminalConnectLogCountPO::getCount));
// 构建每天的数据
List<Integer> data = rangeDays.stream()
.map(s -> countMap.getOrDefault(s, 0))
.collect(Collectors.toList());
return LineSingleChartData.builder()
.x(rangeDays)
.data(data)
.build();
}
}

View File

@@ -141,6 +141,7 @@ public class ExecCommandServiceImpl implements ExecCommandService {
// 查询主机信息
List<HostDO> hosts = hostDAO.selectBatchIds(hostIdList);
// 查询主机配置
// TODO 待优化
Map<Long, HostSshConfigModel> hostConfigMap = hostConfigService.buildHostConfigMap(hosts, HostTypeEnum.SSH);
// 插入日志
ExecLogDO execLog = ExecLogDO.builder()

View File

@@ -408,6 +408,7 @@ public class ExecJobServiceImpl implements ExecJobService {
.like(ExecJobDO::getName, request.getName())
.like(ExecJobDO::getCommand, request.getCommand())
.eq(ExecJobDO::getStatus, request.getStatus())
.eq(ExecJobDO::getExecUserId, request.getExecUserId())
.orderByDesc(ExecJobDO::getId);
}

View File

@@ -388,6 +388,7 @@ public class ExecLogServiceImpl implements ExecLogService {
}
Valid.notEmpty(hostLogs, ErrorMessage.LOG_ABSENT);
// 获取编码集
// TODO 待优化
List<Long> hostIdList = hostLogs.stream()
.map(ExecHostLogDO::getHostId)
.collect(Collectors.toList());

View File

@@ -25,7 +25,6 @@ package org.dromara.visor.module.asset.service.impl;
import cn.orionsec.kit.lang.function.Functions;
import cn.orionsec.kit.lang.utils.collect.Maps;
import org.dromara.visor.framework.common.handler.data.model.GenericsDataModel;
import org.dromara.visor.framework.common.handler.data.strategy.GenericsDataStrategy;
import org.dromara.visor.framework.common.utils.Valid;
import org.dromara.visor.framework.security.core.utils.SecurityUtils;
import org.dromara.visor.module.asset.entity.request.host.HostExtraQueryRequest;
@@ -120,7 +119,6 @@ public class HostExtraServiceImpl implements HostExtraService {
Long hostId = request.getHostId();
Long userId = SecurityUtils.getLoginUserId();
HostExtraItemEnum item = Valid.valid(HostExtraItemEnum::of, request.getItem());
GenericsDataStrategy<GenericsDataModel> strategy = item.getStrategy();
// 查询原始配置
DataExtraQueryDTO query = DataExtraQueryDTO.builder()
.userId(userId)
@@ -136,7 +134,7 @@ public class HostExtraServiceImpl implements HostExtraService {
GenericsDataModel newExtra = item.parse(request.getExtra());
GenericsDataModel beforeExtra = item.parse(beforeExtraItem.getValue());
// 更新验证
strategy.doValid(beforeExtra, newExtra);
item.doValid(beforeExtra, newExtra);
// 更新配置
return dataExtraApi.updateExtraValue(beforeExtraItem.getId(), newExtra.serial());
}
@@ -167,9 +165,8 @@ public class HostExtraServiceImpl implements HostExtraService {
* @return defaultValue
*/
private String checkInitItem(HostExtraItemEnum item, Long userId, Long hostId) {
GenericsDataStrategy<GenericsDataModel> strategy = item.getStrategy();
// 初始化默认数据
String extraValue = strategy.getDefault().serial();
String extraValue = item.getDefault().serial();
// 插入默认值
DataExtraSetDTO set = DataExtraSetDTO.builder()
.userId(userId)

View File

@@ -116,7 +116,7 @@ public class HostServiceImpl implements HostService {
this.checkHostNamePresent(record);
this.checkHostCodePresent(record);
// 设置主机配置
record.setConfig(type.getStrategy().getDefault().serial());
record.setConfig(type.getDefault().serial());
// 插入主机
int effect = hostDAO.insert(record);
log.info("HostService-createHost effect: {}", effect);
@@ -193,7 +193,7 @@ public class HostServiceImpl implements HostService {
OperatorLogs.add(OperatorLogs.ID, id);
OperatorLogs.add(OperatorLogs.NAME, host.getName());
// 更新前校验
type.getStrategy().doValid(beforeConfig, newConfig);
type.doValid(beforeConfig, newConfig);
// 修改配置
HostDO updateHost = HostDO.builder()
.id(id)

View File

@@ -26,6 +26,12 @@
<result column="deleted" property="deleted"/>
</resultMap>
<!-- 数量询映射结果 -->
<resultMap id="CountResultMap" type="org.dromara.visor.module.asset.entity.po.ExecLogCountPO">
<result column="exec_date" property="execDate"/>
<result column="total_count" property="count"/>
</resultMap>
<!-- 通用查询结果列 -->
<sql id="Base_Column_List">
id, user_id, username, source, source_id, exec_mode, description, exec_seq, command, parameter_schema, timeout, script_exec, status, start_time, finish_time, create_time, update_time, creator, updater, deleted
@@ -47,4 +53,16 @@
) sub ON e.id = sub.max_id
</select>
<select id="selectExecLogCount" resultMap="CountResultMap">
SELECT DATE(create_time) exec_date, COUNT(1) total_count
FROM exec_log
WHERE deleted = 0
<if test="userId != null">
AND user_id = #{userId}
</if>
AND source = #{source}
AND create_time BETWEEN #{startTime} AND #{endTime}
GROUP BY exec_date
</select>
</mapper>

View File

@@ -21,6 +21,13 @@
<result column="deleted" property="deleted"/>
</resultMap>
<!-- 数量查询映射结果 -->
<resultMap id="CountResultMap" type="org.dromara.visor.module.asset.entity.po.TerminalConnectLogCountPO">
<result column="connect_date" property="connectDate"/>
<result column="type" property="type"/>
<result column="total_count" property="count"/>
</resultMap>
<!-- 通用查询结果列 -->
<sql id="Base_Column_List">
id, user_id, username, host_id, host_name, host_address, type, token, status, start_time, end_time, extra_info, create_time, update_time, deleted
@@ -36,4 +43,13 @@
LIMIT #{limit}
</select>
<select id="selectConnectLogUserCount" resultMap="CountResultMap">
SELECT DATE(create_time) connect_date, COUNT(1) total_count
FROM terminal_connect_log
WHERE deleted = 0
AND user_id = #{userId}
AND create_time BETWEEN #{startTime} AND #{endTime}
GROUP BY connect_date
</select>
</mapper>

View File

@@ -0,0 +1,65 @@
/*
* Copyright (c) 2023 - present Dromara, All rights reserved.
*
* https://visor.dromara.org
* https://visor.dromara.org.cn
* https://visor.orionsec.cn
*
* Members:
* Jiahang Li - ljh1553488six@139.com - author
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.dromara.visor.module.infra.controller;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.extern.slf4j.Slf4j;
import org.dromara.visor.framework.log.core.annotation.IgnoreLog;
import org.dromara.visor.framework.log.core.enums.IgnoreLogMode;
import org.dromara.visor.framework.web.core.annotation.RestWrapper;
import org.dromara.visor.module.infra.entity.vo.InfraWorkplaceStatisticsVO;
import org.dromara.visor.module.infra.service.InfraStatisticsService;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
/**
* infra - 统计服务
*
* @author Jiahang Li
* @version 1.0.0
* @since 2024/12/23 16:07
*/
@Tag(name = "infra - 统计服务")
@Slf4j
@Validated
@RestWrapper
@RestController
@RequestMapping("/infra/statistics")
public class InfraStatisticsController {
@Resource
private InfraStatisticsService infraStatisticsService;
@IgnoreLog(IgnoreLogMode.RET)
@GetMapping("/get-workplace")
@Operation(summary = "查询工作台统计信息")
public InfraWorkplaceStatisticsVO getWorkplaceStatisticsData() {
return infraStatisticsService.getWorkplaceStatisticsData();
}
}

View File

@@ -24,9 +24,12 @@ package org.dromara.visor.module.infra.dao;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.dromara.visor.framework.mybatis.core.mapper.IMapper;
import org.dromara.visor.module.infra.entity.domain.OperatorLogDO;
import org.dromara.visor.module.infra.entity.po.OperatorLogCountPO;
import java.util.Date;
import java.util.List;
/**
@@ -63,4 +66,16 @@ public interface OperatorLogDAO extends IMapper<OperatorLogDO> {
return this.delete(wrapper);
}
/**
* 查询操作日志数量
*
* @param userId userId
* @param startTime startTime
* @param endTime endTime
* @return rows
*/
List<OperatorLogCountPO> selectOperatorLogCount(@Param("userId") Long userId,
@Param("startTime") Date startTime,
@Param("endTime") Date endTime);
}

View File

@@ -0,0 +1,49 @@
/*
* Copyright (c) 2023 - present Dromara, All rights reserved.
*
* https://visor.dromara.org
* https://visor.dromara.org.cn
* https://visor.orionsec.cn
*
* Members:
* Jiahang Li - ljh1553488six@139.com - author
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.dromara.visor.module.infra.define.cache;
import cn.orionsec.kit.lang.define.cache.key.CacheKeyBuilder;
import cn.orionsec.kit.lang.define.cache.key.CacheKeyDefine;
import cn.orionsec.kit.lang.define.cache.key.struct.RedisCacheStruct;
import org.dromara.visor.module.infra.entity.vo.InfraWorkplaceStatisticsVO;
import java.util.concurrent.TimeUnit;
/**
* infra 模块统计缓存 key
*
* @author Jiahang Li
* @version 1.0.0
* @since 2024/12/23 16:10
*/
public interface InfraStatisticsCacheKeyDefine {
CacheKeyDefine WORKPLACE_DATA = new CacheKeyBuilder()
.key("data:statistics:infra-workplace:{}:{}")
.desc("基建模块工作台统计 ${userId} ${time}")
.type(InfraWorkplaceStatisticsVO.class)
.struct(RedisCacheStruct.STRING)
.timeout(10, TimeUnit.MINUTES)
.build();
}

View File

@@ -0,0 +1,58 @@
/*
* Copyright (c) 2023 - present Dromara, All rights reserved.
*
* https://visor.dromara.org
* https://visor.dromara.org.cn
* https://visor.orionsec.cn
*
* Members:
* Jiahang Li - ljh1553488six@139.com - author
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.dromara.visor.module.infra.entity.po;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
/**
* 操作日志数量
*
* @author Jiahang Li
* @version 1.0.0
* @since 2024/12/23 16:24
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Schema(name = "OperatorLogCountPO", description = "操作日志数量")
public class OperatorLogCountPO implements Serializable {
private static final long serialVersionUID = 1L;
@Schema(description = "操作日期")
private String operatorDate;
@Schema(description = "结果")
private Integer result;
@Schema(description = "操作次数")
private Integer count;
}

View File

@@ -0,0 +1,73 @@
/*
* Copyright (c) 2023 - present Dromara, All rights reserved.
*
* https://visor.dromara.org
* https://visor.dromara.org.cn
* https://visor.orionsec.cn
*
* Members:
* Jiahang Li - ljh1553488six@139.com - author
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.dromara.visor.module.infra.entity.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.dromara.visor.framework.common.entity.chart.LineSingleChartData;
import java.util.Date;
import java.util.List;
/**
* 基建模块工作台统计响应
*
* @author Jiahang Li
* @version 1.0.0
* @since 2024/12/23 17:38
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Schema(name = "InfraWorkplaceStatisticsVO", description = "基建模块工作台统计响应")
public class InfraWorkplaceStatisticsVO {
@Schema(description = "userId")
private Long userId;
@Schema(description = "用户名")
private String username;
@Schema(description = "花名")
private String nickname;
@Schema(description = "未读消息数量")
private Integer unreadMessageCount;
@Schema(description = "上次登录时间")
private Date lastLoginTime;
@Schema(description = "当前登录会话数量")
private Integer userSessionCount;
@Schema(description = "系统操作数量图表")
private LineSingleChartData operatorChart;
@Schema(description = "用户登录日志")
private List<LoginHistoryVO> loginHistoryList;
}

View File

@@ -23,7 +23,7 @@
package org.dromara.visor.module.infra.enums;
import lombok.Getter;
import org.dromara.visor.framework.common.handler.data.GenericsDataDefinition;
import org.dromara.visor.framework.common.handler.data.GenericsStrategyDefinition;
import org.dromara.visor.framework.common.handler.data.model.GenericsDataModel;
import org.dromara.visor.framework.common.handler.data.strategy.GenericsDataStrategy;
import org.dromara.visor.module.infra.handler.preference.strategy.SystemPreferenceStrategy;
@@ -37,7 +37,7 @@ import org.dromara.visor.module.infra.handler.preference.strategy.TerminalPrefer
* @since 2023/10/8 11:31
*/
@Getter
public enum PreferenceTypeEnum implements GenericsDataDefinition {
public enum PreferenceTypeEnum implements GenericsStrategyDefinition {
/**
* 系统偏好

View File

@@ -24,7 +24,7 @@ package org.dromara.visor.module.infra.enums;
import lombok.AllArgsConstructor;
import lombok.Getter;
import org.dromara.visor.framework.common.handler.data.GenericsDataDefinition;
import org.dromara.visor.framework.common.handler.data.GenericsStrategyDefinition;
import org.dromara.visor.framework.common.handler.data.model.GenericsDataModel;
import org.dromara.visor.framework.common.handler.data.strategy.GenericsDataStrategy;
import org.dromara.visor.module.infra.handler.setting.strategy.SftpSystemSettingStrategy;
@@ -38,7 +38,7 @@ import org.dromara.visor.module.infra.handler.setting.strategy.SftpSystemSetting
*/
@Getter
@AllArgsConstructor
public enum SystemSettingTypeEnum implements GenericsDataDefinition {
public enum SystemSettingTypeEnum implements GenericsStrategyDefinition {
/**
* SFTP 配置

View File

@@ -0,0 +1,52 @@
/*
* Copyright (c) 2023 - present Dromara, All rights reserved.
*
* https://visor.dromara.org
* https://visor.dromara.org.cn
* https://visor.orionsec.cn
*
* Members:
* Jiahang Li - ljh1553488six@139.com - author
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.dromara.visor.module.infra.service;
import org.dromara.visor.framework.common.entity.chart.LineSingleChartData;
import org.dromara.visor.module.infra.entity.vo.InfraWorkplaceStatisticsVO;
/**
* 基建模块统计服务
*
* @author Jiahang Li
* @version 1.0.0
* @since 2024/12/23 16:52
*/
public interface InfraStatisticsService {
/**
* 查询工作台统计信息
*
* @return data
*/
InfraWorkplaceStatisticsVO getWorkplaceStatisticsData();
/**
* 获取用户操作日志图表
*
* @param userId userId
* @return data
*/
LineSingleChartData getUserOperatorLogChart(Long userId);
}

View File

@@ -62,6 +62,14 @@ public interface SystemMessageService {
*/
Map<String, Integer> getSystemMessageCount(Boolean queryUnread);
/**
* 查询未读消息数量
*
* @param receiverId receiverId
* @return count
*/
Integer getUnreadSystemMessageCount(Long receiverId);
/**
* 查询是否有未读消息
*

View File

@@ -36,6 +36,14 @@ import java.util.List;
*/
public interface SystemUserManagementService {
/**
* 获取用户会话数量
*
* @param userId userId
* @return count
*/
Integer getUserSessionCount(Long userId);
/**
* 获取用户会话列表
*

View File

@@ -0,0 +1,139 @@
/*
* Copyright (c) 2023 - present Dromara, All rights reserved.
*
* https://visor.dromara.org
* https://visor.dromara.org.cn
* https://visor.orionsec.cn
*
* Members:
* Jiahang Li - ljh1553488six@139.com - author
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.dromara.visor.module.infra.service.impl;
import cn.orionsec.kit.lang.utils.time.Dates;
import lombok.extern.slf4j.Slf4j;
import org.dromara.visor.framework.common.entity.StatisticsRange;
import org.dromara.visor.framework.common.entity.chart.LineSingleChartData;
import org.dromara.visor.framework.redis.core.utils.RedisStrings;
import org.dromara.visor.framework.security.core.utils.SecurityUtils;
import org.dromara.visor.module.infra.dao.OperatorLogDAO;
import org.dromara.visor.module.infra.dao.SystemUserDAO;
import org.dromara.visor.module.infra.define.cache.InfraStatisticsCacheKeyDefine;
import org.dromara.visor.module.infra.entity.domain.SystemUserDO;
import org.dromara.visor.module.infra.entity.po.OperatorLogCountPO;
import org.dromara.visor.module.infra.entity.vo.InfraWorkplaceStatisticsVO;
import org.dromara.visor.module.infra.entity.vo.LoginHistoryVO;
import org.dromara.visor.module.infra.service.InfraStatisticsService;
import org.dromara.visor.module.infra.service.OperatorLogService;
import org.dromara.visor.module.infra.service.SystemMessageService;
import org.dromara.visor.module.infra.service.SystemUserManagementService;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
/**
* 基建模块统计服务实现类
*
* @author Jiahang Li
* @version 1.0.0
* @since 2024/12/23 16:52
*/
@Slf4j
@Service
public class InfraStatisticsServiceImpl implements InfraStatisticsService {
@Resource
private SystemUserDAO systemUserDAO;
@Resource
private OperatorLogDAO operatorLogDAO;
@Resource
private OperatorLogService operatorLogService;
@Resource
private SystemMessageService systemMessageService;
@Resource
private SystemUserManagementService systemUserManagementService;
@Override
public InfraWorkplaceStatisticsVO getWorkplaceStatisticsData() {
Long userId = SecurityUtils.getLoginUserId();
// 读取缓存
String cacheKey = InfraStatisticsCacheKeyDefine.WORKPLACE_DATA.format(userId, Dates.current(Dates.YMD2));
InfraWorkplaceStatisticsVO data = RedisStrings.getJson(cacheKey, InfraStatisticsCacheKeyDefine.WORKPLACE_DATA);
if (data == null) {
// 查询用户操作日志图表
LineSingleChartData operatorChart = this.getUserOperatorLogChart(userId);
data = InfraWorkplaceStatisticsVO.builder()
.userId(userId)
.operatorChart(operatorChart)
.build();
// 设置缓存
RedisStrings.setJson(cacheKey, InfraStatisticsCacheKeyDefine.WORKPLACE_DATA, data);
}
// 查询用户信息
SystemUserDO user = systemUserDAO.of()
.createWrapper()
.select(SystemUserDO::getId,
SystemUserDO::getUsername,
SystemUserDO::getNickname,
SystemUserDO::getLastLoginTime)
.eq(SystemUserDO::getId, userId)
.then()
.getOne();
data.setUsername(user.getUsername());
data.setNickname(user.getNickname());
data.setLastLoginTime(user.getLastLoginTime());
// 查询未读消息数量
Integer unreadMessageCount = systemMessageService.getUnreadSystemMessageCount(userId);
data.setUnreadMessageCount(unreadMessageCount);
// 查询当前登录会话数量
Integer userSessionCount = systemUserManagementService.getUserSessionCount(userId);
data.setUserSessionCount(userSessionCount);
// 查询用户登录日志
List<LoginHistoryVO> loginHistoryList = operatorLogService.getLoginHistory(user.getUsername(), 10);
data.setLoginHistoryList(loginHistoryList);
return data;
}
@Override
public LineSingleChartData getUserOperatorLogChart(Long userId) {
Date endTime = new Date();
Date startTime = Dates.stream()
.clearHms()
.addDay(-6)
.get();
List<String> rangeDays = StatisticsRange.WEEK.getDateRanges(startTime);
// 查询操作数量
Map<String, Integer> countMap = operatorLogDAO.selectOperatorLogCount(userId, startTime, endTime)
.stream()
.collect(Collectors.toMap(OperatorLogCountPO::getOperatorDate, OperatorLogCountPO::getCount));
// 构建每天的数据
List<Integer> data = rangeDays.stream()
.map(s -> countMap.getOrDefault(s, 0))
.collect(Collectors.toList());
return LineSingleChartData.builder()
.x(rangeDays)
.data(data)
.build();
}
}

View File

@@ -164,9 +164,7 @@ public class PreferenceServiceImpl implements PreferenceService {
public Map<String, Object> getDefaultPreferenceByType(String type, List<String> items) {
PreferenceTypeEnum preferenceType = Valid.valid(PreferenceTypeEnum::of, type);
// 获取默认值
Map<String, Object> defaultModel = preferenceType.getStrategy()
.getDefault()
.toMap();
Map<String, Object> defaultModel = preferenceType.getDefault().toMap();
Map<String, Object> result = Maps.newMap();
if (Lists.isEmpty(items)) {
defaultModel.forEach((k, v) -> result.put(k, defaultModel.get(k)));
@@ -240,9 +238,7 @@ public class PreferenceServiceImpl implements PreferenceService {
// 初始化
if (Maps.isEmpty(config)) {
// 获取默认值
Map<String, Object> defaultConfig = type.getStrategy()
.getDefault()
.toMap();
Map<String, Object> defaultConfig = type.getDefault().toMap();
config = Maps.map(defaultConfig, Function.identity(), Refs::json);
// 插入默认值
List<PreferenceDO> entities = config

View File

@@ -114,6 +114,17 @@ public class SystemMessageServiceImpl implements SystemMessageService {
Functions.right()));
}
@Override
public Integer getUnreadSystemMessageCount(Long receiverId) {
return systemMessageDAO.of()
.createWrapper()
.eq(SystemMessageDO::getReceiverId, receiverId)
.eq(SystemMessageDO::getStatus, MessageStatusEnum.UNREAD.getStatus())
.then()
.count()
.intValue();
}
@Override
public Boolean checkHasUnreadMessage() {
// 查询

View File

@@ -148,9 +148,7 @@ public class SystemSettingServiceImpl implements SystemSettingService {
// 初始化
if (Maps.isEmpty(settings)) {
// 获取默认值
Map<String, Object> defaultConfig = settingType.getStrategy()
.getDefault()
.toMap();
Map<String, Object> defaultConfig = settingType.getDefault().toMap();
settings = Maps.map(defaultConfig, Function.identity(), Refs::json);
// 插入默认值
List<SystemSettingDO> entities = settings

View File

@@ -62,6 +62,13 @@ public class SystemUserManagementServiceImpl implements SystemUserManagementServ
@Resource
private SystemUserDAO systemUserDAO;
@Override
public Integer getUserSessionCount(Long userId) {
// 扫描缓存
Set<String> keys = RedisStrings.scanKeys(UserCacheKeyDefine.LOGIN_TOKEN.format(userId, "*"));
return Lists.size(keys);
}
@Override
public List<UserSessionVO> getUserSessionList(Long userId) {
// 扫描缓存

View File

@@ -26,9 +26,25 @@
<result column="deleted" property="deleted"/>
</resultMap>
<!-- 操作数量映射结果 -->
<resultMap id="CountResultMap" type="org.dromara.visor.module.infra.entity.po.OperatorLogCountPO">
<result column="operator_date" property="operatorDate"/>
<result column="result" property="result"/>
<result column="total_count" property="count"/>
</resultMap>
<!-- 通用查询结果列 -->
<sql id="Base_Column_List">
id, user_id, username, trace_id, address, location, user_agent, risk_level, module, type, log_info, extra, result, error_message, return_value, duration, start_time, end_time, create_time, deleted
</sql>
<select id="selectOperatorLogCount" resultMap="CountResultMap">
SELECT DATE(create_time) operator_date, COUNT(1) total_count
FROM operator_log
WHERE deleted = 0
AND user_id = #{userId}
AND create_time BETWEEN #{startTime} AND #{endTime}
GROUP BY operator_date
</select>
</mapper>

View File

@@ -1,3 +1,3 @@
VITE_API_BASE_URL= 'http://127.0.0.1:9200/orion-visor/api'
VITE_WS_BASE_URL= 'ws://127.0.0.1:9200/orion-visor/keep-alive'
VITE_APP_VERSION= '2.2.2'
VITE_APP_VERSION= '2.2.3'

View File

@@ -1,3 +1,3 @@
VITE_API_BASE_URL= '/orion-visor/api'
VITE_WS_BASE_URL= '/orion-visor/keep-alive'
VITE_APP_VERSION= '2.2.2'
VITE_APP_VERSION= '2.2.3'

View File

@@ -1,7 +1,7 @@
{
"name": "orion-visor-ui",
"description": "Orion Visor UI",
"version": "2.2.2",
"version": "2.2.3",
"private": true,
"author": "Jiahang Li",
"license": "Apache 2.0",

View File

@@ -1,6 +1,8 @@
<template>
<a-config-provider :locale="locale">
<!-- 路由 -->
<router-view />
<!-- 应用设置 -->
<app-setting ref="appSettingRef" />
</a-config-provider>
</template>

View File

@@ -48,6 +48,7 @@ export interface ExecJobQueryRequest extends Pagination {
name?: string;
command?: string;
status?: number;
execUserId?: number;
queryRecentLog?: boolean;
}

View File

@@ -69,8 +69,10 @@ axios.interceptors.response.use(
if (!responseUrl || !responseUrl.includes('/logout')) {
await useUserStore().logout();
}
// 重新加载自动跳转登录页面
window.location.reload();
// 非登录页面就重新加载, 会自动跳转登录页面
if (!window.location.pathname.includes('/login')) {
window.location.reload();
}
});
} else {
// 其他异常 判断是否弹出错误信息

View File

@@ -0,0 +1,26 @@
import type { LineSingleChartData } from '@/types/global';
import type { TerminalConnectLogQueryResponse } from '@/api/asset/terminal-connect-log';
import type { ExecLogQueryResponse } from '@/api/exec/exec-log';
import axios from 'axios';
/**
* 资产模块工作台响应
*/
export interface AssetWorkplaceStatisticsResponse {
execJobCount: number;
todayTerminalConnectCount: number;
todayExecCommandCount: number;
weekTerminalConnectCount: number;
weekExecCommandCount: number;
execCommandChart: LineSingleChartData;
terminalConnectChart: LineSingleChartData;
terminalConnectList: Array<TerminalConnectLogQueryResponse>;
execLogList: Array<ExecLogQueryResponse>;
}
/**
* 查询资产模块工作台统计信息
*/
export function getAssetWorkplaceStatisticsData() {
return axios.get<AssetWorkplaceStatisticsResponse>('/asset/statistics/get-workplace');
}

View File

@@ -0,0 +1,24 @@
import type { LineSingleChartData } from '@/types/global';
import type { LoginHistoryQueryResponse } from '@/api/user/user';
import axios from 'axios';
/**
* 基建模块工作台响应
*/
export interface InfraWorkplaceStatisticsResponse {
userId: number;
username: string;
nickname: string;
unreadMessageCount: number;
lastLoginTime: number;
userSessionCount: number;
operatorChart: LineSingleChartData;
loginHistoryList: Array<LoginHistoryQueryResponse>;
}
/**
* 查询基建模块工作台统计信息
*/
export function getInfraWorkplaceStatisticsData() {
return axios.get<InfraWorkplaceStatisticsResponse>('/infra/statistics/get-workplace');
}

View File

@@ -1,4 +1,5 @@
import axios from 'axios';
import { dateFormat } from '@/utils';
/**
* 系统配置类型
@@ -63,7 +64,7 @@ export function getSystemAppInfo() {
* 获取应用最新版本信息
*/
export function getAppLatestRelease() {
return axios.get<AppReleaseResponse>('https://visor.orionsec.cn/releases-latest.json', {
return axios.get<AppReleaseResponse>(`https://visor.orionsec.cn/releases-latest.json?${dateFormat(new Date(), 'yyyyMMddHH')}`, {
// 不添加请求头 否则会报 401
setAuthorization: false,
// 返回原始输出

View File

@@ -72,6 +72,11 @@ body {
align-items: center;
}
.flex-content-end {
display: flex;
justify-content: flex-end;
}
// -- click-icon
.click-icon-wrapper {
display: flex;

View File

@@ -532,8 +532,14 @@ body[terminal-theme='dark'] .arco-modal-container {
.terminal-setting-container {
padding: 32px 16px 16px 16px;
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
overflow: auto;
&::-webkit-scrollbar {
display: none;
}
.terminal-setting-wrapper {
min-width: 932px;

View File

@@ -9,7 +9,8 @@
<a-link target="_blank" :href="`https://github.com/dromara/orion-visor/releases/tag/v${version}`">v{{ version }}</a-link>
</a-space>
<span class="copyright">
Copyright<icon-copyright /> 2023 - {{ new Date().getFullYear() }} Jiahang Li, All rights reserved.
Copyright<icon-copyright /> 2023 - {{ new Date().getFullYear() }} <a href="https://dromara.org">Dromara</a>, All rights reserved. Designed by
<a href="https://orionsec.cn" target="_blank">Jiahang Li.</a>
</span>
</a-space>
</a-layout-footer>

View File

@@ -325,6 +325,8 @@
}
};
defineExpose({ setMessageBoxVisible });
onMounted(() => {
// 查询未读消息
pullHasUnreadMessage();

View File

@@ -16,7 +16,7 @@
<script lang="ts" setup>
import type { TreeNodeData } from '@arco-design/web-vue';
import { computed, onBeforeMount, ref } from 'vue';
import { computed, onActivated, onMounted, ref } from 'vue';
import { useCacheStore } from '@/store';
import useLoading from '@/hooks/loading';
@@ -44,7 +44,7 @@
const treeData = ref<Array<TreeNodeData>>([]);
// 初始化选项
onBeforeMount(async () => {
const initOptions = async () => {
setLoading(true);
try {
treeData.value = await cacheStore.loadHostGroups();
@@ -52,7 +52,11 @@
} finally {
setLoading(false);
}
});
};
// 初始化选项
onMounted(initOptions);
onActivated(initOptions);
</script>

View File

@@ -29,7 +29,7 @@
<script lang="ts" setup>
import type { SelectOptionData } from '@arco-design/web-vue';
import { computed, onBeforeMount, ref } from 'vue';
import { computed, onActivated, onMounted, ref } from 'vue';
import { useCacheStore } from '@/store';
import useLoading from '@/hooks/loading';
@@ -60,7 +60,7 @@
const optionData = ref<Array<SelectOptionData>>([]);
// 初始化选项
onBeforeMount(async () => {
const initOptions = async () => {
setLoading(true);
try {
const hostIdentities = props.authorized
@@ -77,7 +77,11 @@
} finally {
setLoading(false);
}
});
};
// 初始化选项
onMounted(initOptions);
onActivated(initOptions);
</script>

View File

@@ -14,7 +14,7 @@
<script lang="ts" setup>
import type { SelectOptionData } from '@arco-design/web-vue';
import { computed, onBeforeMount, ref } from 'vue';
import { computed, onActivated, onMounted, ref } from 'vue';
import { useCacheStore } from '@/store';
import useLoading from '@/hooks/loading';
@@ -45,7 +45,7 @@
const optionData = ref<Array<SelectOptionData>>([]);
// 初始化选项
onBeforeMount(async () => {
const initOptions = async () => {
setLoading(true);
try {
const hostKeys = props.authorized
@@ -61,7 +61,12 @@
} finally {
setLoading(false);
}
});
};
// 初始化选项
onMounted(initOptions);
onActivated(initOptions);
</script>
<style lang="less" scoped>

View File

@@ -16,7 +16,7 @@
<script lang="ts" setup>
import type { SelectOptionData } from '@arco-design/web-vue';
import type { HostType } from '@/api/asset/host';
import { computed, onBeforeMount, ref } from 'vue';
import { computed, onActivated, onMounted, ref } from 'vue';
import { useCacheStore } from '@/store';
import useLoading from '@/hooks/loading';
@@ -51,7 +51,7 @@
const optionData = ref<Array<SelectOptionData>>([]);
// 初始化选项
onBeforeMount(async () => {
const initOptions = async () => {
setLoading(true);
try {
const hosts = await cacheStore.loadHosts(props.type);
@@ -66,7 +66,11 @@
} finally {
setLoading(false);
}
});
};
// 初始化选项
onMounted(initOptions);
onActivated(initOptions);
</script>

View File

@@ -17,7 +17,7 @@
<script lang="ts" setup>
import type { SelectOptionData } from '@arco-design/web-vue';
import { computed, onBeforeMount, ref } from 'vue';
import { computed, onActivated, onMounted, ref } from 'vue';
import { useCacheStore } from '@/store';
import { labelFilter } from '@/types/form';
import useLoading from '@/hooks/loading';
@@ -57,7 +57,7 @@
const optionData = ref<Array<SelectOptionData>>([]);
// 初始化选项
onBeforeMount(async () => {
const initOptions = async () => {
setLoading(true);
try {
const dictKeys = await cacheStore.loadExecJobs();
@@ -71,7 +71,11 @@
} finally {
setLoading(false);
}
});
};
// 初始化选项
onMounted(initOptions);
onActivated(initOptions);
</script>

View File

@@ -26,7 +26,7 @@
<script lang="ts" setup>
import type { SelectOptionData } from '@arco-design/web-vue';
import type { TagCreateRequest } from '@/api/meta/tag';
import { ref, computed, onBeforeMount } from 'vue';
import { ref, computed, onMounted, onActivated } from 'vue';
import { useCacheStore } from '@/store';
import { dataColor } from '@/utils';
import { createTag } from '@/api/meta/tag';
@@ -113,7 +113,7 @@
};
// 初始化选项
onBeforeMount(async () => {
const initOptions = async () => {
setLoading(true);
try {
const tags = await cacheStore.loadTags(props.type as string);
@@ -130,7 +130,11 @@
} finally {
setLoading(false);
}
});
};
// 初始化选项
onMounted(initOptions);
onActivated(initOptions);
</script>

View File

@@ -17,7 +17,7 @@
<script lang="ts" setup>
import type { SelectOptionData } from '@arco-design/web-vue';
import { computed, onBeforeMount, ref } from 'vue';
import { computed, onActivated, onMounted, ref } from 'vue';
import { useCacheStore } from '@/store';
import { labelFilter } from '@/types/form';
import useLoading from '@/hooks/loading';
@@ -59,7 +59,7 @@
const optionData = ref<Array<SelectOptionData>>([]);
// 初始化选项
onBeforeMount(async () => {
const initOptions = async () => {
setLoading(true);
try {
const dictKeys = await cacheStore.loadDictKeys();
@@ -74,7 +74,11 @@
} finally {
setLoading(false);
}
});
};
// 初始化选项
onMounted(initOptions);
onActivated(initOptions);
</script>

View File

@@ -22,7 +22,13 @@
</td>
<!-- 子菜单 -->
<td>
<a-checkbox :value="childrenMenu.id">
<!-- 默认路由 -->
<a-checkbox v-if="childrenMenu.component === DEFAULT_ROUTE_NAME"
:value="childrenMenu.id">
{{ childrenMenu.name }} <span class="span-red">(必选)</span>
</a-checkbox>
<!-- 普通子菜单 -->
<a-checkbox v-else :value="childrenMenu.id">
{{ childrenMenu.name }}
</a-checkbox>
</td>
@@ -73,6 +79,7 @@
import type { MenuQueryResponse } from '@/api/system/menu';
import { useCacheStore } from '@/store';
import { ref, watch } from 'vue';
import { DEFAULT_ROUTE_NAME } from '@/router/constants';
import { findNode, flatNodeKeys, flatNodes } from '@/utils/tree';
const cacheStore = useCacheStore();

View File

@@ -17,7 +17,7 @@
<script lang="ts" setup>
import type { TreeNodeData } from '@arco-design/web-vue';
import { useCacheStore } from '@/store';
import { computed, onBeforeMount, ref } from 'vue';
import { computed, onActivated, onMounted, ref } from 'vue';
import { MenuType } from '@/views/system/menu/types/const';
import { titleFilter } from '@/types/form';
import useLoading from '@/hooks/loading';
@@ -39,11 +39,16 @@
return props.modelValue;
},
set(e) {
emits('update:modelValue', e);
if (e) {
emits('update:modelValue', e);
} else {
emits('update:modelValue', null);
}
}
});
onBeforeMount(async () => {
// 初始化选项
const initOptions = async () => {
let render = (arr: any[]): TreeNodeData[] => {
return arr.map((s) => {
// 为 function 返回空
@@ -77,7 +82,11 @@
} finally {
setLoading(false);
}
});
};
// 初始化选项
onMounted(initOptions);
onActivated(initOptions);
</script>

View File

@@ -30,6 +30,7 @@
<a-button class="header-button"
type="text"
size="small"
title="清空全部已读消息"
@click="clearAllMessage">
清空
</a-button>

View File

@@ -17,7 +17,7 @@
<script lang="ts" setup>
import type { SelectOptionData } from '@arco-design/web-vue';
import { computed, onBeforeMount, ref } from 'vue';
import { computed, onActivated, onMounted, ref } from 'vue';
import { useCacheStore } from '@/store';
import { RoleStatus } from '@/views/user/role/types/const';
import { labelFilter } from '@/types/form';
@@ -40,13 +40,21 @@
return props.modelValue;
},
set(e) {
emits('update:modelValue', e);
if (e) {
emits('update:modelValue', e);
} else {
if (props.multiple) {
emits('update:modelValue', []);
} else {
emits('update:modelValue', null);
}
}
}
});
const optionData = ref<Array<SelectOptionData>>([]);
// 初始化选项
onBeforeMount(async () => {
const initOptions = async () => {
setLoading(true);
try {
const roles = await cacheStore.loadRoles();
@@ -61,7 +69,11 @@
} finally {
setLoading(false);
}
});
};
// 初始化选项
onMounted(initOptions);
onActivated(initOptions);
</script>

View File

@@ -17,7 +17,7 @@
<script lang="ts" setup>
import type { SelectOptionData } from '@arco-design/web-vue';
import { computed, ref, onMounted } from 'vue';
import { computed, ref, onMounted, onActivated } from 'vue';
import { useCacheStore } from '@/store';
import { labelFilter } from '@/types/form';
import useLoading from '@/hooks/loading';
@@ -39,13 +39,21 @@
return props.modelValue;
},
set(e) {
emits('update:modelValue', e);
if (e) {
emits('update:modelValue', e);
} else {
if (props.multiple) {
emits('update:modelValue', []);
} else {
emits('update:modelValue', null);
}
}
}
});
const optionData = ref<Array<SelectOptionData>>([]);
// 初始化选项
onMounted(async () => {
const initOptions = async () => {
setLoading(true);
try {
// 加载用户列表
@@ -60,7 +68,11 @@
} finally {
setLoading(false);
}
});
};
// 初始化选项
onMounted(initOptions);
onActivated(initOptions);
</script>

View File

@@ -7,16 +7,16 @@
<script lang="ts" setup>
import type { EChartsOption } from 'echarts';
import { computed, nextTick, ref } from 'vue';
import { nextTick, ref } from 'vue';
import { useAppStore } from '@/store';
import VCharts from 'vue-echarts';
const props = withDefaults(defineProps<{
options: EChartsOption,
autoResize: boolean,
width: string,
height: string,
}>(), {
const props = withDefaults(defineProps<Partial<{
options: EChartsOption;
autoResize: boolean;
width: string;
height: string;
}>>(), {
options: () => {
return {};
},
@@ -27,12 +27,6 @@
const appStore = useAppStore();
// 监听暗色模式
const theme = computed(() => {
if (appStore.theme === 'dark') return 'dark';
return '';
});
const renderChart = ref(false);
nextTick(() => {

View File

@@ -1,25 +1,34 @@
import type { EChartsOption } from 'echarts';
import { computed } from 'vue';
import { useAppStore } from '@/store';
import useThemes from '@/hooks/themes';
// for code hints
// import { SeriesOption } from 'echarts';
// Because there are so many configuration items, this provides a relatively convenient code hint.
// When using vue, pay attention to the reactive issues. It is necessary to ensure that corresponding functions can be triggered, TypeScript does not report errors, and code writing is convenient.
interface optionsFn {
(isDark: boolean): EChartsOption;
// 配置生成
interface OptionsFn {
(isDark: boolean, themeTextColor: string, themeLineColor: string): EChartsOption;
}
export default function useChartOption(sourceOption: optionsFn) {
const appStore = useAppStore();
const isDark = computed(() => {
return appStore.theme === 'dark';
});
// echarts support https://echarts.apache.org/zh/theme-builder.html
// It's not used here
// 亮色文本色
const lightTextColor = '#4E5969';
// 暗色文本色
const darkTextColor = 'rgba(255, 255, 255, 0.7)';
// 亮色线色
const lightLineColor = '#F2F3F5';
// 暗色线色
const darkLineColor = '#2E2E30';
export default function useChartOption(sourceOption: OptionsFn) {
const { isDark } = useThemes();
// 配置
const chartOption = computed<EChartsOption>(() => {
return sourceOption(isDark.value);
return sourceOption(isDark.value,
isDark.value ? darkTextColor : lightTextColor,
isDark.value ? darkLineColor : lightLineColor);
});
return {
chartOption,
};

View File

@@ -2,7 +2,7 @@
<a-layout class="layout" :class="{ mobile: appStore.hideMenu }">
<!-- tab bar -->
<div v-if="navbar" class="layout-navbar">
<nav-bar />
<nav-bar ref="navRef" />
</div>
<a-layout style="flex-direction: row;">
<!-- 左侧菜单栏 -->
@@ -58,7 +58,7 @@
import { useRoute, useRouter } from 'vue-router';
import { useAppStore, useUserStore } from '@/store';
import useResponsive from '@/hooks/responsive';
import { toggleDrawerMenuKey } from '@/types/symbol';
import { openMessageBox, toggleDrawerMenuKey } from '@/types/symbol';
import PageLayout from './page-layout.vue';
import NavBar from '@/components/app/navbar/index.vue';
import TabBar from '@/components/app/tab-bar/index.vue';
@@ -71,7 +71,8 @@
const route = useRoute();
useResponsive(true);
const isInit = ref(false);
const render = ref(false);
const navRef = ref();
const navbarHeight = `60px`;
const navbar = computed(() => appStore.navbar);
const renderMenu = computed(() => appStore.menu && !appStore.topMenu);
@@ -95,7 +96,7 @@
// 设置菜单展开状态
const setCollapsed = (val: boolean) => {
if (!isInit.value) return;
if (!render.value) return;
appStore.updateSettings({ menuCollapse: val });
};
@@ -109,8 +110,13 @@
drawerVisible.value = !drawerVisible.value;
});
// 对外暴露打开消息盒子
provide(openMessageBox, () => {
navRef.value.setMessageBoxVisible();
});
onMounted(() => {
isInit.value = true;
render.value = true;
});
</script>

View File

@@ -1,11 +1,12 @@
// 缓存类型
export type CacheType = 'users' | 'menus' | 'roles'
| 'hostGroups' | 'hostKeys' | 'hostIdentities'
| 'hostGroups' | 'hostKeys' | 'hostIdentities' | 'host_*'
| 'dictKeys'
| 'execJob'
| 'authorizedHostKeys' | 'authorizedHostIdentities'
| 'commandSnippetGroups' | 'pathBookmarkGroups'
| 'commandSnippets' | 'pathBookmarks'
| 'execJob'
| '*_Tags' | 'preference_*' | 'system_setting_*' | 'footer_setting'
| string
export interface CacheState {

View File

@@ -189,7 +189,7 @@ export default defineStore('terminal', {
// 添加到最近连接
this.hosts.latestHosts = [...new Set([tab.hostId, ...this.hosts.latestHosts])];
// 重新打开会话
await this.sessionManager.reOpenSession(tab.type, sessionId, newSessionId);
await this.sessionManager.reOpenSession(sessionId, newSessionId);
},
// 复制并且打开会话

View File

@@ -0,0 +1,88 @@
import type { LineSeriesOption } from 'echarts';
/**
* 折线图系列定义
*/
export interface LineSeriesColor {
lineColor: string;
itemBorderColor: string;
}
/**
* 折线图系列常量
*/
export const LineSeriesColors: Record<string, LineSeriesColor> = {
BLUE: {
lineColor: '#4263EB',
itemBorderColor: '#DBE4FF',
},
CYAN: {
lineColor: '#1098AD',
itemBorderColor: '#C5F6FA',
},
GREEN: {
lineColor: '#37B24D',
itemBorderColor: '#D3F9D8',
},
PURPLE: {
lineColor: '#AE3EC9',
itemBorderColor: '#F3D9FA',
},
ORANGE: {
lineColor: '#F76707',
itemBorderColor: '#FFF3BF',
},
VIOLET: {
lineColor: '#7048E8',
itemBorderColor: '#E5DBFF',
},
YELLOW: {
lineColor: '#F59F00',
itemBorderColor: '#FFF3BF',
},
TEAL: {
lineColor: '#0CA678',
itemBorderColor: '#C3FAE8',
},
RED: {
lineColor: '#F03E3E',
itemBorderColor: '#FFE3E3',
}
};
/**
* 生成折线图系列
*/
export const createLineSeries = (name: string,
lineColor: string,
itemBorderColor: string,
data: number[]): LineSeriesOption => {
return {
name,
data,
type: 'line',
smooth: true,
symbol: 'circle',
symbolSize: 10,
itemStyle: {
color: lineColor,
},
emphasis: {
focus: 'series',
itemStyle: {
color: lineColor,
borderWidth: 2,
borderColor: itemBorderColor,
},
},
lineStyle: {
width: 2,
color: lineColor,
},
showSymbol: data.length === 1,
areaStyle: {
opacity: 0.1,
color: lineColor,
},
};
};

View File

@@ -46,3 +46,8 @@ export interface DataGrid<T> {
}
export type TimeRanger = [string, string];
export interface LineSingleChartData {
x: string[];
data: Array<number>;
}

View File

@@ -1,6 +1,9 @@
// 切换菜单状态
export const toggleDrawerMenuKey = Symbol();
// 打开消息盒子
export const openMessageBox = Symbol();
// 打开偏好设置
export const openAppSettingKey = Symbol();

View File

@@ -155,11 +155,12 @@
<!-- 操作 -->
<template #handle="{ record }">
<div class="table-handle-wrapper">
<!-- 详情 -->
<a-button type="text"
<!-- 连接 -->
<a-button v-permission="['asset:terminal:access']"
type="text"
size="mini"
@click="emits('openDetail', record)">
详情
@click="openNewRoute({ name: 'terminal', query: { connect: record.hostId, type: record.type } })">
连接
</a-button>
<!-- 下线 -->
<a-popconfirm v-if="record.status === TerminalConnectStatus.CONNECTING"
@@ -174,6 +175,12 @@
下线
</a-button>
</a-popconfirm>
<!-- 详情 -->
<a-button type="text"
size="mini"
@click="emits('openDetail', record)">
详情
</a-button>
<!-- 删除 -->
<a-popconfirm content="确认删除这条记录吗?"
position="left"
@@ -204,17 +211,20 @@
import { deleteTerminalConnectLog, getTerminalConnectLogPage, hostForceOffline } from '@/api/asset/terminal-connect-log';
import { connectStatusKey, connectTypeKey, TerminalConnectStatus } from '../types/const';
import { useTablePagination, useRowSelection } from '@/hooks/table';
import { useDictStore } from '@/store';
import { useDictStore, useUserStore } from '@/store';
import { Message } from '@arco-design/web-vue';
import columns from '../types/table.columns';
import useLoading from '@/hooks/loading';
import { copy } from '@/hooks/copy';
import { useRoute } from 'vue-router';
import { dateFormat } from '@/utils';
import { openNewRoute } from '@/router';
import UserSelector from '@/components/user/user/selector/index.vue';
import HostSelector from '@/components/asset/host/selector/index.vue';
const emits = defineEmits(['openClear', 'openDetail']);
const route = useRoute();
const pagination = useTablePagination();
const rowSelection = useRowSelection();
const { loading, setLoading } = useLoading();
@@ -254,8 +264,6 @@
doFetchTableData({ page, limit, ...form });
};
defineExpose({ fetchTableData });
// 打开清空
const openClear = () => {
emits('openClear', { ...formModel, id: undefined });
@@ -283,8 +291,8 @@
await deleteTerminalConnectLog(selectedKeys.value);
Message.success(`成功删除 ${selectedKeys.value.length} 条数据`);
selectedKeys.value = [];
// 重新加载数据
fetchTableData();
// 重新加载
reload();
} catch (e) {
} finally {
setLoading(false);
@@ -299,15 +307,33 @@
await deleteTerminalConnectLog([record.id]);
Message.success('删除成功');
selectedKeys.value = [];
// 重新加载数据
fetchTableData();
// 重新加载
reload();
} catch (e) {
} finally {
setLoading(false);
}
};
// 重新加载
const reload = () => {
// 重新加载数据
fetchTableData();
};
defineExpose({ reload });
onMounted(() => {
// 当前用户
const action = route.query.action as string;
if (action === 'self') {
formModel.userId = useUserStore().id;
}
// id
const id = route.query.id as string;
if (id) {
formModel.id = Number.parseInt(id);
}
fetchTableData();
});

View File

@@ -6,7 +6,7 @@
@open-detail="(s) => detailModal.open(s)" />
<!-- 清空模态框 -->
<connect-log-clear-modal ref="clearModal"
@clear="() => table.fetchTableData()" />
@clear="() => table.reload()" />
<!-- 详情模态框 -->
<connect-log-detail-drawer ref="detailModal" />
</div>
@@ -19,8 +19,8 @@
</script>
<script lang="ts" setup>
import { ref, onBeforeMount, onUnmounted } from 'vue';
import { useCacheStore, useDictStore } from '@/store';
import { ref, onBeforeMount } from 'vue';
import { useDictStore } from '@/store';
import { dictKeys } from './types/const';
import ConnectLogTable from './components/connect-log-table.vue';
import ConnectLogClearModal from './components/connect-log-clear-modal.vue';
@@ -38,12 +38,6 @@
render.value = true;
});
// 重置缓存
onUnmounted(() => {
const cacheStore = useCacheStore();
cacheStore.reset('users');
});
</script>
<style lang="less" scoped>

View File

@@ -50,7 +50,7 @@ const columns = [
}, {
title: '操作',
slotName: 'handle',
width: 180,
width: 218,
align: 'left',
fixed: 'right',
},

View File

@@ -95,6 +95,13 @@
<!-- 操作 -->
<template #handle="{ record }">
<div class="table-handle-wrapper">
<!-- 连接 -->
<a-button v-permission="['asset:terminal:access']"
type="text"
size="mini"
@click="openNewRoute({ name: 'terminal', query: { connect: record.hostId, type: record.type } })">
连接
</a-button>
<!-- 下线 -->
<a-popconfirm content="确认要强制下线吗?"
position="left"
@@ -129,6 +136,7 @@
import columns from '../types/table.columns';
import useLoading from '@/hooks/loading';
import { copy } from '@/hooks/copy';
import { openNewRoute } from '@/router';
import UserSelector from '@/components/user/user/selector/index.vue';
import HostSelector from '@/components/asset/host/selector/index.vue';

View File

@@ -12,8 +12,8 @@
</script>
<script lang="ts" setup>
import { ref, onBeforeMount, onUnmounted } from 'vue';
import { useCacheStore, useDictStore } from '@/store';
import { ref, onBeforeMount } from 'vue';
import { useDictStore } from '@/store';
import { dictKeys } from './types/const';
import ConnectSessionTable from './components/connect-session-table.vue';
@@ -26,12 +26,6 @@
render.value = true;
});
// 重置缓存
onUnmounted(() => {
const cacheStore = useCacheStore();
cacheStore.reset('users');
});
</script>
<style lang="less" scoped>

View File

@@ -47,7 +47,7 @@ const columns = [
}, {
title: '操作',
slotName: 'handle',
width: 118,
width: 154,
align: 'center',
fixed: 'right',
},

View File

@@ -247,8 +247,8 @@
await deleteTerminalSftpLog(selectedKeys.value);
Message.success(`成功删除 ${selectedKeys.value.length} 条数据`);
selectedKeys.value = [];
// 重新加载数据
fetchTableData();
// 重新加载
reload();
} catch (e) {
} finally {
setLoading(false);
@@ -263,14 +263,20 @@
await deleteTerminalSftpLog([record.id]);
Message.success('删除成功');
selectedKeys.value = [];
// 重新加载数据
fetchTableData();
// 重新加载
reload();
} catch (e) {
} finally {
setLoading(false);
}
};
// 重新加载
const reload = () => {
// 重新加载数据
fetchTableData();
};
onMounted(() => {
fetchTableData();
});

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