Add project secret & bug fix

This commit is contained in:
simon987 2019-02-19 19:38:54 -05:00
parent 94c3ce3267
commit f235bfb588
27 changed files with 443 additions and 54 deletions

View File

@ -91,6 +91,8 @@ func New() *WebAPI {
api.router.POST("/project/request_access", LogRequestMiddleware(api.CreateWorkerAccess)) api.router.POST("/project/request_access", LogRequestMiddleware(api.CreateWorkerAccess))
api.router.POST("/project/accept_request/:id/:wid", LogRequestMiddleware(api.AcceptAccessRequest)) api.router.POST("/project/accept_request/:id/:wid", LogRequestMiddleware(api.AcceptAccessRequest))
api.router.POST("/project/reject_request/:id/:wid", LogRequestMiddleware(api.RejectAccessRequest)) api.router.POST("/project/reject_request/:id/:wid", LogRequestMiddleware(api.RejectAccessRequest))
api.router.GET("/project/secret/:id", LogRequestMiddleware(api.GetSecret))
api.router.POST("/project/secret/:id", LogRequestMiddleware(api.SetSecret))
api.router.POST("/task/submit", LogRequestMiddleware(api.SubmitTask)) api.router.POST("/task/submit", LogRequestMiddleware(api.SubmitTask))
api.router.GET("/task/get/:project", LogRequestMiddleware(api.GetTaskFromProject)) api.router.GET("/task/get/:project", LogRequestMiddleware(api.GetTaskFromProject))

View File

@ -214,6 +214,10 @@ type ReleaseTaskRequest struct {
Verification int64 `json:"verification"` Verification int64 `json:"verification"`
} }
func (r *ReleaseTaskRequest) IsValid() bool {
return r.TaskId != 0
}
type ReleaseTaskResponse struct { type ReleaseTaskResponse struct {
Updated bool `json:"updated"` Updated bool `json:"updated"`
} }
@ -276,3 +280,11 @@ type Info struct {
Name string `json:"name"` Name string `json:"name"`
Version string `json:"version"` Version string `json:"version"`
} }
type SetSecretRequest struct {
Secret string `json:"secret"`
}
type GetSecretResponse struct {
Secret string `json:"secret"`
}

View File

@ -10,7 +10,13 @@ import (
func (api *WebAPI) GetProject(r *Request) { func (api *WebAPI) GetProject(r *Request) {
id, err := strconv.ParseInt(r.Ctx.UserValue("id").(string), 10, 64) id, err := strconv.ParseInt(r.Ctx.UserValue("id").(string), 10, 64)
handleErr(err, r) //todo handle invalid id if err != nil || id <= 0 {
r.Json(JsonResponse{
Ok: false,
Message: "Invalid worker id",
}, 400)
return
}
sess := api.Session.StartFasthttp(r.Ctx) sess := api.Session.StartFasthttp(r.Ctx)
manager := sess.Get("manager") manager := sess.Get("manager")
@ -263,7 +269,13 @@ func (api *WebAPI) GetProjectList(r *Request) {
func (api *WebAPI) GetAssigneeStatsForProject(r *Request) { func (api *WebAPI) GetAssigneeStatsForProject(r *Request) {
id, err := strconv.ParseInt(r.Ctx.UserValue("id").(string), 10, 64) id, err := strconv.ParseInt(r.Ctx.UserValue("id").(string), 10, 64)
handleErr(err, r) //todo handle invalid id if err != nil || id <= 0 {
r.Json(JsonResponse{
Ok: false,
Message: "Invalid worker id",
}, 400)
return
}
stats := api.Database.GetAssigneeStats(id, 16) stats := api.Database.GetAssigneeStats(id, 16)
@ -281,7 +293,13 @@ func (api *WebAPI) GetWorkerAccessListForProject(r *Request) {
manager := sess.Get("manager") manager := sess.Get("manager")
id, err := strconv.ParseInt(r.Ctx.UserValue("id").(string), 10, 64) id, err := strconv.ParseInt(r.Ctx.UserValue("id").(string), 10, 64)
handleErr(err, r) //todo handle invalid id if err != nil || id <= 0 {
r.Json(JsonResponse{
Ok: false,
Message: "Invalid worker id",
}, 400)
return
}
if !isActionOnProjectAuthorized(id, manager, storage.ROLE_MANAGE_ACCESS, api.Database) { if !isActionOnProjectAuthorized(id, manager, storage.ROLE_MANAGE_ACCESS, api.Database) {
r.Json(JsonResponse{ r.Json(JsonResponse{
@ -352,10 +370,22 @@ func (api *WebAPI) CreateWorkerAccess(r *Request) {
func (api *WebAPI) AcceptAccessRequest(r *Request) { func (api *WebAPI) AcceptAccessRequest(r *Request) {
pid, err := strconv.ParseInt(r.Ctx.UserValue("id").(string), 10, 64) pid, err := strconv.ParseInt(r.Ctx.UserValue("id").(string), 10, 64)
handleErr(err, r) //todo handle invalid id if err != nil || pid <= 0 {
r.Json(JsonResponse{
Ok: false,
Message: "Invalid worker id",
}, 400)
return
}
wid, err := strconv.ParseInt(r.Ctx.UserValue("wid").(string), 10, 64) wid, err := strconv.ParseInt(r.Ctx.UserValue("wid").(string), 10, 64)
handleErr(err, r) //todo handle invalid id if err != nil || wid <= 0 {
r.Json(JsonResponse{
Ok: false,
Message: "Invalid worker id",
}, 400)
return
}
sess := api.Session.StartFasthttp(r.Ctx) sess := api.Session.StartFasthttp(r.Ctx)
manager := sess.Get("manager") manager := sess.Get("manager")
@ -385,10 +415,22 @@ func (api *WebAPI) AcceptAccessRequest(r *Request) {
func (api *WebAPI) RejectAccessRequest(r *Request) { func (api *WebAPI) RejectAccessRequest(r *Request) {
pid, err := strconv.ParseInt(r.Ctx.UserValue("id").(string), 10, 64) pid, err := strconv.ParseInt(r.Ctx.UserValue("id").(string), 10, 64)
handleErr(err, r) //todo handle invalid id if err != nil || pid <= 0 {
r.Json(JsonResponse{
Ok: false,
Message: "Invalid project id",
}, 400)
return
}
wid, err := strconv.ParseInt(r.Ctx.UserValue("wid").(string), 10, 64) wid, err := strconv.ParseInt(r.Ctx.UserValue("wid").(string), 10, 64)
handleErr(err, r) //todo handle invalid id if err != nil || wid <= 0 {
r.Json(JsonResponse{
Ok: false,
Message: "Invalid worker id",
}, 400)
return
}
ok := api.Database.RejectAccessRequest(wid, pid) ok := api.Database.RejectAccessRequest(wid, pid)
@ -407,7 +449,13 @@ func (api *WebAPI) RejectAccessRequest(r *Request) {
func (api *WebAPI) SetManagerRoleOnProject(r *Request) { func (api *WebAPI) SetManagerRoleOnProject(r *Request) {
pid, err := strconv.ParseInt(r.Ctx.UserValue("id").(string), 10, 64) pid, err := strconv.ParseInt(r.Ctx.UserValue("id").(string), 10, 64)
handleErr(err, r) //todo handle invalid id if err != nil || pid <= 0 {
r.Json(JsonResponse{
Ok: false,
Message: "Invalid project id",
}, 400)
return
}
req := &SetManagerRoleOnProjectRequest{} req := &SetManagerRoleOnProjectRequest{}
err = json.Unmarshal(r.Ctx.Request.Body(), req) err = json.Unmarshal(r.Ctx.Request.Body(), req)
@ -435,3 +483,95 @@ func (api *WebAPI) SetManagerRoleOnProject(r *Request) {
Ok: true, Ok: true,
}) })
} }
func (api *WebAPI) SetSecret(r *Request) {
pid, err := strconv.ParseInt(r.Ctx.UserValue("id").(string), 10, 64)
handleErr(err, r) //todo handle invalid id
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.ROLE_EDIT, api.Database) {
r.Json(JsonResponse{
Ok: false,
Message: "Unauthorized",
}, 403)
return
}
req := &SetSecretRequest{}
err = json.Unmarshal(r.Ctx.Request.Body(), req)
if err != nil {
r.Json(JsonResponse{
Ok: false,
Message: "Could not parse request",
}, 400)
return
}
api.Database.SetSecret(pid, req.Secret)
r.OkJson(JsonResponse{
Ok: true,
})
}
func (api *WebAPI) GetSecret(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
}
var secret string
worker, err := api.validateSignature(r)
if err == nil {
secret, err = api.Database.GetSecret(pid, worker.Id)
if err != nil {
r.Json(JsonResponse{
Ok: false,
Message: "Unauthorized",
}, 403)
return
}
r.OkJson(JsonResponse{
Ok: true,
Content: GetSecretResponse{
Secret: secret,
},
})
return
}
sess := api.Session.StartFasthttp(r.Ctx)
manager := sess.Get("manager")
if !isActionOnProjectAuthorized(pid, manager, storage.ROLE_EDIT, api.Database) {
r.Json(JsonResponse{
Ok: false,
Message: "Unauthorized",
}, 403)
return
}
secret, _ = api.Database.GetSecret(pid, 0)
r.OkJson(JsonResponse{
Ok: true,
Content: GetSecretResponse{
Secret: secret,
},
})
}

View File

@ -209,7 +209,17 @@ func (api *WebAPI) ReleaseTask(r *Request) {
Ok: false, Ok: false,
Message: "Could not parse request", Message: "Could not parse request",
}, 400) }, 400)
return
} }
if !req.IsValid() {
r.Json(JsonResponse{
Ok: false,
Message: "Invalid request",
}, 400)
return
}
res := api.Database.ReleaseTask(req.TaskId, worker.Id, req.Result, req.Verification) res := api.Database.ReleaseTask(req.TaskId, worker.Id, req.Result, req.Verification)
response := JsonResponse{ response := JsonResponse{

View File

@ -3,7 +3,8 @@ server:
database: database:
conn_str: "user=task_tracker dbname=task_tracker_test sslmode=disable" conn_str: "user=task_tracker dbname=task_tracker_test sslmode=disable"
log_levels: ["debug", "error", "trace", "info", "warn"] # log_levels: ["debug", "error", "trace", "info", "warn"]
log_levels: ["error", "info", "warn"]
git: git:
webhook_secret: "very_secret_secret" webhook_secret: "very_secret_secret"

View File

@ -3,6 +3,7 @@ package main
import ( import (
"github.com/simon987/task_tracker/api" "github.com/simon987/task_tracker/api"
"github.com/simon987/task_tracker/config" "github.com/simon987/task_tracker/config"
//"github.com/simon987/task_tracker/storage"
"math/rand" "math/rand"
"time" "time"
) )

View File

@ -1,8 +1,6 @@
DROP TABLE IF EXISTS worker, project, task, log_entry, DROP TABLE IF EXISTS worker, project, task, log_entry,
worker_access, manager, manager_has_role_on_project, project_monitoring_snapshot, worker_access, manager, manager_has_role_on_project, project_monitoring_snapshot,
worker_verifies_task; worker_verifies_task;
DROP TYPE IF EXISTS status;
DROP TYPE IF EXISTS log_level;
CREATE TABLE worker CREATE TABLE worker
( (
@ -25,7 +23,8 @@ CREATE TABLE project
clone_url TEXT NOT NULL, clone_url TEXT NOT NULL,
git_repo TEXT UNIQUE NOT NULL, git_repo TEXT UNIQUE NOT NULL,
version TEXT NOT NULL, version TEXT NOT NULL,
motd TEXT NOT NULL motd TEXT NOT NULL,
secret TEXT NOT NULL DEFAULT '{}'
); );
CREATE TABLE worker_access CREATE TABLE worker_access
@ -83,7 +82,8 @@ CREATE TABLE manager_has_role_on_project
( (
manager INTEGER REFERENCES manager (id) NOT NULL, manager INTEGER REFERENCES manager (id) NOT NULL,
role SMALLINT NOT NULL, role SMALLINT NOT NULL,
project INTEGER REFERENCES project (id) NOT NULL project INTEGER REFERENCES project (id) NOT NULL,
PRIMARY KEY (manager, project)
); );
CREATE TABLE project_monitoring_snapshot CREATE TABLE project_monitoring_snapshot
@ -102,7 +102,10 @@ $$
DECLARE DECLARE
chain INTEGER; chain INTEGER;
BEGIN BEGIN
UPDATE project SET closed_task_count=closed_task_count + 1 WHERE id = OLD.project returning project.chain into chain; if OLD.assignee IS NOT NULL THEN
UPDATE project
SET closed_task_count=closed_task_count + 1
WHERE id = OLD.project returning project.chain into chain;
UPDATE worker SET closed_task_count=closed_task_count + 1 WHERE id = OLD.assignee; UPDATE worker SET closed_task_count=closed_task_count + 1 WHERE id = OLD.assignee;
IF chain != 0 THEN IF chain != 0 THEN
INSERT into task (hash64, project, assignee, max_assign_time, assign_time, verification_count, INSERT into task (hash64, project, assignee, max_assign_time, assign_time, verification_count,
@ -111,6 +114,7 @@ BEGIN
old.verification_count, old.priority, 0, old.max_retries, 1, old.verification_count, old.priority, 0, old.max_retries, 1,
old.recipe); old.recipe);
end if; end if;
end if;
RETURN OLD; RETURN OLD;
END; END;
$$ LANGUAGE 'plpgsql'; $$ LANGUAGE 'plpgsql';
@ -135,7 +139,7 @@ CREATE TRIGGER on_manager_insert
FOR EACH ROW FOR EACH ROW
EXECUTE PROCEDURE on_manager_insert(); EXECUTE PROCEDURE on_manager_insert();
CREATE OR REPLACE FUNCTION release_task_ok(wid INT, tid INT, ver INT) RETURNS BOOLEAN AS CREATE OR REPLACE FUNCTION release_task_ok(wid INT, tid INT, ver BIGINT) RETURNS BOOLEAN AS
$$ $$
DECLARE DECLARE
res INT = NULL; res INT = NULL;

View File

@ -39,6 +39,8 @@ func (database *Database) getDB() *sql.DB {
logrus.Fatal(err) logrus.Fatal(err)
} }
db.SetMaxOpenConns(50)
database.db = db database.db = db
} else { } else {
err := database.db.Ping() err := database.db.Ping()

View File

@ -192,3 +192,36 @@ func (database *Database) GetAssigneeStats(pid int64, count int64) *[]AssignedTa
return &assignees return &assignees
} }
func (database *Database) GetSecret(pid int64, workerId int64) (secret string, err error) {
db := database.getDB()
var row *sql.Row
if workerId == 0 {
row = db.QueryRow(`SELECT secret FROM project WHERE id=$1`, pid)
} else {
row = db.QueryRow(`SELECT secret FROM project
WHERE id =$1 AND (
SELECT a.role_assign FROM worker_access a WHERE a.worker=$2 AND a.project=$1
)`, pid, workerId)
}
err = row.Scan(&secret)
handleErr(err)
return
}
func (database *Database) SetSecret(pid int64, secret string) {
db := database.getDB()
res, err := db.Exec(`UPDATE project SET secret=$1 WHERE id=$2`, secret, pid)
handleErr(err)
rowsAffected, _ := res.RowsAffected()
logrus.WithFields(logrus.Fields{
"rowsAffected": rowsAffected,
"project": pid,
}).Info("Set secret")
}

View File

@ -83,7 +83,7 @@ func (database *Database) GetTask(worker *Worker) *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 assignee IS NULL AND task.status=1
AND (project.public OR EXISTS ( 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
)) ))
AND wvt.task IS NULL AND wvt.task IS NULL
@ -144,9 +144,10 @@ func (database Database) ReleaseTask(id int64, workerId int64, result TaskResult
var taskUpdated bool var taskUpdated bool
if result == TR_OK { if result == TR_OK {
row := db.QueryRow(`SELECT release_task_ok($1,$2,$3)`, workerId, id, verification) row := db.QueryRow(fmt.Sprintf(`SELECT release_task_ok(%d,%d,%d)`, workerId, id, verification))
_ = row.Scan(&taskUpdated) err := row.Scan(&taskUpdated)
handleErr(err)
} else if result == TR_FAIL { } else if result == TR_FAIL {
res, err := db.Exec(`UPDATE task SET (status, assignee, retries) = res, err := db.Exec(`UPDATE task SET (status, assignee, retries) =
(CASE WHEN retries+1 >= max_retries THEN 2 ELSE 1 END, NULL, retries+1) (CASE WHEN retries+1 >= max_retries THEN 2 ELSE 1 END, NULL, retries+1)

View File

@ -4,6 +4,7 @@ import (
"fmt" "fmt"
"github.com/simon987/task_tracker/api" "github.com/simon987/task_tracker/api"
"github.com/simon987/task_tracker/storage" "github.com/simon987/task_tracker/storage"
"math"
"testing" "testing"
) )
@ -780,6 +781,42 @@ func TestTaskChain(t *testing.T) {
} }
} }
func TestTaskReleaseBigInt(t *testing.T) {
createTask(api.SubmitTaskRequest{
Project: testProject,
VerificationCount: 1,
Recipe: "bigint",
}, testWorker)
createTask(api.SubmitTaskRequest{
Project: testProject,
VerificationCount: 1,
Recipe: "smallint",
}, testWorker)
tid := getTaskFromProject(testProject, testWorker).Content.Task.Id
tid2 := getTaskFromProject(testProject, testWorker).Content.Task.Id
r := releaseTask(api.ReleaseTaskRequest{
Verification: math.MaxInt64,
Result: storage.TR_OK,
TaskId: tid,
}, testWorker)
r2 := releaseTask(api.ReleaseTaskRequest{
Verification: math.MinInt64,
Result: storage.TR_OK,
TaskId: tid2,
}, testWorker)
if r.Content.Updated != true {
t.Error()
}
if r2.Content.Updated != true {
t.Error()
}
}
func createTask(request api.SubmitTaskRequest, worker *storage.Worker) (ar api.JsonResponse) { func createTask(request api.SubmitTaskRequest, worker *storage.Worker) (ar api.JsonResponse) {
r := Post("/task/submit", request, worker, nil) r := Post("/task/submit", request, worker, nil)
UnmarshalResponse(r, &ar) UnmarshalResponse(r, &ar)

View File

@ -3,6 +3,7 @@ package test
import ( import (
"github.com/simon987/task_tracker/api" "github.com/simon987/task_tracker/api"
"github.com/simon987/task_tracker/config" "github.com/simon987/task_tracker/config"
"github.com/simon987/task_tracker/storage"
"net/http" "net/http"
"testing" "testing"
"time" "time"
@ -12,6 +13,9 @@ var testApi *api.WebAPI
var testAdminCtx *http.Client var testAdminCtx *http.Client
var testUserCtx *http.Client var testUserCtx *http.Client
var testProject int64
var testWorker *storage.Worker
func TestMain(m *testing.M) { func TestMain(m *testing.M) {
config.SetupConfig() config.SetupConfig()
@ -25,6 +29,19 @@ func TestMain(m *testing.M) {
testAdminCtx = getSessionCtx("testadmin", "testadmin", true) testAdminCtx = getSessionCtx("testadmin", "testadmin", true)
testUserCtx = getSessionCtx("testuser", "testuser", false) testUserCtx = getSessionCtx("testuser", "testuser", false)
testProject = createProjectAsAdmin(api.CreateProjectRequest{
Name: "generictestproject",
Public: false,
}).Content.Id
testWorker = createWorker(api.CreateWorkerRequest{
Alias: "generictestworker",
}).Content.Worker
requestAccess(api.CreateWorkerAccessRequest{
Project: testProject,
Assign: true,
Submit: true,
}, testWorker)
acceptAccessRequest(testProject, testWorker.Id, testAdminCtx)
m.Run() m.Run()
} }

View File

@ -1,8 +1,6 @@
DROP TABLE IF EXISTS worker, project, task, log_entry, DROP TABLE IF EXISTS worker, project, task, log_entry,
worker_access, manager, manager_has_role_on_project, project_monitoring_snapshot, worker_access, manager, manager_has_role_on_project, project_monitoring_snapshot,
worker_verifies_task; worker_verifies_task;
DROP TYPE IF EXISTS status;
DROP TYPE IF EXISTS log_level;
CREATE TABLE worker CREATE TABLE worker
( (
@ -25,7 +23,8 @@ CREATE TABLE project
clone_url TEXT NOT NULL, clone_url TEXT NOT NULL,
git_repo TEXT UNIQUE NOT NULL, git_repo TEXT UNIQUE NOT NULL,
version TEXT NOT NULL, version TEXT NOT NULL,
motd TEXT NOT NULL motd TEXT NOT NULL,
secret TEXT NOT NULL DEFAULT '{}'
); );
CREATE TABLE worker_access CREATE TABLE worker_access
@ -103,7 +102,10 @@ $$
DECLARE DECLARE
chain INTEGER; chain INTEGER;
BEGIN BEGIN
UPDATE project SET closed_task_count=closed_task_count + 1 WHERE id = OLD.project returning project.chain into chain; if OLD.assignee IS NOT NULL THEN
UPDATE project
SET closed_task_count=closed_task_count + 1
WHERE id = OLD.project returning project.chain into chain;
UPDATE worker SET closed_task_count=closed_task_count + 1 WHERE id = OLD.assignee; UPDATE worker SET closed_task_count=closed_task_count + 1 WHERE id = OLD.assignee;
IF chain != 0 THEN IF chain != 0 THEN
INSERT into task (hash64, project, assignee, max_assign_time, assign_time, verification_count, INSERT into task (hash64, project, assignee, max_assign_time, assign_time, verification_count,
@ -112,6 +114,7 @@ BEGIN
old.verification_count, old.priority, 0, old.max_retries, 1, old.verification_count, old.priority, 0, old.max_retries, 1,
old.recipe); old.recipe);
end if; end if;
end if;
RETURN OLD; RETURN OLD;
END; END;
$$ LANGUAGE 'plpgsql'; $$ LANGUAGE 'plpgsql';
@ -136,7 +139,7 @@ CREATE TRIGGER on_manager_insert
FOR EACH ROW FOR EACH ROW
EXECUTE PROCEDURE on_manager_insert(); EXECUTE PROCEDURE on_manager_insert();
CREATE OR REPLACE FUNCTION release_task_ok(wid INT, tid INT, ver INT) RETURNS BOOLEAN AS CREATE OR REPLACE FUNCTION release_task_ok(wid INT, tid INT, ver BIGINT) RETURNS BOOLEAN AS
$$ $$
DECLARE DECLARE
res INT = NULL; res INT = NULL;

View File

@ -6,7 +6,7 @@ import {Credentials} from "./models/credentials";
@Injectable() @Injectable()
export class ApiService { export class ApiService {
private url: string = "http://localhost/api"; public url: string = "http://localhost/api";
private options: { private options: {
withCredentials: true, withCredentials: true,
responseType: "json" responseType: "json"
@ -66,36 +66,44 @@ export class ApiService {
} }
getProjectAccess(project: number) { getProjectAccess(project: number) {
return this.http.get(this.url + `/project/access_list/${project}`) return this.http.get(this.url + `/project/access_list/${project}`, this.options)
} }
getManagerList() { getManagerList() {
return this.http.get(this.url + "/manager/list") return this.http.get(this.url + "/manager/list", this.options)
} }
getManagerListWithRoleOn(project: number) { getManagerListWithRoleOn(project: number) {
return this.http.get(this.url + "/manager/list_for_project/" + project) return this.http.get(this.url + "/manager/list_for_project/" + project, this.options)
} }
promote(managerId: number) { promote(managerId: number) {
return this.http.get(this.url + `/manager/promote/${managerId}`) return this.http.get(this.url + `/manager/promote/${managerId}`, this.options)
} }
demote(managerId: number) { demote(managerId: number) {
return this.http.get(this.url + `/manager/demote/${managerId}`) return this.http.get(this.url + `/manager/demote/${managerId}`, this.options)
} }
acceptWorkerAccessRequest(wid: number, pid: number) { acceptWorkerAccessRequest(wid: number, pid: number) {
return this.http.post(this.url + `/project/accept_request/${pid}/${wid}`, null) return this.http.post(this.url + `/project/accept_request/${pid}/${wid}`, null, this.options)
} }
rejectWorkerAccessRequest(wid: number, pid: number) { rejectWorkerAccessRequest(wid: number, pid: number) {
return this.http.post(this.url + `/project/reject_request/${pid}/${wid}`, null) return this.http.post(this.url + `/project/reject_request/${pid}/${wid}`, null, this.options)
} }
setManagerRoleOnProject(pid: number, role: number, manager: number) { setManagerRoleOnProject(pid: number, role: number, manager: number) {
return this.http.post(this.url + `/manager/set_role_for_project/${pid}`, return this.http.post(this.url + `/manager/set_role_for_project/${pid}`,
{"role": role, "manager": manager}) {"role": role, "manager": manager}, this.options)
}
getSecret(pid: number) {
return this.http.get(this.url + `/project/secret/${pid}`,)
}
setSecret(pid: number, secret: string) {
return this.http.post(this.url + `/project/secret/${pid}`, {"secret": secret})
} }
} }

View File

@ -13,8 +13,11 @@ import {AccountDetailsComponent} from "./account-details/account-details.compone
import {WorkerDashboardComponent} from "./worker-dashboard/worker-dashboard.component"; import {WorkerDashboardComponent} from "./worker-dashboard/worker-dashboard.component";
import {ProjectPermsComponent} from "./project-perms/project-perms.component"; import {ProjectPermsComponent} from "./project-perms/project-perms.component";
import {ManagerListComponent} from "./manager-list/manager-list.component"; import {ManagerListComponent} from "./manager-list/manager-list.component";
import {IndexComponent} from "./index/index.component";
import {ProjectSecretComponent} from "./project-secret/project-secret.component";
const routes: Routes = [ const routes: Routes = [
{path: "", component: IndexComponent},
{path: "log", component: LogsComponent}, {path: "log", component: LogsComponent},
{path: "login", component: LoginComponent}, {path: "login", component: LoginComponent},
{path: "account", component: AccountDetailsComponent}, {path: "account", component: AccountDetailsComponent},
@ -22,9 +25,10 @@ const routes: Routes = [
{path: "project/:id", component: ProjectDashboardComponent}, {path: "project/:id", component: ProjectDashboardComponent},
{path: "project/:id/update", component: UpdateProjectComponent}, {path: "project/:id/update", component: UpdateProjectComponent},
{path: "project/:id/perms", component: ProjectPermsComponent}, {path: "project/:id/perms", component: ProjectPermsComponent},
{path: "project/:id/secret", component: ProjectSecretComponent},
{path: "new_project", component: CreateProjectComponent}, {path: "new_project", component: CreateProjectComponent},
{path: "workers", component: WorkerDashboardComponent}, {path: "workers", component: WorkerDashboardComponent},
{path: "manager_list", component: ManagerListComponent} {path: "manager_list", component: ManagerListComponent},
]; ];
@NgModule({ @NgModule({

View File

@ -27,6 +27,7 @@ import {
MatSlideToggleModule, MatSlideToggleModule,
MatSnackBarModule, MatSnackBarModule,
MatSortModule, MatSortModule,
MatStepperModule,
MatTableModule, MatTableModule,
MatTabsModule, MatTabsModule,
MatToolbarModule, MatToolbarModule,
@ -52,6 +53,8 @@ import {ManagerListComponent} from './manager-list/manager-list.component';
import {ProjectSelectComponent} from './project-select/project-select.component'; import {ProjectSelectComponent} from './project-select/project-select.component';
import {ManagerSelectComponent} from './manager-select/manager-select.component'; 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 {ProjectSecretComponent} from './project-secret/project-secret.component';
export function createTranslateLoader(http: HttpClient) { export function createTranslateLoader(http: HttpClient) {
@ -76,6 +79,8 @@ export function createTranslateLoader(http: HttpClient) {
ProjectSelectComponent, ProjectSelectComponent,
ManagerSelectComponent, ManagerSelectComponent,
ProjectIconComponent, ProjectIconComponent,
IndexComponent,
ProjectSecretComponent,
], ],
imports: [ imports: [
BrowserModule, BrowserModule,
@ -114,7 +119,8 @@ export function createTranslateLoader(http: HttpClient) {
MatProgressBarModule, MatProgressBarModule,
MatTabsModule, MatTabsModule,
MatListModule, MatListModule,
MatButtonToggleModule MatButtonToggleModule,
MatStepperModule
], ],
exports: [], exports: [],

View File

@ -6,6 +6,7 @@ import {MatPaginator, MatSort, MatTableDataSource} from "@angular/material";
import * as moment from "moment" import * as moment from "moment"
import {AuthService} from "../auth.service"; import {AuthService} from "../auth.service";
import {Manager} from "../models/manager";
@Component({ @Component({
selector: 'app-manager-list', selector: 'app-manager-list',

View File

@ -56,11 +56,13 @@
</mat-card-content> </mat-card-content>
<mat-card-actions> <mat-card-actions>
<!--TODO: auth--> <button mat-raised-button [routerLink]="'/projects'">Back</button>
<button mat-raised-button color="primary" *ngIf="project" <button mat-raised-button color="primary" *ngIf="project && auth.logged"
[routerLink]="'/project/' + project.id + '/update'">{{"project.update" | translate}}</button> [routerLink]="'/project/' + project.id + '/update'">{{"project.update" | translate}}</button>
<button mat-raised-button color="primary" *ngIf="project" <button mat-raised-button color="primary" *ngIf="project && auth.logged"
[routerLink]="'/project/' + project.id + '/perms'">{{"project.perms" | translate}}</button> [routerLink]="'/project/' + project.id + '/perms'">{{"project.perms" | translate}}</button>
<button mat-raised-button color="primary" *ngIf="project && auth.logged"
[routerLink]="'/project/' + project.id + '/secret'">{{"project.secret" | translate}}</button>
</mat-card-actions> </mat-card-actions>
</mat-card> </mat-card>
</div> </div>

View File

@ -7,6 +7,7 @@ import {Chart} from "chart.js";
import {AssignedTasks, MonitoringSnapshot} from "../models/monitoring"; 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";
@Component({ @Component({
@ -46,6 +47,7 @@ export class ProjectDashboardComponent implements OnInit {
constructor(private apiService: ApiService, constructor(private apiService: ApiService,
private route: ActivatedRoute, private route: ActivatedRoute,
private translate: TranslateService, private translate: TranslateService,
public auth: AuthService,
private messenger: MessengerService) { private messenger: MessengerService) {
} }

View File

@ -29,6 +29,10 @@
*ngIf="authService.logged"> *ngIf="authService.logged">
<mat-icon>perm_identity</mat-icon> <mat-icon>perm_identity</mat-icon>
{{"project.perms" | translate}}</button> {{"project.perms" | translate}}</button>
<button mat-raised-button color="primary"
*ngIf="authService.logged"
[routerLink]="'/project/' + project.id + '/secret'">
{{"project.secret" | translate}}</button>
</div> </div>
</mat-expansion-panel> </mat-expansion-panel>
<span *ngIf="projects && projects.length == 0"> <span *ngIf="projects && projects.length == 0">

View File

@ -9,7 +9,7 @@
<mat-card-subtitle>{{"perms.subtitle" | translate}}</mat-card-subtitle> <mat-card-subtitle>{{"perms.subtitle" | translate}}</mat-card-subtitle>
</mat-card-header> </mat-card-header>
<mat-card-content *ngIf="!unauthorized || !auth.account"> <mat-card-content *ngIf="!(unauthorized || !auth.account)">
<h3>{{"perms.workers" | translate}}</h3> <h3>{{"perms.workers" | translate}}</h3>
<mat-list *ngIf="accesses && accesses.length>0"> <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">
@ -65,5 +65,8 @@
</p> </p>
</mat-card-content> </mat-card-content>
<mat-card-actions>
<button mat-raised-button [routerLink]="'../'">Back</button>
</mat-card-actions>
</mat-card> </mat-card>
</div> </div>

View File

@ -0,0 +1,3 @@
.mat-form-field {
width: 100%;
}

View File

@ -0,0 +1,22 @@
<div class="container">
<mat-card>
<mat-card-header>
<mat-card-title>{{"secret.title" | translate}}</mat-card-title>
<mat-card-subtitle>{{"secret.subtitle" | translate}}</mat-card-subtitle>
</mat-card-header>
<mat-form-field appearance="outline">
<mat-label>{{"secret.secret" | translate}}</mat-label>
<textarea matInput [(ngModel)]="secret"
[placeholder]="'secret.secret'|translate"
name="secret"></textarea>
</mat-form-field>
<mat-card-actions>
<button mat-raised-button [routerLink]="'../'">Back</button>
<button mat-raised-button color="primary"
(click)="onUpdate()">{{"secret.update" | translate}}</button>
</mat-card-actions>
</mat-card>
</div>

View File

@ -0,0 +1,46 @@
import {Component, OnInit} from '@angular/core';
import {AuthService} from "../auth.service";
import {ApiService} from "../api.service";
import {ActivatedRoute} from "@angular/router";
import {TranslateService} from "@ngx-translate/core";
import {MessengerService} from "../messenger.service";
@Component({
selector: 'app-project-secret',
templateUrl: './project-secret.component.html',
styleUrls: ['./project-secret.component.css']
})
export class ProjectSecretComponent implements OnInit {
secret: string;
projectId: number;
constructor(private auth: AuthService,
private apiService: ApiService,
private translate: TranslateService,
private messenger: MessengerService,
private route: ActivatedRoute) {
}
ngOnInit() {
this.route.params.subscribe(params => {
this.projectId = params["id"];
this.getSecret();
});
}
getSecret() {
this.apiService.getSecret(this.projectId).subscribe(data => {
this.secret = data["content"]["secret"]
}, error => {
this.translate.get("messenger.unauthorized").subscribe(t => this.messenger.show(t))
})
}
onUpdate() {
this.apiService.setSecret(this.projectId, this.secret).subscribe(data => {
this.translate.get("secret.ok").subscribe(t => this.messenger.show(t))
})
}
}

View File

@ -41,6 +41,7 @@
</form> </form>
</mat-card-content> </mat-card-content>
<mat-card-actions> <mat-card-actions>
<button mat-raised-button [routerLink]="'../'">Back</button>
<button type="submit" form="uf" mat-raised-button color="primary">{{"project.update" | translate}}</button> <button type="submit" form="uf" mat-raised-button color="primary">{{"project.update" | translate}}</button>
</mat-card-actions> </mat-card-actions>
</mat-card> </mat-card>

View File

@ -67,7 +67,8 @@
"perms": "Permissions", "perms": "Permissions",
"chain": "Chain tasks to", "chain": "Chain tasks to",
"manager_select": "Give access to manager", "manager_select": "Give access to manager",
"version": "Git version (commit hash)" "version": "Git version (commit hash)",
"secret": "Secret"
}, },
"dashboard": { "dashboard": {
"title": "Dashboard for", "title": "Dashboard for",
@ -137,5 +138,16 @@
"project_select": { "project_select": {
"list_loading": "Loading project list...", "list_loading": "Loading project list...",
"none": "None" "none": "None"
},
"index": {
"version": "Latest commit hash here",
"alias": "Relevent alias"
},
"secret": {
"title": "Project secret",
"subtitle": "You can set project configuration here. It is only accessible by workers with ASSIGN access to this project",
"secret": "Secret",
"update": "Update",
"ok": "Updated"
} }
} }

View File

@ -118,7 +118,8 @@
"set": "Changements enregistrés", "set": "Changements enregistrés",
"workers": "Workers", "workers": "Workers",
"no_workers": "Aucun Worker n'a explicitement accès à ce projet", "no_workers": "Aucun Worker n'a explicitement accès à ce projet",
"managers": "Managers" "managers": "Managers",
"secret": "Secret"
}, },
"messenger": { "messenger": {
"close": "Fermer", "close": "Fermer",
@ -139,6 +140,17 @@
"project_select": { "project_select": {
"list_loading": "Chargement de la liste de projets...", "list_loading": "Chargement de la liste de projets...",
"none": "Aucun" "none": "Aucun"
},
"index": {
"version": "Le dernier hash de commit",
"alias": "Alias pertinent"
},
"secret": {
"title": "Secret",
"subtitle": "Vous pouvez définir la configuration du projet ici. Ce n'est accessible que par les Workers ayant accès au projet",
"secret": "Secret",
"update": "Mettre à jour",
"ok": "Mis à jour"
} }
} }