diff --git a/api/main.go b/api/main.go index 5c12015..54e3c54 100644 --- a/api/main.go +++ b/api/main.go @@ -98,6 +98,7 @@ func New() *WebAPI { api.router.POST("/project/secret/:id", LogRequestMiddleware(api.SetSecret)) api.router.GET("/project/webhook_secret/:id", LogRequestMiddleware(api.GetWebhookSecret)) api.router.POST("/project/webhook_secret/:id", LogRequestMiddleware(api.SetWebhookSecret)) + api.router.POST("/project/reset_failed_tasks/:id", LogRequestMiddleware(api.ResetFailedTasks)) 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 c00dc1e..b972eb4 100644 --- a/api/models.go +++ b/api/models.go @@ -15,7 +15,7 @@ const ( type JsonResponse struct { Ok bool `json:"ok"` Message string `json:"message,omitempty"` - RateLimitDelay string `json:"rate_limit_delay,omitempty"` + RateLimitDelay float64 `json:"rate_limit_delay,omitempty"` Content interface{} `json:"content,omitempty"` } @@ -305,6 +305,11 @@ type GetSecretResponse struct { type SetWebhookSecretRequest struct { WebhookSecret string `json:"webhook_secret"` } + type GetWebhookSecretResponse struct { WebhookSecret string `json:"webhook_secret"` } + +type ResetFailedTaskResponse struct { + AffectedTasks int64 `json:"affected_tasks"` +} diff --git a/api/project.go b/api/project.go index 9a9ea66..c68a4e3 100644 --- a/api/project.go +++ b/api/project.go @@ -120,7 +120,7 @@ func (api *WebAPI) CreateProject(r *Request) { } api.Database.SetManagerRoleOn(manager.(*storage.Manager).Id, id, - storage.RoleManageAccess|storage.RoleRead|storage.RoleEdit|storage.RoleSecret) + storage.RoleManageAccess|storage.RoleRead|storage.RoleEdit|storage.RoleSecret|storage.RoleMaintenance) r.OkJson(JsonResponse{ Ok: true, Content: CreateProjectResponse{ @@ -209,6 +209,9 @@ func (api *WebAPI) UpdateProject(r *Request) { "project": project, }).Warn("Error during project update") } else { + api.SubmitLimiters.Delete(project.Id) + api.AssignLimiters.Delete(project.Id) + r.OkJson(JsonResponse{ Ok: true, }) @@ -688,3 +691,35 @@ func (api *WebAPI) SetWebhookSecret(r *Request) { }) } } + +func (api *WebAPI) ResetFailedTasks(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.ResetFailedTasks(pid) + + r.OkJson(JsonResponse{ + Ok: true, + Content: ResetFailedTaskResponse{ + AffectedTasks: res, + }, + }) +} diff --git a/api/task.go b/api/task.go index fa724c5..fa77fe4 100644 --- a/api/task.go +++ b/api/task.go @@ -60,7 +60,7 @@ func (api *WebAPI) SubmitTask(r *Request) { r.Json(JsonResponse{ Ok: false, Message: "Too many requests", - RateLimitDelay: strconv.FormatFloat(delay, 'f', -1, 64), + RateLimitDelay: delay, }, 429) return } @@ -111,7 +111,7 @@ func (api *WebAPI) GetTaskFromProject(r *Request) { r.Json(JsonResponse{ Ok: false, Message: "Too many requests", - RateLimitDelay: strconv.FormatFloat(delay, 'f', -1, 64), + RateLimitDelay: delay, }, 429) return } diff --git a/schema.sql b/schema.sql index 0c9a6b8..0089cbb 100755 --- a/schema.sql +++ b/schema.sql @@ -48,7 +48,7 @@ CREATE TABLE task project INTEGER REFERENCES project (id), assignee INTEGER REFERENCES worker (id), max_assign_time INTEGER DEFAULT 0, - assign_time INTEGER DEFAULT 0, + assign_time INTEGER DEFAULT NULL, verification_count INTEGER DEFAULT 0, priority SMALLINT DEFAULT 0, retries SMALLINT DEFAULT 0, diff --git a/storage/auth.go b/storage/auth.go index 5c69e2f..6d34cb5 100644 --- a/storage/auth.go +++ b/storage/auth.go @@ -16,6 +16,7 @@ const ( RoleEdit ManagerRole = 2 RoleManageAccess ManagerRole = 4 RoleSecret ManagerRole = 8 + RoleMaintenance ManagerRole = 16 ) type Manager struct { diff --git a/storage/maintenance.go b/storage/maintenance.go new file mode 100644 index 0000000..0aa6255 --- /dev/null +++ b/storage/maintenance.go @@ -0,0 +1,13 @@ +package storage + +func (database *Database) ResetFailedTasks(pid int64) int64 { + + db := database.getDB() + + res, err := db.Exec(`UPDATE task SET status=1, retries=0, assign_time=NULL, assignee=NULL + WHERE project=$1 AND status=2`, pid) + handleErr(err) + + rowsAffected, _ := res.RowsAffected() + return rowsAffected +} diff --git a/test/schema.sql b/test/schema.sql index 79559a4..83c4f08 100755 --- a/test/schema.sql +++ b/test/schema.sql @@ -16,19 +16,19 @@ CREATE TABLE project id SERIAL PRIMARY KEY NOT NULL, priority INTEGER DEFAULT 0 NOT NULL, closed_task_count INT DEFAULT 0 NOT NULL, - chain INT DEFAULT NULL REFERENCES project (id), - public boolean NOT NULL, - hidden boolean NOT NULL, - paused boolean NOT NULL, - name TEXT UNIQUE NOT NULL, - clone_url TEXT NOT NULL, - git_repo TEXT NOT NULL, - version TEXT NOT NULL, - motd TEXT NOT NULL, - secret TEXT NOT NULL DEFAULT '{}', - webhook_secret TEXT NOT NULL, - assign_rate DOUBLE PRECISION NOT NULL, - submit_rate DOUBLE PRECISION NOT NULL + chain INT DEFAULT NULL REFERENCES project (id), + public boolean NOT NULL, + hidden boolean NOT NULL, + paused boolean NOT NULL, + name TEXT UNIQUE NOT NULL, + clone_url TEXT NOT NULL, + git_repo TEXT NOT NULL, + version TEXT NOT NULL, + motd TEXT NOT NULL, + secret TEXT NOT NULL DEFAULT '{}', + webhook_secret TEXT NOT NULL, + assign_rate DOUBLE PRECISION NOT NULL, + submit_rate DOUBLE PRECISION NOT NULL ); CREATE TABLE worker_access @@ -48,7 +48,7 @@ CREATE TABLE task project INTEGER REFERENCES project (id), assignee INTEGER REFERENCES worker (id), max_assign_time INTEGER DEFAULT 0, - assign_time INTEGER DEFAULT 0, + assign_time INTEGER DEFAULT NULL, verification_count INTEGER DEFAULT 0, priority SMALLINT DEFAULT 0, retries SMALLINT DEFAULT 0, diff --git a/web/angular/src/app/api.service.ts b/web/angular/src/app/api.service.ts index f0fca8d..272ece8 100755 --- a/web/angular/src/app/api.service.ts +++ b/web/angular/src/app/api.service.ts @@ -99,19 +99,23 @@ export class ApiService { } getSecret(pid: number) { - return this.http.get(this.url + `/project/secret/${pid}`,) + return this.http.get(this.url + `/project/secret/${pid}`, this.options) } setSecret(pid: number, secret: string) { - return this.http.post(this.url + `/project/secret/${pid}`, {"secret": secret}) + return this.http.post(this.url + `/project/secret/${pid}`, {"secret": secret}, this.options) } getWebhookSecret(pid: number) { - return this.http.get(this.url + `/project/webhook_secret/${pid}`,) + return this.http.get(this.url + `/project/webhook_secret/${pid}`, this.options) } setWebhookSecret(pid: number, secret: string) { - return this.http.post(this.url + `/project/webhook_secret/${pid}`, {"webhook_secret": secret}) + return this.http.post(this.url + `/project/webhook_secret/${pid}`, {"webhook_secret": secret}, this.options) + } + + resetFailedTasks(pid: number) { + return this.http.post(this.url + `/project/reset_failed_tasks/${pid}`, null, this.options) } } diff --git a/web/angular/src/app/models/project.ts b/web/angular/src/app/models/project.ts index 3ce77bf..263bac2 100644 --- a/web/angular/src/app/models/project.ts +++ b/web/angular/src/app/models/project.ts @@ -10,4 +10,6 @@ export interface Project { chain: number; hidden: boolean; paused: boolean; + assign_rate: number; + submit_rate: number; } 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 6a3ae1d..d54dc5f 100644 --- a/web/angular/src/app/project-dashboard/project-dashboard.component.html +++ b/web/angular/src/app/project-dashboard/project-dashboard.component.html @@ -84,7 +84,7 @@ - + + diff --git a/web/angular/src/app/project-secret/project-secret.component.html b/web/angular/src/app/project-secret/project-secret.component.html index fe73f85..a731572 100644 --- a/web/angular/src/app/project-secret/project-secret.component.html +++ b/web/angular/src/app/project-secret/project-secret.component.html @@ -39,7 +39,7 @@ - + diff --git a/web/angular/src/app/update-project/update-project.component.html b/web/angular/src/app/update-project/update-project.component.html index 628c7ca..f20ffb6 100644 --- a/web/angular/src/app/update-project/update-project.component.html +++ b/web/angular/src/app/update-project/update-project.component.html @@ -38,10 +38,18 @@ {{'project.git_repo_hint'|translate}} + + {{"project.assign_rate"|translate}} + + + + {{"project.submit_rate"|translate}} + + - + diff --git a/web/angular/src/assets/i18n/en.json b/web/angular/src/assets/i18n/en.json index 6f9b888..4e8ad98 100644 --- a/web/angular/src/assets/i18n/en.json +++ b/web/angular/src/assets/i18n/en.json @@ -9,7 +9,7 @@ "worker_dashboard": "Workers", "account": "Account", "manager_list": "Managers", - "back": "back" + "back": "Back" }, "logs": { "title": "Logs", @@ -69,7 +69,11 @@ "chain": "Chain tasks to", "manager_select": "Give access to manager", "version": "Git version (commit hash)", - "secret": "Secret" + "secret": "Secret", + "reset_response": "Reset failed tasks: ", + "assign_rate": "Task assign rate limit", + "submit_rate": "Task submit rate limit", + "rate": "per second" }, "dashboard": { "title": "Dashboard for", diff --git a/web/angular/src/assets/i18n/fr.json b/web/angular/src/assets/i18n/fr.json index c2c53c4..cef1007 100644 --- a/web/angular/src/assets/i18n/fr.json +++ b/web/angular/src/assets/i18n/fr.json @@ -9,7 +9,7 @@ "worker_dashboard": "Workers", "account": "Compte", "manager_list": "Managers", - "back": "Arrière" + "back": "Retour" }, "logs": { "title": "Journaux", @@ -69,7 +69,11 @@ "perms": "Permissions", "chain": "Enchainer les tâches vers", "manager_select": "Donner accès à", - "version": "Version git (hash du commit)" + "version": "Version git (hash du commit)", + "reset_response": "Réinitialisé les tâches en échec: ", + "assign_rate": "Taux d'assignation de tâches", + "submit_rate": "Taux de soumission de tâches", + "rate": "par seconde" }, "dashboard": { "title": "Tableau de bord pour ",