Commit 7e970091 by Johannes Edmeier

Polish contribution

- use MimeMessage with Content-Type text/hmtl - use a single template for subject and body - use a Spring aware TemplateResolver
parent 1283b1c1
......@@ -61,12 +61,21 @@
<sourceDocumentName>index.adoc</sourceDocumentName>
<backend>html5</backend>
<sourceHighlighter>coderay</sourceHighlighter>
<resources>
<resource>
<directory>src/main/asciidoc</directory>
<excludes>
<exclude>**/*.adoc</exclude>
</excludes>
</resource>
</resources>
<attributes>
<commit-id>${git.commit.id.abbrev}</commit-id>
<commit-time>${git.commit.time}</commit-time>
<project-version>${project.version}</project-version>
<spring-cloud-version>${spring-cloud.version}</spring-cloud-version>
<samples-dir>${basedir}/../spring-boot-admin-samples/</samples-dir>
<main-dir>${basedir}/../</main-dir>
</attributes>
</configuration>
</execution>
......
This diff was suppressed by a .gitattributes entry.
......@@ -81,8 +81,17 @@ TIP: This example combines the reminding and filtering notifiers. This allows yo
[[mail-notifications]]
==== Mail notifications ====
Configure a `JavaMailSender` using `spring-boot-starter-mail` and set a recipient.
Mail notifications will be delivered as HTML emails rendered using https://www.thymeleaf.org/[Thymeleaf] templates.
To enable Mail notifications, configure a `JavaMailSender` using `spring-boot-starter-mail` and set a recipient.
.Sample Mail Notification
image::mail-notification-sample.png[Sample Mail Notification]
NOTE: To prevent disclosure of sensitive information, the default mail template doesn't show any metadata of
the instance. If you want to you show some of the metadata you can use a custom template.
. Add spring-boot-starter-mail to your dependencies:
+
[source,xml]
.pom.xml
----
......@@ -92,12 +101,16 @@ Configure a `JavaMailSender` using `spring-boot-starter-mail` and set a recipien
</dependency>
----
. Configure a JavaMailSender
+
.application.properties
----
spring.mail.host=smtp.example.com
spring.boot.admin.notify.mail.to=admin@example.com
----
. Configure the mail with the options below
+
.Mail notifications configuration options
|===
| Property name |Description |Default value
......@@ -110,6 +123,10 @@ spring.boot.admin.notify.mail.to=admin@example.com
| Comma-delimited list of status changes to be ignored. Format: "<from-status>:<to-status>". Wildcards allowed.
| `"UNKNOWN:UP"`
| spring.boot.admin.notify.mail.template
| Resource path to the Thymelef template used for rendering.
| `"classpath:/META-INF/spring-boot-admin-server/mail/status-changed.html"`
| spring.boot.admin.notify.mail.to
| Comma-delimited list of mail recipients
| `"root@localhost"`
......@@ -120,36 +137,14 @@ spring.boot.admin.notify.mail.to=admin@example.com
| spring.boot.admin.notify.mail.from
| Mail sender
|
| `"Spring Boot Admin <noreply@localhost>"`
| spring.boot.admin.notify.mail.subject
| Mail subject. SpEL-expressions are supported
| `+++"#{application.name} (#{application.id}) is #{to.status}"+++`
| spring.boot.admin.notify.mail.additional-properties
| Additional properties which can be accessed from the template
|
| spring.boot.admin.notify.mail.text
| Mail body. SpEL-expressions are supported
| `+++"#{application.name} (#{application.id})\nstatus changed from #{from.status} to #{to.status}\n\n#{application.healthUrl}"+++`
|===
Mail notifications will be delivered as HTML emails composed of multiple https://www.thymeleaf.org/[Thymeleaf] templates.
The default body looks like this:
....
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
</head>
<body>
<span>some-service-id</span> (<span>a16d130feb17</span>)
status changed from <span>UP</span> to <span>DOWN</span>
<br />
<span>http://some.domain:8081/actuator/health</span>
</body>
</html>
....
[[pagerduty-notifications]]
==== PagerDuty notifications ====
To enable https://www.pagerduty.com/[PagerDuty] notifications you just have to add a generic service to your PagerDuty-account and set `spring.boot.admin.notify.pagerduty.service-key` to the service-key you received.
......
......@@ -45,6 +45,10 @@
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-mail</artifactId>
</dependency>
<dependency>
<groupId>de.codecentric</groupId>
<artifactId>spring-boot-admin-starter-client</artifactId>
</dependency>
......
......@@ -19,14 +19,18 @@ package de.codecentric.boot.admin;
import de.codecentric.boot.admin.server.config.AdminServerProperties;
import de.codecentric.boot.admin.server.config.EnableAdminServer;
import de.codecentric.boot.admin.server.domain.entities.InstanceRepository;
import de.codecentric.boot.admin.server.notify.CompositeNotifier;
import de.codecentric.boot.admin.server.notify.LoggingNotifier;
import de.codecentric.boot.admin.server.notify.Notifier;
import de.codecentric.boot.admin.server.notify.RemindingNotifier;
import de.codecentric.boot.admin.server.notify.filter.FilteringNotifier;
import de.codecentric.boot.admin.server.web.client.InstanceExchangeFilterFunction;
import java.time.Duration;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.context.annotation.Bean;
......@@ -99,12 +103,19 @@ public class SpringBootAdminApplication {
};
}
@Bean
public LoggingNotifier loggerNotifier(InstanceRepository repository) {
return new LoggingNotifier(repository);
}
@Configuration
public static class NotifierConfig {
private final InstanceRepository repository;
private final ObjectProvider<List<Notifier>> otherNotifiers;
public NotifierConfig(InstanceRepository repository) {
public NotifierConfig(InstanceRepository repository, ObjectProvider<List<Notifier>> otherNotifiers) {
this.repository = repository;
this.otherNotifiers = otherNotifiers;
}
@Primary
......@@ -118,12 +129,7 @@ public class SpringBootAdminApplication {
@Bean
public FilteringNotifier filteringNotifier() {
return new FilteringNotifier(loggerNotifier(), repository);
}
@Bean
public LoggingNotifier loggerNotifier() {
return new LoggingNotifier(repository);
return new FilteringNotifier(new CompositeNotifier(otherNotifiers.getObject()), repository);
}
}
}
......@@ -21,6 +21,7 @@ import de.codecentric.boot.admin.server.config.AdminServerProperties;
import de.codecentric.boot.admin.server.config.AdminServerWebConfiguration;
import de.codecentric.boot.admin.server.ui.web.UiController;
import java.nio.charset.StandardCharsets;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
......@@ -32,6 +33,7 @@ import org.springframework.context.annotation.Configuration;
import org.springframework.web.reactive.config.WebFluxConfigurer;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.thymeleaf.spring5.templateresolver.SpringResourceTemplateResolver;
import org.thymeleaf.templatemode.TemplateMode;
@Configuration
@ConditionalOnBean(AdminServerMarkerConfiguration.Marker.class)
......@@ -62,8 +64,8 @@ public class AdminServerUiAutoConfiguration {
resolver.setApplicationContext(this.applicationContext);
resolver.setPrefix(uiProperties.getTemplateLocation());
resolver.setSuffix(".html");
resolver.setTemplateMode("HTML");
resolver.setCharacterEncoding("UTF-8");
resolver.setTemplateMode(TemplateMode.HTML);
resolver.setCharacterEncoding(StandardCharsets.UTF_8.name());
resolver.setCacheable(uiProperties.isCacheTemplates());
resolver.setOrder(10);
resolver.setCheckExistence(true);
......
......@@ -32,11 +32,8 @@ import de.codecentric.boot.admin.server.notify.TelegramNotifier;
import de.codecentric.boot.admin.server.notify.filter.FilteringNotifier;
import de.codecentric.boot.admin.server.notify.filter.web.NotificationFilterController;
import java.util.HashSet;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.Set;
import org.apache.commons.lang.CharEncoding;
import org.reactivestreams.Publisher;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
......@@ -47,15 +44,17 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnSingleCandi
import org.springframework.boot.autoconfigure.condition.NoneNestedConditions;
import org.springframework.boot.autoconfigure.mail.MailSenderAutoConfiguration;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.mail.MailSender;
import org.springframework.mail.javamail.JavaMailSender;
import org.thymeleaf.TemplateEngine;
import org.thymeleaf.spring5.SpringTemplateEngine;
import org.thymeleaf.spring5.templateresolver.SpringResourceTemplateResolver;
import org.thymeleaf.templatemode.TemplateMode;
import org.thymeleaf.templateresolver.ClassLoaderTemplateResolver;
@Configuration
@AutoConfigureAfter({MailSenderAutoConfiguration.class})
......@@ -113,29 +112,28 @@ public class AdminServerNotifierAutoConfiguration {
@AutoConfigureBefore({NotifierTriggerConfiguration.class, CompositeNotifierConfiguration.class})
@ConditionalOnBean(MailSender.class)
public static class MailNotifierConfiguration {
private final ApplicationContext applicationContext;
public MailNotifierConfiguration(ApplicationContext applicationContext) {
this.applicationContext = applicationContext;
}
@Bean
@ConditionalOnMissingBean
@ConfigurationProperties("spring.boot.admin.notify.mail")
public MailNotifier mailNotifier(MailSender mailSender, InstanceRepository repository) {
public MailNotifier mailNotifier(JavaMailSender mailSender, InstanceRepository repository) {
return new MailNotifier(mailSender, repository, mailNotifierTemplateEngine());
}
@Bean
public TemplateEngine mailNotifierTemplateEngine() {
final ClassLoaderTemplateResolver templateResolver = new ClassLoaderTemplateResolver();
templateResolver.setOrder(Integer.valueOf(1));
templateResolver.setPrefix("/templates/");
final Set<String> notificationTemplateNames = new HashSet<>();
notificationTemplateNames.add("notification-template-subject.*");
notificationTemplateNames.add("notification-template-body.*");
templateResolver.setResolvablePatterns(notificationTemplateNames);
templateResolver.setTemplateMode(TemplateMode.TEXT);
templateResolver.setCharacterEncoding(CharEncoding.UTF_8);
SpringResourceTemplateResolver resolver = new SpringResourceTemplateResolver();
resolver.setApplicationContext(this.applicationContext);
resolver.setTemplateMode(TemplateMode.HTML);
resolver.setCharacterEncoding(StandardCharsets.UTF_8.name());
final SpringTemplateEngine templateEngine = new SpringTemplateEngine();
templateEngine.addTemplateResolver(templateResolver);
SpringTemplateEngine templateEngine = new SpringTemplateEngine();
templateEngine.addTemplateResolver(resolver);
return templateEngine;
}
}
......
......@@ -19,21 +19,29 @@ package de.codecentric.boot.admin.server.notify;
import de.codecentric.boot.admin.server.domain.entities.Instance;
import de.codecentric.boot.admin.server.domain.entities.InstanceRepository;
import de.codecentric.boot.admin.server.domain.events.InstanceEvent;
import org.springframework.mail.MailSender;
import org.springframework.mail.SimpleMailMessage;
import org.thymeleaf.TemplateEngine;
import org.thymeleaf.context.Context;
import reactor.core.publisher.Mono;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import javax.mail.MessagingException;
import javax.mail.internet.MimeMessage;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.mail.javamail.MimeMessageHelper;
import org.thymeleaf.TemplateEngine;
import org.thymeleaf.context.Context;
import static java.util.Collections.singleton;
/**
* Notifier sending emails.
* Notifier sending emails using thymleaf templates.
*
* @author Johannes Edmeier
*/
public class MailNotifier extends AbstractStatusChangeNotifier {
private final MailSender sender;
private final JavaMailSender mailSender;
private final TemplateEngine templateEngine;
/**
* recipients of the mail
......@@ -43,44 +51,65 @@ public class MailNotifier extends AbstractStatusChangeNotifier {
/**
* cc-recipients of the mail
*/
private String[] cc;
private String[] cc = {};
/**
* sender of the change
*/
private String from = null;
private String from = "Spring Boot Admin <noreply@localhost>";
/**
* Mail Subject. SpEL template using event as root;
* Additional properties to be set for the template
*/
private String subject;
private Map<String, Object> additionalProperties = new HashMap<>();
/**
* Mail Text. Is prepared via thymeleaf template;
* Base-URL used for hyperlinks in mail
*/
private TemplateEngine templateEngine;
private String baseUrl;
public MailNotifier(MailSender sender, InstanceRepository repository, TemplateEngine templateEngine) {
/**
* Thymleaf template for mail
*/
private String template = "classpath:/META-INF/spring-boot-admin-server/mail/status-changed.html";
public MailNotifier(JavaMailSender mailSender, InstanceRepository repository, TemplateEngine templateEngine) {
super(repository);
this.sender = sender;
this.mailSender = mailSender;
this.templateEngine = templateEngine;
}
@Override
protected Mono<Void> doNotify(InstanceEvent event, Instance instance) {
final Context ctx = new Context();
ctx.setVariable("event", event);
ctx.setVariable("instance", instance);
ctx.setVariable("lastStatus", getLastStatus(event.getInstance()));
return Mono.fromRunnable(() -> {
Context ctx = new Context();
ctx.setVariables(additionalProperties);
ctx.setVariable("baseUrl", this.baseUrl);
ctx.setVariable("event", event);
ctx.setVariable("instance", instance);
ctx.setVariable("lastStatus", getLastStatus(event.getInstance()));
try {
MimeMessage mimeMessage = mailSender.createMimeMessage();
MimeMessageHelper message = new MimeMessageHelper(mimeMessage, StandardCharsets.UTF_8.name());
message.setText(getBody(ctx).replaceAll("\\s+\\n", "\n"), true);
message.setSubject(getSubject(ctx));
message.setTo(this.to);
message.setCc(this.cc);
message.setFrom(this.from);
mailSender.send(mimeMessage);
} catch (MessagingException ex) {
throw new RuntimeException("Error sending mail notification", ex);
}
});
}
final SimpleMailMessage message = new SimpleMailMessage();
message.setTo(to);
message.setFrom(from);
message.setSubject(templateEngine.process("notification-template-subject.txt", ctx));
message.setText(templateEngine.process("notification-template-body.html", ctx));
message.setCc(cc);
protected String getBody(Context ctx) {
return templateEngine.process(this.template, ctx);
}
return Mono.fromRunnable(() -> sender.send(message));
protected String getSubject(Context ctx) {
return templateEngine.process(this.template, singleton("subject"), ctx).trim();
}
public void setTo(String[] to) {
......@@ -107,12 +136,27 @@ public class MailNotifier extends AbstractStatusChangeNotifier {
return from;
}
public void setSubject(String subject) {
this.subject = subject;
public String getTemplate() {
return template;
}
public void setTemplate(String template) {
this.template = template;
}
public String getBaseUrl() {
return baseUrl;
}
public void setBaseUrl(String baseUrl) {
this.baseUrl = baseUrl;
}
public String getSubject() {
return subject;
public Map<String, Object> getAdditionalProperties() {
return additionalProperties;
}
public void setAdditionalProperties(Map<String, Object> additionalProperties) {
this.additionalProperties = additionalProperties;
}
}
<!DOCTYPE html>
<!--
~ Copyright 2014-2018 the original author or authors.
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
<style>
h1, h2, h3, h4, h5, h6 {
font-weight: 400
}
ul {
list-style: none
}
html {
box-sizing: border-box
}
*, :after, :before {
box-sizing: inherit
}
table {
border-collapse: collapse;
border-spacing: 0
}
td, th {
text-align: left
}
body, button {
font-family: BlinkMacSystemFont, -apple-system, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, "Fira Sans", "Droid Sans", "Helvetica Neue", Helvetica, Arial, sans-serif
}
code, pre {
-moz-osx-font-smoothing: auto;
-webkit-font-smoothing: auto;
font-family: monospace
}
</style>
</head>
<body>
<th:block th:remove="all">
<!-- This block will not appear in the body and is used for the subject -->
<th:block th:remove="tag" th:fragment="subject">
[[${instance.registration.name}]] ([[(${instance.id})]]) is [[${event.statusInfo.status}]]
</th:block>
</th:block>
<h1><span th:text="${instance.registration.name}"/> (<span th:text="${instance.id}"/>)
is <span th:text="${event.statusInfo.status}"/>
</h1>
<p>
Instance <a th:if="${baseUrl}" th:href="@{${baseUrl + '/#/instances/' + instance.id + '/'}}"><span
th:text="${instance.id}"/></a>
<span th:unless="${baseUrl}" th:text="${instance.id}"/>
changed status from <span th:text="${lastStatus}"/> to <span th:text="${event.statusInfo.status}"/>
</p>
<h2>Status Details</h2>
<dl th:fragment="statusDetails" th:with="details = ${details ?: event.statusInfo.details}">
<th:block th:each="detail : ${details}">
<dt th:text="${detail.key}"/>
<dd th:unless="${detail.value instanceof T(java.util.Map)}" th:text="${detail.value}"/>
<dd th:if="${detail.value instanceof T(java.util.Map)}">
<dl th:replace="${#execInfo.templateName} :: statusDetails (details = ${detail.value})"/>
</dd>
</th:block>
</dl>
<h2>Registration</h2>
<table>
<tr th:if="${instance.registration.serviceUrl}">
<td>Service Url</td>
<td>
<a th:href="${instance.registration.serviceUrl}" th:text="${instance.registration.serviceUrl}"></a>
</td>
</tr>
<tr>
<td>Health Url</td>
<td>
<a th:href="${instance.registration.healthUrl}" th:text="${instance.registration.healthUrl}"></a>
</td>
</tr>
<tr th:if="${instance.registration.managementUrl}">
<td>Management Url</td>
<td>
<a th:href="${instance.registration.managementUrl}" th:text="${instance.registration.managementUrl}"></a>
</td>
</tr>
</table>
</body>
</html>
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
</head>
<body>
<span th:text="${instance.registration.name}"/> (<span th:text="${instance.id}"/>)
status changed from <span th:text="${lastStatus}"/> to <span th:text="${event.statusInfo.status}"/>
<br />
<span th:text="${instance.registration.healthUrl}"/>
</body>
</html>
[(${instance.registration.name})] ([(${instance.id})]) is [(${event.statusInfo.status})]
<!DOCTYPE html>
<!--
~ Copyright 2014-2018 the original author or authors.
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
</head>
<th:block th:remove="all">
<!-- this section is excluded from mail body -->
<th:block th:fragment="subject">
[[${instance.registration.name}]] ([[(${instance.id})]]) is [[${event.statusInfo.status}]]
</th:block>
</th:block>
<body>
<h1 th:text="${customProperty}"></h1>
<span th:text="${instance.registration.name}"/> (<span th:text="${instance.id}"/>)
status changed from <span th:text="${lastStatus}"/> to <span th:text="${event.statusInfo.status}"/>
<br/>
<span th:text="${instance.registration.healthUrl}"/>
</body>
</html>
<!DOCTYPE html>
<!--
~ Copyright 2014-2018 the original author or authors.
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
</head>
<body>
<h1>HELLO WORLD!</h1>
<span>application-name</span> (<span>cafebabe</span>)
status changed from <span>UNKNOWN</span> to <span>DOWN</span>
<br/>
<span>http://localhost:8081/actuator/health</span>
</body>
</html>
<!DOCTYPE html>
<!--
~ Copyright 2014-2018 the original author or authors.
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
<style>
h1, h2, h3, h4, h5, h6 {
font-weight: 400
}
ul {
list-style: none
}
html {
box-sizing: border-box
}
*, :after, :before {
box-sizing: inherit
}
table {
border-collapse: collapse;
border-spacing: 0
}
td, th {
text-align: left
}
body, button {
font-family: BlinkMacSystemFont, -apple-system, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, "Fira Sans", "Droid Sans", "Helvetica Neue", Helvetica, Arial, sans-serif
}
code, pre {
-moz-osx-font-smoothing: auto;
-webkit-font-smoothing: auto;
font-family: monospace
}
</style>
</head>
<body>
<h1><span>application-name</span> (<span>cafebabe</span>)
is <span>DOWN</span>
</h1>
<p>
Instance <a href="http://localhost:8080/#/instances/cafebabe/"><span>cafebabe</span></a>
changed status from <span>UNKNOWN</span> to <span>DOWN</span>
</p>
<h2>Status Details</h2>
<dl>
<dt>Complex Value</dt>
<dd>
<dl>
<dt>Nested Simple Value</dt>
<dd>99!</dd>
</dl>
</dd>
<dt>Simple Value</dt>
<dd>1234</dd>
</dl>
<h2>Registration</h2>
<table>
<tr>
<td>Service Url</td>
<td>
<a href="http://localhost:8081/">http://localhost:8081/</a>
</td>
</tr>
<tr>
<td>Health Url</td>
<td>
<a href="http://localhost:8081/actuator/health">http://localhost:8081/actuator/health</a>
</td>
</tr>
<tr>
<td>Management Url</td>
<td>
<a href="http://localhost:8081/actuator">http://localhost:8081/actuator</a>
</td>
</tr>
</table>
</body>
</html>
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment