Some work on project actions

This commit is contained in:
simon987 2019-02-22 20:44:27 -05:00
parent 016676e0f3
commit d44b9924e7
25 changed files with 237 additions and 30 deletions

View File

@ -148,6 +148,7 @@ type UpdateProjectRequest struct {
Public bool `json:"public"` Public bool `json:"public"`
Hidden bool `json:"hidden"` Hidden bool `json:"hidden"`
Chain int64 `json:"chain"` Chain int64 `json:"chain"`
Paused bool `json:"paused"`
} }
func (req *UpdateProjectRequest) isValid() bool { func (req *UpdateProjectRequest) isValid() bool {
@ -204,6 +205,9 @@ func (req *SubmitTaskRequest) IsValid() bool {
if req.Hash64 != 0 && req.UniqueString != "" { if req.Hash64 != 0 && req.UniqueString != "" {
return false return false
} }
if req.Project == 0 {
return false
}
return true return true
} }

View File

@ -158,6 +158,7 @@ func (api *WebAPI) UpdateProject(r *Request) {
Public: updateReq.Public, Public: updateReq.Public,
Hidden: updateReq.Hidden, Hidden: updateReq.Hidden,
Chain: updateReq.Chain, Chain: updateReq.Chain,
Paused: updateReq.Paused,
} }
sess := api.Session.StartFasthttp(r.Ctx) sess := api.Session.StartFasthttp(r.Ctx)
manager := sess.Get("manager") manager := sess.Get("manager")

View File

@ -2,7 +2,7 @@ server:
address: "0.0.0.0:42901" address: "0.0.0.0:42901"
database: database:
conn_str: "user=task_tracker dbname=task_tracker sslmode=disable" conn_str: "user=task_tracker password=task_tracker dbname=task_tracker sslmode=disable"
# log_levels: ["debug", "error", "trace", "info", "warn"] # log_levels: ["debug", "error", "trace", "info", "warn"]
log_levels: ["error", "info", "warn"] log_levels: ["error", "info", "warn"]
@ -19,8 +19,8 @@ log:
session: session:
cookie_name: "tt" cookie_name: "tt"
expiration: "25m" expiration: "8h"
monitoring: monitoring:
snapshot_interval: "60s" snapshot_interval: "120s"
history_length: "400h" history_length: "400h"

View File

@ -19,6 +19,7 @@ CREATE TABLE project
chain INT DEFAULT NULL REFERENCES project (id), chain INT DEFAULT NULL REFERENCES project (id),
public boolean NOT NULL, public boolean NOT NULL,
hidden boolean NOT NULL, hidden boolean NOT NULL,
paused boolean NOT NULL,
name TEXT UNIQUE NOT NULL, name TEXT UNIQUE NOT NULL,
clone_url TEXT NOT NULL, clone_url TEXT NOT NULL,
git_repo TEXT UNIQUE NOT NULL, git_repo TEXT UNIQUE NOT NULL,

View File

@ -17,6 +17,7 @@ type Project struct {
Public bool `json:"public"` Public bool `json:"public"`
Hidden bool `json:"hidden"` Hidden bool `json:"hidden"`
Chain int64 `json:"chain"` Chain int64 `json:"chain"`
Paused bool `json:"paused"`
} }
type AssignedTasks struct { type AssignedTasks struct {
@ -28,10 +29,10 @@ func (database *Database) SaveProject(project *Project) (int64, error) {
db := database.getDB() db := database.getDB()
row := db.QueryRow(`INSERT INTO project (name, git_repo, clone_url, version, priority, row := db.QueryRow(`INSERT INTO project (name, git_repo, clone_url, version, priority,
motd, public, hidden, chain) motd, public, hidden, chain, paused)
VALUES ($1,$2,$3,$4,$5,$6,$7,$8,NULLIF($9, 0)) RETURNING id`, VALUES ($1,$2,$3,$4,$5,$6,$7,$8,NULLIF($9, 0),$10) RETURNING id`,
project.Name, project.GitRepo, project.CloneUrl, project.Version, project.Priority, project.Motd, project.Name, project.GitRepo, project.CloneUrl, project.Version, project.Priority, project.Motd,
project.Public, project.Hidden, project.Chain) project.Public, project.Hidden, project.Chain, project.Paused)
var id int64 var id int64
err := row.Scan(&id) err := row.Scan(&id)
@ -57,7 +58,7 @@ func (database *Database) GetProject(id int64) *Project {
db := database.getDB() db := database.getDB()
row := db.QueryRow(`SELECT id, priority, name, clone_url, git_repo, version, row := db.QueryRow(`SELECT id, priority, name, clone_url, git_repo, version,
motd, public, hidden, COALESCE(chain, 0) motd, public, hidden, COALESCE(chain, 0), paused
FROM project WHERE id=$1`, id) FROM project WHERE id=$1`, id)
project, err := scanProject(row) project, err := scanProject(row)
@ -80,7 +81,7 @@ func scanProject(row *sql.Row) (*Project, error) {
p := &Project{} p := &Project{}
err := row.Scan(&p.Id, &p.Priority, &p.Name, &p.CloneUrl, &p.GitRepo, &p.Version, err := row.Scan(&p.Id, &p.Priority, &p.Name, &p.CloneUrl, &p.GitRepo, &p.Version,
&p.Motd, &p.Public, &p.Hidden, &p.Chain) &p.Motd, &p.Public, &p.Hidden, &p.Chain, &p.Paused)
return p, err return p, err
} }
@ -89,7 +90,7 @@ func (database *Database) GetProjectWithRepoName(repoName string) *Project {
db := database.getDB() db := database.getDB()
row := db.QueryRow(`SELECT id, priority, name, clone_url, git_repo, version, row := db.QueryRow(`SELECT id, priority, name, clone_url, git_repo, version,
motd, public, hidden, COALESCE(chain, 0) FROM project WHERE LOWER(git_repo)=$1`, motd, public, hidden, COALESCE(chain, 0), paused FROM project WHERE LOWER(git_repo)=$1`,
strings.ToLower(repoName)) strings.ToLower(repoName))
project, err := scanProject(row) project, err := scanProject(row)
@ -108,11 +109,11 @@ func (database *Database) UpdateProject(project *Project) error {
db := database.getDB() db := database.getDB()
res, err := db.Exec(`UPDATE project res, err := db.Exec(`UPDATE project
SET (priority, name, clone_url, git_repo, version, motd, public, hidden, chain) = SET (priority, name, clone_url, git_repo, version, motd, public, hidden, chain, paused) =
($1,$2,$3,$4,$5,$6,$7,$8,NULLIF($9, 0)) ($1,$2,$3,$4,$5,$6,$7,$8,NULLIF($9, 0), $10)
WHERE id=$10`, WHERE id=$11`,
project.Priority, project.Name, project.CloneUrl, project.GitRepo, project.Version, project.Motd, project.Priority, project.Name, project.CloneUrl, project.GitRepo, project.Version, project.Motd,
project.Public, project.Hidden, project.Chain, project.Id) project.Public, project.Hidden, project.Chain, project.Paused, project.Id)
if err != nil { if err != nil {
return err return err
} }
@ -135,13 +136,13 @@ func (database Database) GetAllProjects(managerId int64) *[]Project {
var err error var err error
if managerId == 0 { if managerId == 0 {
rows, err = db.Query(`SELECT rows, err = db.Query(`SELECT
Id, priority, name, clone_url, git_repo, version, motd, public, hidden, COALESCE(chain,0) Id, priority, name, clone_url, git_repo, version, motd, public, hidden, COALESCE(chain,0), paused
FROM project FROM project
WHERE NOT hidden WHERE NOT hidden
ORDER BY name`) ORDER BY name`)
} else { } else {
rows, err = db.Query(`SELECT rows, err = db.Query(`SELECT
Id, priority, name, clone_url, git_repo, version, motd, public, hidden, COALESCE(chain,0) Id, priority, name, clone_url, git_repo, version, motd, public, hidden, COALESCE(chain,0), paused
FROM project FROM project
LEFT JOIN manager_has_role_on_project mhrop ON mhrop.project = id AND mhrop.manager=$1 LEFT JOIN manager_has_role_on_project mhrop ON mhrop.project = id AND mhrop.manager=$1
WHERE NOT hidden OR mhrop.role & 1 = 1 OR (SELECT tracker_admin FROM manager WHERE id=$1) WHERE NOT hidden OR mhrop.role & 1 = 1 OR (SELECT tracker_admin FROM manager WHERE id=$1)
@ -153,7 +154,7 @@ func (database Database) GetAllProjects(managerId int64) *[]Project {
p := Project{} p := Project{}
err := rows.Scan(&p.Id, &p.Priority, &p.Name, &p.CloneUrl, err := rows.Scan(&p.Id, &p.Priority, &p.Name, &p.CloneUrl,
&p.GitRepo, &p.Version, &p.Motd, &p.Public, &p.Hidden, &p.GitRepo, &p.Version, &p.Motd, &p.Public, &p.Hidden,
&p.Chain) &p.Chain, &p.Paused)
handleErr(err) handleErr(err)
projects = append(projects, p) projects = append(projects, p)
} }

View File

@ -82,7 +82,7 @@ func (database *Database) GetTask(worker *Worker) *Task {
FROM task FROM task
INNER JOIN project project on task.project = project.id INNER JOIN project project on task.project = project.id
LEFT JOIN worker_verifies_task wvt on task.id = wvt.task AND wvt.worker=$1 LEFT JOIN worker_verifies_task wvt on task.id = wvt.task AND wvt.worker=$1
WHERE assignee IS NULL AND task.status=1 WHERE NOT project.paused AND assignee IS NULL AND task.status=1
AND (project.public OR ( AND (project.public OR (
SELECT a.role_assign FROM worker_access a WHERE a.worker=$1 AND a.project=project.id SELECT a.role_assign FROM worker_access a WHERE a.worker=$1 AND a.project=project.id
)) ))
@ -186,7 +186,7 @@ func (database *Database) GetTaskFromProject(worker *Worker, projectId int64) *T
FROM task FROM task
INNER JOIN project project on task.project = project.id INNER JOIN project project on task.project = project.id
LEFT JOIN worker_verifies_task wvt on task.id = wvt.task AND wvt.worker=$1 LEFT JOIN worker_verifies_task wvt on task.id = wvt.task AND wvt.worker=$1
WHERE assignee IS NULL AND project.id=$2 AND status=1 WHERE NOT project.paused AND assignee IS NULL AND project.id=$2 AND status=1
AND (project.public OR ( AND (project.public OR (
SELECT a.role_assign FROM worker_access a WHERE a.worker=$1 AND a.project=$2 SELECT a.role_assign FROM worker_access a WHERE a.worker=$1 AND a.project=$2
)) ))

View File

@ -142,6 +142,7 @@ func TestUpdateProjectValid(t *testing.T) {
Motd: "MotdB", Motd: "MotdB",
Public: false, Public: false,
Hidden: true, Hidden: true,
Paused: true,
}, pid, testAdminCtx) }, pid, testAdminCtx)
if updateResp.Ok != true { if updateResp.Ok != true {
@ -168,6 +169,9 @@ func TestUpdateProjectValid(t *testing.T) {
if proj.Project.Hidden != true { if proj.Project.Hidden != true {
t.Error() t.Error()
} }
if proj.Project.Paused != true {
t.Error()
}
} }
func TestUpdateProjectInvalid(t *testing.T) { func TestUpdateProjectInvalid(t *testing.T) {
@ -444,6 +448,42 @@ func TestAdminShouldSeeHiddenProjectInList(t *testing.T) {
} }
} }
func TestPausedProjectShouldNotDispatchTasks(t *testing.T) {
createTask(api.SubmitTaskRequest{
Project: testProject,
Recipe: "...",
}, testWorker)
createTask(api.SubmitTaskRequest{
Project: testProject,
Recipe: "...",
}, testWorker)
createTask(api.SubmitTaskRequest{
Project: testProject,
Recipe: "...",
}, testWorker)
task1 := getTaskFromProject(testProject, testWorker).Content.Task
if task1 == nil {
t.Error()
}
updateProject(api.UpdateProjectRequest{
Paused: true,
Name: "generictestproject",
}, testProject, testAdminCtx)
task2 := getTaskFromProject(testProject, testWorker).Content.Task
if task2 != nil {
t.Error()
}
updateProject(api.UpdateProjectRequest{
Paused: false,
Name: "generictestproject",
}, testProject, testAdminCtx)
}
func createProjectAsAdmin(req api.CreateProjectRequest) CreateProjectAR { func createProjectAsAdmin(req api.CreateProjectRequest) CreateProjectAR {
return createProject(req, testAdminCtx) return createProject(req, testAdminCtx)
} }

View File

@ -2,7 +2,7 @@ server:
address: "127.0.0.1:5001" address: "127.0.0.1:5001"
database: database:
conn_str: "user=task_tracker dbname=task_tracker_test sslmode=disable" conn_str: "user=task_tracker password=task_tracker dbname=task_tracker sslmode=disable"
log_levels: ["debug", "error", "trace", "info", "warn"] log_levels: ["debug", "error", "trace", "info", "warn"]
git: git:

View File

@ -19,6 +19,7 @@ CREATE TABLE project
chain INT DEFAULT NULL REFERENCES project (id), chain INT DEFAULT NULL REFERENCES project (id),
public boolean NOT NULL, public boolean NOT NULL,
hidden boolean NOT NULL, hidden boolean NOT NULL,
paused boolean NOT NULL,
name TEXT UNIQUE NOT NULL, name TEXT UNIQUE NOT NULL,
clone_url TEXT NOT NULL, clone_url TEXT NOT NULL,
git_repo TEXT UNIQUE NOT NULL, git_repo TEXT UNIQUE NOT NULL,

View File

@ -0,0 +1,13 @@
<div class="container">
<mat-card class="mat-elevation-z8">
<mat-card-header>
<mat-card-title>{{"admin_panel.title"|translate}}</mat-card-title>
<mat-card-subtitle>{{"admin_panel.subtitle"|translate}}</mat-card-subtitle>
</mat-card-header>
<mat-card-content>
</mat-card-content>
<mat-card-actions></mat-card-actions>
</mat-card>
</div>

View File

@ -0,0 +1,16 @@
import {Component, OnInit} from '@angular/core';
@Component({
selector: 'app-admin-panel',
templateUrl: './admin-panel.component.html',
styleUrls: ['./admin-panel.component.css']
})
export class AdminPanelComponent implements OnInit {
constructor() {
}
ngOnInit() {
}
}

View File

@ -12,6 +12,7 @@ import {
MatButtonToggleModule, MatButtonToggleModule,
MatCardModule, MatCardModule,
MatCheckboxModule, MatCheckboxModule,
MatDialogModule,
MatDividerModule, MatDividerModule,
MatExpansionModule, MatExpansionModule,
MatFormFieldModule, MatFormFieldModule,
@ -55,6 +56,8 @@ import {ManagerSelectComponent} from './manager-select/manager-select.component'
import {ProjectIconComponent} from './project-icon/project-icon.component'; import {ProjectIconComponent} from './project-icon/project-icon.component';
import {IndexComponent} from './index/index.component'; import {IndexComponent} from './index/index.component';
import {ProjectSecretComponent} from './project-secret/project-secret.component'; import {ProjectSecretComponent} from './project-secret/project-secret.component';
import {AdminPanelComponent} from './admin-panel/admin-panel.component';
import {AreYouSureComponent} from './are-you-sure/are-you-sure.component';
export function createTranslateLoader(http: HttpClient) { export function createTranslateLoader(http: HttpClient) {
@ -81,6 +84,8 @@ export function createTranslateLoader(http: HttpClient) {
ProjectIconComponent, ProjectIconComponent,
IndexComponent, IndexComponent,
ProjectSecretComponent, ProjectSecretComponent,
AdminPanelComponent,
AreYouSureComponent,
], ],
imports: [ imports: [
BrowserModule, BrowserModule,
@ -120,7 +125,8 @@ export function createTranslateLoader(http: HttpClient) {
MatTabsModule, MatTabsModule,
MatListModule, MatListModule,
MatButtonToggleModule, MatButtonToggleModule,
MatStepperModule MatStepperModule,
MatDialogModule,
], ],
exports: [], exports: [],
@ -131,6 +137,7 @@ export function createTranslateLoader(http: HttpClient) {
], ],
entryComponents: [ entryComponents: [
SnackBarComponent, SnackBarComponent,
AreYouSureComponent
], ],
bootstrap: [AppComponent] bootstrap: [AppComponent]
}) })

View File

@ -0,0 +1,3 @@
.mat-dialog-actions {
justify-content: end;
}

View File

@ -0,0 +1,8 @@
<h1 mat-dialog-title>{{"dialog.confirmation"|translate}}</h1>
<div mat-dialog-content>
<p>{{"dialog.are_you_sure"|translate}}</p>
</div>
<div mat-dialog-actions>
<button mat-button (click)="onNoClick()">{{"dialog.no"|translate}}</button>
<button mat-button color="warn" (click)="onYesClick()">{{"dialog.ok"|translate}}</button>
</div>

View File

@ -0,0 +1,25 @@
import {Component, OnInit} from '@angular/core';
import {MatDialogRef} from "@angular/material";
@Component({
selector: 'app-are-you-sure',
templateUrl: './are-you-sure.component.html',
styleUrls: ['./are-you-sure.component.css']
})
export class AreYouSureComponent implements OnInit {
constructor(public dialogRef: MatDialogRef<AreYouSureComponent>) {
}
ngOnInit() {
}
onNoClick() {
this.dialogRef.close(false)
}
onYesClick() {
this.dialogRef.close(true)
}
}

View File

@ -9,4 +9,5 @@ export interface Project {
public: boolean; public: boolean;
chain: number; chain: number;
hidden: boolean; hidden: boolean;
paused: boolean;
} }

View File

@ -38,3 +38,11 @@
display: none; display: none;
} }
} }
.project-actions {
margin-top: 1em;
}
.project-actions button {
margin-right: 1em;
}

View File

@ -52,6 +52,28 @@
<pre>{{project | json}}</pre> <pre>{{project | json}}</pre>
</mat-expansion-panel> </mat-expansion-panel>
<mat-expansion-panel *ngIf="project && auth.logged" class="project-actions">
<mat-expansion-panel-header>
<mat-panel-title>{{"dashboard.actions" | translate}}</mat-panel-title>
</mat-expansion-panel-header>
<button mat-raised-button color="accent" (click)="resetFailedTasks()">
<mat-icon>replay</mat-icon>
{{"dashboard.reset_failed"|translate}}
</button>
<button mat-raised-button
color="primary"
(click)="pauseProject()"
*ngIf="!project.paused"
[title]="'dashboard.pause_hint'|translate">{{"dashboard.pause"|translate}}</button>
<button mat-raised-button color="primary" (click)="pauseProject()">
<mat-icon>pause</mat-icon>
{{"dashboard.pause"|translate}}</button>
<button mat-raised-button color="warn" (click)="hardReset()">
<mat-icon>warning</mat-icon>
{{"dashboard.hard_reset"|translate}}
</button>
</mat-expansion-panel>
</mat-card-content> </mat-card-content>
<mat-card-actions> <mat-card-actions>
<button mat-raised-button [routerLink]="'/projects'">Back</button> <button mat-raised-button [routerLink]="'/projects'">Back</button>

View File

@ -8,6 +8,8 @@ import {AssignedTasks, MonitoringSnapshot} from "../models/monitoring";
import {TranslateService} from "@ngx-translate/core"; import {TranslateService} from "@ngx-translate/core";
import {MessengerService} from "../messenger.service"; import {MessengerService} from "../messenger.service";
import {AuthService} from "../auth.service"; import {AuthService} from "../auth.service";
import {MatDialog} from "@angular/material";
import {AreYouSureComponent} from "../are-you-sure/are-you-sure.component";
@Component({ @Component({
@ -48,6 +50,7 @@ export class ProjectDashboardComponent implements OnInit {
private route: ActivatedRoute, private route: ActivatedRoute,
private translate: TranslateService, private translate: TranslateService,
public auth: AuthService, public auth: AuthService,
public dialog: MatDialog,
private messenger: MessengerService) { private messenger: MessengerService) {
} }
@ -340,4 +343,32 @@ export class ProjectDashboardComponent implements OnInit {
this.messenger.show(t)) this.messenger.show(t))
}) })
} }
resetFailedTasks() {
this.dialog.open(AreYouSureComponent, {
width: '250px',
}).afterClosed().subscribe(result => {
if (result) {
alert("yes")
}
});
}
pauseProject() {
this.dialog.open(AreYouSureComponent, {
width: '250px',
}).afterClosed().subscribe(result => {
if (result) {
this.project.paused = true;
this.apiService.updateProject(this.project).subscribe(() => {
this.translate.get("messenger.acknowledged").subscribe(t =>
this.messenger.show(t))
})
}
});
}
hardReset() {
}
} }

View File

@ -1,3 +1,5 @@
<mat-icon *ngIf="project.public" [title]="'project.public' | translate">public</mat-icon> <mat-icon *ngIf="project.public && !project.paused" [title]="'project.public' | translate">public</mat-icon>
<mat-icon *ngIf="!project.public && !project.hidden" [title]="'project.private'|translate">lock</mat-icon> <mat-icon *ngIf="!project.public && !project.hidden && !project.paused" [title]="'project.private'|translate">lock
<mat-icon *ngIf="project.hidden" [title]="'project.hidden'|translate">visibility_off</mat-icon> </mat-icon>
<mat-icon *ngIf="project.hidden && !project.paused" [title]="'project.hidden'|translate">visibility_off</mat-icon>
<mat-icon *ngIf="project.paused" [title]="'project.paused'|translate">pause</mat-icon>

View File

@ -5,3 +5,11 @@ button {
mat-panel-title > project-icon { mat-panel-title > project-icon {
margin-right: 1em; margin-right: 1em;
} }
.paused {
color: #9a9a9a;
}
.mat-expansion-panel-header-description {
flex-grow: 0;
}

View File

@ -12,11 +12,12 @@
<mat-accordion> <mat-accordion>
<mat-expansion-panel *ngFor="let project of projects" style="margin-top: 1em"> <mat-expansion-panel *ngFor="let project of projects" style="margin-top: 1em">
<mat-expansion-panel-header> <mat-expansion-panel-header>
<mat-panel-title> <mat-panel-title [class.paused]="project.paused">
<project-icon [project]="project"></project-icon> <project-icon [project]="project"></project-icon>
<span style="width: 3em">{{project.id}}</span>{{project.name}} <span style="width: 3em; align-self: center">{{project.id}}</span>
<span style="align-self: center">{{project.name}}</span>
</mat-panel-title> </mat-panel-title>
<mat-panel-description>{{project.motd}}</mat-panel-description> <mat-panel-description style="align-self: center">{{project.motd}}</mat-panel-description>
</mat-expansion-panel-header> </mat-expansion-panel-header>
<pre>{{project | json}}</pre> <pre>{{project | json}}</pre>
<div> <div>

View File

@ -74,7 +74,12 @@
"title": "Dashboard for", "title": "Dashboard for",
"metadata": "Project metadata", "metadata": "Project metadata",
"empty": "No tasks", "empty": "No tasks",
"refresh": "Refresh" "refresh": "Refresh",
"actions": "Actions",
"reset_failed": "Reset failed tasks",
"pause": "Pause",
"resume": "Resume",
"hard_reset": "Hard_reset"
}, },
"login": { "login": {
"title": "Login", "title": "Login",
@ -121,7 +126,8 @@
}, },
"messenger": { "messenger": {
"close": "Close", "close": "Close",
"unauthorized": "Unauthorized" "unauthorized": "Unauthorized",
"acknowledged": "Changes saved"
}, },
"manager_list": { "manager_list": {
"title": "Manager list", "title": "Manager list",
@ -149,5 +155,11 @@
"secret": "Secret", "secret": "Secret",
"update": "Update", "update": "Update",
"ok": "Updated" "ok": "Updated"
},
"dialog": {
"confirmation": "Confirmation",
"are_you_sure": "Are you sure?",
"no": "No",
"ok": "Ok"
} }
} }

View File

@ -74,7 +74,8 @@
"title": "Tableau de bord pour ", "title": "Tableau de bord pour ",
"metadata": "Métadonnés du projet", "metadata": "Métadonnés du projet",
"empty": "Aucune tâche", "empty": "Aucune tâche",
"refresh": "Rafraîchir" "refresh": "Rafraîchir",
"actions": "Actions"
}, },
"login": { "login": {
"title": "Ouvrir un session", "title": "Ouvrir un session",
@ -123,7 +124,8 @@
}, },
"messenger": { "messenger": {
"close": "Fermer", "close": "Fermer",
"unauthorized": "Non autorisé" "unauthorized": "Non autorisé",
"acknowledged": "Changements enregistrés"
}, },
"manager_list": { "manager_list": {
"title": "Liste ", "title": "Liste ",