diff --git a/NOTICE b/COPYRIGHT
similarity index 77%
rename from NOTICE
rename to COPYRIGHT
index f5da1a0ba26c0b2b4388922ebff7a5b843058d00..ac5e5f52a3f182430ef8f7aab108813d1362e9fc 100644
--- a/NOTICE
+++ b/COPYRIGHT
@@ -1,11 +1,11 @@
 Copyright 2025 GÉANT Association
 
 Contributions to this work were made on behalf of the GÉANT project, a project that has received funding
-from the European Union’s Horizon 2020 research and innovation programme under Grant Agreement No. 731122 (GN4-2)
-and Grant Agreement No. 856726 (GN4-3).
+from the European Union’s Horizon 2020 research and innovation programme under Grant Agreement No. 731122 (GN4-2),
+Grant Agreement No. 856726 (GN4-3) and Grant Agreement No. 101100680 (GN5-1).
 
-On behalf of GÉANT project, GÉANT Association is the sole owner of the copyright in all material
-which was developed by the members of the GÉANT project:
+On behalf of GÉANT project, GÉANT Association is the sole owner of the copyright in all material which was developed
+by the members of the GÉANT project:
 - Poznan Supercomputing and Networking Center (PSNC), Poland;
 - RENATER, France;
 - University of Belgrade (UoB), Serbia;
diff --git a/build.gradle b/build.gradle
index b13e85b54780fea8729dd1ecf7dfeba7d0ac0d89..34f0707183e6c6741a090eca85db187bd1d2d0cc 100644
--- a/build.gradle
+++ b/build.gradle
@@ -2,7 +2,7 @@ plugins {
     id 'java'
     id 'idea'
     id 'jacoco'
-    id 'org.springframework.boot' version '3.4.5'
+    id 'org.springframework.boot' version '3.4.6'
     id 'io.spring.dependency-management' version '1.1.7'
     id 'com.gorylenko.gradle-git-properties' version '2.5.0'
     id 'org.sonarqube' version '6.2.0.5505'
@@ -88,7 +88,6 @@ dependencies {
     implementation('org.springframework.boot:spring-boot-starter-mail')
     implementation('org.springframework.boot:spring-boot-starter-data-jpa')
     implementation('org.springframework.boot:spring-boot-starter-actuator')
-    implementation('org.springframework.boot:spring-boot-devtools')
     implementation('org.springframework.boot:spring-boot-starter-quartz')
     implementation('org.springframework.boot:spring-boot-starter-validation')
     implementation 'org.springframework.boot:spring-boot-starter-cache'
@@ -112,7 +111,7 @@ dependencies {
     runtimeOnly('org.postgresql:postgresql')
 
     // Database audit trail
-    implementation 'org.hibernate.orm:hibernate-envers:6.6.15.Final'
+    implementation 'org.hibernate.orm:hibernate-envers:6.6.16.Final'
 
     // Database migrations
     implementation('org.flywaydb:flyway-core:11.8.2')
@@ -126,7 +125,7 @@ dependencies {
     implementation('io.micrometer:micrometer-registry-prometheus:1.15.0')
 
     // Kubernetes API client
-    implementation('io.fabric8:kubernetes-client:7.3.0')
+    implementation('io.fabric8:kubernetes-client:7.3.1')
 
     // GitLab API client
     implementation 'org.gitlab4j:gitlab4j-api:6.0.0-rc.10' // version 6 for jakarta
@@ -141,7 +140,7 @@ dependencies {
     implementation('com.opencsv:opencsv:5.11')
 
     // Tests
-    testImplementation('org.mockito:mockito-core:5.17.0')
+    testImplementation('org.mockito:mockito-core:5.18.0')
     testImplementation('org.springframework.boot:spring-boot-starter-test')
 
     integrationTestImplementation('org.springframework.boot:spring-boot-starter-test')
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index ca025c83a7cc5e4f5eb7bc7a5ff8cae62df35ffb..002b867c48b3289ed9c179a968f56bef2859d3da 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -1,6 +1,6 @@
 distributionBase=GRADLE_USER_HOME
 distributionPath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-bin.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.1-bin.zip
 networkTimeout=10000
 validateDistributionUrl=true
 zipStoreBase=GRADLE_USER_HOME
diff --git a/src/main/java/net/geant/nmaas/externalservices/kubernetes/ClusterConfigView.java b/src/main/java/net/geant/nmaas/externalservices/kubernetes/ClusterConfigView.java
index 642850b3a2950cbbec81aff53e1ffefe02e20c91..0b4b8e6e0af4984f8d0c2edf2c7f79a24c46ffbb 100644
--- a/src/main/java/net/geant/nmaas/externalservices/kubernetes/ClusterConfigView.java
+++ b/src/main/java/net/geant/nmaas/externalservices/kubernetes/ClusterConfigView.java
@@ -39,6 +39,7 @@ public class ClusterConfigView {
                 .append("}");
         return sb.toString();
     }
+
     @Getter
     @Setter
     @AllArgsConstructor
@@ -126,13 +127,13 @@ public class ClusterConfigView {
     public static class UserToken {
         private String token;
         @JsonProperty("client-certificate-data")
-        private String client_certificate_data;
+        private String clientCertificateData;
         @JsonProperty("client-key-data")
-        private String client_key_data;
+        private String clientKeyData;
 
         @Override
         public String toString() {
-            return "{ token: " + token + ", client certificate data: " + client_certificate_data + ", client key data: " +client_key_data + " }";
+            return "{ token: " + token + ", client certificate data: " + clientCertificateData + ", client key data: " + clientKeyData + " }";
         }
     }
 }
diff --git a/src/main/java/net/geant/nmaas/externalservices/kubernetes/ClusterMonitoringJob.java b/src/main/java/net/geant/nmaas/externalservices/kubernetes/ClusterMonitoringJob.java
index 2423eb67c11c24480e5e0cc06320cbde16bfd02c..2815b5fc52164840cd662f1ee67eec1c3c8b292c 100644
--- a/src/main/java/net/geant/nmaas/externalservices/kubernetes/ClusterMonitoringJob.java
+++ b/src/main/java/net/geant/nmaas/externalservices/kubernetes/ClusterMonitoringJob.java
@@ -2,23 +2,22 @@ package net.geant.nmaas.externalservices.kubernetes;
 
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
+import org.quartz.Job;
 import org.quartz.JobExecutionContext;
 import org.quartz.JobExecutionException;
 import org.springframework.stereotype.Service;
-import org.quartz.Job;
 
-@Slf4j
-@RequiredArgsConstructor
 @Service
+@RequiredArgsConstructor
+@Slf4j
 public class ClusterMonitoringJob implements Job {
 
-    private final RemoteClusterManager remoteClusterManager;
+    private final ClusterMonitoringService clusterMonitoringService;
 
     @Override
     public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
         log.info("Triggering cluster health check...");
-        remoteClusterManager.restoreFileIfMissing();
-        log.info("File checked, everything looks fine. Next stage: Update clusters state.");
-        remoteClusterManager.updateAllClusterState();
+        clusterMonitoringService.updateAllClusterState();
     }
+
 }
diff --git a/src/main/java/net/geant/nmaas/externalservices/kubernetes/ClusterMonitoringService.java b/src/main/java/net/geant/nmaas/externalservices/kubernetes/ClusterMonitoringService.java
new file mode 100644
index 0000000000000000000000000000000000000000..ac297e9c2c296da699f50087ee832005dc161015
--- /dev/null
+++ b/src/main/java/net/geant/nmaas/externalservices/kubernetes/ClusterMonitoringService.java
@@ -0,0 +1,7 @@
+package net.geant.nmaas.externalservices.kubernetes;
+
+public interface ClusterMonitoringService {
+
+    void updateAllClusterState();
+
+}
diff --git a/src/main/java/net/geant/nmaas/externalservices/kubernetes/RemoteClusterManager.java b/src/main/java/net/geant/nmaas/externalservices/kubernetes/RemoteClusterManager.java
index 217cd98881535adfbb879bd20146fa4f6ab0dcc6..b1442b410254c2819f5cac8591976031f0d8c657 100644
--- a/src/main/java/net/geant/nmaas/externalservices/kubernetes/RemoteClusterManager.java
+++ b/src/main/java/net/geant/nmaas/externalservices/kubernetes/RemoteClusterManager.java
@@ -38,8 +38,8 @@ import java.security.MessageDigest;
 import java.security.NoSuchAlgorithmException;
 import java.time.OffsetDateTime;
 import java.util.ArrayList;
-import java.util.Collections;
 import java.util.HashMap;
+import java.util.Collections;
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
@@ -49,9 +49,7 @@ import java.util.stream.Collectors;
 @Service
 @Slf4j
 @RequiredArgsConstructor
-public class RemoteClusterManager {
-
-    private final static ModelMapper modelMapper = new ModelMapper();
+public class RemoteClusterManager implements ClusterMonitoringService {
 
     private final KClusterRepository clusterRepository;
     private final KubernetesClusterIngressManager kClusterIngressManager;
@@ -59,7 +57,7 @@ public class RemoteClusterManager {
     private final DomainService domainService;
     private final ApplicationEventPublisher eventPublisher;
     private final UserService userService;
-
+    private final ModelMapper modelMapper;
 
     public RemoteClusterView getClusterView(Long id) {
         Optional<KCluster> cluster = clusterRepository.findById(id);
@@ -72,7 +70,7 @@ public class RemoteClusterManager {
 
     public List<RemoteClusterView> getAllClusterView() {
         List<KCluster> clusters = clusterRepository.findAll();
-        return clusters.stream().map(RemoteClusterManager::toView).collect(Collectors.toList());
+        return clusters.stream().map(this::toView).collect(Collectors.toList());
     }
 
     public KCluster getCluster(Long id) {
@@ -141,11 +139,11 @@ public class RemoteClusterManager {
                                 .state(KClusterState.UNKNOWN)
                                 .contactEmail(view.getContactEmail())
                                 .currentStateSince(OffsetDateTime.now())
-                                .domains(view.getDomainNames().stream().map(d -> {
+                                .domains(!view.getDomainNames().isEmpty() ?  view.getDomainNames().stream().map(d -> {
                                             Optional<Domain> dom = domainService.findDomain(d);
                                             return dom.orElse(null);
                                         }
-                                ).toList())
+                                ).toList() : Collections.emptyList())
                                 .build(),
                         file);
 
@@ -153,9 +151,7 @@ public class RemoteClusterManager {
                 log.error("More than 1 cluster provided, not implemented yet");
             }
 
-        } catch (IOException e) {
-            throw new RuntimeException(e);
-        } catch (NoSuchAlgorithmException e) {
+        } catch (IOException | NoSuchAlgorithmException e) {
             throw new RuntimeException(e);
         }
 
@@ -189,7 +185,6 @@ public class RemoteClusterManager {
                 updated = clusterRepository.save(updated);
                 //TODO : implement file update logic
                 return toView(updated);
-
             }
         }
 
@@ -239,25 +234,27 @@ public class RemoteClusterManager {
         if (view.getCodename() == null) {
             throw new IllegalArgumentException("Codename of the cluster is null");
         }
-
     }
 
-    public static RemoteClusterView toView(KCluster KCluster) {
+    private RemoteClusterView toView(KCluster KCluster) {
         RemoteClusterView view = modelMapper.map(KCluster, RemoteClusterView.class);
         view.setDomainNames(KCluster.getDomains().stream().map(Domain::getName).toList());
         return view;
     }
 
+    @Override
     public void updateAllClusterState() {
+        restoreFileIfMissing();
         List<KCluster> kClusters = clusterRepository.findAll();
         kClusters.forEach(cluster -> {
             Config config = null;
             try {
                 config = Config.fromKubeconfig(Files.readString(Path.of(cluster.getPathConfigFile())));
-            } catch (IOException  e) {
-                log.error("IO error with accesing the file {}", e.getMessage());
+            } catch (IOException e) {
+                log.error("IO error with accessing the file {}", e.getMessage());
                 updateStateIfNeeded(cluster, KClusterState.UNKNOWN);
             }
+
             try {
                 KubernetesClient client = new KubernetesClientBuilder().withConfig(config).build();
                 log.debug("Get kubernetes version , something works {}", client.getKubernetesVersion().getPlatform());
@@ -268,12 +265,9 @@ public class RemoteClusterManager {
                 log.error("Can not connect to cluster {}", cluster.getCodename());
                 log.error(e.getMessage());
                 updateStateIfNeeded(cluster, KClusterState.DOWN);
-
-
-            }  catch (RuntimeException  ex) {
+            } catch (RuntimeException ex) {
                 log.error("Runtime error while checking health of cluster {}", ex.getMessage());
                 updateStateIfNeeded(cluster, KClusterState.UNKNOWN);
-
             } catch (Exception ex) {
                 log.error("Caught unexpected exception: {}", ex.getMessage(), ex);
             }
@@ -282,14 +276,13 @@ public class RemoteClusterManager {
         clusterRepository.saveAll(kClusters);
     }
 
-
     private void updateStateIfNeeded(KCluster cluster, KClusterState newState) {
         if (!cluster.getState().equals(newState)) {
             cluster.setState(newState);
             cluster.setCurrentStateSince(OffsetDateTime.now());
-            if(cluster.getState().equals(KClusterState.DOWN) || cluster.getState().equals(KClusterState.UNKNOWN)) {
+            if (cluster.getState().equals(KClusterState.DOWN) || cluster.getState().equals(KClusterState.UNKNOWN)) {
                 sendMail(cluster, MailType.REMOTE_CLUSTER_UNAVAILABLE);
-            };
+            }
         }
     }
 
@@ -312,7 +305,6 @@ public class RemoteClusterManager {
                 .build();
 
         this.eventPublisher.publishEvent(new NotificationEvent(this, mailAttributes));
-
     }
 
     public void restoreFileIfMissing() {
@@ -334,9 +326,9 @@ public class RemoteClusterManager {
         });
     }
 
-
     public boolean isFileAvailable(String pathStr) {
         Path path = Paths.get(pathStr);
         return Files.exists(path) && Files.isRegularFile(path) && Files.isReadable(path);
     }
+
 }
\ No newline at end of file
diff --git a/src/main/java/net/geant/nmaas/monitor/MonitorConfig.java b/src/main/java/net/geant/nmaas/monitor/MonitorConfig.java
deleted file mode 100644
index 47b82c3ccb68d1947e6c350d55cdb5859645b181..0000000000000000000000000000000000000000
--- a/src/main/java/net/geant/nmaas/monitor/MonitorConfig.java
+++ /dev/null
@@ -1,60 +0,0 @@
-package net.geant.nmaas.monitor;
-
-import lombok.extern.slf4j.Slf4j;
-import net.geant.nmaas.monitor.model.MonitorEntryView;
-import net.geant.nmaas.scheduling.ScheduleManager;
-import org.springframework.beans.factory.InitializingBean;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.context.annotation.Bean;
-import org.springframework.context.annotation.Configuration;
-import org.springframework.transaction.annotation.Transactional;
-
-import java.util.Arrays;
-import java.util.List;
-import java.util.Optional;
-
-@Configuration
-@Slf4j
-public class MonitorConfig {
-
-    @Bean
-    public InitializingBean insertDefaultMonitoringJobs() {
-        return new InitializingBean() {
-
-            @Autowired
-            private List<MonitorService> monitorServices;
-
-            @Autowired
-            private ScheduleManager scheduleManager;
-
-            @Autowired
-            private MonitorManager monitorManager;
-
-            @Override
-            @Transactional
-            public void afterPropertiesSet() {
-                Arrays.stream(ServiceType.values())
-                        .filter(serviceType -> !scheduleManager.jobExists(serviceType.toString())) // if job does not exist
-                        .forEach(serviceType -> {
-                            MonitorEntryView monitorEntry;
-                            if (monitorManager.existsByServiceName(serviceType)) { // if entry exists
-                                monitorEntry = monitorManager.getMonitorEntries(serviceType.toString()); // read it from database
-                            } else {
-                                monitorEntry = serviceType.getDefaultMonitorEntry(); // if entry does not exist
-                                monitorManager.createMonitorEntry(monitorEntry); // create new default entry
-                            }
-                            Optional<MonitorService> service = monitorServices.stream()
-                                    .filter(s -> s.getServiceType().equals(serviceType))
-                                    .filter(MonitorService::schedulable)
-                                    .findFirst();
-                            if (service.isPresent()) {
-                                scheduleManager.createJob(service.get(), monitorEntry);
-                            } else {
-                                log.warn("Monitor service for {} not found or is not schedulable", serviceType);
-                            }
-                        });
-            }
-        };
-    }
-
-}
diff --git a/src/main/java/net/geant/nmaas/monitor/MonitorService.java b/src/main/java/net/geant/nmaas/monitor/MonitorService.java
index 264117c5728ac578a4b1072e3ff8279f3cc23214..cbb34a1c2debe2ebdaf431f9983ff1c3d9579485 100644
--- a/src/main/java/net/geant/nmaas/monitor/MonitorService.java
+++ b/src/main/java/net/geant/nmaas/monitor/MonitorService.java
@@ -23,7 +23,7 @@ public abstract class MonitorService implements Job {
         this.monitorManager.updateMonitorEntry(new Date(), this.getServiceType(), status);
     }
 
-    protected boolean schedulable() {
+    public boolean schedulable() {
         return true;
     }
 
diff --git a/src/main/java/net/geant/nmaas/nmservice/deployment/NmServiceRepositoryManager.java b/src/main/java/net/geant/nmaas/nmservice/deployment/NmServiceRepositoryManager.java
index ff222aefc7a2e484ee89b7eaaf46b7e991a82eb8..4c9d0b2e5c26e43375e778698880905959fdcd5a 100644
--- a/src/main/java/net/geant/nmaas/nmservice/deployment/NmServiceRepositoryManager.java
+++ b/src/main/java/net/geant/nmaas/nmservice/deployment/NmServiceRepositoryManager.java
@@ -9,11 +9,11 @@ import net.geant.nmaas.nmservice.deployment.entities.NmServiceInfo;
 import net.geant.nmaas.nmservice.deployment.repository.NmServiceInfoRepository;
 import net.geant.nmaas.orchestration.Identifier;
 import net.geant.nmaas.orchestration.exceptions.InvalidDeploymentIdException;
-import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.context.event.EventListener;
 import org.springframework.transaction.annotation.Propagation;
 import org.springframework.transaction.annotation.Transactional;
 
+import java.time.LocalDateTime;
 import java.util.List;
 import java.util.Map;
 import java.util.Optional;
@@ -21,12 +21,14 @@ import java.util.Optional;
 @Slf4j
 public abstract class NmServiceRepositoryManager<T extends NmServiceInfo> {
 
-    @Autowired
     protected GitLabProjectRepository gitLabProjectRepository;
-
-    @Autowired
     protected NmServiceInfoRepository<T> repository;
 
+    public NmServiceRepositoryManager(GitLabProjectRepository gitLabProjectRepository, NmServiceInfoRepository<T> repository) {
+        this.gitLabProjectRepository = gitLabProjectRepository;
+        this.repository = repository;
+    }
+
     @Transactional(propagation = Propagation.REQUIRES_NEW)
     public void storeService(T serviceInfo) {
         if (repository.findByDeploymentId(serviceInfo.getDeploymentId()).isEmpty()) {
@@ -69,8 +71,7 @@ public abstract class NmServiceRepositoryManager<T extends NmServiceInfo> {
         try {
             updateServiceState(event.getDeploymentId(), event.getState());
         } catch (Exception ex) {
-            long timestamp = System.currentTimeMillis();
-            log.error("Error reported at {}", timestamp, ex);
+            log.error("Error reported at {}", LocalDateTime.now(), ex);
         }
     }
 
diff --git a/src/main/java/net/geant/nmaas/nmservice/deployment/containerorchestrators/kubernetes/KubernetesRepositoryManager.java b/src/main/java/net/geant/nmaas/nmservice/deployment/containerorchestrators/kubernetes/KubernetesRepositoryManager.java
index 6d5763f0776311c862ca69451931822f18f46788..a6fd0c4771d34bbb36b8c79908aa9a4cb02098c3 100644
--- a/src/main/java/net/geant/nmaas/nmservice/deployment/containerorchestrators/kubernetes/KubernetesRepositoryManager.java
+++ b/src/main/java/net/geant/nmaas/nmservice/deployment/containerorchestrators/kubernetes/KubernetesRepositoryManager.java
@@ -1,11 +1,13 @@
 package net.geant.nmaas.nmservice.deployment.containerorchestrators.kubernetes;
 
+import net.geant.nmaas.nmservice.configuration.repositories.GitLabProjectRepository;
 import net.geant.nmaas.nmservice.deployment.NmServiceRepositoryManager;
 import net.geant.nmaas.nmservice.deployment.containerorchestrators.kubernetes.entities.KubernetesNmServiceInfo;
 import net.geant.nmaas.nmservice.deployment.containerorchestrators.kubernetes.entities.ServiceAccessMethod;
 import net.geant.nmaas.nmservice.deployment.containerorchestrators.kubernetes.entities.ServiceStorageVolume;
 import net.geant.nmaas.nmservice.deployment.containerorchestrators.kubernetes.repositories.ServiceAccessMethodRepository;
 import net.geant.nmaas.nmservice.deployment.containerorchestrators.kubernetes.repositories.ServiceStorageVolumeRepository;
+import net.geant.nmaas.nmservice.deployment.repository.NmServiceInfoRepository;
 import net.geant.nmaas.orchestration.Identifier;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.context.annotation.Scope;
@@ -25,11 +27,15 @@ import static net.geant.nmaas.nmservice.deployment.containerorchestrators.kubern
 @Scope(proxyMode = ScopedProxyMode.TARGET_CLASS)
 public class KubernetesRepositoryManager extends NmServiceRepositoryManager<KubernetesNmServiceInfo> {
 
-    @Autowired
-    private ServiceStorageVolumeRepository storageVolumeRepository;
+    private final ServiceStorageVolumeRepository storageVolumeRepository;
+    private final ServiceAccessMethodRepository accessMethodRepository;
 
     @Autowired
-    private ServiceAccessMethodRepository accessMethodRepository;
+    public KubernetesRepositoryManager(GitLabProjectRepository gitLabProjectRepository, NmServiceInfoRepository<KubernetesNmServiceInfo> repository, ServiceStorageVolumeRepository storageVolumeRepository, ServiceAccessMethodRepository accessMethodRepository) {
+        super(gitLabProjectRepository, repository);
+        this.storageVolumeRepository = storageVolumeRepository;
+        this.accessMethodRepository = accessMethodRepository;
+    }
 
     @Override
     @Transactional(propagation = Propagation.REQUIRES_NEW)
diff --git a/src/main/java/net/geant/nmaas/nmservice/deployment/containerorchestrators/kubernetes/components/helm/HelmCommandExecutor.java b/src/main/java/net/geant/nmaas/nmservice/deployment/containerorchestrators/kubernetes/components/helm/HelmCommandExecutor.java
index 09b4dde389d79c1c394df8bd662f855127a36798..01a3fd9b4307cc5eea008c719b76d0442d1d6b4c 100644
--- a/src/main/java/net/geant/nmaas/nmservice/deployment/containerorchestrators/kubernetes/components/helm/HelmCommandExecutor.java
+++ b/src/main/java/net/geant/nmaas/nmservice/deployment/containerorchestrators/kubernetes/components/helm/HelmCommandExecutor.java
@@ -1,5 +1,6 @@
 package net.geant.nmaas.nmservice.deployment.containerorchestrators.kubernetes.components.helm;
 
+import lombok.NoArgsConstructor;
 import lombok.Setter;
 import net.geant.nmaas.nmservice.deployment.containerorchestrators.kubernetes.components.helm.commands.HelmDeleteCommand;
 import net.geant.nmaas.nmservice.deployment.containerorchestrators.kubernetes.components.helm.commands.HelmInstallCommand;
@@ -23,23 +24,28 @@ import java.util.Map;
 
 import static net.geant.nmaas.nmservice.deployment.containerorchestrators.kubernetes.components.helm.HelmCommand.HELM_VERSION_2;
 
+@NoArgsConstructor
 @Component
 public class HelmCommandExecutor {
 
-    @Autowired
     private CommandExecutor commandExecutor;
-
     @Setter
-    @Value("${helm.version:v3}")
     private String helmVersion;
-
     @Setter
-    @Value("${helm.repositoryName}")
     private String helmRepositoryName;
-
-    @Value("${helm.enableTls:false}")
     private Boolean enableTls;
 
+    @Autowired
+    public HelmCommandExecutor(CommandExecutor commandExecutor,
+                               @Value("${helm.version:v3}") String helmVersion,
+                               @Value("${helm.repositoryName}") String helmRepositoryName,
+                               @Value("${helm.enableTls:false}") Boolean enableTls) {
+        this.commandExecutor = commandExecutor;
+        this.helmVersion = helmVersion;
+        this.helmRepositoryName = helmRepositoryName;
+        this.enableTls = enableTls;
+    }
+
     void executeHelmInstallCommand(String namespace, String releaseName, KubernetesTemplate template, Map<String, String> arguments) {
         executeInstall(namespace, releaseName, template, arguments);
     }
diff --git a/src/main/java/net/geant/nmaas/orchestration/jobs/DomainGroupJob.java b/src/main/java/net/geant/nmaas/orchestration/jobs/DomainGroupJob.java
new file mode 100644
index 0000000000000000000000000000000000000000..7fe42451ebba65a1061e0981e789e9c0d823826a
--- /dev/null
+++ b/src/main/java/net/geant/nmaas/orchestration/jobs/DomainGroupJob.java
@@ -0,0 +1,55 @@
+package net.geant.nmaas.orchestration.jobs;
+
+import lombok.extern.slf4j.Slf4j;
+import net.geant.nmaas.orchestration.exceptions.WebServiceCommunicationException;
+import net.geant.nmaas.portal.api.domain.DomainGroupView;
+import net.geant.nmaas.portal.api.domain.DomainGroupWebhookDto;
+import net.geant.nmaas.portal.api.domain.WebhookEventDto;
+import net.geant.nmaas.portal.api.exception.MissingElementException;
+import net.geant.nmaas.portal.persistent.entity.WebhookEventType;
+import net.geant.nmaas.portal.service.WebhookEventService;
+import org.modelmapper.ModelMapper;
+import org.quartz.JobDataMap;
+import org.quartz.JobExecutionContext;
+import org.quartz.JobExecutionException;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+import org.springframework.web.client.RestClient;
+
+import java.security.GeneralSecurityException;
+
+@Slf4j
+@Component
+public class DomainGroupJob extends WebhookJob {
+
+    @Autowired
+    public DomainGroupJob(RestClient restClient, WebhookEventService webhookEventService, ModelMapper modelMapper) {
+        super(restClient, webhookEventService, modelMapper);
+    }
+
+    @Override
+    public void execute(JobExecutionContext context) throws JobExecutionException {
+        JobDataMap dataMap = context.getJobDetail().getJobDataMap();
+        Long webhookId = dataMap.getLong("webhookId");
+        String action = dataMap.getString("action");
+        DomainGroupView domainGroup = (DomainGroupView) dataMap.get("domainGroup");
+
+        try {
+            WebhookEventDto webhook = webhookEventService.getById(webhookId);
+            if (!WebhookEventType.DOMAIN_GROUP_CHANGE.equals(webhook.getEventType())) {
+                log.warn("Webhook's event type with id {} has been updated. DomainGroupJob is abandoned", webhookId);
+                return;
+            }
+            DomainGroupWebhookDto view = new DomainGroupWebhookDto(domainGroup, action);
+            callWebhook(webhook, view);
+        } catch (GeneralSecurityException e) {
+            log.error("Failed to decrypt webhook with id {}", webhookId);
+            throw new JobExecutionException("Failed webhook decryption");
+        } catch (MissingElementException e) {
+            log.warn("Webhook does not exist. DomainGroupJob is abandoned");
+        } catch (WebServiceCommunicationException e) {
+            log.error("Failed to communicate with external system for the webhoook of domain group with id {}", domainGroup.getId());
+            throw new JobExecutionException("Failed communication with external system");
+        }
+    }
+}
diff --git a/src/main/java/net/geant/nmaas/portal/api/domain/ApplicationStatePerDomainView.java b/src/main/java/net/geant/nmaas/portal/api/domain/ApplicationStatePerDomainView.java
index 7ed46a20911b349308d0eebcdb3bbd0b93c36dcd..ccce6b28f866030a0897efc855ff62458a58f250 100644
--- a/src/main/java/net/geant/nmaas/portal/api/domain/ApplicationStatePerDomainView.java
+++ b/src/main/java/net/geant/nmaas/portal/api/domain/ApplicationStatePerDomainView.java
@@ -3,9 +3,11 @@ package net.geant.nmaas.portal.api.domain;
 import lombok.Getter;
 import lombok.Setter;
 
+import java.io.Serializable;
+
 @Getter
 @Setter
-public class ApplicationStatePerDomainView {
+public class ApplicationStatePerDomainView  implements Serializable {
     Long applicationBaseId;
     String applicationBaseName;
     boolean enabled;
diff --git a/src/main/java/net/geant/nmaas/portal/api/domain/DomainBase.java b/src/main/java/net/geant/nmaas/portal/api/domain/DomainBase.java
index 9bfd26a5b81a42a51a4c4dfe7c763ec4214a4e22..cf77585ec84652b6db6cbc25f83f9b9a8fe00981 100644
--- a/src/main/java/net/geant/nmaas/portal/api/domain/DomainBase.java
+++ b/src/main/java/net/geant/nmaas/portal/api/domain/DomainBase.java
@@ -4,10 +4,12 @@ import lombok.Getter;
 import lombok.NoArgsConstructor;
 import lombok.Setter;
 
+import java.io.Serializable;
+
 @Getter
 @Setter
 @NoArgsConstructor
-public class DomainBase {
+public class DomainBase  implements Serializable {
 
     Long id;
     String name;
diff --git a/src/main/java/net/geant/nmaas/portal/api/domain/DomainGroupView.java b/src/main/java/net/geant/nmaas/portal/api/domain/DomainGroupView.java
index 2e40d9005a04d0013f0e5867c3fffcd1ac2a7fed..a5855816dd63f59af5c3088119d4c7230cbe539a 100644
--- a/src/main/java/net/geant/nmaas/portal/api/domain/DomainGroupView.java
+++ b/src/main/java/net/geant/nmaas/portal/api/domain/DomainGroupView.java
@@ -6,6 +6,7 @@ import lombok.Getter;
 import lombok.NoArgsConstructor;
 import lombok.Setter;
 
+import java.io.Serializable;
 import java.util.ArrayList;
 import java.util.List;
 
@@ -13,7 +14,7 @@ import java.util.List;
 @NoArgsConstructor
 @Getter
 @Setter
-public class DomainGroupView {
+public class DomainGroupView implements Serializable {
 
     @NotNull
     private Long id;
diff --git a/src/main/java/net/geant/nmaas/portal/api/domain/DomainGroupWebhookDto.java b/src/main/java/net/geant/nmaas/portal/api/domain/DomainGroupWebhookDto.java
new file mode 100644
index 0000000000000000000000000000000000000000..75685180793fe87af636eabd3623888851ca0aeb
--- /dev/null
+++ b/src/main/java/net/geant/nmaas/portal/api/domain/DomainGroupWebhookDto.java
@@ -0,0 +1,14 @@
+package net.geant.nmaas.portal.api.domain;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.Setter;
+
+@AllArgsConstructor
+@Getter
+@Setter
+public class DomainGroupWebhookDto {
+
+    private DomainGroupView domainGroup;
+    private String action;
+}
diff --git a/src/main/java/net/geant/nmaas/portal/api/domain/WebhookEventDto.java b/src/main/java/net/geant/nmaas/portal/api/domain/WebhookEventDto.java
index 9049b9c921ed43cbec2e5e99d1557bfeca286512..fe179f6636ba9bbcfca008fd8bb805f183668d4b 100644
--- a/src/main/java/net/geant/nmaas/portal/api/domain/WebhookEventDto.java
+++ b/src/main/java/net/geant/nmaas/portal/api/domain/WebhookEventDto.java
@@ -25,7 +25,7 @@ public class WebhookEventDto {
     @Pattern(regexp = "^(Authorization|X-.*)?$", message = "Authorization header must be either 'Authorization' or start with 'X-'")
     private String authorizationHeader;
 
-    public WebhookEventDto (Long id, String name, String targetUrl, WebhookEventType eventType){
+    public WebhookEventDto(Long id, String name, String targetUrl, WebhookEventType eventType) {
         this.id = id;
         this.name = name;
         this.targetUrl = targetUrl;
diff --git a/src/main/java/net/geant/nmaas/portal/api/info/DashboardController.java b/src/main/java/net/geant/nmaas/portal/api/info/DashboardController.java
index baaed209904a5850dfc2048b7491e1475587e97d..2e423bde14dc0e2a9d60d0b7f6007ab906bbd3a4 100644
--- a/src/main/java/net/geant/nmaas/portal/api/info/DashboardController.java
+++ b/src/main/java/net/geant/nmaas/portal/api/info/DashboardController.java
@@ -5,11 +5,15 @@ import lombok.extern.slf4j.Slf4j;
 import net.geant.nmaas.portal.api.domain.ContentView;
 import net.geant.nmaas.portal.persistent.entity.Content;
 import net.geant.nmaas.portal.service.DashboardService;
+import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.web.bind.annotation.GetMapping;
 import org.springframework.web.bind.annotation.PathVariable;
 import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
 import org.springframework.web.bind.annotation.RestController;
 
+import java.time.OffsetDateTime;
+
 @RestController
 @AllArgsConstructor
 @RequestMapping("/api/dashboard")
@@ -19,8 +23,11 @@ public class DashboardController {
     private DashboardService dashboardService;
 
     @GetMapping("/admin")
-    public DashboardView getDashboardAdmin() {
-        DashboardView view = dashboardService.getSystemDashboard();
+    @PreAuthorize("hasRole('ROLE_SYSTEM_ADMIN')")
+    public DashboardView getDashboardAdmin( @RequestParam("startDate") OffsetDateTime startDate,
+                                            @RequestParam("end") OffsetDateTime endDate) {
+        checkDate(startDate,endDate);
+        DashboardView view = dashboardService.getSystemDashboard(startDate,endDate);
         log.error("View : {}", view.toString());
 
         return view;
@@ -32,4 +39,14 @@ public class DashboardController {
         log.error("View : {}", view.toString());
         return view;
     }
+
+    private void checkDate(OffsetDateTime startDate, OffsetDateTime endDate) {
+        if(startDate == null || endDate == null) {
+            throw new IllegalArgumentException("Start date can not be null");
+        }
+
+        if(startDate.isAfter(endDate)) {
+            throw new IllegalArgumentException("Start date is after end date.");
+        }
+    }
 }
diff --git a/src/main/java/net/geant/nmaas/portal/api/market/AppInstanceController.java b/src/main/java/net/geant/nmaas/portal/api/market/AppInstanceController.java
index ca76028b193748dc1aa5763788e62f5b0512d737..81365d8a6ed7c899f34f4ee9eeff9ac7f03351a2 100644
--- a/src/main/java/net/geant/nmaas/portal/api/market/AppInstanceController.java
+++ b/src/main/java/net/geant/nmaas/portal/api/market/AppInstanceController.java
@@ -57,6 +57,7 @@ import org.springframework.web.bind.annotation.PathVariable;
 import org.springframework.web.bind.annotation.PostMapping;
 import org.springframework.web.bind.annotation.RequestBody;
 import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
 import org.springframework.web.bind.annotation.RestController;
 
 import java.lang.reflect.Field;
@@ -252,7 +253,9 @@ public class AppInstanceController extends AppBaseController {
     @Transactional
     public Id createAppInstance(@RequestBody AppInstanceRequest appInstanceRequest,
                                 @NotNull Principal principal,
-                                @PathVariable Long domainId) {
+                                @PathVariable Long domainId,
+                                @RequestParam(name = "clusterId",required = false) Long clusterId) {
+        log.error("Cluster = {}", clusterId);
         Application app = getApp(appInstanceRequest.getApplicationId());
         Domain domain = domainService.findDomain(domainId)
                 .orElseThrow(() -> new MissingElementException("Domain not found"));
diff --git a/src/main/java/net/geant/nmaas/portal/api/market/WebhookEventAdvice.java b/src/main/java/net/geant/nmaas/portal/api/market/WebhookEventAdvice.java
index 36715f90ccc58a89d4303c7fbd7a16c18313e3e6..29373cd06012f63eae82d7318e269db931f52e85 100644
--- a/src/main/java/net/geant/nmaas/portal/api/market/WebhookEventAdvice.java
+++ b/src/main/java/net/geant/nmaas/portal/api/market/WebhookEventAdvice.java
@@ -1,5 +1,6 @@
 package net.geant.nmaas.portal.api.market;
 
+import lombok.extern.slf4j.Slf4j;
 import org.springframework.http.HttpStatus;
 import org.springframework.http.ResponseEntity;
 import org.springframework.web.bind.MethodArgumentNotValidException;
@@ -10,6 +11,7 @@ import java.util.HashMap;
 import java.util.Map;
 
 @ControllerAdvice(assignableTypes = WebhookEventController.class)
+@Slf4j
 public class WebhookEventAdvice {
 
     @ExceptionHandler(MethodArgumentNotValidException.class)
@@ -19,6 +21,8 @@ public class WebhookEventAdvice {
         ex.getBindingResult().getFieldErrors().forEach(error ->
                 errors.put(error.getField(), error.getDefaultMessage()));
 
+        log.warn("Responding with 400 with errors: {}", errors);
+
         return new ResponseEntity<>(errors, HttpStatus.BAD_REQUEST);
     }
 
diff --git a/src/main/java/net/geant/nmaas/portal/api/security/EncryptionService.java b/src/main/java/net/geant/nmaas/portal/api/security/EncryptionService.java
index 91b2f05e98c65dc45146b954ca640fab646fa31d..e15c1e4ed29c58420fc0960f7d82391eeb95cfd8 100644
--- a/src/main/java/net/geant/nmaas/portal/api/security/EncryptionService.java
+++ b/src/main/java/net/geant/nmaas/portal/api/security/EncryptionService.java
@@ -1,5 +1,6 @@
 package net.geant.nmaas.portal.api.security;
 
+import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.beans.factory.annotation.Value;
 import org.springframework.stereotype.Service;
 
@@ -18,11 +19,15 @@ public class EncryptionService {
     private static final int GCM_TAG_LENGTH = 128;
     private static final int IV_LENGTH = 12;
 
-    @Value("${security.encryption.secret-key}")
-    private String secretKey;
+    private final String secretKey;
+    private final String algorithm;
 
-    @Value("${security.encryption.algorithm}")
-    private String algorithm;
+    @Autowired
+    public EncryptionService(@Value("${security.encryption.secret-key}") String secretKey,
+                             @Value("${security.encryption.algorithm}") String algorithm) {
+        this.secretKey = secretKey;
+        this.algorithm = algorithm;
+    }
 
     public String encrypt(String plainText) throws GeneralSecurityException {
         Cipher cipher = Cipher.getInstance(algorithm);
diff --git a/src/main/java/net/geant/nmaas/portal/persistent/entity/Domain.java b/src/main/java/net/geant/nmaas/portal/persistent/entity/Domain.java
index 8f36be69979e7cb408093cdf4b820eb0f0e54ebd..1a740275d82971bc0163ff8dce08872c95a12ed3 100644
--- a/src/main/java/net/geant/nmaas/portal/persistent/entity/Domain.java
+++ b/src/main/java/net/geant/nmaas/portal/persistent/entity/Domain.java
@@ -1,14 +1,5 @@
 package net.geant.nmaas.portal.persistent.entity;
 
-import lombok.AccessLevel;
-import lombok.EqualsAndHashCode;
-import lombok.Getter;
-import lombok.NoArgsConstructor;
-import lombok.Setter;
-import net.geant.nmaas.dcn.deployment.entities.DomainDcnDetails;
-import net.geant.nmaas.orchestration.entities.DomainTechDetails;
-import net.geant.nmaas.externalservices.kubernetes.entities.KCluster;
-
 import jakarta.persistence.CascadeType;
 import jakarta.persistence.Column;
 import jakarta.persistence.ElementCollection;
@@ -24,14 +15,22 @@ import jakarta.persistence.OneToOne;
 import jakarta.persistence.Table;
 import jakarta.persistence.UniqueConstraint;
 import jakarta.validation.constraints.NotNull;
+import lombok.AccessLevel;
+import lombok.EqualsAndHashCode;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+import net.geant.nmaas.dcn.deployment.entities.DomainDcnDetails;
+import net.geant.nmaas.externalservices.kubernetes.entities.KCluster;
+import net.geant.nmaas.orchestration.entities.DomainTechDetails;
+
 import java.io.Serializable;
 import java.util.ArrayList;
 import java.util.List;
-import java.util.stream.Collectors;
 
 @Entity
 @Table(uniqueConstraints = {
-		@UniqueConstraint(columnNames={"name"}), @UniqueConstraint(columnNames={"codename"})
+        @UniqueConstraint(columnNames = {"name"}), @UniqueConstraint(columnNames = {"codename"})
 })
 @NoArgsConstructor(access = AccessLevel.PROTECTED)
 @Getter
@@ -39,86 +38,85 @@ import java.util.stream.Collectors;
 @EqualsAndHashCode(onlyExplicitlyIncluded = true)
 public class Domain implements Serializable {
 
-	@Id
-	@GeneratedValue(strategy = GenerationType.IDENTITY)
-	Long id;
+    @Id
+    @GeneratedValue(strategy = GenerationType.IDENTITY)
+    Long id;
 
-	@EqualsAndHashCode.Include
-	@NotNull
+    @EqualsAndHashCode.Include
+    @NotNull
     @Column(nullable = false, unique = true)
     private String codename;
 
-	@EqualsAndHashCode.Include
-	@NotNull
-	@Column(nullable = false, unique=true)
-	String name;
-
-	@OneToOne(cascade=CascadeType.ALL, orphanRemoval=true)
-	private DomainDcnDetails domainDcnDetails;
-
-	@OneToOne(cascade=CascadeType.ALL, orphanRemoval=true)
-	private DomainTechDetails domainTechDetails;
-	
-	boolean active;
-
-	boolean deleted;
-
-	/** List of applications with state per domain **/
-	@ElementCollection(fetch = FetchType.LAZY)
-	private List<ApplicationStatePerDomain> applicationStatePerDomain = new ArrayList<>();
-
-	@ManyToMany(fetch = FetchType.LAZY, cascade = {CascadeType.ALL})
-	@JoinTable(
-			name = "domains_groups",
-			joinColumns = { @JoinColumn(name = "domain_id") },
-			inverseJoinColumns = { @JoinColumn(name = "group_id") }
-	)
-	private List<DomainGroup> groups = new ArrayList<>();
-
-	@ManyToMany(mappedBy = "domains")
-	private List<KCluster> clusters = new ArrayList<>();
-
-	public Domain(String name, String codename) {
-		super();
-		this.name = name;
-		this.codename = codename;
-		this.active = true;
-	}
-
-	public Domain(String name, String codename, boolean active) {
-		this(name, codename);
-		this.active = active;
-	}
-	
-	public Domain(Long id, String name, String codename) {
-		this(name, codename);
-		this.id = id;
-	}
-
-	public Domain(Long id, String name, String codename, boolean active) {
-		this(id, name, codename);
-		this.active = active;
-	}
-
-	public void addApplicationState(ApplicationBase applicationBase){
-	    this.addApplicationState(applicationBase, true);
+    @EqualsAndHashCode.Include
+    @NotNull
+    @Column(nullable = false, unique = true)
+    String name;
+
+    @OneToOne(cascade = CascadeType.ALL, orphanRemoval = true)
+    private DomainDcnDetails domainDcnDetails;
+
+    @OneToOne(cascade = CascadeType.ALL, orphanRemoval = true)
+    private DomainTechDetails domainTechDetails;
+
+    boolean active;
+
+    boolean deleted;
+
+    /**
+     * List of applications with state per domain
+     **/
+    @ElementCollection(fetch = FetchType.LAZY)
+    private List<ApplicationStatePerDomain> applicationStatePerDomain = new ArrayList<>();
+
+    @ManyToMany(fetch = FetchType.LAZY, cascade = {CascadeType.ALL})
+    @JoinTable(
+            name = "domains_groups",
+            joinColumns = {@JoinColumn(name = "domain_id")},
+            inverseJoinColumns = {@JoinColumn(name = "group_id")}
+    )
+    private List<DomainGroup> groups = new ArrayList<>();
+
+    @ManyToMany(mappedBy = "domains")
+    private List<KCluster> clusters = new ArrayList<>();
+
+    public Domain(String name, String codename) {
+        super();
+        this.name = name;
+        this.codename = codename;
+        this.active = true;
     }
 
-	public void addApplicationState(ApplicationBase applicationBase, boolean enabled){
-		this.addApplicationState(new ApplicationStatePerDomain(applicationBase, enabled));
+    public Domain(String name, String codename, boolean active) {
+        this(name, codename);
+        this.active = active;
     }
 
-    public void addApplicationState(ApplicationStatePerDomain appState) {
-		if (!this.applicationStatePerDomain.stream().map(ApplicationStatePerDomain::getApplicationBase)
-				.map(ApplicationBase::getId).collect(Collectors.toList()).contains(appState.getApplicationBase().getId())) {
-			this.applicationStatePerDomain.add(appState);
-		}
-	}
+    public Domain(Long id, String name, String codename) {
+        this(name, codename);
+        this.id = id;
+    }
 
-	public void addGroup(DomainGroup group) {
-		this.groups.add(group);
-		group.getDomains().add(this);
-	}
+    public Domain(Long id, String name, String codename, boolean active) {
+        this(id, name, codename);
+        this.active = active;
+    }
 
-}
+    public void addApplicationState(ApplicationBase applicationBase) {
+        this.addApplicationState(applicationBase, true);
+    }
+
+    public void addApplicationState(ApplicationBase applicationBase, boolean enabled) {
+        this.addApplicationState(new ApplicationStatePerDomain(applicationBase, enabled));
+    }
 
+    public void addApplicationState(ApplicationStatePerDomain appState) {
+        if (!this.applicationStatePerDomain.stream()
+                .map(ApplicationStatePerDomain::getApplicationBase)
+                .map(ApplicationBase::getId)
+                .toList()
+                .contains(appState.getApplicationBase().getId())) {
+            this.applicationStatePerDomain.add(appState);
+        }
+    }
+
+}
diff --git a/src/main/java/net/geant/nmaas/portal/persistent/entity/DomainGroup.java b/src/main/java/net/geant/nmaas/portal/persistent/entity/DomainGroup.java
index 186f96dcf0397919d061d6c389ae6f42e4bfb751..74d554474081b8ed4233bba8c9d4b2ae03ac7669 100644
--- a/src/main/java/net/geant/nmaas/portal/persistent/entity/DomainGroup.java
+++ b/src/main/java/net/geant/nmaas/portal/persistent/entity/DomainGroup.java
@@ -67,7 +67,7 @@ public class DomainGroup implements Serializable {
     public DomainGroup(String name, String codename) {
         super();
         this.name = name;
-        this.codename = name;
+        this.codename = codename;
     }
 
     public DomainGroup(Long id, String name, String codename) {
diff --git a/src/main/java/net/geant/nmaas/portal/persistent/repositories/AppInstanceRepository.java b/src/main/java/net/geant/nmaas/portal/persistent/repositories/AppInstanceRepository.java
index 2208598413ba01837521d2a0ee46fb809f0a7928..3002af2e17a43f61ef9bb1fcfb515436054de9d5 100644
--- a/src/main/java/net/geant/nmaas/portal/persistent/repositories/AppInstanceRepository.java
+++ b/src/main/java/net/geant/nmaas/portal/persistent/repositories/AppInstanceRepository.java
@@ -49,11 +49,14 @@ public interface AppInstanceRepository extends JpaRepository<AppInstance, Long>
 	@Query("select count(ai.id) FROM AppInstance ai  where ai.createdAt >= :sinceTime")
 	int countAllDeployedSinceTime(@Param("sinceTime") long sinceTime);
 
+	@Query("select count(ai.id) FROM AppInstance ai  where ai.createdAt >= :sinceTime AND ai.createdAt <= :endTime")
+	int countAllDeployedSinceTime(@Param("sinceTime") long sinceTime, @Param("sinceTime") long toTime);
+
 	@Query("select count(ai.id) FROM AppInstance ai JOIN AppDeployment ad on ad.deploymentId = ai.internalId where ai.application.name = ?1")
 	int countByName(String name);
 
-	@Query("select ai FROM AppInstance ai where ai.createdAt >= :sinceTime")
-	List<AppInstance> findAllInTimePeriod(@Param("sinceTime") long sinceTime);
+	@Query("select ai FROM AppInstance ai where ai.createdAt >= :sinceTime AND ai.createdAt <= :endTime")
+	List<AppInstance> findAllInTimePeriod(@Param("sinceTime") long start, @Param("endTime") long end);
 
 	int countAllByOwner(User user);
 
diff --git a/src/main/java/net/geant/nmaas/portal/service/ApplicationBaseService.java b/src/main/java/net/geant/nmaas/portal/service/ApplicationBaseService.java
index 0f6ce93f96b401c9bada376339e903d6556bb9e0..18aba500d32976210be1fa1057164f939898d0ba 100644
--- a/src/main/java/net/geant/nmaas/portal/service/ApplicationBaseService.java
+++ b/src/main/java/net/geant/nmaas/portal/service/ApplicationBaseService.java
@@ -1,6 +1,5 @@
 package net.geant.nmaas.portal.service;
 
-import net.geant.nmaas.portal.api.domain.ApplicationBaseS;
 import net.geant.nmaas.portal.api.domain.ApplicationBaseViewS;
 import net.geant.nmaas.portal.persistent.entity.ApplicationBase;
 import net.geant.nmaas.portal.persistent.entity.ApplicationState;
@@ -10,6 +9,7 @@ import java.util.List;
 public interface ApplicationBaseService {
 
     ApplicationBase create(ApplicationBase applicationBase);
+
     ApplicationBase update(ApplicationBase applicationBase);
 
     ApplicationBase updateOwner(Long id, String owner);
@@ -17,14 +17,17 @@ public interface ApplicationBaseService {
     void updateApplicationVersionState(String name, String version, ApplicationState state);
 
     List<ApplicationBase> findAll();
+
     List<ApplicationBase> findAllActiveApps();
 
     List<ApplicationBaseViewS> findAllActiveAppsSmall();
 
     ApplicationBase getBaseApp(Long id);
+
     ApplicationBase findByName(String name);
 
     boolean exists(String name);
+
     boolean isAppActive(ApplicationBase application);
 
     void deleteAppBase(ApplicationBase base);
diff --git a/src/main/java/net/geant/nmaas/portal/service/DashboardService.java b/src/main/java/net/geant/nmaas/portal/service/DashboardService.java
index 53307d2275d9f4d2aea107fb5a959869c671a137..2c172aacac41010fb4b3e072ec1eb70b6afde746 100644
--- a/src/main/java/net/geant/nmaas/portal/service/DashboardService.java
+++ b/src/main/java/net/geant/nmaas/portal/service/DashboardService.java
@@ -3,9 +3,11 @@ package net.geant.nmaas.portal.service;
 import net.geant.nmaas.portal.api.info.DashboardView;
 import net.geant.nmaas.portal.api.info.DomainDashboardView;
 
+import java.time.OffsetDateTime;
+
 public interface DashboardService {
 
-    public DashboardView getSystemDashboard();
+    public DashboardView getSystemDashboard(OffsetDateTime startDate, OffsetDateTime endDate);
 
     public DomainDashboardView getSystemDomainDashboard(Long domainId);
 }
diff --git a/src/main/java/net/geant/nmaas/portal/service/impl/ApplicationBaseServiceImpl.java b/src/main/java/net/geant/nmaas/portal/service/impl/ApplicationBaseServiceImpl.java
index 0231ea424db52d97c5655dbd9a8e316598c9b0e3..5e77ddf1b9091ef1bf47c1e68634d9a8731267d5 100644
--- a/src/main/java/net/geant/nmaas/portal/service/impl/ApplicationBaseServiceImpl.java
+++ b/src/main/java/net/geant/nmaas/portal/service/impl/ApplicationBaseServiceImpl.java
@@ -1,10 +1,8 @@
 package net.geant.nmaas.portal.service.impl;
 
 import jakarta.transaction.Transactional;
-import lombok.AllArgsConstructor;
-import lombok.extern.log4j.Log4j2;
+import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
-import net.geant.nmaas.portal.api.domain.AppDescriptionView;
 import net.geant.nmaas.portal.api.domain.ApplicationBaseS;
 import net.geant.nmaas.portal.api.domain.ApplicationBaseViewS;
 import net.geant.nmaas.portal.api.exception.MissingElementException;
@@ -21,7 +19,6 @@ import net.geant.nmaas.portal.service.ApplicationStatePerDomainService;
 import net.geant.nmaas.portal.service.DomainService;
 import org.apache.commons.lang3.StringUtils;
 import org.modelmapper.ModelMapper;
-import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.cache.annotation.CacheEvict;
 import org.springframework.cache.annotation.CachePut;
 import org.springframework.context.ApplicationEventPublisher;
@@ -36,7 +33,7 @@ import java.util.Optional;
 import java.util.stream.Collectors;
 
 @Service
-@AllArgsConstructor
+@RequiredArgsConstructor
 @Slf4j
 public class ApplicationBaseServiceImpl implements ApplicationBaseService {
 
@@ -47,9 +44,7 @@ public class ApplicationBaseServiceImpl implements ApplicationBaseService {
     private final ApplicationStatePerDomainService applicationStatePerDomainService;
     private final ApplicationEventPublisher eventPublisher;
     private final DomainService domainService;
-
-    @Autowired
-    protected ModelMapper modelMapper;
+    private final ModelMapper modelMapper;
 
     @Override
     @Transactional
diff --git a/src/main/java/net/geant/nmaas/portal/service/impl/ConfigurationManagerImpl.java b/src/main/java/net/geant/nmaas/portal/service/impl/ConfigurationManagerImpl.java
index 9c4252840c81db87da30490f2698a559f11d3d89..2c7c5427605a31c98b52b510ae0989c577862936 100644
--- a/src/main/java/net/geant/nmaas/portal/service/impl/ConfigurationManagerImpl.java
+++ b/src/main/java/net/geant/nmaas/portal/service/impl/ConfigurationManagerImpl.java
@@ -10,7 +10,7 @@ import net.geant.nmaas.portal.persistent.entity.InternationalizationSimple;
 import net.geant.nmaas.portal.persistent.repositories.ConfigurationRepository;
 import net.geant.nmaas.portal.persistent.repositories.InternationalizationSimpleRepository;
 import net.geant.nmaas.portal.service.ConfigurationManager;
-import net.geant.nmaas.scheduling.BulkDeploymentScheduleConfig;
+import net.geant.nmaas.scheduling.BulkDeploymentScheduleInit;
 import net.geant.nmaas.scheduling.ScheduleManager;
 import org.modelmapper.ModelMapper;
 import org.springframework.stereotype.Component;
@@ -57,8 +57,8 @@ public class ConfigurationManagerImpl implements ConfigurationManager {
         }
         if (!updatedConfiguration.getBulkDeploymentJobCron().equalsIgnoreCase(configuration.get().getBulkDeploymentJobCron())) {
             // job needs to be recreated
-            scheduleManager.deleteJob(BulkDeploymentScheduleConfig.BULK_DEPLOYMENT_JOB);
-            scheduleManager.createJob(bulkDeploymentJob, BulkDeploymentScheduleConfig.BULK_DEPLOYMENT_JOB, updatedConfiguration.getBulkDeploymentJobCron());
+            scheduleManager.deleteJob(BulkDeploymentScheduleInit.BULK_DEPLOYMENT_JOB);
+            scheduleManager.createJob(bulkDeploymentJob, BulkDeploymentScheduleInit.BULK_DEPLOYMENT_JOB, updatedConfiguration.getBulkDeploymentJobCron());
         }
         repository.save(modelMapper.map(updatedConfiguration, Configuration.class));
     }
diff --git a/src/main/java/net/geant/nmaas/portal/service/impl/DashboardServiceImpl.java b/src/main/java/net/geant/nmaas/portal/service/impl/DashboardServiceImpl.java
index 1569cfee20dba94bf5c523dcffd7610d72c12bd3..420bf728bc72fac8b601463b616f1e3c4c739945 100644
--- a/src/main/java/net/geant/nmaas/portal/service/impl/DashboardServiceImpl.java
+++ b/src/main/java/net/geant/nmaas/portal/service/impl/DashboardServiceImpl.java
@@ -47,14 +47,16 @@ public class DashboardServiceImpl implements DashboardService {
 
 
     @Override
-    public DashboardView getSystemDashboard() {
+    public DashboardView getSystemDashboard(OffsetDateTime startDate, OffsetDateTime endDate) {
+
+        long startTimeStamp = System.currentTimeMillis() - startDate.toEpochSecond();
+        long endTimeStamp = System.currentTimeMillis() - endDate.toEpochSecond();
 
-        long weekTimestamp = System.currentTimeMillis() - Duration.ofDays(7).toMillis();
 
         List<String> baseNames = applicationBaseRepository.findAllNames();
         Map<String, Integer> applicationDeploymentCountPerName = new HashMap<>();
 
-        List<DashboardDeploymentsView> deploymentsViews = appInstanceRepo.findAllInTimePeriod(weekTimestamp)
+        List<DashboardDeploymentsView> deploymentsViews = appInstanceRepo.findAllInTimePeriod(startTimeStamp, endTimeStamp)
                 .stream().map(entry -> DashboardDeploymentsView.builder().user(entry.getOwner().getUsername())
                         .domainName(entry.getDomain().getName())
                         .applicationName(entry.getApplication().getName())
@@ -72,7 +74,7 @@ public class DashboardServiceImpl implements DashboardService {
                 .domainsCount(domainRepository.count())
                 .userCount(userRepository.count())
                 .instanceCount(appInstanceRepo.count())
-                .instanceCountInPeriod(appInstanceRepo.countAllDeployedSinceTime(weekTimestamp))
+                .instanceCountInPeriod(appInstanceRepo.countAllDeployedSinceTime(startTimeStamp, endTimeStamp))
                 .instanceCountInPeriodDetails(deploymentsViews)
                 .popularApps(applicationDeploymentCountPerName).build();
     }
diff --git a/src/main/java/net/geant/nmaas/portal/service/impl/DomainGroupServiceImpl.java b/src/main/java/net/geant/nmaas/portal/service/impl/DomainGroupServiceImpl.java
index 3dbc1baf929b1624f7abf0b246166ea6966ce1e9..2823c728ce858495ce6f708d8968d072848e1503 100644
--- a/src/main/java/net/geant/nmaas/portal/service/impl/DomainGroupServiceImpl.java
+++ b/src/main/java/net/geant/nmaas/portal/service/impl/DomainGroupServiceImpl.java
@@ -2,6 +2,8 @@ package net.geant.nmaas.portal.service.impl;
 
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
+import net.geant.nmaas.orchestration.jobs.DomainCreationJob;
+import net.geant.nmaas.orchestration.jobs.DomainGroupJob;
 import net.geant.nmaas.portal.api.domain.ApplicationStatePerDomainView;
 import net.geant.nmaas.portal.api.domain.DomainGroupView;
 import net.geant.nmaas.portal.api.exception.MissingElementException;
@@ -11,16 +13,21 @@ import net.geant.nmaas.portal.persistent.entity.ApplicationStatePerDomain;
 import net.geant.nmaas.portal.persistent.entity.Domain;
 import net.geant.nmaas.portal.persistent.entity.DomainGroup;
 import net.geant.nmaas.portal.persistent.entity.User;
+import net.geant.nmaas.portal.persistent.entity.WebhookEventType;
 import net.geant.nmaas.portal.persistent.repositories.DomainGroupRepository;
+import net.geant.nmaas.portal.persistent.repositories.WebhookEventRepository;
 import net.geant.nmaas.portal.service.ApplicationStatePerDomainService;
 import net.geant.nmaas.portal.service.DomainGroupService;
+import net.geant.nmaas.scheduling.ScheduleManager;
 import org.apache.commons.lang3.StringUtils;
 import org.modelmapper.ModelMapper;
 import org.springframework.stereotype.Service;
 
+import java.time.LocalDateTime;
 import java.util.ArrayList;
 import java.util.Iterator;
 import java.util.List;
+import java.util.Map;
 import java.util.Optional;
 import java.util.stream.Collectors;
 
@@ -31,6 +38,8 @@ public class DomainGroupServiceImpl implements DomainGroupService {
 
     private final DomainGroupRepository domainGroupRepository;
     private final ApplicationStatePerDomainService applicationStatePerDomainService;
+    private final WebhookEventRepository webhookEventRepository;
+    private final ScheduleManager scheduleManager;
 
 
     private final ModelMapper modelMapper;
@@ -55,6 +64,10 @@ public class DomainGroupServiceImpl implements DomainGroupService {
         DomainGroup domainGroupEntity = modelMapper.map(domainGroup, DomainGroup.class);
         domainGroupEntity.setApplicationStatePerDomain(applicationStatePerDomainList);
         domainGroupEntity = domainGroupRepository.save(domainGroupEntity);
+
+        //call existing webhooks
+        DomainGroupView domainGroupView = modelMapper.map(domainGroupEntity, DomainGroupView.class);
+        webhookEventRepository.findIdByEventType(WebhookEventType.DOMAIN_GROUP_CHANGE).forEach(id -> scheduleManager.createOneTimeJob(DomainGroupJob.class, "DomainGroup_" + id + "_" + domainGroupView.getId()+ "_" + LocalDateTime.now(), Map.of("webhookId", id, "action", "create","domainGroup",domainGroupView)));
         return modelMapper.map(domainGroupEntity, DomainGroupView.class);
     }
 
@@ -81,6 +94,7 @@ public class DomainGroupServiceImpl implements DomainGroupService {
     @Override
     public void deleteDomainGroup(Long domainGroupId) {
         DomainGroup domainGroup = domainGroupRepository.findById(domainGroupId).orElseThrow();
+        DomainGroupView domainGroupView = modelMapper.map(domainGroup, DomainGroupView.class);
         List<Domain> toRemove = new ArrayList<>(domainGroup.getDomains());
         Iterator<Domain> iterator = toRemove.iterator();
         while (iterator.hasNext()) {
@@ -90,6 +104,8 @@ public class DomainGroupServiceImpl implements DomainGroupService {
             iterator.remove();
         }
         domainGroupRepository.deleteById(domainGroupId);
+        //call existing webhooks
+        webhookEventRepository.findIdByEventType(WebhookEventType.DOMAIN_GROUP_CHANGE).forEach(id -> scheduleManager.createOneTimeJob(DomainGroupJob.class, "DomainGroup_" + id + "_" + domainGroup.getId()+ "_" + LocalDateTime.now(), Map.of("webhookId", id, "action", "delete","domainGroup",domainGroupView)));
     }
 
     @Override
@@ -128,7 +144,11 @@ public class DomainGroupServiceImpl implements DomainGroupService {
         }
 
         domainGroupRepository.save(domainGroup);
-        return modelMapper.map(domainGroup, DomainGroupView.class);
+
+        //call existing webhooks
+        DomainGroupView domainGroupView = modelMapper.map(domainGroup, DomainGroupView.class);
+        webhookEventRepository.findIdByEventType(WebhookEventType.DOMAIN_GROUP_CHANGE).forEach(id -> scheduleManager.createOneTimeJob(DomainGroupJob.class, "DomainGroup_" + id + "_" + domainGroupView.getId()+ "_" + LocalDateTime.now(), Map.of("webhookId", id, "action", "update","domainGroup",domainGroupView)));
+        return domainGroupView;
     }
 
     protected void checkParam(DomainGroupView domainGroup) {
diff --git a/src/main/java/net/geant/nmaas/scheduling/AppUpgradeScheduleConfig.java b/src/main/java/net/geant/nmaas/scheduling/AppUpgradeScheduleConfig.java
deleted file mode 100644
index 79e7298c15cd0e4477bf5376d357edf0ad71511a..0000000000000000000000000000000000000000
--- a/src/main/java/net/geant/nmaas/scheduling/AppUpgradeScheduleConfig.java
+++ /dev/null
@@ -1,64 +0,0 @@
-package net.geant.nmaas.scheduling;
-
-import com.google.common.base.Strings;
-import lombok.extern.slf4j.Slf4j;
-import net.geant.nmaas.nmservice.deployment.bulks.BulkDeploymentJob;
-import net.geant.nmaas.orchestration.AppUpgradeSummaryJob;
-import net.geant.nmaas.orchestration.AppUpgradeTriggerJob;
-import org.springframework.beans.factory.InitializingBean;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.beans.factory.annotation.Value;
-import org.springframework.context.annotation.Bean;
-import org.springframework.context.annotation.Configuration;
-import org.springframework.transaction.annotation.Transactional;
-
-@Configuration
-@Slf4j
-public class AppUpgradeScheduleConfig {
-
-    private static final String APP_UPGRADE_JOB_NAME = "AppUpgradeJob";
-    private static final String APP_UPGRADE_SUMMARY_JOB_NAME = "AppUpgradeSummaryJob";
-
-
-    @Bean
-    public InitializingBean insertDefaultAppUpgradeJob() {
-        return new InitializingBean() {
-
-            @Autowired
-            private AppUpgradeTriggerJob appUpgradeTriggerJob;
-
-            @Autowired
-            private AppUpgradeSummaryJob appUpgradeSummaryJob;
-
-            @Autowired
-            private BulkDeploymentJob bulkDeploymentJob;
-
-            @Autowired
-            private ScheduleManager scheduleManager;
-
-            @Value("${nmaas.service.upgrade.cron}")
-            String appUpgradeCron;
-
-            @Value("${nmaas.service.upgrade-summary.cron}")
-            String appUpgradeSummaryCron;
-
-            @Override
-            @Transactional
-            public void afterPropertiesSet() {
-                if (Strings.isNullOrEmpty(appUpgradeCron)) {
-                    log.warn("Application upgrade cron expression not provided");
-                    log.warn("Automatic application upgrades are disabled!");
-                } else {
-                    this.scheduleManager.createJob(appUpgradeTriggerJob, APP_UPGRADE_JOB_NAME, appUpgradeCron);
-                }
-                if (Strings.isNullOrEmpty(appUpgradeSummaryCron)) {
-                    log.warn("Application upgrade summary cron expression not provided");
-                    log.warn("Won't send out email notifications about automatic upgrades in given period");
-                } else {
-                    this.scheduleManager.createJob(appUpgradeSummaryJob, APP_UPGRADE_SUMMARY_JOB_NAME, appUpgradeSummaryCron);
-                }
-            }
-        };
-    }
-
-}
diff --git a/src/main/java/net/geant/nmaas/scheduling/AppUpgradeScheduleInit.java b/src/main/java/net/geant/nmaas/scheduling/AppUpgradeScheduleInit.java
new file mode 100644
index 0000000000000000000000000000000000000000..0558c7f8db4e1f79a8ecc26d91f7754c4c19e661
--- /dev/null
+++ b/src/main/java/net/geant/nmaas/scheduling/AppUpgradeScheduleInit.java
@@ -0,0 +1,56 @@
+package net.geant.nmaas.scheduling;
+
+import com.google.common.base.Strings;
+import lombok.extern.slf4j.Slf4j;
+import net.geant.nmaas.orchestration.AppUpgradeSummaryJob;
+import net.geant.nmaas.orchestration.AppUpgradeTriggerJob;
+import org.springframework.beans.factory.InitializingBean;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Component;
+import org.springframework.transaction.annotation.Transactional;
+
+@Component
+@Slf4j
+public class AppUpgradeScheduleInit implements InitializingBean {
+
+    private static final String APP_UPGRADE_JOB_NAME = "AppUpgradeJob";
+    private static final String APP_UPGRADE_SUMMARY_JOB_NAME = "AppUpgradeSummaryJob";
+
+    private final AppUpgradeTriggerJob appUpgradeTriggerJob;
+    private final AppUpgradeSummaryJob appUpgradeSummaryJob;
+    private final ScheduleManager scheduleManager;
+    private final String appUpgradeCron;
+    private final String appUpgradeSummaryCron;
+
+    @Autowired
+    public AppUpgradeScheduleInit(AppUpgradeTriggerJob appUpgradeTriggerJob,
+                                  AppUpgradeSummaryJob appUpgradeSummaryJob,
+                                  ScheduleManager scheduleManager,
+                                  @Value("${nmaas.service.upgrade.cron}") String appUpgradeCron,
+                                  @Value("${nmaas.service.upgrade-summary.cron}") String appUpgradeSummaryCron) {
+        this.appUpgradeTriggerJob = appUpgradeTriggerJob;
+        this.appUpgradeSummaryJob = appUpgradeSummaryJob;
+        this.scheduleManager = scheduleManager;
+        this.appUpgradeCron = appUpgradeCron;
+        this.appUpgradeSummaryCron = appUpgradeSummaryCron;
+    }
+
+    @Override
+    @Transactional
+    public void afterPropertiesSet() {
+        if (Strings.isNullOrEmpty(appUpgradeCron)) {
+            log.warn("Application upgrade cron expression not provided");
+            log.warn("Automatic application upgrades are disabled!");
+        } else {
+            this.scheduleManager.createJob(appUpgradeTriggerJob, APP_UPGRADE_JOB_NAME, appUpgradeCron);
+        }
+        if (Strings.isNullOrEmpty(appUpgradeSummaryCron)) {
+            log.warn("Application upgrade summary cron expression not provided");
+            log.warn("Won't send out email notifications about automatic upgrades in given period");
+        } else {
+            this.scheduleManager.createJob(appUpgradeSummaryJob, APP_UPGRADE_SUMMARY_JOB_NAME, appUpgradeSummaryCron);
+        }
+    }
+
+}
\ No newline at end of file
diff --git a/src/main/java/net/geant/nmaas/scheduling/BulkDeploymentScheduleConfig.java b/src/main/java/net/geant/nmaas/scheduling/BulkDeploymentScheduleConfig.java
deleted file mode 100644
index dfa061644f71810f0debdc1968078caad54451bd..0000000000000000000000000000000000000000
--- a/src/main/java/net/geant/nmaas/scheduling/BulkDeploymentScheduleConfig.java
+++ /dev/null
@@ -1,54 +0,0 @@
-package net.geant.nmaas.scheduling;
-
-import com.google.common.base.Strings;
-import lombok.extern.slf4j.Slf4j;
-import net.geant.nmaas.nmservice.deployment.bulks.BulkDeploymentJob;
-import net.geant.nmaas.portal.service.ConfigurationManager;
-import org.springframework.beans.factory.InitializingBean;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.beans.factory.annotation.Value;
-import org.springframework.context.annotation.Bean;
-import org.springframework.context.annotation.Configuration;
-import org.springframework.context.annotation.DependsOn;
-import org.springframework.transaction.annotation.Transactional;
-
-@Configuration
-@Slf4j
-public class BulkDeploymentScheduleConfig {
-
-    public static final String BULK_DEPLOYMENT_JOB = "BulkDeploymentJob";
-
-    @Bean
-    @DependsOn({"portalConfiguration"})
-    public InitializingBean insertDefaultBulkDeploymentJob() {
-        return new InitializingBean() {
-
-            @Autowired
-            private BulkDeploymentJob bulkDeploymentJob;
-
-            @Autowired
-            private ScheduleManager scheduleManager;
-
-            @Autowired
-            private ConfigurationManager configurationManager;
-
-            @Value("${nmaas.service.bulk-deployment.cron}")
-            String bulkDeploymentCron;
-
-            @Override
-            @Transactional
-            public void afterPropertiesSet() {
-                String bulkDeploymentCronFromDb = configurationManager.getConfiguration().getBulkDeploymentJobCron();
-                if (!Strings.isNullOrEmpty(bulkDeploymentCronFromDb)) {
-                    log.debug("Scheduling bulk deployment job based on cron loaded from the database");
-                    this.scheduleManager.createJob(bulkDeploymentJob, BULK_DEPLOYMENT_JOB, bulkDeploymentCronFromDb);
-                } else if (Strings.isNullOrEmpty(bulkDeploymentCron)) {
-                    log.warn("Bulk deployment cron expression not provided");
-                } else {
-                    this.scheduleManager.createJob(bulkDeploymentJob, BULK_DEPLOYMENT_JOB, bulkDeploymentCron);
-                }
-            }
-        };
-    }
-
-}
\ No newline at end of file
diff --git a/src/main/java/net/geant/nmaas/scheduling/BulkDeploymentScheduleInit.java b/src/main/java/net/geant/nmaas/scheduling/BulkDeploymentScheduleInit.java
new file mode 100644
index 0000000000000000000000000000000000000000..25e6b52bce83d21ad2e55b368cd536a6f4e7bfef
--- /dev/null
+++ b/src/main/java/net/geant/nmaas/scheduling/BulkDeploymentScheduleInit.java
@@ -0,0 +1,51 @@
+package net.geant.nmaas.scheduling;
+
+import com.google.common.base.Strings;
+import lombok.extern.slf4j.Slf4j;
+import net.geant.nmaas.nmservice.deployment.bulks.BulkDeploymentJob;
+import net.geant.nmaas.portal.service.ConfigurationManager;
+import org.springframework.beans.factory.InitializingBean;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.annotation.DependsOn;
+import org.springframework.stereotype.Component;
+import org.springframework.transaction.annotation.Transactional;
+
+@Component
+@DependsOn({"portalConfiguration"})
+@Slf4j
+public class BulkDeploymentScheduleInit implements InitializingBean {
+
+    public static final String BULK_DEPLOYMENT_JOB = "BulkDeploymentJob";
+
+    private final BulkDeploymentJob bulkDeploymentJob;
+    private final ScheduleManager scheduleManager;
+    private final ConfigurationManager configurationManager;
+    private final String bulkDeploymentCron;
+
+    @Autowired
+    public BulkDeploymentScheduleInit(BulkDeploymentJob bulkDeploymentJob,
+                                      ScheduleManager scheduleManager,
+                                      ConfigurationManager configurationManager,
+                                      @Value("${nmaas.service.bulk-deployment.cron}") String bulkDeploymentCron) {
+        this.bulkDeploymentJob = bulkDeploymentJob;
+        this.scheduleManager = scheduleManager;
+        this.configurationManager = configurationManager;
+        this.bulkDeploymentCron = bulkDeploymentCron;
+    }
+
+    @Override
+    @Transactional
+    public void afterPropertiesSet() {
+        String bulkDeploymentCronFromDb = configurationManager.getConfiguration().getBulkDeploymentJobCron();
+        if (!Strings.isNullOrEmpty(bulkDeploymentCronFromDb)) {
+            log.debug("Scheduling bulk deployment job based on cron loaded from the database");
+            this.scheduleManager.createJob(bulkDeploymentJob, BULK_DEPLOYMENT_JOB, bulkDeploymentCronFromDb);
+        } else if (Strings.isNullOrEmpty(bulkDeploymentCron)) {
+            log.warn("Bulk deployment cron expression not provided");
+        } else {
+            this.scheduleManager.createJob(bulkDeploymentJob, BULK_DEPLOYMENT_JOB, bulkDeploymentCron);
+        }
+    }
+
+}
\ No newline at end of file
diff --git a/src/main/java/net/geant/nmaas/scheduling/ClusterHealthCheckInit.java b/src/main/java/net/geant/nmaas/scheduling/ClusterHealthCheckScheduleInit.java
similarity index 82%
rename from src/main/java/net/geant/nmaas/scheduling/ClusterHealthCheckInit.java
rename to src/main/java/net/geant/nmaas/scheduling/ClusterHealthCheckScheduleInit.java
index aff057a2f1baedee36b5cad8b1715713aba7f42f..e92a04e3ee42bd7f29520167f0f47f38a7837c36 100644
--- a/src/main/java/net/geant/nmaas/scheduling/ClusterHealthCheckInit.java
+++ b/src/main/java/net/geant/nmaas/scheduling/ClusterHealthCheckScheduleInit.java
@@ -14,7 +14,7 @@ import org.springframework.transaction.annotation.Transactional;
 @Component
 @DependsOn({"portalConfiguration"})
 @Slf4j
-public class ClusterHealthCheckInit implements InitializingBean {
+public class ClusterHealthCheckScheduleInit implements InitializingBean {
 
     public static final String CLUSTER_HEALTH_CHECK = "ClusterHealthCheck";
 
@@ -24,10 +24,10 @@ public class ClusterHealthCheckInit implements InitializingBean {
     private final String healthCheckJobCron;
 
     @Autowired
-    public ClusterHealthCheckInit(ClusterMonitoringJob clusterMonitoringJob,
-                                  ScheduleManager scheduleManager,
-                                  ConfigurationManager configurationManager,
-                                  @Value("${nmaas.service.health-check.cron}") String healthCheckJobCron) {
+    public ClusterHealthCheckScheduleInit(ClusterMonitoringJob clusterMonitoringJob,
+                                          ScheduleManager scheduleManager,
+                                          ConfigurationManager configurationManager,
+                                          @Value("${nmaas.service.health-check.cron}") String healthCheckJobCron) {
         this.clusterMonitoringJob = clusterMonitoringJob;
         this.scheduleManager = scheduleManager;
         this.configurationManager = configurationManager;
diff --git a/src/main/java/net/geant/nmaas/scheduling/JobDescriptor.java b/src/main/java/net/geant/nmaas/scheduling/JobDescriptor.java
index da683123c09f6ac0c979a88c17e41666649869c7..59500eecefc6b74988164bc69154d0454f10087f 100644
--- a/src/main/java/net/geant/nmaas/scheduling/JobDescriptor.java
+++ b/src/main/java/net/geant/nmaas/scheduling/JobDescriptor.java
@@ -1,25 +1,13 @@
 package net.geant.nmaas.scheduling;
 
-import lombok.AllArgsConstructor;
-import lombok.Getter;
-import lombok.NoArgsConstructor;
-import lombok.Setter;
 import net.geant.nmaas.monitor.ServiceType;
 import net.geant.nmaas.monitor.TimeFormat;
 import org.quartz.Trigger;
 
-@Getter
-@Setter
-@NoArgsConstructor
-@AllArgsConstructor
-public class JobDescriptor {
-    private ServiceType serviceName;
-
-    private Long checkInterval;
-
-    private TimeFormat timeFormat;
+public record JobDescriptor(ServiceType serviceName, Long checkInterval, TimeFormat timeFormat) {
 
     Trigger buildTrigger() {
         return new TriggerDescriptor(serviceName, checkInterval, timeFormat).buildTrigger();
     }
+
 }
diff --git a/src/main/java/net/geant/nmaas/scheduling/MonitorScheduleInit.java b/src/main/java/net/geant/nmaas/scheduling/MonitorScheduleInit.java
new file mode 100644
index 0000000000000000000000000000000000000000..27066a19a948d88f9673464419cd7207b814495b
--- /dev/null
+++ b/src/main/java/net/geant/nmaas/scheduling/MonitorScheduleInit.java
@@ -0,0 +1,51 @@
+package net.geant.nmaas.scheduling;
+
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import net.geant.nmaas.monitor.MonitorManager;
+import net.geant.nmaas.monitor.MonitorService;
+import net.geant.nmaas.monitor.ServiceType;
+import net.geant.nmaas.monitor.model.MonitorEntryView;
+import org.springframework.beans.factory.InitializingBean;
+import org.springframework.stereotype.Component;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.Optional;
+
+@Component
+@RequiredArgsConstructor
+@Slf4j
+public class MonitorScheduleInit implements InitializingBean {
+
+    private final List<MonitorService> monitorServices;
+    private final ScheduleManager scheduleManager;
+    private final MonitorManager monitorManager;
+
+    @Override
+    @Transactional
+    public void afterPropertiesSet() {
+        Arrays.stream(ServiceType.values())
+                .filter(serviceType -> !scheduleManager.jobExists(serviceType.toString())) // if job does not exist
+                .forEach(serviceType -> {
+                    MonitorEntryView monitorEntry;
+                    if (monitorManager.existsByServiceName(serviceType)) { // if entry exists
+                        monitorEntry = monitorManager.getMonitorEntries(serviceType.toString()); // read it from database
+                    } else {
+                        monitorEntry = serviceType.getDefaultMonitorEntry(); // if entry does not exist
+                        monitorManager.createMonitorEntry(monitorEntry); // create new default entry
+                    }
+                    Optional<MonitorService> service = monitorServices.stream()
+                            .filter(s -> s.getServiceType().equals(serviceType))
+                            .filter(MonitorService::schedulable)
+                            .findFirst();
+                    if (service.isPresent()) {
+                        scheduleManager.createJob(service.get(), monitorEntry);
+                    } else {
+                        log.warn("Monitor service for {} not found or is not schedulable", serviceType);
+                    }
+                });
+    }
+
+}
\ No newline at end of file
diff --git a/src/main/java/net/geant/nmaas/scheduling/OneTimeJobListener.java b/src/main/java/net/geant/nmaas/scheduling/OneTimeJobListener.java
index 343943a3fba7fd557da39da7ff04019173ca6b6f..fad1d93347ce67d81a9575aa7332971201000951 100644
--- a/src/main/java/net/geant/nmaas/scheduling/OneTimeJobListener.java
+++ b/src/main/java/net/geant/nmaas/scheduling/OneTimeJobListener.java
@@ -15,6 +15,7 @@ import org.quartz.TriggerBuilder;
 @RequiredArgsConstructor
 @Slf4j
 public class OneTimeJobListener implements JobListener {
+
     private final Scheduler scheduler;
     private final JobKey jobKey;
 
@@ -24,10 +25,12 @@ public class OneTimeJobListener implements JobListener {
     }
 
     @Override
-    public void jobToBeExecuted(JobExecutionContext context) {}
+    public void jobToBeExecuted(JobExecutionContext context) {
+    }
 
     @Override
-    public void jobExecutionVetoed(JobExecutionContext context) {}
+    public void jobExecutionVetoed(JobExecutionContext context) {
+    }
 
     @Override
     public void jobWasExecuted(JobExecutionContext context, JobExecutionException jobException) {
diff --git a/src/main/java/net/geant/nmaas/scheduling/ScheduleManager.java b/src/main/java/net/geant/nmaas/scheduling/ScheduleManager.java
index bea3ad79324a45e30ef9be8f3936c71904dfaae9..d1bb8a301e6cf19fd619cc51ecd38b5c5f312f52 100644
--- a/src/main/java/net/geant/nmaas/scheduling/ScheduleManager.java
+++ b/src/main/java/net/geant/nmaas/scheduling/ScheduleManager.java
@@ -2,11 +2,9 @@ package net.geant.nmaas.scheduling;
 
 import com.google.common.collect.ImmutableSet;
 import lombok.RequiredArgsConstructor;
-import lombok.extern.log4j.Log4j2;
 import lombok.extern.slf4j.Slf4j;
 import net.geant.nmaas.monitor.MonitorService;
 import net.geant.nmaas.monitor.model.MonitorEntryView;
-import net.geant.nmaas.orchestration.jobs.WebhookJob;
 import org.quartz.Job;
 import org.quartz.JobDataMap;
 import org.quartz.JobDetail;
@@ -45,13 +43,13 @@ public class ScheduleManager {
         JobDescriptor jobDescriptor = new JobDescriptor(monitorEntryView.getServiceName(), monitorEntryView.getCheckInterval(), monitorEntryView.getTimeFormat());
         validateJobDescriptor(jobDescriptor);
         try {
-            if (scheduler.checkExists(jobKey(jobDescriptor.getServiceName().getName()))) {
-                log.error("Job with name {} already exists", jobDescriptor.getServiceName());
-                throw new IllegalStateException(String.format("Job with name %s already exists", jobDescriptor.getServiceName()));
+            if (scheduler.checkExists(jobKey(jobDescriptor.serviceName().getName()))) {
+                log.error("Job with name {} already exists", jobDescriptor.serviceName());
+                throw new IllegalStateException(String.format("Job with name %s already exists", jobDescriptor.serviceName()));
             } else {
-                JobDetail jobDetail = newJob(service.getClass()).withIdentity(jobDescriptor.getServiceName().getName()).build();
+                JobDetail jobDetail = newJob(service.getClass()).withIdentity(jobDescriptor.serviceName().getName()).build();
                 Trigger trigger = jobDescriptor.buildTrigger();
-                log.info("Scheduling job: {}", jobDescriptor.getServiceName().toString());
+                log.info("Scheduling job: {}", jobDescriptor.serviceName().toString());
                 scheduler.scheduleJob(jobDetail, ImmutableSet.of(trigger), false);
             }
         } catch (SchedulerException e) {
@@ -80,10 +78,10 @@ public class ScheduleManager {
         JobDescriptor jobDescriptor = new JobDescriptor(monitorEntryView.getServiceName(), monitorEntryView.getCheckInterval(), monitorEntryView.getTimeFormat());
         validateJobDescriptor(jobDescriptor);
         try{
-            Trigger trigger = scheduler.getTrigger(TriggerKey.triggerKey(jobDescriptor.getServiceName().getName()));
+            Trigger trigger = scheduler.getTrigger(TriggerKey.triggerKey(jobDescriptor.serviceName().getName()));
             if (trigger != null) {
                 trigger = jobDescriptor.buildTrigger();
-                scheduler.rescheduleJob(TriggerKey.triggerKey(jobDescriptor.getServiceName().getName()), trigger);
+                scheduler.rescheduleJob(TriggerKey.triggerKey(jobDescriptor.serviceName().getName()), trigger);
                 if(!monitorEntryView.isActive()){
                     this.pauseJob(trigger.getJobKey().getName());
                 }
@@ -149,9 +147,9 @@ public class ScheduleManager {
     }
 
     private void validateJobDescriptor(JobDescriptor jobDescriptor){
-        if(jobDescriptor.getServiceName() == null)
+        if(jobDescriptor.serviceName() == null)
             throw new IllegalStateException("Service name cannot be null");
-        if(jobDescriptor.getCheckInterval() == null || jobDescriptor.getCheckInterval() <= 0)
+        if(jobDescriptor.checkInterval() == null || jobDescriptor.checkInterval() <= 0)
             throw new IllegalStateException("Check interval cannot be less or equal 0");
     }
 
diff --git a/src/main/java/net/geant/nmaas/scheduling/TriggerDescriptor.java b/src/main/java/net/geant/nmaas/scheduling/TriggerDescriptor.java
index 6a46fa6ffaaefee0e1f91375e0963993ceab1b09..4cd35788293b43d481bd60995e87443bfc0c28d3 100644
--- a/src/main/java/net/geant/nmaas/scheduling/TriggerDescriptor.java
+++ b/src/main/java/net/geant/nmaas/scheduling/TriggerDescriptor.java
@@ -28,22 +28,22 @@ public class TriggerDescriptor {
     private String createCronString() {
         if (timeFormat.equals(TimeFormat.H) && checkInterval.equals(24L)) {
             return "0 0 0 * * ?";
-        } else if(timeFormat.equals(TimeFormat.H)) {
+        } else if (timeFormat.equals(TimeFormat.H)) {
             return String.format("0 0 0/%d * * ?", checkInterval);
         }
         return String.format("0 0/%d * * * ?", checkInterval);
     }
 
     Trigger buildTrigger() {
-        if(cron != null && !cron.isEmpty()) {
-            if(!isValidExpression(cron)) {
+        if (cron != null && !cron.isEmpty()) {
+            if (!isValidExpression(cron)) {
                 throw new IllegalStateException("Provided interval is incorrect. You can choose from 1-59 minutes and 1-24 hours.");
             }
             return newTrigger()
                     .withIdentity(serviceName.getName())
                     .withSchedule(cronSchedule(cron)
-                                    .withMisfireHandlingInstructionFireAndProceed()
-                                    .inTimeZone(TimeZone.getTimeZone(ZoneId.systemDefault())))
+                            .withMisfireHandlingInstructionFireAndProceed()
+                            .inTimeZone(TimeZone.getTimeZone(ZoneId.systemDefault())))
                     .usingJobData("cron", cron)
                     .build();
         }
diff --git a/src/test/java/net/geant/nmaas/externalservices/kubernetes/ClusterServiceTest.java b/src/test/java/net/geant/nmaas/externalservices/kubernetes/ClusterServiceTest.java
index 817e7ec0b67c1c8d06117eb06012f88b3ee62112..3900192a944632388da86024f2e3b3f8e435c3ff 100644
--- a/src/test/java/net/geant/nmaas/externalservices/kubernetes/ClusterServiceTest.java
+++ b/src/test/java/net/geant/nmaas/externalservices/kubernetes/ClusterServiceTest.java
@@ -3,11 +3,7 @@ package net.geant.nmaas.externalservices.kubernetes;
 import net.geant.nmaas.externalservices.kubernetes.api.model.RemoteClusterView;
 import net.geant.nmaas.externalservices.kubernetes.entities.KCluster;
 import net.geant.nmaas.externalservices.kubernetes.repositories.KClusterRepository;
-import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
-import org.mockito.InjectMocks;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
 import org.modelmapper.ModelMapper;
 
 import java.util.List;
@@ -15,51 +11,41 @@ import java.util.Optional;
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
 class ClusterServiceTest {
 
-    @Mock
-    private KClusterRepository KClusterRepository;
-
-    @Mock
-    private KubernetesClusterIngressManager kClusterIngressManager;
-
-    @Mock
-    private KubernetesClusterDeploymentManager kClusterDeploymentManager;
-
-    @InjectMocks
-    private RemoteClusterManager remoteClusterManager;
-
+    private final KClusterRepository kClusterRepository = mock(KClusterRepository.class);
+    private final KubernetesClusterIngressManager kClusterIngressManager = mock(KubernetesClusterIngressManager.class);
+    private final KubernetesClusterDeploymentManager kClusterDeploymentManager = mock(KubernetesClusterDeploymentManager.class);
     private final ModelMapper modelMapper = new ModelMapper();
 
-    @BeforeEach
-    void setUp() {
-        MockitoAnnotations.openMocks(this);
-    }
+    private final RemoteClusterManager remoteClusterManager = new RemoteClusterManager(
+            kClusterRepository, kClusterIngressManager, kClusterDeploymentManager, null, null, modelMapper);
 
     @Test
     void getClusterView_validId_returnsRemoteClusterView() {
         Long id = 1L;
         KCluster remoteCluster = KCluster.builder().id(id).name("Cluster").description("Description").build();
         RemoteClusterView remoteClusterView = modelMapper.map(remoteCluster, RemoteClusterView.class);
-        when(KClusterRepository.findById(id)).thenReturn(Optional.of(remoteCluster));
+        when(kClusterRepository.findById(id)).thenReturn(Optional.of(remoteCluster));
 
         RemoteClusterView result = remoteClusterManager.getClusterView(id);
 
         assertEquals(remoteClusterView.getName(), result.getName());
-        verify(KClusterRepository, times(1)).findById(id);
+        verify(kClusterRepository, times(1)).findById(id);
     }
 
     @Test
     void getClusterView_invalidId_throwsException() {
         Long id = 100L;
-        when(KClusterRepository.findById(id)).thenReturn(Optional.empty());
+        when(kClusterRepository.findById(id)).thenReturn(Optional.empty());
 
         assertThrows(IllegalArgumentException.class, () -> remoteClusterManager.getClusterView(id));
-        verify(KClusterRepository, times(1)).findById(id);
+        verify(kClusterRepository, times(1)).findById(id);
     }
 
     @Test
@@ -67,12 +53,12 @@ class ClusterServiceTest {
         KCluster cluster1 = KCluster.builder().id(1L).name("Cluster1").build();
         KCluster cluster2 = KCluster.builder().id(2L).name("Cluster2").build();
 
-        when(KClusterRepository.findAll()).thenReturn(List.of(cluster1, cluster2));
+        when(kClusterRepository.findAll()).thenReturn(List.of(cluster1, cluster2));
 
         List<RemoteClusterView> result = remoteClusterManager.getAllClusterView();
 
         assertEquals(2, result.size());
-        verify(KClusterRepository, times(1)).findAll();
+        verify(kClusterRepository, times(1)).findAll();
     }
 
 //    @Test
diff --git a/src/test/java/net/geant/nmaas/nmservice/deployment/containerorchestrators/kubernetes/components/helm/HelmCommandExecutorTest.java b/src/test/java/net/geant/nmaas/nmservice/deployment/containerorchestrators/kubernetes/components/helm/HelmCommandExecutorTest.java
index e96d14b86a73f90cdd51edecd8e2759898a5a549..d9b0c8479e07ca3d17c2ee27fbb78d7dad11d15e 100644
--- a/src/test/java/net/geant/nmaas/nmservice/deployment/containerorchestrators/kubernetes/components/helm/HelmCommandExecutorTest.java
+++ b/src/test/java/net/geant/nmaas/nmservice/deployment/containerorchestrators/kubernetes/components/helm/HelmCommandExecutorTest.java
@@ -8,23 +8,20 @@ import static org.hamcrest.MatcherAssert.assertThat;
 public class HelmCommandExecutorTest {
 
     private static final String EXAMPLE_HELM_STATUS_COMMAND_OUTPUT =
-            "LAST DEPLOYED: Wed Dec  6 13:48:59 2017\n" +
-                    "NAMESPACE: default\n" +
-                    "STATUS: DEPLOYED\n" +
-                    "" +
-                    "RESOURCES:" +
-                    "==> v1/PersistentVolumeClaim" +
-                    "NAME                                  STATUS  VOLUME                                    CAPACITY  ACCESSMODES  STORAGECLASS         AGE" +
-                    "c21584cd-666c-42de-9df7-d72b7bae5aae  Bound   pvc-d4eb67d2-da83-11e7-bec9-5254002cd33f  1Gi       RWO          managed-nfs-storage  1m" +
-                    "" +
-                    "==> v1/Service" +
-                    "NAME                                                 CLUSTER-IP    EXTERNAL-IP  PORT(S)  AGE" +
-                    "c21584cd-666c-42de-9df7-d72b7bae5aae-nmaas-oxidized  10.13.82.246  <none>       80/TCP   1m" +
-                    "" +
-                    "==> v1beta1/Deployment" +
-                    "NAME                                                 DESIRED  CURRENT  UP-TO-DATE  AVAILABLE  AGE" +
-                    "c21584cd-666c-42de-9df7-d72b7bae5aae-nmaas-oxidized  1        1        1           1          1m" +
-                    "";
+            """
+                    LAST DEPLOYED: Wed Dec  6 13:48:59 2017
+                    NAMESPACE: default
+                    STATUS: DEPLOYED
+                    RESOURCES:\
+                    ==> v1/PersistentVolumeClaim\
+                    NAME                                  STATUS  VOLUME                                    CAPACITY  ACCESSMODES  STORAGECLASS         AGE\
+                    c21584cd-666c-42de-9df7-d72b7bae5aae  Bound   pvc-d4eb67d2-da83-11e7-bec9-5254002cd33f  1Gi       RWO          managed-nfs-storage  1m\
+                    ==> v1/Service\
+                    NAME                                                 CLUSTER-IP    EXTERNAL-IP  PORT(S)  AGE\
+                    c21584cd-666c-42de-9df7-d72b7bae5aae-nmaas-oxidized  10.13.82.246  <none>       80/TCP   1m\
+                    ==> v1beta1/Deployment\
+                    NAME                                                 DESIRED  CURRENT  UP-TO-DATE  AVAILABLE  AGE\
+                    c21584cd-666c-42de-9df7-d72b7bae5aae-nmaas-oxidized  1        1        1           1          1m""";
 
     private static final String EXAMPLE_HELM_STATUS_FOR_V3_COMMAND_OUTPUT =
             "NAME: testbastion\n" +
diff --git a/src/test/java/net/geant/nmaas/portal/api/info/DashboardControllerTest.java b/src/test/java/net/geant/nmaas/portal/api/info/DashboardControllerTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..bc11435c61a1723aa7a7f9cb4b467e0d1e33bfa6
--- /dev/null
+++ b/src/test/java/net/geant/nmaas/portal/api/info/DashboardControllerTest.java
@@ -0,0 +1,68 @@
+package net.geant.nmaas.portal.api.info;
+
+import net.geant.nmaas.portal.api.info.DashboardView;
+import net.geant.nmaas.portal.api.info.DomainDashboardView;
+import net.geant.nmaas.portal.service.DashboardService;
+import org.junit.jupiter.api.Test;
+
+import java.time.OffsetDateTime;
+
+import static org.junit.jupiter.api.Assertions.*;
+import static org.mockito.Mockito.*;
+
+public class DashboardControllerTest {
+
+    private final DashboardService dashboardService = mock(DashboardService.class);
+    private final DashboardController dashboardController = new DashboardController(dashboardService);
+
+    @Test
+    void shouldGetDashboardAdmin() {
+        OffsetDateTime start = OffsetDateTime.now().minusDays(1);
+        OffsetDateTime end = OffsetDateTime.now();
+        DashboardView dashboardView = DashboardView.builder().domainsCount(1L).build();
+
+        when(dashboardService.getSystemDashboard(start, end)).thenReturn(dashboardView);
+
+        DashboardView result = dashboardController.getDashboardAdmin(start, end);
+
+        assertNotNull(result);
+        assertEquals(1L, result.getDomainsCount());
+    }
+
+    @Test
+    void shouldGetDashboardDomain() {
+        Long domainId = 1L;
+        DomainDashboardView domainDashboardView = DomainDashboardView.builder().build();
+
+        when(dashboardService.getSystemDomainDashboard(domainId)).thenReturn(domainDashboardView);
+
+        DomainDashboardView result = dashboardController.getDashboardDomain(domainId);
+
+        assertNotNull(result);
+    }
+
+    @Test
+    void shouldThrowExceptionWhenStartDateIsNull() {
+        OffsetDateTime end = OffsetDateTime.now();
+        assertThrows(IllegalArgumentException.class, () -> {
+            dashboardController.getDashboardAdmin(null, end);
+        });
+    }
+
+    @Test
+    void shouldThrowExceptionWhenEndDateIsNull() {
+        OffsetDateTime start = OffsetDateTime.now();
+        assertThrows(IllegalArgumentException.class, () -> {
+            dashboardController.getDashboardAdmin(start, null);
+        });
+    }
+
+    @Test
+    void shouldThrowExceptionWhenStartDateAfterEndDate() {
+        OffsetDateTime start = OffsetDateTime.now();
+        OffsetDateTime end = start.minusDays(1);
+        assertThrows(IllegalArgumentException.class, () -> {
+            dashboardController.getDashboardAdmin(start, end);
+        });
+    }
+}
diff --git a/src/test/java/net/geant/nmaas/portal/api/market/AppInstanceControllerTest.java b/src/test/java/net/geant/nmaas/portal/api/market/AppInstanceControllerTest.java
index 2faa5e053ba5dd25799e05cf608467f0513c0fa7..6cd0015acc4573aa191cb1c4aeb38e6971c9d33a 100644
--- a/src/test/java/net/geant/nmaas/portal/api/market/AppInstanceControllerTest.java
+++ b/src/test/java/net/geant/nmaas/portal/api/market/AppInstanceControllerTest.java
@@ -453,7 +453,7 @@ public class AppInstanceControllerTest {
         when(applicationService.findApplication(appInstanceRequest.getApplicationId())).thenReturn(Optional.of(application));
 
         assertThrows(IllegalArgumentException.class, () -> {
-            this.appInstanceController.createAppInstance(appInstanceRequest, principal,domainId);
+            this.appInstanceController.createAppInstance(appInstanceRequest, principal,domainId, null);
         });
     }
 
diff --git a/src/test/java/net/geant/nmaas/portal/api/security/EncryptionServiceTest.java b/src/test/java/net/geant/nmaas/portal/api/security/EncryptionServiceTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..f8d1e2a1010e67784795c80f1f5b7cd93e9af761
--- /dev/null
+++ b/src/test/java/net/geant/nmaas/portal/api/security/EncryptionServiceTest.java
@@ -0,0 +1,23 @@
+package net.geant.nmaas.portal.api.security;
+
+import org.junit.jupiter.api.Test;
+
+import java.security.GeneralSecurityException;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class EncryptionServiceTest {
+
+    private static final String KEY = "nmaasplatformgn5";
+    private static final String ALGORITHM = "AES/GCM/NoPadding";
+
+    private final EncryptionService service = new EncryptionService(KEY, ALGORITHM);
+
+    @Test
+    void shouldEncryptAndDecrypt() throws GeneralSecurityException {
+        final String plainText = "test-content";
+        String encryptedText = service.encrypt(plainText);
+        assertThat(plainText).isEqualTo(service.decrypt(encryptedText));
+    }
+
+}
diff --git a/src/test/java/net/geant/nmaas/portal/persistent/entity/AppRateTest.java b/src/test/java/net/geant/nmaas/portal/persistent/entity/AppRateTest.java
index d356fb068d44ce69996ac709f27f892182dc4696..1f97d123f82a05c8c25d67dbc96f1461e091820d 100644
--- a/src/test/java/net/geant/nmaas/portal/persistent/entity/AppRateTest.java
+++ b/src/test/java/net/geant/nmaas/portal/persistent/entity/AppRateTest.java
@@ -8,7 +8,7 @@ import static org.junit.jupiter.api.Assertions.assertNotEquals;
 public class AppRateTest {
 
     @Test
-    public void shouldBeEqual() {
+    void shouldBeEqual() {
         assertEquals(new AppRate.AppRateId(1L, 2L), new AppRate.AppRateId(1L, 2L));
         assertNotEquals(new AppRate.AppRateId(1L, 2L), new AppRate.AppRateId(1L, 3L));
     }
diff --git a/src/test/java/net/geant/nmaas/portal/persistent/entity/ApplicationSubscriptionTest.java b/src/test/java/net/geant/nmaas/portal/persistent/entity/ApplicationSubscriptionTest.java
index b69b8884bf52bcbc81203d5979ddf296585697ac..676fa6173696a48377f1d2dca5c255d9be0de3c6 100644
--- a/src/test/java/net/geant/nmaas/portal/persistent/entity/ApplicationSubscriptionTest.java
+++ b/src/test/java/net/geant/nmaas/portal/persistent/entity/ApplicationSubscriptionTest.java
@@ -7,7 +7,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
 public class ApplicationSubscriptionTest {
 
     @Test
-    public void shouldBeEqual() {
+    void shouldBeEqual() {
         assertEquals(
                 new ApplicationSubscription(
                         new Domain("name", "codename"),
diff --git a/src/test/java/net/geant/nmaas/portal/persistent/entity/ApplicationTest.java b/src/test/java/net/geant/nmaas/portal/persistent/entity/ApplicationTest.java
index ca73a44d6b83e7e495f6e0402be7013de0b78d43..be1169b203504f2b38d979864612dfa90f66a75d 100644
--- a/src/test/java/net/geant/nmaas/portal/persistent/entity/ApplicationTest.java
+++ b/src/test/java/net/geant/nmaas/portal/persistent/entity/ApplicationTest.java
@@ -7,7 +7,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
 public class ApplicationTest {
 
     @Test
-    public void shouldBeEqual() {
+    void shouldBeEqual() {
         assertEquals(new Application(1L, "name1","testversion"), new Application(1L, "name2","testversion"));
     }
 
diff --git a/src/test/java/net/geant/nmaas/portal/persistent/entity/DomainGroupTest.java b/src/test/java/net/geant/nmaas/portal/persistent/entity/DomainGroupTest.java
index 30322ea6ef04f24e81033e0671e2200b5f789f7f..db982dd99547aecbf5ccdcd142e2b4c6eb2afb78 100644
--- a/src/test/java/net/geant/nmaas/portal/persistent/entity/DomainGroupTest.java
+++ b/src/test/java/net/geant/nmaas/portal/persistent/entity/DomainGroupTest.java
@@ -7,9 +7,10 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
 public class DomainGroupTest {
 
     @Test
-    public void shouldBeEqual() {
+    void shouldBeEqual() {
         final String commonName = "name";
         final String commonCodeName = "codename";
         assertEquals(new DomainGroup(commonName, commonCodeName), new DomainGroup(commonName, commonCodeName));
     }
+
 }
diff --git a/src/test/java/net/geant/nmaas/portal/persistent/entity/DomainTest.java b/src/test/java/net/geant/nmaas/portal/persistent/entity/DomainTest.java
index 628a7313001909afa2e8f9c4fc8ca5e6c343ac0e..1f2f685cf3bae96ec75f3eb0a73790646e3c61a7 100644
--- a/src/test/java/net/geant/nmaas/portal/persistent/entity/DomainTest.java
+++ b/src/test/java/net/geant/nmaas/portal/persistent/entity/DomainTest.java
@@ -7,11 +7,11 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
 public class DomainTest {
 
     @Test
-    public void shouldBeEqual() {
+    void shouldBeEqual() {
         final String commonName = "name";
         final String commonCodeName = "codename";
         assertEquals(new Domain(commonName, commonCodeName), new Domain(commonName, commonCodeName));
         assertEquals(new Domain(1L, commonName, commonCodeName), new Domain(2L, commonName, commonCodeName));
     }
 
-}
+}
\ No newline at end of file
diff --git a/src/test/java/net/geant/nmaas/portal/persistent/entity/InternationalizationTest.java b/src/test/java/net/geant/nmaas/portal/persistent/entity/InternationalizationTest.java
index 83911122a5462c6b6e0a206888321d55d32d35a5..f0868eeb87dd9c68054c6a2a7d3f34b87cecfcbf 100644
--- a/src/test/java/net/geant/nmaas/portal/persistent/entity/InternationalizationTest.java
+++ b/src/test/java/net/geant/nmaas/portal/persistent/entity/InternationalizationTest.java
@@ -9,6 +9,7 @@ import org.junit.platform.commons.util.StringUtils;
 import java.io.IOException;
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 import static org.junit.jupiter.api.Assertions.fail;
 
@@ -19,52 +20,52 @@ public class InternationalizationTest {
     private InternationalizationSimple internationalizationSimple;
 
     private static final String content = "{\n" +
-                "  \"NAVBAR\": {\n" +
-                "    \"MARKET\": \"Applications\",\n" +
-                "    \"SUBSCRIPTIONS\": \"Subscriptions\",\n" +
-                "    \"INSTANCES\": \"Instances\",\n" +
-                "    \"MANAGEMENT\": \"Management\",\n" +
-                "    \"DOMAINS\": \"Domains\",\n" +
-                "    \"USERS\": \"Users\",\n" +
-                "    \"INVENTORY\": \"External components\",\n" +
-                "    \"MONITOR\": \"Monitoring\",\n" +
-                "    \"SETTINGS\": \"Settings\",\n" +
-                "    \"PROFILE\": \"Profile\",\n" +
-                "    \"LOGOUT\": \"Logout\",\n" +
-                "    \"LOGIN_REGISTER\": \"Login | Register\",\n" +
-                "    \"LANGUAGE\": \"Language\",\n" +
-                "    \"ABOUT\": \"About\",\n" +
-                "    \"BACK\": \"Back to Home Page\",\n" +
-                "    \"LANGUAGES\": \"Languages\"\n" +
-                "  }\n" +
-                "}";
+            "  \"NAVBAR\": {\n" +
+            "    \"MARKET\": \"Applications\",\n" +
+            "    \"SUBSCRIPTIONS\": \"Subscriptions\",\n" +
+            "    \"INSTANCES\": \"Instances\",\n" +
+            "    \"MANAGEMENT\": \"Management\",\n" +
+            "    \"DOMAINS\": \"Domains\",\n" +
+            "    \"USERS\": \"Users\",\n" +
+            "    \"INVENTORY\": \"External components\",\n" +
+            "    \"MONITOR\": \"Monitoring\",\n" +
+            "    \"SETTINGS\": \"Settings\",\n" +
+            "    \"PROFILE\": \"Profile\",\n" +
+            "    \"LOGOUT\": \"Logout\",\n" +
+            "    \"LOGIN_REGISTER\": \"Login | Register\",\n" +
+            "    \"LANGUAGE\": \"Language\",\n" +
+            "    \"ABOUT\": \"About\",\n" +
+            "    \"BACK\": \"Back to Home Page\",\n" +
+            "    \"LANGUAGES\": \"Languages\"\n" +
+            "  }\n" +
+            "}";
 
     @BeforeEach
-    public void setup(){
+    void setup() {
         this.internationalization = new InternationalizationView("english", true, content);
     }
 
     @Test
-    public void internationalizationShouldDeserializeToInternationalizationSimple(){
+    void internationalizationShouldDeserializeToInternationalizationSimple() {
         this.internationalizationSimple = this.internationalization.getAsInternationalizationSimple();
         assertTrue(StringUtils.isNotBlank(internationalization.getContent()));
 //        assertEquals(internationalization.getId(), internationalizationSimple.getId());
         assertEquals(internationalization.getLanguage(), internationalizationSimple.getLanguage());
         assertEquals(internationalization.isEnabled(), internationalizationSimple.isEnabled());
-        assertTrue(internationalizationSimple.getLanguageNodes().size() > 0);
+        assertFalse(internationalizationSimple.getLanguageNodes().isEmpty());
     }
 
     @Test
-    public void InternationalizationSimpleShouldSerializeToInternationalization(){
+    void InternationalizationSimpleShouldSerializeToInternationalization() {
         this.internationalizationSimple = this.internationalization.getAsInternationalizationSimple();
         InternationalizationView test = this.internationalizationSimple.getAsInternationalizationView();
 //        assertEquals(internationalization.getId(), test.getId());
         assertEquals(internationalization.getLanguage(), test.getLanguage());
         assertEquals(internationalization.isEnabled(), test.isEnabled());
         ObjectMapper om = new ObjectMapper();
-        try{
+        try {
             assertEquals(om.readTree(internationalization.getContent()), om.readTree(test.getContent()));
-        } catch (IOException ioe){
+        } catch (IOException ioe) {
             fail();
         }
     }
diff --git a/src/test/java/net/geant/nmaas/portal/persistent/entity/SSHKeyEntityTest.java b/src/test/java/net/geant/nmaas/portal/persistent/entity/SSHKeyEntityTest.java
index 75a508c1807b70ce76b37a16a02f1cf42ca70fde..6a99077dbbe39e9df6da0c504c1350c875923f8e 100644
--- a/src/test/java/net/geant/nmaas/portal/persistent/entity/SSHKeyEntityTest.java
+++ b/src/test/java/net/geant/nmaas/portal/persistent/entity/SSHKeyEntityTest.java
@@ -5,6 +5,7 @@ import org.junit.jupiter.api.Test;
 import static org.junit.jupiter.api.Assertions.assertEquals;
 
 public class SSHKeyEntityTest {
+
     // ubuntu 18.04 LTS
     //    https://stackoverflow.com/questions/9607295/calculate-rsa-key-fingerprint
     //    ssh-keygen -t rsa -b 4096
diff --git a/src/test/java/net/geant/nmaas/portal/persistent/entity/UserRoleTest.java b/src/test/java/net/geant/nmaas/portal/persistent/entity/UserRoleTest.java
index f79d9782a56fadff70129f47eaf5ed01b78b5614..51f32630ed549e7357acb22a9a5f0e2e0fa41301 100644
--- a/src/test/java/net/geant/nmaas/portal/persistent/entity/UserRoleTest.java
+++ b/src/test/java/net/geant/nmaas/portal/persistent/entity/UserRoleTest.java
@@ -11,13 +11,13 @@ public class UserRoleTest {
     final private Role role = Role.ROLE_DOMAIN_ADMIN;
 
     @Test
-    public void shouldBeEqual() {
+    void shouldBeEqual() {
         assertEquals(new UserRole(user, domain, role).getId(),
                      new UserRole(new User("username"), new Domain(1L, "name", "codename"), Role.ROLE_DOMAIN_ADMIN).getId());
     }
 
     @Test
-    public void shouldGetCorrectAuthorityString() {
+    void shouldGetCorrectAuthorityString() {
         UserRole userRole = new UserRole(user, domain, role);
         assertEquals("1:ROLE_DOMAIN_ADMIN", userRole.getAuthority());
     }
diff --git a/src/test/java/net/geant/nmaas/portal/persistent/entity/UserTest.java b/src/test/java/net/geant/nmaas/portal/persistent/entity/UserTest.java
index e4042848692ec06ec1c6e5fc023a195dfef74522..a359592ee8e6c5dd3ca79c0c46196ebb9d8783e5 100644
--- a/src/test/java/net/geant/nmaas/portal/persistent/entity/UserTest.java
+++ b/src/test/java/net/geant/nmaas/portal/persistent/entity/UserTest.java
@@ -7,7 +7,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
 public class UserTest {
 
     @Test
-    public void shouldBeEqual() {
+    void shouldBeEqual() {
         final String commonUsername = "username";
         assertEquals(new User(commonUsername), new User(commonUsername));
         assertEquals(new User(1L, commonUsername, true, new Domain(), Role.ROLE_GUEST),
diff --git a/src/test/java/net/geant/nmaas/portal/service/impl/DashboardServiceImplTest.java b/src/test/java/net/geant/nmaas/portal/service/impl/DashboardServiceImplTest.java
index 7cbd86f0dab7037c5e5ead9f1ed0bf7a8ad4ad32..ae02141d1f25687a9ab34e683cac3d4ba49930cb 100644
--- a/src/test/java/net/geant/nmaas/portal/service/impl/DashboardServiceImplTest.java
+++ b/src/test/java/net/geant/nmaas/portal/service/impl/DashboardServiceImplTest.java
@@ -15,6 +15,7 @@ import net.geant.nmaas.portal.service.DomainService;
 import net.geant.nmaas.portal.service.UserLoginRegisterService;
 import net.geant.nmaas.portal.service.UserService;
 import org.junit.jupiter.api.Test;
+import org.mockito.ArgumentCaptor;
 
 import java.time.OffsetDateTime;
 import java.util.Collections;
@@ -49,11 +50,13 @@ public class DashboardServiceImplTest {
             userLoginRegisterService
     );
 
-    @Test
+      @Test
     void getSystemDashboardShouldThrowExceptionWhenRepositoryFails() {
         when(domainRepository.count()).thenThrow(new RuntimeException("Database error"));
 
-        assertThrows(RuntimeException.class, dashboardService::getSystemDashboard);
+        assertThrows(RuntimeException.class, () -> 
+            dashboardService.getSystemDashboard(OffsetDateTime.now().minusDays(1), OffsetDateTime.now())
+        );
     }
 
     @Test
@@ -118,9 +121,41 @@ public class DashboardServiceImplTest {
         when(appInstanceRepo.countAllDeployedSinceTime(anyLong())).thenReturn((int) 3L);
         when(applicationBaseRepository.findAllNames()).thenReturn(Collections.emptyList());
 
-        DashboardView result = dashboardService.getSystemDashboard();
+        DashboardView result = dashboardService.getSystemDashboard(OffsetDateTime.now().minusDays(1), OffsetDateTime.now());
 
         assert result != null;
         assert result.getPopularApps().isEmpty();
     }
+
+    @Test
+void getSystemDashboardShouldCalculateCorrectTimestamps() {
+    OffsetDateTime startDate = OffsetDateTime.now().minusHours(5);
+    OffsetDateTime endDate = OffsetDateTime.now();
+
+    // Mock required repository methods
+    when(domainRepository.count()).thenReturn(1L);
+    when(userRepository.count()).thenReturn(1L);
+    when(appInstanceRepo.count()).thenReturn(1L);
+    when(appInstanceRepo.countAllDeployedSinceTime(org.mockito.ArgumentMatchers.anyLong(), org.mockito.ArgumentMatchers.anyLong())).thenReturn(1);
+    when(applicationBaseRepository.findAllNames()).thenReturn(Collections.emptyList());
+    when(appInstanceRepo.findAllInTimePeriod(org.mockito.ArgumentMatchers.anyLong(), org.mockito.ArgumentMatchers.anyLong())).thenReturn(Collections.emptyList());
+
+    // Call the method
+    dashboardService.getSystemDashboard(startDate, endDate);
+
+    // Capture the arguments
+    ArgumentCaptor<Long> startCaptor = ArgumentCaptor.forClass(Long.class);
+    ArgumentCaptor<Long> endCaptor = ArgumentCaptor.forClass(Long.class);
+
+    // Verify that the method was called with calculated timestamps
+    org.mockito.Mockito.verify(appInstanceRepo).countAllDeployedSinceTime(startCaptor.capture(), endCaptor.capture());
+
+    long startTimestamp = startCaptor.getValue();
+    long endTimestamp = endCaptor.getValue();
+
+    // The timestamps should be positive and start should be greater than end (since it's calculated as now - toEpochSecond)
+    assert startTimestamp > 0;
+    assert endTimestamp > 0;
+    assert startTimestamp > endTimestamp;
+}
 }
diff --git a/src/test/java/net/geant/nmaas/portal/service/impl/DomainGroupServiceTest.java b/src/test/java/net/geant/nmaas/portal/service/impl/DomainGroupServiceTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..2b1c864275d91a098134dccf3cf1d81e22f73751
--- /dev/null
+++ b/src/test/java/net/geant/nmaas/portal/service/impl/DomainGroupServiceTest.java
@@ -0,0 +1,144 @@
+package net.geant.nmaas.portal.service.impl;
+
+import net.geant.nmaas.orchestration.jobs.DomainGroupJob;
+import net.geant.nmaas.portal.api.domain.DomainGroupView;
+import net.geant.nmaas.portal.persistent.entity.DomainGroup;
+import net.geant.nmaas.portal.persistent.entity.WebhookEvent;
+import net.geant.nmaas.portal.persistent.entity.WebhookEventType;
+import net.geant.nmaas.portal.persistent.repositories.DomainGroupRepository;
+import net.geant.nmaas.portal.persistent.repositories.WebhookEventRepository;
+import net.geant.nmaas.portal.service.ApplicationStatePerDomainService;
+import net.geant.nmaas.portal.service.DomainGroupService;
+import net.geant.nmaas.scheduling.ScheduleManager;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.modelmapper.ModelMapper;
+import org.quartz.JobListener;
+import org.quartz.ListenerManager;
+import org.quartz.Matcher;
+import org.quartz.Scheduler;
+import org.quartz.SchedulerException;
+
+import java.util.Optional;
+import java.util.stream.Stream;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.argThat;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+class DomainGroupServiceTest {
+
+    DomainGroupRepository domainGroupRepository = mock(DomainGroupRepository.class);
+    ApplicationStatePerDomainService applicationStatePerDomainService = mock(ApplicationStatePerDomainService.class);
+    WebhookEventRepository webhookEventRepository = mock(WebhookEventRepository.class);
+    Scheduler scheduler = mock(Scheduler.class);
+    ListenerManager listenerManager = mock(ListenerManager.class);
+    ScheduleManager scheduleManager;
+    ModelMapper modelMapper = new ModelMapper();
+    DomainGroupService domainGroupService;
+
+    @BeforeEach
+    void setup() {
+        scheduleManager = new ScheduleManager(scheduler);
+        domainGroupService = new DomainGroupServiceImpl(domainGroupRepository, applicationStatePerDomainService, webhookEventRepository, scheduleManager, modelMapper);
+    }
+
+    @Test
+    void shouldCreateDomainGroup() throws SchedulerException {
+        // Setup webhook event
+        WebhookEvent webhookEvent = new WebhookEvent(1L, "webhook", "https://example.com/webhook", WebhookEventType.DOMAIN_GROUP_CHANGE, null, null);
+        when(webhookEventRepository.findIdByEventType(WebhookEventType.DOMAIN_GROUP_CHANGE))
+                .thenReturn(Stream.of(1L));
+        when(webhookEventRepository.findById(1L))
+                .thenReturn(Optional.of(webhookEvent));
+
+        // Setup domain group
+        String name = "testgroup";
+        String codename = "testgrp";
+        DomainGroup domainGroup = new DomainGroup(name, codename);
+        domainGroup.setId(10L);
+        when(domainGroupRepository.save(any(DomainGroup.class))).thenReturn(domainGroup);
+        when(domainGroupRepository.findById(10L)).thenReturn(Optional.of(domainGroup));
+        when(scheduler.getListenerManager()).thenReturn(listenerManager);
+        doNothing().when(listenerManager).addJobListener(any(JobListener.class), any(Matcher.class));
+
+        // Create domain group
+        DomainGroupView domainGroupView = new DomainGroupView();
+        domainGroupView.setName(name);
+        domainGroupView.setCodename(codename);
+        DomainGroupView result = this.domainGroupService.createDomainGroup(domainGroupView);
+
+        // Verify webhook job was scheduled with correct parameters for creation
+        verify(scheduler, times(1)).scheduleJob(
+            argThat(jobDetail -> 
+                jobDetail.getKey().getName().startsWith("DomainGroup_1_10_") &&
+                jobDetail.getJobClass().equals(DomainGroupJob.class)
+            ),
+            argThat(trigger -> 
+                trigger.getKey().getName().startsWith("DomainGroup_1_10_")
+            )
+        );
+
+        // Verify domain group was created correctly
+        assertThat("Codenames are not the same", result.getCodename().equals(codename));
+        assertThat("Names are not the same", result.getName().equals(name));
+
+        // Update domain group
+        when(webhookEventRepository.findIdByEventType(WebhookEventType.DOMAIN_GROUP_CHANGE))
+                .thenReturn(Stream.of(1L));
+        when(webhookEventRepository.findById(1L))
+                .thenReturn(Optional.of(webhookEvent));
+        domainGroupView.setCodename(codename + "2");
+        domainGroupView.setId(10L);
+        result = this.domainGroupService.updateDomainGroup(10L, domainGroupView);
+
+        // Verify webhook job was scheduled with correct parameters for update
+        verify(scheduler, times(2)).scheduleJob(
+            argThat(jobDetail -> 
+                jobDetail.getKey().getName().startsWith("DomainGroup_1_10_") &&
+                jobDetail.getJobClass().equals(DomainGroupJob.class)
+            ),
+            argThat(trigger -> 
+                trigger.getKey().getName().startsWith("DomainGroup_1_10_")
+            )
+        );
+
+        // Verify domain group was updated correctly
+        assertThat("Updated codenames are not the same", result.getCodename().equals(codename + "2"));
+        assertThat("Names are not the same after update", result.getName().equals(name));
+    }
+
+    @Test
+    void shouldDeleteDomainGroup() throws SchedulerException {
+        // Setup webhook event
+        WebhookEvent webhookEvent = new WebhookEvent(1L, "webhook", "https://example.com/webhook", WebhookEventType.DOMAIN_GROUP_CHANGE, null, null);
+        when(webhookEventRepository.findIdByEventType(WebhookEventType.DOMAIN_GROUP_CHANGE))
+                .thenReturn(Stream.of(1L));
+        when(webhookEventRepository.findById(1L))
+                .thenReturn(Optional.of(webhookEvent));
+
+        DomainGroup domainGroup = new DomainGroup("testgroup", "testgrp");
+        domainGroup.setId(10L);
+        when(domainGroupRepository.findById(10L)).thenReturn(Optional.of(domainGroup));
+        when(scheduler.getListenerManager()).thenReturn(listenerManager);
+        doNothing().when(listenerManager).addJobListener(any(JobListener.class), any(Matcher.class));
+        this.domainGroupService.deleteDomainGroup(10L);
+        verify(domainGroupRepository, times(1)).deleteById(10L);
+
+        verify(scheduler, times(1)).scheduleJob(
+                argThat(jobDetail ->
+                        jobDetail.getKey().getName().startsWith("DomainGroup_1_10_") &&
+                                jobDetail.getJobClass().equals(DomainGroupJob.class)
+                ),
+                argThat(trigger ->
+                        trigger.getKey().getName().startsWith("DomainGroup_1_10_")
+                )
+        );
+    }
+}
diff --git a/src/test/java/net/geant/nmaas/portal/service/impl/DomainServiceTest.java b/src/test/java/net/geant/nmaas/portal/service/impl/DomainServiceTest.java
index 6f9276d7ff38e95d21e9293a6cd9cad9f5308adf..5fb8cb4c800f2519d943b89133014c20531a2147 100644
--- a/src/test/java/net/geant/nmaas/portal/service/impl/DomainServiceTest.java
+++ b/src/test/java/net/geant/nmaas/portal/service/impl/DomainServiceTest.java
@@ -94,8 +94,6 @@ class DomainServiceTest {
     Scheduler scheduler = mock(Scheduler.class);
     ListenerManager listenerManager = mock(ListenerManager.class);
     ScheduleManager scheduleManager;
-    EncryptionService encryptionService = mock(EncryptionService.class);
-    WebhookEventService webhookEventService;
 
     DomainService domainService;
 
@@ -103,15 +101,14 @@ class DomainServiceTest {
     void setup() {
         validator = new DefaultCodenameValidator("[a-z-]{2,12}");
         namespaceValidator = new DefaultCodenameValidator("[a-z-]{0,64}");
-        domainGroupService = new DomainGroupServiceImpl(domainGroupRepository, applicationStatePerDomainService, modelMapper);
         scheduleManager = new ScheduleManager( scheduler);
+        domainGroupService = new DomainGroupServiceImpl(domainGroupRepository, applicationStatePerDomainService, webhookEventRepository, scheduleManager, modelMapper);
         domainService = new DomainServiceImpl(validator,
                 namespaceValidator, domainRepository,
                 domainDcnDetailsRepository, domainTechDetailsRepository, userService,
                 userRoleRepo, dcnRepositoryManager,
                 modelMapper, applicationStatePerDomainService, domainGroupService, eventPublisher, domainAnnotationsRepository, webhookEventRepository, scheduleManager);
         ((DomainServiceImpl) domainService).globalDomain = "GLOBAL";
-        webhookEventService = new WebhookEventService(webhookEventRepository, encryptionService, modelMapper);
     }
 
     @Test
diff --git a/src/test/java/net/geant/nmaas/scheduling/SchedulingManagerTest.java b/src/test/java/net/geant/nmaas/scheduling/SchedulingManagerTest.java
index 259997e46e8dae575a7806951a48ab661253a19a..8e7e559c057c89e7d3f6bff241d9d3588671c1c4 100644
--- a/src/test/java/net/geant/nmaas/scheduling/SchedulingManagerTest.java
+++ b/src/test/java/net/geant/nmaas/scheduling/SchedulingManagerTest.java
@@ -1,11 +1,11 @@
 package net.geant.nmaas.scheduling;
 
 import net.geant.nmaas.gitlab.GitLabManager;
-import net.geant.nmaas.monitor.targets.GitLabMonitorService;
 import net.geant.nmaas.monitor.MonitorManager;
 import net.geant.nmaas.monitor.ServiceType;
 import net.geant.nmaas.monitor.TimeFormat;
 import net.geant.nmaas.monitor.model.MonitorEntryView;
+import net.geant.nmaas.monitor.targets.GitLabMonitorService;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 import org.quartz.JobKey;
@@ -33,22 +33,22 @@ public class SchedulingManagerTest {
     private ScheduleManager scheduleManager;
 
     @BeforeEach
-    public void setup() throws Exception{
+    void setup() throws Exception {
         gitLabMonitorService = new GitLabMonitorService();
         gitLabMonitorService.setGitLabManager(gitLabManager);
         gitLabMonitorService.setMonitorManager(monitorManager);
-        scheduleManager = new ScheduleManager( scheduler);
+        scheduleManager = new ScheduleManager(scheduler);
         when(scheduler.checkExists(JobKey.jobKey(ServiceType.GITLAB.getName()))).thenReturn(false);
     }
 
     @Test
-    public void shouldCreateJob() throws Exception {
+    void shouldCreateJob() throws Exception {
         this.scheduleManager.createJob(this.gitLabMonitorService, monitorEntryView);
         verify(scheduler, times(1)).scheduleJob(any(), anySet(), anyBoolean());
     }
 
     @Test
-    public void shouldNotCreateJobWhenJobExists() {
+    void shouldNotCreateJobWhenJobExists() {
         assertThrows(IllegalStateException.class, () -> {
             when(scheduler.checkExists(JobKey.jobKey(ServiceType.GITLAB.getName()))).thenReturn(true);
             this.scheduleManager.createJob(this.gitLabMonitorService, monitorEntryView);
@@ -56,7 +56,7 @@ public class SchedulingManagerTest {
     }
 
     @Test
-    public void shouldUpdateJob() throws Exception {
+    void shouldUpdateJob() throws Exception {
         JobDescriptor jobDescriptor = new JobDescriptor(ServiceType.GITLAB, 3L, TimeFormat.MIN);
         when(scheduler.getTrigger(TriggerKey.triggerKey(ServiceType.GITLAB.getName()))).thenReturn(jobDescriptor.buildTrigger());
         this.scheduleManager.updateJob(monitorEntryView);
@@ -64,7 +64,7 @@ public class SchedulingManagerTest {
     }
 
     @Test
-    public void shouldNotUpdate() throws Exception {
+    void shouldNotUpdate() throws Exception {
         when(scheduler.getTrigger(TriggerKey.triggerKey(ServiceType.GITLAB.getName()))).thenReturn(null);
         this.scheduleManager.updateJob(monitorEntryView);
         verify(scheduler, times(0)).rescheduleJob(any(), any());
diff --git a/src/test/shell/data/i18n/de.json b/src/test/shell/data/i18n/de.json
index 4d8597ce9478703a17493293fef4a55df1d87057..3a07ad01ea518de6b8fadbe92c36b89fe0cf5d69 100644
--- a/src/test/shell/data/i18n/de.json
+++ b/src/test/shell/data/i18n/de.json
@@ -192,7 +192,8 @@
     "DOWN" : "Down",
     "UNKNOWN" : "Unknown",
     "DETAILS" : "Details",
-    "STATE_SINCE" : "State last change"
+    "STATE_SINCE" : "State last change",
+    "CONTACT_MAIL" : "Contact mail"
   },
   "GITLAB": {
     "TITLE": "GitLab Konfiguration",
@@ -514,7 +515,9 @@
     "DEPLOY_BUTTON": "Installieren",
     "USE_DEFAULT_VERSION": "Standardversion verwenden",
     "SELECT_VERSION": "Version auswählen",
-    "AUTO_UPGRADES_ENABLED": "Automatische upgrades aktivieren"
+    "AUTO_UPGRADES_ENABLED": "Automatische upgrades aktivieren",
+    "REMOTE_CLUSTER" : "Use remote cluster",
+    "SELECT_CLUSTER": "Remote location"
   },
   "ADD_MEMBERS_MODAL": {
     "HEADER": "Add members",
@@ -1307,5 +1310,21 @@
   },
   "SHARED" : {
     "TOGGLE" : "Toggle all"
+  },
+  "WEBHOOKS" : {
+    "TITLE" : "Webhooks settings",
+    "TITLE_SHORT" : "Webhooks",
+    "ID" : "Id",
+    "NAME" : "Name",
+    "TARGET_URL" : "Target Url",
+    "TYPE" : "Webhook type",
+    "TOKEN" : "Token value",
+    "AUTH" : "Authorization header",
+    "DOMAIN_CREATION" : "Domain creation",
+    "APPLICATION_DEPLOYMENT" : "Application deployment",
+    "USER_ASSIGNMENT" : "User assignment",
+    "DOMAIN_GROUP_CHANGE" : "Domain group change",
+    "NEW" : "Add webhook",
+    "DETAILS" : "Webhooks details"
   }
 }
diff --git a/src/test/shell/data/i18n/en.json b/src/test/shell/data/i18n/en.json
index e2aea66b117a82b6da5343dadcc94354ebcae2c1..6484fdeaec15e61cf86d41d39c73b4bec33e264e 100644
--- a/src/test/shell/data/i18n/en.json
+++ b/src/test/shell/data/i18n/en.json
@@ -192,6 +192,7 @@
     "UP" : "Up",
     "DOWN" : "Down",
     "UNKNOWN" : "Unknown",
+    "CONTACT_MAIL" : "Contact mail",
     "DETAILS" : "Details",
     "STATE_SINCE" : "State last change"
   },
@@ -516,7 +517,9 @@
     "DEPLOY_BUTTON": "Deploy",
     "USE_DEFAULT_VERSION": "Use default version",
     "SELECT_VERSION": "Select version",
-    "AUTO_UPGRADES_ENABLED": "Enable automatic upgrades"
+    "AUTO_UPGRADES_ENABLED": "Enable automatic upgrades",
+    "REMOTE_CLUSTER" : "Use remote cluster",
+    "SELECT_CLUSTER": "Remote location"
   },
   "ADD_MEMBERS_MODAL": {
     "HEADER": "Add members",
@@ -1312,5 +1315,21 @@
   },
   "SHARED" : {
     "TOGGLE" : "Toggle all"
+  },
+  "WEBHOOKS" : {
+    "TITLE" : "Webhooks settings",
+    "TITLE_SHORT" : "Webhooks",
+    "ID" : "Id",
+    "NAME" : "Name",
+    "TARGET_URL" : "Target Url",
+    "TYPE" : "Webhook type",
+    "TOKEN" : "Token value",
+    "AUTH" : "Authorization header",
+    "DOMAIN_CREATION" : "Domain creation",
+    "APPLICATION_DEPLOYMENT" : "Application deployment",
+    "USER_ASSIGNMENT" : "User assignment",
+    "DOMAIN_GROUP_CHANGE" : "Domain group change",
+    "NEW" : "Add webhook",
+    "DETAILS" : "Webhooks details"
   }
 }
diff --git a/src/test/shell/data/i18n/fr.json b/src/test/shell/data/i18n/fr.json
index 5051d97030f59fd51f7f4e1848de3bb1f27c4801..449bdc580a1e0151fc743896e5d7a56e21695968 100644
--- a/src/test/shell/data/i18n/fr.json
+++ b/src/test/shell/data/i18n/fr.json
@@ -193,6 +193,7 @@
     "UP" : "Up",
     "DOWN" : "Down",
     "UNKNOWN" : "Unknown",
+    "CONTACT_MAIL" : "Contact mail",
     "DETAILS" : "Details",
     "STATE_SINCE" : "State last change"
   },
@@ -516,7 +517,9 @@
     "DEPLOY_BUTTON": "Déployer",
     "USE_DEFAULT_VERSION": "Utiliser la version par défaut",
     "SELECT_VERSION": "Sélectionnez la version",
-    "AUTO_UPGRADES_ENABLED": "Activer les mises à jour automatiques"
+    "AUTO_UPGRADES_ENABLED": "Activer les mises à jour automatiques",
+    "REMOTE_CLUSTER" : "Use remote cluster",
+    "SELECT_CLUSTER": "Remote location"
   },
   "ADD_MEMBERS_MODAL": {
     "HEADER": "Add members",
@@ -1311,5 +1314,21 @@
   },
   "SHARED" : {
     "TOGGLE" : "Toggle all"
+  },
+  "WEBHOOKS" : {
+    "TITLE" : "Webhooks settings",
+    "TITLE_SHORT" : "Webhooks",
+    "ID" : "Id",
+    "NAME" : "Name",
+    "TARGET_URL" : "Target Url",
+    "TYPE" : "Webhook type",
+    "TOKEN" : "Token value",
+    "AUTH" : "Authorization header",
+    "DOMAIN_CREATION" : "Domain creation",
+    "APPLICATION_DEPLOYMENT" : "Application deployment",
+    "USER_ASSIGNMENT" : "User assignment",
+    "DOMAIN_GROUP_CHANGE" : "Domain group change",
+    "NEW" : "Add webhook",
+    "DETAILS" : "Webhooks details"
   }
 }
diff --git a/src/test/shell/data/i18n/pl.json b/src/test/shell/data/i18n/pl.json
index f3e91fadca23a09c96a470ee314048e333909297..ae51c715ddda44898afc023c818db0fb15c47109 100644
--- a/src/test/shell/data/i18n/pl.json
+++ b/src/test/shell/data/i18n/pl.json
@@ -192,6 +192,7 @@
     "UP" : "Aktywny",
     "DOWN" : "Nieaktywny",
     "UNKNOWN" : "Nieznany",
+    "CONTACT_MAIL" : "Email kontaktowy",
     "DETAILS" : "Detale",
     "STATE_SINCE" : "Stan od"
   },
@@ -515,7 +516,9 @@
     "DEPLOY_BUTTON": "Uruchom",
     "USE_DEFAULT_VERSION": "Użyj domyślnej wersji",
     "SELECT_VERSION": "Wybierz wersję",
-    "AUTO_UPGRADES_ENABLED": "Włącz automatyczne aktualizacje"
+    "AUTO_UPGRADES_ENABLED": "Włącz automatyczne aktualizacje",
+    "REMOTE_CLUSTER" : "Uruchomienie na zewnęntrzym klastrze",
+    "SELECT_CLUSTER": "Zdalna lokalizacja"
   },
   "ADD_MEMBERS_MODAL": {
     "HEADER": "Dodaj użytkowników",
@@ -1310,5 +1313,21 @@
   },
   "SHARED" : {
     "TOGGLE" : "Toggle all"
+  },
+  "WEBHOOKS" : {
+    "TITLE" : "Webhooks settings",
+    "TITLE_SHORT" : "Webhooks",
+    "ID" : "Id",
+    "NAME" : "Name",
+    "TARGET_URL" : "Target Url",
+    "TYPE" : "Webhook type",
+    "TOKEN" : "Token value",
+    "AUTH" : "Authorization header",
+    "DOMAIN_CREATION" : "Domain creation",
+    "APPLICATION_DEPLOYMENT" : "Application deployment",
+    "USER_ASSIGNMENT" : "User assignment",
+    "DOMAIN_GROUP_CHANGE" : "Domain group change",
+    "NEW" : "Add webhook",
+    "DETAILS" : "Webhooks details"
   }
 }
diff --git a/src/test/shell/data/mails/clusterSupportEmail.json b/src/test/shell/data/mails/clusterSupportEmail.json
index 192f7daa3413adb05771036fc34018fec0516938..91d233386a0b4af00a7ac227b6fc87b0d17db805 100644
--- a/src/test/shell/data/mails/clusterSupportEmail.json
+++ b/src/test/shell/data/mails/clusterSupportEmail.json
@@ -8,10 +8,10 @@
   "templates": [
     {
       "language": "en",
-      "subject": "nmaas: Cluster support",
+      "subject": "nmaas: Remote cluster support",
       "template": {
         "HEADER": "Dear ${username}",
-        "CONTENT": "<p>Your email address is added to support remote cluster <b>${clusterCodename}</b>. </p>  <p>If you do not agree or do not proceed this action please contact administration for further information.</p>",
+        "CONTENT": "<p>Your email address was set as a support contact for remote cluster <b>${clusterCodename}</b> added to nmaas.</p>  <p>If you do not agree or consider this as a mistake please contact the nmaas administrator for further information.</p>",
         "SENDER": "Best regards,<br />nmaas Team",
         "NOREPLY": "This is an automatically generated message, please do not reply.",
         "SENDER_POLICY": ""
@@ -19,11 +19,10 @@
     },
     {
       "language": "fr",
-      "subject": "nmaas: Cluster is unavailable",
+      "subject": "nmaas: Remote cluster support",
       "template": {
         "HEADER": "Dear ${username}",
-        "CONTENT": "<p>Your email address is added to support remote cluster <b>${clusterCodename}</b>. </p>  <p>If you do not agree or do not proceed this action please contact administration for further information.</p>",
-
+        "CONTENT": "<p>Your email address was set as a support contact for remote cluster <b>${clusterCodename}</b> added to nmaas.</p>  <p>If you do not agree or consider this as a mistake please contact the nmaas administrator for further information.</p>",
         "SENDER": "Best regards,<br />nmaas Team",
         "NOREPLY": "This is an automatically generated message, please do not reply.",
         "SENDER_POLICY": ""
@@ -31,10 +30,10 @@
     },
     {
       "language": "de",
-      "subject": "nmaas: Cluster is unavailable",
+      "subject": "nmaas: Remote cluster support",
       "template": {
         "HEADER": "Dear ${username}",
-        "CONTENT": "<p>Your email address is added to support remote cluster <b>${clusterCodename}</b>. </p>  <p>If you do not agree or do not proceed this action please contact administration for further information.</p>",
+        "CONTENT": "<p>Your email address was set as a support contact for remote cluster <b>${clusterCodename}</b> added to nmaas.</p>  <p>If you do not agree or consider this as a mistake please contact the nmaas administrator for further information.</p>",
         "SENDER": "Best regards,<br />nmaas Team",
         "NOREPLY": "This is an automatically generated message, please do not reply.",
         "SENDER_POLICY": ""
@@ -42,10 +41,10 @@
     },
     {
       "language": "pl",
-      "subject": "nmaas: Cluster jest niedostępny",
+      "subject": "nmaas: Wsparcie zdalnego klastra",
       "template": {
         "HEADER": "Drogi ${username}",
-        "CONTENT": "<p>Your email address is added to support remote cluster <b>${clusterCodename}</b>. </p>  <p>If you do not agree or do not proceed this action please contact administration for further information.</p>",
+        "CONTENT": "<p>Twój adres e-mail został ustawiony jako kontakt pomocy technicznej dla zdalnego klastra <b>${clusterCodename}</b> dodanego do nmaas.</p> <p>Jeśli się z tym nie zgadzasz lub uważasz to za pomyłkę, skontaktuj się z administratorem nmaas, aby uzyskać dalsze informacje.</p>",
         "SENDER": "Z pozdrowieniami,<br />Zespół nmaas",
         "NOREPLY": "Ta wiadomość została wygenerowana automatycznie.",
         "SENDER_POLICY": ""
diff --git a/src/test/shell/data/mails/clusterUnavailable.json b/src/test/shell/data/mails/clusterUnavailable.json
index b126a6d6fbd55f29d996e7dd69e5ef8f7757cbf3..b6d76247d4f8e11fbfbe94218a1b5b70fae932b1 100644
--- a/src/test/shell/data/mails/clusterUnavailable.json
+++ b/src/test/shell/data/mails/clusterUnavailable.json
@@ -8,10 +8,10 @@
   "templates": [
     {
       "language": "en",
-      "subject": "nmaas: Cluster is unavailable",
+      "subject": "nmaas: Remote cluster is unavailable",
       "template": {
         "HEADER": "Dear ${username}",
-        "CONTENT": "<p> Cluster <b>${clusterCodename}</b> is unavailable.</p> <p><b> Cluster ID:<b> ${clusterId}</p> <p><b> Cluster CodeName:<b> ${clusterCodename}</p> <p>Please contact the administration for further information.</p>",
+        "CONTENT": "<p>Internal nmaas monitoring indicates that cluster <b>${clusterCodename}</b> became unavailable.</p> <p><b> Cluster ID:<b> ${clusterId}</p> <p><b> Cluster CodeName:<b> ${clusterCodename}</p> <p>Please contact the nmaas administrator for further information.</p>",
         "SENDER": "Best regards,<br />nmaas Team",
         "NOREPLY": "This is an automatically generated message, please do not reply.",
         "SENDER_POLICY": ""
@@ -19,11 +19,10 @@
     },
     {
       "language": "fr",
-      "subject": "nmaas: Cluster is unavailable",
+      "subject": "nmaas: Remote cluster is unavailable",
       "template": {
         "HEADER": "Dear ${username}",
-        "CONTENT": "<p> Cluster <b>${clusterCodename}</b> is unavailable.</p> <p><b> Cluster ID:<b> ${clusterId}</p> <p><b> Cluster CodeName:<b> ${clusterCodename}</p> <p>Please contact the administration for further information.</p>",
-
+        "CONTENT": "<p>Internal nmaas monitoring indicates that cluster <b>${clusterCodename}</b> became unavailable.</p> <p><b> Cluster ID:<b> ${clusterId}</p> <p><b> Cluster CodeName:<b> ${clusterCodename}</p> <p>Please contact the nmaas administrator for further information.</p>",
         "SENDER": "Best regards,<br />nmaas Team",
         "NOREPLY": "This is an automatically generated message, please do not reply.",
         "SENDER_POLICY": ""
@@ -31,10 +30,10 @@
     },
     {
       "language": "de",
-      "subject": "nmaas: Cluster is unavailable",
+      "subject": "nmaas: Remote cluster is unavailable",
       "template": {
         "HEADER": "Dear ${username}",
-        "CONTENT": "<p> Cluster <b>${clusterCodename}</b> is unavailable.</p> <p><b> Cluster ID:<b> ${clusterId}</p> <p><b> Cluster CodeName:<b> ${clusterCodename}</p> <p>Please contact the administration for further information.</p>",
+        "CONTENT": "<p>Internal nmaas monitoring indicates that cluster <b>${clusterCodename}</b> became unavailable.</p> <p><b> Cluster ID:<b> ${clusterId}</p> <p><b> Cluster CodeName:<b> ${clusterCodename}</p> <p>Please contact the nmaas administrator for further information.</p>",
         "SENDER": "Best regards,<br />nmaas Team",
         "NOREPLY": "This is an automatically generated message, please do not reply.",
         "SENDER_POLICY": ""
@@ -42,10 +41,10 @@
     },
     {
       "language": "pl",
-      "subject": "nmaas: Cluster jest niedostępny",
+      "subject": "nmaas: Zdalny klaster jest niedostępny",
       "template": {
         "HEADER": "Drogi ${username}",
-        "CONTENT": "<p> Cluster <b>${clusterCodename}</b> is unavailable.</p> <p><b> Cluster ID:</b> ${clusterId}</p> <p><b> Cluster CodeName:</b> ${clusterCodename}</p> <p> <i>Please contact the administration for further information. </i></p>",
+        "CONTENT": "<p>Wewnętrzny monitoring nmaas wskazuje, że klaster <b>${clusterCodename</b> stał się niedostępny.</p> <p><b> Identyfikator klastra:<b> ${clusterId</p> <p><b> Nazwa kodowa klastra:<b> ${clusterCodename</p> <p>Aby uzyskać więcej informacji, skontaktuj się z administratorem nmaas.</p>",
         "SENDER": "Z pozdrowieniami,<br />Zespół nmaas",
         "NOREPLY": "Ta wiadomość została wygenerowana automatycznie.",
         "SENDER_POLICY": ""