Commit 1ebc75e9 by Johannes Edmeier

Document Customizing options

parent aac3f00a
......@@ -76,6 +76,7 @@
<spring-cloud-version>${spring-cloud.version}</spring-cloud-version>
<samples-dir>${basedir}/../spring-boot-admin-samples/</samples-dir>
<main-dir>${basedir}/../</main-dir>
<github-src>https://github.com/codecentric/spring-boot-admin/tree/master</github-src>
</attributes>
</configuration>
</execution>
......
[[customizing]]
== Customizing ==
[[customizing-notifiers]]
=== Custom Notifiers ===
You can add your own Notifiers by adding Spring Beans which implement the `Notifier` interface, at best by extending
`AbstractEventNotifier` or `AbstractStatusChangeNotifier`
[source,java,indent=0]
----
include::{samples-dir}/spring-boot-admin-sample-servlet/src/main/java/de/codecentric/boot/admin/CustomNotifier.java[tags=customization-notifiers]
----
[[customizing-headers]]
=== Injecting Custom HTTP Headers ===
In case you need to inject custom HTTP headers into the requests made to the monitored application's actuator endpoints you can easily add a `HttpHeadersProvider`:
[source,java,indent=0]
----
include::{samples-dir}/spring-boot-admin-sample-servlet/src/main/java/de/codecentric/boot/admin/SpringBootAdminApplication.java[tags=customization-http-headers-providers]
----
[[customizing-instance-filter]]
=== Intercepting Requests And Responses ===
You can intercept and modify requests and responses made to the monitored application's actuator endpoints by implementing the `InstanceExchangeFilterFunction` interface.
This can be useful for auditing or adding some extra security checks.
[source,java,indent=0]
----
include::{samples-dir}/spring-boot-admin-sample-servlet/src/main/java/de/codecentric/boot/admin/SpringBootAdminApplication.java[tags=customization-instance-exchange-filter-function]
----
[[customizing-custom-views]]
=== Custom Views ===
It is possible to add custom views to the ui. The views must be implemented as https://vuejs.org/[Vue.js] components.
The Javascript-Bundle and Css-Stlysheet must be placed on the classpath at `/META-INF/spring-boot-admin-server-ui/extensions/{name}/` so the server can pick them up.
The {github-src}/spring-boot-admin-samples/spring-boot-admin-sample-custom-ui/[spring-boot-admin-sample-custom-ui] module contains a sample which has the necessary maven setup to build such a module.
The custom extension registers itself by calling `SBA.use()` and need to expose a `install()` function, which is called by the ui when setting up the routes.
The `install()` function receives a parameter object referencing the {github-src}/spring-boot-admin-server-ui/src/main/frontend/viewRegistry.js[viewRegistry] and the {github-src}/spring-boot-admin-server-ui/src/main/frontend/store.js[applicationStore] in order to register views and/or callbacks.
[[customizing-custom-views-top-level]]
==== Adding a Top-Level View ====
Here is a simple top level view just listing all registered applications:
[source,html]
----
include::{samples-dir}/spring-boot-admin-sample-custom-ui/src/custom.vue[lines=17..-1]
----
<1> If you define a `applications` prop the component will receive all registered applications.
TIP: There are some helpful methods on the application and instances object available. Have a look at {github-src}/spring-boot-admin-server-ui/src/main/frontend/services/application.js[application.js] and {github-src}/spring-boot-admin-server-ui/src/main/frontend/services/instance.js[instance.js]
And this is how you register the top-level view.
[source,javascript]
----
include::{samples-dir}/spring-boot-admin-sample-custom-ui/src/index.js[tags=customization-ui-toplevel]
----
<1> Name of the view and the route to the view
<2> Path where the view will be accessible
<3> The imported custom component, which will be rendered on the route.
<4> The label for the custom view to be shown in the top navigation bar.
<5> Order for the view. Views in the top navigation bar are sorted by ascending order.
[[customizing-custom-views-instance]]
==== Visualizing a Custom Endpoint ====
Here is a view to show a custom endpoint:
[source,html]
----
include::{samples-dir}/spring-boot-admin-sample-custom-ui/src/custom-endpoint.vue[lines=17..-1]
----
<1> If you define a `instance` prop the component will receive the instance the view should be rendered for.
<2> Each instance has a preconfigured https://github.com/axios/axios[axios] instance to access the endpoints with the correct path and headers.
Registering the instance view works like for the top-level view with some additional properties:
[source,javascript]
----
include::{samples-dir}/spring-boot-admin-sample-custom-ui/src/index.js[tags=customization-ui-endpoint]
----
<1> The parent must be 'instances' in order to render the new custom view for a single instance.
<2> If you add a `isEnabled` callback you can figure out dynamically if the view should be show for the particular instance.
......@@ -36,9 +36,9 @@ public class SpringBootAdminApplication {
}
----
NOTE: If you want to setup the Spring Boot Admin Server via war-deployment in a servlet-container, please have a look at the https://github.com/codecentric/spring-boot-admin/blob/master/spring-boot-admin-samples/spring-boot-admin-sample-war/[spring-boot-admin-sample-war].
NOTE: If you want to setup the Spring Boot Admin Server via war-deployment in a servlet-container, please have a look at the {github-src}/spring-boot-admin-samples/spring-boot-admin-sample-war/[spring-boot-admin-sample-war].
See also the https://github.com/codecentric/spring-boot-admin/tree/master/spring-boot-admin-samples/spring-boot-admin-sample-servlet/[spring-boot-admin-sample-servlet] project, which also adds security.
See also the {github-src}/spring-boot-admin-samples/spring-boot-admin-sample-servlet/[spring-boot-admin-sample-servlet] project, which also adds security.
[[register-client-applications]]
=== Registering client applications ===
......@@ -81,14 +81,14 @@ management.endpoints.web.exposure.include: "*" #<2>
+
[source,java]
----
@Configuration
public static class SecurityPermitAllConfig extends WebSecurityConfigurerAdapter {
@Configuration
public static class SecurityPermitAllConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().anyRequest().permitAll() //<1>
.and().csrf().disable();
}
}
}
----
<1> For the sake of brevity we're disabling the security for now. Have a look at the <<securing-spring-boot-admin,security section>> on how to deal with secured endpoints.
......@@ -98,7 +98,7 @@ management.endpoints.web.exposure.include: "*" #<2>
If you already use Spring Cloud Discovery for your applications you don't need the SBA Client. Just add a DiscoveryClient to Spring Boot Admin Server, the rest is done by our AutoConfiguration.
The following steps uses Eureka, but other Spring Cloud Discovery implementations are supported as well. There are examples using https://github.com/codecentric/spring-boot-admin/tree/master/spring-boot-admin-samples/spring-boot-admin-sample-consul/[Consul] and https://github.com/codecentric/spring-boot-admin/tree/master/spring-boot-admin-samples/spring-boot-admin-sample-zookeeper/[Zookeeper].
The following steps uses Eureka, but other Spring Cloud Discovery implementations are supported as well. There are examples using {github-src}/spring-boot-admin-samples/spring-boot-admin-sample-consul/[Consul] and https://github.com/codecentric/spring-boot-admin/tree/master/spring-boot-admin-samples/spring-boot-admin-sample-zookeeper/[Zookeeper].
Also have a look at the http://projects.spring.io/spring-cloud/spring-cloud.html[Spring Cloud documentation].
......@@ -146,6 +146,6 @@ include::{samples-dir}/spring-boot-admin-sample-eureka/src/main/resources/applic
<1> Configuration section for the Eureka client
<2> As with Spring Boot 2 most of the endpoints aren't exposed via http by default, we expose all of them. For production you should carefully choose which endpoints to expose.
See also https://github.com/codecentric/spring-boot-admin/tree/master/spring-boot-admin-samples/spring-boot-admin-sample-eureka/[spring-boot-admin-sample-eureka].
See also {github-src}/spring-boot-admin-samples/spring-boot-admin-sample-eureka/[spring-boot-admin-sample-eureka].
TIP: You can include the Spring Boot Admin Server to your Eureka server. Setup everything as described above and set `spring.boot.admin.context-path` to something different than `"/"` so that the Spring Boot Admin Server UI won't clash with Eureka's one.
......@@ -27,6 +27,8 @@ include::server.adoc[]
include::security.adoc[]
include::customizing.adoc[]
include::changes-2.x.adoc[]
include::faqs.adoc[]
......@@ -7,7 +7,7 @@ Since there are several approaches on solving authentication and authorization i
By default `spring-boot-admin-server-ui` provides a login page and a logout button.
A Spring Security configuration could look like this:
[source,java]
[source,java,indent=0]
----
include::{samples-dir}/spring-boot-admin-sample-servlet/src/main/java/de/codecentric/boot/admin/SpringBootAdminApplication.java[tags=configuration-spring-security]
----
......@@ -19,7 +19,7 @@ include::{samples-dir}/spring-boot-admin-sample-servlet/src/main/java/de/codecen
<6> Disables CRSF-Protection the endpoint the Spring Boot Admin Client uses to register.
<7> Disables CRSF-Protection for the actuator endpoints.
For a complete sample look at https://github.com/codecentric/spring-boot-admin/tree/master/spring-boot-admin-samples/spring-boot-admin-sample-servlet/[spring-boot-admin-sample-servlet.
For a complete sample look at {github-src}/spring-boot-admin-samples/spring-boot-admin-sample-servlet/[spring-boot-admin-sample-servlet].
NOTE: If you protect the `/instances` endpoint don't forget to configure the username and password on your SBA-Client using `spring.boot.admin.client.username` and `spring.boot.admin.client.password`.
......
......@@ -14,7 +14,7 @@ include::{samples-dir}/spring-boot-admin-sample-hazelcast/pom.xml[tags=dependenc
. Instantiate a HazelcastConfig:
+
[source,java]
[source,java,indent=0]
----
include::{samples-dir}/spring-boot-admin-sample-hazelcast/src/main/java/de/codecentric/boot/admin/SpringBootAdminApplication.java[tags=application-hazelcast]
----
......
......@@ -40,7 +40,7 @@ If you add a `FilteringNotifier` to your `ApplicationContext` a RESTful interfac
This notifier is useful if you don't want recieve notifications when deploying your applications. Before stopping the application you can add an (expiring) filter either via a `POST` request.
.How to configure filtering
[source,java]
[source,java,indent=0]
----
include::{samples-dir}/spring-boot-admin-sample-servlet/src/main/java/de/codecentric/boot/admin/SpringBootAdminApplication.java[tags=configuration-filtering-notifier]
----
......
......@@ -24,7 +24,7 @@
<script>
export default {
props: {
instance: {
instance: { //<1>
type: Object,
required: true
}
......@@ -33,7 +33,7 @@ export default {
text: ''
}),
async created() {
const response = await this.instance.axios.get('actuator/custom');
const response = await this.instance.axios.get('actuator/custom'); //<2>
this.text = response.data;
}
};
......
......@@ -21,7 +21,7 @@
<script>
export default {
props: {
applications: {
applications: { //<1>
type: Array,
required: true
}
......@@ -31,6 +31,3 @@ export default {
}
};
</script>
<style>
</style>
......@@ -14,30 +14,36 @@
* limitations under the License.
*/
/* global SBA */
import custom from './custom';
import customEndpoint from './custom-endpoint';
/* global SBA */
SBA.extensions.push({
// tag::customization-ui-toplevel[]
SBA.use({
install({viewRegistry}) {
viewRegistry.addView({
name: 'custom',
path: '/custom',
component: custom,
props: true,
label: 'Custom',
order: 1000,
name: 'custom', //<1>
path: '/custom', //<2>
component: custom, //<3>
label: 'Custom', //<4>
order: 1000, //<5>
});
}
});
// end::customization-ui-toplevel[]
// tag::customization-ui-endpoint[]
SBA.use({
install({viewRegistry}) {
viewRegistry.addView({
name: 'instances/custom',
parent: 'instances',
parent: 'instances', // <1>
path: 'custom',
component: customEndpoint,
props: true,
label: 'Custom',
order: 1000,
isEnabled: ({instance}) => instance.hasEndpoint('custom')
isEnabled: ({instance}) => instance.hasEndpoint('custom') // <2>
});
}
});
// end::customization-ui-endpoint[]
......@@ -33,11 +33,11 @@ import com.hazelcast.config.EvictionPolicy;
import com.hazelcast.config.InMemoryFormat;
import com.hazelcast.config.MapConfig;
// tag::application-hazelcast[]
@Configuration
@EnableAutoConfiguration
@EnableAdminServer
public class SpringBootAdminApplication {
// tag::application-hazelcast[]
@Bean
public Config hazelcastConfig() {
MapConfig mapConfig = new MapConfig("spring-boot-admin-event-store").setInMemoryFormat(InMemoryFormat.OBJECT)
......@@ -45,6 +45,7 @@ public class SpringBootAdminApplication {
.setEvictionPolicy(EvictionPolicy.NONE);
return new Config().setProperty("hazelcast.jmx", "true").addMapConfig(mapConfig);
}
// end::application-hazelcast[]
@Profile("insecure")
@Configuration
......@@ -97,4 +98,3 @@ public class SpringBootAdminApplication {
}
}
// end::application-hazelcast[]
/*
* 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.
*/
package de.codecentric.boot.admin;
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 de.codecentric.boot.admin.server.domain.events.InstanceStatusChangedEvent;
import de.codecentric.boot.admin.server.notify.AbstractEventNotifier;
import de.codecentric.boot.admin.server.notify.LoggingNotifier;
import reactor.core.publisher.Mono;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
// tag::customization-notifiers[]
public class CustomNotifier extends AbstractEventNotifier {
private static final Logger LOGGER = LoggerFactory.getLogger(LoggingNotifier.class);
public CustomNotifier(InstanceRepository repository) {
super(repository);
}
@Override
protected Mono<Void> doNotify(InstanceEvent event, Instance instance) {
return Mono.fromRunnable(() -> {
if (event instanceof InstanceStatusChangedEvent) {
LOGGER.info("Instance {} ({}) is {}", instance.getRegistration().getName(), event.getInstance(),
((InstanceStatusChangedEvent) event).getStatusInfo().getStatus());
} else {
LOGGER.info("Instance {} ({}) {}", instance.getRegistration().getName(), event.getInstance(),
event.getType());
}
});
}
}
// end::customization-notifiers[]
......@@ -20,10 +20,10 @@ 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.HttpHeadersProvider;
import de.codecentric.boot.admin.server.web.client.InstanceExchangeFilterFunction;
import java.time.Duration;
......@@ -38,6 +38,7 @@ import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.context.annotation.Profile;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
......@@ -105,6 +106,7 @@ public class SpringBootAdminApplication {
}
// end::configuration-spring-security[]
// tag::customization-instance-exchange-filter-function[]
@Bean
public InstanceExchangeFilterFunction auditLog() {
return (instance, request, next) -> {
......@@ -114,10 +116,11 @@ public class SpringBootAdminApplication {
return next.exchange(request);
};
}
// end::customization-instance-exchange-filter-function[]
@Bean
public LoggingNotifier loggerNotifier(InstanceRepository repository) {
return new LoggingNotifier(repository);
public CustomNotifier customNotifier(InstanceRepository repository) {
return new CustomNotifier(repository);
}
@Bean
......@@ -125,6 +128,18 @@ public class SpringBootAdminApplication {
return new CustomEndpoint();
}
// tag::customization-http-headers-providers[]
@Bean
public HttpHeadersProvider customHttpHeadersProvider() {
return instance -> {
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.add("X-CUSTOM", "My Custom Value");
return httpHeaders;
};
}
// end::customization-http-headers-providers[]
// tag::configuration-filtering-notifier[]
@Configuration
public static class NotifierConfig {
......
......@@ -29,9 +29,9 @@
</th:block>
<link rel="preload" href="assets/js/vendors.js" as="script">
<th:block th:each="jsFile : ${jsExtensions}">
<link rel="preload" th:src="${jsFile.resourcePath}" as="script">
<link rel="preload" th:href="${jsFile.resourcePath}" as="script">
</th:block>
<link rel="preload" src="assets/js/sba-core.js" as="script">
<link rel="preload" href="assets/js/sba-core.js" as="script">
<link href="assets/css/sba-core.css" rel="stylesheet">
<th:block th:each="cssFile : ${cssExtensions}">
......@@ -48,7 +48,10 @@
var SBA = {
uiSettings: /*[[${uiSettings}]]*/ {},
user: /*[[${user}]]*/ null,
extensions: []
extensions: [],
use(ext) {
this.extensions.push(ext);
}
}
</script>
<script lang="javascript" src="assets/js/vendors.js" defer></script>
......
......@@ -27,6 +27,8 @@ import ViewRegistry from './viewRegistry';
import views from './views';
moment.locale(window.navigator.language);
Vue.use(VueRouter);
Vue.use(components);
const applicationStore = new Store();
const viewRegistry = new ViewRegistry();
......@@ -40,12 +42,10 @@ const installables = [
installables.forEach(view => view.install({
viewRegistry,
applicationStore
applicationStore,
vue: Vue
}));
Vue.use(VueRouter);
Vue.use(components);
new Vue({
router: new VueRouter({
linkActiveClass: 'is-active',
......
......@@ -130,7 +130,6 @@
parent: 'instances',
path: 'auditevents',
component: this,
props: true,
label: 'Audit Log',
order: 600,
isEnabled: ({instance}) => instance.hasEndpoint('auditevents')
......
......@@ -152,7 +152,6 @@
parent: 'instances',
path: '',
component: this,
props: true,
label: 'Details',
order: 0
});
......
......@@ -139,7 +139,6 @@
parent: 'instances',
path: 'env',
component: this,
props: true,
label: 'Environment',
order: 100,
isEnabled: ({instance}) => instance.hasEndpoint('env')
......
......@@ -128,7 +128,6 @@
parent: 'instances',
path: 'flyway',
component: this,
props: true,
label: 'Flyway',
order: 900,
isEnabled: ({instance}) => instance.hasEndpoint('flyway')
......
......@@ -50,7 +50,6 @@
parent: 'instances',
path: 'heapdump',
component: this,
props: true,
label: 'Heap Dump',
order: 800,
isEnabled: ({instance}) => instance.hasEndpoint('heapdump')
......
......@@ -240,7 +240,6 @@
parent: 'instances',
path: 'httptrace',
component: this,
props: true,
label: 'Http Traces',
order: 500,
isEnabled: ({instance}) => instance.hasEndpoint('httptrace')
......
......@@ -227,7 +227,6 @@
parent: 'instances',
path: 'jolokia',
component: this,
props: true,
label: 'JMX',
order: 350,
isEnabled: ({instance}) => instance.hasEndpoint('jolokia')
......
......@@ -148,7 +148,6 @@
parent: 'instances',
path: 'liquibase',
component: this,
props: true,
label: 'Liquibase',
order: 900,
isEnabled: ({instance}) => instance.hasEndpoint('liquibase')
......
......@@ -131,7 +131,6 @@
parent: 'instances',
path: 'logfile',
component: this,
props: true,
label: 'Logfile',
order: 200,
isEnabled: ({instance}) => instance.hasEndpoint('logfile')
......
......@@ -206,7 +206,6 @@
parent: 'instances',
path: 'loggers',
component: this,
props: true,
label: 'Loggers',
order: 300,
isEnabled: ({instance}) => instance.hasEndpoint('loggers')
......
......@@ -218,7 +218,6 @@
parent: 'instances',
path: 'metrics',
component: this,
props: true,
label: 'Metrics',
order: 50,
isEnabled: ({instance}) => instance.hasEndpoint('metrics')
......
......@@ -153,7 +153,6 @@
parent: 'instances',
path: 'sessions',
component: this,
props: true,
label: 'Sessions',
order: 700,
isEnabled: ({instance}) => instance.hasEndpoint('sessions')
......
......@@ -132,7 +132,6 @@
parent: 'instances',
path: 'threaddump',
component: this,
props: true,
label: 'Threads',
order: 400,
isEnabled: ({instance}) => instance.hasEndpoint('threaddump')
......
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