Various little improvements. More work on permissions

This commit is contained in:
simon987
2019-02-14 22:04:00 -05:00
parent c3e5bd77f7
commit 8fe41b8fbb
31 changed files with 434 additions and 96 deletions

View File

@@ -13,6 +13,10 @@
{{"account.username" | translate}}: 
<pre>{{authService.account.username}}</pre>
</mat-list-item>
<mat-list-item>
{{"account.register_time" | translate}}:&nbsp;
<pre>{{moment.unix(authService.account.register_time).utc().format("YYYY-MM-DD HH:mm:ss UTC")}}</pre>
</mat-list-item>
</mat-list>
<mat-expansion-panel>

View File

@@ -1,6 +1,8 @@
import {Component, OnInit} from '@angular/core';
import {AuthService} from "../auth.service";
import * as moment from "moment"
@Component({
selector: 'app-account-details',
templateUrl: './account-details.component.html',
@@ -8,6 +10,8 @@ import {AuthService} from "../auth.service";
})
export class AccountDetailsComponent implements OnInit {
public moment = moment;
constructor(public authService: AuthService) {
}

View File

@@ -69,4 +69,16 @@ export class ApiService {
return this.http.get(this.url + `/project/requests/${project}`)
}
getAllManagers() {
return this.http.get(this.url + "/manager/list")
}
promote(managerId: number) {
return this.http.get(this.url + `/manager/promote/${managerId}`)
}
demote(managerId: number) {
return this.http.get(this.url + `/manager/demote/${managerId}`)
}
}

View File

@@ -12,6 +12,7 @@ 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";
import {ManagerListComponent} from "./manager-list/manager-list.component";
const routes: Routes = [
{path: "log", component: LogsComponent},
@@ -22,7 +23,8 @@ const routes: Routes = [
{path: "project/:id/update", component: UpdateProjectComponent},
{path: "project/:id/perms", component: ProjectPermsComponent},
{path: "new_project", component: CreateProjectComponent},
{path: "workers", component: WorkerDashboardComponent}
{path: "workers", component: WorkerDashboardComponent},
{path: "manager_list", component: ManagerListComponent}
];
@NgModule({

View File

@@ -11,6 +11,10 @@
<button mat-button [class.mat-accent]="router.url == '/new_project'" class="nav-link"
[routerLink]="'new_project'"
*ngIf="authService.logged">{{"nav.new_project" | translate}}</button>
<button mat-button [class.mat-accent]="router.url == '/manager_list'" class="nav-link"
[routerLink]="'manager_list'"
*ngIf="authService.logged && authService.account.tracker_admin"
>{{"nav.manager_list" | translate}}</button>
</div>
<div class="small-nav">
<button mat-button [matMenuTriggerFor]="smallNav">
@@ -28,6 +32,10 @@
<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>
<button mat-button [class.mat-accent]="router.url == '/manager_list'" class="nav-link"
[routerLink]="'manager_list'"
*ngIf="authService.logged && authService.account.tracker_admin"
>{{"nav.manager_list" | translate}}</button>
</mat-menu>
</div>
<span class="nav-spacer"></span>

View File

@@ -48,6 +48,7 @@ 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';
import {ManagerListComponent} from './manager-list/manager-list.component';
export function createTranslateLoader(http: HttpClient) {
@@ -68,6 +69,7 @@ export function createTranslateLoader(http: HttpClient) {
AccountDetailsComponent,
WorkerDashboardComponent,
ProjectPermsComponent,
ManagerListComponent,
],
imports: [
BrowserModule,

View File

@@ -60,6 +60,7 @@ export class AuthService {
.subscribe(() =>
this.apiService.getAccountDetails()
.subscribe((data: any) => {
this.logged = true;
this.account = data.manager;
this.router.navigateByUrl("/account");
}),

View File

@@ -11,7 +11,7 @@
<mat-form-field appearance="outline">
<mat-label>{{ "project.clone_url" | translate}}</mat-label>
<input type="text" matInput [(ngModel)]="project.clone_url"
<input type="text" matInput [(ngModel)]="project.clone_url" (change)="cloneUrlChange()"
[placeholder]="'project.clone_url_placeholder' | translate">
</mat-form-field>
<mat-form-field appearance="outline">
@@ -23,7 +23,9 @@
</mat-hint>
</mat-form-field>
<mat-checkbox matInput [(ngModel)]="project.public" style="padding-top: 1em">
<mat-checkbox [(ngModel)]="project.public"
[disabled]="!authService.logged || !authService.account.tracker_admin"
style="padding-top: 1em">
{{"project.public" | translate}}</mat-checkbox>
</mat-card-content>

View File

@@ -3,6 +3,7 @@ import {Project} from "../models/project";
import {ApiService} from "../api.service";
import {MessengerService} from "../messenger.service";
import {Router} from "@angular/router";
import {AuthService} from "../auth.service";
@Component({
@@ -16,14 +17,21 @@ export class CreateProjectComponent implements OnInit {
constructor(private apiService: ApiService,
private messengerService: MessengerService,
public authService: AuthService,
private router: Router) {
this.project.name = "test";
this.project.public = true;
}
ngOnInit() {
}
cloneUrlChange() {
let tokens = this.project.clone_url.split("/");
if (tokens.length > 2) {
this.project.git_repo = tokens[tokens.length - 2] + "/" + tokens[tokens.length - 1]
}
}
onSubmit() {
this.apiService.createProject(this.project).subscribe(
data => {

View File

@@ -1,5 +1,5 @@
.table-container {
height: 600px;
/*height: 600px;*/
}
.mat-table {

View File

@@ -1,56 +1,63 @@
<div class="container">
<div class="table-container">
<mat-form-field style="margin-right: 10px">
<input matInput (keyup)="applyFilter($event.target.value)" [placeholder]="'logs.filter' | translate">
</mat-form-field>
<mat-button-toggle-group name="level" aria-label="Font Style" (change)="filterLevelChange($event)">
<mat-button-toggle value="1">{{"logs.fatal" | translate}}</mat-button-toggle>
<mat-button-toggle value="2">{{"logs.panic" | translate}}</mat-button-toggle>
<mat-button-toggle value="3">{{"logs.error" | translate}}</mat-button-toggle>
<mat-button-toggle value="4">{{"logs.warn" | translate}}</mat-button-toggle>
<mat-button-toggle value="5">{{"logs.info" | translate}}</mat-button-toggle>
<mat-button-toggle value="6">{{"logs.debug" | translate}}</mat-button-toggle>
<mat-button-toggle value="7">{{"logs.trace" | translate}}</mat-button-toggle>
</mat-button-toggle-group>
<mat-card class="table-container">
<mat-card-header>
<mat-card-title>{{"logs.title" | translate}}</mat-card-title>
<mat-card-subtitle>{{"logs.subtitle" | translate}}</mat-card-subtitle>
</mat-card-header>
<mat-card-content>
<mat-form-field style="margin-right: 10px">
<input matInput (keyup)="applyFilter($event.target.value)" [placeholder]="'logs.filter' | translate">
</mat-form-field>
<mat-button-toggle-group name="level" aria-label="Font Style" (change)="filterLevelChange($event)">
<mat-button-toggle value="1">{{"logs.fatal" | translate}}</mat-button-toggle>
<mat-button-toggle value="2">{{"logs.panic" | translate}}</mat-button-toggle>
<mat-button-toggle value="3">{{"logs.error" | translate}}</mat-button-toggle>
<mat-button-toggle value="4">{{"logs.warn" | translate}}</mat-button-toggle>
<mat-button-toggle value="5">{{"logs.info" | translate}}</mat-button-toggle>
<mat-button-toggle value="6">{{"logs.debug" | translate}}</mat-button-toggle>
<mat-button-toggle value="7">{{"logs.trace" | translate}}</mat-button-toggle>
</mat-button-toggle-group>
<button mat-raised-button style="float: right"
[title]="'dashboard.refresh' | translate"
(click)="refresh()">
<mat-icon>refresh</mat-icon>
</button>
<div class="mat-elevation-z8">
<button mat-raised-button style="float: right"
[title]="'dashboard.refresh' | translate"
(click)="refresh()">
<mat-icon>refresh</mat-icon>
</button>
<div class="mat-elevation-z8">
<mat-table [dataSource]="data" matSort matSortActive="timestamp"
matSortDirection="desc">
<mat-table [dataSource]="data" matSort matSortActive="timestamp"
matSortDirection="desc">
<ng-container matColumnDef="level">
<mat-header-cell style="flex: 0 0 9em" mat-sort-header
*matHeaderCellDef>{{"logs.level" | translate}}</mat-header-cell>
<mat-cell style="flex: 0 0 8em"
*matCellDef="let entry"> {{("logs." + entry.level) | translate}} </mat-cell>
</ng-container>
<ng-container matColumnDef="timestamp">
<mat-header-cell style="flex: 0 0 15em" mat-sort-header
*matHeaderCellDef>{{"logs.time" | translate}}</mat-header-cell>
<mat-cell style="flex: 0 0 12em" *matCellDef="let entry"> {{entry.timestamp}} </mat-cell>
</ng-container>
<ng-container matColumnDef="message">
<mat-header-cell mat-sort-header *matHeaderCellDef>{{"logs.message" | translate}}</mat-header-cell>
<mat-cell style="flex: 0 0 30em" *matCellDef="let entry"> {{entry.message}} </mat-cell>
</ng-container>
<ng-container matColumnDef="data">
<mat-header-cell mat-sort-header *matHeaderCellDef>{{"logs.data" | translate}}</mat-header-cell>
<mat-cell *matCellDef="let entry">
<pre>{{entry.data}}</pre>
</mat-cell>
</ng-container>
<ng-container matColumnDef="level">
<mat-header-cell style="flex: 0 0 9em" mat-sort-header
*matHeaderCellDef>{{"logs.level" | translate}}</mat-header-cell>
<mat-cell style="flex: 0 0 8em"
*matCellDef="let entry"> {{("logs." + entry.level) | translate}} </mat-cell>
</ng-container>
<ng-container matColumnDef="timestamp">
<mat-header-cell style="flex: 0 0 15em" mat-sort-header
*matHeaderCellDef>{{"logs.time" | translate}}</mat-header-cell>
<mat-cell style="flex: 0 0 12em" *matCellDef="let entry"> {{entry.timestamp}} </mat-cell>
</ng-container>
<ng-container matColumnDef="message">
<mat-header-cell mat-sort-header
*matHeaderCellDef>{{"logs.message" | translate}}</mat-header-cell>
<mat-cell style="flex: 0 0 30em" *matCellDef="let entry"> {{entry.message}} </mat-cell>
</ng-container>
<ng-container matColumnDef="data">
<mat-header-cell mat-sort-header *matHeaderCellDef>{{"logs.data" | translate}}</mat-header-cell>
<mat-cell *matCellDef="let entry">
<pre>{{entry.data}}</pre>
</mat-cell>
</ng-container>
<mat-header-row *matHeaderRowDef="logsCols"></mat-header-row>
<mat-row *matRowDef="let row; columns: logsCols;"></mat-row>
</mat-table>
<mat-header-row *matHeaderRowDef="logsCols"></mat-header-row>
<mat-row *matRowDef="let row; columns: logsCols;"></mat-row>
</mat-table>
<mat-paginator [length]="logs.length" [pageSizeOptions]="[5,10,25,100]" [pageSize]="5"></mat-paginator>
</div>
</div>
<mat-paginator [length]="logs.length" [pageSizeOptions]="[5,10,25,100]" [pageSize]="5"></mat-paginator>
</div>
</mat-card-content>
</mat-card>
</div>

View File

@@ -39,7 +39,7 @@ export class LogsComponent implements OnInit {
this.getLogs(Number(event.value))
}
private refresh() {
public refresh() {
this.getLogs(this.filterLevel)
}

View File

@@ -0,0 +1,53 @@
<div class="container">
<mat-card class="mat-elevation-z8">
<mat-card-header>
<mat-card-title>{{"manager_list.title" | translate}}</mat-card-title>
<mat-card-subtitle>{{"manager_list.subtitle" | translate}}</mat-card-subtitle>
</mat-card-header>
<mat-card-content>
<mat-table [dataSource]="data" matSort matSortActive="username" matSortDirection="asc">
<ng-container matColumnDef="username">
<mat-header-cell mat-sort-header
*matHeaderCellDef>{{"manager_list.username" | translate}}</mat-header-cell>
<mat-cell *matCellDef="let manager"> {{manager.username}} </mat-cell>
</ng-container>
<ng-container matColumnDef="tracker_admin">
<mat-header-cell *matHeaderCellDef
mat-sort-header>{{"manager_list.role" | translate}}</mat-header-cell>
<mat-cell *matCellDef="let manager">
<mat-icon *ngIf="manager.tracker_admin" [title]="'manager_list.tracker_admin' | translate">
supervisor_account
</mat-icon>
</mat-cell>
</ng-container>
<ng-container matColumnDef="register_time">
<mat-header-cell *matHeaderCellDef>{{"manager_list.register_time" | translate}}</mat-header-cell>
<mat-cell *matCellDef="let manager">
{{moment.unix(manager.register_time).utc().format("UTC YYYY-MM-DD HH:mm:ss")}}
</mat-cell>
</ng-container>
<ng-container matColumnDef="actions">
<mat-header-cell *matHeaderCellDef>{{"manager_list.actions" | translate}}</mat-header-cell>
<mat-cell *matCellDef="let manager">
<button mat-raised-button color="primary"
*ngIf="canPromote(manager)"
(click)="promote(manager)">{{"manager_list.promote" | translate}}</button>
<button mat-raised-button color="warn"
*ngIf="canDemote(manager)"
(click)="demote(manager)">{{"manager_list.demote" | translate}}</button>
</mat-cell>
</ng-container>
<mat-header-row *matHeaderRowDef="cols"></mat-header-row>
<mat-row *matRowDef="let row; columns: cols;"></mat-row>
</mat-table>
<mat-paginator [class.hidden]="managers.length<25" [length]="managers.length"
[pageSizeOptions]="[25,50,100]" [pageSize]="25"></mat-paginator>
</mat-card-content>
</mat-card>
</div>

View File

@@ -0,0 +1,70 @@
import {Component, OnInit, ViewChild} from '@angular/core';
import {ApiService} from "../api.service";
import {MessengerService} from "../messenger.service";
import {TranslateService} from "@ngx-translate/core";
import {MatPaginator, MatSort, MatTableDataSource} from "@angular/material";
import * as moment from "moment"
import {AuthService} from "../auth.service";
@Component({
selector: 'app-manager-list',
templateUrl: './manager-list.component.html',
styleUrls: ['./manager-list.component.css']
})
export class ManagerListComponent implements OnInit {
managers = [];
data;
moment = moment;
cols = ['username', 'tracker_admin', 'register_time', 'actions'];
@ViewChild(MatPaginator) paginator: MatPaginator;
@ViewChild(MatSort) sort: MatSort;
constructor(private apiService: ApiService,
private messengerService: MessengerService,
private translate: TranslateService,
private authService: AuthService
) {
this.data = new MatTableDataSource<Manager>()
}
ngOnInit() {
this.getManagers();
this.data.paginator = this.paginator;
this.data.sort = this.sort;
}
canPromote(manager: Manager) {
return !manager.tracker_admin
}
canDemote(manager: Manager) {
return manager.tracker_admin && manager.username != this.authService.account.username
}
public promote(manager: Manager) {
this.apiService.promote(manager.id)
.subscribe(() => this.getManagers())
}
public demote(manager: Manager) {
this.apiService.demote(manager.id)
.subscribe(() => this.getManagers())
}
private getManagers() {
this.apiService.getAllManagers()
.subscribe(data => {
this.data.data = data["managers"]
},
error => {
if (error && (error.status == 401 || error.status == 403)) {
console.log(error.error.message);
this.translate.get("manager_list.unauthorized")
.subscribe(t => this.messengerService.show(t));
}
})
}
}

View File

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

View File

@@ -18,9 +18,11 @@
<div>
<button mat-raised-button color="primary" [routerLink]="'/project/' + project.id">
<mat-icon>timeline</mat-icon>{{"projects.dashboard" | translate}}</button>
<button mat-raised-button color="primary" [routerLink]="'/project/' + project.id + '/update'">
<button mat-raised-button color="primary" [routerLink]="'/project/' + project.id + '/update'"
*ngIf="authService.logged">
<mat-icon>build</mat-icon>{{"project.update" | translate}}</button>
<button mat-raised-button color="primary" [routerLink]="'/project/' + project.id + '/perms'">
<button mat-raised-button color="primary" [routerLink]="'/project/' + project.id + '/perms'"
*ngIf="authService.logged">
<mat-icon>perm_identity</mat-icon>
{{"project.perms" | translate}}</button>
</div>

View File

@@ -1,6 +1,7 @@
import {Component, OnInit} from '@angular/core';
import {ApiService} from "../api.service";
import {Project} from "../models/project";
import {AuthService} from "../auth.service";
@Component({
selector: 'app-project-list',
@@ -9,7 +10,8 @@ import {Project} from "../models/project";
})
export class ProjectListComponent implements OnInit {
constructor(private apiService: ApiService) {
constructor(private apiService: ApiService,
public authService: AuthService) {
}
projects: Project[];

View File

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

View File

@@ -16,7 +16,8 @@
<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>
<span
class="text-mono">{{moment.unix(w.created).utc().format("UTC YYYY-MM-DD HH:mm:ss")}}</span>
</div>
<span style="flex: 1 1 auto;"></span>
<button mat-raised-button color="primary" [title]="'perms.grant' | translate">

View File

@@ -54,7 +54,7 @@ export class ProjectPermsComponent implements OnInit {
})
}
private refresh() {
public refresh() {
this.getProjectRequests()
}
}

View File

@@ -20,7 +20,7 @@ export class WorkerDashboardComponent implements OnInit {
this.refresh()
}
private refresh() {
public refresh() {
this.apiService.getWorkerStats()
.subscribe((data: any) => {
this.updateChart(data.stats)

View File

@@ -7,9 +7,12 @@
"new_project": "New Project",
"login": "Login",
"worker_dashboard": "Workers",
"account": "Account"
"account": "Account",
"manager_list": "Managers"
},
"logs": {
"title": "Logs",
"subtitle": "",
"filter": "Filter",
"time": "Time (UTC)",
"level": "Level",
@@ -43,7 +46,8 @@
"login": "Login",
"new_account": "Create account",
"account": "Account details",
"workers": "Workers"
"workers": "Workers",
"manager_list": "Managers"
},
"project": {
"name": "Project name",
@@ -82,7 +86,8 @@
"title": "Account details",
"subtitle": "toto: subtitle",
"username": "Username",
"logout": "Logout"
"logout": "Logout",
"register_time": "Register date"
},
"workers": {
"title": "Completed tasks per worker",
@@ -100,5 +105,17 @@
"messenger": {
"close": "Close",
"unauthorized": "Unauthorized"
},
"manager_list": {
"title": "Manager list",
"subtitle": "",
"username": "Username",
"role": "Role",
"tracker_admin": "Tracker administrator",
"actions": "Actions",
"unauthorized": "You are not authorized to access this page",
"promote": "Promote",
"demote": "Demote",
"register_time": "Register date"
}
}

View File

@@ -2,14 +2,17 @@
"nav": {
"title": "task_tracker (fr)",
"lang_select": "Langue",
"logs": "Journal",
"logs": "Journaux",
"project_list": "Projets",
"new_project": "Nouveau projet",
"login": "Ouvrir un session",
"worker_dashboard": "Workers",
"account": "Compte"
"account": "Compte",
"manager_list": "Managers"
},
"logs": {
"title": "Journaux",
"subtitle": "",
"filter": "Filtrer",
"time": "Date (UTC)",
"level": "Niveau",
@@ -44,7 +47,8 @@
"login": "Ouverture de session",
"new_account": "Création de compte",
"account": "Compte",
"workers": "Workers"
"workers": "Workers",
"manager_list": "Managers"
},
"project": {
"name": "Nom du projet",
@@ -84,7 +88,8 @@
"title": "Détails du compte",
"subtitle": "toto: sous-titre",
"username": "Nom d'utilisateur",
"logout": "Fermer la session"
"logout": "Fermer la session",
"register_time": "Date de création"
},
"workers": {
"title": "Tâches complétés par worker",
@@ -102,6 +107,18 @@
"messenger": {
"close": "Fermer",
"unauthorized": "Non autorisé"
},
"manager_list": {
"title": "Liste ",
"subtitle": "",
"username": "Nom d'utilisateur",
"role": "Rôle",
"tracker_admin": "Administrateur",
"actions": "Actions",
"unauthorized": "Vou n'êtes pas authorisé à accéder à cette page",
"promote": "Promouvoir",
"demote": "Rétrograder",
"register_time": "Date d'inscription"
}
}

View File

@@ -17,7 +17,7 @@ body {
padding-left: 15px;
margin-right: auto;
margin-left: auto;
margin-top: 15px;
margin-top: 30px;
}
@media (min-width: 576px) {
@@ -86,3 +86,11 @@ pre {
.hidden {
display: none !important;
}
.unauthorized {
text-align: center;
color: #616161;
min-height: 3em;
margin-top: 2em !important;
}