NIFI-12734: Import from Registry (#8354)

* NIFI-12734:
- Import from Registry.

* NIFI-12734:
- Providing better guidance when there are no registry clients available based on user permissions.
- Showing form validation errors when there are no buckets or no flows.

This closes #8354
This commit is contained in:
Matt Gilman 2024-02-07 12:02:13 -05:00 committed by GitHub
parent e00d2b6d5e
commit 7ec2dd07c9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
28 changed files with 1153 additions and 77 deletions

View File

@ -0,0 +1,58 @@
/*
* 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 { ImportFromRegistryRequest } from '../state/flow';
@Injectable({ providedIn: 'root' })
export class RegistryService {
private static readonly API: string = '../nifi-api';
constructor(private httpClient: HttpClient) {}
getRegistryClients(): Observable<any> {
return this.httpClient.get(`${RegistryService.API}/flow/registries`);
}
getBuckets(registryId: string): Observable<any> {
return this.httpClient.get(`${RegistryService.API}/flow/registries/${registryId}/buckets`);
}
getFlows(registryId: string, bucketId: string): Observable<any> {
return this.httpClient.get(`${RegistryService.API}/flow/registries/${registryId}/buckets/${bucketId}/flows`);
}
getFlowVersions(registryId: string, bucketId: string, flowId: string): Observable<any> {
return this.httpClient.get(
`${RegistryService.API}/flow/registries/${registryId}/buckets/${bucketId}/flows/${flowId}/versions`
);
}
importFromRegistry(processGroupId: string, request: ImportFromRegistryRequest): Observable<any> {
return this.httpClient.post(
`${RegistryService.API}/process-groups/${processGroupId}/process-groups`,
request.payload,
{
params: {
parameterContextHandlingStrategy: request.keepExistingParameterContext ? 'KEEP_EXISTING' : 'REPLACE'
}
}
);
}
}

View File

@ -71,7 +71,9 @@ import {
NavigateToQueueListing,
StartProcessGroupResponse,
StopProcessGroupResponse,
CenterComponentRequest
CenterComponentRequest,
ImportFromRegistryDialogRequest,
ImportFromRegistryRequest
} from './index';
import { StatusHistoryRequest } from '../../../../state/status-history';
@ -275,6 +277,16 @@ export const openNewPortDialog = createAction(
export const createPort = createAction(`${CANVAS_PREFIX} Create Port`, props<{ request: CreatePortRequest }>());
export const openImportFromRegistryDialog = createAction(
`${CANVAS_PREFIX} Open Import From Registry Dialog`,
props<{ request: ImportFromRegistryDialogRequest }>()
);
export const importFromRegistry = createAction(
`${CANVAS_PREFIX} Import From Registry`,
props<{ request: ImportFromRegistryRequest }>()
);
export const createComponentSuccess = createAction(
`${CANVAS_PREFIX} Create Component Success`,
props<{ response: CreateComponentResponse }>()

View File

@ -40,6 +40,7 @@ import {
CreateProcessGroupDialogRequest,
DeleteComponentResponse,
GroupComponentsDialogRequest,
ImportFromRegistryDialogRequest,
LoadProcessGroupRequest,
LoadProcessGroupResponse,
Snippet,
@ -63,7 +64,13 @@ import { ConnectionManager } from '../../service/manager/connection-manager.serv
import { MatDialog } from '@angular/material/dialog';
import { CreatePort } from '../../ui/canvas/items/port/create-port/create-port.component';
import { EditPort } from '../../ui/canvas/items/port/edit-port/edit-port.component';
import { ComponentType } from '../../../../state/shared';
import {
BucketEntity,
ComponentType,
RegistryClientEntity,
VersionedFlowEntity,
VersionedFlowSnapshotMetadataEntity
} from '../../../../state/shared';
import { Router } from '@angular/router';
import { Client } from '../../../../service/client.service';
import { CanvasUtils } from '../../service/canvas-utils.service';
@ -83,6 +90,10 @@ import { ControllerServiceService } from '../../service/controller-service.servi
import { YesNoDialog } from '../../../../ui/common/yes-no-dialog/yes-no-dialog.component';
import { PropertyTableHelperService } from '../../../../service/property-table-helper.service';
import { ParameterHelperService } from '../../service/parameter-helper.service';
import { RegistryService } from '../../service/registry.service';
import { ImportFromRegistry } from '../../ui/canvas/items/flow/import-from-registry/import-from-registry.component';
import { selectCurrentUser } from '../../../../state/current-user/current-user.selectors';
import { NoRegistryClientsDialog } from '../../ui/common/no-registry-clients-dialog/no-registry-clients-dialog.component';
@Injectable()
export class FlowEffects {
@ -91,6 +102,7 @@ export class FlowEffects {
private store: Store<NiFiState>,
private flowService: FlowService,
private controllerServiceService: ControllerServiceService,
private registryService: RegistryService,
private client: Client,
private canvasUtils: CanvasUtils,
private canvasView: CanvasView,
@ -217,6 +229,18 @@ export class FlowEffects {
case ComponentType.InputPort:
case ComponentType.OutputPort:
return of(FlowActions.openNewPortDialog({ request }));
case ComponentType.Flow:
return from(this.registryService.getRegistryClients()).pipe(
map((response) => {
const dialogRequest: ImportFromRegistryDialogRequest = {
request,
registryClients: response.registries
};
return FlowActions.openImportFromRegistryDialog({ request: dialogRequest });
}),
catchError((error) => of(FlowActions.flowApiError({ error: error.error })))
);
default:
return of(FlowActions.flowApiError({ error: 'Unsupported type of Component.' }));
}
@ -587,6 +611,95 @@ export class FlowEffects {
)
);
openImportFromRegistryDialog$ = createEffect(
() =>
this.actions$.pipe(
ofType(FlowActions.openImportFromRegistryDialog),
map((action) => action.request),
concatLatestFrom(() => this.store.select(selectCurrentUser)),
tap(([request, currentUser]) => {
const someRegistries = request.registryClients.some(
(registryClient: RegistryClientEntity) => registryClient.permissions.canRead
);
if (someRegistries) {
const dialogReference = this.dialog.open(ImportFromRegistry, {
data: request,
panelClass: 'medium-dialog'
});
dialogReference.componentInstance.getBuckets = (
registryId: string
): Observable<BucketEntity[]> => {
return this.registryService.getBuckets(registryId).pipe(
take(1),
map((response) => response.buckets)
);
};
dialogReference.componentInstance.getFlows = (
registryId: string,
bucketId: string
): Observable<VersionedFlowEntity[]> => {
return this.registryService.getFlows(registryId, bucketId).pipe(
take(1),
map((response) => response.versionedFlows)
);
};
dialogReference.componentInstance.getFlowVersions = (
registryId: string,
bucketId: string,
flowId: string
): Observable<VersionedFlowSnapshotMetadataEntity[]> => {
return this.registryService.getFlowVersions(registryId, bucketId, flowId).pipe(
take(1),
map((response) => response.versionedFlowSnapshotMetadataSet)
);
};
dialogReference.afterClosed().subscribe(() => {
this.store.dispatch(FlowActions.setDragging({ dragging: false }));
});
} else {
this.dialog
.open(NoRegistryClientsDialog, {
data: {
controllerPermissions: currentUser.controllerPermissions
},
panelClass: 'medium-dialog'
})
.afterClosed()
.subscribe(() => {
this.store.dispatch(FlowActions.setDragging({ dragging: false }));
});
}
})
),
{ dispatch: false }
);
importFromRegistry$ = createEffect(() =>
this.actions$.pipe(
ofType(FlowActions.importFromRegistry),
map((action) => action.request),
concatLatestFrom(() => this.store.select(selectCurrentProcessGroupId)),
switchMap(([request, processGroupId]) =>
from(this.registryService.importFromRegistry(processGroupId, request)).pipe(
map((response) =>
FlowActions.createComponentSuccess({
response: {
type: ComponentType.ProcessGroup,
payload: response
}
})
),
catchError((error) => of(FlowActions.flowApiError({ error: error.error })))
)
)
)
);
createComponentSuccess$ = createEffect(() =>
this.actions$.pipe(
ofType(FlowActions.createComponentSuccess),

View File

@ -23,6 +23,7 @@ import {
DocumentedType,
ParameterContextReferenceEntity,
Permissions,
RegistryClientEntity,
Revision,
SelectOption
} from '../../../../state/shared';
@ -165,6 +166,20 @@ export interface CreateProcessGroupDialogRequest {
parameterContexts: ParameterContextEntity[];
}
export interface NoRegistryClientsDialogRequest {
controllerPermissions: Permissions;
}
export interface ImportFromRegistryDialogRequest {
request: CreateComponentRequest;
registryClients: RegistryClientEntity[];
}
export interface ImportFromRegistryRequest {
payload: any;
keepExistingParameterContext: boolean;
}
export interface OpenGroupComponentsDialogRequest {
position: Position;
moveComponents: MoveComponentRequest[];

View File

@ -48,6 +48,11 @@
[disabled]="!canvasPermissions.canWrite"
iconClass="icon-funnel"
iconHoverClass="icon-funnel-add"></new-canvas-item>
<new-canvas-item
[type]="ComponentType.Flow"
[disabled]="!canvasPermissions.canWrite"
iconClass="icon-import-from-registry"
iconHoverClass="icon-import-from-registry-add"></new-canvas-item>
<new-canvas-item
[type]="ComponentType.Label"
[disabled]="!canvasPermissions.canWrite"

View File

@ -15,23 +15,24 @@
* limitations under the License.
*/
.icon {
font-size: 32px;
.new-canvas-item {
.icon {
font-size: 32px;
&.hovering {
cursor: move;
cursor: grab;
cursor: -moz-grab;
cursor: -webkit-grab;
&.hovering {
cursor: grab;
}
&.dragging {
cursor: grabbing;
}
&:disabled {
box-shadow: none;
}
}
&.dragging {
cursor: grabbing;
cursor: -moz-grabbing;
cursor: -webkit-grabbing;
}
&:disabled {
box-shadow: none;
.icon-import-from-registry-add:before {
margin-left: 1px;
}
}

View File

@ -0,0 +1,154 @@
<!--
~ 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>Import From Registry</h2>
<form class="import-from-registry-form" [formGroup]="importFromRegistryForm">
<error-banner></error-banner>
<mat-dialog-content>
<mat-form-field>
<mat-label>Registry</mat-label>
<mat-select formControlName="registry" (selectionChange)="registryChanged($event.value)">
<ng-container *ngFor="let option of registryClientOptions">
<ng-container *ngIf="option.description; else noDescription">
<mat-option
[value]="option.value"
nifiTooltip
[tooltipComponentType]="TextTip"
[tooltipInputData]="getSelectOptionTipData(option)"
[delayClose]="false"
>{{ option.text }}</mat-option
>
</ng-container>
<ng-template #noDescription>
<mat-option [value]="option.value">{{ option.text }}</mat-option>
</ng-template>
</ng-container>
</mat-select>
</mat-form-field>
<mat-form-field>
<mat-label>Bucket</mat-label>
<mat-select formControlName="bucket" (selectionChange)="bucketChanged($event.value)">
<ng-container *ngFor="let option of bucketOptions">
<ng-container *ngIf="option.description; else noDescription">
<mat-option
[value]="option.value"
nifiTooltip
[tooltipComponentType]="TextTip"
[tooltipInputData]="getSelectOptionTipData(option)"
[delayClose]="false"
>{{ option.text }}</mat-option
>
</ng-container>
<ng-template #noDescription>
<mat-option [value]="option.value">{{ option.text }}</mat-option>
</ng-template>
</ng-container>
</mat-select>
<mat-error *ngIf="importFromRegistryForm.controls['bucket'].hasError('required')"
>No buckets available</mat-error
>
</mat-form-field>
<mat-form-field>
<mat-label>Flow</mat-label>
<mat-select formControlName="flow" (selectionChange)="flowChanged($event.value)">
<ng-container *ngFor="let option of flowOptions">
<ng-container *ngIf="option.description; else noDescription">
<mat-option
[value]="option.value"
nifiTooltip
[tooltipComponentType]="TextTip"
[tooltipInputData]="getSelectOptionTipData(option)"
[delayClose]="false"
>{{ option.text }}</mat-option
>
</ng-container>
<ng-template #noDescription>
<mat-option [value]="option.value">{{ option.text }}</mat-option>
</ng-template>
</ng-container>
</mat-select>
<mat-error *ngIf="importFromRegistryForm.controls['flow'].hasError('required')"
>No flows available</mat-error
>
</mat-form-field>
<div class="mb-5">
<mat-checkbox color="primary" formControlName="keepParameterContexts">
Keep existing Parameter Contexts
</mat-checkbox>
</div>
<div class="flex flex-col mb-5">
<div>Flow Description</div>
<div class="value">{{ selectedFlowDescription || 'No description provided' }}</div>
</div>
<div class="listing-table">
<div class="h-48 overflow-y-auto overflow-x-hidden border">
<table
mat-table
[dataSource]="dataSource"
matSort
matSortDisableClear
(matSortChange)="sortData($event)"
[matSortActive]="sort.active"
[matSortDirection]="sort.direction">
<!-- Version Column -->
<ng-container matColumnDef="version">
<th mat-header-cell *matHeaderCellDef mat-sort-header>Version</th>
<td mat-cell *matCellDef="let item">
{{ item.version }}
</td>
</ng-container>
<!-- Create Column -->
<ng-container matColumnDef="created">
<th mat-header-cell *matHeaderCellDef mat-sort-header>Created</th>
<td mat-cell *matCellDef="let item">
{{ formatTimestamp(item) }}
</td>
</ng-container>
<!-- Comments Column -->
<ng-container matColumnDef="comments">
<th mat-header-cell *matHeaderCellDef mat-sort-header>Comments</th>
<td mat-cell *matCellDef="let item">
{{ item.comments }}
</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)"
(dblclick)="importFromRegistry()"
[class.selected]="isSelected(row)"
[class.even]="even"></tr>
</table>
</div>
</div>
</mat-dialog-content>
<mat-dialog-actions align="end" *ngIf="{ value: (saving$ | async)! } as saving">
<button color="primary" mat-stroked-button mat-dialog-close>Cancel</button>
<button
[disabled]="importFromRegistryForm.invalid || saving.value"
type="button"
color="primary"
(click)="importFromRegistry()"
mat-raised-button>
<span *nifiSpinner="saving.value">Import</span>
</button>
</mat-dialog-actions>
</form>

View File

@ -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 '@angular/material' as mat;
.import-from-registry-form {
@include mat.button-density(-1);
.mat-mdc-form-field {
width: 100%;
}
.mat-mdc-form-field-error {
font-size: 12px;
}
.listing-table {
table {
width: auto;
table-layout: unset;
.mat-column-version {
width: 75px;
}
.mat-column-created {
width: 200px;
}
}
}
}

View File

@ -0,0 +1,136 @@
/*
* 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 { ImportFromRegistry } from './import-from-registry.component';
import { ImportFromRegistryDialogRequest } from '../../../../../state/flow';
import { MAT_DIALOG_DATA } from '@angular/material/dialog';
import { ComponentType } from '../../../../../../../state/shared';
import { provideMockStore } from '@ngrx/store/testing';
import { initialState } from '../../../../../state/flow/flow.reducer';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { EMPTY } from 'rxjs';
describe('ImportFromRegistry', () => {
let component: ImportFromRegistry;
let fixture: ComponentFixture<ImportFromRegistry>;
const data: ImportFromRegistryDialogRequest = {
request: {
revision: {
clientId: '88cd6620-bd6d-41fa-aa5a-be2b33501e31',
version: 0
},
type: ComponentType.Flow,
position: {
x: 461,
y: 58
}
},
registryClients: [
{
revision: {
version: 0
},
id: '6a088515-018d-1000-ce79-5ae44266bc20',
uri: 'https://localhost:4200/nifi-api/controller/registry-clients/6a088515-018d-1000-ce79-5ae44266bc20',
permissions: {
canRead: true,
canWrite: true
},
component: {
id: '6a088515-018d-1000-ce79-5ae44266bc20',
name: 'My Registry',
description: '',
type: 'org.apache.nifi.registry.flow.NifiRegistryFlowRegistryClient',
bundle: {
group: 'org.apache.nifi',
artifact: 'nifi-flow-registry-client-nar',
version: '2.0.0-SNAPSHOT'
},
properties: {
url: 'http://localhost:18080/nifi-registry',
'ssl-context-service': null
},
descriptors: {
url: {
name: 'url',
displayName: 'URL',
description: 'URL of the NiFi Registry',
required: true,
sensitive: false,
dynamic: false,
supportsEl: false,
expressionLanguageScope: 'Not Supported',
dependencies: []
},
'ssl-context-service': {
name: 'ssl-context-service',
displayName: 'SSL Context Service',
description: 'Specifies the SSL Context Service to use for communicating with NiFiRegistry',
allowableValues: [],
required: false,
sensitive: false,
dynamic: false,
supportsEl: false,
expressionLanguageScope: 'Not Supported',
identifiesControllerService: 'org.apache.nifi.ssl.SSLContextService',
identifiesControllerServiceBundle: {
group: 'org.apache.nifi',
artifact: 'nifi-standard-services-api-nar',
version: '2.0.0-SNAPSHOT'
},
dependencies: []
}
},
supportsSensitiveDynamicProperties: false,
restricted: false,
deprecated: false,
validationStatus: 'VALID',
multipleVersionsAvailable: false,
extensionMissing: false
}
}
]
};
beforeEach(() => {
TestBed.configureTestingModule({
imports: [ImportFromRegistry, BrowserAnimationsModule],
providers: [{ provide: MAT_DIALOG_DATA, useValue: data }, provideMockStore({ initialState })]
});
fixture = TestBed.createComponent(ImportFromRegistry);
component = fixture.componentInstance;
component.getBuckets = () => {
return EMPTY;
};
component.getFlows = () => {
return EMPTY;
};
component.getFlowVersions = () => {
return EMPTY;
};
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,337 @@
/*
* 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, Input, OnInit } from '@angular/core';
import { MAT_DIALOG_DATA, MatDialogModule } from '@angular/material/dialog';
import { ImportFromRegistryDialogRequest } from '../../../../../state/flow';
import { Store } from '@ngrx/store';
import { CanvasState } from '../../../../../state';
import {
BucketEntity,
isDefinedAndNotNull,
RegistryClientEntity,
SelectOption,
TextTipInput,
VersionedFlow,
VersionedFlowEntity,
VersionedFlowSnapshotMetadata,
VersionedFlowSnapshotMetadataEntity
} from '../../../../../../../state/shared';
import { selectSaving } from '../../../../../state/flow/flow.selectors';
import { AsyncPipe, JsonPipe, NgForOf, NgIf, NgTemplateOutlet } from '@angular/common';
import { ErrorBanner } from '../../../../../../../ui/common/error-banner/error-banner.component';
import { MatButtonModule } from '@angular/material/button';
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 { NifiSpinnerDirective } from '../../../../../../../ui/common/spinner/nifi-spinner.directive';
import { FormBuilder, FormControl, FormGroup, ReactiveFormsModule, Validators } from '@angular/forms';
import { TextTip } from '../../../../../../../ui/common/tooltips/text-tip/text-tip.component';
import { NifiTooltipDirective } from '../../../../../../../ui/common/tooltips/nifi-tooltip.directive';
import { MatIconModule } from '@angular/material/icon';
import { Observable, take } from 'rxjs';
import { MatCheckboxModule } from '@angular/material/checkbox';
import { MatTableDataSource, MatTableModule } from '@angular/material/table';
import { MatSortModule, Sort } from '@angular/material/sort';
import { NiFiCommon } from '../../../../../../../service/nifi-common.service';
import { selectTimeOffset } from '../../../../../../../state/flow-configuration/flow-configuration.selectors';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { Client } from '../../../../../../../service/client.service';
import { importFromRegistry } from '../../../../../state/flow/flow.actions';
@Component({
selector: 'import-from-registry',
standalone: true,
imports: [
AsyncPipe,
ErrorBanner,
MatButtonModule,
MatDialogModule,
MatFormFieldModule,
MatInputModule,
NgIf,
NifiSpinnerDirective,
ReactiveFormsModule,
MatOptionModule,
MatSelectModule,
NgForOf,
NifiTooltipDirective,
MatIconModule,
NgTemplateOutlet,
JsonPipe,
MatCheckboxModule,
MatSortModule,
MatTableModule
],
templateUrl: './import-from-registry.component.html',
styleUrls: ['./import-from-registry.component.scss']
})
export class ImportFromRegistry implements OnInit {
@Input() getBuckets!: (registryId: string) => Observable<BucketEntity[]>;
@Input() getFlows!: (registryId: string, bucketId: string) => Observable<VersionedFlowEntity[]>;
@Input() getFlowVersions!: (
registryId: string,
bucketId: string,
flowId: string
) => Observable<VersionedFlowSnapshotMetadataEntity[]>;
saving$ = this.store.select(selectSaving);
timeOffset = 0;
protected readonly TextTip = TextTip;
importFromRegistryForm: FormGroup;
registryClientOptions: SelectOption[] = [];
bucketOptions: SelectOption[] = [];
flowOptions: SelectOption[] = [];
flowLookup: Map<string, VersionedFlow> = new Map<string, VersionedFlow>();
selectedFlowDescription: string | undefined;
sort: Sort = {
active: 'version',
direction: 'desc'
};
displayedColumns: string[] = ['version', 'created', 'comments'];
dataSource: MatTableDataSource<VersionedFlowSnapshotMetadata> =
new MatTableDataSource<VersionedFlowSnapshotMetadata>();
selectedFlowVersion: number | null = null;
constructor(
@Inject(MAT_DIALOG_DATA) private dialogRequest: ImportFromRegistryDialogRequest,
private formBuilder: FormBuilder,
private store: Store<CanvasState>,
private nifiCommon: NiFiCommon,
private client: Client
) {
this.store
.select(selectTimeOffset)
.pipe(isDefinedAndNotNull(), takeUntilDestroyed())
.subscribe((timeOffset: number) => {
this.timeOffset = timeOffset;
});
const sortedRegistries = dialogRequest.registryClients.slice().sort((a, b) => {
return this.nifiCommon.compareString(a.component.name, b.component.name);
});
sortedRegistries.forEach((registryClient: RegistryClientEntity) => {
if (registryClient.permissions.canRead) {
this.registryClientOptions.push({
text: registryClient.component.name,
value: registryClient.id,
description: registryClient.component.description
});
}
});
this.importFromRegistryForm = this.formBuilder.group({
registry: new FormControl(this.registryClientOptions[0].value, Validators.required),
bucket: new FormControl(null, Validators.required),
flow: new FormControl(null, Validators.required),
keepParameterContexts: new FormControl(true, Validators.required)
});
}
ngOnInit(): void {
const selectedRegistryId = this.importFromRegistryForm.get('registry')?.value;
if (selectedRegistryId) {
this.loadBuckets(selectedRegistryId);
}
}
getSelectOptionTipData(option: SelectOption): TextTipInput {
return {
// @ts-ignore
text: option.description
};
}
registryChanged(registryId: string): void {
this.loadBuckets(registryId);
}
bucketChanged(bucketId: string): void {
const registryId = this.importFromRegistryForm.get('registry')?.value;
this.loadFlows(registryId, bucketId);
}
flowChanged(flowId: string): void {
const registryId = this.importFromRegistryForm.get('registry')?.value;
const bucketId = this.importFromRegistryForm.get('bucket')?.value;
this.loadVersions(registryId, bucketId, flowId);
}
loadBuckets(registryId: string): void {
this.bucketOptions = [];
this.getBuckets(registryId)
.pipe(take(1))
.subscribe((buckets: BucketEntity[]) => {
if (buckets.length > 0) {
buckets.forEach((entity: BucketEntity) => {
if (entity.permissions.canRead) {
this.bucketOptions.push({
text: entity.bucket.name,
value: entity.id,
description: entity.bucket.description
});
}
});
const bucketId = this.bucketOptions[0].value;
if (bucketId) {
this.importFromRegistryForm.get('bucket')?.setValue(bucketId);
this.loadFlows(registryId, bucketId);
}
}
});
}
loadFlows(registryId: string, bucketId: string): void {
this.flowOptions = [];
this.flowLookup.clear();
this.getFlows(registryId, bucketId)
.pipe(take(1))
.subscribe((versionedFlows: VersionedFlowEntity[]) => {
if (versionedFlows.length > 0) {
versionedFlows.forEach((entity: VersionedFlowEntity) => {
this.flowLookup.set(entity.versionedFlow.flowId, entity.versionedFlow);
this.flowOptions.push({
text: entity.versionedFlow.flowName,
value: entity.versionedFlow.flowId,
description: entity.versionedFlow.description
});
});
const flowId = this.flowOptions[0].value;
if (flowId) {
this.importFromRegistryForm.get('flow')?.setValue(flowId);
this.loadVersions(registryId, bucketId, flowId);
}
}
});
}
loadVersions(registryId: string, bucketId: string, flowId: string): void {
this.dataSource.data = [];
this.selectedFlowDescription = this.flowLookup.get(flowId)?.description;
this.getFlowVersions(registryId, bucketId, flowId)
.pipe(take(1))
.subscribe((metadataEntities: VersionedFlowSnapshotMetadataEntity[]) => {
if (metadataEntities.length > 0) {
const flowVersions = metadataEntities.map(
(entity: VersionedFlowSnapshotMetadataEntity) => entity.versionedFlowSnapshotMetadata
);
const sortedFlowVersions = this.sortVersions(flowVersions, this.sort);
this.selectedFlowVersion = sortedFlowVersions[0].version;
this.dataSource.data = sortedFlowVersions;
}
});
}
formatTimestamp(flowVersion: VersionedFlowSnapshotMetadata) {
// 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(flowVersion.timestamp + userTimeOffset + this.timeOffset);
return this.nifiCommon.formatDateTime(date);
}
sortData(sort: Sort) {
this.sort = sort;
this.dataSource.data = this.sortVersions(this.dataSource.data, sort);
}
sortVersions(data: VersionedFlowSnapshotMetadata[], sort: Sort): VersionedFlowSnapshotMetadata[] {
if (!data) {
return [];
}
return data.slice().sort((a, b) => {
const isAsc = sort.direction === 'asc';
let retVal = 0;
switch (sort.active) {
case 'version':
retVal = this.nifiCommon.compareNumber(a.version, b.version);
break;
case 'created':
retVal = this.nifiCommon.compareNumber(a.timestamp, b.timestamp);
break;
case 'comments':
retVal = this.nifiCommon.compareString(a.comments, b.comments);
break;
}
return retVal * (isAsc ? 1 : -1);
});
}
select(flowVersion: VersionedFlowSnapshotMetadata): void {
this.selectedFlowVersion = flowVersion.version;
}
isSelected(flowVersion: VersionedFlowSnapshotMetadata): boolean {
if (this.selectedFlowVersion) {
return flowVersion.version == this.selectedFlowVersion;
}
return false;
}
importFromRegistry(): void {
if (this.selectedFlowVersion != null) {
const payload: any = {
revision: this.client.getRevision({
revision: {
version: 0
}
}),
// disconnectedNodeAcknowledged: nfStorage.isDisconnectionAcknowledged(),
component: {
position: {
x: this.dialogRequest.request.position.x,
y: this.dialogRequest.request.position.y
},
versionControlInformation: {
registryId: this.importFromRegistryForm.get('registry')?.value,
bucketId: this.importFromRegistryForm.get('bucket')?.value,
flowId: this.importFromRegistryForm.get('flow')?.value,
version: this.selectedFlowVersion
}
}
};
this.store.dispatch(
importFromRegistry({
request: {
payload,
keepExistingParameterContext: this.importFromRegistryForm.get('keepParameterContexts')?.value
}
})
);
}
}
}

View File

@ -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.
-->
<h2 mat-dialog-title>No Registry clients available</h2>
<mat-dialog-content>
<div class="text-sm max-w-sm">
{{
request.controllerPermissions.canRead
? 'In order to import a flow a Registry Client must be configured in Controller Settings.'
: 'Cannot import a Flow because there are no available Registry Clients.'
}}
</div>
</mat-dialog-content>
<mat-dialog-actions align="end">
<ng-container *ngIf="request.controllerPermissions.canRead; else noPermissions">
<button mat-stroked-button mat-dialog-close color="primary">Cancel</button>
<button mat-raised-button mat-dialog-close color="primary" [routerLink]="['/settings', 'registry-clients']">
Configure
</button>
</ng-container>
<ng-template #noPermissions>
<button mat-raised-button mat-dialog-close color="primary">Ok</button>
</ng-template>
</mat-dialog-actions>

View File

@ -0,0 +1,22 @@
/*
* 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;
mat-dialog-actions {
@include mat.button-density(-1);
}

View File

@ -0,0 +1,50 @@
/*
* 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 { NoRegistryClientsDialog } from './no-registry-clients-dialog.component';
import { MAT_DIALOG_DATA } from '@angular/material/dialog';
describe('NoRegistryClientsDialog', () => {
let component: NoRegistryClientsDialog;
let fixture: ComponentFixture<NoRegistryClientsDialog>;
beforeEach(() => {
TestBed.configureTestingModule({
imports: [NoRegistryClientsDialog],
providers: [
{
provide: MAT_DIALOG_DATA,
useValue: {
controllerPermissions: {
canRead: true,
canWrite: true
}
}
}
]
});
fixture = TestBed.createComponent(NoRegistryClientsDialog);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -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 { Component, Inject } from '@angular/core';
import { MAT_DIALOG_DATA, MatDialogModule } from '@angular/material/dialog';
import { MatButtonModule } from '@angular/material/button';
import { NoRegistryClientsDialogRequest } from '../../../state/flow';
import { NgIf } from '@angular/common';
import { RouterLink } from '@angular/router';
@Component({
selector: 'no-registry-clients-dialog',
standalone: true,
imports: [MatDialogModule, MatButtonModule, NgIf, RouterLink],
templateUrl: './no-registry-clients-dialog.component.html',
styleUrls: ['./no-registry-clients-dialog.component.scss']
})
export class NoRegistryClientsDialog {
constructor(@Inject(MAT_DIALOG_DATA) public request: NoRegistryClientsDialogRequest) {}
}

View File

@ -23,10 +23,9 @@ import { NiFiCommon } from '../../../service/nifi-common.service';
import {
CreateRegistryClientRequest,
DeleteRegistryClientRequest,
EditRegistryClientRequest,
RegistryClientEntity
EditRegistryClientRequest
} from '../state/registry-clients';
import { PropertyDescriptorRetriever } from '../../../state/shared';
import { PropertyDescriptorRetriever, RegistryClientEntity } from '../../../state/shared';
@Injectable({ providedIn: 'root' })
export class RegistryClientService implements PropertyDescriptorRetriever {

View File

@ -15,7 +15,7 @@
* limitations under the License.
*/
import { BulletinEntity, DocumentedType, Permissions, Revision } from '../../../../state/shared';
import { DocumentedType, RegistryClientEntity, Revision } from '../../../../state/shared';
export const registryClientsFeatureKey = 'registryClients';
@ -70,16 +70,6 @@ export interface SelectRegistryClientRequest {
id: string;
}
export interface RegistryClientEntity {
permissions: Permissions;
operatePermissions?: Permissions;
revision: Revision;
bulletins?: BulletinEntity[];
id: string;
uri: string;
component: any;
}
export interface RegistryClientsState {
registryClients: RegistryClientEntity[];
saving: boolean;

View File

@ -18,7 +18,8 @@
import { createSelector } from '@ngrx/store';
import { selectSettingsState, SettingsState } from '../index';
import { selectCurrentRoute } from '../../../../state/router/router.selectors';
import { RegistryClientEntity, registryClientsFeatureKey, RegistryClientsState } from './index';
import { registryClientsFeatureKey, RegistryClientsState } from './index';
import { RegistryClientEntity } from '../../../../state/shared';
export const selectRegistryClientsState = createSelector(
selectSettingsState,

View File

@ -23,8 +23,7 @@ import { TextTip } from '../../../../../ui/common/tooltips/text-tip/text-tip.com
import { BulletinsTip } from '../../../../../ui/common/tooltips/bulletins-tip/bulletins-tip.component';
import { ValidationErrorsTip } from '../../../../../ui/common/tooltips/validation-errors-tip/validation-errors-tip.component';
import { NiFiCommon } from '../../../../../service/nifi-common.service';
import { BulletinsTipInput, ValidationErrorsTipInput } from '../../../../../state/shared';
import { RegistryClientEntity } from '../../../state/registry-clients';
import { BulletinsTipInput, RegistryClientEntity, ValidationErrorsTipInput } from '../../../../../state/shared';
@Component({
selector: 'registry-client-table',

View File

@ -32,12 +32,13 @@ import {
resetRegistryClientsState,
selectClient
} from '../../state/registry-clients/registry-clients.actions';
import { RegistryClientEntity, RegistryClientsState } from '../../state/registry-clients';
import { RegistryClientsState } from '../../state/registry-clients';
import { initialState } from '../../state/registry-clients/registry-clients.reducer';
import { selectCurrentUser } from '../../../../state/current-user/current-user.selectors';
import { NiFiState } from '../../../../state';
import { filter, switchMap, take } from 'rxjs';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { RegistryClientEntity } from '../../../../state/shared';
@Component({
selector: 'registry-clients',

View File

@ -29,3 +29,8 @@ export const selectSupportsManagedAuthorizer = createSelector(
selectFlowConfiguration,
(flowConfiguration: FlowConfiguration | null) => flowConfiguration?.supportsManagedAuthorizer
);
export const selectTimeOffset = createSelector(
selectFlowConfiguration,
(flowConfiguration: FlowConfiguration | null) => flowConfiguration?.timeOffset
);

View File

@ -386,7 +386,8 @@ export enum ComponentType {
ReportingTask = 'ReportingTask',
FlowAnalysisRule = 'FlowAnalysisRule',
ParameterProvider = 'ParameterProvider',
FlowRegistryClient = 'FlowRegistryClient'
FlowRegistryClient = 'FlowRegistryClient',
Flow = 'Flow'
}
export interface ControllerServiceReferencingComponent {
@ -481,6 +482,57 @@ export interface AllowableValueEntity {
allowableValue: AllowableValue;
}
export interface RegistryClientEntity {
permissions: Permissions;
operatePermissions?: Permissions;
revision: Revision;
bulletins?: BulletinEntity[];
id: string;
uri: string;
component: any;
}
export interface BucketEntity {
id: string;
permissions: Permissions;
bucket: Bucket;
}
export interface Bucket {
created: number;
description: string;
id: string;
name: string;
}
export interface VersionedFlowEntity {
versionedFlow: VersionedFlow;
}
export interface VersionedFlow {
registryId: string;
bucketId: string;
flowId: string;
flowName: string;
description: string;
comments: string;
action: string;
}
export interface VersionedFlowSnapshotMetadataEntity {
registryId: string;
versionedFlowSnapshotMetadata: VersionedFlowSnapshotMetadata;
}
export interface VersionedFlowSnapshotMetadata {
bucketIdentifier: string;
flowIdentifier: string;
version: number;
timestamp: number;
author: string;
comments: string;
}
export interface SelectOption {
text: string;
value: string | null;

View File

@ -17,7 +17,7 @@
<h2 mat-dialog-title>{{ request.title }}</h2>
<mat-dialog-content>
<div class="text-sm">{{ request.message }}</div>
<div class="text-sm max-w-sm">{{ request.message }}</div>
</mat-dialog-content>
<mat-dialog-actions align="end">
<button mat-raised-button mat-dialog-close cdkFocusInitial color="primary">Ok</button>

View File

@ -1,12 +1,12 @@
@font-face {
font-family: 'flowfont';
src: url('./flowfont.eot?33669169');
src: url('./flowfont.eot?8516181');
src:
url('./flowfont.eot?33669169#iefix') format('embedded-opentype'),
url('./flowfont.woff2?33669169') format('woff2'),
url('./flowfont.woff?33669169') format('woff'),
url('./flowfont.ttf?33669169') format('truetype'),
url('./flowfont.svg?33669169#flowfont') format('svg');
url('./flowfont.eot?8516181#iefix') format('embedded-opentype'),
url('./flowfont.woff2?8516181') format('woff2'),
url('./flowfont.woff?8516181') format('woff'),
url('./flowfont.ttf?8516181') format('truetype'),
url('./flowfont.svg?8516181#flowfont') format('svg');
font-weight: normal;
font-style: normal;
}
@ -16,11 +16,10 @@
@media screen and (-webkit-min-device-pixel-ratio:0) {
@font-face {
font-family: 'flowfont';
src: url('../font/flowfont.svg?33669169#flowfont') format('svg');
src: url('../font/flowfont.svg?8516181#flowfont') format('svg');
}
}
*/
[class^='icon-']:before,
[class*=' icon-']:before {
font-family: 'flowfont';
@ -126,6 +125,12 @@
.icon-lineage:before {
content: '\e816';
} /* '' */
.icon-import-from-registry-add:before {
content: '\e81d';
} /* '' */
.icon-import-from-registry:before {
content: '\e81e';
} /* '' */
.icon-port-in:before {
content: '\e832';
} /* '' */

View File

@ -1,68 +1,72 @@
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg xmlns="http://www.w3.org/2000/svg">
<metadata>Copyright (C) 2016 by original authors @ fontello.com</metadata>
<metadata>Copyright (C) 2023 by original authors @ fontello.com</metadata>
<defs>
<font id="flowfont" horiz-adv-x="1000" >
<font-face font-family="flowfont" font-weight="400" font-stretch="normal" units-per-em="1000" ascent="860" descent="-140" />
<font-face font-family="flowfont" font-weight="400" font-stretch="normal" units-per-em="1000" ascent="850" descent="-150" />
<missing-glyph horiz-adv-x="1000" />
<glyph glyph-name="funnel-add" unicode="&#xe800;" d="M948 796h-114a52 52 0 0 1-53-52v-147l-183-77a305 305 0 0 1-35 11l78 110h51a52 52 0 0 1 52 52v115a52 52 0 0 1-52 52h-114a52 52 0 0 1-52-52v-114a52 52 0 0 1 52-53h29l-74-108c-9 0-18-2-28-2s-19 4-28 3l-73 107h18a52 52 0 0 1 52 52v115a52 52 0 0 1-52 52h-114a52 52 0 0 1-51-52v-114a52 52 0 0 1 51-53h61l75-111a379 379 0 0 1-38-8l-187 75v147a52 52 0 0 1-53 52h-114a52 52 0 0 1-52-52v-114a52 52 0 0 1 52-51h156l159-68a39 39 0 0 1-28-29c0-28 74-51 166-51s167 22 167 50a40 40 0 0 1-29 31l159 67h146a52 52 0 0 1 52 51v114a52 52 0 0 1-52 52z m-365-128a31 31 0 0 0-32 31v104a31 31 0 0 0 32 31h105a31 31 0 0 0 31-31v-104a31 31 0 0 0-31-31h-105z m-270 0a31 31 0 0 0-32 31v104a31 31 0 0 0 32 31h104a31 31 0 0 0 31-31v-104a31 31 0 0 0-31-31h-104z m-121-33a31 31 0 0 0-31-32h-104a31 31 0 0 0-31 32v104a31 31 0 0 0 31 31h104a31 31 0 0 0 31-31v-104z m781 0a31 31 0 0 0-31-32h-103a31 31 0 0 0-31 32v104a31 31 0 0 0 31 31h104a31 31 0 0 0 31-31v-104z m-689-257a234 234 0 1 1 234-234 234 234 0 0 1-234 234z m140-268a9 9 0 0 0-9-9h-81a9 9 0 0 1-9-9v-81a9 9 0 0 0-9-9h-66a9 9 0 0 0-9 9v81a9 9 0 0 1-9 9h-80a9 9 0 0 0-9 9v66a9 9 0 0 0 9 9h81a9 9 0 0 1 10 9v82a9 9 0 0 0 7 8h65a9 9 0 0 0 9-9v-81a9 9 0 0 1 9-9h81a9 9 0 0 0 9-9v-66z m153 252v-164a18 18 0 0 1 19-19h60c12 0 14-7 7-15l-115-122a281 281 0 0 1-164 366 514 514 0 0 1 121-14c20 0 194 2 202 80h12v-2a31 31 0 0 0 0-5c0-69-78-95-142-104z m51-301v-150a52 52 0 0 0-52-51h-138a52 52 0 0 0-43 22 281 281 0 0 1 43 23 31 31 0 0 1 18-6h104a31 31 0 0 1 31 31v96z" horiz-adv-x="1000" />
<glyph glyph-name="funnel-add" unicode="&#xe800;" d="M948 786h-114a52 52 0 0 1-53-52v-147l-183-77a305 305 0 0 1-35 11l78 110h51a52 52 0 0 1 52 52v115a52 52 0 0 1-52 52h-114a52 52 0 0 1-52-52v-114a52 52 0 0 1 52-53h29l-74-108c-9 0-18-2-28-2s-19 4-28 3l-73 107h18a52 52 0 0 1 52 52v115a52 52 0 0 1-52 52h-114a52 52 0 0 1-51-52v-114a52 52 0 0 1 51-53h61l75-111a379 379 0 0 1-38-8l-187 75v147a52 52 0 0 1-53 52h-114a52 52 0 0 1-52-52v-114a52 52 0 0 1 52-51h156l159-68a39 39 0 0 1-28-29c0-28 74-51 166-51s167 22 167 50a40 40 0 0 1-29 31l159 67h146a52 52 0 0 1 52 51v114a52 52 0 0 1-52 52z m-365-128a31 31 0 0 0-32 31v104a31 31 0 0 0 32 31h105a31 31 0 0 0 31-31v-104a31 31 0 0 0-31-31h-105z m-270 0a31 31 0 0 0-32 31v104a31 31 0 0 0 32 31h104a31 31 0 0 0 31-31v-104a31 31 0 0 0-31-31h-104z m-121-33a31 31 0 0 0-31-32h-104a31 31 0 0 0-31 32v104a31 31 0 0 0 31 31h104a31 31 0 0 0 31-31v-104z m781 0a31 31 0 0 0-31-32h-103a31 31 0 0 0-31 32v104a31 31 0 0 0 31 31h104a31 31 0 0 0 31-31v-104z m-689-257a234 234 0 1 1 234-234 234 234 0 0 1-234 234z m140-268a9 9 0 0 0-9-9h-81a9 9 0 0 1-9-9v-81a9 9 0 0 0-9-9h-66a9 9 0 0 0-9 9v81a9 9 0 0 1-9 9h-80a9 9 0 0 0-9 9v66a9 9 0 0 0 9 9h81a9 9 0 0 1 10 9v82a9 9 0 0 0 7 8h65a9 9 0 0 0 9-9v-81a9 9 0 0 1 9-9h81a9 9 0 0 0 9-9v-66z m153 252v-164a18 18 0 0 1 19-19h60c12 0 14-7 7-15l-115-122a281 281 0 0 1-164 366 514 514 0 0 1 121-14c20 0 194 2 202 80h12v-2a31 31 0 0 0 0-5c0-69-78-95-142-104z m51-301v-150a52 52 0 0 0-52-51h-138a52 52 0 0 0-43 22 281 281 0 0 1 43 23 31 31 0 0 1 18-6h104a31 31 0 0 1 31 31v96z" horiz-adv-x="1000" />
<glyph glyph-name="counter" unicode="&#xe801;" d="M878-7h-756c-67 0-122 54-122 121v492c0 67 55 121 122 121h756c68 0 122-54 122-121v-492c0-67-54-121-122-121z m-756 670c-32 0-58-26-58-57v-492c0-31 26-57 58-57h756c32 0 58 26 58 57v492c0 31-26 57-58 57h-756z m358-535h-314v62l148 158c20 22 35 42 45 58 10 17 15 33 15 47 0 21-5 37-15 48-11 12-25 18-45 18-20 0-36-7-48-21-12-15-18-33-18-56h-91c0 28 7 53 20 76 13 23 32 41 56 54 25 13 52 20 83 20 47 0 84-11 110-34s39-55 39-96c0-22-6-45-18-69-11-23-31-51-60-82l-104-110h197v-73z m344 457c8-17 12-36 12-57 0-21-7-41-19-58-13-18-30-31-51-41 26-9 45-23 58-41 13-19 19-40 19-66 0-40-15-73-44-97s-69-37-118-37c-46 0-83 12-112 36-29 24-44 56-44 96h91c0-18 7-32 20-42 12-11 28-17 47-17 22 0 39 6 51 17 12 12 18 27 18 46 0 45-25 68-75 68h-48v71h48c23 0 40 6 51 17 12 12 17 27 17 46 0 19-5 33-16 43-11 11-26 16-45 16l140 0z m-141 0c-18 0-32-5-44-15-11-9-17-21-17-37h-91c0 19 4 36 12 52h140z m0-457l25 39h92v-39h-117z" horiz-adv-x="1000" />
<glyph glyph-name="counter" unicode="&#xe801;" d="M878-17h-756c-67 0-122 54-122 121v492c0 67 55 121 122 121h756c68 0 122-54 122-121v-492c0-67-54-121-122-121z m-756 670c-32 0-58-26-58-57v-492c0-31 26-57 58-57h756c32 0 58 26 58 57v492c0 31-26 57-58 57h-756z m358-535h-314v62l148 158c20 22 35 42 45 58 10 17 15 33 15 47 0 21-5 37-15 48-11 12-25 18-45 18-20 0-36-7-48-21-12-15-18-33-18-56h-91c0 28 7 53 20 76 13 23 32 41 56 54 25 13 52 20 83 20 47 0 84-11 110-34s39-55 39-96c0-22-6-45-18-69-11-23-31-51-60-82l-104-110h197v-73z m344 457c8-17 12-36 12-57 0-21-7-41-19-58-13-18-30-31-51-41 26-9 45-23 58-41 13-19 19-40 19-66 0-40-15-73-44-97s-69-37-118-37c-46 0-83 12-112 36-29 24-44 56-44 96h91c0-18 7-32 20-42 12-11 28-17 47-17 22 0 39 6 51 17 12 12 18 27 18 46 0 45-25 68-75 68h-48v71h48c23 0 40 6 51 17 12 12 17 27 17 46 0 19-5 33-16 43-11 11-26 16-45 16l140 0z m-141 0c-18 0-32-5-44-15-11-9-17-21-17-37h-91c0 19 4 36 12 52h140z m0-457l25 39h92v-39h-117z" horiz-adv-x="1000" />
<glyph glyph-name="enable-false" unicode="&#xe802;" d="M912-113l-297 296-144-308c-5-9-14-15-25-15-3 0-6 1-9 1-13 4-21 17-18 30l96 392-71 70-150-37c-3-1-5-1-7-1-7 0-14 2-19 7-7 5-10 14-8 23l38 154-251 250 41 42 865-862-41-42z m-117 683c5 9 3 20-4 27-5 5-12 9-20 9-3 0-5-1-7-1l-238-59 102 278c2 4 3 7 3 11 0 14-12 25-27 25h-197c-12 0-23-8-26-19l-42-172 343-342 113 243z" horiz-adv-x="1000" />
<glyph glyph-name="enable-false" unicode="&#xe802;" d="M912-123l-297 296-144-308c-5-9-14-15-25-15-3 0-6 1-9 1-13 4-21 17-18 30l96 392-71 70-150-37c-3-1-5-1-7-1-7 0-14 2-19 7-7 5-10 14-8 23l38 154-251 250 41 42 865-862-41-42z m-117 683c5 9 3 20-4 27-5 5-12 9-20 9-3 0-5-1-7-1l-238-59 102 278c2 4 3 7 3 11 0 14-12 25-27 25h-197c-12 0-23-8-26-19l-42-172 343-342 113 243z" horiz-adv-x="1000" />
<glyph glyph-name="funnel" unicode="&#xe803;" d="M782 744v-147l-183-77c-11 2-21 9-35 11l77 111h51c28 0 52 23 52 52v114c0 29-24 52-52 52h-114c-29 0-52-23-52-52v-114c0-29 23-52 52-52h29l-74-108c-9 0-18-2-28-2-9 0-19 3-28 3l-73 107h18c29 0 52 23 52 52v114c0 29-23 52-52 52h-113c-29 0-53-23-53-52v-114c0-29 24-52 53-52h60l75-111c-13-2-27-6-38-9l-188 75v147c0 29-23 52-52 52h-114c-29 0-52-23-52-52v-114c0-29 23-52 52-52h157l158-68c-18-8-28-19-28-29 0-27 74-50 166-50 92 0 166 22 166 49 0 10-10 22-28 30l159 68h146c29 0 52 23 52 52v114c0 29-23 52-52 52h-114c-29 0-52-23-52-52z m-231-45v104c0 17 14 31 32 31h104c17 0 31-14 31-31v-104c0-17-14-31-31-31h-104c-18 0-32 14-32 31z m-269 0v104c0 17 14 31 31 31h105c17 0 31-14 31-31v-104c0-17-14-31-31-31h-105c-17 0-31 14-31 31z m-121-95h-104c-17 0-31 14-31 31v104c0 17 14 31 31 31h104c17 0 31-14 31-31v-104c0-17-14-31-31-31z m782 0h-104c-17 0-31 14-31 31v104c0 17 14 31 31 31h104c17 0 31-14 31-31v-104c0-17-14-31-31-31z m-452-592c8-9 20-9 28 0l144 153c8 8 5 16-6 16h-61c-11 0-19 7-19 18v164c64 9 142 35 142 104 0 2-1 3-1 5l-1 2-10 0c-8-78-182-80-202-80-20 0-194 2-202 80l-10 0 0-2c0-2 0-3 0-5 0-69 79-95 143-104v-164c0-11-11-18-22-18h-60c-11 0-14-8-7-16l144-153z m137 50v-150c0-29-23-52-52-52h-139c-29 0-52 23-52 52v150l38-36v-96c0-18 14-32 31-32h105c17 0 31 14 31 32v96l38 36z" horiz-adv-x="1000" />
<glyph glyph-name="funnel" unicode="&#xe803;" d="M782 734v-147l-183-77c-11 2-21 9-35 11l77 111h51c28 0 52 23 52 52v114c0 29-24 52-52 52h-114c-29 0-52-23-52-52v-114c0-29 23-52 52-52h29l-74-108c-9 0-18-2-28-2-9 0-19 3-28 3l-73 107h18c29 0 52 23 52 52v114c0 29-23 52-52 52h-113c-29 0-53-23-53-52v-114c0-29 24-52 53-52h60l75-111c-13-2-27-6-38-9l-188 75v147c0 29-23 52-52 52h-114c-29 0-52-23-52-52v-114c0-29 23-52 52-52h157l158-68c-18-8-28-19-28-29 0-27 74-50 166-50 92 0 166 22 166 49 0 10-10 22-28 30l159 68h146c29 0 52 23 52 52v114c0 29-23 52-52 52h-114c-29 0-52-23-52-52z m-231-45v104c0 17 14 31 32 31h104c17 0 31-14 31-31v-104c0-17-14-31-31-31h-104c-18 0-32 14-32 31z m-269 0v104c0 17 14 31 31 31h105c17 0 31-14 31-31v-104c0-17-14-31-31-31h-105c-17 0-31 14-31 31z m-121-95h-104c-17 0-31 14-31 31v104c0 17 14 31 31 31h104c17 0 31-14 31-31v-104c0-17-14-31-31-31z m782 0h-104c-17 0-31 14-31 31v104c0 17 14 31 31 31h104c17 0 31-14 31-31v-104c0-17-14-31-31-31z m-452-592c8-9 20-9 28 0l144 153c8 8 5 16-6 16h-61c-11 0-19 7-19 18v164c64 9 142 35 142 104 0 2-1 3-1 5l-1 2-10 0c-8-78-182-80-202-80-20 0-194 2-202 80l-10 0 0-2c0-2 0-3 0-5 0-69 79-95 143-104v-164c0-11-11-18-22-18h-60c-11 0-14-8-7-16l144-153z m137 50v-150c0-29-23-52-52-52h-139c-29 0-52 23-52 52v150l38-36v-96c0-18 14-32 31-32h105c17 0 31 14 31 32v96l38 36z" horiz-adv-x="1000" />
<glyph glyph-name="group" unicode="&#xe804;" d="M82 852c-45 0-82-36-82-82v-110h192v192h-110z m53-134h-77v36c0 22 18 41 41 41h36v-77z m783 134h-110v-192h192v110c0 46-37 82-82 82z m24-134h-77v77h36c23 0 41-19 41-41v-36z m-942-658v-110c0-46 37-82 82-82h110v192h-192z m135-135h-36c-23 0-41 19-41 41v36h77v-77z m673 135v-192h110c45 0 82 36 82 82v110h-192z m134-94c0-22-18-41-41-41h-36v77h77v-36z m-883 126v536h75v-536h-75z m807 0v536h75v-536h-75z m-634 627v75h536v-75h-536z m0-793v75h536v-75h-536z m454 530h-92v71c0 46-37 82-82 82h-179c-45 0-82-36-82-82v-178c0-46 37-82 82-82h92v-72c0-45 37-82 82-82h179c45 0 82 37 82 82v179c0 45-37 82-82 82z m-261-82v-67h-84c-27 0-49 22-49 49v164c0 27 22 49 49 49h163c27 0 49-22 49-49v-64h-46c-45 0-82-37-82-82z m302-172c0-27-22-49-49-49h-164c-27 0-49 22-49 49v164c0 27 22 49 49 49h164c27 0 49-22 49-49v-163z" horiz-adv-x="1000" />
<glyph glyph-name="group" unicode="&#xe804;" d="M82 842c-45 0-82-36-82-82v-110h192v192h-110z m53-134h-77v36c0 22 18 41 41 41h36v-77z m783 134h-110v-192h192v110c0 46-37 82-82 82z m24-134h-77v77h36c23 0 41-19 41-41v-36z m-942-658v-110c0-46 37-82 82-82h110v192h-192z m135-135h-36c-23 0-41 19-41 41v36h77v-77z m673 135v-192h110c45 0 82 36 82 82v110h-192z m134-94c0-22-18-41-41-41h-36v77h77v-36z m-883 126v536h75v-536h-75z m807 0v536h75v-536h-75z m-634 627v75h536v-75h-536z m0-793v75h536v-75h-536z m454 530h-92v71c0 46-37 82-82 82h-179c-45 0-82-36-82-82v-178c0-46 37-82 82-82h92v-72c0-45 37-82 82-82h179c45 0 82 37 82 82v179c0 45-37 82-82 82z m-261-82v-67h-84c-27 0-49 22-49 49v164c0 27 22 49 49 49h163c27 0 49-22 49-49v-64h-46c-45 0-82-37-82-82z m302-172c0-27-22-49-49-49h-164c-27 0-49 22-49 49v164c0 27 22 49 49 49h164c27 0 49-22 49-49v-163z" horiz-adv-x="1000" />
<glyph glyph-name="group-remote" unicode="&#xe805;" d="M82 852c-45 0-82-36-82-82v-110h192v192h-110z m53-134h-77v36c0 22 18 41 41 41h36v-77z m783 134h-110v-192h192v110c0 46-37 82-82 82z m24-134h-77v77h36c23 0 41-19 41-41v-36z m-942-658v-110c0-46 37-82 82-82h110v192h-192z m135-135h-36c-23 0-41 19-41 41v36h77v-77z m673 135v-192h110c45 0 82 36 82 82v110h-192z m134-94c0-22-18-41-41-41h-36v77h77v-36z m-211 433c-5 78-69 138-147 138-42 0-82-18-110-49-16 17-38 26-61 26-48 0-87-39-87-86l0-2c-4 1-7 1-11 1-67 0-122-55-122-122 0-67 55-122 122-122h384c60 0 110 50 110 111 0 49-32 91-78 105z m-32-158h-384c-86 0-75 143 11 127l68-13-10 68c-4 28 34 43 51 22l39-50 40 50c49 62 157 27 159-54l1-38 37-9c57-13 46-103-12-103z m-640-149v536h75v-536h-75z m882 0h-75v536h75v-536z m-709 627v75h536v-75h-536z m0-793v75h536v-75h-536z" horiz-adv-x="1000" />
<glyph glyph-name="group-remote" unicode="&#xe805;" d="M82 842c-45 0-82-36-82-82v-110h192v192h-110z m53-134h-77v36c0 22 18 41 41 41h36v-77z m783 134h-110v-192h192v110c0 46-37 82-82 82z m24-134h-77v77h36c23 0 41-19 41-41v-36z m-942-658v-110c0-46 37-82 82-82h110v192h-192z m135-135h-36c-23 0-41 19-41 41v36h77v-77z m673 135v-192h110c45 0 82 36 82 82v110h-192z m134-94c0-22-18-41-41-41h-36v77h77v-36z m-211 433c-5 78-69 138-147 138-42 0-82-18-110-49-16 17-38 26-61 26-48 0-87-39-87-86l0-2c-4 1-7 1-11 1-67 0-122-55-122-122 0-67 55-122 122-122h384c60 0 110 50 110 111 0 49-32 91-78 105z m-32-158h-384c-86 0-75 143 11 127l68-13-10 68c-4 28 34 43 51 22l39-50 40 50c49 62 157 27 159-54l1-38 37-9c57-13 46-103-12-103z m-640-149v536h75v-536h-75z m882 0h-75v536h75v-536z m-709 627v75h536v-75h-536z m0-793v75h536v-75h-536z" horiz-adv-x="1000" />
<glyph glyph-name="label" unicode="&#xe806;" d="M670 167c2 4 5 7 8 11l322 321v-43l-303-303c-3-3-7-6-11-8l-36-18 20 40z m-225-271l315 93c7 2 13 6 18 11l222 222v83l-207-207c-3-3-5-5-6-8l-40-65c-5-8-12-14-21-17l-167-61-5 10c-15 33-41 59-74 75l-10 5 60 166c3 9 10 17 18 21l62 38c3 2 6 4 8 6l382 382v83l-475-473c-5-5-8-11-10-18l-96-320c-5-16 10-31 26-26z m160 712c40 0 70 18 70 41s-30 40-70 40h-535c-40 0-70-17-70-40s30-41 70-41h535z m-59-136c40 0 70 18 70 41s-30 41-70 41h-476c-40 0-70-18-70-41s30-41 70-41h476z m-228-135c40 0 70 17 70 40 0 24-30 41-70 41h-248c-40 0-70-17-70-40 0-24 30-41 70-41h248z m178 407c40 0 70 17 70 40s-30 41-70 41h-426c-40 0-70-17-70-41s30-40 70-40h426z" horiz-adv-x="1000" />
<glyph glyph-name="label" unicode="&#xe806;" d="M670 157c2 4 5 7 8 11l322 321v-43l-303-303c-3-3-7-6-11-8l-36-18 20 40z m-225-271l315 93c7 2 13 6 18 11l222 222v83l-207-207c-3-3-5-5-6-8l-40-65c-5-8-12-14-21-17l-167-61-5 10c-15 33-41 59-74 75l-10 5 60 166c3 9 10 17 18 21l62 38c3 2 6 4 8 6l382 382v83l-475-473c-5-5-8-11-10-18l-96-320c-5-16 10-31 26-26z m160 712c40 0 70 18 70 41s-30 40-70 40h-535c-40 0-70-17-70-40s30-41 70-41h535z m-59-136c40 0 70 18 70 41s-30 41-70 41h-476c-40 0-70-18-70-41s30-41 70-41h476z m-228-135c40 0 70 17 70 40 0 24-30 41-70 41h-248c-40 0-70-17-70-40 0-24 30-41 70-41h248z m178 407c40 0 70 17 70 40s-30 41-70 41h-426c-40 0-70-17-70-41s30-40 70-40h426z" horiz-adv-x="1000" />
<glyph glyph-name="processor" unicode="&#xe807;" d="M101 842l-50-50-39-88 39 39v-120l-39-88 39 39v-120l-39-87 39 39v-121l-39-87 39 39v-121l-39-87 39 39v-121l-39-87 63 63h121l-15-63 63 63h121l-15-63 63 63h120l-14-63 63 63h120l-14-63 63 63h120l-15-63 114 113c11 12 18 28 18 44v760c0 46-37 83-83 83h-760c-16 0-32-7-44-18z m798-850h-738c-23 0-41 18-41 41v738c0 23 18 42 41 42h738c23 0 42-19 42-42v-738c0-23-19-41-42-41z m-119 564c-2 9-4 19-11 27-57 75-144 119-239 119-65 0-128-21-180-60-127-96-156-278-66-409 1-3 2-6 4-8 57-76 144-120 239-120 66 0 128 21 180 61 68 51 111 123 122 202 9 66-8 133-49 188z m-54-322c-39-30-87-45-137-45-80 0-157 40-208 106-11 15-28 24-46 24-13 0-25-4-36-12-12-10-20-23-23-39 0-4 1-7 1-11-60 110-39 246 55 317 39 29 86 45 136 45 80 0 158-40 208-107 11-15 28-23 47-23 13 0 25 4 35 11 16 13 23 32 22 51 63-110 42-245-54-317z" horiz-adv-x="1000" />
<glyph glyph-name="processor" unicode="&#xe807;" d="M101 832l-50-50-39-88 39 39v-120l-39-88 39 39v-120l-39-87 39 39v-121l-39-87 39 39v-121l-39-87 39 39v-121l-39-87 63 63h121l-15-63 63 63h121l-15-63 63 63h120l-14-63 63 63h120l-14-63 63 63h120l-15-63 114 113c11 12 18 28 18 44v760c0 46-37 83-83 83h-760c-16 0-32-7-44-18z m798-850h-738c-23 0-41 18-41 41v738c0 23 18 42 41 42h738c23 0 42-19 42-42v-738c0-23-19-41-42-41z m-119 564c-2 9-4 19-11 27-57 75-144 119-239 119-65 0-128-21-180-60-127-96-156-278-66-409 1-3 2-6 4-8 57-76 144-120 239-120 66 0 128 21 180 61 68 51 111 123 122 202 9 66-8 133-49 188z m-54-322c-39-30-87-45-137-45-80 0-157 40-208 106-11 15-28 24-46 24-13 0-25-4-36-12-12-10-20-23-23-39 0-4 1-7 1-11-60 110-39 246 55 317 39 29 86 45 136 45 80 0 158-40 208-107 11-15 28-23 47-23 13 0 25 4 35 11 16 13 23 32 22 51 63-110 42-245-54-317z" horiz-adv-x="1000" />
<glyph glyph-name="provenance" unicode="&#xe808;" d="M827 170v-258c0-29-23-52-51-52h-724c-29 0-52 23-52 52v724c0 28 23 51 52 51h258v-465c0-29 23-52 52-52h465z m-193 293c-17 0-31 14-31 31v271h-150c-17 0-31-13-31-30v-422c0-17 14-30 31-30h422c17 0 30 13 30 30v150h-271z m366 145v193c0 33-26 59-59 59h-193c-32 0-58-26-58-59v-193c0-32 26-58 58-58h193c33 0 59 26 59 58z m-241 183h172v-172h-172v172z" horiz-adv-x="1000" />
<glyph glyph-name="provenance" unicode="&#xe808;" d="M827 160v-258c0-29-23-52-51-52h-724c-29 0-52 23-52 52v724c0 28 23 51 52 51h258v-465c0-29 23-52 52-52h465z m-193 293c-17 0-31 14-31 31v271h-150c-17 0-31-13-31-30v-422c0-17 14-30 31-30h422c17 0 30 13 30 30v150h-271z m366 145v193c0 33-26 59-59 59h-193c-32 0-58-26-58-59v-193c0-32 26-58 58-58h193c33 0 59 26 59 58z m-241 183h172v-172h-172v172z" horiz-adv-x="1000" />
<glyph glyph-name="template" unicode="&#xe809;" d="M995 423v-157a31 31 0 0 0-31-31h-231l-195-156h153a31 31 0 0 0 32-31v-157a31 31 0 0 0-32-31h-434a31 31 0 0 0-31 31v157a31 31 0 0 0 31 31h231l196 156h-153a31 31 0 0 0-31 31v157a31 31 0 0 0 31 31h122l-373 187h-244a31 31 0 0 0-31 32v156a31 31 0 0 0 31 31h433a31 31 0 0 0 31-31v-156a31 31 0 0 0-31-32h-122l373-187h244a31 31 0 0 0 31-31z" horiz-adv-x="1000" />
<glyph glyph-name="template" unicode="&#xe809;" d="M995 413v-157a31 31 0 0 0-31-31h-231l-195-156h153a31 31 0 0 0 32-31v-157a31 31 0 0 0-32-31h-434a31 31 0 0 0-31 31v157a31 31 0 0 0 31 31h231l196 156h-153a31 31 0 0 0-31 31v157a31 31 0 0 0 31 31h122l-373 187h-244a31 31 0 0 0-31 32v156a31 31 0 0 0 31 31h433a31 31 0 0 0 31-31v-156a31 31 0 0 0-31-32h-122l373-187h244a31 31 0 0 0 31-31z" horiz-adv-x="1000" />
<glyph glyph-name="transmit-false" unicode="&#xe80a;" d="M44 845l-44-44 163-163c-52-74-83-165-83-262 0-255 206-461 460-461 98 0 189 32 264 85l125-125 44 44-929 926z m287-374l60-60c-2-11-4-23-4-36 0-84 68-153 153-153 12 0 24 2 36 5l60-60c-29-14-62-22-96-22-127 0-230 103-230 230 0 35 8 67 21 96z m209-479c-212 0-383 172-383 383 0 77 23 148 62 208l55-55c-26-45-41-97-41-153 0-169 138-306 307-306 56 0 108 15 153 41l56-55c-60-40-132-63-209-63z m0 767c212 0 383-172 383-383 0-75-21-145-58-204l55-55v0c50 73 80 163 80 258 0 255-206 460-460 460-96 0-186-29-260-80l56-55c59 37 129 59 204 59z m269-531c24 44 38 94 38 148 0 169-138 306-307 306-54 0-104-14-148-38l57-57c28 12 59 18 91 18 127 0 230-102 230-230 0-32-7-62-18-90l57-57z m-299 298l180-180c2 10 3 19 3 30 0 84-68 153-153 153-10 0-20-1-30-3z" horiz-adv-x="1000" />
<glyph glyph-name="transmit-false" unicode="&#xe80a;" d="M44 835l-44-44 163-163c-52-74-83-165-83-262 0-255 206-461 460-461 98 0 189 32 264 85l125-125 44 44-929 926z m287-374l60-60c-2-11-4-23-4-36 0-84 68-153 153-153 12 0 24 2 36 5l60-60c-29-14-62-22-96-22-127 0-230 103-230 230 0 35 8 67 21 96z m209-479c-212 0-383 172-383 383 0 77 23 148 62 208l55-55c-26-45-41-97-41-153 0-169 138-306 307-306 56 0 108 15 153 41l56-55c-60-40-132-63-209-63z m0 767c212 0 383-172 383-383 0-75-21-145-58-204l55-55v0c50 73 80 163 80 258 0 255-206 460-460 460-96 0-186-29-260-80l56-55c59 37 129 59 204 59z m269-531c24 44 38 94 38 148 0 169-138 306-307 306-54 0-104-14-148-38l57-57c28 12 59 18 91 18 127 0 230-102 230-230 0-32-7-62-18-90l57-57z m-299 298l180-180c2 10 3 19 3 30 0 84-68 153-153 153-10 0-20-1-30-3z" horiz-adv-x="1000" />
<glyph glyph-name="zoom-actual" unicode="&#xe80b;" d="M42-140c-23 0-42 19-42 42v916c0 23 19 42 42 42h83c23 0 42-19 42-42v-916c0-23-19-42-42-42h-83z m416 233c-22 0-41 19-41 42v108c0 23 19 42 41 42h84c23 0 41-19 41-42v-108c0-23-18-42-41-42h-84z m0 343c-22 0-41 18-41 41v108c0 23 19 42 41 42h84c23 0 41-19 41-42v-108c0-23-18-41-41-41h-84z m417-576c-23 0-42 19-42 42v916c0 23 19 42 42 42h83c23 0 42-19 42-42v-916c0-23-19-42-42-42h-83z" horiz-adv-x="1000" />
<glyph glyph-name="zoom-actual" unicode="&#xe80b;" d="M42-150c-23 0-42 19-42 42v916c0 23 19 42 42 42h83c23 0 42-19 42-42v-916c0-23-19-42-42-42h-83z m416 233c-22 0-41 19-41 42v108c0 23 19 42 41 42h84c23 0 41-19 41-42v-108c0-23-18-42-41-42h-84z m0 343c-22 0-41 18-41 41v108c0 23 19 42 41 42h84c23 0 41-19 41-42v-108c0-23-18-41-41-41h-84z m417-576c-23 0-42 19-42 42v916c0 23 19 42 42 42h83c23 0 42-19 42-42v-916c0-23-19-42-42-42h-83z" horiz-adv-x="1000" />
<glyph glyph-name="zoom-fit" unicode="&#xe80c;" d="M637 789c-12 12-15 30-9 45 7 16 22 26 39 26h291c23 0 42-19 42-42v-291c0-17-10-32-25-39-6-2-11-3-17-3-11 0-21 5-29 12l-292 292z m-566-292c-8-7-18-12-29-12-6 0-11 1-16 3-16 7-26 22-26 39v291c0 23 19 42 42 42h291c17 0 32-10 39-26 6-15 3-33-9-45l-292-292z m292-566c12-12 15-30 9-45-7-16-22-26-39-26h-291c-23 0-42 19-42 42v291c0 17 10 32 26 39 15 6 33 3 45-9l292-292z m566 292c12 12 30 15 46 9 15-7 25-22 25-39v-291c0-23-19-42-42-42h-291c-17 0-32 10-39 26-6 15-3 33 9 45l292 292z" horiz-adv-x="1000" />
<glyph glyph-name="zoom-fit" unicode="&#xe80c;" d="M637 779c-12 12-15 30-9 45 7 16 22 26 39 26h291c23 0 42-19 42-42v-291c0-17-10-32-25-39-6-2-11-3-17-3-11 0-21 5-29 12l-292 292z m-566-292c-8-7-18-12-29-12-6 0-11 1-16 3-16 7-26 22-26 39v291c0 23 19 42 42 42h291c17 0 32-10 39-26 6-15 3-33-9-45l-292-292z m292-566c12-12 15-30 9-45-7-16-22-26-39-26h-291c-23 0-42 19-42 42v291c0 17 10 32 26 39 15 6 33 3 45-9l292-292z m566 292c12 12 30 15 46 9 15-7 25-22 25-39v-291c0-23-19-42-42-42h-291c-17 0-32 10-39 26-6 15-3 33 9 45l292 292z" horiz-adv-x="1000" />
<glyph glyph-name="label-add" unicode="&#xe80d;" d="M670 167a42 42 0 0 0 8 11l322 321v-43l-303-303a42 42 0 0 0-11-8l-36-17z m-65 441c40 0 70 18 70 41s-31 40-70 40h-535c-39 0-70-16-70-40s31-41 70-41h535z m-58-135c40 0 70 17 70 40s-32 41-70 41h-477c-39 0-70-18-70-41s31-40 70-40h477z m-51 271c40 0 70 17 70 40s-31 41-70 41h-426c-39 0-70-17-70-41s31-40 70-40h426z m-426-406h6a283 283 0 0 0 137 81h-143c-39-1-70-18-70-41s31-41 70-41z m214 40a234 234 0 1 1 234-234 234 234 0 0 1-234 234z m140-268a9 9 0 0 0-9-9h-81a9 9 0 0 1-9-9v-81a9 9 0 0 0-9-9h-66a9 9 0 0 0-9 9v81a9 9 0 0 1-9 9h-80a9 9 0 0 0-9 9v66a9 9 0 0 0 9 9h81a9 9 0 0 1 10 9v82a9 9 0 0 0 7 8h65a9 9 0 0 0 9-9v-81a9 9 0 0 1 9-9h81a9 9 0 0 0 9-9v-66z m363-22l-40-64a42 42 0 0 0-21-18l-167-60-4 10a156 156 0 0 1-28 40 285 285 0 0 0-100-99 20 20 0 0 1 18-3l315 94a42 42 0 0 1 18 11l222 223v82l-207-206a42 42 0 0 1-6-8z m-250 182a281 281 0 0 0 17-43l56 34a42 42 0 0 1 8 6l382 383v83z" horiz-adv-x="1000" />
<glyph glyph-name="label-add" unicode="&#xe80d;" d="M670 157a42 42 0 0 0 8 11l322 321v-43l-303-303a42 42 0 0 0-11-8l-36-17z m-65 441c40 0 70 18 70 41s-31 40-70 40h-535c-39 0-70-16-70-40s31-41 70-41h535z m-58-135c40 0 70 17 70 40s-32 41-70 41h-477c-39 0-70-18-70-41s31-40 70-40h477z m-51 271c40 0 70 17 70 40s-31 41-70 41h-426c-39 0-70-17-70-41s31-40 70-40h426z m-426-406h6a283 283 0 0 0 137 81h-143c-39-1-70-18-70-41s31-41 70-41z m214 40a234 234 0 1 1 234-234 234 234 0 0 1-234 234z m140-268a9 9 0 0 0-9-9h-81a9 9 0 0 1-9-9v-81a9 9 0 0 0-9-9h-66a9 9 0 0 0-9 9v81a9 9 0 0 1-9 9h-80a9 9 0 0 0-9 9v66a9 9 0 0 0 9 9h81a9 9 0 0 1 10 9v82a9 9 0 0 0 7 8h65a9 9 0 0 0 9-9v-81a9 9 0 0 1 9-9h81a9 9 0 0 0 9-9v-66z m363-22l-40-64a42 42 0 0 0-21-18l-167-60-4 10a156 156 0 0 1-28 40 285 285 0 0 0-100-99 20 20 0 0 1 18-3l315 94a42 42 0 0 1 18 11l222 223v82l-207-206a42 42 0 0 1-6-8z m-250 182a281 281 0 0 0 17-43l56 34a42 42 0 0 1 8 6l382 383v83z" horiz-adv-x="1000" />
<glyph glyph-name="template-add" unicode="&#xe80e;" d="M723 48v-157a31 31 0 0 0-32-31h-406a283 283 0 0 1 276 219h132a31 31 0 0 0 30-31z m241 406h-244l-372 187h121a31 31 0 0 1 31 32v156a31 31 0 0 1-31 31h-433a31 31 0 0 1-31-31v-156a31 31 0 0 1 31-32h244l372-187h-121a31 31 0 0 1-31-31v-94a283 283 0 0 0 53-94h132l-117-94a284 284 0 0 0-5-43l170 137h231a31 31 0 0 1 31 31v157a31 31 0 0 1-31 31z m-446-310a234 234 0 1 0-234 234 234 234 0 0 0 234-234z m-94 32a9 9 0 0 1-9 9h-81a9 9 0 0 0-9 9v81a9 9 0 0 1-9 9h-66a9 9 0 0 1-9-9v-81a9 9 0 0 0-9-9h-80a9 9 0 0 1-9-9v-66a9 9 0 0 1 9-9h81a9 9 0 0 0 10-9v-81a9 9 0 0 1 9-9h65a9 9 0 0 1 9 9v81a9 9 0 0 0 9 9h81a9 9 0 0 1 8 9v66z" horiz-adv-x="1000" />
<glyph glyph-name="template-add" unicode="&#xe80e;" d="M723 38v-157a31 31 0 0 0-32-31h-406a283 283 0 0 1 276 219h132a31 31 0 0 0 30-31z m241 406h-244l-372 187h121a31 31 0 0 1 31 32v156a31 31 0 0 1-31 31h-433a31 31 0 0 1-31-31v-156a31 31 0 0 1 31-32h244l372-187h-121a31 31 0 0 1-31-31v-94a283 283 0 0 0 53-94h132l-117-94a284 284 0 0 0-5-43l170 137h231a31 31 0 0 1 31 31v157a31 31 0 0 1-31 31z m-446-310a234 234 0 1 0-234 234 234 234 0 0 0 234-234z m-94 32a9 9 0 0 1-9 9h-81a9 9 0 0 0-9 9v81a9 9 0 0 1-9 9h-66a9 9 0 0 1-9-9v-81a9 9 0 0 0-9-9h-80a9 9 0 0 1-9-9v-66a9 9 0 0 1 9-9h81a9 9 0 0 0 10-9v-81a9 9 0 0 1 9-9h65a9 9 0 0 1 9 9v81a9 9 0 0 0 9 9h81a9 9 0 0 1 8 9v66z" horiz-adv-x="1000" />
<glyph glyph-name="group-add" unicode="&#xe80f;" d="M82 852a82 82 0 0 1-82-82v-110h192v192h-110z m53-134h-77v35a41 41 0 0 0 41 41h36v-76z m783 134h-110v-192h192v110a82 82 0 0 1-82 82z m24-134h-77v76h36a41 41 0 0 0 41-41v-35z m-134-657v-192h110a82 82 0 0 1 82 81v110h-192z m134-94a41 41 0 0 0-41-40h-36v76h77v-36z m-76 126v535h75v-536h-75z m-634 626v74h536v-75h-536z m536-719v-75h-302a285 285 0 0 1 62 75h240z m-485 375a234 234 0 1 1 235-234 234 234 0 0 1-234 237z m141-265a9 9 0 0 0-9-9h-81a9 9 0 0 1-9-9v-81a9 9 0 0 0-9-9h-66a9 9 0 0 0-9 9v81a9 9 0 0 1-9 9h-80a9 9 0 0 0-9 9v66a9 9 0 0 0 9 9h81a9 9 0 0 1 10 9v82a9 9 0 0 0 7 8h65a9 9 0 0 0 9-9v-81a9 9 0 0 1 9-9h81a9 9 0 0 0 9-9v-66z m-365 206v312h75v-243a285 285 0 0 1-75-69z m709 58v-179a82 82 0 0 0-82-82h-120a286 286 0 0 1 1 31v10h111a49 49 0 0 1 49 50v162a49 49 0 0 1-49 49h-164a49 49 0 0 1-49-49v-6a284 284 0 0 1-38 27 82 82 0 0 0 80 67h46v66a49 49 0 0 1-49 49h-163a49 49 0 0 1-49-49v-94h-8a286 286 0 0 1-31-2v103a82 82 0 0 0 80 83h179a82 82 0 0 0 82-83v-72h92a82 82 0 0 0 82-81z" horiz-adv-x="1000" />
<glyph glyph-name="group-add" unicode="&#xe80f;" d="M82 842a82 82 0 0 1-82-82v-110h192v192h-110z m53-134h-77v35a41 41 0 0 0 41 41h36v-76z m783 134h-110v-192h192v110a82 82 0 0 1-82 82z m24-134h-77v76h36a41 41 0 0 0 41-41v-35z m-134-657v-192h110a82 82 0 0 1 82 81v110h-192z m134-94a41 41 0 0 0-41-40h-36v76h77v-36z m-76 126v535h75v-536h-75z m-634 626v74h536v-75h-536z m536-719v-75h-302a285 285 0 0 1 62 75h240z m-485 375a234 234 0 1 1 235-234 234 234 0 0 1-234 237z m141-265a9 9 0 0 0-9-9h-81a9 9 0 0 1-9-9v-81a9 9 0 0 0-9-9h-66a9 9 0 0 0-9 9v81a9 9 0 0 1-9 9h-80a9 9 0 0 0-9 9v66a9 9 0 0 0 9 9h81a9 9 0 0 1 10 9v82a9 9 0 0 0 7 8h65a9 9 0 0 0 9-9v-81a9 9 0 0 1 9-9h81a9 9 0 0 0 9-9v-66z m-365 206v312h75v-243a285 285 0 0 1-75-69z m709 58v-179a82 82 0 0 0-82-82h-120a286 286 0 0 1 1 31v10h111a49 49 0 0 1 49 50v162a49 49 0 0 1-49 49h-164a49 49 0 0 1-49-49v-6a284 284 0 0 1-38 27 82 82 0 0 0 80 67h46v66a49 49 0 0 1-49 49h-163a49 49 0 0 1-49-49v-94h-8a286 286 0 0 1-31-2v103a82 82 0 0 0 80 83h179a82 82 0 0 0 82-83v-72h92a82 82 0 0 0 82-81z" horiz-adv-x="1000" />
<glyph glyph-name="template-import" unicode="&#xe810;" d="M990 442v-153a31 31 0 0 0-31-31h-430a31 31 0 0 0-31 31v153a31 31 0 0 0 31 31h116l-361 172h-243a31 31 0 0 0-31 31v153a31 31 0 0 0 31 31h430a31 31 0 0 0 31-31v-153a31 31 0 0 0-31-32h-115l360-171h243a31 31 0 0 0 31-31z m-656-558h-113a63 63 0 0 0-57 38h-154v-31a31 31 0 0 1 31-31h474a31 31 0 0 1 31 31v31h-154a63 63 0 0 0-57-39z m-115 31h115a31 31 0 0 1 31 31v151a13 13 0 0 0 14 13h159c8 0 10 4 5 9l-256 259a13 13 0 0 1-19 0l-255-258c-5-5-3-10 4-10h159a13 13 0 0 0 14-13v-152a31 31 0 0 1 31-31z" horiz-adv-x="1000" />
<glyph glyph-name="template-import" unicode="&#xe810;" d="M990 432v-153a31 31 0 0 0-31-31h-430a31 31 0 0 0-31 31v153a31 31 0 0 0 31 31h116l-361 172h-243a31 31 0 0 0-31 31v153a31 31 0 0 0 31 31h430a31 31 0 0 0 31-31v-153a31 31 0 0 0-31-32h-115l360-171h243a31 31 0 0 0 31-31z m-656-558h-113a63 63 0 0 0-57 38h-154v-31a31 31 0 0 1 31-31h474a31 31 0 0 1 31 31v31h-154a63 63 0 0 0-57-39z m-115 31h115a31 31 0 0 1 31 31v151a13 13 0 0 0 14 13h159c8 0 10 4 5 9l-256 259a13 13 0 0 1-19 0l-255-258c-5-5-3-10 4-10h159a13 13 0 0 0 14-13v-152a31 31 0 0 1 31-31z" horiz-adv-x="1000" />
<glyph glyph-name="template-save" unicode="&#xe811;" d="M990 442v-153a31 31 0 0 0-31-31h-430a31 31 0 0 0-31 31v153a31 31 0 0 0 31 31h116l-361 172h-243a31 31 0 0 0-31 31v153a31 31 0 0 0 31 31h430a31 31 0 0 0 31-31v-153a31 31 0 0 0-31-32h-115l360-171h243a31 31 0 0 0 31-31z m-508-560a22 22 0 0 0-22-22h-438a22 22 0 0 0-22 22v437a22 22 0 0 0 22 22h312a47 47 0 0 0 29-12l106-106a47 47 0 0 0 12-28v-313z m-40 18v286a25 25 0 0 1-7 15l-91 92a44 44 0 0 1-23 9v-139a22 22 0 0 0-22-22h-197a22 22 0 0 0-22 22v139h-40v-402h40v139a22 22 0 0 0 22 22h278a22 22 0 0 0 22-22v-139h40z m-81 0v121h-240v-121h241z m-80 402h-93v-121h71a22 22 0 0 1 22 23v98z" horiz-adv-x="1000" />
<glyph glyph-name="template-save" unicode="&#xe811;" d="M990 432v-153a31 31 0 0 0-31-31h-430a31 31 0 0 0-31 31v153a31 31 0 0 0 31 31h116l-361 172h-243a31 31 0 0 0-31 31v153a31 31 0 0 0 31 31h430a31 31 0 0 0 31-31v-153a31 31 0 0 0-31-32h-115l360-171h243a31 31 0 0 0 31-31z m-508-560a22 22 0 0 0-22-22h-438a22 22 0 0 0-22 22v437a22 22 0 0 0 22 22h312a47 47 0 0 0 29-12l106-106a47 47 0 0 0 12-28v-313z m-40 18v286a25 25 0 0 1-7 15l-91 92a44 44 0 0 1-23 9v-139a22 22 0 0 0-22-22h-197a22 22 0 0 0-22 22v139h-40v-402h40v139a22 22 0 0 0 22 22h278a22 22 0 0 0 22-22v-139h40z m-81 0v121h-240v-121h241z m-80 402h-93v-121h71a22 22 0 0 1 22 23v98z" horiz-adv-x="1000" />
<glyph glyph-name="group-remote-add" unicode="&#xe812;" d="M82 852a82 82 0 0 1-82-82v-110h192v192h-110z m53-134h-77v35a41 41 0 0 0 41 41h36v-76z m783 134h-110v-192h192v110a82 82 0 0 1-82 82z m24-134h-77v76h36a41 41 0 0 0 41-41v-35z m-134-657v-192h110a82 82 0 0 1 82 81v110h-192z m134-94a41 41 0 0 0-41-40h-36v76h77v-36z m0 126h-75v535h75v-536z m-710 626v74h536v-75h-536z m536-719v-75h-302a285 285 0 0 1 62 75h240z m-485 375a234 234 0 1 1 235-234 234 234 0 0 1-234 237z m141-265a9 9 0 0 0-9-9h-81a9 9 0 0 1-9-9v-81a9 9 0 0 0-9-9h-66a9 9 0 0 0-9 9v81a9 9 0 0 1-9 9h-80a9 9 0 0 0-9 9v66a9 9 0 0 0 9 9h81a9 9 0 0 1 10 9v82a9 9 0 0 0 7 8h65a9 9 0 0 0 9-9v-81a9 9 0 0 1 9-9h81a9 9 0 0 0 9-9v-66z m-365 206v312h75v-243a285 285 0 0 1-75-69z m750-22a111 111 0 0 0-111-111h-134a281 281 0 0 1-14 59h148a53 53 0 0 1 13 103l-37 9v38a91 91 0 0 1-160 53l-39-50-39 50a29 29 0 0 1-52-22l3-16a281 281 0 0 1-61 16v3a87 87 0 0 0 148 61 148 148 0 0 0 257-89 110 110 0 0 0 78-104z" horiz-adv-x="1000" />
<glyph glyph-name="group-remote-add" unicode="&#xe812;" d="M82 842a82 82 0 0 1-82-82v-110h192v192h-110z m53-134h-77v35a41 41 0 0 0 41 41h36v-76z m783 134h-110v-192h192v110a82 82 0 0 1-82 82z m24-134h-77v76h36a41 41 0 0 0 41-41v-35z m-134-657v-192h110a82 82 0 0 1 82 81v110h-192z m134-94a41 41 0 0 0-41-40h-36v76h77v-36z m0 126h-75v535h75v-536z m-710 626v74h536v-75h-536z m536-719v-75h-302a285 285 0 0 1 62 75h240z m-485 375a234 234 0 1 1 235-234 234 234 0 0 1-234 237z m141-265a9 9 0 0 0-9-9h-81a9 9 0 0 1-9-9v-81a9 9 0 0 0-9-9h-66a9 9 0 0 0-9 9v81a9 9 0 0 1-9 9h-80a9 9 0 0 0-9 9v66a9 9 0 0 0 9 9h81a9 9 0 0 1 10 9v82a9 9 0 0 0 7 8h65a9 9 0 0 0 9-9v-81a9 9 0 0 1 9-9h81a9 9 0 0 0 9-9v-66z m-365 206v312h75v-243a285 285 0 0 1-75-69z m750-22a111 111 0 0 0-111-111h-134a281 281 0 0 1-14 59h148a53 53 0 0 1 13 103l-37 9v38a91 91 0 0 1-160 53l-39-50-39 50a29 29 0 0 1-52-22l3-16a281 281 0 0 1-61 16v3a87 87 0 0 0 148 61 148 148 0 0 0 257-89 110 110 0 0 0 78-104z" horiz-adv-x="1000" />
<glyph glyph-name="port-out-add" unicode="&#xe813;" d="M284 378a234 234 0 1 1 234-234 234 234 0 0 1-234 234z m140-268a9 9 0 0 0-9-9h-81a9 9 0 0 1-9-9v-81a9 9 0 0 0-9-9h-66a9 9 0 0 0-9 9v81a9 9 0 0 1-9 9h-80a9 9 0 0 0-9 9v66a9 9 0 0 0 9 9h81a9 9 0 0 1 10 9v82a9 9 0 0 0 7 8h65a9 9 0 0 0 9-9v-81a9 9 0 0 1 9-9h81a9 9 0 0 0 9-9v-66z m365 594v-102l-39 39v63a38 38 0 0 1-39 37h-615a38 38 0 0 1-38-37v-390a281 281 0 0 1-58-170v579l41 41a58 58 0 0 0 41 17h630a77 77 0 0 0 77-77z m205-421l-341-339c-9-8-16-6-16 7v192a22 22 0 0 1-22 22h-49a281 281 0 0 1-11 63h133a11 11 0 0 0 10-11v-132c0-6 4-7 8-3l212 209a11 11 0 0 1 0 15l-212 210c-4 5-8 3-8-3v-130a11 11 0 0 0-10-11h-208a11 11 0 0 1-11-12 283 283 0 0 1-63 40v10a22 22 0 0 0 22 22h188a22 22 0 0 1 22 21v194c0 11 6 15 15 6l341-339a22 22 0 0 0 0-31z" horiz-adv-x="1000" />
<glyph glyph-name="port-out-add" unicode="&#xe813;" d="M284 368a234 234 0 1 1 234-234 234 234 0 0 1-234 234z m140-268a9 9 0 0 0-9-9h-81a9 9 0 0 1-9-9v-81a9 9 0 0 0-9-9h-66a9 9 0 0 0-9 9v81a9 9 0 0 1-9 9h-80a9 9 0 0 0-9 9v66a9 9 0 0 0 9 9h81a9 9 0 0 1 10 9v82a9 9 0 0 0 7 8h65a9 9 0 0 0 9-9v-81a9 9 0 0 1 9-9h81a9 9 0 0 0 9-9v-66z m365 594v-102l-39 39v63a38 38 0 0 1-39 37h-615a38 38 0 0 1-38-37v-390a281 281 0 0 1-58-170v579l41 41a58 58 0 0 0 41 17h630a77 77 0 0 0 77-77z m205-421l-341-339c-9-8-16-6-16 7v192a22 22 0 0 1-22 22h-49a281 281 0 0 1-11 63h133a11 11 0 0 0 10-11v-132c0-6 4-7 8-3l212 209a11 11 0 0 1 0 15l-212 210c-4 5-8 3-8-3v-130a11 11 0 0 0-10-11h-208a11 11 0 0 1-11-12 283 283 0 0 1-63 40v10a22 22 0 0 0 22 22h188a22 22 0 0 1 22 21v194c0 11 6 15 15 6l341-339a22 22 0 0 0 0-31z" horiz-adv-x="1000" />
<glyph glyph-name="port-in-add" unicode="&#xe814;" d="M786 158h-54v276h54v-276z m-179 154l-353 351c-9 10-16 6-16-6v-200a23 23 0 0 0-22-23h-193a23 23 0 0 1-23-22v-231a22 22 0 0 1 2-8 281 281 0 0 0 62 149v38a11 11 0 0 0 11 11h39a281 281 0 0 0 170 57h18v88c0 7 3 8 8 4l218-217a11 11 0 0 0 0-15 281 281 0 0 0 27-60l53 52a23 23 0 0 1-1 32z m-323 66a234 234 0 1 1 234-234 234 234 0 0 1-234 234z m140-268a9 9 0 0 0-9-9h-81a9 9 0 0 1-9-9v-81a9 9 0 0 0-9-9h-66a9 9 0 0 0-9 9v81a9 9 0 0 1-9 9h-80a9 9 0 0 0-9 9v66a9 9 0 0 0 9 9h81a9 9 0 0 1 10 9v82a9 9 0 0 0 7 8h65a9 9 0 0 0 9-9v-81a9 9 0 0 1 9-9h81a9 9 0 0 0 9-9v-66z m534 668a60 60 0 0 1-42 18h-671a63 63 0 0 1-62-63l41-40v22a40 40 0 0 0 40 40h637a40 40 0 0 0 39-40v-636a40 40 0 0 0-39-40h-354a281 281 0 0 0-31-60h463l21 21v735z" horiz-adv-x="1000" />
<glyph glyph-name="port-in-add" unicode="&#xe814;" d="M786 148h-54v276h54v-276z m-179 154l-353 351c-9 10-16 6-16-6v-200a23 23 0 0 0-22-23h-193a23 23 0 0 1-23-22v-231a22 22 0 0 1 2-8 281 281 0 0 0 62 149v38a11 11 0 0 0 11 11h39a281 281 0 0 0 170 57h18v88c0 7 3 8 8 4l218-217a11 11 0 0 0 0-15 281 281 0 0 0 27-60l53 52a23 23 0 0 1-1 32z m-323 66a234 234 0 1 1 234-234 234 234 0 0 1-234 234z m140-268a9 9 0 0 0-9-9h-81a9 9 0 0 1-9-9v-81a9 9 0 0 0-9-9h-66a9 9 0 0 0-9 9v81a9 9 0 0 1-9 9h-80a9 9 0 0 0-9 9v66a9 9 0 0 0 9 9h81a9 9 0 0 1 10 9v82a9 9 0 0 0 7 8h65a9 9 0 0 0 9-9v-81a9 9 0 0 1 9-9h81a9 9 0 0 0 9-9v-66z m534 668a60 60 0 0 1-42 18h-671a63 63 0 0 1-62-63l41-40v22a40 40 0 0 0 40 40h637a40 40 0 0 0 39-40v-636a40 40 0 0 0-39-40h-354a281 281 0 0 0-31-60h463l21 21v735z" horiz-adv-x="1000" />
<glyph glyph-name="processor-add" unicode="&#xe815;" d="M988 777v-761a63 63 0 0 0-19-43l-113-113 15 62h-121l-62-62 14 62h-120l-63-62 15 62h-73a285 285 0 0 1 62 69h375a42 42 0 0 1 42 42v738a42 42 0 0 1-42 42h-737a42 42 0 0 1-42-42v-396a285 285 0 0 1-68-69v99l-39-38 39 87v120l-39-39 39 88v120l-39-39 39 88 50 50a63 63 0 0 0 44 18h760a83 83 0 0 0 83-83z m-469-634a234 234 0 1 0-234 235 234 234 0 0 0 233-234z m-94 32a9 9 0 0 1-9 9h-81a9 9 0 0 0-9 9v81a9 9 0 0 1-9 9h-67a9 9 0 0 1-9-9v-81a9 9 0 0 0-9-9h-80a9 9 0 0 1-9-9v-65a9 9 0 0 1 9-9h81a9 9 0 0 0 10-9v-81a9 9 0 0 1 9-9h65a9 9 0 0 1 9 9v81a9 9 0 0 0 9 9h81a9 9 0 0 1 8 9v66z m356 381a57 57 0 0 1-10 27 299 299 0 0 1-539-160l14 2a221 221 0 0 0 223 194 263 263 0 0 0 208-107 58 58 0 0 1 104 39 242 242 0 0 0-55-316 225 225 0 0 0-137-45 242 242 0 0 0-26 0 256 256 0 0 0 0-82 297 297 0 0 1 142 58 304 304 0 0 1 122 202 256 256 0 0 1-46 188z" horiz-adv-x="1000" />
<glyph glyph-name="processor-add" unicode="&#xe815;" d="M988 767v-761a63 63 0 0 0-19-43l-113-113 15 62h-121l-62-62 14 62h-120l-63-62 15 62h-73a285 285 0 0 1 62 69h375a42 42 0 0 1 42 42v738a42 42 0 0 1-42 42h-737a42 42 0 0 1-42-42v-396a285 285 0 0 1-68-69v99l-39-38 39 87v120l-39-39 39 88v120l-39-39 39 88 50 50a63 63 0 0 0 44 18h760a83 83 0 0 0 83-83z m-469-634a234 234 0 1 0-234 235 234 234 0 0 0 233-234z m-94 32a9 9 0 0 1-9 9h-81a9 9 0 0 0-9 9v81a9 9 0 0 1-9 9h-67a9 9 0 0 1-9-9v-81a9 9 0 0 0-9-9h-80a9 9 0 0 1-9-9v-65a9 9 0 0 1 9-9h81a9 9 0 0 0 10-9v-81a9 9 0 0 1 9-9h65a9 9 0 0 1 9 9v81a9 9 0 0 0 9 9h81a9 9 0 0 1 8 9v66z m356 381a57 57 0 0 1-10 27 299 299 0 0 1-539-160l14 2a221 221 0 0 0 223 194 263 263 0 0 0 208-107 58 58 0 0 1 104 39 242 242 0 0 0-55-316 225 225 0 0 0-137-45 242 242 0 0 0-26 0 256 256 0 0 0 0-82 297 297 0 0 1 142 58 304 304 0 0 1 122 202 256 256 0 0 1-46 188z" horiz-adv-x="1000" />
<glyph glyph-name="lineage" unicode="&#xe816;" d="M785 415a214 214 0 0 1-83-17l-58 87a215 215 0 1 1-266-18l-82-184a215 215 0 1 1 72-31l82 185a210 210 0 0 1 127 8l59-90a215 215 0 1 1 149 60z m-539-438a98 98 0 1 0 98 98 98 98 0 0 0-98-98z m157 668a98 98 0 1 0 97-97 98 98 0 0 0-97 97z m382-543a98 98 0 1 0 98 98 98 98 0 0 0-98-98z" horiz-adv-x="1000" />
<glyph glyph-name="lineage" unicode="&#xe816;" d="M785 405a214 214 0 0 1-83-17l-58 87a215 215 0 1 1-266-18l-82-184a215 215 0 1 1 72-31l82 185a210 210 0 0 1 127 8l59-90a215 215 0 1 1 149 60z m-539-438a98 98 0 1 0 98 98 98 98 0 0 0-98-98z m157 668a98 98 0 1 0 97-97 98 98 0 0 0-97 97z m382-543a98 98 0 1 0 98 98 98 98 0 0 0-98-98z" horiz-adv-x="1000" />
<glyph glyph-name="port-in" unicode="&#xe832;" d="M1000 0v735l-42 43a60 60 0 0 1-42 18h-671a63 63 0 0 1-62-63l41-40v22a40 40 0 0 0 40 40h637a40 40 0 0 0 39-40v-636a40 40 0 0 0-39-40h-449l-60-60h587z m-1000 412v-231a23 23 0 0 1 23-23h192a23 23 0 0 0 23-22v-200c0-13 7-15 16-7l353 351a23 23 0 0 1 0 32l-353 351c-9 10-16 6-16-6v-200a23 23 0 0 0-22-23h-193a23 23 0 0 1-23-22z m63-52a11 11 0 0 0 11 11h216a11 11 0 0 1 12 12v133c0 7 3 8 8 4l218-217a11 11 0 0 0 0-15l-218-217c-5-4-8-3-8 4v135a11 11 0 0 1-12 12h-215a11 11 0 0 0-12 11v127z m722-201h-53v275h54v-276z" horiz-adv-x="1000" />
<glyph glyph-name="import-from-registry-add" unicode="&#xe81d;" d="M890 579c7 37 2 91-47 125-52 37-106 21-135 7-24 43-92 131-251 125-84-3-149-29-190-78-45-51-53-114-54-145-48-8-161-42-160-195 0-58 29-99 65-127 9 12 17 22 27 32-28 20-50 50-50 95-1 151 134 155 140 155h23l-3 22c0 0-7 77 43 134 34 39 88 61 160 64 177 7 219-115 220-120l10-27 23 17c0 0 57 40 108 5 49-35 27-98 25-100l-9-25 27-3c6 0 146-17 136-157-7-109-143-107-148-108h-121v-42h124c63 0 180 30 187 147 8 129-84 183-150 198v1z m-40-375h-179c-5 0-10 2-14 6-4 4-7 8-7 14v173c0 5-2 10-6 14-4 4-8 7-14 7h-207c-5 0-10-2-15-6-2-3-3-7-4-10 34-8 67-22 95-41h84c0 0 5-1 8-3 2-1 2-4 2-7v-101c15-32 25-67 27-103h103c6 0 8-3 4-8l-141-142c-23-41-56-76-96-101l23-23c4-3 9-5 14-5 5 0 10 2 14 5l314 316c0 0 6 8 4 10-1 3-4 4-10 4l1 1z m-506 164c-42 0-81-10-117-31-36-21-64-49-85-85-21-36-32-75-32-118 0-42 11-81 32-117 21-36 49-65 85-85 36-21 75-32 117-32 43 0 82 11 118 32 36 20 64 49 85 85 21 36 31 75 31 117 0 43-10 82-31 118-21 36-49 64-85 85-36 21-75 31-118 31z m140-268c0 0 0-5-2-7-2-1-4-2-7-2h-81c0 0-5-1-6-3-2-1-3-4-3-6v-81c0 0-1-5-2-7-2-2-5-2-7-2h-66c0 0-5 0-7 2-1 2-2 4-2 7v81c0 0 0 5-3 6-1 2-4 3-6 3h-80c0 0-5 1-7 2-2 2-2 5-2 7v66c0 0 0 5 2 7 2 1 4 2 7 2h81c0 0 5 0 6 3 2 1 4 4 4 6v82c0 5 3 7 6 8h65c0 0 5-1 7-2 2-2 2-4 2-7v-81c0 0 0-5 3-6 2-2 4-3 7-3h80c0 0 5-1 7-2 2-2 3-5 3-7v-66h1z" horiz-adv-x="1083" />
<glyph glyph-name="port-out" unicode="&#xe833;" d="M0 13v710l41 41a58 58 0 0 0 41 17h630a77 77 0 0 0 77-77v-102l-39 39v63a38 38 0 0 1-39 37h-615a38 38 0 0 1-38-37v-616a38 38 0 0 1 38-38h482v-58h-558z m653-69l341 339a22 22 0 0 1 0 31l-341 339c-9 9-16 6-16-6v-193a22 22 0 0 0-22-22h-187a22 22 0 0 1-22-22v-222a22 22 0 0 1 22-22h187a22 22 0 0 0 22-22v-193c0-13 7-16 16-7z m45 141v131a11 11 0 0 1-10 11h-208a11 11 0 0 0-11 11v122a11 11 0 0 0 11 11h208a11 11 0 0 1 10 11v130c0 6 4 7 8 3l212-209a11 11 0 0 0 0-15l-212-209c-4-3-8-3-8 3z m-467 347a127 127 0 0 1-118-134 127 127 0 0 1 118-133 127 127 0 0 1 119 133 127 127 0 0 1-119 134z m0-223a84 84 0 0 0-79 89 84 84 0 0 0 79 89 84 84 0 0 0 79-89 84 84 0 0 0-79-89z" horiz-adv-x="1000" />
<glyph glyph-name="import-from-registry" unicode="&#xe81e;" d="M913 579c6 37 1 91-47 125-53 37-107 21-136 7-23 43-92 131-251 125-84-3-148-29-190-78-45-51-52-114-53-145-48-8-162-42-160-195 1-142 159-182 242-184h28v42h-28c-10 0-200 7-201 143-1 151 134 154 140 154h22l-2 23c0 0-7 77 42 134 34 39 89 61 161 63 177 8 218-114 220-119l9-27 24 16c0 0 57 41 108 5 49-34 27-97 25-100l-9-25 26-2c6 0 146-16 137-158-7-108-143-106-148-107h-121v-42h123c64 0 180 30 188 147 8 129-84 183-150 198h1z m-246-168c0 0 6-9 6-14v-173c0-5 1-10 6-14 4-4 9-6 14-6h180c5 0 8-1 10-4 0-2 0-6-5-10l-314-316c-4-3-9-5-14-5-5 0-10 2-14 5l-314 316c0 0-5 8-4 10 0 3 4 4 10 4h179c5 0 10 3 14 7 4 4 6 9 6 14v172c0 6 1 11 6 15 4 4 9 6 15 6h206c5 0 10-3 14-7h-1z m-174-50c0 0-5-1-8-3-2-3-2-5-2-8v-192c0 0-1-6-4-8-2-2-5-3-7-3h-121c-6 0-8-3-3-8l194-195c0 0 4-2 6-2 3 0 5 0 7 2l194 195c4 5 3 8-3 8h-119c0 0-6 1-8 3-2 3-3 5-3 8v193c0 0-1 5-3 7-1 3-4 3-7 3h-113z" horiz-adv-x="1083" />
<glyph glyph-name="connect" unicode="&#xe834;" d="M853 713a500 500 0 1 0-753-53 31 31 0 0 0 46 2l279-279a16 16 0 0 0 0-22l-93-94a8 8 0 0 1 3-13l332-74a8 8 0 0 1 9 9l-72 334a8 8 0 0 1-13 3l-94-93a16 16 0 0 0-22 0l-278 280a31 31 0 0 0 4 47 500 500 0 0 0 652-47z" horiz-adv-x="1000" />
<glyph glyph-name="port-in" unicode="&#xe832;" d="M1000-10v735l-42 43a60 60 0 0 1-42 18h-671a63 63 0 0 1-62-63l41-40v22a40 40 0 0 0 40 40h637a40 40 0 0 0 39-40v-636a40 40 0 0 0-39-40h-449l-60-60h587z m-1000 412v-231a23 23 0 0 1 23-23h192a23 23 0 0 0 23-22v-200c0-13 7-15 16-7l353 351a23 23 0 0 1 0 32l-353 351c-9 10-16 6-16-6v-200a23 23 0 0 0-22-23h-193a23 23 0 0 1-23-22z m63-52a11 11 0 0 0 11 11h216a11 11 0 0 1 12 12v133c0 7 3 8 8 4l218-217a11 11 0 0 0 0-15l-218-217c-5-4-8-3-8 4v135a11 11 0 0 1-12 12h-215a11 11 0 0 0-12 11v127z m722-201h-53v275h54v-276z" horiz-adv-x="1000" />
<glyph glyph-name="connect-add" unicode="&#xe835;" d="M853 713a500 500 0 1 0-752-53 31 31 0 0 0 46 2l154-154a16 16 0 0 0 0-22l-94-94a8 8 0 0 1 4-13l333-73a8 8 0 0 1 9 10l-74 331a8 8 0 0 1-13 4l-94-94a16 16 0 0 0-22 0l-152 156a31 31 0 0 0 3 47 500 500 0 0 0 652-47z m-55-523a8 8 0 0 1 8 8v70a8 8 0 0 0 7 8h70a8 8 0 0 1 8 8v56a8 8 0 0 1-8 8h-70a8 8 0 0 0-8 8v70a8 8 0 0 1-8 8h-56a8 8 0 0 1-8-8v-70a8 8 0 0 0-8-8h-69a8 8 0 0 1-8-8v-56a8 8 0 0 1 8-8h71a8 8 0 0 0 7-8v-70a8 8 0 0 1 8-8h56z" horiz-adv-x="1000" />
<glyph glyph-name="port-out" unicode="&#xe833;" d="M0 3v710l41 41a58 58 0 0 0 41 17h630a77 77 0 0 0 77-77v-102l-39 39v63a38 38 0 0 1-39 37h-615a38 38 0 0 1-38-37v-616a38 38 0 0 1 38-38h482v-58h-558z m653-69l341 339a22 22 0 0 1 0 31l-341 339c-9 9-16 6-16-6v-193a22 22 0 0 0-22-22h-187a22 22 0 0 1-22-22v-222a22 22 0 0 1 22-22h187a22 22 0 0 0 22-22v-193c0-13 7-16 16-7z m45 141v131a11 11 0 0 1-10 11h-208a11 11 0 0 0-11 11v122a11 11 0 0 0 11 11h208a11 11 0 0 1 10 11v130c0 6 4 7 8 3l212-209a11 11 0 0 0 0-15l-212-209c-4-3-8-3-8 3z m-467 347a127 127 0 0 1-118-134 127 127 0 0 1 118-133 127 127 0 0 1 119 133 127 127 0 0 1-119 134z m0-223a84 84 0 0 0-79 89 84 84 0 0 0 79 89 84 84 0 0 0 79-89 84 84 0 0 0-79-89z" horiz-adv-x="1000" />
<glyph glyph-name="threads" unicode="&#xe83f;" d="M308 360a149 149 0 1 0-149 149 149 149 0 0 0 149-149z m10 341a159 159 0 1 1-159-158 159 159 0 0 1 159 158z m-66 0a94 94 0 1 0-94 94 94 94 0 0 0 94-94z m248 149a149 149 0 1 1 149-149 149 149 0 0 1-149 149z m-182-831a159 159 0 1 1-159-159 159 159 0 0 1 159 159z m-66 0a94 94 0 1 0-94 94 94 94 0 0 0 94-94z m248 149a149 149 0 1 1 149-149 149 149 0 0 1-149 149z m341 0a149 149 0 1 1 149-149 149 149 0 0 1-149 149z m159 192a159 159 0 1 1-159-159 159 159 0 0 1 159 159z m-66 0a94 94 0 1 0-94 94 94 94 0 0 0 94-94z m66 341a159 159 0 1 1-159-158 159 159 0 0 1 159 158z m-66 0a94 94 0 1 0-94 94 94 94 0 0 0 94-94z m-434-192a149 149 0 1 1 149-149 149 149 0 0 1-149 149z" horiz-adv-x="1000" />
<glyph glyph-name="connect" unicode="&#xe834;" d="M853 703a500 500 0 1 0-753-53 31 31 0 0 0 46 2l279-279a16 16 0 0 0 0-22l-93-94a8 8 0 0 1 3-13l332-74a8 8 0 0 1 9 9l-72 334a8 8 0 0 1-13 3l-94-93a16 16 0 0 0-22 0l-278 280a31 31 0 0 0 4 47 500 500 0 0 0 652-47z" horiz-adv-x="1000" />
<glyph glyph-name="drop" unicode="&#xe888;" d="M507-28v104a8 8 0 0 0 8 8h104v-112h-112z m0-111a333 333 0 0 1 112 21v56h-104a8 8 0 0 1-8-8v-70z m237 236v112h-103a8 8 0 0 1-8-8v-103h111z m14-139a23 23 0 0 1 6 17v89h-111v-112h88a23 23 0 0 1 17 7z m-98-98a334 334 0 0 1 83 51h-83v-51z m174 238a332 332 0 0 1 22 111h-78v-111h56z m-28-126a333 333 0 0 1 50 83h-50v-83z m0 0a333 333 0 0 1 50 83h-50v-83z m21 258h-209a8 8 0 0 1-8-8v-117h-117a8 8 0 0 1-8-8v-209a342 342 0 0 0-341 343c0 162 127 318 214 432a1279 1279 0 0 1 125 195 4 4 0 0 0 7 0 1279 1279 0 0 1 125-195c85-114 212-270 212-432z m-489 326l-28 5a1143 1143 0 0 1-80-123 406 406 0 0 1-59-255 320 320 0 0 1 107-192 314 314 0 0 1 39-29 4 4 0 0 1 5 7 344 344 0 0 0-94 156 435 435 0 0 0 31 279 1210 1210 0 0 0 68 136z m72 138l-19-26c-20-28-41-55-62-82l24-3c18 29 36 58 53 86l14 25c3 7-6 6-10 0z" horiz-adv-x="1000" />
<glyph glyph-name="connect-add" unicode="&#xe835;" d="M853 703a500 500 0 1 0-752-53 31 31 0 0 0 46 2l154-154a16 16 0 0 0 0-22l-94-94a8 8 0 0 1 4-13l333-73a8 8 0 0 1 9 10l-74 331a8 8 0 0 1-13 4l-94-94a16 16 0 0 0-22 0l-152 156a31 31 0 0 0 3 47 500 500 0 0 0 652-47z m-55-523a8 8 0 0 1 8 8v70a8 8 0 0 0 7 8h70a8 8 0 0 1 8 8v56a8 8 0 0 1-8 8h-70a8 8 0 0 0-8 8v70a8 8 0 0 1-8 8h-56a8 8 0 0 1-8-8v-70a8 8 0 0 0-8-8h-69a8 8 0 0 1-8-8v-56a8 8 0 0 1 8-8h71a8 8 0 0 0 7-8v-70a8 8 0 0 1 8-8h56z" horiz-adv-x="1000" />
<glyph glyph-name="threads" unicode="&#xe83f;" d="M308 350a149 149 0 1 0-149 149 149 149 0 0 0 149-149z m10 341a159 159 0 1 1-159-158 159 159 0 0 1 159 158z m-66 0a94 94 0 1 0-94 94 94 94 0 0 0 94-94z m248 149a149 149 0 1 1 149-149 149 149 0 0 1-149 149z m-182-831a159 159 0 1 1-159-159 159 159 0 0 1 159 159z m-66 0a94 94 0 1 0-94 94 94 94 0 0 0 94-94z m248 149a149 149 0 1 1 149-149 149 149 0 0 1-149 149z m341 0a149 149 0 1 1 149-149 149 149 0 0 1-149 149z m159 192a159 159 0 1 1-159-159 159 159 0 0 1 159 159z m-66 0a94 94 0 1 0-94 94 94 94 0 0 0 94-94z m66 341a159 159 0 1 1-159-158 159 159 0 0 1 159 158z m-66 0a94 94 0 1 0-94 94 94 94 0 0 0 94-94z m-434-192a149 149 0 1 1 149-149 149 149 0 0 1-149 149z" horiz-adv-x="1000" />
<glyph glyph-name="drop" unicode="&#xe888;" d="M507-38v104a8 8 0 0 0 8 8h104v-112h-112z m0-111a333 333 0 0 1 112 21v56h-104a8 8 0 0 1-8-8v-70z m237 236v112h-103a8 8 0 0 1-8-8v-103h111z m14-139a23 23 0 0 1 6 17v89h-111v-112h88a23 23 0 0 1 17 7z m-98-98a334 334 0 0 1 83 51h-83v-51z m174 238a332 332 0 0 1 22 111h-78v-111h56z m-28-126a333 333 0 0 1 50 83h-50v-83z m0 0a333 333 0 0 1 50 83h-50v-83z m21 258h-209a8 8 0 0 1-8-8v-117h-117a8 8 0 0 1-8-8v-209a342 342 0 0 0-341 343c0 162 127 318 214 432a1279 1279 0 0 1 125 195 4 4 0 0 0 7 0 1279 1279 0 0 1 125-195c85-114 212-270 212-432z m-489 326l-28 5a1143 1143 0 0 1-80-123 406 406 0 0 1-59-255 320 320 0 0 1 107-192 314 314 0 0 1 39-29 4 4 0 0 1 5 7 344 344 0 0 0-94 156 435 435 0 0 0 31 279 1210 1210 0 0 0 68 136z m72 138l-19-26c-20-28-41-55-62-82l24-3c18 29 36 58 53 86l14 25c3 7-6 6-10 0z" horiz-adv-x="1000" />
</font>
</defs>
</svg>
</svg>

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 24 KiB