diff --git a/kouncil-backend/src/main/java/com/consdata/kouncil/clusters/converter/ClusterConfigConverter.java b/kouncil-backend/src/main/java/com/consdata/kouncil/clusters/converter/ClusterConfigConverter.java index 5593d681..a653127c 100644 --- a/kouncil-backend/src/main/java/com/consdata/kouncil/clusters/converter/ClusterConfigConverter.java +++ b/kouncil-backend/src/main/java/com/consdata/kouncil/clusters/converter/ClusterConfigConverter.java @@ -43,6 +43,7 @@ private static void setClusterConfigBrokers(ClusterConfig clusterConfig, Cluster clusterDto.getBrokers().forEach(brokerDto -> { BrokerConfig brokerConfig = new BrokerConfig(); String[] hostPort = brokerDto.getBootstrapServer().split(":"); + //todo walidacja na FE na poprawność brokerConfig.setHost(hostPort[0]); brokerConfig.setPort(Integer.parseInt(hostPort[1])); brokerConfig.setJmxUser(brokerDto.getJmxUser()); diff --git a/kouncil-backend/src/main/java/com/consdata/kouncil/datamasking/PolicyController.java b/kouncil-backend/src/main/java/com/consdata/kouncil/datamasking/PolicyController.java index 60ddd349..412e9273 100644 --- a/kouncil-backend/src/main/java/com/consdata/kouncil/datamasking/PolicyController.java +++ b/kouncil-backend/src/main/java/com/consdata/kouncil/datamasking/PolicyController.java @@ -1,10 +1,15 @@ package com.consdata.kouncil.datamasking; +import com.consdata.kouncil.datamasking.dto.PolicyDto; import com.consdata.kouncil.model.admin.SystemFunctionName.Fields; import javax.annotation.security.RolesAllowed; import lombok.AllArgsConstructor; import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @@ -16,6 +21,24 @@ public class PolicyController { private final PolicyService policyService; + @RolesAllowed({Fields.POLICY_DETAILS, Fields.POLICY_UPDATE}) + @GetMapping(path = "/{policyId}") + public PolicyDto getPolicyById(@PathVariable("policyId") Long id) { + return policyService.getPolicyById(id); + } + + @RolesAllowed(Fields.POLICY_CREATE) + @PostMapping() + public void addNewPolicy(@RequestBody PolicyDto policyDto) { + policyService.savePolicy(policyDto); + } + + @RolesAllowed(Fields.POLICY_UPDATE) + @PutMapping() + public void updatePolicy(@RequestBody PolicyDto policyDto) { + policyService.savePolicy(policyDto); + } + @RolesAllowed(Fields.POLICY_DELETE) @DeleteMapping(path = "/{id}") public void deletePolicy(@PathVariable("id") Long id) { diff --git a/kouncil-backend/src/main/java/com/consdata/kouncil/datamasking/PolicyService.java b/kouncil-backend/src/main/java/com/consdata/kouncil/datamasking/PolicyService.java index f827eef6..9d522f7e 100644 --- a/kouncil-backend/src/main/java/com/consdata/kouncil/datamasking/PolicyService.java +++ b/kouncil-backend/src/main/java/com/consdata/kouncil/datamasking/PolicyService.java @@ -1,5 +1,8 @@ package com.consdata.kouncil.datamasking; +import com.consdata.kouncil.datamasking.converter.PolicyConverter; +import com.consdata.kouncil.datamasking.converter.PolicyDtoConverter; +import com.consdata.kouncil.datamasking.dto.PolicyDto; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; @@ -9,7 +12,16 @@ public class PolicyService { private final PolicyRepository policyRepository; + public void savePolicy(PolicyDto policyDto) { + policyRepository.save(PolicyConverter.convert(policyDto)); + } + public void deletePolicy(Long id) { policyRepository.deleteById(id); } + + public PolicyDto getPolicyById(Long id) { + return policyRepository.findById(id).map(PolicyDtoConverter::convertToDto) + .orElseThrow(() -> new IllegalArgumentException(String.format("Policy with id=%s not found", id))); + } } diff --git a/kouncil-backend/src/main/java/com/consdata/kouncil/datamasking/converter/PolicyConverter.java b/kouncil-backend/src/main/java/com/consdata/kouncil/datamasking/converter/PolicyConverter.java new file mode 100644 index 00000000..fabe432e --- /dev/null +++ b/kouncil-backend/src/main/java/com/consdata/kouncil/datamasking/converter/PolicyConverter.java @@ -0,0 +1,36 @@ +package com.consdata.kouncil.datamasking.converter; + +import com.consdata.kouncil.datamasking.dto.PolicyDto; +import com.consdata.kouncil.model.datamasking.Policy; +import com.consdata.kouncil.model.datamasking.PolicyField; +import com.consdata.kouncil.model.datamasking.PolicyResource; +import java.util.HashSet; +import lombok.AccessLevel; +import lombok.NoArgsConstructor; +import org.springframework.beans.BeanUtils; + +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public final class PolicyConverter { + + public static Policy convert(PolicyDto policyDto) { + Policy policy = new Policy(); + BeanUtils.copyProperties(policyDto, policy); + + policy.setFields(new HashSet<>()); + + policyDto.getFields().forEach(field -> { + PolicyField policyField = new PolicyField(); + BeanUtils.copyProperties(field, policyField); + policy.getFields().add(policyField); + }); + + policy.setResources(new HashSet<>()); + policyDto.getResources().forEach(resource -> { + PolicyResource policyResource = new PolicyResource(); + BeanUtils.copyProperties(resource, policyResource); + policy.getResources().add(policyResource); + }); + + return policy; + } +} diff --git a/kouncil-backend/src/main/java/com/consdata/kouncil/datamasking/converter/PolicyDtoConverter.java b/kouncil-backend/src/main/java/com/consdata/kouncil/datamasking/converter/PolicyDtoConverter.java index 1b8c2b90..46cb1fc9 100644 --- a/kouncil-backend/src/main/java/com/consdata/kouncil/datamasking/converter/PolicyDtoConverter.java +++ b/kouncil-backend/src/main/java/com/consdata/kouncil/datamasking/converter/PolicyDtoConverter.java @@ -1,7 +1,10 @@ package com.consdata.kouncil.datamasking.converter; import com.consdata.kouncil.datamasking.dto.PolicyDto; +import com.consdata.kouncil.datamasking.dto.PolicyFieldDto; +import com.consdata.kouncil.datamasking.dto.PolicyResourceDto; import com.consdata.kouncil.model.datamasking.Policy; +import java.util.HashSet; import lombok.AccessLevel; import lombok.NoArgsConstructor; import org.springframework.beans.BeanUtils; @@ -12,6 +15,22 @@ public final class PolicyDtoConverter { public static PolicyDto convertToDto(Policy policy) { PolicyDto policyDto = new PolicyDto(); BeanUtils.copyProperties(policy, policyDto); + + policyDto.setFields(new HashSet<>()); + policy.getFields().forEach(field -> { + PolicyFieldDto policyFieldDto = new PolicyFieldDto(); + BeanUtils.copyProperties(field, policyFieldDto); + policyDto.getFields().add(policyFieldDto); + }); + + + policyDto.setResources(new HashSet<>()); + policy.getResources().forEach(resource -> { + PolicyResourceDto policyResource = new PolicyResourceDto(); + BeanUtils.copyProperties(resource, policyResource); + policyDto.getResources().add(policyResource); + }); + return policyDto; } } diff --git a/kouncil-backend/src/main/java/com/consdata/kouncil/datamasking/dto/PolicyDto.java b/kouncil-backend/src/main/java/com/consdata/kouncil/datamasking/dto/PolicyDto.java index 6b48ddf1..35b4e506 100644 --- a/kouncil-backend/src/main/java/com/consdata/kouncil/datamasking/dto/PolicyDto.java +++ b/kouncil-backend/src/main/java/com/consdata/kouncil/datamasking/dto/PolicyDto.java @@ -9,7 +9,8 @@ public class PolicyDto { private Long id; private String name; - private MaskingType type; - private Set fields; + private MaskingType maskingType; + private Boolean applyToAllResources; + private Set fields; private Set resources; } diff --git a/kouncil-backend/src/main/java/com/consdata/kouncil/datamasking/dto/PolicyFieldDto.java b/kouncil-backend/src/main/java/com/consdata/kouncil/datamasking/dto/PolicyFieldDto.java new file mode 100644 index 00000000..cc7d6487 --- /dev/null +++ b/kouncil-backend/src/main/java/com/consdata/kouncil/datamasking/dto/PolicyFieldDto.java @@ -0,0 +1,12 @@ +package com.consdata.kouncil.datamasking.dto; + +import com.consdata.kouncil.model.datamasking.FieldFindRule; +import lombok.Data; + +@Data +public class PolicyFieldDto { + + private Long id; + private FieldFindRule findRule; + private String field; +} diff --git a/kouncil-backend/src/main/java/com/consdata/kouncil/datamasking/dto/PolicyResourceDto.java b/kouncil-backend/src/main/java/com/consdata/kouncil/datamasking/dto/PolicyResourceDto.java index a815b892..5a3b34c0 100644 --- a/kouncil-backend/src/main/java/com/consdata/kouncil/datamasking/dto/PolicyResourceDto.java +++ b/kouncil-backend/src/main/java/com/consdata/kouncil/datamasking/dto/PolicyResourceDto.java @@ -1,12 +1,11 @@ package com.consdata.kouncil.datamasking.dto; -import com.consdata.kouncil.clusters.dto.ClusterDto; import lombok.Data; @Data public class PolicyResourceDto { private Long id; - private ClusterDto cluster; + private Long cluster; private String topic; } diff --git a/kouncil-backend/src/main/java/com/consdata/kouncil/model/datamasking/FieldFindRule.java b/kouncil-backend/src/main/java/com/consdata/kouncil/model/datamasking/FieldFindRule.java new file mode 100644 index 00000000..bbe29035 --- /dev/null +++ b/kouncil-backend/src/main/java/com/consdata/kouncil/model/datamasking/FieldFindRule.java @@ -0,0 +1,7 @@ +package com.consdata.kouncil.model.datamasking; + +public enum FieldFindRule { + + ANY_LEVEL, + EXACT_PATH +} diff --git a/kouncil-backend/src/main/java/com/consdata/kouncil/model/datamasking/Policy.java b/kouncil-backend/src/main/java/com/consdata/kouncil/model/datamasking/Policy.java index afec8b73..df3aefeb 100644 --- a/kouncil-backend/src/main/java/com/consdata/kouncil/model/datamasking/Policy.java +++ b/kouncil-backend/src/main/java/com/consdata/kouncil/model/datamasking/Policy.java @@ -2,9 +2,7 @@ import java.util.Set; import javax.persistence.CascadeType; -import javax.persistence.CollectionTable; import javax.persistence.Column; -import javax.persistence.ElementCollection; import javax.persistence.Entity; import javax.persistence.EnumType; import javax.persistence.Enumerated; @@ -36,12 +34,11 @@ public class Policy { @Column(name = "MASKING_TYPE") @Enumerated(EnumType.STRING) - private MaskingType type; + private MaskingType maskingType; - @ElementCollection - @CollectionTable(name = "POLICY_FIELDS", joinColumns = @JoinColumn(name = "POLICY_ID")) - @Column(name = "FIELD") - private Set fields; + @OneToMany(cascade = {CascadeType.PERSIST, CascadeType.MERGE}, fetch = FetchType.EAGER, orphanRemoval = true) + @JoinColumn(name = "POLICY_ID") + private Set fields; @Column(name = "APPLY_TO_ALL_RESOURCES") private Boolean applyToAllResources; diff --git a/kouncil-backend/src/main/java/com/consdata/kouncil/model/datamasking/PolicyField.java b/kouncil-backend/src/main/java/com/consdata/kouncil/model/datamasking/PolicyField.java new file mode 100644 index 00000000..67378b08 --- /dev/null +++ b/kouncil-backend/src/main/java/com/consdata/kouncil/model/datamasking/PolicyField.java @@ -0,0 +1,40 @@ +package com.consdata.kouncil.model.datamasking; + +import javax.persistence.CascadeType; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.EnumType; +import javax.persistence.Enumerated; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; +import javax.persistence.SequenceGenerator; +import javax.persistence.Table; +import lombok.Getter; +import lombok.Setter; + +@Entity +@Table(name = "POLICY_FIELD") +@Getter +@Setter +public class PolicyField { + + @Id + @Column(name = "ID") + @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "SEQ_POLICY_FIELD_GEN") + @SequenceGenerator(name = "SEQ_POLICY_FIELD_GEN", sequenceName = "SEQ_POLICY_FIELD", initialValue = 1, allocationSize = 1) + private Long id; + + @Column(name = "FIELD") + private String field; + + @Column(name = "FIND_RULE") + @Enumerated(EnumType.STRING) + private FieldFindRule findRule; + + @ManyToOne(cascade = {CascadeType.PERSIST, CascadeType.MERGE}) + @JoinColumn(name = "POLICY_ID", insertable = false, updatable = false) + private Policy policy; +} diff --git a/kouncil-backend/src/main/java/com/consdata/kouncil/model/datamasking/PolicyResource.java b/kouncil-backend/src/main/java/com/consdata/kouncil/model/datamasking/PolicyResource.java index 70ac7c57..65bac2cb 100644 --- a/kouncil-backend/src/main/java/com/consdata/kouncil/model/datamasking/PolicyResource.java +++ b/kouncil-backend/src/main/java/com/consdata/kouncil/model/datamasking/PolicyResource.java @@ -1,6 +1,5 @@ package com.consdata.kouncil.model.datamasking; -import com.consdata.kouncil.model.cluster.Cluster; import javax.persistence.CascadeType; import javax.persistence.Column; import javax.persistence.Entity; @@ -26,9 +25,8 @@ public class PolicyResource { @SequenceGenerator(name = "SEQ_POLICY_RESOURCE_GEN", sequenceName = "SEQ_POLICY_RESOURCE", initialValue = 1, allocationSize = 1) private Long id; - @ManyToOne(cascade = {CascadeType.PERSIST, CascadeType.MERGE}) - @JoinColumn(name = "CLUSTER_ID", insertable = false, updatable = false) - private Cluster cluster; + @Column(name = "CLUSTER_ID") + private Long cluster; @Column(name = "TOPIC") private String topic; diff --git a/kouncil-backend/src/main/resources/db/migration/V7__data_masking.sql b/kouncil-backend/src/main/resources/db/migration/V7__data_masking.sql index f20fefad..d00cc83a 100644 --- a/kouncil-backend/src/main/resources/db/migration/V7__data_masking.sql +++ b/kouncil-backend/src/main/resources/db/migration/V7__data_masking.sql @@ -1,15 +1,18 @@ create table policy ( - id bigint not null primary key, - name varchar(255), - masking_type varchar(255), - apply_to_all_resources boolean + id bigint not null primary key, + apply_to_all_resources boolean, + name varchar(255), + masking_type varchar(255) ); -create table policy_fields + +create table policy_field ( - policy_id bigint not null - constraint fkq4ltt72s1qqgpolry1fnmy67b references policy, - field varchar(255) + id bigint not null primary key, + field varchar(255), + find_rule varchar(255), + policy_id bigint + constraint fkppv2kuelp29jag142kqgvober references policy ); create table policy_resource @@ -24,6 +27,7 @@ create table policy_resource CREATE SEQUENCE SEQ_POLICY MINVALUE 1 START WITH 1 INCREMENT BY 1 CACHE 10; CREATE SEQUENCE SEQ_POLICY_RESOURCE MINVALUE 1 START WITH 1 INCREMENT BY 1 CACHE 10; +CREATE SEQUENCE SEQ_POLICY_FIELD MINVALUE 1 START WITH 1 INCREMENT BY 1 CACHE 10; insert into system_function(id, name, label, function_group) VALUES (nextval('SEQ_SYSTEM_FUNCTION'), 'POLICY_LIST', 'Policies list', 'DATA_MASKING'), diff --git a/kouncil-backend/src/test/java/com/consdata/kouncil/datamasking/converter/PolicyConverterTest.java b/kouncil-backend/src/test/java/com/consdata/kouncil/datamasking/converter/PolicyConverterTest.java new file mode 100644 index 00000000..c170af08 --- /dev/null +++ b/kouncil-backend/src/test/java/com/consdata/kouncil/datamasking/converter/PolicyConverterTest.java @@ -0,0 +1,55 @@ +package com.consdata.kouncil.datamasking.converter; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertAll; + +import com.consdata.kouncil.datamasking.dto.PolicyDto; +import com.consdata.kouncil.datamasking.dto.PolicyFieldDto; +import com.consdata.kouncil.datamasking.dto.PolicyResourceDto; +import com.consdata.kouncil.model.datamasking.MaskingType; +import com.consdata.kouncil.model.datamasking.Policy; +import java.util.HashSet; +import org.junit.jupiter.api.Test; + +class PolicyConverterTest { + + @Test + void should_convert_to_entity() { + //given + PolicyDto policyDto = new PolicyDto(); + policyDto.setId(1L); + policyDto.setName("test"); + policyDto.setApplyToAllResources(false); + policyDto.setMaskingType(MaskingType.ALL); + policyDto.setFields(new HashSet<>()); + policyDto.getFields().add(createField(1L)); + policyDto.getFields().add(createField(2L)); + policyDto.getFields().add(createField(3L)); + policyDto.setResources(new HashSet<>()); + policyDto.getResources().add(createResource(1L)); + policyDto.getResources().add(createResource(2L)); + //when + Policy policy = PolicyConverter.convert(policyDto); + //then + assertAll( + () -> assertThat(policy.getId()).isEqualTo(policyDto.getId()), + () -> assertThat(policy.getName()).isEqualTo(policyDto.getName()), + () -> assertThat(policy.getApplyToAllResources()).isEqualTo(policyDto.getApplyToAllResources()), + () -> assertThat(policy.getMaskingType()).isEqualTo(policyDto.getMaskingType()), + () -> assertThat(policy.getFields()).hasSize(policyDto.getFields().size()), + () -> assertThat(policy.getResources()).hasSize(policyDto.getResources().size()) + ); + } + + private PolicyResourceDto createResource(long id) { + PolicyResourceDto policyResource = new PolicyResourceDto(); + policyResource.setId(id); + return policyResource; + } + + private PolicyFieldDto createField(long id) { + PolicyFieldDto policyField = new PolicyFieldDto(); + policyField.setId(id); + return policyField; + } +} diff --git a/kouncil-backend/src/test/java/com/consdata/kouncil/datamasking/converter/PolicyDtoConverterTest.java b/kouncil-backend/src/test/java/com/consdata/kouncil/datamasking/converter/PolicyDtoConverterTest.java new file mode 100644 index 00000000..fa63f39d --- /dev/null +++ b/kouncil-backend/src/test/java/com/consdata/kouncil/datamasking/converter/PolicyDtoConverterTest.java @@ -0,0 +1,55 @@ +package com.consdata.kouncil.datamasking.converter; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertAll; + +import com.consdata.kouncil.datamasking.dto.PolicyDto; +import com.consdata.kouncil.model.datamasking.MaskingType; +import com.consdata.kouncil.model.datamasking.Policy; +import com.consdata.kouncil.model.datamasking.PolicyField; +import com.consdata.kouncil.model.datamasking.PolicyResource; +import java.util.HashSet; +import org.junit.jupiter.api.Test; + +class PolicyDtoConverterTest { + + @Test + void should_convert_to_dto() { + //given + Policy policy = new Policy(); + policy.setId(1L); + policy.setName("test"); + policy.setApplyToAllResources(false); + policy.setMaskingType(MaskingType.ALL); + policy.setFields(new HashSet<>()); + policy.getFields().add(createField(1L)); + policy.getFields().add(createField(2L)); + policy.getFields().add(createField(3L)); + policy.setResources(new HashSet<>()); + policy.getResources().add(createResource(1L)); + policy.getResources().add(createResource(2L)); + //when + PolicyDto policyDto = PolicyDtoConverter.convertToDto(policy); + //then + assertAll( + () -> assertThat(policyDto.getId()).isEqualTo(policy.getId()), + () -> assertThat(policyDto.getName()).isEqualTo(policy.getName()), + () -> assertThat(policyDto.getApplyToAllResources()).isEqualTo(policy.getApplyToAllResources()), + () -> assertThat(policyDto.getMaskingType()).isEqualTo(policy.getMaskingType()), + () -> assertThat(policyDto.getFields()).hasSize(policy.getFields().size()), + () -> assertThat(policyDto.getResources()).hasSize(policy.getResources().size()) + ); + } + + private PolicyResource createResource(long id) { + PolicyResource policyResource = new PolicyResource(); + policyResource.setId(id); + return policyResource; + } + + private PolicyField createField(long id) { + PolicyField policyField = new PolicyField(); + policyField.setId(id); + return policyField; + } +} diff --git a/kouncil-frontend/apps/kouncil/src/app/routing/routing.module.ts b/kouncil-frontend/apps/kouncil/src/app/routing/routing.module.ts index 601771b7..e2bb4859 100644 --- a/kouncil-frontend/apps/kouncil/src/app/routing/routing.module.ts +++ b/kouncil-frontend/apps/kouncil/src/app/routing/routing.module.ts @@ -33,7 +33,12 @@ import { ClustersComponent } from '@app/feat-clusters'; import {UserGroupsComponent, UserGroupsFunctionsMatrixComponent} from '@app/feat-user-groups'; -import {PoliciesComponent} from '@app/feat-data-masking'; +import { + PoliciesComponent, + PolicyFormCreateComponent, + PolicyFormEditComponent, + PolicyFormViewComponent +} from '@app/feat-data-masking'; @Injectable() export class ReloadingRouterStrategy extends RouteReuseStrategy { @@ -211,6 +216,30 @@ const routes: Routes = [ data: { roles: [SystemFunctionName.POLICY_LIST] } + }, + { + path: 'data-masking-policy', + component: PolicyFormCreateComponent, + canActivate: [AuthGuard], + data: { + roles: [SystemFunctionName.POLICY_CREATE] + } + }, + { + path: 'data-masking-policy/:id/edit', + component: PolicyFormEditComponent, + canActivate: [AuthGuard], + data: { + roles: [SystemFunctionName.POLICY_UPDATE] + } + }, + { + path: 'data-masking-policy/:id', + component: PolicyFormViewComponent, + canActivate: [AuthGuard], + data: { + roles: [SystemFunctionName.POLICY_DETAILS] + } } ] }, diff --git a/kouncil-frontend/libs/common-components/src/index.ts b/kouncil-frontend/libs/common-components/src/index.ts index fad07a7c..9842af04 100644 --- a/kouncil-frontend/libs/common-components/src/index.ts +++ b/kouncil-frontend/libs/common-components/src/index.ts @@ -13,3 +13,4 @@ export {PasswordFieldComponent} from './lib/password-field/password-field.compon export {SelectFieldComponent} from './lib/select-field/select-field.component'; export {RadioFieldComponent} from './lib/radio-field/radio-field.component'; export {NumberFieldComponent} from './lib/number-field/number-field.component'; +export {CheckboxFieldComponent} from './lib/checkbox-field/checkbox-field.component'; diff --git a/kouncil-frontend/libs/common-components/src/lib/checkbox-field/checkbox-field.component.scss b/kouncil-frontend/libs/common-components/src/lib/checkbox-field/checkbox-field.component.scss new file mode 100644 index 00000000..879cc533 --- /dev/null +++ b/kouncil-frontend/libs/common-components/src/lib/checkbox-field/checkbox-field.component.scss @@ -0,0 +1,27 @@ +@import '../../../../../apps/kouncil/src/styles/palette'; +@import '../../../../../apps/kouncil/src/styles/spaces'; + +:host { + display: block; + padding-top: $space-3; + + .label { + font-size: 14px; + font-weight: 500; + margin-bottom: $space-3; + color: $main-60; + padding-right: $space-2; + } + + .requiredField { + color: $red-50; + } + + .full-width { + width: 100%; + } + + .error { + font-size: 12px; + } +} diff --git a/kouncil-frontend/libs/common-components/src/lib/checkbox-field/checkbox-field.component.ts b/kouncil-frontend/libs/common-components/src/lib/checkbox-field/checkbox-field.component.ts new file mode 100644 index 00000000..5848d2d8 --- /dev/null +++ b/kouncil-frontend/libs/common-components/src/lib/checkbox-field/checkbox-field.component.ts @@ -0,0 +1,46 @@ +import {ChangeDetectionStrategy, Component, forwardRef, Input} from '@angular/core'; +import {FormGroup, NG_VALUE_ACCESSOR} from '@angular/forms'; + +@Component({ + selector: 'app-common-checkbox-field', + template: ` +
+ + {{ label }} + * + + + + + Field is required + + +
+ `, + changeDetection: ChangeDetectionStrategy.OnPush, + styleUrls: ['./checkbox-field.component.scss'], + providers: [ + { + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => CheckboxFieldComponent), + multi: true, + } + ] +}) +export class CheckboxFieldComponent { + + @Input() form: FormGroup; + @Input() controlName: string; + @Input() label: string; + @Input() labelPosition: 'before' | 'after'; + @Input() required: boolean = false; + @Input() readonly: boolean = false; + + isFieldInvalid(): boolean { + return this.form.get(this.controlName).touched && this.form.get(this.controlName).invalid; + } + + hasError(errorCode: string): boolean { + return this.form.get(this.controlName)?.hasError(errorCode); + } +} diff --git a/kouncil-frontend/libs/common-components/src/lib/common-components.module.ts b/kouncil-frontend/libs/common-components/src/lib/common-components.module.ts index db362788..70e1e691 100644 --- a/kouncil-frontend/libs/common-components/src/lib/common-components.module.ts +++ b/kouncil-frontend/libs/common-components/src/lib/common-components.module.ts @@ -23,6 +23,7 @@ import {MatRadioModule} from '@angular/material/radio'; import {RadioFieldComponent} from './radio-field/radio-field.component'; import {NumberFieldComponent} from './number-field/number-field.component'; import {MatButtonModule} from '@angular/material/button'; +import {CheckboxFieldComponent} from './checkbox-field/checkbox-field.component'; @NgModule({ imports: [ @@ -52,7 +53,8 @@ import {MatButtonModule} from '@angular/material/button'; PasswordFieldComponent, SelectFieldComponent, RadioFieldComponent, - NumberFieldComponent + NumberFieldComponent, + CheckboxFieldComponent ], exports: [ AutocompleteComponent, @@ -63,7 +65,8 @@ import {MatButtonModule} from '@angular/material/button'; PasswordFieldComponent, SelectFieldComponent, RadioFieldComponent, - NumberFieldComponent + NumberFieldComponent, + CheckboxFieldComponent ] }) export class CommonComponentsModule { diff --git a/kouncil-frontend/libs/common-components/src/lib/select-field/select-field.component.scss b/kouncil-frontend/libs/common-components/src/lib/select-field/select-field.component.scss index 1a560e98..63acf825 100644 --- a/kouncil-frontend/libs/common-components/src/lib/select-field/select-field.component.scss +++ b/kouncil-frontend/libs/common-components/src/lib/select-field/select-field.component.scss @@ -20,6 +20,10 @@ width: 100%; } + .error { + font-size: 12px; + } + .clear-btn { z-index: 100; height: 40px; diff --git a/kouncil-frontend/libs/feat-clusters/src/index.ts b/kouncil-frontend/libs/feat-clusters/src/index.ts index 6efea1db..636aa239 100644 --- a/kouncil-frontend/libs/feat-clusters/src/index.ts +++ b/kouncil-frontend/libs/feat-clusters/src/index.ts @@ -15,3 +15,4 @@ export { export {ClusterService} from './lib/cluster-form/cluster.service'; export {ClusterBackendService} from './lib/cluster-form/cluster.backend.service'; export {ClusterDemoService} from './lib/cluster-form/cluster.demo.service'; +export {Clusters} from './lib/cluster.model'; diff --git a/kouncil-frontend/libs/feat-data-masking/jest.config.ts b/kouncil-frontend/libs/feat-data-masking/jest.config.ts index 3009faf1..8fa0dc48 100644 --- a/kouncil-frontend/libs/feat-data-masking/jest.config.ts +++ b/kouncil-frontend/libs/feat-data-masking/jest.config.ts @@ -1,6 +1,6 @@ /* eslint-disable */ export default { - displayName: 'feat-clusters', + displayName: 'feat-data-masking', preset: '../../jest.preset.js', setupFilesAfterEnv: ['/src/test-setup.ts'], globals: { @@ -9,7 +9,7 @@ export default { stringifyContentPathRegex: '\\.(html|svg)$', }, }, - coverageDirectory: '../../coverage/libs/feat-clusters', + coverageDirectory: '../../coverage/libs/feat-data-masking', transform: { '^.+\\.(ts|mjs|js|html)$': 'jest-preset-angular', }, diff --git a/kouncil-frontend/libs/feat-data-masking/project.json b/kouncil-frontend/libs/feat-data-masking/project.json index cc6e7cde..0b28c785 100644 --- a/kouncil-frontend/libs/feat-data-masking/project.json +++ b/kouncil-frontend/libs/feat-data-masking/project.json @@ -1,15 +1,15 @@ { - "name": "feat-clusters", + "name": "feat-data-masking", "$schema": "../../node_modules/nx/schemas/project-schema.json", "projectType": "library", - "sourceRoot": "libs/feat-clusters/src", + "sourceRoot": "libs/feat-data-masking/src", "prefix": "app", "targets": { "test": { "executor": "@nrwl/jest:jest", - "outputs": ["{workspaceRoot}/coverage/libs/feat-clusters"], + "outputs": ["{workspaceRoot}/coverage/libs/feat-data-masking"], "options": { - "jestConfig": "libs/feat-clusters/jest.config.ts", + "jestConfig": "libs/feat-data-masking/jest.config.ts", "passWithNoTests": true } }, @@ -17,8 +17,8 @@ "executor": "@nrwl/linter:eslint", "options": { "lintFilePatterns": [ - "libs/feat-clusters/**/*.ts", - "libs/feat-clusters/**/*.html" + "libs/feat-data-masking/**/*.ts", + "libs/feat-data-masking/**/*.html" ] } } diff --git a/kouncil-frontend/libs/feat-data-masking/src/index.ts b/kouncil-frontend/libs/feat-data-masking/src/index.ts index d39794e4..cbaf68f3 100644 --- a/kouncil-frontend/libs/feat-data-masking/src/index.ts +++ b/kouncil-frontend/libs/feat-data-masking/src/index.ts @@ -6,3 +6,6 @@ export {PoliciesDemoService} from './lib/policies/policies.demo.service'; export {PolicyService} from './lib/policy/policy.service'; export {PolicyBackendService} from './lib/policy/policy-backend.service'; export {PolicyDemoService} from './lib/policy/policy-demo.service'; +export {PolicyFormCreateComponent} from './lib/policy/policy-create/policy-form-create.component'; +export {PolicyFormEditComponent} from './lib/policy/policy-edit/policy-form-edit.component'; +export {PolicyFormViewComponent} from './lib/policy/policy-view/policy-form-view.component'; diff --git a/kouncil-frontend/libs/feat-data-masking/src/lib/feat-data-masking.module.ts b/kouncil-frontend/libs/feat-data-masking/src/lib/feat-data-masking.module.ts index f3faa656..176c2a5c 100644 --- a/kouncil-frontend/libs/feat-data-masking/src/lib/feat-data-masking.module.ts +++ b/kouncil-frontend/libs/feat-data-masking/src/lib/feat-data-masking.module.ts @@ -6,8 +6,24 @@ import {CommonComponentsModule} from '@app/common-components'; import {MatSortModule} from '@angular/material/sort'; import {DragDropModule} from '@angular/cdk/drag-drop'; import {CommonUtilsModule} from '@app/common-utils'; -import {PoliciesComponent} from "./policies/policies.component"; -import {MatButtonModule} from "@angular/material/button"; +import {PoliciesComponent} from './policies/policies.component'; +import {MatButtonModule} from '@angular/material/button'; +import {PolicyFormCreateComponent} from './policy/policy-create/policy-form-create.component'; +import {PolicyFormEditComponent} from './policy/policy-edit/policy-form-edit.component'; +import {PolicyFormViewComponent} from './policy/policy-view/policy-form-view.component'; +import {PolicyFormComponent} from './policy/policy-form.component'; +import {MatIconModule} from '@angular/material/icon'; +import {FeatBreadcrumbModule} from '@app/feat-breadcrumb'; +import {FormsModule} from '@angular/forms'; +import { + PolicyFormActionsComponent +} from './policy/sections/policy-form-actions/policy-form-actions.component'; +import { + PolicyFormFieldsComponent +} from './policy/sections/policy-form-fields/policy-form-fields.component'; +import { + PolicyFormResourcesComponent +} from './policy/sections/policy-form-resources/policy-form-resources.component'; @NgModule({ imports: [ @@ -18,13 +34,26 @@ import {MatButtonModule} from "@angular/material/button"; MatSortModule, DragDropModule, CommonUtilsModule, - MatButtonModule + MatButtonModule, + MatIconModule, + FeatBreadcrumbModule, + FormsModule ], declarations: [ - PoliciesComponent + PoliciesComponent, + PolicyFormCreateComponent, + PolicyFormEditComponent, + PolicyFormViewComponent, + PolicyFormComponent, + PolicyFormActionsComponent, + PolicyFormFieldsComponent, + PolicyFormResourcesComponent ], exports: [ - PoliciesComponent + PoliciesComponent, + PolicyFormCreateComponent, + PolicyFormEditComponent, + PolicyFormViewComponent ] }) export class FeatDataMaskingModule { diff --git a/kouncil-frontend/libs/feat-data-masking/src/lib/policies/policies.backend.service.ts b/kouncil-frontend/libs/feat-data-masking/src/lib/policies/policies.backend.service.ts index 87f9e1e6..2e2be147 100644 --- a/kouncil-frontend/libs/feat-data-masking/src/lib/policies/policies.backend.service.ts +++ b/kouncil-frontend/libs/feat-data-masking/src/lib/policies/policies.backend.service.ts @@ -1,8 +1,8 @@ import {Injectable} from '@angular/core'; import {PoliciesService} from './policies.service'; -import {Observable} from "rxjs"; -import {Policy} from "../policy.model"; -import {HttpClient} from "@angular/common/http"; +import {Observable} from 'rxjs'; +import {Policy} from '../policy.model'; +import {HttpClient} from '@angular/common/http'; @Injectable({ providedIn: 'root' @@ -13,7 +13,7 @@ export class PoliciesBackendService implements PoliciesService { } getPolicies$(): Observable> { - return this.http.get>('/api/policies') + return this.http.get>('/api/policies'); } } diff --git a/kouncil-frontend/libs/feat-data-masking/src/lib/policies/policies.component.ts b/kouncil-frontend/libs/feat-data-masking/src/lib/policies/policies.component.ts index 1c2f01e8..962f4e25 100644 --- a/kouncil-frontend/libs/feat-data-masking/src/lib/policies/policies.component.ts +++ b/kouncil-frontend/libs/feat-data-masking/src/lib/policies/policies.component.ts @@ -10,17 +10,22 @@ import { SnackBarComponent, SnackBarData } from '@app/common-utils'; -import {MaskingType, Policy} from "../policy.model"; -import {ConfirmService} from "@app/feat-confirm"; -import {MatSnackBar} from "@angular/material/snack-bar"; -import {PolicyService} from "../policy/policy.service"; +import {MaskingType, Policy, PolicyField} from '../policy.model'; +import {ConfirmService} from '@app/feat-confirm'; +import {MatSnackBar} from '@angular/material/snack-bar'; +import {PolicyService} from '../policy/policy.service'; +import {Router} from '@angular/router'; @Component({ selector: 'app-data-masking-policies', template: ` -
+
+
@@ -35,7 +40,8 @@ import {PolicyService} from "../policy/policy.service"; [actionColumns]="actionColumns" matSort [sort]="sort" cdkDropList cdkDropListOrientation="horizontal" - (cdkDropListDropped)="drop($event)"> + (cdkDropListDropped)="drop($event)" + (rowClickedAction)="navigateToDetails($event)"> ) => value.join(", ") + valueFormatter: (value: Array): string => value.map(field => field.field).join(', ') }, ]; @@ -122,6 +128,7 @@ export class PoliciesComponent extends AbstractTableComponent implements OnInit, private confirmService: ConfirmService, private snackbar: MatSnackBar, private policyService: PolicyService, + private router: Router, protected authService: AuthService) { super(); } @@ -139,11 +146,10 @@ export class PoliciesComponent extends AbstractTableComponent implements OnInit, })); } - private loadPolicies(): void { this.subscription.add(this.dataMaskingPoliciesService.getPolicies$() .pipe(first()) - .subscribe((data: any) => { + .subscribe((data: Array) => { this.policies = data; this.filter(this.searchService.currentPhrase); this.progressBarService.setProgress(false); @@ -194,4 +200,12 @@ export class PoliciesComponent extends AbstractTableComponent implements OnInit, } })); } + + createPolicy(): void { + this.router.navigate([`/data-masking-policy`]); + } + + navigateToDetails(policy: Policy): void { + this.router.navigate([`data-masking-policy/${policy.id}`]); + } } diff --git a/kouncil-frontend/libs/feat-data-masking/src/lib/policies/policies.demo.service.ts b/kouncil-frontend/libs/feat-data-masking/src/lib/policies/policies.demo.service.ts index 6f8ff3cd..e0858c60 100644 --- a/kouncil-frontend/libs/feat-data-masking/src/lib/policies/policies.demo.service.ts +++ b/kouncil-frontend/libs/feat-data-masking/src/lib/policies/policies.demo.service.ts @@ -1,7 +1,7 @@ import {Injectable} from '@angular/core'; import {Observable, of} from 'rxjs'; import {PoliciesService} from './policies.service'; -import {Policy} from "../policy.model"; +import {Policy} from '../policy.model'; @Injectable({ providedIn: 'root' diff --git a/kouncil-frontend/libs/feat-data-masking/src/lib/policies/policies.service.ts b/kouncil-frontend/libs/feat-data-masking/src/lib/policies/policies.service.ts index ece8a8b2..7be301fc 100644 --- a/kouncil-frontend/libs/feat-data-masking/src/lib/policies/policies.service.ts +++ b/kouncil-frontend/libs/feat-data-masking/src/lib/policies/policies.service.ts @@ -1,6 +1,6 @@ import {Injectable} from '@angular/core'; import {Observable} from 'rxjs'; -import {Policy} from "../policy.model"; +import {Policy} from '../policy.model'; @Injectable() export abstract class PoliciesService { diff --git a/kouncil-frontend/libs/feat-data-masking/src/lib/policy.model.ts b/kouncil-frontend/libs/feat-data-masking/src/lib/policy.model.ts index 028d21c8..aeaaf314 100644 --- a/kouncil-frontend/libs/feat-data-masking/src/lib/policy.model.ts +++ b/kouncil-frontend/libs/feat-data-masking/src/lib/policy.model.ts @@ -1,11 +1,30 @@ export class Policy { - constructor(public name: string, public type: MaskingType, public fields: Array) { + constructor(public id: number, public name: string, public maskingType: MaskingType, + public applyToAllResources: boolean, public fields: Array, + public resources: Array) { } } +export class PolicyResource { + constructor(public id: number, public cluster: number, public topic: string) { + } +} + +export class PolicyField { + + constructor(public id: number, public findRule: FindRule, public field: string) { + } +} + + export enum MaskingType { ALL = 'Hide all', FIRST_5 = 'Hide first 5 signs', LAST_5 = 'Hide last 5 signs' } + +export enum FindRule { + ANY_LEVEL = 'Find at any level', + EXACT_PATH = 'Find field at this level' +} diff --git a/kouncil-frontend/libs/feat-data-masking/src/lib/policy/policy-backend.service.ts b/kouncil-frontend/libs/feat-data-masking/src/lib/policy/policy-backend.service.ts index 2f753904..be2840cb 100644 --- a/kouncil-frontend/libs/feat-data-masking/src/lib/policy/policy-backend.service.ts +++ b/kouncil-frontend/libs/feat-data-masking/src/lib/policy/policy-backend.service.ts @@ -1,7 +1,8 @@ import {Injectable} from '@angular/core'; import {PolicyService} from './policy.service'; -import {Observable} from "rxjs"; -import {HttpClient} from "@angular/common/http"; +import {Observable} from 'rxjs'; +import {HttpClient} from '@angular/common/http'; +import {Policy} from '../policy.model'; @Injectable({ providedIn: 'root' @@ -11,8 +12,19 @@ export class PolicyBackendService implements PolicyService { constructor(private http: HttpClient) { } + addNewPolicy$(model: Policy): Observable { + return this.http.post('/api/policy', model); + } + + updatePolicy$(model: Policy): Observable { + return this.http.put('/api/policy', model); + } + + getPolicyById$(policyId: number): Observable { + return this.http.get(`/api/policy/${policyId}`); + } + deletePolicy$(id: number): Observable { return this.http.delete(`/api/policy/${id}`); } - } diff --git a/kouncil-frontend/libs/feat-data-masking/src/lib/policy/policy-create/policy-form-create.component.scss b/kouncil-frontend/libs/feat-data-masking/src/lib/policy/policy-create/policy-form-create.component.scss new file mode 100644 index 00000000..e69de29b diff --git a/kouncil-frontend/libs/feat-data-masking/src/lib/policy/policy-create/policy-form-create.component.ts b/kouncil-frontend/libs/feat-data-masking/src/lib/policy/policy-create/policy-form-create.component.ts new file mode 100644 index 00000000..fde3e8b1 --- /dev/null +++ b/kouncil-frontend/libs/feat-data-masking/src/lib/policy/policy-create/policy-form-create.component.ts @@ -0,0 +1,14 @@ +import {Component} from '@angular/core'; +import {ViewMode} from '@app/common-utils'; + +@Component({ + selector: 'app-policy-create', + template: ` + + `, + styleUrls: ['./policy-form-create.component.scss'] +}) +export class PolicyFormCreateComponent { + ViewMode: typeof ViewMode = ViewMode; + +} diff --git a/kouncil-frontend/libs/feat-data-masking/src/lib/policy/policy-demo.service.ts b/kouncil-frontend/libs/feat-data-masking/src/lib/policy/policy-demo.service.ts index cace1ab7..9f49f3db 100644 --- a/kouncil-frontend/libs/feat-data-masking/src/lib/policy/policy-demo.service.ts +++ b/kouncil-frontend/libs/feat-data-masking/src/lib/policy/policy-demo.service.ts @@ -1,13 +1,26 @@ import {Injectable} from '@angular/core'; import {Observable, of} from 'rxjs'; import {PolicyService} from './policy.service'; +import {Policy} from '../policy.model'; @Injectable({ providedIn: 'root' }) export class PolicyDemoService implements PolicyService { - deletePolicy$(id: number): Observable { + deletePolicy$(_id: number): Observable { + return of(); + } + + addNewPolicy$(_model: Policy): Observable { + return of(); + } + + updatePolicy$(_model: Policy): Observable { + return of(); + } + + getPolicyById$(_policyId: number): Observable { return of(); } diff --git a/kouncil-frontend/libs/feat-data-masking/src/lib/policy/policy-edit/policy-form-edit.component.scss b/kouncil-frontend/libs/feat-data-masking/src/lib/policy/policy-edit/policy-form-edit.component.scss new file mode 100644 index 00000000..e69de29b diff --git a/kouncil-frontend/libs/feat-data-masking/src/lib/policy/policy-edit/policy-form-edit.component.ts b/kouncil-frontend/libs/feat-data-masking/src/lib/policy/policy-edit/policy-form-edit.component.ts new file mode 100644 index 00000000..d25e8bc7 --- /dev/null +++ b/kouncil-frontend/libs/feat-data-masking/src/lib/policy/policy-edit/policy-form-edit.component.ts @@ -0,0 +1,13 @@ +import {Component} from '@angular/core'; +import {ViewMode} from '@app/common-utils'; + +@Component({ + selector: 'app-policy-edit', + template: ` + + `, + styleUrls: ['./policy-form-edit.component.scss'] +}) +export class PolicyFormEditComponent { + ViewMode: typeof ViewMode = ViewMode; +} diff --git a/kouncil-frontend/libs/feat-data-masking/src/lib/policy/policy-form.component.scss b/kouncil-frontend/libs/feat-data-masking/src/lib/policy/policy-form.component.scss new file mode 100644 index 00000000..654a48e2 --- /dev/null +++ b/kouncil-frontend/libs/feat-data-masking/src/lib/policy/policy-form.component.scss @@ -0,0 +1,66 @@ +@use '../../../../../apps/kouncil/src/styles/buttons'; +@import "../../../../../apps/kouncil/src/styles/spaces"; +@import "../../../../../apps/kouncil/src/styles/palette"; + +.policy-form { + margin: $space-5; + + .policy-form-header { + display: flex; + flex-direction: row; + align-items: center; + margin-bottom: $space-5; + line-height: 28px; + } + + .policy-form-container{ + background: $main-0; + padding: $space-5; + } + + + + .fields-section, .resources-section { + margin-top: $space-5; + margin-bottom: $space-3; + min-height: 50px; + + .fields-section-header, .resources-section-header { + float: left; + font-size: 16px !important; + padding-bottom: $space-3; + padding-top: $space-3; + font-weight: 500; + } + + .add-field-btn, .add-resource-btn { + float: right; + + .add-button-icon { + color: $main-0; + } + } + + .field-section, .resource-fields { + display: inline-flex; + width: 100%; + gap: $space-5; + align-items: end; + } + } + + .full-width { + width: 100%; + } + + .action-button-blue { + @include buttons.button-blue; + } + + .action-button-white { + @include buttons.button-white; + height: 40px !important; + } +} + + diff --git a/kouncil-frontend/libs/feat-data-masking/src/lib/policy/policy-form.component.ts b/kouncil-frontend/libs/feat-data-masking/src/lib/policy/policy-form.component.ts new file mode 100644 index 00000000..d7221c32 --- /dev/null +++ b/kouncil-frontend/libs/feat-data-masking/src/lib/policy/policy-form.component.ts @@ -0,0 +1,192 @@ +import {Component, Input, OnDestroy, OnInit, ViewChild} from '@angular/core'; +import {ActivatedRoute, Router} from '@angular/router'; +import {Subscription} from 'rxjs'; +import {ViewMode} from '@app/common-utils'; +import {MaskingType, Policy, PolicyField, PolicyResource} from '../policy.model'; +import { + FormArray, + FormControl, + FormGroup, + ValidationErrors, + ValidatorFn, + Validators +} from '@angular/forms'; +import {SelectableItem} from '@app/common-components'; +import {PolicyService} from './policy.service'; +import { + PolicyFormFieldsComponent +} from './sections/policy-form-fields/policy-form-fields.component'; +import { + PolicyFormResourcesComponent +} from './sections/policy-form-resources/policy-form-resources.component'; +import {first} from 'rxjs/operators'; +import {Clusters, ClustersService} from '@app/feat-clusters'; + +@Component({ + selector: 'app-policy', + template: ` +
+
+ +
+ +
+ + + + + + + +
+ + +
+ `, + styleUrls: ['./policy-form.component.scss'] +}) +export class PolicyFormComponent implements OnInit, OnDestroy { + + @Input() viewMode: ViewMode; + + model: Policy = {} as Policy; + subscriptions: Subscription = new Subscription(); + ViewMode: typeof ViewMode = ViewMode; + policyForm: FormGroup = new FormGroup({ + id: new FormControl(), + name: new FormControl('', [Validators.required]), + maskingType: new FormControl('', [Validators.required]), + applyToAllResources: new FormControl(false), + fields: new FormArray([], { + validators: this.validateFields(), + updateOn: 'change' + }), + resources: new FormArray([], { + validators: this.validateResources(), + updateOn: 'change' + }) + }); + + maskingTypeOptions: Array = Object.keys(MaskingType) + .map(maskingType => new SelectableItem(MaskingType[maskingType], maskingType, false)); + clusters: Array = []; + + + @ViewChild(PolicyFormFieldsComponent) fields: PolicyFormFieldsComponent; + @ViewChild(PolicyFormResourcesComponent) resources: PolicyFormResourcesComponent; + + + constructor(private route: ActivatedRoute, + private router: Router, + private policyService: PolicyService, + private clustersService: ClustersService) { + } + + ngOnInit(): void { + + this.subscriptions.add(this.clustersService.getClusters$() + .pipe(first()) + .subscribe((data: Clusters) => { + data.clusters.forEach(cluster => { + this.clusters.push(new SelectableItem(cluster.name, cluster.id, false)); + }); + + this.processRouteParams(); + })); + } + + ngOnDestroy(): void { + this.subscriptions.unsubscribe(); + } + + getHeaderMessage(): string { + switch (this.viewMode) { + case ViewMode.CREATE: + return `Create a new policy`; + case ViewMode.EDIT: + return `Edit ${this.model.name} policy`; + case ViewMode.VIEW: + return `${this.model.name}`; + } + return ''; + } + + private processRouteParams() { + this.subscriptions.add(this.route.params.subscribe((params) => { + const policyId = params['id']; + if (policyId) { + this.getPolicyById(policyId); + } + })); + } + + private getPolicyById(policyId: number) { + this.subscriptions.add(this.policyService.getPolicyById$(policyId).subscribe(policy => { + this.model = policy; + this.model.fields.forEach(() => this.fields.addField()); + this.model.resources.forEach(() => this.resources.addResource()); + this.policyForm.patchValue(this.model); + this.defineDisabled(this.policyForm); + })); + } + + private validateResources(): ValidatorFn { + return (): ValidationErrors | null => { + const applyToAllResources = this.policyForm?.get('applyToAllResources')?.value; + const resources: Array = this.policyForm?.get('resources')?.value; + return !applyToAllResources && resources && (resources.length === 0 || resources.some(resource => !resource.cluster || !resource.topic)) + ? {required: true} + : null; + }; + } + + private validateFields(): ValidatorFn { + return (): ValidationErrors | null => { + const fields: Array = this.policyForm?.get('fields')?.value; + return fields && (fields.length === 0 || fields.some(field => !field.field || !field.findRule)) ? {required: true} : null; + }; + } + + savePolicy(): void { + if (this.policyForm.valid) { + this.model = Object.assign({}, this.policyForm.getRawValue()); + if (this.model.id) { + this.subscriptions.add(this.policyService.updatePolicy$(this.model).subscribe(() => { + this.navigateToList(); + })); + } else { + this.subscriptions.add(this.policyService.addNewPolicy$(this.model).subscribe(() => { + this.navigateToList(); + })); + } + } + } + + private navigateToList(): void { + this.router.navigate(['/data-masking-policies']); + } + + private defineDisabled(form: FormGroup | FormArray) { + if (ViewMode.VIEW === this.viewMode) { + Object.keys(form.controls).forEach(key => { + if (form.get(key) instanceof FormGroup || form.get(key) instanceof FormArray) { + this.defineDisabled((form.get(key) as FormArray | FormGroup)); + } + form.get(key).disable(); + }); + } + } +} diff --git a/kouncil-frontend/libs/feat-data-masking/src/lib/policy/policy-view/policy-form-view.component.scss b/kouncil-frontend/libs/feat-data-masking/src/lib/policy/policy-view/policy-form-view.component.scss new file mode 100644 index 00000000..e69de29b diff --git a/kouncil-frontend/libs/feat-data-masking/src/lib/policy/policy-view/policy-form-view.component.ts b/kouncil-frontend/libs/feat-data-masking/src/lib/policy/policy-view/policy-form-view.component.ts new file mode 100644 index 00000000..8020af3c --- /dev/null +++ b/kouncil-frontend/libs/feat-data-masking/src/lib/policy/policy-view/policy-form-view.component.ts @@ -0,0 +1,14 @@ +import {Component} from '@angular/core'; +import {ViewMode} from '@app/common-utils'; + +@Component({ + selector: 'app-policy-view', + template: ` + + `, + styleUrls: ['./policy-form-view.component.scss'] +}) +export class PolicyFormViewComponent { + ViewMode: typeof ViewMode = ViewMode; + +} diff --git a/kouncil-frontend/libs/feat-data-masking/src/lib/policy/policy.service.ts b/kouncil-frontend/libs/feat-data-masking/src/lib/policy/policy.service.ts index 897517fc..3c35fb33 100644 --- a/kouncil-frontend/libs/feat-data-masking/src/lib/policy/policy.service.ts +++ b/kouncil-frontend/libs/feat-data-masking/src/lib/policy/policy.service.ts @@ -1,8 +1,15 @@ import {Injectable} from '@angular/core'; import {Observable} from 'rxjs'; +import {Policy} from '../policy.model'; @Injectable() export abstract class PolicyService { abstract deletePolicy$(id: number): Observable; + + abstract addNewPolicy$(model: Policy): Observable; + + abstract updatePolicy$(model: Policy): Observable; + + abstract getPolicyById$(policyId: number): Observable; } diff --git a/kouncil-frontend/libs/feat-data-masking/src/lib/policy/sections/policy-form-actions/policy-form-actions.component.scss b/kouncil-frontend/libs/feat-data-masking/src/lib/policy/sections/policy-form-actions/policy-form-actions.component.scss new file mode 100644 index 00000000..37c70efe --- /dev/null +++ b/kouncil-frontend/libs/feat-data-masking/src/lib/policy/sections/policy-form-actions/policy-form-actions.component.scss @@ -0,0 +1,25 @@ +@use '../../../../../../../apps/kouncil/src/styles/buttons'; +@import "../../../../../../../apps/kouncil/src/styles/spaces"; +@import "../../../../../../../apps/kouncil/src/styles/palette"; + +.actions { + float: right; + margin-top: $space-5; + + .action-button-white { + margin-right: $space-5; + } + + .action-button-white:disabled, .action-button-blue:disabled { + @include buttons.button-disabled; + } +} + +.action-button-blue { + @include buttons.button-blue; +} + +.action-button-white { + @include buttons.button-white; + height: 40px !important; +} diff --git a/kouncil-frontend/libs/feat-data-masking/src/lib/policy/sections/policy-form-actions/policy-form-actions.component.ts b/kouncil-frontend/libs/feat-data-masking/src/lib/policy/sections/policy-form-actions/policy-form-actions.component.ts new file mode 100644 index 00000000..badb9605 --- /dev/null +++ b/kouncil-frontend/libs/feat-data-masking/src/lib/policy/sections/policy-form-actions/policy-form-actions.component.ts @@ -0,0 +1,51 @@ +import {Component, Input, OnDestroy} from '@angular/core'; +import {Subscription} from 'rxjs'; +import {ViewMode} from '@app/common-utils'; +import {FormGroup} from '@angular/forms'; + +@Component({ + selector: 'app-policy-actions', + template: ` +
+ + + + + +
+ `, + styleUrls: ['./policy-form-actions.component.scss'] +}) +export class PolicyFormActionsComponent implements OnDestroy { + + @Input() viewMode: ViewMode; + @Input() policyForm: FormGroup; + @Input() policyId: number; + ViewMode: typeof ViewMode = ViewMode; + subscriptions: Subscription = new Subscription(); + + constructor() { + } + + ngOnDestroy(): void { + this.subscriptions.unsubscribe(); + } + + isVisible(viewModes: ViewMode[]): boolean { + return viewModes.includes(this.viewMode); + } +} diff --git a/kouncil-frontend/libs/feat-data-masking/src/lib/policy/sections/policy-form-fields/policy-form-fields.component.scss b/kouncil-frontend/libs/feat-data-masking/src/lib/policy/sections/policy-form-fields/policy-form-fields.component.scss new file mode 100644 index 00000000..da867147 --- /dev/null +++ b/kouncil-frontend/libs/feat-data-masking/src/lib/policy/sections/policy-form-fields/policy-form-fields.component.scss @@ -0,0 +1,47 @@ +@use '../../../../../../../apps/kouncil/src/styles/buttons'; +@import "../../../../../../../apps/kouncil/src/styles/spaces"; +@import "../../../../../../../apps/kouncil/src/styles/palette"; + +.fields-section { + margin-top: $space-5; + margin-bottom: $space-3; + min-height: 50px; + + .fields-section-header { + float: left; + font-size: 16px !important; + padding-bottom: $space-3; + padding-top: $space-3; + font-weight: 500; + } + + .add-field-btn { + float: right; + + .add-button-icon { + color: $main-0; + } + } + + .field-section { + display: inline-flex; + width: 100%; + gap: $space-5; + align-items: end; + } +} + +.full-width { + width: 100%; +} + +.action-button-blue { + @include buttons.button-blue; +} + +.action-button-white { + @include buttons.button-white; + height: 40px !important; +} + + diff --git a/kouncil-frontend/libs/feat-data-masking/src/lib/policy/sections/policy-form-fields/policy-form-fields.component.ts b/kouncil-frontend/libs/feat-data-masking/src/lib/policy/sections/policy-form-fields/policy-form-fields.component.ts new file mode 100644 index 00000000..8b647166 --- /dev/null +++ b/kouncil-frontend/libs/feat-data-masking/src/lib/policy/sections/policy-form-fields/policy-form-fields.component.ts @@ -0,0 +1,81 @@ +import {AfterViewInit, Component, Input} from '@angular/core'; +import {ViewMode} from '@app/common-utils'; +import {FindRule} from '../../../policy.model'; +import {FormArray, FormControl, FormGroup} from '@angular/forms'; +import {SelectableItem} from '@app/common-components'; + +@Component({ + selector: 'app-policy-fields', + template: ` +
+
+
Fields
+
+ +
+
+ + +
+ + + + + + + +
+
+ +
+ `, + styleUrls: ['./policy-form-fields.component.scss'] +}) +export class PolicyFormFieldsComponent implements AfterViewInit{ + + @Input() viewMode: ViewMode; + @Input() policyForm: FormGroup; + ViewMode: typeof ViewMode = ViewMode; + + findRuleOptions: Array = Object.keys(FindRule) + .map(findRule => new SelectableItem(FindRule[findRule], findRule, false)); + + constructor() { + } + + ngAfterViewInit(): void { + if (this.viewMode === ViewMode.CREATE) { + this.addField(); + } + } + + get fields(): FormArray { + return this.policyForm.get('fields') as FormArray; + } + + addField(): void { + this.fields.push(new FormGroup({ + field: new FormControl(), + findRule: new FormControl() + })); + } + + removeField(index: number): void { + this.fields.removeAt(index); + } +} diff --git a/kouncil-frontend/libs/feat-data-masking/src/lib/policy/sections/policy-form-resources/policy-form-resources.component.scss b/kouncil-frontend/libs/feat-data-masking/src/lib/policy/sections/policy-form-resources/policy-form-resources.component.scss new file mode 100644 index 00000000..4e4aed5c --- /dev/null +++ b/kouncil-frontend/libs/feat-data-masking/src/lib/policy/sections/policy-form-resources/policy-form-resources.component.scss @@ -0,0 +1,48 @@ +@use '../../../../../../../apps/kouncil/src/styles/buttons'; +@import "../../../../../../../apps/kouncil/src/styles/spaces"; +@import "../../../../../../../apps/kouncil/src/styles/palette"; + + +.resources-section { + margin-top: $space-5; + margin-bottom: $space-3; + min-height: 50px; + + .resources-section-header { + float: left; + font-size: 16px !important; + padding-bottom: $space-3; + padding-top: $space-3; + font-weight: 500; + } + + .add-resource-btn { + float: right; + + .add-button-icon { + color: $main-0; + } + } + + .resource-fields { + display: inline-flex; + width: 100%; + gap: $space-5; + align-items: end; + } +} + +.full-width { + width: 100%; +} + +.action-button-blue { + @include buttons.button-blue; +} + +.action-button-white { + @include buttons.button-white; + height: 40px !important; +} + + diff --git a/kouncil-frontend/libs/feat-data-masking/src/lib/policy/sections/policy-form-resources/policy-form-resources.component.ts b/kouncil-frontend/libs/feat-data-masking/src/lib/policy/sections/policy-form-resources/policy-form-resources.component.ts new file mode 100644 index 00000000..750ea353 --- /dev/null +++ b/kouncil-frontend/libs/feat-data-masking/src/lib/policy/sections/policy-form-resources/policy-form-resources.component.ts @@ -0,0 +1,106 @@ +import {AfterViewInit, Component, Input, OnDestroy, OnInit} from '@angular/core'; +import {ViewMode} from '@app/common-utils'; +import {FormArray, FormControl, FormGroup} from '@angular/forms'; +import {SelectableItem} from '@app/common-components'; +import {Subscription} from 'rxjs'; + +@Component({ + selector: 'app-policy-resources', + template: ` +
+
+
Resources
+
+ +
+
+ +
+ +
+ +
+ +
+ + + + + +
+
+
+
+ `, + styleUrls: ['./policy-form-resources.component.scss'] +}) +export class PolicyFormResourcesComponent implements OnInit, OnDestroy, AfterViewInit { + + @Input() viewMode: ViewMode; + @Input() policyForm: FormGroup; + @Input() clusters: Array; + ViewMode: typeof ViewMode = ViewMode; + subscriptions: Subscription = new Subscription(); + + constructor() { + } + + ngOnInit(): void { + if (this.viewMode === ViewMode.CREATE) { + this.subscriptions.add(this.policyForm.get('applyToAllResources').valueChanges.subscribe( + value => { + if (value) { + (this.policyForm.get('resources') as FormArray).clear(); + (this.policyForm.get('resources') as FormArray).setErrors(null); + } else { + this.addResource(); + } + } + )); + } + } + + ngAfterViewInit(): void { + if (this.viewMode === ViewMode.CREATE) { + this.addResource(); + } + } + + ngOnDestroy(): void { + this.subscriptions.unsubscribe(); + } + + get resources(): FormArray { + return this.policyForm.get('resources') as FormArray; + } + + addResource(): void { + this.resources.push(new FormGroup({ + cluster: new FormControl(), + topic: new FormControl() + })); + } + + removeResource(index: number): void { + this.resources.removeAt(index); + } +}