mirror of
https://github.com/simon987/task_tracker.git
synced 2025-12-10 13:44:30 +00:00
monitoring setup, project dashboard, account page
This commit is contained in:
@@ -1,3 +1,29 @@
|
||||
<pre>
|
||||
{{authService.account | json}}
|
||||
</pre>
|
||||
<div class="container">
|
||||
<mat-card class="mat-elevation-z8" *ngIf="account">
|
||||
|
||||
<mat-card-header>
|
||||
<mat-card-title>{{"account.title" | translate}}</mat-card-title>
|
||||
<mat-card-subtitle>{{"account.subtitle" | translate}}</mat-card-subtitle>
|
||||
</mat-card-header>
|
||||
|
||||
<mat-card-content>
|
||||
|
||||
<mat-list>
|
||||
<mat-list-item>
|
||||
{{"account.username" | translate}}:
|
||||
<pre>{{account.username}}</pre>
|
||||
</mat-list-item>
|
||||
</mat-list>
|
||||
|
||||
<mat-expansion-panel>
|
||||
<mat-expansion-panel-header>{{"account.metadata" | translate}}</mat-expansion-panel-header>
|
||||
<pre> {{account | json}}</pre>
|
||||
</mat-expansion-panel>
|
||||
</mat-card-content>
|
||||
|
||||
<mat-card-actions>
|
||||
|
||||
|
||||
</mat-card-actions>
|
||||
</mat-card>
|
||||
</div>
|
||||
|
||||
@@ -8,10 +8,13 @@ import {AuthService} from "../auth.service";
|
||||
})
|
||||
export class AccountDetailsComponent implements OnInit {
|
||||
|
||||
account: Manager;
|
||||
|
||||
constructor(private authService: AuthService) {
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.account = this.authService.account;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ export class ApiService {
|
||||
}
|
||||
|
||||
getLogs() {
|
||||
return this.http.post(this.url + "/logs", "{\"level\":6, \"since\":1}", this.options);
|
||||
return this.http.post(this.url + "/logs", "{\"level\":4, \"since\":1}", this.options);
|
||||
}
|
||||
|
||||
getProjects() {
|
||||
@@ -49,5 +49,12 @@ export class ApiService {
|
||||
return this.http.get(this.url + "/account", this.options)
|
||||
}
|
||||
|
||||
getMonitoringSnapshots(count: number, project: number) {
|
||||
return this.http.get(this.url + `/project/monitoring/${project}?count=${count}`, this.options)
|
||||
}
|
||||
|
||||
getAssigneeStats(project: number) {
|
||||
return this.http.get(this.url + `/project/assignees/${project}`, this.options)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ import {
|
||||
MatFormFieldModule,
|
||||
MatIconModule,
|
||||
MatInputModule,
|
||||
MatListModule,
|
||||
MatMenuModule,
|
||||
MatPaginatorIntl,
|
||||
MatPaginatorModule,
|
||||
@@ -98,7 +99,8 @@ export function createTranslateLoader(http: HttpClient) {
|
||||
),
|
||||
MatSelectModule,
|
||||
MatProgressBarModule,
|
||||
MatTabsModule
|
||||
MatTabsModule,
|
||||
MatListModule
|
||||
|
||||
],
|
||||
exports: [],
|
||||
|
||||
50
web/angular/src/app/auth.service.ts
Normal file
50
web/angular/src/app/auth.service.ts
Normal file
@@ -0,0 +1,50 @@
|
||||
import {Injectable} from '@angular/core';
|
||||
import {ApiService} from "./api.service";
|
||||
import {Credentials} from "./models/credentials";
|
||||
import {MessengerService} from "./messenger.service";
|
||||
import {Router} from "@angular/router";
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class AuthService {
|
||||
|
||||
account: Manager;
|
||||
|
||||
constructor(private apiService: ApiService,
|
||||
private messengerService: MessengerService,
|
||||
private router: Router) {
|
||||
}
|
||||
|
||||
public login(credentials: Credentials) {
|
||||
return this.apiService.login(credentials)
|
||||
.subscribe(
|
||||
() => {
|
||||
this.apiService.getAccountDetails()
|
||||
.subscribe((data: any) => {
|
||||
this.account = data.manager;
|
||||
this.router.navigateByUrl("/account");
|
||||
})
|
||||
},
|
||||
error => {
|
||||
console.log(error);
|
||||
this.messengerService.show(error.error.message);
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
public register(credentials: Credentials) {
|
||||
return this.apiService.register(credentials)
|
||||
.subscribe(() =>
|
||||
this.apiService.getAccountDetails()
|
||||
.subscribe((data: any) => {
|
||||
this.account = data.manager;
|
||||
this.router.navigateByUrl("/account");
|
||||
}),
|
||||
error => {
|
||||
console.log(error);
|
||||
this.messengerService.show(error.error.message);
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -28,16 +28,7 @@ export class LoginComponent implements OnInit {
|
||||
}
|
||||
|
||||
register() {
|
||||
this.apiService.register(this.credentials)
|
||||
.subscribe(
|
||||
() => {
|
||||
this.router.navigateByUrl("/account")
|
||||
},
|
||||
error => {
|
||||
console.log(error);
|
||||
this.messengerService.show(error.error.message);
|
||||
}
|
||||
)
|
||||
this.authService.register(this.credentials)
|
||||
}
|
||||
|
||||
canCreate(): boolean {
|
||||
|
||||
@@ -10,3 +10,8 @@
|
||||
.mat-cell {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.checkbox-wrapper mat-checkbox {
|
||||
margin: 3px 5px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
@@ -1,22 +1,31 @@
|
||||
<div class="container">
|
||||
<div class="table-container">
|
||||
<mat-form-field>
|
||||
<input matInput (keyup)="applyFilter($event.target.value)" [placeholder]="'logs.filter' | translate">
|
||||
</mat-form-field>
|
||||
<div class="checkbox-wrapper">
|
||||
<mat-form-field style="margin-right: 10px">
|
||||
<input matInput (keyup)="applyFilter($event.target.value)" [placeholder]="'logs.filter' | translate">
|
||||
</mat-form-field>
|
||||
<mat-checkbox>{{"logs.fatal" | translate}}</mat-checkbox>
|
||||
<mat-checkbox>{{"logs.panic" | translate}}</mat-checkbox>
|
||||
<mat-checkbox>{{"logs.error" | translate}}</mat-checkbox>
|
||||
<mat-checkbox>{{"logs.warn" | translate}}</mat-checkbox>
|
||||
<mat-checkbox>{{"logs.info" | translate}}</mat-checkbox>
|
||||
<mat-checkbox>{{"logs.debug" | translate}}</mat-checkbox>
|
||||
</div>
|
||||
<div class="mat-elevation-z8">
|
||||
|
||||
<mat-table [dataSource]="data" matSort matSortActive="timestamp"
|
||||
matSortDirection="desc">
|
||||
|
||||
<ng-container matColumnDef="level">
|
||||
<mat-header-cell style="flex: 0 0 6em" mat-sort-header
|
||||
<mat-header-cell style="flex: 0 0 9em" mat-sort-header
|
||||
*matHeaderCellDef>{{"logs.level" | translate}}</mat-header-cell>
|
||||
<mat-cell style="flex: 0 0 6em" *matCellDef="let entry"> {{entry.level}} </mat-cell>
|
||||
<mat-cell style="flex: 0 0 8em"
|
||||
*matCellDef="let entry"> {{("logs." + entry.level) | translate}} </mat-cell>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="timestamp">
|
||||
<mat-header-cell style="flex: 0 0 21em" mat-sort-header
|
||||
<mat-header-cell style="flex: 0 0 15em" mat-sort-header
|
||||
*matHeaderCellDef>{{"logs.time" | translate}}</mat-header-cell>
|
||||
<mat-cell style="flex: 0 0 17em" *matCellDef="let entry"> {{entry.timestamp}} </mat-cell>
|
||||
<mat-cell style="flex: 0 0 12em" *matCellDef="let entry"> {{entry.timestamp}} </mat-cell>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="message">
|
||||
<mat-header-cell mat-sort-header *matHeaderCellDef>{{"logs.message" | translate}}</mat-header-cell>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import {Component, OnInit, ViewChild} from '@angular/core';
|
||||
import {ApiService} from "../api.service";
|
||||
import {LogEntry} from "../models/logentry";
|
||||
import {getLogLevel, LogEntry} from "../models/logentry";
|
||||
|
||||
import _ from "lodash"
|
||||
import * as moment from "moment";
|
||||
@@ -44,9 +44,9 @@ export class LogsComponent implements OnInit {
|
||||
this.data.data = _.map(data["logs"], (entry) => {
|
||||
return <LogEntry>{
|
||||
message: entry.message,
|
||||
timestamp: moment.unix(entry.timestamp).toISOString(),
|
||||
timestamp: moment.unix(entry.timestamp).format("YYYY-MM-DD HH:mm:ss"),
|
||||
data: JSON.stringify(JSON.parse(entry.data), null, 2),
|
||||
level: entry.level
|
||||
level: getLogLevel(entry.level),
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -4,3 +4,32 @@ export interface LogEntry {
|
||||
data: any,
|
||||
timestamp: string,
|
||||
}
|
||||
|
||||
export enum LogLevel {
|
||||
FATAL = "fatal",
|
||||
PANIC = "panic",
|
||||
ERROR = "error",
|
||||
WARN = "warn",
|
||||
INFO = "info",
|
||||
DEBUG = "debug",
|
||||
TRACE = "trace",
|
||||
}
|
||||
|
||||
export function getLogLevel(level: number): string {
|
||||
switch (level) {
|
||||
case 1:
|
||||
return LogLevel.FATAL;
|
||||
case 2:
|
||||
return LogLevel.PANIC;
|
||||
case 3:
|
||||
return LogLevel.ERROR;
|
||||
case 4:
|
||||
return LogLevel.WARN;
|
||||
case 5:
|
||||
return LogLevel.INFO;
|
||||
case 6:
|
||||
return LogLevel.DEBUG;
|
||||
case 7:
|
||||
return LogLevel.TRACE;
|
||||
}
|
||||
}
|
||||
|
||||
12
web/angular/src/app/models/monitoring.ts
Normal file
12
web/angular/src/app/models/monitoring.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
export interface MonitoringSnapshot {
|
||||
new_task_count: number
|
||||
failed_task_count: number
|
||||
closed_task_count: number
|
||||
awaiting_verification_count: number
|
||||
time_stamp: number
|
||||
}
|
||||
|
||||
export interface AssignedTasks {
|
||||
assignee: string
|
||||
task_count: number
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
#timeline-wrapper {
|
||||
width: 100%;
|
||||
display: none;
|
||||
}
|
||||
|
||||
#status-pie-wrapper {
|
||||
@@ -12,3 +13,28 @@
|
||||
height: 50%;
|
||||
width: 400px;
|
||||
}
|
||||
|
||||
#no-tasks {
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#side-charts {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@media (min-width: 992px) {
|
||||
#side-charts {
|
||||
display: initial;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 576px) {
|
||||
#timeline-wrapper {
|
||||
display: initial;
|
||||
}
|
||||
|
||||
#small-screen-stats {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,13 @@
|
||||
<mat-card-title *ngIf="project">{{"dashboard.title" | translate}} "{{project.name}}"</mat-card-title>
|
||||
<mat-card-content style="padding: 2em 0 1em">
|
||||
|
||||
<button mat-raised-button style="float: right"
|
||||
[title]="'dashboard.refresh' | translate"
|
||||
(click)="refresh()"
|
||||
>
|
||||
<mat-icon>refresh</mat-icon>
|
||||
</button>
|
||||
|
||||
<p *ngIf="project">
|
||||
{{"project.git_repo" | translate}}:
|
||||
<a target="_blank" [href]="project['clone_url']">{{project.git_repo}}</a>
|
||||
@@ -10,12 +17,13 @@
|
||||
<p>{{"project.motd" | translate}}:</p>
|
||||
<pre *ngIf="project">{{project.motd}}</pre>
|
||||
|
||||
|
||||
<div style="display: flex; align-items: center; justify-content: center">
|
||||
<div id="timeline-wrapper">
|
||||
<canvas id="timeline"></canvas>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div [class.hidden]="noTasks" id="side-charts">
|
||||
<div id="status-pie-wrapper">
|
||||
<canvas id="status-pie"></canvas>
|
||||
</div>
|
||||
@@ -23,6 +31,18 @@
|
||||
<canvas id="assignees-pie"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div *ngIf="noTasks" id="no-tasks">
|
||||
<mat-icon>priority_high</mat-icon>
|
||||
<p>{{"dashboard.empty" | translate}}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div id="small-screen-stats">
|
||||
<p>Small screen stats</p>
|
||||
<p>Latest monitoring snapshot:</p>
|
||||
<pre>{{ lastSnapshot | json }}</pre>
|
||||
<p>Assignees</p>
|
||||
<pre>{{ assignees | json }}</pre>
|
||||
</div>
|
||||
|
||||
<mat-expansion-panel *ngIf="project" style="margin-top: 1em">
|
||||
|
||||
@@ -3,7 +3,8 @@ import {ApiService} from "../api.service";
|
||||
import {Project} from "../models/project";
|
||||
import {ActivatedRoute} from "@angular/router";
|
||||
|
||||
import {Chart, ChartData, Point} from "chart.js";
|
||||
import {Chart} from "chart.js";
|
||||
import {AssignedTasks, MonitoringSnapshot} from "../models/monitoring";
|
||||
|
||||
|
||||
@Component({
|
||||
@@ -15,22 +16,30 @@ export class ProjectDashboardComponent implements OnInit {
|
||||
|
||||
private projectId;
|
||||
project: Project;
|
||||
noTasks = false;
|
||||
|
||||
private timeline: Chart;
|
||||
private statusPie: Chart;
|
||||
private assigneesPir: Chart;
|
||||
private assigneesPie: Chart;
|
||||
|
||||
|
||||
private colors = {
|
||||
new: "#76FF03",
|
||||
failed: "#FF3D00",
|
||||
closed: "#E0E0E0",
|
||||
awaiting: "#FFB74D"
|
||||
awaiting: "#FFB74D",
|
||||
random: [
|
||||
"#3D5AFE", "#2979FF", "#2196F3",
|
||||
"#7C4DFF", "#673AB7", "#7C4DFF",
|
||||
"#FFC400", "#FFD740", "#FFC107",
|
||||
"#FF3D00", "#FF6E40", "#FF5722",
|
||||
"#76FF03", "#B2FF59", "#8BC34A"
|
||||
]
|
||||
};
|
||||
|
||||
tmpLabels = [];
|
||||
tmpNew = [];
|
||||
tmpFailed = [];
|
||||
tmpClosed = [];
|
||||
tmpAwaiting = [];
|
||||
snapshots: MonitoringSnapshot[] = [];
|
||||
lastSnapshot: MonitoringSnapshot;
|
||||
assignees: AssignedTasks[];
|
||||
|
||||
constructor(private apiService: ApiService, private route: ActivatedRoute) {
|
||||
}
|
||||
@@ -42,19 +51,110 @@ export class ProjectDashboardComponent implements OnInit {
|
||||
this.getProject();
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
let n = 40;
|
||||
for (let i = 0; i < n; i++) {
|
||||
this.tmpLabels.push((1549501926 + 600 * i) * 1000);
|
||||
this.tmpNew.push(Math.ceil(Math.random() * 30))
|
||||
this.tmpClosed.push(Math.ceil(Math.random() * 100))
|
||||
this.tmpFailed.push(Math.ceil(Math.random() * 13))
|
||||
this.tmpAwaiting.push(Math.ceil(Math.random() * 40))
|
||||
}
|
||||
public refresh() {
|
||||
|
||||
this.setupTimeline();
|
||||
this.setupStatusPie();
|
||||
this.setupAssigneesPie();
|
||||
this.apiService.getMonitoringSnapshots(60, this.projectId)
|
||||
.subscribe((data: any) => {
|
||||
this.snapshots = data.snapshots;
|
||||
this.lastSnapshot = this.snapshots ? this.snapshots.sort((a, b) => {
|
||||
return b.time_stamp - a.time_stamp
|
||||
})[0] : null;
|
||||
|
||||
if (this.lastSnapshot == null || (this.lastSnapshot.awaiting_verification_count == 0 &&
|
||||
this.lastSnapshot.closed_task_count == 0 &&
|
||||
this.lastSnapshot.new_task_count == 0 &&
|
||||
this.lastSnapshot.failed_task_count == 0)) {
|
||||
this.noTasks = true;
|
||||
return
|
||||
}
|
||||
this.noTasks = false;
|
||||
|
||||
this.timeline.data.labels = this.snapshots.map(s => s.time_stamp as any);
|
||||
this.timeline.data.datasets = this.makeTimelineDataset(this.snapshots);
|
||||
this.timeline.update();
|
||||
this.statusPie.data.datasets = [
|
||||
{
|
||||
label: "Task status",
|
||||
data: [
|
||||
this.lastSnapshot.new_task_count,
|
||||
this.lastSnapshot.failed_task_count,
|
||||
this.lastSnapshot.closed_task_count,
|
||||
this.lastSnapshot.awaiting_verification_count,
|
||||
],
|
||||
backgroundColor: [
|
||||
this.colors.new,
|
||||
this.colors.failed,
|
||||
this.colors.closed,
|
||||
this.colors.awaiting
|
||||
],
|
||||
}
|
||||
];
|
||||
this.statusPie.update();
|
||||
|
||||
this.apiService.getAssigneeStats(this.projectId)
|
||||
.subscribe((data: any) => {
|
||||
this.assignees = data.assignees;
|
||||
let colors = this.assignees.map(() => {
|
||||
return this.colors.random[Math.floor(Math.random() * this.colors.random.length)]
|
||||
});
|
||||
this.assigneesPie.data.labels = this.assignees.map(x => x.assignee);
|
||||
this.assigneesPie.data.datasets = [
|
||||
{
|
||||
label: "Task status",
|
||||
data: this.assignees.map(x => x.task_count),
|
||||
backgroundColor: colors,
|
||||
}
|
||||
];
|
||||
this.assigneesPie.update();
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
private makeTimelineDataset(snapshots: MonitoringSnapshot[]) {
|
||||
return [
|
||||
{
|
||||
label: "New",
|
||||
type: "line",
|
||||
fill: false,
|
||||
borderColor: this.colors.new,
|
||||
backgroundColor: this.colors.new,
|
||||
data: snapshots.map(s => s.new_task_count),
|
||||
pointRadius: 0,
|
||||
lineTension: 0.2,
|
||||
},
|
||||
{
|
||||
label: "Failed",
|
||||
type: "line",
|
||||
fill: false,
|
||||
borderColor: this.colors.failed,
|
||||
backgroundColor: this.colors.failed,
|
||||
data: snapshots.map(s => s.failed_task_count),
|
||||
pointRadius: 0,
|
||||
lineTension: 0.2,
|
||||
},
|
||||
{
|
||||
label: "Closed",
|
||||
type: "line",
|
||||
fill: false,
|
||||
borderColor: this.colors.closed,
|
||||
backgroundColor: this.colors.closed,
|
||||
pointRadius: 0,
|
||||
data: snapshots.map(s => s.closed_task_count),
|
||||
lineTension: 0.2,
|
||||
},
|
||||
{
|
||||
label: "Awaiting verification",
|
||||
type: "line",
|
||||
fill: false,
|
||||
borderColor: this.colors.awaiting,
|
||||
backgroundColor: this.colors.awaiting,
|
||||
data: snapshots.map(s => s.awaiting_verification_count),
|
||||
pointRadius: 0,
|
||||
lineTension: 0.2,
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
private setupTimeline() {
|
||||
@@ -64,49 +164,8 @@ export class ProjectDashboardComponent implements OnInit {
|
||||
this.timeline = new Chart(ctx, {
|
||||
type: "bar",
|
||||
data: {
|
||||
labels: this.tmpLabels,
|
||||
datasets: [
|
||||
{
|
||||
label: "New",
|
||||
type: "line",
|
||||
fill: false,
|
||||
borderColor: this.colors.new,
|
||||
backgroundColor: this.colors.new,
|
||||
data: this.tmpNew,
|
||||
pointRadius: 0,
|
||||
lineTension: 0.2,
|
||||
},
|
||||
{
|
||||
label: "Failed",
|
||||
type: "line",
|
||||
fill: false,
|
||||
borderColor: this.colors.failed,
|
||||
backgroundColor: this.colors.failed,
|
||||
data: this.tmpFailed,
|
||||
pointRadius: 0,
|
||||
lineTension: 0.2,
|
||||
},
|
||||
{
|
||||
label: "Closed",
|
||||
type: "line",
|
||||
fill: false,
|
||||
borderColor: this.colors.closed,
|
||||
backgroundColor: this.colors.closed,
|
||||
pointRadius: 0,
|
||||
data: this.tmpClosed,
|
||||
lineTension: 0.2,
|
||||
},
|
||||
{
|
||||
label: "Awaiting verification",
|
||||
type: "line",
|
||||
fill: false,
|
||||
borderColor: this.colors.awaiting,
|
||||
backgroundColor: this.colors.awaiting,
|
||||
data: this.tmpAwaiting,
|
||||
pointRadius: 0,
|
||||
lineTension: 0.2,
|
||||
},
|
||||
],
|
||||
labels: this.snapshots.map(s => s.time_stamp as any),
|
||||
datasets: this.makeTimelineDataset(this.snapshots),
|
||||
},
|
||||
options: {
|
||||
title: {
|
||||
@@ -124,10 +183,6 @@ export class ProjectDashboardComponent implements OnInit {
|
||||
ticks: {
|
||||
source: "auto"
|
||||
},
|
||||
time: {
|
||||
unit: "minute",
|
||||
unitStepSize: 10,
|
||||
}
|
||||
}]
|
||||
},
|
||||
tooltips: {
|
||||
@@ -136,12 +191,20 @@ export class ProjectDashboardComponent implements OnInit {
|
||||
mode: "index",
|
||||
position: "nearest",
|
||||
},
|
||||
responsive: true
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private setupStatusPie() {
|
||||
|
||||
if (this.lastSnapshot == null || (this.lastSnapshot.awaiting_verification_count == 0 &&
|
||||
this.lastSnapshot.closed_task_count == 0 &&
|
||||
this.lastSnapshot.new_task_count == 0 &&
|
||||
this.lastSnapshot.failed_task_count == 0)) {
|
||||
this.noTasks = true;
|
||||
}
|
||||
|
||||
let elem = document.getElementById("status-pie") as any;
|
||||
let ctx = elem.getContext("2d");
|
||||
|
||||
@@ -158,10 +221,10 @@ export class ProjectDashboardComponent implements OnInit {
|
||||
{
|
||||
label: "Task status",
|
||||
data: [
|
||||
10,
|
||||
24,
|
||||
301,
|
||||
90,
|
||||
this.lastSnapshot.new_task_count,
|
||||
this.lastSnapshot.failed_task_count,
|
||||
this.lastSnapshot.closed_task_count,
|
||||
this.lastSnapshot.awaiting_verification_count,
|
||||
],
|
||||
backgroundColor: [
|
||||
this.colors.new,
|
||||
@@ -186,7 +249,7 @@ export class ProjectDashboardComponent implements OnInit {
|
||||
animation: {
|
||||
animateScale: true,
|
||||
animateRotate: true
|
||||
}
|
||||
},
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -196,30 +259,19 @@ export class ProjectDashboardComponent implements OnInit {
|
||||
let elem = document.getElementById("assignees-pie") as any;
|
||||
let ctx = elem.getContext("2d");
|
||||
|
||||
this.statusPie = new Chart(ctx, {
|
||||
let colors = this.assignees.map(() => {
|
||||
return this.colors.random[Math.floor(Math.random() * this.colors.random.length)]
|
||||
});
|
||||
|
||||
this.assigneesPie = new Chart(ctx, {
|
||||
type: "doughnut",
|
||||
data: {
|
||||
labels: [
|
||||
"marc",
|
||||
"simon",
|
||||
"bernie",
|
||||
"natasha",
|
||||
],
|
||||
labels: this.assignees.map(x => x.assignee),
|
||||
datasets: [
|
||||
{
|
||||
label: "Task status",
|
||||
data: [
|
||||
10,
|
||||
24,
|
||||
1,
|
||||
23,
|
||||
],
|
||||
backgroundColor: [
|
||||
this.colors.new,
|
||||
this.colors.failed,
|
||||
this.colors.closed,
|
||||
this.colors.awaiting
|
||||
],
|
||||
data: this.assignees.map(x => x.task_count),
|
||||
backgroundColor: colors,
|
||||
}
|
||||
],
|
||||
},
|
||||
@@ -237,23 +289,35 @@ export class ProjectDashboardComponent implements OnInit {
|
||||
animation: {
|
||||
animateScale: true,
|
||||
animateRotate: true
|
||||
}
|
||||
},
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private getProject() {
|
||||
this.apiService.getProject(this.projectId).subscribe(data => {
|
||||
this.project = <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"],
|
||||
}
|
||||
this.apiService.getProject(this.projectId).subscribe((data: any) => {
|
||||
this.project = data.project;
|
||||
|
||||
this.apiService.getMonitoringSnapshots(60, this.projectId)
|
||||
.subscribe((data: any) => {
|
||||
this.snapshots = data.snapshots;
|
||||
this.lastSnapshot = this.snapshots ? this.snapshots.sort((a, b) => {
|
||||
return b.time_stamp - a.time_stamp
|
||||
})[0] : null;
|
||||
|
||||
this.setupTimeline();
|
||||
this.setupStatusPie();
|
||||
|
||||
if (!this.snapshots) {
|
||||
return
|
||||
}
|
||||
|
||||
this.apiService.getAssigneeStats(this.projectId)
|
||||
.subscribe((data: any) => {
|
||||
this.assignees = data.assignees;
|
||||
this.setupAssigneesPie();
|
||||
});
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
},
|
||||
"logs": {
|
||||
"filter": "Filter",
|
||||
"time": "Time",
|
||||
"time": "Time (UTC)",
|
||||
"level": "Level",
|
||||
"message": "Message",
|
||||
"data": "Details",
|
||||
@@ -18,7 +18,14 @@
|
||||
"of": "of",
|
||||
"items_per_page": "Items per page",
|
||||
"next_page": "Next page",
|
||||
"prev_page": "Previous page"
|
||||
"prev_page": "Previous page",
|
||||
"fatal": "Fatal",
|
||||
"panic": "Panic",
|
||||
"error": "Error",
|
||||
"warn": "Warning",
|
||||
"info": "Info",
|
||||
"debug": "Debug",
|
||||
"trace": "Trace"
|
||||
},
|
||||
"projects": {
|
||||
"projects": "Projects",
|
||||
@@ -47,11 +54,13 @@
|
||||
"create": "Create",
|
||||
"git_repo": "Git repository name",
|
||||
"motd": "Message of the day",
|
||||
"update": "Update"
|
||||
"update": "Edit"
|
||||
},
|
||||
"dashboard": {
|
||||
"title": "Dashboard for",
|
||||
"metadata": "Project metadata"
|
||||
"metadata": "Project metadata",
|
||||
"empty": "No tasks",
|
||||
"refresh": "Refresh"
|
||||
},
|
||||
"login": {
|
||||
"title": "Login",
|
||||
@@ -64,5 +73,11 @@
|
||||
"create_account": {
|
||||
"title": "Register",
|
||||
"create": "Create account"
|
||||
},
|
||||
"account": {
|
||||
"metadata": "Account metadata",
|
||||
"title": "Account details",
|
||||
"subtitle": "toto: subtitle",
|
||||
"username": "Username"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
},
|
||||
"logs": {
|
||||
"filter": "Filtrer",
|
||||
"time": "Date",
|
||||
"time": "Date (UTC)",
|
||||
"level": "Niveau",
|
||||
"message": "Message",
|
||||
"data": "Details",
|
||||
@@ -18,7 +18,14 @@
|
||||
"of": "de",
|
||||
"items_per_page": "Items par page",
|
||||
"next_page": "Page suivante",
|
||||
"prev_page": "Page précédante"
|
||||
"prev_page": "Page précédante",
|
||||
"fatal": "Fatal",
|
||||
"panic": "Panique",
|
||||
"error": "Erreur",
|
||||
"warn": "Avertissement",
|
||||
"info": "Information",
|
||||
"debug": "Débugage",
|
||||
"trace": "Trace"
|
||||
},
|
||||
"projects": {
|
||||
"projects": "Projets",
|
||||
@@ -52,7 +59,9 @@
|
||||
},
|
||||
"dashboard": {
|
||||
"title": "Tableau de bord pour ",
|
||||
"metadata": "Métadonnés du projet"
|
||||
"metadata": "Métadonnés du projet",
|
||||
"empty": "Aucune tâche",
|
||||
"refresh": "Rafraîchir"
|
||||
},
|
||||
"login": {
|
||||
"title": "Ouvrir un session",
|
||||
@@ -65,6 +74,12 @@
|
||||
"create_account": {
|
||||
"title": "Créer un compte",
|
||||
"create": "Créer un compte"
|
||||
},
|
||||
"account": {
|
||||
"metadata": "Métadonnés du compte",
|
||||
"title": "Détails du compte",
|
||||
"subtitle": "toto: sous-titre",
|
||||
"username": "Nom d'utilisateur"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
|
||||
<base href="/">
|
||||
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||||
<link rel="icon" type="image/x-icon" href="favicon.ico">
|
||||
</head>
|
||||
<body>
|
||||
|
||||
@@ -78,3 +78,11 @@ body {
|
||||
.mat-tab-body {
|
||||
padding-top: 1em;
|
||||
}
|
||||
|
||||
pre {
|
||||
color: #616161;
|
||||
}
|
||||
|
||||
.hidden {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user