Skip to content

Commit d2f064f

Browse files
committed
feat(#1510): implement grouping of services in wallboard
1 parent 3e8cfc7 commit d2f064f

File tree

5 files changed

+322
-333
lines changed

5 files changed

+322
-333
lines changed

spring-boot-admin-server-ui/src/main/frontend/composables/useApplicationStore.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ export function createApplicationStore() {
1616
}
1717

1818
type ApplicationStoreValue = {
19-
applications: Ref<UnwrapRef<Application[]>>;
19+
applications: Ref<Application[]>;
2020
applicationsInitialized: Ref<boolean>;
2121
error: Ref<any>;
2222
applicationStore: ApplicationStore;

spring-boot-admin-server-ui/src/main/frontend/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ import views from './views';
3939

4040
import eventBus from '@/services/bus';
4141
import sbaShell from '@/shell';
42+
import VueClickAwayPlugin from "vue3-click-away";
4243

4344
const applicationStore = createApplicationStore();
4445
const viewRegistry = createViewRegistry();
@@ -128,6 +129,7 @@ const app = createApp({
128129

129130
app.use(i18n);
130131
app.use(components);
132+
app.use(VueClickAwayPlugin);
131133
app.use(NotificationcenterPlugin, {
132134
duration: 10_000,
133135
});
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import Instance from "@/services/instance";
2+
import {groupBy, sortBy, transform} from "lodash-es";
3+
import Application from "@/services/application";
4+
5+
const groupingFunctions = {
6+
'application': (instance: Instance) => instance.registration.name,
7+
'group': (instance: Instance) => instance.registration.metadata?.['group'] ?? "term.no_group",
8+
}
9+
10+
export type GroupingType = keyof typeof groupingFunctions;
11+
12+
export type InstancesListType = {
13+
name?: string;
14+
statusKey?: string;
15+
status?: string;
16+
instances?: Instance[];
17+
applications?: Application[];
18+
}
19+
20+
export const groupApplicationsBy = (applications: Application[], groupingFunction: GroupingType) => {
21+
const instances = applications.flatMap(application => application.instances);
22+
return groupInstancesBy(instances, groupingFunction);
23+
}
24+
25+
export const groupInstancesBy = (instances: Instance[], groupingFunction: GroupingType) => {
26+
const grouped = groupBy<Instance>(
27+
instances,
28+
groupingFunctions[groupingFunction]
29+
);
30+
31+
const list = transform<Instance[], InstancesListType[]>(
32+
grouped,
33+
(result, instances, name) => {
34+
result.push({
35+
name,
36+
instances: sortBy(instances, [
37+
(instance) => instance.registration.name,
38+
]),
39+
});
40+
}, []);
41+
42+
return sortBy(list, [(item) => item.status]);
43+
}
44+

spring-boot-admin-server-ui/src/main/frontend/services/notification-filter.ts

Lines changed: 61 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -13,90 +13,85 @@
1313
* See the License for the specific language governing permissions and
1414
* limitations under the License.
1515
*/
16-
import moment from 'moment';
17-
1816
import sbaConfig from '@/sba-config';
1917
import axios from '@/utils/axios';
2018
import uri from '@/utils/uri';
19+
import Application from "@/services/application";
20+
import Instance from "@/services/instance";
21+
22+
export type NotificationFilterProps = {
23+
id: string,
24+
applicationName: string,
25+
instanceId: string,
26+
expiry: string,
27+
expired: boolean
28+
}
2129

2230
class NotificationFilter {
23-
private id: string;
24-
private applicationName: string;
25-
private instanceId: string;
26-
private expiry: moment.Moment | null;
27-
28-
constructor({ expiry, ...filter }) {
29-
Object.assign(this, filter);
30-
this.expiry = expiry ? moment(expiry) : null;
31-
}
32-
33-
affects(obj) {
34-
if (!obj) {
35-
return false;
31+
public readonly expired: boolean;
32+
private readonly id: string;
33+
private readonly applicationName: string;
34+
private readonly instanceId: string;
35+
36+
constructor({id, applicationName, instanceId, expiry, expired, ...filter}: NotificationFilterProps) {
37+
Object.assign(this, filter);
38+
this.id = id;
39+
this.applicationName = applicationName;
40+
this.instanceId = instanceId;
41+
this.expired = expired;
3642
}
3743

38-
if (this.isApplicationFilter) {
39-
return this.applicationName === obj.name;
44+
static isSupported() {
45+
return Boolean(sbaConfig.uiSettings.notificationFilterEnabled);
4046
}
4147

42-
if (this.isInstanceFilter) {
43-
return this.instanceId === obj.id;
48+
static async getFilters() {
49+
return axios.get('notifications/filters', {
50+
transformResponse: NotificationFilter._transformResponse,
51+
});
4452
}
4553

46-
return false;
47-
}
48-
49-
get isApplicationFilter() {
50-
return this.applicationName != null;
51-
}
52-
53-
get isInstanceFilter() {
54-
return this.instanceId != null;
55-
}
54+
static async addFilter(object: Instance | Application, ttl: number) {
55+
const params = {ttl} as { ttl: number, applicationName?: string; instanceId?: string };
56+
if (object instanceof Application) {
57+
params.applicationName = object.name;
58+
} else if ('id' in object) {
59+
params.instanceId = object.id;
60+
}
61+
return axios.post('notifications/filters', null, {
62+
params,
63+
transformResponse: NotificationFilter._transformResponse,
64+
});
65+
}
5666

57-
async delete() {
58-
return axios.delete(uri`notifications/filters/${this.id}`);
59-
}
67+
static _transformResponse(data: any) {
68+
if (!data) {
69+
return data;
70+
}
71+
const json = JSON.parse(data);
72+
if (json instanceof Array) {
73+
return json
74+
.map((notificationFilter) => new NotificationFilter(notificationFilter))
75+
.filter((f) => !f.expired);
76+
}
77+
return new NotificationFilter(json);
78+
}
6079

61-
static isSupported() {
62-
return Boolean(sbaConfig.uiSettings.notificationFilterEnabled);
63-
}
80+
affects(obj: Instance | Application) {
81+
if (!obj) {
82+
return false;
83+
}
6484

65-
static async getFilters() {
66-
return axios.get('notifications/filters', {
67-
transformResponse: NotificationFilter._transformResponse,
68-
});
69-
}
85+
if (obj instanceof Application) {
86+
return this.applicationName === obj.name;
87+
}
7088

71-
static async addFilter(object, ttl) {
72-
const params = { ttl };
73-
if ('name' in object) {
74-
params.applicationName = object.name;
75-
} else if ('id' in object) {
76-
params.instanceId = object.id;
89+
return this.instanceId === obj.id;
7790
}
78-
return axios.post('notifications/filters', null, {
79-
params,
80-
transformResponse: NotificationFilter._transformResponse,
81-
});
82-
}
8391

84-
static _transformResponse(data) {
85-
if (!data) {
86-
return data;
92+
async delete() {
93+
return axios.delete(uri`notifications/filters/${this.id}`);
8794
}
88-
const json = JSON.parse(data);
89-
if (json instanceof Array) {
90-
return json
91-
.map(NotificationFilter._toNotificationFilters)
92-
.filter((f) => !f.expired);
93-
}
94-
return NotificationFilter._toNotificationFilters(json);
95-
}
96-
97-
static _toNotificationFilters(notificationFilter) {
98-
return new NotificationFilter(notificationFilter);
99-
}
10095
}
10196

10297
export default NotificationFilter;

0 commit comments

Comments
 (0)