This is an automated email from the ASF dual-hosted git repository.
riemer pushed a commit to branch 4190-support-multi-select-actions-in-tables
in repository https://gitbox.apache.org/repos/asf/streampipes.git
The following commit(s) were added to
refs/heads/4190-support-multi-select-actions-in-tables by this push:
new 02c94d18ca feat(#4190): Add multi-select actions to pipelines
02c94d18ca is described below
commit 02c94d18ca3555660d7329651a546ae6086d98ba
Author: Dominik Riemer <[email protected]>
AuthorDate: Mon Feb 23 09:22:40 2026 +0100
feat(#4190): Add multi-select actions to pipelines
---
...nt.scss => sp-table-multi-actions.directive.ts} | 25 +-
.../components/sp-table/sp-table.component.html | 142 ++++++++++-
.../components/sp-table/sp-table.component.scss | 10 +-
.../lib/components/sp-table/sp-table.component.ts | 272 ++++++++++++++++++++-
.../streampipes/shared-ui/src/public-api.ts | 1 +
.../pipeline-overview.component.html | 5 +-
.../pipeline-overview.component.ts | 70 ++++++
.../start-all-pipelines-dialog.component.ts | 5 +-
ui/src/scss/sp/forms.scss | 12 +
9 files changed, 512 insertions(+), 30 deletions(-)
diff --git
a/ui/projects/streampipes/shared-ui/src/lib/components/sp-table/sp-table.component.scss
b/ui/projects/streampipes/shared-ui/src/lib/components/sp-table/sp-table-multi-actions.directive.ts
similarity index 65%
copy from
ui/projects/streampipes/shared-ui/src/lib/components/sp-table/sp-table.component.scss
copy to
ui/projects/streampipes/shared-ui/src/lib/components/sp-table/sp-table-multi-actions.directive.ts
index 4d0b280941..21511f378f 100644
---
a/ui/projects/streampipes/shared-ui/src/lib/components/sp-table/sp-table.component.scss
+++
b/ui/projects/streampipes/shared-ui/src/lib/components/sp-table/sp-table-multi-actions.directive.ts
@@ -1,4 +1,4 @@
-/*!
+/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
@@ -16,24 +16,7 @@
*
*/
-.paginator-container {
- border-top: 1px solid rgba(0, 0, 0, 0.12);
-}
+import { Directive } from '@angular/core';
-.mat-mdc-row:hover {
- background-color: var(--color-bg-1);
-}
-
-.mat-mdc-no-data-row {
- height: var(--mat-table-row-item-container-height, 52px);
- text-align: center;
-}
-
-.cursor-pointer {
- cursor: pointer;
-}
-
-.right-column {
- text-align: right; /* align contents inside cell */
- margin-left: auto; /* push this column to the far right */
-}
+@Directive({ selector: 'ng-template[spTableMultiActions]' })
+export class SpTableMultiActionsDirective {}
diff --git
a/ui/projects/streampipes/shared-ui/src/lib/components/sp-table/sp-table.component.html
b/ui/projects/streampipes/shared-ui/src/lib/components/sp-table/sp-table.component.html
index 340dfff202..86ba8b3f4e 100644
---
a/ui/projects/streampipes/shared-ui/src/lib/components/sp-table/sp-table.component.html
+++
b/ui/projects/streampipes/shared-ui/src/lib/components/sp-table/sp-table.component.html
@@ -17,9 +17,145 @@
-->
<div fxLayout="column">
+ @if (showSelectionCheckboxes) {
+ <div
+ class="selection-toolbar"
+ fxLayout="row wrap"
+ fxLayoutAlign="space-between center"
+ fxLayoutGap="8px"
+ >
+ <div
+ fxLayout="row wrap"
+ fxLayoutAlign="start center"
+ fxLayoutGap="8px"
+ >
+ <div
+ fxLayout="row wrap"
+ fxLayoutAlign="start center"
+ fxLayoutGap="4px"
+ >
+ <button
+ mat-flat-button
+ class="mat-basic"
+ (click)="selectVisiblePageRows()"
+ [disabled]="!visiblePageRows.length"
+ >
+ <mat-icon>select_all</mat-icon>
+ {{ 'Select visible' | translate }}
+ </button>
+ <button
+ mat-flat-button
+ class="mat-basic"
+ (click)="clearSelection()"
+ [disabled]="!selectedRows.length"
+ >
+ <mat-icon>deselect</mat-icon>
+ {{ 'Select none' | translate }}
+ </button>
+ </div>
+ </div>
+
+ @if (hasMultiActionsToolbarControls()) {
+ <div fxLayout="row" fxLayoutGap="5px">
+ @if (hasBuiltInMultiActionSelect()) {
+ <sp-form-field
+ [level]="3"
+ [label]="multiActionsSelectLabel | translate"
+ margin="0"
+ >
+ <mat-form-field class="form-field-small">
+ <mat-select
+ panelClass="small-select-panel"
+ [placeholder]="'Select action' | translate"
+ [value]="selectedMultiAction"
+ (valueChange)="
+ onSelectedMultiActionChange($event)
+ "
+ >
+ @for (
+ actionOption of multiActionOptions;
+ track actionOption.value
+ ) {
+ <mat-option
+ [value]="actionOption.value"
+ [disabled]="actionOption.disabled"
+ >
+ @if (actionOption.icon) {
+ <mat-icon
+
class="selection-toolbar__action-option-icon"
+ >
+ {{ actionOption.icon }}
+ </mat-icon>
+ }
+ {{ actionOption.label | translate
}}
+ </mat-option>
+ }
+ </mat-select>
+ </mat-form-field>
+ </sp-form-field>
+ }
+
+ @if (multiActionsTemplate) {
+ <ng-container
+ *ngTemplateOutlet="
+ multiActionsTemplate;
+ context: multiActionsContext
+ "
+ >
+ </ng-container>
+ }
+
+ @if (showMultiActionsExecuteButton) {
+ <sp-form-field [level]="3" label=" " margin="0">
+ <button
+ mat-flat-button
+ (click)="emitMultiActionsExecute()"
+ [disabled]="
+ isMultiActionsExecuteButtonDisabled()
+ "
+ >
+ {{ multiActionsExecuteLabel | translate }}
+ </button>
+ </sp-form-field>
+ }
+ </div>
+ }
+ </div>
+ }
+
<table mat-table class="sp-table" [dataSource]="dataSource">
<ng-content></ng-content>
+ @if (showSelectionCheckboxes) {
+ <ng-container [matColumnDef]="selectionColumnId">
+ <th
+ mat-header-cell
+ *matHeaderCellDef
+ class="checkbox-multi-select"
+ >
+ <mat-checkbox
+ [checked]="areAllVisibleRowsSelected()"
+ [indeterminate]="areSomeVisibleRowsSelected()"
+ (change)="toggleSelectAllVisibleRows($event.checked)"
+ (click)="$event.stopPropagation()"
+ >
+ </mat-checkbox>
+ </th>
+ <td
+ mat-cell
+ *matCellDef="let element"
+ class="checkbox-multi-select"
+ >
+ <mat-checkbox
+ [checked]="isRowSelected(element)"
+ (change)="toggleRowSelection(element, $event.checked)"
+ (click)="$event.stopPropagation()"
+ >
+ </mat-checkbox>
+ </td>
+ </ng-container>
+ }
+
@if (showActionsMenu) {
<ng-container matColumnDef="actions">
<th mat-header-cell *matHeaderCellDef></th>
@@ -72,10 +208,10 @@
</ng-container>
}
- <tr mat-header-row *matHeaderRowDef="columns"></tr>
+ <tr mat-header-row *matHeaderRowDef="renderedColumns"></tr>
<tr
mat-row
- *matRowDef="let row; columns: columns"
+ *matRowDef="let row; columns: renderedColumns"
(click)="rowClicked.emit(row)"
[ngClass]="rowsClickable ? 'cursor-pointer' : ''"
></tr>
@@ -84,7 +220,7 @@
<td
data-cy="no-table-entries"
class="mat-cell"
- [colSpan]="columns.length"
+ [colSpan]="renderedColumns.length"
>
{{ 'No entries available.' | translate }}
</td>
diff --git
a/ui/projects/streampipes/shared-ui/src/lib/components/sp-table/sp-table.component.scss
b/ui/projects/streampipes/shared-ui/src/lib/components/sp-table/sp-table.component.scss
index 4d0b280941..e58390be1f 100644
---
a/ui/projects/streampipes/shared-ui/src/lib/components/sp-table/sp-table.component.scss
+++
b/ui/projects/streampipes/shared-ui/src/lib/components/sp-table/sp-table.component.scss
@@ -17,7 +17,11 @@
*/
.paginator-container {
- border-top: 1px solid rgba(0, 0, 0, 0.12);
+ border-top: 1px solid var(--color-bg-3);
+}
+
+.selection-toolbar {
+ border-bottom: 1px solid var(--color-bg-3);
}
.mat-mdc-row:hover {
@@ -37,3 +41,7 @@
text-align: right; /* align contents inside cell */
margin-left: auto; /* push this column to the far right */
}
+
+.checkbox-multi-select {
+ width: 100px;
+}
diff --git
a/ui/projects/streampipes/shared-ui/src/lib/components/sp-table/sp-table.component.ts
b/ui/projects/streampipes/shared-ui/src/lib/components/sp-table/sp-table.component.ts
index f750f75381..bdae3abb34 100644
---
a/ui/projects/streampipes/shared-ui/src/lib/components/sp-table/sp-table.component.ts
+++
b/ui/projects/streampipes/shared-ui/src/lib/components/sp-table/sp-table.component.ts
@@ -25,12 +25,16 @@ import {
EventEmitter,
inject,
Input,
+ OnChanges,
+ OnDestroy,
Output,
QueryList,
Signal,
+ SimpleChanges,
TemplateRef,
ViewChild,
} from '@angular/core';
+import { SelectionModel } from '@angular/cdk/collections';
import {
MatCell,
MatCellDef,
@@ -48,6 +52,7 @@ import {
import { MatPaginator, PageEvent } from '@angular/material/paginator';
import { SpTableActionsDirective } from './sp-table-actions.directive';
import { MatMenu, MatMenuTrigger } from '@angular/material/menu';
+import { SpTableMultiActionsDirective } from
'./sp-table-multi-actions.directive';
import { LocalStorageService } from
'../../services/local-storage-settings.service';
import { FeatureCardService } from '../feature-card-host/feature-card.service';
import {
@@ -55,12 +60,30 @@ import {
LayoutAlignDirective,
LayoutDirective,
} from '@ngbracket/ngx-layout/flex';
-import { MatIconButton } from '@angular/material/button';
+import { LayoutGapDirective } from '@ngbracket/ngx-layout';
+import { MatButton, MatIconButton } from '@angular/material/button';
import { MatTooltip } from '@angular/material/tooltip';
import { MatIcon } from '@angular/material/icon';
import { NgClass, NgTemplateOutlet } from '@angular/common';
import { ClassDirective } from '@ngbracket/ngx-layout/extended';
import { TranslatePipe } from '@ngx-translate/core';
+import { MatCheckbox } from '@angular/material/checkbox';
+import { MatFormField } from '@angular/material/form-field';
+import { Subscription } from 'rxjs';
+import { MatOption, MatSelect } from '@angular/material/select';
+import { FormFieldComponent } from '../form-field/form-field.component';
+
+export interface SpTableMultiActionOption {
+ value: string;
+ label: string;
+ icon?: string;
+ disabled?: boolean;
+}
+
+export interface SpTableMultiActionExecuteEvent<T> {
+ selectedRows: T[];
+ action: string | null;
+}
@Component({
selector: 'sp-table',
@@ -76,10 +99,15 @@ import { TranslatePipe } from '@ngx-translate/core';
MatCell,
LayoutAlignDirective,
MatIconButton,
+ MatButton,
MatTooltip,
MatIcon,
+ MatCheckbox,
+ MatFormField,
MatMenuTrigger,
MatMenu,
+ MatSelect,
+ MatOption,
NgTemplateOutlet,
MatHeaderRowDef,
MatHeaderRow,
@@ -91,9 +119,15 @@ import { TranslatePipe } from '@ngx-translate/core';
FlexDirective,
MatPaginator,
TranslatePipe,
+ LayoutGapDirective,
+ FormFieldComponent,
],
})
-export class SpTableComponent<T> implements AfterViewInit, AfterContentInit {
+export class SpTableComponent<T>
+ implements AfterViewInit, AfterContentInit, OnChanges, OnDestroy
+{
+ readonly selectionColumnId = 'spSelection';
+
@ContentChildren(MatHeaderRowDef) headerRowDefs:
QueryList<MatHeaderRowDef>;
@ContentChildren(MatRowDef) rowDefs: QueryList<MatRowDef<T>>;
@ContentChildren(MatColumnDef) columnDefs: QueryList<MatColumnDef>;
@@ -104,22 +138,41 @@ export class SpTableComponent<T> implements
AfterViewInit, AfterContentInit {
@Input() columns: string[];
@Input() rowsClickable = false;
@Input() showActionsMenu = false;
+ @Input() showSelectionCheckboxes = false;
+ @Input() showMultiActionsExecuteButton = false;
+ @Input() multiActionsExecuteLabel = 'Execute';
+ @Input() multiActionsExecuteDisabled = false;
+ @Input() multiActionsSelectLabel = 'Action';
+ @Input() multiActionOptions: SpTableMultiActionOption[] = [];
@Input() featureCardId: string;
@Input() resourceIdKey = 'elementId';
@Input() dataSource: MatTableDataSource<T>;
@Output() rowClicked = new EventEmitter<T>();
+ @Output() selectionChanged = new EventEmitter<T[]>();
+ @Output() multiActionsExecute = new EventEmitter<
+ SpTableMultiActionExecuteEvent<T>
+ >();
+ @Output() multiActionSelectionChanged = new EventEmitter<string | null>();
@ViewChild('paginator') paginator: MatPaginator;
@ContentChild(SpTableActionsDirective, { read: TemplateRef })
actionsTemplate?: TemplateRef<any>;
+ @ContentChild(SpTableMultiActionsDirective, { read: TemplateRef })
+ multiActionsTemplate?: TemplateRef<any>;
timedOutCloser: any;
trigger: MatMenuTrigger | undefined = undefined;
+ visiblePageRows: T[] = [];
+ selectedMultiAction: string | null = null;
+
+ readonly selection = new SelectionModel<T>(true, []);
private localStorageService = inject(LocalStorageService);
private featureCardService = inject(FeatureCardService);
+ private renderedDataSubscription?: Subscription;
+ private viewInitialized = false;
readonly pageSize: Signal<number>;
@@ -131,7 +184,8 @@ export class SpTableComponent<T> implements AfterViewInit,
AfterContentInit {
}
ngAfterViewInit() {
- this.dataSource.paginator = this.paginator;
+ this.viewInitialized = true;
+ this.bindDataSource();
}
ngAfterContentInit() {
@@ -145,6 +199,34 @@ export class SpTableComponent<T> implements AfterViewInit,
AfterContentInit {
this.table.setNoDataRow(this.noDataRow);
}
+ ngOnChanges(changes: SimpleChanges) {
+ if (changes['dataSource']) {
+ this.selection.clear();
+ this.emitSelection();
+ this.visiblePageRows = [];
+ if (this.viewInitialized) {
+ this.bindDataSource();
+ }
+ }
+
+ if (
+ changes['showSelectionCheckboxes'] &&
+ !this.showSelectionCheckboxes &&
+ this.selection.hasValue()
+ ) {
+ this.selection.clear();
+ this.emitSelection();
+ }
+
+ if (changes['multiActionOptions']) {
+ this.ensureValidSelectedMultiAction();
+ }
+ }
+
+ ngOnDestroy() {
+ this.renderedDataSubscription?.unsubscribe();
+ }
+
mouseEnter(trigger) {
if (this.timedOutCloser) {
clearTimeout(this.timedOutCloser);
@@ -173,4 +255,188 @@ export class SpTableComponent<T> implements
AfterViewInit, AfterContentInit {
element[this.resourceIdKey],
);
}
+
+ get renderedColumns(): string[] {
+ const baseColumns = this.columns ?? [];
+ if (
+ !this.showSelectionCheckboxes ||
+ baseColumns.includes(this.selectionColumnId)
+ ) {
+ return baseColumns;
+ }
+
+ return [this.selectionColumnId, ...baseColumns];
+ }
+
+ get selectedRows(): T[] {
+ return this.selection.selected;
+ }
+
+ get multiActionsContext() {
+ return {
+ $implicit: this.selectedRows,
+ selectedRows: this.selectedRows,
+ selectedCount: this.selectedRows.length,
+ visiblePageRows: this.visiblePageRows,
+ visiblePageRowCount: this.visiblePageRows.length,
+ };
+ }
+
+ hasBuiltInMultiActionSelect(): boolean {
+ return this.multiActionOptions?.length > 0;
+ }
+
+ hasMultiActionsToolbarControls(): boolean {
+ return (
+ this.hasBuiltInMultiActionSelect() ||
+ !!this.multiActionsTemplate ||
+ this.showMultiActionsExecuteButton
+ );
+ }
+
+ isRowSelected(row: T): boolean {
+ return this.selection.isSelected(row);
+ }
+
+ toggleRowSelection(row: T, checked: boolean) {
+ if (checked) {
+ this.selection.select(row);
+ } else {
+ this.selection.deselect(row);
+ }
+
+ this.emitSelection();
+ }
+
+ selectVisiblePageRows() {
+ if (!this.visiblePageRows.length) {
+ return;
+ }
+
+ this.selection.select(...this.visiblePageRows);
+ this.emitSelection();
+ }
+
+ clearSelection() {
+ if (!this.selection.hasValue()) {
+ return;
+ }
+
+ this.selection.clear();
+ this.emitSelection();
+ }
+
+ toggleSelectAllVisibleRows(checked: boolean) {
+ if (checked) {
+ this.selectVisiblePageRows();
+ return;
+ }
+
+ if (!this.visiblePageRows.length) {
+ return;
+ }
+
+ this.selection.deselect(...this.visiblePageRows);
+ this.emitSelection();
+ }
+
+ areAllVisibleRowsSelected(): boolean {
+ return (
+ this.visiblePageRows.length > 0 &&
+ this.visiblePageRows.every(row => this.selection.isSelected(row))
+ );
+ }
+
+ areSomeVisibleRowsSelected(): boolean {
+ return (
+ this.visiblePageRows.some(row => this.selection.isSelected(row)) &&
+ !this.areAllVisibleRowsSelected()
+ );
+ }
+
+ private bindDataSource() {
+ if (!this.dataSource || !this.paginator) {
+ return;
+ }
+
+ this.dataSource.paginator = this.paginator;
+
+ this.renderedDataSubscription?.unsubscribe();
+ this.renderedDataSubscription = this.dataSource.connect().subscribe({
+ next: rows => {
+ this.visiblePageRows = rows ?? [];
+ this.pruneSelection();
+ },
+ });
+ }
+
+ private pruneSelection() {
+ if (!this.selection.hasValue() || !this.dataSource) {
+ return;
+ }
+
+ const availableRows = new Set(this.dataSource.filteredData ?? []);
+ const rowsToRemove = this.selection.selected.filter(
+ row => !availableRows.has(row),
+ );
+
+ if (!rowsToRemove.length) {
+ return;
+ }
+
+ this.selection.deselect(...rowsToRemove);
+ this.emitSelection();
+ }
+
+ private emitSelection() {
+ this.selectionChanged.emit(this.selection.selected);
+ }
+
+ emitMultiActionsExecute() {
+ this.multiActionsExecute.emit({
+ selectedRows: this.selection.selected,
+ action: this.selectedMultiAction,
+ });
+ }
+
+ onSelectedMultiActionChange(action: string | null) {
+ this.selectedMultiAction = action;
+ this.multiActionSelectionChanged.emit(action);
+ }
+
+ isMultiActionsExecuteButtonDisabled(): boolean {
+ if (
+ !this.selection.selected.length ||
+ this.multiActionsExecuteDisabled
+ ) {
+ return true;
+ }
+
+ if (this.hasBuiltInMultiActionSelect() && !this.selectedMultiAction) {
+ return true;
+ }
+
+ const selectedOption = this.multiActionOptions?.find(
+ option => option.value === this.selectedMultiAction,
+ );
+
+ return !!selectedOption?.disabled;
+ }
+
+ private ensureValidSelectedMultiAction() {
+ if (!this.selectedMultiAction) {
+ return;
+ }
+
+ const actionStillExists = (this.multiActionOptions ?? []).some(
+ option => option.value === this.selectedMultiAction,
+ );
+
+ if (actionStillExists) {
+ return;
+ }
+
+ this.selectedMultiAction = null;
+ this.multiActionSelectionChanged.emit(null);
+ }
}
diff --git a/ui/projects/streampipes/shared-ui/src/public-api.ts
b/ui/projects/streampipes/shared-ui/src/public-api.ts
index 94f476cbe4..5b4a0b2060 100644
--- a/ui/projects/streampipes/shared-ui/src/public-api.ts
+++ b/ui/projects/streampipes/shared-ui/src/public-api.ts
@@ -44,6 +44,7 @@ export * from
'./lib/components/sp-exception-message/exception-details/exception
export * from './lib/components/sp-label/sp-label.component';
export * from './lib/components/sp-table/sp-table.component';
export * from './lib/components/sp-table/sp-table-actions.directive';
+export * from './lib/components/sp-table/sp-table-multi-actions.directive';
export * from './lib/components/alert-banner/alert-banner.component';
export * from './lib/components/time-selector/time-selector.model';
export * from './lib/components/time-selector/time-range-selector.component';
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 4d41790d41..684769168c 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
@@ -19,10 +19,14 @@
<sp-table
[dataSource]="dataSource"
[columns]="displayedColumns"
+ [showSelectionCheckboxes]="hasPipelineWritePrivileges"
+ [showMultiActionsExecuteButton]="true"
+ [multiActionOptions]="bulkPipelineActionOptions"
featureCardId="pipeline"
resourceIdKey="_id"
[showActionsMenu]="true"
[rowsClickable]="true"
+ (multiActionsExecute)="executeSelectedPipelineAction($event)"
(rowClicked)="
pipelineOperationsService.showPipelineDetails($event.elementId)
"
@@ -133,7 +137,6 @@
}
@if (pipeline.running) {
<button
- color="accent"
mat-icon-button
[matTooltip]="'Stop pipeline' | translate"
matTooltipPosition="above"
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 299c77a774..e37e1a43ab 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
@@ -27,6 +27,7 @@ import {
Output,
ViewChild,
} from '@angular/core';
+import { StartAllPipelinesDialogComponent } from
'../../dialog/start-all-pipelines/start-all-pipelines-dialog.component';
import { PipelineOperationsService } from
'../../services/pipeline-operations.service';
import {
MatCell,
@@ -41,6 +42,11 @@ import { AuthService } from '../../../services/auth.service';
import { UserPrivilege } from '../../../_enums/user-privilege.enum';
import {
CurrentUserService,
+ DialogRef,
+ DialogService,
+ PanelType,
+ SpTableMultiActionExecuteEvent,
+ SpTableMultiActionOption,
SpTableActionsDirective,
SpTableComponent,
} from '@streampipes/shared-ui';
@@ -105,12 +111,18 @@ export class PipelineOverviewComponent implements OnInit,
OnDestroy {
starting = false;
stopping = false;
hasPipelineWritePrivileges = false;
+ readonly bulkPipelineActionOptions: SpTableMultiActionOption[] = [
+ { value: 'start', label: 'Start selected', icon: 'play_arrow' },
+ { value: 'stop', label: 'Stop selected', icon: 'stop' },
+ { value: 'forceStop', label: 'Force stop selected', icon: 'stop' },
+ ];
userSub: Subscription;
public pipelineOperationsService = inject(PipelineOperationsService);
private authService = inject(AuthService);
private currentUserService = inject(CurrentUserService);
+ private dialogService = inject(DialogService);
ngOnInit() {
this.userSub = this.currentUserService.user$.subscribe(user => {
@@ -161,6 +173,64 @@ export class PipelineOverviewComponent implements OnInit,
OnDestroy {
});
}
+ startStopSelectedPipelines(
+ selectedPipelines: Pipeline[],
+ action: boolean,
+ forceStop = false,
+ ) {
+ const pipelines = selectedPipelines.filter(pipeline =>
+ action ? !pipeline.running && pipeline.valid : pipeline.running,
+ );
+
+ if (!pipelines.length) {
+ return;
+ }
+
+ const dialogRef: DialogRef<StartAllPipelinesDialogComponent> =
+ this.dialogService.open(StartAllPipelinesDialogComponent, {
+ panelType: PanelType.STANDARD_PANEL,
+ title: (action ? 'Start' : 'Stop') + ' selected pipelines',
+ width: '70vw',
+ data: {
+ pipelines,
+ action,
+ forceStop,
+ },
+ });
+
+ dialogRef.afterClosed().subscribe(refresh => {
+ if (refresh) {
+ this.refreshPipelinesEmitter.emit(true);
+ }
+ });
+ }
+
+ executeSelectedPipelineAction(
+ event: SpTableMultiActionExecuteEvent<Pipeline>,
+ ) {
+ if (
+ !this.hasPipelineWritePrivileges ||
+ this.starting ||
+ this.stopping
+ ) {
+ return;
+ }
+
+ if (
+ event.action !== 'start' &&
+ event.action !== 'stop' &&
+ event.action !== 'forceStop'
+ ) {
+ return;
+ }
+
+ this.startStopSelectedPipelines(
+ event.selectedRows,
+ event.action === 'start',
+ event.action === 'forceStop',
+ );
+ }
+
ngOnDestroy() {
this.userSub?.unsubscribe();
}
diff --git
a/ui/src/app/pipelines/dialog/start-all-pipelines/start-all-pipelines-dialog.component.ts
b/ui/src/app/pipelines/dialog/start-all-pipelines/start-all-pipelines-dialog.component.ts
index 0b9d576e02..3d62cda9ce 100644
---
a/ui/src/app/pipelines/dialog/start-all-pipelines/start-all-pipelines-dialog.component.ts
+++
b/ui/src/app/pipelines/dialog/start-all-pipelines/start-all-pipelines-dialog.component.ts
@@ -33,6 +33,9 @@ export class StartAllPipelinesDialogComponent implements
OnInit {
@Input()
pipelines: Pipeline[];
+ @Input()
+ forceStop = false;
+
pipelinesToModify: Pipeline[];
installationStatus: any;
installationFinished: boolean;
@@ -133,7 +136,7 @@ export class StartAllPipelinesDialogComponent implements
OnInit {
stopPipeline(pipeline, index) {
this.pipelineService
- .stopPipeline(pipeline._id)
+ .stopPipeline(pipeline._id, this.forceStop)
.subscribe(
data => {
this.installationStatus[index].status = data.success
diff --git a/ui/src/scss/sp/forms.scss b/ui/src/scss/sp/forms.scss
index bd31a182e5..335228e26c 100644
--- a/ui/src/scss/sp/forms.scss
+++ b/ui/src/scss/sp/forms.scss
@@ -97,6 +97,18 @@ mat-form-field.mat-mdc-form-field.form-field-size-smaller {
}
}
+.small-select-panel {
+ --mat-option-label-text-size: var(--font-size-md);
+ font-size: var(--font-size-md);
+
+ .mat-icon {
+ width: var(--font-size-md);
+ height: var(--font-size-md);
+ font-size: var(--font-size-md);
+ line-height: var(--font-size-md);
+ }
+}
+
.form-field-smaller {
.mat-mdc-form-field-input-control.mat-mdc-form-field-input-control {
letter-spacing: 0;