This is an automated email from the ASF dual-hosted git repository. riemer pushed a commit to branch add-public-link-creation-to-dashboard in repository https://gitbox.apache.org/repos/asf/streampipes.git
commit 8fd7050d00229cd9e6eeaf6e821b71fbd53d53d4 Author: Dominik Riemer <[email protected]> AuthorDate: Thu Aug 21 18:55:03 2025 +0200 feat(#3725): Add feature to mark dashboards as public --- .../streampipes/model/client/user/Permission.java | 9 + .../management/PermissionResourceManager.java | 8 + .../rest/security/SpPermissionEvaluator.java | 122 +++++--- .../service/core/UnauthenticatedInterfaces.java | 3 +- .../management/util/GrantedPermissionsBuilder.java | 2 +- .../src/lib/model/gen/streampipes-model-client.ts | 4 +- .../existing-adapters.component.html | 2 +- .../object-permission-dialog.component.html | 325 ++++++++++++--------- .../object-permission-dialog.component.scss | 9 + .../object-permission-dialog.component.ts | 40 ++- .../dashboard-overview-table.component.html | 2 +- .../dashboard-overview-table.component.ts | 6 + .../services/data-explorer-shared.service.ts | 9 +- .../data-explorer-overview-table.component.html | 2 +- ui/src/app/pipelines/pipelines.component.html | 4 +- 15 files changed, 353 insertions(+), 194 deletions(-) diff --git a/streampipes-model-client/src/main/java/org/apache/streampipes/model/client/user/Permission.java b/streampipes-model-client/src/main/java/org/apache/streampipes/model/client/user/Permission.java index 4e20ca0daa..6bbec97c34 100644 --- a/streampipes-model-client/src/main/java/org/apache/streampipes/model/client/user/Permission.java +++ b/streampipes-model-client/src/main/java/org/apache/streampipes/model/client/user/Permission.java @@ -40,6 +40,7 @@ public class Permission implements Storable { private String objectInstanceId; private String objectClassName; private boolean publicElement; + private boolean readAnonymous; private String ownerSid; @@ -126,4 +127,12 @@ public class Permission implements Storable { public void setPublicElement(boolean publicElement) { this.publicElement = publicElement; } + + public boolean isReadAnonymous() { + return readAnonymous; + } + + public void setReadAnonymous(boolean readAnonymous) { + this.readAnonymous = readAnonymous; + } } diff --git a/streampipes-resource-management/src/main/java/org/apache/streampipes/resource/management/PermissionResourceManager.java b/streampipes-resource-management/src/main/java/org/apache/streampipes/resource/management/PermissionResourceManager.java index 0d48e15949..53b5a36f5c 100644 --- a/streampipes-resource-management/src/main/java/org/apache/streampipes/resource/management/PermissionResourceManager.java +++ b/streampipes-resource-management/src/main/java/org/apache/streampipes/resource/management/PermissionResourceManager.java @@ -19,6 +19,7 @@ package org.apache.streampipes.resource.management; import org.apache.streampipes.model.client.user.Permission; import org.apache.streampipes.model.client.user.PermissionBuilder; +import org.apache.streampipes.model.dashboard.DashboardModel; import org.apache.streampipes.storage.api.IPermissionStorage; import org.apache.streampipes.storage.management.StorageDispatcher; @@ -26,6 +27,10 @@ import java.util.List; public class PermissionResourceManager extends AbstractResourceManager<IPermissionStorage> { + private final List<String> readAnonymousAllowedClasses = List.of( + DashboardModel.class.getCanonicalName() + ); + public PermissionResourceManager() { super(StorageDispatcher.INSTANCE.getNoSqlStore().getPermissionStorage()); } @@ -59,6 +64,9 @@ public class PermissionResourceManager extends AbstractResourceManager<IPermissi } public void update(Permission permission) { + if (!readAnonymousAllowedClasses.contains(permission.getObjectClassName())) { + permission.setReadAnonymous(false); + } db.updateElement(permission); } diff --git a/streampipes-rest/src/main/java/org/apache/streampipes/rest/security/SpPermissionEvaluator.java b/streampipes-rest/src/main/java/org/apache/streampipes/rest/security/SpPermissionEvaluator.java index 86c0a74d80..cfe8bcb6a2 100644 --- a/streampipes-rest/src/main/java/org/apache/streampipes/rest/security/SpPermissionEvaluator.java +++ b/streampipes-rest/src/main/java/org/apache/streampipes/rest/security/SpPermissionEvaluator.java @@ -21,20 +21,29 @@ import org.apache.streampipes.model.client.user.DefaultRole; import org.apache.streampipes.model.client.user.Permission; import org.apache.streampipes.model.pipeline.PipelineElementRecommendation; import org.apache.streampipes.model.pipeline.PipelineElementRecommendationMessage; +import org.apache.streampipes.storage.api.IPermissionStorage; import org.apache.streampipes.storage.management.StorageDispatcher; import org.apache.streampipes.user.management.model.PrincipalUserDetails; import org.springframework.context.annotation.Configuration; import org.springframework.security.access.PermissionEvaluator; +import org.springframework.security.authentication.AnonymousAuthenticationToken; import org.springframework.security.core.Authentication; import java.io.Serializable; import java.util.List; +import java.util.Objects; import java.util.function.Predicate; @Configuration public class SpPermissionEvaluator implements PermissionEvaluator { + private final IPermissionStorage permissionStorage; + + public SpPermissionEvaluator() { + this.permissionStorage = StorageDispatcher.INSTANCE.getNoSqlStore().getPermissionStorage(); + } + /** * Evaluates whether the user has the necessary permissions for a given resource. * @@ -50,19 +59,22 @@ public class SpPermissionEvaluator implements PermissionEvaluator { Object targetDomainObject, Object permission ) { - PrincipalUserDetails<?> userDetails = getUserDetails(authentication); - if (targetDomainObject instanceof PipelineElementRecommendationMessage) { - return isAdmin(userDetails) || filterRecommendation( - authentication, - (PipelineElementRecommendationMessage) targetDomainObject - ); - } else { - String objectInstanceId = (String) targetDomainObject; - if (isAdmin(userDetails)) { - return true; - } - return hasPermission(authentication, objectInstanceId); + if (targetDomainObject instanceof PipelineElementRecommendationMessage msg) { + return handleRecommendationMessage(authentication, msg); + } + + String objectId = String.valueOf(targetDomainObject); + List<Permission> perms = getObjectPermission(objectId); + + if (isAnonymousAccess(perms)) { + return true; } + + if (isAdmin(authentication)) { + return true; + } + + return hasPermissionForId(authentication, perms, objectId); } /** @@ -81,45 +93,75 @@ public class SpPermissionEvaluator implements PermissionEvaluator { String targetType, Object permission ) { - PrincipalUserDetails<?> userDetails = getUserDetails(authentication); - if (isAdmin(userDetails)) { - return true; - } - return hasPermission(authentication, targetId.toString()); + // We do not use targetType in this implementation + return hasPermission(authentication, targetId, permission); } - private boolean filterRecommendation(Authentication auth, PipelineElementRecommendationMessage message) { - Predicate<PipelineElementRecommendation> isForbidden = r -> !hasPermission(auth, r.getElementId()); - message.getPossibleElements() - .removeIf(isForbidden); + private boolean handleRecommendationMessage(Authentication auth, + PipelineElementRecommendationMessage message) { + Predicate<PipelineElementRecommendation> isForbidden = rec -> { + String elementId = rec.getElementId(); + List<Permission> perms = getObjectPermission(elementId); + if (isAnonymousAccess(perms)) { + return false; + } + if (isAdmin(auth)) { + return false; + } + return !hasPermissionForId(auth, perms, elementId); // remove if not allowed + }; + + message.getPossibleElements().removeIf(isForbidden); return true; } - private boolean hasPermission(Authentication auth, String objectInstanceId) { - return isPublicElement(objectInstanceId) - || getUserDetails(auth).getAllObjectPermissions() - .contains(objectInstanceId); + private boolean hasPermissionForId(Authentication auth, + List<Permission> permissions, + String objectInstanceId) { + if (isPublicOrAnonymousElement(permissions)) { + return true; + } + + PrincipalUserDetails<?> user = getUserDetailsOrNull(auth); + if (user == null) { + return false; + } + + return user.getAllObjectPermissions().contains(objectInstanceId); + } + + private PrincipalUserDetails<?> getUserDetailsOrNull(Authentication authentication) { + if (authentication == null + || authentication instanceof AnonymousAuthenticationToken) { + return null; + } + Object principal = authentication.getPrincipal(); + return (principal instanceof PrincipalUserDetails) ? (PrincipalUserDetails<?>) principal : null; + } + + private boolean isAdmin(Authentication authentication) { + PrincipalUserDetails<?> userDetails = getUserDetailsOrNull(authentication); + if (userDetails == null) { + return false; + } + + return userDetails.getAuthorities().stream() + .anyMatch(a -> + Objects.equals(a.getAuthority(), DefaultRole.Constants.ROLE_ADMIN_VALUE) + ); } - private PrincipalUserDetails<?> getUserDetails(Authentication authentication) { - return (PrincipalUserDetails<?>) authentication.getPrincipal(); + private boolean isPublicOrAnonymousElement(List<Permission> permissions) { + return !permissions.isEmpty() + && (permissions.get(0).isPublicElement() || permissions.get(0).isReadAnonymous()); } - private boolean isPublicElement(String objectInstanceId) { - List<Permission> permissions = - StorageDispatcher.INSTANCE.getNoSqlStore() - .getPermissionStorage() - .getUserPermissionsForObject(objectInstanceId); - return permissions.size() > 0 && permissions.get(0) - .isPublicElement(); + private boolean isAnonymousAccess(List<Permission> permissions) { + return !permissions.isEmpty() && permissions.get(0).isReadAnonymous(); } - private boolean isAdmin(PrincipalUserDetails<?> userDetails) { - return userDetails - .getAuthorities() - .stream() - .anyMatch(a -> a.getAuthority() - .equals(DefaultRole.Constants.ROLE_ADMIN_VALUE)); + private List<Permission> getObjectPermission(String objectInstanceId) { + return permissionStorage.getUserPermissionsForObject(objectInstanceId); } } diff --git a/streampipes-service-core/src/main/java/org/apache/streampipes/service/core/UnauthenticatedInterfaces.java b/streampipes-service-core/src/main/java/org/apache/streampipes/service/core/UnauthenticatedInterfaces.java index ebadac6489..359de1170d 100644 --- a/streampipes-service-core/src/main/java/org/apache/streampipes/service/core/UnauthenticatedInterfaces.java +++ b/streampipes-service-core/src/main/java/org/apache/streampipes/service/core/UnauthenticatedInterfaces.java @@ -42,7 +42,8 @@ public class UnauthenticatedInterfaces { "/error", "/", "/streampipes-backend/", - "/streampipes-backend/index.html" + "/streampipes-backend/index.html", + "/api/v3/datalake/dashboard/*/composite" ); } } diff --git a/streampipes-user-management/src/main/java/org/apache/streampipes/user/management/util/GrantedPermissionsBuilder.java b/streampipes-user-management/src/main/java/org/apache/streampipes/user/management/util/GrantedPermissionsBuilder.java index 83fbc85d28..1674849767 100644 --- a/streampipes-user-management/src/main/java/org/apache/streampipes/user/management/util/GrantedPermissionsBuilder.java +++ b/streampipes-user-management/src/main/java/org/apache/streampipes/user/management/util/GrantedPermissionsBuilder.java @@ -25,7 +25,7 @@ import java.util.Set; public class GrantedPermissionsBuilder { - private Principal principal; + private final Principal principal; public GrantedPermissionsBuilder(Principal principal) { this.principal = principal; diff --git a/ui/projects/streampipes/platform-services/src/lib/model/gen/streampipes-model-client.ts b/ui/projects/streampipes/platform-services/src/lib/model/gen/streampipes-model-client.ts index 09c378e914..bb58115952 100644 --- a/ui/projects/streampipes/platform-services/src/lib/model/gen/streampipes-model-client.ts +++ b/ui/projects/streampipes/platform-services/src/lib/model/gen/streampipes-model-client.ts @@ -20,7 +20,7 @@ /* tslint:disable */ /* eslint-disable */ // @ts-nocheck -// Generated using typescript-generator version 3.2.1263 on 2025-08-19 20:00:50. +// Generated using typescript-generator version 3.2.1263 on 2025-08-21 14:22:05. import { Storable } from './streampipes-model'; @@ -83,6 +83,7 @@ export class Permission implements Storable { ownerSid: string; permissionId: string; publicElement: boolean; + readAnonymous: boolean; rev: string; static fromData(data: Permission, target?: Permission): Permission { @@ -99,6 +100,7 @@ export class Permission implements Storable { instance.ownerSid = data.ownerSid; instance.permissionId = data.permissionId; instance.publicElement = data.publicElement; + instance.readAnonymous = data.readAnonymous; instance.rev = data.rev; return instance; } 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 25d93d4d95..057e54fd02 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 @@ -99,7 +99,7 @@ ></sp-basic-header-title-component> <div fxFlex="100" fxLayout="row" fxLayoutAlign="center start"> <sp-table - fxFlex="90" + fxFlex="100" [columns]="displayedColumns" [dataSource]="dataSource" data-cy="all-adapters-table" diff --git a/ui/src/app/core-ui/object-permission-dialog/object-permission-dialog.component.html b/ui/src/app/core-ui/object-permission-dialog/object-permission-dialog.component.html index 460e0d9162..08095d79fc 100644 --- a/ui/src/app/core-ui/object-permission-dialog/object-permission-dialog.component.html +++ b/ui/src/app/core-ui/object-permission-dialog/object-permission-dialog.component.html @@ -17,140 +17,201 @@ --> <div class="sp-dialog-container"> - <div class="sp-dialog-content" *ngIf="usersLoaded"> - <div fxFlex="100" fxLayout="column" class="p-15"> - <h4>{{ headerTitle }}</h4> - <form [formGroup]="parentForm" fxFlex="100" fxLayout="column"> - <div class="general-options-panel" fxLayout="column"> - <span class="general-options-header">{{ - 'Basics' | translate - }}</span> - <mat-form-field color="accent"> - <mat-label>{{ 'Owner' | translate }}</mat-label> - <mat-select formControlName="owner" fxFlex required> - <mat-option - *ngFor="let user of allUsers" - [value]="user.principalId" - >{{ user.username }}</mat-option - > - </mat-select> - </mat-form-field> - <mat-checkbox - data-cy="permission-public-element" - formControlName="publicElement" - > - {{ 'Public Element' | translate }} - </mat-checkbox> - </div> - <div - fxLayout="column" - class="general-options-panel" - *ngIf="!permission.publicElement" - > - <span class="general-options-header">{{ - 'Users' | translate - }}</span> - <mat-form-field color="accent"> - <mat-label>{{ - 'Authorized Users' | translate - }}</mat-label> - <mat-chip-grid - #chipList - [attr.aria-label]="'User selection' | translate" - > - <mat-chip-row - *ngFor="let user of grantedUserAuthorities" - selectable="true" - removable="true" - (removed)="removeUser(user)" - > - {{ user.username }} - <button matChipRemove> - <mat-icon>cancel</mat-icon> - </button> - </mat-chip-row> - <input - [placeholder]="'Add' | translate" - #userInput - [formControl]="userCtrl" - [matAutocomplete]="auto" - [matChipInputFor]="chipList" - [matChipInputSeparatorKeyCodes]=" - separatorKeysCodes - " - data-cy="authorized-user" - (matChipInputTokenEnd)="addUser($event)" - /> - </mat-chip-grid> - <mat-autocomplete - #auto="matAutocomplete" - (optionSelected)="userSelected($event)" - > - <mat-option - *ngFor="let user of filteredUsers | async" - [value]="user" - [attr.data-cy]="'user-option-' + user.username" - > - {{ user.username }} - </mat-option> - </mat-autocomplete> - </mat-form-field> - </div> - <div - fxLayout="column" - class="general-options-panel" - *ngIf="!permission.publicElement" - > - <span class="general-options-header">{{ - 'Groups' | translate - }}</span> - <mat-form-field color="accent"> - <mat-label data-cy="authorized-groups-label">{{ - 'Authorized Groups' | translate - }}</mat-label> - <mat-chip-grid - #chipList - [attr.aria-label]="'Group selection' | translate" - > - <mat-chip-row - *ngFor="let group of grantedGroupAuthorities" - selectable="true" - removable="true" - (removed)="removeGroup(group)" - > - {{ group.groupName }} - <button matChipRemove> - <mat-icon>cancel</mat-icon> - </button> - </mat-chip-row> - <input - [placeholder]="'Add' | translate" - #groupInput - [formControl]="groupCtrl" - [matAutocomplete]="auto" - [matChipInputFor]="chipList" - [matChipInputSeparatorKeyCodes]=" - separatorKeysCodes - " - (matChipInputTokenEnd)="addGroup($event)" - /> - </mat-chip-grid> - <mat-autocomplete - #auto="matAutocomplete" - (optionSelected)="groupSelected($event)" + @if (usersLoaded) { + <div class="sp-dialog-content"> + <div fxFlex="100" fxLayout="column" class="p-15"> + <h4>{{ headerTitle }}</h4> + <form [formGroup]="parentForm" fxFlex="100" fxLayout="column"> + <div class="general-options-panel" fxLayout="column"> + <span class="general-options-header">{{ + 'Basics' | translate + }}</span> + <mat-form-field color="accent"> + <mat-label>{{ 'Owner' | translate }}</mat-label> + <mat-select formControlName="owner" fxFlex required> + <mat-option + *ngFor="let user of allUsers" + [value]="user.principalId" + >{{ user.username }}</mat-option + > + </mat-select> + </mat-form-field> + <mat-checkbox + data-cy="permission-public-element" + formControlName="publicElement" > - <mat-option - *ngFor="let group of filteredGroups | async" - [value]="group" + {{ 'Public Element' | translate }} ({{ + 'visible to registered users' | translate + }}) + </mat-checkbox> + </div> + @if (!parentForm.get('publicElement')?.value) { + <div fxLayout="column" class="general-options-panel"> + <span class="general-options-header">{{ + 'Users' | translate + }}</span> + <mat-form-field color="accent"> + <mat-label>{{ + 'Authorized Users' | translate + }}</mat-label> + <mat-chip-grid + #chipList + [attr.aria-label]=" + 'User selection' | translate + " + > + <mat-chip-row + *ngFor=" + let user of grantedUserAuthorities + " + selectable="true" + removable="true" + (removed)="removeUser(user)" + > + {{ user.username }} + <button matChipRemove> + <mat-icon>cancel</mat-icon> + </button> + </mat-chip-row> + <input + matInput + [placeholder]="'Add' | translate" + #userInput + [formControl]="userCtrl" + [matAutocomplete]="userAuto" + [matChipInputFor]="chipList" + [matChipInputSeparatorKeyCodes]=" + separatorKeysCodes + " + data-cy="authorized-user" + (matChipInputTokenEnd)="addUser($event)" + /> + </mat-chip-grid> + <mat-autocomplete + #userAuto="matAutocomplete" + (optionSelected)="userSelected($event)" + > + <mat-option + *ngFor=" + let user of filteredUsers | async + " + [value]="user" + [attr.data-cy]=" + 'user-option-' + user.username + " + > + {{ user.username }} + </mat-option> + </mat-autocomplete> + </mat-form-field> + </div> + } + @if (!parentForm.get('publicElement')?.value) { + <div fxLayout="column" class="general-options-panel"> + <span class="general-options-header">{{ + 'Groups' | translate + }}</span> + <mat-form-field color="accent"> + <mat-label data-cy="authorized-groups-label">{{ + 'Authorized Groups' | translate + }}</mat-label> + <mat-chip-grid + #chipList + [attr.aria-label]=" + 'Group selection' | translate + " + > + <mat-chip-row + *ngFor=" + let group of grantedGroupAuthorities + " + selectable="true" + removable="true" + (removed)="removeGroup(group)" + > + {{ group.groupName }} + <button matChipRemove> + <mat-icon>cancel</mat-icon> + </button> + </mat-chip-row> + <input + matInput + [placeholder]="'Add' | translate" + #groupInput + [formControl]="groupCtrl" + [matAutocomplete]="groupAuto" + [matChipInputFor]="chipList" + [matChipInputSeparatorKeyCodes]=" + separatorKeysCodes + " + (matChipInputTokenEnd)=" + addGroup($event) + " + /> + </mat-chip-grid> + <mat-autocomplete + #groupAuto="matAutocomplete" + (optionSelected)="groupSelected($event)" + > + <mat-option + *ngFor=" + let group of filteredGroups | async + " + [value]="group" + > + {{ group.groupName }} + </mat-option> + </mat-autocomplete> + </mat-form-field> + </div> + } + @if ( + anonymousReadSupported && + parentForm.contains('readAnonymous') + ) { + <div fxLayout="column" class="general-options-panel"> + <span class="general-options-header">{{ + 'Public Link' | translate + }}</span> + <mat-checkbox + data-cy="permission-anonymous-read" + formControlName="readAnonymous" > - {{ group.groupName }} - </mat-option> - </mat-autocomplete> - </mat-form-field> - </div> - </form> + {{ + 'Allow anonymous access through public link' + | translate + }} + </mat-checkbox> + @if (parentForm.get('readAnonymous')?.value) { + <div + fxLayout="row" + fxFlex="100" + class="mt-10" + fxLayoutGap="10px" + fxLayoutAlign="start center" + > + <span> + {{ 'URL' | translate }} + </span> + <span class="public-link" fxFlex> + {{ publicLink }} + </span> + <button + mat-icon-button + color="accent" + [matTooltip]="'Copy' | translate" + [cdkCopyToClipboard]="publicLink" + > + <mat-icon>content_copy</mat-icon> + </button> + </div> + } + </div> + } + </form> + </div> </div> - </div> + } <mat-divider></mat-divider> <div class="sp-dialog-actions"> <div fxLayout="row"> @@ -160,7 +221,7 @@ color="accent" (click)="save()" style="margin-right: 10px" - [disabled]="!parentForm.valid" + [disabled]="parentForm.invalid" data-cy="sp-manage-permissions-save" > <i class="material-icons">save</i diff --git a/ui/src/app/core-ui/object-permission-dialog/object-permission-dialog.component.scss b/ui/src/app/core-ui/object-permission-dialog/object-permission-dialog.component.scss index c38d325ac5..9925783cfb 100644 --- a/ui/src/app/core-ui/object-permission-dialog/object-permission-dialog.component.scss +++ b/ui/src/app/core-ui/object-permission-dialog/object-permission-dialog.component.scss @@ -23,3 +23,12 @@ .form-field .mat-form-field-infix { border-top: 0; } + +.public-link { + background: var(--color-bg-2); + border: 1px solid var(--color-bg-1); + padding: 10px; + color: var(--color-default-text); + margin-left: 10px; + margin-right: 10px; +} diff --git a/ui/src/app/core-ui/object-permission-dialog/object-permission-dialog.component.ts b/ui/src/app/core-ui/object-permission-dialog/object-permission-dialog.component.ts index 02d104a3dc..2dbda6e745 100644 --- a/ui/src/app/core-ui/object-permission-dialog/object-permission-dialog.component.ts +++ b/ui/src/app/core-ui/object-permission-dialog/object-permission-dialog.component.ts @@ -55,6 +55,12 @@ export class ObjectPermissionDialogComponent implements OnInit { @Input() headerTitle: string; + @Input() + anonymousReadSupported = false; + + @Input() + publicLink = ''; + parentForm: UntypedFormGroup; permission: Permission; @@ -90,17 +96,6 @@ export class ObjectPermissionDialogComponent implements OnInit { ngOnInit(): void { this.loadUsersAndGroups(); this.parentForm = this.fb.group({}); - this.parentForm.valueChanges.subscribe(v => { - this.permission.publicElement = v.publicElement; - if (v.publicElement) { - this.permission.grantedAuthorities = []; - this.grantedGroupAuthorities = []; - this.grantedUserAuthorities = []; - } - if (v.owner) { - this.permission.ownerSid = v.owner; - } - }); } loadUsersAndGroups() { @@ -137,6 +132,12 @@ export class ObjectPermissionDialogComponent implements OnInit { Validators.required, ), ); + if (this.anonymousReadSupported) { + this.parentForm.addControl( + 'readAnonymous', + new UntypedFormControl(this.permission.readAnonymous), + ); + } this.filteredUsers = this.userCtrl.valueChanges.pipe( startWith(null), map((username: string | null) => { @@ -166,12 +167,25 @@ export class ObjectPermissionDialogComponent implements OnInit { this.addUserToSelection(authority); } }); - } else { - console.log('No permission entry found for item'); } } save() { + const { owner, publicElement, readAnonymous } = + this.parentForm.getRawValue(); + this.permission.publicElement = publicElement; + if (this.anonymousReadSupported) { + this.permission.readAnonymous = readAnonymous || false; + } + if (this.permission.publicElement) { + this.permission.grantedAuthorities = []; + this.grantedGroupAuthorities = []; + this.grantedUserAuthorities = []; + } + if (owner) { + this.permission.ownerSid = owner; + } + this.permission.grantedAuthorities = this.grantedUserAuthorities .map(u => { return { principalType: u.principalType, sid: u.principalId }; 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 112ba222b1..f3117836fe 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 @@ -22,7 +22,7 @@ ></sp-basic-header-title-component> <div fxFlex="100" fxLayout="row" fxLayoutAlign="center start"> <sp-table - fxFlex="90" + fxFlex="100" [columns]="displayedColumns" [dataSource]="dataSource" > diff --git a/ui/src/app/dashboard/components/overview/dashboard-overview-table/dashboard-overview-table.component.ts b/ui/src/app/dashboard/components/overview/dashboard-overview-table/dashboard-overview-table.component.ts index b2c53014aa..43053f6068 100644 --- a/ui/src/app/dashboard/components/overview/dashboard-overview-table/dashboard-overview-table.component.ts +++ b/ui/src/app/dashboard/components/overview/dashboard-overview-table/dashboard-overview-table.component.ts @@ -71,6 +71,8 @@ export class DashboardOverviewTableComponent extends SpDataExplorerOverviewDirec this.translateService.instant( `Manage permissions for dashboard ${dashboard.name}`, ), + true, + this.makeDashboardKioskUrl(dashboard.elementId), ); dialogRef.afterClosed().subscribe(refresh => { @@ -152,4 +154,8 @@ export class DashboardOverviewTableComponent extends SpDataExplorerOverviewDirec openDashboardInKioskMode(dashboard: Dashboard) { this.router.navigate(['dashboard-kiosk', dashboard.elementId]); } + + makeDashboardKioskUrl(dashboardId: string): string { + return `${window.location.protocol}//${window.location.host}/#/dashboard-kiosk/${dashboardId}`; + } } diff --git a/ui/src/app/data-explorer-shared/services/data-explorer-shared.service.ts b/ui/src/app/data-explorer-shared/services/data-explorer-shared.service.ts index b3d77875c7..c164d058aa 100644 --- a/ui/src/app/data-explorer-shared/services/data-explorer-shared.service.ts +++ b/ui/src/app/data-explorer-shared/services/data-explorer-shared.service.ts @@ -38,7 +38,12 @@ export class DataExplorerSharedService { private translateService: TranslateService, ) {} - openPermissionsDialog(elementId: string, headerTitle: string) { + openPermissionsDialog( + elementId: string, + headerTitle: string, + anonymousReadSupported: boolean = false, + publicLink: string = '', + ) { return this.dialogService.open(ObjectPermissionDialogComponent, { panelType: PanelType.SLIDE_IN_PANEL, title: this.translateService.instant('Manage permissions'), @@ -46,6 +51,8 @@ export class DataExplorerSharedService { data: { objectInstanceId: elementId, headerTitle, + anonymousReadSupported, + publicLink, }, }); } diff --git a/ui/src/app/data-explorer/components/overview/data-explorer-overview-table/data-explorer-overview-table.component.html b/ui/src/app/data-explorer/components/overview/data-explorer-overview-table/data-explorer-overview-table.component.html index 65a615065c..6857516236 100644 --- a/ui/src/app/data-explorer/components/overview/data-explorer-overview-table/data-explorer-overview-table.component.html +++ b/ui/src/app/data-explorer/components/overview/data-explorer-overview-table/data-explorer-overview-table.component.html @@ -22,7 +22,7 @@ ></sp-basic-header-title-component> <div fxFlex="100" fxLayout="row" fxLayoutAlign="center start"> <sp-table - fxFlex="90" + fxFlex="100" [columns]="displayedColumns" [dataSource]="dataSource" > diff --git a/ui/src/app/pipelines/pipelines.component.html b/ui/src/app/pipelines/pipelines.component.html index dd7ddf1096..077b3e861e 100644 --- a/ui/src/app/pipelines/pipelines.component.html +++ b/ui/src/app/pipelines/pipelines.component.html @@ -89,7 +89,7 @@ title="Pipelines" ></sp-basic-header-title-component> <div fxFlex="100" fxLayout="row" fxLayoutAlign="center start"> - <div fxFlex="90"> + <div fxFlex="100"> <sp-pipeline-overview [pipelines]="filteredPipelines" (refreshPipelinesEmitter)="getPipelines()" @@ -106,7 +106,7 @@ fxLayout="row" fxLayoutAlign="center start" > - <div fxFlex="90"> + <div fxFlex="100"> <sp-functions-overview [functions]="functions" *ngIf="functionsReady"
