commit
d1bc995830
8
client/package-lock.json
generated
8
client/package-lock.json
generated
@ -5387,13 +5387,15 @@
|
|||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": false,
|
"resolved": false,
|
||||||
"integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=",
|
"integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=",
|
||||||
"dev": true
|
"dev": true,
|
||||||
|
"optional": true
|
||||||
},
|
},
|
||||||
"brace-expansion": {
|
"brace-expansion": {
|
||||||
"version": "1.1.11",
|
"version": "1.1.11",
|
||||||
"resolved": false,
|
"resolved": false,
|
||||||
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
|
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"optional": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"balanced-match": "^1.0.0",
|
"balanced-match": "^1.0.0",
|
||||||
"concat-map": "0.0.1"
|
"concat-map": "0.0.1"
|
||||||
@ -5416,7 +5418,8 @@
|
|||||||
"version": "0.0.1",
|
"version": "0.0.1",
|
||||||
"resolved": false,
|
"resolved": false,
|
||||||
"integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=",
|
"integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=",
|
||||||
"dev": true
|
"dev": true,
|
||||||
|
"optional": true
|
||||||
},
|
},
|
||||||
"console-control-strings": {
|
"console-control-strings": {
|
||||||
"version": "1.1.0",
|
"version": "1.1.0",
|
||||||
@ -5583,6 +5586,7 @@
|
|||||||
"resolved": false,
|
"resolved": false,
|
||||||
"integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
|
"integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"optional": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"brace-expansion": "^1.1.7"
|
"brace-expansion": "^1.1.7"
|
||||||
}
|
}
|
||||||
|
@ -29,7 +29,7 @@ export class DataSendService {
|
|||||||
tap(
|
tap(
|
||||||
response => {
|
response => {
|
||||||
// TODO: Message, Notify, Etc
|
// TODO: Message, Notify, Etc
|
||||||
console.log('New Model added. Response :\n', response);
|
console.log('New Model added. Response ::\n', response);
|
||||||
},
|
},
|
||||||
error => console.error('createModel has returned an Error:\n', error)
|
error => console.error('createModel has returned an Error:\n', error)
|
||||||
)
|
)
|
||||||
@ -56,7 +56,7 @@ export class DataSendService {
|
|||||||
tap(
|
tap(
|
||||||
response => {
|
response => {
|
||||||
// TODO: Message, Notify, Etc
|
// TODO: Message, Notify, Etc
|
||||||
console.log('Update model. Response :\n', response);
|
console.log('Update model. Response ::\n', response);
|
||||||
},
|
},
|
||||||
error => console.error('updateModel has returned an Error:\n', error)
|
error => console.error('updateModel has returned an Error:\n', error)
|
||||||
)
|
)
|
||||||
|
@ -16,8 +16,11 @@
|
|||||||
</mat-toolbar>
|
</mat-toolbar>
|
||||||
|
|
||||||
<mat-menu #ellipsisMenu="matMenu">
|
<mat-menu #ellipsisMenu="matMenu">
|
||||||
<button mat-menu-item *ngFor="let item of menuList" (click)=clickMenu(item)>
|
|
||||||
|
<ng-container *ngFor="let item of menuList">
|
||||||
|
<button mat-menu-item *ngIf="opHasPerm(item.perm)" (click)=clickMenu(item)>
|
||||||
<fa-icon *ngIf="item.icon" [icon]='item.icon'></fa-icon>
|
<fa-icon *ngIf="item.icon" [icon]='item.icon'></fa-icon>
|
||||||
{{item.text | translate}}
|
{{item.text | translate}}
|
||||||
</button>
|
</button>
|
||||||
|
</ng-container>
|
||||||
</mat-menu>
|
</mat-menu>
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import { Component, OnInit, Input, Output, EventEmitter } from '@angular/core';
|
import { Component, OnInit, Input, Output, EventEmitter } from '@angular/core';
|
||||||
|
import { OperatorService } from '../../../core/services/operator.service';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reusable head bar component for Apps.
|
* Reusable head bar component for Apps.
|
||||||
@ -86,7 +87,7 @@ export class HeadBarComponent implements OnInit {
|
|||||||
/**
|
/**
|
||||||
* Empty constructor
|
* Empty constructor
|
||||||
*/
|
*/
|
||||||
public constructor() {}
|
public constructor(private op: OperatorService) {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* empty onInit
|
* empty onInit
|
||||||
@ -107,4 +108,22 @@ export class HeadBarComponent implements OnInit {
|
|||||||
public clickPlusButton(): void {
|
public clickPlusButton(): void {
|
||||||
this.plusButtonClicked.emit(true);
|
this.plusButtonClicked.emit(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine if the operator has the correct permission to use a button in the menu
|
||||||
|
* @param perm
|
||||||
|
*/
|
||||||
|
public opHasPerm(perm: string): boolean {
|
||||||
|
// return false if the operator is not yet loaded
|
||||||
|
if (this.op) {
|
||||||
|
// if no permission was required, return true
|
||||||
|
if (!perm) {
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return this.op.hasPerms(perm);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -11,6 +11,10 @@ export class Group extends BaseModel<Group> {
|
|||||||
|
|
||||||
public constructor(input?: any) {
|
public constructor(input?: any) {
|
||||||
super('users/group', input);
|
super('users/group', input);
|
||||||
|
if (!input) {
|
||||||
|
// permissions are required for new groups
|
||||||
|
this.permissions = [];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public getTitle(): string {
|
public getTitle(): string {
|
||||||
|
@ -0,0 +1,88 @@
|
|||||||
|
<mat-toolbar color='primary'>
|
||||||
|
<button *osPerms="'users.can_manage'" (click)='newGroupButton()' class='generic-mini-button on-transition-fade'
|
||||||
|
mat-mini-fab>
|
||||||
|
<fa-icon *ngIf="!newGroup" icon='plus'></fa-icon>
|
||||||
|
<fa-icon *ngIf="newGroup" icon='times'></fa-icon>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<div class="on-transition-fade">
|
||||||
|
<span translate>Groups</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<span class='spacer'></span>
|
||||||
|
</mat-toolbar>
|
||||||
|
|
||||||
|
<div class="on-transition-fade new-group-form" *ngIf="newGroup">
|
||||||
|
<form #newGroupForm="ngForm" (ngSubmit)="submitNewGroup(newGroupForm.form)" (keydown)="keyDownFunction($event)">
|
||||||
|
<mat-form-field>
|
||||||
|
<input type="text" matInput name="name" ngModel #nameField="ngModel" placeholder="{{ 'New group name' | translate}}">
|
||||||
|
</mat-form-field>
|
||||||
|
|
||||||
|
<button type="submit" mat-mini-fab color="primary">
|
||||||
|
<fa-icon icon="save"></fa-icon>
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="on-transition-fade new-group-form" *ngIf="editGroup">
|
||||||
|
<form #editGroupForm="ngForm" (ngSubmit)="submitEditedGroup(editGroupForm.form)">
|
||||||
|
<mat-form-field>
|
||||||
|
<input type="text" matInput name="name" [(ngModel)]="selectedGroup.name" #nameField="ngModel" placeholder="{{ 'Edit group name' | translate}}">
|
||||||
|
</mat-form-field>
|
||||||
|
|
||||||
|
<button type="submit" mat-mini-fab color="primary">
|
||||||
|
<fa-icon icon="save"></fa-icon>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button type="button" mat-mini-fab color="warn" (click)="deleteSelectedGroup()" [disabled]="isProtected(selectedGroup)">
|
||||||
|
<fa-icon icon="trash"></fa-icon>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button type="button" mat-mini-fab color="primary" (click)="cancelEditing()">
|
||||||
|
<fa-icon icon="times"></fa-icon>
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="hint-text on-transition-fade">
|
||||||
|
<span translate>All your changes are saved immediately.</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<mat-accordion *ngFor="let app of repo.appPermissions">
|
||||||
|
<mat-expansion-panel class="mat-elevation-z0" [expanded]=true>
|
||||||
|
<mat-expansion-panel-header>
|
||||||
|
<mat-panel-title translate>
|
||||||
|
{{ app.name }}
|
||||||
|
</mat-panel-title>
|
||||||
|
</mat-expansion-panel-header>
|
||||||
|
|
||||||
|
<div class="scrollable-perm-matrix">
|
||||||
|
<table mat-table class='on-transition-fade' [dataSource]="getTableDataSource(app.permissions)">
|
||||||
|
<ng-container matColumnDef="perm" sticky>
|
||||||
|
<mat-header-cell *matHeaderCellDef translate> Permissions </mat-header-cell>
|
||||||
|
<mat-cell *matCellDef="let perm" translate>
|
||||||
|
{{ perm.display_name }}
|
||||||
|
</mat-cell>
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
|
<div *ngFor=" let group of groups; trackBy: trackGroupArray">
|
||||||
|
<ng-container [matColumnDef]="group.name">
|
||||||
|
<mat-header-cell class="group-head-table-cell" *matHeaderCellDef (click)="selectGroup(group)">
|
||||||
|
<div class="inner-table" translate>
|
||||||
|
{{ group.name }}
|
||||||
|
</div>
|
||||||
|
</mat-header-cell>
|
||||||
|
<mat-cell *matCellDef="let perm">
|
||||||
|
<div class="inner-table">
|
||||||
|
<mat-checkbox [checked]="group.hasPermission(perm.value)" (change)='togglePerm(group, perm.value)'></mat-checkbox>
|
||||||
|
</div>
|
||||||
|
</mat-cell>
|
||||||
|
</ng-container>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<mat-header-row *matHeaderRowDef="headerRowDef"></mat-header-row>
|
||||||
|
<mat-row *matRowDef="let row; columns: headerRowDef"></mat-row>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</mat-expansion-panel>
|
||||||
|
</mat-accordion>
|
@ -0,0 +1,39 @@
|
|||||||
|
table {
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
.mat-cell {
|
||||||
|
min-width: 80px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mat-column-perm {
|
||||||
|
min-width: 130px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.inner-table {
|
||||||
|
width: 100%;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.group-head-table-cell {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.hint-text {
|
||||||
|
padding-top: 30px;
|
||||||
|
padding-left: 25px;
|
||||||
|
background-color: #ffffff; // put in theme later
|
||||||
|
}
|
||||||
|
|
||||||
|
.new-group-form {
|
||||||
|
text-align: center;
|
||||||
|
padding-top: 10px;
|
||||||
|
background-color: #ffffff; // put in theme later
|
||||||
|
button {
|
||||||
|
margin-left: 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.scrollable-perm-matrix {
|
||||||
|
overflow: auto;
|
||||||
|
}
|
@ -0,0 +1,26 @@
|
|||||||
|
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { GroupListComponent } from './group-list.component';
|
||||||
|
import { E2EImportsModule } from '../../../../../e2e-imports.module';
|
||||||
|
|
||||||
|
describe('GroupListComponent', () => {
|
||||||
|
let component: GroupListComponent;
|
||||||
|
let fixture: ComponentFixture<GroupListComponent>;
|
||||||
|
|
||||||
|
beforeEach(async(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
imports: [E2EImportsModule],
|
||||||
|
declarations: [GroupListComponent]
|
||||||
|
}).compileComponents();
|
||||||
|
}));
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
fixture = TestBed.createComponent(GroupListComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
@ -0,0 +1,192 @@
|
|||||||
|
import { Component, OnInit } from '@angular/core';
|
||||||
|
import { Title } from '@angular/platform-browser';
|
||||||
|
import { TranslateService } from '@ngx-translate/core';
|
||||||
|
import { MatTableDataSource } from '@angular/material';
|
||||||
|
import { FormGroup } from '@angular/forms';
|
||||||
|
|
||||||
|
import { GroupRepositoryService } from '../../services/group-repository.service';
|
||||||
|
import { ViewGroup } from '../../models/view-group';
|
||||||
|
import { Group } from '../../../../shared/models/users/group';
|
||||||
|
import { BaseComponent } from '../../../../base.component';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Component for the Group-List and permission matrix
|
||||||
|
*/
|
||||||
|
@Component({
|
||||||
|
selector: 'os-group-list',
|
||||||
|
templateUrl: './group-list.component.html',
|
||||||
|
styleUrls: ['./group-list.component.scss']
|
||||||
|
})
|
||||||
|
export class GroupListComponent extends BaseComponent implements OnInit {
|
||||||
|
/**
|
||||||
|
* Holds all Groups
|
||||||
|
*/
|
||||||
|
public groups: ViewGroup[];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The header rows that the table should show
|
||||||
|
*/
|
||||||
|
public headerRowDef: string[] = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show or hide the new groups box
|
||||||
|
*/
|
||||||
|
public newGroup = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show or hide edit Group features
|
||||||
|
*/
|
||||||
|
public editGroup = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Store the group to edit
|
||||||
|
*/
|
||||||
|
public selectedGroup: ViewGroup;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor
|
||||||
|
*
|
||||||
|
* @param titleService Title Service
|
||||||
|
* @param translate Translations
|
||||||
|
* @param DS The Data Store
|
||||||
|
* @param constants Constants
|
||||||
|
*/
|
||||||
|
public constructor(titleService: Title, translate: TranslateService, public repo: GroupRepositoryService) {
|
||||||
|
super(titleService, translate);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Trigger for the new Group button
|
||||||
|
*/
|
||||||
|
public newGroupButton(): void {
|
||||||
|
this.editGroup = false;
|
||||||
|
this.newGroup = !this.newGroup;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Saves a newly created group.
|
||||||
|
* @param form form data given by the group
|
||||||
|
*/
|
||||||
|
public submitNewGroup(form: FormGroup): void {
|
||||||
|
if (form.value) {
|
||||||
|
this.repo.create(form.value).subscribe(response => {
|
||||||
|
if (response) {
|
||||||
|
form.reset();
|
||||||
|
// commenting the next line would allow to create multiple groups without reopening the form
|
||||||
|
this.newGroup = false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Saves an edited group.
|
||||||
|
* @param form form data given by the group
|
||||||
|
*/
|
||||||
|
public submitEditedGroup(form: FormGroup): void {
|
||||||
|
if (form.value) {
|
||||||
|
const updateData = new Group({ name: form.value.name });
|
||||||
|
|
||||||
|
this.repo.update(updateData, this.selectedGroup).subscribe(response => {
|
||||||
|
if (response) {
|
||||||
|
this.cancelEditing();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deletes the selected Group
|
||||||
|
*/
|
||||||
|
public deleteSelectedGroup(): void {
|
||||||
|
this.repo.delete(this.selectedGroup).subscribe(response => this.cancelEditing());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cancel the editing
|
||||||
|
*/
|
||||||
|
public cancelEditing(): void {
|
||||||
|
this.editGroup = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Select group in head bar
|
||||||
|
*/
|
||||||
|
public selectGroup(group: ViewGroup): void {
|
||||||
|
this.newGroup = false;
|
||||||
|
this.selectedGroup = group;
|
||||||
|
this.editGroup = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Triggers when a permission was toggled
|
||||||
|
* @param group
|
||||||
|
* @param perm
|
||||||
|
*/
|
||||||
|
public togglePerm(viewGroup: ViewGroup, perm: string): void {
|
||||||
|
const updateData = new Group({ permissions: viewGroup.getAlteredPermissions(perm) });
|
||||||
|
this.repo.update(updateData, viewGroup).subscribe();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the rowDefinition after Reloading or changes
|
||||||
|
*/
|
||||||
|
public updateRowDef(): void {
|
||||||
|
// reset the rowDef list first
|
||||||
|
this.headerRowDef = ['perm'];
|
||||||
|
this.groups.forEach(viewGroup => {
|
||||||
|
this.headerRowDef.push('' + viewGroup.name);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Required to detect changes in *ngFor loops
|
||||||
|
*
|
||||||
|
* @param group Corresponding group that was changed
|
||||||
|
*/
|
||||||
|
public trackGroupArray(group: ViewGroup): number {
|
||||||
|
return group.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts a permission string into MatTableDataSource
|
||||||
|
* @param permissions
|
||||||
|
*/
|
||||||
|
public getTableDataSource(permissions: string[]): MatTableDataSource<any> {
|
||||||
|
const dataSource = new MatTableDataSource();
|
||||||
|
dataSource.data = permissions;
|
||||||
|
return dataSource;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine if a group is protected from deletion
|
||||||
|
* @param group ViewGroup
|
||||||
|
*/
|
||||||
|
public isProtected(group: ViewGroup): boolean {
|
||||||
|
return group.id === 1 || group.id === 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clicking escape while in #newGroupForm should toggle newGroup.
|
||||||
|
*/
|
||||||
|
public keyDownFunction(event: KeyboardEvent): void {
|
||||||
|
if (event.keyCode === 27) {
|
||||||
|
this.newGroup = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Init function.
|
||||||
|
*
|
||||||
|
* Monitor the repository for changes and update the local groups array
|
||||||
|
*/
|
||||||
|
public ngOnInit(): void {
|
||||||
|
super.setTitle('Groups');
|
||||||
|
this.repo.getViewModelListObservable().subscribe(newViewGroups => {
|
||||||
|
if (newViewGroups) {
|
||||||
|
this.groups = newViewGroups;
|
||||||
|
this.updateRowDef();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -24,7 +24,8 @@ export class UserListComponent extends ListViewBaseComponent<ViewUser> implement
|
|||||||
{
|
{
|
||||||
text: 'Groups',
|
text: 'Groups',
|
||||||
icon: 'users',
|
icon: 'users',
|
||||||
action: 'toGroups'
|
action: 'toGroups',
|
||||||
|
perm: 'users.can_manage'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: 'Import',
|
text: 'Import',
|
||||||
@ -81,7 +82,7 @@ export class UserListComponent extends ListViewBaseComponent<ViewUser> implement
|
|||||||
* TODO: implement
|
* TODO: implement
|
||||||
*/
|
*/
|
||||||
public toGroups(): void {
|
public toGroups(): void {
|
||||||
console.log('to Groups');
|
this.router.navigate(['./groups'], { relativeTo: this.route });
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
76
client/src/app/site/users/models/view-group.ts
Normal file
76
client/src/app/site/users/models/view-group.ts
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
import { BaseViewModel } from '../../base/base-view-model';
|
||||||
|
import { Group } from '../../../shared/models/users/group';
|
||||||
|
import { BaseModel } from '../../../shared/models/base/base-model';
|
||||||
|
|
||||||
|
export class ViewGroup extends BaseViewModel {
|
||||||
|
private _group: Group;
|
||||||
|
|
||||||
|
public get group(): Group {
|
||||||
|
return this._group ? this._group : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get id(): number {
|
||||||
|
return this.group ? this.group.id : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get name(): string {
|
||||||
|
return this.group ? this.group.name : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* required for renaming purpose
|
||||||
|
*/
|
||||||
|
public set name(newName: string) {
|
||||||
|
if (this.group) {
|
||||||
|
this.group.name = newName;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public get permissions(): string[] {
|
||||||
|
return this.group ? this.group.permissions : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public constructor(group?: Group) {
|
||||||
|
super();
|
||||||
|
this._group = group;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns an array of permissions where the given perm is included
|
||||||
|
* or removed.
|
||||||
|
*
|
||||||
|
* Avoids touching the local DataStore.
|
||||||
|
*
|
||||||
|
* @param perm
|
||||||
|
*/
|
||||||
|
public getAlteredPermissions(perm: string): string[] {
|
||||||
|
// clone the array, avoids altering the local dataStore
|
||||||
|
const currentPermissions = this.permissions.slice();
|
||||||
|
|
||||||
|
if (this.hasPermission(perm)) {
|
||||||
|
// remove the permission from currentPermissions-List
|
||||||
|
const indexOfPerm = currentPermissions.indexOf(perm);
|
||||||
|
if (indexOfPerm !== -1) {
|
||||||
|
currentPermissions.splice(indexOfPerm, 1);
|
||||||
|
return currentPermissions;
|
||||||
|
} else {
|
||||||
|
return currentPermissions;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
currentPermissions.push(perm);
|
||||||
|
return currentPermissions;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public hasPermission(perm: string): boolean {
|
||||||
|
return this.permissions.includes(perm);
|
||||||
|
}
|
||||||
|
|
||||||
|
public getTitle(): string {
|
||||||
|
return this.name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public updateValues(update: BaseModel): void {
|
||||||
|
console.log('ViewGroups wants to update Values with : ', update);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,16 @@
|
|||||||
|
import { TestBed, inject } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { GroupRepositoryService } from './group-repository.service';
|
||||||
|
import { E2EImportsModule } from '../../../../e2e-imports.module';
|
||||||
|
|
||||||
|
describe('GroupRepositoryService', () => {
|
||||||
|
beforeEach(() =>
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
imports: [E2EImportsModule],
|
||||||
|
providers: [GroupRepositoryService]
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should be created', inject([GroupRepositoryService], (service: GroupRepositoryService) => {
|
||||||
|
expect(service).toBeTruthy();
|
||||||
|
}));
|
||||||
|
});
|
140
client/src/app/site/users/services/group-repository.service.ts
Normal file
140
client/src/app/site/users/services/group-repository.service.ts
Normal file
@ -0,0 +1,140 @@
|
|||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
import { Observable } from 'rxjs';
|
||||||
|
|
||||||
|
import { ViewGroup } from '../models/view-group';
|
||||||
|
import { BaseRepository } from '../../base/base-repository';
|
||||||
|
import { Group } from '../../../shared/models/users/group';
|
||||||
|
import { DataStoreService } from '../../../core/services/data-store.service';
|
||||||
|
import { DataSendService } from '../../../core/services/data-send.service';
|
||||||
|
import { ConstantsService } from '../../../core/services/constants.service';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set rules to define the shape of an app permission
|
||||||
|
*/
|
||||||
|
interface AppPermission {
|
||||||
|
name: string;
|
||||||
|
permissions: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Repository service for Groups
|
||||||
|
*
|
||||||
|
* Documentation partially provided in {@link BaseRepository}
|
||||||
|
*/
|
||||||
|
@Injectable({
|
||||||
|
providedIn: 'root'
|
||||||
|
})
|
||||||
|
export class GroupRepositoryService extends BaseRepository<ViewGroup, Group> {
|
||||||
|
/**
|
||||||
|
* holds sorted permissions per app.
|
||||||
|
*/
|
||||||
|
public appPermissions: AppPermission[] = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor calls the parent constructor
|
||||||
|
* @param DS Store
|
||||||
|
* @param dataSend Sending Data
|
||||||
|
*/
|
||||||
|
public constructor(DS: DataStoreService, private dataSend: DataSendService, private constants: ConstantsService) {
|
||||||
|
super(DS, Group);
|
||||||
|
this.sortPermsPerApp();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add an entry to appPermissions
|
||||||
|
*
|
||||||
|
* @param appId number that indicates the app
|
||||||
|
* @param perm certain permission as string
|
||||||
|
* @param appName Indicates the header in the Permission Matrix
|
||||||
|
*/
|
||||||
|
private addAppPerm(appId: number, perm: string, appName: string): void {
|
||||||
|
if (!this.appPermissions[appId]) {
|
||||||
|
this.appPermissions[appId] = {
|
||||||
|
name: appName,
|
||||||
|
permissions: []
|
||||||
|
};
|
||||||
|
}
|
||||||
|
this.appPermissions[appId].permissions.push(perm);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* read the constants, add them to an array of apps
|
||||||
|
*/
|
||||||
|
private sortPermsPerApp(): void {
|
||||||
|
this.constants.get('permissions').subscribe(perms => {
|
||||||
|
perms.forEach(perm => {
|
||||||
|
// extract the apps name
|
||||||
|
const permApp = perm.value.split('.')[0];
|
||||||
|
switch (permApp) {
|
||||||
|
case 'core':
|
||||||
|
if (perm.value.indexOf('projector') > -1) {
|
||||||
|
this.addAppPerm(0, perm, 'Projector');
|
||||||
|
} else {
|
||||||
|
this.addAppPerm(6, perm, 'General');
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'agenda':
|
||||||
|
this.addAppPerm(1, perm, 'Agenda');
|
||||||
|
break;
|
||||||
|
case 'motions':
|
||||||
|
this.addAppPerm(2, perm, 'Motions');
|
||||||
|
break;
|
||||||
|
case 'assignments':
|
||||||
|
this.addAppPerm(3, perm, 'Assignments');
|
||||||
|
break;
|
||||||
|
case 'mediafiles':
|
||||||
|
this.addAppPerm(4, perm, 'Mediafiles');
|
||||||
|
break;
|
||||||
|
case 'users':
|
||||||
|
this.addAppPerm(5, perm, 'Users');
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
// plugins
|
||||||
|
const displayName = `${permApp.charAt(0).toUpperCase}${permApp.slice(1)}`;
|
||||||
|
// check if the plugin exists as app
|
||||||
|
const result = this.appPermissions.findIndex(app => {
|
||||||
|
return app.name === displayName;
|
||||||
|
});
|
||||||
|
const pluginId = result === -1 ? this.appPermissions.length : result;
|
||||||
|
this.addAppPerm(pluginId, perm, displayName);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* creates and saves a new user
|
||||||
|
*
|
||||||
|
* @param groupData form value. Usually not yet a real user
|
||||||
|
*/
|
||||||
|
public create(groupData: Partial<Group>): Observable<any> {
|
||||||
|
const newGroup = new Group();
|
||||||
|
newGroup.patchValues(groupData);
|
||||||
|
return this.dataSend.createModel(newGroup);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the given Group with the new permission
|
||||||
|
*
|
||||||
|
* @param permission the new permission
|
||||||
|
* @param viewGroup the selected Group
|
||||||
|
*/
|
||||||
|
public update(groupData: Partial<Group>, viewGroup: ViewGroup): Observable<any> {
|
||||||
|
const updateGroup = new Group();
|
||||||
|
updateGroup.patchValues(viewGroup.group);
|
||||||
|
updateGroup.patchValues(groupData);
|
||||||
|
return this.dataSend.updateModel(updateGroup, 'put');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deletes a given group
|
||||||
|
*/
|
||||||
|
public delete(viewGroup: ViewGroup): Observable<any> {
|
||||||
|
return this.dataSend.delete(viewGroup.group);
|
||||||
|
}
|
||||||
|
|
||||||
|
public createViewModel(group: Group): ViewGroup {
|
||||||
|
return new ViewGroup(group);
|
||||||
|
}
|
||||||
|
}
|
@ -46,9 +46,7 @@ export class UserRepositoryService extends BaseRepository<ViewUser, User> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @ignore
|
* Deletes a given user
|
||||||
*
|
|
||||||
* TODO: used over not-yet-existing detail view
|
|
||||||
*/
|
*/
|
||||||
public delete(viewUser: ViewUser): Observable<any> {
|
public delete(viewUser: ViewUser): Observable<any> {
|
||||||
return this.dataSend.delete(viewUser.user);
|
return this.dataSend.delete(viewUser.user);
|
||||||
@ -65,7 +63,7 @@ export class UserRepositoryService extends BaseRepository<ViewUser, User> {
|
|||||||
// collectionString of userData is still empty
|
// collectionString of userData is still empty
|
||||||
newUser.patchValues(userData);
|
newUser.patchValues(userData);
|
||||||
|
|
||||||
// if the username is not presend, delete.
|
// if the username is not present, delete.
|
||||||
// The server will generate a one
|
// The server will generate a one
|
||||||
if (!newUser.username) {
|
if (!newUser.username) {
|
||||||
delete newUser.username;
|
delete newUser.username;
|
||||||
|
@ -2,6 +2,7 @@ import { NgModule } from '@angular/core';
|
|||||||
import { Routes, RouterModule } from '@angular/router';
|
import { Routes, RouterModule } from '@angular/router';
|
||||||
import { UserListComponent } from './components/user-list/user-list.component';
|
import { UserListComponent } from './components/user-list/user-list.component';
|
||||||
import { UserDetailComponent } from './components/user-detail/user-detail.component';
|
import { UserDetailComponent } from './components/user-detail/user-detail.component';
|
||||||
|
import { GroupListComponent } from './components/group-list/group-list.component';
|
||||||
|
|
||||||
const routes: Routes = [
|
const routes: Routes = [
|
||||||
{
|
{
|
||||||
@ -12,6 +13,17 @@ const routes: Routes = [
|
|||||||
path: 'new',
|
path: 'new',
|
||||||
component: UserDetailComponent
|
component: UserDetailComponent
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: 'groups',
|
||||||
|
component: GroupListComponent
|
||||||
|
/**
|
||||||
|
* FIXME: CRITICAL:
|
||||||
|
* Refreshing the page, even while having the required permission, will navigate you back to "/"
|
||||||
|
* Makes developing protected areas impossible.
|
||||||
|
* Has the be (temporarily) removed if this page should be edited.
|
||||||
|
*/
|
||||||
|
// data: { basePerm: 'users.can_manage' }
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: ':id',
|
path: ':id',
|
||||||
component: UserDetailComponent
|
component: UserDetailComponent
|
||||||
|
@ -5,9 +5,10 @@ import { UsersRoutingModule } from './users-routing.module';
|
|||||||
import { SharedModule } from '../../shared/shared.module';
|
import { SharedModule } from '../../shared/shared.module';
|
||||||
import { UserListComponent } from './components/user-list/user-list.component';
|
import { UserListComponent } from './components/user-list/user-list.component';
|
||||||
import { UserDetailComponent } from './components/user-detail/user-detail.component';
|
import { UserDetailComponent } from './components/user-detail/user-detail.component';
|
||||||
|
import { GroupListComponent } from './components/group-list/group-list.component';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [CommonModule, UsersRoutingModule, SharedModule],
|
imports: [CommonModule, UsersRoutingModule, SharedModule],
|
||||||
declarations: [UserListComponent, UserDetailComponent]
|
declarations: [UserListComponent, UserDetailComponent, GroupListComponent]
|
||||||
})
|
})
|
||||||
export class UsersModule {}
|
export class UsersModule {}
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
{
|
{
|
||||||
"Abort": "",
|
"Abort": "",
|
||||||
"About Me": "",
|
"About Me": "",
|
||||||
"Agenda": "Tagesordnung",
|
|
||||||
"Assignments": "Wahlen",
|
|
||||||
"Category": "",
|
"Category": "",
|
||||||
"Change Password": "Passwort ändern",
|
"Change Password": "Passwort ändern",
|
||||||
"Changed version": "",
|
"Changed version": "",
|
||||||
@ -35,13 +33,11 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"FILTER": "",
|
"FILTER": "",
|
||||||
"Files": "Dateien",
|
|
||||||
"Final version": "",
|
"Final version": "",
|
||||||
"First Name": "",
|
"First Name": "",
|
||||||
"French": "Französisch",
|
"French": "Französisch",
|
||||||
"German": "Deutsch",
|
"German": "Deutsch",
|
||||||
"Groups": "",
|
"Groups": "",
|
||||||
"Home": "Startseite",
|
|
||||||
"Identifier": "",
|
"Identifier": "",
|
||||||
"Initial Password": "",
|
"Initial Password": "",
|
||||||
"Inline": "",
|
"Inline": "",
|
||||||
@ -71,7 +67,6 @@
|
|||||||
"Original version": "",
|
"Original version": "",
|
||||||
"Outside": "",
|
"Outside": "",
|
||||||
"Participant Number": "",
|
"Participant Number": "",
|
||||||
"Participants": "Teilnehmer",
|
|
||||||
"Personal Note": "",
|
"Personal Note": "",
|
||||||
"Personal note": "",
|
"Personal note": "",
|
||||||
"Prefix": "",
|
"Prefix": "",
|
||||||
@ -85,7 +80,6 @@
|
|||||||
"Reset recommendation": "",
|
"Reset recommendation": "",
|
||||||
"SORT": "",
|
"SORT": "",
|
||||||
"Selected Values": "",
|
"Selected Values": "",
|
||||||
"Settings": "Einstellungen",
|
|
||||||
"State": "",
|
"State": "",
|
||||||
"Structure Level": "",
|
"Structure Level": "",
|
||||||
"Submitters": "",
|
"Submitters": "",
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
{
|
{
|
||||||
"Abort": "",
|
"Abort": "",
|
||||||
"About Me": "",
|
"About Me": "",
|
||||||
"Agenda": "",
|
|
||||||
"Assignments": "",
|
|
||||||
"Category": "",
|
"Category": "",
|
||||||
"Change Password": "",
|
"Change Password": "",
|
||||||
"Changed version": "",
|
"Changed version": "",
|
||||||
@ -35,13 +33,11 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"FILTER": "",
|
"FILTER": "",
|
||||||
"Files": "",
|
|
||||||
"Final version": "",
|
"Final version": "",
|
||||||
"First Name": "",
|
"First Name": "",
|
||||||
"French": "",
|
"French": "",
|
||||||
"German": "",
|
"German": "",
|
||||||
"Groups": "",
|
"Groups": "",
|
||||||
"Home": "",
|
|
||||||
"Identifier": "",
|
"Identifier": "",
|
||||||
"Initial Password": "",
|
"Initial Password": "",
|
||||||
"Inline": "",
|
"Inline": "",
|
||||||
@ -71,7 +67,6 @@
|
|||||||
"Original version": "",
|
"Original version": "",
|
||||||
"Outside": "",
|
"Outside": "",
|
||||||
"Participant Number": "",
|
"Participant Number": "",
|
||||||
"Participants": "",
|
|
||||||
"Personal Note": "",
|
"Personal Note": "",
|
||||||
"Personal note": "",
|
"Personal note": "",
|
||||||
"Prefix": "",
|
"Prefix": "",
|
||||||
@ -85,7 +80,6 @@
|
|||||||
"Reset recommendation": "",
|
"Reset recommendation": "",
|
||||||
"SORT": "",
|
"SORT": "",
|
||||||
"Selected Values": "",
|
"Selected Values": "",
|
||||||
"Settings": "",
|
|
||||||
"State": "",
|
"State": "",
|
||||||
"Structure Level": "",
|
"Structure Level": "",
|
||||||
"Submitters": "",
|
"Submitters": "",
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
{
|
{
|
||||||
"Abort": "",
|
"Abort": "",
|
||||||
"About Me": "",
|
"About Me": "",
|
||||||
"Agenda": "",
|
|
||||||
"Assignments": "",
|
|
||||||
"Category": "",
|
"Category": "",
|
||||||
"Change Password": "",
|
"Change Password": "",
|
||||||
"Changed version": "",
|
"Changed version": "",
|
||||||
@ -35,13 +33,11 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"FILTER": "",
|
"FILTER": "",
|
||||||
"Files": "",
|
|
||||||
"Final version": "",
|
"Final version": "",
|
||||||
"First Name": "",
|
"First Name": "",
|
||||||
"French": "",
|
"French": "",
|
||||||
"German": "",
|
"German": "",
|
||||||
"Groups": "",
|
"Groups": "",
|
||||||
"Home": "",
|
|
||||||
"Identifier": "",
|
"Identifier": "",
|
||||||
"Initial Password": "",
|
"Initial Password": "",
|
||||||
"Inline": "",
|
"Inline": "",
|
||||||
@ -71,7 +67,6 @@
|
|||||||
"Original version": "",
|
"Original version": "",
|
||||||
"Outside": "",
|
"Outside": "",
|
||||||
"Participant Number": "",
|
"Participant Number": "",
|
||||||
"Participants": "",
|
|
||||||
"Personal Note": "",
|
"Personal Note": "",
|
||||||
"Personal note": "",
|
"Personal note": "",
|
||||||
"Prefix": "",
|
"Prefix": "",
|
||||||
@ -85,7 +80,6 @@
|
|||||||
"Reset recommendation": "",
|
"Reset recommendation": "",
|
||||||
"SORT": "",
|
"SORT": "",
|
||||||
"Selected Values": "",
|
"Selected Values": "",
|
||||||
"Settings": "",
|
|
||||||
"State": "",
|
"State": "",
|
||||||
"Structure Level": "",
|
"Structure Level": "",
|
||||||
"Submitters": "",
|
"Submitters": "",
|
||||||
|
Loading…
Reference in New Issue
Block a user