diff --git a/api/main.go b/api/main.go
index efc3140..4cbe2bf 100644
--- a/api/main.go
+++ b/api/main.go
@@ -106,6 +106,7 @@ func New() *WebAPI {
api.router.POST("/project/webhook_secret/:id", LogRequestMiddleware(api.SetWebhookSecret))
api.router.POST("/project/reset_failed_tasks/:id", LogRequestMiddleware(api.ResetFailedTasks))
api.router.POST("/project/hard_reset/:id", LogRequestMiddleware(api.HardReset))
+ api.router.POST("/project/reclaim_assigned_tasks/:id", LogRequestMiddleware(api.ReclaimAssignedTasks))
api.router.POST("/task/submit", LogRequestMiddleware(api.SubmitTask))
api.router.GET("/task/get/:project", LogRequestMiddleware(api.GetTaskFromProject))
diff --git a/api/models.go b/api/models.go
index 8061e72..ea372c6 100644
--- a/api/models.go
+++ b/api/models.go
@@ -318,3 +318,7 @@ type ResetFailedTaskResponse struct {
type HardResetResponse struct {
AffectedTasks int64 `json:"affected_tasks"`
}
+
+type ReclaimAssignedTasksResponse struct {
+ AffectedTasks int64 `json:"affected_tasks"`
+}
diff --git a/api/project.go b/api/project.go
index fad5d73..6f0f6dc 100644
--- a/api/project.go
+++ b/api/project.go
@@ -756,3 +756,35 @@ func (api *WebAPI) HardReset(r *Request) {
},
})
}
+
+func (api *WebAPI) ReclaimAssignedTasks(r *Request) {
+
+ pid, err := strconv.ParseInt(r.Ctx.UserValue("id").(string), 10, 64)
+ if err != nil || pid <= 0 {
+ r.Json(JsonResponse{
+ Ok: false,
+ Message: "Invalid project id",
+ }, 400)
+ return
+ }
+
+ sess := api.Session.StartFasthttp(r.Ctx)
+ manager := sess.Get("manager")
+
+ if !isActionOnProjectAuthorized(pid, manager, storage.RoleMaintenance, api.Database) {
+ r.Json(JsonResponse{
+ Ok: false,
+ Message: "Unauthorized",
+ }, 403)
+ return
+ }
+
+ res := api.Database.ReclaimAssignedTasks(pid)
+
+ r.OkJson(JsonResponse{
+ Ok: true,
+ Content: ReclaimAssignedTasksResponse{
+ AffectedTasks: res,
+ },
+ })
+}
diff --git a/storage/maintenance.go b/storage/maintenance.go
index 88d87ea..30f06a5 100644
--- a/storage/maintenance.go
+++ b/storage/maintenance.go
@@ -50,3 +50,21 @@ func (database Database) HardReset(pid int64) int64 {
return rowsAffected
}
+
+func (database Database) ReclaimAssignedTasks(pid int64) int64 {
+
+ db := database.getDB()
+
+ res, err := db.Exec(`UPDATE task SET assignee=NULL, assign_time=NULL
+ WHERE project=$1 AND status=1`, pid)
+ handleErr(err)
+
+ rowsAffected, _ := res.RowsAffected()
+
+ logrus.WithFields(logrus.Fields{
+ "rowsAffected": rowsAffected,
+ "project": pid,
+ }).Info("Reclaim assigned tasks")
+
+ return rowsAffected
+}
diff --git a/web/angular/src/app/api.service.ts b/web/angular/src/app/api.service.ts
index 42b0f73..0c40d08 100755
--- a/web/angular/src/app/api.service.ts
+++ b/web/angular/src/app/api.service.ts
@@ -122,4 +122,8 @@ export class ApiService {
return this.http.post(this.url + `/project/hard_reset/${pid}`, null, this.options);
}
+ reclaimAssignedTasks(pid: number) {
+ return this.http.post(this.url + `/project/reclaim_assigned_tasks/${pid}`, null, this.options);
+ }
+
}
diff --git a/web/angular/src/app/project-dashboard/project-dashboard.component.html b/web/angular/src/app/project-dashboard/project-dashboard.component.html
index da03a7a..b7dc301 100644
--- a/web/angular/src/app/project-dashboard/project-dashboard.component.html
+++ b/web/angular/src/app/project-dashboard/project-dashboard.component.html
@@ -85,6 +85,10 @@
warning
{{"dashboard.hard_reset"|translate}}
+
diff --git a/web/angular/src/app/project-dashboard/project-dashboard.component.ts b/web/angular/src/app/project-dashboard/project-dashboard.component.ts
index a47871d..897115a 100644
--- a/web/angular/src/app/project-dashboard/project-dashboard.component.ts
+++ b/web/angular/src/app/project-dashboard/project-dashboard.component.ts
@@ -442,4 +442,20 @@ export class ProjectDashboardComponent implements OnInit {
}
});
}
+
+ reclaimAssignedTasks() {
+ this.dialog.open(AreYouSureComponent, {
+ width: '250px',
+ }).afterClosed().subscribe(result => {
+ if (result) {
+ this.apiService.reclaimAssignedTasks(this.project.id).subscribe(data => {
+ this.translate.get('project.reclaim_response').subscribe(t =>
+ this.messenger.show(t + data['content']['affected_tasks']));
+ }, error => {
+ this.translate.get('messenger.unauthorized').subscribe(t =>
+ this.messenger.show(t));
+ });
+ }
+ });
+ }
}
diff --git a/web/angular/src/assets/i18n/en.json b/web/angular/src/assets/i18n/en.json
index b57e3ce..101e6cd 100644
--- a/web/angular/src/assets/i18n/en.json
+++ b/web/angular/src/assets/i18n/en.json
@@ -72,6 +72,7 @@
"secret": "Secret",
"reset_response": "Reset failed tasks: ",
"hard_reset_response": "Deleted tasks: ",
+ "reclaim_response": "Reclaimed tasks: ",
"assign_rate": "Task assign rate limit",
"submit_rate": "Task submit rate limit",
"rate": "per second",
@@ -87,7 +88,8 @@
"reset_failed": "Reset failed tasks",
"pause": "Pause",
"resume": "Resume",
- "hard_reset": "Hard_reset"
+ "hard_reset": "Hard reset",
+ "reclaim": "Reclaim assigned tasks"
},
"login": {
"title": "Login",
diff --git a/web/angular/src/assets/i18n/fr.json b/web/angular/src/assets/i18n/fr.json
index 151d041..6e4e264 100644
--- a/web/angular/src/assets/i18n/fr.json
+++ b/web/angular/src/assets/i18n/fr.json
@@ -72,6 +72,7 @@
"version": "Version git (hash du commit)",
"reset_response": "Réinitialisé les tâches en échec: ",
"hard_reset_response": "Supprimé toutes les tâches: ",
+ "reclaim_response": "Désaffecté les tâches: ",
"assign_rate": "Taux d'assignation de tâches",
"submit_rate": "Taux de soumission de tâches",
"rate": "par seconde",
@@ -83,7 +84,8 @@
"metadata": "Métadonnés du projet",
"empty": "Aucune tâche",
"refresh": "Rafraîchir",
- "actions": "Actions"
+ "actions": "Actions",
+ "reclaim": "Déaffecter les tâches"
},
"login": {
"title": "Ouvrir un session",