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 e2eaa319fb fix: Align UI views with dashboard and data explorer 
permissions (#3961)
e2eaa319fb is described below

commit e2eaa319fbc1d655273cf4998bd8935b880d54c7
Author: Dominik Riemer <[email protected]>
AuthorDate: Tue Nov 25 12:48:14 2025 +0100

    fix: Align UI views with dashboard and data explorer permissions (#3961)
    
    Co-authored-by: Philipp Zehnder <[email protected]>
---
 .../support/utils/dataExplorer/DataExplorerBtns.ts |  15 +-
 .../utils/dataExplorer/DataExplorerUtils.ts        |  47 ++++-
 .../dataExplorer/addAssetToDashboard.smoke.spec.ts |   2 +-
 .../testAddAssetOnResourceCreation.ts              |   4 +-
 .../userManagement/testUserRoleCharts.spec.ts      |  54 ++----
 .../userManagement/testUserRoleDashboard.spec.ts   | 204 +++++++++++++++++++++
 .../dashboard-overview-table.component.html        |   5 +-
 .../chart-selection/chart-selection.component.html |  20 +-
 .../chart-selection/chart-selection.component.ts   |  12 +-
 .../panel/dashboard-panel.component.html           |   4 +-
 .../components/panel/dashboard-panel.component.ts  |   8 +-
 .../dashboard-toolbar.component.html               |   4 +-
 .../dashboard-toolbar.component.ts                 |   2 +-
 .../data-explorer-chart-container.component.html   |  32 ++--
 .../data-explorer-chart-container.component.ts     |   4 +
 .../data-explorer-chart-view.component.ts          |  26 ++-
 .../data-explorer-overview-table.component.ts      |   5 +-
 .../pipeline-details-toolbar.component.html        |  26 +--
 .../pipeline-details-toolbar.component.ts          |   3 +
 .../pipeline-details.component.html                |   1 +
 .../pipeline-details/pipeline-details.component.ts |  12 +-
 21 files changed, 383 insertions(+), 107 deletions(-)

diff --git a/ui/cypress/support/utils/dataExplorer/DataExplorerBtns.ts 
b/ui/cypress/support/utils/dataExplorer/DataExplorerBtns.ts
index da3de0faef..bbbbf0687f 100644
--- a/ui/cypress/support/utils/dataExplorer/DataExplorerBtns.ts
+++ b/ui/cypress/support/utils/dataExplorer/DataExplorerBtns.ts
@@ -25,9 +25,14 @@ export class DataExplorerBtns {
         return cy.dataCy('save-data-view-btn', { timeout: 10000 });
     }
 
-    public static saveDashboard() {
+    public static saveDataViewBtn() {
         return cy.dataCy('save-data-view');
     }
+
+    public static saveDashboardBtn() {
+        return cy.dataCy('save-dashboard-btn');
+    }
+
     public static saveChartsToAssetBtn() {
         return cy
             .dataCy('add-to-Asset-data-view-btn', { timeout: 10000 })
@@ -63,10 +68,18 @@ export class DataExplorerBtns {
         return cy.dataCy('remove-' + dataViewName);
     }
 
+    public static createChartBtn() {
+        return cy.dataCy('create-chart-button');
+    }
+
     public static editDashboardBtn(dashboardName) {
         return cy.dataCy('edit-dashboard-' + dashboardName);
     }
 
+    public static viewDashboardBtn(dashboardName) {
+        return cy.dataCy('view-dashboard-' + dashboardName);
+    }
+
     public static editDashboardSettingsBtn(dashboardName) {
         return cy.dataCy('edit-dashboard-settings-' + dashboardName);
     }
diff --git a/ui/cypress/support/utils/dataExplorer/DataExplorerUtils.ts 
b/ui/cypress/support/utils/dataExplorer/DataExplorerUtils.ts
index 752816d319..bfc3d20bd1 100644
--- a/ui/cypress/support/utils/dataExplorer/DataExplorerUtils.ts
+++ b/ui/cypress/support/utils/dataExplorer/DataExplorerUtils.ts
@@ -40,7 +40,15 @@ export class DataExplorerUtils {
 
     public static checkAmountOfCharts(amount: number) {
         DataExplorerUtils.goToDatalake();
+        this.checkAmount(amount);
+    }
+
+    public static checkAmountOfDashboards(amount: number) {
+        DataExplorerUtils.goToDashboard();
+        this.checkAmount(amount);
+    }
 
+    private static checkAmount(amount: number) {
         if (amount === 0) {
             // The wait is needed because the default value is the 
no-table-entries element.
             // It must be waited till the data is loaded. Once a better 
solution is found, this can be removed.
@@ -61,6 +69,16 @@ export class DataExplorerUtils {
         DataExplorerBtns.editDataViewButton(chartName).should('not.exist');
     }
 
+    public static checkDashboardCanBeEdited(dashboardName: string) {
+        GeneralUtils.openMenuForRow(dashboardName);
+        DataExplorerBtns.editDashboardBtn(dashboardName).should('exist');
+    }
+
+    public static checkDashboardCanNotBeEdited(dashboardName: string) {
+        GeneralUtils.openMenuForRow(dashboardName);
+        DataExplorerBtns.editDashboardBtn(dashboardName).should('not.exist');
+    }
+
     public static initDataLakeTests() {
         cy.initStreamPipesTest();
         DataExplorerUtils.loadRandomDataSetIntoDataLake();
@@ -161,11 +179,14 @@ export class DataExplorerUtils {
         });
     }
 
-    public static createDashboard(name) {
-        // Create new data view
-        DataExplorerBtns.newDashboardDialogBtn().click();
+    public static createNewDashboard(name: string) {
+        DataExplorerUtils.goToDashboard();
+        DataExplorerUtils.addNewDashboard(name);
+        DataExplorerUtils.saveDataView();
+    }
 
-        // Configure data view
+    public static addNewDashboard(name: string) {
+        DataExplorerBtns.newDashboardDialogBtn().click();
         cy.dataCy('data-view-name').type(name);
     }
 
@@ -183,14 +204,19 @@ export class DataExplorerUtils {
         DataExplorerUtils.goToDashboard();
 
         //ADD Assets
-        DataExplorerUtils.createDashboard(name);
+        DataExplorerUtils.addNewDashboard(name);
         DataExplorerUtils.addAssetsToDashboard(assetNameList);
-        DataExplorerUtils.saveDashboard();
+        DataExplorerUtils.saveDataView();
+    }
+
+    public static saveDataView() {
+        return DataExplorerBtns.saveDataViewBtn().click();
     }
 
     public static saveDashboard() {
-        return DataExplorerBtns.saveDashboard().click();
+        return DataExplorerBtns.saveDashboardBtn().click();
     }
+
     public static addDataViewAndTableWidget(
         dataViewName: string,
         dataSet: string,
@@ -237,7 +263,7 @@ export class DataExplorerUtils {
 
         // Configure data view
         cy.dataCy('data-view-name').type(name);
-        DataExplorerBtns.saveDashboard().click();
+        DataExplorerBtns.saveDataViewBtn().click();
 
         this.editDashboard(name);
     }
@@ -269,6 +295,11 @@ export class DataExplorerUtils {
         DataExplorerBtns.editDashboardBtn(dashboardName).click();
     }
 
+    public static viewDashboard(dashboardName: string) {
+        GeneralUtils.openMenuForRow(dashboardName);
+        DataExplorerBtns.viewDashboardBtn(dashboardName).click();
+    }
+
     public static editDashboardSettings(dashboardName: string) {
         GeneralUtils.openMenuForRow(dashboardName);
         DataExplorerBtns.editDashboardSettingsBtn(dashboardName).click();
diff --git a/ui/cypress/tests/dataExplorer/addAssetToDashboard.smoke.spec.ts 
b/ui/cypress/tests/dataExplorer/addAssetToDashboard.smoke.spec.ts
index defb473209..073243033a 100644
--- a/ui/cypress/tests/dataExplorer/addAssetToDashboard.smoke.spec.ts
+++ b/ui/cypress/tests/dataExplorer/addAssetToDashboard.smoke.spec.ts
@@ -70,7 +70,7 @@ describe('Test add Assets To Dashboard', () => {
         DataExplorerUtils.renameDashboard('NEW');
         const assetNameList2 = [assetName2, assetName3];
         DataExplorerUtils.addToAsset(assetNameList2);
-        DataExplorerUtils.saveDashboard();
+        DataExplorerUtils.saveDataView();
 
         AssetUtils.checkAmountOfLinkedResourcesByAssetName(assetName1, 1);
         AssetUtils.checkAmountOfLinkedResourcesByAssetName(assetName3, 1);
diff --git a/ui/cypress/tests/userManagement/testAddAssetOnResourceCreation.ts 
b/ui/cypress/tests/userManagement/testAddAssetOnResourceCreation.ts
index a27908b732..97c963a015 100644
--- a/ui/cypress/tests/userManagement/testAddAssetOnResourceCreation.ts
+++ b/ui/cypress/tests/userManagement/testAddAssetOnResourceCreation.ts
@@ -123,7 +123,7 @@ describe('Test that resources can be added to assets on 
creation', () => {
     it('Check Role Asset Admin in Dashboard', () => {
         UserUtils.switchUser(newUser);
         DataExplorerUtils.goToDashboard();
-        DataExplorerUtils.createDashboard('Test');
+        DataExplorerUtils.addNewDashboard('Test');
 
         DataExplorerBtns.dashboardAssetCheckboxBtn().should('exist');
         DataExplorerBtns.closeDashboardCreate().click();
@@ -133,7 +133,7 @@ describe('Test that resources can be added to assets on 
creation', () => {
         UserUtils.switchUser(newUser);
 
         DataExplorerUtils.goToDashboard();
-        DataExplorerUtils.createDashboard('Test');
+        DataExplorerUtils.addNewDashboard('Test');
         DataExplorerBtns.dashboardAssetCheckboxBtn().should('not.exist');
     });
 });
diff --git a/ui/cypress/tests/userManagement/testUserRoleCharts.spec.ts 
b/ui/cypress/tests/userManagement/testUserRoleCharts.spec.ts
index e1f391afcf..282b5e3324 100644
--- a/ui/cypress/tests/userManagement/testUserRoleCharts.spec.ts
+++ b/ui/cypress/tests/userManagement/testUserRoleCharts.spec.ts
@@ -54,12 +54,10 @@ describe('Test User Roles for Charts', () => {
         setup();
 
         // check admin
-        assertChartIsVisibleAndEditableCanChangePermissions(
-            UserUtils.adminUser,
-        );
+        chartIsVisibleAndEditableCanChangePermissions(UserUtils.adminUser);
 
         // check other users
-        assertChartIsNotVisible(chartAdmin2);
+        chartIsNotVisible(chartAdmin2);
     });
 
     it('Make chart public', () => {
@@ -67,13 +65,11 @@ describe('Test User Roles for Charts', () => {
 
         PermissionUtils.markElementAsPublic(chartName);
 
-        assertChartIsVisibleAndEditableCanChangePermissions(
-            UserUtils.adminUser,
-        );
+        chartIsVisibleAndEditableCanChangePermissions(UserUtils.adminUser);
 
-        assertChartIsVisibleButNotEditable(chartUser1);
+        chartIsVisibleButNotEditable(chartUser1);
 
-        assertChartIsVisibleAndEditableCannotChangePermissions(chartAdmin2);
+        chartIsVisibleAndEditableCannotChangePermissions(chartAdmin2);
     });
 
     it('Share chart with other user and change ownership', () => {
@@ -81,27 +77,23 @@ describe('Test User Roles for Charts', () => {
 
         PermissionUtils.authorizeUser(chartName, chartAdmin2.email);
 
-        assertChartIsVisibleAndEditableCanChangePermissions(
-            UserUtils.adminUser,
-        );
+        chartIsVisibleAndEditableCanChangePermissions(UserUtils.adminUser);
 
-        assertChartIsVisibleAndEditableCannotChangePermissions(chartAdmin2);
+        chartIsVisibleAndEditableCannotChangePermissions(chartAdmin2);
 
-        assertChartIsNotVisible(chartUser1);
+        chartIsNotVisible(chartUser1);
 
         UserUtils.switchUser(chartAdmin1);
         DataExplorerUtils.goToDatalake();
         PermissionUtils.changeOwnership(chartName, chartAdmin2.email);
 
-        assertChartIsNotVisible(chartAdmin1);
+        chartIsNotVisible(chartAdmin1);
 
-        assertChartIsVisibleAndEditableCanChangePermissions(
-            UserUtils.adminUser,
-        );
+        chartIsVisibleAndEditableCanChangePermissions(UserUtils.adminUser);
 
-        assertChartIsVisibleAndEditableCanChangePermissions(chartAdmin2);
+        chartIsVisibleAndEditableCanChangePermissions(chartAdmin2);
 
-        assertChartIsNotVisible(chartUser1);
+        chartIsNotVisible(chartUser1);
     });
 
     it('Chart is shared with group for user 2', () => {
@@ -116,13 +108,11 @@ describe('Test User Roles for Charts', () => {
 
         PermissionUtils.authorizeGroup(chartName, chartAdminGroup);
 
-        assertChartIsVisibleAndEditableCanChangePermissions(
-            UserUtils.adminUser,
-        );
+        chartIsVisibleAndEditableCanChangePermissions(UserUtils.adminUser);
 
-        assertChartIsNotVisible(chartUser1);
+        chartIsNotVisible(chartUser1);
 
-        assertChartIsVisibleAndEditableCannotChangePermissions(chartAdmin2);
+        chartIsVisibleAndEditableCannotChangePermissions(chartAdmin2);
     });
 
     function setup() {
@@ -137,38 +127,32 @@ describe('Test User Roles for Charts', () => {
         DataExplorerUtils.goToDatalake();
     }
 
-    function assertChartIsVisibleAndEditableCanChangePermissions(user: User) {
+    function chartIsVisibleAndEditableCanChangePermissions(user: User) {
         UserUtils.switchUser(user);
-        DataExplorerUtils.goToDatalake();
         DataExplorerUtils.checkAmountOfCharts(1);
         DataExplorerUtils.checkChartCanBeEdited(chartName);
 
         PermissionUtils.validateUserCanChangePermissions(chartName);
     }
 
-    function assertChartIsVisibleAndEditableCannotChangePermissions(
-        user: User,
-    ) {
+    function chartIsVisibleAndEditableCannotChangePermissions(user: User) {
         UserUtils.switchUser(user);
-        DataExplorerUtils.goToDatalake();
         DataExplorerUtils.checkAmountOfCharts(1);
         DataExplorerUtils.checkChartCanBeEdited(chartName);
 
         PermissionUtils.validateUserCanNotChangePermissions(chartName);
     }
 
-    function assertChartIsVisibleButNotEditable(user: User) {
+    function chartIsVisibleButNotEditable(user: User) {
         UserUtils.switchUser(user);
-        DataExplorerUtils.goToDatalake();
         DataExplorerUtils.checkAmountOfCharts(1);
         DataExplorerUtils.checkChartCanNotBeEdited(chartName);
 
         PermissionUtils.validateUserCanNotChangePermissions(chartName);
     }
 
-    function assertChartIsNotVisible(user: User) {
+    function chartIsNotVisible(user: User) {
         UserUtils.switchUser(user);
-        DataExplorerUtils.goToDatalake();
         DataExplorerUtils.checkAmountOfCharts(0);
     }
 });
diff --git a/ui/cypress/tests/userManagement/testUserRoleDashboard.spec.ts 
b/ui/cypress/tests/userManagement/testUserRoleDashboard.spec.ts
new file mode 100644
index 0000000000..59c794985a
--- /dev/null
+++ b/ui/cypress/tests/userManagement/testUserRoleDashboard.spec.ts
@@ -0,0 +1,204 @@
+/*
+ * 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 { UserRole } from '../../../src/app/_enums/user-role.enum';
+import { UserUtils } from '../../support/utils/UserUtils';
+import { ConnectUtils } from '../../support/utils/connect/ConnectUtils';
+import { User } from '../../support/model/User';
+import { DataExplorerUtils } from 
'../../support/utils/dataExplorer/DataExplorerUtils';
+import { PermissionUtils } from '../../support/utils/user/PermissionUtils';
+import { DataExplorerBtns } from 
'../../support/utils/dataExplorer/DataExplorerBtns';
+
+describe('Test User Roles for Dashboards', () => {
+    const dashboardName = 'test-dashboard';
+    let dashboardUser1: User;
+    let dashboardAdmin1: User;
+    let dashboardAdmin2: User;
+
+    beforeEach('Setup Test', () => {
+        cy.initStreamPipesTest();
+
+        dashboardUser1 = UserUtils.createUser(
+            'dashboardUser1',
+            UserRole.ROLE_DASHBOARD_USER,
+        );
+
+        dashboardAdmin1 = UserUtils.createUser(
+            'dashboardAdmin1',
+            UserRole.ROLE_DASHBOARD_ADMIN,
+            UserRole.ROLE_DATA_EXPLORER_ADMIN,
+            UserRole.ROLE_CONNECT_ADMIN,
+            UserRole.ROLE_PIPELINE_ADMIN,
+        );
+
+        dashboardAdmin2 = UserUtils.createUser(
+            'dashboardAdmin2',
+            UserRole.ROLE_DASHBOARD_ADMIN,
+        );
+    });
+
+    it('Dashboard is not shared with other users', () => {
+        UserUtils.switchUser(dashboardAdmin1);
+        DataExplorerUtils.createNewDashboard(dashboardName);
+
+        // check admin
+        dashboardIsVisibleAndEditableCanChangePermissions(UserUtils.adminUser);
+
+        // check other users
+        dashboardIsNotVisible(dashboardAdmin2);
+    });
+
+    it('Make dashboard public', () => {
+        UserUtils.switchUser(dashboardAdmin1);
+        DataExplorerUtils.createNewDashboard(dashboardName);
+        PermissionUtils.markElementAsPublic(dashboardName);
+
+        dashboardIsVisibleAndEditableCanChangePermissions(UserUtils.adminUser);
+
+        dashboardIsVisibleButNotEditable(dashboardUser1);
+
+        dashboardIsVisibleAndEditableCannotChangePermissions(dashboardAdmin2);
+    });
+
+    it('Share dashboard with other user and change ownership', () => {
+        UserUtils.switchUser(dashboardAdmin1);
+        DataExplorerUtils.createNewDashboard(dashboardName);
+
+        PermissionUtils.authorizeUser(dashboardName, dashboardAdmin2.email);
+
+        dashboardIsVisibleAndEditableCanChangePermissions(UserUtils.adminUser);
+
+        dashboardIsVisibleAndEditableCannotChangePermissions(dashboardAdmin2);
+
+        dashboardIsNotVisible(dashboardUser1);
+
+        UserUtils.switchUser(dashboardAdmin1);
+        DataExplorerUtils.goToDashboard();
+        PermissionUtils.changeOwnership(dashboardName, dashboardAdmin2.email);
+
+        dashboardIsNotVisible(dashboardAdmin1);
+
+        dashboardIsVisibleAndEditableCanChangePermissions(UserUtils.adminUser);
+
+        dashboardIsVisibleAndEditableCanChangePermissions(dashboardAdmin2);
+
+        dashboardIsNotVisible(dashboardUser1);
+    });
+
+    it('Dashboard is shared with group for user 2', () => {
+        const dashboardAdminGroup = 'dashboard_admin_group';
+        UserUtils.createGroup(
+            dashboardAdminGroup,
+            UserRole.ROLE_DASHBOARD_ADMIN,
+        );
+        UserUtils.addGroupToUser(dashboardAdminGroup, dashboardAdmin2.name);
+
+        UserUtils.switchUser(dashboardAdmin1);
+        DataExplorerUtils.createNewDashboard(dashboardName);
+
+        PermissionUtils.authorizeGroup(dashboardName, dashboardAdminGroup);
+
+        dashboardIsVisibleAndEditableCanChangePermissions(UserUtils.adminUser);
+
+        dashboardIsNotVisible(dashboardUser1);
+
+        dashboardIsVisibleAndEditableCannotChangePermissions(dashboardAdmin2);
+    });
+
+    it('Test Dashboard and Data Explorer Permissions', () => {
+        UserUtils.switchUser(dashboardAdmin1);
+
+        ConnectUtils.addMachineDataSimulator('simulator', true);
+        addChart('chart1');
+        cy.wait(1000);
+        addChart('chart2');
+
+        DataExplorerUtils.createNewDashboard(dashboardName);
+
+        DataExplorerUtils.editDashboard(dashboardName);
+        DataExplorerUtils.addDataViewToDashboard('chart1', true);
+        DataExplorerUtils.addDataViewToDashboard('chart2', true);
+        DataExplorerUtils.saveDashboard();
+
+        PermissionUtils.markElementAsPublic(dashboardName);
+
+        UserUtils.switchUser(dashboardAdmin2);
+        DataExplorerUtils.goToDashboard();
+        DataExplorerUtils.viewDashboard(dashboardName);
+        DataExplorerBtns.moreOptionsBtn('chart1').should('exist');
+        DataExplorerBtns.moreOptionsBtn('chart2').should('exist');
+        DataExplorerBtns.removeWidgetBtn('chart1').should('not.exist');
+        DataExplorerBtns.removeWidgetBtn('chart2').should('not.exist');
+
+        DataExplorerUtils.goToDashboard();
+        DataExplorerUtils.editDashboard(dashboardName);
+        DataExplorerBtns.moreOptionsBtn('chart1').should('exist');
+        DataExplorerBtns.moreOptionsBtn('chart2').should('exist');
+        DataExplorerBtns.removeWidgetBtn('chart1').should('exist');
+        DataExplorerBtns.removeWidgetBtn('chart2').should('exist');
+
+        // Validate to add new widget to dashboard
+        DataExplorerBtns.createChartBtn().should('not.exist');
+        DataExplorerBtns.removeWidgetBtn('chart2').click();
+        DataExplorerUtils.saveDashboard();
+
+        UserUtils.switchUser(dashboardUser1);
+        DataExplorerUtils.goToDashboard();
+        DataExplorerUtils.viewDashboard(dashboardName);
+        DataExplorerBtns.moreOptionsBtn('chart1').should('exist');
+        DataExplorerBtns.moreOptionsBtn('chart2').should('not.exist');
+    });
+
+    function dashboardIsVisibleAndEditableCanChangePermissions(user: User) {
+        UserUtils.switchUser(user);
+        DataExplorerUtils.checkAmountOfDashboards(1);
+        DataExplorerUtils.checkDashboardCanBeEdited(dashboardName);
+
+        PermissionUtils.validateUserCanChangePermissions(dashboardName);
+    }
+
+    function dashboardIsVisibleAndEditableCannotChangePermissions(user: User) {
+        UserUtils.switchUser(user);
+        DataExplorerUtils.checkAmountOfDashboards(1);
+        DataExplorerUtils.checkDashboardCanBeEdited(dashboardName);
+
+        PermissionUtils.validateUserCanNotChangePermissions(dashboardName);
+    }
+
+    function dashboardIsVisibleButNotEditable(user: User) {
+        UserUtils.switchUser(user);
+        DataExplorerUtils.checkAmountOfDashboards(1);
+        DataExplorerUtils.checkDashboardCanNotBeEdited(dashboardName);
+
+        PermissionUtils.validateUserCanNotChangePermissions(dashboardName);
+    }
+
+    function dashboardIsNotVisible(user: User) {
+        UserUtils.switchUser(user);
+        DataExplorerUtils.checkAmountOfDashboards(0);
+    }
+
+    function addChart(chartName: string) {
+        DataExplorerUtils.addDataViewAndTableWidget(
+            chartName,
+            'simulator',
+            true,
+        );
+        DataExplorerUtils.saveDataViewConfiguration();
+    }
+});
diff --git 
a/ui/src/app/dashboard/components/overview/dashboard-overview-table/dashboard-overview-table.component.html
 
b/ui/src/app/dashboard/components/overview/dashboard-overview-table/dashboard-overview-table.component.html
index 4725718082..f7f114bf28 100644
--- 
a/ui/src/app/dashboard/components/overview/dashboard-overview-table/dashboard-overview-table.component.html
+++ 
b/ui/src/app/dashboard/components/overview/dashboard-overview-table/dashboard-overview-table.component.html
@@ -91,6 +91,7 @@
                 <button
                     mat-menu-item
                     (click)="showDashboard(element); $event.stopPropagation()"
+                    [attr.data-cy]="'view-dashboard-' + element.name"
                 >
                     <mat-icon>visibility</mat-icon>
                     <span>{{ 'Show' | translate }}</span>
@@ -135,9 +136,7 @@
                 }
                 <button
                     mat-menu-item
-                    [attr.data-cy]="
-                        'manage-dashboard-permissions-' + element.name
-                    "
+                    data-cy="open-manage-permissions"
                     (click)="showPermissionsDialog(element)"
                 >
                     <mat-icon>share</mat-icon>
diff --git 
a/ui/src/app/dashboard/components/panel/chart-selection-panel/chart-selection/chart-selection.component.html
 
b/ui/src/app/dashboard/components/panel/chart-selection-panel/chart-selection/chart-selection.component.html
index 0346b5bbb7..0737ebe5b0 100644
--- 
a/ui/src/app/dashboard/components/panel/chart-selection-panel/chart-selection/chart-selection.component.html
+++ 
b/ui/src/app/dashboard/components/panel/chart-selection-panel/chart-selection/chart-selection.component.html
@@ -33,14 +33,18 @@
                 'No charts found - create a new chart first to add it to this 
dashboard.'
                     | translate
             }}</span>
-            <button
-                mat-button
-                color="accent"
-                class="mt-10"
-                (click)="navigateToDataViewCreation()"
-            >
-                {{ 'Create chart' | translate }}
-            </button>
+
+            @if (hasChartWritePrivileges) {
+                <button
+                    mat-button
+                    color="accent"
+                    class="mt-10"
+                    data-cy="create-chart-button"
+                    (click)="navigateToDataViewCreation()"
+                >
+                    {{ 'Create chart' | translate }}
+                </button>
+            }
         </div>
     }
 </div>
diff --git 
a/ui/src/app/dashboard/components/panel/chart-selection-panel/chart-selection/chart-selection.component.ts
 
b/ui/src/app/dashboard/components/panel/chart-selection-panel/chart-selection/chart-selection.component.ts
index 220849af4d..76edaa1c7e 100644
--- 
a/ui/src/app/dashboard/components/panel/chart-selection-panel/chart-selection/chart-selection.component.ts
+++ 
b/ui/src/app/dashboard/components/panel/chart-selection-panel/chart-selection/chart-selection.component.ts
@@ -16,12 +16,14 @@
  *
  */
 
-import { Component, EventEmitter, OnInit, Output } from '@angular/core';
+import { Component, EventEmitter, inject, OnInit, Output } from 
'@angular/core';
 import {
     ChartService,
     DataExplorerWidgetModel,
 } from '@streampipes/platform-services';
 import { Router } from '@angular/router';
+import { AuthService } from '../../../../../services/auth.service';
+import { UserPrivilege } from '../../../../../_enums/user-privilege.enum';
 
 @Component({
     selector: 'sp-chart-selection',
@@ -30,11 +32,15 @@ import { Router } from '@angular/router';
     standalone: false,
 })
 export class ChartSelectionComponent implements OnInit {
+    private authService = inject(AuthService);
+
     @Output()
     addChartEmitter: EventEmitter<string> = new EventEmitter();
 
     charts: DataExplorerWidgetModel[] = [];
 
+    hasChartWritePrivileges: boolean = false;
+
     constructor(
         private dataViewService: ChartService,
         private router: Router,
@@ -48,6 +54,10 @@ export class ChartSelectionComponent implements OnInit {
                 ),
             );
         });
+
+        this.hasChartWritePrivileges = this.authService.hasRole(
+            UserPrivilege.PRIVILEGE_WRITE_DATA_EXPLORER_VIEW,
+        );
     }
 
     navigateToDataViewCreation(): void {
diff --git 
a/ui/src/app/dashboard/components/panel/dashboard-panel.component.html 
b/ui/src/app/dashboard/components/panel/dashboard-panel.component.html
index a2aca9c431..e7daf7b908 100644
--- a/ui/src/app/dashboard/components/panel/dashboard-panel.component.html
+++ b/ui/src/app/dashboard/components/panel/dashboard-panel.component.html
@@ -31,9 +31,7 @@
                     [dashboard]="dashboard"
                     [editMode]="editMode"
                     [(viewMode)]="viewMode"
-                    [hasDataExplorerWritePrivileges]="
-                        hasDataExplorerWritePrivileges
-                    "
+                    [hasDashboardWritePrivileges]="hasDashboardWritePrivileges"
                     [timeRangeVisible]="timeRangeVisible"
                     [timeSettings]="timeSettings"
                     (saveDashboardEmitter)="persistDashboardChanges()"
diff --git a/ui/src/app/dashboard/components/panel/dashboard-panel.component.ts 
b/ui/src/app/dashboard/components/panel/dashboard-panel.component.ts
index 0946164609..93ca7bb5fc 100644
--- a/ui/src/app/dashboard/components/panel/dashboard-panel.component.ts
+++ b/ui/src/app/dashboard/components/panel/dashboard-panel.component.ts
@@ -80,7 +80,7 @@ export class DashboardPanelComponent
     _dashboardGrid: DashboardGridViewComponent;
     _dashboardSlide: DashboardSlideViewComponent;
 
-    hasDataExplorerWritePrivileges = false;
+    hasDashboardWritePrivileges = false;
 
     public items: Dashboard[];
 
@@ -114,10 +114,10 @@ export class DashboardPanelComponent
         this.getDashboard(params.id, startTime, endTime);
 
         this.authSubscription = this.currentUserService.user$.subscribe(_ => {
-            this.hasDataExplorerWritePrivileges = this.authService.hasRole(
-                UserPrivilege.PRIVILEGE_WRITE_DATA_EXPLORER_VIEW,
+            this.hasDashboardWritePrivileges = this.authService.hasRole(
+                UserPrivilege.PRIVILEGE_WRITE_DASHBOARD,
             );
-            if (queryParams.editMode && this.hasDataExplorerWritePrivileges) {
+            if (queryParams.editMode && this.hasDashboardWritePrivileges) {
                 this.editMode = true;
             }
         });
diff --git 
a/ui/src/app/dashboard/components/panel/dashboard-toolbar/dashboard-toolbar.component.html
 
b/ui/src/app/dashboard/components/panel/dashboard-toolbar/dashboard-toolbar.component.html
index 22f0e0fcfb..0d85549398 100644
--- 
a/ui/src/app/dashboard/components/panel/dashboard-toolbar/dashboard-toolbar.component.html
+++ 
b/ui/src/app/dashboard/components/panel/dashboard-toolbar/dashboard-toolbar.component.html
@@ -100,7 +100,7 @@
             <mat-icon>more_vert</mat-icon>
         </button>
         <mat-menu #optMenu="matMenu">
-            @if (!editMode && hasDataExplorerWritePrivileges) {
+            @if (!editMode && hasDashboardWritePrivileges) {
                 <button
                     mat-menu-item
                     (click)="triggerEditModeEmitter.emit()"
@@ -122,7 +122,7 @@
                     <span>{{ 'Hide time range selector' | translate }}</span>
                 </button>
             }
-            @if (hasDataExplorerWritePrivileges) {
+            @if (hasDashboardWritePrivileges) {
                 <button mat-menu-item (click)="deleteDashboardEmitter.emit()">
                     <mat-icon>clear</mat-icon>
                     <span>{{ 'Delete dashboard' | translate }}</span>
diff --git 
a/ui/src/app/dashboard/components/panel/dashboard-toolbar/dashboard-toolbar.component.ts
 
b/ui/src/app/dashboard/components/panel/dashboard-toolbar/dashboard-toolbar.component.ts
index 21664ed1aa..9f979d9100 100644
--- 
a/ui/src/app/dashboard/components/panel/dashboard-toolbar/dashboard-toolbar.component.ts
+++ 
b/ui/src/app/dashboard/components/panel/dashboard-toolbar/dashboard-toolbar.component.ts
@@ -43,7 +43,7 @@ export class DashboardToolbarComponent {
     timeRangeVisible: boolean;
 
     @Input()
-    hasDataExplorerWritePrivileges: boolean;
+    hasDashboardWritePrivileges: boolean;
 
     @Input()
     timeSettings: TimeSettings;
diff --git 
a/ui/src/app/data-explorer-shared/components/chart-container/data-explorer-chart-container.component.html
 
b/ui/src/app/data-explorer-shared/components/chart-container/data-explorer-chart-container.component.html
index 6928d45086..f0fb79ee9e 100644
--- 
a/ui/src/app/data-explorer-shared/components/chart-container/data-explorer-chart-container.component.html
+++ 
b/ui/src/app/data-explorer-shared/components/chart-container/data-explorer-chart-container.component.html
@@ -146,21 +146,23 @@
                                 </sp-time-selector-menu>
                             }
                         </mat-menu>
-                        @if (!dataViewMode) {
-                            @if (editMode && hasDataExplorerWritePrivileges) {
-                                <button
-                                    mat-icon-button
-                                    (click)="removeWidget()"
-                                    [matTooltip]="'Delete Chart' | translate"
-                                    [attr.data-cy]="
-                                        'remove-' +
-                                        configuredWidget.baseAppearanceConfig
-                                            .widgetTitle
-                                    "
-                                >
-                                    <mat-icon>clear</mat-icon>
-                                </button>
-                            }
+                        @if (
+                            !dataViewMode &&
+                            editMode &&
+                            hasDashboardWritePrivileges
+                        ) {
+                            <button
+                                mat-icon-button
+                                (click)="removeWidget()"
+                                [matTooltip]="'Delete Chart' | translate"
+                                [attr.data-cy]="
+                                    'remove-' +
+                                    configuredWidget.baseAppearanceConfig
+                                        .widgetTitle
+                                "
+                            >
+                                <mat-icon>clear</mat-icon>
+                            </button>
                         }
                     </div>
                 }
diff --git 
a/ui/src/app/data-explorer-shared/components/chart-container/data-explorer-chart-container.component.ts
 
b/ui/src/app/data-explorer-shared/components/chart-container/data-explorer-chart-container.component.ts
index de5144a8c3..4fb1bb090e 100644
--- 
a/ui/src/app/data-explorer-shared/components/chart-container/data-explorer-chart-container.component.ts
+++ 
b/ui/src/app/data-explorer-shared/components/chart-container/data-explorer-chart-container.component.ts
@@ -138,6 +138,7 @@ export class DataExplorerChartContainerComponent
     };
 
     hasDataExplorerWritePrivileges = false;
+    hasDashboardWritePrivileges = false;
 
     authSubscription: Subscription;
     widgetTypeChangedSubscription: Subscription;
@@ -175,6 +176,9 @@ export class DataExplorerChartContainerComponent
                 this.hasDataExplorerWritePrivileges = this.authService.hasRole(
                     UserPrivilege.PRIVILEGE_WRITE_DATA_EXPLORER_VIEW,
                 );
+                this.hasDashboardWritePrivileges = this.authService.hasRole(
+                    UserPrivilege.PRIVILEGE_WRITE_DASHBOARD,
+                );
             },
         );
         this.widgetLoaded = true;
diff --git 
a/ui/src/app/data-explorer/components/chart-view/data-explorer-chart-view.component.ts
 
b/ui/src/app/data-explorer/components/chart-view/data-explorer-chart-view.component.ts
index 4935fab6c6..0489ead3bc 100644
--- 
a/ui/src/app/data-explorer/components/chart-view/data-explorer-chart-view.component.ts
+++ 
b/ui/src/app/data-explorer/components/chart-view/data-explorer-chart-view.component.ts
@@ -20,6 +20,7 @@ import {
     Component,
     ElementRef,
     inject,
+    OnDestroy,
     OnInit,
     ViewChild,
 } from '@angular/core';
@@ -40,6 +41,7 @@ import {
 import {
     AssetSaveService,
     ConfirmDialogComponent,
+    CurrentUserService,
     DialogService,
     PanelType,
     TimeSelectionService,
@@ -48,12 +50,14 @@ import { DataExplorerRoutingService } from 
'../../../data-explorer-shared/servic
 import { DataExplorerSharedService } from 
'../../../data-explorer-shared/services/data-explorer-shared.service';
 import { DataExplorerDetectChangesService } from 
'../../services/data-explorer-detect-changes.service';
 import { SupportsUnsavedChangeDialog } from 
'../../../data-explorer-shared/models/dataview-dashboard.model';
-import { Observable, of } from 'rxjs';
+import { Observable, of, Subscription } from 'rxjs';
 import { MatDialog } from '@angular/material/dialog';
 import { catchError, map } from 'rxjs/operators';
 import { TranslateService } from '@ngx-translate/core';
 import { ResizeEchartsService } from 
'../../../data-explorer-shared/services/resize-echarts.service';
 import { AssetDialogComponent } from '../../dialog/asset-dialog.component';
+import { AuthService } from '../../../services/auth.service';
+import { UserRole } from '../../../_enums/user-role.enum';
 
 @Component({
     selector: 'sp-data-explorer-data-view',
@@ -62,7 +66,7 @@ import { AssetDialogComponent } from 
'../../dialog/asset-dialog.component';
     standalone: false,
 })
 export class DataExplorerChartViewComponent
-    implements OnInit, SupportsUnsavedChangeDialog
+    implements OnInit, OnDestroy, SupportsUnsavedChangeDialog
 {
     dataViewLoaded = false;
     timeSettings: TimeSettings;
@@ -91,9 +95,13 @@ export class DataExplorerChartViewComponent
     private timeSelectionService = inject(TimeSelectionService);
     private translateService = inject(TranslateService);
     private dialogService = inject(DialogService);
+    private currentUserService = inject(CurrentUserService);
+    private authService = inject(AuthService);
 
     private assetSaveService = inject(AssetSaveService);
 
+    currentUser$: Subscription;
+
     chartNotFound = false;
 
     observableGenerator =
@@ -103,7 +111,14 @@ export class DataExplorerChartViewComponent
 
     ngOnInit() {
         const dataViewId = this.route.snapshot.params.id;
-        this.editMode = this.route.snapshot.queryParams.editMode;
+
+        this.currentUser$ = this.currentUserService.user$.subscribe(user => {
+            if (!this.authService.hasRole(UserRole.ROLE_DATA_EXPLORER_ADMIN)) {
+                this.editMode = false;
+            } else {
+                this.editMode = this.route.snapshot.queryParams.editMode;
+            }
+        });
 
         if (dataViewId) {
             this.loadDataView(dataViewId);
@@ -342,7 +357,6 @@ export class DataExplorerChartViewComponent
             this.deselectedAssets,
             this.originalAssets,
         );
-        //this.dialogRef.close(true);
     }
 
     saveToAssets(data: DataExplorerWidgetModel): void {
@@ -364,4 +378,8 @@ export class DataExplorerChartViewComponent
             },
         ];
     }
+
+    ngOnDestroy() {
+        this.currentUser$?.unsubscribe();
+    }
 }
diff --git 
a/ui/src/app/data-explorer/components/overview/data-explorer-overview-table/data-explorer-overview-table.component.ts
 
b/ui/src/app/data-explorer/components/overview/data-explorer-overview-table/data-explorer-overview-table.component.ts
index e28ddf8c81..0edf6c1cbf 100644
--- 
a/ui/src/app/data-explorer/components/overview/data-explorer-overview-table/data-explorer-overview-table.component.ts
+++ 
b/ui/src/app/data-explorer/components/overview/data-explorer-overview-table/data-explorer-overview-table.component.ts
@@ -87,7 +87,10 @@ export class SpDataExplorerDataViewOverviewComponent 
implements OnInit {
     }
 
     openDataView(dataView: DataExplorerWidgetModel, editMode: boolean): void {
-        this.routingService.navigateToDataView(editMode, dataView.elementId);
+        this.routingService.navigateToDataView(
+            editMode && this.hasDataExplorerWritePrivileges,
+            dataView.elementId,
+        );
     }
 
     showPermissionsDialog(chart: DataExplorerWidgetModel) {
diff --git 
a/ui/src/app/pipeline-details/components/pipeline-details-toolbar/pipeline-details-toolbar.component.html
 
b/ui/src/app/pipeline-details/components/pipeline-details-toolbar/pipeline-details-toolbar.component.html
index 1df6b32768..7acfa2659c 100644
--- 
a/ui/src/app/pipeline-details/components/pipeline-details-toolbar/pipeline-details-toolbar.component.html
+++ 
b/ui/src/app/pipeline-details/components/pipeline-details-toolbar/pipeline-details-toolbar.component.html
@@ -34,18 +34,20 @@
             >
         </div>
     </button>
-    <button
-        mat-button
-        color="accent"
-        [matTooltip]="'Pipeline as code' | translate"
-        [matTooltipPosition]="'above'"
-        (click)="openCodeDialogEmitter.emit()"
-    >
-        <div fxLayoutAlign="start center" fxLayout="row">
-            <i class="material-icons">code</i>
-            <span>&nbsp;{{ 'View pipeline as code' | translate }}</span>
-        </div>
-    </button>
+    @if (hasPipelineWritePrivileges) {
+        <button
+            mat-button
+            color="accent"
+            [matTooltip]="'Pipeline as code' | translate"
+            [matTooltipPosition]="'above'"
+            (click)="openCodeDialogEmitter.emit()"
+        >
+            <div fxLayoutAlign="start center" fxLayout="row">
+                <i class="material-icons">code</i>
+                <span>&nbsp;{{ 'View pipeline as code' | translate }}</span>
+            </div>
+        </button>
+    }
     <div fxFlex></div>
     <div fxLayoutAlign="end center">
         <button
diff --git 
a/ui/src/app/pipeline-details/components/pipeline-details-toolbar/pipeline-details-toolbar.component.ts
 
b/ui/src/app/pipeline-details/components/pipeline-details-toolbar/pipeline-details-toolbar.component.ts
index ede7f4eab4..c08ba9108a 100644
--- 
a/ui/src/app/pipeline-details/components/pipeline-details-toolbar/pipeline-details-toolbar.component.ts
+++ 
b/ui/src/app/pipeline-details/components/pipeline-details-toolbar/pipeline-details-toolbar.component.ts
@@ -27,6 +27,9 @@ export class PipelineDetailsToolbarComponent {
     @Input()
     autoRefresh: boolean;
 
+    @Input()
+    hasPipelineWritePrivileges: boolean;
+
     @Input()
     previewModeActive: boolean;
 
diff --git a/ui/src/app/pipeline-details/pipeline-details.component.html 
b/ui/src/app/pipeline-details/pipeline-details.component.html
index e024d3d147..277b64b496 100644
--- a/ui/src/app/pipeline-details/pipeline-details.component.html
+++ b/ui/src/app/pipeline-details/pipeline-details.component.html
@@ -24,6 +24,7 @@
     <div nav fxFlex="100" fxLayoutAlign="start center" class="mr-5">
         <sp-pipeline-details-toolbar
             fxFlex="100"
+            [hasPipelineWritePrivileges]="hasPipelineWritePrivileges"
             [autoRefresh]="autoRefresh"
             [previewModeActive]="previewModeActive"
             (togglePreviewEmitter)="toggleLivePreview()"
diff --git a/ui/src/app/pipeline-details/pipeline-details.component.ts 
b/ui/src/app/pipeline-details/pipeline-details.component.ts
index 1cf208269c..b6edcbaaa8 100644
--- a/ui/src/app/pipeline-details/pipeline-details.component.ts
+++ b/ui/src/app/pipeline-details/pipeline-details.component.ts
@@ -66,8 +66,8 @@ export class SpPipelineDetailsComponent implements OnInit, 
OnDestroy {
     previewModeActive = false;
     pipelineNotFound = false;
 
-    currentUserSub: Subscription;
-    autoRefreshSub: Subscription;
+    currentUser$: Subscription;
+    autoRefresh$: Subscription;
 
     @ViewChild('pipelinePreviewComponent')
     pipelinePreviewComponent: PipelinePreviewComponent;
@@ -84,7 +84,7 @@ export class SpPipelineDetailsComponent implements OnInit, 
OnDestroy {
     ) {}
 
     ngOnInit(): void {
-        this.currentUserSub = this.currentUserService.user$.subscribe(user => {
+        this.currentUser$ = this.currentUserService.user$.subscribe(user => {
             this.hasPipelineWritePrivileges = this.authService.hasRole(
                 UserPrivilege.PRIVILEGE_WRITE_PIPELINE,
             );
@@ -147,7 +147,7 @@ export class SpPipelineDetailsComponent implements OnInit, 
OnDestroy {
     }
 
     setupAutoRefresh(): void {
-        this.autoRefreshSub = interval(5000)
+        this.autoRefresh$ = interval(5000)
             .pipe(
                 filter(() => this.autoRefresh),
                 switchMap(() => this.getMonitoringObservables(true)),
@@ -212,7 +212,7 @@ export class SpPipelineDetailsComponent implements OnInit, 
OnDestroy {
     }
 
     ngOnDestroy() {
-        this.currentUserSub?.unsubscribe();
-        this.autoRefreshSub?.unsubscribe();
+        this.currentUser$?.unsubscribe();
+        this.autoRefresh$?.unsubscribe();
     }
 }


Reply via email to