Worker management UI for managers

This commit is contained in:
simon987 2019-05-05 20:13:47 -04:00
parent 72c8e18044
commit 39dd89b001
13 changed files with 129 additions and 9 deletions

View File

@ -72,6 +72,14 @@ func (api *WebAPI) GetWorker(r *Request) {
if worker != nil {
sess := api.Session.StartFasthttp(r.Ctx)
manager := sess.Get("manager")
var secret []byte = nil
if manager != nil && manager.(*storage.Manager).WebsiteAdmin {
secret = worker.Secret
}
r.OkJson(JsonResponse{
Ok: true,
Content: GetWorkerResponse{
@ -79,6 +87,8 @@ func (api *WebAPI) GetWorker(r *Request) {
Alias: worker.Alias,
Id: worker.Id,
Created: worker.Created,
Paused: worker.Paused,
Secret: secret,
},
},
})

View File

@ -15,6 +15,8 @@ type Worker struct {
type WorkerStats struct {
Alias string `json:"alias"`
ClosedTaskCount int64 `json:"closed_task_count"`
Paused bool `json:"paused"`
Id int64 `json:"id"`
}
type WorkerAccess struct {
@ -173,7 +175,7 @@ func (database *Database) GetAllAccesses(projectId int64) *[]WorkerAccess {
db := database.getDB()
rows, err := db.Query(`SELECT id, alias, created, role_assign, role_submit, request
rows, err := db.Query(`SELECT id, alias, created, paused, role_assign, role_submit, request
FROM worker_access
INNER JOIN worker w on worker_access.worker = w.id
WHERE project=$1 ORDER BY request, alias`,
@ -186,7 +188,7 @@ func (database *Database) GetAllAccesses(projectId int64) *[]WorkerAccess {
wa := WorkerAccess{
Project: projectId,
}
_ = rows.Scan(&wa.Worker.Id, &wa.Worker.Alias, &wa.Worker.Created,
_ = rows.Scan(&wa.Worker.Id, &wa.Worker.Alias, &wa.Worker.Created, &wa.Worker.Paused,
&wa.Assign, &wa.Submit, &wa.Request)
requests = append(requests, wa)
}
@ -197,13 +199,15 @@ func (database *Database) GetAllAccesses(projectId int64) *[]WorkerAccess {
func (database *Database) GetAllWorkerStats() *[]WorkerStats {
db := database.getDB()
rows, err := db.Query(`SELECT alias, closed_task_count FROM worker WHERE closed_task_count>0 LIMIT 50`)
rows, err := db.Query(`SELECT alias, closed_task_count, paused, worker.id
FROM worker WHERE closed_task_count>0 LIMIT 50`)
handleErr(err)
stats := make([]WorkerStats, 0)
for rows.Next() {
s := WorkerStats{}
_ = rows.Scan(&s.Alias, &s.ClosedTaskCount)
_ = rows.Scan(&s.Alias, &s.ClosedTaskCount, &s.Paused, &s.Id)
stats = append(stats, s)
}

View File

@ -636,6 +636,10 @@ func TestTaskChainUpdateRequiresRole(t *testing.T) {
}
}
func TestGetAccessList(t *testing.T) {
//TODO!
}
func createProjectAsAdmin(req api.CreateProjectRequest) CreateProjectAR {
return createProject(req, testAdminCtx)
}

View File

@ -126,4 +126,13 @@ export class ApiService {
return this.http.post(this.url + `/project/reclaim_assigned_tasks/${pid}`, null, this.options);
}
workerSetPaused(wid: number, paused: boolean) {
return this.http.post(this.url + '/worker/set_paused',
{'worker': wid, 'paused': paused}, this.options);
}
getWorker(wid: number) {
return this.http.get(this.url + `/worker/get/${wid}`, this.options);
}
}

View File

@ -3,4 +3,5 @@ export interface Worker {
alias: string;
created: number;
secret: string;
paused: boolean;
}

View File

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

View File

@ -12,16 +12,24 @@
<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-list-item *ngFor="let wa of accesses" [class.request]="wa.request"
[class.paused]="wa.worker.paused">
<mat-icon mat-list-icon *ngIf="wa.worker.paused" [title]="'workers.paused'|translate">pause
</mat-icon>
<mat-icon mat-list-icon *ngIf="wa.submit" [title]="'perms.submit'|translate">library_add</mat-icon>
<mat-icon mat-list-icon *ngIf="wa.assign" [title]="'perms.assign'|translate">get_app</mat-icon>
<h4 mat-line>{{wa.worker.alias}} {{wa.request ? ('perms.pending' | translate) : ''}}</h4>
<h4 mat-line>{{wa.worker.alias}} {{wa.request ? ('perms.pending' | translate) : ''}}
{{wa.worker.paused ? ('workers.paused' | translate) : ''}}</h4>
<div mat-line>
Id=<span class="text-mono">{{wa.worker.id}}</span>, {{"perms.created" | translate}}
<span
class="text-mono">{{moment.unix(wa.worker.created).utc().format("UTC YYYY-MM-DD HH:mm:ss")}}</span>
</div>
<span style="flex: 1 1 auto;"></span>
<button mat-raised-button color="secondary" [title]="'workers.pause' | translate"
(click)="togglePaused(wa.worker)">
<mat-icon>pause</mat-icon>
</button>
<button mat-raised-button color="primary" [title]="'perms.grant' | translate"
*ngIf="wa.request" (click)="acceptRequest(wa)">
<mat-icon>check</mat-icon>

View File

@ -9,6 +9,7 @@ import {AuthService} from '../auth.service';
import {Manager, ManagerRoleOnProject} from '../models/manager';
import {MessengerService} from '../messenger.service';
import {TranslateService} from '@ngx-translate/core';
import {Worker} from "../models/worker";
@Component({
selector: 'app-project-perms',
@ -56,6 +57,14 @@ export class ProjectPermsComponent implements OnInit {
});
}
public togglePaused(w: Worker) {
this.apiService.workerSetPaused(w.id, !w.paused)
.subscribe(() => {
this.getProjectAccesses();
this.translate.get('perms.set').subscribe(t => this.messenger.show(t));
});
}
private getProject() {
this.apiService.getProject(this.projectId).subscribe(data => {
this.project = data['content']['project'];

View File

@ -0,0 +1,3 @@
button {
margin-left: 15px;
}

View File

@ -14,5 +14,32 @@
<mat-card-content>
<canvas id="worker-stats"></canvas>
</mat-card-content>
<mat-expansion-panel *ngIf="authService.logged" class="">
<mat-expansion-panel-header>
<mat-panel-title>{{"workers.manage" | translate}}</mat-panel-title>
</mat-expansion-panel-header>
<mat-list>
<mat-list-item *ngFor="let worker of workers" [class.paused]="worker.paused">
<mat-icon mat-list-icon *ngIf="worker.paused" [title]="'workers.paused'|translate">pause</mat-icon>
<mat-icon mat-list-icon *ngIf="!worker.paused">_blank_</mat-icon>
<h4 mat-line>{{worker.alias}}</h4>
<div mat-line *ngIf="workerInfo && workerInfo.id === worker.id">
<p class="text-mono">{{workerInfo|json}}</p>
</div>
<span style="flex: 1 1 auto;"></span>
<button mat-raised-button color="secondary" [title]="'workers.pause' | translate"
(click)="togglePaused(worker)">
<mat-icon>pause</mat-icon>
</button>
<button mat-raised-button color="secondary" [title]="'workers.info' | translate"
(click)="getInfo(worker)">
<mat-icon>info</mat-icon>
</button>
</mat-list-item>
</mat-list>
</mat-expansion-panel>
</mat-card>
</div>

View File

@ -2,6 +2,10 @@ import {Component, OnInit} from '@angular/core';
import {ApiService} from '../api.service';
import {Chart} from 'chart.js';
import {AuthService} from "../auth.service";
import {Worker} from "../models/worker";
import {TranslateService} from "@ngx-translate/core";
import {MessengerService} from "../messenger.service";
@Component({
selector: 'app-worker-dashboard',
@ -11,8 +15,13 @@ import {Chart} from 'chart.js';
export class WorkerDashboardComponent implements OnInit {
private chart: Chart;
workers: Worker[];
workerInfo: Worker;
constructor(private apiService: ApiService) {
constructor(private apiService: ApiService,
private translate: TranslateService,
private messenger: MessengerService,
public authService: AuthService) {
}
ngOnInit() {
@ -20,10 +29,36 @@ export class WorkerDashboardComponent implements OnInit {
this.refresh();
}
public togglePaused(w: Worker) {
this.workerInfo = undefined;
this.apiService.workerSetPaused(w.id, !w.paused)
.subscribe(() => {
this.refresh();
this.translate.get('perms.set').subscribe(t => this.messenger.show(t));
});
}
public getInfo(w: Worker) {
if (this.workerInfo && this.workerInfo.id == w.id) {
this.workerInfo = undefined;
return
}
this.apiService.getWorker(w.id)
.subscribe(data => {
this.workerInfo = data['content']['worker'];
});
}
public refresh() {
this.apiService.getWorkerStats()
.subscribe(data => {
this.updateChart(data['content']['stats']);
this.workers = data['content']['stats'].sort((a, b) =>
(a.alias > b.alias) ? 1 : -1);
}
);
}

View File

@ -112,7 +112,10 @@
},
"workers": {
"title": "Completed tasks per worker",
"subtitle": "Real-time data for all projects"
"subtitle": "Real-time data for all projects",
"paused": "(paused)",
"pause": "Pause",
"manage": "Manager workers"
},
"perms": {
"title": "Project permissions",

View File

@ -109,7 +109,10 @@
},
"workers": {
"title": "Tâches complétés par worker",
"subtitle": "Données en temps réél pour tous les projets"
"subtitle": "Données en temps réél pour tous les projets",
"paused": "(en pause)",
"pause": "Pauser",
"manage": "Gérer les workers"
},
"perms": {
"title": "Permissions du projet",