Skip to content

Commit

Permalink
feat(#1510): implement grouping of services (#2797)
Browse files Browse the repository at this point in the history
  • Loading branch information
SteKoe authored Oct 6, 2023
1 parent b2095db commit 3e8cfc7
Show file tree
Hide file tree
Showing 29 changed files with 1,219 additions and 1,274 deletions.
24 changes: 19 additions & 5 deletions spring-boot-admin-docs/src/site/asciidoc/_server-discovery.adoc
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
[[spring-cloud-discovery-support]]
== Spring Cloud Discovery ==

The Spring Boot Admin Server can use Spring Clouds `DiscoveryClient` to discover applications. The advantage is that the clients don't have to include the `spring-boot-admin-starter-client`. You just have to add a `DiscoveryClient` implementation to your admin server - everything else is done by AutoConfiguration.
The Spring Boot Admin Server can use Spring Clouds `DiscoveryClient` to discover applications.
The advantage is that the clients don't have to include the `spring-boot-admin-starter-client`.
You just have to add a `DiscoveryClient` implementation to your admin server - everything else is done by AutoConfiguration.

[[spring-cloud-discovery-static-config]]
=== Static Configuration using SimpleDiscoveryClient ===

Spring Cloud provides a `SimpleDiscoveryClient`. It allows you to specify client applications via static configuration:
Spring Cloud provides a `SimpleDiscoveryClient`.
It allows you to specify client applications via static configuration:

[source,xml]
.pom.xml
Expand Down Expand Up @@ -36,14 +39,20 @@ spring:
----

=== Other DiscoveryClients ===
Spring Boot Admin supports all other implementations of Spring Cloud's `DiscoveryClient` (https://docs.spring.io/spring-cloud-netflix/docs/current/reference/html/#service-discovery-eureka-clients/[Eureka], https://docs.spring.io/spring-cloud-zookeeper/docs/current/reference/html/#spring-cloud-zookeeper-discovery[Zookeeper], https://docs.spring.io/spring-cloud-consul/docs/current/reference/html/#spring-cloud-consul-discovery[Consul], https://docs.spring.io/spring-cloud-kubernetes/docs/current/reference/html/#discoveryclient-for-kubernetes[Kubernetes], ...). You need to add it to the Spring Boot Admin Server and configure it properly.

Spring Boot Admin supports all other implementations of Spring Cloud's `DiscoveryClient` (https://docs.spring.io/spring-cloud-netflix/docs/current/reference/html/#service-discovery-eureka-clients/[Eureka], https://docs.spring.io/spring-cloud-zookeeper/docs/current/reference/html/#spring-cloud-zookeeper-discovery[Zookeeper], https://docs.spring.io/spring-cloud-consul/docs/current/reference/html/#spring-cloud-consul-discovery[Consul], https://docs.spring.io/spring-cloud-kubernetes/docs/current/reference/html/#discoveryclient-for-kubernetes[Kubernetes], ...).
You need to add it to the Spring Boot Admin Server and configure it properly.
An <<getting-started#discover-clients-via-spring-cloud-discovery,example setup using Eureka>> is shown above.

=== Converting ServiceInstances ===

The information from the service registry are converted by the `ServiceInstanceConverter`. Spring Boot Admin ships with a default and Eureka converter implementation. The correct one is selected by AutoConfiguration.
The information from the service registry are converted by the `ServiceInstanceConverter`.
Spring Boot Admin ships with a default and Eureka converter implementation.
The correct one is selected by AutoConfiguration.

TIP: You can modify how the information from the registry is used to register the application by using SBA Server configuration options and instance metadata. The values from the metadata takes precedence over the server config. If the plenty of options don't fit your needs you can provide your own `ServiceInstanceConverter`.
TIP: You can modify how the information from the registry is used to register the application by using SBA Server configuration options and instance metadata.
The values from the metadata takes precedence over the server config.
If the plenty of options don't fit your needs you can provide your own `ServiceInstanceConverter`.

NOTE: When using Eureka, the `healthCheckUrl` known to Eureka is used for health-checking, which can be set on your client using `eureka.instance.healthCheckUrl`.

Expand Down Expand Up @@ -75,6 +84,10 @@ user.password
| health.path
| The path is appended to the service URL and will be used for the health-checking. Ignored by the `EurekaServiceInstanceConverter`.
| `${spring.boot.admin.discovery.converter.health-endpoint}`

| group
| The group is used to group services in the UI by the group name instead of application name.
|
|===

.Discovery configuration options
Expand Down Expand Up @@ -111,6 +124,7 @@ user.password
|===

=== CloudFoundry ===

If you are deploying your applications to CloudFoundry then `vcap.application.application_id` and `vcap.application.instance_index` *_must_* be added to the metadata for proper registration of applications with Spring Boot Admin Server.
Here is a sample configuration for Eureka:

Expand Down
2 changes: 1 addition & 1 deletion spring-boot-admin-server-ui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"private": true,
"description": "Spring Boot Admin UI",
"scripts": {
"build": "vite build --emptyOutDir",
"build": "vite build --emptyOutDir --sourcemap",
"build:dev": "NODE_ENV=development vite build --emptyOutDir --sourcemap --mode development",
"build:watch": "NODE_ENV=development vite build --emptyOutDir --watch --mode development",
"dev": "vite",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@ import { faStopCircle as farStopCircle } from '@fortawesome/free-regular-svg-ico
import { faTimesCircle as farTimesCircle } from '@fortawesome/free-regular-svg-icons/faTimesCircle';
import {
faAngleDoubleLeft,
faCogs,
faEye,
faCogs, faExpand,
faEye, faList,
faPowerOff,
faUndoAlt,
} from '@fortawesome/free-solid-svg-icons';
Expand Down Expand Up @@ -91,6 +91,8 @@ library.add(
faInfoCircle,
faExclamationCircle,
faHome,
faList,
faExpand,
faMapMarkerAlt,
faCheckCircle,
faMinusCircle,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,23 +1,26 @@
<template>
<a
v-if="href"
:href="href"
:target="target"
class="sba-nav-item"
rel="noopener noreferrer"
v-if="href"
:href="href"
:target="target"
class="sba-nav-item"
rel="noopener noreferrer"
>
<slot />
<slot/>
</a>
<router-link v-else-if="to" :to="to" class="sba-nav-item">
<slot />
<slot/>
</router-link>

<div v-else class="sba-nav-item">
<slot />
<slot/>
</div>
</template>

<script lang="ts" setup>
import {RouteLocationRaw} from "vue-router";
import {PropType} from "vue";
defineProps({
href: {
type: String,
Expand All @@ -28,7 +31,7 @@ defineProps({
default: '_blank',
},
to: {
type: String,
type: Object as PropType<RouteLocationRaw>,
default: null,
},
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,12 @@
class="rounded-t flex justify-between px-4 pt-5 pb-5 border-b sm:px-6 items-center bg-white transition-all"
>
<h3 class="text-lg leading-6 font-medium text-gray-900">
<span v-text="title" />&nbsp;
<span v-if="subtitle" class="text-sm text-gray-500" v-text="subtitle" />
<slot v-if="'title' in $slots" name="title" />
<button @click="$emit('title-click')" class="flex items-center">
<slot v-if="'prefix' in $slots" name="prefix" />
<span v-text="title" />
<span v-if="subtitle" class="ml-2 text-sm text-gray-500 self-end" v-text="subtitle" />
<slot v-if="'title' in $slots" name="title" />
</button>
</h3>

<div>
Expand Down Expand Up @@ -95,7 +98,7 @@ export default {
default: false,
},
},
emits: ['close'],
emits: ['close', 'title-click'],
data() {
return {
headerTopValue: 0,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,6 @@ export default {
status: {
type: [HealthStatus, String, Number],
required: true,
validator(value) {
return Object.prototype.hasOwnProperty.call(
HealthStatus,
value.toUpperCase()
);
},
},
},
computed: {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,39 +1,47 @@
import { ref } from 'vue';
import {Ref, ref, UnwrapRef} from 'vue';

import ApplicationStore from '../store.js';
import Application from "@/services/application";

let applicationStore: ApplicationStore | null = null;
const applications = ref([]);
const applications = ref([] as Application[]);
const applicationsInitialized = ref(false);
const error = ref(null);

export function createApplicationStore() {
if (applicationStore) throw new Error('ApplicationStore already created!');
if (applicationStore) throw new Error('ApplicationStore already created!');

applicationStore = new ApplicationStore();
return applicationStore;
applicationStore = new ApplicationStore();
return applicationStore;
}

export function useApplicationStore() {
applicationStore.addEventListener('connected', () => {
applicationsInitialized.value = true;
error.value = null;
});
type ApplicationStoreValue = {
applications: Ref<UnwrapRef<Application[]>>;
applicationsInitialized: Ref<boolean>;
error: Ref<any>;
applicationStore: ApplicationStore;
}

export function useApplicationStore(): ApplicationStoreValue {
applicationStore.addEventListener('connected', () => {
applicationsInitialized.value = true;
error.value = null;
});

applicationStore.addEventListener('changed', (newApplications) => {
applicationsInitialized.value = true;
applications.value = newApplications;
error.value = null;
});
applicationStore.addEventListener('changed', (newApplications) => {
applicationsInitialized.value = true;
applications.value = newApplications;
error.value = null;
});

applicationStore.addEventListener('error', (errorResponse) => {
applicationsInitialized.value = true;
error.value = errorResponse;
});
applicationStore.addEventListener('error', (errorResponse) => {
applicationsInitialized.value = true;
error.value = errorResponse;
});

applicationStore.addEventListener('removed', () => {
applicationsInitialized.value = false;
});
applicationStore.addEventListener('removed', () => {
applicationsInitialized.value = false;
});

return { applications, applicationsInitialized, error, applicationStore };
return {applications, applicationsInitialized, error, applicationStore};
}
3 changes: 2 additions & 1 deletion spring-boot-admin-server-ui/src/main/frontend/global.d.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Component, Raw, RenderFunction } from 'vue';
import {Component, Raw, RenderFunction} from 'vue';

import ViewRegistry from '@/viewRegistry';

Expand All @@ -8,6 +8,7 @@ declare global {
type ApplicationStream = {
data: any;
} & MessageEvent;

interface Window {
SBA: SBASettings;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
"hours": "{count} Stunde | {count} Stunden",
"instance": "Instanz",
"instances": "Instanzen",
"instances_tc": "{count} Instanz | {count} Instanzen",
"integer": "Integer",
"menu": {
"open": "Menü öffnen"
Expand All @@ -42,6 +43,7 @@
"suppress": "Unterdrücken",
"time": "Zeit",
"unsuppress": "Reaktivieren",
"no_group": "Keine Gruppe",
"username": "Username",
"go_to_previous_page": "Gehe zur vorherigen Seite",
"go_to_page_n": "Gehe zu Seite {page}",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@
"hours": "{count} hour | {count} hours",
"instance": "Instance",
"instances": "Instances",
"instances_tc": "{n} instance | {n} instances",
"instances_tc": "{count} instance | {count} instances",
"integer": "Integer",
"menu": {
"open": "Open menu"
Expand All @@ -50,6 +50,7 @@
"suppress": "Suppress",
"time": "Time",
"unsuppress": "Unsuppress",
"no_group": "No Group",
"username": "Username",
"go_to_previous_page": "Go to previous page",
"go_to_page_n": "Go to page {page}",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,11 @@ export const convertBody = (responses) =>
});

class Application {
readonly name: string;
readonly instances: Instance[];
public readonly name: string;
public readonly instances: Instance[];
public readonly buildVersion? = {} as {value: string};
public readonly status: string;
public readonly statusTimestamp: string;

private readonly axios: AxiosInstance;

Expand Down
Loading

0 comments on commit 3e8cfc7

Please sign in to comment.