Compare commits

..

22 Commits

Author SHA1 Message Date
李佳航
318e9f30b8 Merge pull request #36 from dromara/dev
🚀 修改 e2e 配置.
2024-06-27 14:46:36 +08:00
lijiahang
39a1001510 🚀 修改 e2e 配置. 2024-06-27 14:43:59 +08:00
李佳航
5e7b7ebfa7 Merge pull request #35 from dromara/dev
Dev
2024-06-27 13:46:39 +08:00
lijiahang
8d85cdf173 🚀 修改 e2e 配置. 2024-06-27 13:44:39 +08:00
lijiahang
79d95d1997 📝 修改 sql. 2024-06-27 13:31:10 +08:00
李佳航
1eb07d0b24 Merge pull request #34 from dromara/dev
Dev
2024-06-27 13:06:49 +08:00
lijiahang
601564b573 🔖 升级版本. 2024-06-27 13:03:25 +08:00
lijiahang
41384fab17 🐛 字典值排序无效. 2024-06-26 10:23:32 +08:00
lijiahang
f0a122d862 自动清理配置. 2024-06-25 10:42:32 +08:00
lijiahang
b08d75be62 🔨 更新 spring 配置描述文件. 2024-06-25 10:16:15 +08:00
lijiahangmax
02f5bef6b4 📝 修改命名. 2024-06-24 22:49:17 +08:00
lijiahang
ef10c8b8b6 修改前端包结构. 2024-06-24 09:57:33 +08:00
lijiahang
6c4e9cd5c6 🚀 修改 e2e 配置. 2024-06-24 09:54:57 +08:00
lijiahang
8dec40553d 优化 terminal 逻辑. 2024-06-21 10:15:33 +08:00
lijiahang
9ae5a6c627 🐛 修复机器码获取失败. 2024-06-20 11:55:00 +08:00
lijiahang
2ec1678f01 🔨 修改 ip 获取逻辑. 2024-06-20 11:53:39 +08:00
李佳航
f9e436e885 Merge pull request #30 from LinuxSuRen/e2e
🚀 add e2e testing base on docker compose.
2024-06-20 11:28:46 +08:00
rick
2a49e7670d fix the health check command 2024-06-20 02:56:16 +00:00
Rick
95d8988f11 Update e2e.yaml 2024-06-20 10:25:23 +08:00
Rick
e04a972df5 Update Dockerfile 2024-06-20 10:24:58 +08:00
rick
1ca9311625 fix the password in e2e 2024-06-20 01:52:10 +00:00
rick
630a1fd3cd test: add e2e testing base on docker compose 2024-06-20 01:29:43 +00:00
115 changed files with 1053 additions and 535 deletions

22
.github/workflows/e2e.yaml vendored Normal file
View File

@@ -0,0 +1,22 @@
name: E2E
on:
pull_request:
branches:
- main
- dev
concurrency:
group: ${{github.workflow}} - ${{github.ref}}
cancel-in-progress: true
jobs:
testing:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: E2E Testing
run: |
sudo curl -L https://github.com/docker/compose/releases/download/v2.23.0/docker-compose-linux-x86_64 -o /usr/local/bin/docker-compose
sudo chmod u+x /usr/local/bin/docker-compose
docker compose -f docker-compose-testing.yml up --build testing --exit-code-from testing --remove-orphans

View File

@@ -0,0 +1,79 @@
version: '3.3'
services:
orion-visor-service:
image: registry.cn-hangzhou.aliyuncs.com/lijiahangmax/orion-visor-service:2.0.10
privileged: true
ports:
- 1081:80
environment:
- MYSQL_HOST=orion-visor-mysql
- MYSQL_PORT=3306
- MYSQL_DATABASE=orion_visor
- MYSQL_USER=root
- MYSQL_PASSWORD=Data@123456
- REDIS_HOST=orion-visor-redis
- REDIS_PASSWORD=Data@123456
- SECRET_KEY=uQeacXV8b3isvKLK
- DEMO_MODE=false
volumes:
- /data/orion-visor-space/docker-volumes/service/root-orion:/root/orion
healthcheck:
test: [ "CMD", "curl", "http://127.0.0.1:9200/orion-visor/api/server/bootstrap/health" ]
interval: 3s
timeout: 300s
retries: 200
start_period: 3s
depends_on:
orion-visor-mysql:
condition: service_healthy
orion-visor-redis:
condition: service_healthy
links:
- orion-visor-mysql
- orion-visor-redis
orion-visor-mysql:
image: registry.cn-hangzhou.aliyuncs.com/lijiahangmax/orion-visor-mysql:2.0.10
privileged: true
ports:
- 3307:3306
environment:
- MYSQL_DATABASE=orion_visor
- MYSQL_USER=orion
- MYSQL_PASSWORD=Data@123456
- MYSQL_ROOT_PASSWORD=Data@123456
volumes:
- /data/orion-visor-space/docker-volumes/mysql/var-lib-mysql:/var/lib/mysql
- /data/orion-visor-space/docker-volumes/mysql/var-lib-mysql-files:/var/lib/mysql-files
- /data/orion-visor-space/docker-volumes/mysql/etc-mysql:/etc/mysql
healthcheck:
test: [ "CMD", "bash", "-c", "cat < /dev/null > /dev/tcp/127.0.0.1/3306" ]
interval: 3s
timeout: 60s
retries: 10
start_period: 3s
orion-visor-redis:
image: registry.cn-hangzhou.aliyuncs.com/lijiahangmax/orion-visor-redis:2.0.10
privileged: true
ports:
- 6380:6379
environment:
- REDIS_PASSWORD=Data@123456
volumes:
- /data/orion-visor-space/docker-volumes/redis/data:/data
command: sh -c "redis-server /usr/local/redis.conf --requirepass $${REDIS_PASSWORD}"
healthcheck:
test: [ "CMD", "redis-cli", "--raw", "incr", "ping" ]
interval: 3s
timeout: 60s
retries: 10
start_period: 3s
testing:
build:
context: ./docker/e2e
environment:
SERVER: http://orion-visor-service:80
depends_on:
orion-visor-service:
condition: service_healthy
links:
- orion-visor-service

View File

@@ -1,7 +1,7 @@
version: '3.3' version: '3.3'
services: services:
orion-visor-service: orion-visor-service:
image: registry.cn-hangzhou.aliyuncs.com/lijiahangmax/orion-visor-service:2.0.9 image: registry.cn-hangzhou.aliyuncs.com/lijiahangmax/orion-visor-service:2.0.10
privileged: true privileged: true
ports: ports:
- 1081:80 - 1081:80
@@ -16,12 +16,23 @@ services:
- SECRET_KEY=uQeacXV8b3isvKLK - SECRET_KEY=uQeacXV8b3isvKLK
- DEMO_MODE=false - DEMO_MODE=false
volumes: volumes:
- /data/orion-visor-space/docker-volumes/orion-visor-service/root-orion:/root/orion - /data/orion-visor-space/docker-volumes/service/root-orion:/root/orion
healthcheck:
test: [ "CMD", "curl", "http://127.0.0.1:9200/orion-visor/api/server/bootstrap/health" ]
interval: 3s
timeout: 300s
retries: 200
start_period: 3s
depends_on: depends_on:
orion-visor-mysql:
condition: service_healthy
orion-visor-redis:
condition: service_healthy
links:
- orion-visor-mysql - orion-visor-mysql
- orion-visor-redis - orion-visor-redis
orion-visor-mysql: orion-visor-mysql:
image: registry.cn-hangzhou.aliyuncs.com/lijiahangmax/orion-visor-mysql:2.0.9 image: registry.cn-hangzhou.aliyuncs.com/lijiahangmax/orion-visor-mysql:2.0.10
privileged: true privileged: true
ports: ports:
- 3307:3306 - 3307:3306
@@ -31,19 +42,31 @@ services:
- MYSQL_PASSWORD=Data@123456 - MYSQL_PASSWORD=Data@123456
- MYSQL_ROOT_PASSWORD=Data@123456 - MYSQL_ROOT_PASSWORD=Data@123456
volumes: volumes:
- /data/orion-visor-space/docker-volumes/orion-visor-mysql/var-lib-mysql:/var/lib/mysql - /data/orion-visor-space/docker-volumes/mysql/var-lib-mysql:/var/lib/mysql
- /data/orion-visor-space/docker-volumes/orion-visor-mysql/var-lib-mysql-files:/var/lib/mysql-files - /data/orion-visor-space/docker-volumes/mysql/var-lib-mysql-files:/var/lib/mysql-files
- /data/orion-visor-space/docker-volumes/orion-visor-mysql/etc-mysql:/etc/mysql - /data/orion-visor-space/docker-volumes/mysql/etc-mysql:/etc/mysql
healthcheck:
test: [ "CMD", "bash", "-c", "cat < /dev/null > /dev/tcp/127.0.0.1/3306" ]
interval: 3s
timeout: 60s
retries: 10
start_period: 3s
orion-visor-redis: orion-visor-redis:
image: registry.cn-hangzhou.aliyuncs.com/lijiahangmax/orion-visor-redis:2.0.9 image: registry.cn-hangzhou.aliyuncs.com/lijiahangmax/orion-visor-redis:2.0.10
privileged: true privileged: true
ports: ports:
- 6380:6379 - 6380:6379
environment: environment:
- REDIS_PASSWORD=Data@123456 - REDIS_PASSWORD=Data@123456
volumes: volumes:
- /data/orion-visor-space/docker-volumes/orion-visor-redis/data:/data - /data/orion-visor-space/docker-volumes/redis/data:/data
command: sh -c "redis-server /usr/local/redis.conf --requirepass $${REDIS_PASSWORD}" command: sh -c "redis-server /usr/local/redis.conf --requirepass $${REDIS_PASSWORD}"
healthcheck:
test: [ "CMD", "redis-cli", "--raw", "incr", "ping" ]
interval: 3s
timeout: 60s
retries: 10
start_period: 3s
orion-visor-adminer: orion-visor-adminer:
image: adminer image: adminer
ports: ports:

7
docker/e2e/Dockerfile Normal file
View File

@@ -0,0 +1,7 @@
FROM ghcr.io/linuxsuren/api-testing:v0.0.17
WORKDIR /workspace
COPY . .
RUN chmod 777 *
CMD [ "/workspace/entrypoint.sh" ]

3
docker/e2e/entrypoint.sh Normal file
View File

@@ -0,0 +1,3 @@
#!/bin/bash
set -e
atest run -p testsuite.yaml --report md

57
docker/e2e/testsuite.yaml Normal file
View File

@@ -0,0 +1,57 @@
#!api-testing
# yaml-language-server: $schema=https://linuxsuren.github.io/api-testing/api-testing-schema.json
name: orion-visor
api: |
{{default "http://orion-visor-service:80" (env "SERVER")}}
items:
- name: login
request:
api: /orion-visor/api/infra/auth/login
method: POST
header:
Content-type: application/json
body: |
{"username":"admin","password":"21232f297a57a5a743894a0e4a801fc3"}
expect:
bodyFieldsExpect:
code: 200
- name: userPermission
request:
api: /orion-visor/api/infra/permission/user
header:
Authorization: Bearer {{.login.data.token}}
expect:
bodyFieldsExpect:
code: 200
msg: "success"
- name: menu
request:
api: /orion-visor/api/infra/permission/menu
header:
Authorization: Bearer {{.login.data.token}}
expect:
bodyFieldsExpect:
code: 200
msg: "success"
- name: haveUnRead
request:
api: /orion-visor/api/infra/system-message/has-unread
header:
Authorization: Bearer {{.login.data.token}}
- name: queryOperatorLog
request:
api: /orion-visor/api/infra/mine/query-operator-log
method: POST
header:
Authorization: Bearer {{.login.data.token}}
- name: hostList
request:
api: /orion-visor/api/infra/tag/list?type=HOST
header:
Authorization: Bearer {{.login.data.token}}
- name: queryHost
request:
api: /orion-visor/api/asset/host/query
method: POST
header:
Authorization: Bearer {{.login.data.token}}

View File

@@ -1,5 +1,5 @@
#/bin/bash #/bin/bash
version=2.0.9 version=2.0.10
cp -r ../../sql ./sql cp -r ../../sql ./sql
docker build -t orion-visor-mysql:${version} . docker build -t orion-visor-mysql:${version} .
rm -rf ./sql rm -rf ./sql

View File

@@ -1,5 +1,5 @@
#/bin/bash #/bin/bash
version=2.0.9 version=2.0.10
docker build -t orion-visor-redis:${version} . docker build -t orion-visor-redis:${version} .
docker tag orion-visor-redis:${version} registry.cn-hangzhou.aliyuncs.com/lijiahangmax/orion-visor-redis:${version} docker tag orion-visor-redis:${version} registry.cn-hangzhou.aliyuncs.com/lijiahangmax/orion-visor-redis:${version}
docker push registry.cn-hangzhou.aliyuncs.com/lijiahangmax/orion-visor-redis:${version} docker push registry.cn-hangzhou.aliyuncs.com/lijiahangmax/orion-visor-redis:${version}

View File

@@ -1,5 +1,5 @@
#/bin/bash #/bin/bash
version=2.0.9 version=2.0.10
mv ../../orion-visor-launch/target/orion-visor-launch.jar ./orion-visor-launch.jar mv ../../orion-visor-launch/target/orion-visor-launch.jar ./orion-visor-launch.jar
mv ../../orion-visor-ui/dist ./dist mv ../../orion-visor-ui/dist ./dist
docker build -t orion-visor-service:${version} . docker build -t orion-visor-service:${version} .

View File

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

View File

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

View File

@@ -83,8 +83,12 @@ public interface ErrorMessage {
String UNKNOWN_TYPE = "未知类型"; String UNKNOWN_TYPE = "未知类型";
String ERROR_TYPE = "错误的类型";
String FILE_ABSENT = "文件不存在"; String FILE_ABSENT = "文件不存在";
String FILE_ABSENT_CLEAR = "文件不存在 (可能已被清理)";
String LOG_ABSENT = "日志不存在"; String LOG_ABSENT = "日志不存在";
String TASK_ABSENT = "任务不存在"; String TASK_ABSENT = "任务不存在";
@@ -95,6 +99,8 @@ public interface ErrorMessage {
String FILE_READ_ERROR = "文件读取失败"; String FILE_READ_ERROR = "文件读取失败";
String FILE_READ_ERROR_CLEAR = "文件读取失败 (可能已被清理)";
String PLEASE_CHECK_HOST_SSH = "请检查主机 {} 是否存在/权限/SSH配置"; String PLEASE_CHECK_HOST_SSH = "请检查主机 {} 是否存在/权限/SSH配置";
String CLIENT_ABORT = "手动中断"; String CLIENT_ABORT = "手动中断";

View File

@@ -1,13 +1,13 @@
package com.orion.visor.framework.common.constant; package com.orion.visor.framework.common.constant;
/** /**
* 路径常量 * 文件常量
* *
* @author Jiahang Li * @author Jiahang Li
* @version 1.0.0 * @version 1.0.0
* @since 2024/4/17 10:35 * @since 2024/4/17 10:35
*/ */
public interface PathConst { public interface FileConst {
String ERROR_LOG = "error.log"; String ERROR_LOG = "error.log";

View File

@@ -0,0 +1,25 @@
package com.orion.visor.framework.common.entity;
import lombok.Data;
/**
* 自动清理配置
*
* @author Jiahang Li
* @version 1.0.0
* @since 2024/6/24 15:03
*/
@Data
public class AutoClearConfig {
/**
* 是否开启
*/
private Boolean enabled;
/**
* 保留周期 (天)
*/
private Integer keepPeriod;
}

View File

@@ -0,0 +1,45 @@
package com.orion.visor.framework.common.enums;
import com.orion.lang.utils.Strings;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* 端点定义
*
* @author Jiahang Li
* @version 1.0.0
* @since 2024/6/21 19:15
*/
@Getter
@AllArgsConstructor
public enum EndpointDefine {
/**
* 上传临时分区
*/
UPLOAD_SWAP("/upload/swap/{}"),
/**
* 批量执行日志
*/
EXEC_LOG("/exec/{}/{}.log"),
;
/**
* 端点
*/
private final String endpoint;
/**
* 格式化
*
* @param params params
* @return path
*/
public String format(Object... params) {
return Strings.format(this.endpoint, params);
}
}

View File

@@ -2,8 +2,12 @@ package com.orion.visor.framework.common.utils;
import com.orion.ext.location.Region; import com.orion.ext.location.Region;
import com.orion.ext.location.region.LocationRegions; import com.orion.ext.location.region.LocationRegions;
import com.orion.lang.constant.StandardHttpHeader;
import com.orion.lang.utils.Strings;
import com.orion.visor.framework.common.constant.Const; import com.orion.visor.framework.common.constant.Const;
import com.orion.web.servlet.web.Servlets;
import javax.servlet.http.HttpServletRequest;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
@@ -21,6 +25,22 @@ public class IpUtils {
private IpUtils() { private IpUtils() {
} }
/**
* 获取请求地址
*
* @param request request
* @return addr
*/
public static String getRemoteAddr(HttpServletRequest request) {
// 获取实际地址
String realIp = request.getHeader(StandardHttpHeader.X_REAL_IP);
if (!Strings.isBlank(realIp)) {
return realIp;
}
// 获取请求地址
return Servlets.getRemoteAddr(request);
}
/** /**
* 获取 ip 位置 * 获取 ip 位置
* *

View File

@@ -41,7 +41,7 @@ public class Requests {
.map(s -> (ServletRequestAttributes) s) .map(s -> (ServletRequestAttributes) s)
.map(ServletRequestAttributes::getRequest) .map(ServletRequestAttributes::getRequest)
.ifPresent(request -> { .ifPresent(request -> {
String address = Servlets.getRemoteAddr(request); String address = IpUtils.getRemoteAddr(request);
identity.setAddress(address); identity.setAddress(address);
identity.setLocation(IpUtils.getLocation(address)); identity.setLocation(IpUtils.getLocation(address));
identity.setUserAgent(Servlets.getUserAgent(request)); identity.setUserAgent(Servlets.getUserAgent(request));

View File

@@ -10,7 +10,7 @@
{ {
"name": "orion.async.executor.core-pool-size", "name": "orion.async.executor.core-pool-size",
"type": "java.lang.Integer", "type": "java.lang.Integer",
"description": "核心线程数量", "description": "核心线程数量.",
"defaultValue": "8" "defaultValue": "8"
}, },
{ {

View File

@@ -1,6 +1,7 @@
package com.orion.visor.framework.log.configuration.config; package com.orion.visor.framework.log.configuration.config;
import com.orion.visor.framework.common.utils.ConfigUtils; import com.orion.visor.framework.common.utils.ConfigUtils;
import com.orion.visor.framework.log.core.enums.LogPrinterMode;
import lombok.Data; import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.context.properties.ConfigurationProperties;
@@ -17,6 +18,11 @@ import java.util.List;
@ConfigurationProperties("orion.logging.printer") @ConfigurationProperties("orion.logging.printer")
public class LogPrinterConfig { public class LogPrinterConfig {
/**
* 类型
*/
private LogPrinterMode mode;
/** /**
* 字段配置 * 字段配置
*/ */
@@ -32,12 +38,31 @@ public class LogPrinterConfig {
*/ */
private String expression; private String expression;
public void setField(LogPrinterFieldConfig field) {
this.field = field;
}
public void setHeaders(List<String> headers) { public void setHeaders(List<String> headers) {
this.headers = ConfigUtils.parseStringList(headers, String::toLowerCase); this.headers = ConfigUtils.parseStringList(headers, String::toLowerCase);
} }
@Data
public static class LogPrinterFieldConfig {
/**
* 忽略的字段
*/
private List<String> ignore;
/**
* 脱敏的字段
*/
private List<String> desensitize;
public void setIgnore(List<String> ignore) {
this.ignore = ConfigUtils.parseStringList(ignore);
}
public void setDesensitize(List<String> desensitize) {
this.desensitize = ConfigUtils.parseStringList(desensitize);
}
}
} }

View File

@@ -1,36 +0,0 @@
package com.orion.visor.framework.log.configuration.config;
import com.orion.visor.framework.common.utils.ConfigUtils;
import lombok.Data;
import java.util.List;
/**
* 日志打印字段配置
*
* @author Jiahang Li
* @version 1.0.0
* @since 2023/6/28 22:36
*/
@Data
public class LogPrinterFieldConfig {
/**
* 忽略的字段
*/
private List<String> ignore;
/**
* 脱敏的字段
*/
private List<String> desensitize;
public void setIgnore(List<String> ignore) {
this.ignore = ConfigUtils.parseStringList(ignore);
}
public void setDesensitize(List<String> desensitize) {
this.desensitize = ConfigUtils.parseStringList(desensitize);
}
}

View File

@@ -3,6 +3,7 @@ package com.orion.visor.framework.log.core.interceptor;
import com.orion.lang.utils.Exceptions; import com.orion.lang.utils.Exceptions;
import com.orion.lang.utils.Strings; import com.orion.lang.utils.Strings;
import com.orion.lang.utils.time.Dates; import com.orion.lang.utils.time.Dates;
import com.orion.visor.framework.common.utils.IpUtils;
import com.orion.visor.framework.common.utils.SwaggerUtils; import com.orion.visor.framework.common.utils.SwaggerUtils;
import com.orion.visor.framework.log.configuration.config.LogPrinterConfig; import com.orion.visor.framework.log.configuration.config.LogPrinterConfig;
import com.orion.web.servlet.web.Servlets; import com.orion.web.servlet.web.Servlets;
@@ -60,7 +61,7 @@ public class PrettyLogPrinterInterceptor extends AbstractLogPrinterInterceptor {
// http // http
if (request != null) { if (request != null) {
// remoteAddr // remoteAddr
requestLog.append("\tremoteAddr: ").append(Servlets.getRemoteAddr(request)).append('\n'); requestLog.append("\tremoteAddr: ").append(IpUtils.getRemoteAddr(request)).append('\n');
// header // header
Servlets.getHeaderMap(request).forEach((hk, hv) -> { Servlets.getHeaderMap(request).forEach((hk, hv) -> {
if (headerFilter.test(hk.toLowerCase())) { if (headerFilter.test(hk.toLowerCase())) {

View File

@@ -4,6 +4,7 @@ import com.alibaba.fastjson.JSON;
import com.orion.lang.utils.Exceptions; import com.orion.lang.utils.Exceptions;
import com.orion.lang.utils.Strings; import com.orion.lang.utils.Strings;
import com.orion.lang.utils.time.Dates; import com.orion.lang.utils.time.Dates;
import com.orion.visor.framework.common.utils.IpUtils;
import com.orion.visor.framework.common.utils.SwaggerUtils; import com.orion.visor.framework.common.utils.SwaggerUtils;
import com.orion.visor.framework.log.configuration.config.LogPrinterConfig; import com.orion.visor.framework.log.configuration.config.LogPrinterConfig;
import com.orion.visor.framework.log.core.enums.LogFieldConst; import com.orion.visor.framework.log.core.enums.LogFieldConst;
@@ -61,7 +62,7 @@ public class RowLogPrinterInterceptor extends AbstractLogPrinterInterceptor impl
// http // http
if (request != null) { if (request != null) {
// remoteAddr // remoteAddr
fields.put(REMOTE_ADDR, Servlets.getRemoteAddr(request)); fields.put(REMOTE_ADDR, IpUtils.getRemoteAddr(request));
// header // header
Map<String, Object> headers = new LinkedHashMap<>(); Map<String, Object> headers = new LinkedHashMap<>();
Servlets.getHeaderMap(request).forEach((hk, hv) -> { Servlets.getHeaderMap(request).forEach((hk, hv) -> {

View File

@@ -2,9 +2,9 @@ package com.orion.visor.framework.security.configuration;
import com.orion.visor.framework.common.constant.AutoConfigureOrderConst; import com.orion.visor.framework.common.constant.AutoConfigureOrderConst;
import com.orion.visor.framework.common.crypto.ValueCrypto; import com.orion.visor.framework.common.crypto.ValueCrypto;
import com.orion.visor.framework.security.configuration.config.CryptoConfig; import com.orion.visor.framework.security.configuration.config.AesCryptoConfig;
import com.orion.visor.framework.security.core.crypto.PrimaryValueCrypto; import com.orion.visor.framework.security.core.crypto.PrimaryValueCrypto;
import com.orion.visor.framework.security.core.crypto.aes.AesCryptoProcessor; import com.orion.visor.framework.security.core.crypto.processor.AesCryptoProcessor;
import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.AutoConfigureOrder; import org.springframework.boot.autoconfigure.AutoConfigureOrder;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
@@ -12,8 +12,6 @@ import org.springframework.boot.context.properties.EnableConfigurationProperties
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Primary; import org.springframework.context.annotation.Primary;
import javax.annotation.Resource;
/** /**
* 项目加密解密配置 * 项目加密解密配置
* *
@@ -22,13 +20,10 @@ import javax.annotation.Resource;
* @since 2023/7/7 23:59 * @since 2023/7/7 23:59
*/ */
@AutoConfiguration @AutoConfiguration
@EnableConfigurationProperties(CryptoConfig.class) @EnableConfigurationProperties({AesCryptoConfig.class})
@AutoConfigureOrder(AutoConfigureOrderConst.FRAMEWORK_SECURITY_CRYPTO) @AutoConfigureOrder(AutoConfigureOrderConst.FRAMEWORK_SECURITY_CRYPTO)
public class OrionCryptoAutoConfiguration { public class OrionCryptoAutoConfiguration {
@Resource
private CryptoConfig config;
/** /**
* @return 默认加密器 * @return 默认加密器
*/ */
@@ -43,8 +38,8 @@ public class OrionCryptoAutoConfiguration {
*/ */
@Bean(initMethod = "init") @Bean(initMethod = "init")
@ConditionalOnProperty(value = "orion.crypto.aes.enabled", havingValue = "true") @ConditionalOnProperty(value = "orion.crypto.aes.enabled", havingValue = "true")
public ValueCrypto aesValueCrypto() { public ValueCrypto aesValueCrypto(AesCryptoConfig config) {
return new AesCryptoProcessor(config.getAes()); return new AesCryptoProcessor(config);
} }
} }

View File

@@ -1,4 +1,4 @@
package com.orion.visor.framework.security.core.crypto.aes; package com.orion.visor.framework.security.configuration.config;
import com.orion.lang.utils.crypto.CryptoConst; import com.orion.lang.utils.crypto.CryptoConst;
import com.orion.lang.utils.crypto.enums.PaddingMode; import com.orion.lang.utils.crypto.enums.PaddingMode;
@@ -6,6 +6,7 @@ import com.orion.lang.utils.crypto.enums.WorkingMode;
import com.orion.visor.framework.security.core.crypto.CryptoConfig; import com.orion.visor.framework.security.core.crypto.CryptoConfig;
import lombok.Data; import lombok.Data;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
import org.springframework.boot.context.properties.ConfigurationProperties;
/** /**
* aes 加密器配置 * aes 加密器配置
@@ -16,6 +17,7 @@ import lombok.EqualsAndHashCode;
*/ */
@Data @Data
@EqualsAndHashCode(callSuper = true) @EqualsAndHashCode(callSuper = true)
@ConfigurationProperties("orion.crypto.aes")
public class AesCryptoConfig extends CryptoConfig { public class AesCryptoConfig extends CryptoConfig {
/** /**

View File

@@ -1,23 +0,0 @@
package com.orion.visor.framework.security.configuration.config;
import com.orion.visor.framework.security.core.crypto.aes.AesCryptoConfig;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
/**
* 加密配置
*
* @author Jiahang Li
* @version 1.0.0
* @since 2023/7/8 0:01
*/
@Data
@ConfigurationProperties("orion.crypto")
public class CryptoConfig {
/**
* aes 加密器配置
*/
private AesCryptoConfig aes;
}

View File

@@ -1,4 +1,4 @@
package com.orion.visor.framework.security.core.crypto.aes; package com.orion.visor.framework.security.core.crypto.processor;
import com.orion.lang.utils.Strings; import com.orion.lang.utils.Strings;
import com.orion.lang.utils.crypto.Keys; import com.orion.lang.utils.crypto.Keys;
@@ -6,6 +6,7 @@ import com.orion.lang.utils.crypto.enums.CipherAlgorithm;
import com.orion.lang.utils.crypto.enums.WorkingMode; import com.orion.lang.utils.crypto.enums.WorkingMode;
import com.orion.lang.utils.crypto.symmetric.SymmetricBuilder; import com.orion.lang.utils.crypto.symmetric.SymmetricBuilder;
import com.orion.lang.utils.crypto.symmetric.SymmetricCrypto; import com.orion.lang.utils.crypto.symmetric.SymmetricCrypto;
import com.orion.visor.framework.security.configuration.config.AesCryptoConfig;
import com.orion.visor.framework.security.core.crypto.CryptoProcessor; import com.orion.visor.framework.security.core.crypto.CryptoProcessor;
import javax.crypto.SecretKey; import javax.crypto.SecretKey;

View File

@@ -5,15 +5,10 @@
"type": "com.orion.visor.framework.security.configuration.config.SecurityConfig", "type": "com.orion.visor.framework.security.configuration.config.SecurityConfig",
"sourceType": "com.orion.visor.framework.security.configuration.config.SecurityConfig" "sourceType": "com.orion.visor.framework.security.configuration.config.SecurityConfig"
}, },
{
"name": "orion.crypto",
"type": "com.orion.visor.framework.security.configuration.config.CryptoConfig",
"sourceType": "com.orion.visor.framework.security.configuration.config.CryptoConfig"
},
{ {
"name": "orion.crypto.aes", "name": "orion.crypto.aes",
"type": "com.orion.visor.framework.security.core.crypto.aes.AesCryptoConfig", "type": "com.orion.visor.framework.security.configuration.config.AesCryptoConfig",
"sourceType": "com.orion.visor.framework.security.core.crypto.aes.AesCryptoConfig" "sourceType": "com.orion.visor.framework.security.configuration.config.AesCryptoConfig"
} }
], ],
"properties": [ "properties": [

View File

@@ -2,7 +2,8 @@ package com.orion.visor.framework.storage.configuration;
import com.orion.visor.framework.common.constant.AutoConfigureOrderConst; import com.orion.visor.framework.common.constant.AutoConfigureOrderConst;
import com.orion.visor.framework.common.file.FileClient; import com.orion.visor.framework.common.file.FileClient;
import com.orion.visor.framework.storage.configuration.config.StorageConfig; import com.orion.visor.framework.storage.configuration.config.LocalStorageConfig;
import com.orion.visor.framework.storage.configuration.config.LogsStorageConfig;
import com.orion.visor.framework.storage.core.client.PrimaryFileClient; import com.orion.visor.framework.storage.core.client.PrimaryFileClient;
import com.orion.visor.framework.storage.core.client.local.LocalFileClient; import com.orion.visor.framework.storage.core.client.local.LocalFileClient;
import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.autoconfigure.AutoConfiguration;
@@ -12,8 +13,6 @@ import org.springframework.boot.context.properties.EnableConfigurationProperties
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Primary; import org.springframework.context.annotation.Primary;
import javax.annotation.Resource;
/** /**
* 存储配置类 * 存储配置类
* *
@@ -25,12 +24,9 @@ import javax.annotation.Resource;
*/ */
@AutoConfiguration @AutoConfiguration
@AutoConfigureOrder(AutoConfigureOrderConst.FRAMEWORK_STORAGE) @AutoConfigureOrder(AutoConfigureOrderConst.FRAMEWORK_STORAGE)
@EnableConfigurationProperties(StorageConfig.class) @EnableConfigurationProperties({LocalStorageConfig.class, LogsStorageConfig.class})
public class OrionStorageAutoConfiguration { public class OrionStorageAutoConfiguration {
@Resource
private StorageConfig config;
/** /**
* @return 默认文件客户端 * @return 默认文件客户端
*/ */
@@ -45,8 +41,8 @@ public class OrionStorageAutoConfiguration {
*/ */
@Bean @Bean
@ConditionalOnProperty(value = "orion.storage.local.enabled", havingValue = "true") @ConditionalOnProperty(value = "orion.storage.local.enabled", havingValue = "true")
public FileClient localFileClient() { public FileClient localFileClient(LocalStorageConfig config) {
return new LocalFileClient(config.getLocal()); return new LocalFileClient(config);
} }
/** /**
@@ -54,8 +50,8 @@ public class OrionStorageAutoConfiguration {
*/ */
@Bean @Bean
@ConditionalOnProperty(value = "orion.storage.logs.enabled", havingValue = "true") @ConditionalOnProperty(value = "orion.storage.logs.enabled", havingValue = "true")
public FileClient logsFileClient() { public FileClient logsFileClient(LogsStorageConfig config) {
return new LocalFileClient(config.getLogs()); return new LocalFileClient(config);
} }
} }

View File

@@ -2,27 +2,19 @@ package com.orion.visor.framework.storage.configuration.config;
import com.orion.visor.framework.storage.core.client.local.LocalFileClientConfig; import com.orion.visor.framework.storage.core.client.local.LocalFileClientConfig;
import lombok.Data; import lombok.Data;
import lombok.EqualsAndHashCode;
import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.context.properties.ConfigurationProperties;
/** /**
* 存储配置 * 本地存储配置
* *
* @author Jiahang Li * @author Jiahang Li
* @version 1.0.0 * @version 1.0.0
* @since 2023/6/30 18:40 * @since 2023/6/30 18:40
*/ */
@Data @Data
@ConfigurationProperties(prefix = "orion.storage") @EqualsAndHashCode(callSuper = true)
public class StorageConfig { @ConfigurationProperties(prefix = "orion.storage.local")
public class LocalStorageConfig extends LocalFileClientConfig {
/**
* 本地文件客户端 配置
*/
private LocalFileClientConfig local;
/**
* 日志文件客户端 配置
*/
private LocalFileClientConfig logs;
} }

View File

@@ -0,0 +1,20 @@
package com.orion.visor.framework.storage.configuration.config;
import com.orion.visor.framework.storage.core.client.local.LocalFileClientConfig;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.springframework.boot.context.properties.ConfigurationProperties;
/**
* 日志存储配置
*
* @author Jiahang Li
* @version 1.0.0
* @since 2023/6/30 18:40
*/
@Data
@EqualsAndHashCode(callSuper = true)
@ConfigurationProperties(prefix = "orion.storage.logs")
public class LogsStorageConfig extends LocalFileClientConfig {
}

View File

@@ -1,19 +1,14 @@
{ {
"groups": [ "groups": [
{
"name": "orion.storage",
"type": "com.orion.visor.framework.storage.configuration.config.StorageConfig",
"sourceType": "com.orion.visor.framework.storage.configuration.config.StorageConfig"
},
{ {
"name": "orion.storage.local", "name": "orion.storage.local",
"type": "com.orion.visor.framework.storage.core.client.local.LocalFileClientConfig", "type": "com.orion.visor.framework.storage.configuration.config.LocalStorageConfig",
"sourceType": "com.orion.visor.framework.storage.core.client.local.LocalFileClientConfig" "sourceType": "com.orion.visor.framework.storage.configuration.config.LocalStorageConfig"
}, },
{ {
"name": "orion.storage.logs", "name": "orion.storage.logs",
"type": "com.orion.visor.framework.storage.core.client.local.LocalFileClientConfig", "type": "com.orion.visor.framework.storage.configuration.config.LogsStorageConfig",
"sourceType": "com.orion.visor.framework.storage.core.client.local.LocalFileClientConfig" "sourceType": "com.orion.visor.framework.storage.configuration.config.LogsStorageConfig"
} }
], ],
"properties": [ "properties": [

View File

@@ -10,7 +10,7 @@
{ {
"name": "orion.websocket.prefix", "name": "orion.websocket.prefix",
"type": "java.lang.String", "type": "java.lang.String",
"description": "公共 websocket 前缀" "description": "公共 websocket 前缀."
}, },
{ {
"name": "orion.websocket.binary-buffer-size", "name": "orion.websocket.binary-buffer-size",

View File

@@ -189,10 +189,16 @@ app:
exec-log: exec-log:
# 是否拼接 ansi 执行状态日志 # 是否拼接 ansi 执行状态日志
append-ansi: true append-ansi: true
# 自动清理执行文件 # 自动清理配置
auto-clear: true auto-clear:
# 保留周期 (天) # 批量执行日志
keep-period: 30 exec-log:
enabled: false
keep-period: 30
# 主机连接日志
host-connect-log:
enabled: false
keep-period: 30
# orion framework config # orion framework config
orion: orion:

View File

@@ -0,0 +1,22 @@
package com.orion.visor.module.asset.define.config;
import com.orion.visor.framework.common.entity.AutoClearConfig;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
/**
* 批量执行日志自动清理配置
*
* @author Jiahang Li
* @version 1.0.0
* @since 2024/6/24 15:01
*/
@Data
@Component
@EqualsAndHashCode(callSuper = true)
@ConfigurationProperties(prefix = "app.auto-clear.exec-log")
public class AppExecLogAutoClearConfig extends AutoClearConfig {
}

View File

@@ -21,20 +21,8 @@ public class AppExecLogConfig {
*/ */
private Boolean appendAnsi; private Boolean appendAnsi;
/**
* 自动清理执行文件
*/
private Boolean autoClear;
/**
* 保留周期 (天)
*/
private Integer keepPeriod;
public AppExecLogConfig() { public AppExecLogConfig() {
this.appendAnsi = true; this.appendAnsi = true;
this.autoClear = true;
this.keepPeriod = 30;
} }
} }

View File

@@ -0,0 +1,22 @@
package com.orion.visor.module.asset.define.config;
import com.orion.visor.framework.common.entity.AutoClearConfig;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
/**
* 主机连接日志自动清理配置
*
* @author Jiahang Li
* @version 1.0.0
* @since 2024/6/24 15:01
*/
@Data
@Component
@EqualsAndHashCode(callSuper = true)
@ConfigurationProperties(prefix = "app.auto-clear.host-connect-log")
public class AppHostConnectLogAutoClearConfig extends AutoClearConfig {
}

View File

@@ -7,6 +7,7 @@ import lombok.*;
import javax.validation.constraints.Size; import javax.validation.constraints.Size;
import java.util.Date; import java.util.Date;
import java.util.List;
/** /**
* 批量执行日志 查询请求对象 * 批量执行日志 查询请求对象
@@ -54,4 +55,11 @@ public class ExecLogQueryRequest extends PageRequest {
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private Date[] startTimeRange; private Date[] startTimeRange;
@Schema(description = "创建时间 <=")
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private Date createTimeLe;
@Schema(description = "状态")
private List<String> statusList;
} }

View File

@@ -56,4 +56,11 @@ public class HostConnectLogQueryRequest extends PageRequest {
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private Date[] startTimeRange; private Date[] startTimeRange;
@Schema(description = "创建时间 <=")
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private Date createTimeLe;
@Schema(description = "状态")
private List<String> statusList;
} }

View File

@@ -1,8 +1,11 @@
package com.orion.visor.module.asset.enums; package com.orion.visor.module.asset.enums;
import com.orion.lang.utils.collect.Lists;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.Getter; import lombok.Getter;
import java.util.List;
/** /**
* 批量执行状态 * 批量执行状态
* *
@@ -36,6 +39,8 @@ public enum ExecStatusEnum {
; ;
public static final List<String> FINISH_STATUS_LIST = Lists.of(COMPLETED.name(), FAILED.name());
private final boolean closeable; private final boolean closeable;
public static ExecStatusEnum of(String status) { public static ExecStatusEnum of(String status) {

View File

@@ -1,5 +1,9 @@
package com.orion.visor.module.asset.enums; package com.orion.visor.module.asset.enums;
import com.orion.lang.utils.collect.Lists;
import java.util.List;
/** /**
* 主机连接状态 * 主机连接状态
* *
@@ -31,6 +35,8 @@ public enum HostConnectStatusEnum {
; ;
public static final List<String> FINISH_STATUS_LIST = Lists.of(COMPLETE.name(), FAILED.name(), FORCE_OFFLINE.name());
public static HostConnectStatusEnum of(String type) { public static HostConnectStatusEnum of(String type) {
if (type == null) { if (type == null) {
return null; return null;

View File

@@ -31,7 +31,7 @@ public class ExecCommandAnsiHandler extends BaseExecCommandHandler {
// 拼接启动日志 // 拼接启动日志
AnsiAppender appender = AnsiAppender.create() AnsiAppender appender = AnsiAppender.create()
.append(AnsiForeground.BRIGHT_GREEN, "> 准备执行命令 ") .append(AnsiForeground.BRIGHT_GREEN, "> 准备执行命令 ")
.append(Dates.current()) .append(this.getCurrentTime())
.newLine() .newLine()
.append(AnsiForeground.BRIGHT_BLUE, "执行记录: ") .append(AnsiForeground.BRIGHT_BLUE, "执行记录: ")
.append(execCommand.getLogId()) .append(execCommand.getLogId())
@@ -81,7 +81,7 @@ public class ExecCommandAnsiHandler extends BaseExecCommandHandler {
// 非脚本执行拼接开始执行日志 // 非脚本执行拼接开始执行日志
if (!Booleans.isTrue(execCommand.getScriptExec())) { if (!Booleans.isTrue(execCommand.getScriptExec())) {
appender.append(AnsiForeground.BRIGHT_GREEN, "> 开始执行命令 ") appender.append(AnsiForeground.BRIGHT_GREEN, "> 开始执行命令 ")
.append(Dates.current()) .append(this.getCurrentTime())
.newLine(); .newLine();
} }
this.appendLog(appender); this.appendLog(appender);
@@ -94,7 +94,7 @@ public class ExecCommandAnsiHandler extends BaseExecCommandHandler {
AnsiAppender startAppender = AnsiAppender.create() AnsiAppender startAppender = AnsiAppender.create()
.newLine() .newLine()
.append(AnsiForeground.BRIGHT_GREEN, "> 准备上传脚本 ") .append(AnsiForeground.BRIGHT_GREEN, "> 准备上传脚本 ")
.append(Dates.current()) .append(this.getCurrentTime())
.newLine() .newLine()
.append(AnsiForeground.BRIGHT_BLUE, "文件路径: ") .append(AnsiForeground.BRIGHT_BLUE, "文件路径: ")
.append(execHostCommand.getScriptPath()) .append(execHostCommand.getScriptPath())
@@ -105,18 +105,18 @@ public class ExecCommandAnsiHandler extends BaseExecCommandHandler {
// 拼接完成日志 // 拼接完成日志
AnsiAppender finishAppender = AnsiAppender.create() AnsiAppender finishAppender = AnsiAppender.create()
.append(AnsiForeground.BRIGHT_GREEN, "< 脚本上传成功 ") .append(AnsiForeground.BRIGHT_GREEN, "< 脚本上传成功 ")
.append(Dates.current()) .append(this.getCurrentTime())
.newLine() .newLine()
.newLine() .newLine()
.append(AnsiForeground.BRIGHT_GREEN, "> 开始执行脚本 ") .append(AnsiForeground.BRIGHT_GREEN, "> 开始执行脚本 ")
.append(Dates.current()) .append(this.getCurrentTime())
.newLine(); .newLine();
this.appendLog(finishAppender); this.appendLog(finishAppender);
} catch (Exception e) { } catch (Exception e) {
// 拼接失败日志 // 拼接失败日志
AnsiAppender errorAppender = AnsiAppender.create() AnsiAppender errorAppender = AnsiAppender.create()
.append(AnsiForeground.BRIGHT_RED, "< 脚本上传失败 ") .append(AnsiForeground.BRIGHT_RED, "< 脚本上传失败 ")
.append(Dates.current()) .append(this.getCurrentTime())
.newLine(); .newLine();
this.appendLog(errorAppender); this.appendLog(errorAppender);
throw e; throw e;
@@ -133,12 +133,12 @@ public class ExecCommandAnsiHandler extends BaseExecCommandHandler {
if (this.status == ExecHostStatusEnum.INTERRUPTED) { if (this.status == ExecHostStatusEnum.INTERRUPTED) {
// 中断执行 // 中断执行
appender.append(AnsiForeground.BRIGHT_YELLOW, "< 命令执行中断 ") appender.append(AnsiForeground.BRIGHT_YELLOW, "< 命令执行中断 ")
.append(Dates.current()) .append(this.getCurrentTime())
.newLine(); .newLine();
} else if (this.status == ExecHostStatusEnum.FAILED) { } else if (this.status == ExecHostStatusEnum.FAILED) {
// 执行失败 // 执行失败
appender.append(AnsiForeground.BRIGHT_RED, "< 命令执行失败 ") appender.append(AnsiForeground.BRIGHT_RED, "< 命令执行失败 ")
.append(Dates.current()) .append(this.getCurrentTime())
.newLine() .newLine()
.append(AnsiForeground.BRIGHT_RED, "错误原因: ") .append(AnsiForeground.BRIGHT_RED, "错误原因: ")
.append(this.getErrorMessage(e)) .append(this.getErrorMessage(e))
@@ -146,14 +146,14 @@ public class ExecCommandAnsiHandler extends BaseExecCommandHandler {
} else if (this.status == ExecHostStatusEnum.TIMEOUT) { } else if (this.status == ExecHostStatusEnum.TIMEOUT) {
// 更新执行超时 // 更新执行超时
appender.append(AnsiForeground.BRIGHT_YELLOW, "< 命令执行超时 ") appender.append(AnsiForeground.BRIGHT_YELLOW, "< 命令执行超时 ")
.append(Dates.current()) .append(this.getCurrentTime())
.newLine(); .newLine();
} else { } else {
long ms = updateRecord.getFinishTime().getTime() - updateRecord.getStartTime().getTime(); long ms = updateRecord.getFinishTime().getTime() - updateRecord.getStartTime().getTime();
Integer exitCode = updateRecord.getExitCode(); Integer exitCode = updateRecord.getExitCode();
// 执行完成 // 执行完成
appender.append(AnsiForeground.BRIGHT_GREEN, "< 命令执行完成 ") appender.append(AnsiForeground.BRIGHT_GREEN, "< 命令执行完成 ")
.append(Dates.current()) .append(this.getCurrentTime())
.newLine() .newLine()
.append(AnsiForeground.BRIGHT_BLUE, "exit: "); .append(AnsiForeground.BRIGHT_BLUE, "exit: ");
if (ExitCode.isSuccess(exitCode)) { if (ExitCode.isSuccess(exitCode)) {
@@ -173,4 +173,13 @@ public class ExecCommandAnsiHandler extends BaseExecCommandHandler {
this.appendLog(appender); this.appendLog(appender);
} }
/**
* 获取当前时间
*
* @return 当前时间
*/
private String getCurrentTime() {
return Const.SPACE + Dates.current();
}
} }

View File

@@ -13,6 +13,7 @@ import com.orion.visor.framework.common.enums.BooleanBit;
import com.orion.visor.framework.websocket.core.utils.WebSockets; import com.orion.visor.framework.websocket.core.utils.WebSockets;
import com.orion.visor.module.asset.dao.HostDAO; import com.orion.visor.module.asset.dao.HostDAO;
import com.orion.visor.module.asset.define.operator.HostTerminalOperatorType; import com.orion.visor.module.asset.define.operator.HostTerminalOperatorType;
import com.orion.visor.module.asset.entity.domain.HostConnectLogDO;
import com.orion.visor.module.asset.entity.domain.HostDO; import com.orion.visor.module.asset.entity.domain.HostDO;
import com.orion.visor.module.asset.entity.dto.HostTerminalConnectDTO; import com.orion.visor.module.asset.entity.dto.HostTerminalConnectDTO;
import com.orion.visor.module.asset.entity.request.host.HostConnectLogCreateRequest; import com.orion.visor.module.asset.entity.request.host.HostConnectLogCreateRequest;
@@ -95,9 +96,9 @@ public class TerminalCheckHandler extends AbstractTerminalHandler<TerminalCheckR
log.error("TerminalCheckHandler-handle exception userId: {}, hostId: {}, sessionId: {}", userId, hostId, sessionId, e); log.error("TerminalCheckHandler-handle exception userId: {}, hostId: {}, sessionId: {}", userId, hostId, sessionId, e);
} }
// 记录主机日志 // 记录主机日志
Long logId = this.saveHostLog(channel, userId, host, startTime, ex, sessionId, connectType); HostConnectLogDO connectLog = this.saveHostLog(channel, userId, host, startTime, ex, sessionId, connectType);
if (connect != null) { if (connect != null) {
connect.setLogId(logId); connect.setLogId(connectLog.getId());
} }
// 响应检查结果 // 响应检查结果
this.send(channel, this.send(channel,
@@ -170,15 +171,15 @@ public class TerminalCheckHandler extends AbstractTerminalHandler<TerminalCheckR
* @param ex ex * @param ex ex
* @param sessionId sessionId * @param sessionId sessionId
* @param connectType connectType * @param connectType connectType
* @return logId * @return connectLog
*/ */
private Long saveHostLog(WebSocketSession channel, private HostConnectLogDO saveHostLog(WebSocketSession channel,
Long userId, Long userId,
HostDO host, HostDO host,
long startTime, long startTime,
Exception ex, Exception ex,
String sessionId, String sessionId,
HostConnectTypeEnum connectType) { HostConnectTypeEnum connectType) {
Long hostId = host.getId(); Long hostId = host.getId();
String hostName = host.getName(); String hostName = host.getName();
String username = WebSockets.getAttr(channel, ExtraFieldConst.USERNAME); String username = WebSockets.getAttr(channel, ExtraFieldConst.USERNAME);

View File

@@ -8,6 +8,7 @@ import com.orion.net.host.SessionStore;
import com.orion.net.host.sftp.SftpExecutor; import com.orion.net.host.sftp.SftpExecutor;
import com.orion.spring.SpringHolder; import com.orion.spring.SpringHolder;
import com.orion.visor.framework.common.constant.Const; import com.orion.visor.framework.common.constant.Const;
import com.orion.visor.framework.common.enums.EndpointDefine;
import com.orion.visor.framework.common.file.FileClient; import com.orion.visor.framework.common.file.FileClient;
import com.orion.visor.framework.common.utils.PathUtils; import com.orion.visor.framework.common.utils.PathUtils;
import com.orion.visor.module.asset.dao.UploadTaskFileDAO; import com.orion.visor.module.asset.dao.UploadTaskFileDAO;
@@ -18,7 +19,6 @@ import com.orion.visor.module.asset.enums.HostSshOsTypeEnum;
import com.orion.visor.module.asset.enums.UploadTaskFileStatusEnum; import com.orion.visor.module.asset.enums.UploadTaskFileStatusEnum;
import com.orion.visor.module.asset.handler.host.upload.model.FileUploadFileItemDTO; import com.orion.visor.module.asset.handler.host.upload.model.FileUploadFileItemDTO;
import com.orion.visor.module.asset.service.HostTerminalService; import com.orion.visor.module.asset.service.HostTerminalService;
import com.orion.visor.module.asset.service.UploadTaskService;
import com.orion.visor.module.asset.utils.SftpUtils; import com.orion.visor.module.asset.utils.SftpUtils;
import lombok.Getter; import lombok.Getter;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
@@ -134,7 +134,7 @@ public class FileUploader implements IFileUploader {
this.updateStatus(file, UploadTaskFileStatusEnum.UPLOADING); this.updateStatus(file, UploadTaskFileStatusEnum.UPLOADING);
try { try {
// 获取本地文件路径 // 获取本地文件路径
String endpoint = Strings.format(UploadTaskService.SWAP_ENDPOINT, taskId); String endpoint = EndpointDefine.UPLOAD_SWAP.format(taskId);
String localPath = localFileClient.getReturnPath(endpoint + Const.SLASH + file.getFileId()); String localPath = localFileClient.getReturnPath(endpoint + Const.SLASH + file.getFileId());
// 检查文件是否存在 // 检查文件是否存在
String remotePath = file.getRemotePath(); String remotePath = file.getRemotePath();

View File

@@ -71,6 +71,14 @@ public interface ExecLogService {
*/ */
Integer deleteExecLogByIdList(List<Long> idList, String source); Integer deleteExecLogByIdList(List<Long> idList, String source);
/**
* 批量删除批量执行日志
*
* @param idList idList
* @return effect
*/
Integer deleteExecLogByIdList(List<Long> idList);
/** /**
* 查询批量执行日志数量 * 查询批量执行日志数量
* *
@@ -128,4 +136,11 @@ public interface ExecLogService {
*/ */
void downloadLogFile(Long id, String source, HttpServletResponse response); void downloadLogFile(Long id, String source, HttpServletResponse response);
/**
* 异步删除日志文件
*
* @param idList idList
*/
void asyncDeleteLogFiles(List<Long> idList);
} }

View File

@@ -1,6 +1,7 @@
package com.orion.visor.module.asset.service; package com.orion.visor.module.asset.service;
import com.orion.lang.define.wrapper.DataGrid; import com.orion.lang.define.wrapper.DataGrid;
import com.orion.visor.module.asset.entity.domain.HostConnectLogDO;
import com.orion.visor.module.asset.entity.request.host.HostConnectLogCreateRequest; import com.orion.visor.module.asset.entity.request.host.HostConnectLogCreateRequest;
import com.orion.visor.module.asset.entity.request.host.HostConnectLogQueryRequest; import com.orion.visor.module.asset.entity.request.host.HostConnectLogQueryRequest;
import com.orion.visor.module.asset.entity.vo.HostConnectLogVO; import com.orion.visor.module.asset.entity.vo.HostConnectLogVO;
@@ -25,9 +26,9 @@ public interface HostConnectLogService {
* *
* @param type type * @param type type
* @param request request * @param request request
* @return id * @return record
*/ */
Long create(HostConnectTypeEnum type, HostConnectLogCreateRequest request); HostConnectLogDO create(HostConnectTypeEnum type, HostConnectLogCreateRequest request);
/** /**
* 分页查询主机连接日志 * 分页查询主机连接日志

View File

@@ -19,8 +19,6 @@ import java.util.List;
*/ */
public interface UploadTaskService { public interface UploadTaskService {
String SWAP_ENDPOINT = "/upload/swap/{}";
/** /**
* 创建上传任务 * 创建上传任务
* *

View File

@@ -14,7 +14,8 @@ import com.orion.visor.framework.biz.operator.log.core.utils.OperatorLogs;
import com.orion.visor.framework.common.annotation.Keep; import com.orion.visor.framework.common.annotation.Keep;
import com.orion.visor.framework.common.constant.Const; import com.orion.visor.framework.common.constant.Const;
import com.orion.visor.framework.common.constant.ErrorMessage; import com.orion.visor.framework.common.constant.ErrorMessage;
import com.orion.visor.framework.common.constant.PathConst; import com.orion.visor.framework.common.constant.FileConst;
import com.orion.visor.framework.common.enums.EndpointDefine;
import com.orion.visor.framework.common.file.FileClient; import com.orion.visor.framework.common.file.FileClient;
import com.orion.visor.framework.common.security.LoginUser; import com.orion.visor.framework.common.security.LoginUser;
import com.orion.visor.framework.common.utils.PathUtils; import com.orion.visor.framework.common.utils.PathUtils;
@@ -140,10 +141,10 @@ public class ExecCommandServiceImpl implements ExecCommandService {
execLogDAO.insert(execLog); execLogDAO.insert(execLog);
Long execId = execLog.getId(); Long execId = execLog.getId();
// 获取内置参数 // 获取内置参数
Map<String, Object> builtinsParams = this.getBaseBuiltinsParams(execId, request); Map<String, Object> builtinParams = this.getBaseBuiltinParams(execId, request);
// 设置主机日志 // 设置主机日志
List<ExecHostLogDO> execHostLogs = hosts.stream() List<ExecHostLogDO> execHostLogs = hosts.stream()
.map(s -> this.convertExecHostLog(s, execLog, hostConfigMap.get(s.getId()), builtinsParams)) .map(s -> this.convertExecHostLog(s, execLog, hostConfigMap.get(s.getId()), builtinParams))
.collect(Collectors.toList()); .collect(Collectors.toList());
execHostLogDAO.insertBatch(execHostLogs); execHostLogDAO.insertBatch(execHostLogs);
// 操作日志 // 操作日志
@@ -228,16 +229,16 @@ public class ExecCommandServiceImpl implements ExecCommandService {
/** /**
* 转换为 execHostLog * 转换为 execHostLog
* *
* @param host host * @param host host
* @param execLog execLog * @param execLog execLog
* @param config config * @param config config
* @param builtinsParams builtinsParams * @param builtinParams builtinParams
* @return execHostLog * @return execHostLog
*/ */
private ExecHostLogDO convertExecHostLog(HostDO host, private ExecHostLogDO convertExecHostLog(HostDO host,
ExecLogDO execLog, ExecLogDO execLog,
HostSshConfigModel config, HostSshConfigModel config,
Map<String, Object> builtinsParams) { Map<String, Object> builtinParams) {
Long execId = execLog.getId(); Long execId = execLog.getId();
Long hostId = host.getId(); Long hostId = host.getId();
// 脚本路径 // 脚本路径
@@ -246,7 +247,7 @@ public class ExecCommandServiceImpl implements ExecCommandService {
scriptPath = this.buildScriptPath(config.getUsername(), config.getOsType(), execId, hostId); scriptPath = this.buildScriptPath(config.getUsername(), config.getOsType(), execId, hostId);
} }
// 获取参数 // 获取参数
String parameter = JSON.toJSONString(this.getHostParams(builtinsParams, host, config, scriptPath)); String parameter = JSON.toJSONString(this.getHostParams(builtinParams, host, config, scriptPath));
return ExecHostLogDO.builder() return ExecHostLogDO.builder()
.logId(execId) .logId(execId)
.hostId(hostId) .hostId(hostId)
@@ -267,7 +268,7 @@ public class ExecCommandServiceImpl implements ExecCommandService {
* @param request request * @param request request
* @return params * @return params
*/ */
private Map<String, Object> getBaseBuiltinsParams(Long execId, ExecCommandExecDTO request) { private Map<String, Object> getBaseBuiltinParams(Long execId, ExecCommandExecDTO request) {
String uuid = UUIds.random(); String uuid = UUIds.random();
Date date = new Date(); Date date = new Date();
// 输入参数 // 输入参数
@@ -353,8 +354,7 @@ public class ExecCommandServiceImpl implements ExecCommandService {
* @return logPath * @return logPath
*/ */
private String buildLogPath(Long logId, Long hostId) { private String buildLogPath(Long logId, Long hostId) {
String logFile = "/" + PathConst.EXEC + "/" + logId + "/" + logId + "_" + hostId + ".log"; return logsFileClient.getReturnPath(EndpointDefine.EXEC_LOG.format(logId, hostId));
return logsFileClient.getReturnPath(logFile);
} }
/** /**
@@ -368,11 +368,11 @@ public class ExecCommandServiceImpl implements ExecCommandService {
*/ */
private String buildScriptPath(String username, String osType, Long logId, Long hostId) { private String buildScriptPath(String username, String osType, Long logId, Long hostId) {
HostSshOsTypeEnum os = HostSshOsTypeEnum.of(osType); HostSshOsTypeEnum os = HostSshOsTypeEnum.of(osType);
String name = PathConst.EXEC String name = FileConst.EXEC
+ "_" + logId + "/" + logId
+ "_" + hostId + "/" + hostId
+ os.getScriptSuffix(); + os.getScriptSuffix();
return PathUtils.buildAppPath(HostSshOsTypeEnum.WINDOWS.equals(os), username, PathConst.SCRIPT, name); return PathUtils.buildAppPath(HostSshOsTypeEnum.WINDOWS.equals(os), username, FileConst.SCRIPT, name);
} }
} }

View File

@@ -11,11 +11,13 @@ import com.orion.lang.utils.Strings;
import com.orion.lang.utils.collect.Lists; import com.orion.lang.utils.collect.Lists;
import com.orion.lang.utils.io.Files1; import com.orion.lang.utils.io.Files1;
import com.orion.lang.utils.io.Streams; import com.orion.lang.utils.io.Streams;
import com.orion.spring.SpringHolder;
import com.orion.visor.framework.biz.operator.log.core.utils.OperatorLogs; import com.orion.visor.framework.biz.operator.log.core.utils.OperatorLogs;
import com.orion.visor.framework.common.annotation.Keep; import com.orion.visor.framework.common.annotation.Keep;
import com.orion.visor.framework.common.constant.Const; import com.orion.visor.framework.common.constant.Const;
import com.orion.visor.framework.common.constant.ErrorMessage; import com.orion.visor.framework.common.constant.ErrorMessage;
import com.orion.visor.framework.common.constant.PathConst; import com.orion.visor.framework.common.constant.FileConst;
import com.orion.visor.framework.common.enums.EndpointDefine;
import com.orion.visor.framework.common.file.FileClient; import com.orion.visor.framework.common.file.FileClient;
import com.orion.visor.framework.common.utils.Valid; import com.orion.visor.framework.common.utils.Valid;
import com.orion.visor.framework.redis.core.utils.RedisStrings; import com.orion.visor.framework.redis.core.utils.RedisStrings;
@@ -46,11 +48,13 @@ import com.orion.visor.module.asset.service.ExecLogService;
import com.orion.visor.module.asset.service.HostConfigService; import com.orion.visor.module.asset.service.HostConfigService;
import com.orion.web.servlet.web.Servlets; import com.orion.web.servlet.web.Servlets;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
import javax.annotation.Resource; import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.InputStream; import java.io.InputStream;
import java.util.*; import java.util.*;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@@ -173,20 +177,7 @@ public class ExecLogServiceImpl implements ExecLogService {
@Override @Override
@Transactional(rollbackFor = Exception.class) @Transactional(rollbackFor = Exception.class)
public Integer deleteExecLogById(Long id, String source) { public Integer deleteExecLogById(Long id, String source) {
log.info("ExecLogService-deleteExecLogById id: {}", id); return this.deleteExecLogByIdList(Lists.singleton(id), source);
// 检查数据是否存在
ExecLogDO record = execLogDAO.selectByIdSource(id, source);
Valid.notNull(record, ErrorMessage.DATA_ABSENT);
// 中断命令执行
this.interruptTask(Lists.singleton(id));
// 删除执行日志
int effect = execLogDAO.deleteById(id);
// 删除主机日志
execHostLogService.deleteExecHostLogByLogId(Lists.singleton(id));
log.info("ExecLogService-deleteExecLogById id: {}, effect: {}", id, effect);
// 设置日志参数
OperatorLogs.add(OperatorLogs.COUNT, effect);
return effect;
} }
@Override @Override
@@ -201,15 +192,28 @@ public class ExecLogServiceImpl implements ExecLogService {
.count() .count()
.intValue(); .intValue();
Valid.isTrue(idList.size() == count, ErrorMessage.DATA_MODIFIED); Valid.isTrue(idList.size() == count, ErrorMessage.DATA_MODIFIED);
// 删除
return this.deleteExecLogByIdList(idList);
}
@Override
public Integer deleteExecLogByIdList(List<Long> idList) {
log.info("ExecLogService-deleteExecLogByIdList start: {}", idList);
if (Lists.isEmpty(idList)) {
OperatorLogs.add(OperatorLogs.COUNT, Const.N_0);
return Const.N_0;
}
// 中断命令执行 // 中断命令执行
this.interruptTask(idList); this.interruptTask(idList);
// 删除执行日志 // 删除执行日志
int effect = execLogDAO.deleteBatchIds(idList); int effect = execLogDAO.deleteBatchIds(idList);
// 删除主机日志 // 删除主机日志
execHostLogService.deleteExecHostLogByLogId(idList); execHostLogService.deleteExecHostLogByLogId(idList);
log.info("ExecLogService-deleteExecLogByIdList effect: {}", effect); log.info("ExecLogService-deleteExecLogByIdList end effect: {}", effect);
// 设置日志参数 // 设置日志参数
OperatorLogs.add(OperatorLogs.COUNT, effect); OperatorLogs.add(OperatorLogs.COUNT, effect);
// 异步删除文件
SpringHolder.getBean(ExecLogService.class).asyncDeleteLogFiles(idList);
return effect; return effect;
} }
@@ -229,19 +233,8 @@ public class ExecLogServiceImpl implements ExecLogService {
.stream() .stream()
.map(ExecLogDO::getId) .map(ExecLogDO::getId)
.collect(Collectors.toList()); .collect(Collectors.toList());
int effect = 0; // 删除
if (!idList.isEmpty()) { return this.deleteExecLogByIdList(idList);
// 中断命令执行
this.interruptTask(idList);
// 删除执行日志
effect = execLogDAO.delete(wrapper);
// 删除主机日志
execHostLogService.deleteExecHostLogByLogId(idList);
}
log.info("ExecLogService.clearExecLog finish {}", effect);
// 设置日志参数
OperatorLogs.add(OperatorLogs.COUNT, effect);
return effect;
} }
@Override @Override
@@ -428,19 +421,35 @@ public class ExecLogServiceImpl implements ExecLogService {
} catch (Exception e) { } catch (Exception e) {
log.error("ExecLogService.downloadLogFile error id: {}", id, e); log.error("ExecLogService.downloadLogFile error id: {}", id, e);
Streams.close(in); Streams.close(in);
String errorMessage = ErrorMessage.FILE_READ_ERROR; String errorMessage = ErrorMessage.FILE_READ_ERROR_CLEAR;
if (e instanceof InvalidArgumentException) { if (e instanceof InvalidArgumentException) {
errorMessage = e.getMessage(); errorMessage = e.getMessage();
} }
// 响应错误信息 // 响应错误信息
try { try {
Servlets.transfer(response, Strings.bytes(errorMessage), PathConst.ERROR_LOG); Servlets.transfer(response, Strings.bytes(errorMessage), FileConst.ERROR_LOG);
} catch (Exception ex) { } catch (Exception ex) {
log.error("ExecLogService.downloadLogFile transfer-error id: {}", id, ex); log.error("ExecLogService.downloadLogFile transfer-error id: {}", id, ex);
} }
} }
} }
@Override
@Async("asyncExecutor")
public void asyncDeleteLogFiles(List<Long> idList) {
if (Lists.isEmpty(idList)) {
return;
}
// 删除
idList.stream()
.map(s -> EndpointDefine.EXEC_LOG.format(s, Const.EMPTY))
.map(logsFileClient::getReturnPath)
.map(logsFileClient::getAbsolutePath)
.map(Files1::getParentPath)
.map(File::new)
.forEach(Files1::delete);
}
/** /**
* 构建查询 wrapper * 构建查询 wrapper
* *
@@ -457,8 +466,10 @@ public class ExecLogServiceImpl implements ExecLogService {
.like(ExecLogDO::getDescription, request.getDescription()) .like(ExecLogDO::getDescription, request.getDescription())
.like(ExecLogDO::getCommand, request.getCommand()) .like(ExecLogDO::getCommand, request.getCommand())
.eq(ExecLogDO::getStatus, request.getStatus()) .eq(ExecLogDO::getStatus, request.getStatus())
.in(ExecLogDO::getStatus, request.getStatusList())
.ge(ExecLogDO::getStartTime, Arrays1.getIfPresent(request.getStartTimeRange(), 0)) .ge(ExecLogDO::getStartTime, Arrays1.getIfPresent(request.getStartTimeRange(), 0))
.le(ExecLogDO::getStartTime, Arrays1.getIfPresent(request.getStartTimeRange(), 1)) .le(ExecLogDO::getStartTime, Arrays1.getIfPresent(request.getStartTimeRange(), 1))
.le(ExecLogDO::getCreateTime, request.getCreateTimeLe())
.orderByDesc(ExecLogDO::getId); .orderByDesc(ExecLogDO::getId);
} }

View File

@@ -2,13 +2,13 @@ package com.orion.visor.module.asset.service.impl;
import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.orion.lang.constant.Const;
import com.orion.lang.define.wrapper.DataGrid; import com.orion.lang.define.wrapper.DataGrid;
import com.orion.lang.utils.Arrays1; import com.orion.lang.utils.Arrays1;
import com.orion.lang.utils.Valid;
import com.orion.lang.utils.collect.Lists; import com.orion.lang.utils.collect.Lists;
import com.orion.visor.framework.biz.operator.log.core.utils.OperatorLogs; import com.orion.visor.framework.biz.operator.log.core.utils.OperatorLogs;
import com.orion.visor.framework.common.constant.Const;
import com.orion.visor.framework.common.constant.ErrorMessage; import com.orion.visor.framework.common.constant.ErrorMessage;
import com.orion.visor.framework.common.utils.Valid;
import com.orion.visor.framework.security.core.utils.SecurityUtils; import com.orion.visor.framework.security.core.utils.SecurityUtils;
import com.orion.visor.module.asset.convert.HostConnectLogConvert; import com.orion.visor.module.asset.convert.HostConnectLogConvert;
import com.orion.visor.module.asset.dao.HostConnectLogDAO; import com.orion.visor.module.asset.dao.HostConnectLogDAO;
@@ -52,7 +52,7 @@ public class HostConnectLogServiceImpl implements HostConnectLogService {
private HostTerminalManager hostTerminalManager; private HostTerminalManager hostTerminalManager;
@Override @Override
public Long create(HostConnectTypeEnum type, HostConnectLogCreateRequest request) { public HostConnectLogDO create(HostConnectTypeEnum type, HostConnectLogCreateRequest request) {
HostConnectLogDO record = HostConnectLogConvert.MAPPER.to(request); HostConnectLogDO record = HostConnectLogConvert.MAPPER.to(request);
record.setType(type.name()); record.setType(type.name());
String status = request.getStatus(); String status = request.getStatus();
@@ -64,7 +64,7 @@ public class HostConnectLogServiceImpl implements HostConnectLogService {
record.setEndTime(new Date()); record.setEndTime(new Date());
} }
hostConnectLogDAO.insert(record); hostConnectLogDAO.insert(record);
return record.getId(); return record;
} }
@Override @Override
@@ -169,6 +169,11 @@ public class HostConnectLogServiceImpl implements HostConnectLogService {
@Override @Override
public Integer deleteHostConnectLog(List<Long> idList) { public Integer deleteHostConnectLog(List<Long> idList) {
log.info("HostConnectLogService.deleteHostConnectLog start {}", JSON.toJSONString(idList)); log.info("HostConnectLogService.deleteHostConnectLog start {}", JSON.toJSONString(idList));
if (Lists.isEmpty(idList)) {
OperatorLogs.add(OperatorLogs.COUNT, Const.N_0);
return Const.N_0;
}
// 删除
int effect = hostConnectLogDAO.deleteBatchIds(idList); int effect = hostConnectLogDAO.deleteBatchIds(idList);
log.info("HostConnectLogService.deleteHostConnectLog finish {}", effect); log.info("HostConnectLogService.deleteHostConnectLog finish {}", effect);
// 设置日志参数 // 设置日志参数
@@ -184,13 +189,21 @@ public class HostConnectLogServiceImpl implements HostConnectLogService {
@Override @Override
public Integer clearHostConnectLog(HostConnectLogQueryRequest request) { public Integer clearHostConnectLog(HostConnectLogQueryRequest request) {
log.info("HostConnectLogService.clearHostConnectLog start {}", JSON.toJSONString(request)); log.info("HostConnectLogService.clearHostConnectLog start {}", JSON.toJSONString(request));
// 查询
LambdaQueryWrapper<HostConnectLogDO> wrapper = this.buildQueryWrapper(request)
.select(HostConnectLogDO::getId);
List<HostConnectLogDO> list = hostConnectLogDAO.selectList(wrapper);
if (list.isEmpty()) {
log.info("HostConnectLogService.clearHostConnectLog empty");
// 设置日志参数
OperatorLogs.add(OperatorLogs.COUNT, Const.N_0);
return Const.N_0;
}
// 删除 // 删除
LambdaQueryWrapper<HostConnectLogDO> wrapper = this.buildQueryWrapper(request); List<Long> idList = list.stream()
int effect = hostConnectLogDAO.delete(wrapper); .map(HostConnectLogDO::getId)
log.info("HostConnectLogService.clearHostConnectLog finish {}", effect); .collect(Collectors.toList());
// 设置日志参数 return this.deleteHostConnectLog(idList);
OperatorLogs.add(OperatorLogs.COUNT, effect);
return effect;
} }
@Override @Override
@@ -229,8 +242,10 @@ public class HostConnectLogServiceImpl implements HostConnectLogService {
.eq(HostConnectLogDO::getType, request.getType()) .eq(HostConnectLogDO::getType, request.getType())
.like(HostConnectLogDO::getToken, request.getToken()) .like(HostConnectLogDO::getToken, request.getToken())
.eq(HostConnectLogDO::getStatus, request.getStatus()) .eq(HostConnectLogDO::getStatus, request.getStatus())
.in(HostConnectLogDO::getStatus, request.getStatusList())
.ge(HostConnectLogDO::getStartTime, Arrays1.getIfPresent(request.getStartTimeRange(), 0)) .ge(HostConnectLogDO::getStartTime, Arrays1.getIfPresent(request.getStartTimeRange(), 0))
.le(HostConnectLogDO::getStartTime, Arrays1.getIfPresent(request.getStartTimeRange(), 1)) .le(HostConnectLogDO::getStartTime, Arrays1.getIfPresent(request.getStartTimeRange(), 1))
.le(HostConnectLogDO::getCreateTime, request.getCreateTimeLe())
.orderByDesc(HostConnectLogDO::getId); .orderByDesc(HostConnectLogDO::getId);
} }

View File

@@ -14,6 +14,7 @@ import com.orion.lang.utils.time.Dates;
import com.orion.visor.framework.biz.operator.log.core.utils.OperatorLogs; import com.orion.visor.framework.biz.operator.log.core.utils.OperatorLogs;
import com.orion.visor.framework.common.annotation.Keep; import com.orion.visor.framework.common.annotation.Keep;
import com.orion.visor.framework.common.constant.Const; import com.orion.visor.framework.common.constant.Const;
import com.orion.visor.framework.common.enums.EndpointDefine;
import com.orion.visor.framework.common.constant.ErrorMessage; import com.orion.visor.framework.common.constant.ErrorMessage;
import com.orion.visor.framework.common.file.FileClient; import com.orion.visor.framework.common.file.FileClient;
import com.orion.visor.framework.common.security.LoginUser; import com.orion.visor.framework.common.security.LoginUser;
@@ -140,7 +141,7 @@ public class UploadTaskServiceImpl implements UploadTaskService {
} }
uploadTaskFileDAO.insertBatch(uploadFiles); uploadTaskFileDAO.insertBatch(uploadFiles);
// 设置 uploadToken // 设置 uploadToken
String token = fileUploadApi.createUploadToken(user.getId(), Strings.format(SWAP_ENDPOINT, id)); String token = fileUploadApi.createUploadToken(user.getId(), EndpointDefine.UPLOAD_SWAP.format(id));
log.info("UploadTaskService-createUploadTask id: {}, effect: {}", id, effect); log.info("UploadTaskService-createUploadTask id: {}, effect: {}", id, effect);
// 添加日志参数 // 添加日志参数
OperatorLogs.add(OperatorLogs.NAME, record.getDescription()); OperatorLogs.add(OperatorLogs.NAME, record.getDescription());
@@ -296,7 +297,7 @@ public class UploadTaskServiceImpl implements UploadTaskService {
} }
// 查询记录 // 查询记录
List<String> paths = idList.stream() List<String> paths = idList.stream()
.map(s -> Strings.format(SWAP_ENDPOINT, s)) .map(EndpointDefine.UPLOAD_SWAP::format)
.map(localFileClient::getReturnPath) .map(localFileClient::getReturnPath)
.map(localFileClient::getAbsolutePath) .map(localFileClient::getAbsolutePath)
.collect(Collectors.toList()); .collect(Collectors.toList());
@@ -348,7 +349,7 @@ public class UploadTaskServiceImpl implements UploadTaskService {
.collect(Collectors.groupingBy(UploadTaskFileDO::getFileId)); .collect(Collectors.groupingBy(UploadTaskFileDO::getFileId));
fileIdGroups.forEach((k, v) -> { fileIdGroups.forEach((k, v) -> {
// 获取文件实际路径 // 获取文件实际路径
String path = localFileClient.getReturnPath(Strings.format(SWAP_ENDPOINT, id) + Const.SLASH + k); String path = localFileClient.getReturnPath(EndpointDefine.UPLOAD_SWAP.format(id) + Const.SLASH + k);
File file = new File(localFileClient.getAbsolutePath(path)); File file = new File(localFileClient.getAbsolutePath(path));
// 文件不存在/大小不正确 // 文件不存在/大小不正确
if (!Files1.isFile(file) || file.length() != v.get(0).getFileSize()) { if (!Files1.isFile(file) || file.length() != v.get(0).getFileSize()) {

View File

@@ -1,22 +1,18 @@
package com.orion.visor.module.asset.task; package com.orion.visor.module.asset.task;
import com.orion.lang.utils.Strings;
import com.orion.lang.utils.io.Files1;
import com.orion.lang.utils.time.Dates; import com.orion.lang.utils.time.Dates;
import com.orion.visor.framework.common.annotation.Keep;
import com.orion.visor.framework.common.file.FileClient;
import com.orion.visor.framework.common.utils.LockerUtils; import com.orion.visor.framework.common.utils.LockerUtils;
import com.orion.visor.module.asset.dao.ExecHostLogDAO; import com.orion.visor.module.asset.define.config.AppExecLogAutoClearConfig;
import com.orion.visor.module.asset.define.config.AppExecLogConfig; import com.orion.visor.module.asset.entity.request.exec.ExecLogQueryRequest;
import com.orion.visor.module.asset.entity.domain.ExecHostLogDO; import com.orion.visor.module.asset.enums.ExecStatusEnum;
import com.orion.visor.module.asset.service.ExecLogService;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.scheduling.annotation.Scheduled; import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import javax.annotation.Resource; import javax.annotation.Resource;
import java.io.File; import java.util.Date;
import java.util.List;
/** /**
* 执行日志文件自动清理 * 执行日志文件自动清理
@@ -27,23 +23,19 @@ import java.util.List;
*/ */
@Slf4j @Slf4j
@Component @Component
@ConditionalOnProperty(value = "app.exec-log.auto-clear", havingValue = "true", matchIfMissing = true) @ConditionalOnProperty(value = "app.auto-clear.exec-log.enabled", havingValue = "true")
public class ExecLogFileAutoClearTask { public class ExecLogFileAutoClearTask {
/** /**
* 分布式锁名称 * 分布式锁名称
*/ */
private static final String LOCK_KEY = "clear:elf:lock"; private static final String LOCK_KEY = "clear:exl:lock";
@Resource @Resource
private AppExecLogConfig appExecLogConfig; private AppExecLogAutoClearConfig appExecLogAutoClearConfig;
@Keep
@Resource
private FileClient logsFileClient;
@Resource @Resource
private ExecHostLogDAO execHostLogDAO; private ExecLogService execLogService;
/** /**
* 清理 * 清理
@@ -52,45 +44,23 @@ public class ExecLogFileAutoClearTask {
public void clear() { public void clear() {
log.info("ExecLogFileAutoClearTask.clear start"); log.info("ExecLogFileAutoClearTask.clear start");
// 获取锁并执行 // 获取锁并执行
LockerUtils.tryLock(LOCK_KEY, this::doClearFile); LockerUtils.tryLock(LOCK_KEY, this::doClear);
log.info("ExecLogFileAutoClearTask.clear finish"); log.info("ExecLogFileAutoClearTask.clear finish");
} }
/** /**
* 执行清理文件 * 执行清理
*/ */
private void doClearFile() { private void doClear() {
// 删除的时间区间 // 删除的时间区间
String maxPeriod = Dates.stream() Date createLessEq = Dates.stream()
.subDay(appExecLogConfig.getKeepPeriod()) .subDay(appExecLogAutoClearConfig.getKeepPeriod())
.format(); .date();
// 获取需要删除的最大id // 清理
ExecHostLogDO hostLog = execHostLogDAO.of() ExecLogQueryRequest request = new ExecLogQueryRequest();
.createWrapper() request.setCreateTimeLe(createLessEq);
.select(ExecHostLogDO::getLogId, ExecHostLogDO::getLogPath) request.setStatusList(ExecStatusEnum.FINISH_STATUS_LIST);
.lt(ExecHostLogDO::getCreateTime, maxPeriod) execLogService.clearExecLog(request);
.orderByDesc(ExecHostLogDO::getId)
.then()
.getOne();
if (hostLog == null) {
return;
}
// 获取执行日志根目录
String hostLogPath = logsFileClient.getAbsolutePath(hostLog.getLogPath());
String execLogPath = Files1.getParentPath(hostLogPath);
String parentPath = Files1.getParentPath(execLogPath);
// 获取需要删除的文件
List<File> files = Files1.listFilesFilter(parentPath, s -> {
if (!Strings.isInteger(s.getName())) {
return false;
}
return Long.parseLong(s.getName()) <= hostLog.getLogId();
}, false, true);
if (files.isEmpty()) {
return;
}
// 删除日志文件
files.forEach(Files1::delete);
} }
} }

View File

@@ -0,0 +1,66 @@
package com.orion.visor.module.asset.task;
import com.orion.lang.utils.time.Dates;
import com.orion.visor.framework.common.utils.LockerUtils;
import com.orion.visor.module.asset.define.config.AppHostConnectLogAutoClearConfig;
import com.orion.visor.module.asset.entity.request.host.HostConnectLogQueryRequest;
import com.orion.visor.module.asset.enums.HostConnectStatusEnum;
import com.orion.visor.module.asset.service.HostConnectLogService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.Date;
/**
* 主机连接日志自动清理
*
* @author Jiahang Li
* @version 1.0.0
* @since 2024/4/24 23:50
*/
@Slf4j
@Component
@ConditionalOnProperty(value = "app.auto-clear.host-connect-log.enabled", havingValue = "true")
public class HostConnectLogAutoClearTask {
/**
* 分布式锁名称
*/
private static final String LOCK_KEY = "clear:hcl:lock";
@Resource
private AppHostConnectLogAutoClearConfig appHostConnectLogAutoClearConfig;
@Resource
private HostConnectLogService hostConnectLogService;
/**
* 清理
*/
@Scheduled(cron = "0 10 3 * * ?")
public void clear() {
log.info("HostConnectLogAutoClearTask.clear start");
// 获取锁并执行
LockerUtils.tryLock(LOCK_KEY, this::doClear);
log.info("HostConnectLogAutoClearTask.clear finish");
}
/**
* 执行清理
*/
private void doClear() {
// 删除的时间区间
Date createLessEq = Dates.stream()
.subDay(appHostConnectLogAutoClearConfig.getKeepPeriod())
.date();
// 清理
HostConnectLogQueryRequest request = new HostConnectLogQueryRequest();
request.setCreateTimeLe(createLessEq);
request.setStatusList(HostConnectStatusEnum.FINISH_STATUS_LIST);
hostConnectLogService.clearHostConnectLog(request);
}
}

View File

@@ -14,25 +14,35 @@
"name": "app.exec-log", "name": "app.exec-log",
"type": "com.orion.visor.module.asset.define.config.AppExecLogConfig", "type": "com.orion.visor.module.asset.define.config.AppExecLogConfig",
"sourceType": "com.orion.visor.module.asset.define.config.AppExecLogConfig" "sourceType": "com.orion.visor.module.asset.define.config.AppExecLogConfig"
},
{
"name": "app.auto-clear.exec-log",
"type": "com.orion.visor.module.asset.define.config.AppExecLogAutoClearConfig",
"sourceType": "com.orion.visor.module.asset.define.config.AppExecLogAutoClearConfig"
},
{
"name": "app.auto-clear.host-connect-log",
"type": "com.orion.visor.module.asset.define.config.AppHostConnectLogAutoClearConfig",
"sourceType": "com.orion.visor.module.asset.define.config.AppHostConnectLogAutoClearConfig"
} }
], ],
"properties": [ "properties": [
{ {
"name": "app.tracker.offset", "name": "app.tracker.offset",
"type": "java.lang.Integer", "type": "java.lang.Integer",
"description": "加载偏移量 (行)", "description": "加载偏移量 (行).",
"defaultValue": "300" "defaultValue": "300"
}, },
{ {
"name": "app.tracker.delay", "name": "app.tracker.delay",
"type": "java.lang.Integer", "type": "java.lang.Integer",
"description": "延迟时间 (ms)", "description": "延迟时间 (ms).",
"defaultValue": "100" "defaultValue": "100"
}, },
{ {
"name": "app.tracker.wait-times", "name": "app.tracker.wait-times",
"type": "java.lang.Integer", "type": "java.lang.Integer",
"description": "文件未找到等待次数", "description": "文件未找到等待次数.",
"defaultValue": "100" "defaultValue": "100"
}, },
{ {
@@ -54,16 +64,24 @@
"defaultValue": "true" "defaultValue": "true"
}, },
{ {
"name": "app.exec-log.auto-clear", "name": "app.auto-clear.exec-log.enabled",
"type": "java.lang.Boolean", "type": "java.lang.Boolean",
"description": "自动清理执行文件.", "description": "开启 批量执行日志自动清理."
"defaultValue": "true"
}, },
{ {
"name": "app.exec-log.keep-period", "name": "app.auto-clear.exec-log.keep-period",
"type": "java.lang.Integer", "type": "java.lang.Integer",
"description": "保留周期 (天)", "description": "批量执行日志自动清理 保留周期 (天)."
"defaultValue": "30" },
{
"name": "app.auto-clear.host-connect-log.enabled",
"type": "java.lang.Boolean",
"description": "开启 主机连接日志自动清理."
},
{
"name": "app.auto-clear.host-connect-log.keep-period",
"type": "java.lang.Integer",
"description": "主机连接日志自动清理 保留周期 (天)."
} }
] ]
} }

View File

@@ -1,4 +1,52 @@
[ [
{
"name": "Dracula",
"dark": true,
"schema": {
"background": "#1E1F29",
"foreground": "#F8F8F2",
"cursor": "#BBBBBB",
"selectionBackground": "#44475A",
"black": "#000000",
"red": "#FF5555",
"green": "#50FA7B",
"yellow": "#F1FA8C",
"blue": "#BD93F9",
"cyan": "#8BE9FD",
"white": "#BBBBBB",
"brightBlack": "#555555",
"brightRed": "#FF5555",
"brightGreen": "#50FA7B",
"brightYellow": "#F1FA8C",
"brightBlue": "#BD93F9",
"brightCyan": "#8BE9FD",
"brightWhite": "#FFFFFF"
}
},
{
"name": "Atom",
"dark": true,
"schema": {
"background": "#161719",
"foreground": "#C5C8C6",
"cursor": "#D0D0D0",
"selectionBackground": "#444444",
"black": "#000000",
"red": "#FD5FF1",
"green": "#87C38A",
"yellow": "#FFD7B1",
"blue": "#85BEFD",
"cyan": "#85BEFD",
"white": "#E0E0E0",
"brightBlack": "#000000",
"brightRed": "#FD5FF1",
"brightGreen": "#94FA36",
"brightYellow": "#F5FFA8",
"brightBlue": "#96CBFE",
"brightCyan": "#85BEFD",
"brightWhite": "#E0E0E0"
}
},
{ {
"name": "catppuccin-mocha", "name": "catppuccin-mocha",
"dark": true, "dark": true,
@@ -95,54 +143,6 @@
"brightWhite": "#DCDFE4" "brightWhite": "#DCDFE4"
} }
}, },
{
"name": "Dracula",
"dark": true,
"schema": {
"background": "#1E1F29",
"foreground": "#F8F8F2",
"cursor": "#BBBBBB",
"selectionBackground": "#44475A",
"black": "#000000",
"red": "#FF5555",
"green": "#50FA7B",
"yellow": "#F1FA8C",
"blue": "#BD93F9",
"cyan": "#8BE9FD",
"white": "#BBBBBB",
"brightBlack": "#555555",
"brightRed": "#FF5555",
"brightGreen": "#50FA7B",
"brightYellow": "#F1FA8C",
"brightBlue": "#BD93F9",
"brightCyan": "#8BE9FD",
"brightWhite": "#FFFFFF"
}
},
{
"name": "Atom",
"dark": true,
"schema": {
"background": "#161719",
"foreground": "#C5C8C6",
"cursor": "#D0D0D0",
"selectionBackground": "#444444",
"black": "#000000",
"red": "#FD5FF1",
"green": "#87C38A",
"yellow": "#FFD7B1",
"blue": "#85BEFD",
"cyan": "#85BEFD",
"white": "#E0E0E0",
"brightBlack": "#000000",
"brightRed": "#FD5FF1",
"brightGreen": "#94FA36",
"brightYellow": "#F5FFA8",
"brightBlue": "#96CBFE",
"brightCyan": "#85BEFD",
"brightWhite": "#E0E0E0"
}
},
{ {
"name": "Apple System Colors", "name": "Apple System Colors",
"dark": true, "dark": true,

View File

@@ -28,9 +28,9 @@ public class TerminalThemeGenerator {
List<File> files = Files1.listFiles("D:\\idea-project\\iTerm2-Color-Schemes\\vhs"); List<File> files = Files1.listFiles("D:\\idea-project\\iTerm2-Color-Schemes\\vhs");
// 过滤的 theme // 过滤的 theme
List<String> schemaFilter = Lists.of( List<String> schemaFilter = Lists.of(
"Dracula", "Atom",
"catppuccin-mocha", "MaterialDesignColors", "catppuccin-mocha", "MaterialDesignColors",
"catppuccin-macchiato", "OneHalfDark", "catppuccin-macchiato", "OneHalfDark",
"Dracula", "Atom",
"Apple System Colors", "Builtin Tango Light", "Apple System Colors", "Builtin Tango Light",
"Duotone Dark", "BlulocoLight", "Duotone Dark", "BlulocoLight",
"Chester", "CLRS", "Chester", "CLRS",

View File

@@ -27,7 +27,7 @@ import java.util.Map;
public class PreferenceUpdatePartialRequest implements Serializable { public class PreferenceUpdatePartialRequest implements Serializable {
@NotBlank @NotBlank
@Size(max = 12) @Size(max = 16)
@Schema(description = "类型") @Schema(description = "类型")
private String type; private String type;

View File

@@ -25,7 +25,7 @@ import java.io.Serializable;
public class PreferenceUpdateRequest implements Serializable { public class PreferenceUpdateRequest implements Serializable {
@NotBlank @NotBlank
@Size(max = 12) @Size(max = 16)
@Schema(description = "类型") @Schema(description = "类型")
private String type; private String type;

View File

@@ -29,7 +29,7 @@ public class TerminalPreferenceStrategy extends AbstractGenericsDataStrategy<Ter
.builder() .builder()
.fontFamily("_") .fontFamily("_")
.fontSize(14) .fontSize(14)
.lineHeight(1.12) .lineHeight(1.20)
.letterSpacing(0) .letterSpacing(0)
.fontWeight("normal") .fontWeight("normal")
.fontWeightBold("bold") .fontWeightBold("bold")

View File

@@ -97,7 +97,7 @@ public class AuthenticationServiceImpl implements AuthenticationService {
// 重设用户缓存 // 重设用户缓存
this.setUserCache(user); this.setUserCache(user);
// 获取登录信息 // 获取登录信息
String remoteAddr = Servlets.getRemoteAddr(servletRequest); String remoteAddr = IpUtils.getRemoteAddr(servletRequest);
String location = IpUtils.getLocation(remoteAddr); String location = IpUtils.getLocation(remoteAddr);
String userAgent = Servlets.getUserAgent(servletRequest); String userAgent = Servlets.getUserAgent(servletRequest);
long current = System.currentTimeMillis(); long current = System.currentTimeMillis();

View File

@@ -396,6 +396,7 @@ public class DictValueServiceImpl implements DictValueService {
Map<String, String> schema = dictKeyService.getDictSchema(key); Map<String, String> schema = dictKeyService.getDictSchema(key);
// 转换 // 转换
return values.stream() return values.stream()
.sorted(Comparator.comparing(DictValueDO::getSort))
.map(s -> { .map(s -> {
// 设置值 // 设置值
JSONObject item = new JSONObject(); JSONObject item = new JSONObject();

View File

@@ -1,8 +1,10 @@
package com.orion.visor.module.infra.service.impl; package com.orion.visor.module.infra.service.impl;
import com.orion.ext.process.Processes; import com.orion.ext.process.ProcessAwaitExecutor;
import com.orion.lang.support.Attempt;
import com.orion.lang.utils.Arrays1; import com.orion.lang.utils.Arrays1;
import com.orion.lang.utils.Strings; import com.orion.lang.utils.Strings;
import com.orion.lang.utils.io.Streams;
import com.orion.visor.framework.common.constant.AppConst; import com.orion.visor.framework.common.constant.AppConst;
import com.orion.visor.framework.common.constant.Const; import com.orion.visor.framework.common.constant.Const;
import com.orion.visor.framework.common.utils.Mixes; import com.orion.visor.framework.common.utils.Mixes;
@@ -10,6 +12,8 @@ import com.orion.visor.module.infra.entity.vo.AppInfoVO;
import com.orion.visor.module.infra.service.SystemSettingService; import com.orion.visor.module.infra.service.SystemSettingService;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import java.io.ByteArrayOutputStream;
/** /**
* 系统服务 实现类 * 系统服务 实现类
* *
@@ -39,22 +43,33 @@ public class SystemSettingServiceImpl implements SystemSettingService {
if (this.uuid != null) { if (this.uuid != null) {
return this.uuid; return this.uuid;
} }
String[] cmd = new String[]{"cat /sys/class/dmi/id/product_serial", "dmidecode -s system-uuid", "wmic csproduct get uuid"}; String[][] cmd = new String[][]{
for (String s : cmd) { new String[]{"/bin/sh", "-c", "cat /sys/class/dmi/id/product_serial"},
new String[]{"/bin/bash", "-c", "cat /sys/class/dmi/id/product_serial"},
new String[]{"/bin/sh", "-c", "dmidecode -s system-uuid"},
new String[]{"/bin/bash", "-c", "dmidecode -s system-uuid"},
new String[]{"cmd", "/c", "wmic csproduct get uuid"}
};
for (String[] s : cmd) {
try { try {
// 执行命令获取 uuid String uuid = this.getCommandOutput(s);
String uuid = Processes.getOutputResultString(s);
if (Strings.isBlank(uuid)) { if (Strings.isBlank(uuid)) {
continue; continue;
} }
// 去除符号并且转为大写 // 去除符号并且转为大写
uuid = uuid.replaceAll(Const.DASHED, Const.EMPTY).toUpperCase(); uuid = uuid.replaceAll(Const.DASHED, Const.EMPTY)
// 去除特殊字符 .toUpperCase()
.trim();
// 去除 \n
String extraUuid = Arrays1.last(uuid.trim().split(Const.LF)); String extraUuid = Arrays1.last(uuid.trim().split(Const.LF));
if (!Strings.isBlank(extraUuid)) { if (!Strings.isBlank(extraUuid)) {
uuid = extraUuid; uuid = extraUuid.trim();
}
// 去除 :
extraUuid = Arrays1.last(uuid.trim().split(Const.COLON));
if (!Strings.isBlank(extraUuid)) {
uuid = extraUuid.trim();
} }
// 转义
return this.uuid = Mixes.obfuscate(uuid); return this.uuid = Mixes.obfuscate(uuid);
} catch (Exception e) { } catch (Exception e) {
// IGNORED // IGNORED
@@ -63,4 +78,25 @@ public class SystemSettingServiceImpl implements SystemSettingService {
return this.uuid = Const.UNKNOWN; return this.uuid = Const.UNKNOWN;
} }
/**
* 获取输出结果
*
* @param command command
* @return result
*/
private String getCommandOutput(String[] command) {
ByteArrayOutputStream out = new ByteArrayOutputStream();
ProcessAwaitExecutor executor = new ProcessAwaitExecutor(command);
try {
executor.streamHandler(i -> Attempt.uncheck(Streams::transfer, i, out))
.waitFor()
.sync()
.exec();
return out.toString();
} finally {
Streams.close(out);
Streams.close(executor);
}
}
} }

View File

@@ -1,6 +1,6 @@
VITE_API_BASE_URL= 'http://127.0.0.1:9200/orion-visor/api' 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_WS_BASE_URL= 'ws://127.0.0.1:9200/orion-visor/keep-alive'
VITE_APP_VERSION= '2.0.9' VITE_APP_VERSION= '2.0.10'
VITE_APP_RELEASE= 'community' VITE_APP_RELEASE= 'community'
VITE_SFTP_PREVIEW_MB= 2 VITE_SFTP_PREVIEW_MB= 2
VITE_DEMO_MODE= false VITE_DEMO_MODE= false

View File

@@ -1,6 +1,6 @@
VITE_API_BASE_URL= '/orion-visor/api' VITE_API_BASE_URL= '/orion-visor/api'
VITE_WS_BASE_URL= '/orion-visor/keep-alive' VITE_WS_BASE_URL= '/orion-visor/keep-alive'
VITE_APP_VERSION= '2.0.9' VITE_APP_VERSION= '2.0.10'
VITE_APP_RELEASE= 'community' VITE_APP_RELEASE= 'community'
VITE_SFTP_PREVIEW_MB= 2 VITE_SFTP_PREVIEW_MB= 2
VITE_DEMO_MODE= false VITE_DEMO_MODE= false

View File

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

View File

@@ -1,7 +1,7 @@
import axios from 'axios'; import axios from 'axios';
import qs from 'query-string'; import qs from 'query-string';
type PreferenceType = 'SYSTEM' | 'TERMINAL' export type PreferenceType = 'SYSTEM' | 'TERMINAL'
/** /**
* 用户偏好更新请求-单个 * 用户偏好更新请求-单个

View File

@@ -16,7 +16,7 @@ export interface TemplateParam {
} }
// 内置参数 // 内置参数
export const builtinsParams: Array<TemplateParam> = [ export const builtinParams: Array<TemplateParam> = [
{ {
name: 'source', name: 'source',
desc: '执行来源' desc: '执行来源'

View File

@@ -12,7 +12,7 @@
<script lang="ts" setup> <script lang="ts" setup>
import type { TemplateParam } from './const'; import type { TemplateParam } from './const';
import { onMounted, onUnmounted, ref } from 'vue'; import { onMounted, onUnmounted, ref } from 'vue';
import { builtinsParams, templatePrefix, templateSuffix, triggerPrefix } from './const'; import { builtinParams, templatePrefix, templateSuffix, triggerPrefix } from './const';
import * as monaco from 'monaco-editor'; import * as monaco from 'monaco-editor';
import { language } from 'monaco-editor/esm/vs/basic-languages/shell/shell.js'; import { language } from 'monaco-editor/esm/vs/basic-languages/shell/shell.js';
@@ -46,7 +46,7 @@
}); });
}); });
// 内置参数提示 // 内置参数提示
builtinsParams.forEach(s => { builtinParams.forEach(s => {
suggestions.push({ suggestions.push({
label: triggerPrefix + s.name, label: triggerPrefix + s.name,
kind: monaco.languages.CompletionItemKind.Variable, kind: monaco.languages.CompletionItemKind.Variable,

View File

@@ -5,7 +5,7 @@ type ViewType = 'table' | 'card' | undefined;
/** /**
* 应用状态 * 应用状态
*/ */
export interface AppState extends AppSetting, UserPreferenceLayout, UserPreferenceData, UserPreferenceViews { export interface AppState extends AppSetting, UserPreferenceLayout, UserPreferenceData, UserPreferenceView {
[key: string]: unknown; [key: string]: unknown;
} }
@@ -43,7 +43,7 @@ export interface UserPreferenceData {
/** /**
* 用户偏好 - 页面视图 * 用户偏好 - 页面视图
*/ */
export interface UserPreferenceViews { export interface UserPreferenceView {
hostView: ViewType; hostView: ViewType;
hostKeyView: ViewType; hostKeyView: ViewType;
hostIdentityView: ViewType; hostIdentityView: ViewType;

View File

@@ -2,6 +2,8 @@ import type { CacheState } from './types';
import type { AxiosResponse } from 'axios'; import type { AxiosResponse } from 'axios';
import type { TagType } from '@/api/meta/tag'; import type { TagType } from '@/api/meta/tag';
import { getTagList } from '@/api/meta/tag'; import { getTagList } from '@/api/meta/tag';
import type { PreferenceType } from '@/api/user/preference';
import { getPreference } from '@/api/user/preference';
import { defineStore } from 'pinia'; import { defineStore } from 'pinia';
import { getUserList } from '@/api/user/user'; import { getUserList } from '@/api/user/user';
import { getRoleList } from '@/api/user/role'; import { getRoleList } from '@/api/user/role';
@@ -131,5 +133,15 @@ export default defineStore('cache', {
return await this.load('execJob', getExecJobList, force); return await this.load('execJob', getExecJobList, force);
}, },
// 加载偏好
async loadPreference<T>(type: PreferenceType, force = false) {
return await this.load(`preference_${type}`, () => getPreference<T>(type), force);
},
// 加载偏好项
async loadPreferenceItem<T>(type: PreferenceType, item: string, force = false) {
return await this.load(`preference_${type}_${item}`, () => getPreference<T>(type, [item]), force);
},
} }
}); });

View File

@@ -3,6 +3,13 @@ import { dateFormat } from '@/utils';
const columns = [ const columns = [
{ {
title: 'id',
dataIndex: 'id',
slotName: 'id',
width: 100,
align: 'left',
fixed: 'left',
}, {
title: '连接用户', title: '连接用户',
dataIndex: 'username', dataIndex: 'username',
slotName: 'username', slotName: 'username',

View File

@@ -3,6 +3,13 @@ import { dateFormat } from '@/utils';
const columns = [ const columns = [
{ {
title: 'id',
dataIndex: 'id',
slotName: 'id',
width: 100,
align: 'left',
fixed: 'left',
}, {
title: '操作用户', title: '操作用户',
dataIndex: 'username', dataIndex: 'username',
slotName: 'username', slotName: 'username',

View File

@@ -13,6 +13,7 @@
:before-change="s => updateStatus(s as number)" /> :before-change="s => updateStatus(s as number)" />
</div> </div>
</template> </template>
<!-- 表单 -->
<a-spin v-show="config.status" :loading="loading" class="config-form-wrapper"> <a-spin v-show="config.status" :loading="loading" class="config-form-wrapper">
<!-- 表单 --> <!-- 表单 -->
<a-form :model="formModel" <a-form :model="formModel"
@@ -140,16 +141,16 @@
<script lang="ts" setup> <script lang="ts" setup>
import type { FieldRule } from '@arco-design/web-vue'; import type { FieldRule } from '@arco-design/web-vue';
import type { HostSshConfig } from './types/const'; import type { HostSshConfig } from '../types/const';
import { reactive, ref, watch } from 'vue'; import { reactive, ref, watch } from 'vue';
import { updateHostConfigStatus, updateHostConfig } from '@/api/asset/host-config'; import { updateHostConfigStatus, updateHostConfig } from '@/api/asset/host-config';
import { sshAuthTypeKey, sshOsTypeKey, SshAuthType, SshOsType } from './types/const'; import { sshAuthTypeKey, sshOsTypeKey, SshAuthType, SshOsType } from '../types/const';
import rules from './types/form.rules'; import rules from '../types/ssh-form.rules';
import { Message } from '@arco-design/web-vue'; import { Message } from '@arco-design/web-vue';
import useLoading from '@/hooks/loading'; import useLoading from '@/hooks/loading';
import { useDictStore } from '@/store'; import { useDictStore } from '@/store';
import { EnabledStatus } from '@/types/const'; import { EnabledStatus } from '@/types/const';
import { HostConfigType } from '../../../types/const'; import { HostConfigType } from '../types/const';
import HostKeySelector from '@/components/asset/host-key/selector/index.vue'; import HostKeySelector from '@/components/asset/host-key/selector/index.vue';
import HostIdentitySelector from '@/components/asset/host-identity/selector/index.vue'; import HostIdentitySelector from '@/components/asset/host-identity/selector/index.vue';

View File

@@ -30,16 +30,15 @@
</script> </script>
<script lang="ts" setup> <script lang="ts" setup>
import type { HostConfigWrapper } from '../../types/const'; import type { HostConfigWrapper, HostSshConfig } from '../types/const';
import type { HostSshConfig } from './ssh/types/const';
import { ref } from 'vue'; import { ref } from 'vue';
import useVisible from '@/hooks/visible'; import useVisible from '@/hooks/visible';
import useLoading from '@/hooks/loading'; import useLoading from '@/hooks/loading';
import { Message } from '@arco-design/web-vue'; import { Message } from '@arco-design/web-vue';
import { getHostConfigList } from '@/api/asset/host-config'; import { getHostConfigList } from '@/api/asset/host-config';
import { useCacheStore, useDictStore } from '@/store'; import { useCacheStore, useDictStore } from '@/store';
import { dictKeys } from './ssh/types/const'; import { dictKeys } from '../types/const';
import SshConfigForm from './ssh/ssh-config-form.vue'; import SshConfigForm from '../components/ssh-config-form.vue';
const { visible, setVisible } = useVisible(); const { visible, setVisible } = useVisible();
const { loading, setLoading } = useLoading(); const { loading, setLoading } = useLoading();

View File

@@ -1,3 +1,10 @@
// 主机所有配置
export interface HostConfigWrapper {
ssh: HostSshConfig;
[key: string]: unknown;
}
// 主机 SSH 配置 // 主机 SSH 配置
export interface HostSshConfig { export interface HostSshConfig {
osType?: string; osType?: string;
@@ -31,6 +38,11 @@ export const SshOsType = {
WINDOWS: 'WINDOWS', WINDOWS: 'WINDOWS',
}; };
// 主机配置类型
export const HostConfigType = {
SSH: 'ssh'
};
// 主机验证方式 字典项 // 主机验证方式 字典项
export const sshAuthTypeKey = 'hostSshAuthType'; export const sshAuthTypeKey = 'hostSshAuthType';

View File

@@ -72,7 +72,7 @@
import { useCacheStore } from '@/store'; import { useCacheStore } from '@/store';
import { Message } from '@arco-design/web-vue'; import { Message } from '@arco-design/web-vue';
import { updateHostGroupRel } from '@/api/asset/host-group'; import { updateHostGroupRel } from '@/api/asset/host-group';
import HostTransfer from './host-transfer.vue'; import HostTransfer from '../components/host-transfer.vue';
import HostGroupTree from '@/components/asset/host-group/tree/index.vue'; import HostGroupTree from '@/components/asset/host-group/tree/index.vue';
const { visible, setVisible } = useVisible(); const { visible, setVisible } = useVisible();

View File

@@ -37,8 +37,8 @@
import HostTable from './components/host-table.vue'; import HostTable from './components/host-table.vue';
import HostCardList from './components/host-card-list.vue'; import HostCardList from './components/host-card-list.vue';
import HostFormModal from './components/host-form-modal.vue'; import HostFormModal from './components/host-form-modal.vue';
import HostConfigDrawer from './components/config/host-config-drawer.vue'; import HostConfigDrawer from '../host-config/drawer/index.vue';
import HostGroupDrawer from './components/group/host-group-drawer.vue'; import HostGroupDrawer from '../host-group/drawer/index.vue';
const table = ref(); const table = ref();
const card = ref(); const card = ref();

View File

@@ -1,17 +1,3 @@
import type { HostSshConfig } from '../components/config/ssh/types/const';
// 主机所有配置
export interface HostConfigWrapper {
ssh: HostSshConfig;
[key: string]: unknown;
}
// 主机配置类型
export const HostConfigType = {
SSH: 'ssh'
};
// tag 颜色 // tag 颜色
export const tagColor = [ export const tagColor = [
'arcoblue', 'arcoblue',

View File

@@ -76,15 +76,15 @@
</script> </script>
<script lang="ts" setup> <script lang="ts" setup>
import type { ISshSession } from '@/views/host/terminal/types/terminal.type'; import type { ISshSession } from '../../types/terminal.type';
import type { CommandSnippetWrapperResponse, CommandSnippetQueryResponse } from '@/api/asset/command-snippet'; import type { CommandSnippetWrapperResponse, CommandSnippetQueryResponse } from '@/api/asset/command-snippet';
import { ref, watch, provide } from 'vue'; import { ref, watch, provide } from 'vue';
import useVisible from '@/hooks/visible'; import useVisible from '@/hooks/visible';
import useLoading from '@/hooks/loading'; import useLoading from '@/hooks/loading';
import { deleteCommandSnippet, getCommandSnippetList } from '@/api/asset/command-snippet'; import { deleteCommandSnippet, getCommandSnippetList } from '@/api/asset/command-snippet';
import { useCacheStore, useTerminalStore } from '@/store'; import { useCacheStore, useTerminalStore } from '@/store';
import { openUpdateSnippetKey, removeSnippetKey } from '../types/const'; import { openUpdateSnippetKey, removeSnippetKey } from './types/const';
import { PanelSessionType } from '@/views/host/terminal/types/terminal.const'; import { PanelSessionType } from '../../types/terminal.const';
import CommandSnippetListItem from './command-snippet-list-item.vue'; import CommandSnippetListItem from './command-snippet-list-item.vue';
import CommandSnippetListGroup from './command-snippet-list-group.vue'; import CommandSnippetListGroup from './command-snippet-list-group.vue';
import CommandSnippetFormDrawer from './command-snippet-form-drawer.vue'; import CommandSnippetFormDrawer from './command-snippet-form-drawer.vue';

View File

@@ -52,7 +52,7 @@
import useLoading from '@/hooks/loading'; import useLoading from '@/hooks/loading';
import useVisible from '@/hooks/visible'; import useVisible from '@/hooks/visible';
import { createCommandSnippet, updateCommandSnippet } from '@/api/asset/command-snippet'; import { createCommandSnippet, updateCommandSnippet } from '@/api/asset/command-snippet';
import formRules from '../types/form.rules'; import formRules from './types/form.rules';
import { Message } from '@arco-design/web-vue'; import { Message } from '@arco-design/web-vue';
import CommandSnippetGroupSelect from './command-snippet-group-select.vue'; import CommandSnippetGroupSelect from './command-snippet-group-select.vue';

View File

@@ -113,14 +113,14 @@
</script> </script>
<script lang="ts" setup> <script lang="ts" setup>
import type { ISshSession } from '@/views/host/terminal/types/terminal.type'; import type { ISshSession } from '../../types/terminal.type';
import type { CommandSnippetQueryResponse } from '@/api/asset/command-snippet'; import type { CommandSnippetQueryResponse } from '@/api/asset/command-snippet';
import { useTerminalStore } from '@/store'; import { useTerminalStore } from '@/store';
import { useDebounceFn } from '@vueuse/core'; import { useDebounceFn } from '@vueuse/core';
import { copy } from '@/hooks/copy'; import { copy } from '@/hooks/copy';
import { inject } from 'vue'; import { inject } from 'vue';
import { openUpdateSnippetKey, removeSnippetKey } from '../types/const'; import { openUpdateSnippetKey, removeSnippetKey } from './types/const';
import { PanelSessionType } from '@/views/host/terminal/types/terminal.const'; import { PanelSessionType } from '../../types/terminal.const';
const props = defineProps<{ const props = defineProps<{
item: CommandSnippetQueryResponse; item: CommandSnippetQueryResponse;

View File

@@ -39,12 +39,12 @@
import { onMounted, onUnmounted, watch } from 'vue'; import { onMounted, onUnmounted, watch } from 'vue';
import { addEventListen, removeEventListen } from '@/utils/event'; import { addEventListen, removeEventListen } from '@/utils/event';
import EmptyRecommend from './empty-recommend.vue'; import EmptyRecommend from './empty-recommend.vue';
import TerminalPanelsView from './terminal-panels-view.vue';
import NewConnectionView from '../new-connection/new-connection-view.vue'; import NewConnectionView from '../new-connection/new-connection-view.vue';
import TerminalDisplaySetting from '../setting/display/terminal-display-setting.vue'; import TerminalDisplaySetting from '../setting/display/terminal-display-setting.vue';
import TerminalThemeSetting from '../setting/theme/terminal-theme-setting.vue'; import TerminalThemeSetting from '../setting/theme/terminal-theme-setting.vue';
import TerminalGeneralSetting from '../setting/general/terminal-general-setting.vue'; import TerminalGeneralSetting from '../setting/general/terminal-general-setting.vue';
import TerminalShortcutSetting from '../setting/shortcut/terminal-shortcut-setting.vue'; import TerminalShortcutSetting from '../setting/shortcut/terminal-shortcut-setting.vue';
import TerminalPanelsView from '@/views/host/terminal/components/layout/terminal-panels-view.vue';
const emits = defineEmits(['openCommandSnippet', 'openPathBookmark', 'openTransferList', 'screenshot']); const emits = defineEmits(['openCommandSnippet', 'openPathBookmark', 'openTransferList', 'screenshot']);

View File

@@ -76,15 +76,15 @@
</script> </script>
<script lang="ts" setup> <script lang="ts" setup>
import { ISshSession } from '@/views/host/terminal/types/terminal.type'; import type { ISshSession } from '../../types/terminal.type';
import type { PathBookmarkWrapperResponse, PathBookmarkQueryResponse } from '@/api/asset/path-bookmark'; import type { PathBookmarkWrapperResponse, PathBookmarkQueryResponse } from '@/api/asset/path-bookmark';
import { ref, provide, onMounted, watch } from 'vue'; import { ref, provide, watch } from 'vue';
import useVisible from '@/hooks/visible'; import useVisible from '@/hooks/visible';
import useLoading from '@/hooks/loading'; import useLoading from '@/hooks/loading';
import { deletePathBookmark, getPathBookmarkList } from '@/api/asset/path-bookmark'; import { deletePathBookmark, getPathBookmarkList } from '@/api/asset/path-bookmark';
import { useCacheStore, useDictStore, useTerminalStore } from '@/store'; import { useCacheStore, useTerminalStore } from '@/store';
import { PanelSessionType } from '@/views/host/terminal/types/terminal.const'; import { PanelSessionType } from '../../types/terminal.const';
import { dictKeys, openUpdatePathKey, removePathKey } from '../types/const'; import { openUpdatePathKey, removePathKey } from './types/const';
import PathBookmarkListItem from './path-bookmark-list-item.vue'; import PathBookmarkListItem from './path-bookmark-list-item.vue';
import PathBookmarkListGroup from './path-bookmark-list-group.vue'; import PathBookmarkListGroup from './path-bookmark-list-group.vue';
import PathBookmarkFormDrawer from './path-bookmark-form-drawer.vue'; import PathBookmarkFormDrawer from './path-bookmark-form-drawer.vue';
@@ -281,11 +281,6 @@
getCurrentSession<ISshSession>(PanelSessionType.SSH.type)?.focus(); getCurrentSession<ISshSession>(PanelSessionType.SSH.type)?.focus();
}; };
//
onMounted(() => {
useDictStore().loadKeys(dictKeys);
});
</script> </script>
<style lang="less" scoped> <style lang="less" scoped>

View File

@@ -55,8 +55,9 @@
import useLoading from '@/hooks/loading'; import useLoading from '@/hooks/loading';
import useVisible from '@/hooks/visible'; import useVisible from '@/hooks/visible';
import { createPathBookmark, updatePathBookmark } from '@/api/asset/path-bookmark'; import { createPathBookmark, updatePathBookmark } from '@/api/asset/path-bookmark';
import formRules from '../types/form.rules'; import formRules from './types/form.rules';
import { PathBookmarkType, pathBookmarkTypeKey } from '../types/const'; import { PathBookmarkType } from './types/const';
import { pathBookmarkTypeKey } from '../../types/terminal.const';
import { useDictStore } from '@/store'; import { useDictStore } from '@/store';
import { Message } from '@arco-design/web-vue'; import { Message } from '@arco-design/web-vue';
import PathBookmarkGroupSelect from './path-bookmark-group-select.vue'; import PathBookmarkGroupSelect from './path-bookmark-group-select.vue';

View File

@@ -112,15 +112,15 @@
</script> </script>
<script lang="ts" setup> <script lang="ts" setup>
import type { ISftpSession, ISshSession } from '@/views/host/terminal/types/terminal.type'; import type { ISftpSession, ISshSession } from '../../types/terminal.type';
import type { PathBookmarkQueryResponse } from '@/api/asset/path-bookmark'; import type { PathBookmarkQueryResponse } from '@/api/asset/path-bookmark';
import { useTerminalStore } from '@/store'; import { useTerminalStore } from '@/store';
import { useDebounceFn } from '@vueuse/core'; import { useDebounceFn } from '@vueuse/core';
import { copy } from '@/hooks/copy'; import { copy } from '@/hooks/copy';
import { inject } from 'vue'; import { inject } from 'vue';
import { getParentPath } from '@/utils/file'; import { getParentPath } from '@/utils/file';
import { openUpdatePathKey, PathBookmarkType, removePathKey } from '../types/const'; import { openUpdatePathKey, PathBookmarkType, removePathKey } from './types/const';
import { PanelSessionType } from '@/views/host/terminal/types/terminal.const'; import { PanelSessionType } from '../../types/terminal.const';
const props = defineProps<{ const props = defineProps<{
item: PathBookmarkQueryResponse; item: PathBookmarkQueryResponse;

View File

@@ -9,9 +9,3 @@ export const openUpdatePathKey = Symbol();
// 删除 path key // 删除 path key
export const removePathKey = Symbol(); export const removePathKey = Symbol();
// 路径书签类型 字典项
export const pathBookmarkTypeKey = 'pathBookmarkType';
// 加载的字典值
export const dictKeys = [pathBookmarkTypeKey];

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