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 bc88e1f2cf3e0291e3edd79b75b5d98d87bf6c8e..2423eb67c11c24480e5e0cc06320cbde16bfd02c 100644
--- a/src/main/java/net/geant/nmaas/externalservices/kubernetes/ClusterMonitoringJob.java
+++ b/src/main/java/net/geant/nmaas/externalservices/kubernetes/ClusterMonitoringJob.java
@@ -16,7 +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.info("File checked, everything looks fine. Next stage: 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 f9ddb9cf405c294741bf1764ae2e0898bd8dfb73..f4e74e07cd9034b089f902e7eabeb0af72a0073c 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,8 +105,9 @@ 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());
+ sendMail(cluster, MailType.REMOTE_CLUSTER_WELCOME_SUPPORT);
return toView(cluster);
}
@@ -126,10 +133,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 +161,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 +184,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,17 +247,18 @@ public class RemoteClusterManager {
}
public void updateAllClusterState() {
- List<KCluster> kClusters = KClusterRepository.findAll();
+ List<KCluster> kClusters = clusterRepository.findAll();
kClusters.forEach(cluster -> {
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();
- 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);
@@ -258,7 +267,8 @@ public class RemoteClusterManager {
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);
@@ -267,7 +277,7 @@ public class RemoteClusterManager {
}
});
- KClusterRepository.saveAll(kClusters);
+ clusterRepository.saveAll(kClusters);
}
@@ -275,7 +285,50 @@ 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, 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)
+ .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 0000000000000000000000000000000000000000..42b7fea0646fff4a0df32035a1f21d373e374069
--- /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 563190078758521fb6bff87ff3c44e0ff414c8a2..9404460b9ca0327cf8b23ca1124398451f02d924 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 e5e81ce51debe3b7d3c1379917b8b3878861c637..a8aab72d92d81621e423631089e8772c38215c3c 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 9291f9814ef56e4379faf28360b94ff2b8fab817..feec41f299d8e4b8409febe38d67bd4bdaa9be90 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,7 @@ public enum MailType {
NEW_BULK_SSO_LOGIN,
NEW_BULK_LOGIN,
- VLAB_REQUEST
+ VLAB_REQUEST,
+ REMOTE_CLUSTER_UNAVAILABLE,
+ REMOTE_CLUSTER_WELCOME_SUPPORT
}
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 0862efacea6fdea90ed6ee0e147a2771c0778d37..1569cfee20dba94bf5c523dcffd7610d72c12bd3 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())
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 0000000000000000000000000000000000000000..ad0284c06de346a25bcee80ae39624fec5a79b88
--- /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/i18n/de.json b/src/test/shell/data/i18n/de.json
index 76e4a329fb6dc4fc2ce6fd31d92995e1b75cc306..4d8597ce9478703a17493293fef4a55df1d87057 100644
--- a/src/test/shell/data/i18n/de.json
+++ b/src/test/shell/data/i18n/de.json
@@ -190,7 +190,9 @@
"STATE" : "State",
"UP" : "Up",
"DOWN" : "Down",
- "UNKNOWN" : "Unknown"
+ "UNKNOWN" : "Unknown",
+ "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 a6aee3818aac82cfb7c9a488dc7d06774c578dcb..e2aea66b117a82b6da5343dadcc94354ebcae2c1 100644
--- a/src/test/shell/data/i18n/en.json
+++ b/src/test/shell/data/i18n/en.json
@@ -191,7 +191,9 @@
"STATE" : "State",
"UP" : "Up",
"DOWN" : "Down",
- "UNKNOWN" : "Unknown"
+ "UNKNOWN" : "Unknown",
+ "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 147d5f3e958f54c54832190cddf91214dcbac223..5051d97030f59fd51f7f4e1848de3bb1f27c4801 100644
--- a/src/test/shell/data/i18n/fr.json
+++ b/src/test/shell/data/i18n/fr.json
@@ -192,7 +192,9 @@
"STATE" : "State",
"UP" : "Up",
"DOWN" : "Down",
- "UNKNOWN" : "Unknown"
+ "UNKNOWN" : "Unknown",
+ "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 7aac0a1066bb582b38a38dab7b5619c57f68e566..f3e91fadca23a09c96a470ee314048e333909297 100644
--- a/src/test/shell/data/i18n/pl.json
+++ b/src/test/shell/data/i18n/pl.json
@@ -191,7 +191,9 @@
"STATE" : "Stan",
"UP" : "Aktywny",
"DOWN" : "Nieaktywny",
- "UNKNOWN" : "Nieznany"
+ "UNKNOWN" : "Nieznany",
+ "DETAILS" : "Detale",
+ "STATE_SINCE" : "Stan od"
},
"GITLAB": {
"TITLE": "Konfiguracja GitLab",
diff --git a/src/test/shell/data/mails/clusterSupportEmail.json b/src/test/shell/data/mails/clusterSupportEmail.json
new file mode 100644
index 0000000000000000000000000000000000000000..192f7daa3413adb05771036fc34018fec0516938
--- /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": "Ⓒ 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 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": ""
+ }
+ },
+ {
+ "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 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": ""
+ }
+ },
+ {
+ "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 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": ""
+ }
+ },
+ {
+ "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 please contact 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
diff --git a/src/test/shell/data/mails/clusterUnavailable.json b/src/test/shell/data/mails/clusterUnavailable.json
new file mode 100644
index 0000000000000000000000000000000000000000..b126a6d6fbd55f29d996e7dd69e5ef8f7757cbf3
--- /dev/null
+++ b/src/test/shell/data/mails/clusterUnavailable.json
@@ -0,0 +1,55 @@
+{
+ "mailType": "REMOTE_CLUSTER_UNAVAILABLE",
+ "globalInformation": {
+ "LOGO_ALT": "GÉANT logo",
+ "PORTAL_LOGO_ALT": "nmaas logo",
+ "SENDER_INFO": "Ⓒ 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> <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": ""
+ }
+ }
+ ]
+}
\ No newline at end of file