Some work on permissions (lacks tests)

This commit is contained in:
simon987
2019-02-13 21:54:18 -05:00
parent 4edf354f8d
commit c3e5bd77f7
34 changed files with 650 additions and 273 deletions

View File

@@ -65,4 +65,8 @@ export class ApiService {
return this.http.get(this.url + `/worker/stats`, this.options)
}
getProjectAccessRequests(project: number) {
return this.http.get(this.url + `/project/requests/${project}`)
}
}

View File

@@ -11,6 +11,7 @@ import {TranslateService} from "@ngx-translate/core";
import {LoginComponent} from "./login/login.component";
import {AccountDetailsComponent} from "./account-details/account-details.component";
import {WorkerDashboardComponent} from "./worker-dashboard/worker-dashboard.component";
import {ProjectPermsComponent} from "./project-perms/project-perms.component";
const routes: Routes = [
{path: "log", component: LogsComponent},
@@ -19,6 +20,7 @@ const routes: Routes = [
{path: "projects", component: ProjectListComponent},
{path: "project/:id", component: ProjectDashboardComponent},
{path: "project/:id/update", component: UpdateProjectComponent},
{path: "project/:id/perms", component: ProjectPermsComponent},
{path: "new_project", component: CreateProjectComponent},
{path: "workers", component: WorkerDashboardComponent}
];

View File

@@ -6,10 +6,11 @@
[routerLink]="'log'">{{"nav.logs" | translate}}</button>
<button mat-button [class.mat-accent]="router.url == '/projects'" class="nav-link"
[routerLink]="'projects'">{{"nav.project_list" | translate}}</button>
<button mat-button [class.mat-accent]="router.url == '/new_project'" class="nav-link"
[routerLink]="'new_project'">{{"nav.new_project" | translate}}</button>
<button mat-button [class.mat-accent]="router.url == '/workers'" class="nav-link"
[routerLink]="'workers'">{{"nav.worker_dashboard" | translate}}</button>
<button mat-button [class.mat-accent]="router.url == '/new_project'" class="nav-link"
[routerLink]="'new_project'"
*ngIf="authService.logged">{{"nav.new_project" | translate}}</button>
</div>
<div class="small-nav">
<button mat-button [matMenuTriggerFor]="smallNav">
@@ -22,10 +23,11 @@
[routerLink]="'log'">{{"nav.logs" | translate}}</button>
<button mat-menu-item [class.mat-accent]="router.url == '/projects'" class="nav-link"
[routerLink]="'projects'">{{"nav.project_list" | translate}}</button>
<button mat-menu-item [class.mat-accent]="router.url == '/new_project'" class="nav-link"
[routerLink]="'new_project'">{{"nav.new_project" | translate}}</button>
<button mat-menu-item [class.mat-accent]="router.url == '/workers'" class="nav-link"
[routerLink]="'workers'">{{"nav.worker_dashboard" | translate}}</button>
<button mat-menu-item [class.mat-accent]="router.url == '/new_project'" class="nav-link"
[routerLink]="'new_project'"
*ngIf="authService.logged">{{"nav.new_project" | translate}}</button>
</mat-menu>
</div>
<span class="nav-spacer"></span>

View File

@@ -20,7 +20,7 @@ export class AppComponent {
];
constructor(private translate: TranslateService,
private router: Router,
public router: Router,
public authService: AuthService) {
translate.addLangs([

View File

@@ -47,6 +47,7 @@ import {TranslatedPaginator} from "./TranslatedPaginatorConfiguration";
import {LoginComponent} from './login/login.component';
import {AccountDetailsComponent} from './account-details/account-details.component';
import {WorkerDashboardComponent} from './worker-dashboard/worker-dashboard.component';
import {ProjectPermsComponent} from './project-perms/project-perms.component';
export function createTranslateLoader(http: HttpClient) {
@@ -66,6 +67,7 @@ export function createTranslateLoader(http: HttpClient) {
LoginComponent,
AccountDetailsComponent,
WorkerDashboardComponent,
ProjectPermsComponent,
],
imports: [
BrowserModule,

View File

@@ -10,6 +10,7 @@ import {Router} from "@angular/router";
export class AuthService {
account: Manager;
logged: boolean;
constructor(private apiService: ApiService,
private messengerService: MessengerService,
@@ -17,6 +18,7 @@ export class AuthService {
this.apiService.getAccountDetails()
.subscribe((data: any) => {
this.account = data.manager;
this.logged = data.logged_in;
})
}
@@ -27,6 +29,7 @@ export class AuthService {
this.apiService.getAccountDetails()
.subscribe((data: any) => {
this.account = data.manager;
this.logged = true;
this.router.navigateByUrl("/account");
})
},
@@ -42,7 +45,8 @@ export class AuthService {
.subscribe(
() => {
this.account = null;
this.router.navigateByUrl("");
this.logged = false;
this.router.navigateByUrl("login");
},
error => {
console.log(error);

View File

@@ -3,40 +3,47 @@
<mat-tab-group>
<mat-tab [label]="'login.title' | translate" class="pad">
<mat-form-field appearance="outline">
<mat-label>{{"login.username" | translate}}</mat-label>
<input type="text" matInput [(ngModel)]="credentials.username">
</mat-form-field>
<form (ngSubmit)="login()">
<mat-form-field appearance="outline">
<mat-label>{{"login.username" | translate}}</mat-label>
<input type="text" matInput [(ngModel)]="credentials.username"
[ngModelOptions]="{standalone: true}">
</mat-form-field>
<mat-form-field appearance="outline">
<mat-label>{{ "login.password" | translate}}</mat-label>
<input type="password" matInput [(ngModel)]="credentials.password">
</mat-form-field>
<mat-form-field appearance="outline">
<mat-label>{{ "login.password" | translate}}</mat-label>
<input type="password" matInput [(ngModel)]="credentials.password"
[ngModelOptions]="{standalone: true}">
</mat-form-field>
<button mat-raised-button color="primary"
(click)="login()">{{"login.login" | translate}}</button>
<button mat-raised-button color="primary"
type="submit">{{"login.login" | translate}}</button>
</form>
</mat-tab>
<mat-tab [label]="'create_account.title' | translate" class="pad">
<mat-form-field appearance="outline" [hideRequiredMarker]="true">
<mat-label>{{"login.username" | translate}}</mat-label>
<mat-hint align="end">{{credentials.username?.length || 0}}/16</mat-hint>
<input maxlength="16" type="text" matInput [(ngModel)]="credentials.username" name="username"
required>
</mat-form-field>
<form (ngSubmit)="register()">
<mat-form-field appearance="outline" [hideRequiredMarker]="true">
<mat-label>{{"login.username" | translate}}</mat-label>
<mat-hint align="end">{{credentials.username?.length || 0}}/16</mat-hint>
<input maxlength="16" type="text" matInput [(ngModel)]="credentials.username" name="username"
[ngModelOptions]="{standalone: true}" required>
</mat-form-field>
<mat-form-field appearance="outline" [hideRequiredMarker]="true">
<mat-label>{{ "login.password" | translate}}</mat-label>
<input type="password" matInput [(ngModel)]="credentials.password" name="password" required>
</mat-form-field>
<mat-form-field appearance="outline" [hideRequiredMarker]="true">
<mat-label>{{ "login.password" | translate}}</mat-label>
<input type="password" matInput [(ngModel)]="credentials.password" name="password"
[ngModelOptions]="{standalone: true}">
</mat-form-field>
<mat-form-field appearance="outline">
<mat-label>{{ "login.repeat_password" | translate}}</mat-label>
<input type="password" matInput [(ngModel)]="credentials.repeatPassword" name="password2"
>
</mat-form-field>
<mat-form-field appearance="outline">
<mat-label>{{ "login.repeat_password" | translate}}</mat-label>
<input type="password" matInput [(ngModel)]="credentials.repeatPassword" name="password2"
[ngModelOptions]="{standalone: true}" required>
</mat-form-field>
<button mat-raised-button color="primary" [disabled]="!canCreate()"
(click)="register()">{{"create_account.create" | translate}}</button>
<button mat-raised-button color="primary" [disabled]="!canCreate()"
type="submit">{{"create_account.create" | translate}}</button>
</form>
</mat-tab>
</mat-tab-group>
</mat-card>

View File

@@ -3,6 +3,7 @@ import {MessengerService} from "../messenger.service";
import {MessengerState} from "./messenger";
import {Subscription} from "rxjs";
import {MatSnackBar, MatSnackBarConfig} from "@angular/material";
import {TranslateService} from "@ngx-translate/core";
@Component({
selector: 'messenger-snack-bar',
@@ -13,7 +14,10 @@ export class SnackBarComponent implements OnInit {
private subscription: Subscription;
constructor(private messengerService: MessengerService, private snackBar: MatSnackBar) {
constructor(
private messengerService: MessengerService,
private snackBar: MatSnackBar,
private translate: TranslateService) {
}
@@ -23,9 +27,11 @@ export class SnackBarComponent implements OnInit {
if (state.hidden) {
this.snackBar.dismiss();
} else {
this.snackBar.open(state.message, "Close", <MatSnackBarConfig>{
duration: 10 * 1000,
})
this.translate.get("messenger.close")
.subscribe(t =>
this.snackBar.open(state.message, t, <MatSnackBarConfig>{
duration: 10 * 1000,
}))
}
});
}

View File

@@ -1,3 +1,5 @@
interface Manager {
id: number;
username: string
website_admin: boolean;
}

View File

@@ -0,0 +1,6 @@
export interface Worker {
id: number
alias: string
created: number
secret: string
}

View File

@@ -56,6 +56,8 @@
<mat-card-actions>
<button mat-raised-button color="primary" *ngIf="project"
[routerLink]="'/project/' + project.id + '/update'">{{"project.update" | translate}}</button>
<button mat-raised-button color="primary" *ngIf="project"
[routerLink]="'/project/' + project.id + '/perms'">{{"project.perms" | translate}}</button>
</mat-card-actions>
</mat-card>
</div>

View File

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

View File

@@ -7,16 +7,22 @@
<mat-accordion>
<mat-expansion-panel *ngFor="let project of projects">
<mat-expansion-panel-header>
<mat-panel-title><span style="width: 3em">{{project.id}}</span>{{project.name}}
<mat-panel-title>
<mat-icon *ngIf="project.public">public</mat-icon>
<mat-icon *ngIf="!project.public">lock</mat-icon>
<span style="width: 3em">{{project.id}}</span>{{project.name}}
</mat-panel-title>
<mat-panel-description>{{project.motd}}</mat-panel-description>
</mat-expansion-panel-header>
<pre>{{project | json}}</pre>
<div>
<button mat-raised-button [routerLink]="'/project/' + project.id">
<button mat-raised-button color="primary" [routerLink]="'/project/' + project.id">
<mat-icon>timeline</mat-icon>{{"projects.dashboard" | translate}}</button>
<button mat-raised-button [routerLink]="'/project/' + project.id + '/update'">
<button mat-raised-button color="primary" [routerLink]="'/project/' + project.id + '/update'">
<mat-icon>build</mat-icon>{{"project.update" | translate}}</button>
<button mat-raised-button color="primary" [routerLink]="'/project/' + project.id + '/perms'">
<mat-icon>perm_identity</mat-icon>
{{"project.perms" | translate}}</button>
</div>
</mat-expansion-panel>
<span *ngIf="projects && projects.length == 0">

View File

@@ -0,0 +1,15 @@
.unauthorized {
text-align: center;
color: #616161;
min-height: 3em;
margin-top: 2em !important;
}
.text-mono {
font-family: Hack, Courier, "Courier New", monospace;
color: #ff4081;
}
button {
margin-left: 15px;
}

View File

@@ -0,0 +1,37 @@
<div class="container">
<mat-card class="mat-elevation-z8">
<button mat-button [title]="'perms.refresh' | translate" style="float:right"
(click)="refresh()">
<mat-icon>refresh</mat-icon>
</button>
<mat-card-header>
<mat-card-title>{{"perms.title" | translate}}</mat-card-title>
<mat-card-subtitle>{{"perms.subtitle" | translate}}</mat-card-subtitle>
</mat-card-header>
<mat-card-content>
<mat-list *ngIf="!unauthorized">
<mat-list-item *ngFor="let w of requests">
<mat-icon mat-list-icon>person_add</mat-icon>
<h4 mat-line>{{w.alias}}</h4>
<div mat-line>
Id=<span class="text-mono">{{w.id}}</span>, {{"perms.created" | translate}}
<span class="text-mono">{{moment.unix(w.created).format("YYYY-MM-DD HH:mm:ss UTC")}}</span>
</div>
<span style="flex: 1 1 auto;"></span>
<button mat-raised-button color="primary" [title]="'perms.grant' | translate">
<mat-icon>check</mat-icon>
</button>
<button mat-raised-button color="warn" [title]="'perms.reject' | translate">
<mat-icon>close</mat-icon>
</button>
</mat-list-item>
</mat-list>
<p *ngIf="unauthorized" class="unauthorized">
<mat-icon>block</mat-icon>
{{"perms.unauthorized" | translate}}
</p>
</mat-card-content>
</mat-card>
</div>

View File

@@ -0,0 +1,60 @@
import {Worker} from "../models/worker"
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 * as moment from "moment"
@Component({
selector: 'app-project-perms',
templateUrl: './project-perms.component.html',
styleUrls: ['./project-perms.component.css']
})
export class ProjectPermsComponent implements OnInit {
constructor(private apiService: ApiService,
private route: ActivatedRoute,
private messengerService: MessengerService,
private translate: TranslateService,
private router: Router) {
}
project: Project;
private projectId: number;
requests: Worker[];
unauthorized: boolean = false;
moment = moment;
ngOnInit() {
this.route.params.subscribe(params => {
this.projectId = params["id"];
this.getProject();
this.getProjectRequests();
})
}
private getProject() {
this.apiService.getProject(this.projectId).subscribe(data => {
this.project = data["project"]
})
}
private getProjectRequests() {
this.apiService.getProjectAccessRequests(this.projectId).subscribe(
data => {
this.requests = data["requests"]
},
error => {
if (error && (error.status == 401 || error.status == 403)) {
this.unauthorized = true;
}
})
}
private refresh() {
this.getProjectRequests()
}
}

View File

@@ -29,16 +29,7 @@ export class UpdateProjectComponent implements OnInit {
private getProject() {
this.apiService.getProject(this.projectId).subscribe(data => {
this.project = <Project>{
id: data["project"]["id"],
name: data["project"]["name"],
clone_url: data["project"]["clone_url"],
git_repo: data["project"]["git_repo"],
motd: data["project"]["motd"],
priority: data["project"]["priority"],
version: data["project"]["version"],
public: data["project"]["public"],
}
this.project = data["project"]
})
}

View File

@@ -57,7 +57,8 @@
"create": "Create",
"git_repo": "Git repository name",
"motd": "Message of the day",
"update": "Edit"
"update": "Edit",
"perms": "Permissions"
},
"dashboard": {
"title": "Dashboard for",
@@ -86,5 +87,18 @@
"workers": {
"title": "Completed tasks per worker",
"subtitle": "Real-time data for all projects"
},
"perms": {
"title": "Project permissions",
"subtitle": "Workers need to request access to work on private projects",
"unauthorized": "You need ROLE_MANAGE_ACCESS on this project to access this page",
"created": "Created on",
"grant": "Accept request",
"reject": "Deny request",
"refresh": "Refresh"
},
"messenger": {
"close": "Close",
"unauthorized": "Unauthorized"
}
}

View File

@@ -58,7 +58,8 @@
"public": "Publique",
"create": "Créer",
"motd": "Message du jour",
"update": "Mettre à jour"
"update": "Mettre à jour",
"perms": "Permissions"
},
"dashboard": {
"title": "Tableau de bord pour ",
@@ -88,6 +89,19 @@
"workers": {
"title": "Tâches complétés par worker",
"subtitle": "Données en temps réél pour tous les projets"
},
"perms": {
"title": "Permissions du projet",
"subtitle": "Les Workers doivent faire un 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",
"reject": "Rejeter la requête",
"refresh": "Refraichir"
},
"messenger": {
"close": "Fermer",
"unauthorized": "Non autorisé"
}
}