Added i18n, started working on monitoring

This commit is contained in:
simon987
2019-02-05 20:11:52 -05:00
parent 22f4a6b358
commit 87f4d08984
22 changed files with 230 additions and 785 deletions

View File

@@ -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) {

View File

@@ -0,0 +1,3 @@
.nav-spacer {
flex: 1 1 auto;
}

View File

@@ -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>

View File

@@ -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");
}
}

View File

@@ -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: [],

View File

@@ -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,

View File

@@ -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;

View File

@@ -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 {
}
}

View File

@@ -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>

View File

@@ -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"]);
}
}

View File

@@ -17,7 +17,7 @@ export class UpdateProjectComponent implements OnInit {
private router: Router) {
}
private project: Project;
project: Project;
private projectId: number;
ngOnInit() {

View File

@@ -0,0 +1,6 @@
{
"nav": {
"title": "task_tracker",
"langSelect": "Language"
}
}

View File

@@ -0,0 +1,6 @@
{
"nav": {
"title": "task_tracker (fr)",
"langSelect": "Langue"
}
}