chore(webui): format code with prettier

This commit is contained in:
Antoine CARON 2019-02-05 18:18:04 +01:00 committed by Traefiker Bot
parent 115ddc6a4a
commit c5c8382742
32 changed files with 1525 additions and 827 deletions

3
webui/.prettierrc Normal file
View file

@ -0,0 +1,3 @@
{
"singleQuote": true
}

View file

@ -14,3 +14,4 @@ COPY . $WEBUI_DIR/
EXPOSE 8080 EXPOSE 8080
RUN yarn lint RUN yarn lint
RUN yarn format --check

View file

@ -13,6 +13,7 @@
"build": "ng build --prod --no-delete-output-path --output-path ../static/", "build": "ng build --prod --no-delete-output-path --output-path ../static/",
"test": "ng test", "test": "ng test",
"lint": "ng lint", "lint": "ng lint",
"format": "yarn prettier 'src/**/*.{js,ts,html}' '*.md'",
"e2e": "ng e2e" "e2e": "ng e2e"
}, },
"dependencies": { "dependencies": {
@ -34,9 +35,9 @@
"date-fns": "^1.29.0", "date-fns": "^1.29.0",
"lodash": "^4.17.5", "lodash": "^4.17.5",
"rxjs": "^6.4.0", "rxjs": "^6.4.0",
"rxjs-compat": "^6.0.0-rc.0",
"tslib": "^1.9.0", "tslib": "^1.9.0",
"zone.js": "^0.8.19", "zone.js": "^0.8.19"
"rxjs-compat": "^6.0.0-rc.0"
}, },
"devDependencies": { "devDependencies": {
"@angular-devkit/build-angular": "~0.13.0", "@angular-devkit/build-angular": "~0.13.0",
@ -54,9 +55,11 @@
"karma-coverage-istanbul-reporter": "^2.0.4", "karma-coverage-istanbul-reporter": "^2.0.4",
"karma-jasmine": "~2.0.1", "karma-jasmine": "~2.0.1",
"karma-jasmine-html-reporter": "^1.4.0", "karma-jasmine-html-reporter": "^1.4.0",
"prettier": "^1.16.4",
"protractor": "~5.4.2", "protractor": "~5.4.2",
"ts-node": "~8.0.2", "ts-node": "~8.0.2",
"tslint": "~5.12.1", "tslint": "~5.12.1",
"tslint-config-prettier": "^1.18.0",
"typescript": "~3.2.4" "typescript": "~3.2.4"
} }
} }

View file

@ -5,6 +5,7 @@ Access to Traefik Web UI, ex: http://localhost:8080
## Interface ## Interface
Traefik Web UI provide 2 types of informations: Traefik Web UI provide 2 types of informations:
- Providers with their backends and frontends information. - Providers with their backends and frontends information.
- Health of the web server. - Health of the web server.
@ -27,9 +28,11 @@ make generate-webui # Generate static contents in `traefik/static/` folder.
- Go to the directory `webui` - Go to the directory `webui`
- To install dependencies, execute the following commands: - To install dependencies, execute the following commands:
- `yarn install` - `yarn install`
- Build static Web UI, execute the following command: - Build static Web UI, execute the following command:
- `yarn run build` - `yarn run build`
- Static contents are build in the directory `static` - Static contents are build in the directory `static`
@ -44,7 +47,6 @@ make generate-webui # Generate static contents in `traefik/static/` folder.
- all images will be optimized at build - all images will be optimized at build
- bundle JavaScript in one file - bundle JavaScript in one file
## How to edit (only for frontends developer) ## How to edit (only for frontends developer)
**Don't change manually the files in the directory `static`** **Don't change manually the files in the directory `static`**

View file

@ -1,17 +1,12 @@
import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
import { async, TestBed } from '@angular/core/testing'; import { async, TestBed } from '@angular/core/testing';
import { AppComponent } from './app.component'; import { AppComponent } from './app.component';
import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
describe('AppComponent', () => { describe('AppComponent', () => {
beforeEach(async(() => { beforeEach(async(() => {
TestBed.configureTestingModule({ TestBed.configureTestingModule({
declarations: [ declarations: [AppComponent],
AppComponent schemas: [CUSTOM_ELEMENTS_SCHEMA]
],
schemas: [
CUSTOM_ELEMENTS_SCHEMA
]
}).compileComponents(); }).compileComponents();
})); }));

View file

@ -7,4 +7,4 @@ import { Component } from '@angular/core';
<router-outlet></router-outlet> <router-outlet></router-outlet>
` `
}) })
export class AppComponent { } export class AppComponent {}

View file

@ -38,14 +38,11 @@ import { WindowService } from './services/window.service';
HttpClientModule, HttpClientModule,
FormsModule, FormsModule,
RouterModule.forRoot([ RouterModule.forRoot([
{path: '', component: ProvidersComponent, pathMatch: 'full'}, { path: '', component: ProvidersComponent, pathMatch: 'full' },
{path: 'status', component: HealthComponent} { path: 'status', component: HealthComponent }
]) ])
], ],
providers: [ providers: [ApiService, WindowService],
ApiService,
WindowService
],
bootstrap: [AppComponent] bootstrap: [AppComponent]
}) })
export class AppModule { } export class AppModule {}

View file

@ -2,6 +2,6 @@
<div class="loading-text" [class.is-hidden]="!loading"> <div class="loading-text" [class.is-hidden]="!loading">
<span> <span>
<span>Loading, please wait...</span> <span>Loading, please wait...</span>
<img src="./assets/images/loader.svg" class="main-loader"> <img src="./assets/images/loader.svg" class="main-loader" />
</span> </span>
</div> </div>

View file

@ -1,7 +1,7 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { BarChartComponent } from './bar-chart.component';
import { WindowService } from '../../services/window.service'; import { WindowService } from '../../services/window.service';
import { BarChartComponent } from './bar-chart.component';
describe('BarChartComponent', () => { describe('BarChartComponent', () => {
let component: BarChartComponent; let component: BarChartComponent;
@ -9,10 +9,9 @@ describe('BarChartComponent', () => {
beforeEach(async(() => { beforeEach(async(() => {
TestBed.configureTestingModule({ TestBed.configureTestingModule({
declarations: [ BarChartComponent ], declarations: [BarChartComponent],
providers: [{provide: WindowService, useInstance: {}}] providers: [{ provide: WindowService, useInstance: {} }]
}) }).compileComponents();
.compileComponents();
})); }));
beforeEach(() => { beforeEach(() => {
@ -27,5 +26,4 @@ describe('BarChartComponent', () => {
it('should initially go to loading state', () => { it('should initially go to loading state', () => {
expect(component.loading).toBeTruthy(); expect(component.loading).toBeTruthy();
}); });
}); });

View file

@ -1,4 +1,11 @@
import { Component, ElementRef, Input, OnChanges, OnInit, SimpleChanges } from '@angular/core'; import {
Component,
ElementRef,
Input,
OnChanges,
OnInit,
SimpleChanges
} from '@angular/core';
import { axisBottom, axisLeft, max, scaleBand, scaleLinear, select } from 'd3'; import { axisBottom, axisLeft, max, scaleBand, scaleLinear, select } from 'd3';
import { format } from 'd3-format'; import { format } from 'd3-format';
import * as _ from 'lodash'; import * as _ from 'lodash';
@ -18,19 +25,22 @@ export class BarChartComponent implements OnInit, OnChanges {
g: any; g: any;
width: number; width: number;
height: number; height: number;
margin = {top: 40, right: 40, bottom: 40, left: 40}; margin = { top: 40, right: 40, bottom: 40, left: 40 };
loading: boolean; loading: boolean;
data: any[]; data: any[];
previousData: any[]; previousData: any[];
constructor(public elementRef: ElementRef, public windowService: WindowService) { constructor(
public elementRef: ElementRef,
public windowService: WindowService
) {
this.loading = true; this.loading = true;
} }
ngOnInit() { ngOnInit() {
this.barChartEl = this.elementRef.nativeElement.querySelector('.bar-chart'); this.barChartEl = this.elementRef.nativeElement.querySelector('.bar-chart');
this.setup(); this.setup();
setTimeout(() => this.loading = false, 1000); setTimeout(() => (this.loading = false), 1000);
this.windowService.resize.subscribe(w => this.draw()); this.windowService.resize.subscribe(w => this.draw());
} }
@ -49,39 +59,44 @@ export class BarChartComponent implements OnInit, OnChanges {
} }
setup(): void { setup(): void {
this.width = this.barChartEl.clientWidth - this.margin.left - this.margin.right; this.width =
this.height = this.barChartEl.clientHeight - this.margin.top - this.margin.bottom; this.barChartEl.clientWidth - this.margin.left - this.margin.right;
this.height =
this.barChartEl.clientHeight - this.margin.top - this.margin.bottom;
this.svg = select(this.barChartEl) this.svg = select(this.barChartEl)
.append('svg') .append('svg')
.attr('width', this.width + this.margin.left + this.margin.right) .attr('width', this.width + this.margin.left + this.margin.right)
.attr('height', this.height + this.margin.top + this.margin.bottom); .attr('height', this.height + this.margin.top + this.margin.bottom);
this.g = this.svg.append('g') this.g = this.svg
.append('g')
.attr('transform', `translate(${this.margin.left}, ${this.margin.top})`); .attr('transform', `translate(${this.margin.left}, ${this.margin.top})`);
this.x = scaleBand().padding(0.05); this.x = scaleBand().padding(0.05);
this.y = scaleLinear(); this.y = scaleLinear();
this.g.append('g') this.g.append('g').attr('class', 'axis axis--x');
.attr('class', 'axis axis--x');
this.g.append('g') this.g.append('g').attr('class', 'axis axis--y');
.attr('class', 'axis axis--y');
} }
draw(): void { draw(): void {
if (this.barChartEl.clientWidth === 0 || this.barChartEl.clientHeight === 0) { if (
this.barChartEl.clientWidth === 0 ||
this.barChartEl.clientHeight === 0
) {
this.previousData = []; this.previousData = [];
} else { } else {
this.width = this.barChartEl.clientWidth - this.margin.left - this.margin.right; this.width =
this.height = this.barChartEl.clientHeight - this.margin.top - this.margin.bottom; this.barChartEl.clientWidth - this.margin.left - this.margin.right;
this.height =
this.barChartEl.clientHeight - this.margin.top - this.margin.bottom;
} }
this.x.domain(this.data.map((d: any) => d.code)); this.x.domain(this.data.map((d: any) => d.code));
this.y.domain([0, max(this.data, (d: any) => d.count)]); this.y.domain([0, max(this.data, (d: any) => d.count)]);
this.svg this.svg
.attr('width', this.width + this.margin.left + this.margin.right) .attr('width', this.width + this.margin.left + this.margin.right)
.attr('height', this.height + this.margin.top + this.margin.bottom); .attr('height', this.height + this.margin.top + this.margin.bottom);
@ -89,28 +104,38 @@ export class BarChartComponent implements OnInit, OnChanges {
this.x.rangeRound([0, this.width]); this.x.rangeRound([0, this.width]);
this.y.rangeRound([this.height, 0]); this.y.rangeRound([this.height, 0]);
this.g.select('.axis--x') this.g
.select('.axis--x')
.attr('transform', `translate(0, ${this.height})`) .attr('transform', `translate(0, ${this.height})`)
.call(axisBottom(this.x)); .call(axisBottom(this.x));
this.g.select('.axis--y') this.g.select('.axis--y').call(
.call(axisLeft(this.y).tickFormat(format('~s')).tickSize(-this.width)); axisLeft(this.y)
.tickFormat(format('~s'))
.tickSize(-this.width)
);
// Clean previous graph // Clean previous graph
this.g.selectAll('.bar').remove(); this.g.selectAll('.bar').remove();
const bars = this.g.selectAll('.bar').data(this.data); const bars = this.g.selectAll('.bar').data(this.data);
bars.enter() bars
.enter()
.append('rect') .append('rect')
.attr('class', 'bar') .attr('class', 'bar')
.style('fill', (d: any) => 'hsl(' + Math.floor(((d.code - 100) * 310 / 427) + 50) + ', 50%, 50%)') .style(
'fill',
(d: any) =>
'hsl(' + Math.floor(((d.code - 100) * 310) / 427 + 50) + ', 50%, 50%)'
)
.attr('x', (d: any) => this.x(d.code)) .attr('x', (d: any) => this.x(d.code))
.attr('y', (d: any) => this.y(d.count)) .attr('y', (d: any) => this.y(d.count))
.attr('width', this.x.bandwidth()) .attr('width', this.x.bandwidth())
.attr('height', (d: any) => (this.height - this.y(d.count)) < 0 ? 0 : this.height - this.y(d.count)); .attr('height', (d: any) =>
this.height - this.y(d.count) < 0 ? 0 : this.height - this.y(d.count)
);
bars.exit().remove(); bars.exit().remove();
} }
} }

View file

@ -2,6 +2,6 @@
<div class="loading-text line-chart-loading" [class.is-hidden]="!loading"> <div class="loading-text line-chart-loading" [class.is-hidden]="!loading">
<span> <span>
<span>Loading, please wait...</span> <span>Loading, please wait...</span>
<img src="./assets/images/loader.svg" class="main-loader"> <img src="./assets/images/loader.svg" class="main-loader" />
</span> </span>
</div> </div>

View file

@ -1,4 +1,11 @@
import { Component, ElementRef, Input, OnChanges, OnInit, SimpleChanges } from '@angular/core'; import {
Component,
ElementRef,
Input,
OnChanges,
OnInit,
SimpleChanges
} from '@angular/core';
import { import {
axisBottom, axisBottom,
axisLeft, axisLeft,
@ -21,7 +28,7 @@ import { WindowService } from '../../services/window.service';
templateUrl: 'line-chart.component.html' templateUrl: 'line-chart.component.html'
}) })
export class LineChartComponent implements OnChanges, OnInit { export class LineChartComponent implements OnChanges, OnInit {
@Input() value: { count: number, date: string }; @Input() value: { count: number; date: string };
firstDisplay: boolean; firstDisplay: boolean;
dirty: boolean; dirty: boolean;
@ -42,14 +49,21 @@ export class LineChartComponent implements OnChanges, OnInit {
yAxis: any; yAxis: any;
height: number; height: number;
width: number; width: number;
margin = {top: 40, right: 40, bottom: 60, left: 60}; margin = { top: 40, right: 40, bottom: 60, left: 60 };
loading = true; loading = true;
constructor(private elementRef: ElementRef, public windowService: WindowService) { } constructor(
private elementRef: ElementRef,
public windowService: WindowService
) {}
ngOnInit() { ngOnInit() {
this.lineChartEl = this.elementRef.nativeElement.querySelector('.line-chart'); this.lineChartEl = this.elementRef.nativeElement.querySelector(
this.loadingEl = this.elementRef.nativeElement.querySelector('.line-chart-loading'); '.line-chart'
);
this.loadingEl = this.elementRef.nativeElement.querySelector(
'.line-chart-loading'
);
this.limit = 40; this.limit = 40;
// related to the Observable.timer(0, 3000) in health component // related to the Observable.timer(0, 3000) in health component
@ -77,7 +91,10 @@ export class LineChartComponent implements OnChanges, OnInit {
render() { render() {
// When the lineChartEl is not displayed (is-hidden), width and length are equal to 0. // When the lineChartEl is not displayed (is-hidden), width and length are equal to 0.
let elt; let elt;
if (this.lineChartEl.clientWidth === 0 || this.lineChartEl.clientHeight === 0) { if (
this.lineChartEl.clientWidth === 0 ||
this.lineChartEl.clientHeight === 0
) {
elt = this.loadingEl; elt = this.loadingEl;
} else { } else {
elt = this.lineChartEl; elt = this.lineChartEl;
@ -85,7 +102,6 @@ export class LineChartComponent implements OnChanges, OnInit {
this.width = elt.clientWidth - this.margin.left - this.margin.right; this.width = elt.clientWidth - this.margin.left - this.margin.right;
this.height = elt.clientHeight - this.margin.top - this.margin.bottom; this.height = elt.clientHeight - this.margin.top - this.margin.bottom;
const el = this.lineChartEl.querySelector('svg'); const el = this.lineChartEl.querySelector('svg');
if (el) { if (el) {
el.parentNode.removeChild(el); el.parentNode.removeChild(el);
@ -105,11 +121,16 @@ export class LineChartComponent implements OnChanges, OnInit {
this.x = scaleTime().range([0, this.width - 10]); this.x = scaleTime().range([0, this.width - 10]);
this.y = scaleLinear().range([this.height, 0]); this.y = scaleLinear().range([this.height, 0]);
this.x.domain([<any>this.now - (this.limit - 2), <any>this.now - this.duration]); this.x.domain([
(this.now as any) - (this.limit - 2),
(this.now as any) - this.duration
]);
this.y.domain([0, max(this.data, (d: any) => d)]); this.y.domain([0, max(this.data, (d: any) => d)]);
this.line = line() this.line = line()
.x((d: any, i: number) => this.x(<any>this.now - (this.limit - 1 - i) * this.duration)) .x((d: any, i: number) =>
this.x((this.now as any) - (this.limit - 1 - i) * this.duration)
)
.y((d: any) => this.y(d)) .y((d: any) => this.y(d))
.curve(curveLinear); .curve(curveLinear);
@ -121,16 +142,24 @@ export class LineChartComponent implements OnChanges, OnInit {
.attr('width', this.width) .attr('width', this.width)
.attr('height', this.height); .attr('height', this.height);
this.xAxis = this.svg.append('g') this.xAxis = this.svg
.append('g')
.attr('class', 'x axis') .attr('class', 'x axis')
.attr('transform', `translate(0, ${this.height})`) .attr('transform', `translate(0, ${this.height})`)
.call(axisBottom(this.x).tickSize(-this.height).ticks(timeSecond, 5).tickFormat(timeFormat('%H:%M:%S'))); .call(
axisBottom(this.x)
.tickSize(-this.height)
.ticks(timeSecond, 5)
.tickFormat(timeFormat('%H:%M:%S'))
);
this.yAxis = this.svg.append('g') this.yAxis = this.svg
.append('g')
.attr('class', 'y axis') .attr('class', 'y axis')
.call(axisLeft(this.y).tickSize(-this.width)); .call(axisLeft(this.y).tickSize(-this.width));
this.path = this.svg.append('g') this.path = this.svg
.append('g')
.attr('clip-path', 'url(#clip)') .attr('clip-path', 'url(#clip)')
.append('path') .append('path')
.data([this.data]) .data([this.data])
@ -149,8 +178,12 @@ export class LineChartComponent implements OnChanges, OnInit {
this.data.push(value * 1000000); this.data.push(value * 1000000);
this.now = new Date(); this.now = new Date();
this.x.domain([<any>this.now - (this.limit - 2) * this.duration, <any>this.now - this.duration]); this.x.domain([
const minv = min(this.data, (d: any) => d) > 0 ? min(this.data, (d: any) => d) - 4 : 0; (this.now as any) - (this.limit - 2) * this.duration,
(this.now as any) - this.duration
]);
const minv =
min(this.data, (d: any) => d) > 0 ? min(this.data, (d: any) => d) - 4 : 0;
const maxv = max(this.data, (d: any) => d) + 4; const maxv = max(this.data, (d: any) => d) + 4;
this.y.domain([minv, maxv]); this.y.domain([minv, maxv]);
@ -158,7 +191,12 @@ export class LineChartComponent implements OnChanges, OnInit {
.transition() .transition()
.duration(this.firstDisplay || this.dirty ? 0 : this.duration) .duration(this.firstDisplay || this.dirty ? 0 : this.duration)
.ease(easeLinear) .ease(easeLinear)
.call(axisBottom(this.x).tickSize(-this.height).ticks(timeSecond, 5).tickFormat(timeFormat('%H:%M:%S'))); .call(
axisBottom(this.x)
.tickSize(-this.height)
.ticks(timeSecond, 5)
.tickFormat(timeFormat('%H:%M:%S'))
);
this.xAxis this.xAxis
.transition() .transition()
@ -183,7 +221,12 @@ export class LineChartComponent implements OnChanges, OnInit {
.transition() .transition()
.duration(this.duration) .duration(this.duration)
.ease(easeLinear) .ease(easeLinear)
.attr('transform', `translate(${this.x(<any>this.now - (this.limit - 1) * this.duration)})`); .attr(
'transform',
`translate(${this.x(
(this.now as any) - (this.limit - 1) * this.duration
)})`
);
this.firstDisplay = false; this.firstDisplay = false;
this.dirty = false; this.dirty = false;

View file

@ -1,11 +1,23 @@
<nav class="navbar is-fixed-top is-transparent" role="navigation" aria-label="main navigation"> <nav
class="navbar is-fixed-top is-transparent"
role="navigation"
aria-label="main navigation"
>
<div class="container"> <div class="container">
<div class="navbar-brand"> <div class="navbar-brand">
<a class="navbar-item" routerLink="/" (click)="burger = false"> <a class="navbar-item" routerLink="/" (click)="burger = false">
<img src="./assets/images/traefik.logo.svg" alt="Traefik" class="navbar-logo"> <img
src="./assets/images/traefik.logo.svg"
alt="Traefik"
class="navbar-logo"
/>
</a> </a>
<div class="navbar-burger burger" data-target="navbarMain" (click)="burger = !burger" [class.is-active]="burger"> <div
class="navbar-burger burger"
data-target="navbarMain"
(click)="burger = !burger"
[class.is-active]="burger"
>
<span aria-hidden="true"></span> <span aria-hidden="true"></span>
<span aria-hidden="true"></span> <span aria-hidden="true"></span>
<span aria-hidden="true"></span> <span aria-hidden="true"></span>
@ -14,10 +26,21 @@
<div id="navbarMain" class="navbar-menu" [class.is-active]="burger"> <div id="navbarMain" class="navbar-menu" [class.is-active]="burger">
<div class="navbar-start"> <div class="navbar-start">
<a class="navbar-item" routerLink="/" routerLinkActive="is-active" [routerLinkActiveOptions]="{ exact: true }" (click)="burger = false"> <a
class="navbar-item"
routerLink="/"
routerLinkActive="is-active"
[routerLinkActiveOptions]="{ exact: true }"
(click)="burger = false"
>
Providers Providers
</a> </a>
<a class="navbar-item" routerLink="/status" routerLinkActive="is-active" (click)="burger = false"> <a
class="navbar-item"
routerLink="/status"
routerLinkActive="is-active"
(click)="burger = false"
>
Health Health
</a> </a>
</div> </div>
@ -30,6 +53,5 @@
</a> </a>
</div> </div>
</div> </div>
</div> </div>
</nav> </nav>

View file

@ -11,14 +11,14 @@ export class HeaderComponent implements OnInit {
releaseLink: string; releaseLink: string;
burger: boolean; burger: boolean;
constructor(private apiService: ApiService) { } constructor(private apiService: ApiService) {}
ngOnInit() { ngOnInit() {
this.apiService.fetchVersion() this.apiService.fetchVersion().subscribe(data => {
.subscribe(data => {
this.version = data.Version; this.version = data.Version;
this.codename = data.Codename; this.codename = data.Codename;
this.releaseLink = 'https://github.com/containous/traefik/tree/' + data.Version; this.releaseLink =
'https://github.com/containous/traefik/tree/' + data.Version;
}); });
} }
} }

View file

@ -1,7 +1,6 @@
<div class="container"> <div class="container">
<div class="content"> <div class="content">
<div class="columns is-multiline"> <div class="columns is-multiline">
<div class="column is-12"> <div class="column is-12">
<div class="content-item"> <div class="content-item">
<div class="content-item-data"> <div class="content-item-data">
@ -9,7 +8,9 @@
<div class="column is-4"> <div class="column is-4">
<div class="item-data border-right"> <div class="item-data border-right">
<span class="data-grey">Total Response Time</span> <span class="data-grey">Total Response Time</span>
<span class="data-blue" [title]="exactTotalResponseTime">{{ totalResponseTime }}</span> <span class="data-blue" [title]="exactTotalResponseTime">{{
totalResponseTime
}}</span>
</div> </div>
</div> </div>
<div class="column is-4"> <div class="column is-4">
@ -20,7 +21,9 @@
</div> </div>
<div class="column is-4"> <div class="column is-4">
<div class="item-data"> <div class="item-data">
<span class="data-grey">Uptime Since <br/>{{ uptimeSince }}</span> <span class="data-grey"
>Uptime Since <br />{{ uptimeSince }}</span
>
<span class="data-blue">{{ uptime }}</span> <span class="data-blue">{{ uptime }}</span>
</div> </div>
</div> </div>
@ -33,7 +36,9 @@
<div class="column is-4"> <div class="column is-4">
<div class="item-data border-right"> <div class="item-data border-right">
<span class="data-grey">Average Response Time</span> <span class="data-grey">Average Response Time</span>
<span class="data-blue" [title]="exactAverageResponseTime">{{ averageResponseTime }}</span> <span class="data-blue" [title]="exactAverageResponseTime">{{
averageResponseTime
}}</span>
</div> </div>
</div> </div>
<div class="column is-4"> <div class="column is-4">
@ -69,7 +74,6 @@
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
@ -82,15 +86,23 @@
<td>Request</td> <td>Request</td>
<td>Time</td> <td>Time</td>
</tr> </tr>
<tr *ngFor="let entry of recentErrors; trackBy: trackRecentErrors;"> <tr *ngFor="let entry of recentErrors; trackBy: trackRecentErrors">
<td> <td>
<span class="tag is-info" [title]="entry.status">{{ entry.status_code }}</span>&nbsp;<span class="is-hidden-mobile is-hidden-desktop-only">{{ entry.status }}</span> <span class="tag is-info" [title]="entry.status">{{
entry.status_code
}}</span
>&nbsp;<span class="is-hidden-mobile is-hidden-desktop-only">{{
entry.status
}}</span>
</td> </td>
<td> <td>
<span class="tag">{{ entry.method }}</span>&nbsp;<span>{{ entry.host }}{{ entry.path }}</span> <span class="tag">{{ entry.method }}</span
>&nbsp;<span>{{ entry.host }}{{ entry.path }}</span>
</td> </td>
<td> <td>
<span [title]="entry.time | date:'yyyy-MM-dd HH:mm:ss:SSS a z'">{{ entry.time | date:'yyyy-MM-dd HH:mm:ss a z' }}</span> <span [title]="entry.time | date: 'yyyy-MM-dd HH:mm:ss:SSS a z'">{{
entry.time | date: 'yyyy-MM-dd HH:mm:ss a z'
}}</span>
</td> </td>
</tr> </tr>
<tr *ngIf="!recentErrors?.length"> <tr *ngIf="!recentErrors?.length">

View file

@ -29,7 +29,7 @@ export class HealthComponent implements OnInit, OnDestroy {
chartValue: any; chartValue: any;
statusCodeValue: any; statusCodeValue: any;
constructor(private apiService: ApiService) { } constructor(private apiService: ApiService) {}
ngOnInit() { ngOnInit() {
this.sub = Observable.timer(0, 3000) this.sub = Observable.timer(0, 3000)
@ -42,16 +42,30 @@ export class HealthComponent implements OnInit, OnDestroy {
this.recentErrors = data.recent_errors; this.recentErrors = data.recent_errors;
} }
this.chartValue = {count: data.average_response_time_sec, date: data.time}; this.chartValue = {
this.statusCodeValue = Object.keys(data.total_status_code_count) count: data.average_response_time_sec,
.map(key => ({code: key, count: data.total_status_code_count[key]})); date: data.time
};
this.statusCodeValue = Object.keys(data.total_status_code_count).map(
key => ({ code: key, count: data.total_status_code_count[key] })
);
this.pid = data.pid; this.pid = data.pid;
this.uptime = distanceInWordsStrict(subSeconds(new Date(), data.uptime_sec), new Date()); this.uptime = distanceInWordsStrict(
this.uptimeSince = format(subSeconds(new Date(), data.uptime_sec), 'YYYY-MM-DD HH:mm:ss Z'); subSeconds(new Date(), data.uptime_sec),
this.totalResponseTime = distanceInWordsStrict(subSeconds(new Date(), data.total_response_time_sec), new Date()); new Date()
);
this.uptimeSince = format(
subSeconds(new Date(), data.uptime_sec),
'YYYY-MM-DD HH:mm:ss Z'
);
this.totalResponseTime = distanceInWordsStrict(
subSeconds(new Date(), data.total_response_time_sec),
new Date()
);
this.exactTotalResponseTime = data.total_response_time; this.exactTotalResponseTime = data.total_response_time;
this.averageResponseTime = Math.floor(data.average_response_time_sec * 1000) + ' ms'; this.averageResponseTime =
Math.floor(data.average_response_time_sec * 1000) + ' ms';
this.exactAverageResponseTime = data.average_response_time; this.exactAverageResponseTime = data.average_response_time;
this.codeCount = data.count; this.codeCount = data.count;
this.totalCodeCount = data.total_count; this.totalCodeCount = data.total_count;

File diff suppressed because it is too large Load diff

View file

@ -18,7 +18,7 @@ export class ProvidersComponent implements OnInit, OnDestroy {
tab: string; tab: string;
keyword: string; keyword: string;
constructor(private apiService: ApiService) { } constructor(private apiService: ApiService) {}
ngOnInit() { ngOnInit() {
this.maxItem = 100; this.maxItem = 100;

View file

@ -8,9 +8,12 @@ interface LetContext<T> {
selector: '[appLet]' selector: '[appLet]'
}) })
export class LetDirective<T> { export class LetDirective<T> {
private _context: LetContext<T> = {appLet: null}; private _context: LetContext<T> = { appLet: null };
constructor(_viewContainer: ViewContainerRef, _templateRef: TemplateRef<LetContext<T>>) { constructor(
_viewContainer: ViewContainerRef,
_templateRef: TemplateRef<LetContext<T>>
) {
_viewContainer.createEmbeddedView(_templateRef, this._context); _viewContainer.createEmbeddedView(_templateRef, this._context);
} }

View file

@ -11,7 +11,10 @@ export class BackendFilterPipe implements PipeTransform {
} }
const keyword = filter.toLowerCase(); const keyword = filter.toLowerCase();
return items.filter(d => d.id.toLowerCase().includes(keyword) return items.filter(
|| d.servers.some(r => r.url.toLowerCase().includes(keyword))); d =>
d.id.toLowerCase().includes(keyword) ||
d.servers.some(r => r.url.toLowerCase().includes(keyword))
);
} }
} }

View file

@ -11,8 +11,11 @@ export class FrontendFilterPipe implements PipeTransform {
} }
const keyword = filter.toLowerCase(); const keyword = filter.toLowerCase();
return items.filter(d => d.id.toLowerCase().includes(keyword) return items.filter(
|| d.backend.toLowerCase().includes(keyword) d =>
|| d.routes.some(r => r.rule.toLowerCase().includes(keyword))); d.id.toLowerCase().includes(keyword) ||
d.backend.toLowerCase().includes(keyword) ||
d.routes.some(r => r.rule.toLowerCase().includes(keyword))
);
} }
} }

View file

@ -3,38 +3,39 @@ import { HumanReadableFilterPipe } from './humanreadable.filter.pipe';
describe('HumanReadableFilterPipe', () => { describe('HumanReadableFilterPipe', () => {
const pipe = new HumanReadableFilterPipe(); const pipe = new HumanReadableFilterPipe();
const datatable = [{ const datatable = [
'given': '180000000000', {
'expected': '180s' given: '180000000000',
expected: '180s'
}, },
{ {
'given': '4096.0', given: '4096.0',
'expected': '4096ns' expected: '4096ns'
}, },
{ {
'given': '7200000000000', given: '7200000000000',
'expected': '120m' expected: '120m'
}, },
{ {
'given': '1337', given: '1337',
'expected': '1337ns' expected: '1337ns'
}, },
{ {
'given': 'traefik', given: 'traefik',
'expected': 'traefik', expected: 'traefik'
}, },
{ {
'given': '-23', given: '-23',
'expected': '-23', expected: '-23'
}, },
{ {
'given': '0', given: '0',
'expected': '0', expected: '0'
}, }
]; ];
datatable.forEach(item => { datatable.forEach(item => {
it((item.given + ' should be transformed to ' + item.expected ), () => { it(item.given + ' should be transformed to ' + item.expected, () => {
expect(pipe.transform(item.given)).toEqual(item.expected); expect(pipe.transform(item.given)).toEqual(item.expected);
}); });
}); });
@ -42,5 +43,4 @@ describe('HumanReadableFilterPipe', () => {
it('create an instance', () => { it('create an instance', () => {
expect(pipe).toBeTruthy(); expect(pipe).toBeTruthy();
}); });
}); });

View file

@ -4,7 +4,7 @@ import { Pipe, PipeTransform } from '@angular/core';
* HumanReadableFilterPipe converts a time period in nanoseconds to a human-readable * HumanReadableFilterPipe converts a time period in nanoseconds to a human-readable
* string. * string.
*/ */
@Pipe({name: 'humanreadable'}) @Pipe({ name: 'humanreadable' })
export class HumanReadableFilterPipe implements PipeTransform { export class HumanReadableFilterPipe implements PipeTransform {
transform(value): any { transform(value): any {
let result = ''; let result = '';

View file

@ -1,6 +1,6 @@
import { Pipe, PipeTransform } from '@angular/core'; import { Pipe, PipeTransform } from '@angular/core';
@Pipe({name: 'keys'}) @Pipe({ name: 'keys' })
export class KeysPipe implements PipeTransform { export class KeysPipe implements PipeTransform {
transform(value, args: string[]): any { transform(value, args: string[]): any {
return Object.keys(value); return Object.keys(value);

View file

@ -1,11 +1,15 @@
import { HttpClient, HttpErrorResponse, HttpHeaders } from '@angular/common/http'; import {
HttpClient,
HttpErrorResponse,
HttpHeaders
} from '@angular/common/http';
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import 'rxjs/add/observable/of'; import 'rxjs/add/observable/of';
import 'rxjs/add/operator/catch'; import 'rxjs/add/operator/catch';
import 'rxjs/add/operator/map'; import 'rxjs/add/operator/map';
import 'rxjs/add/operator/retry'; import 'rxjs/add/operator/retry';
import { Observable } from 'rxjs/Observable';
import { EMPTY } from 'rxjs/internal/observable/empty'; import { EMPTY } from 'rxjs/internal/observable/empty';
import { Observable } from 'rxjs/Observable';
export interface ProviderType { export interface ProviderType {
[provider: string]: { [provider: string]: {
@ -25,28 +29,37 @@ export class ApiService {
} }
fetchVersion(): Observable<any> { fetchVersion(): Observable<any> {
return this.http.get('../api/version', {headers: this.headers}) return this.http
.get('../api/version', { headers: this.headers })
.retry(4) .retry(4)
.catch((err: HttpErrorResponse) => { .catch((err: HttpErrorResponse) => {
console.error(`[version] returned code ${err.status}, body was: ${err.error}`); console.error(
`[version] returned code ${err.status}, body was: ${err.error}`
);
return EMPTY; return EMPTY;
}); });
} }
fetchHealthStatus(): Observable<any> { fetchHealthStatus(): Observable<any> {
return this.http.get('../health', {headers: this.headers}) return this.http
.get('../health', { headers: this.headers })
.retry(2) .retry(2)
.catch((err: HttpErrorResponse) => { .catch((err: HttpErrorResponse) => {
console.error(`[health] returned code ${err.status}, body was: ${err.error}`); console.error(
`[health] returned code ${err.status}, body was: ${err.error}`
);
return EMPTY; return EMPTY;
}); });
} }
fetchProviders(): Observable<any> { fetchProviders(): Observable<any> {
return this.http.get('../api/providers', {headers: this.headers}) return this.http
.get('../api/providers', { headers: this.headers })
.retry(2) .retry(2)
.catch((err: HttpErrorResponse) => { .catch((err: HttpErrorResponse) => {
console.error(`[providers] returned code ${err.status}, body was: ${err.error}`); console.error(
`[providers] returned code ${err.status}, body was: ${err.error}`
);
return Observable.of<any>({}); return Observable.of<any>({});
}) })
.map((data: any): ProviderType => this.parseProviders(data)); .map((data: any): ProviderType => this.parseProviders(data));
@ -58,34 +71,47 @@ export class ApiService {
.reduce((acc, curr) => { .reduce((acc, curr) => {
acc[curr] = {}; acc[curr] = {};
acc[curr].frontends = this.toArray(data[curr].frontends, 'id') acc[curr].frontends = this.toArray(data[curr].frontends, 'id').map(
.map(frontend => { frontend => {
frontend.routes = this.toArray(frontend.routes, 'id'); frontend.routes = this.toArray(frontend.routes, 'id');
frontend.errors = this.toArray(frontend.errors, 'id'); frontend.errors = this.toArray(frontend.errors, 'id');
if (frontend.headers) { if (frontend.headers) {
frontend.headers.customRequestHeaders = this.toHeaderArray(frontend.headers.customRequestHeaders); frontend.headers.customRequestHeaders = this.toHeaderArray(
frontend.headers.customResponseHeaders = this.toHeaderArray(frontend.headers.customResponseHeaders); frontend.headers.customRequestHeaders
frontend.headers.sslProxyHeaders = this.toHeaderArray(frontend.headers.sslProxyHeaders); );
frontend.headers.customResponseHeaders = this.toHeaderArray(
frontend.headers.customResponseHeaders
);
frontend.headers.sslProxyHeaders = this.toHeaderArray(
frontend.headers.sslProxyHeaders
);
} }
if (frontend.ratelimit && frontend.ratelimit.rateset) { if (frontend.ratelimit && frontend.ratelimit.rateset) {
frontend.ratelimit.rateset = this.toArray(frontend.ratelimit.rateset, 'id'); frontend.ratelimit.rateset = this.toArray(
frontend.ratelimit.rateset,
'id'
);
} }
return frontend; return frontend;
}); }
);
acc[curr].backends = this.toArray(data[curr].backends, 'id') acc[curr].backends = this.toArray(data[curr].backends, 'id').map(
.map(backend => { backend => {
backend.servers = this.toArray(backend.servers, 'id'); backend.servers = this.toArray(backend.servers, 'id');
return backend; return backend;
}); }
);
return acc; return acc;
}, {}); }, {});
} }
toHeaderArray(data: any): any[] { toHeaderArray(data: any): any[] {
return Object.keys(data || {}).map(key => ({name: key, value: data[key]})); return Object.keys(data || {}).map(key => ({
name: key,
value: data[key]
}));
} }
toArray(data: any, fieldKeyName: string): any[] { toArray(data: any, fieldKeyName: string): any[] {
@ -94,5 +120,4 @@ export class ApiService {
return data[key]; return data[key];
}); });
} }
} }

View file

@ -8,10 +8,12 @@ export class WindowService {
constructor(private eventManager: EventManager) { constructor(private eventManager: EventManager) {
this.resize = new Subject(); this.resize = new Subject();
this.eventManager.addGlobalEventListener('window', 'resize', this.onResize); this.eventManager.addGlobalEventListener(
} 'window',
'resize',
onResize = (event: UIEvent) => { (event: UIEvent) => {
this.resize.next(event.target); this.resize.next(event.target);
} }
);
}
} }

View file

@ -1,13 +1,17 @@
<!doctype html> <!DOCTYPE html>
<html class="has-navbar-fixed-top"> <html class="has-navbar-fixed-top">
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8" />
<title>Traefik</title> <title>Traefik</title>
<base href="./"> <base href="./" />
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="icon" type="image/x-icon" href="./assets/images/traefik.icon.png"> <link
</head> rel="icon"
<body> type="image/x-icon"
href="./assets/images/traefik.icon.png"
/>
</head>
<body>
<app-root></app-root> <app-root></app-root>
</body> </body>
</html> </html>

View file

@ -8,5 +8,6 @@ if (environment.production) {
enableProdMode(); enableProdMode();
} }
platformBrowserDynamic().bootstrapModule(AppModule) platformBrowserDynamic()
.bootstrapModule(AppModule)
.catch(err => console.log(err)); .catch(err => console.log(err));

View file

@ -1,6 +1,3 @@
/** /**
* Required to support Web Animations `@angular/platform-browser/animations`. * Required to support Web Animations `@angular/platform-browser/animations`.
* Needed for: All but Chrome, Firefox and Opera. http://caniuse.com/#feat=web-animation * Needed for: All but Chrome, Firefox and Opera. http://caniuse.com/#feat=web-animation
@ -12,11 +9,11 @@
* user can disable parts of macroTask/DomEvents patch by setting following flags * user can disable parts of macroTask/DomEvents patch by setting following flags
*/ */
// (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame // (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame
// (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick // (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick
// (window as any).__zone_symbol__BLACK_LISTED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames // (window as any).__zone_symbol__BLACK_LISTED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames
/* /*
* in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js * in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js
* with the following flag, it will bypass `zone.js` patch for IE/Edge * with the following flag, it will bypass `zone.js` patch for IE/Edge
*/ */
@ -27,8 +24,6 @@
*/ */
import 'zone.js/dist/zone'; // Included with Angular CLI. import 'zone.js/dist/zone'; // Included with Angular CLI.
/*************************************************************************************************** /***************************************************************************************************
* APPLICATION IMPORTS * APPLICATION IMPORTS
*/ */

View file

@ -1,11 +1,11 @@
// This file is required by karma.conf.js and loads recursively all the .spec and framework files // This file is required by karma.conf.js and loads recursively all the .spec and framework files
import 'zone.js/dist/zone-testing';
import { getTestBed } from '@angular/core/testing'; import { getTestBed } from '@angular/core/testing';
import { import {
BrowserDynamicTestingModule, BrowserDynamicTestingModule,
platformBrowserDynamicTesting platformBrowserDynamicTesting
} from '@angular/platform-browser-dynamic/testing'; } from '@angular/platform-browser-dynamic/testing';
import 'zone.js/dist/zone-testing';
declare const require: any; declare const require: any;

View file

@ -138,5 +138,8 @@
"use-pipe-transform-interface": true, "use-pipe-transform-interface": true,
"component-class-suffix": true, "component-class-suffix": true,
"directive-class-suffix": true "directive-class-suffix": true
} },
"extends": [
"tslint-config-prettier"
]
} }

View file

@ -5706,6 +5706,11 @@ preserve@^0.2.0:
version "0.2.0" version "0.2.0"
resolved "https://registry.yarnpkg.com/preserve/-/preserve-0.2.0.tgz#815ed1f6ebc65926f865b310c0713bcb3315ce4b" resolved "https://registry.yarnpkg.com/preserve/-/preserve-0.2.0.tgz#815ed1f6ebc65926f865b310c0713bcb3315ce4b"
prettier@^1.16.4:
version "1.16.4"
resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.16.4.tgz#73e37e73e018ad2db9c76742e2647e21790c9717"
integrity sha512-ZzWuos7TI5CKUeQAtFd6Zhm2s6EpAD/ZLApIhsF9pRvRtM1RFo61dM/4MSRUA0SuLugA/zgrZD8m0BaY46Og7g==
process-nextick-args@~1.0.6: process-nextick-args@~1.0.6:
version "1.0.7" version "1.0.7"
resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-1.0.7.tgz#150e20b756590ad3f91093f25a4f2ad8bff30ba3" resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-1.0.7.tgz#150e20b756590ad3f91093f25a4f2ad8bff30ba3"
@ -7342,6 +7347,11 @@ tslib@^1.9.0:
resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.9.3.tgz#d7e4dd79245d85428c4d7e4822a79917954ca286" resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.9.3.tgz#d7e4dd79245d85428c4d7e4822a79917954ca286"
integrity sha512-4krF8scpejhaOgqzBEcGM7yDIEfi0/8+8zDRZhNZZ2kjmHJ4hv3zCbQWxoJGz1iw5U0Jl0nma13xzHXcncMavQ== integrity sha512-4krF8scpejhaOgqzBEcGM7yDIEfi0/8+8zDRZhNZZ2kjmHJ4hv3zCbQWxoJGz1iw5U0Jl0nma13xzHXcncMavQ==
tslint-config-prettier@^1.18.0:
version "1.18.0"
resolved "https://registry.yarnpkg.com/tslint-config-prettier/-/tslint-config-prettier-1.18.0.tgz#75f140bde947d35d8f0d238e0ebf809d64592c37"
integrity sha512-xPw9PgNPLG3iKRxmK7DWr+Ea/SzrvfHtjFt5LBl61gk2UBG/DB9kCXRjv+xyIU1rUtnayLeMUVJBcMX8Z17nDg==
tslint@~5.12.1: tslint@~5.12.1:
version "5.12.1" version "5.12.1"
resolved "https://registry.yarnpkg.com/tslint/-/tslint-5.12.1.tgz#8cec9d454cf8a1de9b0a26d7bdbad6de362e52c1" resolved "https://registry.yarnpkg.com/tslint/-/tslint-5.12.1.tgz#8cec9d454cf8a1de9b0a26d7bdbad6de362e52c1"