mirror of https://github.com/apache/nifi.git
[NIFI-12778] manage remote ports (#8433)
* [NIFI-12778] manage remote ports * update last refreshed timestamp and loadedTimestamp * address review feedback * final touches * address addition review comments * formatDuration check isDurationBlank This closes #8433
This commit is contained in:
parent
455159f6ac
commit
f9c1c3f042
|
@ -39,6 +39,19 @@ const routes: Routes = [
|
|||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: 'remote-process-group/:rpgId',
|
||||
component: FlowDesigner,
|
||||
children: [
|
||||
{
|
||||
path: 'manage-remote-ports',
|
||||
loadChildren: () =>
|
||||
import('../ui/manage-remote-ports/manage-remote-ports.module').then(
|
||||
(m) => m.ManageRemotePortsModule
|
||||
)
|
||||
}
|
||||
]
|
||||
},
|
||||
{ path: '', component: RootGroupRedirector, canActivate: [rootGroupGuard] }
|
||||
];
|
||||
|
||||
|
|
|
@ -32,6 +32,7 @@ import {
|
|||
navigateToEditComponent,
|
||||
navigateToEditCurrentProcessGroup,
|
||||
navigateToManageComponentPolicies,
|
||||
navigateToManageRemotePorts,
|
||||
navigateToProvenanceForComponent,
|
||||
navigateToQueueListing,
|
||||
navigateToViewStatusHistoryForComponent,
|
||||
|
@ -771,12 +772,20 @@ export class CanvasContextMenu implements ContextMenuDefinitionProvider {
|
|||
},
|
||||
{
|
||||
condition: (selection: any) => {
|
||||
return this.canvasUtils.isRemoteProcessGroup(selection);
|
||||
return this.canvasUtils.canRead(selection) && this.canvasUtils.isRemoteProcessGroup(selection);
|
||||
},
|
||||
clazz: 'fa fa-cloud',
|
||||
text: 'Manage remote ports',
|
||||
action: () => {
|
||||
// TODO - remotePorts
|
||||
action: (selection: any) => {
|
||||
const selectionData = selection.datum();
|
||||
|
||||
this.store.dispatch(
|
||||
navigateToManageRemotePorts({
|
||||
request: {
|
||||
id: selectionData.id
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
},
|
||||
{
|
||||
|
|
|
@ -0,0 +1,72 @@
|
|||
/*
|
||||
* 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 { Observable } from 'rxjs';
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
import { NiFiCommon } from '../../../service/nifi-common.service';
|
||||
import { ConfigureRemotePortRequest, ToggleRemotePortTransmissionRequest } from '../state/manage-remote-ports';
|
||||
import { Client } from '../../../service/client.service';
|
||||
import { ComponentType } from '../../../state/shared';
|
||||
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class ManageRemotePortService {
|
||||
private static readonly API: string = '../nifi-api';
|
||||
|
||||
constructor(
|
||||
private httpClient: HttpClient,
|
||||
private client: Client,
|
||||
private nifiCommon: NiFiCommon
|
||||
) {}
|
||||
|
||||
getRemotePorts(rpgId: string): Observable<any> {
|
||||
return this.httpClient.get(`${ManageRemotePortService.API}/remote-process-groups/${rpgId}`);
|
||||
}
|
||||
|
||||
updateRemotePort(configureRemotePortRequest: ConfigureRemotePortRequest): Observable<any> {
|
||||
const type =
|
||||
configureRemotePortRequest.payload.type === ComponentType.InputPort ? 'input-ports' : 'output-ports';
|
||||
return this.httpClient.put(
|
||||
`${this.nifiCommon.stripProtocol(configureRemotePortRequest.uri)}/${type}/${
|
||||
configureRemotePortRequest.payload.remoteProcessGroupPort.id
|
||||
}`,
|
||||
{
|
||||
revision: configureRemotePortRequest.payload.revision,
|
||||
remoteProcessGroupPort: configureRemotePortRequest.payload.remoteProcessGroupPort,
|
||||
disconnectedNodeAcknowledged: configureRemotePortRequest.payload.disconnectedNodeAcknowledged
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
updateRemotePortTransmission(
|
||||
toggleRemotePortTransmissionRequest: ToggleRemotePortTransmissionRequest
|
||||
): Observable<any> {
|
||||
const payload: any = {
|
||||
revision: this.client.getRevision(toggleRemotePortTransmissionRequest.rpg),
|
||||
disconnectedNodeAcknowledged: toggleRemotePortTransmissionRequest.disconnectedNodeAcknowledged,
|
||||
state: toggleRemotePortTransmissionRequest.state
|
||||
};
|
||||
|
||||
const type =
|
||||
toggleRemotePortTransmissionRequest.type === ComponentType.InputPort ? 'input-ports' : 'output-ports';
|
||||
|
||||
return this.httpClient.put(
|
||||
`${ManageRemotePortService.API}/remote-process-groups/${toggleRemotePortTransmissionRequest.rpg.id}/${type}/${toggleRemotePortTransmissionRequest.portId}/run-status`,
|
||||
payload
|
||||
);
|
||||
}
|
||||
}
|
|
@ -76,7 +76,8 @@ import {
|
|||
ImportFromRegistryDialogRequest,
|
||||
ImportFromRegistryRequest,
|
||||
GoToRemoteProcessGroupRequest,
|
||||
RefreshRemoteProcessGroupRequest
|
||||
RefreshRemoteProcessGroupRequest,
|
||||
RpgManageRemotePortsRequest
|
||||
} from './index';
|
||||
import { StatusHistoryRequest } from '../../../../state/status-history';
|
||||
|
||||
|
@ -400,6 +401,11 @@ export const openEditRemoteProcessGroupDialog = createAction(
|
|||
props<{ request: EditComponentDialogRequest }>()
|
||||
);
|
||||
|
||||
export const navigateToManageRemotePorts = createAction(
|
||||
`${CANVAS_PREFIX} Open Remote Process Group Manage Remote Ports`,
|
||||
props<{ request: RpgManageRemotePortsRequest }>()
|
||||
);
|
||||
|
||||
export const updateComponent = createAction(
|
||||
`${CANVAS_PREFIX} Update Component`,
|
||||
props<{ request: UpdateComponentRequest }>()
|
||||
|
|
|
@ -1319,6 +1319,18 @@ export class FlowEffects {
|
|||
{ dispatch: false }
|
||||
);
|
||||
|
||||
navigateToManageRemotePorts$ = createEffect(
|
||||
() =>
|
||||
this.actions$.pipe(
|
||||
ofType(FlowActions.navigateToManageRemotePorts),
|
||||
map((action) => action.request),
|
||||
tap((request) => {
|
||||
this.router.navigate(['/remote-process-group', request.id, 'manage-remote-ports']);
|
||||
})
|
||||
),
|
||||
{ dispatch: false }
|
||||
);
|
||||
|
||||
openEditRemoteProcessGroupDialog$ = createEffect(
|
||||
() =>
|
||||
this.actions$.pipe(
|
||||
|
|
|
@ -270,6 +270,14 @@ export interface EditComponentDialogRequest {
|
|||
entity: any;
|
||||
}
|
||||
|
||||
export interface EditRemotePortDialogRequest extends EditComponentDialogRequest {
|
||||
rpg?: any;
|
||||
}
|
||||
|
||||
export interface RpgManageRemotePortsRequest {
|
||||
id: string;
|
||||
}
|
||||
|
||||
export interface NavigateToControllerServicesRequest {
|
||||
id: string;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,100 @@
|
|||
/*
|
||||
* 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 { ComponentType } from '../../../../state/shared';
|
||||
|
||||
export const remotePortsFeatureKey = 'remotePortListing';
|
||||
|
||||
export interface PortSummary {
|
||||
batchSettings: {
|
||||
count?: number;
|
||||
size?: string;
|
||||
duration?: string;
|
||||
};
|
||||
comments: string;
|
||||
concurrentlySchedulableTaskCount: number;
|
||||
connected: boolean;
|
||||
exists: boolean;
|
||||
groupId: string;
|
||||
id: string;
|
||||
name: string;
|
||||
targetId: string;
|
||||
targetRunning: boolean;
|
||||
transmitting: boolean;
|
||||
useCompression: boolean;
|
||||
versionedComponentId: string;
|
||||
type?: ComponentType.InputPort | ComponentType.OutputPort;
|
||||
}
|
||||
|
||||
export interface EditRemotePortDialogRequest {
|
||||
id: string;
|
||||
port: PortSummary;
|
||||
rpg: any;
|
||||
}
|
||||
|
||||
export interface ToggleRemotePortTransmissionRequest {
|
||||
rpg: any;
|
||||
portId: string;
|
||||
disconnectedNodeAcknowledged: boolean;
|
||||
state: string;
|
||||
type: ComponentType.InputPort | ComponentType.OutputPort | undefined;
|
||||
}
|
||||
|
||||
export interface StartRemotePortTransmissionRequest {
|
||||
rpg: any;
|
||||
port: PortSummary;
|
||||
}
|
||||
|
||||
export interface StopRemotePortTransmissionRequest {
|
||||
rpg: any;
|
||||
port: PortSummary;
|
||||
}
|
||||
|
||||
export interface LoadRemotePortsRequest {
|
||||
rpgId: string;
|
||||
}
|
||||
|
||||
export interface LoadRemotePortsResponse {
|
||||
ports: PortSummary[];
|
||||
rpg: any;
|
||||
loadedTimestamp: string;
|
||||
}
|
||||
|
||||
export interface ConfigureRemotePortRequest {
|
||||
id: string;
|
||||
uri: string;
|
||||
payload: any;
|
||||
postUpdateNavigation?: string[];
|
||||
}
|
||||
|
||||
export interface ConfigureRemotePortSuccess {
|
||||
id: string;
|
||||
port: any;
|
||||
}
|
||||
|
||||
export interface SelectRemotePortRequest {
|
||||
rpgId: string;
|
||||
id: string;
|
||||
}
|
||||
|
||||
export interface RemotePortsState {
|
||||
ports: PortSummary[];
|
||||
saving: boolean;
|
||||
rpg: any;
|
||||
loadedTimestamp: string;
|
||||
status: 'pending' | 'loading' | 'success';
|
||||
}
|
|
@ -0,0 +1,77 @@
|
|||
/*
|
||||
* 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 {
|
||||
ConfigureRemotePortRequest,
|
||||
ConfigureRemotePortSuccess,
|
||||
EditRemotePortDialogRequest,
|
||||
LoadRemotePortsRequest,
|
||||
LoadRemotePortsResponse,
|
||||
SelectRemotePortRequest,
|
||||
StartRemotePortTransmissionRequest,
|
||||
StopRemotePortTransmissionRequest
|
||||
} from './index';
|
||||
|
||||
export const resetRemotePortsState = createAction('[Manage Remote Ports] Reset Remote Ports State');
|
||||
|
||||
export const loadRemotePorts = createAction(
|
||||
'[Manage Remote Ports] Load Remote Ports',
|
||||
props<{ request: LoadRemotePortsRequest }>()
|
||||
);
|
||||
|
||||
export const loadRemotePortsSuccess = createAction(
|
||||
'[Manage Remote Ports] Load Remote Ports Success',
|
||||
props<{ response: LoadRemotePortsResponse }>()
|
||||
);
|
||||
|
||||
export const remotePortsBannerApiError = createAction(
|
||||
'[Manage Remote Ports] Remote Ports Banner Api Error',
|
||||
props<{ error: string }>()
|
||||
);
|
||||
|
||||
export const navigateToEditPort = createAction('[Manage Remote Ports] Navigate To Edit Port', props<{ id: string }>());
|
||||
|
||||
export const openConfigureRemotePortDialog = createAction(
|
||||
'[Manage Remote Ports] Open Configure Port Dialog',
|
||||
props<{ request: EditRemotePortDialogRequest }>()
|
||||
);
|
||||
|
||||
export const configureRemotePort = createAction(
|
||||
'[Manage Remote Ports] Configure Port',
|
||||
props<{ request: ConfigureRemotePortRequest }>()
|
||||
);
|
||||
|
||||
export const configureRemotePortSuccess = createAction(
|
||||
'[Manage Remote Ports] Configure Port Success',
|
||||
props<{ response: ConfigureRemotePortSuccess }>()
|
||||
);
|
||||
|
||||
export const startRemotePortTransmission = createAction(
|
||||
'[Manage Remote Ports] Start Port Transmission',
|
||||
props<{ request: StartRemotePortTransmissionRequest }>()
|
||||
);
|
||||
|
||||
export const stopRemotePortTransmission = createAction(
|
||||
'[Manage Remote Ports] Stop Port Transmission',
|
||||
props<{ request: StopRemotePortTransmissionRequest }>()
|
||||
);
|
||||
|
||||
export const selectRemotePort = createAction(
|
||||
'[Manage Remote Ports] Select Port Summary',
|
||||
props<{ request: SelectRemotePortRequest }>()
|
||||
);
|
|
@ -0,0 +1,277 @@
|
|||
/*
|
||||
* 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, concatLatestFrom, createEffect, ofType } from '@ngrx/effects';
|
||||
import * as ManageRemotePortsActions from './manage-remote-ports.actions';
|
||||
import { catchError, from, map, of, switchMap, tap } from 'rxjs';
|
||||
import { MatDialog } from '@angular/material/dialog';
|
||||
import { Store } from '@ngrx/store';
|
||||
import { NiFiState } from '../../../../state';
|
||||
import { Router } from '@angular/router';
|
||||
import { selectRpg, selectRpgIdFromRoute, selectStatus } from './manage-remote-ports.selectors';
|
||||
import * as ErrorActions from '../../../../state/error/error.actions';
|
||||
import { ErrorHelper } from '../../../../service/error-helper.service';
|
||||
import { HttpErrorResponse } from '@angular/common/http';
|
||||
import { ManageRemotePortService } from '../../service/manage-remote-port.service';
|
||||
import { PortSummary } from './index';
|
||||
import { EditRemotePortComponent } from '../../ui/manage-remote-ports/edit-remote-port/edit-remote-port.component';
|
||||
import { EditRemotePortDialogRequest } from '../flow';
|
||||
import { ComponentType, isDefinedAndNotNull } from '../../../../state/shared';
|
||||
import { selectTimeOffset } from '../../../../state/flow-configuration/flow-configuration.selectors';
|
||||
import { selectAbout } from '../../../../state/about/about.selectors';
|
||||
|
||||
@Injectable()
|
||||
export class ManageRemotePortsEffects {
|
||||
constructor(
|
||||
private actions$: Actions,
|
||||
private store: Store<NiFiState>,
|
||||
private manageRemotePortService: ManageRemotePortService,
|
||||
private errorHelper: ErrorHelper,
|
||||
private dialog: MatDialog,
|
||||
private router: Router
|
||||
) {}
|
||||
|
||||
loadRemotePorts$ = createEffect(() =>
|
||||
this.actions$.pipe(
|
||||
ofType(ManageRemotePortsActions.loadRemotePorts),
|
||||
map((action) => action.request),
|
||||
concatLatestFrom(() => [
|
||||
this.store.select(selectStatus),
|
||||
this.store.select(selectTimeOffset).pipe(isDefinedAndNotNull()),
|
||||
this.store.select(selectAbout).pipe(isDefinedAndNotNull())
|
||||
]),
|
||||
switchMap(([request, status, timeOffset, about]) => {
|
||||
return this.manageRemotePortService.getRemotePorts(request.rpgId).pipe(
|
||||
map((response) => {
|
||||
// get the current user time to properly convert the server time
|
||||
const now: Date = new Date();
|
||||
|
||||
// convert the user offset to millis
|
||||
const userTimeOffset: number = now.getTimezoneOffset() * 60 * 1000;
|
||||
|
||||
// create the proper date by adjusting by the offsets
|
||||
const date: Date = new Date(Date.now() + userTimeOffset + timeOffset);
|
||||
|
||||
const ports: PortSummary[] = [];
|
||||
|
||||
response.component.contents.inputPorts.forEach((inputPort: PortSummary) => {
|
||||
const port = {
|
||||
...inputPort,
|
||||
type: ComponentType.InputPort
|
||||
} as PortSummary;
|
||||
|
||||
ports.push(port);
|
||||
});
|
||||
|
||||
response.component.contents.outputPorts.forEach((outputPort: PortSummary) => {
|
||||
const port = {
|
||||
...outputPort,
|
||||
type: ComponentType.OutputPort
|
||||
} as PortSummary;
|
||||
|
||||
ports.push(port);
|
||||
});
|
||||
|
||||
return ManageRemotePortsActions.loadRemotePortsSuccess({
|
||||
response: {
|
||||
ports,
|
||||
rpg: response,
|
||||
loadedTimestamp: `${date.getHours()}:${date.getMinutes()}:${date.getSeconds()} ${
|
||||
about.timezone
|
||||
}`
|
||||
}
|
||||
});
|
||||
}),
|
||||
catchError((errorResponse: HttpErrorResponse) =>
|
||||
of(this.errorHelper.handleLoadingError(status, errorResponse))
|
||||
)
|
||||
);
|
||||
})
|
||||
)
|
||||
);
|
||||
|
||||
navigateToEditPort$ = createEffect(
|
||||
() =>
|
||||
this.actions$.pipe(
|
||||
ofType(ManageRemotePortsActions.navigateToEditPort),
|
||||
map((action) => action.id),
|
||||
concatLatestFrom(() => this.store.select(selectRpgIdFromRoute)),
|
||||
tap(([id, rpgId]) => {
|
||||
this.router.navigate(['/remote-process-group', rpgId, 'manage-remote-ports', id, 'edit']);
|
||||
})
|
||||
),
|
||||
{ dispatch: false }
|
||||
);
|
||||
|
||||
remotePortsBannerApiError$ = createEffect(() =>
|
||||
this.actions$.pipe(
|
||||
ofType(ManageRemotePortsActions.remotePortsBannerApiError),
|
||||
map((action) => action.error),
|
||||
switchMap((error) => of(ErrorActions.addBannerError({ error })))
|
||||
)
|
||||
);
|
||||
|
||||
startRemotePortTransmission$ = createEffect(() =>
|
||||
this.actions$.pipe(
|
||||
ofType(ManageRemotePortsActions.startRemotePortTransmission),
|
||||
map((action) => action.request),
|
||||
switchMap((request) => {
|
||||
return this.manageRemotePortService
|
||||
.updateRemotePortTransmission({
|
||||
portId: request.port.id,
|
||||
rpg: request.rpg,
|
||||
disconnectedNodeAcknowledged: false,
|
||||
type: request.port.type,
|
||||
state: 'TRANSMITTING'
|
||||
})
|
||||
.pipe(
|
||||
map((response) => {
|
||||
return ManageRemotePortsActions.loadRemotePorts({
|
||||
request: {
|
||||
rpgId: response.remoteProcessGroupPort.groupId
|
||||
}
|
||||
});
|
||||
}),
|
||||
catchError((errorResponse: HttpErrorResponse) =>
|
||||
of(ErrorActions.snackBarError({ error: errorResponse.error }))
|
||||
)
|
||||
);
|
||||
})
|
||||
)
|
||||
);
|
||||
|
||||
stopRemotePortTransmission$ = createEffect(() =>
|
||||
this.actions$.pipe(
|
||||
ofType(ManageRemotePortsActions.stopRemotePortTransmission),
|
||||
map((action) => action.request),
|
||||
switchMap((request) => {
|
||||
return this.manageRemotePortService
|
||||
.updateRemotePortTransmission({
|
||||
portId: request.port.id,
|
||||
rpg: request.rpg,
|
||||
disconnectedNodeAcknowledged: false,
|
||||
type: request.port.type,
|
||||
state: 'STOPPED'
|
||||
})
|
||||
.pipe(
|
||||
map((response) => {
|
||||
return ManageRemotePortsActions.loadRemotePorts({
|
||||
request: {
|
||||
rpgId: response.remoteProcessGroupPort.groupId
|
||||
}
|
||||
});
|
||||
}),
|
||||
catchError((errorResponse: HttpErrorResponse) =>
|
||||
of(ErrorActions.snackBarError({ error: errorResponse.error }))
|
||||
)
|
||||
);
|
||||
})
|
||||
)
|
||||
);
|
||||
|
||||
selectRemotePort$ = createEffect(
|
||||
() =>
|
||||
this.actions$.pipe(
|
||||
ofType(ManageRemotePortsActions.selectRemotePort),
|
||||
map((action) => action.request),
|
||||
tap((request) => {
|
||||
this.router.navigate(['/remote-process-group', request.rpgId, 'manage-remote-ports', request.id]);
|
||||
})
|
||||
),
|
||||
{ dispatch: false }
|
||||
);
|
||||
|
||||
openConfigureRemotePortDialog$ = createEffect(
|
||||
() =>
|
||||
this.actions$.pipe(
|
||||
ofType(ManageRemotePortsActions.openConfigureRemotePortDialog),
|
||||
map((action) => action.request),
|
||||
concatLatestFrom(() => [this.store.select(selectRpg).pipe(isDefinedAndNotNull())]),
|
||||
tap(([request, rpg]) => {
|
||||
const portId: string = request.id;
|
||||
|
||||
const editDialogReference = this.dialog.open(EditRemotePortComponent, {
|
||||
data: {
|
||||
type: request.port.type,
|
||||
entity: request.port,
|
||||
rpg
|
||||
} as EditRemotePortDialogRequest,
|
||||
id: portId
|
||||
});
|
||||
|
||||
editDialogReference.afterClosed().subscribe((response) => {
|
||||
this.store.dispatch(ErrorActions.clearBannerErrors());
|
||||
if (response != 'ROUTED') {
|
||||
this.store.dispatch(
|
||||
ManageRemotePortsActions.selectRemotePort({
|
||||
request: {
|
||||
rpgId: rpg.id,
|
||||
id: portId
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
});
|
||||
})
|
||||
),
|
||||
{ dispatch: false }
|
||||
);
|
||||
|
||||
configureRemotePort$ = createEffect(() =>
|
||||
this.actions$.pipe(
|
||||
ofType(ManageRemotePortsActions.configureRemotePort),
|
||||
map((action) => action.request),
|
||||
switchMap((request) =>
|
||||
from(this.manageRemotePortService.updateRemotePort(request)).pipe(
|
||||
map((response) =>
|
||||
ManageRemotePortsActions.configureRemotePortSuccess({
|
||||
response: {
|
||||
id: request.id,
|
||||
port: response.remoteProcessGroupPort
|
||||
}
|
||||
})
|
||||
),
|
||||
catchError((errorResponse: HttpErrorResponse) => {
|
||||
if (this.errorHelper.showErrorInContext(errorResponse.status)) {
|
||||
return of(
|
||||
ManageRemotePortsActions.remotePortsBannerApiError({
|
||||
error: errorResponse.error
|
||||
})
|
||||
);
|
||||
} else {
|
||||
this.dialog.getDialogById(request.id)?.close('ROUTED');
|
||||
return of(this.errorHelper.fullScreenError(errorResponse));
|
||||
}
|
||||
})
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
configureRemotePortSuccess$ = createEffect(
|
||||
() =>
|
||||
this.actions$.pipe(
|
||||
ofType(ManageRemotePortsActions.configureRemotePortSuccess),
|
||||
map((action) => action.response),
|
||||
tap(() => {
|
||||
this.dialog.closeAll();
|
||||
})
|
||||
),
|
||||
{ dispatch: false }
|
||||
);
|
||||
}
|
|
@ -0,0 +1,75 @@
|
|||
/*
|
||||
* 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 { createReducer, on } from '@ngrx/store';
|
||||
import {
|
||||
configureRemotePort,
|
||||
configureRemotePortSuccess,
|
||||
loadRemotePorts,
|
||||
loadRemotePortsSuccess,
|
||||
remotePortsBannerApiError,
|
||||
resetRemotePortsState
|
||||
} from './manage-remote-ports.actions';
|
||||
import { produce } from 'immer';
|
||||
import { RemotePortsState } from './index';
|
||||
|
||||
export const initialState: RemotePortsState = {
|
||||
ports: [],
|
||||
saving: false,
|
||||
loadedTimestamp: '',
|
||||
rpg: null,
|
||||
status: 'pending'
|
||||
};
|
||||
|
||||
export const manageRemotePortsReducer = createReducer(
|
||||
initialState,
|
||||
on(resetRemotePortsState, () => ({
|
||||
...initialState
|
||||
})),
|
||||
on(loadRemotePorts, (state) => ({
|
||||
...state,
|
||||
status: 'loading' as const
|
||||
})),
|
||||
on(loadRemotePortsSuccess, (state, { response }) => ({
|
||||
...state,
|
||||
ports: response.ports,
|
||||
loadedTimestamp: response.loadedTimestamp,
|
||||
rpg: response.rpg,
|
||||
status: 'success' as const
|
||||
})),
|
||||
on(remotePortsBannerApiError, (state) => ({
|
||||
...state,
|
||||
saving: false
|
||||
})),
|
||||
on(configureRemotePort, (state) => ({
|
||||
...state,
|
||||
saving: true
|
||||
})),
|
||||
on(configureRemotePortSuccess, (state, { response }) => {
|
||||
return produce(state, (draftState) => {
|
||||
const componentIndex: number = draftState.ports.findIndex((f: any) => response.id === f.id);
|
||||
const port = {
|
||||
...response.port,
|
||||
type: state.ports[componentIndex].type
|
||||
};
|
||||
if (componentIndex > -1) {
|
||||
draftState.ports[componentIndex] = port;
|
||||
}
|
||||
draftState.saving = false;
|
||||
});
|
||||
})
|
||||
);
|
|
@ -0,0 +1,55 @@
|
|||
/*
|
||||
* 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 { createFeatureSelector, createSelector } from '@ngrx/store';
|
||||
import { selectCurrentRoute } from '../../../../state/router/router.selectors';
|
||||
import { remotePortsFeatureKey, RemotePortsState } from './index';
|
||||
|
||||
export const selectRemotePortsState = createFeatureSelector<RemotePortsState>(remotePortsFeatureKey);
|
||||
|
||||
export const selectSaving = createSelector(selectRemotePortsState, (state: RemotePortsState) => state.saving);
|
||||
|
||||
export const selectStatus = createSelector(selectRemotePortsState, (state: RemotePortsState) => state.status);
|
||||
|
||||
export const selectRpgIdFromRoute = createSelector(selectCurrentRoute, (route) => {
|
||||
if (route) {
|
||||
// always select the rpg id from the route
|
||||
return route.params.rpgId;
|
||||
}
|
||||
return null;
|
||||
});
|
||||
|
||||
export const selectPortIdFromRoute = createSelector(selectCurrentRoute, (route) => {
|
||||
if (route) {
|
||||
// always select the port id from the route
|
||||
return route.params.id;
|
||||
}
|
||||
return null;
|
||||
});
|
||||
|
||||
export const selectSingleEditedPort = createSelector(selectCurrentRoute, (route) => {
|
||||
if (route?.routeConfig?.path == 'edit') {
|
||||
return route.params.id;
|
||||
}
|
||||
return null;
|
||||
});
|
||||
|
||||
export const selectPorts = createSelector(selectRemotePortsState, (state: RemotePortsState) => state.ports);
|
||||
export const selectRpg = createSelector(selectRemotePortsState, (state: RemotePortsState) => state.rpg);
|
||||
|
||||
export const selectPort = (id: string) =>
|
||||
createSelector(selectPorts, (port: any[]) => port.find((port) => id == port.id));
|
|
@ -22,7 +22,6 @@ import { provideMockStore } from '@ngrx/store/testing';
|
|||
import { initialState } from '../../state/controller-services/controller-services.reducer';
|
||||
import { RouterTestingModule } from '@angular/router/testing';
|
||||
import { Component } from '@angular/core';
|
||||
import { ControllerServicesModule } from './controller-services.module';
|
||||
import { HttpClientTestingModule } from '@angular/common/http/testing';
|
||||
|
||||
describe('ControllerServices', () => {
|
||||
|
@ -39,7 +38,7 @@ describe('ControllerServices', () => {
|
|||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ControllerServices],
|
||||
imports: [RouterTestingModule, MockNavigation, ControllerServicesModule, HttpClientTestingModule],
|
||||
imports: [RouterTestingModule, MockNavigation, HttpClientTestingModule],
|
||||
providers: [
|
||||
provideMockStore({
|
||||
initialState
|
||||
|
|
|
@ -0,0 +1,45 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
@use 'sass:map';
|
||||
@use '@angular/material' as mat;
|
||||
|
||||
@mixin nifi-theme($theme, $canvas-theme) {
|
||||
// Get the color config from the theme.
|
||||
$color-config: mat.get-color-config($theme);
|
||||
$canvas-color-config: mat.get-color-config($canvas-theme);
|
||||
|
||||
// Get the color palette from the color-config.
|
||||
$primary-palette: map.get($color-config, 'primary');
|
||||
$canvas-accent-palette: map.get($canvas-color-config, 'accent');
|
||||
|
||||
// Get hues from palette
|
||||
$primary-palette-500: mat.get-color-from-palette($primary-palette, 500);
|
||||
$canvas-accent-palette-A200: mat.get-color-from-palette($canvas-accent-palette, 'A200');
|
||||
|
||||
.manage-remote-ports-header {
|
||||
color: $primary-palette-500;
|
||||
}
|
||||
|
||||
.manage-remote-ports-table {
|
||||
.listing-table {
|
||||
.fa.fa-warning {
|
||||
color: $canvas-accent-palette-A200;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,72 @@
|
|||
<!--
|
||||
~ 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.
|
||||
-->
|
||||
|
||||
<h2 mat-dialog-title>Edit Remote {{ portTypeLabel }}</h2>
|
||||
<form class="edit-remote-port-form" [formGroup]="editPortForm">
|
||||
<error-banner></error-banner>
|
||||
<mat-dialog-content>
|
||||
<div>
|
||||
<div class="flex flex-col mb-5">
|
||||
<div>Name</div>
|
||||
<div class="overflow-ellipsis overflow-hidden whitespace-nowrap value" [title]="request.entity.name">
|
||||
{{ request.entity.name }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<mat-form-field>
|
||||
<mat-label>Concurrent Tasks</mat-label>
|
||||
<input matInput formControlName="concurrentTasks" type="text" />
|
||||
</mat-form-field>
|
||||
</div>
|
||||
<div class="mb-3.5">
|
||||
<mat-label>Compressed</mat-label>
|
||||
<mat-checkbox color="primary" formControlName="compressed"></mat-checkbox>
|
||||
</div>
|
||||
<div>
|
||||
<mat-form-field>
|
||||
<mat-label>Batch Count</mat-label>
|
||||
<input matInput formControlName="count" type="text" />
|
||||
</mat-form-field>
|
||||
</div>
|
||||
<div>
|
||||
<mat-form-field>
|
||||
<mat-label>Batch Size</mat-label>
|
||||
<input matInput formControlName="size" type="text" />
|
||||
</mat-form-field>
|
||||
</div>
|
||||
<div>
|
||||
<mat-form-field>
|
||||
<mat-label>Batch Duration</mat-label>
|
||||
<input matInput formControlName="duration" type="text" />
|
||||
</mat-form-field>
|
||||
</div>
|
||||
</mat-dialog-content>
|
||||
@if ({ value: (saving$ | async)! }; as saving) {
|
||||
<mat-dialog-actions align="end">
|
||||
<button color="primary" mat-stroked-button mat-dialog-close>Cancel</button>
|
||||
<button
|
||||
[disabled]="!editPortForm.dirty || editPortForm.invalid || saving.value"
|
||||
type="button"
|
||||
color="primary"
|
||||
(click)="editRemotePort()"
|
||||
mat-raised-button>
|
||||
<span *nifiSpinner="saving.value">Apply</span>
|
||||
</button>
|
||||
</mat-dialog-actions>
|
||||
}
|
||||
</form>
|
|
@ -0,0 +1,27 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
@use '@angular/material' as mat;
|
||||
|
||||
.edit-remote-port-form {
|
||||
@include mat.button-density(-1);
|
||||
|
||||
width: 500px;
|
||||
.mat-mdc-form-field {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
|
@ -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 { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { EditRemotePortComponent } from './edit-remote-port.component';
|
||||
import { MAT_DIALOG_DATA } from '@angular/material/dialog';
|
||||
import { provideMockStore } from '@ngrx/store/testing';
|
||||
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
|
||||
import { EditComponentDialogRequest } from '../../../state/flow';
|
||||
import { ComponentType } from '../../../../../state/shared';
|
||||
import { initialState } from '../../../state/manage-remote-ports/manage-remote-ports.reducer';
|
||||
|
||||
describe('EditRemotePortComponent', () => {
|
||||
let component: EditRemotePortComponent;
|
||||
let fixture: ComponentFixture<EditRemotePortComponent>;
|
||||
|
||||
const data: EditComponentDialogRequest = {
|
||||
type: ComponentType.OutputPort,
|
||||
uri: 'https://localhost:4200/nifi-api/remote-process-groups/95a4b210-018b-1000-772a-5a9ebfa03287',
|
||||
entity: {
|
||||
id: 'a687e30e-018b-1000-f904-849a9f8e6bdb',
|
||||
groupId: '95a4b210-018b-1000-772a-5a9ebfa03287',
|
||||
name: 'out',
|
||||
transmitting: false,
|
||||
concurrentlySchedulableTaskCount: 1,
|
||||
useCompression: true,
|
||||
batchSettings: {
|
||||
count: '',
|
||||
size: '',
|
||||
duration: ''
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [EditRemotePortComponent, NoopAnimationsModule],
|
||||
providers: [{ provide: MAT_DIALOG_DATA, useValue: data }, provideMockStore({ initialState })]
|
||||
});
|
||||
fixture = TestBed.createComponent(EditRemotePortComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
|
@ -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 { Component, Inject } from '@angular/core';
|
||||
import { MAT_DIALOG_DATA, MatDialogModule } from '@angular/material/dialog';
|
||||
import { FormBuilder, FormControl, FormGroup, ReactiveFormsModule, Validators } from '@angular/forms';
|
||||
import { Store } from '@ngrx/store';
|
||||
import { MatInputModule } from '@angular/material/input';
|
||||
import { MatCheckboxModule } from '@angular/material/checkbox';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { AsyncPipe } from '@angular/common';
|
||||
import { ErrorBanner } from '../../../../../ui/common/error-banner/error-banner.component';
|
||||
import { NifiSpinnerDirective } from '../../../../../ui/common/spinner/nifi-spinner.directive';
|
||||
import { selectSaving } from '../../../state/manage-remote-ports/manage-remote-ports.selectors';
|
||||
import { EditRemotePortDialogRequest } from '../../../state/flow';
|
||||
import { Client } from '../../../../../service/client.service';
|
||||
import { ComponentType } from '../../../../../state/shared';
|
||||
import { PortSummary } from '../../../state/manage-remote-ports';
|
||||
import { configureRemotePort } from '../../../state/manage-remote-ports/manage-remote-ports.actions';
|
||||
|
||||
@Component({
|
||||
standalone: true,
|
||||
templateUrl: './edit-remote-port.component.html',
|
||||
imports: [
|
||||
ReactiveFormsModule,
|
||||
ErrorBanner,
|
||||
MatDialogModule,
|
||||
MatInputModule,
|
||||
MatCheckboxModule,
|
||||
MatButtonModule,
|
||||
AsyncPipe,
|
||||
NifiSpinnerDirective
|
||||
],
|
||||
styleUrls: ['./edit-remote-port.component.scss']
|
||||
})
|
||||
export class EditRemotePortComponent {
|
||||
saving$ = this.store.select(selectSaving);
|
||||
|
||||
editPortForm: FormGroup;
|
||||
portTypeLabel: string;
|
||||
|
||||
constructor(
|
||||
@Inject(MAT_DIALOG_DATA) public request: EditRemotePortDialogRequest,
|
||||
private formBuilder: FormBuilder,
|
||||
private store: Store<CanvasState>,
|
||||
private client: Client
|
||||
) {
|
||||
// set the port type name
|
||||
if (ComponentType.InputPort == this.request.type) {
|
||||
this.portTypeLabel = 'Input Port';
|
||||
} else {
|
||||
this.portTypeLabel = 'Output Port';
|
||||
}
|
||||
|
||||
// build the form
|
||||
this.editPortForm = this.formBuilder.group({
|
||||
concurrentTasks: new FormControl(request.entity.concurrentlySchedulableTaskCount, Validators.required),
|
||||
compressed: new FormControl(request.entity.useCompression),
|
||||
count: new FormControl(request.entity.batchSettings.count),
|
||||
size: new FormControl(request.entity.batchSettings.size),
|
||||
duration: new FormControl(request.entity.batchSettings.duration)
|
||||
});
|
||||
}
|
||||
|
||||
editRemotePort() {
|
||||
const payload: any = {
|
||||
revision: this.client.getRevision(this.request.rpg),
|
||||
disconnectedNodeAcknowledged: false,
|
||||
type: this.request.type,
|
||||
remoteProcessGroupPort: {
|
||||
concurrentlySchedulableTaskCount: this.editPortForm.get('concurrentTasks')?.value,
|
||||
useCompression: this.editPortForm.get('compressed')?.value,
|
||||
batchSettings: {
|
||||
count: this.editPortForm.get('count')?.value,
|
||||
size: this.editPortForm.get('size')?.value,
|
||||
duration: this.editPortForm.get('duration')?.value
|
||||
},
|
||||
id: this.request.entity.id,
|
||||
groupId: this.request.entity.groupId
|
||||
} as PortSummary
|
||||
};
|
||||
|
||||
this.store.dispatch(
|
||||
configureRemotePort({
|
||||
request: {
|
||||
id: this.request.entity.id,
|
||||
uri: this.request.rpg.uri,
|
||||
payload
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
|
@ -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 { NgModule } from '@angular/core';
|
||||
import { RouterModule, Routes } from '@angular/router';
|
||||
import { ManageRemotePorts } from './manage-remote-ports.component';
|
||||
|
||||
const routes: Routes = [
|
||||
{
|
||||
path: '',
|
||||
component: ManageRemotePorts,
|
||||
children: [
|
||||
{
|
||||
path: ':id',
|
||||
component: ManageRemotePorts,
|
||||
children: [{ path: 'edit', component: ManageRemotePorts }]
|
||||
}
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
imports: [RouterModule.forChild(routes)],
|
||||
exports: [RouterModule]
|
||||
})
|
||||
export class ManageRemotePortsRoutingModule {}
|
|
@ -0,0 +1,240 @@
|
|||
<!--
|
||||
~ 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="pb-5 flex flex-col h-screen justify-between gap-y-5">
|
||||
<header class="nifi-header">
|
||||
<navigation></navigation>
|
||||
</header>
|
||||
<div class="px-5 flex-1 flex flex-col">
|
||||
<h3 class="text-xl bold manage-remote-ports-header pb-5">Manage Remote Ports</h3>
|
||||
@if (portsState$ | async; as portsState) {
|
||||
<div class="grid-container grid grid-cols-2">
|
||||
<div class="col-span-1 pr-5">
|
||||
<div class="flex flex-col mb-5">
|
||||
<div>Name</div>
|
||||
<div
|
||||
class="overflow-ellipsis overflow-hidden whitespace-nowrap value"
|
||||
[title]="portsState.rpg?.id">
|
||||
{{ portsState.rpg?.id }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-span-1">
|
||||
<div class="flex flex-col mb-5">
|
||||
<div>Urls</div>
|
||||
<div
|
||||
class="overflow-ellipsis overflow-hidden whitespace-nowrap value"
|
||||
[title]="portsState.rpg?.component?.targetUri">
|
||||
{{ portsState.rpg?.component?.targetUri }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@if (isInitialLoading(portsState)) {
|
||||
<div>
|
||||
<ngx-skeleton-loader count="3"></ngx-skeleton-loader>
|
||||
</div>
|
||||
} @else {
|
||||
<div class="flex flex-col h-full gap-y-2">
|
||||
<div class="flex-1">
|
||||
<div class="manage-remote-ports-table relative h-full border">
|
||||
<div class="listing-table absolute inset-0 overflow-y-auto">
|
||||
<table
|
||||
mat-table
|
||||
[dataSource]="dataSource"
|
||||
matSort
|
||||
matSortDisableClear
|
||||
(matSortChange)="sortData($event)"
|
||||
[matSortActive]="initialSortColumn"
|
||||
[matSortDirection]="initialSortDirection">
|
||||
<!-- More Details Column -->
|
||||
<ng-container matColumnDef="moreDetails">
|
||||
<th mat-header-cell *matHeaderCellDef></th>
|
||||
<td mat-cell *matCellDef="let item">
|
||||
<div class="flex items-center gap-x-3">
|
||||
@if (hasComments(item)) {
|
||||
<div>
|
||||
<div
|
||||
class="pointer fa fa-comment"
|
||||
nifiTooltip
|
||||
[delayClose]="false"
|
||||
[tooltipComponentType]="TextTip"
|
||||
[tooltipInputData]="getCommentsTipData(item)"></div>
|
||||
</div>
|
||||
}
|
||||
@if (portExists(item)) {
|
||||
<div>
|
||||
<div
|
||||
class="pointer fa fa-warning"
|
||||
nifiTooltip
|
||||
[delayClose]="false"
|
||||
[tooltipComponentType]="TextTip"
|
||||
[tooltipInputData]="getDisconnectedTipData()"></div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<!-- Name Column -->
|
||||
<ng-container matColumnDef="name">
|
||||
<th mat-header-cell *matHeaderCellDef mat-sort-header>
|
||||
<div class="overflow-ellipsis overflow-hidden whitespace-nowrap">Name</div>
|
||||
</th>
|
||||
<td mat-cell *matCellDef="let item" [title]="formatName(item)">
|
||||
{{ formatName(item) }}
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<!-- Type Column -->
|
||||
<ng-container matColumnDef="type">
|
||||
<th mat-header-cell *matHeaderCellDef mat-sort-header>
|
||||
<div class="overflow-ellipsis overflow-hidden whitespace-nowrap">Type</div>
|
||||
</th>
|
||||
<td mat-cell *matCellDef="let item" [title]="formatType(item)">
|
||||
{{ formatType(item) }}
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<!-- Tasks Column -->
|
||||
<ng-container matColumnDef="tasks">
|
||||
<th mat-header-cell *matHeaderCellDef mat-sort-header>
|
||||
<div class="overflow-ellipsis overflow-hidden whitespace-nowrap">
|
||||
Concurrent Tasks
|
||||
</div>
|
||||
</th>
|
||||
<td mat-cell *matCellDef="let item" [title]="formatTasks(item)">
|
||||
<span [class.blank]="!item.concurrentlySchedulableTaskCount">
|
||||
{{ formatTasks(item) }}
|
||||
</span>
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<!-- Compression Column -->
|
||||
<ng-container matColumnDef="compression">
|
||||
<th mat-header-cell *matHeaderCellDef mat-sort-header>
|
||||
<div class="overflow-ellipsis overflow-hidden whitespace-nowrap">
|
||||
Compressed
|
||||
</div>
|
||||
</th>
|
||||
<td mat-cell *matCellDef="let item" [title]="formatCompression(item)">
|
||||
{{ formatCompression(item) }}
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<!-- Batch Count Column -->
|
||||
<ng-container matColumnDef="count">
|
||||
<th mat-header-cell *matHeaderCellDef mat-sort-header>
|
||||
<div class="overflow-ellipsis overflow-hidden whitespace-nowrap">
|
||||
Batch Count
|
||||
</div>
|
||||
</th>
|
||||
<td mat-cell *matCellDef="let item" [title]="formatCount(item)">
|
||||
<span [class.blank]="isCountBlank(item)">
|
||||
{{ formatCount(item) }}
|
||||
</span>
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<!-- Batch Size Column -->
|
||||
<ng-container matColumnDef="size">
|
||||
<th mat-header-cell *matHeaderCellDef mat-sort-header>
|
||||
<div class="overflow-ellipsis overflow-hidden whitespace-nowrap">
|
||||
Batch Size
|
||||
</div>
|
||||
</th>
|
||||
<td mat-cell *matCellDef="let item" [title]="formatSize(item)">
|
||||
<span [class.blank]="isSizeBlank(item)">
|
||||
{{ formatSize(item) }}
|
||||
</span>
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<!-- Batch Duration Column -->
|
||||
<ng-container matColumnDef="duration">
|
||||
<th mat-header-cell *matHeaderCellDef mat-sort-header>
|
||||
<div class="overflow-ellipsis overflow-hidden whitespace-nowrap">
|
||||
Batch Duration
|
||||
</div>
|
||||
</th>
|
||||
<td mat-cell *matCellDef="let item" [title]="formatDuration(item)">
|
||||
<span [class.blank]="isDurationBlank(item)">
|
||||
{{ formatDuration(item) }}
|
||||
</span>
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<!-- Actions Column -->
|
||||
<ng-container matColumnDef="actions">
|
||||
<th mat-header-cell *matHeaderCellDef></th>
|
||||
<td mat-cell *matCellDef="let port">
|
||||
<div class="flex items-center gap-x-3">
|
||||
@if (
|
||||
port.exists === true &&
|
||||
port.connected === true &&
|
||||
port.transmitting === false
|
||||
) {
|
||||
<div
|
||||
class="pointer fa fa-pencil"
|
||||
(click)="configureClicked(port, $event)"
|
||||
title="Edit Port"></div>
|
||||
}
|
||||
|
||||
@if (currentRpg) {
|
||||
@if (port.transmitting) {
|
||||
<div
|
||||
class="pointer transmitting fa fa-bullseye"
|
||||
(click)="toggleTransmission(port)"
|
||||
title="Transmitting: click to toggle port transmission"></div>
|
||||
} @else {
|
||||
@if (port.connected && port.exists) {
|
||||
<div
|
||||
class="pointer not-transmitting icon icon-transmit-false"
|
||||
(click)="toggleTransmission(port)"
|
||||
title="Not Transmitting: click to toggle port transmission"></div>
|
||||
}
|
||||
}
|
||||
}
|
||||
</div>
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<tr mat-header-row *matHeaderRowDef="displayedColumns; sticky: true"></tr>
|
||||
<tr
|
||||
mat-row
|
||||
*matRowDef="let row; let even = even; columns: displayedColumns"
|
||||
(click)="select(row)"
|
||||
[class.selected]="isSelected(row)"
|
||||
[class.even]="even"></tr>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex justify-between">
|
||||
<div class="refresh-container flex items-center gap-x-2">
|
||||
<button class="nifi-button" (click)="refreshManageRemotePortsListing()">
|
||||
<i class="fa fa-refresh" [class.fa-spin]="portsState.status === 'loading'"></i>
|
||||
</button>
|
||||
<div>Last updated:</div>
|
||||
<div class="refresh-timestamp">{{ portsState.loadedTimestamp }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
</div>
|
||||
</div>
|
|
@ -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.
|
||||
*/
|
||||
|
||||
@use '@angular/material' as mat;
|
||||
|
||||
.refresh-container {
|
||||
line-height: normal;
|
||||
}
|
||||
|
||||
.manage-remote-ports-table.listing-table {
|
||||
@include mat.table-density(-4);
|
||||
|
||||
table {
|
||||
.mat-column-moreDetails {
|
||||
width: 32px;
|
||||
min-width: 32px;
|
||||
}
|
||||
|
||||
.mat-column-actions {
|
||||
width: 75px;
|
||||
min-width: 75px;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,56 @@
|
|||
/*
|
||||
* 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 { ManageRemotePorts } from './manage-remote-ports.component';
|
||||
import { provideMockStore } from '@ngrx/store/testing';
|
||||
import { RouterTestingModule } from '@angular/router/testing';
|
||||
import { Component } from '@angular/core';
|
||||
import { HttpClientTestingModule } from '@angular/common/http/testing';
|
||||
import { initialState } from '../../state/manage-remote-ports/manage-remote-ports.reducer';
|
||||
|
||||
describe('ManageRemotePorts', () => {
|
||||
let component: ManageRemotePorts;
|
||||
let fixture: ComponentFixture<ManageRemotePorts>;
|
||||
|
||||
@Component({
|
||||
selector: 'navigation',
|
||||
standalone: true,
|
||||
template: ''
|
||||
})
|
||||
class MockNavigation {}
|
||||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ManageRemotePorts],
|
||||
imports: [RouterTestingModule, MockNavigation, HttpClientTestingModule],
|
||||
providers: [
|
||||
provideMockStore({
|
||||
initialState
|
||||
})
|
||||
]
|
||||
});
|
||||
fixture = TestBed.createComponent(ManageRemotePorts);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
|
@ -0,0 +1,358 @@
|
|||
/*
|
||||
* 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 { Store } from '@ngrx/store';
|
||||
import { filter, switchMap, take, tap } from 'rxjs';
|
||||
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
||||
import {
|
||||
selectRemotePortsState,
|
||||
selectPort,
|
||||
selectPortIdFromRoute,
|
||||
selectPorts,
|
||||
selectRpg,
|
||||
selectRpgIdFromRoute,
|
||||
selectSingleEditedPort
|
||||
} from '../../state/manage-remote-ports/manage-remote-ports.selectors';
|
||||
import { RemotePortsState, PortSummary } from '../../state/manage-remote-ports';
|
||||
import {
|
||||
loadRemotePorts,
|
||||
navigateToEditPort,
|
||||
openConfigureRemotePortDialog,
|
||||
resetRemotePortsState,
|
||||
selectRemotePort,
|
||||
startRemotePortTransmission,
|
||||
stopRemotePortTransmission
|
||||
} from '../../state/manage-remote-ports/manage-remote-ports.actions';
|
||||
import { initialState } from '../../state/manage-remote-ports/manage-remote-ports.reducer';
|
||||
import { isDefinedAndNotNull, TextTipInput } from '../../../../state/shared';
|
||||
import { selectCurrentUser } from '../../../../state/current-user/current-user.selectors';
|
||||
import { NiFiState } from '../../../../state';
|
||||
import { NiFiCommon } from '../../../../service/nifi-common.service';
|
||||
import { MatTableDataSource } from '@angular/material/table';
|
||||
import { Sort } from '@angular/material/sort';
|
||||
import { TextTip } from '../../../../ui/common/tooltips/text-tip/text-tip.component';
|
||||
import { concatLatestFrom } from '@ngrx/effects';
|
||||
import { loadFlowConfiguration } from '../../../../state/flow-configuration/flow-configuration.actions';
|
||||
import {
|
||||
selectFlowConfiguration,
|
||||
selectTimeOffset
|
||||
} from '../../../../state/flow-configuration/flow-configuration.selectors';
|
||||
import { selectAbout } from '../../../../state/about/about.selectors';
|
||||
import { loadAbout } from '../../../../state/about/about.actions';
|
||||
|
||||
@Component({
|
||||
templateUrl: './manage-remote-ports.component.html',
|
||||
styleUrls: ['./manage-remote-ports.component.scss']
|
||||
})
|
||||
export class ManageRemotePorts implements OnInit, OnDestroy {
|
||||
initialSortColumn: 'name' | 'type' | 'tasks' | 'count' | 'size' | 'duration' | 'compression' | 'actions' = 'name';
|
||||
initialSortDirection: 'asc' | 'desc' = 'asc';
|
||||
activeSort: Sort = {
|
||||
active: this.initialSortColumn,
|
||||
direction: this.initialSortDirection
|
||||
};
|
||||
portsState$ = this.store.select(selectRemotePortsState);
|
||||
selectedRpgId$ = this.store.select(selectRpgIdFromRoute);
|
||||
selectedPortId!: string;
|
||||
currentUser$ = this.store.select(selectCurrentUser);
|
||||
flowConfiguration$ = this.store.select(selectFlowConfiguration).pipe(isDefinedAndNotNull());
|
||||
displayedColumns: string[] = [
|
||||
'moreDetails',
|
||||
'name',
|
||||
'type',
|
||||
'tasks',
|
||||
'count',
|
||||
'size',
|
||||
'duration',
|
||||
'compression',
|
||||
'actions'
|
||||
];
|
||||
dataSource: MatTableDataSource<PortSummary> = new MatTableDataSource<PortSummary>([]);
|
||||
protected readonly TextTip = TextTip;
|
||||
|
||||
private currentRpgId!: string;
|
||||
protected currentRpg: any | null = null;
|
||||
|
||||
constructor(
|
||||
private store: Store<NiFiState>,
|
||||
private nifiCommon: NiFiCommon
|
||||
) {
|
||||
// load the ports after the flow configuration `timeOffset` and about `timezone` are loaded into the store
|
||||
this.store
|
||||
.select(selectTimeOffset)
|
||||
.pipe(
|
||||
isDefinedAndNotNull(),
|
||||
switchMap(() => this.store.select(selectAbout)),
|
||||
isDefinedAndNotNull(),
|
||||
switchMap(() => this.store.select(selectRpgIdFromRoute)),
|
||||
tap((rpgId) => (this.currentRpgId = rpgId)),
|
||||
takeUntilDestroyed()
|
||||
)
|
||||
.subscribe((rpgId) => {
|
||||
this.store.dispatch(
|
||||
loadRemotePorts({
|
||||
request: {
|
||||
rpgId
|
||||
}
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
// track selection using the port id from the route
|
||||
this.store
|
||||
.select(selectPortIdFromRoute)
|
||||
.pipe(isDefinedAndNotNull(), takeUntilDestroyed())
|
||||
.subscribe((portId) => {
|
||||
this.selectedPortId = portId;
|
||||
});
|
||||
|
||||
// data for table
|
||||
this.store
|
||||
.select(selectPorts)
|
||||
.pipe(isDefinedAndNotNull(), takeUntilDestroyed())
|
||||
.subscribe((ports) => {
|
||||
this.dataSource = new MatTableDataSource<PortSummary>(this.sortEntities(ports, this.activeSort));
|
||||
});
|
||||
|
||||
// the current RPG Entity
|
||||
this.store
|
||||
.select(selectRpg)
|
||||
.pipe(
|
||||
isDefinedAndNotNull(),
|
||||
tap((rpg) => (this.currentRpg = rpg)),
|
||||
takeUntilDestroyed()
|
||||
)
|
||||
.subscribe();
|
||||
|
||||
// handle editing remote port deep link
|
||||
this.store
|
||||
.select(selectSingleEditedPort)
|
||||
.pipe(
|
||||
isDefinedAndNotNull(),
|
||||
switchMap((id: string) =>
|
||||
this.store.select(selectPort(id)).pipe(
|
||||
filter((entity) => entity != null),
|
||||
take(1)
|
||||
)
|
||||
),
|
||||
concatLatestFrom(() => [this.store.select(selectRpg).pipe(isDefinedAndNotNull())]),
|
||||
takeUntilDestroyed()
|
||||
)
|
||||
.subscribe(([entity, rpg]) => {
|
||||
if (entity) {
|
||||
this.store.dispatch(
|
||||
openConfigureRemotePortDialog({
|
||||
request: {
|
||||
id: entity.id,
|
||||
port: entity,
|
||||
rpg
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.store.dispatch(loadFlowConfiguration());
|
||||
this.store.dispatch(loadAbout());
|
||||
}
|
||||
|
||||
isInitialLoading(state: RemotePortsState): boolean {
|
||||
// using the current timestamp to detect the initial load event
|
||||
return state.loadedTimestamp == initialState.loadedTimestamp;
|
||||
}
|
||||
|
||||
refreshManageRemotePortsListing(): void {
|
||||
this.store.dispatch(
|
||||
loadRemotePorts({
|
||||
request: {
|
||||
rpgId: this.currentRpgId
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
formatName(entity: PortSummary): string {
|
||||
return entity.name;
|
||||
}
|
||||
|
||||
formatTasks(entity: PortSummary): string {
|
||||
return entity.concurrentlySchedulableTaskCount ? `${entity.concurrentlySchedulableTaskCount}` : 'No value set';
|
||||
}
|
||||
|
||||
formatCount(entity: PortSummary): string {
|
||||
if (!this.isCountBlank(entity)) {
|
||||
return `${entity.batchSettings.count}`;
|
||||
}
|
||||
return 'No value set';
|
||||
}
|
||||
|
||||
isCountBlank(entity: PortSummary): boolean {
|
||||
return this.nifiCommon.isUndefined(entity.batchSettings.count);
|
||||
}
|
||||
|
||||
formatSize(entity: PortSummary): string {
|
||||
if (!this.isSizeBlank(entity)) {
|
||||
return `${entity.batchSettings.size}`;
|
||||
}
|
||||
return 'No value set';
|
||||
}
|
||||
|
||||
isSizeBlank(entity: PortSummary): boolean {
|
||||
return this.nifiCommon.isBlank(entity.batchSettings.size);
|
||||
}
|
||||
|
||||
formatDuration(entity: PortSummary): string {
|
||||
if (!this.isDurationBlank(entity)) {
|
||||
return `${entity.batchSettings.duration}`;
|
||||
}
|
||||
return 'No value set';
|
||||
}
|
||||
|
||||
isDurationBlank(entity: PortSummary): boolean {
|
||||
return this.nifiCommon.isBlank(entity.batchSettings.duration);
|
||||
}
|
||||
|
||||
formatCompression(entity: PortSummary): string {
|
||||
return entity.useCompression ? 'Yes' : 'No';
|
||||
}
|
||||
|
||||
formatType(entity: PortSummary): string {
|
||||
return entity.type || '';
|
||||
}
|
||||
|
||||
configureClicked(port: PortSummary, event: MouseEvent): void {
|
||||
event.stopPropagation();
|
||||
this.store.dispatch(
|
||||
navigateToEditPort({
|
||||
id: port.id
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
hasComments(entity: PortSummary): boolean {
|
||||
return !this.nifiCommon.isBlank(entity.comments);
|
||||
}
|
||||
|
||||
portExists(entity: PortSummary): boolean {
|
||||
return !entity.exists;
|
||||
}
|
||||
|
||||
getCommentsTipData(entity: PortSummary): TextTipInput {
|
||||
return {
|
||||
text: entity.comments
|
||||
};
|
||||
}
|
||||
|
||||
getDisconnectedTipData(): TextTipInput {
|
||||
return {
|
||||
text: 'This port has been removed.'
|
||||
};
|
||||
}
|
||||
|
||||
toggleTransmission(port: PortSummary): void {
|
||||
if (this.currentRpg) {
|
||||
if (port.transmitting) {
|
||||
this.store.dispatch(
|
||||
stopRemotePortTransmission({
|
||||
request: {
|
||||
rpg: this.currentRpg,
|
||||
port
|
||||
}
|
||||
})
|
||||
);
|
||||
} else {
|
||||
if (port.connected && port.exists) {
|
||||
this.store.dispatch(
|
||||
startRemotePortTransmission({
|
||||
request: {
|
||||
rpg: this.currentRpg,
|
||||
port
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
select(entity: PortSummary): void {
|
||||
this.store.dispatch(
|
||||
selectRemotePort({
|
||||
request: {
|
||||
rpgId: this.currentRpgId,
|
||||
id: entity.id
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
isSelected(entity: any): boolean {
|
||||
if (this.selectedPortId) {
|
||||
return entity.id == this.selectedPortId;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
sortData(sort: Sort) {
|
||||
this.activeSort = sort;
|
||||
this.dataSource.data = this.sortEntities(this.dataSource.data, sort);
|
||||
}
|
||||
|
||||
private sortEntities(data: PortSummary[], sort: Sort): PortSummary[] {
|
||||
if (!data) {
|
||||
return [];
|
||||
}
|
||||
return data.slice().sort((a, b) => {
|
||||
const isAsc = sort.direction === 'asc';
|
||||
let retVal = 0;
|
||||
|
||||
switch (sort.active) {
|
||||
case 'name':
|
||||
retVal = this.nifiCommon.compareString(this.formatName(a), this.formatName(b));
|
||||
break;
|
||||
case 'type':
|
||||
retVal = this.nifiCommon.compareString(this.formatType(a), this.formatType(b));
|
||||
break;
|
||||
case 'tasks':
|
||||
retVal = this.nifiCommon.compareString(this.formatTasks(a), this.formatTasks(b));
|
||||
break;
|
||||
case 'compression':
|
||||
retVal = this.nifiCommon.compareString(this.formatCompression(a), this.formatCompression(b));
|
||||
break;
|
||||
case 'count':
|
||||
retVal = this.nifiCommon.compareString(this.formatCount(a), this.formatCount(b));
|
||||
break;
|
||||
case 'size':
|
||||
retVal = this.nifiCommon.compareString(this.formatSize(a), this.formatSize(b));
|
||||
break;
|
||||
case 'duration':
|
||||
retVal = this.nifiCommon.compareString(this.formatDuration(a), this.formatDuration(b));
|
||||
break;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
return retVal * (isAsc ? 1 : -1);
|
||||
});
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
this.store.dispatch(resetRemotePortsState());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,52 @@
|
|||
/*
|
||||
* 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 { CommonModule } from '@angular/common';
|
||||
import { ManageRemotePorts } from './manage-remote-ports.component';
|
||||
import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader';
|
||||
import { ControllerServiceTable } from '../../../../ui/common/controller-service/controller-service-table/controller-service-table.component';
|
||||
import { ManageRemotePortsRoutingModule } from './manage-remote-ports-routing.module';
|
||||
import { Breadcrumbs } from '../common/breadcrumbs/breadcrumbs.component';
|
||||
import { Navigation } from '../../../../ui/common/navigation/navigation.component';
|
||||
import { MatTableModule } from '@angular/material/table';
|
||||
import { MatSortModule } from '@angular/material/sort';
|
||||
import { NifiTooltipDirective } from '../../../../ui/common/tooltips/nifi-tooltip.directive';
|
||||
import { StoreModule } from '@ngrx/store';
|
||||
import { EffectsModule } from '@ngrx/effects';
|
||||
import { ManageRemotePortsEffects } from '../../state/manage-remote-ports/manage-remote-ports.effects';
|
||||
import { remotePortsFeatureKey } from '../../state/manage-remote-ports';
|
||||
import { manageRemotePortsReducer } from '../../state/manage-remote-ports/manage-remote-ports.reducer';
|
||||
|
||||
@NgModule({
|
||||
declarations: [ManageRemotePorts],
|
||||
exports: [ManageRemotePorts],
|
||||
imports: [
|
||||
CommonModule,
|
||||
NgxSkeletonLoaderModule,
|
||||
ManageRemotePortsRoutingModule,
|
||||
StoreModule.forFeature(remotePortsFeatureKey, manageRemotePortsReducer),
|
||||
EffectsModule.forFeature(ManageRemotePortsEffects),
|
||||
ControllerServiceTable,
|
||||
Breadcrumbs,
|
||||
Navigation,
|
||||
MatTableModule,
|
||||
MatSortModule,
|
||||
NifiTooltipDirective
|
||||
]
|
||||
})
|
||||
export class ManageRemotePortsModule {}
|
|
@ -184,6 +184,33 @@ export class NiFiCommon {
|
|||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if the specified object is defined and not null.
|
||||
*
|
||||
* @argument {object} obj The object to test
|
||||
*/
|
||||
public isDefinedAndNotNull(obj: any) {
|
||||
return !this.isUndefined(obj) && !this.isNull(obj);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if the specified object is undefined.
|
||||
*
|
||||
* @argument {object} obj The object to test
|
||||
*/
|
||||
public isUndefined(obj: any) {
|
||||
return typeof obj === 'undefined';
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if the specified object is null.
|
||||
*
|
||||
* @argument {object} obj The object to test
|
||||
*/
|
||||
public isNull(obj: any) {
|
||||
return obj === null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if the specified array is empty. If the specified arg is not an
|
||||
* array, then true is returned.
|
||||
|
|
|
@ -170,7 +170,7 @@ $nifi-canvas-dark-palette: (
|
|||
500: #acacac, // g.connection rect.backpressure-object, g.connection rect.backpressure-data-size, .cdk-drag-disabled, .resizable-triangle
|
||||
600: #545454, // .canvas-background, .navigation-control, .operation-control, .lineage
|
||||
700: #696060, // .canvas-background, g.component rect.body.unauthorized, g.component rect.processor-icon-container.unauthorized, g.connection rect.body.unauthorized, #birdseye, .lineage
|
||||
800: rgba(#6b6464, 0.5), // .even, .remote-process-group-sent-stats, .processor-stats-in-out, .process-group-queued-stats, .process-group-read-write-stats
|
||||
800: rgba(#6b6464, 1), // .even, .remote-process-group-sent-stats, .processor-stats-in-out, .process-group-queued-stats, .process-group-read-write-stats
|
||||
900: rgba(#252424, 0.97), // circle.flowfile-link, .processor-read-write-stats, .process-group-stats-in-out, .tooltip, .property-editor, .disabled, .enabled, .stopped, .running, .has-errors, .invalid, .validating, .transmitting, .not-transmitting, .up-to-date, .locally-modified, .sync-failure, .stale, .locally-modified-and-stale, g.component rect.body, text.bulletin-icon, rect.processor-icon-container, circle.restricted-background, circle.is-primary-background, g.connection rect.body, text.connection-to-run-status, text.expiration-icon, text.load-balance-icon, text.penalized-icon, g.connection rect.backpressure-tick.data-size-prediction.prediction-down, g.connection rect.backpressure-tick.object-prediction.prediction-down, text.version-control, .breadcrumb-container, #birdseye, .controller-bulletins .fa, .search-container:hover, .search-container.open, .login-background, table th, .mat-sort-header-arrow, .CodeMirror, #status-history-chart-container, #status-history-chart-control-container, #status-history-chart-control-container,
|
||||
|
||||
// some analog colors for headers and hover states, inputs, stats, etc
|
||||
|
|
|
@ -171,7 +171,7 @@ $nifi-canvas-dark-palette: (
|
|||
500: #acacac, // g.connection rect.backpressure-object, g.connection rect.backpressure-data-size, .cdk-drag-disabled, .resizable-triangle
|
||||
600: #545454, // .canvas-background, .navigation-control, .operation-control, .lineage
|
||||
700: #696060, // .canvas-background, g.component rect.body.unauthorized, g.component rect.processor-icon-container.unauthorized, g.connection rect.body.unauthorized, #birdseye, .lineage
|
||||
800: rgba(#6b6464, 0.5), // .even, .remote-process-group-sent-stats, .processor-stats-in-out, .process-group-queued-stats, .process-group-read-write-stats
|
||||
800: rgba(#6b6464, 1), // .even, .remote-process-group-sent-stats, .processor-stats-in-out, .process-group-queued-stats, .process-group-read-write-stats
|
||||
900: rgba(#252424, 0.97), // circle.flowfile-link, .processor-read-write-stats, .process-group-stats-in-out, .tooltip, .property-editor, .disabled, .enabled, .stopped, .running, .has-errors, .invalid, .validating, .transmitting, .not-transmitting, .up-to-date, .locally-modified, .sync-failure, .stale, .locally-modified-and-stale, g.component rect.body, text.bulletin-icon, rect.processor-icon-container, circle.restricted-background, circle.is-primary-background, g.connection rect.body, text.connection-to-run-status, text.expiration-icon, text.load-balance-icon, text.penalized-icon, g.connection rect.backpressure-tick.data-size-prediction.prediction-down, g.connection rect.backpressure-tick.object-prediction.prediction-down, text.version-control, .breadcrumb-container, #birdseye, .controller-bulletins .fa, .search-container:hover, .search-container.open, .login-background, table th, .mat-sort-header-arrow, .CodeMirror, #status-history-chart-container, #status-history-chart-control-container, #status-history-chart-control-container,
|
||||
|
||||
// some analog colors for headers and hover states, inputs, stats, etc
|
||||
|
|
|
@ -41,6 +41,7 @@
|
|||
@use 'app/pages/flow-designer/ui/canvas/items/remote-process-group/create-remote-process-group/create-remote-process-group.component-theme' as create-remote-process-group;
|
||||
@use 'app/pages/flow-designer/ui/common/banner/banner.component-theme' as banner;
|
||||
@use 'app/pages/flow-designer/ui/controller-service/controller-services.component-theme' as controller-service;
|
||||
@use 'app/pages/flow-designer/ui/manage-remote-ports/manage-remote-ports.component-theme' as manage-remote-ports;
|
||||
@use 'app/pages/login/feature/login.component-theme' as login;
|
||||
@use 'app/pages/login/ui/login-form/login-form.component-theme' as login-form;
|
||||
@use 'app/pages/provenance/feature/provenance.component-theme' as provenance;
|
||||
|
@ -460,6 +461,10 @@ $appFontPath: '~roboto-fontface/fonts';
|
|||
cursor: pointer;
|
||||
}
|
||||
|
||||
.disabled {
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.value,
|
||||
.refresh-timestamp {
|
||||
font-weight: 500;
|
||||
|
@ -483,6 +488,7 @@ $appFontPath: '~roboto-fontface/fonts';
|
|||
@include canvas.nifi-theme($material-theme-light, $nifi-canvas-theme-light);
|
||||
@include banner.nifi-theme($material-theme-light);
|
||||
@include controller-service.nifi-theme($material-theme-light);
|
||||
@include manage-remote-ports.nifi-theme($material-theme-light, $nifi-canvas-theme-light);
|
||||
@include footer.nifi-theme($material-theme-light, $nifi-canvas-theme-light);
|
||||
@include navigation-control.nifi-theme($material-theme-light, $nifi-canvas-theme-light);
|
||||
@include birdseye-control.nifi-theme($material-theme-light, $nifi-canvas-theme-light);
|
||||
|
@ -535,6 +541,7 @@ $appFontPath: '~roboto-fontface/fonts';
|
|||
@include canvas.nifi-theme($material-theme-dark, $nifi-canvas-theme-dark);
|
||||
@include banner.nifi-theme($material-theme-dark);
|
||||
@include controller-service.nifi-theme($material-theme-dark);
|
||||
@include manage-remote-ports.nifi-theme($material-theme-dark, $nifi-canvas-theme-dark);
|
||||
@include footer.nifi-theme($material-theme-dark, $nifi-canvas-theme-dark);
|
||||
@include navigation-control.nifi-theme($material-theme-dark, $nifi-canvas-theme-dark);
|
||||
@include birdseye-control.nifi-theme($material-theme-dark, $nifi-canvas-theme-dark);
|
||||
|
|
Loading…
Reference in New Issue