More work on perms page

This commit is contained in:
simon987
2019-02-17 14:09:52 -05:00
parent b936513eb9
commit 94c3ce3267
30 changed files with 425 additions and 70 deletions

View File

@@ -69,10 +69,14 @@ export class ApiService {
return this.http.get(this.url + `/project/access_list/${project}`)
}
getAllManagers() {
getManagerList() {
return this.http.get(this.url + "/manager/list")
}
getManagerListWithRoleOn(project: number) {
return this.http.get(this.url + "/manager/list_for_project/" + project)
}
promote(managerId: number) {
return this.http.get(this.url + `/manager/promote/${managerId}`)
}
@@ -89,4 +93,9 @@ export class ApiService {
return this.http.post(this.url + `/project/reject_request/${pid}/${wid}`, null)
}
setManagerRoleOnProject(pid: number, role: number, manager: number) {
return this.http.post(this.url + `/manager/set_role_for_project/${pid}`,
{"role": role, "manager": manager})
}
}

View File

@@ -1,7 +1,3 @@
.nav-spacer {
flex: 1 1 auto;
}
.icon {
padding: 0 14px;
}

View File

@@ -38,7 +38,7 @@
>{{"nav.manager_list" | translate}}</button>
</mat-menu>
</div>
<span class="nav-spacer"></span>
<span class="spacer"></span>
<button mat-button [class.mat-accent]="router.url == '/login'"
class="nav-link" *ngIf="!authService.account"
[routerLink]="'login'">{{"nav.login" | translate}}</button>

View File

@@ -50,6 +50,8 @@ import {WorkerDashboardComponent} from './worker-dashboard/worker-dashboard.comp
import {ProjectPermsComponent} from './project-perms/project-perms.component';
import {ManagerListComponent} from './manager-list/manager-list.component';
import {ProjectSelectComponent} from './project-select/project-select.component';
import {ManagerSelectComponent} from './manager-select/manager-select.component';
import {ProjectIconComponent} from './project-icon/project-icon.component';
export function createTranslateLoader(http: HttpClient) {
@@ -72,6 +74,8 @@ export function createTranslateLoader(http: HttpClient) {
ProjectPermsComponent,
ManagerListComponent,
ProjectSelectComponent,
ManagerSelectComponent,
ProjectIconComponent,
],
imports: [
BrowserModule,

View File

@@ -3,6 +3,7 @@ import {ApiService} from "./api.service";
import {Credentials} from "./models/credentials";
import {MessengerService} from "./messenger.service";
import {Router} from "@angular/router";
import {Manager} from "./models/manager";
@Injectable({
providedIn: 'root'

View File

@@ -6,13 +6,20 @@
<mat-card-content>
<mat-form-field appearance="outline">
<mat-label>{{"project.name" | translate}}</mat-label>
<input type="text" matInput [(ngModel)]="project.name" [placeholder]="'project.name' | translate">
<input type="text" matInput [(ngModel)]="project.name" [placeholder]="'project.name' | translate"
required>
</mat-form-field>
<mat-form-field appearance="outline">
<mat-label>{{ "project.clone_url" | translate}}</mat-label>
<input type="text" matInput [(ngModel)]="project.clone_url" (change)="cloneUrlChange()"
[placeholder]="'project.clone_url_placeholder' | translate">
[placeholder]="'project.clone_url_placeholder' | translate" required>
</mat-form-field>
<mat-form-field appearance="outline">
<mat-label>{{"project.version" | translate}}</mat-label>
<input type="text" matInput [(ngModel)]="project.version" name="version"
[placeholder]="'project.version'|translate"
>
</mat-form-field>
<mat-form-field appearance="outline">
<mat-label>{{ "project.git_repo" | translate }}</mat-label>

View File

@@ -55,7 +55,7 @@ export class ManagerListComponent implements OnInit {
}
private getManagers() {
this.apiService.getAllManagers()
this.apiService.getManagerList()
.subscribe(data => {
this.data.data = data["content"]["managers"]
},

View File

@@ -0,0 +1,3 @@
.mat-form-field {
width: 100%;
}

View File

@@ -0,0 +1,16 @@
<mat-form-field appearance="outline" style="margin-top: 1em">
<mat-label>{{"project.manager_select" | translate}}</mat-label>
<mat-select [(ngModel)]="manager" (selectionChange)="managerChange.emit($event.value)"
[placeholder]="'perms.manager_select' | translate"
(opened)="loadManagerList()">
<mat-select-trigger></mat-select-trigger>
<mat-option disabled *ngIf="managerList == undefined">
{{"project_select.loading" | translate}}
</mat-option>
<mat-option *ngFor="let m of managerList" [value]="m">
<mat-icon *ngIf="m.tracker_admin">supervisor_account</mat-icon>
<mat-icon *ngIf="!m.tracker_admin">person</mat-icon>
{{m.username}}
</mat-option>
</mat-select>
</mat-form-field>

View File

@@ -0,0 +1,30 @@
import {Component, EventEmitter, OnInit, Output} from '@angular/core';
import {ApiService} from "../api.service";
import {Manager} from "../models/manager";
@Component({
selector: 'manager-select',
templateUrl: './manager-select.component.html',
styleUrls: ['./manager-select.component.css']
})
export class ManagerSelectComponent implements OnInit {
manager: Manager;
managerList: Manager[];
@Output()
managerChange = new EventEmitter<Manager>();
constructor(private apiService: ApiService) {
}
ngOnInit() {
}
loadManagerList() {
this.apiService.getManagerList()
.subscribe(data => this.managerList = data["content"]["managers"])
}
}

View File

@@ -1,6 +1,54 @@
interface Manager {
export interface Manager {
id: number;
username: string
tracker_admin: boolean
register_time: number
}
export class ManagerRoleOnProject {
manager: Manager;
role: number;
public static fromEntity(data: { role: number, manager: Manager }): ManagerRoleOnProject {
let m = new ManagerRoleOnProject();
m.role = data.role;
m.manager = data.manager;
return m;
}
get readRole(): boolean {
return (this.role & 1) != 0
}
set readRole(role: boolean) {
if (role) {
this.role |= 1
} else {
this.role &= ~1
}
}
get editRole(): boolean {
return (this.role & 2) != 0
}
set editRole(role: boolean) {
if (role) {
this.role |= 2
} else {
this.role &= ~2
}
}
get manageRole(): boolean {
return (this.role & 4) != 0
}
set manageRole(role: boolean) {
if (role) {
this.role |= 4
} else {
this.role &= ~4
}
}
}

View File

@@ -5,6 +5,8 @@ import {ActivatedRoute} from "@angular/router";
import {Chart} from "chart.js";
import {AssignedTasks, MonitoringSnapshot} from "../models/monitoring";
import {TranslateService} from "@ngx-translate/core";
import {MessengerService} from "../messenger.service";
@Component({
@@ -41,7 +43,10 @@ export class ProjectDashboardComponent implements OnInit {
lastSnapshot: MonitoringSnapshot;
assignees: AssignedTasks[];
constructor(private apiService: ApiService, private route: ActivatedRoute) {
constructor(private apiService: ApiService,
private route: ActivatedRoute,
private translate: TranslateService,
private messenger: MessengerService) {
}
ngOnInit(): void {
@@ -305,28 +310,32 @@ export class ProjectDashboardComponent implements OnInit {
private getProject() {
this.apiService.getProject(this.projectId).subscribe((data: any) => {
this.project = data.content.project;
this.project = data.content.project;
this.apiService.getMonitoringSnapshots(60, this.projectId)
.subscribe((data: any) => {
this.snapshots = data.content.snapshots;
this.lastSnapshot = this.snapshots ? this.snapshots.sort((a, b) => {
return b.time_stamp - a.time_stamp
})[0] : null;
this.apiService.getMonitoringSnapshots(60, this.projectId)
.subscribe((data: any) => {
this.snapshots = data.content.snapshots;
this.lastSnapshot = this.snapshots ? this.snapshots.sort((a, b) => {
return b.time_stamp - a.time_stamp
})[0] : null;
this.setupTimeline();
this.setupStatusPie();
this.setupTimeline();
this.setupStatusPie();
if (!this.snapshots) {
return
}
if (!this.snapshots) {
return
}
this.apiService.getAssigneeStats(this.projectId)
.subscribe((data: any) => {
this.assignees = data.content.assignees;
this.setupAssigneesPie();
});
})
})
this.apiService.getAssigneeStats(this.projectId)
.subscribe((data: any) => {
this.assignees = data.content.assignees;
this.setupAssigneesPie();
});
})
},
error => {
this.translate.get("messenger.unauthorized").subscribe(t =>
this.messenger.show(t))
})
}
}

View File

@@ -0,0 +1,3 @@
<mat-icon *ngIf="project.public" [title]="'project.public' | translate">public</mat-icon>
<mat-icon *ngIf="!project.public && !project.hidden" [title]="'project.private'|translate">lock</mat-icon>
<mat-icon *ngIf="project.hidden" [title]="'project.hidden'|translate">visibility_off</mat-icon>

View File

@@ -0,0 +1,21 @@
import {Component, Input, OnInit} from '@angular/core';
import {Project} from "../models/project";
@Component({
selector: 'project-icon',
templateUrl: './project-icon.component.html',
styleUrls: ['./project-icon.component.css']
})
export class ProjectIconComponent implements OnInit {
@Input()
project: Project;
constructor() {
}
ngOnInit() {
}
}

View File

@@ -2,6 +2,6 @@ button {
margin-right: 15px;
}
mat-panel-title > mat-icon {
mat-panel-title > project-icon {
margin-right: 1em;
}

View File

@@ -13,11 +13,7 @@
<mat-expansion-panel *ngFor="let project of projects" style="margin-top: 1em">
<mat-expansion-panel-header>
<mat-panel-title>
<mat-icon *ngIf="project.public" [title]="'project.public' | translate">public</mat-icon>
<mat-icon *ngIf="!project.public && !project.hidden" [title]="'project.private'|translate">
lock
</mat-icon>
<mat-icon *ngIf="project.hidden" [title]="'project.hidden'|translate">block</mat-icon>
<project-icon [project]="project"></project-icon>
<span style="width: 3em">{{project.id}}</span>{{project.name}}
</mat-panel-title>
<mat-panel-description>{{project.motd}}</mat-panel-description>

View File

@@ -6,3 +6,7 @@ button {
.request {
color: #757575;
}
mat-checkbox {
margin-right: 10px;
}

View File

@@ -9,8 +9,9 @@
<mat-card-subtitle>{{"perms.subtitle" | translate}}</mat-card-subtitle>
</mat-card-header>
<mat-card-content>
<mat-list *ngIf="!unauthorized">
<mat-card-content *ngIf="!unauthorized || !auth.account">
<h3>{{"perms.workers" | translate}}</h3>
<mat-list *ngIf="accesses && accesses.length>0">
<mat-list-item *ngFor="let wa of accesses" [class.request]="wa.request">
<mat-icon mat-list-icon *ngIf="wa.submit" [title]="'perms.assign'|translate">library_add</mat-icon>
<mat-icon mat-list-icon *ngIf="wa.assign" [title]="'perms.submit'|translate">get_app</mat-icon>
@@ -32,7 +33,33 @@
</button>
</mat-list-item>
</mat-list>
<p *ngIf="unauthorized" class="unauthorized">
<p *ngIf="!accesses || accesses.length == 0">{{"perms.no_workers"|translate}}</p>
<h3>{{"perms.managers" | translate}}</h3>
<manager-select (managerChange)="onSelectManager($event)"></manager-select>
<mat-list>
<mat-list-item *ngFor="let m of managerRoles">
<mat-icon *ngIf="m.manager.tracker_admin">supervisor_account</mat-icon>
<mat-icon *ngIf="!m.manager.tracker_admin">person</mat-icon>
{{m.manager.username}}
<span class="spacer"></span>
<mat-checkbox [(ngModel)]="m.readRole"
(change)="onRoleChange(m)"
[disabled]="m.manager.id==auth.account.id"
>{{"perms.read"|translate}}</mat-checkbox>
<mat-checkbox [(ngModel)]="m.editRole"
(change)="onRoleChange(m)"
[disabled]="m.manager.id==auth.account.id"
>{{"perms.edit"|translate}}</mat-checkbox>
<mat-checkbox [(ngModel)]="m.manageRole"
(change)="onRoleChange(m)"
[disabled]="m.manager.id==auth.account.id"
>{{"perms.manage"|translate}}</mat-checkbox>
</mat-list-item>
</mat-list>
</mat-card-content>
<mat-card-content *ngIf="unauthorized">
<p class="unauthorized">
<mat-icon>block</mat-icon>
{{"perms.unauthorized" | translate}}
</p>

View File

@@ -1,12 +1,14 @@
import {Component, OnInit} from '@angular/core';
import {ApiService} from "../api.service";
import {Project} from "../models/project";
import {ActivatedRoute, Router} from "@angular/router";
import {MessengerService} from "../messenger.service";
import {TranslateService} from "@ngx-translate/core";
import {ActivatedRoute} from "@angular/router";
import * as moment from "moment"
import {WorkerAccess} from "../models/worker-access";
import {AuthService} from "../auth.service";
import {Manager, ManagerRoleOnProject} from "../models/manager";
import {MessengerService} from "../messenger.service";
import {TranslateService} from "@ngx-translate/core";
@Component({
selector: 'app-project-perms',
@@ -17,14 +19,15 @@ export class ProjectPermsComponent implements OnInit {
constructor(private apiService: ApiService,
private route: ActivatedRoute,
private messengerService: MessengerService,
private translate: TranslateService,
private router: Router) {
private messenger: MessengerService,
public auth: AuthService) {
}
project: Project;
private projectId: number;
accesses: WorkerAccess[];
managerRoles: ManagerRoleOnProject;
unauthorized: boolean = false;
moment = moment;
@@ -33,17 +36,24 @@ export class ProjectPermsComponent implements OnInit {
this.projectId = params["id"];
this.getProject();
this.getProjectAccesses();
this.getProjectManagers();
})
}
public acceptRequest(wa: WorkerAccess) {
this.apiService.acceptWorkerAccessRequest(wa.worker.id, this.projectId)
.subscribe(() => this.getProjectAccesses())
.subscribe(() => {
this.getProjectAccesses();
this.translate.get("perms.set").subscribe(t => this.messenger.show(t));
})
}
public rejectRequest(wa: WorkerAccess) {
this.apiService.rejectWorkerAccessRequest(wa.worker.id, this.projectId)
.subscribe(() => this.getProjectAccesses())
.subscribe(() => {
this.getProjectAccesses();
this.translate.get("perms.set").subscribe(t => this.messenger.show(t));
})
}
private getProject() {
@@ -64,7 +74,31 @@ export class ProjectPermsComponent implements OnInit {
})
}
private getProjectManagers() {
this.apiService.getManagerListWithRoleOn(this.projectId)
.subscribe(data => {
this.managerRoles = data["content"]["managers"].map(d =>
ManagerRoleOnProject.fromEntity(d))
})
}
public refresh() {
this.getProjectAccesses()
this.getProjectAccesses();
this.getProjectManagers();
}
public onSelectManager(manager: Manager) {
if (manager.id != this.auth.account.id) {
this.apiService.setManagerRoleOnProject(this.projectId, 1, manager.id)
.subscribe(() => this.refresh())
}
}
public onRoleChange(manager: ManagerRoleOnProject) {
this.apiService.setManagerRoleOnProject(this.projectId, manager.role, manager.manager.id)
.subscribe(() => {
this.refresh();
this.translate.get("perms.set").subscribe(t => this.messenger.show(t));
})
}
}

View File

@@ -11,8 +11,7 @@
{{"project_select.none" | translate}}
</mat-option>
<mat-option *ngFor="let p of projectList" [value]="p">
<mat-icon *ngIf="p.public">public</mat-icon>
<mat-icon *ngIf="!p.public">lock</mat-icon>
<project-icon [project]="p"></project-icon>
<span style="width: 3em; display: inline-block">{{p.id}}</span>
{{p.name}}
</mat-option>

View File

@@ -5,25 +5,37 @@
<mat-card-content>
<form (ngSubmit)="onSubmit()" *ngIf="project != undefined" id="uf">
<mat-form-field appearance="outline">
<input type="text" matInput [(ngModel)]="project.name" name="name" placeholder="Name">
<mat-label>{{"project.name" | translate}}</mat-label>
<input type="text" matInput [(ngModel)]="project.name" name="name"
[placeholder]="'project.name'|translate"
>
</mat-form-field>
<mat-form-field appearance="outline">
<textarea matInput [(ngModel)]="project.motd" placeholder="Message of the day"
<mat-label>{{"project.motd" | translate}}</mat-label>
<textarea matInput [(ngModel)]="project.motd"
[placeholder]="'project.motd'|translate"
name="motd"></textarea>
</mat-form-field>
<mat-form-field appearance="outline">
<mat-label>{{"project.clone_url" | translate}}</mat-label>
<input type="text" matInput [(ngModel)]="project.clone_url" name="clone_url"
placeholder="Git clone url">
[placeholder]="'project.clone_url'|translate"
>
</mat-form-field>
<mat-form-field appearance="outline">
<mat-label>{{"project.version" | translate}}</mat-label>
<input type="text" matInput [(ngModel)]="project.version" name="version"
[placeholder]="'project.version'|translate"
>
</mat-form-field>
<mat-form-field appearance="outline">
<mat-label>{{"project.git_repo" | translate}}</mat-label>
<input type="text" matInput [(ngModel)]="project.git_repo" name="git_repo"
placeholder='Full repository name (e.g. "simon987/task_tracker")'>
<mat-hint align="start">Changes on the <strong>master</strong> branch will be tracked if webhooks
are
enabled
</mat-hint>
[placeholder]="'project.git_repo_placeholder'|translate"
>
<mat-hint align="start">{{'project.git_repo_hint'|translate}}</mat-hint>
</mat-form-field>
<project-select [(project)]="selectedProject"></project-select>
</form>

View File

@@ -51,7 +51,7 @@
},
"project": {
"name": "Project name",
"clone_url": "Clone url",
"clone_url": "Git clone url",
"clone_url_placeholder": "Example: \"https://github.com/simon987/task_tracker\"",
"git_repo_placeholder": "Example: \"simon987/task_tracker\"",
"git_repo_hint": "Changes in the master branch will be tracked if webhooks are enabled",
@@ -65,7 +65,9 @@
"motd": "Message of the day",
"update": "Edit",
"perms": "Permissions",
"chain": "Chain tasks to"
"chain": "Chain tasks to",
"manager_select": "Give access to manager",
"version": "Git version (commit hash)"
},
"dashboard": {
"title": "Dashboard for",
@@ -107,7 +109,14 @@
"refresh": "Refresh",
"pending": "(Pending)",
"assign": "Assign",
"submit": "Submit"
"submit": "Submit",
"read": "Read",
"edit": "Edit",
"manage": "Manage permissions",
"set": "Changes saved",
"workers": "Workers",
"no_workers": "No workers have explicit access to this project",
"managers": "Managers"
},
"messenger": {
"close": "Close",

View File

@@ -66,7 +66,9 @@
"motd": "Message du jour",
"update": "Mettre à jour",
"perms": "Permissions",
"chain": "Enchainer les tâches vers"
"chain": "Enchainer les tâches vers",
"manager_select": "Donner accès à",
"version": "Version git (hash du commit)"
},
"dashboard": {
"title": "Tableau de bord pour ",
@@ -100,7 +102,7 @@
},
"perms": {
"title": "Permissions du projet",
"subtitle": "Les Workers doivent faire un requête d'acces pour pouvoir travailler sur les projets privés",
"subtitle": "Les Workers doivent faire une requête d'acces pour pouvoir travailler sur les projets privés",
"unauthorized": "Vous devez avoir ROLE_GESTION_ACCES sur ce project pour accéder à cette page",
"created": "Créé le",
"grant": "Accepter la requête",
@@ -109,7 +111,14 @@
"refresh": "Refraichir",
"pending": "(En attente)",
"assign": "Assigner",
"submit": "Soumettre"
"submit": "Soumettre",
"read": "Lecture",
"edit": "Modification",
"manage": "Gestion des permissions",
"set": "Changements enregistrés",
"workers": "Workers",
"no_workers": "Aucun Worker n'a explicitement accès à ce projet",
"managers": "Managers"
},
"messenger": {
"close": "Fermer",

View File

@@ -99,3 +99,12 @@ pre {
color: #ff4081;
}
.spacer {
flex: 1 1 auto;
}
.mat-list-item-content > mat-icon {
margin-right: 15px;
}