Skip to content

Commit

Permalink
feat(#2538): implement configs to hide URLs in UI (#3757)
Browse files Browse the repository at this point in the history
Co-authored-by: ulrichschulte <[email protected]>
  • Loading branch information
SteKoe and ulischulte authored Oct 25, 2024
1 parent c410e9c commit eff73bb
Show file tree
Hide file tree
Showing 12 changed files with 145 additions and 34 deletions.
16 changes: 16 additions & 0 deletions spring-boot-admin-docs/src/site/asciidoc/customize_ui.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -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.
11 changes: 6 additions & 5 deletions spring-boot-admin-server-ui/src/main/frontend/global.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@ declare global {

type UITheme = {
color: string;
palette: {
backgroundEnabled: boolean;
palette?: {
shade50: string;
shade100: string;
shade200: string;
Expand Down Expand Up @@ -60,18 +61,18 @@ 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[];
routes: string[];
externalViews: ExternalView[];
viewSettings: ViewSettings[];
enableToasts: boolean;
hideInstanceUrl: boolean;
};

type SBASettings = {
Expand All @@ -81,8 +82,8 @@ declare global {
[key: string]: any;
};
extensions: {
js: Extension[];
css: Extension[];
js?: Extension[];
css?: Extension[];
};
csrf: {
headerName: string;
Expand Down
16 changes: 10 additions & 6 deletions spring-boot-admin-server-ui/src/main/frontend/sba-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,17 +18,16 @@ import { merge } from 'lodash-es';
const brand =
'<img src="assets/img/icon-spring-boot-admin.svg">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',
Expand All @@ -45,9 +44,10 @@ const DEFAULT_CONFIG = {
threads: 2500,
logfile: 1000,
},
hideInstanceUrl: false,
},
user: null,
extensions: [],
extensions: {},
csrf: {
parameterName: '_csrf',
headerName: 'X-XSRF-TOKEN',
Expand All @@ -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;
};
Original file line number Diff line number Diff line change
@@ -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<typeof import('@/sba-config')>()),
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);
},
);
});
12 changes: 12 additions & 0 deletions spring-boot-admin-server-ui/src/main/frontend/services/instance.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down Expand Up @@ -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;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ describe('Navbar', function () {
});

render(Navbar);
screen.logTestingPlaygroundURL();

expect(screen.getByTestId('usermenu')).toBeVisible();
expect(screen.getByText('[email protected]')).toBeVisible();
});
Expand Down
4 changes: 4 additions & 0 deletions spring-boot-admin-server-ui/src/main/frontend/tests/setup.ts
Original file line number Diff line number Diff line change
@@ -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(),
Expand All @@ -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());
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<!--
- Copyright 2014-2018 the original author or authors.
- Copyright 2014-2024 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.
Expand Down Expand Up @@ -30,25 +30,39 @@
/>
</div>
<div class="flex-auto xl:flex-1 xl:w-1/4 truncate">
<a
:href="
instance.registration.serviceUrl || instance.registration.healthUrl
"
@click.stop
v-text="
instance.registration.serviceUrl || instance.registration.healthUrl
"
/>
<sba-tag
v-if="instance.registration.metadata?.['group']"
class="ml-2"
:value="instance.registration.metadata?.['group']"
small
/>
<br />
<span class="text-sm italic" v-text="instance.id" />
<template v-if="instance.showUrl()">
<a
:href="
instance.registration.serviceUrl ||
instance.registration.healthUrl
"
@click.stop
v-text="
instance.registration.serviceUrl ||
instance.registration.healthUrl
"
/>
<sba-tag
v-if="instance.registration.metadata?.['group']"
class="ml-2"
:value="instance.registration.metadata?.['group']"
small
/>
<br />
<span class="text-sm italic" v-text="instance.id" />
</template>
<template v-else>
<span v-text="instance.id"></span>
<sba-tag
v-if="instance.registration.metadata?.['group']"
class="ml-2"
:value="instance.registration.metadata?.['group']"
small
/>
</template>
</div>
<div
v-if="Array.isArray(instance.tags)"
class="hidden xl:block w-1/4"
:class="{
'overflow-x-scroll': Object.keys(instance.tags ?? {}).length > 0,
Expand All @@ -71,6 +85,10 @@
import { PropType } from 'vue';
import { useRouter } from 'vue-router';
import SbaStatus from '@/components/sba-status.vue';
import SbaTag from '@/components/sba-tag.vue';
import SbaTags from '@/components/sba-tags.vue';
import Instance from '@/services/instance';
const router = useRouter();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
<div class="flex-1 text-right">
<sba-button-group>
<sba-button
v-if="instance.showUrl()"
:title="instance.registration.serviceUrl"
class="border-gray-400 ml-1"
@click="openLink(instance.registration.serviceUrl)"
Expand All @@ -31,6 +32,7 @@
</sba-button>

<sba-button
v-if="instance.showUrl()"
:title="instance.registration.managementUrl"
class="border-gray-400 ml-1"
@click="openLink(instance.registration.managementUrl)"
Expand All @@ -52,6 +54,7 @@
</sba-button>

<sba-button
v-if="instance.showUrl()"
:title="instance.registration.healthUrl"
class="border-gray-400 ml-1"
@click="openLink(instance.registration.healthUrl)"
Expand Down Expand Up @@ -79,14 +82,16 @@

<script>
import SbaButtonGroup from '@/components/sba-button-group';
import SbaButton from '@/components/sba-button.vue';
import SbaStickySubnav from '@/components/sba-sticky-subnav.vue';
import Application from '@/services/application';
import Instance from '@/services/instance';
import InstanceSwitcher from '@/views/instances/details/instance-switcher';
export default {
name: 'DetailsNav',
components: { SbaButtonGroup, InstanceSwitcher },
components: { SbaButton, SbaStickySubnav, SbaButtonGroup, InstanceSwitcher },
props: {
application: {
type: Application,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2014-2023 the original author or authors.
* Copyright 2014-2024 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.
Expand Down Expand Up @@ -109,6 +109,7 @@ public UiController homeUiController(UiExtensions uiExtensions) throws IOExcepti
.favicon(this.adminUi.getFavicon())
.faviconDanger(this.adminUi.getFaviconDanger())
.enableToasts(this.adminUi.getEnableToasts())
.hideInstanceUrl(this.adminUi.getHideInstanceUrl())
.notificationFilterEnabled(
!this.applicationContext.getBeansOfType(NotificationFilterController.class).isEmpty())
.routes(routes)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2014-2023 the original author or authors.
* Copyright 2014-2024 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.
Expand Down Expand Up @@ -132,6 +132,11 @@ public class AdminServerUiProperties {
*/
private Boolean enableToasts = false;

/**
* Show or hide URL of instances.
*/
private Boolean hideInstanceUrl = false;

private UiTheme theme = new UiTheme();

@lombok.Data
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2014-2023 the original author or authors.
* Copyright 2014-2024 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.
Expand Down Expand Up @@ -150,6 +150,8 @@ public static class Settings {

private final Boolean enableToasts;

private final Boolean hideInstanceUrl;

}

@lombok.Data
Expand Down

0 comments on commit eff73bb

Please sign in to comment.