diff --git a/spring-boot-admin-server-ui/src/main/frontend/services/application.ts b/spring-boot-admin-server-ui/src/main/frontend/services/application.ts index b544057f589..77fab72204e 100644 --- a/spring-boot-admin-server-ui/src/main/frontend/services/application.ts +++ b/spring-boot-admin-server-ui/src/main/frontend/services/application.ts @@ -124,6 +124,10 @@ class Application { return this.hasEndpoint('restart'); } + static async refreshApplications() { + return axios.post('applications'); + } + static async list() { return axios.get('applications', { headers: { Accept: 'application/json', 'X-SBA-REQUEST': true }, diff --git a/spring-boot-admin-server-ui/src/main/frontend/views/applications/applications.spec.ts b/spring-boot-admin-server-ui/src/main/frontend/views/applications/applications.spec.ts index f0f3dcbe1b9..3a3a8a6f9da 100644 --- a/spring-boot-admin-server-ui/src/main/frontend/views/applications/applications.spec.ts +++ b/spring-boot-admin-server-ui/src/main/frontend/views/applications/applications.spec.ts @@ -1,9 +1,11 @@ import userEvent from '@testing-library/user-event'; import { screen, waitFor } from '@testing-library/vue'; +import { HttpResponse, http } from 'msw'; import { beforeEach, describe, expect, it, vi } from 'vitest'; import { Ref, ref } from 'vue'; import { useApplicationStore } from '@/composables/useApplicationStore'; +import { server } from '@/mocks/server'; import Application from '@/services/application'; import Instance, { Registration } from '@/services/instance'; import { render } from '@/test-utils'; @@ -228,5 +230,57 @@ describe('Applications', () => { expect(indexRestricted).toBeLessThan(indexUp); }); }); + + describe('refresh button', () => { + beforeEach(() => { + server.use( + http.post('/applications', () => { + return HttpResponse.json({}); + }), + ); + }); + + it('clicking the refresh button invokes Application.refreshApplications', async () => { + const refreshSpy = vi.spyOn(Application, 'refreshApplications'); + + const refreshButton = screen.getByTitle('Refresh applications'); + // First click - enters confirm mode + await userEvent.click(refreshButton); + + // Second click - confirms and executes + const confirmButton = await screen.findByText('Confirm'); + await userEvent.click(confirmButton); + + await waitFor(() => { + expect(refreshSpy).toHaveBeenCalled(); + }); + }); + + it('logs error when refresh fails without throwing', async () => { + server.use( + http.post('/applications', () => { + return HttpResponse.json({}, { status: 500 }); + }), + ); + + const consoleErrorSpy = vi + .spyOn(console, 'error') + .mockImplementation(() => {}); + + const refreshButton = screen.getByTitle('Refresh applications'); + // First click - enters confirm mode + await userEvent.click(refreshButton); + + // Second click - confirms and executes + const confirmButton = await screen.findByText('Confirm'); + await userEvent.click(confirmButton); + + await waitFor(() => { + expect(consoleErrorSpy).toHaveBeenCalled(); + }); + + consoleErrorSpy.mockRestore(); + }); + }); }); }); diff --git a/spring-boot-admin-server-ui/src/main/frontend/views/applications/index.vue b/spring-boot-admin-server-ui/src/main/frontend/views/applications/index.vue index 4a74efe14e5..1047774086a 100644 --- a/spring-boot-admin-server-ui/src/main/frontend/views/applications/index.vue +++ b/spring-boot-admin-server-ui/src/main/frontend/views/applications/index.vue @@ -24,7 +24,7 @@ @@ -213,7 +213,6 @@ import { useApplicationStore } from '@/composables/useApplicationStore'; import Application from '@/services/application'; import Instance from '@/services/instance'; import NotificationFilter from '@/services/notification-filter'; -import axios from '@/utils/axios'; import { anyValueMatches } from '@/utils/collections'; import { Subject, concatMap, mergeWith, timer } from '@/utils/rxjs'; import { useRouterState } from '@/utils/useRouterState'; @@ -348,10 +347,13 @@ const grouped = computed(() => { return sortBy(list, [(item) => getApplicationStatus(item)]); }); -const refreshContext = () => { - axios.post('/applications').then(() => { +const refreshApplications = async () => { + try { + await Application.refreshApplications(); notificationCenter.success(t('applications.refreshed')); - }); + } catch (error) { + console.error(error); + } }; function getApplicationStatus(item: InstancesListType): string {