mirror of
https://github.com/simon987/task_tracker.git
synced 2025-04-19 18:16:45 +00:00
Various little improvements. More work on permissions
This commit is contained in:
parent
c3e5bd77f7
commit
8fe41b8fbb
104
api/auth.go
104
api/auth.go
@ -4,6 +4,7 @@ import (
|
||||
"encoding/json"
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/simon987/task_tracker/storage"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
const MinPasswordLength = 8
|
||||
@ -31,6 +32,12 @@ type AccountDetails struct {
|
||||
Manager *storage.Manager `json:"manager,omitempty"`
|
||||
}
|
||||
|
||||
type GetAllManagersResponse struct {
|
||||
Ok bool `json:"ok"`
|
||||
Message string `json:"message,omitempty"`
|
||||
Managers *[]storage.Manager `json:"managers"`
|
||||
}
|
||||
|
||||
func (r *RegisterRequest) isValid() bool {
|
||||
return MinUsernameLength <= len(r.Username) &&
|
||||
len(r.Username) <= MaxUsernameLength &&
|
||||
@ -158,3 +165,100 @@ func (api *WebAPI) AccountDetails(r *Request) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (api *WebAPI) GetAllManagers(r *Request) {
|
||||
|
||||
sess := api.Session.StartFasthttp(r.Ctx)
|
||||
manager := sess.Get("manager")
|
||||
|
||||
if manager == nil {
|
||||
r.Json(GetAllManagersResponse{
|
||||
Ok: false,
|
||||
Message: "Unauthorized",
|
||||
}, 401)
|
||||
return
|
||||
}
|
||||
|
||||
managers := api.Database.GetAllManagers()
|
||||
|
||||
r.OkJson(GetAllManagersResponse{
|
||||
Ok: true,
|
||||
Managers: managers,
|
||||
})
|
||||
}
|
||||
|
||||
func (api *WebAPI) PromoteManager(r *Request) {
|
||||
|
||||
id, err := strconv.ParseInt(r.Ctx.UserValue("id").(string), 10, 64)
|
||||
if err != nil || id <= 0 {
|
||||
r.Json(CreateProjectResponse{
|
||||
Ok: false,
|
||||
Message: "Invalid manager id",
|
||||
}, 400)
|
||||
return
|
||||
}
|
||||
|
||||
sess := api.Session.StartFasthttp(r.Ctx)
|
||||
manager := sess.Get("manager")
|
||||
|
||||
if !manager.(*storage.Manager).WebsiteAdmin || manager.(*storage.Manager).Id == id {
|
||||
r.Json(GetAllManagersResponse{
|
||||
Ok: false,
|
||||
Message: "Unauthorized",
|
||||
}, 401)
|
||||
return
|
||||
}
|
||||
|
||||
if !manager.(*storage.Manager).WebsiteAdmin {
|
||||
r.Json(GetAllManagersResponse{
|
||||
Ok: false,
|
||||
Message: "Unauthorized",
|
||||
}, 403)
|
||||
return
|
||||
}
|
||||
|
||||
api.Database.UpdateManager(&storage.Manager{
|
||||
Id: id,
|
||||
WebsiteAdmin: true,
|
||||
})
|
||||
|
||||
r.Ctx.Response.SetStatusCode(204)
|
||||
}
|
||||
|
||||
func (api *WebAPI) DemoteManager(r *Request) {
|
||||
|
||||
id, err := strconv.ParseInt(r.Ctx.UserValue("id").(string), 10, 64)
|
||||
if err != nil || id <= 0 {
|
||||
r.Json(CreateProjectResponse{
|
||||
Ok: false,
|
||||
Message: "Invalid manager id",
|
||||
}, 400)
|
||||
return
|
||||
}
|
||||
|
||||
sess := api.Session.StartFasthttp(r.Ctx)
|
||||
manager := sess.Get("manager")
|
||||
|
||||
if manager == nil {
|
||||
r.Json(GetAllManagersResponse{
|
||||
Ok: false,
|
||||
Message: "Unauthorized",
|
||||
}, 401)
|
||||
return
|
||||
}
|
||||
|
||||
if !manager.(*storage.Manager).WebsiteAdmin || manager.(*storage.Manager).Id == id {
|
||||
r.Json(GetAllManagersResponse{
|
||||
Ok: false,
|
||||
Message: "Unauthorized",
|
||||
}, 403)
|
||||
return
|
||||
}
|
||||
|
||||
api.Database.UpdateManager(&storage.Manager{
|
||||
Id: id,
|
||||
WebsiteAdmin: false,
|
||||
})
|
||||
|
||||
r.Ctx.Response.SetStatusCode(204)
|
||||
}
|
||||
|
@ -106,6 +106,9 @@ func New() *WebAPI {
|
||||
api.router.POST("/login", LogRequestMiddleware(api.Login))
|
||||
api.router.GET("/logout", LogRequestMiddleware(api.Logout))
|
||||
api.router.GET("/account", LogRequestMiddleware(api.AccountDetails))
|
||||
api.router.GET("/manager/list", LogRequestMiddleware(api.GetAllManagers))
|
||||
api.router.GET("/manager/promote/:id", LogRequestMiddleware(api.PromoteManager))
|
||||
api.router.GET("/manager/demote/:id", LogRequestMiddleware(api.DemoteManager))
|
||||
|
||||
api.router.NotFound = func(ctx *fasthttp.RequestCtx) {
|
||||
|
||||
|
@ -3,15 +3,14 @@ package main
|
||||
import (
|
||||
"github.com/simon987/task_tracker/api"
|
||||
"github.com/simon987/task_tracker/config"
|
||||
"github.com/simon987/task_tracker/storage"
|
||||
"math/rand"
|
||||
"time"
|
||||
)
|
||||
|
||||
func tmpDebugSetup() {
|
||||
|
||||
db := storage.Database{}
|
||||
db.Reset()
|
||||
//db := storage.Database{}
|
||||
//db.Reset()
|
||||
|
||||
}
|
||||
|
||||
|
@ -69,7 +69,7 @@ CREATE TABLE manager
|
||||
(
|
||||
id SERIAL PRIMARY KEY,
|
||||
register_time INTEGER NOT NULL,
|
||||
website_admin BOOLEAN NOT NULL,
|
||||
tracker_admin BOOLEAN NOT NULL,
|
||||
username TEXT UNIQUE NOT NULL,
|
||||
password BYTEA NOT NULL
|
||||
);
|
||||
@ -117,7 +117,7 @@ CREATE OR REPLACE FUNCTION on_manager_insert() RETURNS TRIGGER AS
|
||||
$$
|
||||
BEGIN
|
||||
IF NEW.id = 1 THEN
|
||||
UPDATE manager SET website_admin= TRUE WHERE id = 1;
|
||||
UPDATE manager SET tracker_admin= TRUE WHERE id = 1;
|
||||
end if;
|
||||
RETURN NEW;
|
||||
END;
|
||||
|
@ -17,21 +17,22 @@ const (
|
||||
)
|
||||
|
||||
type Manager struct {
|
||||
Id int `json:"id"`
|
||||
Id int64 `json:"id"`
|
||||
Username string `json:"username"`
|
||||
WebsiteAdmin bool `json:"website_admin"`
|
||||
WebsiteAdmin bool `json:"tracker_admin"`
|
||||
RegisterTime int64 `json:"register_time"`
|
||||
}
|
||||
|
||||
func (database *Database) ValidateCredentials(username []byte, password []byte) (*Manager, error) {
|
||||
|
||||
db := database.getDB()
|
||||
|
||||
row := db.QueryRow(`SELECT id, password, website_admin FROM manager WHERE username=$1`,
|
||||
row := db.QueryRow(`SELECT id, password, tracker_admin, register_time FROM manager WHERE username=$1`,
|
||||
username)
|
||||
|
||||
manager := &Manager{}
|
||||
var passwordHash []byte
|
||||
err := row.Scan(&manager.Id, &passwordHash, &manager.WebsiteAdmin)
|
||||
err := row.Scan(&manager.Id, &passwordHash, &manager.WebsiteAdmin, &manager.RegisterTime)
|
||||
if err != nil {
|
||||
logrus.WithError(err).WithFields(logrus.Fields{
|
||||
"username": username,
|
||||
@ -66,11 +67,11 @@ func (database *Database) SaveManager(manager *Manager, password []byte) error {
|
||||
hash.Write([]byte(manager.Username))
|
||||
hashedPassword := hash.Sum(nil)
|
||||
|
||||
row := db.QueryRow(`INSERT INTO manager (username, password, website_admin, register_time)
|
||||
VALUES ($1,$2,$3, extract(epoch from now() at time zone 'utc')) RETURNING ID`,
|
||||
row := db.QueryRow(`INSERT INTO manager (username, password, tracker_admin, register_time)
|
||||
VALUES ($1,$2,$3, extract(epoch from now() at time zone 'utc')) RETURNING ID, register_time`,
|
||||
manager.Username, hashedPassword, manager.WebsiteAdmin)
|
||||
|
||||
err := row.Scan(&manager.Id)
|
||||
err := row.Scan(&manager.Id, &manager.RegisterTime)
|
||||
if err != nil {
|
||||
logrus.WithError(err).WithFields(logrus.Fields{
|
||||
"username": manager,
|
||||
@ -92,16 +93,16 @@ func (database *Database) UpdateManager(manager *Manager) {
|
||||
|
||||
db := database.getDB()
|
||||
|
||||
res, err := db.Exec(`UPDATE manager SET website_admin=$1 WHERE id=$2`,
|
||||
res, err := db.Exec(`UPDATE manager SET tracker_admin=$1 WHERE id=$2`,
|
||||
manager.WebsiteAdmin, manager.Id)
|
||||
handleErr(err)
|
||||
|
||||
rowsAffected, _ := res.RowsAffected()
|
||||
|
||||
logrus.WithError(err).WithFields(logrus.Fields{
|
||||
logrus.WithFields(logrus.Fields{
|
||||
"rowsAffected": rowsAffected,
|
||||
"manager": manager,
|
||||
}).Warning("Database.UpdateManager UPDATE")
|
||||
}).Trace("Database.UpdateManager UPDATE")
|
||||
}
|
||||
|
||||
func (database *Database) UpdateManagerPassword(manager *Manager, newPassword []byte) {
|
||||
@ -119,10 +120,10 @@ func (database *Database) UpdateManagerPassword(manager *Manager, newPassword []
|
||||
|
||||
rowsAffected, _ := res.RowsAffected()
|
||||
|
||||
logrus.WithError(err).WithFields(logrus.Fields{
|
||||
logrus.WithFields(logrus.Fields{
|
||||
"rowsAffected": rowsAffected,
|
||||
"id": manager.Id,
|
||||
}).Warning("Database.UpdateManagerPassword UPDATE")
|
||||
}).Trace("Database.UpdateManagerPassword UPDATE")
|
||||
}
|
||||
|
||||
func (database *Database) ManagerHasRoleOn(manager *Manager, projectId int64) ManagerRole {
|
||||
@ -140,3 +141,20 @@ func (database *Database) ManagerHasRoleOn(manager *Manager, projectId int64) Ma
|
||||
|
||||
return role
|
||||
}
|
||||
|
||||
func (database *Database) GetAllManagers() *[]Manager {
|
||||
|
||||
db := database.getDB()
|
||||
|
||||
rows, _ := db.Query(`SELECT id, register_time, tracker_admin, username FROM manager`)
|
||||
|
||||
managers := make([]Manager, 0)
|
||||
|
||||
for rows.Next() {
|
||||
m := Manager{}
|
||||
_ = rows.Scan(&m.Id, &m.RegisterTime, &m.WebsiteAdmin, &m.Username)
|
||||
managers = append(managers, m)
|
||||
}
|
||||
|
||||
return &managers
|
||||
}
|
||||
|
@ -69,7 +69,7 @@ CREATE TABLE manager
|
||||
(
|
||||
id SERIAL PRIMARY KEY,
|
||||
register_time INTEGER NOT NULL,
|
||||
website_admin BOOLEAN NOT NULL,
|
||||
tracker_admin BOOLEAN NOT NULL,
|
||||
username TEXT UNIQUE NOT NULL,
|
||||
password BYTEA NOT NULL
|
||||
);
|
||||
@ -116,7 +116,7 @@ CREATE OR REPLACE FUNCTION on_manager_insert() RETURNS TRIGGER AS
|
||||
$$
|
||||
BEGIN
|
||||
IF NEW.id = 1 THEN
|
||||
UPDATE manager SET website_admin= TRUE WHERE id = 1;
|
||||
UPDATE manager SET tracker_admin= TRUE WHERE id = 1;
|
||||
end if;
|
||||
RETURN NEW;
|
||||
END;
|
||||
|
@ -13,6 +13,10 @@
|
||||
{{"account.username" | translate}}:
|
||||
<pre>{{authService.account.username}}</pre>
|
||||
</mat-list-item>
|
||||
<mat-list-item>
|
||||
{{"account.register_time" | translate}}:
|
||||
<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>
|
||||
|
@ -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) {
|
||||
}
|
||||
|
||||
|
@ -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}`)
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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({
|
||||
|
@ -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>
|
||||
|
@ -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,
|
||||
|
@ -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");
|
||||
}),
|
||||
|
@ -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>
|
||||
|
@ -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 => {
|
||||
|
@ -1,5 +1,5 @@
|
||||
.table-container {
|
||||
height: 600px;
|
||||
/*height: 600px;*/
|
||||
}
|
||||
|
||||
.mat-table {
|
||||
|
@ -1,5 +1,10 @@
|
||||
<div class="container">
|
||||
<div class="table-container">
|
||||
<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>
|
||||
@ -35,7 +40,8 @@
|
||||
<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-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">
|
||||
@ -51,6 +57,7 @@
|
||||
|
||||
<mat-paginator [length]="logs.length" [pageSizeOptions]="[5,10,25,100]" [pageSize]="5"></mat-paginator>
|
||||
</div>
|
||||
</div>
|
||||
</mat-card-content>
|
||||
</mat-card>
|
||||
</div>
|
||||
|
||||
|
@ -39,7 +39,7 @@ export class LogsComponent implements OnInit {
|
||||
this.getLogs(Number(event.value))
|
||||
}
|
||||
|
||||
private refresh() {
|
||||
public refresh() {
|
||||
this.getLogs(this.filterLevel)
|
||||
}
|
||||
|
||||
|
53
web/angular/src/app/manager-list/manager-list.component.html
Normal file
53
web/angular/src/app/manager-list/manager-list.component.html
Normal 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>
|
70
web/angular/src/app/manager-list/manager-list.component.ts
Normal file
70
web/angular/src/app/manager-list/manager-list.component.ts
Normal 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));
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
@ -1,5 +1,6 @@
|
||||
interface Manager {
|
||||
id: number;
|
||||
username: string
|
||||
website_admin: boolean;
|
||||
tracker_admin: boolean
|
||||
register_time: number
|
||||
}
|
||||
|
@ -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>
|
||||
|
@ -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[];
|
||||
|
@ -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;
|
||||
|
@ -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">
|
||||
|
@ -54,7 +54,7 @@ export class ProjectPermsComponent implements OnInit {
|
||||
})
|
||||
}
|
||||
|
||||
private refresh() {
|
||||
public refresh() {
|
||||
this.getProjectRequests()
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user