This is an automated email from the ASF dual-hosted git repository.
riemer pushed a commit to branch dev
in repository https://gitbox.apache.org/repos/asf/streampipes.git
The following commit(s) were added to refs/heads/dev by this push:
new ebf4b13ba0 feat(#4062): Improve entry page (#4063)
ebf4b13ba0 is described below
commit ebf4b13ba07c409f3f6a72691258218f3dbbc8ed
Author: Dominik Riemer <[email protected]>
AuthorDate: Wed Dec 17 14:10:39 2025 +0100
feat(#4062): Improve entry page (#4063)
---
.../user/management/util/UserInfoUtil.java | 11 +-
.../utils/dataExplorer/DataExplorerUtils.ts | 1 +
...rTest.spec.ts => assetFilterTest.smoke.spec.ts} | 0
ui/deployment/home.service.mst | 3 +-
ui/deployment/modules.yml | 4 +
.../src/lib/services/isa95-type.service.ts | 3 +
.../asset-browser-hierarchy.component.html | 70 --------
.../asset-browser-hierarchy.component.ts | 128 --------------
.../asset-browser-node-info.component.html | 39 -----
.../asset-browser-node-info.component.ts | 63 -------
.../asset-browser-node.component.html | 76 --------
.../asset-browser-node.component.ts | 99 -----------
.../asset-browser-toolbar.component.html | 2 +-
.../asset-browser/asset-browser.component.html | 84 ---------
.../asset-browser/asset-browser.component.ts | 131 --------------
.../asset-browser/asset-browser.service.ts | 4 +-
.../split-section/split-section.component.scss | 20 ++-
.../src/lib/services/colorization.service.ts | 14 +-
.../lib/services/local-storage-settings.service.ts | 8 +
.../shared-ui/src/lib/shared-ui.module.ts | 9 -
.../streampipes/shared-ui/src/public-api.ts | 1 -
.../chart-container/chart-container.component.ts | 4 +-
.../edit-label/edit-label.component.ts | 1 +
.../label-configuration.component.html | 2 +-
.../pipeline-element-icon-stand.component.ts | 2 +-
.../asset-link-chip/asset-link-chip.component.html | 26 +++
.../asset-link-chip.component.scss} | 65 ++++---
.../asset-link-chip/asset-link-chip.component.ts | 50 ++++++
.../asset-map-popup/asset-map-popup.component.html | 68 ++++++++
.../asset-map-popup/asset-map-popup.component.scss | 120 +++++++++++++
.../asset-map-popup/asset-map-popup.component.ts | 74 ++++++++
.../asset-map/home-asset-map.component.html | 30 ++++
.../asset-map/home-asset-map.component.scss} | 13 +-
.../asset-map/home-asset-map.component.ts | 191 +++++++++++++++++++++
.../asset-table-link-preview.component.html | 31 ++++
.../asset-table-link-preview.component.scss | 114 ++++++++++++
.../asset-table-link-preview.component.ts | 50 ++++++
.../asset-table/home-asset-table.component.html | 89 ++++++++++
.../asset-table/home-asset-table.component.scss} | 25 ++-
.../asset-table/home-asset-table.component.ts | 96 +++++++++++
ui/src/app/home/components/status.component.html | 53 ++----
ui/src/app/home/components/status.component.scss | 129 ++++++++++++--
ui/src/app/home/components/status.component.ts | 14 +-
.../home/components/welcome/welcome.component.html | 24 +++
.../components/welcome/welcome.component.scss} | 42 ++---
.../home/components/welcome/welcome.component.ts | 49 ++++++
ui/src/app/home/home.component.html | 134 ++++++++-------
ui/src/app/home/home.component.scss | 77 ++-------
ui/src/app/home/home.component.ts | 112 ++++++++----
ui/src/app/home/home.module.ts | 38 +++-
ui/src/app/home/models/home.model.ts | 1 +
ui/src/scss/sp/_variables.scss | 23 +--
ui/src/scss/sp/main.scss | 33 ++--
ui/src/scss/sp/pipeline-element-options.scss | 2 +-
54 files changed, 1494 insertions(+), 1058 deletions(-)
diff --git
a/streampipes-user-management/src/main/java/org/apache/streampipes/user/management/util/UserInfoUtil.java
b/streampipes-user-management/src/main/java/org/apache/streampipes/user/management/util/UserInfoUtil.java
index e89ae58afa..15bd09b576 100644
---
a/streampipes-user-management/src/main/java/org/apache/streampipes/user/management/util/UserInfoUtil.java
+++
b/streampipes-user-management/src/main/java/org/apache/streampipes/user/management/util/UserInfoUtil.java
@@ -23,6 +23,7 @@ import org.apache.streampipes.model.client.user.Principal;
import org.apache.streampipes.model.client.user.ServiceAccount;
import org.apache.streampipes.model.client.user.UserAccount;
+import java.util.Objects;
import java.util.Set;
public class UserInfoUtil {
@@ -35,7 +36,8 @@ public class UserInfoUtil {
private static UserInfo toUserInfo(UserAccount userAccount,
Set<String> roles) {
- UserInfo userInfo = prepareUserInfo(userAccount, roles);
+ var displayName = Objects.nonNull(userAccount.getFullName()) ?
userAccount.getFullName() : userAccount.getUsername();
+ UserInfo userInfo = prepareUserInfo(userAccount, roles, displayName);
userInfo.setShowTutorial(!userAccount.isHideTutorial());
userInfo.setHasAcknowledged(userAccount.isHasAcknowledged());
userInfo.setLanguage(userAccount.getLanguage());
@@ -44,14 +46,15 @@ public class UserInfoUtil {
private static UserInfo toServiceUserInfo(ServiceAccount serviceAccount,
Set<String> roles) {
- return prepareUserInfo(serviceAccount, roles);
+ return prepareUserInfo(serviceAccount, roles,
serviceAccount.getUsername());
}
private static UserInfo prepareUserInfo(Principal principal,
- Set<String> roles) {
+ Set<String> roles,
+ String displayName) {
UserInfo userInfo = new UserInfo();
userInfo.setUsername(principal.getUsername());
- userInfo.setDisplayName(principal.getUsername());
+ userInfo.setDisplayName(displayName);
userInfo.setRoles(roles);
return userInfo;
diff --git a/ui/cypress/support/utils/dataExplorer/DataExplorerUtils.ts
b/ui/cypress/support/utils/dataExplorer/DataExplorerUtils.ts
index 63b98e218b..a34bf7d20e 100644
--- a/ui/cypress/support/utils/dataExplorer/DataExplorerUtils.ts
+++ b/ui/cypress/support/utils/dataExplorer/DataExplorerUtils.ts
@@ -446,6 +446,7 @@ export class DataExplorerUtils {
* @param amountOfFilter the amount of filters that should be set. 0 if no
filter should be visible
*/
public static checkIfFilterIsSet(amountOfFilter: number) {
+ cy.wait(1000);
if (amountOfFilter === 0) {
cy.dataCy('design-panel-data-settings-filter-field').should(
'not.exist',
diff --git a/ui/cypress/tests/assetManagement/assetFilterTest.spec.ts
b/ui/cypress/tests/assetManagement/assetFilterTest.smoke.spec.ts
similarity index 100%
rename from ui/cypress/tests/assetManagement/assetFilterTest.spec.ts
rename to ui/cypress/tests/assetManagement/assetFilterTest.smoke.spec.ts
diff --git a/ui/deployment/home.service.mst b/ui/deployment/home.service.mst
index 7f28bec2c0..54b71fa1a9 100644
--- a/ui/deployment/home.service.mst
+++ b/ui/deployment/home.service.mst
@@ -67,7 +67,8 @@ export class HomeService {
dataFns: {{{statusBox.dataFns}}},
viewRoles: {{{statusBox.viewRoles}}},
createRoles: {{{statusBox.createRoles}}},
- icon: '{{{icon}}}'
+ icon: '{{{icon}}}',
+ assetLinkTypeId: '{{{assetLinkTypeId}}}'
}
{{/statusBox}}
},
diff --git a/ui/deployment/modules.yml b/ui/deployment/modules.yml
index 9021329c27..f43600590b 100644
--- a/ui/deployment/modules.yml
+++ b/ui/deployment/modules.yml
@@ -71,6 +71,7 @@ spConnect:
showStatusBox: true
statusBox:
link: "['connect']"
+ assetLinkTypeId: 'adapter'
createLinks: "['connect', 'create']"
title: 'Adapters'
createTitle: 'New adapter'
@@ -94,6 +95,7 @@ spPipelines:
showStatusBox: true
statusBox:
link: "['pipelines']"
+ assetLinkTypeId: 'pipeline'
createLinks: "['pipelines', 'create']"
title: 'Pipelines'
createTitle: 'New pipeline'
@@ -132,6 +134,7 @@ spDashboard:
showStatusBox: true
statusBox:
link: "['dashboard']"
+ assetLinkTypeId: 'dashboard'
createLinks: "['dashboard']"
title: 'Dashboards'
createTitle: 'New dashboard'
@@ -155,6 +158,7 @@ spChart:
showStatusBox: true
statusBox:
link: "['chart']"
+ assetLinkTypeId: 'chart'
createLinks: "['chart']"
title: 'Charts'
createTitle: 'New chart'
diff --git
a/ui/projects/streampipes/platform-services/src/lib/services/isa95-type.service.ts
b/ui/projects/streampipes/platform-services/src/lib/services/isa95-type.service.ts
index f9a0afbdaa..27aea68633 100644
---
a/ui/projects/streampipes/platform-services/src/lib/services/isa95-type.service.ts
+++
b/ui/projects/streampipes/platform-services/src/lib/services/isa95-type.service.ts
@@ -43,6 +43,9 @@ export class Isa95TypeService {
}
toLabel(type: Isa95Type): string {
+ if (!type) {
+ return '';
+ }
return type
.toLocaleLowerCase()
.replace(/_/g, ' ')
diff --git
a/ui/projects/streampipes/shared-ui/src/lib/components/asset-browser/asset-browser-hierarchy/asset-browser-hierarchy.component.html
b/ui/projects/streampipes/shared-ui/src/lib/components/asset-browser/asset-browser-hierarchy/asset-browser-hierarchy.component.html
deleted file mode 100644
index 1d4d18e8e4..0000000000
---
a/ui/projects/streampipes/shared-ui/src/lib/components/asset-browser/asset-browser-hierarchy/asset-browser-hierarchy.component.html
+++ /dev/null
@@ -1,70 +0,0 @@
-<!--
-~ 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.
-~
--->
-
-@if (selectedAsset) {
- <div fxLayout="column">
- <mat-tree
- [dataSource]="dataSource"
- [treeControl]="treeControl"
- class="sp-tree"
- #tree
- >
- <mat-nested-tree-node *matTreeNodeDef="let node; when: hasChild">
- <div class="mat-tree-node">
- @if (hasChild(0, node) && !hideAssetChildren) {
- <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>
- }
- @if (!hasChild(0, node)) {
- <span class="mat-icon-button placeholder-icon">
- <mat-icon
class="invisible">chevron_right</mat-icon>
- </span>
- }
- <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>
- @if (treeControl.isExpanded(node)) {
- <div 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/asset-browser/asset-browser-hierarchy/asset-browser-hierarchy.component.ts
b/ui/projects/streampipes/shared-ui/src/lib/components/asset-browser/asset-browser-hierarchy/asset-browser-hierarchy.component.ts
deleted file mode 100644
index 217bd99f9e..0000000000
---
a/ui/projects/streampipes/shared-ui/src/lib/components/asset-browser/asset-browser-hierarchy/asset-browser-hierarchy.component.ts
+++ /dev/null
@@ -1,128 +0,0 @@
-/*
- * 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,
- inject,
- 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';
-import { TranslateService } from '@ngx-translate/core';
-
-@Component({
- selector: 'sp-asset-browser-hierarchy',
- templateUrl: 'asset-browser-hierarchy.component.html',
- styleUrls: ['./asset-browser-hierarchy.component.scss'],
- standalone: false,
-})
-export class AssetBrowserHierarchyComponent implements OnChanges {
- translateService = inject(TranslateService);
-
- @Input()
- assetBrowserData: AssetBrowserData;
-
- @Input()
- allResourcesAlias = this.translateService.instant('Resources');
-
- @Input()
- assetSelectionMode = false;
-
- @Input()
- filteredAssetLinkType: string;
-
- @Input()
- hideAssetChildren = false;
-
- @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: this.translateService.instant(
- 'All {{allResourcesAlias}}',
- { allResourcesAlias: this.allResourcesAlias },
- ),
- assetDescription: '',
- assetLinks: [],
- assets: this.makeAssets(),
- assetType: undefined,
- assetSite: undefined,
- additionalData: {},
- labelIds: [],
- };
- }
-
- private cloneWithoutChildren(assets: SpAsset[]): SpAsset[] {
- return assets.map(a => ({
- ...a,
- assets: [],
- }));
- }
-
- makeAssets(): SpAsset[] {
- return this.hideAssetChildren
- ? this.cloneWithoutChildren(this.assetBrowserData.assets)
- : this.assetBrowserData.assets;
- }
-}
diff --git
a/ui/projects/streampipes/shared-ui/src/lib/components/asset-browser/asset-browser-hierarchy/asset-browser-node/asset-browser-node-info/asset-browser-node-info.component.html
b/ui/projects/streampipes/shared-ui/src/lib/components/asset-browser/asset-browser-hierarchy/asset-browser-node/asset-browser-node-info/asset-browser-node-info.component.html
deleted file mode 100644
index d8477ad760..0000000000
---
a/ui/projects/streampipes/shared-ui/src/lib/components/asset-browser/asset-browser-hierarchy/asset-browser-node/asset-browser-node-info/asset-browser-node-info.component.html
+++ /dev/null
@@ -1,39 +0,0 @@
-<!--
-~ 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">
- @if (assetType) {
- <div>
- <sp-label size="small" [labelText]="assetType"> </sp-label>
- </div>
- }
- @if (assetSite) {
- <div>
- <sp-label size="small" [labelText]="assetSite"> </sp-label>
- </div>
- }
- @for (label of labels; track label) {
- <div fxLayout="row wrap">
- <sp-label
- size="small"
- [labelBackground]="label.color"
- [labelText]="label.label"
- ></sp-label>
- </div>
- }
-</div>
diff --git
a/ui/projects/streampipes/shared-ui/src/lib/components/asset-browser/asset-browser-hierarchy/asset-browser-node/asset-browser-node-info/asset-browser-node-info.component.ts
b/ui/projects/streampipes/shared-ui/src/lib/components/asset-browser/asset-browser-hierarchy/asset-browser-node/asset-browser-node-info/asset-browser-node-info.component.ts
deleted file mode 100644
index 782137ee43..0000000000
---
a/ui/projects/streampipes/shared-ui/src/lib/components/asset-browser/asset-browser-hierarchy/asset-browser-node/asset-browser-node-info/asset-browser-node-info.component.ts
+++ /dev/null
@@ -1,63 +0,0 @@
-/*
- * 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'],
- standalone: false,
-})
-export class AssetBrowserNodeInfoComponent implements OnInit {
- @Input()
- asset: SpAsset;
-
- @Input()
- assetBrowserData: AssetBrowserData;
-
- 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/projects/streampipes/shared-ui/src/lib/components/asset-browser/asset-browser-hierarchy/asset-browser-node/asset-browser-node.component.html
b/ui/projects/streampipes/shared-ui/src/lib/components/asset-browser/asset-browser-hierarchy/asset-browser-node/asset-browser-node.component.html
deleted file mode 100644
index 483021fe4a..0000000000
---
a/ui/projects/streampipes/shared-ui/src/lib/components/asset-browser/asset-browser-hierarchy/asset-browser-node/asset-browser-node.component.html
+++ /dev/null
@@ -1,76 +0,0 @@
-<!--
-~ 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
- fxLayoutAlign="start center"
- [ngClass]="
- nodeResourceCount > 0 || assetSelectionMode
- ? ''
- : 'asset-node-disabled'
- "
- >
- <span fxLayoutAlign="start center">{{ node.assetName }} </span>
- </div>
- <div fxLayoutAlign="end center">
- <div fxLayoutAlign="center center" fxLayout="column">
- @if (hasContextInfo) {
- <mat-icon
- (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>
- @if (!assetSelectionMode) {
- <span
- class="resource-count"
- [ngClass]="
- nodeResourceCount > 0 ? '' : 'resource-count-disabled'
- "
- >{{ nodeResourceCount }}</span
- >
- }
- </div>
-</div>
diff --git
a/ui/projects/streampipes/shared-ui/src/lib/components/asset-browser/asset-browser-hierarchy/asset-browser-node/asset-browser-node.component.ts
b/ui/projects/streampipes/shared-ui/src/lib/components/asset-browser/asset-browser-hierarchy/asset-browser-node/asset-browser-node.component.ts
deleted file mode 100644
index a791a4edc6..0000000000
---
a/ui/projects/streampipes/shared-ui/src/lib/components/asset-browser/asset-browser-hierarchy/asset-browser-node/asset-browser-node.component.ts
+++ /dev/null
@@ -1,99 +0,0 @@
-/*
- * 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'],
- standalone: false,
-})
-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.assetSelectionMode) {
- this.selectedNodeEmitter.emit(node);
- }
- }
-}
diff --git
a/ui/projects/streampipes/shared-ui/src/lib/components/asset-browser/asset-browser-toolbar/asset-browser-toolbar.component.html
b/ui/projects/streampipes/shared-ui/src/lib/components/asset-browser/asset-browser-toolbar/asset-browser-toolbar.component.html
index b61e30d33e..94ac8b118d 100644
---
a/ui/projects/streampipes/shared-ui/src/lib/components/asset-browser/asset-browser-toolbar/asset-browser-toolbar.component.html
+++
b/ui/projects/streampipes/shared-ui/src/lib/components/asset-browser/asset-browser-toolbar/asset-browser-toolbar.component.html
@@ -33,7 +33,7 @@
>filter_alt</mat-icon
>
@if (filterActive) {
- <span class="text-md">{{
+ <span class="text-sm">{{
selectedAssetCount + ' of ' + allAssetCount + ' ' +
'assets'
}}</span>
} @else {
diff --git
a/ui/projects/streampipes/shared-ui/src/lib/components/asset-browser/asset-browser.component.html
b/ui/projects/streampipes/shared-ui/src/lib/components/asset-browser/asset-browser.component.html
deleted file mode 100644
index 78fc665a86..0000000000
---
a/ui/projects/streampipes/shared-ui/src/lib/components/asset-browser/asset-browser.component.html
+++ /dev/null
@@ -1,84 +0,0 @@
-<!--
-~ 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">
- @if (showAssetBrowser) {
- <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
- [allResourcesAlias]="allResourcesAlias"
- [assetBrowserData]="assetBrowserData"
- [assetSelectionMode]="assetSelectionMode"
- [resourceCount]="resourceCount"
- [hideAssetChildren]="hideAssetChildren"
- [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 found - use assets to better organize
resources!'
- | translate
- }}</span>
- <button
- mat-button
- color="accent"
- class="mt-10"
- (click)="navigateToAssetManagement()"
- >
- {{ 'Manage assets' | translate }}
- </button>
- </div>
- }
- @if (!expanded) {
- @if (!expanded) {
- <div
- class="asset-hierarchy asset-browser-text"
- fxLayoutAlign="center center"
- >
- {{ 'Asset Browser' | translate }}
- </div>
- }
- }
- </sp-basic-view>
- </div>
- }
- <div fxFlex>
- <ng-content> </ng-content>
- </div>
-</div>
diff --git
a/ui/projects/streampipes/shared-ui/src/lib/components/asset-browser/asset-browser.component.ts
b/ui/projects/streampipes/shared-ui/src/lib/components/asset-browser/asset-browser.component.ts
deleted file mode 100644
index 8d699705d6..0000000000
---
a/ui/projects/streampipes/shared-ui/src/lib/components/asset-browser/asset-browser.component.ts
+++ /dev/null
@@ -1,131 +0,0 @@
-/*
- * 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,
- inject,
-} 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';
-import { CurrentUserService } from '../../services/current-user.service';
-import { TranslateService } from '@ngx-translate/core';
-
-@Component({
- selector: 'sp-asset-browser',
- templateUrl: 'asset-browser.component.html',
- styleUrls: ['./asset-browser.component.scss'],
- standalone: false,
-})
-export class AssetBrowserComponent implements OnInit, OnDestroy {
- translateService = inject(TranslateService);
-
- @Input()
- showResources = false;
-
- @Input()
- hideAssetChildren = false;
-
- @Input()
- allResourcesAlias = this.translateService.instant('Resources');
-
- @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;
- showAssetBrowser = false;
-
- constructor(
- private assetBrowserService: SpAssetBrowserService,
- private router: Router,
- private currentUserService: CurrentUserService,
- ) {}
-
- ngOnInit(): void {
- this.showAssetBrowser = this.currentUserService.hasAnyRole([
- 'PRIVILEGE_READ_ASSETS',
- 'PRIVILEGE_WRITE_ASSETS',
- ]);
- if (this.showAssetBrowser) {
- 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.assetBrowserService.resetFilters();
- this.assetBrowserDataSub?.unsubscribe();
- this.expandedSub?.unsubscribe();
- }
-}
diff --git
a/ui/projects/streampipes/shared-ui/src/lib/components/asset-browser/asset-browser.service.ts
b/ui/projects/streampipes/shared-ui/src/lib/components/asset-browser/asset-browser.service.ts
index 5988fc7d7b..678c00af81 100644
---
a/ui/projects/streampipes/shared-ui/src/lib/components/asset-browser/asset-browser.service.ts
+++
b/ui/projects/streampipes/shared-ui/src/lib/components/asset-browser/asset-browser.service.ts
@@ -34,11 +34,11 @@ import {
FilterResult,
} from './asset-browser.model';
import { CurrentUserService } from '../../services/current-user.service';
+import { LocalStorageService } from
'../../services/local-storage-settings.service';
@Injectable({ providedIn: 'root' })
export class SpAssetBrowserService {
assetData$ = new BehaviorSubject<AssetBrowserData>(undefined);
- expanded$ = new BehaviorSubject<boolean>(true);
filter$ = new BehaviorSubject<AssetFilter>(undefined);
currentAssetFilter$ = new BehaviorSubject<FilterResult>({
filterActive: false,
@@ -94,7 +94,7 @@ export class SpAssetBrowserService {
this.assetData$.getValue() !== undefined
) {
const data = this.assetData$.getValue();
- const filters: AssetFilter = {
+ const filters = {
selectedSites: [...data.sites].sort((a, b) =>
a.label.localeCompare(b.label),
),
diff --git
a/ui/projects/streampipes/shared-ui/src/lib/components/split-section/split-section.component.scss
b/ui/projects/streampipes/shared-ui/src/lib/components/split-section/split-section.component.scss
index cd1dfe2109..4cb83d551a 100644
---
a/ui/projects/streampipes/shared-ui/src/lib/components/split-section/split-section.component.scss
+++
b/ui/projects/streampipes/shared-ui/src/lib/components/split-section/split-section.component.scss
@@ -18,9 +18,10 @@
:host {
width: 100%;
- display: block;
+ display: flex;
--accent: hsl(221 83% 53%);
--divider: color-mix(in oklab, currentColor 18%, transparent);
+ margin-bottom: 1rem;
}
.section-header {
@@ -46,7 +47,11 @@
}
.section-outer {
- margin-bottom: 1rem;
+ display: flex;
+ flex-direction: column;
+ flex: 1 1 auto;
+ min-height: 0;
+ margin: 0;
}
.title {
@@ -72,16 +77,19 @@
}
.section-body {
- margin-top: 1rem;
- margin-bottom: 1rem;
- margin-left: 0;
- display: block;
+ flex: 1 1 auto;
+ min-height: 0;
+ display: flex;
+ flex-direction: column;
+ margin: 0;
+ padding: 1rem 0;
}
.section-body-compact {
margin-top: 0.5rem;
margin-bottom: 0.5rem;
margin-left: 0;
+ padding: 0;
}
.section.compact {
diff --git
a/ui/projects/streampipes/shared-ui/src/lib/services/colorization.service.ts
b/ui/projects/streampipes/shared-ui/src/lib/services/colorization.service.ts
index 2220d7f3ee..fa42f941bc 100644
--- a/ui/projects/streampipes/shared-ui/src/lib/services/colorization.service.ts
+++ b/ui/projects/streampipes/shared-ui/src/lib/services/colorization.service.ts
@@ -23,17 +23,11 @@ type Rgb = { r: number; g: number; b: number; a: number };
@Injectable({ providedIn: 'root' })
export class SpColorizationService {
generateRandomColor(): string {
- // Hue: full color wheel
const h = Math.floor(Math.random() * 360);
-
- // Saturation: avoid dull or neon colors
- const s = 55 + Math.random() * 25; // 55–80%
-
- // Lightness: avoid too dark or too light
- const l = 40 + Math.random() * 25; // 40–65%
-
- const { r, g, b } = this.hslToRgb(h, s / 100, l / 100);
- return this.rgbToHex({ r, g, b, a: 1 });
+ const s = 55 + Math.random() * 25;
+ const l = 40 + Math.random() * 25;
+ const rgb = this.hslToRgb(h / 360, s / 100, l / 100);
+ return this.rgbToHex({ ...rgb, a: 1 });
}
generateContrastColor(bgColor: string): string {
diff --git
a/ui/projects/streampipes/shared-ui/src/lib/services/local-storage-settings.service.ts
b/ui/projects/streampipes/shared-ui/src/lib/services/local-storage-settings.service.ts
index 33dac4aa97..a13bca0877 100644
---
a/ui/projects/streampipes/shared-ui/src/lib/services/local-storage-settings.service.ts
+++
b/ui/projects/streampipes/shared-ui/src/lib/services/local-storage-settings.service.ts
@@ -38,6 +38,14 @@ export class LocalStorageService {
}
}
+ remove(key: string): void {
+ try {
+ localStorage.removeItem(this.buildKey(key));
+ } catch {
+ // ignore
+ }
+ }
+
set<T>(key: string, value: T): void {
try {
localStorage.setItem(this.buildKey(key), JSON.stringify(value));
diff --git a/ui/projects/streampipes/shared-ui/src/lib/shared-ui.module.ts
b/ui/projects/streampipes/shared-ui/src/lib/shared-ui.module.ts
index 371b6848cd..8a537265ed 100644
--- a/ui/projects/streampipes/shared-ui/src/lib/shared-ui.module.ts
+++ b/ui/projects/streampipes/shared-ui/src/lib/shared-ui.module.ts
@@ -56,14 +56,10 @@ import { SpExceptionDetailsComponent } from
'./components/sp-exception-message/e
import { SpBasicFieldDescriptionComponent } from
'./components/basic-field-description/basic-field-description.component';
import { AssetBrowserToolbarComponent } from
'./components/asset-browser/asset-browser-toolbar/asset-browser-toolbar.component';
import { AssetBrowserFilterComponent } from
'./components/asset-browser/asset-browser-toolbar/asset-browser-filter/asset-browser-filter.component';
-import { AssetBrowserComponent } from
'./components/asset-browser/asset-browser.component';
import { AssetBrowserFilterLabelsComponent } from
'./components/asset-browser/asset-browser-toolbar/asset-browser-filter/asset-browser-filter-labels/asset-browser-filter-labels.component';
import { AssetBrowserFilterOuterComponent } from
'./components/asset-browser/asset-browser-toolbar/asset-browser-filter/asset-browser-filter-outer/asset-browser-filter-outer.component';
import { AssetBrowserFilterSitesComponent } from
'./components/asset-browser/asset-browser-toolbar/asset-browser-filter/asset-browser-filter-sites/asset-browser-filter-sites.component';
import { AssetBrowserFilterTypeComponent } from
'./components/asset-browser/asset-browser-toolbar/asset-browser-filter/asset-browser-filter-type/asset-browser-filter-type.component';
-import { AssetBrowserHierarchyComponent } from
'./components/asset-browser/asset-browser-hierarchy/asset-browser-hierarchy.component';
-import { AssetBrowserNodeComponent } from
'./components/asset-browser/asset-browser-hierarchy/asset-browser-node/asset-browser-node.component';
-import { AssetBrowserNodeInfoComponent } from
'./components/asset-browser/asset-browser-hierarchy/asset-browser-node/asset-browser-node-info/asset-browser-node-info.component';
import { TimeRangeSelectorComponent } from
'./components/time-selector/time-range-selector.component';
import { TimeRangeSelectorMenuComponent } from
'./components/time-selector/time-selector-menu/time-selector-menu.component';
import { CustomTimeRangeSelectionComponent } from
'./components/time-selector/time-selector-menu/custom-time-range-selection/custom-time-range-selection.component';
@@ -119,15 +115,11 @@ import { FeatureCardMetaCreationComponent } from
'./components/feature-card-host
@NgModule({
declarations: [
- AssetBrowserComponent,
AssetBrowserFilterComponent,
AssetBrowserFilterLabelsComponent,
AssetBrowserFilterOuterComponent,
AssetBrowserFilterSitesComponent,
AssetBrowserFilterTypeComponent,
- AssetBrowserHierarchyComponent,
- AssetBrowserNodeComponent,
- AssetBrowserNodeInfoComponent,
AssetBrowserToolbarComponent,
ConfirmDialogComponent,
CustomTimeRangeSelectionComponent,
@@ -225,7 +217,6 @@ import { FeatureCardMetaCreationComponent } from
'./components/feature-card-host
},
],
exports: [
- AssetBrowserComponent,
AssetBrowserToolbarComponent,
AssetLinkConfigurationComponent,
ConfirmDialogComponent,
diff --git a/ui/projects/streampipes/shared-ui/src/public-api.ts
b/ui/projects/streampipes/shared-ui/src/public-api.ts
index 58d0dfb74d..679f6617f2 100644
--- a/ui/projects/streampipes/shared-ui/src/public-api.ts
+++ b/ui/projects/streampipes/shared-ui/src/public-api.ts
@@ -30,7 +30,6 @@ export * from
'./lib/dialog/standard-dialog/standard-dialog.component';
export * from
'./lib/dialog/pipeline-element-help/pipeline-element-help.component';
export * from
'./lib/dialog/object-permission-dialog/object-permission-dialog.component';
-export * from './lib/components/asset-browser/asset-browser.component';
export * from
'./lib/components/asset-browser/asset-browser-toolbar/asset-browser-toolbar.component';
export * from './lib/components/basic-header-title/header-title.component';
export * from './lib/components/basic-inner-panel/basic-inner-panel.component';
diff --git
a/ui/src/app/chart-shared/components/chart-container/chart-container.component.ts
b/ui/src/app/chart-shared/components/chart-container/chart-container.component.ts
index 7f3d583185..8d48ed5caf 100644
---
a/ui/src/app/chart-shared/components/chart-container/chart-container.component.ts
+++
b/ui/src/app/chart-shared/components/chart-container/chart-container.component.ts
@@ -173,7 +173,7 @@ export class ChartContainerComponent
const container = this.el.nativeElement.querySelector(
'.widget-content',
) as HTMLDivElement;
- const obs = new ResizeObserver(entries => {
+ this.resizeObserver = new ResizeObserver(entries => {
clearTimeout(this.resizeTimeout);
this.resizeTimeout = setTimeout(() => {
const { width, height } =
@@ -186,7 +186,7 @@ export class ChartContainerComponent
});
}, 100);
});
- obs.observe(container);
+ this.resizeObserver.observe(container);
}
ngOnChanges(changes: SimpleChanges): void {
diff --git
a/ui/src/app/configuration/label-configuration/edit-label/edit-label.component.ts
b/ui/src/app/configuration/label-configuration/edit-label/edit-label.component.ts
index c00f037cd8..8df1f66af1 100644
---
a/ui/src/app/configuration/label-configuration/edit-label/edit-label.component.ts
+++
b/ui/src/app/configuration/label-configuration/edit-label/edit-label.component.ts
@@ -65,6 +65,7 @@ export class SpEditLabelComponent implements OnInit {
this.saveEmitter.emit(this.label);
if (this.showPreview) {
this.label.color = this.colorizationService.generateRandomColor();
+ console.log(this.label.color);
}
}
}
diff --git
a/ui/src/app/configuration/label-configuration/label-configuration.component.html
b/ui/src/app/configuration/label-configuration/label-configuration.component.html
index aeac48f22a..b69a045cc8 100644
---
a/ui/src/app/configuration/label-configuration/label-configuration.component.html
+++
b/ui/src/app/configuration/label-configuration/label-configuration.component.html
@@ -68,7 +68,7 @@
[color]="label.color"
variant="solid"
shape="pill"
- size="medium"
+ size="small"
data-cy="label-text"
>
</sp-label>
diff --git
a/ui/src/app/editor/components/pipeline-element-icon-stand/pipeline-element-icon-stand.component.ts
b/ui/src/app/editor/components/pipeline-element-icon-stand/pipeline-element-icon-stand.component.ts
index 8bd57fe3d3..3a55ee34ae 100644
---
a/ui/src/app/editor/components/pipeline-element-icon-stand/pipeline-element-icon-stand.component.ts
+++
b/ui/src/app/editor/components/pipeline-element-icon-stand/pipeline-element-icon-stand.component.ts
@@ -40,7 +40,7 @@ export class PipelineElementIconStandComponent
title: 'Data Streams',
filters: [PipelineElementType.DataStream],
open: true,
- color: 'var(--color-stream)',
+ color: 'var(--color-data-source)',
sort: 'name',
},
{
diff --git
a/ui/src/app/home/components/asset-map/asset-map-popup/asset-link-chip/asset-link-chip.component.html
b/ui/src/app/home/components/asset-map/asset-map-popup/asset-link-chip/asset-link-chip.component.html
new file mode 100644
index 0000000000..90c4281c71
--- /dev/null
+++
b/ui/src/app/home/components/asset-map/asset-map-popup/asset-link-chip/asset-link-chip.component.html
@@ -0,0 +1,26 @@
+<!--
+ ~ 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="link-chip"
+ [style.--link-color]="currentLinkType.linkColor"
+ (click)="openPreview(); $event.stopPropagation()"
+>
+ <i class="material-icons">{{ currentLinkType.linkIcon }}</i>
+ <span class="link-label">{{ assetLink.linkLabel }}</span>
+</div>
diff --git
a/ui/projects/streampipes/shared-ui/src/lib/components/asset-browser/asset-browser-hierarchy/asset-browser-node/asset-browser-node.component.scss
b/ui/src/app/home/components/asset-map/asset-map-popup/asset-link-chip/asset-link-chip.component.scss
similarity index 51%
rename from
ui/projects/streampipes/shared-ui/src/lib/components/asset-browser/asset-browser-hierarchy/asset-browser-node/asset-browser-node.component.scss
rename to
ui/src/app/home/components/asset-map/asset-map-popup/asset-link-chip/asset-link-chip.component.scss
index 53509ed109..1c50833509 100644
---
a/ui/projects/streampipes/shared-ui/src/lib/components/asset-browser/asset-browser-hierarchy/asset-browser-node/asset-browser-node.component.scss
+++
b/ui/src/app/home/components/asset-map/asset-map-popup/asset-link-chip/asset-link-chip.component.scss
@@ -16,41 +16,40 @@
*
*/
-.asset-node {
- font-weight: normal;
+.link-chip {
+ display: flex;
+ align-items: center;
+ gap: 6px;
+ background: color-mix(
+ in srgb,
+ var(--color-bg-0) 85%,
+ var(--link-color) 15%
+ );
+ border: 1px solid #e1e4e8;
+ border-radius: 6px;
+ padding: 4px 6px;
cursor: pointer;
- width: 100%;
-}
-
-.asset-node-disabled {
- color: var(--color-bg-3);
- cursor: default;
-}
+ transition: all 0.2s ease;
+ user-select: none;
+ color: var(--link-color, #1d2125);
-.selected-node {
- font-family: 'Roboto-Bold', serif;
- font-weight: bolder;
-}
+ &:hover {
+ border-color: var(--link-color, #39b54a);
+ box-shadow: 0 2px 5px rgba(0, 0, 0, 0.05);
+ }
-.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);
-}
+ i {
+ font-size: 16px;
+ color: var(--link-color, #6b778c);
+ transition: color 0.2s;
+ }
-.info-icon {
- color: var(--color-primary);
- font-size: 12pt;
+ .link-label {
+ font-size: var(--font-size-xs);
+ font-weight: 500;
+ color: inherit;
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ }
}
diff --git
a/ui/src/app/home/components/asset-map/asset-map-popup/asset-link-chip/asset-link-chip.component.ts
b/ui/src/app/home/components/asset-map/asset-map-popup/asset-link-chip/asset-link-chip.component.ts
new file mode 100644
index 0000000000..178ce25940
--- /dev/null
+++
b/ui/src/app/home/components/asset-map/asset-map-popup/asset-link-chip/asset-link-chip.component.ts
@@ -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 { Component, inject, Input, OnInit } from '@angular/core';
+import { AssetLink, AssetLinkType } from '@streampipes/platform-services';
+import { FeatureCardService } from '@streampipes/shared-ui';
+
+@Component({
+ selector: 'sp-asset-map-link-chip',
+ templateUrl: './asset-link-chip.component.html',
+ styleUrls: ['./asset-link-chip.component.scss'],
+ standalone: false,
+})
+export class AssetLinkChipComponent implements OnInit {
+ @Input()
+ assetLink: AssetLink;
+
+ @Input()
+ assetLinkTypes: Record<string, AssetLinkType> = {};
+
+ currentLinkType: AssetLinkType;
+
+ private featureCardService = inject(FeatureCardService);
+
+ ngOnInit() {
+ this.currentLinkType = this.assetLinkTypes[this.assetLink.linkType];
+ }
+
+ openPreview(): void {
+ this.featureCardService.openFeatureCard(
+ this.currentLinkType.linkType,
+ this.assetLink.resourceId,
+ );
+ }
+}
diff --git
a/ui/src/app/home/components/asset-map/asset-map-popup/asset-map-popup.component.html
b/ui/src/app/home/components/asset-map/asset-map-popup/asset-map-popup.component.html
new file mode 100644
index 0000000000..9a1838623d
--- /dev/null
+++
b/ui/src/app/home/components/asset-map/asset-map-popup/asset-map-popup.component.html
@@ -0,0 +1,68 @@
+<!--
+ ~ 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="sp-map-card">
+ <div class="card-header">
+ <div class="asset-identity">
+ <i class="material-icons">precision_manufacturing</i>
+ <h4 [title]="asset.assetName">{{ asset.assetName }}</h4>
+ </div>
+ <div>
+ <sp-label
+ tone="neutral"
+ variant="soft"
+ [labelText]="isa95Type"
+ size="small"
+ ></sp-label>
+ </div>
+ </div>
+
+ <div class="card-body">
+ <div class="meta-item">
+ <label>{{ 'Location' | translate }}</label>
+ <span>{{ site.label || 'Unknown' }}</span>
+ </div>
+ <div class="meta-item">
+ <label>{{ 'Area' | translate }}</label>
+ <span>{{ asset.assetSite.area || '-' }}</span>
+ </div>
+ </div>
+
+ <div class="card-actions">
+ @if (asset.assetLinks && asset.assetLinks.length > 0) {
+ <div class="link-grid">
+ @for (link of asset.assetLinks; track $index) {
+ <sp-asset-map-link-chip
+ [assetLink]="link"
+ [assetLinkTypes]="assetLinkTypes"
+ >
+ </sp-asset-map-link-chip>
+ }
+ </div>
+ } @else {
+ <div class="no-links">
+ <small>{{ 'No linked resources' | translate }}</small>
+ </div>
+ }
+
+ <button mat-flat-button (click)="navigateToAsset()">
+ <mat-icon>arrow_forward</mat-icon>
+ <span>{{ 'Show asset' | translate }}</span>
+ </button>
+ </div>
+</div>
diff --git
a/ui/src/app/home/components/asset-map/asset-map-popup/asset-map-popup.component.scss
b/ui/src/app/home/components/asset-map/asset-map-popup/asset-map-popup.component.scss
new file mode 100644
index 0000000000..d91d69b721
--- /dev/null
+++
b/ui/src/app/home/components/asset-map/asset-map-popup/asset-map-popup.component.scss
@@ -0,0 +1,120 @@
+/*!
+ * 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.
+ *
+ */
+
+:host {
+ display: block;
+}
+
+.sp-map-card {
+ width: 380px;
+ background: var(--color-bg-0);
+ display: flex;
+ flex-direction: column;
+ gap: 12px;
+}
+
+.card-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ border-bottom: 1px solid var(--color-bg-2);
+ padding-bottom: 8px;
+}
+
+.asset-identity {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ color: var(--color-default-text);
+
+ i {
+ color: var(--fg-muted);
+ font-size: 1.1rem;
+ }
+
+ h4 {
+ margin: 0;
+ font-size: 1rem;
+ font-weight: 600;
+ color: var(--color-default-text);
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ max-width: 160px;
+ }
+}
+
+.card-body {
+ display: grid;
+ grid-template-columns: 1fr 1fr;
+ gap: 8px;
+}
+
+.meta-item {
+ display: flex;
+ flex-direction: column;
+ color: var(--color-default-text);
+
+ label {
+ font-size: 0.7rem;
+ color: var(--fg-muted);
+ text-transform: uppercase;
+ letter-spacing: 0.5px;
+ margin-bottom: 2px;
+ }
+
+ span {
+ font-size: 0.9rem;
+ color: var(--color-default-text);
+ font-weight: 500;
+ }
+}
+
+.card-actions {
+ display: flex;
+ flex-direction: column;
+ gap: 8px;
+ padding-top: 4px;
+}
+
+.link-grid {
+ display: grid;
+ grid-template-columns: 1fr 1fr;
+ gap: 8px;
+
+ max-height: 100px;
+ overflow-y: auto;
+ padding-right: 4px;
+
+ &::-webkit-scrollbar {
+ width: 4px;
+ }
+ &::-webkit-scrollbar-track {
+ background: transparent;
+ }
+ &::-webkit-scrollbar-thumb {
+ background: #e0e0e0;
+ border-radius: 4px;
+ }
+}
+
+.no-links {
+ text-align: center;
+ color: var(--color-default-text);
+ padding: 4px 0;
+}
diff --git
a/ui/src/app/home/components/asset-map/asset-map-popup/asset-map-popup.component.ts
b/ui/src/app/home/components/asset-map/asset-map-popup/asset-map-popup.component.ts
new file mode 100644
index 0000000000..23e4da2ac9
--- /dev/null
+++
b/ui/src/app/home/components/asset-map/asset-map-popup/asset-map-popup.component.ts
@@ -0,0 +1,74 @@
+/*
+ * 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,
+ inject,
+ Input,
+ OnInit,
+ Output,
+} from '@angular/core';
+import {
+ AssetLinkType,
+ AssetSiteDesc,
+ Isa95TypeService,
+ SpAssetModel,
+} from '@streampipes/platform-services';
+import { Router } from '@angular/router';
+
+export type PopupAction = 'details' | 'pipelines' | 'dashboards';
+
+@Component({
+ selector: 'sp-asset-map-popup',
+ templateUrl: './asset-map-popup.component.html',
+ styleUrls: ['./asset-map-popup.component.scss'],
+ standalone: false,
+})
+export class AssetMapPopupComponent implements OnInit {
+ @Input()
+ asset: SpAssetModel;
+
+ @Input()
+ site: AssetSiteDesc;
+
+ @Input()
+ assetLinkTypes: Record<string, AssetLinkType> = {};
+
+ @Output() actionClicked = new EventEmitter<PopupAction>();
+
+ isa95Type: string;
+
+ private isa95TypeService = inject(Isa95TypeService);
+ private router = inject(Router);
+
+ ngOnInit() {
+ this.isa95Type = this.isa95TypeService.toLabel(
+ this.asset.assetType.isa95AssetType,
+ );
+ }
+
+ navigateToAsset(): void {
+ this.router.navigate([
+ 'assets',
+ 'details',
+ this.asset.elementId,
+ 'view',
+ ]);
+ }
+}
diff --git a/ui/src/app/home/components/asset-map/home-asset-map.component.html
b/ui/src/app/home/components/asset-map/home-asset-map.component.html
new file mode 100644
index 0000000000..246c50e621
--- /dev/null
+++ b/ui/src/app/home/components/asset-map/home-asset-map.component.html
@@ -0,0 +1,30 @@
+<!--
+ ~ 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="w-100 leaflet-host"
+ [ngStyle]="{ height: '100%' }"
+ leaflet
+ (leafletClick)="onMarkerClicked($event)"
+ [leafletOptions]="mapOptions"
+ (leafletMapReady)="onMapReady($event)"
+>
+ @for (layer of layers; track layer) {
+ <div [leafletLayer]="layer"></div>
+ }
+</div>
diff --git
a/ui/projects/streampipes/shared-ui/src/lib/components/asset-browser/asset-browser-hierarchy/asset-browser-node/asset-browser-node-info/asset-browser-node-info.component.scss
b/ui/src/app/home/components/asset-map/home-asset-map.component.scss
similarity index 75%
rename from
ui/projects/streampipes/shared-ui/src/lib/components/asset-browser/asset-browser-hierarchy/asset-browser-node/asset-browser-node-info/asset-browser-node-info.component.scss
rename to ui/src/app/home/components/asset-map/home-asset-map.component.scss
index c6796b66d5..f415e60ba1 100644
---
a/ui/projects/streampipes/shared-ui/src/lib/components/asset-browser/asset-browser-hierarchy/asset-browser-node/asset-browser-node-info/asset-browser-node-info.component.scss
+++ b/ui/src/app/home/components/asset-map/home-asset-map.component.scss
@@ -16,13 +16,8 @@
*
*/
-.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;
+.leaflet-host {
+ flex: 1 1 auto;
+ min-height: 0;
+ width: 100%;
}
diff --git a/ui/src/app/home/components/asset-map/home-asset-map.component.ts
b/ui/src/app/home/components/asset-map/home-asset-map.component.ts
new file mode 100644
index 0000000000..d06b40c59d
--- /dev/null
+++ b/ui/src/app/home/components/asset-map/home-asset-map.component.ts
@@ -0,0 +1,191 @@
+/*
+ * 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,
+ ComponentRef,
+ createComponent,
+ EnvironmentInjector,
+ inject,
+ Input,
+ OnChanges,
+ OnInit,
+ SimpleChanges,
+} from '@angular/core';
+import {
+ AssetLinkType,
+ AssetSiteDesc,
+ LatLng,
+ LocationConfig,
+ SpAssetModel,
+} from '@streampipes/platform-services';
+import {
+ featureGroup,
+ FeatureGroup,
+ icon,
+ Layer,
+ LeafletMouseEvent,
+ Map,
+ MapOptions,
+ marker,
+ Marker,
+ popup,
+} from 'leaflet';
+import { MapLayerProviderService } from
'../../../core-ui/services/map-layer-provider.service';
+import {
+ AssetMapPopupComponent,
+ PopupAction,
+} from './asset-map-popup/asset-map-popup.component';
+
+@Component({
+ selector: 'sp-home-asset-map',
+ templateUrl: './home-asset-map.component.html',
+ styleUrls: ['./home-asset-map.component.scss'],
+ standalone: false,
+})
+export class HomeAssetMapComponent implements OnInit, OnChanges {
+ @Input()
+ locationConfig: LocationConfig;
+
+ @Input()
+ assets: SpAssetModel[] = [];
+
+ @Input()
+ sites: Record<string, AssetSiteDesc> = {};
+
+ @Input()
+ assetLinkTypes: Record<string, AssetLinkType> = {};
+
+ map: Map;
+ mapOptions: MapOptions;
+ layers: Layer[];
+ marker: Marker;
+ markersGroup: FeatureGroup = featureGroup();
+
+ private currentPopupRef: ComponentRef<AssetMapPopupComponent> | null =
null;
+
+ private mapLayerProviderService = inject(MapLayerProviderService);
+ private injector = inject(EnvironmentInjector);
+
+ ngOnInit() {
+ this.mapOptions = {
+ layers: this.mapLayerProviderService.getMapLayers(
+ this.locationConfig,
+ ),
+ zoom: 10,
+ zoomControl: true,
+ };
+ }
+
+ ngOnChanges(changes: SimpleChanges) {
+ if (changes['assets'] && this.map) {
+ this.refreshMarkersAndView();
+ }
+ }
+
+ onMapReady(map: Map) {
+ this.map = map;
+ this.map.attributionControl.setPrefix('');
+ this.markersGroup.addTo(this.map);
+ this.refreshMarkersAndView();
+
+ setTimeout(() => {
+ map.invalidateSize();
+ }, 0);
+ }
+
+ refreshMarkersAndView(): void {
+ this.markersGroup.clearLayers();
+ const assetsWithSite = this.assets.filter(a => a.assetSite !== null);
+
+ assetsWithSite.forEach(asset => {
+ const site = this.sites[asset.assetSite.siteId];
+ const assetLocation = site.location.coordinates;
+ const marker = this.makeMarker({
+ latitude: assetLocation.latitude,
+ longitude: assetLocation.longitude,
+ });
+ marker.on('click', (e: LeafletMouseEvent) => {
+ this.openPopup(e, asset, site);
+ });
+ this.markersGroup.addLayer(marker);
+ });
+ const bounds = (this.markersGroup as any).getBounds?.();
+ if (!bounds || !bounds.isValid()) return;
+
+ const sw = bounds.getSouthWest();
+ const ne = bounds.getNorthEast();
+
+ if (sw.equals(ne)) {
+ this.map.setView(sw, Math.min(this.map.getMaxZoom() ?? 18, 18));
+ } else {
+ this.map.fitBounds(bounds, { padding: [24, 24] });
+ }
+ }
+
+ makeMarker(location: LatLng): Marker {
+ return marker(
+ { lat: location.latitude, lng: location.longitude },
+ {
+ icon: icon({
+ iconSize: [25, 41],
+ iconAnchor: [13, 41],
+ iconUrl: 'assets/img/marker-icon.png',
+ shadowUrl: 'assets/img/marker-shadow.png',
+ }),
+ },
+ );
+ }
+
+ openPopup(
+ event: LeafletMouseEvent,
+ asset: SpAssetModel,
+ site: AssetSiteDesc,
+ ) {
+ this.currentPopupRef = createComponent(AssetMapPopupComponent, {
+ environmentInjector: this.injector,
+ });
+
+ this.currentPopupRef.instance.asset = asset;
+ this.currentPopupRef.instance.site = site;
+ this.currentPopupRef.instance.assetLinkTypes = this.assetLinkTypes;
+
+ this.currentPopupRef.instance.actionClicked.subscribe(
+ (action: PopupAction) => {
+ this.handlePopupAction(action, asset);
+ },
+ );
+
+ this.currentPopupRef.changeDetectorRef.detectChanges();
+ const popupContent = this.currentPopupRef.location.nativeElement;
+
+ popup({
+ offset: [0, -20],
+ minWidth: 380,
+ closeButton: false,
+ className: 'sp-leaflet-popup-clean',
+ })
+ .setLatLng(event.latlng)
+ .setContent(popupContent)
+ .openOn(this.map);
+ }
+
+ handlePopupAction(action: PopupAction, asset: SpAssetModel) {}
+
+ onMarkerClicked(e: LeafletMouseEvent) {}
+}
diff --git
a/ui/src/app/home/components/asset-table/asset-table-link-preview/asset-table-link-preview.component.html
b/ui/src/app/home/components/asset-table/asset-table-link-preview/asset-table-link-preview.component.html
new file mode 100644
index 0000000000..1eac4b94ae
--- /dev/null
+++
b/ui/src/app/home/components/asset-table/asset-table-link-preview/asset-table-link-preview.component.html
@@ -0,0 +1,31 @@
+<!--
+ ~ 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.
+ ~
+ -->
+
+<button
+ type="button"
+ class="link-chip"
+ [style.--link-color]="currentLinkType.linkColor"
+ (click)="openPreview(); $event.stopPropagation()"
+ [attr.aria-label]="assetLink.linkLabel"
+>
+ <i class="material-icons link-icon">{{ currentLinkType.linkIcon }}</i>
+
+ <span class="chip-expander">
+ <span class="link-label">{{ assetLink.linkLabel }}</span>
+ </span>
+</button>
diff --git
a/ui/src/app/home/components/asset-table/asset-table-link-preview/asset-table-link-preview.component.scss
b/ui/src/app/home/components/asset-table/asset-table-link-preview/asset-table-link-preview.component.scss
new file mode 100644
index 0000000000..b486204c07
--- /dev/null
+++
b/ui/src/app/home/components/asset-table/asset-table-link-preview/asset-table-link-preview.component.scss
@@ -0,0 +1,114 @@
+/*!
+ * 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.
+ *
+ */
+
+.link-chip {
+ --lc: var(--link-color, #6b778c);
+
+ position: relative;
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+
+ height: 28px;
+ padding: 4px 6px;
+
+ background: color-mix(in srgb, var(--color-bg-0) 85%, var(--lc) 15%);
+ border: 1px solid #e1e4e8;
+ border-radius: 6px;
+
+ cursor: pointer;
+ user-select: none;
+ color: var(--lc);
+
+ z-index: 0;
+
+ transition:
+ border-color 0.2s ease,
+ box-shadow 0.2s ease,
+ transform 0.15s ease;
+
+ &:hover,
+ &:focus-visible {
+ border-color: var(--lc);
+ box-shadow: 0 2px 5px rgba(0, 0, 0, 0.05);
+ transform: translateY(-1px);
+ z-index: 50;
+ outline: none;
+ }
+
+ .link-icon {
+ font-size: 16px;
+ line-height: 1;
+ color: var(--lc);
+ position: relative;
+ z-index: 3;
+ }
+
+ .chip-expander {
+ position: absolute;
+ left: -1px;
+ top: -1px;
+ height: calc(100% + 2px);
+
+ width: max-content;
+ max-width: 340px;
+
+ display: inline-flex;
+ align-items: center;
+
+ padding-left: calc(6px + 16px + 6px);
+ padding-right: 10px;
+
+ border-radius: 6px;
+ background: color-mix(in srgb, #ffffff 85%, var(--lc) 15%);
+ border: 1px solid #e1e4e8;
+
+ box-sizing: border-box;
+ pointer-events: none;
+ clip-path: inset(0 100% 0 0 round 6px);
+ opacity: 0;
+
+ transition:
+ clip-path 220ms ease,
+ opacity 120ms ease,
+ box-shadow 220ms ease,
+ border-color 220ms ease;
+
+ z-index: 2;
+ }
+
+ .link-label {
+ font-size: var(--font-size-xs);
+ font-weight: 500;
+ color: var(--lc);
+
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+
+ max-width: 260px;
+ }
+
+ &:hover .chip-expander,
+ &:focus-visible .chip-expander {
+ opacity: 1;
+ clip-path: inset(0 0 0 0 round 6px);
+ border-color: var(--lc);
+ box-shadow: 0 8px 18px rgba(0, 0, 0, 0.1);
+ }
+}
diff --git
a/ui/src/app/home/components/asset-table/asset-table-link-preview/asset-table-link-preview.component.ts
b/ui/src/app/home/components/asset-table/asset-table-link-preview/asset-table-link-preview.component.ts
new file mode 100644
index 0000000000..8981b9167a
--- /dev/null
+++
b/ui/src/app/home/components/asset-table/asset-table-link-preview/asset-table-link-preview.component.ts
@@ -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 { Component, inject, Input, OnInit } from '@angular/core';
+import { AssetLink, AssetLinkType } from '@streampipes/platform-services';
+import { FeatureCardService } from '@streampipes/shared-ui';
+
+@Component({
+ selector: 'sp-asset-table-link-preview',
+ templateUrl: './asset-table-link-preview.component.html',
+ styleUrls: ['./asset-table-link-preview.component.scss'],
+ standalone: false,
+})
+export class AssetTableLinkPreviewComponent implements OnInit {
+ @Input()
+ assetLink: AssetLink;
+
+ @Input()
+ assetLinkTypes: Record<string, AssetLinkType> = {};
+
+ currentLinkType: AssetLinkType;
+
+ private featureCardService = inject(FeatureCardService);
+
+ ngOnInit() {
+ this.currentLinkType = this.assetLinkTypes[this.assetLink.linkType];
+ }
+
+ openPreview(): void {
+ this.featureCardService.openFeatureCard(
+ this.currentLinkType.linkType,
+ this.assetLink.resourceId,
+ );
+ }
+}
diff --git
a/ui/src/app/home/components/asset-table/home-asset-table.component.html
b/ui/src/app/home/components/asset-table/home-asset-table.component.html
new file mode 100644
index 0000000000..635722d0c4
--- /dev/null
+++ b/ui/src/app/home/components/asset-table/home-asset-table.component.html
@@ -0,0 +1,89 @@
+<!--
+ ~ 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-table
+ fxFlex="100"
+ [columns]="displayedColumns"
+ [dataSource]="dataSource"
+ [showActionsMenu]="true"
+ [rowsClickable]="true"
+ (rowClicked)="navigateToAsset($event)"
+ matSort
+>
+ <ng-container matColumnDef="assetName">
+ <th mat-header-cell mat-sort-header *matHeaderCellDef>Asset</th>
+ <td mat-cell *matCellDef="let asset">
+ <span class="cell-nowrap">{{ asset.assetName }}</span>
+ </td>
+ </ng-container>
+
+ <ng-container matColumnDef="assetType">
+ <th mat-header-cell mat-sort-header *matHeaderCellDef>
+ {{ 'Type' | translate }}
+ </th>
+ <td mat-cell *matCellDef="let asset">
+ @if (asset.assetType?.isa95AssetType) {
+ <sp-label
+ tone="neutral"
+ variant="soft"
+ size="small"
+ class="cell-nowrap"
+ >
+ {{ getIsa95Type(asset) }}
+ </sp-label>
+ } @else {
+ <span class="cell-nowrap">-</span>
+ }
+ </td>
+ </ng-container>
+
+ <ng-container matColumnDef="location">
+ <th mat-header-cell mat-sort-header *matHeaderCellDef>
+ {{ 'Location' | translate }}
+ </th>
+ <td mat-cell *matCellDef="let asset">
+ <span class="cell-nowrap">{{ getSite(asset) }}</span>
+ </td>
+ </ng-container>
+
+ <ng-container matColumnDef="area">
+ <th mat-header-cell mat-sort-header *matHeaderCellDef>
+ {{ 'Area' | translate }}
+ </th>
+ <td mat-cell *matCellDef="let asset">
+ <span class="cell-nowrap">{{ asset.assetSite?.area || '-' }}</span>
+ </td>
+ </ng-container>
+
+ <ng-container matColumnDef="assetLinks">
+ <th mat-header-cell *matHeaderCellDef>
+ {{ 'Links' | translate }}
+ </th>
+ <td mat-cell *matCellDef="let asset" class="cell-links">
+ <div class="asset-links p-sm">
+ @for (link of asset.assetLinks; track $index) {
+ <sp-asset-table-link-preview
+ [assetLink]="link"
+ [assetLinkTypes]="assetLinkTypes"
+ >
+ </sp-asset-table-link-preview>
+ }
+ </div>
+ </td>
+ </ng-container>
+</sp-table>
diff --git
a/ui/projects/streampipes/shared-ui/src/lib/components/asset-browser/asset-browser.component.scss
b/ui/src/app/home/components/asset-table/home-asset-table.component.scss
similarity index 71%
rename from
ui/projects/streampipes/shared-ui/src/lib/components/asset-browser/asset-browser.component.scss
rename to ui/src/app/home/components/asset-table/home-asset-table.component.scss
index 1f145dbcb4..d044d3c589 100644
---
a/ui/projects/streampipes/shared-ui/src/lib/components/asset-browser/asset-browser.component.scss
+++ b/ui/src/app/home/components/asset-table/home-asset-table.component.scss
@@ -16,21 +16,16 @@
*
*/
-.asset-hierarchy {
- height: calc(100vh - 145px);
- max-height: calc(100vh - 145px);
- overflow-y: auto;
- margin-left: 5px;
- margin-top: 5px;
+.asset-links {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 5px 5px;
+ align-items: center;
}
-.asset-browser-text {
- text-orientation: mixed;
- writing-mode: tb-rl;
- text-align: center;
-}
-
-.asset-creation-hint {
- font-size: 10pt;
- text-align: center;
+.cell-nowrap {
+ display: block;
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
}
diff --git
a/ui/src/app/home/components/asset-table/home-asset-table.component.ts
b/ui/src/app/home/components/asset-table/home-asset-table.component.ts
new file mode 100644
index 0000000000..6bcc3f421a
--- /dev/null
+++ b/ui/src/app/home/components/asset-table/home-asset-table.component.ts
@@ -0,0 +1,96 @@
+/*
+ * 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,
+ OnChanges,
+ SimpleChanges,
+ ViewChild,
+} from '@angular/core';
+import {
+ AssetLinkType,
+ AssetSiteDesc,
+ Isa95TypeService,
+ LocationConfig,
+ SpAssetModel,
+} from '@streampipes/platform-services';
+import { MatSort } from '@angular/material/sort';
+import { MatTableDataSource } from '@angular/material/table';
+import { Router } from '@angular/router';
+
+@Component({
+ selector: 'sp-home-asset-table',
+ templateUrl: './home-asset-table.component.html',
+ styleUrls: ['./home-asset-table.component.scss'],
+ standalone: false,
+})
+export class HomeAssetTableComponent implements OnChanges {
+ @Input()
+ locationConfig: LocationConfig;
+
+ @Input()
+ assets: SpAssetModel[] = [];
+
+ @Input()
+ sites: Record<string, AssetSiteDesc> = {};
+
+ @Input()
+ assetLinkTypes: Record<string, AssetLinkType> = {};
+
+ displayedColumns: string[] = [
+ 'assetName',
+ 'assetType',
+ 'location',
+ 'area',
+ 'assetLinks',
+ ];
+
+ @ViewChild(MatSort)
+ sort: MatSort;
+
+ dataSource: MatTableDataSource<SpAssetModel> =
+ new MatTableDataSource<SpAssetModel>();
+
+ private isa95TypeService = inject(Isa95TypeService);
+ private router = inject(Router);
+
+ ngOnChanges(changes: SimpleChanges) {
+ this.dataSource.data = this.assets;
+ setTimeout(() => {
+ this.dataSource.sort = this.sort;
+ });
+ }
+
+ getIsa95Type(asset: SpAssetModel): string {
+ return this.isa95TypeService.toLabel(asset.assetType?.isa95AssetType);
+ }
+
+ getSite(asset: SpAssetModel): string {
+ if (!asset.assetSite?.siteId) {
+ return '-';
+ } else {
+ return this.sites[asset.assetSite.siteId].label;
+ }
+ }
+
+ navigateToAsset(asset: SpAssetModel): void {
+ this.router.navigate(['assets', 'details', asset.elementId, 'view']);
+ }
+}
diff --git a/ui/src/app/home/components/status.component.html
b/ui/src/app/home/components/status.component.html
index 79879b43f6..e37497d5c4 100644
--- a/ui/src/app/home/components/status.component.html
+++ b/ui/src/app/home/components/status.component.html
@@ -16,40 +16,25 @@
~
-->
-<div
- fxFlex="100"
- class="status-container p-10"
- (click)="navigate(statusBox.link)"
->
- <div fxLayout="row" fxFlex="100">
- <div fxFlex fxLayout="column" fxLayoutAlign="start start">
- <div fxFlex fxLayout="column">
- <div class="status-container-number text-3xl">
- <span>{{ resourceCount }}</span>
- </div>
- <div class="text-xl" fxFlex fxLayoutAlign="end end">
- <span>{{ statusBox.title }}</span>
- </div>
- </div>
- <div fxFlex="100" fxLayoutAlign="end end" class="w-100">
- @if (showCreateLink) {
- <div fxFlex="100" fxLayoutAlign="start center">
- <a
- (click)="navigate(statusBox.createLink)"
- fxLayoutAlign="start center"
- class="status-container-create-link text-md"
- >
- <i class="material-icons">add_circle</i>
- <span> {{ statusBox.createTitle }}</span>
- </a>
- </div>
- }
- </div>
+<div class="status-container" (click)="navigate(statusBox.link)">
+ <div class="status-content p-sm">
+ <div class="status-main">
+ <div class="status-number">{{ resourceCount }}</div>
+ <div class="status-title">{{ statusBox.title }}</div>
+
+ @if (showCreateLink) {
+ <a
+ class="status-action"
+ (click)="
+ $event.stopPropagation();
navigate(statusBox.createLink)
+ "
+ >
+ <i class="material-icons">add_circle</i>
+ <span>{{ 'New' | translate }}</span>
+ </a>
+ }
</div>
- </div>
- <div fxLayoutAlign="start start">
- <i class="material-icons status-container-icon text-5xl">{{
- statusBox.icon
- }}</i>
+
+ <i class="material-icons status-icon">{{ statusBox.icon }}</i>
</div>
</div>
diff --git a/ui/src/app/home/components/status.component.scss
b/ui/src/app/home/components/status.component.scss
index cb8de94ec8..95f3fa93f4 100644
--- a/ui/src/app/home/components/status.component.scss
+++ b/ui/src/app/home/components/status.component.scss
@@ -16,33 +16,132 @@
*
*/
+:host {
+ --status-box-color: #2c3e50;
+ --status-box-bg: color-mix(
+ in srgb,
+ var(--status-box-color) 14%,
+ var(--color-bg-0)
+ );
+}
+
.status-container {
- height: 150px;
width: 100%;
- color: var(--color-secondary);
- border-radius: 10px;
+ box-sizing: border-box;
+
+ min-height: 112px;
+ height: auto;
+
+ border-radius: 14px;
margin-bottom: 20px;
+
border: 1px solid var(--color-bg-2);
- background: var(--color-bg-1);
+ background: var(--status-box-bg);
+ color: var(--status-box-color);
+
+ transition:
+ transform 160ms ease,
+ box-shadow 160ms ease,
+ border-color 160ms ease;
+ position: relative;
+ overflow: hidden;
}
-.status-container-number {
- font-weight: bold;
+/* Inner layout */
+.status-content {
+ height: 100%;
+ display: grid;
+ grid-template-columns: 1fr auto;
+ align-items: center;
+ gap: 16px;
}
-.status-container:hover {
- cursor: pointer;
- opacity: 0.8;
+/* Left column */
+.status-main {
+ display: grid;
+ gap: 6px;
+ align-content: center;
+ min-width: 0; /* allows text truncation */
+}
+
+.status-number {
+ font-weight: 700;
+ font-size: 2rem; /* ~text-3xl */
+ line-height: 1;
+ letter-spacing: -0.02em;
}
-.status-container-create-link {
- color: var(--color-primary);
+.status-title {
+ font-weight: 600;
+ font-size: 1rem;
+ opacity: 0.9;
+
+ /* avoid overflow on long titles */
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+}
+
+/* Action link */
+.status-action {
+ display: inline-flex;
+ align-items: center;
+ gap: 8px;
+
+ margin-top: 6px;
+ width: fit-content;
+
+ color: var(--status-box-color);
+ text-decoration: none;
+
+ font-weight: 600;
+ font-size: 0.9rem;
+ opacity: 0.9;
+
+ padding: 6px 10px;
+ border-radius: 999px;
+
+ /* subtle “chip” feel without changing colors */
+ background: color-mix(in srgb, var(--status-box-color) 8%, transparent);
+ transition:
+ transform 160ms ease,
+ opacity 160ms ease,
+ background 160ms ease;
+}
+
+.status-action .material-icons {
+ font-size: 18px;
+ line-height: 1;
+}
+
+/* Right icon */
+.status-icon {
+ font-size: 52px; /* big but not overpowering */
+ color: var(--status-box-color);
+ opacity: 0.5;
+ line-height: 1;
+ justify-self: end;
+
+ /* keep the icon visually tucked in */
+ transform: translateY(2px);
+ pointer-events: none;
+}
+
+/* Hover / active */
+.status-container:hover {
+ cursor: pointer;
+ opacity: 1;
+ transform: translateY(-2px);
}
-.status-container-create-link:hover {
- font-weight: bold;
+.status-container:active {
+ transform: translateY(-1px);
+ box-shadow: 0 6px 18px rgba(0, 0, 0, 0.06);
}
-.status-container-icon {
- color: var(--mat-sys-outline-variant);
+/* Action hover */
+.status-action:hover {
+ opacity: 1;
+ transform: translateY(-1px);
+ background: color-mix(in srgb, var(--status-box-color) 12%, transparent);
}
diff --git a/ui/src/app/home/components/status.component.ts
b/ui/src/app/home/components/status.component.ts
index 183ce3cb89..fc6fe103de 100644
--- a/ui/src/app/home/components/status.component.ts
+++ b/ui/src/app/home/components/status.component.ts
@@ -16,9 +16,9 @@
*
*/
-import { Component, Input, OnInit } from '@angular/core';
+import { Component, HostBinding, inject, Input, OnInit } from '@angular/core';
import { Router } from '@angular/router';
-import { UserInfo } from '@streampipes/platform-services';
+import { AssetLinkType, UserInfo } from '@streampipes/platform-services';
import { StatusBox } from '../models/home.model';
import { UserRole } from '../../_enums/user-role.enum';
import { zip } from 'rxjs';
@@ -39,11 +39,18 @@ export class StatusComponent implements OnInit {
@Input()
currentUser: UserInfo;
+ @Input()
+ assetLinkType: AssetLinkType;
+
+ @HostBinding('style.--status-box-color') statusBoxColor: string | null =
+ null;
+
showCreateLink = true;
- constructor(private router: Router) {}
+ private router = inject(Router);
ngOnInit() {
+ this.statusBoxColor = this.assetLinkType.linkColor;
zip(this.statusBox.dataFns).subscribe(res => {
let totalLength = 0;
res.forEach(response => {
@@ -52,6 +59,7 @@ export class StatusComponent implements OnInit {
this.resourceCount = totalLength;
});
+
this.showCreateLink = this.shouldShowCreateLink();
}
diff --git a/ui/src/app/home/components/welcome/welcome.component.html
b/ui/src/app/home/components/welcome/welcome.component.html
new file mode 100644
index 0000000000..0aeaf8f62a
--- /dev/null
+++ b/ui/src/app/home/components/welcome/welcome.component.html
@@ -0,0 +1,24 @@
+<!--
+ ~ 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-basic-header-title-component
+ [level]="1"
+ title="{{ greeting }}{{ displayName ? ',' : '' }} {{
+ displayName || email
+ }}!"
+></sp-basic-header-title-component>
diff --git
a/ui/projects/streampipes/shared-ui/src/lib/components/asset-browser/asset-browser-hierarchy/asset-browser-hierarchy.component.scss
b/ui/src/app/home/components/welcome/welcome.component.scss
similarity index 59%
rename from
ui/projects/streampipes/shared-ui/src/lib/components/asset-browser/asset-browser-hierarchy/asset-browser-hierarchy.component.scss
rename to ui/src/app/home/components/welcome/welcome.component.scss
index 0a17dcbe66..d9b0846cd9 100644
---
a/ui/projects/streampipes/shared-ui/src/lib/components/asset-browser/asset-browser-hierarchy/asset-browser-hierarchy.component.scss
+++ b/ui/src/app/home/components/welcome/welcome.component.scss
@@ -16,39 +16,29 @@
*
*/
-.sp-tree-invisible {
- display: none;
+.home-header {
+ margin-bottom: 24px;
+ padding: 4px 0;
}
-.sp-tree ul,
-.sp-tree li {
- margin-top: 0;
- margin-bottom: 0;
- list-style-type: none;
+.home-header-text {
+ display: flex;
+ flex-direction: column;
+ gap: 4px;
}
-.sp-tree .mat-nested-tree-node div[role='group'] {
- padding-left: 20px;
-}
+.home-title {
+ margin: 0;
-.sp-tree div[role='group'] > .mat-tree-node {
- padding-left: 20px;
+ font-size: var(--font-size-xl);
+ color: var(--color-text-primary, #0f172a);
}
-.mat-tree-node {
- min-height: 35px;
-}
+.home-subtitle {
+ margin: 0;
-.mat-tree-node:hover {
- background: var(--color-bg-1);
-}
-
-.placeholder-icon {
- display: inline-flex;
- justify-content: center;
- align-items: center;
-}
+ font-size: var(--font-size-sm);
+ font-weight: 500;
-.placeholder-icon .mat-icon.invisible {
- visibility: hidden;
+ color: var(--color-text-secondary, #64748b);
}
diff --git a/ui/src/app/home/components/welcome/welcome.component.ts
b/ui/src/app/home/components/welcome/welcome.component.ts
new file mode 100644
index 0000000000..2980a8981b
--- /dev/null
+++ b/ui/src/app/home/components/welcome/welcome.component.ts
@@ -0,0 +1,49 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+import { Component, inject, Input } from '@angular/core';
+import { UserInfo } from '@streampipes/platform-services';
+import { TranslateService } from '@ngx-translate/core';
+
+@Component({
+ selector: 'sp-welcome',
+ templateUrl: './welcome.component.html',
+ styleUrls: ['./welcome.component.scss'],
+ standalone: false,
+})
+export class WelcomeComponent {
+ @Input()
+ user: UserInfo;
+
+ private translate = inject(TranslateService);
+
+ get displayName(): string | null {
+ return this.user?.displayName?.trim() || null;
+ }
+
+ get email(): string {
+ return this.user?.username ?? '';
+ }
+
+ get greeting(): string {
+ const hour = new Date().getHours();
+ if (hour < 12) return this.translate.instant('Good morning');
+ if (hour < 18) return this.translate.instant('Good afternoon');
+ return this.translate.instant('Good evening');
+ }
+}
diff --git a/ui/src/app/home/home.component.html
b/ui/src/app/home/home.component.html
index 451496cfab..07360dbf72 100644
--- a/ui/src/app/home/home.component.html
+++ b/ui/src/app/home/home.component.html
@@ -16,73 +16,79 @@
~
-->
-<sp-basic-view [hideNavbar]="true">
- <div fxFlex fxLayout="row" fxLayoutAlign="start start">
- <div fxFlex="100">
- <div fxFlex="100" fxLayout="column">
- <div class="p-10 header-margin" fxLayoutAlign="center center">
- <span class="text-3xl font-bold"
- >{{ 'Welcome' | translate }}!</span
+<div fxLayout="column" class="page-container page-container-no-bg">
+ @if (currentUser) {
+ <sp-welcome [user]="currentUser"></sp-welcome>
+ }
+ @if (showStatus) {
+ <div fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="10px">
+ @for (box of statusBoxes; track box) {
+ <sp-status
+ [fxFlex]="100 / statusBoxes.length"
+ [statusBox]="box"
+ [assetLinkType]="assetLinkTypes[box.assetLinkTypeId]"
+ [currentUser]="currentUser"
+ ></sp-status>
+ }
+ </div>
+ }
+ <div fxFlex class="assets-wrapper">
+ @if (contentLoaded) {
+ <sp-split-section
+ fxFlex
+ [level]="2"
+ [title]="'Assets' | translate"
+ class="asset-section"
+ >
+ <div section-actions>
+ <mat-button-toggle-group
+ name="Asset view"
+ aria-label="Asset view mode"
+ [value]="selectedView()"
+ (change)="updateView($event.value)"
>
+ <mat-button-toggle value="map"
+ >{{ 'Map' | translate }}
+ </mat-button-toggle>
+ <mat-button-toggle value="table">{{
+ 'Table' | translate
+ }}</mat-button-toggle>
+ </mat-button-toggle-group>
</div>
- <div fxLayout="column" fxFlex="100" class="home-margin">
- @if (showStatus) {
- <div
- fxFlex="100"
- fxLayout="row"
- fxLayoutAlign="start center"
- fxLayoutGap="10px"
+ <div fxFlexFill fxLayout="column" class="assets-content">
+ @if (selectedView() === 'map') {
+ @if (locationConfig && locationConfig.locationEnabled)
{
+ <sp-home-asset-map
+ class="h-100 map"
+ [assetLinkTypes]="assetLinkTypes"
+ [sites]="sites"
+ [locationConfig]="locationConfig"
+ [assets]="filteredAssets"
+ ></sp-home-asset-map>
+ } @else {
+ <sp-alert-banner
+ type="info"
+ [title]="
+ 'Map configuration required' | translate
+ "
+ [description]="
+ 'To enable the map view, a map provider
needs to be configured. Admins can configure map providers under Settings ->
Sites.'
+ | translate
+ "
+ >
+ </sp-alert-banner>
+ }
+ } @else {
+ <sp-home-asset-table
+ [assetLinkTypes]="assetLinkTypes"
+ [sites]="sites"
+ [locationConfig]="locationConfig"
+ [assets]="filteredAssets"
>
- @for (box of statusBoxes; track box) {
- <sp-status
- [fxFlex]="100 / statusBoxes.length"
- [statusBox]="box"
- [currentUser]="currentUser"
- ></sp-status>
- }
- </div>
+ </sp-home-asset-table>
}
- <div fxFlex="100">
- <sp-basic-inner-panel
- innerPadding="0"
- panelTitle="{{ appConstants.APP_NAME }} Modules"
- >
- <div fxFlex="100" fxLayout="column">
- <mat-list class="modules-list">
- @for (link of serviceLinks; track link) {
- <mat-list-item
- (click)="openLink(link)"
- class="list-item"
- >
- <div
- matListItemAvatar
- class="pipeline-avatar
sp-primary-bg"
- >
- <mat-icon>{{
- link.icon
- }}</mat-icon>
- </div>
- <span
- class="text-md font-bold"
- matListItemTitle
- >
- {{ link.name }}
- </span>
- <span
- matListItemLine
- class="text-sm font-medium"
- >
- {{ link.description }}
- </span>
- <mat-divider></mat-divider>
- </mat-list-item>
- }
- </mat-list>
- </div>
- </sp-basic-inner-panel>
- </div>
</div>
- </div>
- </div>
+ </sp-split-section>
+ }
</div>
-</sp-basic-view>
+</div>
diff --git a/ui/src/app/home/home.component.scss
b/ui/src/app/home/home.component.scss
index 9486e96739..22f5d440ab 100644
--- a/ui/src/app/home/home.component.scss
+++ b/ui/src/app/home/home.component.scss
@@ -16,76 +16,21 @@
*
*/
-.mt-0 {
- margin-top: 0;
+.assets-wrapper,
+.assets-content {
+ flex: 1 1 auto;
+ min-height: 0; /* super important so children can fill and scroll
correctly */
}
-.round-border {
- border-top-left-radius: 5px;
- border-top-right-radius: 5px;
+.assets-wrapper > .section-body {
+ height: 100%;
}
-.header-margin {
- margin-top: 20px;
+.asset-section {
+ background: var(--color-bg-0);
}
-.welcome-primary {
- color: var(--color-secondary);
-}
-
-.welcome-accent {
- color: var(--color-primary);
-}
-
-.home-margin {
- margin: 10px;
-}
-
-.home-padding {
- padding: 15px;
-}
-
-.p-10 {
- padding: 10px;
-}
-
-.home-image-small {
- height: 150px;
- width: 100%;
-}
-
-.pipeline-avatar {
- align-items: center;
- justify-content: center;
- display: flex;
- color: white;
-}
-
-.mdc-list-item--with-leading-avatar .mat-mdc-list-item-avatar {
- border-radius: 50%;
- background-color: var(--color-primary);
-}
-
-.pt-0 {
- padding-top: 0;
-}
-
-::ng-deep .modules-list.mat-list-base {
- padding-top: 0;
-}
-
-.list-item:nth-child(even) {
- background-color: var(--color-bg-1);
-}
-.list-item:nth-child(odd) {
- background-color: var(--color-bg-0);
-}
-
-.list-item:hover {
- background-color: var(--color-bg-2);
- cursor: pointer;
-}
-
-.list-item {
- --mdc-list-list-item-two-line-container-height: 68px;
+.map {
+ flex: 1 1 auto;
+ min-height: 0;
}
diff --git a/ui/src/app/home/home.component.ts
b/ui/src/app/home/home.component.ts
index c03da1fb70..07e42751b8 100644
--- a/ui/src/app/home/home.component.ts
+++ b/ui/src/app/home/home.component.ts
@@ -16,7 +16,7 @@
*
*/
-import { Component, OnInit } from '@angular/core';
+import { Component, inject, OnDestroy, OnInit, signal } from '@angular/core';
import { HomeService } from './home.service';
import { Router } from '@angular/router';
import { AppConstants } from '../services/app.constants';
@@ -24,6 +24,7 @@ import {
CurrentUserService,
DialogService,
PanelType,
+ SpAssetBrowserService,
SpBreadcrumbService,
} from '@streampipes/shared-ui';
import { UserRole } from '../_enums/user-role.enum';
@@ -33,30 +34,40 @@ import { ShepherdService } from
'../services/tour/shepherd.service';
import {
AdapterDescription,
AdapterService,
+ AssetConstants,
+ AssetLinkType,
+ AssetManagementService,
+ AssetSiteDesc,
+ GenericStorageService,
+ LocationConfig,
+ LocationConfigService,
NamedStreamPipesEntity,
- Pipeline,
PipelineElementService,
- PipelineService,
+ SpAssetModel,
UserInfo,
} from '@streampipes/platform-services';
-import { zip } from 'rxjs';
+import { forkJoin, Subscription, zip } from 'rxjs';
import { StatusBox } from './models/home.model';
+import { LocalStorageService } from
'../../../projects/streampipes/shared-ui/src/lib/services/local-storage-settings.service';
@Component({
templateUrl: './home.component.html',
styleUrls: ['./home.component.scss'],
standalone: false,
})
-export class HomeComponent implements OnInit {
+export class HomeComponent implements OnInit, OnDestroy {
serviceLinks = [];
showStatus = false;
availablePipelineElements: NamedStreamPipesEntity[] = [];
availableAdapters: AdapterDescription[] = [];
- availablePipelines: Pipeline[] = [];
- runningPipelines: Pipeline[] = [];
statusBoxes: StatusBox[] = [];
+ locationConfig: LocationConfig;
+ assets: SpAssetModel[] = [];
+ filteredAssets: SpAssetModel[] = [];
+ sites: Record<string, AssetSiteDesc> = {};
+ assetLinkTypes: Record<string, AssetLinkType> = {};
requiredAdapterForTutorialAppId: any =
'org.apache.streampipes.connect.iiot.adapters.simulator.machine';
@@ -68,30 +79,65 @@ export class HomeComponent implements OnInit {
isTutorialOpen = false;
currentUser: UserInfo;
+ selectedView = signal<string>('table');
+ contentLoaded = false;
- constructor(
- private homeService: HomeService,
- private currentUserService: CurrentUserService,
- private router: Router,
- public appConstants: AppConstants,
- private breadcrumbService: SpBreadcrumbService,
- private dialogService: DialogService,
- private shepherdService: ShepherdService,
- private pipelineService: PipelineService,
- private pipelineElementService: PipelineElementService,
- private adapterService: AdapterService,
- ) {
+ private homeService = inject(HomeService);
+ private currentUserService = inject(CurrentUserService);
+ private router = inject(Router);
+ public appConstants = inject(AppConstants);
+ private breadcrumbService = inject(SpBreadcrumbService);
+ private dialogService = inject(DialogService);
+ private shepherdService = inject(ShepherdService);
+ private pipelineElementService = inject(PipelineElementService);
+ private adapterService = inject(AdapterService);
+ private genericStorageService = inject(GenericStorageService);
+ private locationService = inject(LocationConfigService);
+ private assetService = inject(AssetManagementService);
+ private localStorageService = inject(LocalStorageService);
+ private assetFilterService = inject(SpAssetBrowserService);
+
+ assetFilter$: Subscription;
+
+ constructor() {
this.serviceLinks = this.homeService.getFilteredServiceLinks();
this.statusBoxes = this.homeService
.getFilteredServiceLinks()
.filter(s => s.showStatusBox)
.map(s => s.statusBox);
+ this.selectedView.set(
+ this.localStorageService.get('default-asset-view', 'table'),
+ );
}
ngOnInit() {
this.currentUser = this.currentUserService.getCurrentUser();
+ this.assetFilter$ =
+ this.assetFilterService.currentAssetFilter$.subscribe(filter => {
+ this.filteredAssets = filter.selectedAssets as SpAssetModel[];
+ });
const isAdmin = this.hasRole(UserRole.ROLE_ADMIN);
- this.showStatus = true;
+ forkJoin([
+ this.genericStorageService.getAllDocuments(
+ AssetConstants.ASSET_LINK_TYPES_DOC_NAME,
+ ),
+ this.locationService.getLocationConfig(),
+ this.assetService.getAllAssets(),
+ this.genericStorageService.getAllDocuments(
+ AssetConstants.ASSET_SITES_APP_DOC_NAME,
+ ),
+ ]).subscribe(res => {
+ res[0].forEach(doc => {
+ this.assetLinkTypes[doc.linkType] = doc;
+ });
+ this.locationConfig = res[1];
+ this.assets = res[2];
+ res[3].forEach(doc => {
+ this.sites[doc._id] = doc;
+ });
+ this.contentLoaded = true;
+ this.showStatus = true;
+ });
if (isAdmin) {
this.loadResources();
}
@@ -102,14 +148,6 @@ export class HomeComponent implements OnInit {
return this.currentUser.roles.indexOf(role) > -1;
}
- openLink(link) {
- if (link.link.newWindow) {
- window.open(link.link.value);
- } else {
- this.router.navigate([link.link.value]);
- }
- }
-
checkForTutorial() {
if (this.currentUser.showTutorial) {
if (this.requiredPipelineElementsForTourPresent()) {
@@ -209,20 +247,26 @@ export class HomeComponent implements OnInit {
loadResources(): void {
zip(
- this.pipelineService.getPipelines(),
this.adapterService.getAdapterDescriptions(),
this.pipelineElementService.getDataStreams(),
this.pipelineElementService.getDataProcessors(),
this.pipelineElementService.getDataSinks(),
).subscribe(res => {
- this.availablePipelines = res[0];
- this.runningPipelines = res[0].filter(p => p.running);
- this.availableAdapters = res[1];
+ this.availableAdapters = res[0];
this.availablePipelineElements = this.availablePipelineElements
+ .concat(...res[1])
.concat(...res[2])
- .concat(...res[3])
- .concat(...res[4]);
+ .concat(...res[3]);
this.checkForTutorial();
});
}
+
+ updateView(view: string): void {
+ this.selectedView.set(view);
+ this.localStorageService.set('default-asset-view', view);
+ }
+
+ ngOnDestroy() {
+ this.assetFilter$?.unsubscribe();
+ }
}
diff --git a/ui/src/app/home/home.module.ts b/ui/src/app/home/home.module.ts
index 9bfd0141f2..0dc059724d 100644
--- a/ui/src/app/home/home.module.ts
+++ b/ui/src/app/home/home.module.ts
@@ -32,6 +32,26 @@ import { WelcomeTourComponent } from
'./dialog/welcome-tour/welcome-tour.compone
import { SharedUiModule } from '@streampipes/shared-ui';
import { RouterModule } from '@angular/router';
import { TranslateModule } from '@ngx-translate/core';
+import { CoreUiModule } from '../core-ui/core-ui.module';
+import { MatButtonToggleModule } from '@angular/material/button-toggle';
+import { WelcomeComponent } from './components/welcome/welcome.component';
+import { HomeAssetMapComponent } from
'./components/asset-map/home-asset-map.component';
+import { LeafletModule } from '@bluehalo/ngx-leaflet';
+import { AssetMapPopupComponent } from
'./components/asset-map/asset-map-popup/asset-map-popup.component';
+import { AssetLinkChipComponent } from
'./components/asset-map/asset-map-popup/asset-link-chip/asset-link-chip.component';
+import { FormsModule } from '@angular/forms';
+import { HomeAssetTableComponent } from
'./components/asset-table/home-asset-table.component';
+import {
+ MatCell,
+ MatCellDef,
+ MatColumnDef,
+ MatHeaderCell,
+ MatHeaderCellDef,
+ MatTableModule,
+} from '@angular/material/table';
+import { MatMenuItem } from '@angular/material/menu';
+import { MatSort, MatSortHeader, MatSortModule } from '@angular/material/sort';
+import { AssetTableLinkPreviewComponent } from
'./components/asset-table/asset-table-link-preview/asset-table-link-preview.component';
@NgModule({
imports: [
@@ -56,8 +76,24 @@ import { TranslateModule } from '@ngx-translate/core';
],
},
]),
+ CoreUiModule,
+ MatButtonToggleModule,
+ LeafletModule,
+ FormsModule,
+ MatSortModule,
+ MatTableModule,
+ ],
+ declarations: [
+ HomeComponent,
+ StatusComponent,
+ WelcomeTourComponent,
+ WelcomeComponent,
+ HomeAssetMapComponent,
+ AssetMapPopupComponent,
+ AssetLinkChipComponent,
+ HomeAssetTableComponent,
+ AssetTableLinkPreviewComponent,
],
- declarations: [HomeComponent, StatusComponent, WelcomeTourComponent],
providers: [HomeService],
})
export class HomeModule {}
diff --git a/ui/src/app/home/models/home.model.ts
b/ui/src/app/home/models/home.model.ts
index 7bea8078c3..e69951af74 100644
--- a/ui/src/app/home/models/home.model.ts
+++ b/ui/src/app/home/models/home.model.ts
@@ -28,6 +28,7 @@ export interface StatusBox {
viewRoles: string[];
createRoles: string[];
icon: string;
+ assetLinkTypeId: string;
}
export interface Link {
diff --git a/ui/src/scss/sp/_variables.scss b/ui/src/scss/sp/_variables.scss
index fde74fb333..76b62d8334 100644
--- a/ui/src/scss/sp/_variables.scss
+++ b/ui/src/scss/sp/_variables.scss
@@ -18,13 +18,6 @@
// variables maintained by StreamPipes committers
-$sp-color-adapter: #7f007f;
-$sp-color-stream: #ffeb3b;
-$sp-color-processor: #009688;
-$sp-color-sink: #3f51b5;
-
-$sp-color-error: #b71c1c;
-
:root {
--mat-sys-body-large-size: var(--font-size-md);
--mat-sys-body-medium-size: var(--font-size-sm);
@@ -40,13 +33,15 @@ $sp-color-error: #b71c1c;
--fg-muted: color-mix(in oklab, currentColor 60%, transparent);
- --color-data-view: rgb(122, 206, 227);
- --color-dashboard: rgb(76, 115, 164);
- --color-adapter: rgb(182, 140, 97);
- --color-data-source: #ffeb3b;
- --color-pipeline: rgb(102, 185, 114);
- --color-measurement: rgb(39, 164, 155);
- --color-file: rgb(163, 98, 190);
+ --color-data-view: rgb(96, 165, 250);
+ --color-dashboard: rgb(79, 129, 189);
+ --color-adapter: rgb(180, 140, 95);
+ --color-data-source: rgb(234, 179, 8);
+ --color-pipeline: rgb(74, 182, 155);
+ --color-measurement: rgb(56, 178, 172);
+ --color-file: rgb(168, 85, 247);
+ --color-processor: #009688;
+ --color-sink: #3f51b5;
--button-border-radius: 5px;
--iconbar-width: 35px;
diff --git a/ui/src/scss/sp/main.scss b/ui/src/scss/sp/main.scss
index 165a199a3e..c61be69160 100644
--- a/ui/src/scss/sp/main.scss
+++ b/ui/src/scss/sp/main.scss
@@ -16,7 +16,6 @@
*
*/
-@use './variables' as spThemeVars;
@use '../custom-theme/custom-variables' as customThemeVars;
html {
@@ -29,6 +28,10 @@ html {
white-space: nowrap;
}
+.leaflet-popup-content-wrapper {
+ background: var(--color-bg-0);
+}
+
md-progress-linear.md-accent .md-container {
background-color: rgb(168, 168, 168);
}
@@ -49,10 +52,6 @@ body {
height: auto !important;
width: 100%;
- --color-adapter: #{spThemeVars.$sp-color-adapter};
- --color-stream: #{spThemeVars.$sp-color-stream};
- --color-processor: #{spThemeVars.$sp-color-processor};
- --color-sink: #{spThemeVars.$sp-color-sink};
--color-tab-border: var(--color-bg-3);
}
@@ -145,8 +144,12 @@ md-content {
.page-container {
margin: 10px;
- min-height: calc(100% - 20px);
- background: var(--color-bg-main-panel-content);
+ min-height: calc(100vh - 76px);
+ background: var(--color-bg-0);
+}
+
+.page-container-no-bg {
+ background: inherit;
}
.page-container-padding-inner {
@@ -372,39 +375,39 @@ md-content {
}
.adapter-label {
- background: spThemeVars.$sp-color-adapter;
+ background: var(--color-adapter);
color: white;
}
.stream-label {
- background: spThemeVars.$sp-color-stream;
+ background: var(--color-data-source);
color: black;
}
.processor-label {
- background: spThemeVars.$sp-color-processor;
+ background: var(--color-processor);
}
.sink-label {
- background: spThemeVars.$sp-color-sink;
+ background: var(--color-sink);
}
.adapter {
border-radius: 50%;
- border: 2px solid spThemeVars.$sp-color-adapter;
+ border: 2px solid var(--color-adapter);
}
.stream {
border-radius: 50%;
- border: 2px solid spThemeVars.$sp-color-stream;
+ border: 2px solid var(--color-data-source);
}
.action {
- border: 2px solid spThemeVars.$sp-color-sink;
+ border: 2px solid var(--color-sink);
}
.sepa {
- border: 2px solid spThemeVars.$sp-color-processor;
+ border: 2px solid var(--color-processor);
}
.draggable-img {
diff --git a/ui/src/scss/sp/pipeline-element-options.scss
b/ui/src/scss/sp/pipeline-element-options.scss
index 732feb9145..6ad08f156a 100644
--- a/ui/src/scss/sp/pipeline-element-options.scss
+++ b/ui/src/scss/sp/pipeline-element-options.scss
@@ -68,6 +68,6 @@
}
.pe-info-stream {
- background: var(--color-stream);
+ background: var(--color-data-source);
color: #343434;
}