mirror of
https://github.com/simon987/task_tracker.git
synced 2025-04-18 01:46:45 +00:00
Added i18n, started working on monitoring
This commit is contained in:
parent
22f4a6b358
commit
87f4d08984
@ -65,8 +65,7 @@ func New() *WebAPI {
|
||||
api.router.POST("/project/create", LogRequestMiddleware(api.ProjectCreate))
|
||||
api.router.GET("/project/get/:id", LogRequestMiddleware(api.ProjectGet))
|
||||
api.router.POST("/project/update/:id", LogRequestMiddleware(api.ProjectUpdate))
|
||||
api.router.GET("/project/stats/:id", LogRequestMiddleware(api.ProjectGetStats))
|
||||
api.router.GET("/project/stats", LogRequestMiddleware(api.ProjectGetAllStats))
|
||||
api.router.GET("/project/list", LogRequestMiddleware(api.ProjectGetAllProjects))
|
||||
|
||||
api.router.POST("/task/create", LogRequestMiddleware(api.TaskCreate))
|
||||
api.router.GET("/task/get/:project", LogRequestMiddleware(api.TaskGetFromProject))
|
||||
|
@ -43,16 +43,10 @@ type GetProjectResponse struct {
|
||||
Project *storage.Project `json:"project,omitempty"`
|
||||
}
|
||||
|
||||
type GetProjectStatsResponse struct {
|
||||
Ok bool `json:"ok"`
|
||||
Message string `json:"message,omitempty"`
|
||||
Stats *storage.ProjectStats `json:"stats,omitempty"`
|
||||
}
|
||||
|
||||
type GetAllProjectsStatsResponse struct {
|
||||
Ok bool `json:"ok"`
|
||||
Message string `json:"message,omitempty"`
|
||||
Stats *[]storage.ProjectStats `json:"stats,omitempty"`
|
||||
type GetAllProjectsResponse struct {
|
||||
Ok bool `json:"ok"`
|
||||
Message string `json:"message,omitempty"`
|
||||
Projects *[]storage.Project `json:"projects,omitempty"`
|
||||
}
|
||||
|
||||
func (api *WebAPI) ProjectCreate(r *Request) {
|
||||
@ -200,38 +194,12 @@ func (api *WebAPI) ProjectGet(r *Request) {
|
||||
}
|
||||
}
|
||||
|
||||
func (api *WebAPI) ProjectGetStats(r *Request) {
|
||||
func (api *WebAPI) ProjectGetAllProjects(r *Request) {
|
||||
|
||||
id, err := strconv.ParseInt(r.Ctx.UserValue("id").(string), 10, 64)
|
||||
if err != nil {
|
||||
r.Json(GetProjectStatsResponse{
|
||||
Ok: false,
|
||||
Message: "Could not parse request",
|
||||
}, 400)
|
||||
return
|
||||
}
|
||||
projects := api.Database.GetAllProjects()
|
||||
|
||||
stats := api.Database.GetProjectStats(id)
|
||||
|
||||
if stats != nil && stats.Project != nil {
|
||||
r.OkJson(GetProjectStatsResponse{
|
||||
Ok: true,
|
||||
Stats: stats,
|
||||
})
|
||||
} else {
|
||||
r.Json(GetProjectStatsResponse{
|
||||
Ok: false,
|
||||
Message: "Project not found",
|
||||
}, 404)
|
||||
}
|
||||
}
|
||||
|
||||
func (api *WebAPI) ProjectGetAllStats(r *Request) {
|
||||
|
||||
stats := api.Database.GetAllProjectsStats()
|
||||
|
||||
r.OkJson(GetAllProjectsStatsResponse{
|
||||
Ok: true,
|
||||
Stats: stats,
|
||||
r.OkJson(GetAllProjectsResponse{
|
||||
Ok: true,
|
||||
Projects: projects,
|
||||
})
|
||||
}
|
||||
|
16
schema.sql
16
schema.sql
@ -1,5 +1,6 @@
|
||||
DROP TABLE IF EXISTS worker_identity, worker, project, task, log_entry,
|
||||
worker_has_access_to_project, manager, manager_has_role_on_project, project_monitoring, worker_verifies_task;
|
||||
worker_has_access_to_project, manager, manager_has_role_on_project, project_monitoring_snapshot,
|
||||
worker_verifies_task;
|
||||
DROP TYPE IF EXISTS status;
|
||||
DROP TYPE IF EXISTS log_level;
|
||||
|
||||
@ -87,12 +88,15 @@ CREATE TABLE manager_has_role_on_project
|
||||
project INTEGER REFERENCES project (id)
|
||||
);
|
||||
|
||||
CREATE TABLE project_monitoring
|
||||
CREATE TABLE project_monitoring_snapshot
|
||||
(
|
||||
project INT REFERENCES project (id),
|
||||
new_task_count INT,
|
||||
failed_task_count INT,
|
||||
closed_task_count INT
|
||||
project INT REFERENCES project (id),
|
||||
new_task_count INT,
|
||||
failed_task_count INT,
|
||||
closed_task_count INT,
|
||||
awaiting_verification_task_count INT,
|
||||
worker_access_count INT,
|
||||
timestamp INT
|
||||
);
|
||||
|
||||
CREATE OR REPLACE FUNCTION on_task_delete_proc() RETURNS TRIGGER AS
|
||||
|
44
storage/monitoring.go
Normal file
44
storage/monitoring.go
Normal file
@ -0,0 +1,44 @@
|
||||
package storage
|
||||
|
||||
type ProjectMonitoringSnapshot struct {
|
||||
NewTaskCount int64
|
||||
FailedTaskCount int64
|
||||
ClosedTaskCount int64
|
||||
WorkerAccessCount int64
|
||||
TimeStamp int64
|
||||
}
|
||||
|
||||
func (database *Database) MakeProjectSnapshots() {
|
||||
|
||||
db := database.getDB()
|
||||
|
||||
_, err := db.Exec(`
|
||||
INSERT INTO project_monitoring_snapshot
|
||||
(project, new_task_count, failed_task_count, closed_task_count, worker_access_count, timestamp)
|
||||
SELECT id,
|
||||
(SELECT COUNT(*) FROM task WHERE task.project = project.id AND status = 1),
|
||||
(SELECT COUNT(*) FROM task WHERE task.project = project.id AND status = 2),
|
||||
closed_task_count,
|
||||
(SELECT COUNT(*) FROM worker_has_access_to_project wa WHERE wa.project = project.id),
|
||||
extract(epoch from now() at time zone 'utc')
|
||||
FROM project`)
|
||||
handleErr(err)
|
||||
}
|
||||
|
||||
func (database *Database) GetMonitoringSnapshots(pid int64, from int64, to int64) (ss *[]ProjectMonitoringSnapshot) {
|
||||
|
||||
db := database.getDB()
|
||||
|
||||
rows, err := db.Query(`SELECT new_task_count, failed_task_count, closed_task_count,
|
||||
worker_access_count, timestamp FROM project_monitoring_snapshot
|
||||
WHERE project=$1 AND timestamp BETWEEN $2 AND $3`, pid, from, to)
|
||||
handleErr(err)
|
||||
|
||||
for rows.Next() {
|
||||
|
||||
s := ProjectMonitoringSnapshot{}
|
||||
err := rows.Scan(&s.NewTaskCount, &s.FailedTaskCount, &s.ClosedTaskCount, &s.WorkerAccessCount, &s.TimeStamp)
|
||||
handleErr(err)
|
||||
}
|
||||
return nil
|
||||
}
|
@ -22,14 +22,6 @@ type AssignedTasks struct {
|
||||
TaskCount int64 `json:"task_count"`
|
||||
}
|
||||
|
||||
type ProjectStats struct {
|
||||
Project *Project `json:"project"`
|
||||
NewTaskCount int64 `json:"new_task_count"`
|
||||
FailedTaskCount int64 `json:"failed_task_count"`
|
||||
ClosedTaskCount int64 `json:"closed_task_count"`
|
||||
Assignees []*AssignedTasks `json:"assignees"`
|
||||
}
|
||||
|
||||
func (database *Database) SaveProject(project *Project) (int64, error) {
|
||||
db := database.getDB()
|
||||
id, projectErr := saveProject(project, db)
|
||||
@ -139,83 +131,28 @@ func (database *Database) UpdateProject(project *Project) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (database *Database) GetProjectStats(id int64) *ProjectStats {
|
||||
|
||||
db := database.getDB()
|
||||
stats := ProjectStats{}
|
||||
|
||||
stats.Project = getProject(id, db)
|
||||
|
||||
if stats.Project != nil {
|
||||
row := db.QueryRow(`SELECT
|
||||
SUM(CASE WHEN status=1 THEN 1 ELSE 0 END) newCount,
|
||||
SUM(CASE WHEN status=2 THEN 1 ELSE 0 END) failedCount,
|
||||
SUM(CASE WHEN status=3 THEN 1 ELSE 0 END) closedCount
|
||||
FROM task WHERE project=$1 GROUP BY project`, id)
|
||||
|
||||
err := row.Scan(&stats.NewTaskCount, &stats.FailedTaskCount, &stats.ClosedTaskCount)
|
||||
if err != nil {
|
||||
logrus.WithError(err).WithFields(logrus.Fields{
|
||||
"id": id,
|
||||
}).Trace("Get project stats: No task for this project")
|
||||
|
||||
}
|
||||
|
||||
rows, err := db.Query(`SELECT worker.alias, COUNT(*) as wc FROM TASK
|
||||
LEFT JOIN worker ON TASK.assignee = worker.id WHERE project=$1
|
||||
GROUP BY worker.id ORDER BY wc LIMIT 10`, id)
|
||||
|
||||
stats.Assignees = []*AssignedTasks{}
|
||||
|
||||
for rows.Next() {
|
||||
assignee := AssignedTasks{}
|
||||
var assigneeAlias sql.NullString
|
||||
err = rows.Scan(&assigneeAlias, &assignee.TaskCount)
|
||||
handleErr(err)
|
||||
|
||||
if assigneeAlias.Valid {
|
||||
assignee.Assignee = assigneeAlias.String
|
||||
} else {
|
||||
assignee.Assignee = "unassigned"
|
||||
}
|
||||
|
||||
stats.Assignees = append(stats.Assignees, &assignee)
|
||||
}
|
||||
}
|
||||
|
||||
return &stats
|
||||
}
|
||||
|
||||
func (database Database) GetAllProjectsStats() *[]ProjectStats {
|
||||
var statsList []ProjectStats
|
||||
func (database Database) GetAllProjects() *[]Project {
|
||||
var projects []Project
|
||||
|
||||
db := database.getDB()
|
||||
rows, err := db.Query(`SELECT
|
||||
SUM(CASE WHEN status= 1 THEN 1 ELSE 0 END) newCount,
|
||||
SUM(CASE WHEN status=2 THEN 1 ELSE 0 END) failedCount,
|
||||
SUM(CASE WHEN status=3 THEN 1 ELSE 0 END) closedCount,
|
||||
p.Id, p.priority, p.name, p.clone_url, p.git_repo, p.version, p.motd,
|
||||
p.public
|
||||
FROM task RIGHT JOIN project p on task.project = p.id
|
||||
GROUP BY p.id ORDER BY p.name`)
|
||||
Id, priority, name, clone_url, git_repo, version, motd, public
|
||||
FROM project
|
||||
ORDER BY name`)
|
||||
handleErr(err)
|
||||
|
||||
for rows.Next() {
|
||||
|
||||
stats := ProjectStats{}
|
||||
p := &Project{}
|
||||
err := rows.Scan(&stats.NewTaskCount, &stats.FailedTaskCount, &stats.ClosedTaskCount,
|
||||
&p.Id, &p.Priority, &p.Name, &p.CloneUrl, &p.GitRepo, &p.Version, &p.Motd, &p.Public)
|
||||
p := Project{}
|
||||
err := rows.Scan(&p.Id, &p.Priority, &p.Name, &p.CloneUrl,
|
||||
&p.GitRepo, &p.Version, &p.Motd, &p.Public)
|
||||
handleErr(err)
|
||||
|
||||
stats.Project = p
|
||||
|
||||
statsList = append(statsList, stats)
|
||||
projects = append(projects, p)
|
||||
}
|
||||
|
||||
logrus.WithFields(logrus.Fields{
|
||||
"statsList": statsList,
|
||||
"projects": projects,
|
||||
}).Trace("Get all projects stats")
|
||||
|
||||
return &statsList
|
||||
return &projects
|
||||
}
|
||||
|
@ -122,84 +122,6 @@ func TestGetProjectNotFound(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetProjectStats(t *testing.T) {
|
||||
|
||||
r := createProject(api.CreateProjectRequest{
|
||||
Motd: "motd",
|
||||
Name: "Name",
|
||||
Version: "version",
|
||||
CloneUrl: "http://github.com/drone/test",
|
||||
GitRepo: "drone/test",
|
||||
Priority: 3,
|
||||
Public: true,
|
||||
})
|
||||
|
||||
pid := r.Id
|
||||
worker := genWid()
|
||||
|
||||
createTask(api.CreateTaskRequest{
|
||||
Priority: 1,
|
||||
Project: pid,
|
||||
MaxRetries: 0,
|
||||
Recipe: "{}",
|
||||
}, worker)
|
||||
createTask(api.CreateTaskRequest{
|
||||
Priority: 2,
|
||||
Project: pid,
|
||||
MaxRetries: 0,
|
||||
Recipe: "{}",
|
||||
}, worker)
|
||||
createTask(api.CreateTaskRequest{
|
||||
Priority: 3,
|
||||
Project: pid,
|
||||
MaxRetries: 0,
|
||||
Recipe: "{}",
|
||||
}, worker)
|
||||
|
||||
stats := getProjectStats(pid)
|
||||
|
||||
if stats.Ok != true {
|
||||
t.Error()
|
||||
}
|
||||
|
||||
if stats.Stats.Project.Id != pid {
|
||||
t.Error()
|
||||
}
|
||||
|
||||
if stats.Stats.NewTaskCount != 3 {
|
||||
t.Error()
|
||||
}
|
||||
|
||||
if stats.Stats.Assignees[0].Assignee != "unassigned" {
|
||||
t.Error()
|
||||
}
|
||||
if stats.Stats.Assignees[0].TaskCount != 3 {
|
||||
t.Error()
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetProjectStatsNotFound(t *testing.T) {
|
||||
|
||||
r := createProject(api.CreateProjectRequest{
|
||||
Motd: "eeeeeeeeej",
|
||||
Name: "Namaaaaaaaaaaaa",
|
||||
Version: "versionsssssssss",
|
||||
CloneUrl: "http://github.com/drone/test1",
|
||||
GitRepo: "drone/test1",
|
||||
Priority: 1,
|
||||
})
|
||||
s := getProjectStats(r.Id)
|
||||
|
||||
if s.Ok != true {
|
||||
t.Error()
|
||||
}
|
||||
|
||||
if s.Stats == nil {
|
||||
t.Error()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestUpdateProjectValid(t *testing.T) {
|
||||
|
||||
pid := createProject(api.CreateProjectRequest{
|
||||
@ -337,18 +259,6 @@ func getProject(id int64) (*api.GetProjectResponse, *http.Response) {
|
||||
return &getResp, r
|
||||
}
|
||||
|
||||
func getProjectStats(id int64) *api.GetProjectStatsResponse {
|
||||
|
||||
r := Get(fmt.Sprintf("/project/stats/%d", id), nil)
|
||||
|
||||
var getResp api.GetProjectStatsResponse
|
||||
data, _ := ioutil.ReadAll(r.Body)
|
||||
err := json.Unmarshal(data, &getResp)
|
||||
handleErr(err)
|
||||
|
||||
return &getResp
|
||||
}
|
||||
|
||||
func updateProject(request api.UpdateProjectRequest, pid int64) *api.UpdateProjectResponse {
|
||||
|
||||
r := Post(fmt.Sprintf("/project/update/%d", pid), request, nil)
|
||||
|
@ -1,5 +1,6 @@
|
||||
DROP TABLE IF EXISTS worker_identity, worker, project, task, log_entry,
|
||||
worker_has_access_to_project, manager, manager_has_role_on_project, project_monitoring, worker_verifies_task;
|
||||
worker_has_access_to_project, manager, manager_has_role_on_project, project_monitoring_snapshot,
|
||||
worker_verifies_task;
|
||||
DROP TYPE IF EXISTS status;
|
||||
DROP TYPE IF EXISTS log_level;
|
||||
|
||||
@ -87,12 +88,15 @@ CREATE TABLE manager_has_role_on_project
|
||||
project INTEGER REFERENCES project (id)
|
||||
);
|
||||
|
||||
CREATE TABLE project_monitoring
|
||||
CREATE TABLE project_monitoring_snapshot
|
||||
(
|
||||
project INT REFERENCES project (id),
|
||||
new_task_count INT,
|
||||
failed_task_count INT,
|
||||
closed_task_count INT
|
||||
project INT REFERENCES project (id),
|
||||
new_task_count INT,
|
||||
failed_task_count INT,
|
||||
closed_task_count INT,
|
||||
awaiting_verification_task_count INT,
|
||||
worker_access_count INT,
|
||||
timestamp INT
|
||||
);
|
||||
|
||||
CREATE OR REPLACE FUNCTION on_task_delete_proc() RETURNS TRIGGER AS
|
||||
|
328
web/angular/package-lock.json
generated
328
web/angular/package-lock.json
generated
@ -787,6 +787,22 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"@ngx-translate/core": {
|
||||
"version": "11.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@ngx-translate/core/-/core-11.0.1.tgz",
|
||||
"integrity": "sha512-nBCa1ZD9fAUY/3eskP3Lql2fNg8OMrYIej1/5GRsfcutx9tG/5fZLCv9m6UCw1aS+u4uK/vXjv1ctG/FdMvaWg==",
|
||||
"requires": {
|
||||
"tslib": "^1.9.0"
|
||||
}
|
||||
},
|
||||
"@ngx-translate/http-loader": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@ngx-translate/http-loader/-/http-loader-4.0.0.tgz",
|
||||
"integrity": "sha512-x8LumqydWD7eX9yQTAVeoCM9gFUIGVTUjZqbxdAUavAA3qVnk9wCQux7iHLPXpydl8vyQmLoPQR+fFU+DUDOMA==",
|
||||
"requires": {
|
||||
"tslib": "^1.9.0"
|
||||
}
|
||||
},
|
||||
"@schematics/angular": {
|
||||
"version": "7.2.2",
|
||||
"resolved": "https://registry.npmjs.org/@schematics/angular/-/angular-7.2.2.tgz",
|
||||
@ -2099,6 +2115,39 @@
|
||||
"integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==",
|
||||
"dev": true
|
||||
},
|
||||
"chart.js": {
|
||||
"version": "2.7.3",
|
||||
"resolved": "https://registry.npmjs.org/chart.js/-/chart.js-2.7.3.tgz",
|
||||
"integrity": "sha512-3+7k/DbR92m6BsMUYP6M0dMsMVZpMnwkUyNSAbqolHKsbIzH2Q4LWVEHHYq7v0fmEV8whXE0DrjANulw9j2K5g==",
|
||||
"requires": {
|
||||
"chartjs-color": "^2.1.0",
|
||||
"moment": "^2.10.2"
|
||||
}
|
||||
},
|
||||
"chartjs-color": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/chartjs-color/-/chartjs-color-2.2.0.tgz",
|
||||
"integrity": "sha1-hKL7dVeH7YXDndbdjHsdiEKbrq4=",
|
||||
"requires": {
|
||||
"chartjs-color-string": "^0.5.0",
|
||||
"color-convert": "^0.5.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"color-convert": {
|
||||
"version": "0.5.3",
|
||||
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-0.5.3.tgz",
|
||||
"integrity": "sha1-vbbGnOZg+t/+CwAHzER+G59ygr0="
|
||||
}
|
||||
}
|
||||
},
|
||||
"chartjs-color-string": {
|
||||
"version": "0.5.0",
|
||||
"resolved": "https://registry.npmjs.org/chartjs-color-string/-/chartjs-color-string-0.5.0.tgz",
|
||||
"integrity": "sha512-amWNvCOXlOUYxZVDSa0YOab5K/lmEhbFNKI55PWc4mlv28BDzA7zaoQTGxSBgJMHIW+hGX8YUrvw/FH4LyhwSQ==",
|
||||
"requires": {
|
||||
"color-name": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"chokidar": {
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.0.4.tgz",
|
||||
@ -2303,8 +2352,7 @@
|
||||
"color-name": {
|
||||
"version": "1.1.3",
|
||||
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
|
||||
"integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=",
|
||||
"dev": true
|
||||
"integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU="
|
||||
},
|
||||
"colors": {
|
||||
"version": "1.1.2",
|
||||
@ -2333,7 +2381,8 @@
|
||||
"commander": {
|
||||
"version": "2.17.1",
|
||||
"resolved": "https://registry.npmjs.org/commander/-/commander-2.17.1.tgz",
|
||||
"integrity": "sha512-wPMUt6FnH2yzG95SA6mzjQOEKUU3aLaDEmzs1ti+1E9h+CsrZghRlqEM/EJ4KscsQVG8uNN4uVreUeT8+drlgg=="
|
||||
"integrity": "sha512-wPMUt6FnH2yzG95SA6mzjQOEKUU3aLaDEmzs1ti+1E9h+CsrZghRlqEM/EJ4KscsQVG8uNN4uVreUeT8+drlgg==",
|
||||
"dev": true
|
||||
},
|
||||
"commondir": {
|
||||
"version": "1.0.1",
|
||||
@ -2695,270 +2744,6 @@
|
||||
"integrity": "sha1-GzN5LhHpFKL9bW7WRHRkRE5fpkA=",
|
||||
"dev": true
|
||||
},
|
||||
"d3": {
|
||||
"version": "5.7.0",
|
||||
"resolved": "https://registry.npmjs.org/d3/-/d3-5.7.0.tgz",
|
||||
"integrity": "sha512-8KEIfx+dFm8PlbJN9PI0suazrZ41QcaAufsKE9PRcqYPWLngHIyWJZX96n6IQKePGgeSu0l7rtlueSSNq8Zc3g==",
|
||||
"requires": {
|
||||
"d3-array": "1",
|
||||
"d3-axis": "1",
|
||||
"d3-brush": "1",
|
||||
"d3-chord": "1",
|
||||
"d3-collection": "1",
|
||||
"d3-color": "1",
|
||||
"d3-contour": "1",
|
||||
"d3-dispatch": "1",
|
||||
"d3-drag": "1",
|
||||
"d3-dsv": "1",
|
||||
"d3-ease": "1",
|
||||
"d3-fetch": "1",
|
||||
"d3-force": "1",
|
||||
"d3-format": "1",
|
||||
"d3-geo": "1",
|
||||
"d3-hierarchy": "1",
|
||||
"d3-interpolate": "1",
|
||||
"d3-path": "1",
|
||||
"d3-polygon": "1",
|
||||
"d3-quadtree": "1",
|
||||
"d3-random": "1",
|
||||
"d3-scale": "2",
|
||||
"d3-scale-chromatic": "1",
|
||||
"d3-selection": "1",
|
||||
"d3-shape": "1",
|
||||
"d3-time": "1",
|
||||
"d3-time-format": "2",
|
||||
"d3-timer": "1",
|
||||
"d3-transition": "1",
|
||||
"d3-voronoi": "1",
|
||||
"d3-zoom": "1"
|
||||
}
|
||||
},
|
||||
"d3-array": {
|
||||
"version": "1.2.4",
|
||||
"resolved": "https://registry.npmjs.org/d3-array/-/d3-array-1.2.4.tgz",
|
||||
"integrity": "sha512-KHW6M86R+FUPYGb3R5XiYjXPq7VzwxZ22buHhAEVG5ztoEcZZMLov530mmccaqA1GghZArjQV46fuc8kUqhhHw=="
|
||||
},
|
||||
"d3-axis": {
|
||||
"version": "1.0.12",
|
||||
"resolved": "https://registry.npmjs.org/d3-axis/-/d3-axis-1.0.12.tgz",
|
||||
"integrity": "sha512-ejINPfPSNdGFKEOAtnBtdkpr24c4d4jsei6Lg98mxf424ivoDP2956/5HDpIAtmHo85lqT4pruy+zEgvRUBqaQ=="
|
||||
},
|
||||
"d3-brush": {
|
||||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npmjs.org/d3-brush/-/d3-brush-1.0.6.tgz",
|
||||
"integrity": "sha512-lGSiF5SoSqO5/mYGD5FAeGKKS62JdA1EV7HPrU2b5rTX4qEJJtpjaGLJngjnkewQy7UnGstnFd3168wpf5z76w==",
|
||||
"requires": {
|
||||
"d3-dispatch": "1",
|
||||
"d3-drag": "1",
|
||||
"d3-interpolate": "1",
|
||||
"d3-selection": "1",
|
||||
"d3-transition": "1"
|
||||
}
|
||||
},
|
||||
"d3-chord": {
|
||||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npmjs.org/d3-chord/-/d3-chord-1.0.6.tgz",
|
||||
"integrity": "sha512-JXA2Dro1Fxw9rJe33Uv+Ckr5IrAa74TlfDEhE/jfLOaXegMQFQTAgAw9WnZL8+HxVBRXaRGCkrNU7pJeylRIuA==",
|
||||
"requires": {
|
||||
"d3-array": "1",
|
||||
"d3-path": "1"
|
||||
}
|
||||
},
|
||||
"d3-collection": {
|
||||
"version": "1.0.7",
|
||||
"resolved": "https://registry.npmjs.org/d3-collection/-/d3-collection-1.0.7.tgz",
|
||||
"integrity": "sha512-ii0/r5f4sjKNTfh84Di+DpztYwqKhEyUlKoPrzUFfeSkWxjW49xU2QzO9qrPrNkpdI0XJkfzvmTu8V2Zylln6A=="
|
||||
},
|
||||
"d3-color": {
|
||||
"version": "1.2.3",
|
||||
"resolved": "https://registry.npmjs.org/d3-color/-/d3-color-1.2.3.tgz",
|
||||
"integrity": "sha512-x37qq3ChOTLd26hnps36lexMRhNXEtVxZ4B25rL0DVdDsGQIJGB18S7y9XDwlDD6MD/ZBzITCf4JjGMM10TZkw=="
|
||||
},
|
||||
"d3-contour": {
|
||||
"version": "1.3.2",
|
||||
"resolved": "https://registry.npmjs.org/d3-contour/-/d3-contour-1.3.2.tgz",
|
||||
"integrity": "sha512-hoPp4K/rJCu0ladiH6zmJUEz6+u3lgR+GSm/QdM2BBvDraU39Vr7YdDCicJcxP1z8i9B/2dJLgDC1NcvlF8WCg==",
|
||||
"requires": {
|
||||
"d3-array": "^1.1.1"
|
||||
}
|
||||
},
|
||||
"d3-dispatch": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/d3-dispatch/-/d3-dispatch-1.0.5.tgz",
|
||||
"integrity": "sha512-vwKx+lAqB1UuCeklr6Jh1bvC4SZgbSqbkGBLClItFBIYH4vqDJCA7qfoy14lXmJdnBOdxndAMxjCbImJYW7e6g=="
|
||||
},
|
||||
"d3-drag": {
|
||||
"version": "1.2.3",
|
||||
"resolved": "https://registry.npmjs.org/d3-drag/-/d3-drag-1.2.3.tgz",
|
||||
"integrity": "sha512-8S3HWCAg+ilzjJsNtWW1Mutl74Nmzhb9yU6igspilaJzeZVFktmY6oO9xOh5TDk+BM2KrNFjttZNoJJmDnkjkg==",
|
||||
"requires": {
|
||||
"d3-dispatch": "1",
|
||||
"d3-selection": "1"
|
||||
}
|
||||
},
|
||||
"d3-dsv": {
|
||||
"version": "1.0.10",
|
||||
"resolved": "https://registry.npmjs.org/d3-dsv/-/d3-dsv-1.0.10.tgz",
|
||||
"integrity": "sha512-vqklfpxmtO2ZER3fq/B33R/BIz3A1PV0FaZRuFM8w6jLo7sUX1BZDh73fPlr0s327rzq4H6EN1q9U+eCBCSN8g==",
|
||||
"requires": {
|
||||
"commander": "2",
|
||||
"iconv-lite": "0.4",
|
||||
"rw": "1"
|
||||
}
|
||||
},
|
||||
"d3-ease": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-1.0.5.tgz",
|
||||
"integrity": "sha512-Ct1O//ly5y5lFM9YTdu+ygq7LleSgSE4oj7vUt9tPLHUi8VCV7QoizGpdWRWAwCO9LdYzIrQDg97+hGVdsSGPQ=="
|
||||
},
|
||||
"d3-fetch": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/d3-fetch/-/d3-fetch-1.1.2.tgz",
|
||||
"integrity": "sha512-S2loaQCV/ZeyTyIF2oP8D1K9Z4QizUzW7cWeAOAS4U88qOt3Ucf6GsmgthuYSdyB2HyEm4CeGvkQxWsmInsIVA==",
|
||||
"requires": {
|
||||
"d3-dsv": "1"
|
||||
}
|
||||
},
|
||||
"d3-force": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/d3-force/-/d3-force-1.1.2.tgz",
|
||||
"integrity": "sha512-p1vcHAUF1qH7yR+e8ip7Bs61AHjLeKkIn8Z2gzwU2lwEf2wkSpWdjXG0axudTHsVFnYGlMkFaEsVy2l8tAg1Gw==",
|
||||
"requires": {
|
||||
"d3-collection": "1",
|
||||
"d3-dispatch": "1",
|
||||
"d3-quadtree": "1",
|
||||
"d3-timer": "1"
|
||||
}
|
||||
},
|
||||
"d3-format": {
|
||||
"version": "1.3.2",
|
||||
"resolved": "https://registry.npmjs.org/d3-format/-/d3-format-1.3.2.tgz",
|
||||
"integrity": "sha512-Z18Dprj96ExragQ0DeGi+SYPQ7pPfRMtUXtsg/ChVIKNBCzjO8XYJvRTC1usblx52lqge56V5ect+frYTQc8WQ=="
|
||||
},
|
||||
"d3-geo": {
|
||||
"version": "1.11.3",
|
||||
"resolved": "https://registry.npmjs.org/d3-geo/-/d3-geo-1.11.3.tgz",
|
||||
"integrity": "sha512-n30yN9qSKREvV2fxcrhmHUdXP9TNH7ZZj3C/qnaoU0cVf/Ea85+yT7HY7i8ySPwkwjCNYtmKqQFTvLFngfkItQ==",
|
||||
"requires": {
|
||||
"d3-array": "1"
|
||||
}
|
||||
},
|
||||
"d3-hierarchy": {
|
||||
"version": "1.1.8",
|
||||
"resolved": "https://registry.npmjs.org/d3-hierarchy/-/d3-hierarchy-1.1.8.tgz",
|
||||
"integrity": "sha512-L+GHMSZNwTpiq4rt9GEsNcpLa4M96lXMR8M/nMG9p5hBE0jy6C+3hWtyZMenPQdwla249iJy7Nx0uKt3n+u9+w=="
|
||||
},
|
||||
"d3-interpolate": {
|
||||
"version": "1.3.2",
|
||||
"resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-1.3.2.tgz",
|
||||
"integrity": "sha512-NlNKGopqaz9qM1PXh9gBF1KSCVh+jSFErrSlD/4hybwoNX/gt1d8CDbDW+3i+5UOHhjC6s6nMvRxcuoMVNgL2w==",
|
||||
"requires": {
|
||||
"d3-color": "1"
|
||||
}
|
||||
},
|
||||
"d3-path": {
|
||||
"version": "1.0.7",
|
||||
"resolved": "https://registry.npmjs.org/d3-path/-/d3-path-1.0.7.tgz",
|
||||
"integrity": "sha512-q0cW1RpvA5c5ma2rch62mX8AYaiLX0+bdaSM2wxSU9tXjU4DNvkx9qiUvjkuWCj3p22UO/hlPivujqMiR9PDzA=="
|
||||
},
|
||||
"d3-polygon": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/d3-polygon/-/d3-polygon-1.0.5.tgz",
|
||||
"integrity": "sha512-RHhh1ZUJZfhgoqzWWuRhzQJvO7LavchhitSTHGu9oj6uuLFzYZVeBzaWTQ2qSO6bz2w55RMoOCf0MsLCDB6e0w=="
|
||||
},
|
||||
"d3-quadtree": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/d3-quadtree/-/d3-quadtree-1.0.5.tgz",
|
||||
"integrity": "sha512-U2tjwDFbZ75JRAg8A+cqMvqPg1G3BE7UTJn3h8DHjY/pnsAfWdbJKgyfcy7zKjqGtLAmI0q8aDSeG1TVIKRaHQ=="
|
||||
},
|
||||
"d3-random": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/d3-random/-/d3-random-1.1.2.tgz",
|
||||
"integrity": "sha512-6AK5BNpIFqP+cx/sreKzNjWbwZQCSUatxq+pPRmFIQaWuoD+NrbVWw7YWpHiXpCQ/NanKdtGDuB+VQcZDaEmYQ=="
|
||||
},
|
||||
"d3-scale": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-2.1.2.tgz",
|
||||
"integrity": "sha512-bESpd64ylaKzCDzvULcmHKZTlzA/6DGSVwx7QSDj/EnX9cpSevsdiwdHFYI9ouo9tNBbV3v5xztHS2uFeOzh8Q==",
|
||||
"requires": {
|
||||
"d3-array": "^1.2.0",
|
||||
"d3-collection": "1",
|
||||
"d3-format": "1",
|
||||
"d3-interpolate": "1",
|
||||
"d3-time": "1",
|
||||
"d3-time-format": "2"
|
||||
}
|
||||
},
|
||||
"d3-scale-chromatic": {
|
||||
"version": "1.3.3",
|
||||
"resolved": "https://registry.npmjs.org/d3-scale-chromatic/-/d3-scale-chromatic-1.3.3.tgz",
|
||||
"integrity": "sha512-BWTipif1CimXcYfT02LKjAyItX5gKiwxuPRgr4xM58JwlLocWbjPLI7aMEjkcoOQXMkYsmNsvv3d2yl/OKuHHw==",
|
||||
"requires": {
|
||||
"d3-color": "1",
|
||||
"d3-interpolate": "1"
|
||||
}
|
||||
},
|
||||
"d3-selection": {
|
||||
"version": "1.3.2",
|
||||
"resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-1.3.2.tgz",
|
||||
"integrity": "sha512-OoXdv1nZ7h2aKMVg3kaUFbLLK5jXUFAMLD/Tu5JA96mjf8f2a9ZUESGY+C36t8R1WFeWk/e55hy54Ml2I62CRQ=="
|
||||
},
|
||||
"d3-shape": {
|
||||
"version": "1.2.2",
|
||||
"resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-1.2.2.tgz",
|
||||
"integrity": "sha512-hUGEozlKecFZ2bOSNt7ENex+4Tk9uc/m0TtTEHBvitCBxUNjhzm5hS2GrrVRD/ae4IylSmxGeqX5tWC2rASMlQ==",
|
||||
"requires": {
|
||||
"d3-path": "1"
|
||||
}
|
||||
},
|
||||
"d3-time": {
|
||||
"version": "1.0.10",
|
||||
"resolved": "https://registry.npmjs.org/d3-time/-/d3-time-1.0.10.tgz",
|
||||
"integrity": "sha512-hF+NTLCaJHF/JqHN5hE8HVGAXPStEq6/omumPE/SxyHVrR7/qQxusFDo0t0c/44+sCGHthC7yNGFZIEgju0P8g=="
|
||||
},
|
||||
"d3-time-format": {
|
||||
"version": "2.1.3",
|
||||
"resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-2.1.3.tgz",
|
||||
"integrity": "sha512-6k0a2rZryzGm5Ihx+aFMuO1GgelgIz+7HhB4PH4OEndD5q2zGn1mDfRdNrulspOfR6JXkb2sThhDK41CSK85QA==",
|
||||
"requires": {
|
||||
"d3-time": "1"
|
||||
}
|
||||
},
|
||||
"d3-timer": {
|
||||
"version": "1.0.9",
|
||||
"resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-1.0.9.tgz",
|
||||
"integrity": "sha512-rT34J5HnQUHhcLvhSB9GjCkN0Ddd5Y8nCwDBG2u6wQEeYxT/Lf51fTFFkldeib/sE/J0clIe0pnCfs6g/lRbyg=="
|
||||
},
|
||||
"d3-transition": {
|
||||
"version": "1.1.3",
|
||||
"resolved": "https://registry.npmjs.org/d3-transition/-/d3-transition-1.1.3.tgz",
|
||||
"integrity": "sha512-tEvo3qOXL6pZ1EzcXxFcPNxC/Ygivu5NoBY6mbzidATAeML86da+JfVIUzon3dNM6UX6zjDx+xbYDmMVtTSjuA==",
|
||||
"requires": {
|
||||
"d3-color": "1",
|
||||
"d3-dispatch": "1",
|
||||
"d3-ease": "1",
|
||||
"d3-interpolate": "1",
|
||||
"d3-selection": "^1.1.0",
|
||||
"d3-timer": "1"
|
||||
}
|
||||
},
|
||||
"d3-voronoi": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/d3-voronoi/-/d3-voronoi-1.1.4.tgz",
|
||||
"integrity": "sha512-dArJ32hchFsrQ8uMiTBLq256MpnZjeuBtdHpaDlYuQyjU0CVzCJl/BVW+SkszaAeH95D/8gxqAhgx0ouAWAfRg=="
|
||||
},
|
||||
"d3-zoom": {
|
||||
"version": "1.7.3",
|
||||
"resolved": "https://registry.npmjs.org/d3-zoom/-/d3-zoom-1.7.3.tgz",
|
||||
"integrity": "sha512-xEBSwFx5Z9T3/VrwDkMt+mr0HCzv7XjpGURJ8lWmIC8wxe32L39eWHIasEe/e7Ox8MPU4p1hvH8PKN2olLzIBg==",
|
||||
"requires": {
|
||||
"d3-dispatch": "1",
|
||||
"d3-drag": "1",
|
||||
"d3-interpolate": "1",
|
||||
"d3-selection": "1",
|
||||
"d3-transition": "1"
|
||||
}
|
||||
},
|
||||
"dashdash": {
|
||||
"version": "1.14.1",
|
||||
"resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz",
|
||||
@ -5235,6 +5020,7 @@
|
||||
"version": "0.4.23",
|
||||
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.23.tgz",
|
||||
"integrity": "sha512-neyTUVFtahjf0mB3dZT77u+8O0QB89jFdnBkd5P1JgYPbPaia3gXXOVL2fq8VyU2gMMD7SaN7QukTB/pmXYvDA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"safer-buffer": ">= 2.1.2 < 3"
|
||||
}
|
||||
@ -8817,11 +8603,6 @@
|
||||
"aproba": "^1.1.1"
|
||||
}
|
||||
},
|
||||
"rw": {
|
||||
"version": "1.3.3",
|
||||
"resolved": "https://registry.npmjs.org/rw/-/rw-1.3.3.tgz",
|
||||
"integrity": "sha1-P4Yt+pGrdmsUiF700BEkv9oHT7Q="
|
||||
},
|
||||
"rxjs": {
|
||||
"version": "6.3.3",
|
||||
"resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.3.3.tgz",
|
||||
@ -8848,7 +8629,8 @@
|
||||
"safer-buffer": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
|
||||
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
|
||||
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
|
||||
"dev": true
|
||||
},
|
||||
"sass-graph": {
|
||||
"version": "2.2.4",
|
||||
|
@ -21,8 +21,10 @@
|
||||
"@angular/platform-browser": "~7.2.0",
|
||||
"@angular/platform-browser-dynamic": "~7.2.0",
|
||||
"@angular/router": "~7.2.0",
|
||||
"@ngx-translate/core": "^11.0.1",
|
||||
"@ngx-translate/http-loader": "^4.0.0",
|
||||
"chart.js": "^2.7.3",
|
||||
"core-js": "^2.5.4",
|
||||
"d3": "^5.7.0",
|
||||
"lodash": "^4.17.11",
|
||||
"moment": "^2.23.0",
|
||||
"rxjs": "~6.3.3",
|
||||
@ -34,9 +36,9 @@
|
||||
"@angular/cli": "~7.2.2",
|
||||
"@angular/compiler-cli": "~7.2.0",
|
||||
"@angular/language-service": "~7.2.0",
|
||||
"@types/node": "~8.9.4",
|
||||
"@types/jasmine": "~2.8.8",
|
||||
"@types/jasminewd2": "~2.0.3",
|
||||
"@types/node": "~8.9.4",
|
||||
"codelyzer": "~4.5.0",
|
||||
"jasmine-core": "~2.99.1",
|
||||
"jasmine-spec-reporter": "~4.2.1",
|
||||
|
@ -16,12 +16,8 @@ export class ApiService {
|
||||
return this.http.post(this.url + "/logs", "{\"level\":\"info\", \"since\":10000}");
|
||||
}
|
||||
|
||||
getProjectStats(id: number) {
|
||||
return this.http.get(this.url + "/project/stats/" + id)
|
||||
}
|
||||
|
||||
getProjects() {
|
||||
return this.http.get(this.url + "/project/stats")
|
||||
return this.http.get(this.url + "/project/list")
|
||||
}
|
||||
|
||||
getProject(id: number) {
|
||||
|
@ -0,0 +1,3 @@
|
||||
.nav-spacer {
|
||||
flex: 1 1 auto;
|
||||
}
|
@ -1,11 +1,23 @@
|
||||
<!--<mat-toolbar>-->
|
||||
<mat-toolbar color="primary">
|
||||
<span>{{ "nav.title" | translate }}</span>
|
||||
<span class="nav-spacer"></span>
|
||||
<mat-icon class="example-icon">favorite</mat-icon>
|
||||
<mat-icon class="example-icon">delete</mat-icon>
|
||||
<mat-form-field [floatLabel]="'never'">
|
||||
<mat-select [placeholder]="'nav.langSelect' | translate" (selectionChange)="langChange($event)">
|
||||
<mat-option *ngFor="let lang of langList" [value]="lang.lang">
|
||||
{{lang.display}}
|
||||
</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
|
||||
</mat-toolbar>
|
||||
<ul>
|
||||
<li><a [routerLink]="''">Index</a></li>
|
||||
<li><a [routerLink]="'log'">Logs</a></li>
|
||||
<li><a [routerLink]="'projects'">list</a></li>
|
||||
<li><a [routerLink]="'new_project'">new project</a></li>
|
||||
</ul>
|
||||
<!--</mat-toolbar>-->
|
||||
|
||||
<messenger-snack-bar></messenger-snack-bar>
|
||||
|
||||
|
@ -1,4 +1,6 @@
|
||||
import {Component} from '@angular/core';
|
||||
import {TranslateService} from "@ngx-translate/core";
|
||||
import {MatSelectChange} from "@angular/material";
|
||||
|
||||
@Component({
|
||||
selector: 'app-root',
|
||||
@ -6,5 +8,24 @@ import {Component} from '@angular/core';
|
||||
styleUrls: ['./app.component.css']
|
||||
})
|
||||
export class AppComponent {
|
||||
title = 'angular';
|
||||
|
||||
langChange(event: MatSelectChange) {
|
||||
this.translate.use(event.value)
|
||||
}
|
||||
|
||||
langList: any[] = [
|
||||
{lang: "fr", display: "Français"},
|
||||
{lang: "en", display: "English"},
|
||||
];
|
||||
|
||||
constructor(private translate: TranslateService) {
|
||||
|
||||
translate.addLangs([
|
||||
"en",
|
||||
"fr"
|
||||
]);
|
||||
|
||||
translate.setDefaultLang("en");
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -18,6 +18,7 @@ import {
|
||||
MatInputModule,
|
||||
MatMenuModule,
|
||||
MatPaginatorModule,
|
||||
MatSelectModule,
|
||||
MatSliderModule,
|
||||
MatSlideToggleModule,
|
||||
MatSnackBarModule,
|
||||
@ -28,13 +29,21 @@ import {
|
||||
} from "@angular/material";
|
||||
import {ApiService} from "./api.service";
|
||||
import {MessengerService} from "./messenger.service";
|
||||
import {HttpClientModule} from "@angular/common/http";
|
||||
import {HttpClient, HttpClientModule} from "@angular/common/http";
|
||||
import {ProjectDashboardComponent} from './project-dashboard/project-dashboard.component';
|
||||
import {ProjectListComponent} from './project-list/project-list.component';
|
||||
import {CreateProjectComponent} from './create-project/create-project.component';
|
||||
import {FormsModule, ReactiveFormsModule} from "@angular/forms";
|
||||
import {UpdateProjectComponent} from './update-project/update-project.component';
|
||||
import {SnackBarComponent} from "./messenger/snack-bar.component";
|
||||
import {TranslateLoader, TranslateModule} from "@ngx-translate/core";
|
||||
import {TranslateHttpLoader} from "@ngx-translate/http-loader";
|
||||
|
||||
|
||||
export function createTranslateLoader(http: HttpClient) {
|
||||
return new TranslateHttpLoader(http, './assets/i18n/', '.json');
|
||||
}
|
||||
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
@ -71,6 +80,15 @@ import {SnackBarComponent} from "./messenger/snack-bar.component";
|
||||
MatCheckboxModule,
|
||||
MatDividerModule,
|
||||
MatSnackBarModule,
|
||||
TranslateModule.forRoot({
|
||||
loader: {
|
||||
provide: TranslateLoader,
|
||||
useFactory: (createTranslateLoader),
|
||||
deps: [HttpClient]
|
||||
}
|
||||
}
|
||||
),
|
||||
MatSelectModule
|
||||
|
||||
],
|
||||
exports: [],
|
||||
|
@ -12,7 +12,7 @@ import {Router} from "@angular/router";
|
||||
})
|
||||
export class CreateProjectComponent implements OnInit {
|
||||
|
||||
private project = new Project();
|
||||
project = new Project();
|
||||
|
||||
constructor(private apiService: ApiService,
|
||||
private messengerService: MessengerService,
|
||||
|
@ -12,9 +12,9 @@ import {MatPaginator, MatSort, MatTableDataSource} from "@angular/material";
|
||||
})
|
||||
export class LogsComponent implements OnInit {
|
||||
|
||||
private logs: LogEntry[] = [];
|
||||
private data: MatTableDataSource<LogEntry>;
|
||||
private logsCols: string[] = ["level", "timestamp", "message", "data"];
|
||||
logs: LogEntry[] = [];
|
||||
data: MatTableDataSource<LogEntry>;
|
||||
logsCols: string[] = ["level", "timestamp", "message", "data"];
|
||||
|
||||
@ViewChild(MatPaginator) paginator: MatPaginator;
|
||||
@ViewChild(MatSort) sort: MatSort;
|
||||
|
@ -1,10 +1,5 @@
|
||||
import {Component, OnInit} from '@angular/core';
|
||||
|
||||
import * as d3 from "d3"
|
||||
import * as _ from "lodash"
|
||||
import {interval} from "rxjs";
|
||||
import {ApiService} from "../api.service";
|
||||
import {ActivatedRoute} from "@angular/router";
|
||||
|
||||
@Component({
|
||||
selector: 'app-project-dashboard',
|
||||
@ -16,269 +11,6 @@ export class ProjectDashboardComponent implements OnInit {
|
||||
private projectId;
|
||||
projectStats;
|
||||
|
||||
private pieWidth = 360;
|
||||
private pieHeight = 360;
|
||||
private pieRadius = Math.min(this.pieWidth, this.pieHeight) / 2;
|
||||
private pieArc = d3.arc()
|
||||
.innerRadius(this.pieRadius / 2)
|
||||
.outerRadius(this.pieRadius);
|
||||
|
||||
private pieFun = d3.pie().value((d) => d.count);
|
||||
private statusColor = d3.scaleOrdinal().range(['#31a6a2', '#8c2627', '#62f24b']);
|
||||
private assigneesColor = d3.scaleOrdinal().range(["", "#AAAAAA"].concat(d3.schemePaired));
|
||||
|
||||
private statusData: any[];
|
||||
private assigneesData: any[];
|
||||
|
||||
private newTaskCounts: any[] = [];
|
||||
private failedTaskCounts: any[] = [];
|
||||
private closedTaskCounts: any[] = [];
|
||||
|
||||
private newTaskPath: any;
|
||||
private failedTaskPath: any;
|
||||
private closedTaskPath: any;
|
||||
|
||||
private maxY: number = 10;
|
||||
private yAxis: any;
|
||||
private yScale: any;
|
||||
private xScale: any;
|
||||
private range: number;
|
||||
private line: any;
|
||||
|
||||
private statusPath: any;
|
||||
private statusSvg: any;
|
||||
private assigneesPath: any;
|
||||
private assigneesSvg: any;
|
||||
|
||||
constructor(private apiService: ApiService, private route: ActivatedRoute) {
|
||||
}
|
||||
|
||||
setupStatusPieChart() {
|
||||
let tooltip = d3.select("#stooltip");
|
||||
|
||||
this.statusSvg = d3.select("#status")
|
||||
.append('svg')
|
||||
.attr('width', this.pieWidth)
|
||||
.attr('height', this.pieHeight)
|
||||
.append("g")
|
||||
.attr("transform", "translate(" + this.pieRadius + "," + this.pieRadius + ")");
|
||||
|
||||
this.statusPath = this.statusSvg.selectAll()
|
||||
.data(this.pieFun(this.statusData))
|
||||
.enter()
|
||||
.append('path')
|
||||
.attr('d', this.pieArc)
|
||||
.attr('fill', (d) => this.statusColor(d.data.label));
|
||||
|
||||
this.setupToolTip(this.statusPath, tooltip)
|
||||
}
|
||||
|
||||
setupAssigneesPieChart() {
|
||||
let tooltip = d3.select("#atooltip");
|
||||
|
||||
this.assigneesSvg = d3.select("#assignees")
|
||||
.append('svg')
|
||||
.attr('width', this.pieWidth)
|
||||
.attr('height', this.pieHeight)
|
||||
.append("g")
|
||||
.attr("transform", "translate(" + this.pieRadius + "," + this.pieRadius + ")");
|
||||
|
||||
this.assigneesPath = this.assigneesSvg.selectAll()
|
||||
.data(this.pieFun(this.assigneesData))
|
||||
.enter()
|
||||
.append('path')
|
||||
.attr('d', this.pieArc)
|
||||
.attr('fill', (d) => this.assigneesColor(d.data.label));
|
||||
|
||||
this.setupToolTip(this.assigneesPath, tooltip)
|
||||
}
|
||||
|
||||
setupToolTip(x, tooltip) {
|
||||
x.on('mouseover', (d) => {
|
||||
let total = d3.sum(this.assigneesData.map((d) => d.count));
|
||||
let percent = Math.round(1000 * d.data.count / total) / 10;
|
||||
tooltip.select('.label').html(d.data.label);
|
||||
tooltip.select('.count').html(d.data.count);
|
||||
tooltip.select('.percent').html(percent + '%');
|
||||
tooltip.style('display', 'block');
|
||||
});
|
||||
x.on('mouseout', function () {
|
||||
tooltip.style('display', 'none');
|
||||
})
|
||||
}
|
||||
|
||||
setupLine() {
|
||||
let margin = {top: 50, right: 50, bottom: 50, left: 50};
|
||||
this.range = 600;
|
||||
let width = 750;
|
||||
let height = 250;
|
||||
|
||||
this.xScale = d3.scaleLinear()
|
||||
.domain([this.range, 0])
|
||||
.range([width, 0]);
|
||||
|
||||
this.yScale = d3.scaleLinear()
|
||||
.domain([0, this.maxY])
|
||||
.range([height, 0]);
|
||||
|
||||
this.line = d3.line()
|
||||
.x((d, i) => this.xScale(i))
|
||||
.y((d) => this.yScale(d.y))
|
||||
.curve(d3.curveMonotoneX);
|
||||
|
||||
let svg = d3.select("#line").append("svg")
|
||||
.attr("width", width + margin.left + margin.right)
|
||||
.attr("height", height + margin.top + margin.bottom)
|
||||
.append("g")
|
||||
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
|
||||
|
||||
svg.append("defs").append("clipPath")
|
||||
.attr("id", "clip")
|
||||
.append("rect")
|
||||
.attr("width", width)
|
||||
.attr("height", height);
|
||||
|
||||
this.newTaskPath = svg
|
||||
.append("path")
|
||||
.attr("clip-path", "url(#clip)")
|
||||
.datum(this.newTaskCounts)
|
||||
.attr("class", "line-new")
|
||||
.attr("d", this.line);
|
||||
this.failedTaskPath = svg
|
||||
.append("path")
|
||||
.attr("clip-path", "url(#clip)")
|
||||
.datum(this.failedTaskCounts)
|
||||
.attr("class", "line-failed")
|
||||
.attr("d", this.line);
|
||||
this.closedTaskPath = svg
|
||||
.append("path")
|
||||
.attr("clip-path", "url(#clip)")
|
||||
.datum(this.closedTaskCounts)
|
||||
.attr("class", "line-closed")
|
||||
.attr("d", this.line);
|
||||
|
||||
let xAxis = svg.append("g")
|
||||
.attr("class", "x-axis")
|
||||
.attr("transform", "translate(0," + height + ")");
|
||||
xAxis.call(d3.axisBottom(this.xScale).tickFormat((d) => (d - 600) + "s"));
|
||||
|
||||
this.yAxis = svg.append("g")
|
||||
.attr("class", "y-axis");
|
||||
this.yAxis.call(d3.axisLeft(this.yScale));
|
||||
}
|
||||
|
||||
getStats() {
|
||||
this.apiService.getProjectStats(this.projectId).subscribe((data) => {
|
||||
|
||||
this.projectStats = data["stats"];
|
||||
|
||||
this.updateLine();
|
||||
this.updatePie();
|
||||
});
|
||||
}
|
||||
|
||||
private updateLine() {
|
||||
|
||||
let newVal = {"y": this.projectStats["new_task_count"]};
|
||||
let failedVal = {"y": this.projectStats["failed_task_count"]};
|
||||
let closedVal = {"y": this.projectStats["closed_task_count"]};
|
||||
|
||||
//Adjust y axis
|
||||
this.maxY = Math.max(newVal["y"], this.maxY);
|
||||
this.yScale.domain([0, this.maxY]);
|
||||
this.yAxis.call(d3.axisLeft(this.yScale));
|
||||
|
||||
this.newTaskPath
|
||||
.attr("d", this.line)
|
||||
.attr("transform", null);
|
||||
this.failedTaskPath
|
||||
.attr("d", this.line)
|
||||
.attr("transform", null);
|
||||
this.closedTaskPath
|
||||
.attr("d", this.line)
|
||||
.attr("transform", null);
|
||||
|
||||
//remove fist element
|
||||
if (this.newTaskCounts.length >= this.range) {
|
||||
this.newTaskCounts.shift();
|
||||
this.newTaskPath
|
||||
.transition()
|
||||
.attr("transform", "translate(" + this.xScale(-1) + ")");
|
||||
}
|
||||
if (this.failedTaskCounts.length >= this.range) {
|
||||
this.failedTaskCounts.shift();
|
||||
this.failedTaskPath
|
||||
.transition()
|
||||
.attr("transform", "translate(" + this.xScale(-1) + ")");
|
||||
}
|
||||
if (this.closedTaskCounts.length >= this.range) {
|
||||
this.closedTaskCounts.shift();
|
||||
this.closedTaskPath
|
||||
.transition()
|
||||
.attr("transform", "translate(" + this.xScale(-1) + ")");
|
||||
}
|
||||
|
||||
this.newTaskCounts.push(newVal);
|
||||
this.failedTaskCounts.push(failedVal);
|
||||
this.closedTaskCounts.push(closedVal);
|
||||
}
|
||||
|
||||
private updatePie() {
|
||||
this.statusData = [
|
||||
{label: "New", count: this.projectStats["new_task_count"]},
|
||||
{label: "Failed", count: this.projectStats["failed_task_count"]},
|
||||
{label: "Closed", count: this.projectStats["closed_task_count"]},
|
||||
];
|
||||
this.assigneesData = _.map(this.projectStats["assignees"], assignedTask => {
|
||||
return {
|
||||
label: assignedTask["assignee"],
|
||||
count: assignedTask["task_count"],
|
||||
}
|
||||
});
|
||||
|
||||
this.statusSvg.selectAll("path")
|
||||
.data(this.pieFun(this.statusData));
|
||||
this.statusPath
|
||||
.attr('d', this.pieArc)
|
||||
.attr('fill', (d) => this.statusColor(d.data.label));
|
||||
this.assigneesSvg.selectAll("path")
|
||||
.data(this.pieFun(this.assigneesData));
|
||||
this.assigneesPath
|
||||
.attr('d', this.pieArc)
|
||||
.attr('fill', (d) => this.assigneesColor(d.data.label));
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.statusData = [
|
||||
{label: 'new', count: 0},
|
||||
{label: 'failed', count: 0},
|
||||
{label: 'closed', count: 0},
|
||||
];
|
||||
this.assigneesData = [
|
||||
{label: 'null', count: 0},
|
||||
{label: 'null', count: 0},
|
||||
{label: 'null', count: 0},
|
||||
{label: 'null', count: 0},
|
||||
{label: 'null', count: 0},
|
||||
{label: 'null', count: 0},
|
||||
{label: 'null', count: 0},
|
||||
{label: 'null', count: 0},
|
||||
{label: 'null', count: 0},
|
||||
{label: 'null', count: 0},
|
||||
{label: 'null', count: 0},
|
||||
];
|
||||
|
||||
this.setupStatusPieChart();
|
||||
this.setupAssigneesPieChart();
|
||||
this.setupLine();
|
||||
|
||||
this.route.params.subscribe(params => {
|
||||
this.projectId = params["id"];
|
||||
this.getStats();
|
||||
interval(1000).subscribe(() => {
|
||||
// this.getStats()
|
||||
})
|
||||
}
|
||||
)
|
||||
ngOnInit(): void {
|
||||
}
|
||||
}
|
||||
|
@ -4,15 +4,15 @@
|
||||
</mat-card-header>
|
||||
<mat-card-content>
|
||||
<mat-accordion>
|
||||
<mat-expansion-panel *ngFor="let stats of projects">
|
||||
<mat-expansion-panel *ngFor="let project of projects">
|
||||
<mat-expansion-panel-header>
|
||||
<mat-panel-title>{{stats.project.id}}: {{stats.project.name}}</mat-panel-title>
|
||||
<mat-panel-description>{{stats.project.motd}}</mat-panel-description>
|
||||
<mat-panel-title>{{project.id}}: {{project.name}}</mat-panel-title>
|
||||
<mat-panel-description>{{project.motd}}</mat-panel-description>
|
||||
</mat-expansion-panel-header>
|
||||
<pre>{{stats.project | json}}</pre>
|
||||
<pre>{{project | json}}</pre>
|
||||
<div style="display: flex;">
|
||||
<a [routerLink]="'/project/' + stats.project.id">Dashboard</a>
|
||||
<a [routerLink]="'/project/' + stats.project.id + '/update'">Update</a>
|
||||
<a [routerLink]="'/project/' + project.id">Dashboard</a>
|
||||
<a [routerLink]="'/project/' + project.id + '/update'">Update</a>
|
||||
</div>
|
||||
</mat-expansion-panel>
|
||||
</mat-accordion>
|
||||
|
@ -1,5 +1,6 @@
|
||||
import {Component, OnInit} from '@angular/core';
|
||||
import {ApiService} from "../api.service";
|
||||
import {Project} from "../models/project";
|
||||
|
||||
@Component({
|
||||
selector: 'app-project-list',
|
||||
@ -11,14 +12,14 @@ export class ProjectListComponent implements OnInit {
|
||||
constructor(private apiService: ApiService) {
|
||||
}
|
||||
|
||||
projects: any[];
|
||||
projects: Project[];
|
||||
|
||||
ngOnInit() {
|
||||
this.getProjects()
|
||||
}
|
||||
|
||||
getProjects() {
|
||||
this.apiService.getProjects().subscribe(data => this.projects = data["stats"]);
|
||||
this.apiService.getProjects().subscribe(data => this.projects = data["projects"]);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -17,7 +17,7 @@ export class UpdateProjectComponent implements OnInit {
|
||||
private router: Router) {
|
||||
}
|
||||
|
||||
private project: Project;
|
||||
project: Project;
|
||||
private projectId: number;
|
||||
|
||||
ngOnInit() {
|
||||
|
6
web/angular/src/assets/i18n/en.json
Normal file
6
web/angular/src/assets/i18n/en.json
Normal file
@ -0,0 +1,6 @@
|
||||
{
|
||||
"nav": {
|
||||
"title": "task_tracker",
|
||||
"langSelect": "Language"
|
||||
}
|
||||
}
|
6
web/angular/src/assets/i18n/fr.json
Normal file
6
web/angular/src/assets/i18n/fr.json
Normal file
@ -0,0 +1,6 @@
|
||||
{
|
||||
"nav": {
|
||||
"title": "task_tracker (fr)",
|
||||
"langSelect": "Langue"
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user