This is an automated email from the ASF dual-hosted git repository.

riemer pushed a commit to branch dev
in repository https://gitbox.apache.org/repos/asf/streampipes.git


The following commit(s) were added to refs/heads/dev by this push:
     new bfa9c9dbe9 feat: Add feature card to adapter page (#4196)
bfa9c9dbe9 is described below

commit bfa9c9dbe9d20950873bcd73ab1a251094678406
Author: Dominik Riemer <[email protected]>
AuthorDate: Tue Feb 24 12:30:27 2026 +0100

    feat: Add feature card to adapter page (#4196)
---
 ui/deployment/feature-cards.yml                    |   4 +
 .../live-preview-table.component.html              |  52 ++++----
 .../live-preview-table.component.scss              |   9 ++
 .../live-preview-table.component.ts                |  21 ++--
 .../pipeline-element-runtime-info.component.html   |   1 +
 .../pipeline-element-runtime-info.component.scss   |  17 ---
 .../pipeline-element-runtime-info.component.ts     |   4 +-
 .../connect-feature-card.component.html            | 116 +++++++++++++++++
 .../connect-feature-card.component.scss            |  99 +++++++++++++++
 .../connect-feature-card.component.ts              | 138 +++++++++++++++++++++
 .../existing-adapters.component.html               |   2 +
 11 files changed, 410 insertions(+), 53 deletions(-)

diff --git a/ui/deployment/feature-cards.yml b/ui/deployment/feature-cards.yml
index 27b4dded52..8265f5adff 100644
--- a/ui/deployment/feature-cards.yml
+++ b/ui/deployment/feature-cards.yml
@@ -29,3 +29,7 @@
   componentPath: 
'./dataset/components/dataset-feature-card/dataset-feature-card.component'
   componentName: DatasetFeatureCardComponent
   moduleName: spDatasets
+- id: adapter
+  componentPath: 
'./connect/components/connect-feature-card/connect-feature-card.component'
+  componentName: ConnectFeatureCardComponent
+  moduleName: spConnect
diff --git 
a/ui/projects/streampipes/shared-ui/src/lib/components/pipeline-element-runtime-info/live-preview-table/live-preview-table.component.html
 
b/ui/projects/streampipes/shared-ui/src/lib/components/pipeline-element-runtime-info/live-preview-table/live-preview-table.component.html
index 8c119527db..0b595ebf3c 100644
--- 
a/ui/projects/streampipes/shared-ui/src/lib/components/pipeline-element-runtime-info/live-preview-table/live-preview-table.component.html
+++ 
b/ui/projects/streampipes/shared-ui/src/lib/components/pipeline-element-runtime-info/live-preview-table/live-preview-table.component.html
@@ -20,7 +20,7 @@
     @if (showTitle) {
         <p>{{ 'Here is a preview of your data:' | translate }}</p>
     }
-    <table mat-table [dataSource]="runtimeInfo">
+    <table mat-table [dataSource]="runtimeInfo" [class.compact]="compact">
         <ng-container matColumnDef="runtimeName">
             <th mat-header-cell *matHeaderCellDef>
                 <strong>{{ 'Runtime Name' | translate }}</strong>
@@ -30,32 +30,34 @@
             </td>
         </ng-container>
 
-        <ng-container matColumnDef="label">
-            <th mat-header-cell *matHeaderCellDef>
-                <strong>{{ 'Field Name' | translate }}</strong>
-            </th>
-            <td mat-cell *matCellDef="let element">
-                {{ element.label }}
-            </td>
-        </ng-container>
+        @if (!compact) {
+            <ng-container matColumnDef="label">
+                <th mat-header-cell *matHeaderCellDef>
+                    <strong>{{ 'Field Name' | translate }}</strong>
+                </th>
+                <td mat-cell *matCellDef="let element">
+                    {{ element.label }}
+                </td>
+            </ng-container>
 
-        <ng-container matColumnDef="description">
-            <th mat-header-cell *matHeaderCellDef>
-                <strong>{{ 'Description' | translate }}</strong>
-            </th>
-            <td mat-cell *matCellDef="let element">
-                {{ element.description }}
-            </td>
-        </ng-container>
+            <ng-container matColumnDef="description">
+                <th mat-header-cell *matHeaderCellDef>
+                    <strong>{{ 'Description' | translate }}</strong>
+                </th>
+                <td mat-cell *matCellDef="let element">
+                    {{ element.description }}
+                </td>
+            </ng-container>
 
-        <ng-container matColumnDef="runtimeType">
-            <th mat-header-cell *matHeaderCellDef>
-                <strong>{{ 'Type' | translate }}</strong>
-            </th>
-            <td mat-cell *matCellDef="let element">
-                {{ element.runtimeType }}
-            </td>
-        </ng-container>
+            <ng-container matColumnDef="runtimeType">
+                <th mat-header-cell *matHeaderCellDef>
+                    <strong>{{ 'Type' | translate }}</strong>
+                </th>
+                <td mat-cell *matCellDef="let element">
+                    {{ element.runtimeType }}
+                </td>
+            </ng-container>
+        }
 
         <ng-container matColumnDef="value">
             <th mat-header-cell *matHeaderCellDef>
diff --git 
a/ui/projects/streampipes/shared-ui/src/lib/components/pipeline-element-runtime-info/live-preview-table/live-preview-table.component.scss
 
b/ui/projects/streampipes/shared-ui/src/lib/components/pipeline-element-runtime-info/live-preview-table/live-preview-table.component.scss
index b6f18bbb57..c5e5de49e4 100644
--- 
a/ui/projects/streampipes/shared-ui/src/lib/components/pipeline-element-runtime-info/live-preview-table/live-preview-table.component.scss
+++ 
b/ui/projects/streampipes/shared-ui/src/lib/components/pipeline-element-runtime-info/live-preview-table/live-preview-table.component.scss
@@ -41,3 +41,12 @@
     text-align: center;
     background: #ffe7ad;
 }
+
+.mat-mdc-table.compact {
+    --mat-table-row-item-container-height: 36px;
+
+    .mat-mdc-header-cell,
+    .mat-mdc-cell {
+        font-size: var(--font-size-xs);
+    }
+}
diff --git 
a/ui/projects/streampipes/shared-ui/src/lib/components/pipeline-element-runtime-info/live-preview-table/live-preview-table.component.ts
 
b/ui/projects/streampipes/shared-ui/src/lib/components/pipeline-element-runtime-info/live-preview-table/live-preview-table.component.ts
index 88d4a8417d..fcafa4fd49 100644
--- 
a/ui/projects/streampipes/shared-ui/src/lib/components/pipeline-element-runtime-info/live-preview-table/live-preview-table.component.ts
+++ 
b/ui/projects/streampipes/shared-ui/src/lib/components/pipeline-element-runtime-info/live-preview-table/live-preview-table.component.ts
@@ -16,7 +16,7 @@
  *
  */
 
-import { Component, Input } from '@angular/core';
+import { Component, Input, OnInit } from '@angular/core';
 import { EventSchema } from '@streampipes/platform-services';
 import { RuntimeInfo } from '../pipeline-element-runtime-info.model';
 import {
@@ -53,7 +53,7 @@ import { TranslatePipe } from '@ngx-translate/core';
         TranslatePipe,
     ],
 })
-export class LivePreviewTableComponent {
+export class LivePreviewTableComponent implements OnInit {
     @Input()
     eventSchema: EventSchema;
 
@@ -63,13 +63,14 @@ export class LivePreviewTableComponent {
     @Input()
     showTitle = true;
 
-    displayedColumns: string[] = [
-        'runtimeName',
-        'label',
-        'description',
-        'runtimeType',
-        'value',
-    ];
+    @Input()
+    compact = false;
+
+    displayedColumns: string[] = [];
 
-    constructor() {}
+    ngOnInit() {
+        this.displayedColumns = this.compact
+            ? ['runtimeName', 'value']
+            : ['runtimeName', 'label', 'description', 'runtimeType', 'value'];
+    }
 }
diff --git 
a/ui/projects/streampipes/shared-ui/src/lib/components/pipeline-element-runtime-info/pipeline-element-runtime-info.component.html
 
b/ui/projects/streampipes/shared-ui/src/lib/components/pipeline-element-runtime-info/pipeline-element-runtime-info.component.html
index 8432e1835c..222e7436fb 100644
--- 
a/ui/projects/streampipes/shared-ui/src/lib/components/pipeline-element-runtime-info/pipeline-element-runtime-info.component.html
+++ 
b/ui/projects/streampipes/shared-ui/src/lib/components/pipeline-element-runtime-info/pipeline-element-runtime-info.component.html
@@ -18,6 +18,7 @@
 
 @if (runtimeInfo) {
     <sp-live-preview-table
+        [compact]="compact"
         [showTitle]="showTitle"
         [eventSchema]="streamDescription.eventSchema"
         [runtimeInfo]="runtimeInfo"
diff --git 
a/ui/projects/streampipes/shared-ui/src/lib/components/pipeline-element-runtime-info/pipeline-element-runtime-info.component.scss
 
b/ui/projects/streampipes/shared-ui/src/lib/components/pipeline-element-runtime-info/pipeline-element-runtime-info.component.scss
deleted file mode 100644
index 13cbc4aacb..0000000000
--- 
a/ui/projects/streampipes/shared-ui/src/lib/components/pipeline-element-runtime-info/pipeline-element-runtime-info.component.scss
+++ /dev/null
@@ -1,17 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *    http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- */
diff --git 
a/ui/projects/streampipes/shared-ui/src/lib/components/pipeline-element-runtime-info/pipeline-element-runtime-info.component.ts
 
b/ui/projects/streampipes/shared-ui/src/lib/components/pipeline-element-runtime-info/pipeline-element-runtime-info.component.ts
index 59c4d0cbfb..8acab3c382 100644
--- 
a/ui/projects/streampipes/shared-ui/src/lib/components/pipeline-element-runtime-info/pipeline-element-runtime-info.component.ts
+++ 
b/ui/projects/streampipes/shared-ui/src/lib/components/pipeline-element-runtime-info/pipeline-element-runtime-info.component.ts
@@ -38,7 +38,6 @@ import { LivePreviewErrorComponent } from 
'./live-preview-error/live-preview-err
 @Component({
     selector: 'sp-pipeline-element-runtime-info',
     templateUrl: './pipeline-element-runtime-info.component.html',
-    styleUrls: ['./pipeline-element-runtime-info.component.scss'],
     imports: [LivePreviewTableComponent, LivePreviewErrorComponent],
 })
 export class PipelineElementRuntimeInfoComponent implements OnInit, OnDestroy {
@@ -48,6 +47,9 @@ export class PipelineElementRuntimeInfoComponent implements 
OnInit, OnDestroy {
     @Input()
     showTitle = true;
 
+    @Input()
+    compact = false;
+
     runtimeData: { runtimeName: string; value: any }[];
     runtimeInfo: RuntimeInfo[];
     timer: any;
diff --git 
a/ui/src/app/connect/components/connect-feature-card/connect-feature-card.component.html
 
b/ui/src/app/connect/components/connect-feature-card/connect-feature-card.component.html
new file mode 100644
index 0000000000..1e6c4ca372
--- /dev/null
+++ 
b/ui/src/app/connect/components/connect-feature-card/connect-feature-card.component.html
@@ -0,0 +1,116 @@
+<!--
+  ~ 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 (adapter) {
+    <div fxFlexFill fxLayout="column" class="feature-card-outer">
+        <sp-feature-card-header
+            [title]="adapter.name"
+            [description]="adapter.description"
+            [icon]="assetLinkType?.linkIcon"
+            [iconColor]="assetLinkType?.linkColor"
+            [detailsLink]="assetLinkType?.navPaths"
+            (close)="onClose?.()"
+            (onDetailsClick)="navigateToAdapter()"
+        >
+        </sp-feature-card-header>
+
+        <div class="feature-card-meta-card meta-card">
+            <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">
+                        {{ adapter.running ? 'play_circle' : 'pause_circle' }}
+                    </mat-icon>
+                    <div>
+                        <div class="feature-card-meta-field-label">
+                            {{ 'Adapter status' | translate }}
+                        </div>
+                        <sp-label
+                            class="mt-xs"
+                            textCase="uppercase"
+                            [tone]="getStatusTone()"
+                            size="small"
+                            variant="soft"
+                        >
+                            {{ getStatusLabel() | translate }}
+                        </sp-label>
+                    </div>
+                </div>
+            </sp-feature-card-meta-section>
+
+            <div class="feature-card-meta-section__label">
+                {{ 'Preview info' | translate }}
+            </div>
+            <div class="adapter-info-table" fxLayout="column">
+                <div class="adapter-info-row">
+                    <span class="adapter-info-key">{{
+                        'Created' | translate
+                    }}</span>
+                    <span class="adapter-info-value">{{
+                        formatDate(adapter.createdAt)
+                    }}</span>
+                </div>
+
+                <div class="adapter-info-row">
+                    <span class="adapter-info-key">{{
+                        'Output stream' | translate
+                    }}</span>
+                    <span class="adapter-info-value">{{
+                        getOutputStreamName()
+                    }}</span>
+                </div>
+
+                <div class="adapter-info-row">
+                    <span class="adapter-info-key">{{
+                        'Schema fields' | translate
+                    }}</span>
+                    <span class="adapter-info-value">{{
+                        getFieldCount()
+                    }}</span>
+                </div>
+            </div>
+        </div>
+
+        <div fxFlex fxLayout="column" class="feature-preview-wrapper">
+            <div class="adapter-live-preview">
+                <div class="feature-card-meta-section__label">
+                    {{ 'Live preview' | translate }}
+                </div>
+
+                <div class="adapter-live-preview__content">
+                    @if (streamDescription) {
+                        <sp-pipeline-element-runtime-info
+                            [compact]="true"
+                            [showTitle]="false"
+                            [streamDescription]="streamDescription"
+                        >
+                        </sp-pipeline-element-runtime-info>
+                    } @else {
+                        <span class="adapter-preview-empty">{{
+                            'No live preview available.' | translate
+                        }}</span>
+                    }
+                </div>
+            </div>
+        </div>
+    </div>
+}
diff --git 
a/ui/src/app/connect/components/connect-feature-card/connect-feature-card.component.scss
 
b/ui/src/app/connect/components/connect-feature-card/connect-feature-card.component.scss
new file mode 100644
index 0000000000..93dec1fe3e
--- /dev/null
+++ 
b/ui/src/app/connect/components/connect-feature-card/connect-feature-card.component.scss
@@ -0,0 +1,99 @@
+/*!
+ * 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 {
+    width: 100%;
+    height: 100%;
+}
+
+.meta-card {
+    display: flex;
+    flex-direction: column;
+}
+
+.adapter-info-table {
+    gap: 0.25rem;
+}
+
+.adapter-info-row {
+    display: grid;
+    grid-template-columns: minmax(7rem, 38%) minmax(0, 1fr);
+    gap: 0.75rem;
+    align-items: start;
+    padding: 0.5rem 0.625rem;
+    border-radius: 0.375rem;
+    border: 1px solid transparent;
+}
+
+.adapter-info-row:nth-child(odd) {
+    background: var(--color-bg-1);
+    border-color: color-mix(in srgb, var(--color-bg-3) 45%, transparent);
+}
+
+.adapter-info-row:nth-child(even) {
+    background: var(--color-bg-0);
+    border-color: color-mix(in srgb, var(--color-bg-3) 65%, transparent);
+}
+
+.adapter-info-key {
+    font-size: var(--text-xs, 0.75rem);
+    font-weight: 600;
+    opacity: 0.78;
+    line-height: 1.2;
+    word-break: break-word;
+    text-transform: capitalize;
+    letter-spacing: 0.03em;
+    padding-top: 0.125rem;
+}
+
+.adapter-info-value {
+    min-width: 0;
+    font-size: var(--text-sm, 0.85rem);
+    font-weight: var(--font-weight-bold);
+    line-height: 1.3;
+    overflow-wrap: anywhere;
+    border-left: 1px solid var(--color-bg-3);
+    padding-left: 0.75rem;
+    opacity: 0.95;
+}
+
+.adapter-live-preview {
+    display: flex;
+    flex-direction: column;
+    flex: 1 1 auto;
+    min-height: 0;
+    gap: 0.5rem;
+}
+
+.adapter-live-preview__content {
+    flex: 1 1 auto;
+    min-height: 0;
+    overflow: auto;
+    border: 1px solid var(--color-bg-3);
+    border-radius: 0.5rem;
+    padding: 0.25rem;
+}
+
+.adapter-preview-empty {
+    display: block;
+    padding: 0.5rem;
+}
+
+.adapter-live-preview__content > sp-pipeline-element-runtime-info {
+    display: block;
+}
diff --git 
a/ui/src/app/connect/components/connect-feature-card/connect-feature-card.component.ts
 
b/ui/src/app/connect/components/connect-feature-card/connect-feature-card.component.ts
new file mode 100644
index 0000000000..6caa979090
--- /dev/null
+++ 
b/ui/src/app/connect/components/connect-feature-card/connect-feature-card.component.ts
@@ -0,0 +1,138 @@
+/*
+ * 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 { Router } from '@angular/router';
+import { FlexFillDirective } from '@ngbracket/ngx-layout';
+import {
+    FlexDirective,
+    LayoutAlignDirective,
+    LayoutDirective,
+    LayoutGapDirective,
+} from '@ngbracket/ngx-layout/flex';
+import { MatIcon } from '@angular/material/icon';
+import { TranslatePipe } from '@ngx-translate/core';
+import { forkJoin } from 'rxjs';
+import {
+    AdapterDescription,
+    AdapterService,
+    AssetConstants,
+    AssetLinkType,
+    GenericStorageService,
+    PipelineElementService,
+    SpDataStream,
+} from '@streampipes/platform-services';
+import {
+    DateFormatService,
+    FeatureCardHeaderComponent,
+    FeatureCardMetaSectionComponent,
+    PipelineElementRuntimeInfoComponent,
+    SpLabelComponent,
+} from '@streampipes/shared-ui';
+
+@Component({
+    selector: 'sp-connect-feature-card',
+    templateUrl: './connect-feature-card.component.html',
+    styleUrls: ['./connect-feature-card.component.scss'],
+    imports: [
+        FlexFillDirective,
+        FlexDirective,
+        LayoutDirective,
+        LayoutAlignDirective,
+        LayoutGapDirective,
+        MatIcon,
+        TranslatePipe,
+        FeatureCardHeaderComponent,
+        FeatureCardMetaSectionComponent,
+        SpLabelComponent,
+        PipelineElementRuntimeInfoComponent,
+    ],
+})
+export class ConnectFeatureCardComponent implements OnInit {
+    @Input()
+    resourceId: string;
+
+    @Input()
+    onClose?: () => void;
+
+    adapter: AdapterDescription;
+    assetLinkType: AssetLinkType;
+    streamDescription: SpDataStream;
+
+    private adapterService = inject(AdapterService);
+    private genericStorageService = inject(GenericStorageService);
+    private pipelineElementService = inject(PipelineElementService);
+    private dateFormatService = inject(DateFormatService);
+    private router = inject(Router);
+
+    ngOnInit(): void {
+        forkJoin([
+            this.adapterService.getAdapter(this.resourceId),
+            this.genericStorageService.getAllDocuments(
+                AssetConstants.ASSET_LINK_TYPES_DOC_NAME,
+            ),
+        ]).subscribe(([adapter, assetLinkTypes]) => {
+            this.adapter = adapter;
+            this.assetLinkType = assetLinkTypes.find(
+                link => link.linkType === 'adapter',
+            );
+
+            if (adapter?.correspondingDataStreamElementId) {
+                this.pipelineElementService
+                    .getDataStreamByElementId(
+                        adapter.correspondingDataStreamElementId,
+                    )
+                    .subscribe(stream => {
+                        this.streamDescription = stream;
+                    });
+            }
+        });
+    }
+
+    formatDate(timestamp?: number): string {
+        return this.dateFormatService.formatDate(timestamp);
+    }
+
+    getFieldCount(): number {
+        return (
+            this.streamDescription?.eventSchema?.eventProperties?.length ?? 0
+        );
+    }
+
+    getOutputStreamName(): string {
+        return (
+            this.streamDescription?.name ||
+            this.adapter?.dataStream?.name ||
+            this.adapter?.correspondingDataStreamElementId ||
+            '–'
+        );
+    }
+
+    getStatusLabel(): string {
+        return this.adapter?.running ? 'Running' : 'Stopped';
+    }
+
+    getStatusTone(): 'success' | 'error' {
+        return this.adapter?.running ? 'success' : 'error';
+    }
+
+    navigateToAdapter(): void {
+        this.onClose?.();
+        this.router.navigate(['connect', 'details', this.resourceId, 'data']);
+    }
+}
diff --git 
a/ui/src/app/connect/components/existing-adapters/existing-adapters.component.html
 
b/ui/src/app/connect/components/existing-adapters/existing-adapters.component.html
index d4369bfba1..c389dcb574 100644
--- 
a/ui/src/app/connect/components/existing-adapters/existing-adapters.component.html
+++ 
b/ui/src/app/connect/components/existing-adapters/existing-adapters.component.html
@@ -95,6 +95,8 @@
         <div fxFlex="100" fxLayout="row" fxLayoutAlign="center start">
             <sp-table
                 fxFlex="100"
+                featureCardId="adapter"
+                resourceIdKey="elementId"
                 [columns]="displayedColumns"
                 [dataSource]="dataSource"
                 [showActionsMenu]="true"

Reply via email to