Merge remote-tracking branch 'refs/remotes/origin/dev-1.0' into dev-1.1

# Conflicts:
#	src/main/java/net/geedge/asw/module/workspace/service/impl/WorkspaceServiceImpl.java
This commit is contained in:
zhangshuai
2024-10-25 11:12:28 +08:00
14 changed files with 276 additions and 4 deletions

View File

@@ -186,6 +186,11 @@
<version>7.0.0.202409031743-r</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-freemarker</artifactId>
</dependency>
</dependencies>
<build>

View File

@@ -0,0 +1,33 @@
package net.geedge.asw.common.config;
import net.geedge.asw.common.util.T;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.ui.freemarker.FreeMarkerConfigurationFactoryBean;
import java.util.Properties;
@Configuration
public class FreeMarkerConfig {
@Value("${asw.template.path:static}")
private String templatePath;
@Bean
public FreeMarkerConfigurationFactoryBean factoryBean() {
FreeMarkerConfigurationFactoryBean freeMarkerConfigurationFactoryBean = new FreeMarkerConfigurationFactoryBean();
// 设置 FreeMarker 模板位置
boolean exist = T.FileUtil.exist(templatePath);
templatePath = exist ? templatePath : "classpath:" + templatePath;
freeMarkerConfigurationFactoryBean.setTemplateLoaderPath(templatePath);
// 其他配置
Properties settings = new Properties();
settings.setProperty("default_encoding", "utf-8");
settings.setProperty("number_format", "0.##");
freeMarkerConfigurationFactoryBean.setFreemarkerSettings(settings);
return freeMarkerConfigurationFactoryBean;
}
}

View File

@@ -0,0 +1,28 @@
package net.geedge.asw.common.util;
import freemarker.cache.StringTemplateLoader;
import freemarker.template.Configuration;
import freemarker.template.Template;
import java.io.IOException;
public class TemplateUtil {
public static Template stringToTemplate(String templateStr,String templateKey) throws IOException {
// 创建配置类
Configuration configuration = new Configuration(Configuration.DEFAULT_INCOMPATIBLE_IMPROVEMENTS);
//创建模板加载器
StringTemplateLoader templateLoader = new StringTemplateLoader();
// 存入模板
templateLoader.putTemplate(templateKey, templateStr); //template = 虚拟名称, 用来当作获取静态文件的key
//加载模板加载器
configuration.setTemplateLoader(templateLoader);
//得到模板
Template template = configuration.getTemplate(templateKey, "utf-8");
return template;
}
}

View File

@@ -85,4 +85,15 @@ public class FeignClientConfiguration {
.target(PcapCommentClient.class, url);
}
@Bean("dashboardClient")
public DashboardClient dashboardClient() {
String url = UrlBuilder.ofHttp(kibanaUrl).toString();
log.info("[kibanaClient] [url: {}]", url);
return Feign.builder()
.encoder(new FormEncoder())
.decoder(new Fastjson2Decoder())
.client(new Http2Client())
.target(DashboardClient.class, url);
}
}

View File

@@ -0,0 +1,22 @@
package net.geedge.asw.module.feign.client;
import com.alibaba.fastjson2.JSONObject;
import feign.Headers;
import feign.Param;
import feign.RequestLine;
import org.springframework.cloud.openfeign.FeignClient;
import java.io.File;
@FeignClient(name = "dashboardClient")
@Headers("Authorization: Bearer {token}")
public interface DashboardClient {
@Headers({
"Content-Type: multipart/form-data",
"osd-xsrf: true",
"kbn-xsrf: true"
})
@RequestLine("POST /api/saved_objects/_import?createNewCopies={createNewCopies}")
JSONObject importDashboard(@Param("token") String token, @Param("file") File file, @Param("createNewCopies") boolean createNewCopies);
}

View File

@@ -10,8 +10,8 @@ import org.springframework.cloud.openfeign.FeignClient;
@Headers("Authorization: Bearer {token}")
public interface KibanaClient {
@RequestLine("GET /api/saved_objects/_find?fields=title&per_page=10000&type=index-pattern&search_fields=title&search={name}")
JSONObject findIndexPattern(@Param("token") String token, @Param("name") String name);
@RequestLine("GET /api/saved_objects/_find?fields=title&per_page=10000&type={type}&search_fields=title&search={name}")
JSONObject findIndexPattern(@Param("token") String token, @Param("type") String type , @Param("name") String name);
@Headers({
"Content-Type: application/json",
@@ -20,4 +20,11 @@ public interface KibanaClient {
@RequestLine("POST /api/saved_objects/index-pattern/{id}")
JSONObject saveIndexPattern(@Param("token") String token, @Param("id") String id, JSONObject body);
@Headers({
"Content-Type: application/json",
"osd-xsrf: true"
})
@RequestLine("DELETE /api/saved_objects/index-pattern/{id}?force={force}")
JSONObject deleteIndexPattern(@Param("token") String token, @Param("id") String id , @Param("force") boolean force);
}

View File

@@ -193,4 +193,10 @@ public class PcapController {
String discoverUrl = pcapService.generateKibanaDiscoverUrl(workspaceId, pcapIds, protocol, streamId);
return R.ok().putData("url", discoverUrl);
}
@GetMapping("/dashboard")
public R dashboard(@RequestParam String workspaceId, @RequestParam String pcapIds) {
String dashboardUrl = pcapService.generateKibanaDashboardUrl(workspaceId, pcapIds);
return R.ok().putData("url", dashboardUrl);
}
}

View File

@@ -22,4 +22,6 @@ public interface IPcapService extends IService<PcapEntity>{
void unparse2session(String[] ids);
String generateKibanaDiscoverUrl(String workspaceId, String pcapIds, String protocol, String streamId);
String generateKibanaDashboardUrl(String workspaceId, String pcapIds);
}

View File

@@ -308,7 +308,7 @@ public class PcapServiceImpl extends ServiceImpl<PcapDao, PcapEntity> implements
SaTokenInfo tokenInfo = StpUtil.getTokenInfo();
String token = tokenInfo.getTokenValue();
JSONObject index = kibanaClient.findIndexPattern(token, indexName);
JSONObject index = kibanaClient.findIndexPattern(token, "index-pattern" ,indexName);
JSONArray savedObjects = index.getJSONArray("saved_objects");
// check if index exists
@@ -372,6 +372,48 @@ public class PcapServiceImpl extends ServiceImpl<PcapDao, PcapEntity> implements
return kibanaDiscoverUrl;
}
@Override
public String generateKibanaDashboardUrl(String workspaceId, String pcapIds) {
WorkspaceEntity workspace = workspaceService.getById(workspaceId);
T.VerifyUtil.is(workspace).notNull(RCode.SYS_RECORD_NOT_FOUND);
List<String> pcapIdList = T.StrUtil.split(pcapIds, ",").stream().filter(s -> T.StrUtil.isNotEmpty(s)).collect(Collectors.toList());
List<PcapEntity> pcapList = this.list(new LambdaQueryWrapper<PcapEntity>().in(PcapEntity::getId, pcapIdList));
T.VerifyUtil.is(pcapList).notEmpty(RCode.SYS_RECORD_NOT_FOUND);
SaTokenInfo tokenInfo = StpUtil.getTokenInfo();
String token = tokenInfo.getTokenValue();
String dashboardId = T.JSONUtil.parseObj(workspace.getProperties()).getStr("dashboardId");
String dashboardName = String.format("workspace-%s", workspace.getName());
// build url
String baseUrl = UrlBuilder.ofHttp(kibanaUrl)
.addPath(T.StrUtil.concat(true, "/app/dashboards"))
.addQuery("jwt", token)
.toString();
String param1 = "_g=(filters:!(),refreshInterval:(pause:!t,value:0),time:(from:now-15m,to:now))";
String filter = pcapList.stream()
.map(PcapEntity::getName)
.map(pcapName -> "\"" + pcapName + "\"")
.collect(Collectors.joining(" or ", "pcap.name:(", ")"));
String param2 = String.format("_a=(description:'',filters:!(),fullScreenMode:!f,options:(hidePanelTitles:!f,useMargins:!t),query:(language:kuery,query:'%s'),timeRestore:!f,title:%s,viewMode:view)", filter, dashboardName);
String param3 = T.StrUtil.concat(true, "#/view/", dashboardId);
// 处理 空格 &
param2 = URLEncodeUtil.encode(param2);
param2 = param2.replaceAll("&", "%26");
String query = String.format("%s?%s&%s", param3, param1, param2);
String kibanaDashboardUrl = baseUrl + query;
if (log.isDebugEnabled()) {
log.debug("[generateKibanaDashboardUrl] [url: {}]", kibanaDashboardUrl);
}
return kibanaDashboardUrl;
}
/**
* calculate Parse Thread Timeout
*

View File

@@ -20,6 +20,7 @@ public class WorkspaceEntity {
private String tags;
private String visibility;
private String description;
private String properties;
private Long createTimestamp;
private Long updateTimestamp;

View File

@@ -1,7 +1,11 @@
package net.geedge.asw.module.workspace.service.impl;
import cn.dev33.satoken.stp.SaTokenInfo;
import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.extra.template.Template;
import cn.hutool.log.Log;
import com.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson2.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
@@ -10,6 +14,9 @@ import net.geedge.asw.common.util.ASWException;
import net.geedge.asw.common.util.Constants;
import net.geedge.asw.common.util.RCode;
import net.geedge.asw.common.util.T;
import net.geedge.asw.module.feign.client.DashboardClient;
import net.geedge.asw.module.feign.client.KibanaClient;
import net.geedge.asw.module.sys.service.ISysConfigService;
import net.geedge.asw.module.app.service.IGitService;
import net.geedge.asw.module.workspace.dao.WorkspaceDao;
import net.geedge.asw.module.workspace.entity.WorkspaceEntity;
@@ -20,6 +27,7 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.io.File;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
@@ -38,6 +46,15 @@ public class WorkspaceServiceImpl extends ServiceImpl<WorkspaceDao, WorkspaceEnt
@Autowired
private IWorkspaceMemberService workspaceMemberService;
@jakarta.annotation.Resource
private DashboardClient dashboardClient;
@jakarta.annotation.Resource
private KibanaClient kibanaClient;
@Autowired
private ISysConfigService sysConfigService;
@Override
public Page queryList(Map<String, Object> params) {
Page page = new Query(WorkspaceEntity.class).getPage(params);
@@ -54,12 +71,15 @@ public class WorkspaceServiceImpl extends ServiceImpl<WorkspaceDao, WorkspaceEnt
workspace.setUpdateUserId(StpUtil.getLoginIdAsString());
workspace.setCreateTimestamp(System.currentTimeMillis());
workspace.setUpdateTimestamp(System.currentTimeMillis());
workspace = this.createWorkspaceDashboard(workspace);
workspaceService.save(workspace);
String id = workspace.getId();
List<WorkspaceMemberEntity> members = workspace.getMembers();
if (T.CollUtil.isNotEmpty(members)) {
members.stream().forEach(x -> {
x.setWorkspaceId(workspace.getId());
x.setWorkspaceId(id);
x.setCreateTimestamp(System.currentTimeMillis());
x.setCreateUserId(StpUtil.getLoginIdAsString());
});
@@ -81,6 +101,92 @@ public class WorkspaceServiceImpl extends ServiceImpl<WorkspaceDao, WorkspaceEnt
return workspace;
}
private WorkspaceEntity createWorkspaceDashboard(WorkspaceEntity workspace) {
log.info("[createWorkspaceDashboard] [workspaceId: {}]", workspace.getId());
File dashboardFile = null;
try {
// index name
String indexName = String.format("workspace-%s-*", workspace.getName());
String indexId = T.StrUtil.uuid();
SaTokenInfo tokenInfo = StpUtil.getTokenInfo();
String token = tokenInfo.getTokenValue();
JSONObject index = kibanaClient.findIndexPattern(token, "index-pattern", indexName);
JSONArray savedObjects = index.getJSONArray("saved_objects");
// check if index exists
boolean indexExists = savedObjects.stream()
.filter(obj -> {
JSONObject attributes = ((JSONObject) obj).getJSONObject("attributes");
if (T.ObjectUtil.isEmpty(attributes)) return false;
String title = attributes.getString("title");
return T.StrUtil.equals(indexName, title);
})
.findFirst()
.isPresent();
log.info("[createWorkspaceDashboard] [workspace index exists:{}] [workspace indexName: {}]", indexExists, indexName);
// delete index-patten
if (indexExists) {
indexId = savedObjects.stream().map(x -> {
return ((JSONObject) x).getString("id");
}).findFirst().get();
kibanaClient.deleteIndexPattern(token, indexId, true);
if (log.isDebugEnabled()) {
log.debug("[createWorkspaceDashboard] [workspace index exists] [delete workspace index] [workspace indexName: {}] [workspace indexId: {}]", indexName, indexId);
}
}
// dashboard Name
String dashboardName = String.format("workspace-%s", workspace.getName());
Map<Object, Object> params = T.MapUtil.builder()
.put("indexId", indexId)
.put("indexName", indexName)
.put("dashboardName", dashboardName)
.build();
// render dashboard template
String opensearchDashboardTemplate = sysConfigService.getValue("opensearch_dashboard_template");
Template template = T.TemplateUtil.createEngine().getTemplate(opensearchDashboardTemplate);
opensearchDashboardTemplate = template.render(params);
dashboardFile = T.FileUtil.file(Constants.TEMP_PATH, "dashboard_template.ndjson");
T.FileUtil.writeString(opensearchDashboardTemplate, dashboardFile, "utf-8");
// create dashboard
dashboardClient.importDashboard(token, dashboardFile, true);
// get dashboardId
JSONObject dashboardObj = kibanaClient.findIndexPattern(token, "dashboard", dashboardName);
savedObjects = dashboardObj.getJSONArray("saved_objects");
String dashboardId = savedObjects.stream().map(x -> {
return ((JSONObject) x).getString("id");
}).findFirst().get();
// get indexId
JSONObject indexObj = kibanaClient.findIndexPattern(token, "index-pattern", indexName);
savedObjects = indexObj.getJSONArray("saved_objects");
indexId = savedObjects.stream().map(x -> {
return ((JSONObject) x).getString("id");
}).findFirst().get();
log.info("[createWorkspaceDashboard] [create dashboard] [dashboard name: {}] [dashboard id: {}]", dashboardName, dashboardId);
String properties = workspace.getProperties();
if (!T.StrUtil.isBlank(properties)){
T.JSONUtil.parseObj(properties).set("dashboardId", dashboardId);
}else {
workspace.setProperties(T.JSONUtil.toJsonStr(Map.of("dashboardId", dashboardId)));
}
workspace.setId(indexId);
return workspace;
}finally {
T.FileUtil.del(dashboardFile);
}
}
@Override
@Transactional(rollbackFor = Exception.class)
public WorkspaceEntity updateWorkspace(WorkspaceEntity workspace) {

View File

@@ -8,6 +8,7 @@
<result property="name" column="name"/>
<result property="tags" column="tags"/>
<result property="visibility" column="visibility"/>
<result property="properties" column="properties"/>
<result property="description" column="description"/>
<result property="createTimestamp" column="create_timestamp"/>
<result property="updateTimestamp" column="update_timestamp"/>

File diff suppressed because one or more lines are too long

View File

@@ -450,6 +450,7 @@ CREATE TABLE `workspace` (
`name` varchar(256) NOT NULL DEFAULT '' COMMENT '名称',
`tags` varchar(256) NOT NULL DEFAULT '' COMMENT '标签,多个逗号分隔',
`visibility` varchar(16) NOT NULL DEFAULT 'private' COMMENT '可见程度,可选值privatepublic 默认private',
`properties` varchar(256) NOT NULL DEFAULT '' COMMENT '属性',
`description` text NOT NULL DEFAULT '' COMMENT '描述信息',
`create_timestamp` bigint(20) NOT NULL COMMENT '创建时间戳',
`update_timestamp` bigint(20) NOT NULL COMMENT '更新时间戳',