Compare commits
23 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c793ebf394 | ||
|
|
45b3e0746a | ||
|
|
aaf18d79bf | ||
|
|
4e3f548ac8 | ||
|
|
cbe1eeec74 | ||
|
|
0557c41454 | ||
|
|
c2311f0682 | ||
|
|
3b89e9bf29 | ||
|
|
6b14c2ef9c | ||
|
|
6606d2ca76 | ||
|
|
1846a496c1 | ||
|
|
7996ae5b63 | ||
|
|
cb5657c685 | ||
|
|
95759adf91 | ||
|
|
4d08a18548 | ||
|
|
8f1e976c2f | ||
|
|
5ee18436b8 | ||
|
|
00ea709f9e | ||
|
|
4873d2cc91 | ||
|
|
4c306fb4f8 | ||
|
|
9017ada3aa | ||
|
|
00c4484184 | ||
|
|
5a6b1bfafd |
11
README.md
11
README.md
@@ -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
|
||||
|
||||

|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -36,7 +36,7 @@ public interface AppConst extends OrionConst {
|
||||
/**
|
||||
* 同 ${orion.version} 迭代时候需要手动更改
|
||||
*/
|
||||
String VERSION = "2.2.2";
|
||||
String VERSION = "2.2.3";
|
||||
|
||||
/**
|
||||
* 同 ${spring.application.name}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -65,4 +65,12 @@ public interface GenericsDataStrategy<M extends GenericsDataModel> {
|
||||
*/
|
||||
void toView(M model);
|
||||
|
||||
/**
|
||||
* 转为视图配置
|
||||
*
|
||||
* @param serialModel serialModel
|
||||
* @return model
|
||||
*/
|
||||
M toView(String serialModel);
|
||||
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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();
|
||||
};
|
||||
|
||||
|
||||
@@ -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();
|
||||
};
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
}
|
||||
@@ -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 额外配置
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
@@ -36,6 +36,7 @@ import java.util.Map;
|
||||
* @version 1.0.0
|
||||
* @since 2023-9-11 14:16
|
||||
*/
|
||||
// TODO 待优化
|
||||
public interface HostConfigService {
|
||||
|
||||
/**
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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()
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
/**
|
||||
* 系统偏好
|
||||
|
||||
@@ -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 配置
|
||||
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
@@ -62,6 +62,14 @@ public interface SystemMessageService {
|
||||
*/
|
||||
Map<String, Integer> getSystemMessageCount(Boolean queryUnread);
|
||||
|
||||
/**
|
||||
* 查询未读消息数量
|
||||
*
|
||||
* @param receiverId receiverId
|
||||
* @return count
|
||||
*/
|
||||
Integer getUnreadSystemMessageCount(Long receiverId);
|
||||
|
||||
/**
|
||||
* 查询是否有未读消息
|
||||
*
|
||||
|
||||
@@ -36,6 +36,14 @@ import java.util.List;
|
||||
*/
|
||||
public interface SystemUserManagementService {
|
||||
|
||||
/**
|
||||
* 获取用户会话数量
|
||||
*
|
||||
* @param userId userId
|
||||
* @return count
|
||||
*/
|
||||
Integer getUserSessionCount(Long userId);
|
||||
|
||||
/**
|
||||
* 获取用户会话列表
|
||||
*
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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() {
|
||||
// 查询
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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) {
|
||||
// 扫描缓存
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
<template>
|
||||
<a-config-provider :locale="locale">
|
||||
<!-- 路由 -->
|
||||
<router-view />
|
||||
<!-- 应用设置 -->
|
||||
<app-setting ref="appSettingRef" />
|
||||
</a-config-provider>
|
||||
</template>
|
||||
|
||||
@@ -48,6 +48,7 @@ export interface ExecJobQueryRequest extends Pagination {
|
||||
name?: string;
|
||||
command?: string;
|
||||
status?: number;
|
||||
execUserId?: number;
|
||||
queryRecentLog?: boolean;
|
||||
}
|
||||
|
||||
|
||||
@@ -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 {
|
||||
// 其他异常 判断是否弹出错误信息
|
||||
|
||||
26
orion-visor-ui/src/api/statistics/asset-statistics.ts
Normal file
26
orion-visor-ui/src/api/statistics/asset-statistics.ts
Normal 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');
|
||||
}
|
||||
24
orion-visor-ui/src/api/statistics/infra-statistics.ts
Normal file
24
orion-visor-ui/src/api/statistics/infra-statistics.ts
Normal 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');
|
||||
}
|
||||
@@ -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,
|
||||
// 返回原始输出
|
||||
|
||||
@@ -72,6 +72,11 @@ body {
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.flex-content-end {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
// -- click-icon
|
||||
.click-icon-wrapper {
|
||||
display: flex;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -325,6 +325,8 @@
|
||||
}
|
||||
};
|
||||
|
||||
defineExpose({ setMessageBoxVisible });
|
||||
|
||||
onMounted(() => {
|
||||
// 查询未读消息
|
||||
pullHasUnreadMessage();
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -30,6 +30,7 @@
|
||||
<a-button class="header-button"
|
||||
type="text"
|
||||
size="small"
|
||||
title="清空全部已读消息"
|
||||
@click="clearAllMessage">
|
||||
清空
|
||||
</a-button>
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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(() => {
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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);
|
||||
},
|
||||
|
||||
// 复制并且打开会话
|
||||
|
||||
88
orion-visor-ui/src/types/chart.ts
Normal file
88
orion-visor-ui/src/types/chart.ts
Normal 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,
|
||||
},
|
||||
};
|
||||
};
|
||||
@@ -46,3 +46,8 @@ export interface DataGrid<T> {
|
||||
}
|
||||
|
||||
export type TimeRanger = [string, string];
|
||||
|
||||
export interface LineSingleChartData {
|
||||
x: string[];
|
||||
data: Array<number>;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
// 切换菜单状态
|
||||
export const toggleDrawerMenuKey = Symbol();
|
||||
|
||||
// 打开消息盒子
|
||||
export const openMessageBox = Symbol();
|
||||
|
||||
// 打开偏好设置
|
||||
export const openAppSettingKey = Symbol();
|
||||
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -50,7 +50,7 @@ const columns = [
|
||||
}, {
|
||||
title: '操作',
|
||||
slotName: 'handle',
|
||||
width: 180,
|
||||
width: 218,
|
||||
align: 'left',
|
||||
fixed: 'right',
|
||||
},
|
||||
|
||||
@@ -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';
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -47,7 +47,7 @@ const columns = [
|
||||
}, {
|
||||
title: '操作',
|
||||
slotName: 'handle',
|
||||
width: 118,
|
||||
width: 154,
|
||||
align: 'center',
|
||||
fixed: 'right',
|
||||
},
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user