mirror of
https://github.com/apache/nifi.git
synced 2025-02-07 18:48:51 +00:00
[NIFI-12560] bulletin board (#8204)
* [NIFI-12560] bulletin board * Add sourceType to BulletinDTO * remove unused import * address review feedback: fix overflow for bulletins tooltip.
This commit is contained in:
parent
1c26b39fcd
commit
726a930b01
@ -38,6 +38,7 @@ public class BulletinDTO {
|
||||
private String level;
|
||||
private String message;
|
||||
private Date timestamp;
|
||||
private String sourceType;
|
||||
|
||||
/**
|
||||
* @return id of this message
|
||||
@ -167,4 +168,14 @@ public class BulletinDTO {
|
||||
this.timestamp = timestamp;
|
||||
}
|
||||
|
||||
@ApiModelProperty(
|
||||
value = "The type of the source component"
|
||||
)
|
||||
public String getSourceType() {
|
||||
return sourceType;
|
||||
}
|
||||
|
||||
public void setSourceType(String sourceType) {
|
||||
this.sourceType = sourceType;
|
||||
}
|
||||
}
|
||||
|
@ -3417,6 +3417,7 @@ public final class DtoFactory {
|
||||
dto.setCategory(bulletin.getCategory());
|
||||
dto.setLevel(bulletin.getLevel());
|
||||
dto.setMessage(bulletin.getMessage());
|
||||
dto.setSourceType(bulletin.getSourceType().name());
|
||||
return dto;
|
||||
}
|
||||
|
||||
@ -4469,6 +4470,7 @@ public final class DtoFactory {
|
||||
copy.setLevel(original.getLevel());
|
||||
copy.setMessage(original.getMessage());
|
||||
copy.setNodeAddress(original.getNodeAddress());
|
||||
copy.setSourceType(original.getSourceType());
|
||||
return copy;
|
||||
}
|
||||
|
||||
|
@ -57,6 +57,11 @@ const routes: Routes = [
|
||||
canMatch: [authenticationGuard],
|
||||
loadChildren: () => import('./pages/summary/feature/summary.module').then((m) => m.SummaryModule)
|
||||
},
|
||||
{
|
||||
path: 'bulletins',
|
||||
canMatch: [authenticationGuard],
|
||||
loadChildren: () => import('./pages/bulletins/feature/bulletins.module').then((m) => m.BulletinsModule)
|
||||
},
|
||||
{
|
||||
path: '',
|
||||
canMatch: [authenticationGuard],
|
||||
|
@ -0,0 +1,33 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { RouterModule, Routes } from '@angular/router';
|
||||
import { NgModule } from '@angular/core';
|
||||
import { Bulletins } from './bulletins.component';
|
||||
|
||||
const routes: Routes = [
|
||||
{
|
||||
path: '',
|
||||
component: Bulletins
|
||||
}
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
imports: [RouterModule.forChild(routes)],
|
||||
exports: [RouterModule]
|
||||
})
|
||||
export class BulletinsRoutingModule {}
|
@ -0,0 +1,28 @@
|
||||
<!--
|
||||
~ Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
~ contributor license agreements. See the NOTICE file distributed with
|
||||
~ this work for additional information regarding copyright ownership.
|
||||
~ The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
~ (the "License"); you may not use this file except in compliance with
|
||||
~ the License. You may obtain a copy of the License at
|
||||
~
|
||||
~ http://www.apache.org/licenses/LICENSE-2.0
|
||||
~
|
||||
~ Unless required by applicable law or agreed to in writing, software
|
||||
~ distributed under the License is distributed on an "AS IS" BASIS,
|
||||
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
~ See the License for the specific language governing permissions and
|
||||
~ limitations under the License.
|
||||
-->
|
||||
|
||||
<div class="p-4 flex flex-col h-screen justify-between gap-y-5">
|
||||
<div class="flex justify-between">
|
||||
<h3 class="text-xl bold bulletin-board-header">NiFi Bulletin Board</h3>
|
||||
<button class="nifi-button" [routerLink]="['/']">
|
||||
<i class="fa fa-times"></i>
|
||||
</button>
|
||||
</div>
|
||||
<div class="flex-1">
|
||||
<bulletin-board></bulletin-board>
|
||||
</div>
|
||||
</div>
|
@ -0,0 +1,20 @@
|
||||
/*!
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
.bulletin-board-header {
|
||||
color: #728e9b;
|
||||
}
|
@ -0,0 +1,16 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
@ -0,0 +1,38 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { Component, OnDestroy, OnInit } from '@angular/core';
|
||||
import { NiFiState } from '../../../state';
|
||||
import { Store } from '@ngrx/store';
|
||||
import { startCurrentUserPolling, stopCurrentUserPolling } from '../../../state/current-user/current-user.actions';
|
||||
|
||||
@Component({
|
||||
selector: 'bulletins',
|
||||
templateUrl: './bulletins.component.html',
|
||||
styleUrls: ['./bulletins.component.scss']
|
||||
})
|
||||
export class Bulletins implements OnInit, OnDestroy {
|
||||
constructor(private store: Store<NiFiState>) {}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.store.dispatch(startCurrentUserPolling());
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
this.store.dispatch(stopCurrentUserPolling());
|
||||
}
|
||||
}
|
@ -0,0 +1,41 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { NgModule } from '@angular/core';
|
||||
import { Bulletins } from './bulletins.component';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { StoreModule } from '@ngrx/store';
|
||||
import { bulletinsFeatureKey, reducers } from '../state';
|
||||
import { EffectsModule } from '@ngrx/effects';
|
||||
import { BulletinBoardEffects } from '../state/bulletin-board/bulletin-board.effects';
|
||||
import { BulletinsRoutingModule } from './bulletins-routing.module';
|
||||
import { CounterListingModule } from '../../counters/ui/counter-listing/counter-listing.module';
|
||||
import { BulletinBoard } from '../ui/bulletin-board/bulletin-board.component';
|
||||
|
||||
@NgModule({
|
||||
declarations: [Bulletins],
|
||||
exports: [Bulletins],
|
||||
imports: [
|
||||
CommonModule,
|
||||
BulletinsRoutingModule,
|
||||
StoreModule.forFeature(bulletinsFeatureKey, reducers),
|
||||
EffectsModule.forFeature(BulletinBoardEffects),
|
||||
CounterListingModule,
|
||||
BulletinBoard
|
||||
]
|
||||
})
|
||||
export class BulletinsModule {}
|
@ -0,0 +1,36 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { Injectable } from '@angular/core';
|
||||
import { HttpClient, HttpParams } from '@angular/common/http';
|
||||
import { Client } from '../../../service/client.service';
|
||||
import { LoadBulletinBoardRequest } from '../state/bulletin-board';
|
||||
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class BulletinBoardService {
|
||||
private static readonly API: string = '../nifi-api';
|
||||
|
||||
constructor(
|
||||
private httpClient: HttpClient,
|
||||
private client: Client
|
||||
) {}
|
||||
|
||||
getBulletins(request: LoadBulletinBoardRequest) {
|
||||
const params: HttpParams = request as HttpParams;
|
||||
return this.httpClient.get(`${BulletinBoardService.API}/flow/bulletin-board`, { params });
|
||||
}
|
||||
}
|
@ -0,0 +1,54 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { createAction, props } from '@ngrx/store';
|
||||
import { BulletinBoardFilterArgs, LoadBulletinBoardRequest, LoadBulletinBoardResponse } from './index';
|
||||
|
||||
const BULLETIN_BOARD_PREFIX = '[Bulletin Board]';
|
||||
|
||||
export const loadBulletinBoard = createAction(
|
||||
`${BULLETIN_BOARD_PREFIX} Load Bulletin Board`,
|
||||
props<{ request: LoadBulletinBoardRequest }>()
|
||||
);
|
||||
|
||||
export const loadBulletinBoardSuccess = createAction(
|
||||
`${BULLETIN_BOARD_PREFIX} Load Bulletin Board Success`,
|
||||
props<{ response: LoadBulletinBoardResponse }>()
|
||||
);
|
||||
|
||||
export const resetBulletinBoardState = createAction(`${BULLETIN_BOARD_PREFIX} Reset Bulletin Board State`);
|
||||
|
||||
export const clearBulletinBoard = createAction(`${BULLETIN_BOARD_PREFIX} Clear Bulletin Board`);
|
||||
|
||||
export const bulletinBoardApiError = createAction(
|
||||
`${BULLETIN_BOARD_PREFIX} Load Bulletin Board Errors`,
|
||||
props<{ error: string }>()
|
||||
);
|
||||
|
||||
export const setBulletinBoardFilter = createAction(
|
||||
`${BULLETIN_BOARD_PREFIX} Set Bulletin Board Filter`,
|
||||
props<{ filter: BulletinBoardFilterArgs }>()
|
||||
);
|
||||
|
||||
export const setBulletinBoardAutoRefresh = createAction(
|
||||
`${BULLETIN_BOARD_PREFIX} Set Auto-Refresh`,
|
||||
props<{ autoRefresh: boolean }>()
|
||||
);
|
||||
|
||||
export const startBulletinBoardPolling = createAction(`${BULLETIN_BOARD_PREFIX} Start polling`);
|
||||
|
||||
export const stopBulletinBoardPolling = createAction(`${BULLETIN_BOARD_PREFIX} Stop polling`);
|
@ -0,0 +1,107 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { Injectable } from '@angular/core';
|
||||
import { Actions, createEffect, ofType } from '@ngrx/effects';
|
||||
import { Store } from '@ngrx/store';
|
||||
import { NiFiState } from '../../../../state';
|
||||
import { Router } from '@angular/router';
|
||||
import * as BulletinBoardActions from './bulletin-board.actions';
|
||||
import { asyncScheduler, from, interval, map, of, switchMap, takeUntil, withLatestFrom } from 'rxjs';
|
||||
import { BulletinBoardService } from '../../service/bulletin-board.service';
|
||||
import { selectBulletinBoardFilter, selectLastBulletinId } from './bulletin-board.selectors';
|
||||
import { LoadBulletinBoardRequest } from './index';
|
||||
|
||||
@Injectable()
|
||||
export class BulletinBoardEffects {
|
||||
constructor(
|
||||
private actions$: Actions,
|
||||
private store: Store<NiFiState>,
|
||||
private router: Router,
|
||||
private bulletinBoardService: BulletinBoardService
|
||||
) {}
|
||||
|
||||
loadBulletinBoard$ = createEffect(() =>
|
||||
this.actions$.pipe(
|
||||
ofType(BulletinBoardActions.loadBulletinBoard),
|
||||
map((action) => action.request),
|
||||
switchMap((request) =>
|
||||
from(
|
||||
this.bulletinBoardService.getBulletins(request).pipe(
|
||||
map((response: any) =>
|
||||
BulletinBoardActions.loadBulletinBoardSuccess({
|
||||
response: {
|
||||
bulletinBoard: response.bulletinBoard,
|
||||
loadedTimestamp: response.bulletinBoard.generated
|
||||
}
|
||||
})
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
setBulletinBoardAutoRefresh$ = createEffect(() =>
|
||||
this.actions$.pipe(
|
||||
ofType(BulletinBoardActions.setBulletinBoardAutoRefresh),
|
||||
map((action) => action.autoRefresh),
|
||||
switchMap((autoRefresh) => {
|
||||
if (autoRefresh) {
|
||||
return of(BulletinBoardActions.startBulletinBoardPolling());
|
||||
}
|
||||
return of(BulletinBoardActions.stopBulletinBoardPolling());
|
||||
})
|
||||
)
|
||||
);
|
||||
|
||||
startBulletinBoardPolling$ = createEffect(() =>
|
||||
this.actions$.pipe(
|
||||
ofType(BulletinBoardActions.startBulletinBoardPolling),
|
||||
switchMap(() =>
|
||||
interval(3000, asyncScheduler).pipe(
|
||||
takeUntil(this.actions$.pipe(ofType(BulletinBoardActions.stopBulletinBoardPolling)))
|
||||
)
|
||||
),
|
||||
withLatestFrom(this.store.select(selectBulletinBoardFilter), this.store.select(selectLastBulletinId)),
|
||||
switchMap(([, filter, lastBulletinId]) => {
|
||||
const request: LoadBulletinBoardRequest = {};
|
||||
if (lastBulletinId > 0) {
|
||||
request.after = lastBulletinId;
|
||||
}
|
||||
if (filter.filterTerm.length > 0) {
|
||||
const filterTerm = filter.filterTerm;
|
||||
switch (filter.filterColumn) {
|
||||
case 'message':
|
||||
request.message = filterTerm;
|
||||
break;
|
||||
case 'id':
|
||||
request.sourceId = filterTerm;
|
||||
break;
|
||||
case 'groupId':
|
||||
request.groupId = filterTerm;
|
||||
break;
|
||||
case 'name':
|
||||
request.sourceName = filterTerm;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return of(BulletinBoardActions.loadBulletinBoard({ request }));
|
||||
})
|
||||
)
|
||||
);
|
||||
}
|
@ -0,0 +1,117 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { BulletinBoardEvent, BulletinBoardItem, BulletinBoardState } from './index';
|
||||
import { createReducer, on } from '@ngrx/store';
|
||||
import {
|
||||
bulletinBoardApiError,
|
||||
clearBulletinBoard,
|
||||
loadBulletinBoard,
|
||||
loadBulletinBoardSuccess,
|
||||
resetBulletinBoardState,
|
||||
setBulletinBoardAutoRefresh,
|
||||
setBulletinBoardFilter
|
||||
} from './bulletin-board.actions';
|
||||
|
||||
export const initialBulletinBoardState: BulletinBoardState = {
|
||||
bulletinBoardItems: [],
|
||||
filter: {
|
||||
filterTerm: '',
|
||||
filterColumn: 'message'
|
||||
},
|
||||
autoRefresh: true,
|
||||
lastBulletinId: 0,
|
||||
status: 'pending',
|
||||
error: null,
|
||||
loadedTimestamp: ''
|
||||
};
|
||||
|
||||
export const bulletinBoardReducer = createReducer(
|
||||
initialBulletinBoardState,
|
||||
|
||||
on(loadBulletinBoard, (state: BulletinBoardState) => ({
|
||||
...state,
|
||||
status: 'loading' as const
|
||||
})),
|
||||
|
||||
on(loadBulletinBoardSuccess, (state: BulletinBoardState, { response }) => {
|
||||
// as bulletins are loaded, we just add them to the existing bulletins in the store
|
||||
const items = response.bulletinBoard.bulletins.map((bulletin) => {
|
||||
const bulletinItem: BulletinBoardItem = {
|
||||
item: bulletin
|
||||
};
|
||||
return bulletinItem;
|
||||
});
|
||||
|
||||
let lastId = state.lastBulletinId;
|
||||
if (response.bulletinBoard.bulletins.length > 0) {
|
||||
lastId = response.bulletinBoard.bulletins[response.bulletinBoard.bulletins.length - 1].id;
|
||||
}
|
||||
|
||||
return {
|
||||
...state,
|
||||
bulletinBoardItems: [...state.bulletinBoardItems, ...items],
|
||||
lastBulletinId: lastId,
|
||||
status: 'success' as const,
|
||||
error: null,
|
||||
loadedTimestamp: response.loadedTimestamp
|
||||
};
|
||||
}),
|
||||
|
||||
on(bulletinBoardApiError, (state, { error }) => ({
|
||||
...state,
|
||||
error,
|
||||
status: 'error' as const
|
||||
})),
|
||||
|
||||
on(resetBulletinBoardState, () => ({ ...initialBulletinBoardState })),
|
||||
|
||||
on(clearBulletinBoard, (state) => ({
|
||||
...state,
|
||||
bulletinBoardItems: []
|
||||
})),
|
||||
|
||||
on(setBulletinBoardFilter, (state: BulletinBoardState, { filter }) => {
|
||||
// add a new bulletin event to the list for the filter
|
||||
const event: BulletinBoardEvent = {
|
||||
type: 'filter',
|
||||
message:
|
||||
filter.filterTerm.length > 0
|
||||
? `Filter by ${filter.filterColumn} matching '${filter.filterTerm}'`
|
||||
: 'Filter removed'
|
||||
};
|
||||
const item: BulletinBoardItem = { item: event };
|
||||
return {
|
||||
...state,
|
||||
bulletinBoardItems: [...state.bulletinBoardItems, item],
|
||||
filter: { ...filter }
|
||||
};
|
||||
}),
|
||||
|
||||
on(setBulletinBoardAutoRefresh, (state: BulletinBoardState, { autoRefresh }) => {
|
||||
const event: BulletinBoardEvent = {
|
||||
type: 'auto-refresh',
|
||||
message: autoRefresh ? `Auto-refresh started` : 'Auto-refresh stopped'
|
||||
};
|
||||
const item: BulletinBoardItem = { item: event };
|
||||
return {
|
||||
...state,
|
||||
bulletinBoardItems: [...state.bulletinBoardItems, item],
|
||||
autoRefresh
|
||||
};
|
||||
})
|
||||
);
|
@ -0,0 +1,35 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { createSelector } from '@ngrx/store';
|
||||
import { BulletinsState, selectBulletinState } from '../index';
|
||||
import { bulletinBoardFeatureKey, BulletinBoardState } from './index';
|
||||
|
||||
export const selectBulletinBoardState = createSelector(
|
||||
selectBulletinState,
|
||||
(state: BulletinsState) => state[bulletinBoardFeatureKey]
|
||||
);
|
||||
|
||||
export const selectLastBulletinId = createSelector(
|
||||
selectBulletinBoardState,
|
||||
(state: BulletinBoardState) => state.lastBulletinId
|
||||
);
|
||||
|
||||
export const selectBulletinBoardFilter = createSelector(
|
||||
selectBulletinBoardState,
|
||||
(state: BulletinBoardState) => state.filter
|
||||
);
|
@ -0,0 +1,63 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { BulletinEntity } from '../../../../state/shared';
|
||||
|
||||
export const bulletinBoardFeatureKey = 'bulletin-board';
|
||||
|
||||
export interface BulletinBoardEntity {
|
||||
bulletins: BulletinEntity[];
|
||||
generated: string;
|
||||
}
|
||||
|
||||
export interface BulletinBoardEvent {
|
||||
type: 'auto-refresh' | 'filter';
|
||||
message: string;
|
||||
}
|
||||
|
||||
export interface BulletinBoardItem {
|
||||
item: BulletinEntity | BulletinBoardEvent;
|
||||
}
|
||||
|
||||
export interface BulletinBoardState {
|
||||
bulletinBoardItems: BulletinBoardItem[];
|
||||
filter: BulletinBoardFilterArgs;
|
||||
autoRefresh: boolean;
|
||||
lastBulletinId: number;
|
||||
loadedTimestamp: string;
|
||||
error: string | null;
|
||||
status: 'pending' | 'loading' | 'error' | 'success';
|
||||
}
|
||||
|
||||
export interface LoadBulletinBoardResponse {
|
||||
bulletinBoard: BulletinBoardEntity;
|
||||
loadedTimestamp: string;
|
||||
}
|
||||
|
||||
export interface LoadBulletinBoardRequest {
|
||||
after?: number;
|
||||
limit?: number;
|
||||
groupId?: string;
|
||||
sourceId?: string;
|
||||
sourceName?: string;
|
||||
message?: string;
|
||||
}
|
||||
|
||||
export interface BulletinBoardFilterArgs {
|
||||
filterTerm: string;
|
||||
filterColumn: string;
|
||||
}
|
@ -0,0 +1,34 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { bulletinBoardFeatureKey, BulletinBoardState } from './bulletin-board';
|
||||
import { Action, combineReducers, createFeatureSelector } from '@ngrx/store';
|
||||
import { bulletinBoardReducer } from './bulletin-board/bulletin-board.reducer';
|
||||
|
||||
export const bulletinsFeatureKey = 'bulletins';
|
||||
|
||||
export interface BulletinsState {
|
||||
[bulletinBoardFeatureKey]: BulletinBoardState;
|
||||
}
|
||||
|
||||
export function reducers(state: BulletinsState | undefined, action: Action) {
|
||||
return combineReducers({
|
||||
[bulletinBoardFeatureKey]: bulletinBoardReducer
|
||||
})(state, action);
|
||||
}
|
||||
|
||||
export const selectBulletinState = createFeatureSelector<BulletinsState>(bulletinsFeatureKey);
|
@ -0,0 +1,83 @@
|
||||
<!--
|
||||
~ Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
~ contributor license agreements. See the NOTICE file distributed with
|
||||
~ this work for additional information regarding copyright ownership.
|
||||
~ The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
~ (the "License"); you may not use this file except in compliance with
|
||||
~ the License. You may obtain a copy of the License at
|
||||
~
|
||||
~ http://www.apache.org/licenses/LICENSE-2.0
|
||||
~
|
||||
~ Unless required by applicable law or agreed to in writing, software
|
||||
~ distributed under the License is distributed on an "AS IS" BASIS,
|
||||
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
~ See the License for the specific language governing permissions and
|
||||
~ limitations under the License.
|
||||
-->
|
||||
|
||||
<div class="bulletin-board-list-container h-full flex flex-col">
|
||||
<div class="bulletin-board-list-filter-container">
|
||||
<form [formGroup]="filterForm">
|
||||
<div class="flex pt-2">
|
||||
<div class="mr-2">
|
||||
<mat-form-field>
|
||||
<mat-label>Filter</mat-label>
|
||||
<input matInput type="text" class="small" formControlName="filterTerm" />
|
||||
</mat-form-field>
|
||||
</div>
|
||||
<div>
|
||||
<mat-form-field>
|
||||
<mat-label>Filter By</mat-label>
|
||||
<mat-select formControlName="filterColumn">
|
||||
<mat-option value="message"> message </mat-option>
|
||||
<mat-option value="name"> name </mat-option>
|
||||
<mat-option value="id"> id </mat-option>
|
||||
<mat-option value="groupId"> group id </mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="flex-1 relative">
|
||||
<div class="bulletin-board-list overflow-y-auto absolute inset-0 border-2 p-4" #scrollContainer>
|
||||
<ul class="flex flex-wrap gap-y-2">
|
||||
<ng-container *ngFor="let item of bulletinBoardItems">
|
||||
<!-- each item can either be a BulletinEntity or BulletinBoardEvent -->
|
||||
<ng-container *ngIf="isBulletin(item); else bulletinEvent">
|
||||
<ng-container *ngIf="asBulletin(item); let bulletin">
|
||||
<li *ngIf="bulletin.canRead">
|
||||
<div class="inline-flex flex-wrap gap-x-1.5">
|
||||
<div>{{ bulletin.timestamp }}</div>
|
||||
<div class="font-bold {{ getSeverity(bulletin.bulletin.level) }}">
|
||||
{{ bulletin.bulletin.level }}
|
||||
</div>
|
||||
<div *ngIf="getRouterLink(bulletin); let link; else: noLink">
|
||||
<a class="link" [routerLink]="link">{{ bulletin.bulletin.sourceId }}</a>
|
||||
</div>
|
||||
<ng-template #noLink>
|
||||
<div>{{ bulletin.bulletin.sourceId }}</div>
|
||||
</ng-template>
|
||||
|
||||
<div *ngIf="!!bulletin.nodeAddress">{{ bulletin.nodeAddress }}</div>
|
||||
<pre class="whitespace-pre-wrap">{{ bulletin.bulletin.message }}</pre>
|
||||
</div>
|
||||
</li>
|
||||
</ng-container>
|
||||
</ng-container>
|
||||
|
||||
<ng-template #bulletinEvent>
|
||||
<ng-container *ngIf="asBulletinEvent(item); let event">
|
||||
<li class="w-full mt-4">
|
||||
<div class="border-b-2 flex-1 p-2">
|
||||
{{ event.message }}
|
||||
</div>
|
||||
</li>
|
||||
</ng-container>
|
||||
</ng-template>
|
||||
</ng-container>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
@ -0,0 +1,32 @@
|
||||
/*!
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
.bulletin-error {
|
||||
color: #ec3030;
|
||||
}
|
||||
|
||||
.bulletin-warn {
|
||||
color: #bda500;
|
||||
}
|
||||
|
||||
.bulletin-normal {
|
||||
color: #016131;
|
||||
}
|
||||
|
||||
.link {
|
||||
font-size: 16px;
|
||||
}
|
@ -0,0 +1,39 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { BulletinBoardList } from './bulletin-board-list.component';
|
||||
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
|
||||
|
||||
describe('BulletinBoardList', () => {
|
||||
let component: BulletinBoardList;
|
||||
let fixture: ComponentFixture<BulletinBoardList>;
|
||||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [BulletinBoardList, NoopAnimationsModule]
|
||||
});
|
||||
fixture = TestBed.createComponent(BulletinBoardList);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
@ -0,0 +1,209 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { AfterViewInit, Component, ElementRef, EventEmitter, Input, Output, ViewChild } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { MatFormFieldModule } from '@angular/material/form-field';
|
||||
import { MatInputModule } from '@angular/material/input';
|
||||
import { MatOptionModule } from '@angular/material/core';
|
||||
import { MatSelectModule } from '@angular/material/select';
|
||||
import { FormBuilder, FormGroup, ReactiveFormsModule } from '@angular/forms';
|
||||
import { NiFiCommon } from '../../../../../service/nifi-common.service';
|
||||
import { BulletinBoardEvent, BulletinBoardFilterArgs, BulletinBoardItem } from '../../../state/bulletin-board';
|
||||
import { BulletinEntity, ComponentType } from '../../../../../state/shared';
|
||||
import { debounceTime, delay, Subject } from 'rxjs';
|
||||
import { RouterLink } from '@angular/router';
|
||||
|
||||
@Component({
|
||||
selector: 'bulletin-board-list',
|
||||
standalone: true,
|
||||
imports: [
|
||||
CommonModule,
|
||||
MatFormFieldModule,
|
||||
MatInputModule,
|
||||
MatOptionModule,
|
||||
MatSelectModule,
|
||||
ReactiveFormsModule,
|
||||
RouterLink
|
||||
],
|
||||
templateUrl: './bulletin-board-list.component.html',
|
||||
styleUrls: ['./bulletin-board-list.component.scss']
|
||||
})
|
||||
export class BulletinBoardList implements AfterViewInit {
|
||||
filterTerm = '';
|
||||
filterColumn: 'message' | 'name' | 'id' | 'groupId' = 'message';
|
||||
filterForm: FormGroup;
|
||||
|
||||
private bulletinsChanged$: Subject<void> = new Subject<void>();
|
||||
|
||||
private _items: BulletinBoardItem[] = [];
|
||||
|
||||
@ViewChild('scrollContainer') private scroll!: ElementRef;
|
||||
|
||||
@Input() set bulletinBoardItems(items: BulletinBoardItem[]) {
|
||||
this._items = items;
|
||||
this.bulletinsChanged$.next();
|
||||
}
|
||||
get bulletinBoardItems(): BulletinBoardItem[] {
|
||||
return this._items;
|
||||
}
|
||||
|
||||
@Output() filterChanged: EventEmitter<BulletinBoardFilterArgs> = new EventEmitter<BulletinBoardFilterArgs>();
|
||||
|
||||
constructor(
|
||||
private formBuilder: FormBuilder,
|
||||
private nifiCommon: NiFiCommon
|
||||
) {
|
||||
this.filterForm = this.formBuilder.group({ filterTerm: '', filterColumn: 'message' });
|
||||
}
|
||||
|
||||
ngAfterViewInit(): void {
|
||||
this.filterForm
|
||||
.get('filterTerm')
|
||||
?.valueChanges.pipe(debounceTime(500))
|
||||
.subscribe((filterTerm: string) => {
|
||||
const filterColumn = this.filterForm.get('filterColumn')?.value;
|
||||
this.applyFilter(filterTerm, filterColumn);
|
||||
});
|
||||
|
||||
this.filterForm.get('filterColumn')?.valueChanges.subscribe((filterColumn: string) => {
|
||||
const filterTerm = this.filterForm.get('filterTerm')?.value;
|
||||
this.applyFilter(filterTerm, filterColumn);
|
||||
});
|
||||
|
||||
// scroll the initial chuck of bulletins
|
||||
this.scrollToBottom();
|
||||
|
||||
this.bulletinsChanged$
|
||||
.pipe(delay(10)) // allow the new data a chance to render so the sizing of the scroll container is correct
|
||||
.subscribe(() => {
|
||||
// auto-scroll
|
||||
this.scrollToBottom();
|
||||
});
|
||||
}
|
||||
|
||||
private scrollToBottom() {
|
||||
if (this.scroll) {
|
||||
this.scroll.nativeElement.scroll({
|
||||
top: this.scroll.nativeElement.scrollHeight,
|
||||
left: 0,
|
||||
behavior: 'smooth'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
applyFilter(filterTerm: string, filterColumn: string) {
|
||||
this.filterChanged.next({
|
||||
filterColumn,
|
||||
filterTerm
|
||||
});
|
||||
}
|
||||
|
||||
isBulletin(bulletinBoardItem: BulletinBoardItem): boolean {
|
||||
const item = bulletinBoardItem.item;
|
||||
return !('type' in item);
|
||||
}
|
||||
|
||||
asBulletin(bulletinBoardItem: BulletinBoardItem): BulletinEntity | null {
|
||||
if (this.isBulletin(bulletinBoardItem)) {
|
||||
return bulletinBoardItem.item as BulletinEntity;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
asBulletinEvent(bulletinBoardItem: BulletinBoardItem): BulletinBoardEvent | null {
|
||||
if (!this.isBulletin(bulletinBoardItem)) {
|
||||
return bulletinBoardItem.item as BulletinBoardEvent;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
getSeverity(severity: string) {
|
||||
switch (severity.toLowerCase()) {
|
||||
case 'error':
|
||||
return 'bulletin-error';
|
||||
case 'warn':
|
||||
case 'warning':
|
||||
return 'bulletin-warn';
|
||||
default:
|
||||
return 'bulletin-normal';
|
||||
}
|
||||
}
|
||||
|
||||
getRouterLink(bulletin: BulletinEntity): string[] | null {
|
||||
const type: ComponentType | null = this.getComponentType(bulletin.bulletin.sourceType);
|
||||
if (type && bulletin.sourceId) {
|
||||
if (type === ComponentType.ControllerService) {
|
||||
if (bulletin.groupId) {
|
||||
// process group controller service
|
||||
return ['/process-groups', bulletin.groupId, 'controller-services', bulletin.sourceId];
|
||||
} else {
|
||||
// management controller service
|
||||
return ['/settings', 'management-controller-services', bulletin.sourceId];
|
||||
}
|
||||
}
|
||||
|
||||
if (type === ComponentType.ReportingTask) {
|
||||
return ['/settings', 'reporting-tasks', bulletin.sourceId];
|
||||
}
|
||||
|
||||
if (type === ComponentType.FlowRegistryClient) {
|
||||
return ['/settings', 'registry-clients', bulletin.sourceId];
|
||||
}
|
||||
|
||||
if (type === ComponentType.FlowAnalysisRule) {
|
||||
return ['/settings', 'flow-analysis-rules', bulletin.sourceId];
|
||||
}
|
||||
|
||||
if (type === ComponentType.ParameterProvider) {
|
||||
return ['/settings', 'parameter-providers', bulletin.sourceId];
|
||||
}
|
||||
|
||||
if (bulletin.groupId) {
|
||||
return ['/process-groups', bulletin.groupId, type, bulletin.sourceId];
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private getComponentType(sourceType: string): ComponentType | null {
|
||||
switch (sourceType) {
|
||||
case 'PROCESSOR':
|
||||
return ComponentType.Processor;
|
||||
case 'REMOTE_PROCESS_GROUP':
|
||||
return ComponentType.RemoteProcessGroup;
|
||||
case 'INPUT_PORT':
|
||||
return ComponentType.InputPort;
|
||||
case 'OUTPUT_PORT':
|
||||
return ComponentType.OutputPort;
|
||||
case 'FUNNEL':
|
||||
return ComponentType.Funnel;
|
||||
case 'CONTROLLER_SERVICE':
|
||||
return ComponentType.ControllerService;
|
||||
case 'REPORTING_TASK':
|
||||
return ComponentType.ReportingTask;
|
||||
case 'FLOW_ANALYSIS_RULE':
|
||||
return ComponentType.FlowAnalysisRule;
|
||||
case 'PARAMETER_PROVIDER':
|
||||
return ComponentType.ParameterProvider;
|
||||
case 'FLOW_REGISTRY_CLIENT':
|
||||
return ComponentType.FlowRegistryClient;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,49 @@
|
||||
<!--
|
||||
~ Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
~ contributor license agreements. See the NOTICE file distributed with
|
||||
~ this work for additional information regarding copyright ownership.
|
||||
~ The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
~ (the "License"); you may not use this file except in compliance with
|
||||
~ the License. You may obtain a copy of the License at
|
||||
~
|
||||
~ http://www.apache.org/licenses/LICENSE-2.0
|
||||
~
|
||||
~ Unless required by applicable law or agreed to in writing, software
|
||||
~ distributed under the License is distributed on an "AS IS" BASIS,
|
||||
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
~ See the License for the specific language governing permissions and
|
||||
~ limitations under the License.
|
||||
-->
|
||||
|
||||
<ng-container *ngIf="bulletinBoardState$ | async; let bulletinBoardState">
|
||||
<div *ngIf="isInitialLoading(bulletinBoardState); else loaded">
|
||||
<ngx-skeleton-loader count="3"></ngx-skeleton-loader>
|
||||
</div>
|
||||
|
||||
<ng-template #loaded>
|
||||
<div class="bulletin-board flex flex-col h-full gap-y-2">
|
||||
<div class="flex-1">
|
||||
<bulletin-board-list
|
||||
(filterChanged)="applyFilter($event)"
|
||||
[bulletinBoardItems]="bulletinBoardState.bulletinBoardItems"></bulletin-board-list>
|
||||
</div>
|
||||
<div class="flex justify-between">
|
||||
<div class="refresh-container flex items-center gap-x-2">
|
||||
<div class="mr-6">
|
||||
<mat-slide-toggle [checked]="autoRefresh" (change)="autoRefreshToggle($event)"
|
||||
>Auto-refresh</mat-slide-toggle
|
||||
>
|
||||
</div>
|
||||
<button class="nifi-button" (click)="refreshBulletinBoard(bulletinBoardState.lastBulletinId)">
|
||||
<i class="fa fa-refresh" [class.fa-spin]="bulletinBoardState.status === 'loading'"></i>
|
||||
</button>
|
||||
<div>Last updated:</div>
|
||||
<div class="refresh-timestamp">{{ bulletinBoardState.loadedTimestamp }}</div>
|
||||
</div>
|
||||
<div class="clear-bulletin-board-container">
|
||||
<a (click)="clear()">Clear</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</ng-template>
|
||||
</ng-container>
|
@ -0,0 +1,19 @@
|
||||
/*!
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
.bulletin-board {
|
||||
}
|
@ -0,0 +1,40 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { BulletinBoard } from './bulletin-board.component';
|
||||
import { provideMockStore } from '@ngrx/store/testing';
|
||||
import { initialBulletinBoardState } from '../../state/bulletin-board/bulletin-board.reducer';
|
||||
|
||||
describe('BulletinBoard', () => {
|
||||
let component: BulletinBoard;
|
||||
let fixture: ComponentFixture<BulletinBoard>;
|
||||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [BulletinBoard],
|
||||
providers: [provideMockStore({ initialState: initialBulletinBoardState })]
|
||||
});
|
||||
fixture = TestBed.createComponent(BulletinBoard);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
@ -0,0 +1,138 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { Component, OnDestroy, OnInit } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { Store } from '@ngrx/store';
|
||||
import { BulletinBoardFilterArgs, BulletinBoardState, LoadBulletinBoardRequest } from '../../state/bulletin-board';
|
||||
import {
|
||||
selectBulletinBoardFilter,
|
||||
selectBulletinBoardState
|
||||
} from '../../state/bulletin-board/bulletin-board.selectors';
|
||||
import {
|
||||
clearBulletinBoard,
|
||||
loadBulletinBoard,
|
||||
resetBulletinBoardState,
|
||||
setBulletinBoardAutoRefresh,
|
||||
setBulletinBoardFilter,
|
||||
startBulletinBoardPolling,
|
||||
stopBulletinBoardPolling
|
||||
} from '../../state/bulletin-board/bulletin-board.actions';
|
||||
import { initialBulletinBoardState } from '../../state/bulletin-board/bulletin-board.reducer';
|
||||
import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader';
|
||||
import { MatFormFieldModule } from '@angular/material/form-field';
|
||||
import { MatInputModule } from '@angular/material/input';
|
||||
import { MatOptionModule } from '@angular/material/core';
|
||||
import { MatSelectModule } from '@angular/material/select';
|
||||
import { ReactiveFormsModule } from '@angular/forms';
|
||||
import { BulletinBoardList } from './bulletin-board-list/bulletin-board-list.component';
|
||||
import { MatSlideToggleChange, MatSlideToggleModule } from '@angular/material/slide-toggle';
|
||||
|
||||
@Component({
|
||||
selector: 'bulletin-board',
|
||||
standalone: true,
|
||||
imports: [
|
||||
CommonModule,
|
||||
NgxSkeletonLoaderModule,
|
||||
MatFormFieldModule,
|
||||
MatInputModule,
|
||||
MatOptionModule,
|
||||
MatSelectModule,
|
||||
ReactiveFormsModule,
|
||||
BulletinBoardList,
|
||||
MatSlideToggleModule
|
||||
],
|
||||
templateUrl: './bulletin-board.component.html',
|
||||
styleUrls: ['./bulletin-board.component.scss']
|
||||
})
|
||||
export class BulletinBoard implements OnInit, OnDestroy {
|
||||
bulletinBoardState$ = this.store.select(selectBulletinBoardState);
|
||||
private bulletinBoardFilter$ = this.store.select(selectBulletinBoardFilter);
|
||||
private currentFilter: BulletinBoardFilterArgs | null = null;
|
||||
|
||||
autoRefresh = true;
|
||||
|
||||
constructor(private store: Store<BulletinBoardState>) {}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.store.dispatch(
|
||||
loadBulletinBoard({
|
||||
request: {
|
||||
limit: 10
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
this.bulletinBoardFilter$.subscribe((filter) => (this.currentFilter = filter));
|
||||
|
||||
this.store.dispatch(startBulletinBoardPolling());
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
this.store.dispatch(resetBulletinBoardState());
|
||||
this.store.dispatch(stopBulletinBoardPolling());
|
||||
}
|
||||
|
||||
isInitialLoading(state: BulletinBoardState): boolean {
|
||||
return state.loadedTimestamp == initialBulletinBoardState.loadedTimestamp;
|
||||
}
|
||||
|
||||
refreshBulletinBoard(lastBulletinId: number) {
|
||||
const request: LoadBulletinBoardRequest = {
|
||||
after: lastBulletinId
|
||||
};
|
||||
if (this.currentFilter) {
|
||||
if (this.currentFilter.filterTerm?.length > 0) {
|
||||
const filterTerm = this.currentFilter.filterTerm;
|
||||
switch (this.currentFilter.filterColumn) {
|
||||
case 'message':
|
||||
request.message = filterTerm;
|
||||
break;
|
||||
case 'id':
|
||||
request.sourceId = filterTerm;
|
||||
break;
|
||||
case 'groupId':
|
||||
request.groupId = filterTerm;
|
||||
break;
|
||||
case 'name':
|
||||
request.sourceName = filterTerm;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
this.store.dispatch(loadBulletinBoard({ request }));
|
||||
}
|
||||
|
||||
clear() {
|
||||
this.store.dispatch(clearBulletinBoard());
|
||||
}
|
||||
|
||||
applyFilter(filter: BulletinBoardFilterArgs) {
|
||||
// only fire the filter changed event if there is something different
|
||||
if (
|
||||
filter.filterTerm.length > 0 ||
|
||||
(filter.filterTerm.length === 0 && filter.filterColumn === this.currentFilter?.filterColumn)
|
||||
) {
|
||||
this.store.dispatch(setBulletinBoardFilter({ filter }));
|
||||
}
|
||||
}
|
||||
|
||||
autoRefreshToggle(event: MatSlideToggleChange) {
|
||||
this.autoRefresh = event.checked;
|
||||
this.store.dispatch(setBulletinBoardAutoRefresh({ autoRefresh: this.autoRefresh }));
|
||||
}
|
||||
}
|
@ -76,7 +76,7 @@
|
||||
<i class="icon fa-fw icon-counter mr-2"></i>
|
||||
Counter
|
||||
</button>
|
||||
<button mat-menu-item class="global-menu-item">
|
||||
<button mat-menu-item class="global-menu-item" [routerLink]="['/bulletins']">
|
||||
<i class="fa fa-fw fa-sticky-note-o mr-2"></i>
|
||||
Bulletin Board
|
||||
</button>
|
||||
|
@ -269,6 +269,7 @@ export interface BulletinEntity {
|
||||
sourceName: string;
|
||||
timestamp: string;
|
||||
nodeAddress?: string;
|
||||
sourceType: string;
|
||||
};
|
||||
}
|
||||
|
||||
@ -367,7 +368,12 @@ export enum ComponentType {
|
||||
OutputPort = 'OutputPort',
|
||||
Label = 'Label',
|
||||
Funnel = 'Funnel',
|
||||
Connection = 'Connection'
|
||||
Connection = 'Connection',
|
||||
ControllerService = 'ControllerService',
|
||||
ReportingTask = 'ReportingTask',
|
||||
FlowAnalysisRule = 'FlowAnalysisRule',
|
||||
ParameterProvider = 'ParameterProvider',
|
||||
FlowRegistryClient = 'FlowRegistryClient'
|
||||
}
|
||||
|
||||
export interface ControllerServiceReferencingComponent {
|
||||
|
@ -21,7 +21,7 @@ import { DisableControllerService } from './disable-controller-service.component
|
||||
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
|
||||
import { provideMockStore } from '@ngrx/store/testing';
|
||||
import { initialState } from '../../../../state/contoller-service-state/controller-service-state.reducer';
|
||||
import { SetEnableControllerServiceDialogRequest } from '../../../../state/shared';
|
||||
import { ComponentType, SetEnableControllerServiceDialogRequest } from '../../../../state/shared';
|
||||
import { MAT_DIALOG_DATA } from '@angular/material/dialog';
|
||||
|
||||
describe('EnableControllerService', () => {
|
||||
@ -54,6 +54,7 @@ describe('EnableControllerService', () => {
|
||||
groupId: 'asdf',
|
||||
sourceId: 'asdf',
|
||||
sourceName: 'asdf',
|
||||
sourceType: ComponentType.Processor,
|
||||
level: 'ERROR',
|
||||
message: 'asdf',
|
||||
timestamp: '14:08:44 EST'
|
||||
|
@ -22,7 +22,7 @@ import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
|
||||
import { provideMockStore } from '@ngrx/store/testing';
|
||||
import { initialState } from '../../../../state/contoller-service-state/controller-service-state.reducer';
|
||||
import { MAT_DIALOG_DATA, MatDialogModule } from '@angular/material/dialog';
|
||||
import { SetEnableControllerServiceDialogRequest } from '../../../../state/shared';
|
||||
import { ComponentType, SetEnableControllerServiceDialogRequest } from '../../../../state/shared';
|
||||
|
||||
describe('EnableControllerService', () => {
|
||||
let component: EnableControllerService;
|
||||
@ -54,6 +54,7 @@ describe('EnableControllerService', () => {
|
||||
groupId: 'asdf',
|
||||
sourceId: 'asdf',
|
||||
sourceName: 'asdf',
|
||||
sourceType: ComponentType.Processor,
|
||||
level: 'ERROR',
|
||||
message: 'asdf',
|
||||
timestamp: '14:08:44 EST'
|
||||
|
@ -15,7 +15,7 @@
|
||||
~ limitations under the License.
|
||||
-->
|
||||
|
||||
<div class="tooltip" [style.left.px]="left" [style.top.px]="top">
|
||||
<div class="tooltip overflow-auto" [style.left.px]="left" [style.top.px]="top">
|
||||
<ul class="flex flex-wrap gap-y-1">
|
||||
<ng-container *ngFor="let bulletinEntity of data?.bulletins">
|
||||
<li *ngIf="bulletinEntity.canRead">
|
||||
|
Loading…
x
Reference in New Issue
Block a user