diff --git a/spring-boot-admin-server-ui/src/main/frontend/components/sba-button.vue b/spring-boot-admin-server-ui/src/main/frontend/components/sba-button.vue index 8b9d967c4e2..a019b1b49d8 100644 --- a/spring-boot-admin-server-ui/src/main/frontend/components/sba-button.vue +++ b/spring-boot-admin-server-ui/src/main/frontend/components/sba-button.vue @@ -4,6 +4,8 @@ :class="cssClasses" :disabled="disabled === true" @click="$emit('click', $event)" + :aria-label="$attrs.ariaLabel" + :title="$attrs.title" > diff --git a/spring-boot-admin-server-ui/src/main/frontend/i18n/i18n.de.json b/spring-boot-admin-server-ui/src/main/frontend/i18n/i18n.de.json index 403e8c06d1e..7157616a9e1 100644 --- a/spring-boot-admin-server-ui/src/main/frontend/i18n/i18n.de.json +++ b/spring-boot-admin-server-ui/src/main/frontend/i18n/i18n.de.json @@ -26,6 +26,10 @@ "fetching_data": "Lade Daten...", "fetch_failed": "Abruf der Daten fehlgeschlagen.", "float": "Float", + "group_by": { + "group": "Nach Gruppen gruppieren", + "application": "Nach Applikationen gruppieren" + }, "hours": "{count} Stunde | {count} Stunden", "instance": "Instanz", "instances": "Instanzen", diff --git a/spring-boot-admin-server-ui/src/main/frontend/i18n/i18n.en.json b/spring-boot-admin-server-ui/src/main/frontend/i18n/i18n.en.json index 3aa37595363..15dce5fc037 100644 --- a/spring-boot-admin-server-ui/src/main/frontend/i18n/i18n.en.json +++ b/spring-boot-admin-server-ui/src/main/frontend/i18n/i18n.en.json @@ -33,6 +33,10 @@ "fetch_failed": "Fetching of data failed.", "float": "Float", "filter": "Filter", + "group_by": { + "group": "Group by group", + "application": "Group by application" + }, "hours": "{count} hour | {count} hours", "instance": "Instance", "instances": "Instances", 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 434ffe03bd6..b300db7ca2d 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 @@ -433,6 +433,13 @@ type Registration = { } type StatusInfo = { - status: string + status: Status details: { [key: string]: string } } + +export type Status = "UNKNOWN" | + "OUT_OF_SERVICE" | + "UP" | + "DOWN" | + "OFFLINE" | + "RESTRICTED"; diff --git a/spring-boot-admin-server-ui/src/main/frontend/services/instanceGroupService.ts b/spring-boot-admin-server-ui/src/main/frontend/services/instanceGroupService.ts index 37bcab2ebeb..4af16ffdf45 100644 --- a/spring-boot-admin-server-ui/src/main/frontend/services/instanceGroupService.ts +++ b/spring-boot-admin-server-ui/src/main/frontend/services/instanceGroupService.ts @@ -1,44 +1,76 @@ import Instance from "@/services/instance"; import {groupBy, sortBy, transform} from "lodash-es"; import Application from "@/services/application"; +import {useApplicationStore} from "@/composables/useApplicationStore"; const groupingFunctions = { - 'application': (instance: Instance) => instance.registration.name, - 'group': (instance: Instance) => instance.registration.metadata?.['group'] ?? "term.no_group", + 'application': (instance: Instance) => instance.registration.name, + 'group': (instance: Instance) => instance.registration.metadata?.['group'] ?? "term.no_group", } export type GroupingType = keyof typeof groupingFunctions; +export const isGroupingType = (grouping: string = ""): grouping is GroupingType => { + return Object.keys(groupingFunctions).includes(grouping); +} -export type InstancesListType = { - name?: string; - statusKey?: string; - status?: string; - instances?: Instance[]; - applications?: Application[]; +export type InstancesListItem = { + name: string; + statusKey?: string; + status?: string; + instances: Instance[]; } export const groupApplicationsBy = (applications: Application[], groupingFunction: GroupingType) => { - const instances = applications.flatMap(application => application.instances); - return groupInstancesBy(instances, groupingFunction); + const {applicationStore} = useApplicationStore(); + const instances = applications.flatMap(application => application.instances); + const groupedInstances = groupInstancesBy(instances, groupingFunction); + + if (groupingFunction === 'application') { + return groupedInstances + .map(item => { + return { + ...applicationStore.findApplicationByInstanceId(item.instances[0].id), + ...item, + } + }); + } + + return groupedInstances + .map(item => { + return { + status: getStatus(item.instances), + ...item, + } + }); +} + +function getStatus(instances: Instance[]) { + if (instances.every(instance => instance.statusInfo.status === 'DOWN')) { + return 'DOWN'; + } + if (instances.every(instance => instance.statusInfo.status === 'UP')) { + return 'UP'; + } + return "RESTRICTED"; } export const groupInstancesBy = (instances: Instance[], groupingFunction: GroupingType) => { - const grouped = groupBy( - instances, - groupingFunctions[groupingFunction] - ); - - const list = transform( - grouped, - (result, instances, name) => { - result.push({ - name, - instances: sortBy(instances, [ - (instance) => instance.registration.name, - ]), - }); - }, []); - - return sortBy(list, [(item) => item.status]); + const grouped = groupBy( + instances, + groupingFunctions[groupingFunction] + ); + + const list = transform( + grouped, + (result, instances, name) => { + result.push({ + name, + instances: sortBy(instances, [ + (instance) => instance.registration.name, + ]), + }); + }, []); + + return sortBy(list, [(item) => item.status]); } diff --git a/spring-boot-admin-server-ui/src/main/frontend/store.ts b/spring-boot-admin-server-ui/src/main/frontend/store.ts index 6d0df303cd4..7a26a206958 100644 --- a/spring-boot-admin-server-ui/src/main/frontend/store.ts +++ b/spring-boot-admin-server-ui/src/main/frontend/store.ts @@ -144,7 +144,7 @@ export default class ApplicationStore { } } - findApplicationByInstanceId(instanceId: string) { + findApplicationByInstanceId(instanceId: string): Application | undefined { return findApplicationForInstance(this.applications, instanceId); } } diff --git a/spring-boot-admin-server-ui/src/main/frontend/views/applications/ApplicationListItemAction.vue b/spring-boot-admin-server-ui/src/main/frontend/views/applications/ApplicationListItemAction.vue index 2049ddf34e0..a49ba496a8f 100644 --- a/spring-boot-admin-server-ui/src/main/frontend/views/applications/ApplicationListItemAction.vue +++ b/spring-boot-admin-server-ui/src/main/frontend/views/applications/ApplicationListItemAction.vue @@ -52,6 +52,7 @@ import {ActionHandler, ApplicationActionHandler, InstanceActionHandler} from "@/ import {useI18n} from "vue-i18n"; import {useNotificationCenter} from "@stekoe/vue-toast-notificationcenter"; import {inject} from "@vue/runtime-core"; +import {PropType} from "vue"; const $sbaModal = inject('$sbaModal'); const {t} = useI18n(); @@ -59,7 +60,7 @@ const notificationCenter = useNotificationCenter({}); const props = defineProps({ item: { - type: [Application, Instance], + type: Object as PropType, required: true, }, hasActiveNotificationFilter: { @@ -80,7 +81,7 @@ if (props.item instanceof Application) { name: 'journal', query: {application: props.item.name}, }; -} else if(props.item instanceof Instance) { +} else if (props.item instanceof Instance) { actionHandler = new InstanceActionHandler($sbaModal, t, notificationCenter); journalLink = {name: 'journal', query: {instanceId: props.item.id}}; } 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 e4fa34636c3..f439f8dae0a 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 @@ -69,10 +69,14 @@ -