diff --git a/pom.xml b/pom.xml
index 35bc7a9..3467cd7 100644
--- a/pom.xml
+++ b/pom.xml
@@ -186,6 +186,11 @@
7.0.0.202409031743-r
+
+ org.springframework.boot
+ spring-boot-starter-freemarker
+
+
diff --git a/src/main/java/net/geedge/asw/common/config/FreeMarkerConfig.java b/src/main/java/net/geedge/asw/common/config/FreeMarkerConfig.java
new file mode 100644
index 0000000..5256d9c
--- /dev/null
+++ b/src/main/java/net/geedge/asw/common/config/FreeMarkerConfig.java
@@ -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;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/net/geedge/asw/common/util/TemplateUtil.java b/src/main/java/net/geedge/asw/common/util/TemplateUtil.java
new file mode 100644
index 0000000..5561a7e
--- /dev/null
+++ b/src/main/java/net/geedge/asw/common/util/TemplateUtil.java
@@ -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;
+
+ }
+
+}
diff --git a/src/main/java/net/geedge/asw/module/feign/FeignClientConfiguration.java b/src/main/java/net/geedge/asw/module/feign/FeignClientConfiguration.java
index ed51910..513b6c0 100644
--- a/src/main/java/net/geedge/asw/module/feign/FeignClientConfiguration.java
+++ b/src/main/java/net/geedge/asw/module/feign/FeignClientConfiguration.java
@@ -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);
+ }
+
}
\ No newline at end of file
diff --git a/src/main/java/net/geedge/asw/module/feign/client/DashboardClient.java b/src/main/java/net/geedge/asw/module/feign/client/DashboardClient.java
new file mode 100644
index 0000000..c7cec4b
--- /dev/null
+++ b/src/main/java/net/geedge/asw/module/feign/client/DashboardClient.java
@@ -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);
+}
diff --git a/src/main/java/net/geedge/asw/module/feign/client/KibanaClient.java b/src/main/java/net/geedge/asw/module/feign/client/KibanaClient.java
index 7995f28..7f45522 100644
--- a/src/main/java/net/geedge/asw/module/feign/client/KibanaClient.java
+++ b/src/main/java/net/geedge/asw/module/feign/client/KibanaClient.java
@@ -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);
+
}
\ No newline at end of file
diff --git a/src/main/java/net/geedge/asw/module/runner/controller/PcapController.java b/src/main/java/net/geedge/asw/module/runner/controller/PcapController.java
index 757e90a..b796015 100644
--- a/src/main/java/net/geedge/asw/module/runner/controller/PcapController.java
+++ b/src/main/java/net/geedge/asw/module/runner/controller/PcapController.java
@@ -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);
+ }
}
\ No newline at end of file
diff --git a/src/main/java/net/geedge/asw/module/runner/service/IPcapService.java b/src/main/java/net/geedge/asw/module/runner/service/IPcapService.java
index c76de40..59f81b9 100644
--- a/src/main/java/net/geedge/asw/module/runner/service/IPcapService.java
+++ b/src/main/java/net/geedge/asw/module/runner/service/IPcapService.java
@@ -22,4 +22,6 @@ public interface IPcapService extends IService{
void unparse2session(String[] ids);
String generateKibanaDiscoverUrl(String workspaceId, String pcapIds, String protocol, String streamId);
+
+ String generateKibanaDashboardUrl(String workspaceId, String pcapIds);
}
diff --git a/src/main/java/net/geedge/asw/module/runner/service/impl/PcapServiceImpl.java b/src/main/java/net/geedge/asw/module/runner/service/impl/PcapServiceImpl.java
index 1591c60..1ed1834 100644
--- a/src/main/java/net/geedge/asw/module/runner/service/impl/PcapServiceImpl.java
+++ b/src/main/java/net/geedge/asw/module/runner/service/impl/PcapServiceImpl.java
@@ -308,7 +308,7 @@ public class PcapServiceImpl extends ServiceImpl 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 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 pcapIdList = T.StrUtil.split(pcapIds, ",").stream().filter(s -> T.StrUtil.isNotEmpty(s)).collect(Collectors.toList());
+ List pcapList = this.list(new LambdaQueryWrapper().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
*
diff --git a/src/main/java/net/geedge/asw/module/workspace/entity/WorkspaceEntity.java b/src/main/java/net/geedge/asw/module/workspace/entity/WorkspaceEntity.java
index cc09a7e..9d1024f 100644
--- a/src/main/java/net/geedge/asw/module/workspace/entity/WorkspaceEntity.java
+++ b/src/main/java/net/geedge/asw/module/workspace/entity/WorkspaceEntity.java
@@ -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;
diff --git a/src/main/java/net/geedge/asw/module/workspace/service/impl/WorkspaceServiceImpl.java b/src/main/java/net/geedge/asw/module/workspace/service/impl/WorkspaceServiceImpl.java
index 3625f3b..0c7c2fe 100644
--- a/src/main/java/net/geedge/asw/module/workspace/service/impl/WorkspaceServiceImpl.java
+++ b/src/main/java/net/geedge/asw/module/workspace/service/impl/WorkspaceServiceImpl.java
@@ -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 params) {
Page page = new Query(WorkspaceEntity.class).getPage(params);
@@ -54,12 +71,15 @@ public class WorkspaceServiceImpl extends ServiceImpl 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 {
+ 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