Skip to content

Commit

Permalink
refactor(access-rights): Reflect access rights setting changes in bet…
Browse files Browse the repository at this point in the history
…ween different tabs (per user, all)
  • Loading branch information
FilipLeitner authored and jmacura committed May 10, 2024
1 parent 8bd1a5c commit 6eb536e
Show file tree
Hide file tree
Showing 6 changed files with 152 additions and 79 deletions.
4 changes: 4 additions & 0 deletions projects/hslayers/common/dialog-set-permissions/todo.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
- cache get roles request
- reset to default
- add PER_ROLE tab
- when going from EVERYONE tab to PER_ROLE rights should be pre-filled based on existing settings
Original file line number Diff line number Diff line change
Expand Up @@ -7,19 +7,19 @@
<div class="btn-group">
<button type="button" class="btn btn-light hs-toolbar-button btn-outline-secondary"
[disabled]="right !== 'read' && access_rights['access_rights.read'] === 'private'"
(click)="accessRightsChanged('access_rights.' + right,grantedTo)"
(click)="accessRightsChanged(right,grantedTo)"
[ngClass]="{'active':access_rights['access_rights.'+ right] === grantedTo}">
{{'SAVECOMPOSITION.public' | translateHs }}
</button>
<button type="button" class="btn btn-light hs-toolbar-button btn-outline-secondary"
(click)="accessRightsChanged('access_rights.' + right,'private')"
(click)="accessRightsChanged(right,'private')"
[ngClass]="{'active':access_rights['access_rights.' + right] === 'private'}">
{{'SAVECOMPOSITION.private' | translateHs }}
</button>
</div>
</div>
</ng-template>
<ul class="nav nav-tabs justify-content-end my-2 py-1">
<ul class="nav nav-tabs justify-content-end my-2 mb-3">
<li class="nav-item" (click)="changeGrantingOptions(grantingOptions.EVERYONE)">
<a [ngClass]="{'active': currentOption === grantingOptions.EVERYONE}" class="nav-link "
aria-current="page">{{'SAVECOMPOSITION.everyone' | translateHs }}</a>
Expand All @@ -30,30 +30,35 @@
</li>
</ul>
<ng-container *ngIf="currentOption === grantingOptions.EVERYONE; else perUserTpl">
<ng-container
*ngTemplateOutlet="accessRightsTpl;context:{accessRights: access_rights, right: 'read', grantedTo: 'EVERYONE'}">
</ng-container>
<ng-container
*ngTemplateOutlet="accessRightsTpl;context:{accessRights: access_rights, right: 'write', grantedTo: 'EVERYONE'}">
<ng-container *ngFor="let type of rightsOptions">
<ng-container
*ngTemplateOutlet="accessRightsTpl;context:{accessRights: access_rights, right: type, grantedTo: 'EVERYONE'}">
</ng-container>
</ng-container>
</ng-container>

<ng-template #perUserTpl>
<div><input type="search" class="form-control hs-save-map-filter" [placeholder]="'COMMON.filter' | translateHs "
[(ngModel)]="userSearch" [ngModelOptions]="{standalone: true}" (input)="refreshList()">
</div>
<div class="hs-save-map-users-list p-1 my-1">
<section class="d-flex">
<div>
<input type="search" class="form-control hs-save-map-filter" [placeholder]="'COMMON.filter' | translateHs "
[(ngModel)]="userSearch" name="rights-per-user-filter" (input)="refreshList()">
</div>
<div class="d-flex justify-content-end gap-5 w-50 align-self-end">
<button *ngFor="let type of rightsOptions" type="button" class="btn btn-primary btn-sm"
style="white-space: nowrap;" (click)="toggleRightsForAllUsers(type)">{{'SAVECOMPOSITION.form.toggleAll'
| translateHs
}}</button>
</div>
</section>
<div class="hs-save-map-users-list p-1 my-1" [class.collapsed]="collapsed">
<div class="d-flex flex-row justify-content-between align-items-start p-1"
[class.bg-primary-subtle]="user.username === this.endpoint.user"
*ngFor="let user of allUsers | filter: userFilter">
<span class="w-50">{{user.hslDisplayName}}</span>
<label style="cursor: pointer;"><input class="me-2" type="checkbox" name="rightToRead"
[(ngModel)]="user.read"
(change)="accessRightsChanged('access_rights.read', user.username, $event)">{{'SAVECOMPOSITION.form.read'
| translateHs }}</label>
<label style="cursor: pointer;"><input class="me-2" type="checkbox" name="rightToWrite"
[(ngModel)]="user.write"
(change)="accessRightsChanged('access_rights.write', user.username, $event)">{{'SAVECOMPOSITION.form.write'
| translateHs }}</label>
<label *ngFor="let type of rightsOptions" style="cursor: pointer;"><input class="me-2" type="checkbox"
[name]="'rightTo'+type" [disabled]="user.username === this.endpoint.user" [(ngModel)]="user[type]"
(change)="accessRightsChanged(type, user.username, $event)">{{ type | translateHs : {module:
'SAVECOMPOSITION.form'} }}</label>
</div>
</div>
</ng-template>
Original file line number Diff line number Diff line change
Expand Up @@ -6,30 +6,34 @@ import {lastValueFrom, map} from 'rxjs';
import {AccessRightsModel} from 'hslayers-ng/types';
import {HsCommonLaymanService} from '../layman.service';
import {HsEndpoint} from 'hslayers-ng/types';
import {HsLanguageService} from 'hslayers-ng/services/language';
import {HsLogService} from 'hslayers-ng/services/log';
import {LaymanUser} from '../types/layman-user.type';

enum GrantingOptions {
PERUSER = 'per_user',
EVERYONE = 'everyone',
}

export type AccessRightsType = 'read' | 'write';

@Component({
selector: 'hs-layman-access-rights',
templateUrl: './layman-access-rights.component.html',
})
export class HsCommonLaymanAccessRightsComponent implements OnInit {
@Input() access_rights: AccessRightsModel;
@Input() collapsed: boolean = false;

@Output() access_rights_changed = new EventEmitter<AccessRightsModel>();

grantingOptions = GrantingOptions;
currentOption: string = GrantingOptions.EVERYONE;
rightsOptions: AccessRightsType[] = ['read', 'write'];

allUsers: LaymanUser[] = [];
userSearch: string;
endpoint: HsEndpoint;
constructor(
private hsLanguageService: HsLanguageService,
private hsCommonLaymanService: HsCommonLaymanService,
private $http: HttpClient,
private hsLog: HsLogService,
Expand All @@ -41,23 +45,40 @@ export class HsCommonLaymanAccessRightsComponent implements OnInit {
if (readAccess.length > 1 || writeAccess.length > 1) {
this.currentOption = GrantingOptions.PERUSER;
await this.getAllUsers();
this.tickFoundUsers(readAccess, 'read');
this.tickFoundUsers(writeAccess, 'write');
}
}

/**
* Tick found user checkboxes in UI that have access rights
* Toggles access rights for all users
* @param users - Username list that have access rights
* @param type - Access right type (read or write)
*/
tickFoundUsers(users: string[], type: string): void {
users.forEach((user) => {
const found = this.allUsers.find((u) => u.username == user);
if (found) {
found[type] = true;
toggleRightsForAllUsers(type: AccessRightsType): void {
/**
* Value to be set.
* Negative of value of bigger part of the users
*/
const value =
this.allUsers.length / 2 >= this.allUsers.filter((u) => u[type]).length;

this.allUsers.forEach((user) => {
//Value for current user cannot be wont be changed
user[type] = user.username === this.endpoint.user ? user[type] : value;
//In case write permission is being added make sure read is granted as well
//when read perrmission is being taken away, make sure write is taken as well
if ((type === 'write' && value) || (type === 'read' && !value)) {
const t = type === 'write' ? 'read' : 'write';
user[t] = value;
this.access_rights[`access_rights.${t}`] = value
? 'EVERYONE'
: 'private';
}
});
//Update access_rights model
this.access_rights[`access_rights.${type}`] = value
? 'EVERYONE'
: 'private';
this.access_rights_changed.emit(this.access_rights);
}

/**
Expand All @@ -66,11 +87,15 @@ export class HsCommonLaymanAccessRightsComponent implements OnInit {
* @param value - Access rights value (private or EVERYONE)
* @param event - Checkbox change event
*/
accessRightsChanged(type: string, value: string, event?: any): void {
accessRightsChanged(
type: AccessRightsType,
value: string,
event?: any,
): void {
if (this.currentOption == this.grantingOptions.PERUSER) {
this.rightsChangedPerUser(type, value, event);
} else {
this.access_rights[type] = value;
this.access_rights[`access_rights.${type}`] = value;
if (
this.access_rights['access_rights.read'] == 'private' &&
this.access_rights['access_rights.write'] != 'private'
Expand All @@ -91,41 +116,42 @@ export class HsCommonLaymanAccessRightsComponent implements OnInit {
* @param value - Access rights value (username for user)
* @param event - Checkbox change event
*/
rightsChangedPerUser(type: string, value: string, event?: any): void {
if (event.target.checked && type == 'access_rights.write') {
this.access_rights['access_rights.read'] = this.access_rights[
'access_rights.read'
].concat(',' + value);
this.findLaymanUser(value).read = event.target.checked;
} else if (!event.target.checked && type == 'access_rights.read') {
this.removeUserRights('access_rights.write', value);
this.findLaymanUser(value).write = event.target.checked;
rightsChangedPerUser(
type: AccessRightsType,
username: LaymanUser['username'],
event?: any,
): void {
const value = event.target.checked;
const user = this.findLaymanUser(username);
if ((type === 'write' && value) || (type === 'read' && !value)) {
const t = type === 'write' ? 'read' : 'write';
user[t] = value;
this.setAcessRightsFromUsers(t);
}
event.target.checked
? (this.access_rights[type] = this.access_rights[type].concat(
',' + value,
))
: this.removeUserRights(type, value);
this.setAcessRightsFromUsers(type);
}

/**
* Find user by username from all Layman users
* @param username - User username provided
* Based on [type] property of users update access_rights value
*/
findLaymanUser(username: string): LaymanUser {
return this.allUsers.find((u) => u.username == username);
setAcessRightsFromUsers(type: AccessRightsType): void {
this.access_rights[`access_rights.${type}`] = this.allUsers
.reduce((acc, curr) => {
if (curr[type]) {
acc.push(curr.username);
}
return acc;
}, [] as string[])
.join(',');
this.access_rights_changed.emit(this.access_rights);
}

/**
* Remove user rights by filtering username from access_rights
* @param type - Access rights type (access_rights.read or access_rights.write)
* Find user by username from all Layman users
* @param username - User username provided
*/
removeUserRights(type: string, username: string): void {
this.access_rights[type] = this.access_rights[type]
.split(',')
.filter((u: string) => u != username)
.join(',');
findLaymanUser(username: string): LaymanUser {
return this.allUsers.find((u) => u.username == username);
}

/**
Expand All @@ -135,43 +161,71 @@ export class HsCommonLaymanAccessRightsComponent implements OnInit {
async changeGrantingOptions(option: 'per_user' | 'everyone'): Promise<void> {
if (option == this.grantingOptions.PERUSER) {
await this.getAllUsers();
this.access_rights['access_rights.read'] = this.endpoint.user;
this.access_rights['access_rights.write'] = this.endpoint.user;
} else {
//In case some users has access rights use EVERYONE, private otherwise
//READ
const rights = this.allUsers.reduce(
(acc, curr) => {
acc.read += curr['read'] ? 1 : 0;
acc.write += curr['write'] ? 1 : 0;
return acc;
},
{read: 0, write: 0},
);
this.allUsers = [];
this.access_rights['access_rights.read'] = 'EVERYONE';
this.access_rights['access_rights.write'] = 'private';

this.access_rights['access_rights.read'] =
rights.read > 1 ? 'EVERYONE' : 'private';

this.access_rights['access_rights.write'] =
rights.write > 1 ? 'EVERYONE' : 'private';
}
this.currentOption = option;
}

/**
* Check whether user has access rights.
*/
userHasAccess(user: string, rights: string[]): boolean {
return rights.includes(user) || rights.includes('EVERYONE');
}

/**
* Get all registered users from Layman's endpoint service
*/
async getAllUsers(): Promise<void> {
if (this.endpoint?.authenticated) {
const url = `${this.endpoint.url}/rest/users`;

const read = this.access_rights['access_rights.read'].split(',');
const write = this.access_rights['access_rights.write'].split(',');

try {
this.allUsers = await lastValueFrom(
this.$http.get<LaymanUser[]>(url, {withCredentials: true}).pipe(
map((res: any[]) => {
return res
.filter((user) => user.username != this.endpoint.user)
.map((user) => {
const laymanUser: LaymanUser = {
username: user.username,
screenName: user.screen_name,
givenName: user.given_name,
familyName: user.family_name,
middleName: user.middle_name,
name: user.name,
};
laymanUser.hslDisplayName = this.getUserName(laymanUser);
return laymanUser;
});
return res.map((user) => {
const isCurrentUser = user.username === this.endpoint.user;
const laymanUser: LaymanUser = {
username: user.username,
screenName: user.screen_name,
givenName: user.given_name,
familyName: user.family_name,
middleName: user.middle_name,
name: user.name,
read:
isCurrentUser || this.userHasAccess(user.username, read),
write:
isCurrentUser || this.userHasAccess(user.username, write),
};
laymanUser.hslDisplayName = this.getUserName(laymanUser);
return laymanUser;
});
}),
),
);
this.setAcessRightsFromUsers('read');
this.setAcessRightsFromUsers('write');
} catch (e) {
this.hsLog.error(e);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
[alt]="'SAVECOMPOSITION.compositionThumbnail' | translateHs " width="256" height="256">
</label>

<hs-layman-access-rights *ngIf="endpoint?.type.includes('layman')"
<hs-layman-access-rights *ngIf="endpoint?.type.includes('layman')" [collapsed]="true"
[(access_rights)]="hsSaveMapManagerService._access_rights" (access_rights_changed)="setAccessRights($event)">
</hs-layman-access-rights>

Expand Down Expand Up @@ -58,4 +58,4 @@
</div>
</div>
</div>
</form>
</form>
7 changes: 6 additions & 1 deletion projects/hslayers/css/hslayers-bootstrap.scss
Original file line number Diff line number Diff line change
Expand Up @@ -309,7 +309,12 @@ $list-group-item-padding-x: 1.25rem;

.hs-save-map-users-list {
overflow-y: auto;
max-height: 10rem;
height: min-content;
max-height: 50vh;

&.collapsed {
max-height: 10rem;
}
}

.hs-layerpanel {
Expand Down
7 changes: 6 additions & 1 deletion projects/hslayers/css/hslayers-wo-bootstrap.scss
Original file line number Diff line number Diff line change
Expand Up @@ -221,7 +221,12 @@ $list-group-item-padding-x: 1.25rem;

.hs-save-map-users-list {
overflow-y: auto;
max-height: 10rem;
height: min-content;
max-height: 50vh;

&.collapsed {
max-height: 10rem;
}
}

.hs-layerpanel {
Expand Down

0 comments on commit 6eb536e

Please sign in to comment.