Commit bb765862 by Johannes Edmeier

Add Turbine UI Module

With this commit the support for Turbine is completly revised. From now on Turbine is supported via a separate module and is integrated as top level view. closes #306
parent ce75960f
......@@ -32,6 +32,7 @@
<module>spring-boot-admin-server-ui</module>
<module>spring-boot-admin-server-ui-activiti</module>
<module>spring-boot-admin-server-ui-hystrix</module>
<module>spring-boot-admin-server-ui-turbine</module>
<module>spring-boot-admin-samples</module>
<module>spring-boot-admin-starter-client</module>
<module>spring-boot-admin-docs</module>
......@@ -238,6 +239,11 @@
</dependency>
<dependency>
<groupId>de.codecentric</groupId>
<artifactId>spring-boot-admin-server-ui-turbine</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>de.codecentric</groupId>
<artifactId>spring-boot-admin-starter-client</artifactId>
<version>${project.version}</version>
</dependency>
......
......@@ -49,6 +49,7 @@
<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>${project.parent.basedir}/spring-boot-admin-samples/</samples-dir>
</attributes>
</configuration>
......
......@@ -4,10 +4,11 @@ Additional to the core UI there are following modules which can be included by a
- spring-boot-admin-server-ui-activiti
- spring-boot-admin-server-ui-hystrix
- spring-boot-admin-server-ui-turbine
==== Hystrix UI Module ====
The Hystrix module uses the hystrix-dashboard to display the metrics from Hystrix or Turbine streams.
The Hystrix module uses the hystrix-dashboard to display the metrics from Hystrix streams.
. Add the ui module to your classpath:
+
......@@ -21,7 +22,7 @@ The Hystrix module uses the hystrix-dashboard to display the metrics from Hystri
</dependency>
----
. Add the `/hystrix.stream` and/or `/turbine.stream` to the proxified endpoints:
. Add the `/hystrix.stream` to the proxified endpoints:
+
[source,yml]
.application.yml
......@@ -29,6 +30,49 @@ The Hystrix module uses the hystrix-dashboard to display the metrics from Hystri
include::{samples-dir}/spring-boot-admin-sample-eureka/src/main/resources/application.yml[tags=configuration-ui-hystrix]
----
==== Turbine UI Module ====
The Turbine module uses the hystrix-dashboard to display the metrics from a Turbine stream. The UI module does not configure Turbine for you. Either you run Turbine as a separate application or integrate it into your Spring Boot Admin application. Please see http://cloud.spring.io/spring-cloud-static/{spring-cloud-version}/#_turbine[Spring Cloud Reference] on setting up Turbine.
. Add the ui module to your classpath:
+
[source,xml,subs="verbatim,attributes"]
.pom.xml
----
<dependency>
<groupId>de.codecentric</groupId>
<artifactId>spring-boot-admin-server-ui-turbine</artifactId>
<version>{project-version}</version>
</dependency>
----
. Configure the Turbine URL and clusters
+
[source,yml]
.application.yml
----
include::{samples-dir}/spring-boot-admin-sample-eureka/src/main/resources/application.yml[tags=configuration-ui-turbine]
----
.Turbine UI Module configuration options
|===
| Property name |Description |Default value
| spring.boot.admin.turbine.enabled
| Enable the Spring Boot Admin backend configuration for Turbine.
| `true`
| spring.boot.admin.turbine.url
| URL to the `turbine.stream`. Must be reachable from the admin server.
|
| spring.boot.admin.turbine.clusters
| List of available Turbine clusters.
| `"default"`
|
|===
==== Activiti UI Module ====
The Activiti module shows information from the `/activti` endpoint.
......
......@@ -90,6 +90,8 @@ services:
mongodb:
image: tutum/mongodb
container_name: mongodb
ports:
- "27017:27017"
networks:
- "discovery"
environment:
......@@ -98,6 +100,8 @@ services:
rabbit:
image: "rabbitmq:3"
container_name: rabbit
ports:
- "5672:5672"
networks:
- "discovery"
......
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>de.codecentric</groupId>
<artifactId>spring-boot-admin-samples</artifactId>
<version>1.4.4-SNAPSHOT</version>
<relativePath>..</relativePath>
</parent>
<artifactId>spring-boot-admin-sample-eureka</artifactId>
<dependencies>
<dependency>
<groupId>de.codecentric</groupId>
<artifactId>spring-boot-admin-server</artifactId>
</dependency>
<dependency>
<groupId>de.codecentric</groupId>
<artifactId>spring-boot-admin-server-ui</artifactId>
</dependency>
<!-- tag::dependency-ui-hystrix[] -->
<dependency>
<groupId>de.codecentric</groupId>
<artifactId>spring-boot-admin-server-ui-hystrix</artifactId>
</dependency>
<!-- end::dependency-ui-hystrix[] -->
<!-- tag::dependency-eureka[] -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
</dependency>
<!-- end::dependency-eureka[] -->
</dependencies>
<build>
<finalName>${project.artifactId}</finalName>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>repackage</goal>
<goal>build-info</goal>
</goals>
</execution>
</executions>
<configuration>
<mainClass>de.codecentric.boot.admin.SpringBootAdminApplication</mainClass>
<addResources>false</addResources>
</configuration>
</plugin>
</plugins>
</build>
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>de.codecentric</groupId>
<artifactId>spring-boot-admin-samples</artifactId>
<version>1.4.4-SNAPSHOT</version>
<relativePath>..</relativePath>
</parent>
<artifactId>spring-boot-admin-sample-eureka</artifactId>
<dependencies>
<dependency>
<groupId>de.codecentric</groupId>
<artifactId>spring-boot-admin-server</artifactId>
</dependency>
<dependency>
<groupId>de.codecentric</groupId>
<artifactId>spring-boot-admin-server-ui</artifactId>
</dependency>
<!-- tag::dependency-eureka[] -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
</dependency>
<!-- end::dependency-eureka[] -->
<!-- tag::dependency-ui-hystrix[] -->
<dependency>
<groupId>de.codecentric</groupId>
<artifactId>spring-boot-admin-server-ui-hystrix</artifactId>
</dependency>
<!-- end::dependency-ui-hystrix[] -->
<!-- tag::dependency-ui-turbine[] -->
<dependency>
<groupId>de.codecentric</groupId>
<artifactId>spring-boot-admin-server-ui-turbine</artifactId>
</dependency>
<!-- end::dependency-ui-turbine[] -->
</dependencies>
<build>
<finalName>${project.artifactId}</finalName>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>repackage</goal>
<goal>build-info</goal>
</goals>
</execution>
</executions>
<configuration>
<mainClass>de.codecentric.boot.admin.SpringBootAdminApplication</mainClass>
<addResources>false</addResources>
</configuration>
</plugin>
</plugins>
</build>
</project>
......@@ -15,5 +15,11 @@ eureka:
# end::configuration-eureka[]
# tag::configuration-ui-hystrix[]
spring.boot.admin.routes.endpoints: env,metrics,trace,dump,jolokia,info,configprops,trace,logfile,refresh,flyway,liquibase,heapdump,hystrix.stream,turbine.stream
spring.boot.admin.routes.endpoints: env,metrics,trace,dump,jolokia,info,configprops,trace,logfile,refresh,flyway,liquibase,heapdump,hystrix.stream
# end::configuration-ui-hystrix[]
# tag::configuration-ui-turbine[]
spring.boot.admin.turbine:
clusters: default
url: http://localhost:8989/turbine.stream
# end::configuration-ui-turbine[]
......@@ -9,88 +9,95 @@
<relativePath>..</relativePath>
</parent>
<artifactId>spring-boot-admin-server-ui-hystrix</artifactId>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<executions>
<execution>
<id>unpack</id>
<phase>generate-resources</phase>
<goals>
<goal>unpack</goal>
</goals>
<configuration>
<artifactItems>
<artifactItem>
<groupId>com.netflix.hystrix</groupId>
<artifactId>hystrix-dashboard</artifactId>
<version>${hystrix-dashboard.version}</version>
<type>war</type>
<overWrite>false</overWrite>
<outputDirectory>${project.build.directory}/hystrix-dashboard</outputDirectory>
<excludes>WEB-INF/**,META-INF/**</excludes>
</artifactItem>
</artifactItems>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<executions>
<execution>
<id>npm-install</id>
<phase>validate</phase>
<goals>
<goal>exec</goal>
</goals>
<configuration>
<executable>npm</executable>
<arguments>
<argument>install</argument>
</arguments>
</configuration>
</execution>
<execution>
<id>npm-build</id>
<phase>generate-resources</phase>
<goals>
<goal>exec</goal>
</goals>
<configuration>
<executable>npm</executable>
<arguments>
<argument>run</argument>
<argument>build</argument>
</arguments>
</configuration>
</execution>
<execution>
<id>npm-test</id>
<phase>test</phase>
<goals>
<goal>exec</goal>
</goals>
<configuration>
<skip>true</skip>
<executable>npm</executable>
<arguments>
<argument>run</argument>
<argument>test</argument>
</arguments>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
<resources>
<resource>
<directory>target/dist</directory>
<targetPath>META-INF/spring-boot-admin-server-ui</targetPath>
</resource>
</resources>
</build>
<dependencies>
<dependency>
<groupId>de.codecentric</groupId>
<artifactId>spring-boot-admin-server-ui</artifactId>
<scope>runtime</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<executions>
<execution>
<id>unpack</id>
<phase>generate-resources</phase>
<goals>
<goal>unpack</goal>
</goals>
<configuration>
<artifactItems>
<artifactItem>
<groupId>com.netflix.hystrix</groupId>
<artifactId>hystrix-dashboard</artifactId>
<version>${hystrix-dashboard.version}</version>
<type>war</type>
<overWrite>false</overWrite>
<outputDirectory>${project.build.directory}/hystrix-dashboard</outputDirectory>
<excludes>WEB-INF/**,META-INF/**</excludes>
</artifactItem>
</artifactItems>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<executions>
<execution>
<id>npm-install</id>
<phase>validate</phase>
<goals>
<goal>exec</goal>
</goals>
<configuration>
<executable>npm</executable>
<arguments>
<argument>install</argument>
</arguments>
</configuration>
</execution>
<execution>
<id>npm-build</id>
<phase>generate-resources</phase>
<goals>
<goal>exec</goal>
</goals>
<configuration>
<executable>npm</executable>
<arguments>
<argument>run</argument>
<argument>build</argument>
</arguments>
</configuration>
</execution>
<execution>
<id>npm-test</id>
<phase>test</phase>
<goals>
<goal>exec</goal>
</goals>
<configuration>
<skip>true</skip>
<executable>npm</executable>
<arguments>
<argument>run</argument>
<argument>test</argument>
</arguments>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
<resources>
<resource>
<directory>target/dist</directory>
<targetPath>META-INF/spring-boot-admin-server-ui</targetPath>
</resource>
</resources>
</build>
</project>
......@@ -18,7 +18,6 @@
var id = 0;
require('./hystrix.css');
require('hystrix/hystrixCommand.css');
var HystrixCommandMonitor = require('hystrix/hystrixCommand');
......@@ -26,13 +25,28 @@ module.exports = {
bindings: {
source: '<eventSource'
},
controller: function () {
controller: function ($element) {
'ngInject';
var ctrl = this;
ctrl.id = id++;
var startMonitor = function (id) {
$element.find('#hystrix-command-' + id).html('<span class="loading">Loading ...</span>');
if (ctrl.source !== null) {
ctrl.monitor = new HystrixCommandMonitor(0, 'hystrix-command-' + id, { includeDetailIcon: false });
ctrl.monitor.sortByErrorThenVolume();
ctrl.source.addEventListener('message', ctrl.monitor.eventSourceMessageListener, false);
}
};
ctrl.$onInit = function () {
ctrl.id = id++;
ctrl.monitor = new HystrixCommandMonitor(0, 'hystrix-command-' + ctrl.id, { includeDetailIcon: false });
ctrl.monitor.sortByErrorThenVolume();
ctrl.source.addEventListener('message', ctrl.monitor.eventSourceMessageListener, false);
startMonitor(ctrl.id);
};
ctrl.$onChanges = function (changes) {
if (changes.source.currentValue !== changes.source.previousValue) {
startMonitor(ctrl.id);
}
};
},
template: require('./hystrixCommand.tpl.html')
......
......@@ -25,13 +25,28 @@ module.exports = {
bindings: {
source: '<eventSource'
},
controller: function () {
controller: function ($element) {
'ngInject';
var ctrl = this;
ctrl.id = id++;
var startMonitor = function (id) {
$element.find('#hystrix-thread-pools-' + id).html('<span class="loading">Loading ...</span>');
if (ctrl.source !== null) {
ctrl.monitor = new HystrixThreadPoolMonitor(0, 'hystrix-thread-pools-' + id);
ctrl.monitor.sortByVolume();
ctrl.source.addEventListener('message', ctrl.monitor.eventSourceMessageListener, false);
}
};
ctrl.$onInit = function () {
ctrl.id = id++;
ctrl.monitor = new HystrixThreadPoolMonitor(0, 'hystrix-thread-pools-' + ctrl.id);
ctrl.monitor.sortByVolume();
ctrl.source.addEventListener('message', ctrl.monitor.eventSourceMessageListener, false);
startMonitor(ctrl.id);
};
ctrl.$onChanges = function (changes) {
if (changes.source.currentValue !== changes.source.previousValue) {
startMonitor(ctrl.id);
}
};
},
template: require('./hystrixThreadPool.tpl.html')
......
......@@ -15,25 +15,23 @@
*/
'use strict';
module.exports = function (path) {
return function ($scope, application) {
'ngInject';
$scope.error = null;
$scope.streamUrl = 'api/applications/' + application.id + '/' + path;
$scope.hystrixStream = new EventSource($scope.streamUrl);
$scope.hystrixStream.addEventListener('message', function () {
if ($scope.error) {
$scope.error = null;
$scope.$apply();
}
}, false);
$scope.hystrixStream.addEventListener('error', function (e) {
$scope.error = e;
module.exports = function ($scope, application) {
'ngInject';
$scope.error = null;
$scope.streamUrl = 'api/applications/' + application.id + '/hystrix.stream';
$scope.hystrixStream = new EventSource($scope.streamUrl);
$scope.hystrixStream.addEventListener('message', function () {
if ($scope.error) {
$scope.error = null;
$scope.$apply();
}, false);
}
}, false);
$scope.hystrixStream.addEventListener('error', function (e) {
$scope.error = e;
$scope.$apply();
}, false);
$scope.$on('$destroy', function () {
$scope.hystrixStream.close();
});
};
$scope.$on('$destroy', function () {
$scope.hystrixStream.close();
});
};
......@@ -17,7 +17,6 @@
var angular = require('angular');
require('hystrix/hystrixThreadPool.css');
var module = angular.module('sba-applications-hystrix', ['sba-applications']);
......@@ -26,8 +25,7 @@ global.sbaModules.push(module.name);
module.component('sbaHystrixCommand', require('./components/hystrixCommand.js'));
module.component('sbaHystrixThreadPool', require('./components/hystrixThreadPool.js'));
module.controller('hystrixCtrl', require('./controllers/hystrixCtrl.js')('hystrix.stream'));
module.controller('turbineCtrl', require('./controllers/hystrixCtrl.js')('turbine.stream'));
module.controller('hystrixCtrl', require('./controllers/hystrixCtrl.js'));
module.config(function ($stateProvider) {
$stateProvider.state('applications.hystrix', {
......@@ -35,15 +33,8 @@ module.config(function ($stateProvider) {
templateUrl: 'applications-hystrix/views/hystrix.html',
controller: 'hystrixCtrl'
});
$stateProvider.state('applications.turbine', {
url: '/turbine',
templateUrl: 'applications-hystrix/views/hystrix.html',
controller: 'turbineCtrl'
});
});
module.run(function (ApplicationViews, $sce, $q, $http) {
var isEventSourceAvailable = function (url) {
var deferred = $q.defer();
......@@ -63,7 +54,6 @@ module.run(function (ApplicationViews, $sce, $q, $http) {
return deferred.promise;
};
ApplicationViews.register({
order: 150,
title: $sce.trustAsHtml('<i class="fa fa-gear fa-fw"></i>Hystrix'),
......@@ -75,15 +65,4 @@ module.run(function (ApplicationViews, $sce, $q, $http) {
return isEventSourceAvailable('api/applications/' + application.id + '/hystrix.stream');
}
});
ApplicationViews.register({
order: 155,
title: $sce.trustAsHtml('<i class="fa fa-gear fa-fw"></i>Turbine'),
state: 'applications.turbine',
show: function (application) {
if (!application.managementUrl || !application.statusInfo.status || application.statusInfo.status === 'OFFLINE') {
return false;
}
return isEventSourceAvailable('api/applications/' + application.id + '/turbine.stream');
}
});
});
env:
browser: true
commonjs: true
extends: 'eslint:recommended'
rules:
indent:
- error
- 2
- SwitchCase: 1
linebreak-style:
- error
- unix
quotes:
- error
- single
semi:
- error
- always
{
"name": "spring-boot-admin-server-ui-turbine",
"version": "1.4.4",
"scripts": {
"build": "npm run build:js",
"build:js": "webpack -p",
"watch:js": "webpack -d --watch",
"dev-server": "webpack-dev-server --port 9090"
},
"dependencies": {
},
"devDependencies": {
"clean-webpack-plugin": "0.1.13",
"copy-webpack-plugin": "3.0.1",
"css-loader": "0.25.0",
"eslint": "3.8.0",
"eslint-loader": "*",
"exports-loader": "0.6.3",
"extract-text-webpack-plugin": "1.0.1",
"file-loader": "0.9.0",
"glob": "7.1.1",
"imports-loader": "0.6.5",
"ng-annotate": "1.2.1",
"ng-annotate-loader": "0.2.0",
"ng-annotate-webpack-plugin": "0.1.3",
"raw-loader": "0.5.1",
"regexp-replace-loader": "0.0.1",
"style-loader": "0.13.1",
"webpack": "1.13.2",
"webpack-dev-server": "1.16.2",
"webpack-sources": "0.1.2",
"yargs": "6.1.1"
}
}
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>de.codecentric</groupId>
<artifactId>spring-boot-admin</artifactId>
<version>1.4.4-SNAPSHOT</version>
<relativePath>..</relativePath>
</parent>
<artifactId>spring-boot-admin-server-ui-turbine</artifactId>
<dependencies>
<dependency>
<groupId>de.codecentric</groupId>
<artifactId>spring-boot-admin-server</artifactId>
</dependency>
<dependency>
<groupId>de.codecentric</groupId>
<artifactId>spring-boot-admin-server-ui-hystrix</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.jayway.jsonpath</groupId>
<artifactId>json-path</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<executions>
<execution>
<id>npm-install</id>
<phase>validate</phase>
<goals>
<goal>exec</goal>
</goals>
<configuration>
<executable>npm</executable>
<arguments>
<argument>install</argument>
</arguments>
</configuration>
</execution>
<execution>
<id>npm-build</id>
<phase>generate-resources</phase>
<goals>
<goal>exec</goal>
</goals>
<configuration>
<executable>npm</executable>
<arguments>
<argument>run</argument>
<argument>build</argument>
</arguments>
</configuration>
</execution>
<execution>
<id>npm-test</id>
<phase>test</phase>
<goals>
<goal>exec</goal>
</goals>
<configuration>
<skip>true</skip>
<executable>npm</executable>
<arguments>
<argument>run</argument>
<argument>test</argument>
</arguments>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
<resources>
<resource>
<directory>target/dist</directory>
<targetPath>META-INF/spring-boot-admin-server-ui</targetPath>
</resource>
<resource>
<directory>src/main/resources</directory>
</resource>
</resources>
</build>
</project>
/*
* Copyright 2013-2016 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 spring.boot.admin.turbine.config;
import java.util.Collection;
import java.util.Collections;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
import org.springframework.boot.autoconfigure.web.ServerProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cloud.netflix.zuul.filters.ZuulProperties;
import org.springframework.cloud.netflix.zuul.filters.ZuulProperties.ZuulRoute;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import de.codecentric.boot.admin.config.AdminServerWebConfiguration;
import de.codecentric.boot.admin.config.RevereseZuulProxyConfiguration;
import spring.boot.admin.turbine.web.TurbineController;
import spring.boot.admin.turbine.zuul.filters.StaticRouteLocator;
/**
* Configures all necessary components for the Turbine view.
*
* @author Johannes Edmeier
*/
@Configuration
@EnableConfigurationProperties(TurbineProperties.class)
@AutoConfigureBefore({ AdminServerWebConfiguration.class, RevereseZuulProxyConfiguration.class })
@Conditional(TurbineEnabledCondition.class)
public class TurbineAutoConfiguration {
@Autowired
private TurbineProperties properties;
@Autowired
private ServerProperties server;
@Autowired
private ZuulProperties zuulProperties;
@Bean
public TurbineController TurbineController() {
return new TurbineController(properties.getClusters());
}
@Bean
@Order(100)
public StaticRouteLocator staticRouteLocator() {
Collection<ZuulRoute> routes = Collections
.singleton(new ZuulRoute("/api/turbine/stream/**", properties.getUrl().toString()));
return new StaticRouteLocator(routes, server.getServletPrefix(), zuulProperties);
}
}
/*
* Copyright 2013-2016 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 spring.boot.admin.turbine.config;
import org.springframework.boot.autoconfigure.condition.AllNestedConditions;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
/**
* Condition for enabling the Turbine components.
*
* @author Johannes Edmeier
*/
public class TurbineEnabledCondition extends AllNestedConditions {
public TurbineEnabledCondition() {
super(ConfigurationPhase.PARSE_CONFIGURATION);
}
@ConditionalOnProperty(value = "spring.boot.admin.turbine.enabled", matchIfMissing = true)
static class EnabledProperty {
}
@ConditionalOnProperty(value = "spring.boot.admin.turbine.url", matchIfMissing = false)
static class TurbinUrlProperty {
}
}
\ No newline at end of file
/*
* Copyright 2013-2016 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 spring.boot.admin.turbine.config;
import java.net.URI;
import org.springframework.boot.context.properties.ConfigurationProperties;
@ConfigurationProperties("spring.boot.admin.turbine")
public class TurbineProperties {
/**
* URL to the <code>turbine.stream</code>. Must be reachable from the admin server.
*/
private URI url;
/**
* List of available Turbine clusters.
*/
private String[] clusters = { "default" };
/**
* Enable the Spring Boot Admin backend configuration for Turbine.
*/
private boolean enabled = true;
public String[] getClusters() {
return clusters;
}
public void setClusters(String[] clusters) {
this.clusters = clusters;
}
public boolean isEnabled() {
return enabled;
}
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
public URI getUrl() {
return url;
}
public void setUrl(URI url) {
this.url = url;
}
}
/*
* Copyright 2013-2016 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 spring.boot.admin.turbine.web;
import java.util.Arrays;
import java.util.Collections;
import java.util.Map;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import de.codecentric.boot.admin.web.AdminController;
/**
* Provides informations for the turbine view. Only available clusters until now.
*
* @author Johannes Edmeier
*/
@AdminController
@ResponseBody
@RequestMapping("/api/turbine")
public class TurbineController {
private final String[] clusters;
public TurbineController(String[] clusters) {
this.clusters = Arrays.copyOf(clusters, clusters.length);
}
@RequestMapping(value = "/clusters", method = RequestMethod.GET)
public Map<String, ?> getClusters() {
return Collections.singletonMap("clusters", clusters);
}
}
/*
* Copyright 2013-2016 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 spring.boot.admin.turbine.zuul.filters;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import org.springframework.cloud.netflix.zuul.filters.SimpleRouteLocator;
import org.springframework.cloud.netflix.zuul.filters.ZuulProperties;
import org.springframework.cloud.netflix.zuul.filters.ZuulProperties.ZuulRoute;
/**
* RouteLocator that uses static non-configurable routes.
*
* @author Johannes Edmeier
*/
public class StaticRouteLocator extends SimpleRouteLocator {
private final Map<String, ZuulRoute> routes = new HashMap<>();
public StaticRouteLocator(Collection<ZuulRoute> routes, String servletPath,
ZuulProperties properties) {
super(servletPath, properties);
for (ZuulRoute route : routes) {
this.routes.put(route.getPath(), route);
}
}
@Override
protected Map<String, ZuulRoute> locateRoutes() {
return Collections.unmodifiableMap(this.routes);
}
}
org.springframework.boot.autoconfigure.EnableAutoConfiguration=spring.boot.admin.turbine.config.TurbineAutoConfiguration
\ No newline at end of file
/*
* Copyright 2014 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.
*/
'use strict';
var angular = require('angular');
var module = angular.module('sba-turbine', ['sba-applications-hystrix']);
global.sbaModules.push(module.name);
module.controller('turbineCtrl', require('./turbineCtrl.js'));
module.config(function ($stateProvider) {
$stateProvider.state('turbine', {
url: '/turbine',
templateUrl: 'turbine/turbine.html',
controller: 'turbineCtrl'
});
});
module.run(function (MainViews) {
MainViews.register({
title: 'Turbine',
state: 'turbine',
order: 100
});
});
<div class="container">
<div class="alert alert-error" ng-if="error">
Unable to connect to Command Metric Stream '{{streamUrl}}'.<br>
<b>Error:</b> {{ error }}
</div>
<form class="form-inline">
<select ng-model="selectedCluster" ng-options="cluster as cluster for cluster in clusters" ng-disabled="clusters.length <= 1"
ng-change="startStream()"></select>
</form>
<sba-info-panel panel-title="Cuircuit Breakers">
<sba-hystrix-command event-source="hystrixStream"></sba-hystrix-command>
</sba-info-panel>
<sba-info-panel panel-title="Thread Pools">
<sba-hystrix-thread-pool event-source="hystrixStream"></sba-hystrix-thread-pool>
</sba-info-panel>
</div>
\ No newline at end of file
/*
* Copyright 2014 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.
*/
'use strict';
module.exports = function ($scope, $http) {
'ngInject';
$scope.error = null;
$scope.hystrixStream = null;
$scope.selectedCluster = 'default';
$scope.clusters = ['default'];
$http.get('api/turbine/clusters').then(function (response) {
if (response.data.clusters.length > 0) {
$scope.clusters = response.data.clusters;
}
$scope.selectedCluster = $scope.clusters[0];
$scope.startStream();
});
$scope.stopStream = function () {
if ($scope.hystrixStream !== null) {
$scope.hystrixStream.close();
$scope.hystrixStream = null;
}
};
$scope.startStream = function () {
$scope.stopStream();
$scope.streamUrl = 'api/turbine/stream?cluster=' + $scope.selectedCluster;
$scope.hystrixStream = new EventSource($scope.streamUrl);
$scope.hystrixStream.addEventListener('message', function () {
if ($scope.error) {
$scope.error = null;
$scope.$apply();
}
}, false);
$scope.hystrixStream.addEventListener('error', function (e) {
$scope.error = e;
$scope.$apply();
}, false);
};
$scope.$on('$destroy', function () {
$scope.stopStream();
});
};
package spring.boot.admin.turbine.config;
import static org.hamcrest.CoreMatchers.instanceOf;
import static org.hamcrest.Matchers.empty;
import static org.junit.Assert.assertThat;
import org.junit.After;
import org.junit.Test;
import org.springframework.boot.autoconfigure.PropertyPlaceholderAutoConfiguration;
import org.springframework.boot.autoconfigure.web.ServerPropertiesAutoConfiguration;
import org.springframework.boot.autoconfigure.web.WebClientAutoConfiguration.RestTemplateConfiguration;
import org.springframework.boot.test.util.EnvironmentTestUtils;
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
import de.codecentric.boot.admin.config.AdminServerWebConfiguration;
import de.codecentric.boot.admin.config.RevereseZuulProxyConfiguration;
import spring.boot.admin.turbine.web.TurbineController;
public class TurbineAutoConfigurationTest {
private AnnotationConfigWebApplicationContext context;
@After
public void close() {
if (this.context != null) {
this.context.close();
}
}
@Test
public void test_disabled() {
load("spring.boot.admin.turbine.enabled:false",
"spring.boot.admin.turbine.url:http://turbine.server:8989/turbine.stream");
assertThat(context.getBeansOfType(TurbineController.class).values(), empty());
}
@Test
public void test_missing_url() {
load();
assertThat(context.getBeansOfType(TurbineController.class).values(), empty());
}
@Test
public void test_enabled() {
load("spring.boot.admin.turbine.url:http://turbine.server:8989/turbine.stream");
assertThat(context.getBean(TurbineController.class), instanceOf(TurbineController.class));
}
private void load(String... environment) {
AnnotationConfigWebApplicationContext applicationContext = new AnnotationConfigWebApplicationContext();
applicationContext.register(PropertyPlaceholderAutoConfiguration.class);
applicationContext.register(RestTemplateConfiguration.class);
applicationContext.register(ServerPropertiesAutoConfiguration.class);
applicationContext.register(AdminServerWebConfiguration.class);
applicationContext.register(RevereseZuulProxyConfiguration.class);
applicationContext.register(TurbineAutoConfiguration.class);
EnvironmentTestUtils.addEnvironment(applicationContext, environment);
applicationContext.refresh();
this.context = applicationContext;
}
}
package spring.boot.admin.turbine.web;
import static java.util.Arrays.asList;
import static org.hamcrest.CoreMatchers.is;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import org.junit.Test;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
public class TurbineControllerTest {
private MockMvc mvc = MockMvcBuilders
.standaloneSetup(new TurbineController(new String[] { "c1", "c2" }))
.build();
@Test
public void test_clusters() throws Exception {
mvc.perform(get("/api/turbine/clusters")).andExpect(status().isOk())
.andExpect(jsonPath("$.clusters").value(is(asList("c1", "c2"))));
}
}
package spring.boot.admin.turbine.zuul.filters;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.CoreMatchers.nullValue;
import static org.junit.Assert.assertThat;
import java.util.Collections;
import java.util.List;
import org.junit.Test;
import org.springframework.cloud.netflix.zuul.filters.ZuulProperties;
import org.springframework.cloud.netflix.zuul.filters.ZuulProperties.ZuulRoute;
public class StaticRouteLocatorTest {
@Test
public void test_route() {
List<ZuulRoute> routes = Collections
.singletonList(new ZuulRoute("/path/**", "http://example.com/target"));
StaticRouteLocator locator = new StaticRouteLocator(routes, "", new ZuulProperties());
assertThat(locator.getRoutes().size(), is(1) );
assertThat(locator.getRoutes().get(0).getPath(), is("/**"));
assertThat(locator.getRoutes().get(0).getPrefix(), is("/path"));
assertThat(locator.getRoutes().get(0).getLocation(), is( "http://example.com/target"));
assertThat(locator.getMatchingRoute("/path/foo").getLocation(),
is("http://example.com/target"));
assertThat(locator.getMatchingRoute("/404/foo"), nullValue());
}
}
'use strict';
var NgAnnotatePlugin = require('ng-annotate-webpack-plugin'),
CopyWebpackPlugin = require('copy-webpack-plugin'),
CleanWebpackPlugin = require('clean-webpack-plugin'),
ExtractTextPlugin = require('extract-text-webpack-plugin'),
path = require('path');
var DIST = path.resolve(__dirname, 'target/dist');
var ROOT = __dirname;
module.exports = {
context: ROOT,
entry: { 'turbine': './src/main/webpack/module.js' },
output: {
path: DIST,
filename: '[name]/module.js'
},
externals: ['angular'],
module: {
preLoaders: [{
test: /\.js$/,
loader: 'eslint',
exclude: [/node_modules/]
}],
loaders: [
{
test: /\.js$/,
exclude: [/node_modules/],
loader: 'ng-annotate'
}, {
test: /\.tpl\.html$/,
loader: 'raw'
}, {
test: /\.css(\?.*)?$/,
loader: ExtractTextPlugin.extract('style', 'css?-minimize')
}
]
},
plugins: [
new CleanWebpackPlugin([DIST]),
new ExtractTextPlugin('[name]/module.css'),
new NgAnnotatePlugin({ add: true }),
new CopyWebpackPlugin([{
from: '**/*.html',
to: 'turbine',
context: 'src/main/webpack'
}
], { ignore: ['*.tpl.html'] })
],
devServer: {
proxy: [
{
context: '/',
target: 'http://localhost:8080',
secure: false,
onProxyRes: function (proxyRes, req, res) {
/* Append the turbine/module.js to the all-modules.js */
if (req.path === '/all-modules.js') {
delete proxyRes.headers['content-length'];
proxyRes.headers['transfer-encoding'] = 'chunked';
proxyRes.__pipe = proxyRes.pipe;
proxyRes.pipe = function (sink, options) {
var opts = options || {};
opts.end = false;
proxyRes.__pipe(sink, opts);
};
var suffixModule = '\n';
require('http').get('http://localhost:9090/turbine/module.js', function (r) {
r.on('data', function (chunk) {
suffixModule += chunk;
});
r.on('end', function () {
res.end(suffixModule);
});
});
}
if (req.path === '/all-modules.css') {
delete proxyRes.headers['content-length'];
proxyRes.headers['transfer-encoding'] = 'chunked';
proxyRes.__pipe = proxyRes.pipe;
proxyRes.pipe = function (sink, options) {
var opts = options || {};
opts.end = false;
proxyRes.__pipe(sink, opts);
};
var suffixCss = '\n';
require('http').get('http://localhost:9090/turbine/module.css', function (r) {
r.on('data', function (chunk) {
suffixCss += chunk;
});
r.on('end', function () {
res.end(suffixCss);
});
});
}
}
}
]
},
node: {
fs: 'empty'
}
};
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