diff --git a/spring-boot-admin-docs/src/site/asciidoc/customize_ui.adoc b/spring-boot-admin-docs/src/site/asciidoc/customize_ui.adoc
index 15366318cd5..59746d1b07f 100644
--- a/spring-boot-admin-docs/src/site/asciidoc/customize_ui.adoc
+++ b/spring-boot-admin-docs/src/site/asciidoc/customize_ui.adoc
@@ -216,3 +216,19 @@ You can very simply hide views in the navbar:
----
include::{samples-dir}/spring-boot-admin-sample-servlet/src/main/resources/application.yml[tags=customization-view-settings]
----
+
+== Hide Service URL ==
+To hide service URLs in Spring Boot Admin UI entirely, set the following property in your Server's configuration:
+
+|===
+| Property name | Default | Usage
+
+| `spring.boot.admin.ui.hide-instance-url`
+| `false`
+| Set to `true` to hide service URLs as well as actions that require them in UI (e.g. jump to /health or /actuator).
+
+|===
+
+If you want to hide the URL for specific instances oncly, you can set the `hide-url` property in the instance metadata while registering a service.
+When using Spring Boot Admin Client you can set the property `spring.boot.admin.client.metadata.hide-url=true` in the corresponding config file.
+The value set in `metadata` does not have any effect, when the URLs are disabled in Server.
diff --git a/spring-boot-admin-server-ui/src/main/frontend/global.d.ts b/spring-boot-admin-server-ui/src/main/frontend/global.d.ts
index c475056a935..3c038256b41 100644
--- a/spring-boot-admin-server-ui/src/main/frontend/global.d.ts
+++ b/spring-boot-admin-server-ui/src/main/frontend/global.d.ts
@@ -20,7 +20,8 @@ declare global {
type UITheme = {
color: string;
- palette: {
+ backgroundEnabled: boolean;
+ palette?: {
shade50: string;
shade100: string;
shade200: string;
@@ -60,11 +61,10 @@ declare global {
type UISettings = {
title: string;
brand: string;
- loginIcon: string;
favicon: string;
faviconDanger: string;
pollTimer: PollTimer;
- uiTheme: UITheme;
+ theme: UITheme;
notificationFilterEnabled: boolean;
rememberMeEnabled: boolean;
availableLanguages: string[];
@@ -72,6 +72,7 @@ declare global {
externalViews: ExternalView[];
viewSettings: ViewSettings[];
enableToasts: boolean;
+ hideInstanceUrl: boolean;
};
type SBASettings = {
@@ -81,8 +82,8 @@ declare global {
[key: string]: any;
};
extensions: {
- js: Extension[];
- css: Extension[];
+ js?: Extension[];
+ css?: Extension[];
};
csrf: {
headerName: string;
diff --git a/spring-boot-admin-server-ui/src/main/frontend/sba-config.ts b/spring-boot-admin-server-ui/src/main/frontend/sba-config.ts
index 0fe5717c31d..fa17aada0ef 100644
--- a/spring-boot-admin-server-ui/src/main/frontend/sba-config.ts
+++ b/spring-boot-admin-server-ui/src/main/frontend/sba-config.ts
@@ -18,17 +18,16 @@ import { merge } from 'lodash-es';
const brand =
'Spring Boot Admin';
-const DEFAULT_CONFIG = {
+const DEFAULT_CONFIG: SBASettings = {
uiSettings: {
+ title: 'Spring Boot Admin',
brand,
theme: {
backgroundEnabled: true,
color: '#42d3a5',
},
- notifications: {
- enabled: true,
- },
rememberMeEnabled: true,
+ enableToasts: false,
externalViews: [] as ExternalView[],
favicon: 'assets/img/favicon.png',
faviconDanger: 'assets/img/favicon-danger.png',
@@ -45,9 +44,10 @@ const DEFAULT_CONFIG = {
threads: 2500,
logfile: 1000,
},
+ hideInstanceUrl: false,
},
user: null,
- extensions: [],
+ extensions: {},
csrf: {
parameterName: '_csrf',
headerName: 'X-XSRF-TOKEN',
@@ -57,10 +57,14 @@ const DEFAULT_CONFIG = {
},
};
-const mergedConfig = merge(DEFAULT_CONFIG, window.SBA);
+const mergedConfig = merge(DEFAULT_CONFIG, window.SBA) as SBASettings;
export const getCurrentUser = () => {
return mergedConfig.user;
};
export default mergedConfig;
+
+export const useSbaConfig = () => {
+ return mergedConfig;
+};
diff --git a/spring-boot-admin-server-ui/src/main/frontend/services/instance.spec.ts b/spring-boot-admin-server-ui/src/main/frontend/services/instance.spec.ts
new file mode 100644
index 00000000000..9604cd031a5
--- /dev/null
+++ b/spring-boot-admin-server-ui/src/main/frontend/services/instance.spec.ts
@@ -0,0 +1,43 @@
+import { describe, expect, test, vi } from 'vitest';
+
+import Instance from '@/services/instance';
+
+const { useSbaConfig } = vi.hoisted(() => ({
+ useSbaConfig: vi.fn().mockReturnValue(true),
+}));
+
+vi.mock('@/sba-config', async (importOriginal) => ({
+ ...(await importOriginal()),
+ useSbaConfig,
+}));
+
+describe('Instance', () => {
+ test.each`
+ hideInstanceUrl | metadataHideUrl | expectUrlToBeShownOnUI
+ ${false} | ${'true'} | ${false}
+ ${false} | ${'false'} | ${true}
+ ${false} | ${undefined} | ${true}
+ ${true} | ${'true'} | ${false}
+ ${true} | ${'false'} | ${false}
+ `(
+ 'showUrl when hideInstanceUrl=$hideInstanceUrl and metadataHideUrl=$metadataHideUrl should return $expectUrlToBeShownOnUI',
+ ({ hideInstanceUrl, metadataHideUrl, expectUrlToBeShownOnUI }) => {
+ useSbaConfig.mockReturnValue({
+ uiSettings: {
+ hideInstanceUrl,
+ },
+ });
+
+ const instance = new Instance({
+ id: 'id',
+ registration: {
+ metadata: {
+ ['hide-url']: metadataHideUrl,
+ },
+ },
+ });
+
+ expect(instance.showUrl()).toBe(expectUrlToBeShownOnUI);
+ },
+ );
+});
diff --git a/spring-boot-admin-server-ui/src/main/frontend/services/instance.ts b/spring-boot-admin-server-ui/src/main/frontend/services/instance.ts
index 9f537913791..b3c7d87e436 100644
--- a/spring-boot-admin-server-ui/src/main/frontend/services/instance.ts
+++ b/spring-boot-admin-server-ui/src/main/frontend/services/instance.ts
@@ -25,6 +25,8 @@ import waitForPolyfill from '../utils/eventsource-polyfill';
import logtail from '../utils/logtail';
import uri from '../utils/uri';
+import { useSbaConfig } from '@/sba-config';
+
const actuatorMimeTypes = [
'application/vnd.spring-boot.actuator.v2+json',
'application/vnd.spring-boot.actuator.v1+json',
@@ -117,6 +119,16 @@ class Instance {
}));
}
+ showUrl() {
+ const sbaConfig = useSbaConfig();
+ if (sbaConfig.uiSettings.hideInstanceUrl) {
+ return false;
+ }
+
+ const hideUrlMetadata = this.registration.metadata?.['hide-url'];
+ return hideUrlMetadata !== 'true';
+ }
+
getId() {
return this.id;
}
diff --git a/spring-boot-admin-server-ui/src/main/frontend/shell/navbar.spec.ts b/spring-boot-admin-server-ui/src/main/frontend/shell/navbar.spec.ts
index 3f4c27d5867..dbe5b978b45 100644
--- a/spring-boot-admin-server-ui/src/main/frontend/shell/navbar.spec.ts
+++ b/spring-boot-admin-server-ui/src/main/frontend/shell/navbar.spec.ts
@@ -37,7 +37,7 @@ describe('Navbar', function () {
});
render(Navbar);
- screen.logTestingPlaygroundURL();
+
expect(screen.getByTestId('usermenu')).toBeVisible();
expect(screen.getByText('mail@example.org')).toBeVisible();
});
diff --git a/spring-boot-admin-server-ui/src/main/frontend/tests/setup.ts b/spring-boot-admin-server-ui/src/main/frontend/tests/setup.ts
index 6801789c0c0..9bf0898bf7e 100644
--- a/spring-boot-admin-server-ui/src/main/frontend/tests/setup.ts
+++ b/spring-boot-admin-server-ui/src/main/frontend/tests/setup.ts
@@ -1,8 +1,10 @@
+import '@testing-library/jest-dom';
import '@testing-library/jest-dom/vitest';
import { cleanup } from '@testing-library/vue';
import { afterAll, afterEach, beforeAll, vi } from 'vitest';
import { server } from '@/mocks/server';
+import sbaConfig from '@/sba-config';
global.IntersectionObserver = vi.fn().mockImplementation(() => ({
observe: vi.fn(),
@@ -15,6 +17,8 @@ global.ResizeObserver = vi.fn().mockImplementation(() => ({
disconnect: vi.fn(),
}));
+global.SBA = sbaConfig;
+
beforeAll(() => server.listen({ onUnhandledRequest: 'error' }));
afterAll(() => server.close());
afterEach(() => server.resetHandlers());
diff --git a/spring-boot-admin-server-ui/src/main/frontend/views/applications/InstancesList.vue b/spring-boot-admin-server-ui/src/main/frontend/views/applications/InstancesList.vue
index ef3ce04328d..89b1128ed60 100644
--- a/spring-boot-admin-server-ui/src/main/frontend/views/applications/InstancesList.vue
+++ b/spring-boot-admin-server-ui/src/main/frontend/views/applications/InstancesList.vue
@@ -1,5 +1,5 @@