Skip to content

Commit fa37e26

Browse files
committed
Add mappings endpoint
closes codecentric#816
1 parent 5bb2b36 commit fa37e26

File tree

10 files changed

+346
-6
lines changed

10 files changed

+346
-6
lines changed

spring-boot-admin-server-ui/package-lock.json

Lines changed: 3 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

spring-boot-admin-server-ui/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@
5757
"eslint": "^4.19.1",
5858
"eslint-loader": "^2.1.0",
5959
"eslint-plugin-html": "^4.0.5",
60-
"eslint-plugin-vue": "^4.7.0",
60+
"eslint-plugin-vue": "^4.7.1",
6161
"extract-text-webpack-plugin": "^3.0.2",
6262
"file-loader": "^1.1.11",
6363
"glob": "^7.1.2",

spring-boot-admin-server-ui/src/main/frontend/assets/css/base.scss

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,10 @@ body {
4040
word-break: break-all;
4141
}
4242

43+
.monospaced {
44+
font-family: $family-monospace;
45+
}
46+
4347
.is-muted {
4448
color: $grey-light;
4549
}

spring-boot-admin-server-ui/src/main/frontend/services/instance.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -220,6 +220,12 @@ class Instance {
220220
});
221221
}
222222

223+
async fetchMappings() {
224+
return this.axios.get(uri`actuator/mappings`, {
225+
headers: {'Accept': actuatorMimeTypes}
226+
});
227+
}
228+
223229
static async fetchEvents() {
224230
return axios.get('instances/events');
225231
}

spring-boot-admin-server-ui/src/main/frontend/views/instances/details/index.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,7 @@
130130
},
131131
methods: {
132132
hasMetric(metric) {
133-
return this.metrics.includes(metric);
133+
return this.metrics && this.metrics.includes(metric);
134134
},
135135
async fetchMetricIndex() {
136136
if (this.instance.hasEndpoint('metrics')) {
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
<!--
2+
- Copyright 2014-2018 the original author or authors.
3+
-
4+
- Licensed under the Apache License, Version 2.0 (the "License");
5+
- you may not use this file except in compliance with the License.
6+
- You may obtain a copy of the License at
7+
-
8+
- http://www.apache.org/licenses/LICENSE-2.0
9+
-
10+
- Unless required by applicable law or agreed to in writing, software
11+
- distributed under the License is distributed on an "AS IS" BASIS,
12+
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
- See the License for the specific language governing permissions and
14+
- limitations under the License.
15+
-->
16+
17+
<template>
18+
<table class="table is-fullwidth">
19+
<template v-for="(handlerMappings, dispatcherName) in this.dispatchers">
20+
<thead :key="`${dispatcherName}`">
21+
<tr>
22+
<th v-text="dispatcherName" colspan="99"/>
23+
</tr>
24+
</thead>
25+
<tbody :key="`${dispatcherName}_mappings`">
26+
<template v-for="(mapping, idx) in handlerMappings">
27+
<template v-if="mapping.details">
28+
<tr :key="`${dispatcherName}_${idx}_pattern`">
29+
<td :rowspan="2 + countNonEmptyArrays(mapping.details.requestMappingConditions, 'methods', 'consumes', 'produces', 'params', 'headers')">
30+
<div v-for="pattern in mapping.details.requestMappingConditions.patterns"
31+
:key="`${dispatcherName}_${idx}_${pattern}`">
32+
<code v-text="pattern"/>
33+
</div>
34+
</td>
35+
</tr>
36+
37+
<tr v-if="mapping.details.requestMappingConditions.methods.length" :key="`${dispatcherName}_${idx}_methods`">
38+
<th class="is-narrow">
39+
<small>Methods</small>
40+
</th>
41+
<td class="monospaced" v-text="mapping.details.requestMappingConditions.methods.join(', ')"/>
42+
</tr>
43+
44+
<tr v-if="mapping.details.requestMappingConditions.consumes.length" :key="`${dispatcherName}_${idx}_consumes`">
45+
<th class="is-narrow">
46+
<small>Consumes</small>
47+
</th>
48+
<td class="monospaced" v-text="mediaTypePredicates(mapping.details.requestMappingConditions.consumes)"/>
49+
</tr>
50+
51+
<tr v-if="mapping.details.requestMappingConditions.produces.length" :key="`${dispatcherName}_${idx}_produces`">
52+
<th class="is-narrow">
53+
<small>Produces</small>
54+
</th>
55+
<td class="monospaced" v-text="mediaTypePredicates(mapping.details.requestMappingConditions.produces)"/>
56+
</tr>
57+
58+
<tr v-if="mapping.details.requestMappingConditions.params.length" :key="`${dispatcherName}_${idx}_params`">
59+
<th class="is-narrow">
60+
<small>Parameters</small>
61+
</th>
62+
<td class="monospaced" v-text="paramPredicates(mapping.details.requestMappingConditions.params)"/>
63+
</tr>
64+
65+
<tr v-if="mapping.details.requestMappingConditions.headers.length" :key="`${dispatcherName}_${idx}_headers`">
66+
<th class="is-narrow">
67+
<small>Headers</small>
68+
</th>
69+
<td class="monospaced" v-text="paramPredicates(mapping.details.requestMappingConditions.headers)"/>
70+
</tr>
71+
72+
<tr :key="`${dispatcherName}_${idx}_handler`">
73+
<th class="is-narrow">
74+
<small>Handler</small>
75+
</th>
76+
<td v-text="mapping.handler"/>
77+
</tr>
78+
</template>
79+
<tr v-else :key="`${dispatcherName}_${idx}`">
80+
<td><code v-text="mapping.predicate"/></td>
81+
<th class="is-narrow">
82+
<small>Handler</small>
83+
</th>
84+
<td v-text="mapping.handler" colspan="4"/>
85+
</tr>
86+
</template>
87+
</tbody>
88+
</template>
89+
</table>
90+
</template>
91+
<script>
92+
export default {
93+
props: {
94+
dispatchers: {
95+
type: Object,
96+
default: () => []
97+
}
98+
},
99+
methods: {
100+
countNonEmptyArrays(obj, ...keys) {
101+
return keys.map(key => obj[key]).filter(a => a && a.length).length;
102+
},
103+
mediaTypePredicates(types) {
104+
return types.map(p => `${p.negate ? '!' : ''}${p.mediaType}`).join(', ');
105+
},
106+
paramPredicates(params) {
107+
return params.map(p => `${p.name}: ${p.negate ? '!' : ''}${p.value}`).join(', ');
108+
}
109+
}
110+
}
111+
</script>
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
<!--
2+
- Copyright 2014-2018 the original author or authors.
3+
-
4+
- Licensed under the Apache License, Version 2.0 (the "License");
5+
- you may not use this file except in compliance with the License.
6+
- You may obtain a copy of the License at
7+
-
8+
- http://www.apache.org/licenses/LICENSE-2.0
9+
-
10+
- Unless required by applicable law or agreed to in writing, software
11+
- distributed under the License is distributed on an "AS IS" BASIS,
12+
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
- See the License for the specific language governing permissions and
14+
- limitations under the License.
15+
-->
16+
17+
<template>
18+
<table class="table is-fullwidth" v-if="servletFilters.length">
19+
<thead>
20+
<tr>
21+
<th>Url Pattern</th>
22+
<th>Servlet Name</th>
23+
<th>Filter Name</th>
24+
<th>Class</th>
25+
</tr>
26+
</thead>
27+
<tbody>
28+
<template v-for="servletFilterMapping in servletFilters">
29+
<tr :key="`${servletFilterMapping.name}`">
30+
<td>
31+
<div v-for="mapping in servletFilterMapping.urlPatternMappings"
32+
:key="`${servletFilterMapping.name}_${mapping}`">
33+
<code v-text="mapping"/>
34+
</div>
35+
</td>
36+
<td>
37+
<div v-for="mapping in servletFilterMapping.servletNameMappings"
38+
:key="`${servletFilterMapping.name}_${mapping}`">
39+
<pre v-text="mapping"/>
40+
</div>
41+
</td>
42+
<td v-text="servletFilterMapping.name"/>
43+
<td v-text="servletFilterMapping.className"/>
44+
</tr>
45+
</template>
46+
</tbody>
47+
</table>
48+
</template>
49+
<script>
50+
export default {
51+
props: {
52+
servletFilters: {
53+
type: Array,
54+
default: () => []
55+
}
56+
}
57+
}
58+
</script>
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
<!--
2+
- Copyright 2014-2018 the original author or authors.
3+
-
4+
- Licensed under the Apache License, Version 2.0 (the "License");
5+
- you may not use this file except in compliance with the License.
6+
- You may obtain a copy of the License at
7+
-
8+
- http://www.apache.org/licenses/LICENSE-2.0
9+
-
10+
- Unless required by applicable law or agreed to in writing, software
11+
- distributed under the License is distributed on an "AS IS" BASIS,
12+
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
- See the License for the specific language governing permissions and
14+
- limitations under the License.
15+
-->
16+
17+
<template>
18+
<table class="table is-fullwidth" v-if="servlets.length">
19+
<thead>
20+
<tr>
21+
<th>Url Pattern</th>
22+
<th>Servlet Name</th>
23+
<th>Class</th>
24+
</tr>
25+
</thead>
26+
<tbody>
27+
<template v-for="servletMapping in servlets">
28+
<tr :key="`${servletMapping.name}`">
29+
<td>
30+
<div v-for="mapping in servletMapping.mappings"
31+
:key="`${servletMapping.name}_${mapping}`">
32+
<code v-text="mapping"/>
33+
</div>
34+
</td>
35+
<td v-text="servletMapping.name"/>
36+
<td v-text="servletMapping.className"/>
37+
</tr>
38+
</template>
39+
</tbody>
40+
</table>
41+
</template>
42+
<script>
43+
export default {
44+
props: {
45+
servlets: {
46+
type: Array,
47+
default: () => []
48+
}
49+
}
50+
}
51+
</script>
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
<!--
2+
- Copyright 2014-2018 the original author or authors.
3+
-
4+
- Licensed under the Apache License, Version 2.0 (the "License");
5+
- you may not use this file except in compliance with the License.
6+
- You may obtain a copy of the License at
7+
-
8+
- http://www.apache.org/licenses/LICENSE-2.0
9+
-
10+
- Unless required by applicable law or agreed to in writing, software
11+
- distributed under the License is distributed on an "AS IS" BASIS,
12+
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
- See the License for the specific language governing permissions and
14+
- limitations under the License.
15+
-->
16+
17+
<template>
18+
<section class="section" :class="{ 'is-loading' : !hasLoaded }">
19+
<div class="container" v-if="hasLoaded">
20+
<div v-if="error" class="message is-danger">
21+
<div class="message-body">
22+
<strong>
23+
<font-awesome-icon class="has-text-danger" icon="exclamation-triangle"/>
24+
Fetching mappings failed.
25+
</strong>
26+
<p v-text="error.message"/>
27+
</div>
28+
</div>
29+
<div v-if="isOldMetrics" class="message is-warning">
30+
<div class="message-body">
31+
Mappings are not supported for Spring Boot 1.x applications.
32+
</div>
33+
</div>
34+
<template v-for="(context, ctxName) in contexts">
35+
<h3 class="title" v-text="ctxName" :key="ctxName"/>
36+
37+
<DispatcherMappings v-if="!isEmpty(context.mappings.dispatcherServlets)"
38+
:key="`${ctxName}_dispatcherServlets`"
39+
:dispatchers="context.mappings.dispatcherServlets"/>
40+
41+
<DispatcherMappings v-if="!isEmpty(context.mappings.dispatcherHandlers)"
42+
:key="`${ctxName}_dispatcherHandlers`"
43+
:dispatchers="context.mappings.dispatcherHandlers"/>
44+
45+
<ServletMappings :key="`${ctxName}_servlets`"
46+
:servlets="context.mappings.servlets"/>
47+
48+
<ServletFilterMappings :key="`${ctxName}_servletFilters`"
49+
:servlet-filters="context.mappings.servletFilters"/>
50+
</template>
51+
</div>
52+
</section>
53+
</template>
54+
55+
<script>
56+
import Instance from '@/services/instance';
57+
import DispatcherMappings from '@/views/instances/mappings/DispatcherMappings';
58+
import ServletFilterMappings from '@/views/instances/mappings/ServletFilterMappings';
59+
import ServletMappings from '@/views/instances/mappings/ServletMappings';
60+
import _ from 'lodash';
61+
62+
export default {
63+
components: {DispatcherMappings, ServletMappings, ServletFilterMappings},
64+
props: {
65+
instance: {
66+
type: Instance,
67+
required: true
68+
}
69+
},
70+
data: () => ({
71+
hasLoaded: false,
72+
error: null,
73+
contexts: null,
74+
isOldMetrics: false
75+
}),
76+
created() {
77+
this.fetchMappings();
78+
},
79+
computed: {},
80+
methods: {
81+
isEmpty: _.isEmpty,
82+
async fetchMappings() {
83+
this.error = null;
84+
try {
85+
const res = await this.instance.fetchMappings();
86+
if (res.headers['content-type'].includes('application/vnd.spring-boot.actuator.v2')) {
87+
this.contexts = res.data.contexts;
88+
} else {
89+
this.isOldMetrics = true;
90+
}
91+
} catch (error) {
92+
console.warn('Fetching mappings failed:', error);
93+
this.error = error;
94+
}
95+
this.hasLoaded = true;
96+
}
97+
},
98+
install({viewRegistry}) {
99+
viewRegistry.addView({
100+
name: 'instances/mappings',
101+
parent: 'instances',
102+
path: 'mappings',
103+
component: this,
104+
label: 'Mappings',
105+
order: 450,
106+
isEnabled: ({instance}) => instance.hasEndpoint('mappings')
107+
});
108+
}
109+
}
110+
</script>

spring-boot-admin-server/src/main/java/de/codecentric/boot/admin/server/config/AdminServerProperties.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ public class AdminServerProperties {
5353
*/
5454
private String[] probedEndpoints = {"health", "env", "metrics", "httptrace:trace", "httptrace", "threaddump:dump",
5555
"threaddump", "jolokia", "info", "logfile", "refresh", "flyway", "liquibase", "heapdump", "loggers",
56-
"auditevents"};
56+
"auditevents", "mappings"};
5757

5858
public void setContextPath(String contextPath) {
5959
this.contextPath = PathUtils.normalizePath(contextPath);

0 commit comments

Comments
 (0)