mirror of
https://github.com/simon987/task_tracker.git
synced 2025-04-19 18:16:45 +00:00
Some work on permissions (lacks tests)
This commit is contained in:
parent
4edf354f8d
commit
c3e5bd77f7
@ -145,7 +145,6 @@ func (api *WebAPI) AccountDetails(r *Request) {
|
|||||||
|
|
||||||
logrus.WithFields(logrus.Fields{
|
logrus.WithFields(logrus.Fields{
|
||||||
"manager": manager,
|
"manager": manager,
|
||||||
"session": sess,
|
|
||||||
}).Trace("Account details request")
|
}).Trace("Account details request")
|
||||||
|
|
||||||
if manager == nil {
|
if manager == nil {
|
||||||
|
@ -90,6 +90,8 @@ func New() *WebAPI {
|
|||||||
api.router.GET("/project/monitoring-between/:id", LogRequestMiddleware(api.GetSnapshotsBetween))
|
api.router.GET("/project/monitoring-between/:id", LogRequestMiddleware(api.GetSnapshotsBetween))
|
||||||
api.router.GET("/project/monitoring/:id", LogRequestMiddleware(api.GetNSnapshots))
|
api.router.GET("/project/monitoring/:id", LogRequestMiddleware(api.GetNSnapshots))
|
||||||
api.router.GET("/project/assignees/:id", LogRequestMiddleware(api.ProjectGetAssigneeStats))
|
api.router.GET("/project/assignees/:id", LogRequestMiddleware(api.ProjectGetAssigneeStats))
|
||||||
|
api.router.GET("/project/requests/:id", LogRequestMiddleware(api.ProjectGetAccessRequests))
|
||||||
|
api.router.GET("/project/request_access/:id", LogRequestMiddleware(api.WorkerRequestAccess))
|
||||||
|
|
||||||
api.router.POST("/task/create", LogRequestMiddleware(api.TaskCreate))
|
api.router.POST("/task/create", LogRequestMiddleware(api.TaskCreate))
|
||||||
api.router.GET("/task/get/:project", LogRequestMiddleware(api.TaskGetFromProject))
|
api.router.GET("/task/get/:project", LogRequestMiddleware(api.TaskGetFromProject))
|
||||||
|
159
api/project.go
159
api/project.go
@ -15,6 +15,7 @@ type CreateProjectRequest struct {
|
|||||||
Priority int64 `json:"priority"`
|
Priority int64 `json:"priority"`
|
||||||
Motd string `json:"motd"`
|
Motd string `json:"motd"`
|
||||||
Public bool `json:"public"`
|
Public bool `json:"public"`
|
||||||
|
Hidden bool `json:"hidden"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type UpdateProjectRequest struct {
|
type UpdateProjectRequest struct {
|
||||||
@ -24,6 +25,7 @@ type UpdateProjectRequest struct {
|
|||||||
Priority int64 `json:"priority"`
|
Priority int64 `json:"priority"`
|
||||||
Motd string `json:"motd"`
|
Motd string `json:"motd"`
|
||||||
Public bool `json:"public"`
|
Public bool `json:"public"`
|
||||||
|
Hidden bool `json:"hidden"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type UpdateProjectResponse struct {
|
type UpdateProjectResponse struct {
|
||||||
@ -55,8 +57,22 @@ type GetAssigneeStatsResponse struct {
|
|||||||
Assignees *[]storage.AssignedTasks `json:"assignees"`
|
Assignees *[]storage.AssignedTasks `json:"assignees"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type WorkerRequestAccessResponse struct {
|
||||||
|
Ok bool `json:"ok"`
|
||||||
|
Message string `json:"message,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ProjectGetAccessRequestsResponse struct {
|
||||||
|
Ok bool `json:"ok"`
|
||||||
|
Message string `json:"message,omitempty"`
|
||||||
|
Requests *[]storage.Worker `json:"requests,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
func (api *WebAPI) ProjectCreate(r *Request) {
|
func (api *WebAPI) ProjectCreate(r *Request) {
|
||||||
|
|
||||||
|
sess := api.Session.StartFasthttp(r.Ctx)
|
||||||
|
manager := sess.Get("manager")
|
||||||
|
|
||||||
createReq := &CreateProjectRequest{}
|
createReq := &CreateProjectRequest{}
|
||||||
err := json.Unmarshal(r.Ctx.Request.Body(), createReq)
|
err := json.Unmarshal(r.Ctx.Request.Body(), createReq)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -74,26 +90,10 @@ func (api *WebAPI) ProjectCreate(r *Request) {
|
|||||||
Priority: createReq.Priority,
|
Priority: createReq.Priority,
|
||||||
Motd: createReq.Motd,
|
Motd: createReq.Motd,
|
||||||
Public: createReq.Public,
|
Public: createReq.Public,
|
||||||
|
Hidden: createReq.Hidden,
|
||||||
}
|
}
|
||||||
|
|
||||||
if isValidProject(project) {
|
if !isValidProject(project) {
|
||||||
id, err := api.Database.SaveProject(project)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
r.Json(CreateProjectResponse{
|
|
||||||
Ok: false,
|
|
||||||
Message: err.Error(),
|
|
||||||
}, 500)
|
|
||||||
} else {
|
|
||||||
r.OkJson(CreateProjectResponse{
|
|
||||||
Ok: true,
|
|
||||||
Id: id,
|
|
||||||
})
|
|
||||||
logrus.WithFields(logrus.Fields{
|
|
||||||
"project": project,
|
|
||||||
}).Debug("Created project")
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
logrus.WithFields(logrus.Fields{
|
logrus.WithFields(logrus.Fields{
|
||||||
"project": project,
|
"project": project,
|
||||||
}).Warn("Invalid project")
|
}).Warn("Invalid project")
|
||||||
@ -102,7 +102,36 @@ func (api *WebAPI) ProjectCreate(r *Request) {
|
|||||||
Ok: false,
|
Ok: false,
|
||||||
Message: "Invalid project",
|
Message: "Invalid project",
|
||||||
}, 400)
|
}, 400)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !isProjectCreationAuthorized(project, manager) {
|
||||||
|
logrus.WithFields(logrus.Fields{
|
||||||
|
"project": project,
|
||||||
|
}).Warn("Unauthorized project creation")
|
||||||
|
|
||||||
|
r.Json(CreateProjectResponse{
|
||||||
|
Ok: false,
|
||||||
|
Message: "You are not permitted to create a project with this configuration",
|
||||||
|
}, 400)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
id, err := api.Database.SaveProject(project)
|
||||||
|
if err != nil {
|
||||||
|
r.Json(CreateProjectResponse{
|
||||||
|
Ok: false,
|
||||||
|
Message: err.Error(),
|
||||||
|
}, 500)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
r.OkJson(CreateProjectResponse{
|
||||||
|
Ok: true,
|
||||||
|
Id: id,
|
||||||
|
})
|
||||||
|
logrus.WithFields(logrus.Fields{
|
||||||
|
"project": project,
|
||||||
|
}).Debug("Created project")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (api *WebAPI) ProjectUpdate(r *Request) {
|
func (api *WebAPI) ProjectUpdate(r *Request) {
|
||||||
@ -133,6 +162,7 @@ func (api *WebAPI) ProjectUpdate(r *Request) {
|
|||||||
Priority: updateReq.Priority,
|
Priority: updateReq.Priority,
|
||||||
Motd: updateReq.Motd,
|
Motd: updateReq.Motd,
|
||||||
Public: updateReq.Public,
|
Public: updateReq.Public,
|
||||||
|
Hidden: updateReq.Hidden,
|
||||||
}
|
}
|
||||||
|
|
||||||
if isValidProject(project) {
|
if isValidProject(project) {
|
||||||
@ -180,6 +210,38 @@ func isValidProject(project *storage.Project) bool {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func isProjectCreationAuthorized(project *storage.Project, manager interface{}) bool {
|
||||||
|
|
||||||
|
return true
|
||||||
|
if manager == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if project.Public && manager.(*storage.Manager).WebsiteAdmin {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func isProjectUpdateAuthorized(project *storage.Project, manager interface{}, db *storage.Database) bool {
|
||||||
|
|
||||||
|
var man storage.Manager
|
||||||
|
if manager != nil {
|
||||||
|
man = manager.(storage.Manager)
|
||||||
|
}
|
||||||
|
|
||||||
|
if man.WebsiteAdmin {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
role := db.ManagerHasRoleOn(&man, project.Id)
|
||||||
|
if role&storage.ROLE_EDIT == 1 {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
func (api *WebAPI) ProjectGet(r *Request) {
|
func (api *WebAPI) ProjectGet(r *Request) {
|
||||||
|
|
||||||
id, err := strconv.ParseInt(r.Ctx.UserValue("id").(string), 10, 64)
|
id, err := strconv.ParseInt(r.Ctx.UserValue("id").(string), 10, 64)
|
||||||
@ -230,3 +292,64 @@ func (api *WebAPI) ProjectGetAssigneeStats(r *Request) {
|
|||||||
Assignees: stats,
|
Assignees: stats,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (api *WebAPI) ProjectGetAccessRequests(r *Request) {
|
||||||
|
|
||||||
|
sess := api.Session.StartFasthttp(r.Ctx)
|
||||||
|
manager := sess.Get("manager")
|
||||||
|
|
||||||
|
id, err := strconv.ParseInt(r.Ctx.UserValue("id").(string), 10, 64)
|
||||||
|
handleErr(err, r) //todo handle invalid id
|
||||||
|
|
||||||
|
if manager == nil {
|
||||||
|
r.Json(ProjectGetAccessRequestsResponse{
|
||||||
|
Ok: false,
|
||||||
|
Message: "Unauthorized",
|
||||||
|
}, 401)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if !manager.(*storage.Manager).WebsiteAdmin &&
|
||||||
|
api.Database.ManagerHasRoleOn(manager.(*storage.Manager), 1)&
|
||||||
|
storage.ROLE_MANAGE_ACCESS == 0 {
|
||||||
|
r.Json(ProjectGetAccessRequestsResponse{
|
||||||
|
Ok: false,
|
||||||
|
Message: "Unauthorized",
|
||||||
|
}, 403)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
requests := api.Database.GetAllAccessRequests(id)
|
||||||
|
|
||||||
|
r.OkJson(ProjectGetAccessRequestsResponse{
|
||||||
|
Ok: true,
|
||||||
|
Requests: requests,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (api *WebAPI) WorkerRequestAccess(r *Request) {
|
||||||
|
|
||||||
|
id, err := strconv.ParseInt(r.Ctx.UserValue("id").(string), 10, 64)
|
||||||
|
handleErr(err, r) //todo handle invalid id
|
||||||
|
|
||||||
|
worker, err := api.validateSignature(r)
|
||||||
|
if err != nil {
|
||||||
|
r.Json(WorkerRequestAccessResponse{
|
||||||
|
Ok: false,
|
||||||
|
Message: err.Error(),
|
||||||
|
}, 401)
|
||||||
|
}
|
||||||
|
|
||||||
|
res := api.Database.SaveAccessRequest(worker, id)
|
||||||
|
|
||||||
|
if res {
|
||||||
|
r.OkJson(WorkerRequestAccessResponse{
|
||||||
|
Ok: true,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
r.Json(WorkerRequestAccessResponse{
|
||||||
|
Ok: false,
|
||||||
|
Message: "Project is public, you already have " +
|
||||||
|
"an active request or you already have access to this project",
|
||||||
|
}, 400)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -177,6 +177,10 @@ func (api WebAPI) validateSignature(r *Request) (*storage.Worker, error) {
|
|||||||
widStr := string(r.Ctx.Request.Header.Peek("X-Worker-Id"))
|
widStr := string(r.Ctx.Request.Header.Peek("X-Worker-Id"))
|
||||||
signature := r.Ctx.Request.Header.Peek("X-Signature")
|
signature := r.Ctx.Request.Header.Peek("X-Signature")
|
||||||
|
|
||||||
|
if widStr == "" {
|
||||||
|
return nil, errors.New("worker id not specified")
|
||||||
|
}
|
||||||
|
|
||||||
wid, err := strconv.ParseInt(widStr, 10, 64)
|
wid, err := strconv.ParseInt(widStr, 10, 64)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logrus.WithError(err).WithFields(logrus.Fields{
|
logrus.WithError(err).WithFields(logrus.Fields{
|
||||||
|
@ -58,11 +58,9 @@ func (api *WebAPI) WorkerCreate(r *Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
identity := getIdentity(r)
|
if !canCreateWorker(r, workerReq) {
|
||||||
if !canCreateWorker(r, workerReq, identity) {
|
|
||||||
|
|
||||||
logrus.WithFields(logrus.Fields{
|
logrus.WithFields(logrus.Fields{
|
||||||
"identity": identity,
|
|
||||||
"createWorkerRequest": workerReq,
|
"createWorkerRequest": workerReq,
|
||||||
}).Warn("Failed CreateWorkerRequest")
|
}).Warn("Failed CreateWorkerRequest")
|
||||||
|
|
||||||
@ -73,7 +71,7 @@ func (api *WebAPI) WorkerCreate(r *Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
worker, err := api.workerCreate(workerReq, getIdentity(r))
|
worker, err := api.workerCreate(workerReq)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
handleErr(err, r)
|
handleErr(err, r)
|
||||||
} else {
|
} else {
|
||||||
@ -185,7 +183,7 @@ func (api *WebAPI) WorkerUpdate(r *Request) {
|
|||||||
r.Json(GetTaskResponse{
|
r.Json(GetTaskResponse{
|
||||||
Ok: false,
|
Ok: false,
|
||||||
Message: err.Error(),
|
Message: err.Error(),
|
||||||
}, 403)
|
}, 401)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -224,24 +222,23 @@ func (api *WebAPI) GetAllWorkerStats(r *Request) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (api *WebAPI) workerCreate(request *CreateWorkerRequest, identity *storage.Identity) (*storage.Worker, error) {
|
func (api *WebAPI) workerCreate(request *CreateWorkerRequest) (*storage.Worker, error) {
|
||||||
|
|
||||||
if request.Alias == "" {
|
if request.Alias == "" {
|
||||||
request.Alias = "default_alias"
|
request.Alias = "default_alias"
|
||||||
}
|
}
|
||||||
|
|
||||||
worker := storage.Worker{
|
worker := storage.Worker{
|
||||||
Created: time.Now().Unix(),
|
Created: time.Now().Unix(),
|
||||||
Identity: identity,
|
Secret: makeSecret(),
|
||||||
Secret: makeSecret(),
|
Alias: request.Alias,
|
||||||
Alias: request.Alias,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
api.Database.SaveWorker(&worker)
|
api.Database.SaveWorker(&worker)
|
||||||
return &worker, nil
|
return &worker, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func canCreateWorker(r *Request, cwr *CreateWorkerRequest, identity *storage.Identity) bool {
|
func canCreateWorker(r *Request, cwr *CreateWorkerRequest) bool {
|
||||||
|
|
||||||
if cwr.Alias == "unassigned" {
|
if cwr.Alias == "unassigned" {
|
||||||
//Reserved alias
|
//Reserved alias
|
||||||
@ -260,13 +257,3 @@ func makeSecret() []byte {
|
|||||||
|
|
||||||
return secret
|
return secret
|
||||||
}
|
}
|
||||||
|
|
||||||
func getIdentity(r *Request) *storage.Identity {
|
|
||||||
|
|
||||||
identity := storage.Identity{
|
|
||||||
RemoteAddr: r.Ctx.RemoteAddr().String(),
|
|
||||||
UserAgent: string(r.Ctx.UserAgent()),
|
|
||||||
}
|
|
||||||
|
|
||||||
return &identity
|
|
||||||
}
|
|
||||||
|
106
schema.sql
106
schema.sql
@ -1,39 +1,30 @@
|
|||||||
DROP TABLE IF EXISTS worker_identity, worker, project, task, log_entry,
|
DROP TABLE IF EXISTS worker, project, task, log_entry,
|
||||||
worker_has_access_to_project, manager, manager_has_role_on_project, project_monitoring_snapshot,
|
worker_has_access_to_project, manager, manager_has_role_on_project, project_monitoring_snapshot,
|
||||||
worker_verifies_task;
|
worker_verifies_task, worker_requests_access_to_project;
|
||||||
DROP TYPE IF EXISTS status;
|
DROP TYPE IF EXISTS status;
|
||||||
DROP TYPE IF EXISTS log_level;
|
DROP TYPE IF EXISTS log_level;
|
||||||
|
|
||||||
CREATE TABLE worker_identity
|
|
||||||
(
|
|
||||||
id SERIAL PRIMARY KEY,
|
|
||||||
remote_addr TEXT,
|
|
||||||
user_agent TEXT,
|
|
||||||
|
|
||||||
UNIQUE (remote_addr)
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE TABLE worker
|
CREATE TABLE worker
|
||||||
(
|
(
|
||||||
id SERIAL PRIMARY KEY,
|
id SERIAL PRIMARY KEY NOT NULL,
|
||||||
alias TEXT,
|
alias TEXT NOT NULL,
|
||||||
created INTEGER,
|
created INTEGER NOT NULL,
|
||||||
identity INTEGER REFERENCES worker_identity (id),
|
secret BYTEA NOT NULL,
|
||||||
secret BYTEA,
|
closed_task_count INTEGER DEFAULT 0 NOT NULL
|
||||||
closed_task_count INTEGER DEFAULT 0
|
|
||||||
);
|
);
|
||||||
|
|
||||||
CREATE TABLE project
|
CREATE TABLE project
|
||||||
(
|
(
|
||||||
id SERIAL PRIMARY KEY,
|
id SERIAL PRIMARY KEY NOT NULL,
|
||||||
priority INTEGER DEFAULT 0,
|
priority INTEGER DEFAULT 0 NOT NULL,
|
||||||
name TEXT UNIQUE,
|
closed_task_count INT DEFAULT 0 NOT NULL,
|
||||||
clone_url TEXT,
|
public boolean NOT NULL,
|
||||||
git_repo TEXT UNIQUE,
|
hidden boolean NOT NULL,
|
||||||
version TEXT,
|
name TEXT UNIQUE NOT NULL,
|
||||||
motd TEXT,
|
clone_url TEXT NOT NULL,
|
||||||
public boolean,
|
git_repo TEXT UNIQUE NOT NULL,
|
||||||
closed_task_count INT DEFAULT 0
|
version TEXT NOT NULL,
|
||||||
|
motd TEXT NOT NULL
|
||||||
);
|
);
|
||||||
|
|
||||||
CREATE TABLE worker_has_access_to_project
|
CREATE TABLE worker_has_access_to_project
|
||||||
@ -61,43 +52,51 @@ CREATE TABLE task
|
|||||||
|
|
||||||
CREATE TABLE worker_verifies_task
|
CREATE TABLE worker_verifies_task
|
||||||
(
|
(
|
||||||
verification_hash BIGINT,
|
verification_hash BIGINT NOT NULL,
|
||||||
task BIGINT REFERENCES task (id) ON DELETE CASCADE,
|
task BIGINT REFERENCES task (id) ON DELETE CASCADE NOT NULL,
|
||||||
worker INT REFERENCES worker (id)
|
worker INT REFERENCES worker (id) NOT NULL
|
||||||
);
|
);
|
||||||
|
|
||||||
CREATE TABLE log_entry
|
CREATE TABLE log_entry
|
||||||
(
|
(
|
||||||
level INTEGER,
|
level INTEGER NOT NULL,
|
||||||
message TEXT,
|
message TEXT NOT NULL,
|
||||||
message_data TEXT,
|
message_data TEXT NOT NULL,
|
||||||
timestamp INTEGER
|
timestamp INTEGER NOT NULL
|
||||||
);
|
);
|
||||||
|
|
||||||
CREATE TABLE manager
|
CREATE TABLE manager
|
||||||
(
|
(
|
||||||
id SERIAL PRIMARY KEY,
|
id SERIAL PRIMARY KEY,
|
||||||
username TEXT UNIQUE,
|
register_time INTEGER NOT NULL,
|
||||||
password BYTEA,
|
website_admin BOOLEAN NOT NULL,
|
||||||
website_admin BOOLEAN
|
username TEXT UNIQUE NOT NULL,
|
||||||
|
password BYTEA NOT NULL
|
||||||
);
|
);
|
||||||
|
|
||||||
CREATE TABLE manager_has_role_on_project
|
CREATE TABLE manager_has_role_on_project
|
||||||
(
|
(
|
||||||
manager INTEGER REFERENCES manager (id),
|
manager INTEGER REFERENCES manager (id) NOT NULL,
|
||||||
role SMALLINT,
|
role SMALLINT NOT NULl,
|
||||||
project INTEGER REFERENCES project (id)
|
project INTEGER REFERENCES project (id) NOT NULL
|
||||||
);
|
);
|
||||||
|
|
||||||
CREATE TABLE project_monitoring_snapshot
|
CREATE TABLE project_monitoring_snapshot
|
||||||
(
|
(
|
||||||
project INT REFERENCES project (id),
|
project INT REFERENCES project (id) NOT NULL,
|
||||||
new_task_count INT,
|
new_task_count INT NOT NULL,
|
||||||
failed_task_count INT,
|
failed_task_count INT NOT NULL,
|
||||||
closed_task_count INT,
|
closed_task_count INT NOT NULL,
|
||||||
awaiting_verification_task_count INT,
|
awaiting_verification_task_count INT NOT NULL,
|
||||||
worker_access_count INT,
|
worker_access_count INT NOT NULL,
|
||||||
timestamp INT
|
timestamp INT NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE worker_requests_access_to_project
|
||||||
|
(
|
||||||
|
worker INT REFERENCES worker (id),
|
||||||
|
project INT REFERENCES project (id),
|
||||||
|
PRIMARY KEY (worker, project)
|
||||||
);
|
);
|
||||||
|
|
||||||
CREATE OR REPLACE FUNCTION on_task_delete_proc() RETURNS TRIGGER AS
|
CREATE OR REPLACE FUNCTION on_task_delete_proc() RETURNS TRIGGER AS
|
||||||
@ -114,6 +113,21 @@ CREATE TRIGGER on_task_delete
|
|||||||
FOR EACH ROW
|
FOR EACH ROW
|
||||||
EXECUTE PROCEDURE on_task_delete_proc();
|
EXECUTE PROCEDURE on_task_delete_proc();
|
||||||
|
|
||||||
|
CREATE OR REPLACE FUNCTION on_manager_insert() RETURNS TRIGGER AS
|
||||||
|
$$
|
||||||
|
BEGIN
|
||||||
|
IF NEW.id = 1 THEN
|
||||||
|
UPDATE manager SET website_admin= TRUE WHERE id = 1;
|
||||||
|
end if;
|
||||||
|
RETURN NEW;
|
||||||
|
END;
|
||||||
|
$$ LANGUAGE 'plpgsql';
|
||||||
|
CREATE TRIGGER on_manager_insert
|
||||||
|
AFTER INSERT
|
||||||
|
ON manager
|
||||||
|
FOR EACH ROW
|
||||||
|
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 INT) RETURNS BOOLEAN AS
|
||||||
$$
|
$$
|
||||||
DECLARE
|
DECLARE
|
||||||
|
@ -10,9 +10,10 @@ import (
|
|||||||
type ManagerRole int
|
type ManagerRole int
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
ROLE_NONE ManagerRole = 0
|
||||||
ROLE_READ ManagerRole = 1
|
ROLE_READ ManagerRole = 1
|
||||||
ROLE_EDIT ManagerRole = 2
|
ROLE_EDIT ManagerRole = 2
|
||||||
ROLE_MANAGE_ACCESS ManagerRole = 3
|
ROLE_MANAGE_ACCESS ManagerRole = 4
|
||||||
)
|
)
|
||||||
|
|
||||||
type Manager struct {
|
type Manager struct {
|
||||||
@ -65,8 +66,8 @@ func (database *Database) SaveManager(manager *Manager, password []byte) error {
|
|||||||
hash.Write([]byte(manager.Username))
|
hash.Write([]byte(manager.Username))
|
||||||
hashedPassword := hash.Sum(nil)
|
hashedPassword := hash.Sum(nil)
|
||||||
|
|
||||||
row := db.QueryRow(`INSERT INTO manager (username, password, website_admin)
|
row := db.QueryRow(`INSERT INTO manager (username, password, website_admin, register_time)
|
||||||
VALUES ($1,$2,$3) RETURNING ID`,
|
VALUES ($1,$2,$3, extract(epoch from now() at time zone 'utc')) RETURNING ID`,
|
||||||
manager.Username, hashedPassword, manager.WebsiteAdmin)
|
manager.Username, hashedPassword, manager.WebsiteAdmin)
|
||||||
|
|
||||||
err := row.Scan(&manager.Id)
|
err := row.Scan(&manager.Id)
|
||||||
@ -78,6 +79,8 @@ func (database *Database) SaveManager(manager *Manager, password []byte) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
manager.WebsiteAdmin = manager.Id == 1
|
||||||
|
|
||||||
logrus.WithFields(logrus.Fields{
|
logrus.WithFields(logrus.Fields{
|
||||||
"manager": manager,
|
"manager": manager,
|
||||||
}).Info("Database.SaveManager INSERT")
|
}).Info("Database.SaveManager INSERT")
|
||||||
@ -121,3 +124,19 @@ func (database *Database) UpdateManagerPassword(manager *Manager, newPassword []
|
|||||||
"id": manager.Id,
|
"id": manager.Id,
|
||||||
}).Warning("Database.UpdateManagerPassword UPDATE")
|
}).Warning("Database.UpdateManagerPassword UPDATE")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (database *Database) ManagerHasRoleOn(manager *Manager, projectId int64) ManagerRole {
|
||||||
|
|
||||||
|
db := database.getDB()
|
||||||
|
|
||||||
|
row := db.QueryRow(`SELECT role FROM manager_has_role_on_project
|
||||||
|
WHERE project=$1 AND manager=$2`, projectId, manager.Id)
|
||||||
|
|
||||||
|
var role ManagerRole
|
||||||
|
err := row.Scan(&role)
|
||||||
|
if err != nil {
|
||||||
|
return ROLE_NONE
|
||||||
|
}
|
||||||
|
|
||||||
|
return role
|
||||||
|
}
|
||||||
|
@ -47,7 +47,7 @@ func (database *Database) MakeProjectSnapshots() {
|
|||||||
"took": time.Now().Sub(startTime),
|
"took": time.Now().Sub(startTime),
|
||||||
"add": inserted,
|
"add": inserted,
|
||||||
"remove": deleted,
|
"remove": deleted,
|
||||||
}).Trace("Took monitoring snapshot")
|
}).Trace("Took project monitoring snapshot")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (database *Database) GetMonitoringSnapshotsBetween(pid int64, from int, to int) (ss *[]ProjectMonitoringSnapshot) {
|
func (database *Database) GetMonitoringSnapshotsBetween(pid int64, from int, to int) (ss *[]ProjectMonitoringSnapshot) {
|
||||||
|
@ -15,6 +15,7 @@ type Project struct {
|
|||||||
Version string `json:"version"`
|
Version string `json:"version"`
|
||||||
Motd string `json:"motd"`
|
Motd string `json:"motd"`
|
||||||
Public bool `json:"public"`
|
Public bool `json:"public"`
|
||||||
|
Hidden bool `json:"hidden"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type AssignedTasks struct {
|
type AssignedTasks struct {
|
||||||
@ -31,9 +32,10 @@ func (database *Database) SaveProject(project *Project) (int64, error) {
|
|||||||
|
|
||||||
func saveProject(project *Project, db *sql.DB) (int64, error) {
|
func saveProject(project *Project, db *sql.DB) (int64, error) {
|
||||||
|
|
||||||
row := db.QueryRow(`INSERT INTO project (name, git_repo, clone_url, version, priority, motd, public)
|
row := db.QueryRow(`INSERT INTO project (name, git_repo, clone_url, version, priority, motd, public, hidden)
|
||||||
VALUES ($1,$2,$3,$4,$5,$6,$7) RETURNING id`,
|
VALUES ($1,$2,$3,$4,$5,$6,$7,$8) RETURNING id`,
|
||||||
project.Name, project.GitRepo, project.CloneUrl, project.Version, project.Priority, project.Motd, project.Public)
|
project.Name, project.GitRepo, project.CloneUrl, project.Version, project.Priority, project.Motd,
|
||||||
|
project.Public, project.Hidden)
|
||||||
|
|
||||||
var id int64
|
var id int64
|
||||||
err := row.Scan(&id)
|
err := row.Scan(&id)
|
||||||
@ -64,7 +66,7 @@ func (database *Database) GetProject(id int64) *Project {
|
|||||||
|
|
||||||
func getProject(id int64, db *sql.DB) *Project {
|
func getProject(id int64, db *sql.DB) *Project {
|
||||||
|
|
||||||
row := db.QueryRow(`SELECT id, priority, name, clone_url, git_repo, version, motd, public
|
row := db.QueryRow(`SELECT id, priority, name, clone_url, git_repo, version, motd, public, hidden
|
||||||
FROM project WHERE id=$1`, id)
|
FROM project WHERE id=$1`, id)
|
||||||
|
|
||||||
project, err := scanProject(row)
|
project, err := scanProject(row)
|
||||||
@ -87,7 +89,7 @@ func scanProject(row *sql.Row) (*Project, error) {
|
|||||||
|
|
||||||
project := &Project{}
|
project := &Project{}
|
||||||
err := row.Scan(&project.Id, &project.Priority, &project.Name, &project.CloneUrl,
|
err := row.Scan(&project.Id, &project.Priority, &project.Name, &project.CloneUrl,
|
||||||
&project.GitRepo, &project.Version, &project.Motd, &project.Public)
|
&project.GitRepo, &project.Version, &project.Motd, &project.Public, &project.Hidden)
|
||||||
|
|
||||||
return project, err
|
return project, err
|
||||||
}
|
}
|
||||||
@ -95,7 +97,7 @@ func scanProject(row *sql.Row) (*Project, error) {
|
|||||||
func (database *Database) GetProjectWithRepoName(repoName string) *Project {
|
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, motd, public
|
row := db.QueryRow(`SELECT id, priority, name, clone_url, git_repo, version, motd, public, hidden
|
||||||
FROM project WHERE LOWER(git_repo)=$1`,
|
FROM project WHERE LOWER(git_repo)=$1`,
|
||||||
strings.ToLower(repoName))
|
strings.ToLower(repoName))
|
||||||
|
|
||||||
@ -115,8 +117,9 @@ 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) = ($1,$2,$3,$4,$5,$6,$7) WHERE id=$8`,
|
SET (priority, name, clone_url, git_repo, version, motd, public, hidden) = ($1,$2,$3,$4,$5,$6,$7,$8) WHERE id=$9`,
|
||||||
project.Priority, project.Name, project.CloneUrl, project.GitRepo, project.Version, project.Motd, project.Public, project.Id)
|
project.Priority, project.Name, project.CloneUrl, project.GitRepo, project.Version, project.Motd,
|
||||||
|
project.Public, project.Hidden, project.Id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -139,26 +142,24 @@ func (database Database) GetAllProjects(workerId int64) *[]Project {
|
|||||||
var err error
|
var err error
|
||||||
if workerId == 0 {
|
if workerId == 0 {
|
||||||
rows, err = db.Query(`SELECT
|
rows, err = db.Query(`SELECT
|
||||||
Id, priority, name, clone_url, git_repo, version, motd, public
|
Id, priority, name, clone_url, git_repo, version, motd, public, hidden
|
||||||
FROM project
|
FROM project
|
||||||
LEFT JOIN worker_has_access_to_project whatp ON whatp.project = id
|
WHERE NOT hidden
|
||||||
WHERE public
|
|
||||||
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
|
Id, priority, name, clone_url, git_repo, version, motd, public, hidden
|
||||||
FROM project
|
FROM project
|
||||||
LEFT JOIN worker_has_access_to_project whatp ON whatp.project = id
|
LEFT JOIN worker_has_access_to_project whatp ON whatp.project = id
|
||||||
WHERE public OR whatp.worker = $1
|
WHERE NOT hidden OR whatp.worker = $1
|
||||||
ORDER BY name`, workerId)
|
ORDER BY name`, workerId)
|
||||||
}
|
}
|
||||||
handleErr(err)
|
handleErr(err)
|
||||||
|
|
||||||
for rows.Next() {
|
for rows.Next() {
|
||||||
|
|
||||||
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.GitRepo, &p.Version, &p.Motd, &p.Public, &p.Hidden)
|
||||||
handleErr(err)
|
handleErr(err)
|
||||||
projects = append(projects, p)
|
projects = append(projects, p)
|
||||||
}
|
}
|
||||||
|
@ -1,22 +1,14 @@
|
|||||||
package storage
|
package storage
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"database/sql"
|
|
||||||
"errors"
|
|
||||||
"github.com/Sirupsen/logrus"
|
"github.com/Sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Identity struct {
|
|
||||||
RemoteAddr string `json:"remote_addr"`
|
|
||||||
UserAgent string `json:"user_agent"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type Worker struct {
|
type Worker struct {
|
||||||
Id int64 `json:"id"`
|
Id int64 `json:"id"`
|
||||||
Created int64 `json:"created"`
|
Created int64 `json:"created"`
|
||||||
Identity *Identity `json:"identity"`
|
Alias string `json:"alias,omitempty"`
|
||||||
Alias string `json:"alias,omitempty"`
|
Secret []byte `json:"secret"`
|
||||||
Secret []byte `json:"secret"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type WorkerStats struct {
|
type WorkerStats struct {
|
||||||
@ -28,10 +20,8 @@ func (database *Database) SaveWorker(worker *Worker) {
|
|||||||
|
|
||||||
db := database.getDB()
|
db := database.getDB()
|
||||||
|
|
||||||
identityId := getOrCreateIdentity(worker.Identity, db)
|
row := db.QueryRow("INSERT INTO worker (created, secret, alias) VALUES ($1,$2,$3) RETURNING id",
|
||||||
|
worker.Created, worker.Secret, worker.Alias)
|
||||||
row := db.QueryRow("INSERT INTO worker (created, identity, secret, alias) VALUES ($1,$2,$3,$4) RETURNING id",
|
|
||||||
worker.Created, identityId, worker.Secret, worker.Alias)
|
|
||||||
|
|
||||||
err := row.Scan(&worker.Id)
|
err := row.Scan(&worker.Id)
|
||||||
handleErr(err)
|
handleErr(err)
|
||||||
@ -46,10 +36,9 @@ func (database *Database) GetWorker(id int64) *Worker {
|
|||||||
db := database.getDB()
|
db := database.getDB()
|
||||||
|
|
||||||
worker := &Worker{}
|
worker := &Worker{}
|
||||||
var identityId int64
|
|
||||||
|
|
||||||
row := db.QueryRow("SELECT id, created, identity, secret, alias FROM worker WHERE id=$1", id)
|
row := db.QueryRow("SELECT id, created, secret, alias FROM worker WHERE id=$1", id)
|
||||||
err := row.Scan(&worker.Id, &worker.Created, &identityId, &worker.Secret, &worker.Alias)
|
err := row.Scan(&worker.Id, &worker.Created, &worker.Secret, &worker.Alias)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logrus.WithFields(logrus.Fields{
|
logrus.WithFields(logrus.Fields{
|
||||||
"id": id,
|
"id": id,
|
||||||
@ -57,9 +46,6 @@ func (database *Database) GetWorker(id int64) *Worker {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
worker.Identity, err = getIdentity(identityId, db)
|
|
||||||
handleErr(err)
|
|
||||||
|
|
||||||
logrus.WithFields(logrus.Fields{
|
logrus.WithFields(logrus.Fields{
|
||||||
"worker": worker,
|
"worker": worker,
|
||||||
}).Trace("Database.getWorker SELECT worker")
|
}).Trace("Database.getWorker SELECT worker")
|
||||||
@ -67,48 +53,6 @@ func (database *Database) GetWorker(id int64) *Worker {
|
|||||||
return worker
|
return worker
|
||||||
}
|
}
|
||||||
|
|
||||||
func getIdentity(id int64, db *sql.DB) (*Identity, error) {
|
|
||||||
|
|
||||||
identity := &Identity{}
|
|
||||||
|
|
||||||
row := db.QueryRow("SELECT remote_addr, user_agent FROM worker_identity WHERE id=$1", id)
|
|
||||||
err := row.Scan(&identity.RemoteAddr, &identity.UserAgent)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.New("identity not found")
|
|
||||||
}
|
|
||||||
|
|
||||||
logrus.WithFields(logrus.Fields{
|
|
||||||
"identity": identity,
|
|
||||||
}).Trace("Database.getIdentity SELECT workerIdentity")
|
|
||||||
|
|
||||||
return identity, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func getOrCreateIdentity(identity *Identity, db *sql.DB) int64 {
|
|
||||||
|
|
||||||
res, err := db.Exec("INSERT INTO worker_identity (remote_addr, user_agent) VALUES ($1,$2) ON CONFLICT DO NOTHING",
|
|
||||||
identity.RemoteAddr, identity.UserAgent)
|
|
||||||
handleErr(err)
|
|
||||||
|
|
||||||
rowsAffected, err := res.RowsAffected()
|
|
||||||
logrus.WithFields(logrus.Fields{
|
|
||||||
"rowsAffected": rowsAffected,
|
|
||||||
}).Trace("Database.saveWorker INSERT workerIdentity")
|
|
||||||
|
|
||||||
row := db.QueryRow("SELECT (id) FROM worker_identity WHERE remote_addr=$1", identity.RemoteAddr)
|
|
||||||
|
|
||||||
var rowId int64
|
|
||||||
err = row.Scan(&rowId)
|
|
||||||
handleErr(err)
|
|
||||||
|
|
||||||
logrus.WithFields(logrus.Fields{
|
|
||||||
"rowId": rowId,
|
|
||||||
}).Trace("Database.saveWorker SELECT workerIdentity")
|
|
||||||
|
|
||||||
return rowId
|
|
||||||
}
|
|
||||||
|
|
||||||
func (database *Database) GrantAccess(workerId int64, projectId int64) bool {
|
func (database *Database) GrantAccess(workerId int64, projectId int64) bool {
|
||||||
|
|
||||||
db := database.getDB()
|
db := database.getDB()
|
||||||
@ -169,6 +113,88 @@ func (database *Database) UpdateWorker(worker *Worker) bool {
|
|||||||
return rowsAffected == 1
|
return rowsAffected == 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (database *Database) SaveAccessRequest(worker *Worker, projectId int64) bool {
|
||||||
|
|
||||||
|
db := database.getDB()
|
||||||
|
|
||||||
|
res, err := db.Exec(`INSERT INTO worker_requests_access_to_project
|
||||||
|
SELECT $1, id FROM project WHERE id=$2 AND NOT project.public
|
||||||
|
AND NOT EXISTS(SELECT * FROM worker_has_access_to_project WHERE worker=$1 AND project=$2)`,
|
||||||
|
worker.Id, projectId)
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
rowsAffected, _ := res.RowsAffected()
|
||||||
|
|
||||||
|
logrus.WithFields(logrus.Fields{
|
||||||
|
"rowsAffected": rowsAffected,
|
||||||
|
}).Trace("Database.SaveAccessRequest INSERT")
|
||||||
|
|
||||||
|
return rowsAffected == 1
|
||||||
|
}
|
||||||
|
|
||||||
|
func (database *Database) AcceptAccessRequest(worker *Worker, projectId int64) bool {
|
||||||
|
|
||||||
|
db := database.getDB()
|
||||||
|
|
||||||
|
res, err := db.Exec(`DELETE FROM worker_requests_access_to_project
|
||||||
|
WHERE worker=$1 AND project=$2`)
|
||||||
|
handleErr(err)
|
||||||
|
|
||||||
|
rowsAffected, _ := res.RowsAffected()
|
||||||
|
if rowsAffected == 1 {
|
||||||
|
_, err := db.Exec(`INSERT INTO worker_has_access_to_project
|
||||||
|
(worker, project) VALUES ($1,$2)`,
|
||||||
|
worker.Id, projectId)
|
||||||
|
handleErr(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
logrus.WithFields(logrus.Fields{
|
||||||
|
"rowsAffected": rowsAffected,
|
||||||
|
}).Trace("Database.AcceptAccessRequest")
|
||||||
|
|
||||||
|
return rowsAffected == 1
|
||||||
|
}
|
||||||
|
|
||||||
|
func (database *Database) RejectAccessRequest(worker *Worker, projectId int64) bool {
|
||||||
|
|
||||||
|
db := database.getDB()
|
||||||
|
|
||||||
|
res, err := db.Exec(`DELETE FROM worker_requests_access_to_project
|
||||||
|
WHERE worker=$1 AND project=$2`, worker.Id, projectId)
|
||||||
|
handleErr(err)
|
||||||
|
|
||||||
|
rowsAffected, _ := res.RowsAffected()
|
||||||
|
|
||||||
|
logrus.WithFields(logrus.Fields{
|
||||||
|
"rowsAffected": rowsAffected,
|
||||||
|
}).Trace("Database.AcceptAccessRequest")
|
||||||
|
|
||||||
|
return rowsAffected == 1
|
||||||
|
}
|
||||||
|
|
||||||
|
func (database *Database) GetAllAccessRequests(projectId int64) *[]Worker {
|
||||||
|
|
||||||
|
db := database.getDB()
|
||||||
|
|
||||||
|
rows, err := db.Query(`SELECT id, alias, created FROM worker_requests_access_to_project
|
||||||
|
INNER JOIN worker w on worker_requests_access_to_project.worker = w.id
|
||||||
|
WHERE project=$1`,
|
||||||
|
projectId)
|
||||||
|
handleErr(err)
|
||||||
|
|
||||||
|
requests := make([]Worker, 0)
|
||||||
|
|
||||||
|
for rows.Next() {
|
||||||
|
w := Worker{}
|
||||||
|
_ = rows.Scan(&w.Id, &w.Alias, &w.Created)
|
||||||
|
requests = append(requests, w)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &requests
|
||||||
|
}
|
||||||
|
|
||||||
func (database *Database) GetAllWorkerStats() *[]WorkerStats {
|
func (database *Database) GetAllWorkerStats() *[]WorkerStats {
|
||||||
|
|
||||||
db := database.getDB()
|
db := database.getDB()
|
||||||
|
@ -163,3 +163,7 @@ func login(request *api.LoginRequest) (*api.LoginResponse, *http.Response) {
|
|||||||
|
|
||||||
return resp, r
|
return resp, r
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getSessionCtx(username string, password string, admin bool) {
|
||||||
|
|
||||||
|
}
|
||||||
|
@ -19,6 +19,7 @@ func TestCreateGetProject(t *testing.T) {
|
|||||||
Priority: 123,
|
Priority: 123,
|
||||||
Motd: "motd",
|
Motd: "motd",
|
||||||
Public: true,
|
Public: true,
|
||||||
|
Hidden: true,
|
||||||
})
|
})
|
||||||
|
|
||||||
id := resp.Id
|
id := resp.Id
|
||||||
@ -59,6 +60,9 @@ func TestCreateGetProject(t *testing.T) {
|
|||||||
if getResp.Project.Public != true {
|
if getResp.Project.Public != true {
|
||||||
t.Error()
|
t.Error()
|
||||||
}
|
}
|
||||||
|
if getResp.Project.Hidden != true {
|
||||||
|
t.Error()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestCreateProjectInvalid(t *testing.T) {
|
func TestCreateProjectInvalid(t *testing.T) {
|
||||||
@ -141,6 +145,7 @@ func TestUpdateProjectValid(t *testing.T) {
|
|||||||
Name: "NameB",
|
Name: "NameB",
|
||||||
Motd: "MotdB",
|
Motd: "MotdB",
|
||||||
Public: false,
|
Public: false,
|
||||||
|
Hidden: true,
|
||||||
}, pid)
|
}, pid)
|
||||||
|
|
||||||
if updateResp.Ok != true {
|
if updateResp.Ok != true {
|
||||||
@ -164,6 +169,9 @@ func TestUpdateProjectValid(t *testing.T) {
|
|||||||
if proj.Project.Priority != 2 {
|
if proj.Project.Priority != 2 {
|
||||||
t.Error()
|
t.Error()
|
||||||
}
|
}
|
||||||
|
if proj.Project.Hidden != true {
|
||||||
|
t.Error()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestUpdateProjectInvalid(t *testing.T) {
|
func TestUpdateProjectInvalid(t *testing.T) {
|
||||||
|
@ -33,12 +33,6 @@ func TestCreateGetWorker(t *testing.T) {
|
|||||||
t.Error()
|
t.Error()
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(getResp.Worker.Identity.RemoteAddr) <= 0 {
|
|
||||||
t.Error()
|
|
||||||
}
|
|
||||||
if len(getResp.Worker.Identity.UserAgent) <= 0 {
|
|
||||||
t.Error()
|
|
||||||
}
|
|
||||||
if resp.Worker.Alias != "my_worker_alias" {
|
if resp.Worker.Alias != "my_worker_alias" {
|
||||||
t.Error()
|
t.Error()
|
||||||
}
|
}
|
||||||
|
@ -15,6 +15,11 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type SessionContext struct {
|
||||||
|
Manager *storage.Manager
|
||||||
|
SessionCookie *http.Cookie
|
||||||
|
}
|
||||||
|
|
||||||
func Post(path string, x interface{}, worker *storage.Worker) *http.Response {
|
func Post(path string, x interface{}, worker *storage.Worker) *http.Response {
|
||||||
|
|
||||||
body, err := json.Marshal(x)
|
body, err := json.Marshal(x)
|
||||||
|
105
test/schema.sql
105
test/schema.sql
@ -1,39 +1,30 @@
|
|||||||
DROP TABLE IF EXISTS worker_identity, worker, project, task, log_entry,
|
DROP TABLE IF EXISTS worker, project, task, log_entry,
|
||||||
worker_has_access_to_project, manager, manager_has_role_on_project, project_monitoring_snapshot,
|
worker_has_access_to_project, manager, manager_has_role_on_project, project_monitoring_snapshot,
|
||||||
worker_verifies_task;
|
worker_verifies_task, worker_requests_access_to_project;
|
||||||
DROP TYPE IF EXISTS status;
|
DROP TYPE IF EXISTS status;
|
||||||
DROP TYPE IF EXISTS log_level;
|
DROP TYPE IF EXISTS log_level;
|
||||||
|
|
||||||
CREATE TABLE worker_identity
|
|
||||||
(
|
|
||||||
id SERIAL PRIMARY KEY,
|
|
||||||
remote_addr TEXT,
|
|
||||||
user_agent TEXT,
|
|
||||||
|
|
||||||
UNIQUE (remote_addr)
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE TABLE worker
|
CREATE TABLE worker
|
||||||
(
|
(
|
||||||
id SERIAL PRIMARY KEY,
|
id SERIAL PRIMARY KEY NOT NULL,
|
||||||
alias TEXT,
|
alias TEXT NOT NULL,
|
||||||
created INTEGER,
|
created INTEGER NOT NULL,
|
||||||
identity INTEGER REFERENCES worker_identity (id),
|
secret BYTEA NOT NULL,
|
||||||
secret BYTEA,
|
closed_task_count INTEGER DEFAULT 0 NOT NULL
|
||||||
closed_task_count INTEGER DEFAULT 0
|
|
||||||
);
|
);
|
||||||
|
|
||||||
CREATE TABLE project
|
CREATE TABLE project
|
||||||
(
|
(
|
||||||
id SERIAL PRIMARY KEY,
|
id SERIAL PRIMARY KEY NOT NULL,
|
||||||
priority INTEGER DEFAULT 0,
|
priority INTEGER DEFAULT 0 NOT NULL,
|
||||||
name TEXT UNIQUE,
|
closed_task_count INT DEFAULT 0 NOT NULL,
|
||||||
clone_url TEXT,
|
public boolean NOT NULL,
|
||||||
git_repo TEXT UNIQUE,
|
hidden boolean NOT NULL,
|
||||||
version TEXT,
|
name TEXT UNIQUE NOT NULL,
|
||||||
motd TEXT,
|
clone_url TEXT NOT NULL,
|
||||||
public boolean,
|
git_repo TEXT UNIQUE NOT NULL,
|
||||||
closed_task_count INT DEFAULT 0
|
version TEXT NOT NULL,
|
||||||
|
motd TEXT NOT NULL
|
||||||
);
|
);
|
||||||
|
|
||||||
CREATE TABLE worker_has_access_to_project
|
CREATE TABLE worker_has_access_to_project
|
||||||
@ -61,43 +52,50 @@ CREATE TABLE task
|
|||||||
|
|
||||||
CREATE TABLE worker_verifies_task
|
CREATE TABLE worker_verifies_task
|
||||||
(
|
(
|
||||||
verification_hash BIGINT,
|
verification_hash BIGINT NOT NULL,
|
||||||
task BIGINT REFERENCES task (id) ON DELETE CASCADE,
|
task BIGINT REFERENCES task (id) ON DELETE CASCADE NOT NULL,
|
||||||
worker INT REFERENCES worker (id)
|
worker INT REFERENCES worker (id) NOT NULL
|
||||||
);
|
);
|
||||||
|
|
||||||
CREATE TABLE log_entry
|
CREATE TABLE log_entry
|
||||||
(
|
(
|
||||||
level INTEGER,
|
level INTEGER NOT NULL,
|
||||||
message TEXT,
|
message TEXT NOT NULL,
|
||||||
message_data TEXT,
|
message_data TEXT NOT NULL,
|
||||||
timestamp INTEGER
|
timestamp INTEGER NOT NULL
|
||||||
);
|
);
|
||||||
|
|
||||||
CREATE TABLE manager
|
CREATE TABLE manager
|
||||||
(
|
(
|
||||||
id SERIAL PRIMARY KEY,
|
id SERIAL PRIMARY KEY,
|
||||||
username TEXT UNIQUE,
|
register_time INTEGER NOT NULL,
|
||||||
password BYTEA,
|
website_admin BOOLEAN NOT NULL,
|
||||||
website_admin BOOLEAN
|
username TEXT UNIQUE NOT NULL,
|
||||||
|
password BYTEA NOT NULL
|
||||||
);
|
);
|
||||||
|
|
||||||
CREATE TABLE manager_has_role_on_project
|
CREATE TABLE manager_has_role_on_project
|
||||||
(
|
(
|
||||||
manager INTEGER REFERENCES manager (id),
|
manager INTEGER REFERENCES manager (id) NOT NULL,
|
||||||
role SMALLINT,
|
role SMALLINT NOT NULl,
|
||||||
project INTEGER REFERENCES project (id)
|
project INTEGER REFERENCES project (id) NOT NULL
|
||||||
);
|
);
|
||||||
|
|
||||||
CREATE TABLE project_monitoring_snapshot
|
CREATE TABLE project_monitoring_snapshot
|
||||||
(
|
(
|
||||||
project INT REFERENCES project (id),
|
project INT REFERENCES project (id) NOT NULL,
|
||||||
new_task_count INT,
|
new_task_count INT NOT NULL,
|
||||||
failed_task_count INT,
|
failed_task_count INT NOT NULL,
|
||||||
closed_task_count INT,
|
closed_task_count INT NOT NULL,
|
||||||
awaiting_verification_task_count INT,
|
awaiting_verification_task_count INT NOT NULL,
|
||||||
worker_access_count INT,
|
worker_access_count INT NOT NULL,
|
||||||
timestamp INT
|
timestamp INT NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE worker_requests_access_to_project
|
||||||
|
(
|
||||||
|
worker INT REFERENCES worker (id) NOT NULL,
|
||||||
|
project INT REFERENCES project (id) NOT NULL
|
||||||
);
|
);
|
||||||
|
|
||||||
CREATE OR REPLACE FUNCTION on_task_delete_proc() RETURNS TRIGGER AS
|
CREATE OR REPLACE FUNCTION on_task_delete_proc() RETURNS TRIGGER AS
|
||||||
@ -114,6 +112,21 @@ CREATE TRIGGER on_task_delete
|
|||||||
FOR EACH ROW
|
FOR EACH ROW
|
||||||
EXECUTE PROCEDURE on_task_delete_proc();
|
EXECUTE PROCEDURE on_task_delete_proc();
|
||||||
|
|
||||||
|
CREATE OR REPLACE FUNCTION on_manager_insert() RETURNS TRIGGER AS
|
||||||
|
$$
|
||||||
|
BEGIN
|
||||||
|
IF NEW.id = 1 THEN
|
||||||
|
UPDATE manager SET website_admin= TRUE WHERE id = 1;
|
||||||
|
end if;
|
||||||
|
RETURN NEW;
|
||||||
|
END;
|
||||||
|
$$ LANGUAGE 'plpgsql';
|
||||||
|
CREATE TRIGGER on_manager_insert
|
||||||
|
AFTER INSERT
|
||||||
|
ON manager
|
||||||
|
FOR EACH ROW
|
||||||
|
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 INT) RETURNS BOOLEAN AS
|
||||||
$$
|
$$
|
||||||
DECLARE
|
DECLARE
|
||||||
|
@ -65,4 +65,8 @@ export class ApiService {
|
|||||||
return this.http.get(this.url + `/worker/stats`, this.options)
|
return this.http.get(this.url + `/worker/stats`, this.options)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getProjectAccessRequests(project: number) {
|
||||||
|
return this.http.get(this.url + `/project/requests/${project}`)
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -11,6 +11,7 @@ import {TranslateService} from "@ngx-translate/core";
|
|||||||
import {LoginComponent} from "./login/login.component";
|
import {LoginComponent} from "./login/login.component";
|
||||||
import {AccountDetailsComponent} from "./account-details/account-details.component";
|
import {AccountDetailsComponent} from "./account-details/account-details.component";
|
||||||
import {WorkerDashboardComponent} from "./worker-dashboard/worker-dashboard.component";
|
import {WorkerDashboardComponent} from "./worker-dashboard/worker-dashboard.component";
|
||||||
|
import {ProjectPermsComponent} from "./project-perms/project-perms.component";
|
||||||
|
|
||||||
const routes: Routes = [
|
const routes: Routes = [
|
||||||
{path: "log", component: LogsComponent},
|
{path: "log", component: LogsComponent},
|
||||||
@ -19,6 +20,7 @@ const routes: Routes = [
|
|||||||
{path: "projects", component: ProjectListComponent},
|
{path: "projects", component: ProjectListComponent},
|
||||||
{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: "new_project", component: CreateProjectComponent},
|
{path: "new_project", component: CreateProjectComponent},
|
||||||
{path: "workers", component: WorkerDashboardComponent}
|
{path: "workers", component: WorkerDashboardComponent}
|
||||||
];
|
];
|
||||||
|
@ -6,10 +6,11 @@
|
|||||||
[routerLink]="'log'">{{"nav.logs" | translate}}</button>
|
[routerLink]="'log'">{{"nav.logs" | translate}}</button>
|
||||||
<button mat-button [class.mat-accent]="router.url == '/projects'" class="nav-link"
|
<button mat-button [class.mat-accent]="router.url == '/projects'" class="nav-link"
|
||||||
[routerLink]="'projects'">{{"nav.project_list" | translate}}</button>
|
[routerLink]="'projects'">{{"nav.project_list" | translate}}</button>
|
||||||
<button mat-button [class.mat-accent]="router.url == '/new_project'" class="nav-link"
|
|
||||||
[routerLink]="'new_project'">{{"nav.new_project" | translate}}</button>
|
|
||||||
<button mat-button [class.mat-accent]="router.url == '/workers'" class="nav-link"
|
<button mat-button [class.mat-accent]="router.url == '/workers'" class="nav-link"
|
||||||
[routerLink]="'workers'">{{"nav.worker_dashboard" | translate}}</button>
|
[routerLink]="'workers'">{{"nav.worker_dashboard" | translate}}</button>
|
||||||
|
<button mat-button [class.mat-accent]="router.url == '/new_project'" class="nav-link"
|
||||||
|
[routerLink]="'new_project'"
|
||||||
|
*ngIf="authService.logged">{{"nav.new_project" | translate}}</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="small-nav">
|
<div class="small-nav">
|
||||||
<button mat-button [matMenuTriggerFor]="smallNav">
|
<button mat-button [matMenuTriggerFor]="smallNav">
|
||||||
@ -22,10 +23,11 @@
|
|||||||
[routerLink]="'log'">{{"nav.logs" | translate}}</button>
|
[routerLink]="'log'">{{"nav.logs" | translate}}</button>
|
||||||
<button mat-menu-item [class.mat-accent]="router.url == '/projects'" class="nav-link"
|
<button mat-menu-item [class.mat-accent]="router.url == '/projects'" class="nav-link"
|
||||||
[routerLink]="'projects'">{{"nav.project_list" | translate}}</button>
|
[routerLink]="'projects'">{{"nav.project_list" | translate}}</button>
|
||||||
<button mat-menu-item [class.mat-accent]="router.url == '/new_project'" class="nav-link"
|
|
||||||
[routerLink]="'new_project'">{{"nav.new_project" | translate}}</button>
|
|
||||||
<button mat-menu-item [class.mat-accent]="router.url == '/workers'" class="nav-link"
|
<button mat-menu-item [class.mat-accent]="router.url == '/workers'" class="nav-link"
|
||||||
[routerLink]="'workers'">{{"nav.worker_dashboard" | translate}}</button>
|
[routerLink]="'workers'">{{"nav.worker_dashboard" | translate}}</button>
|
||||||
|
<button mat-menu-item [class.mat-accent]="router.url == '/new_project'" class="nav-link"
|
||||||
|
[routerLink]="'new_project'"
|
||||||
|
*ngIf="authService.logged">{{"nav.new_project" | translate}}</button>
|
||||||
</mat-menu>
|
</mat-menu>
|
||||||
</div>
|
</div>
|
||||||
<span class="nav-spacer"></span>
|
<span class="nav-spacer"></span>
|
||||||
|
@ -20,7 +20,7 @@ export class AppComponent {
|
|||||||
];
|
];
|
||||||
|
|
||||||
constructor(private translate: TranslateService,
|
constructor(private translate: TranslateService,
|
||||||
private router: Router,
|
public router: Router,
|
||||||
public authService: AuthService) {
|
public authService: AuthService) {
|
||||||
|
|
||||||
translate.addLangs([
|
translate.addLangs([
|
||||||
|
@ -47,6 +47,7 @@ import {TranslatedPaginator} from "./TranslatedPaginatorConfiguration";
|
|||||||
import {LoginComponent} from './login/login.component';
|
import {LoginComponent} from './login/login.component';
|
||||||
import {AccountDetailsComponent} from './account-details/account-details.component';
|
import {AccountDetailsComponent} from './account-details/account-details.component';
|
||||||
import {WorkerDashboardComponent} from './worker-dashboard/worker-dashboard.component';
|
import {WorkerDashboardComponent} from './worker-dashboard/worker-dashboard.component';
|
||||||
|
import {ProjectPermsComponent} from './project-perms/project-perms.component';
|
||||||
|
|
||||||
|
|
||||||
export function createTranslateLoader(http: HttpClient) {
|
export function createTranslateLoader(http: HttpClient) {
|
||||||
@ -66,6 +67,7 @@ export function createTranslateLoader(http: HttpClient) {
|
|||||||
LoginComponent,
|
LoginComponent,
|
||||||
AccountDetailsComponent,
|
AccountDetailsComponent,
|
||||||
WorkerDashboardComponent,
|
WorkerDashboardComponent,
|
||||||
|
ProjectPermsComponent,
|
||||||
],
|
],
|
||||||
imports: [
|
imports: [
|
||||||
BrowserModule,
|
BrowserModule,
|
||||||
|
@ -10,6 +10,7 @@ import {Router} from "@angular/router";
|
|||||||
export class AuthService {
|
export class AuthService {
|
||||||
|
|
||||||
account: Manager;
|
account: Manager;
|
||||||
|
logged: boolean;
|
||||||
|
|
||||||
constructor(private apiService: ApiService,
|
constructor(private apiService: ApiService,
|
||||||
private messengerService: MessengerService,
|
private messengerService: MessengerService,
|
||||||
@ -17,6 +18,7 @@ export class AuthService {
|
|||||||
this.apiService.getAccountDetails()
|
this.apiService.getAccountDetails()
|
||||||
.subscribe((data: any) => {
|
.subscribe((data: any) => {
|
||||||
this.account = data.manager;
|
this.account = data.manager;
|
||||||
|
this.logged = data.logged_in;
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -27,6 +29,7 @@ export class AuthService {
|
|||||||
this.apiService.getAccountDetails()
|
this.apiService.getAccountDetails()
|
||||||
.subscribe((data: any) => {
|
.subscribe((data: any) => {
|
||||||
this.account = data.manager;
|
this.account = data.manager;
|
||||||
|
this.logged = true;
|
||||||
this.router.navigateByUrl("/account");
|
this.router.navigateByUrl("/account");
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
@ -42,7 +45,8 @@ export class AuthService {
|
|||||||
.subscribe(
|
.subscribe(
|
||||||
() => {
|
() => {
|
||||||
this.account = null;
|
this.account = null;
|
||||||
this.router.navigateByUrl("");
|
this.logged = false;
|
||||||
|
this.router.navigateByUrl("login");
|
||||||
},
|
},
|
||||||
error => {
|
error => {
|
||||||
console.log(error);
|
console.log(error);
|
||||||
|
@ -3,40 +3,47 @@
|
|||||||
|
|
||||||
<mat-tab-group>
|
<mat-tab-group>
|
||||||
<mat-tab [label]="'login.title' | translate" class="pad">
|
<mat-tab [label]="'login.title' | translate" class="pad">
|
||||||
<mat-form-field appearance="outline">
|
<form (ngSubmit)="login()">
|
||||||
<mat-label>{{"login.username" | translate}}</mat-label>
|
<mat-form-field appearance="outline">
|
||||||
<input type="text" matInput [(ngModel)]="credentials.username">
|
<mat-label>{{"login.username" | translate}}</mat-label>
|
||||||
</mat-form-field>
|
<input type="text" matInput [(ngModel)]="credentials.username"
|
||||||
|
[ngModelOptions]="{standalone: true}">
|
||||||
|
</mat-form-field>
|
||||||
|
|
||||||
<mat-form-field appearance="outline">
|
<mat-form-field appearance="outline">
|
||||||
<mat-label>{{ "login.password" | translate}}</mat-label>
|
<mat-label>{{ "login.password" | translate}}</mat-label>
|
||||||
<input type="password" matInput [(ngModel)]="credentials.password">
|
<input type="password" matInput [(ngModel)]="credentials.password"
|
||||||
</mat-form-field>
|
[ngModelOptions]="{standalone: true}">
|
||||||
|
</mat-form-field>
|
||||||
|
|
||||||
<button mat-raised-button color="primary"
|
<button mat-raised-button color="primary"
|
||||||
(click)="login()">{{"login.login" | translate}}</button>
|
type="submit">{{"login.login" | translate}}</button>
|
||||||
|
</form>
|
||||||
</mat-tab>
|
</mat-tab>
|
||||||
<mat-tab [label]="'create_account.title' | translate" class="pad">
|
<mat-tab [label]="'create_account.title' | translate" class="pad">
|
||||||
<mat-form-field appearance="outline" [hideRequiredMarker]="true">
|
<form (ngSubmit)="register()">
|
||||||
<mat-label>{{"login.username" | translate}}</mat-label>
|
<mat-form-field appearance="outline" [hideRequiredMarker]="true">
|
||||||
<mat-hint align="end">{{credentials.username?.length || 0}}/16</mat-hint>
|
<mat-label>{{"login.username" | translate}}</mat-label>
|
||||||
<input maxlength="16" type="text" matInput [(ngModel)]="credentials.username" name="username"
|
<mat-hint align="end">{{credentials.username?.length || 0}}/16</mat-hint>
|
||||||
required>
|
<input maxlength="16" type="text" matInput [(ngModel)]="credentials.username" name="username"
|
||||||
</mat-form-field>
|
[ngModelOptions]="{standalone: true}" required>
|
||||||
|
</mat-form-field>
|
||||||
|
|
||||||
<mat-form-field appearance="outline" [hideRequiredMarker]="true">
|
<mat-form-field appearance="outline" [hideRequiredMarker]="true">
|
||||||
<mat-label>{{ "login.password" | translate}}</mat-label>
|
<mat-label>{{ "login.password" | translate}}</mat-label>
|
||||||
<input type="password" matInput [(ngModel)]="credentials.password" name="password" required>
|
<input type="password" matInput [(ngModel)]="credentials.password" name="password"
|
||||||
</mat-form-field>
|
[ngModelOptions]="{standalone: true}">
|
||||||
|
</mat-form-field>
|
||||||
|
|
||||||
<mat-form-field appearance="outline">
|
<mat-form-field appearance="outline">
|
||||||
<mat-label>{{ "login.repeat_password" | translate}}</mat-label>
|
<mat-label>{{ "login.repeat_password" | translate}}</mat-label>
|
||||||
<input type="password" matInput [(ngModel)]="credentials.repeatPassword" name="password2"
|
<input type="password" matInput [(ngModel)]="credentials.repeatPassword" name="password2"
|
||||||
>
|
[ngModelOptions]="{standalone: true}" required>
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
|
|
||||||
<button mat-raised-button color="primary" [disabled]="!canCreate()"
|
<button mat-raised-button color="primary" [disabled]="!canCreate()"
|
||||||
(click)="register()">{{"create_account.create" | translate}}</button>
|
type="submit">{{"create_account.create" | translate}}</button>
|
||||||
|
</form>
|
||||||
</mat-tab>
|
</mat-tab>
|
||||||
</mat-tab-group>
|
</mat-tab-group>
|
||||||
</mat-card>
|
</mat-card>
|
||||||
|
@ -3,6 +3,7 @@ import {MessengerService} from "../messenger.service";
|
|||||||
import {MessengerState} from "./messenger";
|
import {MessengerState} from "./messenger";
|
||||||
import {Subscription} from "rxjs";
|
import {Subscription} from "rxjs";
|
||||||
import {MatSnackBar, MatSnackBarConfig} from "@angular/material";
|
import {MatSnackBar, MatSnackBarConfig} from "@angular/material";
|
||||||
|
import {TranslateService} from "@ngx-translate/core";
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'messenger-snack-bar',
|
selector: 'messenger-snack-bar',
|
||||||
@ -13,7 +14,10 @@ export class SnackBarComponent implements OnInit {
|
|||||||
|
|
||||||
private subscription: Subscription;
|
private subscription: Subscription;
|
||||||
|
|
||||||
constructor(private messengerService: MessengerService, private snackBar: MatSnackBar) {
|
constructor(
|
||||||
|
private messengerService: MessengerService,
|
||||||
|
private snackBar: MatSnackBar,
|
||||||
|
private translate: TranslateService) {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -23,9 +27,11 @@ export class SnackBarComponent implements OnInit {
|
|||||||
if (state.hidden) {
|
if (state.hidden) {
|
||||||
this.snackBar.dismiss();
|
this.snackBar.dismiss();
|
||||||
} else {
|
} else {
|
||||||
this.snackBar.open(state.message, "Close", <MatSnackBarConfig>{
|
this.translate.get("messenger.close")
|
||||||
duration: 10 * 1000,
|
.subscribe(t =>
|
||||||
})
|
this.snackBar.open(state.message, t, <MatSnackBarConfig>{
|
||||||
|
duration: 10 * 1000,
|
||||||
|
}))
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
interface Manager {
|
interface Manager {
|
||||||
|
id: number;
|
||||||
username: string
|
username: string
|
||||||
|
website_admin: boolean;
|
||||||
}
|
}
|
||||||
|
6
web/angular/src/app/models/worker.ts
Normal file
6
web/angular/src/app/models/worker.ts
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
export interface Worker {
|
||||||
|
id: number
|
||||||
|
alias: string
|
||||||
|
created: number
|
||||||
|
secret: string
|
||||||
|
}
|
@ -56,6 +56,8 @@
|
|||||||
<mat-card-actions>
|
<mat-card-actions>
|
||||||
<button mat-raised-button color="primary" *ngIf="project"
|
<button mat-raised-button color="primary" *ngIf="project"
|
||||||
[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"
|
||||||
|
[routerLink]="'/project/' + project.id + '/perms'">{{"project.perms" | translate}}</button>
|
||||||
</mat-card-actions>
|
</mat-card-actions>
|
||||||
</mat-card>
|
</mat-card>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,3 +1,7 @@
|
|||||||
button {
|
button {
|
||||||
margin-right: 15px;
|
margin-right: 15px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mat-panel-title > mat-icon {
|
||||||
|
margin-right: 1em;
|
||||||
|
}
|
||||||
|
@ -7,16 +7,22 @@
|
|||||||
<mat-accordion>
|
<mat-accordion>
|
||||||
<mat-expansion-panel *ngFor="let project of projects">
|
<mat-expansion-panel *ngFor="let project of projects">
|
||||||
<mat-expansion-panel-header>
|
<mat-expansion-panel-header>
|
||||||
<mat-panel-title><span style="width: 3em">{{project.id}}</span>{{project.name}}
|
<mat-panel-title>
|
||||||
|
<mat-icon *ngIf="project.public">public</mat-icon>
|
||||||
|
<mat-icon *ngIf="!project.public">lock</mat-icon>
|
||||||
|
<span style="width: 3em">{{project.id}}</span>{{project.name}}
|
||||||
</mat-panel-title>
|
</mat-panel-title>
|
||||||
<mat-panel-description>{{project.motd}}</mat-panel-description>
|
<mat-panel-description>{{project.motd}}</mat-panel-description>
|
||||||
</mat-expansion-panel-header>
|
</mat-expansion-panel-header>
|
||||||
<pre>{{project | json}}</pre>
|
<pre>{{project | json}}</pre>
|
||||||
<div>
|
<div>
|
||||||
<button mat-raised-button [routerLink]="'/project/' + project.id">
|
<button mat-raised-button color="primary" [routerLink]="'/project/' + project.id">
|
||||||
<mat-icon>timeline</mat-icon>{{"projects.dashboard" | translate}}</button>
|
<mat-icon>timeline</mat-icon>{{"projects.dashboard" | translate}}</button>
|
||||||
<button mat-raised-button [routerLink]="'/project/' + project.id + '/update'">
|
<button mat-raised-button color="primary" [routerLink]="'/project/' + project.id + '/update'">
|
||||||
<mat-icon>build</mat-icon>{{"project.update" | translate}}</button>
|
<mat-icon>build</mat-icon>{{"project.update" | translate}}</button>
|
||||||
|
<button mat-raised-button color="primary" [routerLink]="'/project/' + project.id + '/perms'">
|
||||||
|
<mat-icon>perm_identity</mat-icon>
|
||||||
|
{{"project.perms" | translate}}</button>
|
||||||
</div>
|
</div>
|
||||||
</mat-expansion-panel>
|
</mat-expansion-panel>
|
||||||
<span *ngIf="projects && projects.length == 0">
|
<span *ngIf="projects && projects.length == 0">
|
||||||
|
@ -0,0 +1,15 @@
|
|||||||
|
.unauthorized {
|
||||||
|
text-align: center;
|
||||||
|
color: #616161;
|
||||||
|
min-height: 3em;
|
||||||
|
margin-top: 2em !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-mono {
|
||||||
|
font-family: Hack, Courier, "Courier New", monospace;
|
||||||
|
color: #ff4081;
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
margin-left: 15px;
|
||||||
|
}
|
@ -0,0 +1,37 @@
|
|||||||
|
<div class="container">
|
||||||
|
<mat-card class="mat-elevation-z8">
|
||||||
|
<button mat-button [title]="'perms.refresh' | translate" style="float:right"
|
||||||
|
(click)="refresh()">
|
||||||
|
<mat-icon>refresh</mat-icon>
|
||||||
|
</button>
|
||||||
|
<mat-card-header>
|
||||||
|
<mat-card-title>{{"perms.title" | translate}}</mat-card-title>
|
||||||
|
<mat-card-subtitle>{{"perms.subtitle" | translate}}</mat-card-subtitle>
|
||||||
|
</mat-card-header>
|
||||||
|
|
||||||
|
<mat-card-content>
|
||||||
|
<mat-list *ngIf="!unauthorized">
|
||||||
|
<mat-list-item *ngFor="let w of requests">
|
||||||
|
<mat-icon mat-list-icon>person_add</mat-icon>
|
||||||
|
<h4 mat-line>{{w.alias}}</h4>
|
||||||
|
<div mat-line>
|
||||||
|
Id=<span class="text-mono">{{w.id}}</span>, {{"perms.created" | translate}}
|
||||||
|
<span class="text-mono">{{moment.unix(w.created).format("YYYY-MM-DD HH:mm:ss UTC")}}</span>
|
||||||
|
</div>
|
||||||
|
<span style="flex: 1 1 auto;"></span>
|
||||||
|
<button mat-raised-button color="primary" [title]="'perms.grant' | translate">
|
||||||
|
<mat-icon>check</mat-icon>
|
||||||
|
</button>
|
||||||
|
<button mat-raised-button color="warn" [title]="'perms.reject' | translate">
|
||||||
|
<mat-icon>close</mat-icon>
|
||||||
|
</button>
|
||||||
|
</mat-list-item>
|
||||||
|
</mat-list>
|
||||||
|
<p *ngIf="unauthorized" class="unauthorized">
|
||||||
|
<mat-icon>block</mat-icon>
|
||||||
|
{{"perms.unauthorized" | translate}}
|
||||||
|
</p>
|
||||||
|
</mat-card-content>
|
||||||
|
|
||||||
|
</mat-card>
|
||||||
|
</div>
|
60
web/angular/src/app/project-perms/project-perms.component.ts
Normal file
60
web/angular/src/app/project-perms/project-perms.component.ts
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
import {Worker} from "../models/worker"
|
||||||
|
import {Component, OnInit} from '@angular/core';
|
||||||
|
import {ApiService} from "../api.service";
|
||||||
|
import {Project} from "../models/project";
|
||||||
|
import {ActivatedRoute, Router} from "@angular/router";
|
||||||
|
import {MessengerService} from "../messenger.service";
|
||||||
|
import {TranslateService} from "@ngx-translate/core";
|
||||||
|
|
||||||
|
import * as moment from "moment"
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-project-perms',
|
||||||
|
templateUrl: './project-perms.component.html',
|
||||||
|
styleUrls: ['./project-perms.component.css']
|
||||||
|
})
|
||||||
|
export class ProjectPermsComponent implements OnInit {
|
||||||
|
|
||||||
|
constructor(private apiService: ApiService,
|
||||||
|
private route: ActivatedRoute,
|
||||||
|
private messengerService: MessengerService,
|
||||||
|
private translate: TranslateService,
|
||||||
|
private router: Router) {
|
||||||
|
}
|
||||||
|
|
||||||
|
project: Project;
|
||||||
|
private projectId: number;
|
||||||
|
requests: Worker[];
|
||||||
|
unauthorized: boolean = false;
|
||||||
|
moment = moment;
|
||||||
|
|
||||||
|
ngOnInit() {
|
||||||
|
this.route.params.subscribe(params => {
|
||||||
|
this.projectId = params["id"];
|
||||||
|
this.getProject();
|
||||||
|
this.getProjectRequests();
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
private getProject() {
|
||||||
|
this.apiService.getProject(this.projectId).subscribe(data => {
|
||||||
|
this.project = data["project"]
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
private getProjectRequests() {
|
||||||
|
this.apiService.getProjectAccessRequests(this.projectId).subscribe(
|
||||||
|
data => {
|
||||||
|
this.requests = data["requests"]
|
||||||
|
},
|
||||||
|
error => {
|
||||||
|
if (error && (error.status == 401 || error.status == 403)) {
|
||||||
|
this.unauthorized = true;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
private refresh() {
|
||||||
|
this.getProjectRequests()
|
||||||
|
}
|
||||||
|
}
|
@ -29,16 +29,7 @@ export class UpdateProjectComponent implements OnInit {
|
|||||||
|
|
||||||
private getProject() {
|
private getProject() {
|
||||||
this.apiService.getProject(this.projectId).subscribe(data => {
|
this.apiService.getProject(this.projectId).subscribe(data => {
|
||||||
this.project = <Project>{
|
this.project = data["project"]
|
||||||
id: data["project"]["id"],
|
|
||||||
name: data["project"]["name"],
|
|
||||||
clone_url: data["project"]["clone_url"],
|
|
||||||
git_repo: data["project"]["git_repo"],
|
|
||||||
motd: data["project"]["motd"],
|
|
||||||
priority: data["project"]["priority"],
|
|
||||||
version: data["project"]["version"],
|
|
||||||
public: data["project"]["public"],
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -57,7 +57,8 @@
|
|||||||
"create": "Create",
|
"create": "Create",
|
||||||
"git_repo": "Git repository name",
|
"git_repo": "Git repository name",
|
||||||
"motd": "Message of the day",
|
"motd": "Message of the day",
|
||||||
"update": "Edit"
|
"update": "Edit",
|
||||||
|
"perms": "Permissions"
|
||||||
},
|
},
|
||||||
"dashboard": {
|
"dashboard": {
|
||||||
"title": "Dashboard for",
|
"title": "Dashboard for",
|
||||||
@ -86,5 +87,18 @@
|
|||||||
"workers": {
|
"workers": {
|
||||||
"title": "Completed tasks per worker",
|
"title": "Completed tasks per worker",
|
||||||
"subtitle": "Real-time data for all projects"
|
"subtitle": "Real-time data for all projects"
|
||||||
|
},
|
||||||
|
"perms": {
|
||||||
|
"title": "Project permissions",
|
||||||
|
"subtitle": "Workers need to request access to work on private projects",
|
||||||
|
"unauthorized": "You need ROLE_MANAGE_ACCESS on this project to access this page",
|
||||||
|
"created": "Created on",
|
||||||
|
"grant": "Accept request",
|
||||||
|
"reject": "Deny request",
|
||||||
|
"refresh": "Refresh"
|
||||||
|
},
|
||||||
|
"messenger": {
|
||||||
|
"close": "Close",
|
||||||
|
"unauthorized": "Unauthorized"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -58,7 +58,8 @@
|
|||||||
"public": "Publique",
|
"public": "Publique",
|
||||||
"create": "Créer",
|
"create": "Créer",
|
||||||
"motd": "Message du jour",
|
"motd": "Message du jour",
|
||||||
"update": "Mettre à jour"
|
"update": "Mettre à jour",
|
||||||
|
"perms": "Permissions"
|
||||||
},
|
},
|
||||||
"dashboard": {
|
"dashboard": {
|
||||||
"title": "Tableau de bord pour ",
|
"title": "Tableau de bord pour ",
|
||||||
@ -88,6 +89,19 @@
|
|||||||
"workers": {
|
"workers": {
|
||||||
"title": "Tâches complétés par worker",
|
"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"
|
||||||
|
},
|
||||||
|
"perms": {
|
||||||
|
"title": "Permissions du projet",
|
||||||
|
"subtitle": "Les Workers doivent faire un requête d'acces pour pouvoir travailler sur les projets privés",
|
||||||
|
"unauthorized": "Vous devez avoir ROLE_GESTION_ACCES sur ce project pour accéder à cette page",
|
||||||
|
"created": "Créé le",
|
||||||
|
"grant": "Accepter la requête",
|
||||||
|
"reject": "Rejeter la requête",
|
||||||
|
"refresh": "Refraichir"
|
||||||
|
},
|
||||||
|
"messenger": {
|
||||||
|
"close": "Fermer",
|
||||||
|
"unauthorized": "Non autorisé"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user