This is an automated email from the ASF dual-hosted git repository. riemer pushed a commit to branch 3156-add-asset-browser-to-overview-pages in repository https://gitbox.apache.org/repos/asf/streampipes.git
commit 0dfaec4a69c4afcfb8bbe18ec8961e06c6c03222 Author: Dominik Riemer <[email protected]> AuthorDate: Mon Aug 19 22:50:25 2024 +0200 feat(#3156): Add asset browser to adapters and pipelines --- .../components/sp-label/sp-label.component.scss | 4 +- .../asset-details/asset-details.component.ts | 3 + .../asset-overview/asset-overview.component.ts | 5 + .../existing-adapters.component.html | 500 +++++++++++---------- .../existing-adapters.component.ts | 24 +- .../asset-browser-hierarchy.component.html | 73 +++ .../asset-browser-hierarchy.component.scss} | 33 +- .../asset-browser-hierarchy.component.ts | 98 ++++ .../asset-browser-node-info.component.html | 33 ++ .../asset-browser-node-info.component.scss} | 21 +- .../asset-browser-node-info.component.ts | 63 +++ .../asset-browser-node.component.html | 64 +++ .../asset-browser-node.component.scss} | 43 +- .../asset-browser-node.component.ts | 98 ++++ .../asset-browser-filter-labels.component.html | 79 ++++ .../asset-browser-filter-labels.component.ts} | 31 +- .../asset-browser-filter-outer.component.html | 81 ++++ .../asset-browser-filter-outer.component.ts | 23 + .../asset-browser-filter-sites.component.html | 45 ++ .../asset-browser-filter-sites.component.ts} | 39 +- .../asset-browser-filter-type.component.html | 45 ++ .../asset-browser-filter-type.component.ts | 56 +++ .../asset-browser-filter.component.html | 51 +++ .../asset-browser-filter.component.scss} | 54 ++- .../asset-browser-filter.component.ts | 69 +++ .../asset-browser-toolbar.component.html | 52 +++ .../asset-browser-toolbar.component.ts} | 41 +- .../asset-browser/asset-browser.component.html | 77 ++++ .../asset-browser/asset-browser.component.scss} | 21 +- .../asset-browser/asset-browser.component.ts | 110 +++++ .../core-ui/asset-browser/asset-browser.model.ts} | 29 +- .../core-ui/asset-browser/asset-browser.service.ts | 190 ++++++++ ui/src/app/core-ui/core-ui.module.ts | 21 + ui/src/app/pipelines/pipelines.component.html | 170 +++---- ui/src/app/pipelines/pipelines.component.ts | 24 +- 35 files changed, 1929 insertions(+), 441 deletions(-) diff --git a/ui/projects/streampipes/shared-ui/src/lib/components/sp-label/sp-label.component.scss b/ui/projects/streampipes/shared-ui/src/lib/components/sp-label/sp-label.component.scss index 778b5b97de..a1ac9f6d5d 100644 --- a/ui/projects/streampipes/shared-ui/src/lib/components/sp-label/sp-label.component.scss +++ b/ui/projects/streampipes/shared-ui/src/lib/components/sp-label/sp-label.component.scss @@ -26,6 +26,6 @@ } .small-label { - font-size: 10pt; - padding: 2px 8px; + font-size: 9pt; + padding: 0 8px; } diff --git a/ui/src/app/assets/components/asset-details/asset-details.component.ts b/ui/src/app/assets/components/asset-details/asset-details.component.ts index cbb53dc398..5b7028ef92 100644 --- a/ui/src/app/assets/components/asset-details/asset-details.component.ts +++ b/ui/src/app/assets/components/asset-details/asset-details.component.ts @@ -28,6 +28,7 @@ import { } from '@streampipes/platform-services'; import { SpAssetRoutes } from '../../assets.routes'; import { zip } from 'rxjs'; +import { SpAssetBrowserService } from '../../../core-ui/asset-browser/asset-browser.service'; @Component({ selector: 'sp-asset-details', @@ -48,6 +49,7 @@ export class SpAssetDetailsComponent implements OnInit { constructor( private breadcrumbService: SpBreadcrumbService, private genericStorageService: GenericStorageService, + private assetBrowserService: SpAssetBrowserService, private route: ActivatedRoute, ) {} @@ -91,6 +93,7 @@ export class SpAssetDetailsComponent implements OnInit { this.genericStorageService .updateDocument(AssetConstants.ASSET_APP_DOC_NAME, this.asset) .subscribe(res => { + this.assetBrowserService.loadAssetData(); this.loadResources(); this.editMode = false; }); diff --git a/ui/src/app/assets/components/asset-overview/asset-overview.component.ts b/ui/src/app/assets/components/asset-overview/asset-overview.component.ts index 956c37fb0a..219c0551e2 100644 --- a/ui/src/app/assets/components/asset-overview/asset-overview.component.ts +++ b/ui/src/app/assets/components/asset-overview/asset-overview.component.ts @@ -36,6 +36,7 @@ import { DataExportService } from '../../../configuration/export/data-export.ser import { mergeMap } from 'rxjs/operators'; import { saveAs } from 'file-saver'; import { IdGeneratorService } from '../../../core-services/id-generator/id-generator.service'; +import { SpAssetBrowserService } from '../../../core-ui/asset-browser/asset-browser.service'; @Component({ selector: 'sp-asset-overview', @@ -57,6 +58,7 @@ export class SpAssetOverviewComponent implements OnInit { private router: Router, private dataExportService: DataExportService, private idGeneratorService: IdGeneratorService, + private assetBrowserService: SpAssetBrowserService, ) {} ngOnInit(): void { @@ -105,6 +107,7 @@ export class SpAssetOverviewComponent implements OnInit { dialogRef.afterClosed().subscribe(() => { this.loadAssets(); + this.assetBrowserService.loadAssetData(); }); } @@ -118,6 +121,7 @@ export class SpAssetOverviewComponent implements OnInit { dialogRef.afterClosed().subscribe(reload => { if (reload) { this.loadAssets(); + this.assetBrowserService.loadAssetData(); } }); } @@ -141,6 +145,7 @@ export class SpAssetOverviewComponent implements OnInit { ) .subscribe(result => { this.loadAssets(); + this.assetBrowserService.loadAssetData(); }); } diff --git a/ui/src/app/connect/components/existing-adapters/existing-adapters.component.html b/ui/src/app/connect/components/existing-adapters/existing-adapters.component.html index d2f654bb3e..916ac6240c 100644 --- a/ui/src/app/connect/components/existing-adapters/existing-adapters.component.html +++ b/ui/src/app/connect/components/existing-adapters/existing-adapters.component.html @@ -15,272 +15,284 @@ ~ limitations under the License. ~ --> -<sp-basic-view [showBackLink]="false" [padding]="true"> - <div - nav - fxFlex="100" - fxLayoutAlign="start center" - fxLayout="row" - class="pl-10" - > - <button - mat-raised-button - color="accent" - data-cy="connect-create-new-adapter-button" - (click)="createNewAdapter()" - > - <i class="material-icons">add</i> New adapter - </button> - <button - class="mr-10" - mat-button - color="accent" - data-cy="start-all-adapters-btn" - [disabled]="checkCurrentSelectionStatus(false)" - (click)="startAllAdapters(true)" - > - <mat-icon>play_arrow</mat-icon><span>Start all adapters</span> - </button> - <button - mat-button - color="accent" - data-cy="stop-all-adapters-btn" - [disabled]="checkCurrentSelectionStatus(true)" - (click)="startAllAdapters(false)" + +<sp-asset-browser + filteredAssetLinkType="adapter" + [resourceCount]="existingAdapters.length" + (filterIdsEmitter)="applyAdapterFilters($event)" +> + <sp-basic-view [showBackLink]="false" [padding]="true"> + <div + nav + fxFlex="100" + fxLayoutAlign="start center" + fxLayout="row" + class="pl-10" > - <mat-icon>stop</mat-icon> - <span>Stop all adapters</span> - </button> - <div fxFlex fxLayout="row" fxLayoutAlign="end center"> - <sp-connect-filter-toolbar - class="filter-bar-margin" - (filterChangedEmitter)="applyFilter($event)" + <button + mat-raised-button + color="accent" + data-cy="connect-create-new-adapter-button" + (click)="createNewAdapter()" + > + <i class="material-icons">add</i> New adapter + </button> + <button + class="mr-10" + mat-button + color="accent" + data-cy="start-all-adapters-btn" + [disabled]="checkCurrentSelectionStatus(false)" + (click)="startAllAdapters(true)" > - </sp-connect-filter-toolbar> - <div - fxFlex="100" - fxLayout="row" - fxLayoutAlign="end center" - class="page-container-nav" + <mat-icon>play_arrow</mat-icon> + <span>Start all adapters</span> + </button> + <button + mat-button + color="accent" + data-cy="stop-all-adapters-btn" + [disabled]="checkCurrentSelectionStatus(true)" + (click)="startAllAdapters(false)" > + <mat-icon>stop</mat-icon> + <span>Stop all adapters</span> + </button> + <div fxFlex fxLayout="row" fxLayoutAlign="end center"> + <sp-connect-filter-toolbar + class="filter-bar-margin" + (filterChangedEmitter)="applyFilter($event)" + > + </sp-connect-filter-toolbar> + <div + fxFlex="100" + fxLayout="row" + fxLayoutAlign="end center" + class="page-container-nav" + > + <button + mat-icon-button + color="accent" + id="startAdapterTutorial3" + (click)="startAdapterTutorial()" + matTooltip="Tutorial: Generic Adapter" + [disabled]="tutorialActive" + > + <mat-icon>school</mat-icon> + </button> + </div> <button mat-icon-button + matTooltip="Refresh adapters" + matTooltipPosition="below" color="accent" - id="startAdapterTutorial3" - (click)="startAdapterTutorial()" - matTooltip="Tutorial: Generic Adapter" - [disabled]="tutorialActive" + (click)="getAdaptersRunning()" > - <mat-icon>school</mat-icon> + <i class="material-icons"> refresh </i> </button> </div> - <button - mat-icon-button - matTooltip="Refresh adapters" - matTooltipPosition="below" - color="accent" - (click)="getAdaptersRunning()" - > - <i class="material-icons"> refresh </i> - </button> </div> - </div> - <div fxFlex="100" fxLayout="column"> - <sp-basic-header-title-component - title="Adapters" - ></sp-basic-header-title-component> - <div fxFlex="100" fxLayout="row" fxLayoutAlign="center start"> - <sp-table - fxFlex="90" - [columns]="displayedColumns" - [dataSource]="dataSource" - data-cy="all-adapters-table" - matSort - > - <ng-container matColumnDef="status"> - <th mat-header-cell mat-sort-header *matHeaderCellDef> - Status - </th> - <td mat-cell *matCellDef="let adapter"> - <sp-adapter-status-light - [adapterRunning]="adapter.running" - > - </sp-adapter-status-light> - </td> - </ng-container> - <ng-container matColumnDef="start"> - <th mat-header-cell *matHeaderCellDef>Start</th> - <td - mat-cell - *matCellDef="let adapter" - data-cy="adapters-table" - > - <button - color="accent" - mat-icon-button - matTooltip="Start adapter" - matTooltipPosition="above" - data-cy="start-adapter" - (click)="startAdapter(adapter)" - *ngIf="!adapter.running" - > - <i class="material-icons">play_arrow</i> - </button> - <button - color="accent" - mat-icon-button - matTooltip="Stop adapter" - matTooltipPosition="above" - data-cy="stop-adapter" - (click)="stopAdapter(adapter)" - *ngIf="adapter.running" - > - <i class="material-icons">stop</i> - </button> - </td> - </ng-container> - - <ng-container matColumnDef="name"> - <th mat-header-cell mat-sort-header *matHeaderCellDef> - Name - </th> - <td mat-cell *matCellDef="let adapter"> - <div> - <b - style="margin-bottom: 0" - data-cy="adapter-name" - >{{ adapter.name }}</b + <div fxFlex="100" fxLayout="column"> + <sp-basic-header-title-component + title="Adapters" + ></sp-basic-header-title-component> + <div fxFlex="100" fxLayout="row" fxLayoutAlign="center start"> + <sp-table + fxFlex="90" + [columns]="displayedColumns" + [dataSource]="dataSource" + data-cy="all-adapters-table" + matSort + > + <ng-container matColumnDef="status"> + <th mat-header-cell mat-sort-header *matHeaderCellDef> + Status + </th> + <td mat-cell *matCellDef="let adapter"> + <sp-adapter-status-light + [adapterRunning]="adapter.running" > - </div> - <span>{{ - adapter.description !== '' - ? adapter.description - : '-' - }}</span> - </td> - </ng-container> - <ng-container matColumnDef="adapterBase"> - <th mat-header-cell *matHeaderCellDef>Adapter</th> - <td mat-cell *matCellDef="let adapter"> - <img - class="adapter-icon" - *ngIf="getIconUrl(adapter) && !adapter.icon" - [src]="getIconUrl(adapter)" - [alt]="adapter.name" - /> - <img - class="adapter-icon" - *ngIf="adapter.icon" - [alt]="adapter.name" - [src]="adapter.icon" - /> - </td> - </ng-container> - <ng-container matColumnDef="lastModified"> - <th mat-header-cell *matHeaderCellDef>Created</th> - <td mat-cell *matCellDef="let adapter"> - <h5> - {{ adapter.createdAt | date: 'dd.MM.yyyy HH:mm' }} - </h5> - </td> - </ng-container> - - <ng-container matColumnDef="messagesSent"> - <th - mat-header-cell - *matHeaderCellDef - matTooltip="Messages sent since last start" - > - #Messages - </th> - <td mat-cell *matCellDef="let adapter"> - <h5 class="monitoring-info"> - {{ - adapterMetrics[adapter.elementId] - ? adapterMetrics[adapter.elementId] - .messagesOut.counter - : 0 - }} - </h5> - </td> - </ng-container> - - <ng-container matColumnDef="lastMessage"> - <th mat-header-cell *matHeaderCellDef>Last message</th> - <td mat-cell *matCellDef="let adapter"> - <h5> - {{ - adapterMetrics[adapter.elementId] && - adapterMetrics[adapter.elementId] - .lastTimestamp > 0 - ? (adapterMetrics[adapter.elementId] - .lastTimestamp - | date: 'dd.MM.yyyy HH:mm') - : 'n/a' - }} - </h5> - </td> - </ng-container> - - <ng-container matColumnDef="action"> - <th mat-header-cell *matHeaderCellDef></th> - <td mat-cell *matCellDef="let adapter"> - <div fxLayout="row" fxLayoutAlign="end center"> + </sp-adapter-status-light> + </td> + </ng-container> + <ng-container matColumnDef="start"> + <th mat-header-cell *matHeaderCellDef>Start</th> + <td + mat-cell + *matCellDef="let adapter" + data-cy="adapters-table" + > <button color="accent" mat-icon-button - matTooltip="Show details" + matTooltip="Start adapter" matTooltipPosition="above" - data-cy="details-adapter" - (click)="navigateToDetailsOverviewPage(adapter)" + data-cy="start-adapter" + (click)="startAdapter(adapter)" + *ngIf="!adapter.running" > - <i class="material-icons">search</i> + <i class="material-icons">play_arrow</i> </button> <button color="accent" mat-icon-button - matTooltip="Show info" + matTooltip="Stop adapter" matTooltipPosition="above" - data-cy="info-adapter" - (click)="openHelpDialog(adapter)" + data-cy="stop-adapter" + (click)="stopAdapter(adapter)" + *ngIf="adapter.running" > - <i class="material-icons">help_outline</i> + <i class="material-icons">stop</i> </button> + </td> + </ng-container> - <button - color="accent" - mat-icon-button - matTooltip="Edit adapter" - data-cy="edit-adapter" - matTooltipPosition="above" - (click)="editAdapter(adapter)" - > - <i class="material-icons">edit</i> - </button> - <button - color="accent" - mat-icon-button - matTooltip="Manage permissions" - matTooltipPosition="above" - *ngIf="isAdmin" - (click)="showPermissionsDialog(adapter)" - > - <i class="material-icons">share</i> - </button> - <button - color="accent" - mat-icon-button - matTooltip="Delete adapter" - data-cy="delete-adapter" - matTooltipPosition="above" - (click)="deleteAdapter(adapter)" - > - <i class="material-icons">delete</i> - </button> - </div> - </td> - </ng-container> - </sp-table> + <ng-container matColumnDef="name"> + <th mat-header-cell mat-sort-header *matHeaderCellDef> + Name + </th> + <td mat-cell *matCellDef="let adapter"> + <div> + <b + style="margin-bottom: 0" + data-cy="adapter-name" + >{{ adapter.name }}</b + > + </div> + <span>{{ + adapter.description !== '' + ? adapter.description + : '-' + }}</span> + </td> + </ng-container> + <ng-container matColumnDef="adapterBase"> + <th mat-header-cell *matHeaderCellDef>Adapter</th> + <td mat-cell *matCellDef="let adapter"> + <img + class="adapter-icon" + *ngIf="getIconUrl(adapter) && !adapter.icon" + [src]="getIconUrl(adapter)" + [alt]="adapter.name" + /> + <img + class="adapter-icon" + *ngIf="adapter.icon" + [alt]="adapter.name" + [src]="adapter.icon" + /> + </td> + </ng-container> + <ng-container matColumnDef="lastModified"> + <th mat-header-cell *matHeaderCellDef>Created</th> + <td mat-cell *matCellDef="let adapter"> + <h5> + {{ + adapter.createdAt | date: 'dd.MM.yyyy HH:mm' + }} + </h5> + </td> + </ng-container> + + <ng-container matColumnDef="messagesSent"> + <th + mat-header-cell + *matHeaderCellDef + matTooltip="Messages sent since last start" + > + #Messages + </th> + <td mat-cell *matCellDef="let adapter"> + <h5 class="monitoring-info"> + {{ + adapterMetrics[adapter.elementId] + ? adapterMetrics[adapter.elementId] + .messagesOut.counter + : 0 + }} + </h5> + </td> + </ng-container> + + <ng-container matColumnDef="lastMessage"> + <th mat-header-cell *matHeaderCellDef>Last message</th> + <td mat-cell *matCellDef="let adapter"> + <h5> + {{ + adapterMetrics[adapter.elementId] && + adapterMetrics[adapter.elementId] + .lastTimestamp > 0 + ? (adapterMetrics[adapter.elementId] + .lastTimestamp + | date: 'dd.MM.yyyy HH:mm') + : 'n/a' + }} + </h5> + </td> + </ng-container> + + <ng-container matColumnDef="action"> + <th mat-header-cell *matHeaderCellDef></th> + <td mat-cell *matCellDef="let adapter"> + <div fxLayout="row" fxLayoutAlign="end center"> + <button + color="accent" + mat-icon-button + matTooltip="Show details" + matTooltipPosition="above" + data-cy="details-adapter" + (click)=" + navigateToDetailsOverviewPage(adapter) + " + > + <i class="material-icons">search</i> + </button> + <button + color="accent" + mat-icon-button + matTooltip="Show info" + matTooltipPosition="above" + data-cy="info-adapter" + (click)="openHelpDialog(adapter)" + > + <i class="material-icons">help_outline</i> + </button> + + <button + color="accent" + mat-icon-button + matTooltip="Edit adapter" + data-cy="edit-adapter" + matTooltipPosition="above" + (click)="editAdapter(adapter)" + > + <i class="material-icons">edit</i> + </button> + <button + color="accent" + mat-icon-button + matTooltip="Manage permissions" + matTooltipPosition="above" + *ngIf="isAdmin" + (click)="showPermissionsDialog(adapter)" + > + <i class="material-icons">share</i> + </button> + <button + color="accent" + mat-icon-button + matTooltip="Delete adapter" + data-cy="delete-adapter" + matTooltipPosition="above" + (click)="deleteAdapter(adapter)" + > + <i class="material-icons">delete</i> + </button> + </div> + </td> + </ng-container> + </sp-table> + </div> </div> - </div> -</sp-basic-view> + </sp-basic-view> +</sp-asset-browser> diff --git a/ui/src/app/connect/components/existing-adapters/existing-adapters.component.ts b/ui/src/app/connect/components/existing-adapters/existing-adapters.component.ts index fdf0c5986f..feb5cf6ab7 100644 --- a/ui/src/app/connect/components/existing-adapters/existing-adapters.component.ts +++ b/ui/src/app/connect/components/existing-adapters/existing-adapters.component.ts @@ -271,11 +271,7 @@ export class ExistingAdaptersComponent implements OnInit, OnDestroy { this.adapterService.getAdapters().subscribe(adapters => { this.existingAdapters = adapters; this.existingAdapters.sort((a, b) => a.name.localeCompare(b.name)); - this.filteredAdapters = this.adapterFilter.transform( - this.existingAdapters, - this.currentFilter, - ); - this.dataSource.data = this.filteredAdapters; + this.applyAdapterFilters(); this.getMonitoringInfos(adapters); setTimeout(() => { this.dataSource.sort = this.sort; @@ -283,6 +279,19 @@ export class ExistingAdaptersComponent implements OnInit, OnDestroy { }); } + applyAdapterFilters(elementIds: Set<string> = new Set<string>()): void { + this.filteredAdapters = this.adapterFilter + .transform(this.existingAdapters, this.currentFilter) + .filter(a => { + if (elementIds.size === 0) { + return true; + } else { + return elementIds.has(a.elementId); + } + }); + this.dataSource.data = this.filteredAdapters; + } + startAdapterTutorial() { this.shepherdService.startAdapterTour(); } @@ -296,10 +305,7 @@ export class ExistingAdaptersComponent implements OnInit, OnDestroy { applyFilter(filter: AdapterFilterSettingsModel) { this.currentFilter = filter; if (this.dataSource) { - this.dataSource.data = this.adapterFilter.transform( - this.filteredAdapters, - this.currentFilter, - ); + this.applyAdapterFilters(); } } diff --git a/ui/src/app/core-ui/asset-browser/asset-browser-hierarchy/asset-browser-hierarchy.component.html b/ui/src/app/core-ui/asset-browser/asset-browser-hierarchy/asset-browser-hierarchy.component.html new file mode 100644 index 0000000000..7b3735f8c2 --- /dev/null +++ b/ui/src/app/core-ui/asset-browser/asset-browser-hierarchy/asset-browser-hierarchy.component.html @@ -0,0 +1,73 @@ +<!-- + ~ 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 fxLayout="column" *ngIf="selectedAsset"> + <mat-tree + [dataSource]="dataSource" + [treeControl]="treeControl" + class="sp-tree" + #tree + > + <mat-tree-node *matTreeNodeDef="let node" matTreeNodeToggle> + <sp-asset-browser-node + class="w-100" + [node]="node" + [assetBrowserData]="assetBrowserData" + [assetSelectionMode]="assetSelectionMode" + [filteredAssetLinkType]="filteredAssetLinkType" + [resourceCount]="resourceCount" + [selectedAsset]="selectedAsset" + (selectedNodeEmitter)="selectNode($event)" + > + </sp-asset-browser-node> + </mat-tree-node> + + <mat-nested-tree-node *matTreeNodeDef="let node; when: hasChild"> + <div class="mat-tree-node"> + <button + mat-icon-button + matTreeNodeToggle + [attr.data-cy]="'button-' + node.nodeName" + [attr.aria-label]="'Toggle ' + node.nodeName" + > + <mat-icon class="mat-icon-rtl-mirror"> + {{ + treeControl.isExpanded(node) + ? 'expand_more' + : 'chevron_right' + }} + </mat-icon> + </button> + <sp-asset-browser-node + fxFlex="100" + [node]="node" + [assetBrowserData]="assetBrowserData" + [assetSelectionMode]="assetSelectionMode" + [filteredAssetLinkType]="filteredAssetLinkType" + [resourceCount]="resourceCount" + [selectedAsset]="selectedAsset" + (selectedNodeEmitter)="selectNode($event)" + > + </sp-asset-browser-node> + </div> + <div *ngIf="treeControl.isExpanded(node)" role="group"> + <ng-container matTreeNodeOutlet></ng-container> + </div> + </mat-nested-tree-node> + </mat-tree> +</div> diff --git a/ui/projects/streampipes/shared-ui/src/lib/components/sp-label/sp-label.component.scss b/ui/src/app/core-ui/asset-browser/asset-browser-hierarchy/asset-browser-hierarchy.component.scss similarity index 67% copy from ui/projects/streampipes/shared-ui/src/lib/components/sp-label/sp-label.component.scss copy to ui/src/app/core-ui/asset-browser/asset-browser-hierarchy/asset-browser-hierarchy.component.scss index 778b5b97de..ef8b945b55 100644 --- a/ui/projects/streampipes/shared-ui/src/lib/components/sp-label/sp-label.component.scss +++ b/ui/src/app/core-ui/asset-browser/asset-browser-hierarchy/asset-browser-hierarchy.component.scss @@ -16,16 +16,29 @@ * */ -.basic-label { - border-radius: 15px; - min-width: 50px; - padding: 5px 7px; - border: 1px solid; - display: inline-block; - text-align: center; +.sp-tree-invisible { + display: none; } -.small-label { - font-size: 10pt; - padding: 2px 8px; +.sp-tree ul, +.sp-tree li { + margin-top: 0; + margin-bottom: 0; + list-style-type: none; +} + +.sp-tree .mat-nested-tree-node div[role='group'] { + padding-left: 20px; +} + +.sp-tree div[role='group'] > .mat-tree-node { + padding-left: 20px; +} + +.mat-tree-node { + min-height: 35px; +} + +.mat-tree-node:hover { + background: var(--color-bg-1); } diff --git a/ui/src/app/core-ui/asset-browser/asset-browser-hierarchy/asset-browser-hierarchy.component.ts b/ui/src/app/core-ui/asset-browser/asset-browser-hierarchy/asset-browser-hierarchy.component.ts new file mode 100644 index 0000000000..18c4312478 --- /dev/null +++ b/ui/src/app/core-ui/asset-browser/asset-browser-hierarchy/asset-browser-hierarchy.component.ts @@ -0,0 +1,98 @@ +/* + * 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, + EventEmitter, + Input, + OnChanges, + Output, + SimpleChanges, + ViewChild, +} from '@angular/core'; +import { AssetBrowserData } from '../asset-browser.model'; +import { NestedTreeControl } from '@angular/cdk/tree'; +import { SpAsset } from '@streampipes/platform-services'; +import { MatTreeNestedDataSource } from '@angular/material/tree'; + +@Component({ + selector: 'sp-asset-browser-hierarchy', + templateUrl: 'asset-browser-hierarchy.component.html', + styleUrls: ['./asset-browser-hierarchy.component.scss'], +}) +export class AssetBrowserHierarchyComponent implements OnChanges { + @Input() + assetBrowserData: AssetBrowserData; + + @Input() + assetSelectionMode = false; + + @Input() + filteredAssetLinkType: string; + + @Input() + resourceCount = 0; + + @Output() + selectedAssetEmitter: EventEmitter<SpAsset> = new EventEmitter<SpAsset>(); + + treeControl = new NestedTreeControl<SpAsset>(node => node.assets); + dataSource = new MatTreeNestedDataSource<SpAsset>(); + + selectedAsset: SpAsset = null; + + @ViewChild('tree') tree; + + hasChild = (_: number, node: SpAsset) => + // if asset selection mode is active, only show the direct root children + !!node.assets && + node.assets.length > 0 && + (!this.assetSelectionMode || node.assetId === '_root'); + + ngOnChanges(changes: SimpleChanges) { + if (changes.assetBrowserData) { + this.reloadTree(); + } + } + + reloadTree(): void { + this.treeControl = new NestedTreeControl<SpAsset>(node => node.assets); + this.dataSource = new MatTreeNestedDataSource<SpAsset>(); + const nodes = this.makeRootNode(); + this.selectedAsset = nodes; + this.dataSource.data = [nodes]; + this.treeControl.dataNodes = [nodes]; + this.treeControl.expandAll(); + } + + selectNode(asset: SpAsset) { + this.selectedAssetEmitter.emit(asset); + this.selectedAsset = asset; + } + + makeRootNode(): SpAsset { + return { + assetId: '_root', + assetName: 'All Resources', + assetDescription: '', + assetLinks: [], + assets: this.assetBrowserData.assets, + assetType: undefined, + }; + } +} diff --git a/ui/src/app/core-ui/asset-browser/asset-browser-hierarchy/asset-browser-node/asset-browser-node-info/asset-browser-node-info.component.html b/ui/src/app/core-ui/asset-browser/asset-browser-hierarchy/asset-browser-node/asset-browser-node-info/asset-browser-node-info.component.html new file mode 100644 index 0000000000..daf001a934 --- /dev/null +++ b/ui/src/app/core-ui/asset-browser/asset-browser-hierarchy/asset-browser-node/asset-browser-node-info/asset-browser-node-info.component.html @@ -0,0 +1,33 @@ +<!-- + ~ Licensed to the Apache Software Foundation (ASF) under one or more + ~ contributor license agreements. See the NOTICE file distributed with + ~ this work for additional information regarding copyright ownership. + ~ The ASF licenses this file to You under the Apache License, Version 2.0 + ~ (the "License"); you may not use this file except in compliance with + ~ the License. You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + ~ + --> + +<div fxLayout="column" class="node-info-outer" fxLayoutGap="10px"> + <div *ngIf="assetType"> + <sp-label [small]="true" [labelText]="assetType"> </sp-label> + </div> + <div *ngIf="assetSite"> + <sp-label [small]="true" [labelText]="assetSite"> </sp-label> + </div> + <div *ngFor="let label of labels" fxLayout="row wrap"> + <sp-label + [small]="true" + [labelBackground]="label.color" + [labelText]="label.label" + ></sp-label> + </div> +</div> diff --git a/ui/projects/streampipes/shared-ui/src/lib/components/sp-label/sp-label.component.scss b/ui/src/app/core-ui/asset-browser/asset-browser-hierarchy/asset-browser-node/asset-browser-node-info/asset-browser-node-info.component.scss similarity index 75% copy from ui/projects/streampipes/shared-ui/src/lib/components/sp-label/sp-label.component.scss copy to ui/src/app/core-ui/asset-browser/asset-browser-hierarchy/asset-browser-node/asset-browser-node-info/asset-browser-node-info.component.scss index 778b5b97de..c6796b66d5 100644 --- a/ui/projects/streampipes/shared-ui/src/lib/components/sp-label/sp-label.component.scss +++ b/ui/src/app/core-ui/asset-browser/asset-browser-hierarchy/asset-browser-node/asset-browser-node-info/asset-browser-node-info.component.scss @@ -16,16 +16,13 @@ * */ -.basic-label { - border-radius: 15px; - min-width: 50px; - padding: 5px 7px; - border: 1px solid; - display: inline-block; - text-align: center; -} - -.small-label { - font-size: 10pt; - padding: 2px 8px; +.node-info-outer { + background: var(--color-bg-0); + min-width: 150px; + height: 100%; + border: 1px solid var(--color-bg-2); + padding: 10px; + box-shadow: + rgba(50, 50, 93, 0.25) 0 2px 5px -1px, + rgba(0, 0, 0, 0.3) 0 1px 3px -1px; } diff --git a/ui/src/app/core-ui/asset-browser/asset-browser-hierarchy/asset-browser-node/asset-browser-node-info/asset-browser-node-info.component.ts b/ui/src/app/core-ui/asset-browser/asset-browser-hierarchy/asset-browser-node/asset-browser-node-info/asset-browser-node-info.component.ts new file mode 100644 index 0000000000..3fe8b1f551 --- /dev/null +++ b/ui/src/app/core-ui/asset-browser/asset-browser-hierarchy/asset-browser-node/asset-browser-node-info/asset-browser-node-info.component.ts @@ -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 { Component, Input, OnChanges, OnInit } from '@angular/core'; +import { + Isa95TypeService, + SpAsset, + SpLabel, +} from '@streampipes/platform-services'; +import { AssetBrowserData } from '../../../asset-browser.model'; +import { SpAssetBrowserService } from '../../../asset-browser.service'; + +@Component({ + selector: 'sp-asset-browser-node-info', + templateUrl: 'asset-browser-node-info.component.html', + styleUrls: ['./asset-browser-node-info.component.scss'], +}) +export class AssetBrowserNodeInfoComponent implements OnInit { + @Input() + asset: SpAsset; + + @Input() + assetBrowserData: AssetBrowserData; + s; + + labels: SpLabel[] = []; + assetType: string; + assetSite: string; + + constructor(private isa95TypeService: Isa95TypeService) {} + + ngOnInit() { + this.labels = + this.assetBrowserData.labels.filter( + l => this.asset.labelIds?.find(a => a === l._id) !== undefined, + ) || []; + if (this.asset.assetType?.isa95AssetType !== undefined) { + this.assetType = this.isa95TypeService.toLabel( + this.asset.assetType.isa95AssetType, + ); + } + if (this.asset.assetSite?.siteId) { + this.assetSite = this.assetBrowserData.sites.find( + site => site._id === this.asset.assetSite.siteId, + )?.label; + } + } +} diff --git a/ui/src/app/core-ui/asset-browser/asset-browser-hierarchy/asset-browser-node/asset-browser-node.component.html b/ui/src/app/core-ui/asset-browser/asset-browser-hierarchy/asset-browser-node/asset-browser-node.component.html new file mode 100644 index 0000000000..2cee505d44 --- /dev/null +++ b/ui/src/app/core-ui/asset-browser/asset-browser-hierarchy/asset-browser-node/asset-browser-node.component.html @@ -0,0 +1,64 @@ +<!-- + ~ 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 + [ngClass]=" + node.assetId === selectedAsset.assetId + ? 'asset-node selected-node' + : 'asset-node' + " + fxLayout="row" + fxFlex="100" + (click)="emitSelectedNode(node)" +> + <div fxFlex [ngClass]="nodeResourceCount > 0 ? '' : 'asset-node-disabled'"> + <span fxLayoutAlign="start center">{{ node.assetName }} </span> + </div> + <div fxLayoutAlign="end center"> + <div fxLayoutAlign="center center" fxLayout="column"> + <mat-icon + *ngIf="hasContextInfo" + (mouseover)="infoOpen = true" + (mouseout)="infoOpen = false" + cdkOverlayOrigin + #trigger="cdkOverlayOrigin" + color="primary" + class="mr-5 info-icon" + fxLayoutAlign="center center" + > + info + <ng-template + cdkConnectedOverlay + [cdkConnectedOverlayOrigin]="trigger" + [cdkConnectedOverlayOpen]="infoOpen" + > + <sp-asset-browser-node-info + [asset]="node" + [assetBrowserData]="assetBrowserData" + ></sp-asset-browser-node-info> + </ng-template> + </mat-icon> + </div> + <span + *ngIf="!assetSelectionMode" + class="resource-count" + [ngClass]="nodeResourceCount > 0 ? '' : 'resource-count-disabled'" + >{{ nodeResourceCount }}</span + > + </div> +</div> diff --git a/ui/projects/streampipes/shared-ui/src/lib/components/sp-label/sp-label.component.scss b/ui/src/app/core-ui/asset-browser/asset-browser-hierarchy/asset-browser-node/asset-browser-node.component.scss similarity index 55% copy from ui/projects/streampipes/shared-ui/src/lib/components/sp-label/sp-label.component.scss copy to ui/src/app/core-ui/asset-browser/asset-browser-hierarchy/asset-browser-node/asset-browser-node.component.scss index 778b5b97de..3a4970dd5c 100644 --- a/ui/projects/streampipes/shared-ui/src/lib/components/sp-label/sp-label.component.scss +++ b/ui/src/app/core-ui/asset-browser/asset-browser-hierarchy/asset-browser-node/asset-browser-node.component.scss @@ -16,16 +16,41 @@ * */ -.basic-label { - border-radius: 15px; - min-width: 50px; - padding: 5px 7px; - border: 1px solid; - display: inline-block; +.asset-node { + font-weight: normal; + cursor: pointer; + width: 100%; +} + +.asset-node-disabled { + color: var(--color-bg-3); + cursor: default; +} + +.selected-node { + font-family: 'Roboto-Bold', serif; + font-weight: bolder; +} + +.resource-count { + margin-right: 8px; + min-width: 20px; text-align: center; + font-size: smaller; + font-weight: bolder; + font-family: 'Roboto-Bold', serif; + padding: 3px; + border-radius: 3px; + background: var(--color-bg-3); + color: var(--color-default-text); +} + +.resource-count-disabled { + background: var(--color-bg-2); + color: var(--color-bg-3); } -.small-label { - font-size: 10pt; - padding: 2px 8px; +.info-icon { + color: var(--color-accent); + font-size: 12pt; } diff --git a/ui/src/app/core-ui/asset-browser/asset-browser-hierarchy/asset-browser-node/asset-browser-node.component.ts b/ui/src/app/core-ui/asset-browser/asset-browser-hierarchy/asset-browser-node/asset-browser-node.component.ts new file mode 100644 index 0000000000..23f53f82c1 --- /dev/null +++ b/ui/src/app/core-ui/asset-browser/asset-browser-hierarchy/asset-browser-node/asset-browser-node.component.ts @@ -0,0 +1,98 @@ +/* + * 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, + EventEmitter, + Input, + OnChanges, + OnInit, + Output, + SimpleChanges, +} from '@angular/core'; +import { SpAsset } from '@streampipes/platform-services'; +import { SpAssetBrowserService } from '../../asset-browser.service'; +import { AssetBrowserData } from '../../asset-browser.model'; + +@Component({ + selector: 'sp-asset-browser-node', + templateUrl: 'asset-browser-node.component.html', + styleUrls: ['./asset-browser-node.component.scss'], +}) +export class AssetBrowserNodeComponent implements OnInit, OnChanges { + @Input() + assetBrowserData: AssetBrowserData; + + @Input() + assetSelectionMode = false; + + @Input() + node: SpAsset; + + @Input() + selectedAsset: SpAsset; + + @Input() + filteredAssetLinkType: string; + + @Input() + resourceCount = 0; + + @Output() + selectedNodeEmitter: EventEmitter<SpAsset> = new EventEmitter<SpAsset>(); + + infoOpen = false; + + nodeResourceCount = 0; + hasContextInfo = false; + + constructor(private assetBrowserService: SpAssetBrowserService) {} + + ngOnInit() { + this.hasContextInfo = + this.node.labelIds?.length > 0 || + this.node.assetSite?.siteId !== undefined || + this.node.assetType?.isa95AssetType !== undefined; + } + + ngOnChanges(changes: SimpleChanges): void { + if (changes.filteredAssetLinkType || changes.resourceCount) { + this.reloadCounts(); + } + } + + reloadCounts(): void { + if (this.node.assetId !== '_root') { + const elementIds = new Set<string>(); + this.assetBrowserService.collectElementIds( + this.node, + this.filteredAssetLinkType, + elementIds, + ); + this.nodeResourceCount = elementIds.size; + } else { + this.nodeResourceCount = this.resourceCount; + } + } + + emitSelectedNode(node: SpAsset): void { + if (this.nodeResourceCount > 0) { + this.selectedNodeEmitter.emit(node); + } + } +} diff --git a/ui/src/app/core-ui/asset-browser/asset-browser-toolbar/asset-browser-filter/asset-browser-filter-labels/asset-browser-filter-labels.component.html b/ui/src/app/core-ui/asset-browser/asset-browser-toolbar/asset-browser-filter/asset-browser-filter-labels/asset-browser-filter-labels.component.html new file mode 100644 index 0000000000..f376628dfa --- /dev/null +++ b/ui/src/app/core-ui/asset-browser/asset-browser-toolbar/asset-browser-filter/asset-browser-filter-labels/asset-browser-filter-labels.component.html @@ -0,0 +1,79 @@ +<!-- + ~ 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 + fxLayout="row" + fxFlex="100" + fxLayoutAlign="start center" + class="filter-section" +> + <div + fxFlex="50" + fxLayout="row" + fxLayoutAlign="start center" + fxLayoutGap="10px" + > + <div + class="filter-header" + fxLayout="row" + fxLayoutAlign="start center" + fxFlex="100" + > + <div + fxLayoutAlign="center center" + class="active-filters mr-5" + *ngIf="activeFilters.selectedLabels.length > 0" + > + <i class="material-icons fs-icon">filter_alt</i> + </div> + <span>Labels</span> + </div> + </div> + <div + fxFlex="50" + class="filter-selection" + fxLayout="column" + (click)="$event.stopPropagation()" + fxLayoutAlign="end center" + > + <mat-form-field + appearance="outline" + class="form-field-small filter-selection" + color="accent" + > + <mat-select + multiple + [compareWith]="compare" + [(ngModel)]="activeFilters.selectedLabels" + > + <mat-option + *ngFor="let label of labels; let i = index" + [value]="label" + class="smaller-font-size" + > + <sp-label + [labelText]="label.label" + [small]="true" + [labelBackground]="label.color" + > + </sp-label> + </mat-option> + </mat-select> + </mat-form-field> + </div> +</div> diff --git a/ui/projects/streampipes/shared-ui/src/lib/components/sp-label/sp-label.component.scss b/ui/src/app/core-ui/asset-browser/asset-browser-toolbar/asset-browser-filter/asset-browser-filter-labels/asset-browser-filter-labels.component.ts similarity index 57% copy from ui/projects/streampipes/shared-ui/src/lib/components/sp-label/sp-label.component.scss copy to ui/src/app/core-ui/asset-browser/asset-browser-toolbar/asset-browser-filter/asset-browser-filter-labels/asset-browser-filter-labels.component.ts index 778b5b97de..0b84a97997 100644 --- a/ui/projects/streampipes/shared-ui/src/lib/components/sp-label/sp-label.component.scss +++ b/ui/src/app/core-ui/asset-browser/asset-browser-toolbar/asset-browser-filter/asset-browser-filter-labels/asset-browser-filter-labels.component.ts @@ -1,4 +1,4 @@ -/*! +/* * 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. @@ -16,16 +16,23 @@ * */ -.basic-label { - border-radius: 15px; - min-width: 50px; - padding: 5px 7px; - border: 1px solid; - display: inline-block; - text-align: center; -} +import { Component, Input } from '@angular/core'; +import { SpLabel } from '@streampipes/platform-services'; +import { AssetFilter } from '../../../asset-browser.model'; + +@Component({ + selector: 'sp-asset-browser-filter-labels', + templateUrl: 'asset-browser-filter-labels.component.html', + styleUrls: ['../asset-browser-filter.component.scss'], +}) +export class AssetBrowserFilterLabelsComponent { + @Input() + labels: SpLabel[] = []; + + @Input() + activeFilters: AssetFilter; -.small-label { - font-size: 10pt; - padding: 2px 8px; + compare(o1: SpLabel, o2: SpLabel): boolean { + return o1._id === o2._id; + } } diff --git a/ui/src/app/core-ui/asset-browser/asset-browser-toolbar/asset-browser-filter/asset-browser-filter-outer/asset-browser-filter-outer.component.html b/ui/src/app/core-ui/asset-browser/asset-browser-toolbar/asset-browser-filter/asset-browser-filter-outer/asset-browser-filter-outer.component.html new file mode 100644 index 0000000000..d7e6848f49 --- /dev/null +++ b/ui/src/app/core-ui/asset-browser/asset-browser-toolbar/asset-browser-filter/asset-browser-filter-outer/asset-browser-filter-outer.component.html @@ -0,0 +1,81 @@ +<!-- + ~ 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 + fxLayout="row" + fxFlex="100" + fxLayoutAlign="start center" + class="filter-section" +> + <div + fxFlex="50" + fxLayout="row" + fxLayoutAlign="start center" + fxLayoutGap="10px" + > + <div + class="filter-header" + fxLayout="row" + fxLayoutAlign="start center" + fxFlex="100" + > + <div + fxLayoutAlign="center center" + class="active-filters mr-5" + *ngIf="selectedItems.length !== allItems.length" + > + <i class="material-icons fs-icon">filter_alt</i> + </div> + <span>{{ title }}</span> + <div + fxLayout="row" + fxLayoutAlign="end center" + fxLayoutGap="5px" + class="mr-10" + fxFlex + > + <button + mat-button + mat-raised-button + color="accent" + class="small-button btn-margin" + (click)="selectAllEmitter.emit()" + > + All + </button> + <button + mat-button + mat-raised-button + class="small-button mat-basic btn-margin" + (click)="deselectAllEmitter.emit()" + > + None + </button> + </div> + </div> + </div> + <div + fxFlex="50" + class="filter-selection" + fxLayout="column" + (click)="$event.stopPropagation()" + fxLayoutAlign="end center" + > + <ng-content> </ng-content> + </div> +</div> diff --git a/ui/src/app/core-ui/asset-browser/asset-browser-toolbar/asset-browser-filter/asset-browser-filter-outer/asset-browser-filter-outer.component.ts b/ui/src/app/core-ui/asset-browser/asset-browser-toolbar/asset-browser-filter/asset-browser-filter-outer/asset-browser-filter-outer.component.ts new file mode 100644 index 0000000000..b33e698e04 --- /dev/null +++ b/ui/src/app/core-ui/asset-browser/asset-browser-toolbar/asset-browser-filter/asset-browser-filter-outer/asset-browser-filter-outer.component.ts @@ -0,0 +1,23 @@ +import { Component, EventEmitter, Input, Output } from '@angular/core'; + +@Component({ + selector: 'sp-asset-browser-filter-outer', + templateUrl: 'asset-browser-filter-outer.component.html', + styleUrls: ['../asset-browser-filter.component.scss'], +}) +export class AssetBrowserFilterOuterComponent { + @Input() + selectedItems: any[]; + + @Input() + allItems: any[]; + + @Input() + title: string = ''; + + @Output() + selectAllEmitter = new EventEmitter(); + + @Output() + deselectAllEmitter = new EventEmitter(); +} diff --git a/ui/src/app/core-ui/asset-browser/asset-browser-toolbar/asset-browser-filter/asset-browser-filter-sites/asset-browser-filter-sites.component.html b/ui/src/app/core-ui/asset-browser/asset-browser-toolbar/asset-browser-filter/asset-browser-filter-sites/asset-browser-filter-sites.component.html new file mode 100644 index 0000000000..ca4f1eefa1 --- /dev/null +++ b/ui/src/app/core-ui/asset-browser/asset-browser-toolbar/asset-browser-filter/asset-browser-filter-sites/asset-browser-filter-sites.component.html @@ -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. + ~ + --> + +<sp-asset-browser-filter-outer + [selectedItems]="activeFilters.selectedSites" + [allItems]="sites" + title="Sites" + (selectAllEmitter)="selectAll()" + (deselectAllEmitter)="selectNone()" +> + <mat-form-field + appearance="outline" + class="form-field-small filter-selection" + color="accent" + > + <mat-select + multiple + [compareWith]="compare" + [(ngModel)]="activeFilters.selectedSites" + > + <mat-option + *ngFor="let site of sites; let i = index" + [value]="site" + class="smaller-font-size" + > + {{ site.label }} + </mat-option> + </mat-select> + </mat-form-field> +</sp-asset-browser-filter-outer> diff --git a/ui/projects/streampipes/shared-ui/src/lib/components/sp-label/sp-label.component.scss b/ui/src/app/core-ui/asset-browser/asset-browser-toolbar/asset-browser-filter/asset-browser-filter-sites/asset-browser-filter-sites.component.ts similarity index 51% copy from ui/projects/streampipes/shared-ui/src/lib/components/sp-label/sp-label.component.scss copy to ui/src/app/core-ui/asset-browser/asset-browser-toolbar/asset-browser-filter/asset-browser-filter-sites/asset-browser-filter-sites.component.ts index 778b5b97de..6bb6c6e501 100644 --- a/ui/projects/streampipes/shared-ui/src/lib/components/sp-label/sp-label.component.scss +++ b/ui/src/app/core-ui/asset-browser/asset-browser-toolbar/asset-browser-filter/asset-browser-filter-sites/asset-browser-filter-sites.component.ts @@ -1,4 +1,4 @@ -/*! +/* * 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. @@ -16,16 +16,31 @@ * */ -.basic-label { - border-radius: 15px; - min-width: 50px; - padding: 5px 7px; - border: 1px solid; - display: inline-block; - text-align: center; -} +import { Component, Input } from '@angular/core'; +import { AssetSiteDesc } from '@streampipes/platform-services'; +import { AssetFilter } from '../../../asset-browser.model'; + +@Component({ + selector: 'sp-asset-browser-filter-sites', + templateUrl: 'asset-browser-filter-sites.component.html', + styleUrls: ['../asset-browser-filter.component.scss'], +}) +export class AssetBrowserFilterSitesComponent { + @Input() + sites: AssetSiteDesc[] = []; + + @Input() + activeFilters: AssetFilter; + + selectAll(): void { + this.activeFilters.selectedSites = [...this.sites]; + } + + selectNone(): void { + this.activeFilters.selectedSites = []; + } -.small-label { - font-size: 10pt; - padding: 2px 8px; + compare(o1: AssetSiteDesc, o2: AssetSiteDesc): boolean { + return o1._id === o2._id; + } } diff --git a/ui/src/app/core-ui/asset-browser/asset-browser-toolbar/asset-browser-filter/asset-browser-filter-type/asset-browser-filter-type.component.html b/ui/src/app/core-ui/asset-browser/asset-browser-toolbar/asset-browser-filter/asset-browser-filter-type/asset-browser-filter-type.component.html new file mode 100644 index 0000000000..fd0ee9ebf0 --- /dev/null +++ b/ui/src/app/core-ui/asset-browser/asset-browser-toolbar/asset-browser-filter/asset-browser-filter-type/asset-browser-filter-type.component.html @@ -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. + ~ + --> + +<sp-asset-browser-filter-outer + [selectedItems]="activeFilters.selectedTypes" + [allItems]="allAssetTypes" + title="Type" + (selectAllEmitter)="selectAll()" + (deselectAllEmitter)="selectNone()" +> + <mat-form-field + appearance="outline" + class="form-field-small filter-selection" + color="accent" + > + <mat-select + multiple + [compareWith]="compare" + [(ngModel)]="activeFilters.selectedTypes" + > + <mat-option + *ngFor="let assetType of allAssetTypes; let i = index" + [value]="assetType" + class="smaller-font-size" + > + {{ assetType.label }} + </mat-option> + </mat-select> + </mat-form-field> +</sp-asset-browser-filter-outer> diff --git a/ui/src/app/core-ui/asset-browser/asset-browser-toolbar/asset-browser-filter/asset-browser-filter-type/asset-browser-filter-type.component.ts b/ui/src/app/core-ui/asset-browser/asset-browser-toolbar/asset-browser-filter/asset-browser-filter-type/asset-browser-filter-type.component.ts new file mode 100644 index 0000000000..7c78f320a9 --- /dev/null +++ b/ui/src/app/core-ui/asset-browser/asset-browser-toolbar/asset-browser-filter/asset-browser-filter-type/asset-browser-filter-type.component.ts @@ -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 { Component, Input, OnInit } from '@angular/core'; +import { + Isa95TypeDesc, + Isa95TypeService, +} from '@streampipes/platform-services'; +import { AssetFilter } from '../../../asset-browser.model'; + +@Component({ + selector: 'sp-asset-browser-filter-type', + templateUrl: 'asset-browser-filter-type.component.html', + styleUrls: ['../asset-browser-filter.component.scss'], +}) +export class AssetBrowserFilterTypeComponent implements OnInit { + allAssetTypes: Isa95TypeDesc[] = []; + + @Input() + activeFilters: AssetFilter; + + constructor(private typeService: Isa95TypeService) {} + + ngOnInit() { + this.allAssetTypes = this.typeService + .getTypeDescriptions() + .sort((a, b) => a.label.localeCompare(b.label)); + } + + compare(o1: Isa95TypeDesc, o2: Isa95TypeDesc): boolean { + return o1.type === o2.type; + } + + selectAll(): void { + this.activeFilters.selectedTypes = [...this.allAssetTypes]; + } + + selectNone(): void { + this.activeFilters.selectedTypes = []; + } +} diff --git a/ui/src/app/core-ui/asset-browser/asset-browser-toolbar/asset-browser-filter/asset-browser-filter.component.html b/ui/src/app/core-ui/asset-browser/asset-browser-toolbar/asset-browser-filter/asset-browser-filter.component.html new file mode 100644 index 0000000000..fa78b95f08 --- /dev/null +++ b/ui/src/app/core-ui/asset-browser/asset-browser-toolbar/asset-browser-filter/asset-browser-filter.component.html @@ -0,0 +1,51 @@ +<!-- + ~ 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="menu-content" + (click)="$event.stopPropagation()" + *ngIf="activeFilters" +> + <div fxLayout="column" fxLayoutGap="10px"> + <sp-asset-browser-filter-type [activeFilters]="activeFilters"> + </sp-asset-browser-filter-type> + <sp-asset-browser-filter-sites + [sites]="assetBrowserData.sites" + [activeFilters]="activeFilters" + > + </sp-asset-browser-filter-sites> + <sp-asset-browser-filter-labels + [labels]="assetBrowserData.labels" + [activeFilters]="activeFilters" + > + </sp-asset-browser-filter-labels> + <mat-divider></mat-divider> + <div fxLayout="row" fxLayoutAlign="end center"> + <button + mat-raised-button + class="mat-basic" + (click)="resetFilters()" + > + Reset filters + </button> + <button mat-raised-button color="accent" (click)="applyFilters()"> + Apply + </button> + </div> + </div> +</div> diff --git a/ui/projects/streampipes/shared-ui/src/lib/components/sp-label/sp-label.component.scss b/ui/src/app/core-ui/asset-browser/asset-browser-toolbar/asset-browser-filter/asset-browser-filter.component.scss similarity index 54% copy from ui/projects/streampipes/shared-ui/src/lib/components/sp-label/sp-label.component.scss copy to ui/src/app/core-ui/asset-browser/asset-browser-toolbar/asset-browser-filter/asset-browser-filter.component.scss index 778b5b97de..4b836ee4f5 100644 --- a/ui/projects/streampipes/shared-ui/src/lib/components/sp-label/sp-label.component.scss +++ b/ui/src/app/core-ui/asset-browser/asset-browser-toolbar/asset-browser-filter/asset-browser-filter.component.scss @@ -16,16 +16,46 @@ * */ -.basic-label { - border-radius: 15px; - min-width: 50px; - padding: 5px 7px; - border: 1px solid; - display: inline-block; - text-align: center; -} - -.small-label { - font-size: 10pt; - padding: 2px 8px; +.menu-content { + padding: 10px; + min-width: 550px; +} + +.filter-header { + font-weight: bold; + white-space: nowrap; +} + +.filter-section { + padding: 5px 10px 5px 5px; + border: 1px solid var(--color-bg-3); + background: var(--color-bg-1); +} + +.smaller-font-size { + font-size: smaller; +} + +.filter-selection { + min-width: 300px; + margin-bottom: -10px; +} + +.fs-icon { + font-size: 16px; +} + +.active-filters { + border-radius: 50%; + background: var(--color-primary); + color: var(--color-accent); + width: 20px; + height: 20px; + line-height: 20px; + font-weight: bold; +} + +.empty { + background: var(--color-bg-2); + color: var(--color-bg-3); } diff --git a/ui/src/app/core-ui/asset-browser/asset-browser-toolbar/asset-browser-filter/asset-browser-filter.component.ts b/ui/src/app/core-ui/asset-browser/asset-browser-toolbar/asset-browser-filter/asset-browser-filter.component.ts new file mode 100644 index 0000000000..d18e37ff19 --- /dev/null +++ b/ui/src/app/core-ui/asset-browser/asset-browser-toolbar/asset-browser-filter/asset-browser-filter.component.ts @@ -0,0 +1,69 @@ +/* + * 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, + EventEmitter, + Input, + OnDestroy, + OnInit, + Output, +} from '@angular/core'; +import { AssetBrowserData, AssetFilter } from '../../asset-browser.model'; +import { Subscription } from 'rxjs'; +import { SpAssetBrowserService } from '../../asset-browser.service'; + +@Component({ + selector: 'sp-asset-browser-filter', + templateUrl: 'asset-browser-filter.component.html', + styleUrl: 'asset-browser-filter.component.scss', +}) +export class AssetBrowserFilterComponent implements OnInit, OnDestroy { + @Input() + assetBrowserData: AssetBrowserData; + activeFilters: AssetFilter; + + filterSub: Subscription; + + @Output() + closeMenu: EventEmitter<void> = new EventEmitter(); + + constructor(private assetBrowserService: SpAssetBrowserService) {} + + ngOnInit() { + this.filterSub = this.assetBrowserService.filter$.subscribe( + activeFilters => { + this.activeFilters = activeFilters; + }, + ); + } + + applyFilters(): void { + this.assetBrowserService.applyFilters(this.activeFilters); + this.closeMenu.emit(); + } + + resetFilters(): void { + this.assetBrowserService.resetFilters(); + this.closeMenu.emit(); + } + + ngOnDestroy() { + this.filterSub?.unsubscribe(); + } +} diff --git a/ui/src/app/core-ui/asset-browser/asset-browser-toolbar/asset-browser-toolbar.component.html b/ui/src/app/core-ui/asset-browser/asset-browser-toolbar/asset-browser-toolbar.component.html new file mode 100644 index 0000000000..dce9bb6e8d --- /dev/null +++ b/ui/src/app/core-ui/asset-browser/asset-browser-toolbar/asset-browser-toolbar.component.html @@ -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. + ~ + --> + +<div fxFlex fxLayoutAlign="start center" *ngIf="expanded"> + <h4 class="ml-5">Browse assets</h4> +</div> +<div fxLayoutAlign="end center"> + <button + mat-icon-button + *ngIf="expanded" + color="accent" + [matMenuTriggerFor]="menu" + #menuTrigger="matMenuTrigger" + matTooltip="Filter assets" + (menuClosed)="menuTrigger.closeMenu()" + > + <mat-icon>filter_alt</mat-icon> + </button> + <mat-menu #menu="matMenu" fxFlex="100" class="large-menu"> + <sp-asset-browser-filter + (closeMenu)="closeMenu()" + [assetBrowserData]="assetBrowserData" + > + </sp-asset-browser-filter> + </mat-menu> + <button + mat-icon-button + color="accent" + (click)="toggleExpanded.emit(!expanded)" + > + <mat-icon>{{ + expanded + ? 'keyboard_double_arrow_left' + : 'keyboard_double_arrow_right' + }}</mat-icon> + </button> +</div> diff --git a/ui/projects/streampipes/shared-ui/src/lib/components/sp-label/sp-label.component.scss b/ui/src/app/core-ui/asset-browser/asset-browser-toolbar/asset-browser-toolbar.component.ts similarity index 55% copy from ui/projects/streampipes/shared-ui/src/lib/components/sp-label/sp-label.component.scss copy to ui/src/app/core-ui/asset-browser/asset-browser-toolbar/asset-browser-toolbar.component.ts index 778b5b97de..965f6d6d74 100644 --- a/ui/projects/streampipes/shared-ui/src/lib/components/sp-label/sp-label.component.scss +++ b/ui/src/app/core-ui/asset-browser/asset-browser-toolbar/asset-browser-toolbar.component.ts @@ -1,4 +1,4 @@ -/*! +/* * 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. @@ -16,16 +16,33 @@ * */ -.basic-label { - border-radius: 15px; - min-width: 50px; - padding: 5px 7px; - border: 1px solid; - display: inline-block; - text-align: center; -} +import { + Component, + EventEmitter, + Input, + Output, + ViewChild, +} from '@angular/core'; +import { AssetBrowserData } from '../asset-browser.model'; +import { MatMenuTrigger } from '@angular/material/menu'; + +@Component({ + selector: 'sp-asset-browser-toolbar', + templateUrl: 'asset-browser-toolbar.component.html', +}) +export class AssetBrowserToolbarComponent { + @Input() + expanded: boolean; + + @Output() + toggleExpanded = new EventEmitter<boolean>(); + + @Input() + assetBrowserData: AssetBrowserData; + + @ViewChild('menuTrigger') menu: MatMenuTrigger; -.small-label { - font-size: 10pt; - padding: 2px 8px; + closeMenu(): void { + this.menu.closeMenu(); + } } diff --git a/ui/src/app/core-ui/asset-browser/asset-browser.component.html b/ui/src/app/core-ui/asset-browser/asset-browser.component.html new file mode 100644 index 0000000000..0f19da89e8 --- /dev/null +++ b/ui/src/app/core-ui/asset-browser/asset-browser.component.html @@ -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. + ~ + --> + +<div fxLayout="row" fxFlex="100"> + <div [fxFlex]="expanded ? browserWidth : '75px'"> + <sp-basic-view [showBackLink]="false"> + <div nav fxLayoutAlign="start center" fxFlex="100"> + @if (assetBrowserData) { + <sp-asset-browser-toolbar + fxFlex="100" + [assetBrowserData]="assetBrowserData" + [expanded]="expanded" + (toggleExpanded)="toggleExpanded($event)" + > + </sp-asset-browser-toolbar> + } + </div> + @if (assetBrowserData?.assets?.length > 0 && expanded) { + <sp-asset-browser-hierarchy + [assetBrowserData]="assetBrowserData" + [assetSelectionMode]="assetSelectionMode" + [resourceCount]="resourceCount" + [filteredAssetLinkType]="filteredAssetLinkType" + (selectedAssetEmitter)="applyAssetFilter($event)" + class="asset-hierarchy" + > + </sp-asset-browser-hierarchy> + } @else if (assetBrowserData?.assets?.length === 0 && expanded) { + <div + fxLayoutAlign="center center" + fxLayout="column" + fxFlex="100" + > + <span class="asset-creation-hint" + >No assets configured yet - use assets to better + organize resources!</span + > + <button + mat-button + color="accent" + class="mt-10" + (click)="navigateToAssetManagement()" + > + Manage assets + </button> + </div> + } + @if (!expanded) { + <div + *ngIf="!expanded" + class="asset-hierarchy asset-browser-text" + fxLayoutAlign="center center" + > + Asset Browser + </div> + } + </sp-basic-view> + </div> + <div fxFlex> + <ng-content> </ng-content> + </div> +</div> diff --git a/ui/projects/streampipes/shared-ui/src/lib/components/sp-label/sp-label.component.scss b/ui/src/app/core-ui/asset-browser/asset-browser.component.scss similarity index 75% copy from ui/projects/streampipes/shared-ui/src/lib/components/sp-label/sp-label.component.scss copy to ui/src/app/core-ui/asset-browser/asset-browser.component.scss index 778b5b97de..1f145dbcb4 100644 --- a/ui/projects/streampipes/shared-ui/src/lib/components/sp-label/sp-label.component.scss +++ b/ui/src/app/core-ui/asset-browser/asset-browser.component.scss @@ -16,16 +16,21 @@ * */ -.basic-label { - border-radius: 15px; - min-width: 50px; - padding: 5px 7px; - border: 1px solid; - display: inline-block; +.asset-hierarchy { + height: calc(100vh - 145px); + max-height: calc(100vh - 145px); + overflow-y: auto; + margin-left: 5px; + margin-top: 5px; +} + +.asset-browser-text { + text-orientation: mixed; + writing-mode: tb-rl; text-align: center; } -.small-label { +.asset-creation-hint { font-size: 10pt; - padding: 2px 8px; + text-align: center; } diff --git a/ui/src/app/core-ui/asset-browser/asset-browser.component.ts b/ui/src/app/core-ui/asset-browser/asset-browser.component.ts new file mode 100644 index 0000000000..4884f8a052 --- /dev/null +++ b/ui/src/app/core-ui/asset-browser/asset-browser.component.ts @@ -0,0 +1,110 @@ +/* + * 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, + EventEmitter, + Input, + OnDestroy, + OnInit, + Output, +} from '@angular/core'; +import { SpAssetBrowserService } from './asset-browser.service'; +import { AssetBrowserData } from './asset-browser.model'; +import { Subscription } from 'rxjs'; +import { SpAsset } from '@streampipes/platform-services'; +import { Router } from '@angular/router'; + +@Component({ + selector: 'sp-asset-browser', + templateUrl: 'asset-browser.component.html', + styleUrls: ['./asset-browser.component.scss'], +}) +export class AssetBrowserComponent implements OnInit, OnDestroy { + @Input() + showResources = false; + + @Input() + browserWidth = 20; + + @Input() + filteredAssetLinkType: string; + + @Input() + resourceCount = 0; + + @Input() + assetSelectionMode = false; + + @Output() + filterIdsEmitter: EventEmitter<Set<string>> = new EventEmitter< + Set<string> + >(); + + @Output() + selectedAssetIdEmitter: EventEmitter<string> = new EventEmitter<string>(); + + assetBrowserData: AssetBrowserData; + + assetBrowserDataSub: Subscription; + expandedSub: Subscription; + + expanded = true; + + constructor( + private assetBrowserService: SpAssetBrowserService, + private router: Router, + ) {} + + ngOnInit(): void { + this.assetBrowserDataSub = + this.assetBrowserService.assetData$.subscribe(assetData => { + this.assetBrowserData = assetData; + }); + this.expandedSub = this.assetBrowserService.expanded$.subscribe( + expanded => (this.expanded = expanded), + ); + } + + toggleExpanded(event: boolean): void { + this.assetBrowserService.expanded$.next(event); + } + + applyAssetFilter(asset: SpAsset) { + const elementIds = new Set<string>(); + if (asset.assetId !== '_root') { + this.assetBrowserService.collectElementIds( + asset, + this.filteredAssetLinkType, + elementIds, + ); + this.filterIdsEmitter.emit(elementIds); + } + this.filterIdsEmitter.emit(elementIds); + this.selectedAssetIdEmitter.emit(asset.assetId); + } + + navigateToAssetManagement(): void { + this.router.navigate(['assets', 'overview']); + } + + ngOnDestroy(): void { + this.assetBrowserDataSub?.unsubscribe(); + this.expandedSub?.unsubscribe(); + } +} diff --git a/ui/projects/streampipes/shared-ui/src/lib/components/sp-label/sp-label.component.scss b/ui/src/app/core-ui/asset-browser/asset-browser.model.ts similarity index 66% copy from ui/projects/streampipes/shared-ui/src/lib/components/sp-label/sp-label.component.scss copy to ui/src/app/core-ui/asset-browser/asset-browser.model.ts index 778b5b97de..4523873e0e 100644 --- a/ui/projects/streampipes/shared-ui/src/lib/components/sp-label/sp-label.component.scss +++ b/ui/src/app/core-ui/asset-browser/asset-browser.model.ts @@ -1,4 +1,4 @@ -/*! +/* * 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. @@ -16,16 +16,23 @@ * */ -.basic-label { - border-radius: 15px; - min-width: 50px; - padding: 5px 7px; - border: 1px solid; - display: inline-block; - text-align: center; +import { + AssetLink, + AssetSiteDesc, + Isa95TypeDesc, + SpAsset, + SpLabel, +} from '@streampipes/platform-services'; + +export interface AssetBrowserData { + assets: SpAsset[]; + assetLinks: AssetLink[]; + sites: AssetSiteDesc[]; + labels: SpLabel[]; } -.small-label { - font-size: 10pt; - padding: 2px 8px; +export interface AssetFilter { + selectedSites: AssetSiteDesc[]; + selectedTypes: Isa95TypeDesc[]; + selectedLabels: SpLabel[]; } diff --git a/ui/src/app/core-ui/asset-browser/asset-browser.service.ts b/ui/src/app/core-ui/asset-browser/asset-browser.service.ts new file mode 100644 index 0000000000..01ff24133e --- /dev/null +++ b/ui/src/app/core-ui/asset-browser/asset-browser.service.ts @@ -0,0 +1,190 @@ +/* + * 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 { BehaviorSubject, zip } from 'rxjs'; +import { + AssetSiteDesc, + GenericStorageService, + Isa95TypeDesc, + Isa95TypeService, + SpAsset, + SpLabel, +} from '@streampipes/platform-services'; +import { AssetConstants } from '../../assets/constants/asset.constants'; +import { AssetBrowserData, AssetFilter } from './asset-browser.model'; + +@Injectable({ providedIn: 'root' }) +export class SpAssetBrowserService { + assetData$ = new BehaviorSubject<AssetBrowserData>(undefined); + expanded$ = new BehaviorSubject<boolean>(true); + filter$ = new BehaviorSubject<AssetFilter>(undefined); + + loadedAssetData: AssetBrowserData; + + constructor( + private genericStorageService: GenericStorageService, + private typeService: Isa95TypeService, + ) { + this.loadAssetData(); + } + + loadAssetData(): void { + const assetsReq = this.genericStorageService.getAllDocuments( + AssetConstants.ASSET_APP_DOC_NAME, + ); + const assetLinksReq = this.genericStorageService.getAllDocuments( + AssetConstants.ASSET_LINK_TYPES_DOC_NAME, + ); + const sitesReq = this.genericStorageService.getAllDocuments( + AssetConstants.ASSET_SITES_APP_DOC_NAME, + ); + const labelsReq = + this.genericStorageService.getAllDocuments('sp-labels'); + + zip([assetsReq, assetLinksReq, sitesReq, labelsReq]).subscribe(res => { + this.loadedAssetData = { + assets: res[0].sort((a, b) => + a.assetName.localeCompare(b.assetName), + ), + assetLinks: res[1], + sites: res[2], + labels: res[3].sort((a, b) => a.label.localeCompare(b.label)), + }; + this.assetData$.next(this.loadedAssetData); + this.reloadFilters(); + }); + } + + private reloadFilters(): void { + const data = this.assetData$.getValue(); + const filters: AssetFilter = { + selectedSites: [...data.sites].sort((a, b) => + a.label.localeCompare(b.label), + ), + selectedLabels: [], + selectedTypes: [...this.typeService.getTypeDescriptions()], + }; + this.filter$.next(filters); + } + + resetFilters(): void { + this.assetData$.next(this.loadedAssetData); + this.reloadFilters(); + } + + applyFilters(filter: AssetFilter) { + const clonedLoadedAssetData = JSON.parse( + JSON.stringify(this.loadedAssetData), + ) as AssetBrowserData; + const filteredAssets = clonedLoadedAssetData.assets + .filter( + a => + this.allSelected( + this.typeService.getTypeDescriptions(), + filter.selectedTypes, + ) || this.filterType(a, filter.selectedTypes), + ) + .filter( + a => + this.allSelected( + clonedLoadedAssetData.sites, + filter.selectedSites, + ) || this.filterSites(a, filter.selectedSites), + ) + .filter(a => this.filterLabels(a, filter.selectedLabels)); + + this.assetData$.next({ + assets: filteredAssets, + assetLinks: clonedLoadedAssetData.assetLinks, + sites: clonedLoadedAssetData.sites, + labels: clonedLoadedAssetData.labels, + }); + } + + private allSelected(items: any[], selected: any[]) { + return items.length === selected.length; + } + + private filterType( + asset: SpAsset, + selectedTypes: Isa95TypeDesc[], + ): boolean { + const matchesSelf = selectedTypes.some( + type => type.type === asset.assetType?.isa95AssetType, + ); + + if (asset.assets?.length) { + asset.assets = asset.assets + .map(a => ({ ...a })) + .filter(a => this.filterType(a, selectedTypes)); + return matchesSelf || asset.assets.length > 0; + } + + return matchesSelf; + } + + private filterSites( + asset: SpAsset, + selectedSites: AssetSiteDesc[], + ): boolean { + return ( + selectedSites.find(site => asset.assetSite?.siteId === site._id) !== + undefined + ); + } + + private filterLabels(asset: SpAsset, selectedLabels: SpLabel[]): boolean { + const labelIds = asset.labelIds || []; + const matchesSelf = selectedLabels.every(label => + labelIds.includes(label._id), + ); + + if (asset.assets?.length) { + asset.assets = asset.assets + .map(a => ({ ...a })) + .filter(a => this.filterLabels(a, selectedLabels)); + return matchesSelf || asset.assets.length > 0; + } + + return matchesSelf; + } + + collectElementIds( + asset: SpAsset, + filteredLinkType: string, + elementIds: Set<string>, + ): void { + const assetLinkValues = this.findAssetLinkValues( + asset, + filteredLinkType, + ); + assetLinkValues.forEach(v => elementIds.add(v)); + if (asset.assets !== undefined) { + asset.assets.forEach((a: SpAsset) => { + this.collectElementIds(a, filteredLinkType, elementIds); + }); + } + } + + findAssetLinkValues(asset: SpAsset, filteredLinkType: string): string[] { + return asset.assetLinks + .filter(a => a.linkType === filteredLinkType) + .map(a => a.resourceId); + } +} diff --git a/ui/src/app/core-ui/core-ui.module.ts b/ui/src/app/core-ui/core-ui.module.ts index bf9a91ff30..f44ef42248 100644 --- a/ui/src/app/core-ui/core-ui.module.ts +++ b/ui/src/app/core-ui/core-ui.module.ts @@ -116,6 +116,16 @@ import { StaticTreeInputNodeDetailsComponent } from './static-properties/static- import { SingleMarkerMapComponent } from './single-marker-map/single-marker-map.component'; import { LeafletModule } from '@asymmetrik/ngx-leaflet'; import { StaticTreeInputTextEditorComponent } from './static-properties/static-runtime-resolvable-tree-input/static-tree-input-text-editor/static-tree-input-text-editor.component'; +import { AssetBrowserComponent } from './asset-browser/asset-browser.component'; +import { AssetBrowserToolbarComponent } from './asset-browser/asset-browser-toolbar/asset-browser-toolbar.component'; +import { AssetBrowserHierarchyComponent } from './asset-browser/asset-browser-hierarchy/asset-browser-hierarchy.component'; +import { AssetBrowserNodeComponent } from './asset-browser/asset-browser-hierarchy/asset-browser-node/asset-browser-node.component'; +import { AssetBrowserFilterComponent } from './asset-browser/asset-browser-toolbar/asset-browser-filter/asset-browser-filter.component'; +import { AssetBrowserFilterLabelsComponent } from './asset-browser/asset-browser-toolbar/asset-browser-filter/asset-browser-filter-labels/asset-browser-filter-labels.component'; +import { AssetBrowserFilterSitesComponent } from './asset-browser/asset-browser-toolbar/asset-browser-filter/asset-browser-filter-sites/asset-browser-filter-sites.component'; +import { AssetBrowserFilterTypeComponent } from './asset-browser/asset-browser-toolbar/asset-browser-filter/asset-browser-filter-type/asset-browser-filter-type.component'; +import { AssetBrowserFilterOuterComponent } from './asset-browser/asset-browser-toolbar/asset-browser-filter/asset-browser-filter-outer/asset-browser-filter-outer.component'; +import { AssetBrowserNodeInfoComponent } from './asset-browser/asset-browser-hierarchy/asset-browser-node/asset-browser-node-info/asset-browser-node-info.component'; @NgModule({ imports: [ @@ -168,6 +178,16 @@ import { StaticTreeInputTextEditorComponent } from './static-properties/static-r LeafletModule, ], declarations: [ + AssetBrowserComponent, + AssetBrowserFilterComponent, + AssetBrowserFilterLabelsComponent, + AssetBrowserFilterOuterComponent, + AssetBrowserFilterSitesComponent, + AssetBrowserFilterTypeComponent, + AssetBrowserHierarchyComponent, + AssetBrowserNodeComponent, + AssetBrowserNodeInfoComponent, + AssetBrowserToolbarComponent, DataDownloadDialogComponent, DateInputComponent, DisplayRecommendedPipe, @@ -223,6 +243,7 @@ import { StaticTreeInputTextEditorComponent } from './static-properties/static-r ], providers: [MatDatepickerModule, DisplayRecommendedPipe], exports: [ + AssetBrowserComponent, DataDownloadDialogComponent, DateInputComponent, PipelineElementTemplateConfigComponent, diff --git a/ui/src/app/pipelines/pipelines.component.html b/ui/src/app/pipelines/pipelines.component.html index 73338ecaba..0cb8afd7a1 100644 --- a/ui/src/app/pipelines/pipelines.component.html +++ b/ui/src/app/pipelines/pipelines.component.html @@ -16,92 +16,102 @@ ~ --> -<sp-basic-view [showBackLink]="false" [padding]="true"> - <div - nav - fxFlex="100" - fxLayoutAlign="start center" - fxLayout="row" - class="pl-10" - > - <button - mat-button - mat-raised-button - color="accent" - (click)="navigateToPipelineEditor()" - data-cy="pipelines-navigate-to-editor" +<sp-asset-browser + filteredAssetLinkType="pipeline" + [resourceCount]="pipelines.length" + (filterIdsEmitter)="applyPipelineFilters($event)" +> + <sp-basic-view [showBackLink]="false" [padding]="true"> + <div + nav + fxFlex="100" + fxLayoutAlign="start center" + fxLayout="row" + class="pl-10" > - <i class="material-icons">add</i> New pipeline - </button> - <button - class="mr-10" - mat-button - color="accent" - (click)="startAllPipelines(true)" - [disabled]="checkCurrentSelectionStatus(false)" - *ngIf="hasPipelineWritePrivileges" - > - <mat-icon>play_arrow</mat-icon> - <span>Start all pipelines</span> - </button> - <button - mat-button - color="accent" - (click)="startAllPipelines(false)" - [disabled]="checkCurrentSelectionStatus(true)" - *ngIf="hasPipelineWritePrivileges" - > - <mat-icon>stop</mat-icon> - <span>Stop all pipelines</span> - </button> - <span fxFlex></span> - <button - mat-icon-button - color="accent" - (click)="startPipelineTour()" - [matTooltip]="'Tutorial'" - [disabled]="tutorialActive" - > - <i class="material-icons"> school </i> - </button> - <button - mat-icon-button - color="accent" - matTooltip="Refresh pipelines" - matTooltipPosition="above" - (click)="refreshPipelines()" - > - <i class="material-icons"> refresh </i> - </button> - </div> - <div fxFlex="100" fxLayout="column"> - <div fxLayout="column"> - <sp-basic-header-title-component - title="Pipelines" - ></sp-basic-header-title-component> - <div fxFlex="100" fxLayout="row" fxLayoutAlign="center start"> - <div fxFlex="90"> - <sp-pipeline-overview - [pipelines]="pipelines" - (refreshPipelinesEmitter)="refreshPipelines()" - *ngIf="pipelinesReady" - ></sp-pipeline-overview> - </div> - </div> - <div fxFlex="100" fxLayout="column" style="margin-top: 20px"> + <button + mat-button + mat-raised-button + color="accent" + (click)="navigateToPipelineEditor()" + data-cy="pipelines-navigate-to-editor" + > + <i class="material-icons">add</i> New pipeline + </button> + <button + class="mr-10" + mat-button + color="accent" + (click)="startAllPipelines(true)" + [disabled]="checkCurrentSelectionStatus(false)" + *ngIf="hasPipelineWritePrivileges" + > + <mat-icon>play_arrow</mat-icon> + <span>Start all pipelines</span> + </button> + <button + mat-button + color="accent" + (click)="startAllPipelines(false)" + [disabled]="checkCurrentSelectionStatus(true)" + *ngIf="hasPipelineWritePrivileges" + > + <mat-icon>stop</mat-icon> + <span>Stop all pipelines</span> + </button> + <span fxFlex></span> + <button + mat-icon-button + color="accent" + (click)="startPipelineTour()" + [matTooltip]="'Tutorial'" + [disabled]="tutorialActive" + > + <i class="material-icons"> school </i> + </button> + <button + mat-icon-button + color="accent" + matTooltip="Refresh pipelines" + matTooltipPosition="above" + (click)="getPipelines()" + > + <i class="material-icons"> refresh </i> + </button> + </div> + <div fxFlex="100" fxLayout="column"> + <div fxLayout="column"> <sp-basic-header-title-component - title="Functions" + title="Pipelines" ></sp-basic-header-title-component> <div fxFlex="100" fxLayout="row" fxLayoutAlign="center start"> <div fxFlex="90"> - <sp-functions-overview - [functions]="functions" - *ngIf="functionsReady" - > - </sp-functions-overview> + <sp-pipeline-overview + [pipelines]="filteredPipelines" + (refreshPipelinesEmitter)="getPipelines()" + *ngIf="pipelinesReady" + ></sp-pipeline-overview> + </div> + </div> + <div fxFlex="100" fxLayout="column" style="margin-top: 20px"> + <sp-basic-header-title-component + title="Functions" + ></sp-basic-header-title-component> + <div + fxFlex="100" + fxLayout="row" + fxLayoutAlign="center start" + > + <div fxFlex="90"> + <sp-functions-overview + [functions]="functions" + *ngIf="functionsReady" + > + </sp-functions-overview> + </div> </div> </div> </div> </div> - </div> -</sp-basic-view> + </sp-basic-view> +</sp-asset-browser> diff --git a/ui/src/app/pipelines/pipelines.component.ts b/ui/src/app/pipelines/pipelines.component.ts index 1a4ef6da47..a80eedffd3 100644 --- a/ui/src/app/pipelines/pipelines.component.ts +++ b/ui/src/app/pipelines/pipelines.component.ts @@ -47,6 +47,7 @@ import { Subscription } from 'rxjs'; export class PipelinesComponent implements OnInit, OnDestroy { pipeline: Pipeline; pipelines: Pipeline[] = []; + filteredPipelines: Pipeline[] = []; starting: boolean; stopping: boolean; @@ -111,13 +112,24 @@ export class PipelinesComponent implements OnInit, OnDestroy { this.pipelines = []; this.pipelineService.getPipelines().subscribe(pipelines => { this.pipelines = pipelines; - this.pipelinesReady = true; + this.applyPipelineFilters(new Set<string>()); }); } + applyPipelineFilters(elementIds: Set<string>) { + if (elementIds.size == 0) { + this.filteredPipelines = this.pipelines; + } else { + this.filteredPipelines = this.pipelines.filter(p => + elementIds.has(p.elementId), + ); + } + this.pipelinesReady = true; + } + checkCurrentSelectionStatus(status) { let active = true; - this.pipelines.forEach(pipeline => { + this.filteredPipelines.forEach(pipeline => { if (pipeline.running === status) { active = false; } @@ -132,22 +144,18 @@ export class PipelinesComponent implements OnInit, OnDestroy { title: (action ? 'Start' : 'Stop') + ' all pipelines', width: '70vw', data: { - pipelines: this.pipelines, + pipelines: this.filteredPipelines, action: action, }, }); dialogRef.afterClosed().subscribe(data => { if (data) { - this.refreshPipelines(); + this.getPipelines(); } }); } - refreshPipelines() { - this.getPipelines(); - } - startPipelineTour(): void { this.shepherdService.startPipelineTour(); }
