mirror of
https://github.com/simon987/task_tracker.git
synced 2025-12-10 13:44:30 +00:00
Some work on permissions (lacks tests)
This commit is contained in:
@@ -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}`)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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}
|
||||
];
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -20,7 +20,7 @@ export class AppComponent {
|
||||
];
|
||||
|
||||
constructor(private translate: TranslateService,
|
||||
private router: Router,
|
||||
public router: Router,
|
||||
public authService: AuthService) {
|
||||
|
||||
translate.addLangs([
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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,
|
||||
}))
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
interface Manager {
|
||||
id: number;
|
||||
username: string
|
||||
website_admin: boolean;
|
||||
}
|
||||
|
||||
6
web/angular/src/app/models/worker.ts
Normal file
6
web/angular/src/app/models/worker.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
export interface Worker {
|
||||
id: number
|
||||
alias: string
|
||||
created: number
|
||||
secret: string
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
button {
|
||||
margin-right: 15px;
|
||||
}
|
||||
|
||||
mat-panel-title > mat-icon {
|
||||
margin-right: 1em;
|
||||
}
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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>
|
||||
60
web/angular/src/app/project-perms/project-perms.component.ts
Normal file
60
web/angular/src/app/project-perms/project-perms.component.ts
Normal 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()
|
||||
}
|
||||
}
|
||||
@@ -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"]
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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é"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user