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;
+}


Reply via email to