diff --git a/flink-cyber/cyber-jobs/pom.xml b/flink-cyber/cyber-jobs/pom.xml index eebae8e5d..4c5ccf804 100644 --- a/flink-cyber/cyber-jobs/pom.xml +++ b/flink-cyber/cyber-jobs/pom.xml @@ -91,6 +91,12 @@ ${project.parent.version} provided + + com.cloudera.cyber + cyber-worker-service + ${project.parent.version} + provided + diff --git a/flink-cyber/cyber-jobs/src/main/assemblies/cloudera.xml b/flink-cyber/cyber-jobs/src/main/assemblies/cloudera.xml index 0e05233f9..9240b14c2 100644 --- a/flink-cyber/cyber-jobs/src/main/assemblies/cloudera.xml +++ b/flink-cyber/cyber-jobs/src/main/assemblies/cloudera.xml @@ -87,6 +87,13 @@ 0644 + + ../cyber-services/cyber-worker-service/target/cyber-worker-service-${project.version}.jar + jobs/ + cyber-worker-service-${cybersec.full.version}.jar + 0644 + + ../flink-commands/scoring-commands/target/scoring-commands-${project.version}.jar tools/ diff --git a/flink-cyber/cyber-services/cyber-service-common/pom.xml b/flink-cyber/cyber-services/cyber-service-common/pom.xml index a9aaf6ed6..7acd70138 100644 --- a/flink-cyber/cyber-services/cyber-service-common/pom.xml +++ b/flink-cyber/cyber-services/cyber-service-common/pom.xml @@ -48,5 +48,10 @@ org.springframework.boot spring-boot-autoconfigure + + org.apache.flink + flink-core + provided + diff --git a/flink-cyber/cyber-services/cyber-service-common/src/main/java/com/cloudera/service/common/request/RequestBody.java b/flink-cyber/cyber-services/cyber-service-common/src/main/java/com/cloudera/service/common/request/RequestBody.java index d40700fee..1a954807c 100644 --- a/flink-cyber/cyber-services/cyber-service-common/src/main/java/com/cloudera/service/common/request/RequestBody.java +++ b/flink-cyber/cyber-services/cyber-service-common/src/main/java/com/cloudera/service/common/request/RequestBody.java @@ -5,6 +5,8 @@ import lombok.Data; import lombok.NoArgsConstructor; +import java.util.List; + @Data @Builder @NoArgsConstructor @@ -12,7 +14,9 @@ public class RequestBody { private String clusterServiceId; private String jobIdHex; - private String pipelineDir; + private String pipelineName; private String branch; + private String profileName; + private List jobs; private byte[] payload; } diff --git a/flink-cyber/cyber-services/cyber-service-common/src/main/java/com/cloudera/service/common/request/RequestType.java b/flink-cyber/cyber-services/cyber-service-common/src/main/java/com/cloudera/service/common/request/RequestType.java index f6a247bea..424066ac3 100644 --- a/flink-cyber/cyber-services/cyber-service-common/src/main/java/com/cloudera/service/common/request/RequestType.java +++ b/flink-cyber/cyber-services/cyber-service-common/src/main/java/com/cloudera/service/common/request/RequestType.java @@ -1,5 +1,5 @@ package com.cloudera.service.common.request; public enum RequestType { - GET_ALL_CLUSTERS_SERVICE_REQUEST, GET_CLUSTER_SERVICE_REQUEST, START_JOB_REQUEST, RESTART_JOB_REQUEST, STOP_JOB_REQUEST, GET_JOB_CONFIG_REQUEST, UPDATE_JOB_CONFIG_REQUEST + GET_ALL_CLUSTERS_SERVICE_REQUEST, GET_CLUSTER_SERVICE_REQUEST, START_JOB_REQUEST, RESTART_JOB_REQUEST, STOP_JOB_REQUEST, GET_JOB_CONFIG_REQUEST, CREATE_EMPTY_PIPELINE, START_ARCHIVE_PIPELINE, UPDATE_JOB_CONFIG_REQUEST } diff --git a/flink-cyber/cyber-services/cyber-service-common/src/main/java/com/cloudera/service/common/response/Job.java b/flink-cyber/cyber-services/cyber-service-common/src/main/java/com/cloudera/service/common/response/Job.java index 6b0d5bffd..90efb1965 100644 --- a/flink-cyber/cyber-services/cyber-service-common/src/main/java/com/cloudera/service/common/response/Job.java +++ b/flink-cyber/cyber-services/cyber-service-common/src/main/java/com/cloudera/service/common/response/Job.java @@ -31,6 +31,8 @@ public class Job { private JobType jobType; + private String user; + @AllArgsConstructor @Getter public enum JobType { @@ -46,7 +48,6 @@ public enum JobType { public String[] getScript(Job job) { switch (this) { - case GENERATOR: case PROFILE: case PARSER: return new String[]{scriptName, job.getJobBranch(), job.getJobPipeline(), job.getJobName()}; diff --git a/flink-cyber/cyber-services/cyber-service-common/src/main/java/com/cloudera/service/common/response/Pipeline.java b/flink-cyber/cyber-services/cyber-service-common/src/main/java/com/cloudera/service/common/response/Pipeline.java new file mode 100644 index 000000000..ad84a09b4 --- /dev/null +++ b/flink-cyber/cyber-services/cyber-service-common/src/main/java/com/cloudera/service/common/response/Pipeline.java @@ -0,0 +1,18 @@ +package com.cloudera.service.common.response; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class Pipeline { + String id; + String name; + String clusterName; + String date; + String userName; +} diff --git a/flink-cyber/cyber-services/cyber-service-common/src/main/java/com/cloudera/service/common/response/ResponseType.java b/flink-cyber/cyber-services/cyber-service-common/src/main/java/com/cloudera/service/common/response/ResponseType.java index f67d81e97..c159f4f34 100644 --- a/flink-cyber/cyber-services/cyber-service-common/src/main/java/com/cloudera/service/common/response/ResponseType.java +++ b/flink-cyber/cyber-services/cyber-service-common/src/main/java/com/cloudera/service/common/response/ResponseType.java @@ -1,5 +1,5 @@ package com.cloudera.service.common.response; public enum ResponseType { - GET_ALL_CLUSTERS_SERVICE_RESPONSE, GET_CLUSTER_SERVICE_RESPONSE, START_JOB_RESPONSE, RESTART_JOB_RESPONSE, STOP_JOB_RESPONSE, GET_JOB_CONFIG_RESPONSE, UPDATE_JOB_CONFIG_RESPONSE, ERROR_RESPONSE + GET_ALL_CLUSTERS_SERVICE_RESPONSE, GET_CLUSTER_SERVICE_RESPONSE, START_JOB_RESPONSE, RESTART_JOB_RESPONSE, STOP_JOB_RESPONSE, GET_JOB_CONFIG_RESPONSE, UPDATE_JOB_CONFIG_RESPONSE, CREATE_EMPTY_PIPELINE_RESPONSE, START_ARCHIVE_PIPELINE_RESPONSE, ERROR_RESPONSE } diff --git a/flink-cyber/cyber-services/cyber-worker-service/src/main/java/com/cloudera/cyber/restcli/configuration/AppWorkerConfig.java b/flink-cyber/cyber-services/cyber-worker-service/src/main/java/com/cloudera/cyber/restcli/configuration/AppWorkerConfig.java new file mode 100644 index 000000000..4633b917c --- /dev/null +++ b/flink-cyber/cyber-services/cyber-worker-service/src/main/java/com/cloudera/cyber/restcli/configuration/AppWorkerConfig.java @@ -0,0 +1,18 @@ +package com.cloudera.cyber.restcli.configuration; + +import lombok.Getter; +import lombok.Setter; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Configuration; + +@Configuration +@ConfigurationProperties(prefix = "cluster") +@Getter +@Setter +public class AppWorkerConfig { + private String name; + private String id; + private String status; + private String version; + private String pipelineDir; +} diff --git a/flink-cyber/cyber-services/cyber-worker-service/src/main/java/com/cloudera/cyber/restcli/controller/KafkaListenerController.java b/flink-cyber/cyber-services/cyber-worker-service/src/main/java/com/cloudera/cyber/restcli/controller/KafkaListenerController.java index a6e0e56cc..b85a61350 100644 --- a/flink-cyber/cyber-services/cyber-worker-service/src/main/java/com/cloudera/cyber/restcli/controller/KafkaListenerController.java +++ b/flink-cyber/cyber-services/cyber-worker-service/src/main/java/com/cloudera/cyber/restcli/controller/KafkaListenerController.java @@ -1,6 +1,8 @@ package com.cloudera.cyber.restcli.controller; +import com.cloudera.cyber.restcli.configuration.AppWorkerConfig; import com.cloudera.cyber.restcli.service.JobService; +import com.cloudera.cyber.restcli.service.FilePipelineService; import com.cloudera.service.common.Utils; import com.cloudera.service.common.request.RequestBody; import com.cloudera.service.common.request.RequestType; @@ -29,18 +31,11 @@ @RequiredArgsConstructor @Slf4j public class KafkaListenerController { - - @Value("${cluster.name}") - private String clusterName; - @Value("${cluster.id}") - private String clusterId; - @Value("${cluster.status}") - private String clusterStatus; - @Value("${cluster.version}") - private String clusterVersion; - private final JobService jobService; + private final FilePipelineService pipelineService; + private final AppWorkerConfig config; + //TODO: Rewrite to Spring events. Probably split the events into separate types, such as cluster event, job event, pipeline event, etc. @KafkaListener(topics = "#{kafkaProperties.getRequestTopic()}", containerFactory = "kafkaListenerContainerFactory") @SendTo({"#{kafkaProperties.getReplyTopic()}"}) public Message handleMessage(RequestBody requestBody, @Header(KafkaHeaders.RECEIVED_MESSAGE_KEY) String key, @Header(KafkaHeaders.REPLY_TOPIC) byte[] replyTo, @@ -85,6 +80,26 @@ public Message handleMessage(RequestBody requestBody, @Header(Kafk } catch (IOException e) { return handleErrorResponse(e, replyTo, correlationId); } + case CREATE_EMPTY_PIPELINE: + try { + pipelineService.createEmptyPipeline(requestBody.getPipelineName(), requestBody.getBranch()); + final ResponseBody responseBody = ResponseBody.builder().build(); + return buildResponseMessage(responseBody, ResponseType.CREATE_EMPTY_PIPELINE_RESPONSE, replyTo, correlationId); + } catch (Exception e) { + return handleErrorResponse(e, replyTo, correlationId); + } + case START_ARCHIVE_PIPELINE: + try { + pipelineService.extractPipeline(requestBody.getPayload(), requestBody.getPipelineName(), requestBody.getBranch()); + pipelineService.startPipelineJob(requestBody.getPipelineName(), requestBody.getBranch(), requestBody.getProfileName(), requestBody.getJobs()); + final ResponseBody responseBody = ResponseBody.builder().build(); + return buildResponseMessage(responseBody, ResponseType.START_ARCHIVE_PIPELINE_RESPONSE, replyTo, correlationId); + } catch (Exception e) { + log.error("Exception while processing the Start All request {}", e.getMessage()); + return handleErrorResponse(e, replyTo, correlationId); + + } + } return null; } @@ -95,10 +110,10 @@ private Message getResponseBodyMessage(byte[] replyTo, byte[] corr ResponseBody responseBody = ResponseBody.builder() .jobs(jobs) .clusterMeta(ClusterMeta.builder() - .name(clusterName) - .clusterId(clusterId) - .clusterStatus(clusterStatus) - .version(clusterVersion) + .name(config.getName()) + .clusterId(config.getId()) + .clusterStatus(config.getStatus()) + .version(config.getVersion()) .build()) .build(); return buildResponseMessage(responseBody, responseType, replyTo, correlationId); diff --git a/flink-cyber/cyber-services/cyber-worker-service/src/main/java/com/cloudera/cyber/restcli/service/FilePipelineService.java b/flink-cyber/cyber-services/cyber-worker-service/src/main/java/com/cloudera/cyber/restcli/service/FilePipelineService.java new file mode 100644 index 000000000..53e8de262 --- /dev/null +++ b/flink-cyber/cyber-services/cyber-worker-service/src/main/java/com/cloudera/cyber/restcli/service/FilePipelineService.java @@ -0,0 +1,78 @@ +package com.cloudera.cyber.restcli.service; + +import com.cloudera.cyber.restcli.configuration.AppWorkerConfig; +import com.cloudera.service.common.Utils; +import com.cloudera.service.common.response.Job; +import com.cloudera.service.common.utils.ArchiveUtil; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.io.IOUtils; +import org.apache.commons.lang3.StringUtils; +import org.springframework.stereotype.Service; + +import java.io.File; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.List; +import java.util.stream.Collectors; + +@Slf4j +@Service +@RequiredArgsConstructor +public class FilePipelineService { + private final AppWorkerConfig config; + + + public void createEmptyPipeline(String pipelineName, String branchName) { + String fullPath = this.config.getPipelineDir().endsWith("/") ? this.config.getPipelineDir() + pipelineName + "/" + branchName + : this.config.getPipelineDir() + "/" + pipelineName + "/" + branchName; + File directory = new File(fullPath); + if (directory.mkdirs()) { + log.info("Create full path {}", fullPath); + } + try { + ProcessBuilder processBuilder = new ProcessBuilder("cs-create-pipeline", pipelineName); + processBuilder.directory(directory); + Process process = processBuilder.start(); + process.waitFor(); + } catch (IOException ioe) { + log.error("Caught get IOException {} ", ioe.getMessage()); + } catch (InterruptedException e) { + log.error("Caught Interrupt Exception with message {} ", e.getMessage()); + } + } + + public void extractPipeline(byte[] payload, String pipelineName, String branch) throws IOException { + String fullPipelinePath = pipelineName.endsWith("/") ? this.config.getPipelineDir() + pipelineName + "/" + branch : this.config.getPipelineDir() + "/" + pipelineName + "/" + branch; + ArchiveUtil.decompressFromTarGzInMemory(payload, fullPipelinePath, true); + } + + public void startPipelineJob(String pipelineName, String branch, String profileName, List jobsNames) throws IOException { + String fullPipelinePath = pipelineName.endsWith("/") ?this.config.getPipelineDir() + pipelineName + "/" + branch + : this.config.getPipelineDir() + "/" + pipelineName + "/" + branch; + + List jobs = jobsNames.stream().map(jobName -> Job.builder() + .jobPipeline(pipelineName) + .jobType(Utils.getEnumFromString(jobName, Job.JobType.class, Job.JobType::getName)) + .jobBranch(branch) + .jobName(StringUtils.defaultString(profileName, "main")) + .build()).collect(Collectors.toList()); + for (Job job : jobs) { + job.getJobType().getScript(job); + ProcessBuilder processBuilder = new ProcessBuilder(job.getJobType().getScript(job)); + processBuilder.directory(new File(fullPipelinePath)); + Process process = processBuilder.start(); + Thread clt = new Thread(() -> { + try { + log.info(IOUtils.toString(process.getInputStream(), StandardCharsets.UTF_8)); + log.error(IOUtils.toString(process.getErrorStream(), StandardCharsets.UTF_8)); + } catch (IOException e) { + log.error("Error happens on stream reading from bash {}", e.getMessage()); + } + }); + clt.setDaemon(true); + clt.start(); + } + } + +} diff --git a/flink-cyber/cyber-services/cyber-worker-service/src/main/java/com/cloudera/cyber/restcli/service/JobService.java b/flink-cyber/cyber-services/cyber-worker-service/src/main/java/com/cloudera/cyber/restcli/service/JobService.java index 835bef271..d42f45886 100644 --- a/flink-cyber/cyber-services/cyber-worker-service/src/main/java/com/cloudera/cyber/restcli/service/JobService.java +++ b/flink-cyber/cyber-services/cyber-worker-service/src/main/java/com/cloudera/cyber/restcli/service/JobService.java @@ -1,14 +1,15 @@ package com.cloudera.cyber.restcli.service; +import com.cloudera.cyber.restcli.configuration.AppWorkerConfig; import com.cloudera.service.common.Utils; import com.cloudera.service.common.response.Job; import com.cloudera.service.common.utils.ArchiveUtil; +import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.apache.commons.io.IOUtils; import org.apache.commons.lang3.StringUtils; import org.apache.flink.api.common.JobID; import org.apache.flink.api.common.JobStatus; -import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; import java.io.BufferedReader; @@ -27,12 +28,13 @@ @Slf4j @Service +@RequiredArgsConstructor public class JobService { - @Value("${cluster.pipeline.dir}") - private String pipelineDir; public static final String LOG_CLI_JOB_INFO = "Successfully read jobs from cli with exit code {}. job count '{}' jobs data '[{}]'"; private final Pattern pattern = Pattern.compile("^(?[\\d.:\\s]+)\\s:\\s(?[a-fA-F0-9]+)\\s:\\s(?[\\w.-]+)\\s\\((?\\w+)\\)$"); + private final AppWorkerConfig config; + public List getJobs() throws IOException { List jobs = new ArrayList<>(); @@ -56,8 +58,8 @@ public Job restartJob(String id) throws IOException { log.info("Script command = '{}'", Arrays.toString(job.getJobType().getScript(job))); try { ProcessBuilder processBuilder = new ProcessBuilder(job.getJobType().getScript(job)); - if (pipelineDir != null) { - processBuilder.directory(new File(pipelineDir)); + if (config.getPipelineDir() != null) { + processBuilder.directory(new File(config.getPipelineDir())); } Process process = processBuilder.start(); log.debug("Command input stream '{}' \n Command error stream '{}'", IOUtils.toString(process.getInputStream(), StandardCharsets.UTF_8), IOUtils.toString(process.getErrorStream(), StandardCharsets.UTF_8)); @@ -155,12 +157,12 @@ private void setJobParameters(Job job, String fullJobName) { String[] jobParameters = fullJobName.split("\\."); job.setJobBranch(jobParameters[0]); job.setJobPipeline(jobParameters[1]); - if (job.getJobType() == Job.JobType.PROFILE || job.getJobType() == Job.JobType.GENERATOR || job.getJobType() == Job.JobType.PARSER) { + if (job.getJobType() == Job.JobType.PROFILE || job.getJobType() == Job.JobType.PARSER) { job.setJobName(jobParameters[jobParameters.length - 1]); } } public void updateConfig(byte[] payload) throws IOException { - ArchiveUtil.decompressFromTarGzInMemory(payload, pipelineDir, true); + ArchiveUtil.decompressFromTarGzInMemory(payload, config.getPipelineDir(), true); } } diff --git a/flink-cyber/cyber-services/cyber-worker-service/src/test/resources/application.properties b/flink-cyber/cyber-services/cyber-worker-service/src/test/resources/application.properties index 356c65b6b..2d82a91b6 100644 --- a/flink-cyber/cyber-services/cyber-worker-service/src/test/resources/application.properties +++ b/flink-cyber/cyber-services/cyber-worker-service/src/test/resources/application.properties @@ -2,7 +2,7 @@ cluster.name=testName cluster.id=1 cluster.status=online cluster.version=1.0.0 -cluster.pipeline.dir=/tmp/test +cluster.pipelineDir=/tmp/test spring.kafka.bootstrap-servers=localhost:9092 spring.kafka.consumer.group-id=testGroup spring.kafka.consumer.auto-offset-reset=earliest diff --git a/flink-cyber/flink-commands/json-commands/pom.xml b/flink-cyber/flink-commands/json-commands/pom.xml index d4942b64c..3138c87c7 100644 --- a/flink-cyber/flink-commands/json-commands/pom.xml +++ b/flink-cyber/flink-commands/json-commands/pom.xml @@ -58,6 +58,17 @@ ${log4j.version} provided + + com.fasterxml.jackson.core + jackson-databind + ${jackson.version} + + + com.google.guava + guava + ${guava.version} + test + org.apache.logging.log4j log4j-api diff --git a/flink-cyber/flink-commands/scoring-commands/pom.xml b/flink-cyber/flink-commands/scoring-commands/pom.xml index d9645cdc9..13a9c70e5 100644 --- a/flink-cyber/flink-commands/scoring-commands/pom.xml +++ b/flink-cyber/flink-commands/scoring-commands/pom.xml @@ -28,6 +28,7 @@ org.apache.flink flink-java ${flink.version} + provided org.projectlombok diff --git a/flink-cyber/flink-profiler/pom.xml b/flink-cyber/flink-profiler/pom.xml index 90f80000c..6c8487e69 100644 --- a/flink-cyber/flink-profiler/pom.xml +++ b/flink-cyber/flink-profiler/pom.xml @@ -57,6 +57,11 @@ flink-avro provided + + com.google.guava + guava + ${guava.version} + org.assertj assertj-core diff --git a/flink-cyber/metron-parser-chain/parser-chains-config-service/frontend/parser-chains-client/.eslintrc.json b/flink-cyber/metron-parser-chain/parser-chains-config-service/frontend/parser-chains-client/.eslintrc.json index 106a38bfb..fc97bb6bf 100644 --- a/flink-cyber/metron-parser-chain/parser-chains-config-service/frontend/parser-chains-client/.eslintrc.json +++ b/flink-cyber/metron-parser-chain/parser-chains-config-service/frontend/parser-chains-client/.eslintrc.json @@ -67,6 +67,11 @@ "simple-import-sort/imports": "off", "array-bracket-spacing": "off", "no-underscore-dangle": "off", + "@typescript-eslint/member-ordering": [ + "warn", + { + "default": ["static-field", "instance-field", "static-method", "instance-method"] + }], "@typescript-eslint/no-unused-vars": [ "warn", { diff --git a/flink-cyber/metron-parser-chain/parser-chains-config-service/frontend/parser-chains-client/angular.json b/flink-cyber/metron-parser-chain/parser-chains-config-service/frontend/parser-chains-client/angular.json index db6ddc2ee..8f9c0cd58 100644 --- a/flink-cyber/metron-parser-chain/parser-chains-config-service/frontend/parser-chains-client/angular.json +++ b/flink-cyber/metron-parser-chain/parser-chains-config-service/frontend/parser-chains-client/angular.json @@ -161,14 +161,18 @@ "options": { "browserTarget": "parser-chaining:build" } - }, - "test": { + }, "test": { "builder": "@angular-devkit/build-angular:karma", "options": { "main": "src/test.ts", "polyfills": "src/polyfills.ts", "tsConfig": "src/tsconfig.spec.json", "karmaConfig": "src/karma.conf.js", + "stylePreprocessorOptions": { + "includePaths": [ + "src" + ] + }, "styles": [ "src/styles.scss" ], diff --git a/flink-cyber/metron-parser-chain/parser-chains-config-service/frontend/parser-chains-client/package-lock.json b/flink-cyber/metron-parser-chain/parser-chains-config-service/frontend/parser-chains-client/package-lock.json index 72f3059dc..ddbea7d2d 100644 --- a/flink-cyber/metron-parser-chain/parser-chains-config-service/frontend/parser-chains-client/package-lock.json +++ b/flink-cyber/metron-parser-chain/parser-chains-config-service/frontend/parser-chains-client/package-lock.json @@ -3821,9 +3821,9 @@ } }, "node_modules/@types/eslint": { - "version": "8.56.9", - "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.56.9.tgz", - "integrity": "sha512-W4W3KcqzjJ0sHg2vAq9vfml6OhsJ53TcUjUqfzzZf/EChUtwspszj/S0pzMxnfRcO55/iGq47dscXw71Fxc4Zg==", + "version": "8.56.10", + "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.56.10.tgz", + "integrity": "sha512-Shavhk87gCtY2fhXDctcfS3e6FdxWkCx1iUZ9eEUbh7rTqlZT0/IzOkCOVt0fCjcFuZ9FPYfuezTBImfHCDBGQ==", "dev": true, "dependencies": { "@types/estree": "*", @@ -5550,9 +5550,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001610", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001610.tgz", - "integrity": "sha512-QFutAY4NgaelojVMjY63o6XlZyORPaLfyMnsl3HgnWdJUcX6K0oaJymHjH8PT5Gk7sTm8rvC/c5COUQKXqmOMA==", + "version": "1.0.30001612", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001612.tgz", + "integrity": "sha512-lFgnZ07UhaCcsSZgWW0K5j4e69dK1u/ltrL9lTUiFOwNHs12S3UMIEYgBV0Z6C6hRDev7iRnMzzYmKabYdXF9g==", "dev": true, "funding": [ { @@ -6069,9 +6069,9 @@ } }, "node_modules/core-js-compat": { - "version": "3.36.1", - "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.36.1.tgz", - "integrity": "sha512-Dk997v9ZCt3X/npqzyGdTlq6t7lDBhZwGvV94PKzDArjp7BTRm7WlDAXYd/OWdeFHO8OChQYRJNJvUCqCbrtKA==", + "version": "3.37.0", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.37.0.tgz", + "integrity": "sha512-vYq4L+T8aS5UuFg4UwDhc7YNRWVeVZwltad9C/jV3R2LgVOpS9BDr7l/WL6BN0dbV3k1XejPTHqqEzJgsa0frA==", "dev": true, "dependencies": { "browserslist": "^4.23.0" @@ -6848,9 +6848,9 @@ "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" }, "node_modules/electron-to-chromium": { - "version": "1.4.737", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.737.tgz", - "integrity": "sha512-QvLTxaLHKdy5YxvixAw/FfHq2eWLUL9KvsPjp0aHK1gI5d3EDuDgITkvj0nFO2c6zUY3ZqVAJQiBYyQP9tQpfw==", + "version": "1.4.747", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.747.tgz", + "integrity": "sha512-+FnSWZIAvFHbsNVmUxhEqWiaOiPMcfum1GQzlWCg/wLigVtshOsjXHyEFfmt6cFK6+HkS3QOJBv6/3OPumbBfw==", "dev": true }, "node_modules/emoji-regex": { @@ -15333,9 +15333,9 @@ } }, "node_modules/terser-webpack-plugin/node_modules/terser": { - "version": "5.30.3", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.30.3.tgz", - "integrity": "sha512-STdUgOUx8rLbMGO9IOwHLpCqolkDITFFQSMYYwKE1N2lY6MVSaeoi10z/EhWxRc6ybqoVmKSkhKYH/XUpl7vSA==", + "version": "5.30.4", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.30.4.tgz", + "integrity": "sha512-xRdd0v64a8mFK9bnsKVdoNP9GQIKUAaJPTaqEQDL4w/J8WaW4sWXXoMZ+6SimPkfT5bElreXf8m9HnmPc3E1BQ==", "dev": true, "dependencies": { "@jridgewell/source-map": "^0.3.3", @@ -16132,9 +16132,9 @@ } }, "node_modules/webpack-dev-server/node_modules/ipaddr.js": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.1.0.tgz", - "integrity": "sha512-LlbxQ7xKzfBusov6UMi4MFpEg0m+mAm9xyNGEduwXMEDuf4WfzB/RZwMVYEd7IKGvh4IUkEXYxtAVu9T3OelJQ==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.2.0.tgz", + "integrity": "sha512-Ag3wB2o37wslZS19hZqorUnrnzSkpOVy+IiiDEiTqNubEYpYuHWIf6K4psgN2ZWKExS4xhVCrRVfb/wfW8fWJA==", "dev": true, "engines": { "node": ">= 10" @@ -19208,9 +19208,9 @@ } }, "@types/eslint": { - "version": "8.56.9", - "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.56.9.tgz", - "integrity": "sha512-W4W3KcqzjJ0sHg2vAq9vfml6OhsJ53TcUjUqfzzZf/EChUtwspszj/S0pzMxnfRcO55/iGq47dscXw71Fxc4Zg==", + "version": "8.56.10", + "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.56.10.tgz", + "integrity": "sha512-Shavhk87gCtY2fhXDctcfS3e6FdxWkCx1iUZ9eEUbh7rTqlZT0/IzOkCOVt0fCjcFuZ9FPYfuezTBImfHCDBGQ==", "dev": true, "requires": { "@types/estree": "*", @@ -20503,9 +20503,9 @@ "dev": true }, "caniuse-lite": { - "version": "1.0.30001610", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001610.tgz", - "integrity": "sha512-QFutAY4NgaelojVMjY63o6XlZyORPaLfyMnsl3HgnWdJUcX6K0oaJymHjH8PT5Gk7sTm8rvC/c5COUQKXqmOMA==", + "version": "1.0.30001612", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001612.tgz", + "integrity": "sha512-lFgnZ07UhaCcsSZgWW0K5j4e69dK1u/ltrL9lTUiFOwNHs12S3UMIEYgBV0Z6C6hRDev7iRnMzzYmKabYdXF9g==", "dev": true }, "chalk": { @@ -20887,9 +20887,9 @@ } }, "core-js-compat": { - "version": "3.36.1", - "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.36.1.tgz", - "integrity": "sha512-Dk997v9ZCt3X/npqzyGdTlq6t7lDBhZwGvV94PKzDArjp7BTRm7WlDAXYd/OWdeFHO8OChQYRJNJvUCqCbrtKA==", + "version": "3.37.0", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.37.0.tgz", + "integrity": "sha512-vYq4L+T8aS5UuFg4UwDhc7YNRWVeVZwltad9C/jV3R2LgVOpS9BDr7l/WL6BN0dbV3k1XejPTHqqEzJgsa0frA==", "dev": true, "requires": { "browserslist": "^4.23.0" @@ -21437,9 +21437,9 @@ "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" }, "electron-to-chromium": { - "version": "1.4.737", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.737.tgz", - "integrity": "sha512-QvLTxaLHKdy5YxvixAw/FfHq2eWLUL9KvsPjp0aHK1gI5d3EDuDgITkvj0nFO2c6zUY3ZqVAJQiBYyQP9tQpfw==", + "version": "1.4.747", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.747.tgz", + "integrity": "sha512-+FnSWZIAvFHbsNVmUxhEqWiaOiPMcfum1GQzlWCg/wLigVtshOsjXHyEFfmt6cFK6+HkS3QOJBv6/3OPumbBfw==", "dev": true }, "emoji-regex": { @@ -27571,9 +27571,9 @@ } }, "terser": { - "version": "5.30.3", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.30.3.tgz", - "integrity": "sha512-STdUgOUx8rLbMGO9IOwHLpCqolkDITFFQSMYYwKE1N2lY6MVSaeoi10z/EhWxRc6ybqoVmKSkhKYH/XUpl7vSA==", + "version": "5.30.4", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.30.4.tgz", + "integrity": "sha512-xRdd0v64a8mFK9bnsKVdoNP9GQIKUAaJPTaqEQDL4w/J8WaW4sWXXoMZ+6SimPkfT5bElreXf8m9HnmPc3E1BQ==", "dev": true, "requires": { "@jridgewell/source-map": "^0.3.3", @@ -28199,9 +28199,9 @@ } }, "ipaddr.js": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.1.0.tgz", - "integrity": "sha512-LlbxQ7xKzfBusov6UMi4MFpEg0m+mAm9xyNGEduwXMEDuf4WfzB/RZwMVYEd7IKGvh4IUkEXYxtAVu9T3OelJQ==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.2.0.tgz", + "integrity": "sha512-Ag3wB2o37wslZS19hZqorUnrnzSkpOVy+IiiDEiTqNubEYpYuHWIf6K4psgN2ZWKExS4xhVCrRVfb/wfW8fWJA==", "dev": true }, "schema-utils": { diff --git a/flink-cyber/metron-parser-chain/parser-chains-config-service/frontend/parser-chains-client/src/app/app-routing.module.ts b/flink-cyber/metron-parser-chain/parser-chains-config-service/frontend/parser-chains-client/src/app/app-routing.module.ts index 614703782..390254808 100644 --- a/flink-cyber/metron-parser-chain/parser-chains-config-service/frontend/parser-chains-client/src/app/app-routing.module.ts +++ b/flink-cyber/metron-parser-chain/parser-chains-config-service/frontend/parser-chains-client/src/app/app-routing.module.ts @@ -21,6 +21,9 @@ import {MainContainerComponent} from './misc/main/main.container'; import {PageNotFoundComponent} from './page-not-found/page-not-found.component'; import {ClusterListPageComponent} from "./cluster/cluster-list-page/cluster-list-page.component"; import {ClusterPageComponent} from "./cluster/cluster-page/cluster-page.component"; +import {PipelinesComponent} from 'src/app/cluster/pipelines/pipelines.component'; +import {PipelineCreateComponent} from 'src/app/cluster/pipelines/pipeline-create/pipeline-create.component'; +import {PipelineSubmitComponent} from 'src/app/cluster/pipelines/pipeline-submit/pipeline-submit.component'; export const routes: Routes = [ { path: '404', component: PageNotFoundComponent }, @@ -32,6 +35,9 @@ export const routes: Routes = [ canDeactivate: [ CanDeactivateComponent ] }, { path: 'clusters', component: ClusterListPageComponent }, + { path: 'clusters/pipelines', component: PipelinesComponent }, + { path: 'clusters/pipelines/create', component: PipelineCreateComponent }, + { path: 'clusters/pipelines/submit', component: PipelineSubmitComponent }, { path: 'clusters/:clusterId', component: ClusterPageComponent}, { path: 'parserconfig/chains/:id/new', component: ChainAddParserPageComponent }, diff --git a/flink-cyber/metron-parser-chain/parser-chains-config-service/frontend/parser-chains-client/src/app/app.component.html b/flink-cyber/metron-parser-chain/parser-chains-config-service/frontend/parser-chains-client/src/app/app.component.html index 4b51112af..b138f90e9 100644 --- a/flink-cyber/metron-parser-chain/parser-chains-config-service/frontend/parser-chains-client/src/app/app.component.html +++ b/flink-cyber/metron-parser-chain/parser-chains-config-service/frontend/parser-chains-client/src/app/app.component.html @@ -20,6 +20,9 @@
  • Clusters
  • +
  • + Pipelines +
  • Configuration
      diff --git a/flink-cyber/metron-parser-chain/parser-chains-config-service/frontend/parser-chains-client/src/app/app.constants.ts b/flink-cyber/metron-parser-chain/parser-chains-config-service/frontend/parser-chains-client/src/app/app.constants.ts new file mode 100644 index 000000000..fbf791492 --- /dev/null +++ b/flink-cyber/metron-parser-chain/parser-chains-config-service/frontend/parser-chains-client/src/app/app.constants.ts @@ -0,0 +1,6 @@ +export const JOBS_ENUM = { + PARSER: {label: "Parser", value: "parse"}, + TRIAGE: {label: "Triage", value: "triage"}, + PROFILE: {label: "Profile", value: "profile"}, + INDEX: {label: "Index", value: "index"} +} as const; diff --git a/flink-cyber/metron-parser-chain/parser-chains-config-service/frontend/parser-chains-client/src/app/app.module.ts b/flink-cyber/metron-parser-chain/parser-chains-config-service/frontend/parser-chains-client/src/app/app.module.ts index b2c5f291c..e35b02d34 100644 --- a/flink-cyber/metron-parser-chain/parser-chains-config-service/frontend/parser-chains-client/src/app/app.module.ts +++ b/flink-cyber/metron-parser-chain/parser-chains-config-service/frontend/parser-chains-client/src/app/app.module.ts @@ -46,6 +46,7 @@ import {DragDropModule} from "@angular/cdk/drag-drop"; import {MonacoEditorModule} from "ngx-monaco-editor-v2"; import {ClusterListPageModule} from "./cluster/cluster-list-page/cluster-list-page.module"; import {ClusterPageModule} from "./cluster/cluster-page/cluster-page.module"; +import {PipelinesModule} from 'src/app/cluster/pipelines/pipelines.module'; registerLocaleData(en); @@ -75,6 +76,7 @@ export const metaReducers: MetaReducer[] = !environment.production ChainListPageModule, ChainPageModule, ClusterListPageModule, + PipelinesModule, ClusterPageModule, ChainAddParserPageModule, MonacoEditorModule.forRoot(), diff --git a/flink-cyber/metron-parser-chain/parser-chains-config-service/frontend/parser-chains-client/src/app/chain-add-parser-page/chain-add-parser-page.component.ts b/flink-cyber/metron-parser-chain/parser-chains-config-service/frontend/parser-chains-client/src/app/chain-add-parser-page/chain-add-parser-page.component.ts index 866c4ac3e..60f2c53b7 100644 --- a/flink-cyber/metron-parser-chain/parser-chains-config-service/frontend/parser-chains-client/src/app/chain-add-parser-page/chain-add-parser-page.component.ts +++ b/flink-cyber/metron-parser-chain/parser-chains-config-service/frontend/parser-chains-client/src/app/chain-add-parser-page/chain-add-parser-page.component.ts @@ -35,6 +35,7 @@ export class ChainAddParserPageComponent implements OnInit, OnDestroy { parsersList: ParserModel[] = []; chainId: string; subchainId: string; + currentPipeline: string; getChainSubscription: Subscription; getParserTypesSubscription: Subscription; @@ -81,14 +82,15 @@ export class ChainAddParserPageComponent implements OnInit, OnDestroy { this._activatedRoute.params.subscribe((params) => { this.chainId = params.id; this.subchainId = params.subchain; - }); - - this.getChainSubscription = this._store.pipe(select(getChain({ id: this.chainId }))).subscribe((chain: ParserChainModel) => { - if (!chain) { - this._store.dispatch(new fromParserPageAction.LoadChainDetailsAction({ - id: this.chainId - })); - } + this.currentPipeline = params.pipeline; + this.getChainSubscription = this._store.pipe(select(getChain({ id: this.chainId }))).subscribe((chain: ParserChainModel) => { + if (!chain) { + this._store.dispatch(new fromParserPageAction.LoadChainDetailsAction({ + id: this.chainId, + currentPipeline: this.currentPipeline + })); + } + }); }); this._store.dispatch(new fromActions.GetParserTypesAction()); diff --git a/flink-cyber/metron-parser-chain/parser-chains-config-service/frontend/parser-chains-client/src/app/chain-list-page/chain-list-page.actions.ts b/flink-cyber/metron-parser-chain/parser-chains-config-service/frontend/parser-chains-client/src/app/chain-list-page/chain-list-page.actions.ts deleted file mode 100644 index 8f203ab39..000000000 --- a/flink-cyber/metron-parser-chain/parser-chains-config-service/frontend/parser-chains-client/src/app/chain-list-page/chain-list-page.actions.ts +++ /dev/null @@ -1,224 +0,0 @@ -/* - * Copyright 2020 - 2022 Cloudera. All Rights Reserved. - * - * This file is licensed under the Apache License Version 2.0 (the "License"). You may not use this file - * except in compliance with the License. You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0. - * - * This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, - * either express or implied. Refer to the License for the specific permissions and - * limitations governing your use of the file. - */ - -import {Action} from '@ngrx/store'; - -import {ChainModel, ChainOperationalModel} from './chain.model'; - -export const LOAD_CHAINS = '[Chain List] load all start'; -export const LOAD_CHAINS_SUCCESS = '[Chain List] load all success'; -export const LOAD_CHAINS_FAIL = '[Chain List] load all fail'; -export const DELETE_CHAIN = '[Chain List] delete item'; -export const DELETE_CHAIN_SELECT = '[Chain List] prepare delete item'; -export const DELETE_CHAIN_SUCCESS = '[Chain List] delete item success'; -export const DELETE_CHAIN_FAIL = '[Chain List] delete item fail'; -export const CREATE_CHAIN = '[Chain List] create item'; -export const SHOW_CREATE_MODAL = '[Chain List] show create modal'; -export const HIDE_CREATE_MODAL = '[Chain List] hide create modal'; -export const SHOW_DELETE_MODAL = '[Chain List] show delete modal'; -export const HIDE_DELETE_MODAL = '[Chain List] hide delete modal'; -export const SHOW_RENAME_SELECTED_PIPELINE_MODAL = '[Chain List] show rename selected pipeline modal'; -export const HIDE_RENAME_PIPELINE_MODAL = '[Chain List] hide rename pipeline modal'; -export const CREATE_CHAIN_SUCCESS = '[Chain List] create item success'; -export const CREATE_CHAIN_FAIL = '[Chain List] create item fail'; -export const LOAD_PIPELINES = '[Chain List] load pipelines start'; -export const LOAD_PIPELINES_SUCCESS = '[Chain List] load pipelines success'; -export const LOAD_PIPELINES_FAIL = '[Chain List] load pipelines fail'; -export const CREATE_PIPELINE = '[Chain List] create pipeline start'; -export const CREATE_PIPELINE_SUCCESS = '[Chain List] create pipeline success'; -export const CREATE_PIPELINE_FAIL = '[Chain List] create pipeline fail'; -export const RENAME_SELECTED_PIPELINE = '[Chain List] rename selected pipeline start'; -export const RENAME_PIPELINE_SUCCESS = '[Chain List] rename pipeline success'; -export const RENAME_PIPELINE_FAIL = '[Chain List] rename pipeline fail'; -export const DELETE_SELECTED_PIPELINE = '[Chain List] delete selected pipeline start'; -export const DELETE_PIPELINE_SUCCESS = '[Chain List] delete pipeline success'; -export const DELETE_PIPELINE_FAIL = '[Chain List] delete pipeline fail'; -export const PIPELINE_CHANGED = '[Chain List] selected pipeline changed'; - -export class NoopChainAction implements Action { - readonly type: ''; - constructor(public payload?: any) {} -} -export class LoadChainsAction implements Action { - readonly type = LOAD_CHAINS; - constructor() {} -} - -export class LoadChainsSuccessAction implements Action { - readonly type = LOAD_CHAINS_SUCCESS; - constructor(public chains: ChainModel[]) {} -} - -export class LoadChainsFailAction implements Action { - readonly type = LOAD_CHAINS_FAIL; - constructor(public error: { message: string }) {} -} - -export class DeleteChainAction implements Action { - readonly type = DELETE_CHAIN; - constructor(public chainId: string, public chainName: string) {} -} -export class SelectDeleteChainAction implements Action { - readonly type = DELETE_CHAIN_SELECT; - constructor(public chainId: string) {} -} - -export class DeleteChainSuccessAction implements Action { - readonly type = DELETE_CHAIN_SUCCESS; - constructor(public chainId: string) {} -} - -export class DeleteChainFailAction implements Action { - readonly type = DELETE_CHAIN_FAIL; - constructor(public error: { message: string }) {} -} - -export class CreateChainAction implements Action { - readonly type = CREATE_CHAIN; - constructor(public newChain: ChainOperationalModel) {} - } - -export class ShowCreateModalAction implements Action { - readonly type = SHOW_CREATE_MODAL; - constructor() {} -} - -export class HideCreateModalAction implements Action { - readonly type = HIDE_CREATE_MODAL; - constructor() {} -} - -export class ShowDeleteModalAction implements Action { - readonly type = SHOW_DELETE_MODAL; - constructor() {} -} - -export class HideDeleteModalAction implements Action { - readonly type = HIDE_DELETE_MODAL; - constructor() {} -} - -export class ShowRenameSelectedPipelineModalAction implements Action { - readonly type = SHOW_RENAME_SELECTED_PIPELINE_MODAL; - constructor() {} -} - -export class HideRenamePipelineModalAction implements Action { - readonly type = HIDE_RENAME_PIPELINE_MODAL; - constructor() {} -} - -export class CreateChainSuccessAction implements Action { - readonly type = CREATE_CHAIN_SUCCESS; - constructor(public chain: ChainModel) {} - } - -export class CreateChainFailAction implements Action { - readonly type = CREATE_CHAIN_FAIL; - constructor(public error: { message: string }) {} - } - -export class LoadPipelinesAction implements Action { - readonly type = LOAD_PIPELINES; - constructor() {} - } - -export class LoadPipelinesSuccessAction implements Action { - readonly type = LOAD_PIPELINES_SUCCESS; - constructor(public pipelines: string[]) {} - } - -export class LoadPipelinesFailAction implements Action { - readonly type = LOAD_PIPELINES_FAIL; - constructor(public error: { message: string }) {} - } - -export class CreatePipelineAction implements Action { - readonly type = CREATE_PIPELINE; - constructor(public pipelineName: string) {} - } - -export class CreatePipelineSuccessAction implements Action { - readonly type = CREATE_PIPELINE_SUCCESS; - constructor(public newPipelineName, public pipelines: string[]) {} - } - -export class CreatePipelineFailAction implements Action { - readonly type = CREATE_PIPELINE_FAIL; - constructor(public error: { message: string }) {} - } - -export class RenameSelectedPipelineAction implements Action { - readonly type = RENAME_SELECTED_PIPELINE; - constructor(public newPipelineName: string) {} - } - -export class RenamePipelineSuccessAction implements Action { - readonly type = RENAME_PIPELINE_SUCCESS; - constructor(public newPipelineName, public pipelines: string[]) {} - } - -export class RenamePipelineFailAction implements Action { - readonly type = RENAME_PIPELINE_FAIL; - constructor(public error: { message: string }) {} - } - -export class DeleteSelectedPipelineAction implements Action { - readonly type = DELETE_SELECTED_PIPELINE; - constructor() {} - } - -export class DeletePipelineSuccessAction implements Action { - readonly type = DELETE_PIPELINE_SUCCESS; - constructor(public pipelines: string[]) {} - } - -export class DeletePipelineFailAction implements Action { - readonly type = DELETE_PIPELINE_FAIL; - constructor(public error: { message: string }) {} - } - -export class PipelineChangedAction implements Action { - readonly type = PIPELINE_CHANGED; - constructor(public newPipelineName: string) {} -} - -export type ChainListAction = LoadChainsAction - | LoadChainsSuccessAction - | LoadChainsFailAction - | ShowDeleteModalAction - | HideDeleteModalAction - | SelectDeleteChainAction - | DeleteChainAction - | DeleteChainSuccessAction - | DeleteChainFailAction - | CreateChainAction - | ShowCreateModalAction - | HideCreateModalAction - | ShowRenameSelectedPipelineModalAction - | HideRenamePipelineModalAction - | CreateChainSuccessAction - | CreateChainFailAction - | NoopChainAction - | LoadPipelinesAction - | LoadPipelinesSuccessAction - | LoadPipelinesFailAction - | CreatePipelineAction - | CreatePipelineSuccessAction - | CreatePipelineFailAction - | RenameSelectedPipelineAction - | RenamePipelineSuccessAction - | RenamePipelineFailAction - | DeleteSelectedPipelineAction - | DeletePipelineSuccessAction - | DeletePipelineFailAction - | PipelineChangedAction; diff --git a/flink-cyber/metron-parser-chain/parser-chains-config-service/frontend/parser-chains-client/src/app/chain-list-page/chain-list-page.component.html b/flink-cyber/metron-parser-chain/parser-chains-config-service/frontend/parser-chains-client/src/app/chain-list-page/chain-list-page.component.html index fdb978816..8f4458d72 100644 --- a/flink-cyber/metron-parser-chain/parser-chains-config-service/frontend/parser-chains-client/src/app/chain-list-page/chain-list-page.component.html +++ b/flink-cyber/metron-parser-chain/parser-chains-config-service/frontend/parser-chains-client/src/app/chain-list-page/chain-list-page.component.html @@ -9,137 +9,110 @@ ~ either express or implied. Refer to the License for the specific permissions and ~ limitations governing your use of the file. --> + + + + Parser Chains +
      + +
      - - - - -
      - - Chain name: - - - - -
      -
      - -
      - - Pipeline name: - - - - -
      -
      - - Delete Chain Name - -

      Are you sure you want to delete the chain {{deleteChainItem.name}}?

      -
      - - - - -
      +
      + + Current pipeline + + + + {{ pipeline }} + + + + + + +
      +
      +
      -
      - Select pipeline: - - - - - - - - - -
      + + + + + + + + - - - - - - - - - - - - + - - - - + + + + + delete_forever + Delete + + + + + + +
      Chains list
      Name{{ chain.name }}
      NameAction
      {{ data.name }} - Open - - - - Delete + + + Action + + + chevron_right + Open - -
      +
      + +
      diff --git a/flink-cyber/metron-parser-chain/parser-chains-config-service/frontend/parser-chains-client/src/app/chain-list-page/chain-list-page.component.scss b/flink-cyber/metron-parser-chain/parser-chains-config-service/frontend/parser-chains-client/src/app/chain-list-page/chain-list-page.component.scss index b85ff6534..1da2b08bc 100644 --- a/flink-cyber/metron-parser-chain/parser-chains-config-service/frontend/parser-chains-client/src/app/chain-list-page/chain-list-page.component.scss +++ b/flink-cyber/metron-parser-chain/parser-chains-config-service/frontend/parser-chains-client/src/app/chain-list-page/chain-list-page.component.scss @@ -9,77 +9,135 @@ * either express or implied. Refer to the License for the specific permissions and * limitations governing your use of the file. */ +@use '@angular/material' as mat; +@import "./styles.scss"; -@import "../../../node_modules/ng-zorro-antd/table/style/index.css"; -.chain-delete-btn { - background:transparent; - border: 0; - justify-content: center; - padding-right: 12px; - width: 50px; - z-index: 100; - color: rgb(213, 46, 46); - opacity: 0.8; +.panel-class { + margin-top: 30px !important; } -.chain-delete-btn .anticon-delete { - padding:2px; + +.my-form-field .mat-form-field-wrapper { + flex: 0 0 60%; } -.chain-delete-btn:hover { - opacity: 1; + +::ng-deep .mat-card .mat-card-title-group { + div:first-child { + flex: 1 0 50%; + } } -.chain-open-btn { - background:transparent; - border: 0; - justify-content: center; - padding-right: 12px; - width: 50px; - z-index: 100; - color: #3e508c; +.mat-card { + .mat-card-title-group { + margin-bottom: 10px; + margin-left: 15px; + margin-right: 15px; + } + + .mat-card-header { + display: block; + } +} + +table { + width: 100%; + + .mat-header-row { + background: mat.get-color-from-palette($theme-primary); + display: flex; + align-items: stretch; + } + + .mat-header-cell { + color: white; + border-right: 1px solid rgba(255, 255, 255, 0.6); + display: flex; + align-items: center !important; + } + + .mat-header-cell:last-child { + border-right: none; + } + + .mat-column-name { + flex-grow: 1; + flex-shrink: 1; + flex-basis: 50%; + } + + .mat-column-action { + display: flex; + flex-direction: row; + align-items: stretch; + flex-grow: 1; + flex-shrink: 1; + flex-basis: 50%; + padding-left: 20px; + } + + .mat-row { + display: flex; + flex-direction: row; + align-items: stretch; + } } -.chain-open-btn .anticon-right-square { - opacity: 0.8; - padding:2px; + +.chain-delete-btn { + color: mat.get-color-from-palette(mat.define-palette(mat.$red-palette), 300); } -.chain-open-btn:hover .anticon-right-square { - opacity: 1; +.chain-delete-btn:hover { + color: mat.get-color-from-palette(mat.define-palette(mat.$red-palette), 600); } -.add-new-chain-button { - display: flex; - justify-content: center; - align-items: center; - border-radius: 100%; - width: 30px; - height: 30px; - font-size: 20px; - i { - display: flex; +.chain-open-btn { + color: mat.get-color-from-palette($theme-primary, 300); + .chain-open-btn-square { + background-color: mat.get-color-from-palette($theme-primary, 300); + width: 22px; + height: 22px; + border-radius: 6px; + font-size: 22px; + margin-right: 5px; + .mat-icon { + color: white; } + } } -.pipeline-select { - margin: 1rem; - width: 40rem; - display-model: inline-block; -} +.chain-open-btn:hover { + color: mat.get-color-from-palette($theme-primary, 600); -.pipeline-element { - margin: 0 0.25rem; + .chain-open-btn-square { + background-color: mat.get-color-from-palette($theme-primary, 600); + } } -.pipeline-select-dropdown { - width: 12rem; +.add-new-chain-button { + border-radius: 100%; + width: 56px; + height: 56px; + mat-icon { + width: 36px; + height: 36px; + font-size: 36px; + } } -.add-pipeline-divider { - margin: 4px 0; + +.pipeline-button { + border-radius: 100%; + width: 30px; + height: 30px; + margin-left: 5px; + margin-right: 5px; + margin-bottom: 10px; + mat-icon { + font-size: 22px; + } } -.add-pipeline-container { - padding: 8px; + +.mat-divider-wrapper { + margin: 0 5px; + .mat-divider { + height: 50%; + } } -.add-pipeline-button { - flex: 0 0 auto; - padding: 8px; - display: block; -} \ No newline at end of file diff --git a/flink-cyber/metron-parser-chain/parser-chains-config-service/frontend/parser-chains-client/src/app/chain-list-page/chain-list-page.component.spec.ts b/flink-cyber/metron-parser-chain/parser-chains-config-service/frontend/parser-chains-client/src/app/chain-list-page/chain-list-page.component.spec.ts index 5a7491944..c4bce5122 100644 --- a/flink-cyber/metron-parser-chain/parser-chains-config-service/frontend/parser-chains-client/src/app/chain-list-page/chain-list-page.component.spec.ts +++ b/flink-cyber/metron-parser-chain/parser-chains-config-service/frontend/parser-chains-client/src/app/chain-list-page/chain-list-page.component.spec.ts @@ -12,315 +12,91 @@ import {ComponentFixture, TestBed, waitForAsync} from '@angular/core/testing'; import {FormsModule, ReactiveFormsModule} from '@angular/forms'; -import {By} from '@angular/platform-browser'; import {NoopAnimationsModule} from '@angular/platform-browser/animations'; import {RouterTestingModule} from '@angular/router/testing'; -import {IconDefinition} from '@ant-design/icons-angular'; -import {DeleteFill, PlusOutline, RightSquareFill} from '@ant-design/icons-angular/icons'; -import {Action, Store} from '@ngrx/store'; -import {MockStore, provideMockStore} from '@ngrx/store/testing'; -import {NzModalModule} from 'ng-zorro-antd/modal'; -import {NzIconModule} from 'ng-zorro-antd/icon' -import {of, ReplaySubject} from 'rxjs'; import {ChainListPageService} from '../services/chain-list-page.service'; -import * as fromActions from './chain-list-page.actions'; import {ChainListPageComponent} from './chain-list-page.component'; -import {ChainModel} from './chain.model'; -import {NzMessageService} from "ng-zorro-antd/message"; -import {NzCardModule} from "ng-zorro-antd/card"; -import {NzTableModule} from "ng-zorro-antd/table"; -import {NzDividerModule} from "ng-zorro-antd/divider"; -import {CommonModule} from "@angular/common"; -import {NzInputModule} from "ng-zorro-antd/input"; -import {getCreateModalVisible, getDeleteChain, getDeleteModalVisible} from "./chain-list-page.reducers"; -import {NzToolTipModule} from "ng-zorro-antd/tooltip"; -import {NzButtonModule} from "ng-zorro-antd/button"; -import {NzPopconfirmModule} from "ng-zorro-antd/popconfirm"; -import {NzFormModule} from "ng-zorro-antd/form"; -import {NzLayoutModule} from "ng-zorro-antd/layout"; -import {provideMockActions} from "@ngrx/effects/testing"; -import {NzSelectModule} from "ng-zorro-antd/select"; - -const icons: IconDefinition[] = [PlusOutline, DeleteFill, RightSquareFill]; - -const chains: ChainModel[] = [ - {id: 'id1', name: 'Chain 1'}, - {id: 'id2', name: 'Chain 2'}, - {id: 'id3', name: 'Chain 3'} -]; - -class FakeChainListPageService { - getChains() { - return of(chains); - } - - deleteChain() { - return of([]); - } - - createChain() { - return of({}); - } -} - -describe('ChainListPageComponent', () => { +import {AsyncPipe, NgComponentOutlet, NgForOf, NgIf, NgTemplateOutlet} from '@angular/common'; +import {SharedModule} from 'src/app/shared/share.module'; +import {MatCardModule} from '@angular/material/card'; +import {MatDividerModule} from '@angular/material/divider'; +import {MatTooltipModule} from '@angular/material/tooltip'; +import {MatTableModule} from '@angular/material/table'; +import {MatDialogModule} from '@angular/material/dialog'; +import {MatButtonModule} from '@angular/material/button'; +import {MatIconModule} from '@angular/material/icon'; +import {MatFormFieldModule} from '@angular/material/form-field'; +import {MatInputModule} from '@angular/material/input'; +import {MatSelectModule} from '@angular/material/select'; +import {MatAutocompleteModule} from '@angular/material/autocomplete'; +import {ChainDialogComponent} from 'src/app/chain-list-page/component/chain-dialog.component'; +import {PipelineService} from 'src/app/services/pipeline.service'; +import {of} from 'rxjs'; +import {ChainModel} from 'src/app/chain-list-page/chain.model'; + +const mockPipeline = ['foo-pipeline1', 'foo-pipeline2']; +const mockChains: ChainModel[] = [{id: '1', name: 'test1'}, {id: '2', name: 'test2'}]; + +describe('PipelinesComponent', () => { let component: ChainListPageComponent; let fixture: ComponentFixture; - let actions: ReplaySubject; - let store: MockStore<{ - 'chain-list-page': { - loading: boolean; - error: string; - items: ChainModel[]; - createModalVisible: boolean; - deleteModalVisible: boolean; - } - }>; - - - const chainListPageState = { - loading: false, - error: '', - items: chains, - createModalVisible: false, - deleteModalVisible: false - }; - const initialState = { - 'chain-list-page': chainListPageState - }; - - function clickDeleteBtnOnIndex(index: number) { - fixture.debugElement - .queryAll(By.css('.chain-delete-btn')) - [index].nativeElement.click(); - } + let pipelineService: jasmine.SpyObj + let chainListPageService: jasmine.SpyObj beforeEach(waitForAsync(() => { TestBed.configureTestingModule({ imports: [ - NzModalModule, - CommonModule, - FormsModule, ReactiveFormsModule, - NzInputModule, - NzTableModule, - NzCardModule, - NzDividerModule, - NzToolTipModule, - NzButtonModule, - NzPopconfirmModule, - NzIconModule.forChild(icons), - NzFormModule, - NzLayoutModule, - NzSelectModule, + SharedModule, + MatCardModule, + MatDividerModule, + MatTooltipModule, + MatTableModule, + MatDialogModule, + MatButtonModule, + MatIconModule, + MatFormFieldModule, + MatInputModule, + NgIf, + NgTemplateOutlet, + NgComponentOutlet, + AsyncPipe, + MatSelectModule, + FormsModule, + NgForOf, + MatAutocompleteModule, RouterTestingModule, NoopAnimationsModule, ], - declarations: [ChainListPageComponent], + declarations: [ChainListPageComponent, ChainDialogComponent], providers: [ - provideMockActions(() => actions), - provideMockStore({ - initialState, - selectors: [ - {selector: getDeleteModalVisible, value: false}, - {selector: getDeleteChain, value: null}, - {selector: getCreateModalVisible, value: false} - ] - }), + { + provide: PipelineService, + useValue: jasmine.createSpyObj('PipelineService', ['getPipelines', 'createPipeline', 'renamePipeline', 'deletePipeline']) + }, { provide: ChainListPageService, - useClass: FakeChainListPageService + useValue: jasmine.createSpyObj('ChainListPageService', ['createChain', 'getChains', 'deleteChain', 'getPipelines']) }, - NzMessageService ] }).compileComponents(); })); - beforeEach(() => { - store = TestBed.inject(Store) as MockStore<{ - 'chain-list-page': { - loading: boolean; - error: string; - items: ChainModel[]; - createModalVisible: boolean; - deleteModalVisible: boolean; - } - }>; + beforeEach(waitForAsync(() => { + + pipelineService = TestBed.inject(PipelineService) as jasmine.SpyObj; + pipelineService.getPipelines.and.returnValue(of(mockPipeline)); + + chainListPageService = TestBed.inject(ChainListPageService) as jasmine.SpyObj; + chainListPageService.getChains.and.returnValue(of(mockChains)); + fixture = TestBed.createComponent(ChainListPageComponent); component = fixture.componentInstance; fixture.detectChanges(); - }); + })); it('should create', () => { expect(component).toBeTruthy(); }); - - it('should fire action when user click the delete button', () => { - spyOn(store, 'dispatch').and.callThrough(); - - const indexOfSecondDeleteBtn = 1; - clickDeleteBtnOnIndex(indexOfSecondDeleteBtn); - fixture.detectChanges(); - - const action = new fromActions.SelectDeleteChainAction(chains[indexOfSecondDeleteBtn].id); - expect(store.dispatch).toHaveBeenCalledWith(action); - }); - - it('should not show any modal when modal', () => { - let isDeleteVisible: boolean = null; - let deleteChain: ChainModel = null; - let isCreateVisible: boolean = null; - store.refreshState(); - fixture.detectChanges(); - const modalWindow = document.querySelector('.ant-modal'); - const chainNameField = document.querySelector('[data-qe-id="chain-name"]'); - - component.isChainDeleteModalVisible$.subscribe( - value => isDeleteVisible = value - ); - component.deleteChainItem$.subscribe( - item => deleteChain = item - ); - component.isChainCreateModalVisible$.subscribe( - value => isCreateVisible = value - ); - - expect(chainNameField).toBeNull(); - expect(modalWindow).toBeNull(); - expect(isDeleteVisible).toBe(false); - expect(isCreateVisible).toBe(false); - expect(deleteChain).toBeNull(); - }); - - it('should not show the delete modal when modal is visible but chain is not selected', () => { - let isVisible: boolean = null; - let deleteChain: ChainModel = null; - component.isChainDeleteModalVisible$.subscribe(value => isVisible = value); - component.deleteChainItem$.subscribe(value => deleteChain = value); - - store.overrideSelector(getDeleteModalVisible, true); - store.refreshState(); - fixture.detectChanges(); - - const modalWindow = document.querySelector('.ant-modal'); - - expect(modalWindow).toBeNull(); - expect(isVisible).toBe(true); - expect(deleteChain).toBeNull(); - }); - - it('should not show the delete modal when modal is not visible but chain is selected', () => { - const chain = {id: 'id2', name: 'Chain 2'} as ChainModel; - let isVisible: boolean = null; - let deleteChain: ChainModel = null; - component.isChainDeleteModalVisible$.subscribe(value => isVisible = value); - component.deleteChainItem$.subscribe(value => deleteChain = value); - - store.overrideSelector(getDeleteChain, chain); - store.refreshState(); - fixture.detectChanges(); - - const modalWindow = document.querySelector('.ant-modal'); - - expect(modalWindow).toBeNull(); - expect(isVisible).toBe(false); - expect(deleteChain).toEqual(chain); - }); - - it('should dispatch an action to delete the chain', () => { - spyOn(store, 'dispatch').and.callThrough(); - const chain = {id: 'id2', name: 'Chain 2'}; - let isVisible: boolean = null; - let deleteChain: ChainModel = null; - component.isChainDeleteModalVisible$.subscribe(value => isVisible = value); - component.deleteChainItem$.subscribe(value => deleteChain = value); - - store.overrideSelector(getDeleteModalVisible, true); - store.overrideSelector(getDeleteChain, chain); - store.refreshState(); - fixture.detectChanges(); - - const modalWindow = document.querySelector('.ant-modal'); - expect(modalWindow).toBeTruthy(); - expect(isVisible).toBe(true); - expect(deleteChain).toEqual(chain); - - - document.querySelector('.ant-modal')?.querySelector('button.ant-btn-primary')?.dispatchEvent(new Event('click')); - fixture.detectChanges(); - - const action = new fromActions.DeleteChainAction(chain.id, chain.name); - expect(store.dispatch).toHaveBeenCalledWith(action); - }); - - it('should change the sort order to descending', () => { - component.sortDescription$.next({key: 'name', value: 'descend'}); - fixture.detectChanges(); - component.chainDataSorted$.subscribe( - data => expect(data[0].name).toBe('Chain 3') - ); - }); - - it('should change the sort order to ascending', () => { - component.sortDescription$.next({key: 'name', value: 'ascend'}); - fixture.detectChanges(); - component.chainDataSorted$.subscribe( - data => expect(data[0].name).toBe('Chain 1') - ); - }); - - it('should call the show create modal on add button click', () => { - spyOn(store, 'dispatch').and.callThrough(); - - const addBtn = fixture.nativeElement.querySelector('[data-qe-id="add-chain-btn"]'); - addBtn.click(); - fixture.detectChanges(); - - expect(store.dispatch).toHaveBeenCalledWith(new fromActions.ShowCreateModalAction()); - }); - - it('should show create modal', () => { - spyOn(store, 'dispatch').and.callThrough(); - - store.overrideSelector(getCreateModalVisible, true); - store.refreshState(); - fixture.detectChanges(); - - const predicate = By.css('[data-qe-id="chain-name"]'); - const chainNameField: HTMLInputElement = fixture.debugElement.queryAll(predicate)[0].nativeElement; - expect(chainNameField).toBeDefined(); - - chainNameField.value = 'New Chain'; - chainNameField.dispatchEvent(new Event('input')); - fixture.detectChanges(); - - const createBtn = fixture.debugElement.queryAll(By.css('.ant-modal .ant-btn-primary'))[0].nativeElement; - createBtn.click(); - fixture.detectChanges(); - const action = new fromActions.CreateChainAction({name: 'New Chain'}); - - expect(store.dispatch).toHaveBeenCalledWith(action); - }); - - it('should call the pushValue function', () => { - spyOn(store, 'dispatch').and.callThrough(); - - store.overrideSelector(getCreateModalVisible, true); - store.refreshState(); - fixture.detectChanges(); - - const chainNameField: HTMLInputElement = fixture.debugElement.queryAll(By.css('[data-qe-id="chain-name"]'))[0].nativeElement; - expect(chainNameField).toBeDefined(); - - const pushMethodSpy = spyOn(component, 'pushChain'); - chainNameField.value = 'New Chain'; - chainNameField.dispatchEvent(new Event('input')); - fixture.detectChanges(); - - const createBtn = fixture.debugElement.queryAll(By.css('.ant-modal .ant-btn-primary'))[0].nativeElement; - createBtn.click(); - fixture.detectChanges(); - expect(pushMethodSpy).toHaveBeenCalledTimes(1); - }); }); diff --git a/flink-cyber/metron-parser-chain/parser-chains-config-service/frontend/parser-chains-client/src/app/chain-list-page/chain-list-page.component.ts b/flink-cyber/metron-parser-chain/parser-chains-config-service/frontend/parser-chains-client/src/app/chain-list-page/chain-list-page.component.ts index e4bb7b696..6dde53550 100644 --- a/flink-cyber/metron-parser-chain/parser-chains-config-service/frontend/parser-chains-client/src/app/chain-list-page/chain-list-page.component.ts +++ b/flink-cyber/metron-parser-chain/parser-chains-config-service/frontend/parser-chains-client/src/app/chain-list-page/chain-list-page.component.ts @@ -10,165 +10,186 @@ * limitations governing your use of the file. */ -import {Component, OnInit} from '@angular/core'; -import {UntypedFormBuilder, UntypedFormControl, UntypedFormGroup, Validators} from '@angular/forms'; -import {select, Store} from '@ngrx/store'; -import {BehaviorSubject, combineLatest, Observable, of} from 'rxjs'; -import {switchMap, take} from 'rxjs/operators'; - -import * as fromActions from './chain-list-page.actions'; -import {LoadPipelinesAction} from './chain-list-page.actions'; +import {Component, inject} from '@angular/core'; +import {BehaviorSubject, combineLatest, merge, Observable, of, Subject} from 'rxjs'; +import {catchError, filter, map, scan, shareReplay, startWith, switchMap} from 'rxjs/operators'; +import {ChainModel, ChainOperationalModel, DialogData, DialogForm} from './chain.model'; +import {PipelineService} from '../services/pipeline.service'; +import {ChainListPageService} from '../services/chain-list-page.service'; +import {MatDialog, MatDialogConfig} from '@angular/material/dialog'; +import {ChainDialogComponent} from 'src/app/chain-list-page/component/chain-dialog.component'; import { - ChainListPageState, - getChains, - getCreateModalVisible, - getDeleteChain, - getDeleteModalVisible, - getLoading, - getPipelineRenameModalVisible, - getPipelines, - getSelectedPipeline, -} from './chain-list-page.reducers'; -import {ChainModel, ChainOperationalModel} from './chain.model'; -import {NzMessageService} from "ng-zorro-antd/message"; + ConfirmDeleteDialogComponent, + DeleteDialogData +} from 'src/app/shared/components/confirm-delete-dialog/confirm-delete-dialog.component'; +import {changeStateFn} from 'src/app/shared/utils'; @Component({ selector: 'app-chain-list-page', templateUrl: './chain-list-page.component.html', - styleUrls: ['./chain-list-page.component.scss'] + styleUrls: ['./chain-list-page.component.scss'], }) -export class ChainListPageComponent implements OnInit { - isChainCreateModalVisible$: Observable; - isPipelineRenameModalVisible$: Observable; - isOkLoading$: Observable; - chains$: Observable; - isChainDeleteModalVisible$: Observable; - deleteChainItem$: Observable; - pipelineList$: Observable; - totalRecords = 200; - chainDataSorted$: Observable; - sortDescription$: BehaviorSubject<{ key: string, value: string }> = new BehaviorSubject({key: 'name', value: ''}); - newChainForm: UntypedFormGroup; - renamePipelineForm: UntypedFormGroup; - selectedPipeline$: Observable; - - - constructor( - private _store: Store, - private _fb: UntypedFormBuilder, - private _messageService: NzMessageService, - ) { - _store.dispatch(new LoadPipelinesAction()); - this.chains$ = _store.pipe(select(getChains)); - this.isOkLoading$ = _store.pipe(select(getLoading)); - this.isChainCreateModalVisible$ = _store.pipe(select(getCreateModalVisible)); - this.isChainDeleteModalVisible$ = _store.pipe(select(getDeleteModalVisible)); - this.isPipelineRenameModalVisible$ = _store.pipe(select(getPipelineRenameModalVisible)); - this.deleteChainItem$ = this._store.pipe(select(getDeleteChain)); - this.pipelineList$ = this._store.pipe(select(getPipelines)); - this.selectedPipeline$ = this._store.pipe(select(getSelectedPipeline)); - - this.chainDataSorted$ = combineLatest([ - this.chains$, - this.sortDescription$ +export class ChainListPageComponent { + //Injects and services + private _pipelineService = inject(PipelineService); + private _chainService = inject(ChainListPageService); + private _dialog = inject(MatDialog); + + // Pipelines + private _currentPipelineSubject = new BehaviorSubject(''); + private _currentPipeline$ = this._currentPipelineSubject.asObservable(); + private _pipelinesSubject = new Subject(); + + // Chains Event triggers + private _addChainSubject = new BehaviorSubject(null); + private _deleteChainSubject = new BehaviorSubject(null); + + // Chains Event + private _addChainEvent$ = this._addChainSubject.pipe( + filter(value => value !== null), // Filter out initial null emission + map(chain => (state: ChainModel[]) => [...state, chain])); + private _deleteChainEvent$ = this._deleteChainSubject.pipe( + filter(value => value !== null), // Filter out initial null emission + map(id => changeStateFn(id, 'id'))); + + // fetched Chains of currentPipeline + private _currentChains$ = this._currentPipeline$.pipe( + switchMap(pipeline => this._chainService.getChains(pipeline)), + switchMap(chains => { + return merge( + this._addChainEvent$, + this._deleteChainEvent$ + ).pipe( + startWith(lab => lab), + scan((state, reducer) => reducer(state), chains), + ) + }), + shareReplay(1) + ); + pipelines = merge(this._pipelinesSubject, + this._pipelineService.getPipelines() + .pipe( + catchError(_=> { + return of([]); + })) + ).pipe( + shareReplay(1) + ); + + // Combined data + get vm$() { + return combineLatest([ + this._currentPipeline$, + this.pipelines, + this._currentChains$ ]).pipe( - switchMap(([chains, sortDescription]) => this.sortTable(chains, sortDescription)) + map(([currentPipeline, pipelines, currentChains]) => ({currentPipeline, pipelines, currentChains})) ); } - get chainName() { - return this.newChainForm.get('chainName') as UntypedFormControl; - } - - get newPipelineName() { - return this.renamePipelineForm.get('pipelineName') as UntypedFormControl; - } - - pipelineChanged($event: string) { - this._store.dispatch(new fromActions.PipelineChangedAction($event)) - } - - showAddChainModal(): void { - this._store.dispatch(new fromActions.ShowCreateModalAction()); - } - - showDeleteModal(id): void { - this._store.dispatch(new fromActions.SelectDeleteChainAction(id)); - } - - pushChain(): void { - const chainName = this.chainName.value; - this.chains$.pipe(take(1)).subscribe(chainArr => { - const duplicate = chainArr.some(value => { - return value.name === chainName; - }); - if (!duplicate) { - const chainData: ChainOperationalModel = {name: chainName}; - this.newChainForm.reset(); - this._store.dispatch(new fromActions.CreateChainAction(chainData)); - } else { - this._messageService.create('Error', "Duplicate chain names aren't allowed!"); + openCreateDialog(currentPipeline: string) { + const createChainPipeline = (form: DialogForm): Observable => this._chainService.createChain(form as ChainOperationalModel, currentPipeline); + const config: MatDialogConfig> = { + ...dialogSize, + data: { + type: 'create', + name: 'Chain', + currentValue: '', + action: createChainPipeline.bind(this._chainService), + existValues: this._currentChains$, + columnUniqueKey: 'name' + }, + }; + this._dialog.open(ChainDialogComponent, config).afterClosed().subscribe((result: { + response: ChainModel, + form: DialogForm + }) => { + if (result) { + this._addChainSubject.next(result.response); } - }) - } - - deleteChain(chainId: string, chainName): void { - this._store.dispatch(new fromActions.DeleteChainAction(chainId, chainName)); - } - - handleCancelChainModal(): void { - this._store.dispatch(new fromActions.HideCreateModalAction()); + }); } - handleCancelDeleteModal(): void { - this._store.dispatch(new fromActions.HideDeleteModalAction()); + openChainDeleteDialog(chainDelete: ChainModel, currentPipeline: string) { + const deleteChain = () => this._chainService.deleteChain(chainDelete.id, currentPipeline); + const config: MatDialogConfig = { + ...dialogSize, + data: { + action: deleteChain.bind(this._chainService) + }, + }; + this._dialog.open(ConfirmDeleteDialogComponent, config).afterClosed().subscribe(_ => { + this._deleteChainSubject.next(chainDelete.id); + }); } - sortTable(data: ChainModel[], sortDescription: any): Observable { - const sortValue = sortDescription.value; - const newData = (data || []).slice().sort((a, b) => { - const first = a.name.toLowerCase(); - const second = b.name.toLowerCase(); - if (sortValue === 'ascend') { - return (first < second) ? -1 : 1; - } else if (sortValue === 'descend') { - return (first < second) ? 1 : -1; - } else { - return 0; + addPipeline(pipelines: string[]) { + const addPipeline = (form: DialogForm) => this._pipelineService.createPipeline(form.name); + const config: MatDialogConfig> = { + ...dialogSize, + data: { + type: 'create', + name: 'Pipeline', + action: addPipeline.bind(this._pipelineService), + existValues: pipelines + }, + }; + this._dialog.open(ChainDialogComponent, config).afterClosed().subscribe((result: { + response: string[], + form: DialogForm + }) => { + if (result) { + this._pipelinesSubject.next(result.response); } }); - return of(newData); } - ngOnInit() { - this.newChainForm = this._fb.group({ - chainName: new UntypedFormControl('', [Validators.required, Validators.minLength(3)]), - }); - this.renamePipelineForm = this._fb.group({ - pipelineName: new UntypedFormControl('', [Validators.required, Validators.minLength(3)]), + updatePipeline(pipelines: string[], currentPipeline: string) { + const renamePipeline = (form: DialogForm) => this._pipelineService.renamePipeline(form.name, currentPipeline); + const config: MatDialogConfig> = { + ...dialogSize, + data: { + type: 'edit', + name: 'Pipeline', + currentValue: currentPipeline, + action: renamePipeline.bind(this._pipelineService), + existValues: pipelines + }, + }; + this._dialog.open(ChainDialogComponent, config).afterClosed().subscribe((result: { + response: string[], + form: DialogForm + }) => { + if (result) { + this._pipelinesSubject.next(result.response); + this._currentPipelineSubject.next(result.form.name); + } }); - } - - showPipelineRenameModal() { - this._store.dispatch(new fromActions.ShowRenameSelectedPipelineModalAction()); - } - handlePipelineRenameModalCancel() { - this._store.dispatch(new fromActions.HideRenamePipelineModalAction()); } - createPipeline(inputElement: HTMLInputElement) { - const pipelineName = inputElement.value; - this._store.dispatch(new fromActions.CreatePipelineAction(pipelineName)); - } + deletePipeline(currentPipeline: string) { + const deletePipeline = () => this._pipelineService.deletePipeline(currentPipeline); + const config: MatDialogConfig = { + ...dialogSize, + data: { + action: deletePipeline.bind(this._pipelineService) + }, + }; + this._dialog.open(ConfirmDeleteDialogComponent, config).afterClosed().subscribe((result) => { + this._pipelinesSubject.next(result); + this._currentPipelineSubject.next(''); - deletePipeline() { - this._store.dispatch(new fromActions.DeleteSelectedPipelineAction()); + }); } - renamePipeline() { - const newPipelineName = this.newPipelineName.value; - this.renamePipelineForm.reset(); - this._store.dispatch(new fromActions.RenameSelectedPipelineAction(newPipelineName)); + onPipelineSelected(value: string) { + this._currentPipelineSubject.next(value); } } + +const dialogSize = { + width: '30vw', + minWidth: '350px', + maxWidth: '80vw' +}; diff --git a/flink-cyber/metron-parser-chain/parser-chains-config-service/frontend/parser-chains-client/src/app/chain-list-page/chain-list-page.effects.spec.ts b/flink-cyber/metron-parser-chain/parser-chains-config-service/frontend/parser-chains-client/src/app/chain-list-page/chain-list-page.effects.spec.ts deleted file mode 100644 index 0e917397d..000000000 --- a/flink-cyber/metron-parser-chain/parser-chains-config-service/frontend/parser-chains-client/src/app/chain-list-page/chain-list-page.effects.spec.ts +++ /dev/null @@ -1,232 +0,0 @@ -/* - * Copyright 2020 - 2022 Cloudera. All Rights Reserved. - * - * This file is licensed under the Apache License Version 2.0 (the "License"). You may not use this file - * except in compliance with the License. You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0. - * - * This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, - * either express or implied. Refer to the License for the specific permissions and - * limitations governing your use of the file. - */ - -import {HttpClientTestingModule} from '@angular/common/http/testing'; -import {TestBed, waitForAsync} from '@angular/core/testing'; -import {NoopAnimationsModule} from '@angular/platform-browser/animations'; -import {provideMockActions} from '@ngrx/effects/testing'; -import {NzModalModule,} from 'ng-zorro-antd/modal'; -import {NzMessageService} from 'ng-zorro-antd/message'; -import {of, ReplaySubject, throwError} from 'rxjs'; - -import {ChainListPageService} from '../services/chain-list-page.service'; -import * as fromActions from './chain-list-page.actions'; -import {ChainListEffects} from './chain-list-page.effects'; -import {Action} from "@ngrx/store"; -import {provideMockStore} from "@ngrx/store/testing"; - -const selectedPipeline = 'foo-pipeline'; -const initialState = { - "chain-list-page": { - items: [], - createModalVisible: false, - deleteModalVisible: false, - deleteItem: null, - loading: false, - error: '', - pipelines: null, - pipelineRenameModalVisible: false, - selectedPipeline - } -}; - -describe('ChainListPage: effects', () => { - let effects: ChainListEffects; - let actions: ReplaySubject; - let service: jasmine.SpyObj; - let msgService: jasmine.SpyObj; - - beforeEach(waitForAsync(() => { - TestBed.configureTestingModule({ - imports: [ - NzModalModule, - HttpClientTestingModule, - NoopAnimationsModule, - ], - providers: [ - provideMockStore({ - initialState, - selectors: [] - }), - ChainListEffects, - provideMockActions(() => actions), - { - provide: ChainListPageService, - useValue: jasmine.createSpyObj('ChainListPageService', ["createChain", "deleteChain", "getChains"]) - }, - provideMockActions(() => actions), - { - provide: NzMessageService, - useValue: jasmine.createSpyObj('NzMessageService', ["create", "error"]) - }, - ] - }); - - effects = TestBed.inject(ChainListEffects); - service = TestBed.inject(ChainListPageService) as jasmine.SpyObj; - msgService = TestBed.inject(NzMessageService) as jasmine.SpyObj; - })); - - it('loadChains should call the service and return with entries when it succeeds', (done) => { - const expected = [{ - id: 'id1', - name: 'Chain 1' - }]; - - service.getChains.and.returnValue(of(expected)); - - actions = new ReplaySubject(1); - actions.next(new fromActions.LoadChainsAction()); - - effects.loadChains$.subscribe(result => { - expect(result).toEqual(new fromActions.LoadChainsSuccessAction(expected)); - done(); - }); - - expect(service.getChains).toHaveBeenCalledWith(selectedPipeline); - }); - - it('loadChains should return with an error when it fails and call ant`s message service', (done) => { - const msg = 'Uh-oh!'; - const error = new Error(msg); - service.getChains.and.returnValue(throwError(error)); - - actions = new ReplaySubject(1); - actions.next(new fromActions.LoadChainsAction()); - - effects.loadChains$.subscribe(result => { - expect(result).toEqual(new fromActions.LoadChainsFailAction(error)); - done(); - }); - - expect(msgService.create).toHaveBeenCalledWith('error', msg); - }); - - it('createChain should call the service and return with entries when it succeeds', (done) => { - const chain = {id: 'id2', name: 'Chain 2'}; - service.createChain.and.returnValue(of(chain)); - actions = new ReplaySubject(1); - actions.next(new fromActions.CreateChainAction({name: chain.name})); - - effects.createChain$.subscribe(result => { - expect(result).toEqual( - new fromActions.CreateChainSuccessAction(chain) - ); - done(); - }); - - expect(service.createChain).toHaveBeenCalledWith({name: chain.name}, selectedPipeline); - expect(msgService.create).toHaveBeenCalledWith( - 'success', - 'Chain Chain 2 has been created' - ); - }); - - it('deleteChain should call the service and return with entries when it succeeds', (done) => { - const deleteChain = { - id: 'id1', - name: 'Chain 1' - }; - actions = new ReplaySubject(1); - actions.next(new fromActions.DeleteChainAction(deleteChain.id, deleteChain.name)); - service.deleteChain.and.returnValue(of(deleteChain)); - - effects.deleteChain$.subscribe(result => { - expect(result).toEqual( - new fromActions.DeleteChainSuccessAction(deleteChain.id) - ); - done(); - }); - - expect(service.deleteChain).toHaveBeenCalledWith(deleteChain.id, selectedPipeline); - expect(msgService.create).toHaveBeenCalledWith( - 'success', - `Chain "${deleteChain.name}" deleted Successfully` - ); - }); - - it('deleteChain should return with an error when it fails', (done) => { - const deleteChain = { - id: 'id1', - name: 'Chain 1' - }; - const msg = 'Uh-oh!'; - const error = new Error(msg); - service.deleteChain.and.returnValue(throwError(error)); - actions = new ReplaySubject(1); - actions.next(new fromActions.DeleteChainAction(deleteChain.id, deleteChain.name)); - - effects.deleteChain$.subscribe(result => { - expect(result).toEqual( - new fromActions.DeleteChainFailAction(error) - ); - done(); - }); - - expect(service.deleteChain).toHaveBeenCalledWith('id1', selectedPipeline); - expect(msgService.create).toHaveBeenCalledWith( - 'error', - 'Uh-oh!' - ); - }); - - it('hideCreateModal should return with the success action', (done) => { - actions = new ReplaySubject(1); - actions.next(new fromActions.CreateChainSuccessAction(null)); - - effects.hideCreateModal$.subscribe(result => { - expect(result).toEqual(new fromActions.HideCreateModalAction()); - done(); - }); - }); - - it('hideCreateModal should return with the fail action', (done) => { - actions = new ReplaySubject(1); - actions.next(new fromActions.CreateChainFailAction(null)); - - effects.hideCreateModal$.subscribe(result => { - expect(result).toEqual(new fromActions.HideCreateModalAction()); - done(); - }); - }); - - it('hideDeleteModal should return with the success action', (done) => { - actions = new ReplaySubject(1); - actions.next(new fromActions.DeleteChainSuccessAction("id1")); - - effects.hideDeleteModal$.subscribe(result => { - expect(result).toEqual(new fromActions.HideDeleteModalAction()); - done(); - }); - }); - - it('hideDeleteModal should return with the error action', (done) => { - actions = new ReplaySubject(1); - actions.next(new fromActions.DeleteChainFailAction(null)); - - effects.hideDeleteModal$.subscribe(result => { - expect(result).toEqual(new fromActions.HideDeleteModalAction()); - done(); - }); - }); - - it('showDeleteModal should return with an action', (done) => { - actions = new ReplaySubject(1); - actions.next(new fromActions.SelectDeleteChainAction("id1")); - - effects.showDeleteModal$.subscribe(result => { - expect(result).toEqual(new fromActions.ShowDeleteModalAction()); - done(); - }); - }); - -}); diff --git a/flink-cyber/metron-parser-chain/parser-chains-config-service/frontend/parser-chains-client/src/app/chain-list-page/chain-list-page.effects.ts b/flink-cyber/metron-parser-chain/parser-chains-config-service/frontend/parser-chains-client/src/app/chain-list-page/chain-list-page.effects.ts deleted file mode 100644 index 5bf15af1f..000000000 --- a/flink-cyber/metron-parser-chain/parser-chains-config-service/frontend/parser-chains-client/src/app/chain-list-page/chain-list-page.effects.ts +++ /dev/null @@ -1,202 +0,0 @@ -/* - * Copyright 2020 - 2022 Cloudera. All Rights Reserved. - * - * This file is licensed under the Apache License Version 2.0 (the "License"). You may not use this file - * except in compliance with the License. You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0. - * - * This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, - * either express or implied. Refer to the License for the specific permissions and - * limitations governing your use of the file. - */ - -import {Injectable} from '@angular/core'; -import {Actions, createEffect, ofType} from '@ngrx/effects'; -import {Action, Store} from '@ngrx/store'; -import {NzMessageService} from 'ng-zorro-antd/message'; -import {Observable, of} from 'rxjs'; -import {catchError, map, switchMap, withLatestFrom} from 'rxjs/operators'; - -import {ChainListPageService} from '../services/chain-list-page.service'; -import * as fromActions from './chain-list-page.actions'; -import {ChainModel} from './chain.model'; -import {PipelineService} from "../services/pipeline.service"; -import {ChainListPageState, getSelectedPipeline} from "./chain-list-page.reducers"; - -@Injectable() -export class ChainListEffects { - - loadChains$: Observable = createEffect(() => this._actions$.pipe( - ofType(fromActions.LOAD_CHAINS, fromActions.LOAD_PIPELINES_SUCCESS), - withLatestFrom(this._store$.select(getSelectedPipeline)), - switchMap(([, selectedPipeline]) => { - return this._chainListService.getChains(selectedPipeline) - .pipe( - map((chains: ChainModel[]) => { - return new fromActions.LoadChainsSuccessAction(chains); - }), - catchError((error: { message: string }) => { - this._messageService.create('error', error.message); - return of(new fromActions.LoadChainsFailAction(error)); - }) - ); - }) - )); - - createChain$: Observable = createEffect(() => this._actions$.pipe( - ofType(fromActions.CREATE_CHAIN), - withLatestFrom(this._store$.select(getSelectedPipeline)), - switchMap(([action, selectedPipeline]) => { - const finalAction = (action as fromActions.CreateChainAction) - return this._chainListService.createChain(finalAction.newChain, selectedPipeline) - .pipe( - map((chain: ChainModel) => { - this._messageService.create('success', 'Chain ' + finalAction.newChain.name + ' has been created'); - return new fromActions.CreateChainSuccessAction(chain); - }), - catchError((error: { message: string }) => { - this._messageService.create('error', error.message); - return of(new fromActions.CreateChainFailAction(error)); - }) - ); - }) - )); - - hideCreateModal$: Observable = createEffect(() => this._actions$.pipe( - ofType( - fromActions.CREATE_CHAIN_SUCCESS, - fromActions.CREATE_CHAIN_FAIL - ), - map(() => new fromActions.HideCreateModalAction()) - )) - - hideRenamePipelineModal$: Observable = createEffect(() => this._actions$.pipe( - ofType( - fromActions.RENAME_PIPELINE_SUCCESS, - fromActions.RENAME_PIPELINE_FAIL - ), - map(() => new fromActions.HideRenamePipelineModalAction()) - )) - hideDeleteModal$: Observable = createEffect(() => this._actions$.pipe( - ofType( - fromActions.DELETE_CHAIN_SUCCESS, - fromActions.DELETE_CHAIN_FAIL - ), - map(() => new fromActions.HideDeleteModalAction()) - )) - - showDeleteModal$: Observable = createEffect(() => this._actions$.pipe( - ofType(fromActions.DELETE_CHAIN_SELECT), - map(() => new fromActions.ShowDeleteModalAction()) - ), - ) - - deleteChain$: Observable = createEffect(() => this._actions$.pipe( - ofType(fromActions.DELETE_CHAIN), - withLatestFrom(this._store$.select(getSelectedPipeline)), - switchMap(([action, selectedPipeline]) => { - const finalAction = action as fromActions.DeleteChainAction - return this._chainListService.deleteChain(finalAction.chainId, selectedPipeline) - .pipe( - map(() => { - this._messageService.create('success', 'Chain "' + finalAction.chainName + '" deleted Successfully'); - return new fromActions.DeleteChainSuccessAction(finalAction.chainId); - }), - catchError((error: { message: string }) => { - this._messageService.create('error', error.message); - return of(new fromActions.DeleteChainFailAction(error)); - }) - ); - }) - )); - - loadPipelines$: Observable = createEffect(() => this._actions$.pipe( - ofType(fromActions.LOAD_PIPELINES), - switchMap(_ => { - return this._pipelineService.getPipelines() - .pipe( - map((pipelines: string[]) => { - return new fromActions.LoadPipelinesSuccessAction(pipelines); - }), - catchError((error: { message: string }) => { - this._messageService.create('error', error.message); - return of(new fromActions.LoadPipelinesFailAction(error)); - }) - ); - }) - )); - - createPipeline$: Observable = createEffect(() => this._actions$.pipe( - ofType(fromActions.CREATE_PIPELINE), - switchMap((action: fromActions.CreatePipelineAction) => { - return this._pipelineService.createPipeline(action.pipelineName) - .pipe( - map((pipelines: string[]) => { - return new fromActions.CreatePipelineSuccessAction(action.pipelineName, pipelines); - }), - catchError((error: { message: string }) => { - this._messageService.create('error', error.message); - return of(new fromActions.CreatePipelineFailAction(error)); - }) - ); - }) - )); - - renameSelectedPipeline$: Observable = createEffect(() => this._actions$.pipe( - ofType(fromActions.RENAME_SELECTED_PIPELINE), - withLatestFrom(this._store$.select(getSelectedPipeline)), - switchMap(([action, selectedPipeline]) => { - const finalAction = action as fromActions.RenameSelectedPipelineAction; - return this._pipelineService.renamePipeline(selectedPipeline, finalAction.newPipelineName) - .pipe( - map((pipelines: string[]) => { - return new fromActions.RenamePipelineSuccessAction(finalAction.newPipelineName, pipelines); - }), - catchError((error: { message: string }) => { - this._messageService.create('error', error.message); - return of(new fromActions.RenamePipelineFailAction(error)); - }) - ); - }) - )); - - deleteSelectedPipeline$: Observable = createEffect(() => this._actions$.pipe( - ofType(fromActions.DELETE_SELECTED_PIPELINE), - withLatestFrom(this._store$.select(getSelectedPipeline)), - switchMap(([_,selectedPipeline]) => { - return this._pipelineService.deletePipeline(selectedPipeline) - .pipe( - map((pipelines: string[]) => { - return new fromActions.DeletePipelineSuccessAction(pipelines); - }), - catchError((error: { message: string }) => { - this._messageService.create('error', error.message); - return of(new fromActions.DeletePipelineFailAction(error)); - }) - ); - }) - )); - - triggerPipelineChanged$: Observable = createEffect(() => this._actions$.pipe( - ofType(fromActions.RENAME_PIPELINE_SUCCESS, fromActions.DELETE_PIPELINE_SUCCESS), - switchMap((action: fromActions.RenamePipelineSuccessAction) => { - return of(new fromActions.PipelineChangedAction(action.newPipelineName)) - }) - )); - - pipelineChanged$: Observable = createEffect(() => this._actions$.pipe( - ofType(fromActions.PIPELINE_CHANGED), - switchMap(_ => { - return of(new fromActions.LoadChainsAction()) - }) - )); - - constructor( - private _actions$: Actions, - private _store$: Store, - private _messageService: NzMessageService, - private _chainListService: ChainListPageService, - private _pipelineService: PipelineService - ) { - } -} diff --git a/flink-cyber/metron-parser-chain/parser-chains-config-service/frontend/parser-chains-client/src/app/chain-list-page/chain-list-page.module.ts b/flink-cyber/metron-parser-chain/parser-chains-config-service/frontend/parser-chains-client/src/app/chain-list-page/chain-list-page.module.ts index 41da4d937..cb2666a6d 100644 --- a/flink-cyber/metron-parser-chain/parser-chains-config-service/frontend/parser-chains-client/src/app/chain-list-page/chain-list-page.module.ts +++ b/flink-cyber/metron-parser-chain/parser-chains-config-service/frontend/parser-chains-client/src/app/chain-list-page/chain-list-page.module.ts @@ -10,55 +10,50 @@ * limitations governing your use of the file. */ -import { CommonModule } from '@angular/common'; -import { NgModule } from '@angular/core'; -import { FormsModule, ReactiveFormsModule } from '@angular/forms'; -import { RouterModule } from '@angular/router'; -import { EffectsModule } from '@ngrx/effects'; -import { StoreModule } from '@ngrx/store'; -import { NzTableModule } from 'ng-zorro-antd/table' -import { NzMessageService } from 'ng-zorro-antd/message'; -import { NzInputModule } from 'ng-zorro-antd/input'; -import { NzModalModule} from "ng-zorro-antd/modal"; - -import { ChainListPageComponent } from './chain-list-page.component'; -import { ChainListEffects } from './chain-list-page.effects'; -import { reducer } from './chain-list-page.reducers'; -import {NzCardModule} from "ng-zorro-antd/card"; -import {NzDividerModule} from "ng-zorro-antd/divider"; -import {NzToolTipModule} from "ng-zorro-antd/tooltip"; -import {NzButtonModule} from "ng-zorro-antd/button"; -import {NzPopconfirmModule} from "ng-zorro-antd/popconfirm"; -import { NzIconModule } from 'ng-zorro-antd/icon'; -import { NzFormModule } from 'ng-zorro-antd/form'; -import { NzLayoutModule } from 'ng-zorro-antd/layout'; -import {NzSelectModule} from "ng-zorro-antd/select"; +import {NgModule} from '@angular/core'; +import {FormsModule, ReactiveFormsModule} from '@angular/forms'; +import {RouterModule} from '@angular/router'; +import {ChainListPageComponent} from './chain-list-page.component'; +import {SharedModule} from 'src/app/shared/share.module'; +import {MatCardModule} from '@angular/material/card'; +import {MatDividerModule} from '@angular/material/divider'; +import {MatTooltipModule} from '@angular/material/tooltip'; +import {MatTableModule} from '@angular/material/table'; +import {MatDialogModule} from '@angular/material/dialog'; +import {MatButtonModule} from '@angular/material/button'; +import {MatIconModule} from '@angular/material/icon'; +import {MatFormFieldModule} from '@angular/material/form-field'; +import {MatInputModule} from '@angular/material/input'; +import {AsyncPipe, NgComponentOutlet, NgForOf, NgIf, NgTemplateOutlet} from '@angular/common'; +import {ChainDialogComponent} from 'src/app/chain-list-page/component/chain-dialog.component'; +import {MatSelectModule} from '@angular/material/select'; +import {MatAutocompleteModule} from '@angular/material/autocomplete'; @NgModule({ - declarations: [ ChainListPageComponent ], - imports: [ - NzModalModule, - CommonModule, - FormsModule, - ReactiveFormsModule, - NzInputModule, - NzTableModule, - RouterModule, - StoreModule.forFeature('chain-list-page', reducer), - EffectsModule.forFeature([ChainListEffects]), - NzCardModule, - NzDividerModule, - NzToolTipModule, - NzButtonModule, - NzPopconfirmModule, - NzIconModule, - NzFormModule, - NzLayoutModule, - NzSelectModule, - ], - providers: [ - NzMessageService, + declarations: [ChainListPageComponent, ChainDialogComponent], + imports: [ + ReactiveFormsModule, + RouterModule, + SharedModule, + MatCardModule, + MatDividerModule, + MatTooltipModule, + MatTableModule, + MatDialogModule, + MatButtonModule, + MatIconModule, + MatFormFieldModule, + MatInputModule, + NgIf, + NgTemplateOutlet, + NgComponentOutlet, + AsyncPipe, + MatSelectModule, + FormsModule, + NgForOf, + MatAutocompleteModule ], - exports: [ ChainListPageComponent ] + providers: [], + exports: [ChainListPageComponent] }) export class ChainListPageModule { } diff --git a/flink-cyber/metron-parser-chain/parser-chains-config-service/frontend/parser-chains-client/src/app/chain-list-page/chain-list-page.reducers.spec.ts b/flink-cyber/metron-parser-chain/parser-chains-config-service/frontend/parser-chains-client/src/app/chain-list-page/chain-list-page.reducers.spec.ts deleted file mode 100644 index 3a21c1c30..000000000 --- a/flink-cyber/metron-parser-chain/parser-chains-config-service/frontend/parser-chains-client/src/app/chain-list-page/chain-list-page.reducers.spec.ts +++ /dev/null @@ -1,244 +0,0 @@ -/* - * Copyright 2020 - 2022 Cloudera. All Rights Reserved. - * - * This file is licensed under the Apache License Version 2.0 (the "License"). You may not use this file - * except in compliance with the License. You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0. - * - * This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, - * either express or implied. Refer to the License for the specific permissions and - * limitations governing your use of the file. - */ -import * as fromActions from './chain-list-page.actions'; -import * as fromReducers from './chain-list-page.reducers'; -import {ChainListPageState} from "./chain-list-page.reducers"; - - -describe('chain-list-page: reducers', () => { - it('should return with the initial state.', () => { - expect(fromReducers.reducer(undefined, new fromActions.NoopChainAction())).toEqual(fromReducers.initialState); - }); - - it('should return with the previous state', () => { - const previousState: ChainListPageState = { - items: [], - createModalVisible: false, - deleteModalVisible: false, - deleteItem: null, - loading: false, - error: '', - pipelines: null, - pipelineRenameModalVisible: false, - selectedPipeline: null - }; - expect(fromReducers.reducer(previousState, new fromActions.NoopChainAction())).toEqual(previousState); - }); - - it('should set loading true', () => { - const previousState: ChainListPageState = { - items: [], - createModalVisible: false, - deleteModalVisible: false, - deleteItem: null, - loading: false, - error: '', - pipelines: null, - pipelineRenameModalVisible: false, - selectedPipeline: null - }; - expect(fromReducers.reducer(previousState, new fromActions.LoadChainsAction()) - ).toEqual({ - ...previousState, - loading: true, - }); - }); - - it('should set the error message and set loading false', () => { - const previousState: ChainListPageState = { - items: [], - createModalVisible: false, - deleteModalVisible: false, - deleteItem: null, - loading: true, - error: '', - pipelines: null, - pipelineRenameModalVisible: false, - selectedPipeline: null - }; - expect(fromReducers.reducer(previousState, new fromActions.LoadChainsFailAction({ - message: 'Something went wrong.' - })) - ).toEqual({ - ...previousState, - loading: false, - error: 'Something went wrong.' - }); - }); - - it('should set the items and set loading false', () => { - const items = [{ - id: 'id1', - name: 'Chain 1' - }, { - id: 'id2', - name: 'Chain 2' - }]; - const previousState: ChainListPageState = { - items, - createModalVisible: false, - deleteModalVisible: false, - deleteItem: null, - loading: true, - error: '', - pipelines: null, - pipelineRenameModalVisible: false, - selectedPipeline: null - }; - expect( - fromReducers.reducer(previousState, new fromActions.LoadChainsSuccessAction(items)) - ).toEqual({ - ...previousState, - loading: false, - items - }); - }); - - it('should delete the chain', () => { - const itemList = [{ - id: 'id1', - name: 'Chain 1' - }, { - id: 'id2', - name: 'Chain 2' - }]; - - const previousState: ChainListPageState = { - items: itemList, - createModalVisible: false, - deleteModalVisible: false, - deleteItem: null, - loading: true, - error: '', - pipelines: null, - pipelineRenameModalVisible: false, - selectedPipeline: null - }; - - expect( - fromReducers.reducer(previousState, new fromActions.DeleteChainSuccessAction('id2')) - ).toEqual({ - ...previousState, - loading: false, - items: [{ - id: 'id1', - name: 'Chain 1' - }] - }); - }); - - it('should set the delete error message and set loading false', () => { - const previousState: ChainListPageState = { - items: [], - createModalVisible: false, - deleteModalVisible: false, - deleteItem: null, - loading: true, - error: '', - pipelines: null, - pipelineRenameModalVisible: false, - selectedPipeline: null - }; - expect(fromReducers.reducer(previousState, new fromActions.DeleteChainFailAction({ - message: 'Something went wrong.' - })) - ).toEqual({ - ...previousState, - loading: false, - error: 'Something went wrong.' - }); - }); - - it('should create a chain', () => { - const itemList = [{ - id: 'id1', - name: 'Chain 1' - }]; - - const previousState: ChainListPageState = { - items: itemList, - createModalVisible: false, - deleteModalVisible: false, - deleteItem: null, - loading: true, - error: '', - pipelines: null, - pipelineRenameModalVisible: false, - selectedPipeline: null - }; - - expect( - fromReducers.reducer(previousState, new fromActions.CreateChainSuccessAction({id: 'id2', name: 'Chain 2'})) - ).toEqual({ - ...previousState, - loading: false, - items: [{ - id: 'id1', - name: 'Chain 1' - }, { - id: 'id2', - name: 'Chain 2' - }] - }); - }); - - it('should set the create error message and set loading false', () => { - const previousState: ChainListPageState = { - items: [], - createModalVisible: false, - deleteModalVisible: false, - deleteItem: null, - loading: true, - error: '', - pipelines: null, - pipelineRenameModalVisible: false, - selectedPipeline: null - }; - expect(fromReducers.reducer(previousState, new fromActions.CreateChainFailAction({ - message: 'Something went wrong.' - })) - ).toEqual({ - ...previousState, - loading: false, - error: 'Something went wrong.' - }); - }); -}); - -describe('chain-list-page: selectors', () => { - it('should return with substate', () => { - const expected: ChainListPageState = { - items: [], - createModalVisible: false, - deleteModalVisible: false, - deleteItem: null, - loading: false, - error: '', - pipelines: null, - pipelineRenameModalVisible: false, - selectedPipeline: null - }; - - const state = {'chain-list-page': expected}; - expect(fromReducers.getChainListPageState(state)).toEqual(expected); - }); - - it('should return with types', () => { - const expected = []; - const state = { - 'chain-list-page': { - items: expected - } - }; - expect(fromReducers.getChains(state)).toEqual(expected); - }); -}); diff --git a/flink-cyber/metron-parser-chain/parser-chains-config-service/frontend/parser-chains-client/src/app/chain-list-page/chain-list-page.reducers.ts b/flink-cyber/metron-parser-chain/parser-chains-config-service/frontend/parser-chains-client/src/app/chain-list-page/chain-list-page.reducers.ts deleted file mode 100644 index 1925eba32..000000000 --- a/flink-cyber/metron-parser-chain/parser-chains-config-service/frontend/parser-chains-client/src/app/chain-list-page/chain-list-page.reducers.ts +++ /dev/null @@ -1,238 +0,0 @@ -/* - * Copyright 2020 - 2022 Cloudera. All Rights Reserved. - * - * This file is licensed under the Apache License Version 2.0 (the "License"). You may not use this file - * except in compliance with the License. You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0. - * - * This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, - * either express or implied. Refer to the License for the specific permissions and - * limitations governing your use of the file. - */ - -import {createSelector} from '@ngrx/store'; - -import * as chainListPageActions from './chain-list-page.actions'; -import {ChainModel} from './chain.model'; - -export interface ChainListPageState { - loading: boolean; - createModalVisible: boolean; - deleteModalVisible: boolean; - pipelineRenameModalVisible: boolean; - deleteItem: ChainModel; - error: string; - items: ChainModel[]; - selectedPipeline: string; - pipelines: string[]; -} - -export const initialState: ChainListPageState = { - loading: false, - createModalVisible: false, - deleteModalVisible: false, - pipelineRenameModalVisible: false, - deleteItem: null, - items: [], - selectedPipeline: null, - pipelines: [], - error: '' -}; - -export function reducer( - state: ChainListPageState = initialState, - action: chainListPageActions.ChainListAction -): ChainListPageState { - switch (action.type) { - case chainListPageActions.LOAD_CHAINS: { - return { - ...state, - loading: true, - }; - } - case chainListPageActions.LOAD_CHAINS_SUCCESS: { - return { - ...state, - loading: false, - createModalVisible: false, - items: action.chains - }; - } - case chainListPageActions.LOAD_CHAINS_FAIL: { - return { - ...state, - createModalVisible: false, - error: action.error.message, - loading: false, - }; - } - case chainListPageActions.CREATE_CHAIN: { - return { - ...state, - loading: true, - }; - } - case chainListPageActions.SHOW_CREATE_MODAL: { - return { - ...state, - createModalVisible: true, - } - } - case chainListPageActions.HIDE_CREATE_MODAL: { - return { - ...state, - createModalVisible: false, - } - } - case chainListPageActions.SHOW_RENAME_SELECTED_PIPELINE_MODAL: { - return { - ...state, - pipelineRenameModalVisible: true, - } - } - case chainListPageActions.HIDE_RENAME_PIPELINE_MODAL: { - return { - ...state, - pipelineRenameModalVisible: false, - } - } - case chainListPageActions.SHOW_DELETE_MODAL: { - return { - ...state, - deleteModalVisible: true, - } - } - case chainListPageActions.HIDE_DELETE_MODAL: { - return { - ...state, - deleteModalVisible: false, - items: state.items.map(chainItem => ({...chainItem, selected: false})) - } - } - case chainListPageActions.CREATE_CHAIN_SUCCESS: { - return { - ...state, - loading: false, - items: [...state.items, action.chain] - }; - } - case chainListPageActions.CREATE_CHAIN_FAIL: { - return { - ...state, - error: action.error.message, - loading: false, - }; - } - case chainListPageActions.DELETE_CHAIN_SELECT: { - return { - ...state, - deleteItem: state.items.find(chainItem => chainItem.id === action.chainId) - }; - } - case chainListPageActions.DELETE_CHAIN: { - return { - ...state, - loading: true, - }; - } - case chainListPageActions.DELETE_CHAIN_SUCCESS: { - return { - ...state, - loading: false, - items: state.items.filter(chainItem => chainItem.id !== action.chainId), - deleteItem: null, - }; - } - case chainListPageActions.DELETE_CHAIN_FAIL: { - return { - ...state, - error: action.error.message, - loading: false, - deleteItem: null, - }; - } - case chainListPageActions.LOAD_PIPELINES: - case chainListPageActions.CREATE_PIPELINE: - case chainListPageActions.RENAME_SELECTED_PIPELINE: - case chainListPageActions.DELETE_SELECTED_PIPELINE: { - return { - ...state, - loading: true, - }; - } - case chainListPageActions.LOAD_PIPELINES_SUCCESS: - case chainListPageActions.CREATE_PIPELINE_SUCCESS: - case chainListPageActions.RENAME_PIPELINE_SUCCESS: - case chainListPageActions.DELETE_PIPELINE_SUCCESS: { - return { - ...state, - loading: false, - pipelines: action.pipelines - }; - } - case chainListPageActions.LOAD_PIPELINES_FAIL: - case chainListPageActions.CREATE_PIPELINE_FAIL: - case chainListPageActions.RENAME_PIPELINE_FAIL: - case chainListPageActions.DELETE_PIPELINE_FAIL: { - return { - ...state, - error: action.error.message, - loading: false, - }; - } - case chainListPageActions.PIPELINE_CHANGED: { - return { - ...state, - selectedPipeline: action.newPipelineName, - }; - } - default: { - return state; - } - } -} - -export function getChainListPageState(state: any): ChainListPageState { - return state['chain-list-page']; -} - -export const getChains = createSelector( - getChainListPageState, - (state: ChainListPageState) => state.items -); - -export const getLoading = createSelector( - getChainListPageState, - (state: ChainListPageState) => state.loading -); - -export const getCreateModalVisible = createSelector( - getChainListPageState, - (state: ChainListPageState) => state.createModalVisible -); - -export const getDeleteModalVisible = createSelector( - getChainListPageState, - (state: ChainListPageState) => state.deleteModalVisible -); - -export const getPipelineRenameModalVisible = createSelector( - getChainListPageState, - (state: ChainListPageState) => state.pipelineRenameModalVisible -); - -export const getDeleteChain = createSelector( - getChainListPageState, - (state: ChainListPageState) => state.deleteItem -); - -export const getPipelines = createSelector( - getChainListPageState, - (state: ChainListPageState) => state.pipelines -); - -export const getSelectedPipeline = createSelector( - getChainListPageState, - (state: ChainListPageState) => state.selectedPipeline -); - diff --git a/flink-cyber/metron-parser-chain/parser-chains-config-service/frontend/parser-chains-client/src/app/chain-list-page/chain.model.ts b/flink-cyber/metron-parser-chain/parser-chains-config-service/frontend/parser-chains-client/src/app/chain-list-page/chain.model.ts index de37b4c42..3b4b419ea 100644 --- a/flink-cyber/metron-parser-chain/parser-chains-config-service/frontend/parser-chains-client/src/app/chain-list-page/chain.model.ts +++ b/flink-cyber/metron-parser-chain/parser-chains-config-service/frontend/parser-chains-client/src/app/chain-list-page/chain.model.ts @@ -10,10 +10,24 @@ * limitations governing your use of the file. */ -export interface ChainModel { +import {Observable} from 'rxjs'; + +export type ChainModel = { id: string; name: string; } -export interface ChainOperationalModel { + +export type DialogForm = { + name: string; +} + +export type DialogData = { + currentValue?: string; name: string; + type: 'create' | 'edit'; + action: (form: DialogForm) => Observable; + existValues: T[] | Observable; + columnUniqueKey?: keyof T; } + +export type ChainOperationalModel = Omit diff --git a/flink-cyber/metron-parser-chain/parser-chains-config-service/frontend/parser-chains-client/src/app/chain-list-page/component/chain-dialog.component.html b/flink-cyber/metron-parser-chain/parser-chains-config-service/frontend/parser-chains-client/src/app/chain-list-page/component/chain-dialog.component.html new file mode 100644 index 000000000..d7ae03799 --- /dev/null +++ b/flink-cyber/metron-parser-chain/parser-chains-config-service/frontend/parser-chains-client/src/app/chain-list-page/component/chain-dialog.component.html @@ -0,0 +1,27 @@ +

      Create a new {{name}}

      +

      Update a {{name}}

      +
      + + + + + + {{ errorMessage }} + + + + + + + +
      + + diff --git a/flink-cyber/metron-parser-chain/parser-chains-config-service/frontend/parser-chains-client/src/app/chain-list-page/component/chain-dialog.component.scss b/flink-cyber/metron-parser-chain/parser-chains-config-service/frontend/parser-chains-client/src/app/chain-list-page/component/chain-dialog.component.scss new file mode 100644 index 000000000..e69de29bb diff --git a/flink-cyber/metron-parser-chain/parser-chains-config-service/frontend/parser-chains-client/src/app/chain-list-page/component/chain-dialog.component.ts b/flink-cyber/metron-parser-chain/parser-chains-config-service/frontend/parser-chains-client/src/app/chain-list-page/component/chain-dialog.component.ts new file mode 100644 index 000000000..4f06d7892 --- /dev/null +++ b/flink-cyber/metron-parser-chain/parser-chains-config-service/frontend/parser-chains-client/src/app/chain-list-page/component/chain-dialog.component.ts @@ -0,0 +1,94 @@ +import {MAT_DIALOG_DATA, MatDialogRef} from '@angular/material/dialog'; +import {Component, Inject} from '@angular/core'; +import {Observable} from 'rxjs'; +import {FormControl, FormGroup, Validators} from '@angular/forms'; +import {SnackbarService, SnackBarStatus} from 'src/app/services/snack-bar.service'; +import {TypedFormControls, uniqueAsyncValidator, uniqueValidator} from 'src/app/shared/utils'; +import {DialogData, DialogForm} from 'src/app/chain-list-page/chain.model'; + +@Component({ + selector: 'app-chain-dialog', + templateUrl: './chain-dialog.component.html', + styleUrls: ['./chain-dialog.component.scss'], + animations: [] +}) +export class ChainDialogComponent { + dialogForm = new FormGroup>({ + name: new FormControl(this.currentValue, + this.validator, + this.asyncValidator + ) + }); + + constructor(private _snackBarService: SnackbarService, private _dialogRef: MatDialogRef, @Inject(MAT_DIALOG_DATA) private _data: DialogData) { + } + + + get validator() { + if (this._data.existValues instanceof Observable) { + return [Validators.required, Validators.minLength(3)] + } + return [Validators.required, Validators.minLength(3), uniqueValidator(this._data.existValues, this._data.columnUniqueKey)] + } + + get asyncValidator() { + if (this._data.existValues instanceof Observable) { + return [uniqueAsyncValidator(this._data.existValues, this._data.columnUniqueKey)] + } + return [] + } + + get type() { + return this._data.type; + } + + get name() { + return this._data.name; + } + + get currentValue() { + return this._data.currentValue ? this._data.currentValue : ''; + } + + get formName() { + return this.dialogForm.controls.name; + } + + get errorMessage() { + if (this.formName.errors) { + if (this.formName.errors.required) { + return `${this.name} is required`; + } + if (this.formName.errors.minlength) { + return `${this.name} name should have minimum 3 characters`; + } + if (this.formName.errors.uniqueValue) { + return `${this.name} name is not unique`; + } + if (this.formName.errors.httpError) { + return 'Get error response from server'; + } + } + return 'Unrecognized error'; + } + + submit() { + this._data.action(this.dialogForm.getRawValue()).subscribe( + (value) => { + this._snackBarService.showMessage(`Successfully ${this.type} item`, SnackBarStatus.SUCCESS); + this._dialogRef.close({response: value, form: this.dialogForm.getRawValue()}); + }, + (error) => { + this.formName.setErrors({httpError: true}); + this._snackBarService.showMessage(error.message); + } + ) + } + + disabled() { + if (this.type === 'create') { + return this.dialogForm.invalid; + } + return this.dialogForm.invalid || !this.dialogForm.dirty; + } +} diff --git a/flink-cyber/metron-parser-chain/parser-chains-config-service/frontend/parser-chains-client/src/app/chain-page/chain-page.actions.ts b/flink-cyber/metron-parser-chain/parser-chains-config-service/frontend/parser-chains-client/src/app/chain-page/chain-page.actions.ts index d2e1568d6..2385af3c3 100644 --- a/flink-cyber/metron-parser-chain/parser-chains-config-service/frontend/parser-chains-client/src/app/chain-page/chain-page.actions.ts +++ b/flink-cyber/metron-parser-chain/parser-chains-config-service/frontend/parser-chains-client/src/app/chain-page/chain-page.actions.ts @@ -49,10 +49,11 @@ export const REMOVE_ROUTE = '[Chain Details] remove route'; export const SET_ROUTE_AS_DEFAULT = '[Chain Details] set route as default'; export const ADD_TO_PATH = '[Chain Details] add to path'; export const REMOVE_FROM_PATH = '[Chain Details] remove from path'; +export const LOAD_PIPELINE = '[Chain List] load pipeline'; export class LoadChainDetailsAction implements Action { readonly type = LOAD_CHAIN_DETAILS; - constructor(public payload: { id: string }) {} + constructor(public payload: { id: string, currentPipeline:string }) {} } export class LoadChainDetailsSuccessAction implements Action { @@ -204,6 +205,11 @@ export class SetRouteAsDefaultAction implements Action { }) {} } +export class LoadPipelineAction implements Action { + readonly type = LOAD_PIPELINE; + constructor(public payload: {currentPipeline: string}) {} +} + export type ChainDetailsAction = LoadChainDetailsAction | LoadChainDetailsSuccessAction | LoadChainDetailsFailAction @@ -226,6 +232,7 @@ export type ChainDetailsAction = LoadChainDetailsAction | RemoveRouteAction | SetRouteAsDefaultAction | AddToPathAction + | LoadPipelineAction | RemoveFromPathAction; export type IndexMappingAction = GetIndexMappingsAction diff --git a/flink-cyber/metron-parser-chain/parser-chains-config-service/frontend/parser-chains-client/src/app/chain-page/chain-page.component.spec.ts b/flink-cyber/metron-parser-chain/parser-chains-config-service/frontend/parser-chains-client/src/app/chain-page/chain-page.component.spec.ts index 15ccc0b3a..79788143e 100644 --- a/flink-cyber/metron-parser-chain/parser-chains-config-service/frontend/parser-chains-client/src/app/chain-page/chain-page.component.spec.ts +++ b/flink-cyber/metron-parser-chain/parser-chains-config-service/frontend/parser-chains-client/src/app/chain-page/chain-page.component.spec.ts @@ -13,7 +13,7 @@ import {ComponentFixture, TestBed, waitForAsync} from '@angular/core/testing'; import {ReactiveFormsModule} from '@angular/forms'; import {NoopAnimationsModule} from '@angular/platform-browser/animations'; -import {ActivatedRoute, Router} from '@angular/router'; +import {ActivatedRoute, convertToParamMap, Router} from '@angular/router'; import {EditFill, PlusOutline} from '@ant-design/icons-angular/icons'; import {Store} from '@ngrx/store'; import {NzModalModule} from 'ng-zorro-antd/modal'; @@ -22,17 +22,18 @@ import {of} from 'rxjs'; import * as fromActions from './chain-page.actions'; import {ChainPageComponent} from './chain-page.component'; -import {NzCardModule} from "ng-zorro-antd/card"; -import {provideMockStore} from "@ngrx/store/testing"; -import {NzTableModule} from "ng-zorro-antd/table"; +import {NzCardModule} from 'ng-zorro-antd/card'; +import {provideMockStore} from '@ngrx/store/testing'; +import {NzTableModule} from 'ng-zorro-antd/table'; import {NzTabsModule} from 'ng-zorro-antd/tabs'; -import {IndexingFormComponent} from "./components/indexing-form/indexing-form.component"; -import {ChainViewComponent} from "./components/chain-view/chain-view.component"; +import {IndexingFormComponent} from './components/indexing-form/indexing-form.component'; +import {ChainViewComponent} from './components/chain-view/chain-view.component'; import {MockComponent} from 'ng-mocks'; -import {LiveViewComponent} from "./components/live-view/live-view.component"; -import {NzBreadCrumbModule} from "ng-zorro-antd/breadcrumb"; -import {NzPopoverModule} from "ng-zorro-antd/popover"; -import {findEl} from "src/app/shared/test/test-helper"; +import {LiveViewComponent} from './components/live-view/live-view.component'; +import {NzBreadCrumbModule} from 'ng-zorro-antd/breadcrumb'; +import {NzPopoverModule} from 'ng-zorro-antd/popover'; +import {findEl} from 'src/app/shared/test/test-helper'; +import {OcsfFormComponent} from 'src/app/chain-page/components/ocsf-form/ocsf-form.component'; describe('ChainPageComponent', () => { let component: ChainPageComponent; @@ -56,7 +57,8 @@ describe('ChainPageComponent', () => { ChainPageComponent, MockComponent(ChainViewComponent), MockComponent(LiveViewComponent), - MockComponent(IndexingFormComponent) + MockComponent(IndexingFormComponent), + MockComponent(OcsfFormComponent) ], providers: [ provideMockStore({ @@ -80,7 +82,16 @@ describe('ChainPageComponent', () => { }, } }), - {provide: ActivatedRoute, useValue: {params: of({id: '123'})}}, + {provide: ActivatedRoute, useValue: { + snapshot: { + paramMap: convertToParamMap({ + id: '123' + }), + queryParamMap: convertToParamMap({ + pipeline: 'test-pipeline' + }) + } + }}, {provide: Router, useValue: {events: of({})}}, ] }).compileComponents(); diff --git a/flink-cyber/metron-parser-chain/parser-chains-config-service/frontend/parser-chains-client/src/app/chain-page/chain-page.component.ts b/flink-cyber/metron-parser-chain/parser-chains-config-service/frontend/parser-chains-client/src/app/chain-page/chain-page.component.ts index 723bcb758..5bb0bc9ad 100644 --- a/flink-cyber/metron-parser-chain/parser-chains-config-service/frontend/parser-chains-client/src/app/chain-page/chain-page.component.ts +++ b/flink-cyber/metron-parser-chain/parser-chains-config-service/frontend/parser-chains-client/src/app/chain-page/chain-page.component.ts @@ -49,12 +49,12 @@ export class ChainPageComponent implements OnInit, OnDestroy, DeactivatePrevente parserToBeInvestigated: string[] = []; getChainSubscription: Subscription; forceDeactivate = false; - chainIdBeingEdited: string; getChainsSubscription: Subscription; popOverVisible = false; editChainNameForm: UntypedFormGroup; failedParser$: Observable; indexingFieldMap: { [key: string]: {[key:string]: boolean} }; + currentPipeline: string; constructor( private _store: Store, @@ -81,23 +81,26 @@ export class ChainPageComponent implements OnInit, OnDestroy, DeactivatePrevente } ngOnInit() { - this._activatedRoute.params.subscribe((params) => { - this.chainId = params.id; - }); + //get initial params on component load. The parameter would only be accessed once, when the component loads. + // It won’t be updated, even on value change from within the component. + this.chainId = this._activatedRoute.snapshot.paramMap.get('id'); + this.currentPipeline = this._activatedRoute.snapshot.queryParamMap.get('pipeline'); - this.getChainsSubscription = this._store.pipe(select(getPathWithChains)).subscribe((path) => { - this.breadcrumbs = path; - }); this.getChainSubscription = this._store.pipe(select(getChain({ id: this.chainId }))).subscribe((chain: ParserChainModel) => { if (!chain) { this._store.dispatch(new fromActions.LoadChainDetailsAction({ - id: this.chainId + id: this.chainId, + currentPipeline: this.currentPipeline })); } else { this.chain = chain; } }); + this.getChainsSubscription = this._store.pipe(select(getPathWithChains)).subscribe((path) => { + this.breadcrumbs = path; + }); + this._store.pipe(select(getDirtyStatus)).subscribe((status) => { this.dirtyParsers = status.dirtyParsers; this.dirtyChains = status.dirtyChains; @@ -211,7 +214,8 @@ export class ChainPageComponent implements OnInit, OnDestroy, DeactivatePrevente nzCancelText: 'Cancel', nzOnOk: () => { this._store.dispatch(new fromActions.LoadChainDetailsAction({ - id: this.chainId + id: this.chainId, + currentPipeline: this.currentPipeline })); this._store.dispatch(new fromActions.InvestigateParserAction({ id: '' })); } diff --git a/flink-cyber/metron-parser-chain/parser-chains-config-service/frontend/parser-chains-client/src/app/chain-page/chain-page.effects.spec.ts b/flink-cyber/metron-parser-chain/parser-chains-config-service/frontend/parser-chains-client/src/app/chain-page/chain-page.effects.spec.ts index a1a81f9bf..90aea3645 100644 --- a/flink-cyber/metron-parser-chain/parser-chains-config-service/frontend/parser-chains-client/src/app/chain-page/chain-page.effects.spec.ts +++ b/flink-cyber/metron-parser-chain/parser-chains-config-service/frontend/parser-chains-client/src/app/chain-page/chain-page.effects.spec.ts @@ -73,7 +73,7 @@ const chainListPageInitialState = { error: '', pipelines: null, pipelineRenameModalVisible: false, - selectedPipeline: selectedPipeline + selectedPipeline }; describe('chain parser page: effects', () => { @@ -96,7 +96,8 @@ describe('chain parser page: effects', () => { 'chain-page': { chains: initialChains, parsers: initialParsers, - routes: initialRoutes + routes: initialRoutes, + selectedPipeline }, 'chain-list-page': chainListPageInitialState }, @@ -182,8 +183,8 @@ describe('chain parser page: effects', () => { actions = new ReplaySubject(1); actions.next(new fromActions.LoadChainDetailsAction({ - id: '123' - })); + id: '123', + currentPipeline: selectedPipeline })); effects.loadChainDetails$.subscribe(result => { expect(result).toEqual(new fromActions.LoadChainDetailsSuccessAction(normalizedChain)); diff --git a/flink-cyber/metron-parser-chain/parser-chains-config-service/frontend/parser-chains-client/src/app/chain-page/chain-page.effects.ts b/flink-cyber/metron-parser-chain/parser-chains-config-service/frontend/parser-chains-client/src/app/chain-page/chain-page.effects.ts index 067f88cab..f9d71f0fa 100644 --- a/flink-cyber/metron-parser-chain/parser-chains-config-service/frontend/parser-chains-client/src/app/chain-page/chain-page.effects.ts +++ b/flink-cyber/metron-parser-chain/parser-chains-config-service/frontend/parser-chains-client/src/app/chain-page/chain-page.effects.ts @@ -20,18 +20,17 @@ import {catchError, map, switchMap, withLatestFrom} from 'rxjs/operators'; import {ChainPageService} from '../services/chain-page.service'; import * as fromActions from './chain-page.actions'; import {ChainDetailsModel} from './chain-page.models'; -import {getChainPageState, ParserDescriptor} from './chain-page.reducers'; +import {getChainPageState, getSelectedPipeline, ParserDescriptor} from './chain-page.reducers'; import {denormalizeParserConfig, normalizeParserConfig} from './chain-page.utils'; -import {getSelectedPipeline} from "../chain-list-page/chain-list-page.reducers"; @Injectable() export class ChainPageEffects { loadChainDetails$: Observable = createEffect(() => this._actions$.pipe( ofType(fromActions.LOAD_CHAIN_DETAILS), withLatestFrom(this._store$.select(getSelectedPipeline)), - switchMap(([action, selectedPipeline]) => { + switchMap(([action]) => { const finalAction = action as fromActions.LoadChainDetailsAction; - return this._chainPageService.getChain(finalAction.payload.id, selectedPipeline).pipe( + return this._chainPageService.getChain(finalAction.payload.id, finalAction.payload.currentPipeline).pipe( map((chain: ChainDetailsModel) => { const normalizedParserConfig = normalizeParserConfig(chain); return new fromActions.LoadChainDetailsSuccessAction( diff --git a/flink-cyber/metron-parser-chain/parser-chains-config-service/frontend/parser-chains-client/src/app/chain-page/chain-page.reducers.spec.ts b/flink-cyber/metron-parser-chain/parser-chains-config-service/frontend/parser-chains-client/src/app/chain-page/chain-page.reducers.spec.ts index bcf9efb1a..cbb4391b5 100644 --- a/flink-cyber/metron-parser-chain/parser-chains-config-service/frontend/parser-chains-client/src/app/chain-page/chain-page.reducers.spec.ts +++ b/flink-cyber/metron-parser-chain/parser-chains-config-service/frontend/parser-chains-client/src/app/chain-page/chain-page.reducers.spec.ts @@ -24,7 +24,7 @@ describe('chain-page: reducers', () => { 4533: { id: '4533', name: 'test chain a', - parsers: ['123', '456'] + parsers: ['123', '456'], } }, parsers: { @@ -48,7 +48,8 @@ describe('chain-page: reducers', () => { error: '', parserToBeInvestigated: '', failedParser: '', - indexMappings: {path: '', result: {}} + indexMappings: {path: '', result: {}}, + selectedPipeline: "test-pipeline" }; expect( fromReducers.reducer(state, new fromActions.RemoveParserAction({ @@ -78,7 +79,8 @@ describe('chain-page: reducers', () => { dirtyChains: ['4533'], path: [], indexMappings: {path: '', result: {}}, - error: '' + error: '', + selectedPipeline: "test-pipeline" }); }); @@ -100,6 +102,7 @@ describe('chain-page: reducers', () => { parserToBeInvestigated: '', indexMappings: {path: '', result: {}}, failedParser: '', + selectedPipeline: "test-pipeline" }; const newState = fromReducers.reducer(state, new fromAddParserActions.AddParserAction({ chainId: '456', @@ -130,7 +133,8 @@ describe('chain-page: reducers', () => { dirtyChains: [], path: [], indexMappings: {path: '', result: {}}, - error: '' + error: '', + selectedPipeline: "test-pipeline" }; const chains = {}; const parsers = {}; @@ -164,7 +168,8 @@ describe('chain-page: reducers', () => { dirtyChains: [], path: [], indexMappings: {path: '', result: {}}, - error: '' + error: '', + selectedPipeline: "test-pipeline" }; const newState = fromReducers.reducer(state, new fromActions.UpdateParserAction({ chainId: '123', @@ -201,6 +206,7 @@ describe('chain-page: reducers', () => { parserToBeInvestigated: '', indexMappings: {path: '', result: {}}, failedParser: '', + selectedPipeline: "test-pipeline" }; const newState = fromReducers.reducer(state, new fromActions.UpdateChainAction({ chain: { @@ -229,6 +235,7 @@ describe('chain-page: reducers', () => { parserToBeInvestigated: '', indexMappings: {path: '', result: {}}, failedParser: '', + selectedPipeline: "test-pipeline", }; const newState = fromReducers.reducer(state, new fromActions.AddChainAction({ chain: { @@ -258,7 +265,8 @@ describe('chain-page: reducers', () => { 456: desiredParser }, routes: null, - error: '' + error: '', + selectedPipeline: "test-pipeline" } }; @@ -330,6 +338,7 @@ describe('chain-page: reducers', () => { parserToBeInvestigated: '1234', indexMappings: {path: '', result: {}}, failedParser: '', + selectedPipeline: "test-pipeline" }; const investigatedParserReducer = fromReducers.reducer(state, new fromActions.InvestigateParserAction({id: '1234'})); @@ -442,6 +451,7 @@ describe('chain-page: reducers', () => { parserToBeInvestigated: '', indexMappings: {path: '', result: {}}, failedParser: '', + selectedPipeline: "test-pipeline" }; const newState = fromReducers.reducer(state, new fromActions.RemoveFromPathAction({ @@ -462,6 +472,7 @@ describe('chain-page: reducers', () => { parserToBeInvestigated: '', indexMappings: {path: '', result: {}}, failedParser: '', + selectedPipeline: "test-pipeline" }; const newState = fromReducers.reducer(state, new fromActions.AddToPathAction({ @@ -505,6 +516,7 @@ describe('chain-page: reducers', () => { indexMappings: {path: '', result: {}}, parserToBeInvestigated: '', failedParser: '', + selectedPipeline: "test-pipeline" }; const newState = fromReducers.reducer(state, new fromActions.RemoveRouteAction({ @@ -545,6 +557,7 @@ describe('chain-page: reducers', () => { indexMappings: {path: '', result: {}}, parserToBeInvestigated: '', failedParser: '', + selectedPipeline: "test-pipeline" }; const newState = fromReducers.reducer(state, new fromActions.AddRouteAction({ @@ -607,6 +620,7 @@ describe('chain-page: reducers', () => { indexMappings: {path: '', result: {}}, parserToBeInvestigated: '', failedParser: '', + selectedPipeline: "test-pipeline" }; const newState = fromReducers.reducer(state, new fromActions.UpdateRouteAction({ @@ -642,6 +656,7 @@ describe('chain-page: reducers', () => { parserToBeInvestigated: '', indexMappings: {path: '', result: {}}, failedParser: '', + selectedPipeline: "test-pipeline" }; const newState = fromReducers.reducer(state, new fromActions.GetFormConfigSuccessAction({ @@ -673,6 +688,7 @@ describe('chain-page: reducers', () => { indexMappings: {path: '', result: {}}, parserToBeInvestigated: '', failedParser: '', + selectedPipeline: "test-pipeline" }; const newState = fromReducers.reducer(state, new fromActions.GetFormConfigsSuccessAction({ @@ -705,6 +721,7 @@ describe('chain-page: reducers', () => { parserToBeInvestigated: '', indexMappings: {path: '', result: {}}, failedParser: '', + selectedPipeline: "test-pipeline" }; const newState = fromReducers.reducer(state, new fromActions.SaveParserConfigAction({chainId: '123'})); expect(newState.dirtyChains).toEqual([]); @@ -748,6 +765,7 @@ describe('chain-page: reducers', () => { indexMappings: {path: '', result: {}}, parserToBeInvestigated: '', failedParser: '', + selectedPipeline: "test-pipeline" }; const newState = fromReducers.reducer(state, new fromActions.SetRouteAsDefaultAction({ diff --git a/flink-cyber/metron-parser-chain/parser-chains-config-service/frontend/parser-chains-client/src/app/chain-page/chain-page.reducers.ts b/flink-cyber/metron-parser-chain/parser-chains-config-service/frontend/parser-chains-client/src/app/chain-page/chain-page.reducers.ts index 31f6e3c3e..fcb977e04 100644 --- a/flink-cyber/metron-parser-chain/parser-chains-config-service/frontend/parser-chains-client/src/app/chain-page/chain-page.reducers.ts +++ b/flink-cyber/metron-parser-chain/parser-chains-config-service/frontend/parser-chains-client/src/app/chain-page/chain-page.reducers.ts @@ -19,7 +19,6 @@ import * as chainPageActions from './chain-page.actions'; import {ParserChainModel, ParserModel, RouteModel} from './chain-page.models'; import {denormalizeParserConfig} from './chain-page.utils'; import {CustomFormConfig} from './components/custom-form/custom-form.component'; -import * as chainListPageActions from "../chain-list-page/chain-list-page.actions"; export interface ChainPageState { chains: { [key: string]: ParserChainModel }; @@ -32,6 +31,7 @@ export interface ChainPageState { dirtyParsers: string[]; dirtyChains: string[]; path: string[]; + selectedPipeline: string; indexMappings: { path: string, result: object }; } @@ -40,6 +40,7 @@ export interface ParserDescriptor { name: string; schemaItems: CustomFormConfig[]; } + export const initialState: ChainPageState = { chains: {}, parsers: {}, @@ -51,21 +52,19 @@ export const initialState: ChainPageState = { dirtyParsers: [], dirtyChains: [], path: [], - indexMappings: { path: '', result: {} }, + selectedPipeline: '', + indexMappings: {path: '', result: {}}, }; export const uniqueAdd = (haystack: string[], needle: string): string[] => { - return [ ...haystack.filter(item => item !== needle), needle]; + return [...haystack.filter(item => item !== needle), needle]; }; export function reducer( state: ChainPageState = initialState, - action: chainPageActions.ChainDetailsAction | addParserActions.ParserAction | chainListPageActions.ChainListAction | chainPageActions.IndexMappingAction + action: chainPageActions.ChainDetailsAction | addParserActions.ParserAction | chainPageActions.IndexMappingAction ): ChainPageState { switch (action.type) { - case chainListPageActions.PIPELINE_CHANGED: { - return initialState - } case chainPageActions.LOAD_CHAIN_DETAILS_SUCCESS: { return { ...state, @@ -78,7 +77,7 @@ export function reducer( }; } case chainPageActions.REMOVE_PARSER: { - const parsers = { ...state.parsers }; + const parsers = {...state.parsers}; delete parsers[action.payload.id]; return { ...state, @@ -176,7 +175,7 @@ export function reducer( }; } case chainPageActions.GET_INDEX_MAPPINGS_SUCCESS: { - const { path, result } = action.payload; + const {path, result} = action.payload; return { ...state, indexMappings: {path, result} @@ -230,7 +229,7 @@ export function reducer( }; } case chainPageActions.REMOVE_ROUTE: { - const routes = { ...state.routes }; + const routes = {...state.routes}; delete routes[action.payload.routeId]; return { ...state, @@ -285,6 +284,12 @@ export function reducer( routes }; } + case chainPageActions.LOAD_PIPELINE: { + return { + ...state, + selectedPipeline: action.payload.currentPipeline + } + } } return state; } @@ -379,6 +384,11 @@ export const getPath = createSelector( (state) => state.path ); +export const getSelectedPipeline = createSelector( + getChainPageState, + (state) => state.selectedPipeline +); + export const getPathWithChains = createSelector( getPath, getChains, diff --git a/flink-cyber/metron-parser-chain/parser-chains-config-service/frontend/parser-chains-client/src/app/chain-page/components/live-view/live-view.actions.ts b/flink-cyber/metron-parser-chain/parser-chains-config-service/frontend/parser-chains-client/src/app/chain-page/components/live-view/live-view.actions.ts index bc8f82d5e..305a32b3c 100644 --- a/flink-cyber/metron-parser-chain/parser-chains-config-service/frontend/parser-chains-client/src/app/chain-page/components/live-view/live-view.actions.ts +++ b/flink-cyber/metron-parser-chain/parser-chains-config-service/frontend/parser-chains-client/src/app/chain-page/components/live-view/live-view.actions.ts @@ -21,7 +21,7 @@ export const liveViewInitialized = createAction( export const executionTriggered = createAction( '[LiveView] Sample Data Parsing Triggered', - props<{ sampleData: SampleDataModel, chainConfig: unknown }>() + props<{ sampleData: SampleDataModel, chainConfig: unknown, currentPipeline: string}>() ); export const liveViewRefreshedSuccessfully = createAction( diff --git a/flink-cyber/metron-parser-chain/parser-chains-config-service/frontend/parser-chains-client/src/app/chain-page/components/live-view/live-view.component.spec.ts b/flink-cyber/metron-parser-chain/parser-chains-config-service/frontend/parser-chains-client/src/app/chain-page/components/live-view/live-view.component.spec.ts index 07886c920..6bea71c67 100644 --- a/flink-cyber/metron-parser-chain/parser-chains-config-service/frontend/parser-chains-client/src/app/chain-page/components/live-view/live-view.component.spec.ts +++ b/flink-cyber/metron-parser-chain/parser-chains-config-service/frontend/parser-chains-client/src/app/chain-page/components/live-view/live-view.component.spec.ts @@ -25,13 +25,14 @@ import {executionTriggered, liveViewInitialized, onOffToggleChanged, sampleDataI import {LiveViewComponent} from './live-view.component'; import {LiveViewState} from './live-view.reducers'; import {SampleDataType} from './models/sample-data.model'; -import {SampleDataFormComponent} from "./sample-data-form/sample-data-form.component"; -import {MockComponent} from "ng-mocks"; -import {LiveViewResultComponent} from "./live-view-result/live-view-result.component"; -import {LiveViewConsts} from "./live-view.consts"; -import {NzFormModule} from "ng-zorro-antd/form"; -import {CUSTOM_ELEMENTS_SCHEMA, NO_ERRORS_SCHEMA} from "@angular/core"; -import {ChainDetailsModel} from "../../chain-page.models"; +import {SampleDataFormComponent} from './sample-data-form/sample-data-form.component'; +import {MockComponent} from 'ng-mocks'; +import {LiveViewResultComponent} from './live-view-result/live-view-result.component'; +import {LiveViewConsts} from './live-view.consts'; +import {NzFormModule} from 'ng-zorro-antd/form'; +import {CUSTOM_ELEMENTS_SCHEMA, NO_ERRORS_SCHEMA} from '@angular/core'; +import {ChainDetailsModel} from '../../chain-page.models'; +import {ActivatedRoute, convertToParamMap} from '@angular/router'; describe('LiveViewComponent', () => { @@ -69,6 +70,16 @@ describe('LiveViewComponent', () => { ], providers: [ provideMockStore({initialState}), + {provide: ActivatedRoute, useValue: { + snapshot: { + paramMap: convertToParamMap({ + id: '123' + }), + queryParamMap: convertToParamMap({ + pipeline: 'fakePipe' + }) + } + }}, ], declarations: [ LiveViewComponent, @@ -122,7 +133,7 @@ describe('LiveViewComponent', () => { (component.chainConfig$ as Subject).next({}); tick(LiveViewConsts.LIVE_VIEW_DEBOUNCE_RATE); - const action = executionTriggered({sampleData: testSampleData, chainConfig: {}}); + const action = executionTriggered({sampleData: testSampleData, chainConfig: {}, currentPipeline: 'fakePipe'}); expect(mockStore.dispatch).toHaveBeenCalledWith(action); })); @@ -130,7 +141,7 @@ describe('LiveViewComponent', () => { it('should filter out events without sample data input', fakeAsync(() => { (component.chainConfig$ as Subject).next({}); tick(LiveViewConsts.LIVE_VIEW_DEBOUNCE_RATE); - const action = executionTriggered({sampleData: testSampleData, chainConfig: {}}); + const action = executionTriggered({sampleData: testSampleData, chainConfig: {}, currentPipeline: 'fakePipe'}); expect(mockStore.dispatch).not.toHaveBeenCalledWith(action); })); @@ -146,7 +157,7 @@ describe('LiveViewComponent', () => { (component.chainConfig$ as Subject).next({}); tick(LiveViewConsts.LIVE_VIEW_DEBOUNCE_RATE / 2); - const action = executionTriggered({sampleData: testSampleData, chainConfig: {}}); + const action = executionTriggered({sampleData: testSampleData, chainConfig: {}, currentPipeline: 'fakePipe'}); expect(mockStore.dispatch).not.toHaveBeenCalledWith(action); diff --git a/flink-cyber/metron-parser-chain/parser-chains-config-service/frontend/parser-chains-client/src/app/chain-page/components/live-view/live-view.component.ts b/flink-cyber/metron-parser-chain/parser-chains-config-service/frontend/parser-chains-client/src/app/chain-page/components/live-view/live-view.component.ts index 578ccf7a0..9f07895e2 100644 --- a/flink-cyber/metron-parser-chain/parser-chains-config-service/frontend/parser-chains-client/src/app/chain-page/components/live-view/live-view.component.ts +++ b/flink-cyber/metron-parser-chain/parser-chains-config-service/frontend/parser-chains-client/src/app/chain-page/components/live-view/live-view.component.ts @@ -10,12 +10,12 @@ * limitations governing your use of the file. */ -import { AfterViewInit, Component, EventEmitter, Input, OnDestroy, OnInit, Output } from '@angular/core'; -import { select, Store } from '@ngrx/store'; -import { combineLatest, Observable, Subject } from 'rxjs'; -import { debounceTime, filter, takeUntil } from 'rxjs/operators'; +import {AfterViewInit, Component, EventEmitter, Input, OnDestroy, OnInit, Output} from '@angular/core'; +import {select, Store} from '@ngrx/store'; +import {combineLatest, Observable, Subject} from 'rxjs'; +import {debounceTime, filter, takeUntil} from 'rxjs/operators'; -import { InvestigateParserAction } from '../../chain-page.actions'; +import {InvestigateParserAction} from '../../chain-page.actions'; import { executionTriggered, @@ -23,20 +23,21 @@ import { onOffToggleChanged, sampleDataInputChanged, } from './live-view.actions'; -import { LiveViewConsts } from './live-view.consts'; -import { LiveViewState } from './live-view.reducers'; +import {LiveViewConsts} from './live-view.consts'; +import {LiveViewState} from './live-view.reducers'; import { getExecutionStatus, getIsLiveViewOn, getResults, getSampleData, } from './live-view.selectors'; -import { EntryParsingResultModel } from './models/live-view.model'; +import {EntryParsingResultModel} from './models/live-view.model'; import {SampleDataInternalModel, SampleDataModel} from './models/sample-data.model'; import { ExecutionListTriggeredAction -} from "./sample-data-form/sample-data-text-folder-input/sample-data-text-folder-input.actions"; -import {ChainDetailsModel} from "../../chain-page.models"; +} from './sample-data-form/sample-data-text-folder-input/sample-data-text-folder-input.actions'; +import {ChainDetailsModel} from '../../chain-page.models'; +import {ActivatedRoute} from '@angular/router'; @Component({ selector: 'app-live-view', @@ -55,13 +56,17 @@ export class LiveViewComponent implements OnInit, AfterViewInit, OnDestroy { sampleDataForceChange$ = new Subject(); featureToggleChange$ = new Subject(); selectedTabIndex = 0; + currentPipeline: string; private _unsubscribe$: Subject = new Subject(); - constructor(private _store: Store) { + constructor(private _store: Store, private _activatedRoute: ActivatedRoute + ) { this.isExecuting$ = this._store.pipe(select(getExecutionStatus)); this.results$ = this._store.pipe(select(getResults)); this.sampleData$ = this._store.pipe(select(getSampleData)); this.isLiveViewOn$ = this._store.pipe(select(getIsLiveViewOn)); + this.currentPipeline = this._activatedRoute.snapshot.queryParamMap.get('pipeline'); + } ngOnInit() { @@ -74,7 +79,7 @@ export class LiveViewComponent implements OnInit, AfterViewInit, OnDestroy { } onInvestigateParserAction(parserId) { - this._store.dispatch(new InvestigateParserAction({ id: parserId })); + this._store.dispatch(new InvestigateParserAction({id: parserId})); } onSelectedTabChange(index: number) { @@ -96,23 +101,23 @@ export class LiveViewComponent implements OnInit, AfterViewInit, OnDestroy { this.isLiveViewOn$, ]).pipe( debounceTime(LiveViewConsts.LIVE_VIEW_DEBOUNCE_RATE), - filter(([ sampleData, isLiveViewOn ]) => isLiveViewOn && !!sampleData.source), + filter(([sampleData, isLiveViewOn]) => isLiveViewOn && !!sampleData.source), takeUntil(this._unsubscribe$) - ).subscribe(([ sampleData, chainConfig ]) => { - this._store.dispatch(executionTriggered({ sampleData, chainConfig })); + ).subscribe(([sampleData, chainConfig]) => { + this._store.dispatch(executionTriggered({sampleData, chainConfig, currentPipeline: this.currentPipeline})); }); this.featureToggleChange$.pipe( filter(value => value !== null), ).subscribe(value => { - this._store.dispatch(onOffToggleChanged({ value })); + this._store.dispatch(onOffToggleChanged({value})); }); this.sampleDataChange$.pipe( takeUntil(this._unsubscribe$), filter(sampleData => sampleData !== null), ).subscribe(sampleData => { - this._store.dispatch(sampleDataInputChanged({ sampleData })); + this._store.dispatch(sampleDataInputChanged({sampleData})); }); combineLatest([ @@ -121,7 +126,7 @@ export class LiveViewComponent implements OnInit, AfterViewInit, OnDestroy { takeUntil(this._unsubscribe$), filter((sampleData) => sampleData !== null), ).subscribe(([sampleData, chainConfig]) => { - this._store.dispatch(ExecutionListTriggeredAction({ sampleData, chainConfig })); + this._store.dispatch(ExecutionListTriggeredAction({sampleData, chainConfig, currentPipeline: this.currentPipeline})); }); } diff --git a/flink-cyber/metron-parser-chain/parser-chains-config-service/frontend/parser-chains-client/src/app/chain-page/components/live-view/live-view.effects.spec.ts b/flink-cyber/metron-parser-chain/parser-chains-config-service/frontend/parser-chains-client/src/app/chain-page/components/live-view/live-view.effects.spec.ts index ba2127d60..4ee587b0d 100644 --- a/flink-cyber/metron-parser-chain/parser-chains-config-service/frontend/parser-chains-client/src/app/chain-page/components/live-view/live-view.effects.spec.ts +++ b/flink-cyber/metron-parser-chain/parser-chains-config-service/frontend/parser-chains-client/src/app/chain-page/components/live-view/live-view.effects.spec.ts @@ -102,7 +102,7 @@ describe('live-view.effects', () => { const testSubscriber = jasmine.createSpy('executionTriggeredSpy'); liveViewEffects.execute$.subscribe(testSubscriber); - actions$.next(executionTriggered({...testPayload})); + actions$.next(executionTriggered({...testPayload, currentPipeline: selectedPipeline})); expect(fakeLiveViewService.execute).toHaveBeenCalledWith(testPayload.sampleData, testPayload.chainConfig, selectedPipeline); }); @@ -111,7 +111,7 @@ describe('live-view.effects', () => { const testSubscriber = jasmine.createSpy('executionTriggeredSpy'); liveViewEffects.execute$.subscribe(testSubscriber); - actions$.next(executionTriggered({...testPayload})); + actions$.next(executionTriggered({...testPayload, currentPipeline: selectedPipeline})); expect(testSubscriber).toHaveBeenCalledWith({ liveViewResult: { @@ -127,7 +127,7 @@ describe('live-view.effects', () => { fakeLiveViewService.execute.and.returnValue(throwError({message: 'something went wrong'})); - actions$.next(executionTriggered({...testPayload})); + actions$.next(executionTriggered({...testPayload, currentPipeline: 'fakePipe'})); expect(testSubscriber).toHaveBeenCalledWith({ error: { @@ -142,7 +142,7 @@ describe('live-view.effects', () => { liveViewEffects.execute$.subscribe(); - actions$.next(executionTriggered({...testPayload})); + actions$.next(executionTriggered({...testPayload, currentPipeline: selectedPipeline})); expect(fakeMessageService.create).toHaveBeenCalledWith('error', 'something went wrong'); }); diff --git a/flink-cyber/metron-parser-chain/parser-chains-config-service/frontend/parser-chains-client/src/app/chain-page/components/live-view/live-view.effects.ts b/flink-cyber/metron-parser-chain/parser-chains-config-service/frontend/parser-chains-client/src/app/chain-page/components/live-view/live-view.effects.ts index a450a7076..fcc0858ad 100644 --- a/flink-cyber/metron-parser-chain/parser-chains-config-service/frontend/parser-chains-client/src/app/chain-page/components/live-view/live-view.effects.ts +++ b/flink-cyber/metron-parser-chain/parser-chains-config-service/frontend/parser-chains-client/src/app/chain-page/components/live-view/live-view.effects.ts @@ -12,10 +12,10 @@ import {Injectable} from '@angular/core'; import {Actions, createEffect, ofType} from '@ngrx/effects'; -import {Action, Store} from '@ngrx/store'; +import {Action} from '@ngrx/store'; import {NzMessageService} from 'ng-zorro-antd/message'; import {Observable, of} from 'rxjs'; -import {catchError, map, switchMap, tap, withLatestFrom} from 'rxjs/operators'; +import {catchError, map, switchMap, tap} from 'rxjs/operators'; import { executionTriggered, @@ -30,18 +30,16 @@ import { } from './live-view.actions'; import {LiveViewConsts} from './live-view.consts'; import {LiveViewService} from './services/live-view.service'; -import {ChainListPageState, getSelectedPipeline} from "../../../chain-list-page/chain-list-page.reducers"; @Injectable() export class LiveViewEffects { - execute$: Observable = createEffect( () =>this._actions$.pipe( + execute$: Observable = createEffect( () => this._actions$.pipe( ofType( executionTriggered.type, ), - withLatestFrom(this._store$.select(getSelectedPipeline)), - switchMap(([{sampleData, chainConfig}, selectedPipeline]) => { - return this._liveViewService.execute(sampleData, chainConfig, selectedPipeline).pipe( + switchMap(({sampleData, chainConfig, currentPipeline}) => { + return this._liveViewService.execute(sampleData, chainConfig, currentPipeline).pipe( map(liveViewResult => liveViewRefreshedSuccessfully({ liveViewResult })), catchError(( error: { message: string }) => { this._messageService.create('error', error.message); @@ -91,7 +89,6 @@ export class LiveViewEffects { constructor( private _actions$: Actions, - private _store$: Store, private _liveViewService: LiveViewService, private _messageService: NzMessageService, ) {} diff --git a/flink-cyber/metron-parser-chain/parser-chains-config-service/frontend/parser-chains-client/src/app/chain-page/components/live-view/live-view.reducers.spec.ts b/flink-cyber/metron-parser-chain/parser-chains-config-service/frontend/parser-chains-client/src/app/chain-page/components/live-view/live-view.reducers.spec.ts index f5a00d962..9ee0851ec 100644 --- a/flink-cyber/metron-parser-chain/parser-chains-config-service/frontend/parser-chains-client/src/app/chain-page/components/live-view/live-view.reducers.spec.ts +++ b/flink-cyber/metron-parser-chain/parser-chains-config-service/frontend/parser-chains-client/src/app/chain-page/components/live-view/live-view.reducers.spec.ts @@ -80,12 +80,12 @@ describe('live-view.reducers', () => { }); it('should update isExecuting on executionTriggered action', () => { - const newState = reducer(initialState, executionTriggered({ sampleData: testLiveViewState.sampleData, chainConfig: testConfigState })); + const newState = reducer(initialState, executionTriggered({ sampleData: testLiveViewState.sampleData, chainConfig: testConfigState, currentPipeline: 'fakePipe' })); expect(newState.isExecuting).toBe(true); }); it('should update sampleData on executionTriggered action', () => { - const newState = reducer(initialState, executionTriggered({ sampleData: testLiveViewState.sampleData, chainConfig: testConfigState })); + const newState = reducer(initialState, executionTriggered({ sampleData: testLiveViewState.sampleData, chainConfig: testConfigState, currentPipeline: 'fakePipe' })); expect(newState.isExecuting).toBe(true); }); diff --git a/flink-cyber/metron-parser-chain/parser-chains-config-service/frontend/parser-chains-client/src/app/chain-page/components/live-view/sample-data-form/sample-data-text-folder-input/sample-data-text-folder-input.actions.ts b/flink-cyber/metron-parser-chain/parser-chains-config-service/frontend/parser-chains-client/src/app/chain-page/components/live-view/sample-data-form/sample-data-text-folder-input/sample-data-text-folder-input.actions.ts index 5159f9b5d..160de7d85 100644 --- a/flink-cyber/metron-parser-chain/parser-chains-config-service/frontend/parser-chains-client/src/app/chain-page/components/live-view/sample-data-form/sample-data-text-folder-input/sample-data-text-folder-input.actions.ts +++ b/flink-cyber/metron-parser-chain/parser-chains-config-service/frontend/parser-chains-client/src/app/chain-page/components/live-view/sample-data-form/sample-data-text-folder-input/sample-data-text-folder-input.actions.ts @@ -26,7 +26,7 @@ export const SampleFolderPathRestoredAction = createAction( export const ExecutionListTriggeredAction = createAction( '[SampleFolder] Sample Data List Parsing Triggered', - props<{ sampleData: SampleDataInternalModel[], chainConfig: ChainDetailsModel }>() + props<{ sampleData: SampleDataInternalModel[], chainConfig: ChainDetailsModel, currentPipeline: string }>() ); export const ExecutionListSuccessfulAction = createAction( diff --git a/flink-cyber/metron-parser-chain/parser-chains-config-service/frontend/parser-chains-client/src/app/chain-page/components/live-view/sample-data-form/sample-data-text-folder-input/sample-data-text-folder-input.effects.ts b/flink-cyber/metron-parser-chain/parser-chains-config-service/frontend/parser-chains-client/src/app/chain-page/components/live-view/sample-data-form/sample-data-text-folder-input/sample-data-text-folder-input.effects.ts index 7ec08fed3..484b6bf79 100644 --- a/flink-cyber/metron-parser-chain/parser-chains-config-service/frontend/parser-chains-client/src/app/chain-page/components/live-view/sample-data-form/sample-data-text-folder-input/sample-data-text-folder-input.effects.ts +++ b/flink-cyber/metron-parser-chain/parser-chains-config-service/frontend/parser-chains-client/src/app/chain-page/components/live-view/sample-data-form/sample-data-text-folder-input/sample-data-text-folder-input.effects.ts @@ -51,8 +51,8 @@ export class SampleDataTextFolderInputEffects { ofType( ExecutionListTriggeredAction.type, ), - switchMap(({ sampleData, chainConfig }) => { - return this._sampleFolderService.runTests(sampleData, chainConfig).pipe( + switchMap(({ sampleData, chainConfig, currentPipeline }) => { + return this._sampleFolderService.runTests(sampleData, chainConfig, currentPipeline).pipe( map(sampleFolderResults => ExecutionListSuccessfulAction({ sampleFolderResults })), catchError(( error: { message: string }) => { this._messageService.create('error', error.message); diff --git a/flink-cyber/metron-parser-chain/parser-chains-config-service/frontend/parser-chains-client/src/app/chain-page/components/live-view/services/sample-data-text-folder-input.service.ts b/flink-cyber/metron-parser-chain/parser-chains-config-service/frontend/parser-chains-client/src/app/chain-page/components/live-view/services/sample-data-text-folder-input.service.ts index edc8223ae..1d5c9534a 100644 --- a/flink-cyber/metron-parser-chain/parser-chains-config-service/frontend/parser-chains-client/src/app/chain-page/components/live-view/services/sample-data-text-folder-input.service.ts +++ b/flink-cyber/metron-parser-chain/parser-chains-config-service/frontend/parser-chains-client/src/app/chain-page/components/live-view/services/sample-data-text-folder-input.service.ts @@ -16,10 +16,9 @@ import {from, Observable} from 'rxjs'; import {EntryParsingResultModel} from '../models/live-view.model'; import {SampleDataInternalModel, SampleDataModel, SampleDataType} from '../models/sample-data.model'; -import {LiveViewService} from "./live-view.service"; -import {concatAll, map, reduce} from "rxjs/operators"; -import {Store} from "@ngrx/store"; -import {ChainListPageState, getSelectedPipeline} from "../../../../chain-list-page/chain-list-page.reducers"; +import {LiveViewService} from './live-view.service'; +import {concatAll, map, reduce} from 'rxjs/operators'; +import {ActivatedRoute} from '@angular/router'; @Injectable({ providedIn: 'root' @@ -30,37 +29,37 @@ export class SampleDataTextFolderInputService { constructor( private _http: HttpClient, - private _store$: Store, private _liveViewService: LiveViewService, + private _activatedRoute: ActivatedRoute ) { + } - runTests(sampleDataList: SampleDataInternalModel[], chainConfig: unknown): Observable> { + runTests(sampleDataList: SampleDataInternalModel[], chainConfig: unknown, currentPipeline: string): Observable> { const resultList: Observable<{ id: number, sample: SampleDataInternalModel, results: EntryParsingResultModel[] }>[] = [] - this._store$.select(getSelectedPipeline).subscribe( - selectedPipeline => { - sampleDataList.forEach(value => { - const sample: SampleDataModel = { - source: value.source, - type: SampleDataType.MANUAL - } - const postObservable = - this._liveViewService.execute(sample, chainConfig, selectedPipeline) - .pipe(map(res => { - return { - id: value.id, - sample: value, - results: res.results - } - })) - resultList.push(postObservable) - }) + + sampleDataList.forEach(value => { + const sample: SampleDataModel = { + source: value.source, + type: SampleDataType.MANUAL } - ) + const postObservable = + this._liveViewService.execute(sample, chainConfig, currentPipeline) + .pipe(map(res => { + return { + id: value.id, + sample: value, + results: res.results + } + })) + resultList.push(postObservable) + }); + + return from(resultList).pipe(concatAll(), reduce((acc, value) => { return acc.set(value.id, [value.sample, value.results]) }, new Map())) diff --git a/flink-cyber/metron-parser-chain/parser-chains-config-service/frontend/parser-chains-client/src/app/chain-page/components/ocsf-form/ocsf-form.component.spec.ts b/flink-cyber/metron-parser-chain/parser-chains-config-service/frontend/parser-chains-client/src/app/chain-page/components/ocsf-form/ocsf-form.component.spec.ts index 30f2e2cb4..ed14c73c7 100644 --- a/flink-cyber/metron-parser-chain/parser-chains-config-service/frontend/parser-chains-client/src/app/chain-page/components/ocsf-form/ocsf-form.component.spec.ts +++ b/flink-cyber/metron-parser-chain/parser-chains-config-service/frontend/parser-chains-client/src/app/chain-page/components/ocsf-form/ocsf-form.component.spec.ts @@ -1,12 +1,14 @@ -import {ComponentFixture, TestBed} from '@angular/core/testing'; +import {ComponentFixture, TestBed, waitForAsync} from '@angular/core/testing'; import {OcsfFormComponent} from './ocsf-form.component'; import {ReactiveFormsModule} from "@angular/forms"; import {HttpClientTestingModule} from "@angular/common/http/testing"; import {NzMessageService} from "ng-zorro-antd/message"; -import {ChainPageService} from "../../../services/chain-page.service"; +import {ChainPageService} from 'src/app/services/chain-page.service'; import {provideMockStore} from "@ngrx/store/testing"; import {SampleDataType} from "../live-view/models/sample-data.model"; +import {MatCardModule} from '@angular/material/card'; +import {MatProgressSpinnerModule} from '@angular/material/progress-spinner'; describe('OcsfFormComponent', () => { let component: OcsfFormComponent; @@ -22,12 +24,14 @@ describe('OcsfFormComponent', () => { result: undefined, }; - beforeEach(async () => { - await TestBed.configureTestingModule({ + beforeEach(waitForAsync(() => { + TestBed.configureTestingModule({ declarations: [OcsfFormComponent], imports: [ HttpClientTestingModule, ReactiveFormsModule, + MatCardModule, + MatProgressSpinnerModule ], providers: [ {provide: NzMessageService, useValue: jasmine.createSpyObj('NzMessageService', ['create'])}, @@ -45,7 +49,7 @@ describe('OcsfFormComponent', () => { fixture = TestBed.createComponent(OcsfFormComponent); component = fixture.componentInstance; fixture.detectChanges(); - }); + })); it('should create', () => { expect(component).toBeTruthy(); diff --git a/flink-cyber/metron-parser-chain/parser-chains-config-service/frontend/parser-chains-client/src/app/chain-page/components/ocsf-form/ocsf-form.component.ts b/flink-cyber/metron-parser-chain/parser-chains-config-service/frontend/parser-chains-client/src/app/chain-page/components/ocsf-form/ocsf-form.component.ts index 293744283..ec0834a5a 100644 --- a/flink-cyber/metron-parser-chain/parser-chains-config-service/frontend/parser-chains-client/src/app/chain-page/components/ocsf-form/ocsf-form.component.ts +++ b/flink-cyber/metron-parser-chain/parser-chains-config-service/frontend/parser-chains-client/src/app/chain-page/components/ocsf-form/ocsf-form.component.ts @@ -100,7 +100,7 @@ export class OcsfFormComponent implements OnInit { onOcsfImport() { this._chainPageService.getIndexMappings({filePath: this.ocsfForm.value._filePath}) .pipe( - catchError(err => { + catchError(_ => { this._messageService.create('error', `Couldn't fetch indexing fields for the given path '${this.ocsfForm.value._filePath}'`) return of(null) }), diff --git a/flink-cyber/metron-parser-chain/parser-chains-config-service/frontend/parser-chains-client/src/app/cluster/cluster-list-page/cluster-list-page.component.html b/flink-cyber/metron-parser-chain/parser-chains-config-service/frontend/parser-chains-client/src/app/cluster/cluster-list-page/cluster-list-page.component.html index 232e5b35f..3dcc709c6 100644 --- a/flink-cyber/metron-parser-chain/parser-chains-config-service/frontend/parser-chains-client/src/app/cluster/cluster-list-page/cluster-list-page.component.html +++ b/flink-cyber/metron-parser-chain/parser-chains-config-service/frontend/parser-chains-client/src/app/cluster/cluster-list-page/cluster-list-page.component.html @@ -31,9 +31,9 @@ - - Branches - {{this.getBranches(element.jobs)}} + + Pipelines + {{getPipelines(element.jobs)}} @@ -44,7 +44,7 @@ diff --git a/flink-cyber/metron-parser-chain/parser-chains-config-service/frontend/parser-chains-client/src/app/cluster/cluster-list-page/cluster-list-page.component.scss b/flink-cyber/metron-parser-chain/parser-chains-config-service/frontend/parser-chains-client/src/app/cluster/cluster-list-page/cluster-list-page.component.scss index 6d3533b88..ce44d54d3 100644 --- a/flink-cyber/metron-parser-chain/parser-chains-config-service/frontend/parser-chains-client/src/app/cluster/cluster-list-page/cluster-list-page.component.scss +++ b/flink-cyber/metron-parser-chain/parser-chains-config-service/frontend/parser-chains-client/src/app/cluster/cluster-list-page/cluster-list-page.component.scss @@ -9,6 +9,8 @@ * either express or implied. Refer to the License for the specific permissions and * limitations governing your use of the file. */ +@use '@angular/material' as mat; +@import "./styles.scss"; .demo-table { width: 100%; @@ -21,23 +23,28 @@ -webkit-background-clip: padding-box; background-clip: padding-box; } +th.mat-header-cell { + color: mat.get-contrast-color-from-palette($theme-primary,500); +} +table { + width: 100%; -.mat-header-row { - background-color: #333; - color: white; - font-size: 0.875rem; - text-transform: uppercase; - letter-spacing: normal; + + .mat-header-row { + background: mat.get-color-from-palette($theme-primary); + text-transform: uppercase; + letter-spacing: normal; + } + .mat-header-cell:last-child { + border-right: none; + } } + .mat-header-cell { padding-left: 15px; color: white; } -.mat-row:hover { - background: linear-gradient(10deg, #f1f2f3, #dce2ff); -} - .demo-row-is-clicked { font-weight: bold; } diff --git a/flink-cyber/metron-parser-chain/parser-chains-config-service/frontend/parser-chains-client/src/app/cluster/cluster-list-page/cluster-list-page.component.ts b/flink-cyber/metron-parser-chain/parser-chains-config-service/frontend/parser-chains-client/src/app/cluster/cluster-list-page/cluster-list-page.component.ts index c64afaad7..7526f37a5 100644 --- a/flink-cyber/metron-parser-chain/parser-chains-config-service/frontend/parser-chains-client/src/app/cluster/cluster-list-page/cluster-list-page.component.ts +++ b/flink-cyber/metron-parser-chain/parser-chains-config-service/frontend/parser-chains-client/src/app/cluster/cluster-list-page/cluster-list-page.component.ts @@ -10,12 +10,11 @@ * limitations governing your use of the file. */ -import {Component} from '@angular/core'; -import {Router} from '@angular/router'; +import {Component, inject} from '@angular/core'; import {BehaviorSubject, Observable} from 'rxjs'; -import {ClusterModel, Job} from "./cluster-list-page.model"; -import {ClusterService} from "../../services/cluster.service"; -import {tap} from "rxjs/operators"; +import {ClusterModel, Job} from './cluster-list-page.model'; +import {ClusterService} from '../../services/cluster.service'; +import {tap} from 'rxjs/operators'; @Component({ @@ -24,19 +23,10 @@ import {tap} from "rxjs/operators"; styleUrls: ['./cluster-list-page.component.scss'] }) export class ClusterListPageComponent { - clusters$: Observable = this._clusterService.getClusters().pipe(tap(() => this.isLoading$.next(false))); - displayedColumns: string[] = ['id', 'name', 'status', 'version', 'branches']; + private readonly _clusterService = inject(ClusterService); + displayedColumns: string[] = ['id', 'name', 'status', 'version', 'pipelines']; + clusters$: Observable = this._clusterService.getClusters().pipe(tap(() => this.isLoading$.next(false))); isLoading$: BehaviorSubject = new BehaviorSubject(true); - constructor( - private _clusterService: ClusterService, - private _router: Router, - ) { - } - - getBranches = (jobs: Job[]) => [...new Set(jobs.map(job => job.jobPipeline))].join(', '); - - goToDetailCluster = (clusterId: string | number) => { - this._router.navigate(['clusters', clusterId]); - } + getPipelines = (jobs: Job[]) => [...new Set(jobs.map(job => job.jobPipeline))].join(', '); } diff --git a/flink-cyber/metron-parser-chain/parser-chains-config-service/frontend/parser-chains-client/src/app/cluster/cluster-list-page/cluster-list-page.model.ts b/flink-cyber/metron-parser-chain/parser-chains-config-service/frontend/parser-chains-client/src/app/cluster/cluster-list-page/cluster-list-page.model.ts index b580d6122..aed543372 100644 --- a/flink-cyber/metron-parser-chain/parser-chains-config-service/frontend/parser-chains-client/src/app/cluster/cluster-list-page/cluster-list-page.model.ts +++ b/flink-cyber/metron-parser-chain/parser-chains-config-service/frontend/parser-chains-client/src/app/cluster/cluster-list-page/cluster-list-page.model.ts @@ -27,7 +27,7 @@ export interface ClusterMeta { export interface RequestBody { clusterServiceId?: string, jobIdHex?: string, - pipelineDir?: string, + pipelineName?: string, branch?: string, jobConfigs?: { [key: string]: string }, } diff --git a/flink-cyber/metron-parser-chain/parser-chains-config-service/frontend/parser-chains-client/src/app/cluster/cluster-list-page/cluster-list-page.module.ts b/flink-cyber/metron-parser-chain/parser-chains-config-service/frontend/parser-chains-client/src/app/cluster/cluster-list-page/cluster-list-page.module.ts index 7b5b141c2..e6be3b312 100644 --- a/flink-cyber/metron-parser-chain/parser-chains-config-service/frontend/parser-chains-client/src/app/cluster/cluster-list-page/cluster-list-page.module.ts +++ b/flink-cyber/metron-parser-chain/parser-chains-config-service/frontend/parser-chains-client/src/app/cluster/cluster-list-page/cluster-list-page.module.ts @@ -9,22 +9,24 @@ import {MatCardModule} from "@angular/material/card"; import {CommonModule} from "@angular/common"; import {MatProgressSpinnerModule} from "@angular/material/progress-spinner"; import {ClusterPageModule} from "../cluster-page/cluster-page.module"; +import {RouterLink} from '@angular/router'; @NgModule({ declarations: [ ClusterListPageComponent, ], - imports: [ - MatTableModule, - ClusterPageModule, - StatusIconModule, - MatCheckboxModule, - FormsModule, - MatRadioModule, - MatCardModule, - CommonModule, - MatProgressSpinnerModule, - ], + imports: [ + MatTableModule, + ClusterPageModule, + StatusIconModule, + MatCheckboxModule, + FormsModule, + MatRadioModule, + MatCardModule, + CommonModule, + MatProgressSpinnerModule, + RouterLink, + ], providers: [], exports: [ClusterListPageComponent] }) diff --git a/flink-cyber/metron-parser-chain/parser-chains-config-service/frontend/parser-chains-client/src/app/cluster/cluster-page/cluster-page.component.html b/flink-cyber/metron-parser-chain/parser-chains-config-service/frontend/parser-chains-client/src/app/cluster/cluster-page/cluster-page.component.html index af9410387..c083738fe 100644 --- a/flink-cyber/metron-parser-chain/parser-chains-config-service/frontend/parser-chains-client/src/app/cluster/cluster-page/cluster-page.component.html +++ b/flink-cyber/metron-parser-chain/parser-chains-config-service/frontend/parser-chains-client/src/app/cluster/cluster-page/cluster-page.component.html @@ -11,25 +11,25 @@ --> -
      +
      - - - -