From 70e9c4d3f46d8bfd6f21ad2e532444ec421a7e0c Mon Sep 17 00:00:00 2001 From: Timo Pagel Date: Mon, 29 Jan 2024 15:55:20 +0100 Subject: [PATCH] feat: add teams feat: add matrix dashboard --- .../GrafanaApplicationController.java | 2 +- .../controller/GrafanaController.java | 15 +- .../GrafanaDashboardExportController.java | 95 ++++----- .../GrafanaMatrixDashboardController.java | 38 ++++ .../GrafanaTeamDashboardController.java | 9 +- .../GrafanaTeamVariableController.java | 2 +- .../analyzer/controller/dto/FlattenDate.java | 1 - .../deserialization/ActivityDirector.java | 1 + .../analyzer/deserialization/Application.java | 10 +- .../deserialization/ApplicationDirector.java | 200 ++++++++++++++---- .../analyzer/deserialization/Team.java | 29 +++ .../analyzer/deserialization/YamlScanner.java | 32 ++- .../skeleton/SkeletonActivity.java | 4 +- .../grafana/GrafanaDashboardCreator.java | 109 ++++++---- .../analyzer/grafana/PanelConfiguration.java | 19 ++ .../analyzer/grafana/PanelFactory.java | 76 ++++++- .../analyzer/grafana/TeamDashboard.java | 3 + src/main/resources/application-dev.properties | 18 +- src/main/resources/application.properties | 4 +- src/main/resources/teams.yaml | 12 ++ .../templates/grafana-dashboard-matrix.ftl | 42 ++++ .../templates/grafana-dashboard-team.ftl | 11 +- .../panel-infinity-matrix/heatmap.ftl | 85 ++++++++ .../templates/panel-infinity-team/count.ftl | 79 ++++--- .../timeseries-flatdate.ftl | 137 ++++++++++++ 25 files changed, 812 insertions(+), 221 deletions(-) create mode 100644 src/main/java/org/owasp/dsomm/metricca/analyzer/controller/GrafanaMatrixDashboardController.java create mode 100644 src/main/java/org/owasp/dsomm/metricca/analyzer/deserialization/Team.java create mode 100644 src/main/resources/teams.yaml create mode 100644 src/main/resources/templates/grafana-dashboard-matrix.ftl create mode 100644 src/main/resources/templates/panel-infinity-matrix/heatmap.ftl diff --git a/src/main/java/org/owasp/dsomm/metricca/analyzer/controller/GrafanaApplicationController.java b/src/main/java/org/owasp/dsomm/metricca/analyzer/controller/GrafanaApplicationController.java index 9fb4de4..73833ad 100644 --- a/src/main/java/org/owasp/dsomm/metricca/analyzer/controller/GrafanaApplicationController.java +++ b/src/main/java/org/owasp/dsomm/metricca/analyzer/controller/GrafanaApplicationController.java @@ -32,7 +32,7 @@ public class GrafanaApplicationController { public Collection getApplicationIds() throws IOException, GitAPIException, InstantiationException, IllegalAccessException, ClassNotFoundException { Set applicationIds = new HashSet<>(); for (Application application : applicationDirector.getApplications()) { - applicationIds.add(application.getApplication()); + applicationIds.add(application.getName()); } return applicationIds; } diff --git a/src/main/java/org/owasp/dsomm/metricca/analyzer/controller/GrafanaController.java b/src/main/java/org/owasp/dsomm/metricca/analyzer/controller/GrafanaController.java index 0d5c116..b2c7bb7 100644 --- a/src/main/java/org/owasp/dsomm/metricca/analyzer/controller/GrafanaController.java +++ b/src/main/java/org/owasp/dsomm/metricca/analyzer/controller/GrafanaController.java @@ -56,6 +56,17 @@ public Collection getTeamApplications(@PathVariable String teamName return applicationsToReturn; } + @RequestMapping(value = "/team/{teamName}/application/names", method = RequestMethod.GET) + @ResponseBody + public Collection getTeamApplicationNames(@PathVariable String teamName) throws Exception { + Collection applicationsToReturn = new ArrayList(); + for (Application application : applicationDirector.getApplications()) { + if (application.getTeam().equals(teamName)) { + applicationsToReturn.add(application.getName()); + } + } + return applicationsToReturn; + } @RequestMapping(value = "/activity/{activityName}/simple", method = RequestMethod.GET) @ResponseBody @@ -70,10 +81,10 @@ public Collection getTeamActivity(@PathVariable String teamName, @Path Collection activitiesToReturn = new ArrayList(); for (Application application : applicationDirector.getApplications()) { if (application.getTeam().equals(teamName)) { - if (application.getApplication().equals(applicationId)) { + if (application.getName().equals(applicationId)) { for (Activity activity : application.getActivities()) { if (activity.getName().equals(activityName)) { - logger.debug("Found activity: " + activity.getName() + " in application: " + application.getApplication()); + logger.debug("Found activity: " + activity.getName() + " in application: " + application.getName()); activitiesToReturn.add(activity); } } diff --git a/src/main/java/org/owasp/dsomm/metricca/analyzer/controller/GrafanaDashboardExportController.java b/src/main/java/org/owasp/dsomm/metricca/analyzer/controller/GrafanaDashboardExportController.java index b888a08..986fbe9 100644 --- a/src/main/java/org/owasp/dsomm/metricca/analyzer/controller/GrafanaDashboardExportController.java +++ b/src/main/java/org/owasp/dsomm/metricca/analyzer/controller/GrafanaDashboardExportController.java @@ -1,21 +1,20 @@ package org.owasp.dsomm.metricca.analyzer.controller; -import org.eclipse.jgit.api.errors.GitAPIException; import org.owasp.dsomm.metricca.analyzer.deserialization.ApplicationDirector; -import org.owasp.dsomm.metricca.analyzer.deserialization.skeleton.SkeletonActivity; -import org.owasp.dsomm.metricca.analyzer.grafana.*; +import org.owasp.dsomm.metricca.analyzer.grafana.GrafanaDashboardCreator; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.HttpStatus; import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.server.ResponseStatusException; import java.io.IOException; import java.util.HashMap; -import java.util.Map; @Controller public class GrafanaDashboardExportController { @@ -25,36 +24,31 @@ public class GrafanaDashboardExportController { private ApplicationDirector applicationDirector; @Autowired - private TeamDashboard teamDashboard; - @Autowired - private OverviewDashboard overviewDashboard; - - @Value("${metricCA.grafana.baseurl:http://localhost:3000}") - private String grafanaBaseUrl; + private GrafanaDashboardCreator grafanaDashboardCreator; - @Value("${metricCA.grafana.apiKey}") - private String grafanaApiKey; - @Value("${metricCA.grafana.timeoutInSeconds:10}") - private Integer grafanaApiTimeoutInSeconds; - - @RequestMapping(value = "/dashboard/overview", method = RequestMethod.GET, produces = "application/json") + @RequestMapping(value = "/dashboard/{type}", method = RequestMethod.GET, produces = "application/json") @ResponseBody - public String getOverviewDashboard() throws Exception { - return overviewDashboard.getDashboard(getPanelConfigurations().values()); + public String getOverviewDashboard(@PathVariable String type) throws Exception { + HashMap dashboards = grafanaDashboardCreator.getDashboards(); + if(dashboards.containsKey(type)) { + return dashboards.get(type); + } else { + throw new ResponseStatusException( + HttpStatus.NOT_FOUND, "Dashboard not found" + ); + } } - @RequestMapping(value = "/dashboard/overview/push", method = RequestMethod.GET, produces = "application/json") + @RequestMapping(value = "/dashboards/push", method = RequestMethod.GET, produces = "application/json") @ResponseBody public String pushOverviewDashboard() throws IOException { String status = "{\"status\": \"error\"}"; try { - String dashboardString = overviewDashboard.getDashboard(getPanelConfigurations().values()); - GrafanaDashboardCreator grafanaDashboardCreator = new GrafanaDashboardCreator(grafanaBaseUrl, grafanaApiKey, dashboardString, grafanaApiTimeoutInSeconds); - if (grafanaDashboardCreator.pushDashboard()) { - status = "{\"status\": \"pushed\", \"dashboard\": \"" + dashboardString + "\"}"; + if (grafanaDashboardCreator.pushDashboards()) { + status = "{\"status\": \"pushed\"}"; } else { - status = "{\"status\": \"error\", \"dashboard\": \"" + dashboardString + "\"}"; + status = "{\"status\": \"error\"}"; } } catch (Exception e) { @@ -64,42 +58,27 @@ public String pushOverviewDashboard() throws IOException { return status; } - private Map getPanelConfigurations() throws GitAPIException, IOException, ClassNotFoundException, InstantiationException, IllegalAccessException { - Map panelConfigurations = new HashMap(); - for (SkeletonActivity activity : ApplicationDirector.getSkeletonActivities()) { - for (PanelConfiguration panelConfiguration : activity.getPanelConfigurations()) { - Map fetchedPanelConfigurations = PanelFactory.getPanelsForLevels(panelConfiguration, activity); - for (PanelConfiguration fetchedPanelConfiguration : fetchedPanelConfigurations.values()) { - if (!panelConfigurations.containsKey(fetchedPanelConfiguration.getTitle())) { - panelConfigurations.put(fetchedPanelConfiguration.getTitle(), fetchedPanelConfiguration); - } - } - } - } - for (PanelConfiguration panelConfiguration : panelConfigurations.values()) { - logger.info("panelConfiguration: " + panelConfiguration.getTitle()); - } - return panelConfigurations; - } - @RequestMapping(value = "/dashboard/team", method = RequestMethod.GET, produces = "application/json") @ResponseBody public String getTeamDashboard() throws Exception { - Map panelConfigurations = new HashMap(); - for (SkeletonActivity skeletonActivity : ApplicationDirector.getSkeletonActivities()) { - for (PanelConfiguration panelConfiguration : skeletonActivity.getPanelConfigurations()) { - Map fetchedPanelConfigurations = PanelFactory.getPanelsForLevels(panelConfiguration, skeletonActivity); - for (PanelConfiguration fetchedPanelConfiguration : fetchedPanelConfigurations.values()) { - if (!panelConfigurations.containsKey(fetchedPanelConfiguration.getTitle())) { - panelConfigurations.put(fetchedPanelConfiguration.getTitle(), fetchedPanelConfiguration); - } - } - } - } - for (PanelConfiguration panelConfiguration : panelConfigurations.values()) { - logger.info("panelConfiguration: " + panelConfiguration.getTitle()); - } - return teamDashboard.getDashboard(panelConfigurations.values()); + return grafanaDashboardCreator.getDashboards().get("team"); + +// String dashboardType = "team"; +// Map panelConfigurations = new HashMap(); +// for (SkeletonActivity skeletonActivity : ApplicationDirector.getSkeletonActivities()) { +// for (PanelConfiguration panelConfiguration : skeletonActivity.getPanelConfigurations(dashboardType)) { +// Map fetchedPanelConfigurations = PanelFactory.getPanelsForLevels(panelConfiguration, skeletonActivity); +// for (PanelConfiguration fetchedPanelConfiguration : fetchedPanelConfigurations.values()) { +// if (!panelConfigurations.containsKey(fetchedPanelConfiguration.getTitle())) { +// panelConfigurations.put(fetchedPanelConfiguration.getTitle(), fetchedPanelConfiguration); +// } +// } +// } +// } +// for (PanelConfiguration panelConfiguration : panelConfigurations.values()) { +// logger.info("panelConfiguration: " + panelConfiguration.getTitle()); +// } +// return teamDashboard.getDashboard(panelConfigurations.values()); } } \ No newline at end of file diff --git a/src/main/java/org/owasp/dsomm/metricca/analyzer/controller/GrafanaMatrixDashboardController.java b/src/main/java/org/owasp/dsomm/metricca/analyzer/controller/GrafanaMatrixDashboardController.java new file mode 100644 index 0000000..11309e1 --- /dev/null +++ b/src/main/java/org/owasp/dsomm/metricca/analyzer/controller/GrafanaMatrixDashboardController.java @@ -0,0 +1,38 @@ +package org.owasp.dsomm.metricca.analyzer.controller; + +import org.owasp.dsomm.metricca.analyzer.controller.dto.FlattenDate; +import org.owasp.dsomm.metricca.analyzer.deserialization.Application; +import org.owasp.dsomm.metricca.analyzer.deserialization.ApplicationDirector; +import org.owasp.dsomm.metricca.analyzer.deserialization.activity.Activity; +import org.owasp.dsomm.metricca.analyzer.deserialization.activity.SecurityTrainingActivity; +import org.owasp.dsomm.metricca.analyzer.deserialization.activity.component.DatePeriodHoursAndPeople; +import org.owasp.dsomm.metricca.analyzer.grafana.GrafanaDashboardCreator; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.ResponseBody; + +import java.util.Collection; +import java.util.Date; +import java.util.HashMap; +import java.util.List; + +@Controller +public class GrafanaMatrixDashboardController { + private static final Logger logger = LoggerFactory.getLogger(GrafanaMatrixDashboardController.class); + + @Autowired + private GrafanaDashboardCreator grafanaDashboardCreator; + + + + @RequestMapping(value = "/matrix/overview", method = RequestMethod.GET) + @ResponseBody + public String getOverviewDashboard() throws Exception { + return grafanaDashboardCreator.getDashboards().get("matrix"); + } +} \ No newline at end of file diff --git a/src/main/java/org/owasp/dsomm/metricca/analyzer/controller/GrafanaTeamDashboardController.java b/src/main/java/org/owasp/dsomm/metricca/analyzer/controller/GrafanaTeamDashboardController.java index 23456f6..d325b2f 100644 --- a/src/main/java/org/owasp/dsomm/metricca/analyzer/controller/GrafanaTeamDashboardController.java +++ b/src/main/java/org/owasp/dsomm/metricca/analyzer/controller/GrafanaTeamDashboardController.java @@ -12,7 +12,6 @@ import org.springframework.web.bind.annotation.ResponseBody; import java.util.Collection; -import java.util.LinkedHashMap; @Controller public class GrafanaTeamDashboardController { @@ -27,9 +26,11 @@ public Collection getActivitiesPerTeamFlatSimple(@PathVariable Stri return applicationDirector.getActivitiesPerTeamAndApplicationFlat(application, teamName, activityName); } - @RequestMapping(value = "/team/{teamName}/application/{application}/activity/{activityName}/map", method = RequestMethod.GET) + @RequestMapping(value = "/team/{teamName}/application/{application}/activity/{activityName}/entries", method = RequestMethod.GET) @ResponseBody - public LinkedHashMap> getActivitiesPerTeamFlatAsMap(@PathVariable String teamName, @PathVariable String application, @PathVariable String activityName) throws Exception { + public Collection getActivitiesPerTeamFlatAsMap(@PathVariable String teamName, @PathVariable String application, @PathVariable String activityName) throws Exception { return applicationDirector.getActivitiesPerTeamAndApplicationFlatAsLevelMap(application, teamName, activityName); } -} \ No newline at end of file +} + +//http://192.168.178.27:8080/team/${team}/application/${application}/activity/Conduction%20of%20simple%20threat%20modeling%20on%20technical%20level/simple \ No newline at end of file diff --git a/src/main/java/org/owasp/dsomm/metricca/analyzer/controller/GrafanaTeamVariableController.java b/src/main/java/org/owasp/dsomm/metricca/analyzer/controller/GrafanaTeamVariableController.java index e416821..3047056 100644 --- a/src/main/java/org/owasp/dsomm/metricca/analyzer/controller/GrafanaTeamVariableController.java +++ b/src/main/java/org/owasp/dsomm/metricca/analyzer/controller/GrafanaTeamVariableController.java @@ -41,7 +41,7 @@ public Collection getTeamApplicationIds(@PathVariable String teamName) t Collection applicationsToReturn = new ArrayList(); for (Application application : applicationDirector.getApplications()) { if (application.getTeam().equals(teamName)) { - applicationsToReturn.add(application.getApplication()); + applicationsToReturn.add(application.getName()); } } return applicationsToReturn; diff --git a/src/main/java/org/owasp/dsomm/metricca/analyzer/controller/dto/FlattenDate.java b/src/main/java/org/owasp/dsomm/metricca/analyzer/controller/dto/FlattenDate.java index 7776a8b..3f8583a 100644 --- a/src/main/java/org/owasp/dsomm/metricca/analyzer/controller/dto/FlattenDate.java +++ b/src/main/java/org/owasp/dsomm/metricca/analyzer/controller/dto/FlattenDate.java @@ -28,5 +28,4 @@ public Date getDate() { public void setDate(Date date) { this.date = date; } - } \ No newline at end of file diff --git a/src/main/java/org/owasp/dsomm/metricca/analyzer/deserialization/ActivityDirector.java b/src/main/java/org/owasp/dsomm/metricca/analyzer/deserialization/ActivityDirector.java index da53db0..223801e 100644 --- a/src/main/java/org/owasp/dsomm/metricca/analyzer/deserialization/ActivityDirector.java +++ b/src/main/java/org/owasp/dsomm/metricca/analyzer/deserialization/ActivityDirector.java @@ -20,6 +20,7 @@ public class ActivityDirector { public ActivityDirector(JsonNode activityObjects, List skeletonActivities, String kind) throws JsonProcessingException, InstantiationException, IllegalAccessException, ClassNotFoundException { List activities = new ArrayList<>(); for (SkeletonActivity skeletonActivity : skeletonActivities) { + logger.debug("Skeleton Activity kind: " + skeletonActivity.getKind() + " " + skeletonActivity.getClassName()); if (!skeletonActivity.getKind().equals(kind)) { continue; } diff --git a/src/main/java/org/owasp/dsomm/metricca/analyzer/deserialization/Application.java b/src/main/java/org/owasp/dsomm/metricca/analyzer/deserialization/Application.java index ce4d8e3..3ad7963 100644 --- a/src/main/java/org/owasp/dsomm/metricca/analyzer/deserialization/Application.java +++ b/src/main/java/org/owasp/dsomm/metricca/analyzer/deserialization/Application.java @@ -15,7 +15,7 @@ public class Application { private static final Logger logger = LoggerFactory.getLogger(Application.class); private final List activities; private String team; - private String application; + private String name; private String desiredLevel; public Application(JsonNode applicationYamlReader, List skeletonActivities, String kind) throws SkeletonNotFoundException, IOException, InstantiationException, IllegalAccessException, ClassNotFoundException { @@ -45,12 +45,12 @@ public void setTeam(String team) { this.team = team; } - public String getApplication() { - return application; + public String getName() { + return name; } - public void setApplication(String application) { - this.application = application; + public void setName(String name) { + this.name = name; } public String getDesiredLevel() { diff --git a/src/main/java/org/owasp/dsomm/metricca/analyzer/deserialization/ApplicationDirector.java b/src/main/java/org/owasp/dsomm/metricca/analyzer/deserialization/ApplicationDirector.java index 3870d1c..ccb4b53 100644 --- a/src/main/java/org/owasp/dsomm/metricca/analyzer/deserialization/ApplicationDirector.java +++ b/src/main/java/org/owasp/dsomm/metricca/analyzer/deserialization/ApplicationDirector.java @@ -80,6 +80,7 @@ private List getDeserializeSkeletons() throws IOException, Git private List getDeserializedApplications(List skeletonActivities) throws IOException, ClassNotFoundException, InstantiationException, IllegalAccessException, GitAPIException { List applications = new ArrayList<>(); YamlApplicationNodes yamlApplicationNodes = new YamlApplicationNodes(); + HashMap> teamActivities = new HashMap<>(); for (File yamlApplicationFilePath : yamlScanner.getApplicationYamls()) { logger.info("yamlApplicationFilePath: " + yamlApplicationFilePath.getPath()); ObjectMapper objectMapper = new ObjectMapper(new YAMLFactory()); @@ -99,10 +100,51 @@ private List getDeserializedApplications(List ske } applications.add(application); } + for (Application teamApplication : teamApplications) { + if (teamActivities.containsKey(teamName)) { + teamActivities.get(teamName).addAll(teamApplication.getActivities()); + continue; + } + if (teamActivities.containsKey(teamName)) { + teamActivities.get(teamName).addAll(teamApplication.getActivities()); + } else { + teamActivities.put(teamName, teamApplication.getActivities()); + } + } } + setApplicationFromTeamsYaml(teamActivities); + return applications; } + private void setApplicationFromTeamsYaml(HashMap> teamActivities) throws IOException, ClassNotFoundException, InstantiationException, IllegalAccessException { + List teams = yamlScanner.getTeamsAndApplicationYaml(); + for (Team team : teams) { + logger.debug("team: " + team.getName()); + String settings = "activities:" + System.lineSeparator() + "settings:" + System.lineSeparator() + " team: " + team.getName(); + JsonNode settingsTeamNode = new ObjectMapper(new YAMLFactory()).readTree(settings); + Application applicationTeam = createApplication(settingsTeamNode, skeletonActivities, "team"); + for (String applicationName : team.getApplications()) { + if (isApplicationInList(applications, applicationName)) { + continue; + } + String settingsActivity = settings + System.lineSeparator() + " application: " + applicationName; + JsonNode settingsActivityNode = new ObjectMapper(new YAMLFactory()).readTree(settingsActivity); + Application application = createApplication(settingsActivityNode, skeletonActivities, "application"); + + if (teamActivities.containsKey(team.getName())) { + application.getActivities().addAll(teamActivities.get(team.getName())); + } else { + application.getActivities().addAll(applicationTeam.getActivities()); + } + for(Activity activity : application.getActivities()) { + logger.debug("activity: " + activity.getName() + " " + activity.getKind() + " "); + } + applications.add(application); + } + } + } + private boolean isNameInList(List activities, String name) { boolean isInList = false; for (Activity activity : activities) { @@ -118,7 +160,7 @@ private Application createApplication(JsonNode jsonNode, List JsonNode settings = jsonNode.get("settings"); Application newApp = new Application(jsonNode.get("activities"), skeletonActivities, kind); if (settings.has("application")) { - newApp.setApplication(settings.get("application").asText()); + newApp.setName(settings.get("application").asText()); } if (settings.has("team")) { newApp.setTeam(settings.get("team").asText()); @@ -238,44 +280,15 @@ public Collection getActivitiesPerTeamAndApplicationFlat(String app for (java.util.Date date : datesFromActivities) { FlattenDate flattenDate = new FlattenDate(date); for (Application application : getApplications()) { - if (applicationName != null && !application.getApplication().equals(applicationName)) { + if (applicationName != null && !application.getName().equals(applicationName)) { continue; } if (teamName != null && !application.getTeam().equals(teamName)) { continue; } for (Activity activity : application.getActivities(activityName)) { - boolean value = false; - org.owasp.dsomm.metricca.analyzer.deserialization.activity.threshold.DatePeriod dateComponent = null; - if (activity.getThresholdDatePeriodMap().get(level) == null) { - logger.debug("1activity.getThresholdDatePeriodMap().get(level) == null"); - } else { - if (isDateStartDatePeriod(date, activity, level)) { - logger.info("isDateAStartDatePeriod " + activityName + " " + level + " " + date + " " + application.getTeam()); - value = true; - } else if (isDateEndDatePeriod(date, activity, level)) { - logger.info("isDateAEndDatePeriod " + activityName + " " + level + " " + date); - dateComponent = activity.getThresholdDatePeriodMap().get(level).getDatePeriodEndForDate(date); - value = !dateComponent.getShowEndDate(); - } else if (isDateEndDatePeriodEnforced(date, activity, level)) { - logger.info("isDateAEndDateEnforcedPeriod " + activityName + " " + level + " " + date + " " + application.getTeam()); - dateComponent = activity.getThresholdDatePeriodMap().get(level).getClosestBeforeDatePeriodComponent(date); - if (dateComponent == null) { //no DatePeriod found, that means it is not implemented (yet) - value = false; - } else { - value = dateComponent.isInPeriod(date); - } - } else { // it is a DatePeriod from an other activity - logger.info("an other activity " + activityName + " " + level + " " + date + " " + application.getTeam()); - dateComponent = activity.getThresholdDatePeriodMap().get(level).getClosestBeforeDatePeriodComponent(date); - if (dateComponent == null) { - value = false; // no DatePeriod found, that means it is not implemented (yet) - } else { - value = dateComponent.isInPeriod(date); - } - } - } - String label = getLabel(application.getTeam(), application.getApplication(), activity); + boolean value = isDateActive(activity, date, level, activityName, application.getTeam()); + String label = getLabel(application.getTeam(), application.getName(), activity, level); flattenDate.addDynamicField(label, value); } } @@ -284,16 +297,50 @@ public Collection getActivitiesPerTeamAndApplicationFlat(String app return flattenedActivitiesToReturn; } - private String getLabel(String team, String applicationName, Activity activity) { + private boolean isDateActive(Activity activity, Date date, String level, String activityName, String teamName) { + boolean value = false; + org.owasp.dsomm.metricca.analyzer.deserialization.activity.threshold.DatePeriod dateComponent = null; + + if (activity.getThresholdDatePeriodMap() == null || activity.getThresholdDatePeriodMap().get(level) == null) { + logger.debug("activity.getThresholdDatePeriodMap().get(level) == null"); + } else { + if (isDateStartDatePeriod(date, activity, level)) { + logger.info("isDateAStartDatePeriod " + activity.getName() + " " + level + " " + date + " " + teamName); + value = true; + } else if (isDateEndDatePeriod(date, activity, level)) { + logger.info("isDateAEndDatePeriod " + activity.getName() + " " + level + " " + date); + dateComponent = activity.getThresholdDatePeriodMap().get(level).getDatePeriodEndForDate(date); + value = !dateComponent.getShowEndDate(); + } else if (isDateEndDatePeriodEnforced(date, activity, level)) { + logger.info("isDateAEndDateEnforcedPeriod " + activity.getName() + " " + level + " " + date + " " + teamName); + dateComponent = activity.getThresholdDatePeriodMap().get(level).getClosestBeforeDatePeriodComponent(date); + if (dateComponent == null) { //no DatePeriod found, that means it is not implemented (yet) + value = false; + } else { + value = dateComponent.isInPeriod(date); + } + } else { // it is a DatePeriod from an other activity + logger.debug("an other activity " + activity.getName() + " " + level + " " + date + " " + teamName); + dateComponent = activity.getThresholdDatePeriodMap().get(level).getClosestBeforeDatePeriodComponent(date); + if (dateComponent == null) { + value = false; // no DatePeriod found, that means it is not implemented (yet) + } else { + value = dateComponent.isInPeriod(date); + } + } + } + return value; + } + + private String getLabel(String team, String applicationName, Activity activity, String level) { switch (activity.getKind()) { case "team": - return team; + return team + " " + level; case "application": - return team + " - " + applicationName; + return team + " - " + applicationName + " " + level; default: return ""; } - } private boolean isDateStartDatePeriod(java.util.Date date, Activity activity, String level) { @@ -315,11 +362,84 @@ public Collection getActivitiesFlat(String activityName) throws Exc return getActivitiesPerTeamFlat(null, activityName); } - public LinkedHashMap> getActivitiesPerTeamAndApplicationFlatAsLevelMap(String applicationName, String teamName, String activityName) throws Exception { - LinkedHashMap> flattenedActivitiesToReturn = new LinkedHashMap>(); + public Collection getActivitiesPerTeamAndApplicationFlatAsLevelMap(String applicationName, String teamName, String activityName) throws Exception { + Collection flattenedActivitiesToReturn = new ArrayList(); + for (String level : getLevelsForActivity(activityName)) { - flattenedActivitiesToReturn.put(level, getActivitiesPerTeamAndApplicationFlat(applicationName, teamName, activityName, level)); + Collection flattenedActivities = getActivitiesPerTeamAndApplicationFlat(applicationName, teamName, activityName, level); + flattenedActivitiesToReturn = mergeFlattenDates(flattenedActivitiesToReturn, flattenedActivities, getActivity(activityName, teamName), level, teamName, applicationName); } + return flattenedActivitiesToReturn; } + + private Collection mergeFlattenDates(Collection flattenDates, Collection flattenDatesNew, Activity activity, String level, String teamName, String applicationName) throws GitAPIException, IOException, ClassNotFoundException, InstantiationException, IllegalAccessException { + List mergedFlattenDates = new ArrayList<>(); + + for (FlattenDate flattenDateNew : flattenDatesNew) { + FlattenDate flattenDateFoundInList = null; + for (FlattenDate flattenDate : flattenDates) { + if (flattenDate.getDate().equals(flattenDateNew.getDate())) { + flattenDateFoundInList = flattenDate; + break; + } + } + if (flattenDateFoundInList == null) { + logger.info("flattenDateFoundInList == null" + flattenDateNew.getDate()); + boolean isDateActiveForLevel = isDateActive(activity, flattenDateNew.getDate(), level, activity.getName(), teamName); + flattenDateNew.addDynamicField(getLabel(teamName, applicationName, activity, level), isDateActiveForLevel); + for (String otherLevel : getLevelsForActivity(activity.getName())) { + if (!otherLevel.equals(level)) { + String label = getLabel(teamName, applicationName, activity, otherLevel); + if (flattenDateNew.getEntries() != null && flattenDateNew.getEntries().containsKey(label)) { + logger.info("flattenDateNew.getDynamicFields().containsKey(label) " + label); + continue; + } + boolean isDateActiveForOtherLevel = isDateActive(activity, flattenDateNew.getDate(), otherLevel, activity.getName(), teamName); + flattenDateNew.addDynamicField(label, isDateActiveForOtherLevel); + } + } + } else { + logger.info("flattenDateFoundInList != null" + flattenDateFoundInList.getDate()); + flattenDateFoundInList.getEntries().forEach((key, value) -> { + flattenDateNew.addDynamicField(key, value); + }); + } + + mergedFlattenDates.add(flattenDateNew); + } + return mergedFlattenDates; + } + + private Activity getActivity(String activityName, String teamName) throws Exception { + for (Application application : getApplications()) { + if (application.getTeam().equals(teamName)) { + for (Activity activity : application.getActivities(activityName)) { + if (activity.getName().equals(activityName)) { + return activity; + } + } + } + } + throw new Exception("Activity not found"); + } + + private boolean isApplicationInList(List applications, String name) { + boolean isInList = false; + for (Application application : applications) { + if (application.getName().equals(name)) { + isInList = true; + break; + } + } + return isInList; + } + +// public LinkedHashMap> getActivitiesPerTeamAndApplicationFlatAsLevelMap(String applicationName, String teamName, String activityName) throws Exception { +// LinkedHashMap> flattenedActivitiesToReturn = new LinkedHashMap>(); +// for (String level : getLevelsForActivity(activityName)) { +// flattenedActivitiesToReturn.put(level, getActivitiesPerTeamAndApplicationFlat(applicationName, teamName, activityName, level)); +// } +// return flattenedActivitiesToReturn; +// } } diff --git a/src/main/java/org/owasp/dsomm/metricca/analyzer/deserialization/Team.java b/src/main/java/org/owasp/dsomm/metricca/analyzer/deserialization/Team.java new file mode 100644 index 0000000..0ef95f5 --- /dev/null +++ b/src/main/java/org/owasp/dsomm/metricca/analyzer/deserialization/Team.java @@ -0,0 +1,29 @@ +package org.owasp.dsomm.metricca.analyzer.deserialization; + +import com.fasterxml.jackson.annotation.JsonProperty; + +import java.util.List; + +public class Team { + @JsonProperty("name") + private String name; + + @JsonProperty("applications") + private List applications; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public List getApplications() { + return applications; + } + + public void setApplications(List applications) { + this.applications = applications; + } +} diff --git a/src/main/java/org/owasp/dsomm/metricca/analyzer/deserialization/YamlScanner.java b/src/main/java/org/owasp/dsomm/metricca/analyzer/deserialization/YamlScanner.java index 2f4a1f2..b993892 100644 --- a/src/main/java/org/owasp/dsomm/metricca/analyzer/deserialization/YamlScanner.java +++ b/src/main/java/org/owasp/dsomm/metricca/analyzer/deserialization/YamlScanner.java @@ -1,5 +1,9 @@ package org.owasp.dsomm.metricca.analyzer.deserialization; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.dataformat.yaml.YAMLFactory; import org.eclipse.jgit.api.CloneCommand; import org.eclipse.jgit.api.Git; import org.eclipse.jgit.api.errors.GitAPIException; @@ -17,6 +21,7 @@ import java.nio.file.Path; import java.nio.file.Paths; import java.util.Collection; +import java.util.List; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -31,6 +36,9 @@ public class YamlScanner { private String yamlGitTargetPath; @Value("${metricCA.skeleton.path}") private String yamlSkeletonFilePath; + @Value("${metricCA.teams.path}") + private String yamlTeamsFilePath; + @Value("${metricCA.application.path}") private String yamlApplicationFolderPath; @Value("${metricCA.git.usernameOrToken}") @@ -81,10 +89,10 @@ private void gitClone(boolean enforceGitCloneIfTargetFolderExists) throws GitAPI .setDirectory(yamlGitTargetPathFile) .setBranch(yamlGitBranch); - if(gitUsernameOrToken != null && !gitUsernameOrToken.isEmpty()) { - if(gitPassword == null || gitPassword.isEmpty()) { + if (gitUsernameOrToken != null && !gitUsernameOrToken.isEmpty()) { + if (gitPassword == null || gitPassword.isEmpty()) { logger.info("Password is empty, assuming a token is used"); - }else { + } else { logger.debug("gitUsernameOrToken is set to " + gitUsernameOrToken); } CredentialsProvider credentialsProvider = new UsernamePasswordCredentialsProvider(gitUsernameOrToken, gitPassword); @@ -120,6 +128,24 @@ public File getSkeletonYaml() throws IOException, GitAPIException { return skeletonConfig; } + public List getTeamsAndApplicationYaml() throws IOException { + ObjectMapper objectMapper = new ObjectMapper(new YAMLFactory()); + JsonNode teamsNode = objectMapper.readTree(new File(yamlTeamsFilePath)); + String kind = teamsNode.get("kind").textValue(); + if (!kind.equals("teams")) { + throw new RuntimeException("teams.yaml is not of kind teams (kind: " + kind + ")"); + } + if (!teamsNode.has("teams")) { + throw new RuntimeException("teams.yaml is missing the teams node"); + } + String teamsString = objectMapper.writeValueAsString(teamsNode.get("teams")); + ObjectMapper mapper = new ObjectMapper(); + logger.info("teamsNode.get(\"teams\") " + teamsString); + List teams = objectMapper.readValue(teamsString, new TypeReference>() { + }); + return teams; + } + private boolean isGitEnabled() { return yamlGitUrl != null && !yamlGitUrl.isEmpty(); } diff --git a/src/main/java/org/owasp/dsomm/metricca/analyzer/deserialization/skeleton/SkeletonActivity.java b/src/main/java/org/owasp/dsomm/metricca/analyzer/deserialization/skeleton/SkeletonActivity.java index 58a80d1..bb83d56 100644 --- a/src/main/java/org/owasp/dsomm/metricca/analyzer/deserialization/skeleton/SkeletonActivity.java +++ b/src/main/java/org/owasp/dsomm/metricca/analyzer/deserialization/skeleton/SkeletonActivity.java @@ -32,10 +32,10 @@ public static String urlEncode(String value) { } @JsonIgnore - public List getPanelConfigurations() { + public List getPanelConfigurations(String dashboardType) { List panelConfigurations = new ArrayList(); for (String name : activityNames) { - panelConfigurations.add(new PanelConfiguration(name, grafanaPanelType, "activity/" + urlEncode(name), "")); + panelConfigurations.add(new PanelConfiguration(name, grafanaPanelType, "activity/" + urlEncode(name), "", dashboardType)); } return panelConfigurations; } diff --git a/src/main/java/org/owasp/dsomm/metricca/analyzer/grafana/GrafanaDashboardCreator.java b/src/main/java/org/owasp/dsomm/metricca/analyzer/grafana/GrafanaDashboardCreator.java index b63d871..71cad54 100644 --- a/src/main/java/org/owasp/dsomm/metricca/analyzer/grafana/GrafanaDashboardCreator.java +++ b/src/main/java/org/owasp/dsomm/metricca/analyzer/grafana/GrafanaDashboardCreator.java @@ -1,60 +1,85 @@ package org.owasp.dsomm.metricca.analyzer.grafana; +import freemarker.template.TemplateException; +import org.eclipse.jgit.api.errors.GitAPIException; +import org.owasp.dsomm.metricca.analyzer.deserialization.ApplicationDirector; +import org.owasp.dsomm.metricca.analyzer.deserialization.skeleton.SkeletonActivity; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; -import java.net.URI; -import java.net.http.HttpClient; -import java.net.http.HttpRequest; -import java.net.http.HttpResponse; -import java.time.Duration; +import java.awt.*; +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; +@Component public class GrafanaDashboardCreator { private static final Logger logger = LoggerFactory.getLogger(GrafanaDashboardCreator.class); - private final String grafanaUrl; - private final String apiKey; - private final String jsonContent; + @Autowired + private OverviewDashboard overviewDashboard; - private int connectTimeout = 10; + @Autowired + private TeamDashboard teamDashboard; - public GrafanaDashboardCreator(String grafanaUrl, String apiKey, String jsonContent) { - this.grafanaUrl = grafanaUrl; - this.apiKey = apiKey; - this.jsonContent = jsonContent; + @Autowired + private MatrixDashboard matrixDashboard; + + @Value("${metricCA.grafana.baseurl:http://localhost:3000}") + private String grafanaBaseUrl; + + @Value("${metricCA.grafana.apiKey}") + private String grafanaApiKey; + + @Value("${metricCA.grafana.timeoutInSeconds:10}") + private Integer grafanaApiTimeoutInSeconds; + + + public HashMap getDashboards() throws GitAPIException, IOException, ClassNotFoundException, InstantiationException, IllegalAccessException, TemplateException { + HashMap dashboards = new HashMap(); + dashboards.put("overview", overviewDashboard.getDashboard(getPanelConfigurations("overview").values())); + dashboards.put("team", teamDashboard.getDashboard(getPanelConfigurations("team").values())); + dashboards.put("matrix", matrixDashboard.getDashboard(getMatrixPanel().values())); + return dashboards; } - public GrafanaDashboardCreator(String grafanaUrl, String apiKey, String jsonContent, int connectTimeout) { - this.grafanaUrl = grafanaUrl; - this.apiKey = apiKey; - this.jsonContent = jsonContent; - this.connectTimeout = connectTimeout; + public boolean pushDashboards() throws Exception { + boolean status = true; + for (Map.Entry dashboard : getDashboards().entrySet()) { + logger.info("Pushing dashboard: " + dashboard.getKey()); + GrafanaDashboardPusher grafanaDashboardPusher = new GrafanaDashboardPusher(grafanaBaseUrl, grafanaApiKey, dashboard.getValue(), grafanaApiTimeoutInSeconds); + if (!grafanaDashboardPusher.pushDashboard()) { + status = false; + } + } + return status; } - public boolean pushDashboard() throws Exception { - String grafanaUrl = this.grafanaUrl + "/api/dashboards/db"; - String jsonContentWrapper = "{ \"dashboard\": " + jsonContent + ", \"overwrite\": true }"; - - HttpClient client = HttpClient.newBuilder().connectTimeout(Duration.ofSeconds(connectTimeout)).build(); - - HttpRequest request = HttpRequest.newBuilder() - .uri(URI.create(grafanaUrl)) - .header("Content-Type", "application/json") - .header("Authorization", "Bearer " + apiKey) - .POST(HttpRequest.BodyPublishers.ofString(jsonContentWrapper)) - .build(); - logger.info("URI: " + request.uri() + " - method " + request.method()); - logger.debug("Headers: " + request.headers().map()); - HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString()); - - if (response.statusCode() != 200) { - logger.debug("apiKey: " + apiKey); - logger.info("grafanaUrl: " + grafanaUrl); - logger.error("Status Code: " + response.statusCode() + " - Response Body" + response.body()); - return false; - } else { - logger.debug("Status Code: " + response.statusCode() + " - Response Body" + response.body()); - return true; + private Map getPanelConfigurations(String dashboardType) throws GitAPIException, IOException, ClassNotFoundException, InstantiationException, IllegalAccessException { + Map panelConfigurations = new HashMap(); + for (SkeletonActivity activity : ApplicationDirector.getSkeletonActivities()) { + for (PanelConfiguration panelConfiguration : activity.getPanelConfigurations(dashboardType)) { + Map fetchedPanelConfigurations = PanelFactory.getPanelsForLevels(panelConfiguration, activity); + for (PanelConfiguration fetchedPanelConfiguration : fetchedPanelConfigurations.values()) { + if (!panelConfigurations.containsKey(fetchedPanelConfiguration.getTitle())) { + panelConfigurations.put(fetchedPanelConfiguration.getTitle(), fetchedPanelConfiguration); + } + } + } + } + for (PanelConfiguration panelConfiguration : panelConfigurations.values()) { + logger.info("panelConfiguration: " + panelConfiguration.getTitle()); } + return panelConfigurations; + } + + private Map getMatrixPanel() { + Map panelConfigurations = new HashMap(); + PanelConfiguration panelConfiguration = new PanelConfiguration("Matrix", "heatmap", "https://todo.de", ""); + panelConfigurations.put(panelConfiguration.getTitle(), panelConfiguration); + return panelConfigurations; } } \ No newline at end of file diff --git a/src/main/java/org/owasp/dsomm/metricca/analyzer/grafana/PanelConfiguration.java b/src/main/java/org/owasp/dsomm/metricca/analyzer/grafana/PanelConfiguration.java index 5a5c177..a6cb84d 100644 --- a/src/main/java/org/owasp/dsomm/metricca/analyzer/grafana/PanelConfiguration.java +++ b/src/main/java/org/owasp/dsomm/metricca/analyzer/grafana/PanelConfiguration.java @@ -8,11 +8,22 @@ public class PanelConfiguration { private String title; private String url; + private String dashboardType; + public PanelConfiguration(String title, String type, String url, String description) { this.title = title; this.type = type; this.url = url; this.description = description; + this.dashboardType = "overview"; + } + + public PanelConfiguration(String title, String type, String url, String description, String dashboardType) { + this.title = title; + this.type = type; + this.url = url; + this.description = description; + this.dashboardType = dashboardType; } public String getTitle() { @@ -42,4 +53,12 @@ public String getDescription() { public String getTitleUrlEncoded() { return SkeletonActivity.urlEncode(title); } + + public String getDashboardType() { + return dashboardType; + } + + public void setDashboardType(String dashboardType) { + this.dashboardType = dashboardType; + } } diff --git a/src/main/java/org/owasp/dsomm/metricca/analyzer/grafana/PanelFactory.java b/src/main/java/org/owasp/dsomm/metricca/analyzer/grafana/PanelFactory.java index 2bef865..1be4cca 100644 --- a/src/main/java/org/owasp/dsomm/metricca/analyzer/grafana/PanelFactory.java +++ b/src/main/java/org/owasp/dsomm/metricca/analyzer/grafana/PanelFactory.java @@ -15,16 +15,33 @@ public static Map getPanelsForLevels(PanelConfigurat Map panelConfigurations = new HashMap<>(); switch (panelConfiguration.getType()) { case "timeseries-flatdate": - for (Threshold threshold : activity.getThresholds()) { - PanelConfiguration newPanelConfiguration = new PanelConfiguration( - panelConfiguration.getTitle() + " " + threshold.getLevel(), + if (panelConfiguration.getDashboardType() == "team") { + PanelConfiguration teamNewPanelConfiguration = new PanelConfiguration( + getTitle(panelConfiguration, null), panelConfiguration.getType(), - "activity/" + SkeletonActivity.urlEncode(panelConfiguration.getTitle()) + "/level/" + SkeletonActivity.urlEncode(threshold.getLevel()) + "/flatdate", - threshold.getDescription()); - panelConfigurations.put(newPanelConfiguration.getTitle(), newPanelConfiguration); + getURLTimeSeries(panelConfiguration, null), + "All levels for this application"); + panelConfigurations.put(teamNewPanelConfiguration.getTitle(), teamNewPanelConfiguration); + } else if (panelConfiguration.getDashboardType() == "overview") { + for (Threshold threshold : activity.getThresholds()) { + PanelConfiguration newPanelConfiguration = new PanelConfiguration( + panelConfiguration.getTitle() + " " + threshold.getLevel(), + panelConfiguration.getType(), + getURLTimeSeries(panelConfiguration, threshold), + threshold.getDescription()); + panelConfigurations.put(newPanelConfiguration.getTitle(), newPanelConfiguration); + } } break; case "count": +// if(panelConfiguration.getDashboardType() == "team") { +// PanelConfiguration teamNewPanelConfiguration = new PanelConfiguration( +// getTitle(panelConfiguration, null), +// panelConfiguration.getType(), +// getURLCount( panelConfiguration, null), +// "All levels for this application"); +// panelConfigurations.put(teamNewPanelConfiguration.getTitle(), teamNewPanelConfiguration); +// } else if(panelConfiguration.getDashboardType() == "overview") { for (Threshold threshold : activity.getThresholds()) { PanelConfiguration newPanelConfiguration = new PanelConfiguration( panelConfiguration.getTitle() + " " + threshold.getLevel(), @@ -33,6 +50,8 @@ public static Map getPanelsForLevels(PanelConfigurat threshold.getDescription()); panelConfigurations.put(newPanelConfiguration.getTitle(), newPanelConfiguration); } +// } + break; default: panelConfigurations.put(panelConfiguration.getTitle(), panelConfiguration); @@ -40,4 +59,49 @@ public static Map getPanelsForLevels(PanelConfigurat return panelConfigurations; } + + private static String getURLCount(PanelConfiguration panelConfiguration, Threshold threshold) { + String url = ""; + switch (panelConfiguration.getDashboardType()) { + case "team": + url = "activity/" + SkeletonActivity.urlEncode(panelConfiguration.getTitle()) + "/level/" + SkeletonActivity.urlEncode(threshold.getLevel()) + "/count"; + break; + case "overview": + url = "activity/" + SkeletonActivity.urlEncode(panelConfiguration.getTitle()) + "/level/" + SkeletonActivity.urlEncode(threshold.getLevel()) + "/flatdate"; + break; + default: + logger.error("Could not find panel type for " + panelConfiguration.getDashboardType()); + } + return url; + } + + private static String getURLTimeSeries(PanelConfiguration panelConfiguration, Threshold threshold) { + String url = ""; + switch (panelConfiguration.getDashboardType()) { + case "team": + url = "team/${team}/application/${application}/activity/" + panelConfiguration.getTitleUrlEncoded() + "/entries"; + break; + case "overview": + url = "activity/" + SkeletonActivity.urlEncode(panelConfiguration.getTitle()) + "/level/" + SkeletonActivity.urlEncode(threshold.getLevel()) + "/flatdate"; + break; + default: + logger.error("Could not find panel type for " + panelConfiguration.getDashboardType()); + } + return url; + } + + private static String getTitle(PanelConfiguration panelConfiguration, Threshold threshold) { + String title = ""; + switch (panelConfiguration.getDashboardType()) { + case "team": + title = panelConfiguration.getTitle(); + break; + case "overview": + title = panelConfiguration.getTitle() + " " + threshold.getLevel(); + break; + default: + logger.error("Could not find panel type for " + panelConfiguration.getDashboardType()); + } + return title; + } } diff --git a/src/main/java/org/owasp/dsomm/metricca/analyzer/grafana/TeamDashboard.java b/src/main/java/org/owasp/dsomm/metricca/analyzer/grafana/TeamDashboard.java index 238b13e..41048b8 100644 --- a/src/main/java/org/owasp/dsomm/metricca/analyzer/grafana/TeamDashboard.java +++ b/src/main/java/org/owasp/dsomm/metricca/analyzer/grafana/TeamDashboard.java @@ -25,7 +25,10 @@ public String getDashboardType() { protected Map getDashboardTemplateReplacements(Collection panels) throws TemplateException, IOException { Map replacements = super.getDashboardTemplateReplacements(panels); replacements.put("apiUrl", apiBaseUrl); + replacements.put("infinityDatasourceId", infinityDatasourceId); return replacements; } + + } diff --git a/src/main/resources/application-dev.properties b/src/main/resources/application-dev.properties index 0838c1d..3b88d9d 100644 --- a/src/main/resources/application-dev.properties +++ b/src/main/resources/application-dev.properties @@ -4,16 +4,16 @@ logging.level.org.owasp=INFO logging.level.org.springframework.web.filter.CommonsRequestLoggingFilter=WARN logging.level.org.springframework=WARN -metricCA.git.targetPath=/tmp/metricCA/ metricCA.skeleton.path=src/main/resources/skeleton.yaml metricCA.application.path=definitions/ -metricCA.git.branch= -metricCA.git.url= -metricCA.git.usernameOrToken= -metricCA.git.password= +metricCA.resource.path=src/main/resources +metricCA.teams.path=${metricCA.resource.path}/teams.yaml -metricCA.api.baseurl=http://192.168.178.23:8080 -metricCA.grafana.baseurl=http://192.168.178.23:3111 -metricCA.grafana.apiKey=glsa_vacM3ACVpQB9Qkvoo4gRW10SI8xNtkLU_30d94056 -metricCA.grafana.infinity.datasource.id=ea5c0811-94af-4449-aaf4-fc5e1378a4ae +metricCA.grafana.template.path=${metricCA.resource.path}/templates/ +metricCA.api.baseurl=http://192.168.178.27:8080 +metricCA.grafana.baseurl=http://192.168.178.27:3111 +metricCA.grafana.apiKey=glsa_hVatEFEqy6MzYAspkzZmkrnHC5xnQYKm_a0ea106f +metricCA.grafana.infinity.datasource.id=bf5f79be-56ae-4a3e-869e-fa01fa2b3161 +metricCA.git.usernameOrToken= +metricCA.git.password= \ No newline at end of file diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 18a95fd..7f13e6c 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -3,7 +3,9 @@ metricCA.skeleton.path=src/main/resources/skeleton.yaml metricCA.application.path=definitions/ metricCA.git.branch=main metricCA.git.url=https://github.com/devsecopsmaturitymodel/metricAnalyzer.git -metricCA.grafana.template.path=/app/resources/templates/ +metricCA.resource.path=src/main/resources +metricCA.teams.path=${metricCA.resource.path}/teams.yaml +metricCA.grafana.template.path=${metricCA.resource.path}/templates/ #metricCA.grafana.template.panelbasename= #metricCA.grafana.template.dashboardprefix= #metricCA.grafana.template.filepostfix= diff --git a/src/main/resources/teams.yaml b/src/main/resources/teams.yaml new file mode 100644 index 0000000..4f52550 --- /dev/null +++ b/src/main/resources/teams.yaml @@ -0,0 +1,12 @@ +kind: "teams" + +teams: + - name: two towers + applications: + - sauron + - name: fellowship of the ring + applications: + - gandalf + - name: humans + applications: + - boromir \ No newline at end of file diff --git a/src/main/resources/templates/grafana-dashboard-matrix.ftl b/src/main/resources/templates/grafana-dashboard-matrix.ftl new file mode 100644 index 0000000..ccc1f17 --- /dev/null +++ b/src/main/resources/templates/grafana-dashboard-matrix.ftl @@ -0,0 +1,42 @@ +{ + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "id": 23, + "links": [], + "liveNow": false, + "panels": [ + ${panelsAsString} + ], + "refresh": "", + "schemaVersion": 38, + "tags": [], + "templating": { + "list": [] + }, + "time": { + "from": "now-6h", + "to": "now" + }, + "timepicker": {}, + "timezone": "", + "title": "${title}", + "version": 1, + "weekStart": "" +} \ No newline at end of file diff --git a/src/main/resources/templates/grafana-dashboard-team.ftl b/src/main/resources/templates/grafana-dashboard-team.ftl index 4aca6fd..3ac8970 100644 --- a/src/main/resources/templates/grafana-dashboard-team.ftl +++ b/src/main/resources/templates/grafana-dashboard-team.ftl @@ -36,7 +36,7 @@ }, "datasource": { "type": "yesoreyeram-infinity-datasource", - "uid": "b16ab68b-982a-48aa-9e85-1784182e6b78" + "uid": "${infinityDatasourceId}" }, "definition": "Infinity- (infinity) json", "hide": 1, @@ -54,7 +54,7 @@ "root_selector": "", "source": "url", "type": "json", - "url": "http://${apiUrl}/teams", + "url": "${apiUrl}/teams", "url_options": { "data": "", "method": "GET" @@ -77,7 +77,7 @@ }, "datasource": { "type": "yesoreyeram-infinity-datasource", - "uid": "b16ab68b-982a-48aa-9e85-1784182e6b78" + "uid": "${infinityDatasourceId}" }, "definition": "Infinity- (infinity) json", "hide": 0, @@ -94,7 +94,7 @@ "root_selector": "", "source": "url", "type": "json", - "url": "http://${apiUrl}/team/${r"${team}"}/applicationIds", + "url": "${apiUrl}/team/${r"${team}"}/applicationIds", "url_options": { "data": "", "method": "GET" @@ -112,13 +112,12 @@ ] }, "time": { - "from": "now-6h", + "from": "now-5y", "to": "now" }, "timepicker": {}, "timezone": "", "title": "${title}", - "uid": "${datasourceUuid}", "version": 1, "weekStart": "" } \ No newline at end of file diff --git a/src/main/resources/templates/panel-infinity-matrix/heatmap.ftl b/src/main/resources/templates/panel-infinity-matrix/heatmap.ftl new file mode 100644 index 0000000..fefa80d --- /dev/null +++ b/src/main/resources/templates/panel-infinity-matrix/heatmap.ftl @@ -0,0 +1,85 @@ + { + "datasource": { + "type": "yesoreyeram-infinity-datasource", + "uid": "${infinityDatasourceId}" + }, + "fieldConfig": { + "defaults": { + "custom": { + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "scaleDistribution": { + "type": "linear" + } + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 0 + }, + "id": 1, + "options": { + "calculate": false, + "cellGap": 1, + "color": { + "exponent": 0.5, + "fill": "dark-orange", + "mode": "scheme", + "reverse": false, + "scale": "exponential", + "scheme": "Oranges", + "steps": 64 + }, + "exemplars": { + "color": "rgba(255,0,255,0.7)" + }, + "filterValues": { + "le": 1e-9 + }, + "legend": { + "show": true + }, + "rowsFrame": { + "layout": "auto" + }, + "tooltip": { + "show": true, + "yHistogram": false + }, + "yAxis": { + "axisPlacement": "left", + "reverse": false + } + }, + "pluginVersion": "10.2.2", + "targets": [ + { + "columns": [], + "datasource": { + "type": "yesoreyeram-infinity-datasource", + "uid": "${infinityDatasourceId}" + }, + "filters": [], + "format": "table", + "global_query_id": "", + "refId": "A", + "root_selector": "", + "source": "url", + "type": "json", + "url": "https://github.com/grafana/grafana-infinity-datasource/blob/main/testdata/users.json", + "url_options": { + "data": "", + "method": "GET" + } + } + ], + "title": "Panel Title", + "type": "heatmap" + } \ No newline at end of file diff --git a/src/main/resources/templates/panel-infinity-team/count.ftl b/src/main/resources/templates/panel-infinity-team/count.ftl index 92ddfe4..87cc376 100644 --- a/src/main/resources/templates/panel-infinity-team/count.ftl +++ b/src/main/resources/templates/panel-infinity-team/count.ftl @@ -1,39 +1,5 @@ { - "type": "stat", - "title": "Panel Title", - "gridPos": { - "x": 0, - "y": 0, - "w": 12, - "h": 8 - }, - "datasource": { - "uid": "${infinityDatasourceId}", - "type": "yesoreyeram-infinity-datasource" - }, - "id": 2, - "targets": [ - { - "datasource": { - "type": "yesoreyeram-infinity-datasource", - "uid": "${infinityDatasourceId}" - }, - "refId": "A", - "type": "json", - "source": "inline", - "format": "table", - "url": "https://github.com/grafana/grafana-infinity-datasource/blob/main/testdata/users.json", - "url_options": { - "method": "GET", - "data": "" - }, - "root_selector": "", - "columns": [], - "filters": [], - "global_query_id": "", - "data": "{\n \"fellowship of the ring\": 0,\n \"sauron\": 10\n}" - } - ], + "datasource": {}, "fieldConfig": { "defaults": { "mappings": [], @@ -45,8 +11,8 @@ "value": null }, { - "value": 1, - "color": "green" + "color": "green", + "value": 1 } ] }, @@ -57,8 +23,13 @@ }, "overrides": [] }, - "transformations": [], - "pluginVersion": "10.2.2", + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 0 + }, + "id": 2, "options": { "reduceOptions": { "values": true, @@ -73,5 +44,33 @@ "colorMode": "value", "graphMode": "area", "justifyMode": "auto" - } + }, + "pluginVersion": "10.2.2", + "targets": [ + { + "datasource": { + "type": "yesoreyeram-infinity-datasource", + "uid": "${infinityDatasourceId}" + }, + "columns": [], + "data": "", + "filters": [], + "format": "table", + "global_query_id": "", + "parser": "backend", + "refId": "A", + "root_selector": "", + "source": "url", + "type": "json", + "url": "${apiUrl}", + "url_options": { + "data": "", + "method": "GET" + } + } + ], + "title": "${title}", + "transformations": [], + "type": "stat", + "description": "${description}" } \ No newline at end of file diff --git a/src/main/resources/templates/panel-infinity-team/timeseries-flatdate.ftl b/src/main/resources/templates/panel-infinity-team/timeseries-flatdate.ftl index e69de29..9f73372 100644 --- a/src/main/resources/templates/panel-infinity-team/timeseries-flatdate.ftl +++ b/src/main/resources/templates/panel-infinity-team/timeseries-flatdate.ftl @@ -0,0 +1,137 @@ +{ + "type": "timeseries", + "title": "${title}", + "gridPos": { + "x": 0, + "y": 0, + "w": 12, + "h": 8 + }, + "datasource": { + "uid": "bf5f79be-56ae-4a3e-869e-fa01fa2b3161", + "type": "yesoreyeram-infinity-datasource" + }, + "id": 2, + "targets": [ + { + "datasource": { + "type": "yesoreyeram-infinity-datasource", + "uid": "${infinityDatasourceId}" + }, + "refId": "A", + "type": "json", + "source": "url", + "format": "table", + "url": "${apiUrl}", + "url_options": { + "method": "GET", + "data": "" + }, + "root_selector": "", + "columns": [], + "filters": [], + "global_query_id": "", + "parser": "backend" + } + ], + "options": { + "tooltip": { + "mode": "single", + "sort": "none" + }, + "legend": { + "showLegend": true, + "displayMode": "list", + "placement": "bottom", + "calcs": [] + } + }, + "fieldConfig": { + "defaults": { + "custom": { + "drawStyle": "line", + "lineInterpolation": "linear", + "barAlignment": 0, + "lineWidth": 1, + "fillOpacity": 0, + "gradientMode": "none", + "spanNulls": false, + "insertNulls": false, + "showPoints": "auto", + "pointSize": 5, + "stacking": { + "mode": "none", + "group": "A" + }, + "axisPlacement": "auto", + "axisLabel": "", + "axisColorMode": "text", + "axisBorderShow": false, + "scaleDistribution": { + "type": "linear" + }, + "axisCenteredZero": false, + "hideFrom": { + "tooltip": false, + "viz": false, + "legend": false + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "color": { + "mode": "palette-classic" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "value": null, + "color": "green" + }, + { + "value": 80, + "color": "red" + } + ] + } + }, + "overrides": [] + }, + "transformations": [ + { + "id": "convertFieldType", + "options": { + "fields": {}, + "conversions": [ + { + "targetField": "date", + "destinationType": "time" + } + ] + } + }, + { + "id": "extractFields", + "options": { + "source": "entries" + } + }, + { + "id": "organize", + "options": { + "excludeByName": { + "entries": true + }, + "indexByName": {}, + "renameByName": {} + } + }, + { + "id": "prepareTimeSeries", + "options": {} + } + ] +} \ No newline at end of file