This is an automated email from the ASF dual-hosted git repository.
riemer pushed a commit to branch 4052-add-feature-cards-for-resource-preview
in repository https://gitbox.apache.org/repos/asf/streampipes.git
The following commit(s) were added to
refs/heads/4052-add-feature-cards-for-resource-preview by this push:
new 9e316864b0 feat(#4052): Add feature cards for better preview of
resources
9e316864b0 is described below
commit 9e316864b0cae9861568b83a410e90ea78030905
Author: Dominik Riemer <[email protected]>
AuthorDate: Fri Dec 12 20:50:21 2025 +0100
feat(#4052): Add feature cards for better preview of resources
---
ui/deployment/app-routing.module.mst | 37 +++++-
ui/deployment/feature-cards.yml | 23 ++++
ui/deployment/prebuild.js | 33 ++++-
.../feature-card-header.component.html | 59 +++++++++
.../feature-card-header.component.scss | 141 +++++++++++++++++++++
.../feature-card-header.component.ts} | 41 +++---
.../feature-card-host.component.html | 8 ++
.../feature-card-host.component.scss} | 44 ++++---
.../feature-card-host.component.ts | 35 +++++
.../feature-card-meta-creation.component.html | 40 ++++++
.../feature-card-meta-creation.component.ts} | 33 ++---
.../feature-card-meta-section.component.html | 23 ++++
.../feature-card-meta-section.component.ts} | 31 ++---
.../feature-card-host/feature-card.model.ts} | 27 +---
.../feature-card-host/feature-card.service.ts | 59 +++++++++
.../lib/dialog/base-dialog/base-dialog.model.ts | 1 +
.../lib/dialog/base-dialog/base-dialog.service.ts | 27 ++--
.../card-dialog-config.ts} | 18 +--
.../dialog/card-dialog/card-dialog.component.html | 21 +++
.../card-dialog.component.scss} | 30 ++---
.../dialog/card-dialog/card-dialog.component.ts | 96 ++++++++++++++
.../standard-dialog/standard-dialog.config.ts | 8 --
.../shared-ui/src/lib/shared-ui.module.ts | 14 ++
.../streampipes/shared-ui/src/public-api.ts | 5 +
.../asset-link-table.component.html | 1 +
.../asset-link-table/asset-link-table.component.ts | 13 +-
.../chart-shared/services/chart-shared.service.ts | 15 +++
.../chart-feature-card.component.html | 93 ++++++++++++++
.../chart-feature-card.component.scss} | 44 ++++---
.../chart-feature-card.component.ts | 96 ++++++++++++++
.../chart-overview-table.component.html | 21 ++-
.../chart-overview-table.component.ts | 6 +
.../components/chart-view/chart-view.component.ts | 16 +--
.../pipeline-details/pipeline-details.module.ts | 2 +-
.../pipeline-feature-card.component.html | 52 ++++++++
.../pipeline-feature-card.component.scss} | 44 ++++---
.../pipeline-feature-card.component.ts | 85 +++++++++++++
.../pipeline-preview-meta.component.html | 96 ++++++++++++++
.../pipeline-preview-meta.component.scss} | 28 +---
.../pipeline-preview-meta.component.ts | 65 ++++++++++
.../pipeline-overview.component.html | 16 ++-
.../pipeline-overview.component.ts | 23 ++--
ui/src/scss/main.scss | 1 +
ui/src/scss/sp/feature-card.scss | 86 +++++++++++++
44 files changed, 1396 insertions(+), 261 deletions(-)
diff --git a/ui/deployment/app-routing.module.mst
b/ui/deployment/app-routing.module.mst
index d26252358a..47d93d61c2 100644
--- a/ui/deployment/app-routing.module.mst
+++ b/ui/deployment/app-routing.module.mst
@@ -64,10 +64,43 @@ const routes: Routes = [
{ path: '', component: StreampipesComponent, children: [
{{#modulesActive}}
{{#componentImport}}
- { path: '{{{link}}}', component: {{{component}}}, data: { privileges:
{{{privileges}}}, authPageNames: [{{{pageNames}}}]}},
+ { path: '{{{link}}}', component: {{{component}}}, data: {
+ privileges: {{{privileges}}},
+ authPageNames: [{{{pageNames}}}]
+ {{#hasFeatureCards}},
+ cards: [
+ {{#featureCards}}
+ {
+ id: '{{{id}}}',
+ loadComponent: () =>
+ import('{{{componentPath}}}')
+ .then(m => m.{{{componentName}}})
+ },
+ {{/featureCards}}
+ ]
+ {{/hasFeatureCards}}
+ }
+ },
{{/componentImport}}
{{^componentImport}}
- { path: '{{{link}}}', data: { privileges: {{{privileges}}},
authPageNames: [{{{pageNames}}}]}, loadChildren: () =>
import('{{{path}}}').then(m => m.{{{moduleName}}})},
+ { path: '{{{link}}}', data: {
+ privileges: {{{privileges}}},
+ authPageNames: [{{{pageNames}}}]
+ {{#hasFeatureCards}},
+ cards: [
+ {{#featureCards}}
+ {
+ id: '{{{id}}}',
+ loadComponent: () =>
+ import('{{{componentPath}}}')
+ .then(m => m.{{{componentName}}})
+ },
+ {{/featureCards}}
+ ]
+ {{/hasFeatureCards}}
+ },
+ loadChildren: () => import('{{{path}}}').then(m => m.{{{moduleName}}})
+ },
{{/componentImport}}
{{/modulesActive}}
diff --git a/ui/deployment/feature-cards.yml b/ui/deployment/feature-cards.yml
new file mode 100644
index 0000000000..17fcdd356c
--- /dev/null
+++ b/ui/deployment/feature-cards.yml
@@ -0,0 +1,23 @@
+# 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.
+
+- id: pipeline
+ componentPath:
'./pipelines/components/pipeline-feature-card/pipeline-feature-card.component'
+ componentName: PipelineFeatureCardComponent
+ moduleName: spPipelines
+- id: chart
+ componentPath:
'./chart/components/chart-feature-card/chart-feature-card.component'
+ componentName: ChartFeatureCardComponent
+ moduleName: spChart
diff --git a/ui/deployment/prebuild.js b/ui/deployment/prebuild.js
index 5e2448203c..681391aa05 100644
--- a/ui/deployment/prebuild.js
+++ b/ui/deployment/prebuild.js
@@ -55,6 +55,7 @@ try {
// Read Modules-File and check if it is valid
let modules = {};
let categories = [];
+let featureCards = [];
try {
modules = yaml.load(fs.readFileSync('deployment/modules.yml', 'utf8'));
} catch (error) {
@@ -73,9 +74,37 @@ try {
process.exit(1);
}
+try {
+ featureCards = yaml.load(
+ fs.readFileSync('deployment/feature-cards.yml', 'utf8'),
+ );
+} catch (error) {
+ console.log(
+ 'Invalid file feature-cards.yml. Check if the file exists in your
build configuration. Pre-Build failed.',
+ );
+ process.exit(1);
+}
+
+const featureCardsByModule = {};
+for (const card of featureCards || []) {
+ const moduleKey = card.moduleName;
+ if (!moduleKey) {
+ console.warn('feature-cards.yml entry without moduleName:', card);
+ continue;
+ }
+ if (!featureCardsByModule[moduleKey]) {
+ featureCardsByModule[moduleKey] = [];
+ }
+ featureCardsByModule[moduleKey].push(card);
+}
+
// Add active Modules to Template-Variable
-let modulesActive = { modulesActive: [], categoriesActive: categories };
+let modulesActive = {
+ modulesActive: [],
+ categoriesActive: categories,
+};
for (let module of config.modules) {
+ const cardsForModule = featureCardsByModule[module] || [];
modulesActive['modulesActive'].push({
module: module,
componentImport: modules[module]['componentImport'],
@@ -94,6 +123,8 @@ for (let module of config.modules) {
showStatusBox: modules[module]['showStatusBox'],
statusBox: modules[module]['statusBox'],
category: modules[module]['category'],
+ featureCards: cardsForModule,
+ hasFeatureCards: cardsForModule.length > 0,
});
console.log('Active Angular Module: ' + module);
}
diff --git
a/ui/projects/streampipes/shared-ui/src/lib/components/feature-card-host/feature-card-header/feature-card-header.component.html
b/ui/projects/streampipes/shared-ui/src/lib/components/feature-card-host/feature-card-header/feature-card-header.component.html
new file mode 100644
index 0000000000..20be8f4aae
--- /dev/null
+++
b/ui/projects/streampipes/shared-ui/src/lib/components/feature-card-host/feature-card-header/feature-card-header.component.html
@@ -0,0 +1,59 @@
+<!--
+ ~ 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="feature-card-header"
+ fxLayout="row"
+ fxLayoutAlign="start center"
+ fxLayoutGap="1.25rem"
+ [style.--feature-header-color]="iconColor"
+>
+ <div class="feature-card-header__icon" fxFlex="none" *ngIf="icon">
+ <div class="feature-card-header__icon-circle">
+ <mat-icon [style.color]="iconColor">{{ icon }}</mat-icon>
+ </div>
+ </div>
+
+ <div
+ class="feature-card-header__content"
+ fxFlex
+ fxLayout="column"
+ fxLayoutAlign="start start"
+ >
+ <h2 class="feature-card-header__title">{{ title }}</h2>
+ <p class="feature-card-header__description">{{ description }}</p>
+
+ <button
+ *ngIf="detailsLink?.length"
+ mat-flat-button
+ class="feature-card-header__button"
+ (click)="navigateToDetails()"
+ >
+ More details
+ </button>
+ </div>
+
+ <button
+ mat-icon-button
+ class="feature-card-header__close"
+ aria-label="Close dialog"
+ (click)="close.emit()"
+ >
+ <mat-icon>close</mat-icon>
+ </button>
+</div>
diff --git
a/ui/projects/streampipes/shared-ui/src/lib/components/feature-card-host/feature-card-header/feature-card-header.component.scss
b/ui/projects/streampipes/shared-ui/src/lib/components/feature-card-host/feature-card-header/feature-card-header.component.scss
new file mode 100644
index 0000000000..5e9a671126
--- /dev/null
+++
b/ui/projects/streampipes/shared-ui/src/lib/components/feature-card-host/feature-card-header/feature-card-header.component.scss
@@ -0,0 +1,141 @@
+/*
+ * 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;
+}
+
+.feature-card-header {
+ --feature-header-color: var(--feature-header-color, #3f51b5);
+
+ display: flex;
+ align-items: center;
+ gap: 1.25rem;
+ padding: 1.25rem 1.5rem;
+ border-top-left-radius: 1rem;
+ border-top-right-radius: 1rem;
+ box-sizing: border-box;
+
+ height: 7rem;
+ min-height: 7rem;
+
+ background: var(--feature-header-color);
+ color: #fff;
+
+ /* IMPORTANT: allows absolutely positioned close button */
+ position: relative;
+}
+
+/* Icon wrapper */
+.feature-card-header__icon {
+ flex: 0 0 auto;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+}
+
+/* Circle around icon (lighter chip) */
+.feature-card-header__icon-circle {
+ width: 3rem;
+ height: 3rem;
+ border-radius: 999px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+
+ background: color-mix(
+ in srgb,
+ #ffffff 75%,
+ var(--feature-header-color) 25%
+ );
+
+ mat-icon {
+ font-size: 1.75rem;
+ width: 1.75rem;
+ height: 1.75rem;
+
+ /* lightened icon color vs. header bg */
+ color: color-mix(in srgb, #ffffff 85%, var(--feature-header-color)
15%);
+ }
+}
+
+/* Text + button container */
+.feature-card-header__content {
+ flex: 1 1 auto;
+ display: flex;
+ flex-direction: column;
+ align-items: flex-start;
+ min-width: 0;
+}
+
+/* Title */
+.feature-card-header__title {
+ font-size: var(--text-lg, 1.1rem);
+ font-weight: 600;
+ margin: 0 0 0.25rem;
+ max-width: 100%;
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+}
+
+/* Description */
+.feature-card-header__description {
+ font-size: var(--text-md, 0.9rem);
+ margin: 0;
+ opacity: 0.9;
+ max-width: 100%;
+ display: -webkit-box;
+ -webkit-line-clamp: 2;
+ -webkit-box-orient: vertical;
+ overflow: hidden;
+}
+
+/* Flat button: white background, iconColor text */
+.feature-card-header__button.mat-mdc-unelevated-button {
+ margin-top: 0.75rem;
+ align-self: flex-start;
+ white-space: nowrap;
+ border-radius: 999px;
+ padding-inline: 1.25rem;
+
+ --mdc-filled-button-container-color: #ffffff;
+ --mdc-filled-button-label-text-color: var(--feature-header-color);
+}
+
+.feature-card-header__close {
+ position: absolute;
+ top: 0.5rem;
+ right: 0.5rem;
+
+ /* icon color (slightly lightened) */
+ color: color-mix(
+ in srgb,
+ #ffffff 80%,
+ var(--feature-header-color) 20%
+ ) !important;
+
+ mat-icon {
+ font-size: 1.5rem;
+ }
+}
+
+.feature-card-header__close:hover {
+ background: color-mix(in srgb, #000000 6%, var(--feature-header-color)
94%);
+ border-radius: 999px;
+}
diff --git
a/ui/projects/streampipes/shared-ui/src/lib/dialog/base-dialog/base-dialog.model.ts
b/ui/projects/streampipes/shared-ui/src/lib/components/feature-card-host/feature-card-header/feature-card-header.component.ts
similarity index 53%
copy from
ui/projects/streampipes/shared-ui/src/lib/dialog/base-dialog/base-dialog.model.ts
copy to
ui/projects/streampipes/shared-ui/src/lib/components/feature-card-host/feature-card-header/feature-card-header.component.ts
index 474b3d8f12..0b42fcc42b 100644
---
a/ui/projects/streampipes/shared-ui/src/lib/dialog/base-dialog/base-dialog.model.ts
+++
b/ui/projects/streampipes/shared-ui/src/lib/components/feature-card-host/feature-card-header/feature-card-header.component.ts
@@ -16,28 +16,29 @@
*
*/
-import { PanelDialogComponent } from '../panel-dialog/panel-dialog.component';
-import { StandardDialogComponent } from
'../standard-dialog/standard-dialog.component';
+import { Component, EventEmitter, inject, Input, Output } from '@angular/core';
+import { Router } from '@angular/router';
-export type BaseDialogComponentUnion =
- | PanelDialogComponent<unknown>
- | StandardDialogComponent<unknown>;
+@Component({
+ selector: 'sp-feature-card-header',
+ templateUrl: './feature-card-header.component.html',
+ styleUrls: ['./feature-card-header.component.scss'],
+ standalone: false,
+})
+export class FeatureCardHeaderComponent {
+ @Input() title: string;
+ @Input() description: string;
-export enum PanelType {
- STANDARD_PANEL,
- SLIDE_IN_PANEL,
-}
+ @Input() icon: string;
+ @Input() iconColor: string;
+ @Input() detailsLink: string[];
+ @Output() close: EventEmitter<void> = new EventEmitter();
-export interface DialogConfig {
- width?: string;
- panelType: PanelType;
- disableClose?: boolean;
- autoFocus?: boolean;
- title: string;
- data?: any;
-}
+ private router = inject(Router);
+
+ ngOnInit() {}
-export interface DialogPanelConfig {
- maxWidth: string;
- height: string;
+ navigateToDetails(): void {
+ this.router.navigate(this.detailsLink);
+ }
}
diff --git
a/ui/projects/streampipes/shared-ui/src/lib/components/feature-card-host/feature-card-host.component.html
b/ui/projects/streampipes/shared-ui/src/lib/components/feature-card-host/feature-card-host.component.html
new file mode 100644
index 0000000000..69c70791d5
--- /dev/null
+++
b/ui/projects/streampipes/shared-ui/src/lib/components/feature-card-host/feature-card-host.component.html
@@ -0,0 +1,8 @@
+<div class="card-root">
+ @if (activeComponent) {
+ <ng-container
+ *ngComponentOutlet="activeComponent; inputs: componentInputs"
+ >
+ </ng-container>
+ }
+</div>
diff --git
a/ui/projects/streampipes/shared-ui/src/lib/dialog/base-dialog/base-dialog.model.ts
b/ui/projects/streampipes/shared-ui/src/lib/components/feature-card-host/feature-card-host.component.scss
similarity index 57%
copy from
ui/projects/streampipes/shared-ui/src/lib/dialog/base-dialog/base-dialog.model.ts
copy to
ui/projects/streampipes/shared-ui/src/lib/components/feature-card-host/feature-card-host.component.scss
index 474b3d8f12..e14a844b48 100644
---
a/ui/projects/streampipes/shared-ui/src/lib/dialog/base-dialog/base-dialog.model.ts
+++
b/ui/projects/streampipes/shared-ui/src/lib/components/feature-card-host/feature-card-host.component.scss
@@ -16,28 +16,32 @@
*
*/
-import { PanelDialogComponent } from '../panel-dialog/panel-dialog.component';
-import { StandardDialogComponent } from
'../standard-dialog/standard-dialog.component';
-
-export type BaseDialogComponentUnion =
- | PanelDialogComponent<unknown>
- | StandardDialogComponent<unknown>;
-
-export enum PanelType {
- STANDARD_PANEL,
- SLIDE_IN_PANEL,
+/* The component hosting the dynamic card */
+:host {
+ display: flex;
+ width: 100%;
+ height: 100%;
+ min-width: 0;
+ min-height: 0;
}
-export interface DialogConfig {
- width?: string;
- panelType: PanelType;
- disableClose?: boolean;
- autoFocus?: boolean;
- title: string;
- data?: any;
+/* Root container that holds the dynamic component */
+.card-root {
+ flex: 1 1 auto;
+ display: flex;
+ width: 100%;
+ height: 100%;
+ min-width: 0;
+ min-height: 0;
+ background: var(--color-bg-0);
+ border-radius: 1rem;
}
-export interface DialogPanelConfig {
- maxWidth: string;
- height: string;
+/* The dynamically inserted component's host element */
+.card-root > * {
+ flex: 1 1 auto;
+ width: 100%;
+ height: 100%;
+ min-width: 0;
+ min-height: 0;
}
diff --git
a/ui/projects/streampipes/shared-ui/src/lib/components/feature-card-host/feature-card-host.component.ts
b/ui/projects/streampipes/shared-ui/src/lib/components/feature-card-host/feature-card-host.component.ts
new file mode 100644
index 0000000000..bb2b660b13
--- /dev/null
+++
b/ui/projects/streampipes/shared-ui/src/lib/components/feature-card-host/feature-card-host.component.ts
@@ -0,0 +1,35 @@
+import { FeatureCardRouteData } from './feature-card.model';
+import { Component, inject, Input, OnInit, Type } from '@angular/core';
+import { DialogRef } from '../../dialog/base-dialog/dialog-ref';
+
+@Component({
+ selector: 'sp-feature-card-host',
+ templateUrl: './feature-card-host.component.html',
+ styleUrls: ['./feature-card-host.component.scss'],
+ standalone: false,
+})
+export class FeatureCardHostComponent implements OnInit {
+ activeComponent: Type<any> | null = null;
+
+ @Input()
+ resourceId: string;
+
+ @Input()
+ card: FeatureCardRouteData;
+
+ componentInputs: Record<string, any> | null = null;
+
+ private dialogRef = inject(DialogRef<FeatureCardHostComponent>);
+
+ async ngOnInit() {
+ this.componentInputs = {
+ resourceId: this.resourceId,
+ onClose: () => this.close(),
+ };
+ this.activeComponent = await this.card.loadComponent();
+ }
+
+ close(): void {
+ this.dialogRef.close();
+ }
+}
diff --git
a/ui/projects/streampipes/shared-ui/src/lib/components/feature-card-host/feature-card-meta-creation/feature-card-meta-creation.component.html
b/ui/projects/streampipes/shared-ui/src/lib/components/feature-card-host/feature-card-meta-creation/feature-card-meta-creation.component.html
new file mode 100644
index 0000000000..79ab01734f
--- /dev/null
+++
b/ui/projects/streampipes/shared-ui/src/lib/components/feature-card-host/feature-card-meta-creation/feature-card-meta-creation.component.html
@@ -0,0 +1,40 @@
+<!--
+ ~ Licensed to the Apache Software Foundation (ASF) under one or more
+ ~ contributor license agreements. See the NOTICE file distributed with
+ ~ this work for additional information regarding copyright ownership.
+ ~ The ASF licenses this file to You under the Apache License, Version 2.0
+ ~ (the "License"); you may not use this file except in compliance with
+ ~ the License. You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ ~
+ -->
+
+<sp-feature-card-meta-section [label]="'Lifecycle' | translate">
+ <div fxLayout="row" fxLayoutGap="0.75rem">
+ @if (creationTimestamp) {
+ <mat-icon class="feature-card-meta-icon">create</mat-icon>
+ <div fxLayout="column">
+ <span class="feature-card-meta-field-label">{{
+ 'Created' | translate
+ }}</span>
+ <span class="feature-card-meta-field-value">
+ {{ (creationTimestamp | date: 'short') || '—' }}
+ </span>
+ </div>
+ }
+ <mat-icon class="feature-card-meta-icon">update</mat-icon>
+ <div fxLayout="column">
+ <span class="feature-card-meta-field-label">Last modified</span>
+ <span class="feature-card-meta-field-value">
+ {{ (lastModifiedTimestamp | date: 'short') || '—' }}
+ </span>
+ </div>
+ </div>
+</sp-feature-card-meta-section>
diff --git
a/ui/projects/streampipes/shared-ui/src/lib/dialog/base-dialog/base-dialog.model.ts
b/ui/projects/streampipes/shared-ui/src/lib/components/feature-card-host/feature-card-meta-creation/feature-card-meta-creation.component.ts
similarity index 57%
copy from
ui/projects/streampipes/shared-ui/src/lib/dialog/base-dialog/base-dialog.model.ts
copy to
ui/projects/streampipes/shared-ui/src/lib/components/feature-card-host/feature-card-meta-creation/feature-card-meta-creation.component.ts
index 474b3d8f12..a0d882a330 100644
---
a/ui/projects/streampipes/shared-ui/src/lib/dialog/base-dialog/base-dialog.model.ts
+++
b/ui/projects/streampipes/shared-ui/src/lib/components/feature-card-host/feature-card-meta-creation/feature-card-meta-creation.component.ts
@@ -16,28 +16,17 @@
*
*/
-import { PanelDialogComponent } from '../panel-dialog/panel-dialog.component';
-import { StandardDialogComponent } from
'../standard-dialog/standard-dialog.component';
+import { Component, Input } from '@angular/core';
-export type BaseDialogComponentUnion =
- | PanelDialogComponent<unknown>
- | StandardDialogComponent<unknown>;
+@Component({
+ selector: 'sp-feature-card-meta-creation',
+ templateUrl: './feature-card-meta-creation.component.html',
+ standalone: false,
+})
+export class FeatureCardMetaCreationComponent {
+ @Input()
+ creationTimestamp: number;
-export enum PanelType {
- STANDARD_PANEL,
- SLIDE_IN_PANEL,
-}
-
-export interface DialogConfig {
- width?: string;
- panelType: PanelType;
- disableClose?: boolean;
- autoFocus?: boolean;
- title: string;
- data?: any;
-}
-
-export interface DialogPanelConfig {
- maxWidth: string;
- height: string;
+ @Input()
+ lastModifiedTimestamp: number;
}
diff --git
a/ui/projects/streampipes/shared-ui/src/lib/components/feature-card-host/feature-card-meta-section/feature-card-meta-section.component.html
b/ui/projects/streampipes/shared-ui/src/lib/components/feature-card-host/feature-card-meta-section/feature-card-meta-section.component.html
new file mode 100644
index 0000000000..5391f25648
--- /dev/null
+++
b/ui/projects/streampipes/shared-ui/src/lib/components/feature-card-host/feature-card-meta-section/feature-card-meta-section.component.html
@@ -0,0 +1,23 @@
+<!--
+ ~ 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="feature-card-meta-section">
+ <div class="feature-card-meta-section__label">{{ label }}</div>
+
+ <ng-content></ng-content>
+</div>
diff --git
a/ui/projects/streampipes/shared-ui/src/lib/dialog/base-dialog/base-dialog.model.ts
b/ui/projects/streampipes/shared-ui/src/lib/components/feature-card-host/feature-card-meta-section/feature-card-meta-section.component.ts
similarity index 57%
copy from
ui/projects/streampipes/shared-ui/src/lib/dialog/base-dialog/base-dialog.model.ts
copy to
ui/projects/streampipes/shared-ui/src/lib/components/feature-card-host/feature-card-meta-section/feature-card-meta-section.component.ts
index 474b3d8f12..9694666bc1 100644
---
a/ui/projects/streampipes/shared-ui/src/lib/dialog/base-dialog/base-dialog.model.ts
+++
b/ui/projects/streampipes/shared-ui/src/lib/components/feature-card-host/feature-card-meta-section/feature-card-meta-section.component.ts
@@ -16,28 +16,13 @@
*
*/
-import { PanelDialogComponent } from '../panel-dialog/panel-dialog.component';
-import { StandardDialogComponent } from
'../standard-dialog/standard-dialog.component';
+import { Component, Input } from '@angular/core';
-export type BaseDialogComponentUnion =
- | PanelDialogComponent<unknown>
- | StandardDialogComponent<unknown>;
-
-export enum PanelType {
- STANDARD_PANEL,
- SLIDE_IN_PANEL,
-}
-
-export interface DialogConfig {
- width?: string;
- panelType: PanelType;
- disableClose?: boolean;
- autoFocus?: boolean;
- title: string;
- data?: any;
-}
-
-export interface DialogPanelConfig {
- maxWidth: string;
- height: string;
+@Component({
+ selector: 'sp-feature-card-meta-section',
+ templateUrl: './feature-card-meta-section.component.html',
+ standalone: false,
+})
+export class FeatureCardMetaSectionComponent {
+ @Input() label: string;
}
diff --git
a/ui/projects/streampipes/shared-ui/src/lib/dialog/base-dialog/base-dialog.model.ts
b/ui/projects/streampipes/shared-ui/src/lib/components/feature-card-host/feature-card.model.ts
similarity index 57%
copy from
ui/projects/streampipes/shared-ui/src/lib/dialog/base-dialog/base-dialog.model.ts
copy to
ui/projects/streampipes/shared-ui/src/lib/components/feature-card-host/feature-card.model.ts
index 474b3d8f12..b53b3a8fbb 100644
---
a/ui/projects/streampipes/shared-ui/src/lib/dialog/base-dialog/base-dialog.model.ts
+++
b/ui/projects/streampipes/shared-ui/src/lib/components/feature-card-host/feature-card.model.ts
@@ -16,28 +16,9 @@
*
*/
-import { PanelDialogComponent } from '../panel-dialog/panel-dialog.component';
-import { StandardDialogComponent } from
'../standard-dialog/standard-dialog.component';
+import { Type } from '@angular/core';
-export type BaseDialogComponentUnion =
- | PanelDialogComponent<unknown>
- | StandardDialogComponent<unknown>;
-
-export enum PanelType {
- STANDARD_PANEL,
- SLIDE_IN_PANEL,
-}
-
-export interface DialogConfig {
- width?: string;
- panelType: PanelType;
- disableClose?: boolean;
- autoFocus?: boolean;
- title: string;
- data?: any;
-}
-
-export interface DialogPanelConfig {
- maxWidth: string;
- height: string;
+export interface FeatureCardRouteData {
+ id: string;
+ loadComponent: () => Promise<Type<any>>;
}
diff --git
a/ui/projects/streampipes/shared-ui/src/lib/components/feature-card-host/feature-card.service.ts
b/ui/projects/streampipes/shared-ui/src/lib/components/feature-card-host/feature-card.service.ts
new file mode 100644
index 0000000000..ff8ed94bae
--- /dev/null
+++
b/ui/projects/streampipes/shared-ui/src/lib/components/feature-card-host/feature-card.service.ts
@@ -0,0 +1,59 @@
+/*
+ * 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 { inject, Injectable } from '@angular/core';
+import { FeatureCardHostComponent } from './feature-card-host.component';
+import { Router } from '@angular/router';
+import { FeatureCardRouteData } from './feature-card.model';
+import { DialogService } from '../../dialog/base-dialog/base-dialog.service';
+import { PanelType } from '../../dialog/base-dialog/base-dialog.model';
+
+@Injectable({ providedIn: 'root' })
+export class FeatureCardService {
+ private dialogService = inject(DialogService);
+ private router = inject(Router);
+
+ cards: FeatureCardRouteData[] = [];
+
+ constructor() {
+ const configs = this.router.config;
+ this.cards = configs
+ .find(config => config.path === '')
+ .children.filter(r => r.data && (r.data as any).cards)
+ .flatMap(r => (r.data as any).cards as FeatureCardRouteData[]);
+ }
+
+ openFeatureCard(cardId: string, resourceId: string) {
+ if (this.supportsFeatureCard(cardId)) {
+ const card = this.cards.find(card => card.id === cardId);
+ this.dialogService.open(FeatureCardHostComponent, {
+ panelType: PanelType.CARD,
+ title: '',
+ width: '400px',
+ data: {
+ card,
+ resourceId,
+ },
+ });
+ }
+ }
+
+ supportsFeatureCard(cardId: string) {
+ return this.cards.find(card => card.id === cardId) !== undefined;
+ }
+}
diff --git
a/ui/projects/streampipes/shared-ui/src/lib/dialog/base-dialog/base-dialog.model.ts
b/ui/projects/streampipes/shared-ui/src/lib/dialog/base-dialog/base-dialog.model.ts
index 474b3d8f12..36c6fa913b 100644
---
a/ui/projects/streampipes/shared-ui/src/lib/dialog/base-dialog/base-dialog.model.ts
+++
b/ui/projects/streampipes/shared-ui/src/lib/dialog/base-dialog/base-dialog.model.ts
@@ -26,6 +26,7 @@ export type BaseDialogComponentUnion =
export enum PanelType {
STANDARD_PANEL,
SLIDE_IN_PANEL,
+ CARD,
}
export interface DialogConfig {
diff --git
a/ui/projects/streampipes/shared-ui/src/lib/dialog/base-dialog/base-dialog.service.ts
b/ui/projects/streampipes/shared-ui/src/lib/dialog/base-dialog/base-dialog.service.ts
index 5c29a16d2a..271324b228 100644
---
a/ui/projects/streampipes/shared-ui/src/lib/dialog/base-dialog/base-dialog.service.ts
+++
b/ui/projects/streampipes/shared-ui/src/lib/dialog/base-dialog/base-dialog.service.ts
@@ -20,7 +20,6 @@ import { ComponentType, Overlay, OverlayRef } from
'@angular/cdk/overlay';
import { ComponentRef, Injectable, Injector } from '@angular/core';
import { DialogRef } from './dialog-ref';
import { ComponentPortal, PortalInjector } from '@angular/cdk/portal';
-import { BaseDialogComponent } from './base-dialog.component';
import {
BaseDialogComponentUnion,
DialogConfig,
@@ -31,6 +30,8 @@ import { StandardDialogComponent } from
'../standard-dialog/standard-dialog.comp
import { BaseDialogConfig } from './base-dialog.config';
import { PanelDialogConfig } from '../panel-dialog/panel-dialog.config';
import { StandardDialogConfig } from
'../standard-dialog/standard-dialog.config';
+import { CardDialogComponent } from '../card-dialog/card-dialog.component';
+import { CardDialogConfig } from '../card-dialog/card-dialog-config';
@Injectable({
providedIn: 'root',
@@ -84,9 +85,7 @@ export class DialogService {
return dialogRef;
}
- private createInjector<T, OUTER extends BaseDialogComponent<T>>(
- dialogRef: DialogRef<T>,
- ) {
+ private createInjector<T>(dialogRef: DialogRef<T>) {
const injectorMap = new WeakMap();
injectorMap.set(DialogRef, dialogRef);
return new PortalInjector(this.injector, injectorMap);
@@ -116,14 +115,22 @@ export class DialogService {
}
getPanel(panelType: PanelType): ComponentType<BaseDialogComponentUnion> {
- return panelType === PanelType.SLIDE_IN_PANEL
- ? PanelDialogComponent
- : StandardDialogComponent;
+ if (panelType === PanelType.STANDARD_PANEL) {
+ return StandardDialogComponent;
+ } else if (panelType === PanelType.CARD) {
+ return CardDialogComponent;
+ } else {
+ return PanelDialogComponent;
+ }
}
getConfig(panelType: PanelType): BaseDialogConfig {
- return panelType === PanelType.SLIDE_IN_PANEL
- ? new PanelDialogConfig()
- : new StandardDialogConfig();
+ if (panelType === PanelType.STANDARD_PANEL) {
+ return new StandardDialogConfig();
+ } else if (panelType === PanelType.CARD) {
+ return new CardDialogConfig();
+ } else {
+ return new PanelDialogConfig();
+ }
}
}
diff --git
a/ui/projects/streampipes/shared-ui/src/lib/dialog/standard-dialog/standard-dialog.config.ts
b/ui/projects/streampipes/shared-ui/src/lib/dialog/card-dialog/card-dialog-config.ts
similarity index 69%
copy from
ui/projects/streampipes/shared-ui/src/lib/dialog/standard-dialog/standard-dialog.config.ts
copy to
ui/projects/streampipes/shared-ui/src/lib/dialog/card-dialog/card-dialog-config.ts
index 386df0e428..7570348df3 100644
---
a/ui/projects/streampipes/shared-ui/src/lib/dialog/standard-dialog/standard-dialog.config.ts
+++
b/ui/projects/streampipes/shared-ui/src/lib/dialog/card-dialog/card-dialog-config.ts
@@ -17,30 +17,18 @@
*/
import { BaseDialogConfig } from '../base-dialog/base-dialog.config';
-import { DialogPanelConfig } from '../base-dialog/base-dialog.model';
import { Overlay } from '@angular/cdk/overlay';
-export class StandardDialogConfig implements BaseDialogConfig {
- getConfig(): DialogPanelConfig {
- const config: DialogPanelConfig = {} as DialogPanelConfig;
- config.maxWidth = '90vw';
- config.height = '50vh';
- return config;
- }
-
+export class CardDialogConfig implements BaseDialogConfig {
getPosition(overlay: Overlay) {
- return overlay
- .position()
- .global()
- .centerHorizontally()
- .centerVertically();
+ return overlay.position().global().end('50px').centerVertically();
}
getOverlayConfig(config: any, positionStrategy: any) {
return {
hasBackdrop: true,
positionStrategy,
- panelClass: 'dialog-container',
+ panelClass: 'card-dialog-outer',
width: config.width,
maxWidth: '90vw',
};
diff --git
a/ui/projects/streampipes/shared-ui/src/lib/dialog/card-dialog/card-dialog.component.html
b/ui/projects/streampipes/shared-ui/src/lib/dialog/card-dialog/card-dialog.component.html
new file mode 100644
index 0000000000..f1caba2f2c
--- /dev/null
+++
b/ui/projects/streampipes/shared-ui/src/lib/dialog/card-dialog/card-dialog.component.html
@@ -0,0 +1,21 @@
+<!--
+ ~ 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="card-dialog-outer">
+ <ng-template cdkPortalOutlet #portal></ng-template>
+</div>
diff --git
a/ui/projects/streampipes/shared-ui/src/lib/dialog/base-dialog/base-dialog.model.ts
b/ui/projects/streampipes/shared-ui/src/lib/dialog/card-dialog/card-dialog.component.scss
similarity index 57%
copy from
ui/projects/streampipes/shared-ui/src/lib/dialog/base-dialog/base-dialog.model.ts
copy to
ui/projects/streampipes/shared-ui/src/lib/dialog/card-dialog/card-dialog.component.scss
index 474b3d8f12..e75d8a532a 100644
---
a/ui/projects/streampipes/shared-ui/src/lib/dialog/base-dialog/base-dialog.model.ts
+++
b/ui/projects/streampipes/shared-ui/src/lib/dialog/card-dialog/card-dialog.component.scss
@@ -16,28 +16,14 @@
*
*/
-import { PanelDialogComponent } from '../panel-dialog/panel-dialog.component';
-import { StandardDialogComponent } from
'../standard-dialog/standard-dialog.component';
-
-export type BaseDialogComponentUnion =
- | PanelDialogComponent<unknown>
- | StandardDialogComponent<unknown>;
-
-export enum PanelType {
- STANDARD_PANEL,
- SLIDE_IN_PANEL,
-}
-
-export interface DialogConfig {
- width?: string;
- panelType: PanelType;
- disableClose?: boolean;
- autoFocus?: boolean;
- title: string;
- data?: any;
+card-dialog {
+ width: 100%;
}
-export interface DialogPanelConfig {
- maxWidth: string;
- height: string;
+.card-dialog-outer {
+ width: 100%;
+ height: 90vh;
+ margin-top: auto;
+ margin-bottom: auto;
+ border-radius: 1rem;
}
diff --git
a/ui/projects/streampipes/shared-ui/src/lib/dialog/card-dialog/card-dialog.component.ts
b/ui/projects/streampipes/shared-ui/src/lib/dialog/card-dialog/card-dialog.component.ts
new file mode 100644
index 0000000000..3e2d065e84
--- /dev/null
+++
b/ui/projects/streampipes/shared-ui/src/lib/dialog/card-dialog/card-dialog.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,
+ EventEmitter,
+ HostBinding,
+ HostListener,
+ OnInit,
+ Output,
+ ViewEncapsulation,
+} from '@angular/core';
+import {
+ animate,
+ state,
+ style,
+ transition,
+ trigger,
+} from '@angular/animations';
+import { BaseDialogComponent } from '../base-dialog/base-dialog.component';
+
+@Component({
+ selector: 'card-dialog',
+ templateUrl: './card-dialog.component.html',
+ encapsulation: ViewEncapsulation.None,
+ styleUrls: ['./card-dialog.component.scss'],
+ animations: [
+ trigger('unfoldInOut', [
+ state(
+ 'void',
+ style({
+ transform: 'scale(0)',
+ opacity: 0,
+ }),
+ ),
+ state(
+ 'in',
+ style({
+ transform: 'scale(1)',
+ opacity: 1,
+ }),
+ ),
+ state(
+ 'out',
+ style({
+ transform: 'scale(0)',
+ opacity: 0,
+ }),
+ ),
+ transition('* => *', animate('300ms ease-out')),
+ ]),
+ ],
+ standalone: false,
+})
+export class CardDialogComponent<T>
+ extends BaseDialogComponent<T>
+ implements OnInit
+{
+ constructor() {
+ super();
+ }
+
+ // Use new animation
+ @HostBinding('@unfoldInOut') unfold = 'in';
+
+ @Output()
+ animationStateChanged = new EventEmitter<AnimationEvent>();
+
+ @HostListener('@unfoldInOut.done', ['$event'])
+ startDrawerHandler(event: any): void {
+ if (event.toState === 'out') {
+ this.containerEvent.emit({ key: 'CLOSE' });
+ }
+ }
+
+ ngOnInit() {}
+
+ closeDialog() {
+ this.unfold = 'out';
+ }
+}
diff --git
a/ui/projects/streampipes/shared-ui/src/lib/dialog/standard-dialog/standard-dialog.config.ts
b/ui/projects/streampipes/shared-ui/src/lib/dialog/standard-dialog/standard-dialog.config.ts
index 386df0e428..bc96b4fa1a 100644
---
a/ui/projects/streampipes/shared-ui/src/lib/dialog/standard-dialog/standard-dialog.config.ts
+++
b/ui/projects/streampipes/shared-ui/src/lib/dialog/standard-dialog/standard-dialog.config.ts
@@ -17,17 +17,9 @@
*/
import { BaseDialogConfig } from '../base-dialog/base-dialog.config';
-import { DialogPanelConfig } from '../base-dialog/base-dialog.model';
import { Overlay } from '@angular/cdk/overlay';
export class StandardDialogConfig implements BaseDialogConfig {
- getConfig(): DialogPanelConfig {
- const config: DialogPanelConfig = {} as DialogPanelConfig;
- config.maxWidth = '90vw';
- config.height = '50vh';
- return config;
- }
-
getPosition(overlay: Overlay) {
return overlay
.position()
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 5987737b08..371b6848cd 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
@@ -111,6 +111,11 @@ import { PaginatorService } from
'./components/sp-table/sp-paginator/sp-paginato
import { SpAlertBannerComponent } from
'./components/alert-banner/alert-banner.component';
import { FormFieldComponent } from
'./components/form-field/form-field.component';
import { FormLabelComponent } from
'./components/form-label/form-label.component';
+import { CardDialogComponent } from
'./dialog/card-dialog/card-dialog.component';
+import { FeatureCardHostComponent } from
'./components/feature-card-host/feature-card-host.component';
+import { FeatureCardHeaderComponent } from
'./components/feature-card-host/feature-card-header/feature-card-header.component';
+import { FeatureCardMetaSectionComponent } from
'./components/feature-card-host/feature-card-meta-section/feature-card-meta-section.component';
+import { FeatureCardMetaCreationComponent } from
'./components/feature-card-host/feature-card-meta-creation/feature-card-meta-creation.component';
@NgModule({
declarations: [
@@ -167,6 +172,11 @@ import { FormLabelComponent } from
'./components/form-label/form-label.component
ObjectPermissionDialogComponent,
FormFieldComponent,
FormLabelComponent,
+ CardDialogComponent,
+ FeatureCardHostComponent,
+ FeatureCardHeaderComponent,
+ FeatureCardMetaSectionComponent,
+ FeatureCardMetaCreationComponent,
],
imports: [
CommonModule,
@@ -249,6 +259,10 @@ import { FormLabelComponent } from
'./components/form-label/form-label.component
ObjectPermissionDialogComponent,
FormFieldComponent,
FormLabelComponent,
+ CardDialogComponent,
+ FeatureCardHeaderComponent,
+ FeatureCardMetaSectionComponent,
+ FeatureCardMetaCreationComponent,
],
})
export class SharedUiModule {}
diff --git a/ui/projects/streampipes/shared-ui/src/public-api.ts
b/ui/projects/streampipes/shared-ui/src/public-api.ts
index 8a2ad9efb8..0e0cc503e6 100644
--- a/ui/projects/streampipes/shared-ui/src/public-api.ts
+++ b/ui/projects/streampipes/shared-ui/src/public-api.ts
@@ -22,6 +22,7 @@ export * from './lib/dialog/base-dialog/base-dialog.model';
export * from './lib/dialog/base-dialog/base-dialog.service';
export * from './lib/dialog/base-dialog/dialog-ref';
export * from
'./lib/dialog/data-download-dialog/data-download-dialog.component';
+export * from './lib/dialog/card-dialog/card-dialog.component';
export * from './lib/dialog/confirm-dialog/confirm-dialog.component';
export * from './lib/dialog/panel-dialog/panel-dialog.component';
@@ -58,6 +59,9 @@ export * from
'./lib/components/pipeline-element/pipeline-element.component';
export * from
'./lib/components/input-schema-panel/input-schema-panel.component';
export * from './lib/components/sidebar-resize/sidebar-resize.component';
export * from
'./lib/components/asset-link-configuration/asset-link-configuration.component';
+export * from
'./lib/components/feature-card-host/feature-card-header/feature-card-header.component';
+export * from
'./lib/components/feature-card-host/feature-card-meta-section/feature-card-meta-section.component';
+export * from
'./lib/components/feature-card-host/feature-card-meta-creation/feature-card-meta-creation.component';
export * from './lib/models/sp-navigation.model';
@@ -71,3 +75,4 @@ export * from
'./lib/components/asset-browser/asset-browser.service';
export * from './lib/services/date-format.service';
export * from './lib/services/pipeline-element-schema.service';
export * from './lib/services/asset-configuration.service';
+export * from './lib/components/feature-card-host/feature-card.service';
diff --git
a/ui/src/app/assets/components/asset-details/view-asset/view-asset-links/asset-link-table/asset-link-table.component.html
b/ui/src/app/assets/components/asset-details/view-asset/view-asset-links/asset-link-table/asset-link-table.component.html
index 2b71fa7ce9..da7295734b 100644
---
a/ui/src/app/assets/components/asset-details/view-asset/view-asset-links/asset-link-table/asset-link-table.component.html
+++
b/ui/src/app/assets/components/asset-details/view-asset/view-asset-links/asset-link-table/asset-link-table.component.html
@@ -80,6 +80,7 @@
<button
mat-menu-item
data-cy="details-asset-link"
+ [disabled]="!hasFeatureCard(element.linkType)"
(click)="openDetails(element)"
>
<mat-icon>visibility</mat-icon>
diff --git
a/ui/src/app/assets/components/asset-details/view-asset/view-asset-links/asset-link-table/asset-link-table.component.ts
b/ui/src/app/assets/components/asset-details/view-asset/view-asset-links/asset-link-table/asset-link-table.component.ts
index 5e3c06c98c..f55697766c 100644
---
a/ui/src/app/assets/components/asset-details/view-asset/view-asset-links/asset-link-table/asset-link-table.component.ts
+++
b/ui/src/app/assets/components/asset-details/view-asset/view-asset-links/asset-link-table/asset-link-table.component.ts
@@ -41,6 +41,7 @@ import { EditAssetLinkDialogComponent } from
'../../../../../dialog/edit-asset-l
import {
CurrentUserService,
DialogService,
+ FeatureCardService,
PanelType,
} from '@streampipes/shared-ui';
import { TranslateService } from '@ngx-translate/core';
@@ -91,6 +92,7 @@ export class AssetLinkTableComponent
private currentUserService = inject(CurrentUserService);
private authService = inject(AuthService);
private certificateService = inject(CertificateService);
+ private featureCardService = inject(FeatureCardService);
ngOnInit() {
this.user$ = this.currentUserService.user$.subscribe(user => {
@@ -125,7 +127,12 @@ export class AssetLinkTableComponent
this.dataSource.data = this.asset.assetLinks;
}
- openDetails(assetLink: AssetLink) {}
+ openDetails(assetLink: AssetLink) {
+ this.featureCardService.openFeatureCard(
+ assetLink.linkType,
+ assetLink.resourceId,
+ );
+ }
navigate(assetLink: AssetLink) {
const linkType = this.assetLinkTypes.find(
@@ -169,6 +176,10 @@ export class AssetLinkTableComponent
this.refreshData();
}
+ hasFeatureCard(linkType: string): boolean {
+ return this.featureCardService.supportsFeatureCard(linkType);
+ }
+
ngOnDestroy() {
this.user$?.unsubscribe();
}
diff --git a/ui/src/app/chart-shared/services/chart-shared.service.ts
b/ui/src/app/chart-shared/services/chart-shared.service.ts
index 17ad7c138f..6efa306cad 100644
--- a/ui/src/app/chart-shared/services/chart-shared.service.ts
+++ b/ui/src/app/chart-shared/services/chart-shared.service.ts
@@ -29,6 +29,7 @@ import {
DialogService,
ObjectPermissionDialogComponent,
PanelType,
+ TimeSelectionService,
} from '@streampipes/shared-ui';
import { TranslateService } from '@ngx-translate/core';
import { ObservableGenerator } from '../models/dataview-dashboard.model';
@@ -40,6 +41,20 @@ export class ChartSharedService {
private dataViewQueryGeneratorService = inject(
DataViewQueryGeneratorService,
);
+ private timeSelectionService = inject(TimeSelectionService);
+
+ makeChartTimeSettings(chart: DataExplorerWidgetModel): TimeSettings {
+ if (!chart.timeSettings?.startTime) {
+ return this.timeSelectionService.getDefaultTimeSettings();
+ } else {
+ this.timeSelectionService.updateTimeSettings(
+ this.timeSelectionService.defaultQuickTimeSelections,
+ chart.timeSettings as TimeSettings,
+ new Date(),
+ );
+ return chart.timeSettings as TimeSettings;
+ }
+ }
openPermissionsDialog(
elementId: string,
diff --git
a/ui/src/app/chart/components/chart-feature-card/chart-feature-card.component.html
b/ui/src/app/chart/components/chart-feature-card/chart-feature-card.component.html
new file mode 100644
index 0000000000..fd9499d494
--- /dev/null
+++
b/ui/src/app/chart/components/chart-feature-card/chart-feature-card.component.html
@@ -0,0 +1,93 @@
+<!--
+ ~ 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 (chart && timeSettings) {
+ <div fxFlexFill fxLayout="column" class="feature-card-outer">
+ <sp-feature-card-header
+ [title]="chart.baseAppearanceConfig.widgetTitle"
+ [icon]="assetLinkType.linkIcon"
+ [iconColor]="assetLinkType.linkColor"
+ [detailsLink]="assetLinkType.navPaths"
+ (close)="onClose?.()"
+ >
+ </sp-feature-card-header>
+
+ <div class="feature-card-meta-card">
+ <sp-feature-card-meta-creation
+ [creationTimestamp]="chart.metadata?.createdAtEpochMs"
+ [lastModifiedTimestamp]="chart.metadata?.lastModifiedEpochMs"
+ >
+ </sp-feature-card-meta-creation>
+
+ <sp-feature-card-meta-section [label]="'Info' | translate">
+ <div
+ class="feature-card-meta-box"
+ fxLayout="row"
+ fxLayoutAlign="start center"
+ fxLayoutGap="0.75rem"
+ >
+ <mat-icon class="feature-card-meta-icon"
+ >insert_chart</mat-icon
+ >
+ <div>
+ <div class="feature-card-meta-field-label">
+ {{ 'Chart' | translate }}
+ </div>
+ <div class="feature-card-meta-field-value">
+ {{ chartType }}
+ </div>
+ </div>
+ </div>
+
+ <div
+ class="feature-card-meta-box"
+ fxLayout="row"
+ fxLayoutAlign="start center"
+ fxLayoutGap="0.75rem"
+ >
+ <mat-icon class="feature-card-meta-icon">folder</mat-icon>
+ <div>
+ <div class="feature-card-meta-field-label">
+ {{ 'Dataset' | translate }}
+ </div>
+ <div class="feature-card-meta-field-value">
+ {{ chart.dataConfig.sourceConfigs[0].measureName }}
+ </div>
+ </div>
+ </div>
+ </sp-feature-card-meta-section>
+ </div>
+
+ <span fxFlex></span>
+ <div fxFlex fxLayout="column" class="feature-preview-wrapper">
+ <div class="feature-preview-scale">
+ <div class="chart-preview-inner" style="max-height: 900px">
+ <sp-chart-container
+ fxFlex
+ [dataViewMode]="true"
+ [editMode]="false"
+ [configuredWidget]="chart"
+ [observableGenerator]="observableGenerator"
+ [timeSettings]="timeSettings"
+ >
+ </sp-chart-container>
+ </div>
+ </div>
+ </div>
+ </div>
+}
diff --git
a/ui/projects/streampipes/shared-ui/src/lib/dialog/base-dialog/base-dialog.model.ts
b/ui/src/app/chart/components/chart-feature-card/chart-feature-card.component.scss
similarity index 57%
copy from
ui/projects/streampipes/shared-ui/src/lib/dialog/base-dialog/base-dialog.model.ts
copy to
ui/src/app/chart/components/chart-feature-card/chart-feature-card.component.scss
index 474b3d8f12..1d37e0651c 100644
---
a/ui/projects/streampipes/shared-ui/src/lib/dialog/base-dialog/base-dialog.model.ts
+++
b/ui/src/app/chart/components/chart-feature-card/chart-feature-card.component.scss
@@ -1,4 +1,4 @@
-/*
+/*!
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
@@ -16,28 +16,32 @@
*
*/
-import { PanelDialogComponent } from '../panel-dialog/panel-dialog.component';
-import { StandardDialogComponent } from
'../standard-dialog/standard-dialog.component';
+:host {
+ width: 100%;
+ height: 100%;
+}
-export type BaseDialogComponentUnion =
- | PanelDialogComponent<unknown>
- | StandardDialogComponent<unknown>;
+$chart-scale: 0.5;
-export enum PanelType {
- STANDARD_PANEL,
- SLIDE_IN_PANEL,
-}
+.chart-preview-inner {
+ position: absolute;
+ top: 0;
+ left: 0;
+
+ width: calc(100% / #{$chart-scale});
+ height: calc(100% / #{$chart-scale});
+
+ transform: scale($chart-scale);
+ transform-origin: top left;
-export interface DialogConfig {
- width?: string;
- panelType: PanelType;
- disableClose?: boolean;
- autoFocus?: boolean;
- title: string;
- data?: any;
+ display: flex;
}
-export interface DialogPanelConfig {
- maxWidth: string;
- height: string;
+.chart-preview-inner > chart-container {
+ display: block;
+ flex: 1 1 auto;
+ width: 100%;
+ height: 100%;
+ min-width: 0;
+ min-height: 0;
}
diff --git
a/ui/src/app/chart/components/chart-feature-card/chart-feature-card.component.ts
b/ui/src/app/chart/components/chart-feature-card/chart-feature-card.component.ts
new file mode 100644
index 0000000000..f43cf62ca2
--- /dev/null
+++
b/ui/src/app/chart/components/chart-feature-card/chart-feature-card.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, OnInit } from '@angular/core';
+import {
+ AssetConstants,
+ AssetLinkType,
+ ChartService,
+ DataExplorerWidgetModel,
+ GenericStorageService,
+ TimeSettings,
+} from '@streampipes/platform-services';
+import { forkJoin } from 'rxjs';
+import {
+ DefaultFlexDirective,
+ DefaultLayoutAlignDirective,
+ DefaultLayoutDirective,
+ DefaultLayoutGapDirective,
+ FlexFillDirective,
+} from '@ngbracket/ngx-layout';
+import { SharedUiModule } from '@streampipes/shared-ui';
+import { ChartSharedModule } from '../../../chart-shared/chart-shared.module';
+import { ChartSharedService } from
'../../../chart-shared/services/chart-shared.service';
+import { MatIcon } from '@angular/material/icon';
+import { TranslatePipe } from '@ngx-translate/core';
+import { ChartRegistry } from
'../../../chart-shared/registry/chart-registry.service';
+
+@Component({
+ selector: 'sp-chart-feature-card',
+ templateUrl: './chart-feature-card.component.html',
+ imports: [
+ FlexFillDirective,
+ DefaultLayoutDirective,
+ SharedUiModule,
+ ChartSharedModule,
+ DefaultFlexDirective,
+ DefaultLayoutAlignDirective,
+ DefaultLayoutGapDirective,
+ MatIcon,
+ TranslatePipe,
+ ],
+ styleUrls: ['./chart-feature-card.component.scss'],
+})
+export class ChartFeatureCardComponent implements OnInit {
+ @Input()
+ resourceId: string;
+
+ @Input() onClose?: () => void;
+
+ chart: DataExplorerWidgetModel;
+ assetLinkType: AssetLinkType;
+ timeSettings: TimeSettings;
+
+ private chartService = inject(ChartService);
+ private genericStorageService = inject(GenericStorageService);
+ private chartSharedService = inject(ChartSharedService);
+ private chartRegistryService = inject(ChartRegistry);
+
+ chartType: string = 'Unknown';
+ datasetName: string = 'Unknown';
+
+ observableGenerator = this.chartSharedService.defaultObservableGenerator();
+
+ ngOnInit() {
+ forkJoin([
+ this.chartService.getChart(this.resourceId),
+ this.genericStorageService.getAllDocuments(
+ AssetConstants.ASSET_LINK_TYPES_DOC_NAME,
+ ),
+ ]).subscribe(res => {
+ this.chart = res[0];
+ this.assetLinkType = res[1].find(a => a.linkType === 'chart');
+ this.timeSettings = this.chartSharedService.makeChartTimeSettings(
+ this.chart,
+ );
+ this.chartType = this.chartRegistryService.getChartTemplate(
+ this.chart.widgetType,
+ ).label;
+ });
+ }
+}
diff --git
a/ui/src/app/chart/components/chart-overview/chart-overview-table/chart-overview-table.component.html
b/ui/src/app/chart/components/chart-overview/chart-overview-table/chart-overview-table.component.html
index a4bc291d55..d5025c4f82 100644
---
a/ui/src/app/chart/components/chart-overview/chart-overview-table/chart-overview-table.component.html
+++
b/ui/src/app/chart/components/chart-overview/chart-overview-table/chart-overview-table.component.html
@@ -39,7 +39,26 @@
data-cy="data-views-table-overview"
*matCellDef="let element"
>
- {{ element.baseAppearanceConfig.widgetTitle }}<br />
+ <div
+ fxLayout="row"
+ fxLayoutAlign="start center"
+ fxLayoutGap="5px"
+ >
+ <button
+ mat-icon-button
+ (click)="
+ openFeatureCard(element);
+ $event.stopPropagation()
+ "
+ >
+ <mat-icon>preview</mat-icon>
+ </button>
+ <div fxLayout="column" fxLayoutAlign="start start">
+ <span class="text-sm">{{
+ element.baseAppearanceConfig.widgetTitle
+ }}</span>
+ </div>
+ </div>
</td>
</ng-container>
diff --git
a/ui/src/app/chart/components/chart-overview/chart-overview-table/chart-overview-table.component.ts
b/ui/src/app/chart/components/chart-overview/chart-overview-table/chart-overview-table.component.ts
index cb9b5547ea..1ec94d6390 100644
---
a/ui/src/app/chart/components/chart-overview/chart-overview-table/chart-overview-table.component.ts
+++
b/ui/src/app/chart/components/chart-overview/chart-overview-table/chart-overview-table.component.ts
@@ -25,6 +25,7 @@ import {
import {
ConfirmDialogComponent,
DateFormatService,
+ FeatureCardService,
SpAssetBrowserService,
} from '@streampipes/shared-ui';
import { ChartSharedService } from
'../../../../chart-shared/services/chart-shared.service';
@@ -64,6 +65,7 @@ export class ChartOverviewTableComponent implements OnInit {
private dateFormatService = inject(DateFormatService);
private routingService = inject(ChartRoutingService);
private assetFilterService = inject(SpAssetBrowserService);
+ private featureCardService = inject(FeatureCardService);
assetFilter$: Subscription;
currentFilterIds = new Set<string>();
@@ -175,4 +177,8 @@ export class ChartOverviewTableComponent implements OnInit {
formatDate(timestamp?: number): string {
return this.dateFormatService.formatDate(timestamp);
}
+
+ openFeatureCard(chart: DataExplorerWidgetModel) {
+ this.featureCardService.openFeatureCard('chart', chart.elementId);
+ }
}
diff --git a/ui/src/app/chart/components/chart-view/chart-view.component.ts
b/ui/src/app/chart/components/chart-view/chart-view.component.ts
index b06982dbdb..f8d992b6a5 100644
--- a/ui/src/app/chart/components/chart-view/chart-view.component.ts
+++ b/ui/src/app/chart/components/chart-view/chart-view.component.ts
@@ -193,18 +193,10 @@ export class ChartViewComponent
this.originalDataView = JSON.parse(
JSON.stringify(this.dataView),
);
- if (!this.dataView.timeSettings?.startTime) {
- this.timeSettings = this.makeDefaultTimeSettings();
- } else {
- this.timeSelectionService.updateTimeSettings(
- this.timeSelectionService
- .defaultQuickTimeSelections,
- this.dataView.timeSettings as TimeSettings,
- new Date(),
+ this.timeSettings =
+ this.dataExplorerSharedService.makeChartTimeSettings(
+ this.dataView,
);
- this.timeSettings = this.dataView
- .timeSettings as TimeSettings;
- }
this.afterDataViewLoaded();
}
});
@@ -213,8 +205,6 @@ export class ChartViewComponent
afterDataViewLoaded(): void {
this.dataViewLoaded = true;
setTimeout(() => {
- const width = this.outerPanel.nativeElement.offsetWidth;
- const height = this.outerPanel.nativeElement.offsetHeight;
this.timeSelectionService.notify(this.timeSettings);
this.updateQueryParams(this.timeSettings);
});
diff --git a/ui/src/app/pipeline-details/pipeline-details.module.ts
b/ui/src/app/pipeline-details/pipeline-details.module.ts
index 47e52672c7..7a28618de9 100644
--- a/ui/src/app/pipeline-details/pipeline-details.module.ts
+++ b/ui/src/app/pipeline-details/pipeline-details.module.ts
@@ -80,7 +80,7 @@ import { TranslateModule } from '@ngx-translate/core';
PipelineDetailsToolbarComponent,
],
providers: [],
- exports: [],
+ exports: [PipelinePreviewComponent],
})
export class PipelineDetailsModule {
constructor() {}
diff --git
a/ui/src/app/pipelines/components/pipeline-feature-card/pipeline-feature-card.component.html
b/ui/src/app/pipelines/components/pipeline-feature-card/pipeline-feature-card.component.html
new file mode 100644
index 0000000000..eb10d7e25e
--- /dev/null
+++
b/ui/src/app/pipelines/components/pipeline-feature-card/pipeline-feature-card.component.html
@@ -0,0 +1,52 @@
+<!--
+ ~ Licensed to the Apache Software Foundation (ASF) under one or more
+ ~ contributor license agreements. See the NOTICE file distributed with
+ ~ this work for additional information regarding copyright ownership.
+ ~ The ASF licenses this file to You under the Apache License, Version 2.0
+ ~ (the "License"); you may not use this file except in compliance with
+ ~ the License. You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ ~
+ -->
+
+@if (pipeline && pipelineCanvasMetadata) {
+ <div fxFlexFill fxLayout="column" class="feature-card-outer">
+ <sp-feature-card-header
+ [title]="pipeline.name"
+ [description]="pipeline.description"
+ [icon]="assetLink.linkIcon"
+ [iconColor]="assetLink.linkColor"
+ [detailsLink]="assetLink.navPaths"
+ (close)="onClose?.()"
+ >
+ </sp-feature-card-header>
+
+ <sp-pipeline-preview-meta
+ [lastModifiedAt]="pipeline.createdAt"
+ [status]="pipeline.running"
+ [healthStatus]="pipeline.healthStatus"
+ >
+ </sp-pipeline-preview-meta>
+
+ <mat-divider></mat-divider>
+
+ <div fxFlex fxLayout="column" class="feature-preview-wrapper">
+ <div class="feature-preview-scale">
+ <div class="pipeline-preview-inner">
+ <sp-pipeline-preview
+ [pipeline]="pipeline"
+ [pipelineCanvasMetadata]="pipelineCanvasMetadata"
+ >
+ </sp-pipeline-preview>
+ </div>
+ </div>
+ </div>
+ </div>
+}
diff --git
a/ui/projects/streampipes/shared-ui/src/lib/dialog/base-dialog/base-dialog.model.ts
b/ui/src/app/pipelines/components/pipeline-feature-card/pipeline-feature-card.component.scss
similarity index 57%
copy from
ui/projects/streampipes/shared-ui/src/lib/dialog/base-dialog/base-dialog.model.ts
copy to
ui/src/app/pipelines/components/pipeline-feature-card/pipeline-feature-card.component.scss
index 474b3d8f12..a2d5eb7056 100644
---
a/ui/projects/streampipes/shared-ui/src/lib/dialog/base-dialog/base-dialog.model.ts
+++
b/ui/src/app/pipelines/components/pipeline-feature-card/pipeline-feature-card.component.scss
@@ -1,4 +1,4 @@
-/*
+/*!
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
@@ -16,28 +16,32 @@
*
*/
-import { PanelDialogComponent } from '../panel-dialog/panel-dialog.component';
-import { StandardDialogComponent } from
'../standard-dialog/standard-dialog.component';
+:host {
+ width: 100%;
+ height: 100%;
+}
-export type BaseDialogComponentUnion =
- | PanelDialogComponent<unknown>
- | StandardDialogComponent<unknown>;
+$pipeline-scale: 0.4;
-export enum PanelType {
- STANDARD_PANEL,
- SLIDE_IN_PANEL,
-}
+.pipeline-preview-inner {
+ position: absolute;
+ top: 0;
+ left: 0;
+
+ width: calc(100% / #{$pipeline-scale});
+ height: calc(100% / #{$pipeline-scale});
+
+ transform: scale($pipeline-scale);
+ transform-origin: top left;
-export interface DialogConfig {
- width?: string;
- panelType: PanelType;
- disableClose?: boolean;
- autoFocus?: boolean;
- title: string;
- data?: any;
+ display: flex;
}
-export interface DialogPanelConfig {
- maxWidth: string;
- height: string;
+.pipeline-preview-inner > sp-pipeline-preview {
+ display: block;
+ flex: 1 1 auto;
+ width: 100%;
+ height: 100%;
+ min-width: 0;
+ min-height: 0;
}
diff --git
a/ui/src/app/pipelines/components/pipeline-feature-card/pipeline-feature-card.component.ts
b/ui/src/app/pipelines/components/pipeline-feature-card/pipeline-feature-card.component.ts
new file mode 100644
index 0000000000..90ac3155cc
--- /dev/null
+++
b/ui/src/app/pipelines/components/pipeline-feature-card/pipeline-feature-card.component.ts
@@ -0,0 +1,85 @@
+/*
+ * 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 {
+ AssetConstants,
+ AssetLinkType,
+ GenericStorageService,
+ Pipeline,
+ PipelineCanvasMetadata,
+ PipelineCanvasMetadataService,
+ PipelineService,
+} from '@streampipes/platform-services';
+import { forkJoin } from 'rxjs';
+import { PipelineDetailsModule } from
'../../../pipeline-details/pipeline-details.module';
+import {
+ DefaultFlexDirective,
+ DefaultLayoutDirective,
+ FlexFillDirective,
+} from '@ngbracket/ngx-layout';
+import { SharedUiModule } from '@streampipes/shared-ui';
+import { PipelinePreviewMetaComponent } from
'./pipeline-preview-meta/pipeline-preview-meta.component';
+import { MatDivider } from '@angular/material/list';
+
+@Component({
+ selector: 'sp-pipeline-feature-card',
+ templateUrl: './pipeline-feature-card.component.html',
+ styleUrls: ['./pipeline-feature-card.component.scss'],
+ imports: [
+ PipelineDetailsModule,
+ DefaultFlexDirective,
+ DefaultLayoutDirective,
+ SharedUiModule,
+ PipelinePreviewMetaComponent,
+ FlexFillDirective,
+ MatDivider,
+ ],
+})
+export class PipelineFeatureCardComponent implements OnInit {
+ @Input()
+ resourceId: string;
+
+ @Input() onClose?: () => void;
+
+ pipeline: Pipeline;
+ pipelineCanvasMetadata: PipelineCanvasMetadata;
+ assetLink: AssetLinkType;
+
+ private pipelineService = inject(PipelineService);
+ private pipelineCanvasService = inject(PipelineCanvasMetadataService);
+ private genericStorageService = inject(GenericStorageService);
+
+ ngOnInit() {
+ forkJoin([
+ this.pipelineService.getPipelineById(this.resourceId),
+ this.pipelineCanvasService.getPipelineCanvasMetadata(
+ this.resourceId,
+ ),
+ this.genericStorageService.getAllDocuments(
+ AssetConstants.ASSET_LINK_TYPES_DOC_NAME,
+ ),
+ ]).subscribe(p => {
+ this.pipeline = p[0];
+ this.pipelineCanvasMetadata = p[1]
+ ? p[1]
+ : new PipelineCanvasMetadata();
+ this.assetLink = p[2].find(a => a.linkType === 'pipeline');
+ });
+ }
+}
diff --git
a/ui/src/app/pipelines/components/pipeline-feature-card/pipeline-preview-meta/pipeline-preview-meta.component.html
b/ui/src/app/pipelines/components/pipeline-feature-card/pipeline-preview-meta/pipeline-preview-meta.component.html
new file mode 100644
index 0000000000..323243b9b8
--- /dev/null
+++
b/ui/src/app/pipelines/components/pipeline-feature-card/pipeline-preview-meta/pipeline-preview-meta.component.html
@@ -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.
+ ~
+ -->
+
+<div class="feature-card-meta-card">
+ <sp-feature-card-meta-creation [lastModifiedTimestamp]="lastModifiedAt">
+ </sp-feature-card-meta-creation>
+
+ <sp-feature-card-meta-section [label]="'Status' | translate">
+ <div
+ class="feature-card-meta-box"
+ fxLayout="row"
+ fxLayoutAlign="start center"
+ fxLayoutGap="0.75rem"
+ >
+ <mat-icon class="feature-card-meta-icon"
+ >fiber_manual_record</mat-icon
+ >
+ <div>
+ <div class="feature-card-meta-field-label">State</div>
+ <div class="feature-card-meta-field-value">
+ {{ statusString }}
+ </div>
+ </div>
+ </div>
+
+ <div
+ class="feature-card-meta-box"
+ fxLayout="row"
+ fxLayoutAlign="start center"
+ fxLayoutGap="0.75rem"
+ >
+ <mat-icon class="feature-card-meta-icon">favorite</mat-icon>
+ <div>
+ <div class="feature-card-meta-field-label">Health</div>
+ <div class="feature-card-meta-field-value">
+ {{ healthStatus || 'Not available' }}
+ </div>
+ </div>
+ </div>
+ </sp-feature-card-meta-section>
+
+ <sp-feature-card-meta-section [label]="'Monitoring' | translate">
+ <div
+ class="feature-card-meta-box"
+ fxLayout="row"
+ fxLayoutAlign="space-between center"
+ >
+ <div
+ fxLayout="row"
+ fxLayoutGap="0.5rem"
+ fxLayoutAlign="start center"
+ >
+ <mat-icon class="feature-card-meta-icon"
+ >arrow_downward</mat-icon
+ >
+ <span class="feature-card-meta-field-label">Data in</span>
+ </div>
+ <div class="feature-card-meta-field-value">
+ {{ dataInLabel || '—' }}
+ </div>
+ </div>
+
+ <div
+ class="feature-card-meta-box"
+ fxLayout="row"
+ fxLayoutAlign="space-between center"
+ >
+ <div
+ fxLayout="row"
+ fxLayoutGap="0.5rem"
+ fxLayoutAlign="start center"
+ >
+ <mat-icon
class="feature-card-meta-icon">arrow_upward</mat-icon>
+ <span class="feature-card-meta-field-label">Data out</span>
+ </div>
+ <div class="feature-card-meta-field-value">
+ {{ dataOutLabel || '—' }}
+ </div>
+ </div>
+ </sp-feature-card-meta-section>
+</div>
diff --git
a/ui/projects/streampipes/shared-ui/src/lib/dialog/base-dialog/base-dialog.model.ts
b/ui/src/app/pipelines/components/pipeline-feature-card/pipeline-preview-meta/pipeline-preview-meta.component.scss
similarity index 57%
copy from
ui/projects/streampipes/shared-ui/src/lib/dialog/base-dialog/base-dialog.model.ts
copy to
ui/src/app/pipelines/components/pipeline-feature-card/pipeline-preview-meta/pipeline-preview-meta.component.scss
index 474b3d8f12..96731bafb2 100644
---
a/ui/projects/streampipes/shared-ui/src/lib/dialog/base-dialog/base-dialog.model.ts
+++
b/ui/src/app/pipelines/components/pipeline-feature-card/pipeline-preview-meta/pipeline-preview-meta.component.scss
@@ -1,4 +1,4 @@
-/*
+/*!
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
@@ -16,28 +16,6 @@
*
*/
-import { PanelDialogComponent } from '../panel-dialog/panel-dialog.component';
-import { StandardDialogComponent } from
'../standard-dialog/standard-dialog.component';
-
-export type BaseDialogComponentUnion =
- | PanelDialogComponent<unknown>
- | StandardDialogComponent<unknown>;
-
-export enum PanelType {
- STANDARD_PANEL,
- SLIDE_IN_PANEL,
-}
-
-export interface DialogConfig {
- width?: string;
- panelType: PanelType;
- disableClose?: boolean;
- autoFocus?: boolean;
- title: string;
- data?: any;
-}
-
-export interface DialogPanelConfig {
- maxWidth: string;
- height: string;
+:host {
+ display: block;
}
diff --git
a/ui/src/app/pipelines/components/pipeline-feature-card/pipeline-preview-meta/pipeline-preview-meta.component.ts
b/ui/src/app/pipelines/components/pipeline-feature-card/pipeline-preview-meta/pipeline-preview-meta.component.ts
new file mode 100644
index 0000000000..4f3a9279f0
--- /dev/null
+++
b/ui/src/app/pipelines/components/pipeline-feature-card/pipeline-preview-meta/pipeline-preview-meta.component.ts
@@ -0,0 +1,65 @@
+/*
+ * 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 {
+ DefaultLayoutAlignDirective,
+ DefaultLayoutDirective,
+ DefaultLayoutGapDirective,
+} from '@ngbracket/ngx-layout';
+import { MatIcon } from '@angular/material/icon';
+import { SharedUiModule } from '@streampipes/shared-ui';
+import { TranslatePipe, TranslateService } from '@ngx-translate/core';
+
+@Component({
+ selector: 'sp-pipeline-preview-meta',
+ templateUrl: './pipeline-preview-meta.component.html',
+ styleUrls: ['./pipeline-preview-meta.component.scss'],
+ imports: [
+ DefaultLayoutDirective,
+ DefaultLayoutGapDirective,
+ MatIcon,
+ DefaultLayoutAlignDirective,
+ SharedUiModule,
+ TranslatePipe,
+ ],
+})
+export class PipelinePreviewMetaComponent implements OnInit {
+ @Input() lastModifiedAt?: number;
+
+ @Input() status?: boolean;
+ @Input() healthStatus?: string;
+
+ @Input() dataInLabel?: string;
+ @Input() dataOutLabel?: string;
+
+ statusString: string;
+ statusColor: string;
+
+ private translate = inject(TranslateService);
+
+ ngOnInit() {
+ if (this.status) {
+ this.statusString = this.translate.instant('Running');
+ this.statusColor = 'var(--color-success)';
+ } else {
+ this.statusString = this.translate.instant('Stopped');
+ this.statusColor = 'var(--color-idle)';
+ }
+ }
+}
diff --git
a/ui/src/app/pipelines/components/pipeline-overview/pipeline-overview.component.html
b/ui/src/app/pipelines/components/pipeline-overview/pipeline-overview.component.html
index d57a4b9d99..e20b9cdd18 100644
---
a/ui/src/app/pipelines/components/pipeline-overview/pipeline-overview.component.html
+++
b/ui/src/app/pipelines/components/pipeline-overview/pipeline-overview.component.html
@@ -158,9 +158,19 @@
{{ 'Name' | translate }}
</th>
<td mat-cell *matCellDef="let pipeline" class="truncate">
- <div fxLayout="column" fxLayoutAlign="start start">
- <b>{{ pipeline.name }}</b>
- <small> {{ pipeline.description }}</small>
+ <div fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="5px">
+ <button
+ mat-icon-button
+ (click)="
+ openFeatureCard(pipeline); $event.stopPropagation()
+ "
+ >
+ <mat-icon>preview</mat-icon>
+ </button>
+ <div fxLayout="column" fxLayoutAlign="start start">
+ <span class="text-sm">{{ pipeline.name }}</span>
+ <small> {{ pipeline.description }}</small>
+ </div>
</div>
</td>
</ng-container>
diff --git
a/ui/src/app/pipelines/components/pipeline-overview/pipeline-overview.component.ts
b/ui/src/app/pipelines/components/pipeline-overview/pipeline-overview.component.ts
index 92c150441f..6f5754c05c 100644
---
a/ui/src/app/pipelines/components/pipeline-overview/pipeline-overview.component.ts
+++
b/ui/src/app/pipelines/components/pipeline-overview/pipeline-overview.component.ts
@@ -20,6 +20,7 @@ import { Pipeline } from '@streampipes/platform-services';
import {
Component,
EventEmitter,
+ inject,
Input,
OnDestroy,
OnInit,
@@ -31,7 +32,7 @@ import { MatTableDataSource } from '@angular/material/table';
import { MatSort } from '@angular/material/sort';
import { AuthService } from '../../../services/auth.service';
import { UserPrivilege } from '../../../_enums/user-privilege.enum';
-import { CurrentUserService } from '@streampipes/shared-ui';
+import { CurrentUserService, FeatureCardService } from
'@streampipes/shared-ui';
import { Subscription } from 'rxjs';
@Component({
@@ -58,20 +59,16 @@ export class PipelineOverviewComponent implements OnInit,
OnDestroy {
dataSource: MatTableDataSource<Pipeline> = new MatTableDataSource();
@ViewChild(MatSort) sort: MatSort;
- starting: any;
- stopping: any;
+ starting = false;
+ stopping = false;
hasPipelineWritePrivileges = false;
userSub: Subscription;
- constructor(
- public pipelineOperationsService: PipelineOperationsService,
- private authService: AuthService,
- private currentUserService: CurrentUserService,
- ) {
- this.starting = false;
- this.stopping = false;
- }
+ private featureCardService = inject(FeatureCardService);
+ public pipelineOperationsService = inject(PipelineOperationsService);
+ private authService = inject(AuthService);
+ private currentUserService = inject(CurrentUserService);
ngOnInit() {
this.userSub = this.currentUserService.user$.subscribe(user => {
@@ -125,4 +122,8 @@ export class PipelineOverviewComponent implements OnInit,
OnDestroy {
ngOnDestroy() {
this.userSub?.unsubscribe();
}
+
+ openFeatureCard(pipeline: Pipeline): void {
+ this.featureCardService.openFeatureCard('pipeline', pipeline._id);
+ }
}
diff --git a/ui/src/scss/main.scss b/ui/src/scss/main.scss
index f131e04397..18a2674817 100644
--- a/ui/src/scss/main.scss
+++ b/ui/src/scss/main.scss
@@ -33,6 +33,7 @@
@use './sp/main';
@use './sp/spinner';
+@use './sp/feature-card';
@use './sp/forms';
@use './sp/dialog';
@use './sp/pipeline-element-options';
diff --git a/ui/src/scss/sp/feature-card.scss b/ui/src/scss/sp/feature-card.scss
new file mode 100644
index 0000000000..617e7717dd
--- /dev/null
+++ b/ui/src/scss/sp/feature-card.scss
@@ -0,0 +1,86 @@
+/*!
+ * 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.
+ *
+ */
+
+.feature-card-outer {
+ height: 100%;
+ min-height: 0;
+ display: flex;
+ flex-direction: column;
+}
+
+.feature-card-meta-card {
+ padding: 1.25rem;
+ box-shadow: none;
+ background: #fff;
+ display: flex;
+ flex-direction: column;
+ gap: 1.25rem;
+}
+
+.feature-card-meta-section {
+ display: flex;
+ flex-direction: column;
+ gap: 0.5rem;
+}
+
+.feature-card-meta-section__label {
+ font-size: var(--text-xs, 0.75rem);
+ font-weight: 600;
+ text-transform: uppercase;
+ opacity: 0.7;
+ margin-bottom: 0.25rem;
+}
+
+.feature-card-meta-icon {
+ font-size: 1.2rem !important;
+ width: 1.2rem !important;
+ height: 1.2rem !important;
+ opacity: 0.75;
+}
+
+.feature-card-meta-field-label {
+ font-size: var(--text-xs, 0.75rem);
+ opacity: 0.8;
+}
+
+.feature-card-meta-field-value {
+ font-size: var(--text-sm, 0.85rem);
+ font-weight: 500;
+}
+
+.feature-card-meta-box {
+ padding: 0.5rem 0.75rem;
+ border-radius: 0.5rem;
+ background: rgba(0, 0, 0, 0.03);
+}
+
+.feature-preview-wrapper {
+ flex: 1 1 auto;
+ min-height: 0;
+ display: flex;
+ margin: var(--space-md);
+}
+
+.feature-preview-scale {
+ position: relative;
+ flex: 1 1 auto;
+ min-height: 0;
+ border: 1px solid var(--color-bg-3);
+ border-radius: 10px;
+ overflow: hidden;
+}