From 517bccd5d77539457766b42504d4b4d460c03125 Mon Sep 17 00:00:00 2001
From: kbeyro <121854496+kbeyro@users.noreply.github.com>
Date: Wed, 14 May 2025 15:00:02 +0200
Subject: [PATCH 01/10] add mail type add creation of file in case of missing

---
 .../kubernetes/ClusterMonitoringJob.java      |  2 +
 .../kubernetes/RemoteClusterManager.java      | 83 +++++++++++++++----
 .../kubernetes/StringMultipartFile.java       | 67 +++++++++++++++
 .../api/model/RemoteClusterView.java          |  2 +
 .../kubernetes/entities/KCluster.java         |  4 +
 .../notifications/templates/MailType.java     |  3 +-
 .../shell/data/mails/clusterUnavailable.json  | 55 ++++++++++++
 7 files changed, 198 insertions(+), 18 deletions(-)
 create mode 100644 src/main/java/net/geant/nmaas/externalservices/kubernetes/StringMultipartFile.java
 create mode 100644 src/test/shell/data/mails/clusterUnavailable.json

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 bc88e1f2c..784fa5100 100644
--- a/src/main/java/net/geant/nmaas/externalservices/kubernetes/ClusterMonitoringJob.java
+++ b/src/main/java/net/geant/nmaas/externalservices/kubernetes/ClusterMonitoringJob.java
@@ -17,6 +17,8 @@ public class ClusterMonitoringJob implements Job {
     @Override
     public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
         log.error("Triggering cluster health check...");
+        remoteClusterManager.restoreFileIfMissing();
+        log.warn("File checked, everything looks fine. Next time: Update clusters state.");
         remoteClusterManager.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 f9ddb9cf4..d0bdf10e5 100644
--- a/src/main/java/net/geant/nmaas/externalservices/kubernetes/RemoteClusterManager.java
+++ b/src/main/java/net/geant/nmaas/externalservices/kubernetes/RemoteClusterManager.java
@@ -3,30 +3,31 @@ package net.geant.nmaas.externalservices.kubernetes;
 import com.fasterxml.jackson.databind.ObjectMapper;
 import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;
 import io.fabric8.kubernetes.client.Config;
-import io.fabric8.kubernetes.client.DefaultKubernetesClient;
 import io.fabric8.kubernetes.client.KubernetesClient;
 import io.fabric8.kubernetes.client.KubernetesClientBuilder;
 import io.fabric8.kubernetes.client.KubernetesClientException;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
-import net.geant.nmaas.externalservices.kubernetes.entities.KCluster;
 import net.geant.nmaas.externalservices.kubernetes.api.model.RemoteClusterView;
+import net.geant.nmaas.externalservices.kubernetes.entities.KCluster;
 import net.geant.nmaas.externalservices.kubernetes.entities.KClusterDeployment;
 import net.geant.nmaas.externalservices.kubernetes.entities.KClusterIngress;
 import net.geant.nmaas.externalservices.kubernetes.entities.KClusterState;
 import net.geant.nmaas.externalservices.kubernetes.repositories.KClusterRepository;
+import net.geant.nmaas.notifications.MailAttributes;
+import net.geant.nmaas.notifications.NotificationEvent;
+import net.geant.nmaas.notifications.templates.MailType;
+import net.geant.nmaas.portal.api.domain.UserView;
 import net.geant.nmaas.portal.persistent.entity.Domain;
 import net.geant.nmaas.portal.service.DomainService;
 import org.modelmapper.ModelMapper;
+import org.springframework.context.ApplicationEventPublisher;
 import org.springframework.stereotype.Service;
 import org.springframework.web.multipart.MultipartFile;
 
 import java.io.File;
 import java.io.IOException;
 import java.io.InputStream;
-import java.io.InterruptedIOException;
-import java.net.SocketTimeoutException;
-import java.nio.charset.StandardCharsets;
 import java.nio.file.Files;
 import java.nio.file.Path;
 import java.nio.file.Paths;
@@ -36,7 +37,10 @@ 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.List;
+import java.util.Map;
 import java.util.Objects;
 import java.util.Optional;
 import java.util.stream.Collectors;
@@ -48,13 +52,15 @@ public class RemoteClusterManager {
 
     private final static ModelMapper modelMapper = new ModelMapper();
 
-    private final KClusterRepository KClusterRepository;
+    private final KClusterRepository clusterRepository;
     private final KubernetesClusterIngressManager kClusterIngressManager;
     private final KubernetesClusterDeploymentManager kClusterDeploymentManager;
     private final DomainService domainService;
+    private final ApplicationEventPublisher eventPublisher;
+
 
     public RemoteClusterView getClusterView(Long id) {
-        Optional<KCluster> cluster = KClusterRepository.findById(id);
+        Optional<KCluster> cluster = clusterRepository.findById(id);
         if (cluster.isPresent()) {
             return toView(cluster.get());
         } else {
@@ -63,12 +69,12 @@ public class RemoteClusterManager {
     }
 
     public List<RemoteClusterView> getAllClusterView() {
-        List<KCluster> clusters = KClusterRepository.findAll();
+        List<KCluster> clusters = clusterRepository.findAll();
         return clusters.stream().map(RemoteClusterManager::toView).collect(Collectors.toList());
     }
 
     public KCluster getCluster(Long id) {
-        Optional<KCluster> cluster = KClusterRepository.findById(id);
+        Optional<KCluster> cluster = clusterRepository.findById(id);
         if (cluster.isPresent()) {
             return cluster.get();
         } else {
@@ -99,7 +105,7 @@ public class RemoteClusterManager {
         log.debug("Filed saved in: {}", savedPath);
         entity.setPathConfigFile(savedPath);
 
-        KCluster cluster = this.KClusterRepository.save(entity);
+        KCluster cluster = this.clusterRepository.save(entity);
         log.debug("Cluster saved: {}", cluster.toString());
         return toView(cluster);
     }
@@ -126,10 +132,11 @@ public class RemoteClusterManager {
                                 .creationDate(OffsetDateTime.now())
                                 .modificationDate(OffsetDateTime.now())
                                 .codename(configView.getClusters().stream().findFirst().get().getName())
-                                .clusterConfigFile( new String(file.getBytes()))
+                                .clusterConfigFile(new String(file.getBytes()))
                                 .deployment(deployment)
                                 .ingress(ingress)
                                 .state(KClusterState.UNKNOWN)
+                                .contactEmail(view.getContactEmail())
                                 .currentStateSince(OffsetDateTime.now())
                                 .domains(view.getDomainNames().stream().map(d -> {
                                             Optional<Domain> dom = domainService.findDomain(d);
@@ -153,7 +160,7 @@ public class RemoteClusterManager {
     }
 
     public RemoteClusterView updateCluster(RemoteClusterView cluster, Long id) {
-        Optional<KCluster> entity = KClusterRepository.findById(id);
+        Optional<KCluster> entity = clusterRepository.findById(id);
 
         if (entity.isPresent()) {
             checkRequest(entity.get(), cluster, id);
@@ -176,7 +183,7 @@ public class RemoteClusterManager {
 
                 updated.setDeployment(modelMapper.map(cluster.getDeployment(), KClusterDeployment.class));
 
-                updated = KClusterRepository.save(updated);
+                updated = clusterRepository.save(updated);
                 //TODO : implement file update logic
                 return toView(updated);
 
@@ -239,7 +246,7 @@ public class RemoteClusterManager {
     }
 
     public void updateAllClusterState() {
-        List<KCluster> kClusters = KClusterRepository.findAll();
+        List<KCluster> kClusters = clusterRepository.findAll();
         kClusters.forEach(cluster -> {
             Config config = null;
             try {
@@ -249,7 +256,7 @@ public class RemoteClusterManager {
             }
             try {
                 KubernetesClient client = new KubernetesClientBuilder().withConfig(config).build();
-                log.debug("Get kubernetes version , something works {}",client.getKubernetesVersion().getPlatform());
+                log.debug("Get kubernetes version , something works {}", client.getKubernetesVersion().getPlatform());
                 //try to download kubernetes version to make sure connection to cluster is working
                 updateStateIfNeeded(cluster, KClusterState.UP);
 
@@ -257,17 +264,19 @@ public class RemoteClusterManager {
                 log.error("Can not connect to cluster {}", cluster.getCodename());
                 log.error(e.getMessage());
                 updateStateIfNeeded(cluster, KClusterState.DOWN);
+//                sendMail(cluster);
 
-            } catch (RuntimeException ex ) {
+            } catch (RuntimeException ex) {
                 log.error("Runtime error while checking health of cluster {}", ex.getMessage());
                 updateStateIfNeeded(cluster, KClusterState.UNKNOWN);
+//                sendMail(cluster);
 
             } catch (Exception ex) {
                 log.error("Caught unexpected exception: {}", ex.getMessage(), ex);
             }
 
         });
-        KClusterRepository.saveAll(kClusters);
+        clusterRepository.saveAll(kClusters);
     }
 
 
@@ -278,4 +287,44 @@ public class RemoteClusterManager {
         }
     }
 
+    private void sendMail(KCluster kCluster) {
+        UserView recipient = UserView.builder().email(kCluster.getContactEmail()).username(kCluster.getContactEmail()).selectedLanguage("EN").build();
+        Map<String, Object> attr = new HashMap<>();
+        attr.put("clusterId", kCluster.getId());
+        attr.put("clusterCodename", kCluster.getCodename());
+        attr.put("clusterName", kCluster.getName());
+        MailAttributes mailAttributes = MailAttributes.builder()
+                .mailType(MailType.CLUSTER_UNAVAILABLE)
+                .otherAttributes(attr)
+                .addressees(Collections.singletonList(recipient))
+                .build();
+
+        this.eventPublisher.publishEvent(new NotificationEvent(this, mailAttributes));
+
+    }
+
+    public void restoreFileIfMissing() {
+        List<KCluster> clusters = clusterRepository.findAll();
+        clusters.forEach(cluster -> {
+            if (!isFileAvailable(cluster.getPathConfigFile())) {
+                MultipartFile file = new StringMultipartFile("file",
+                        "config.yaml",
+                        "application/x-yaml",
+                        cluster.getClusterConfigFile());
+                try {
+                    String savedPath = saveFileToTmp(file);
+                    cluster.setPathConfigFile(savedPath);
+                    this.clusterRepository.save(cluster);
+                } catch (IOException | NoSuchAlgorithmException e) {
+                    log.error("Problem with resaved kubernetes config file from string to TMP folder. {}", e.getMessage());
+                }
+            }
+        });
+    }
+
+
+    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/externalservices/kubernetes/StringMultipartFile.java b/src/main/java/net/geant/nmaas/externalservices/kubernetes/StringMultipartFile.java
new file mode 100644
index 000000000..42b7fea06
--- /dev/null
+++ b/src/main/java/net/geant/nmaas/externalservices/kubernetes/StringMultipartFile.java
@@ -0,0 +1,67 @@
+package net.geant.nmaas.externalservices.kubernetes;
+
+import org.springframework.web.multipart.MultipartFile;
+
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+
+public class StringMultipartFile implements MultipartFile {
+
+    private final byte[] content;
+    private final String name;
+    private final String originalFilename;
+    private final String contentType;
+
+    public StringMultipartFile(String name, String originalFilename, String contentType, String contentStr) {
+        this.name = name;
+        this.originalFilename = originalFilename;
+        this.contentType = contentType;
+        this.content = contentStr.getBytes(StandardCharsets.UTF_8);
+    }
+
+    @Override
+    public String getName() {
+        return name;
+    }
+
+    @Override
+    public String getOriginalFilename() {
+        return originalFilename;
+    }
+
+    @Override
+    public String getContentType() {
+        return contentType;
+    }
+
+    @Override
+    public boolean isEmpty() {
+        return content.length == 0;
+    }
+
+    @Override
+    public long getSize() {
+        return content.length;
+    }
+
+    @Override
+    public byte[] getBytes() {
+        return content;
+    }
+
+    @Override
+    public InputStream getInputStream() {
+        return new ByteArrayInputStream(content);
+    }
+
+    @Override
+    public void transferTo(File dest) throws IOException, IllegalStateException {
+        try (FileOutputStream out = new FileOutputStream(dest)) {
+            out.write(content);
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/net/geant/nmaas/externalservices/kubernetes/api/model/RemoteClusterView.java b/src/main/java/net/geant/nmaas/externalservices/kubernetes/api/model/RemoteClusterView.java
index 563190078..9404460b9 100644
--- a/src/main/java/net/geant/nmaas/externalservices/kubernetes/api/model/RemoteClusterView.java
+++ b/src/main/java/net/geant/nmaas/externalservices/kubernetes/api/model/RemoteClusterView.java
@@ -43,4 +43,6 @@ public class RemoteClusterView {
 
     private OffsetDateTime  currentStateSince;
 
+    private String contactEmail;
+
 }
\ No newline at end of file
diff --git a/src/main/java/net/geant/nmaas/externalservices/kubernetes/entities/KCluster.java b/src/main/java/net/geant/nmaas/externalservices/kubernetes/entities/KCluster.java
index e5e81ce51..a8aab72d9 100644
--- a/src/main/java/net/geant/nmaas/externalservices/kubernetes/entities/KCluster.java
+++ b/src/main/java/net/geant/nmaas/externalservices/kubernetes/entities/KCluster.java
@@ -80,6 +80,9 @@ public class KCluster {
     @Column(nullable = false)
     private OffsetDateTime currentStateSince;
 
+    @Column(nullable = false)
+    private String contactEmail;
+
     public List<Domain> getDomains() {
         return domains != null ? domains : new ArrayList<>();
     }
@@ -97,6 +100,7 @@ public class KCluster {
                 ", clusterConfigFile='" + clusterConfigFile + '\'' +
                 ", pathConfigFile='" + pathConfigFile + '\'' +
                 ", state='" + state + '\'' +
+                ", email='" + contactEmail + '\'' +
                 ", ingress=" + (ingress != null ? ingress.getId() : "null") +
                 ", deployment=" + (deployment != null ? deployment.getId() : "null") +
                 '}';
diff --git a/src/main/java/net/geant/nmaas/notifications/templates/MailType.java b/src/main/java/net/geant/nmaas/notifications/templates/MailType.java
index 9291f9814..afa0a6167 100644
--- a/src/main/java/net/geant/nmaas/notifications/templates/MailType.java
+++ b/src/main/java/net/geant/nmaas/notifications/templates/MailType.java
@@ -25,5 +25,6 @@ public enum MailType {
 
     NEW_BULK_SSO_LOGIN,
     NEW_BULK_LOGIN,
-    VLAB_REQUEST
+    VLAB_REQUEST,
+    CLUSTER_UNAVAILABLE
 }
diff --git a/src/test/shell/data/mails/clusterUnavailable.json b/src/test/shell/data/mails/clusterUnavailable.json
new file mode 100644
index 000000000..7930a84d8
--- /dev/null
+++ b/src/test/shell/data/mails/clusterUnavailable.json
@@ -0,0 +1,55 @@
+{
+  "mailType": "CLUSTER_UNAVAILABLE",
+  "globalInformation": {
+    "LOGO_ALT": "GÉANT logo",
+    "PORTAL_LOGO_ALT": "nmaas logo",
+    "SENDER_INFO": "&#9400; GÉANT Association Hoekenrode 3 1102 BR - Amsterdam – Zuidoost- The Netherlands"
+  },
+  "templates": [
+    {
+      "language": "en",
+      "subject": "nmaas: 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>",
+        "SENDER": "Best regards,<br />nmaas Team",
+        "NOREPLY": "This is an automatically generated message, please do not reply.",
+        "SENDER_POLICY": ""
+      }
+    },
+    {
+      "language": "fr",
+      "subject": "nmaas: 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>",
+
+        "SENDER": "Best regards,<br />nmaas Team",
+        "NOREPLY": "This is an automatically generated message, please do not reply.",
+        "SENDER_POLICY": ""
+      }
+    },
+    {
+      "language": "de",
+      "subject": "nmaas: 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>",
+        "SENDER": "Best regards,<br />nmaas Team",
+        "NOREPLY": "This is an automatically generated message, please do not reply.",
+        "SENDER_POLICY": ""
+      }
+    },
+    {
+      "language": "pl",
+      "subject": "nmaas: Cluster 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>Please contact the administration for further information.</p>",
+        "SENDER": "Z pozdrowieniami,<br />Zespół nmaas",
+        "NOREPLY": "Ta wiadomość została wygenerowana automatycznie.",
+        "SENDER_POLICY": ""
+      }
+    }
+  ]
+}
\ No newline at end of file
-- 
GitLab


From 33fdeb2bef6b8acbdbc37478cf4cce1044a87a9b Mon Sep 17 00:00:00 2001
From: kbeyro <121854496+kbeyro@users.noreply.github.com>
Date: Wed, 14 May 2025 15:59:48 +0200
Subject: [PATCH 02/10] catch IO exception and set state

---
 .../externalservices/kubernetes/RemoteClusterManager.java | 8 +++++---
 1 file changed, 5 insertions(+), 3 deletions(-)

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 d0bdf10e5..c18671f50 100644
--- a/src/main/java/net/geant/nmaas/externalservices/kubernetes/RemoteClusterManager.java
+++ b/src/main/java/net/geant/nmaas/externalservices/kubernetes/RemoteClusterManager.java
@@ -29,6 +29,7 @@ import java.io.File;
 import java.io.IOException;
 import java.io.InputStream;
 import java.nio.file.Files;
+import java.nio.file.NoSuchFileException;
 import java.nio.file.Path;
 import java.nio.file.Paths;
 import java.nio.file.StandardCopyOption;
@@ -251,8 +252,9 @@ public class RemoteClusterManager {
             Config config = null;
             try {
                 config = Config.fromKubeconfig(Files.readString(Path.of(cluster.getPathConfigFile())));
-            } catch (IOException e) {
-                throw new RuntimeException(e);
+            } catch (IOException  e) {
+                log.error("IO error with accesing the file {}", e.getMessage());
+                updateStateIfNeeded(cluster, KClusterState.UNKNOWN);
             }
             try {
                 KubernetesClient client = new KubernetesClientBuilder().withConfig(config).build();
@@ -266,7 +268,7 @@ public class RemoteClusterManager {
                 updateStateIfNeeded(cluster, KClusterState.DOWN);
 //                sendMail(cluster);
 
-            } catch (RuntimeException ex) {
+            }  catch (RuntimeException  ex) {
                 log.error("Runtime error while checking health of cluster {}", ex.getMessage());
                 updateStateIfNeeded(cluster, KClusterState.UNKNOWN);
 //                sendMail(cluster);
-- 
GitLab


From cb5339fd3ae032727dcabbe4dd055ba3081f088a Mon Sep 17 00:00:00 2001
From: kbeyro <121854496+kbeyro@users.noreply.github.com>
Date: Wed, 14 May 2025 16:00:04 +0200
Subject: [PATCH 03/10] add labels

---
 src/test/shell/data/i18n/de.json | 3 ++-
 src/test/shell/data/i18n/en.json | 3 ++-
 src/test/shell/data/i18n/fr.json | 3 ++-
 src/test/shell/data/i18n/pl.json | 3 ++-
 4 files changed, 8 insertions(+), 4 deletions(-)

diff --git a/src/test/shell/data/i18n/de.json b/src/test/shell/data/i18n/de.json
index a70f151f6..3e5586f4b 100644
--- a/src/test/shell/data/i18n/de.json
+++ b/src/test/shell/data/i18n/de.json
@@ -190,7 +190,8 @@
     "STATE" : "State",
     "UP" : "Up",
     "DOWN" : "Down",
-    "UNKNOWN" : "Unknown"
+    "UNKNOWN" : "Unknown",
+    "DETAILS" : "Details"
   },
   "GITLAB": {
     "TITLE": "GitLab Konfiguration",
diff --git a/src/test/shell/data/i18n/en.json b/src/test/shell/data/i18n/en.json
index 7588befe2..f8f4354e2 100644
--- a/src/test/shell/data/i18n/en.json
+++ b/src/test/shell/data/i18n/en.json
@@ -191,7 +191,8 @@
     "STATE" : "State",
     "UP" : "Up",
     "DOWN" : "Down",
-    "UNKNOWN" : "Unknown"
+    "UNKNOWN" : "Unknown",
+    "DETAILS" : "Details"
   },
   "GITLAB": {
     "TITLE": "GitLab configuration",
diff --git a/src/test/shell/data/i18n/fr.json b/src/test/shell/data/i18n/fr.json
index 7d83e9fca..fcdb70a03 100644
--- a/src/test/shell/data/i18n/fr.json
+++ b/src/test/shell/data/i18n/fr.json
@@ -192,7 +192,8 @@
     "STATE" : "State",
     "UP" : "Up",
     "DOWN" : "Down",
-    "UNKNOWN" : "Unknown"
+    "UNKNOWN" : "Unknown",
+    "DETAILS" : "Details"
   },
   "GITLAB": {
     "TITLE": "Configuration de GitLab",
diff --git a/src/test/shell/data/i18n/pl.json b/src/test/shell/data/i18n/pl.json
index f9fa8af3f..6e58ed899 100644
--- a/src/test/shell/data/i18n/pl.json
+++ b/src/test/shell/data/i18n/pl.json
@@ -191,7 +191,8 @@
     "STATE" : "Stan",
     "UP" : "Aktywny",
     "DOWN" : "Nieaktywny",
-    "UNKNOWN" : "Nieznany"
+    "UNKNOWN" : "Nieznany",
+    "DETAILS" : "Detale"
   },
   "GITLAB": {
     "TITLE": "Konfiguracja GitLab",
-- 
GitLab


From c6207da535135b7528c8b5e9ba7633ac1ce4b899 Mon Sep 17 00:00:00 2001
From: kbeyro <121854496+kbeyro@users.noreply.github.com>
Date: Thu, 15 May 2025 12:32:00 +0200
Subject: [PATCH 04/10] add migration, change name

---
 .../externalservices/kubernetes/RemoteClusterManager.java      | 3 +--
 .../java/net/geant/nmaas/notifications/templates/MailType.java | 2 +-
 .../common/V1.8.0_20250515_1230__AddContactEmailToCluster.sql  | 1 +
 src/test/shell/data/mails/clusterUnavailable.json              | 2 +-
 4 files changed, 4 insertions(+), 4 deletions(-)
 create mode 100644 src/main/resources/db/migration/common/V1.8.0_20250515_1230__AddContactEmailToCluster.sql

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 c18671f50..e8fc06fdd 100644
--- a/src/main/java/net/geant/nmaas/externalservices/kubernetes/RemoteClusterManager.java
+++ b/src/main/java/net/geant/nmaas/externalservices/kubernetes/RemoteClusterManager.java
@@ -29,7 +29,6 @@ import java.io.File;
 import java.io.IOException;
 import java.io.InputStream;
 import java.nio.file.Files;
-import java.nio.file.NoSuchFileException;
 import java.nio.file.Path;
 import java.nio.file.Paths;
 import java.nio.file.StandardCopyOption;
@@ -296,7 +295,7 @@ public class RemoteClusterManager {
         attr.put("clusterCodename", kCluster.getCodename());
         attr.put("clusterName", kCluster.getName());
         MailAttributes mailAttributes = MailAttributes.builder()
-                .mailType(MailType.CLUSTER_UNAVAILABLE)
+                .mailType(MailType.REMOTE_CLUSTER_UNAVAILABLE)
                 .otherAttributes(attr)
                 .addressees(Collections.singletonList(recipient))
                 .build();
diff --git a/src/main/java/net/geant/nmaas/notifications/templates/MailType.java b/src/main/java/net/geant/nmaas/notifications/templates/MailType.java
index afa0a6167..1a2fb5119 100644
--- a/src/main/java/net/geant/nmaas/notifications/templates/MailType.java
+++ b/src/main/java/net/geant/nmaas/notifications/templates/MailType.java
@@ -26,5 +26,5 @@ public enum MailType {
     NEW_BULK_SSO_LOGIN,
     NEW_BULK_LOGIN,
     VLAB_REQUEST,
-    CLUSTER_UNAVAILABLE
+    REMOTE_CLUSTER_UNAVAILABLE
 }
diff --git a/src/main/resources/db/migration/common/V1.8.0_20250515_1230__AddContactEmailToCluster.sql b/src/main/resources/db/migration/common/V1.8.0_20250515_1230__AddContactEmailToCluster.sql
new file mode 100644
index 000000000..ad0284c06
--- /dev/null
+++ b/src/main/resources/db/migration/common/V1.8.0_20250515_1230__AddContactEmailToCluster.sql
@@ -0,0 +1 @@
+alter table k_cluster add COLUMN contact_email varchar(255) not null;
\ No newline at end of file
diff --git a/src/test/shell/data/mails/clusterUnavailable.json b/src/test/shell/data/mails/clusterUnavailable.json
index 7930a84d8..6b95494ca 100644
--- a/src/test/shell/data/mails/clusterUnavailable.json
+++ b/src/test/shell/data/mails/clusterUnavailable.json
@@ -1,5 +1,5 @@
 {
-  "mailType": "CLUSTER_UNAVAILABLE",
+  "mailType": "REMOTE_CLUSTER_UNAVAILABLE",
   "globalInformation": {
     "LOGO_ALT": "GÉANT logo",
     "PORTAL_LOGO_ALT": "nmaas logo",
-- 
GitLab


From 55113bae9b959b885487e22f1696b2d7e08817de Mon Sep 17 00:00:00 2001
From: kbeyro <121854496+kbeyro@users.noreply.github.com>
Date: Fri, 16 May 2025 14:08:29 +0200
Subject: [PATCH 05/10] add state since

---
 src/test/shell/data/i18n/de.json | 3 ++-
 src/test/shell/data/i18n/en.json | 3 ++-
 src/test/shell/data/i18n/fr.json | 3 ++-
 src/test/shell/data/i18n/pl.json | 3 ++-
 4 files changed, 8 insertions(+), 4 deletions(-)

diff --git a/src/test/shell/data/i18n/de.json b/src/test/shell/data/i18n/de.json
index d676fc09c..4d8597ce9 100644
--- a/src/test/shell/data/i18n/de.json
+++ b/src/test/shell/data/i18n/de.json
@@ -191,7 +191,8 @@
     "UP" : "Up",
     "DOWN" : "Down",
     "UNKNOWN" : "Unknown",
-    "DETAILS" : "Details"
+    "DETAILS" : "Details",
+    "STATE_SINCE" : "State last change"
   },
   "GITLAB": {
     "TITLE": "GitLab Konfiguration",
diff --git a/src/test/shell/data/i18n/en.json b/src/test/shell/data/i18n/en.json
index 1b9ec6abe..e2aea66b1 100644
--- a/src/test/shell/data/i18n/en.json
+++ b/src/test/shell/data/i18n/en.json
@@ -192,7 +192,8 @@
     "UP" : "Up",
     "DOWN" : "Down",
     "UNKNOWN" : "Unknown",
-    "DETAILS" : "Details"
+    "DETAILS" : "Details",
+    "STATE_SINCE" : "State last change"
   },
   "GITLAB": {
     "TITLE": "GitLab configuration",
diff --git a/src/test/shell/data/i18n/fr.json b/src/test/shell/data/i18n/fr.json
index 888c42e46..5051d9703 100644
--- a/src/test/shell/data/i18n/fr.json
+++ b/src/test/shell/data/i18n/fr.json
@@ -193,7 +193,8 @@
     "UP" : "Up",
     "DOWN" : "Down",
     "UNKNOWN" : "Unknown",
-    "DETAILS" : "Details"
+    "DETAILS" : "Details",
+    "STATE_SINCE" : "State last change"
   },
   "GITLAB": {
     "TITLE": "Configuration de GitLab",
diff --git a/src/test/shell/data/i18n/pl.json b/src/test/shell/data/i18n/pl.json
index c092433f5..f3e91fadc 100644
--- a/src/test/shell/data/i18n/pl.json
+++ b/src/test/shell/data/i18n/pl.json
@@ -192,7 +192,8 @@
     "UP" : "Aktywny",
     "DOWN" : "Nieaktywny",
     "UNKNOWN" : "Nieznany",
-    "DETAILS" : "Detale"
+    "DETAILS" : "Detale",
+    "STATE_SINCE" : "Stan od"
   },
   "GITLAB": {
     "TITLE": "Konfiguracja GitLab",
-- 
GitLab


From b852d3d704091dfefa3999dc179302ed3fe3fcd4 Mon Sep 17 00:00:00 2001
From: kbeyro <121854496+kbeyro@users.noreply.github.com>
Date: Fri, 16 May 2025 14:11:53 +0200
Subject: [PATCH 06/10] add REMOTE_CLUSTER welcome message

---
 .../kubernetes/RemoteClusterManager.java      | 11 ++--
 .../notifications/templates/MailType.java     |  3 +-
 .../shell/data/mails/clusterSupportEmail.json | 55 +++++++++++++++++++
 .../shell/data/mails/clusterUnavailable.json  |  8 +--
 4 files changed, 68 insertions(+), 9 deletions(-)
 create mode 100644 src/test/shell/data/mails/clusterSupportEmail.json

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 e8fc06fdd..f4e74e07c 100644
--- a/src/main/java/net/geant/nmaas/externalservices/kubernetes/RemoteClusterManager.java
+++ b/src/main/java/net/geant/nmaas/externalservices/kubernetes/RemoteClusterManager.java
@@ -107,6 +107,7 @@ public class RemoteClusterManager {
 
         KCluster cluster = this.clusterRepository.save(entity);
         log.debug("Cluster saved: {}", cluster.toString());
+        sendMail(cluster, MailType.REMOTE_CLUSTER_WELCOME_SUPPORT);
         return toView(cluster);
     }
 
@@ -265,12 +266,11 @@ public class RemoteClusterManager {
                 log.error("Can not connect to cluster {}", cluster.getCodename());
                 log.error(e.getMessage());
                 updateStateIfNeeded(cluster, KClusterState.DOWN);
-//                sendMail(cluster);
+
 
             }  catch (RuntimeException  ex) {
                 log.error("Runtime error while checking health of cluster {}", ex.getMessage());
                 updateStateIfNeeded(cluster, KClusterState.UNKNOWN);
-//                sendMail(cluster);
 
             } catch (Exception ex) {
                 log.error("Caught unexpected exception: {}", ex.getMessage(), ex);
@@ -285,17 +285,20 @@ public class RemoteClusterManager {
         if (!cluster.getState().equals(newState)) {
             cluster.setState(newState);
             cluster.setCurrentStateSince(OffsetDateTime.now());
+            if(cluster.getState().equals(KClusterState.DOWN) || cluster.getState().equals(KClusterState.UNKNOWN)) {
+                sendMail(cluster, MailType.REMOTE_CLUSTER_UNAVAILABLE);
+            };
         }
     }
 
-    private void sendMail(KCluster kCluster) {
+    private void sendMail(KCluster kCluster, MailType mailType) {
         UserView recipient = UserView.builder().email(kCluster.getContactEmail()).username(kCluster.getContactEmail()).selectedLanguage("EN").build();
         Map<String, Object> attr = new HashMap<>();
         attr.put("clusterId", kCluster.getId());
         attr.put("clusterCodename", kCluster.getCodename());
         attr.put("clusterName", kCluster.getName());
         MailAttributes mailAttributes = MailAttributes.builder()
-                .mailType(MailType.REMOTE_CLUSTER_UNAVAILABLE)
+                .mailType(mailType)
                 .otherAttributes(attr)
                 .addressees(Collections.singletonList(recipient))
                 .build();
diff --git a/src/main/java/net/geant/nmaas/notifications/templates/MailType.java b/src/main/java/net/geant/nmaas/notifications/templates/MailType.java
index 1a2fb5119..feec41f29 100644
--- a/src/main/java/net/geant/nmaas/notifications/templates/MailType.java
+++ b/src/main/java/net/geant/nmaas/notifications/templates/MailType.java
@@ -26,5 +26,6 @@ public enum MailType {
     NEW_BULK_SSO_LOGIN,
     NEW_BULK_LOGIN,
     VLAB_REQUEST,
-    REMOTE_CLUSTER_UNAVAILABLE
+    REMOTE_CLUSTER_UNAVAILABLE,
+    REMOTE_CLUSTER_WELCOME_SUPPORT
 }
diff --git a/src/test/shell/data/mails/clusterSupportEmail.json b/src/test/shell/data/mails/clusterSupportEmail.json
new file mode 100644
index 000000000..264b95477
--- /dev/null
+++ b/src/test/shell/data/mails/clusterSupportEmail.json
@@ -0,0 +1,55 @@
+{
+  "mailType": "REMOTE_CLUSTER_WELCOME_SUPPORT",
+  "globalInformation": {
+    "LOGO_ALT": "GÉANT logo",
+    "PORTAL_LOGO_ALT": "nmaas logo",
+    "SENDER_INFO": "&#9400; GÉANT Association Hoekenrode 3 1102 BR - Amsterdam – Zuidoost- The Netherlands"
+  },
+  "templates": [
+    {
+      "language": "en",
+      "subject": "nmaas: 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 pleace contact administration for futher information.</p>",
+        "SENDER": "Best regards,<br />nmaas Team",
+        "NOREPLY": "This is an automatically generated message, please do not reply.",
+        "SENDER_POLICY": ""
+      }
+    },
+    {
+      "language": "fr",
+      "subject": "nmaas: Cluster is unavailable",
+      "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 pleace contact administration for futher information.</p>",
+
+        "SENDER": "Best regards,<br />nmaas Team",
+        "NOREPLY": "This is an automatically generated message, please do not reply.",
+        "SENDER_POLICY": ""
+      }
+    },
+    {
+      "language": "de",
+      "subject": "nmaas: Cluster is unavailable",
+      "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 pleace contact administration for futher information.</p>",
+        "SENDER": "Best regards,<br />nmaas Team",
+        "NOREPLY": "This is an automatically generated message, please do not reply.",
+        "SENDER_POLICY": ""
+      }
+    },
+    {
+      "language": "pl",
+      "subject": "nmaas: Cluster jest niedostępny",
+      "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 pleace contact administration for futher information.</p>",
+        "SENDER": "Z pozdrowieniami,<br />Zespół nmaas",
+        "NOREPLY": "Ta wiadomość została wygenerowana automatycznie.",
+        "SENDER_POLICY": ""
+      }
+    }
+  ]
+}
\ No newline at end of file
diff --git a/src/test/shell/data/mails/clusterUnavailable.json b/src/test/shell/data/mails/clusterUnavailable.json
index 6b95494ca..02e80b5e2 100644
--- a/src/test/shell/data/mails/clusterUnavailable.json
+++ b/src/test/shell/data/mails/clusterUnavailable.json
@@ -11,7 +11,7 @@
       "subject": "nmaas: 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> 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>",
         "SENDER": "Best regards,<br />nmaas Team",
         "NOREPLY": "This is an automatically generated message, please do not reply.",
         "SENDER_POLICY": ""
@@ -22,7 +22,7 @@
       "subject": "nmaas: 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> 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>",
 
         "SENDER": "Best regards,<br />nmaas Team",
         "NOREPLY": "This is an automatically generated message, please do not reply.",
@@ -34,7 +34,7 @@
       "subject": "nmaas: 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> 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>",
         "SENDER": "Best regards,<br />nmaas Team",
         "NOREPLY": "This is an automatically generated message, please do not reply.",
         "SENDER_POLICY": ""
@@ -45,7 +45,7 @@
       "subject": "nmaas: Cluster 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>Please contact the administration for further information.</p>",
+        "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>",
         "SENDER": "Z pozdrowieniami,<br />Zespół nmaas",
         "NOREPLY": "Ta wiadomość została wygenerowana automatycznie.",
         "SENDER_POLICY": ""
-- 
GitLab


From a1a6b8504c2f2993e5ec6c8cf5d144255f301999 Mon Sep 17 00:00:00 2001
From: kbeyro <121854496+kbeyro@users.noreply.github.com>
Date: Fri, 16 May 2025 14:16:23 +0200
Subject: [PATCH 07/10] update logs for testings

---
 .../externalservices/kubernetes/ClusterMonitoringJob.java     | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

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 784fa5100..2423eb67c 100644
--- a/src/main/java/net/geant/nmaas/externalservices/kubernetes/ClusterMonitoringJob.java
+++ b/src/main/java/net/geant/nmaas/externalservices/kubernetes/ClusterMonitoringJob.java
@@ -16,9 +16,9 @@ public class ClusterMonitoringJob implements Job {
 
     @Override
     public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
-        log.error("Triggering cluster health check...");
+        log.info("Triggering cluster health check...");
         remoteClusterManager.restoreFileIfMissing();
-        log.warn("File checked, everything looks fine. Next time: Update clusters state.");
+        log.info("File checked, everything looks fine. Next stage: Update clusters state.");
         remoteClusterManager.updateAllClusterState();
     }
 }
-- 
GitLab


From 7d1b2d3c1cf4d58422a039c6743e955c12e3491e Mon Sep 17 00:00:00 2001
From: kbeyro <121854496+kbeyro@users.noreply.github.com>
Date: Fri, 16 May 2025 14:28:21 +0200
Subject: [PATCH 08/10] fix typo

---
 src/test/shell/data/mails/clusterSupportEmail.json | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/src/test/shell/data/mails/clusterSupportEmail.json b/src/test/shell/data/mails/clusterSupportEmail.json
index 264b95477..192f7daa3 100644
--- a/src/test/shell/data/mails/clusterSupportEmail.json
+++ b/src/test/shell/data/mails/clusterSupportEmail.json
@@ -11,7 +11,7 @@
       "subject": "nmaas: 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 pleace contact administration for futher information.</p>",
+        "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>",
         "SENDER": "Best regards,<br />nmaas Team",
         "NOREPLY": "This is an automatically generated message, please do not reply.",
         "SENDER_POLICY": ""
@@ -22,7 +22,7 @@
       "subject": "nmaas: Cluster is unavailable",
       "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 pleace contact administration for futher information.</p>",
+        "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>",
 
         "SENDER": "Best regards,<br />nmaas Team",
         "NOREPLY": "This is an automatically generated message, please do not reply.",
@@ -34,7 +34,7 @@
       "subject": "nmaas: Cluster is unavailable",
       "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 pleace contact administration for futher information.</p>",
+        "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>",
         "SENDER": "Best regards,<br />nmaas Team",
         "NOREPLY": "This is an automatically generated message, please do not reply.",
         "SENDER_POLICY": ""
@@ -45,7 +45,7 @@
       "subject": "nmaas: Cluster jest niedostępny",
       "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 pleace contact administration for futher information.</p>",
+        "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>",
         "SENDER": "Z pozdrowieniami,<br />Zespół nmaas",
         "NOREPLY": "Ta wiadomość została wygenerowana automatycznie.",
         "SENDER_POLICY": ""
-- 
GitLab


From 107911f392ca0f6b721130a15209c7eec577ffd2 Mon Sep 17 00:00:00 2001
From: kbeyro <121854496+kbeyro@users.noreply.github.com>
Date: Fri, 16 May 2025 14:31:30 +0200
Subject: [PATCH 09/10] update style

---
 src/test/shell/data/mails/clusterUnavailable.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/test/shell/data/mails/clusterUnavailable.json b/src/test/shell/data/mails/clusterUnavailable.json
index 02e80b5e2..b126a6d6f 100644
--- a/src/test/shell/data/mails/clusterUnavailable.json
+++ b/src/test/shell/data/mails/clusterUnavailable.json
@@ -45,7 +45,7 @@
       "subject": "nmaas: Cluster 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>Please contact the administration for further information.</p>",
+        "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>",
         "SENDER": "Z pozdrowieniami,<br />Zespół nmaas",
         "NOREPLY": "Ta wiadomość została wygenerowana automatycznie.",
         "SENDER_POLICY": ""
-- 
GitLab


From b1e6adbefec71d3e6a5cb11c7be49c2d475e6659 Mon Sep 17 00:00:00 2001
From: kbeyro <121854496+kbeyro@users.noreply.github.com>
Date: Fri, 16 May 2025 14:39:25 +0200
Subject: [PATCH 10/10] filter out 0 values from popular app map

---
 .../geant/nmaas/portal/service/impl/DashboardServiceImpl.java  | 3 +++
 1 file changed, 3 insertions(+)

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 0862eface..1569cfee2 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
@@ -64,6 +64,9 @@ public class DashboardServiceImpl implements DashboardService {
             applicationDeploymentCountPerName.put(name, appInstanceRepo.countByName(name));
         });
 
+        //filter not deployed application
+        applicationDeploymentCountPerName.entrySet().removeIf(app -> app.getValue() == 0);
+
 
         return DashboardView.builder()
                 .domainsCount(domainRepository.count())
-- 
GitLab