From 2d54065082043ca7feb69a8d4084d7265eaa7f89 Mon Sep 17 00:00:00 2001 From: Antoine CARON Date: Fri, 8 Mar 2019 14:08:03 +0100 Subject: [PATCH] feat(webui): migrate to a work in progress webui --- webui/src/app.sass | 10 + webui/src/app/app.component.spec.ts | 18 - webui/src/app/app.component.ts | 8 +- webui/src/app/app.module.ts | 39 +- .../charts/bar-chart/bar-chart.component.html | 7 - .../bar-chart/bar-chart.component.spec.ts | 29 - .../charts/bar-chart/bar-chart.component.ts | 141 -- .../line-chart/line-chart.component.html | 7 - .../charts/line-chart/line-chart.component.ts | 240 --- .../components/header/header.component.html | 57 - .../app/components/header/header.component.ts | 24 - .../components/health/health.component.html | 116 -- .../app/components/health/health.component.ts | 83 -- .../providers/providers.component.html | 1289 ----------------- .../providers/providers.component.ts | 58 - webui/src/app/directives/let.directive.ts | 24 - webui/src/app/pipes/backend.filter.pipe.ts | 20 - webui/src/app/pipes/frontend.filter.pipe.ts | 21 - .../pipes/humanreadable.filter.pipe.spec.ts | 46 - .../app/pipes/humanreadable.filter.pipe.ts | 27 - webui/src/app/pipes/keys.pipe.ts | 8 - webui/src/app/services/api.service.ts | 119 -- webui/src/app/services/window.service.ts | 19 - 23 files changed, 18 insertions(+), 2392 deletions(-) delete mode 100644 webui/src/app/app.component.spec.ts delete mode 100644 webui/src/app/charts/bar-chart/bar-chart.component.html delete mode 100644 webui/src/app/charts/bar-chart/bar-chart.component.spec.ts delete mode 100644 webui/src/app/charts/bar-chart/bar-chart.component.ts delete mode 100644 webui/src/app/charts/line-chart/line-chart.component.html delete mode 100644 webui/src/app/charts/line-chart/line-chart.component.ts delete mode 100644 webui/src/app/components/header/header.component.html delete mode 100644 webui/src/app/components/header/header.component.ts delete mode 100644 webui/src/app/components/health/health.component.html delete mode 100644 webui/src/app/components/health/health.component.ts delete mode 100644 webui/src/app/components/providers/providers.component.html delete mode 100644 webui/src/app/components/providers/providers.component.ts delete mode 100644 webui/src/app/directives/let.directive.ts delete mode 100644 webui/src/app/pipes/backend.filter.pipe.ts delete mode 100644 webui/src/app/pipes/frontend.filter.pipe.ts delete mode 100644 webui/src/app/pipes/humanreadable.filter.pipe.spec.ts delete mode 100644 webui/src/app/pipes/humanreadable.filter.pipe.ts delete mode 100644 webui/src/app/pipes/keys.pipe.ts delete mode 100644 webui/src/app/services/api.service.ts delete mode 100644 webui/src/app/services/window.service.ts diff --git a/webui/src/app.sass b/webui/src/app.sass index 64a380502..7d97267d6 100644 --- a/webui/src/app.sass +++ b/webui/src/app.sass @@ -25,3 +25,13 @@ html font-family: $open-sans height: 100% background: $background + +.wip + display: flex + flex-direction: column + align-items: center + justify-content: center + height: 80vh + + .title + font-size: 4em diff --git a/webui/src/app/app.component.spec.ts b/webui/src/app/app.component.spec.ts deleted file mode 100644 index ccface19f..000000000 --- a/webui/src/app/app.component.spec.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'; -import { async, TestBed } from '@angular/core/testing'; -import { AppComponent } from './app.component'; - -describe('AppComponent', () => { - beforeEach(async(() => { - TestBed.configureTestingModule({ - declarations: [AppComponent], - schemas: [CUSTOM_ELEMENTS_SCHEMA] - }).compileComponents(); - })); - - it('should create the app', async(() => { - const fixture = TestBed.createComponent(AppComponent); - const app = fixture.debugElement.componentInstance; - expect(app).toBeTruthy(); - })); -}); diff --git a/webui/src/app/app.component.ts b/webui/src/app/app.component.ts index 5d475fee0..4218b7340 100644 --- a/webui/src/app/app.component.ts +++ b/webui/src/app/app.component.ts @@ -3,8 +3,12 @@ import { Component } from '@angular/core'; @Component({ selector: 'app-root', template: ` - - +
+ logo +
+

Work in progress...

+
+
` }) export class AppComponent {} diff --git a/webui/src/app/app.module.ts b/webui/src/app/app.module.ts index bf18dad04..4bc979bfd 100644 --- a/webui/src/app/app.module.ts +++ b/webui/src/app/app.module.ts @@ -3,46 +3,11 @@ import { HttpClientModule } from '@angular/common/http'; import { NgModule } from '@angular/core'; import { FormsModule } from '@angular/forms'; import { BrowserModule } from '@angular/platform-browser'; -import { RouterModule } from '@angular/router'; import { AppComponent } from './app.component'; -import { BarChartComponent } from './charts/bar-chart/bar-chart.component'; -import { LineChartComponent } from './charts/line-chart/line-chart.component'; -import { HeaderComponent } from './components/header/header.component'; -import { HealthComponent } from './components/health/health.component'; -import { ProvidersComponent } from './components/providers/providers.component'; -import { LetDirective } from './directives/let.directive'; -import { BackendFilterPipe } from './pipes/backend.filter.pipe'; -import { FrontendFilterPipe } from './pipes/frontend.filter.pipe'; -import { HumanReadableFilterPipe } from './pipes/humanreadable.filter.pipe'; -import { KeysPipe } from './pipes/keys.pipe'; -import { ApiService } from './services/api.service'; -import { WindowService } from './services/window.service'; @NgModule({ - declarations: [ - AppComponent, - HeaderComponent, - ProvidersComponent, - HealthComponent, - LineChartComponent, - BarChartComponent, - KeysPipe, - FrontendFilterPipe, - BackendFilterPipe, - HumanReadableFilterPipe, - LetDirective - ], - imports: [ - BrowserModule, - CommonModule, - HttpClientModule, - FormsModule, - RouterModule.forRoot([ - { path: '', component: ProvidersComponent, pathMatch: 'full' }, - { path: 'status', component: HealthComponent } - ]) - ], - providers: [ApiService, WindowService], + declarations: [AppComponent], + imports: [BrowserModule, CommonModule, HttpClientModule, FormsModule], bootstrap: [AppComponent] }) export class AppModule {} diff --git a/webui/src/app/charts/bar-chart/bar-chart.component.html b/webui/src/app/charts/bar-chart/bar-chart.component.html deleted file mode 100644 index 129efa0c2..000000000 --- a/webui/src/app/charts/bar-chart/bar-chart.component.html +++ /dev/null @@ -1,7 +0,0 @@ -
-
- - Loading, please wait... - - -
diff --git a/webui/src/app/charts/bar-chart/bar-chart.component.spec.ts b/webui/src/app/charts/bar-chart/bar-chart.component.spec.ts deleted file mode 100644 index 1b743e942..000000000 --- a/webui/src/app/charts/bar-chart/bar-chart.component.spec.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { async, ComponentFixture, TestBed } from '@angular/core/testing'; - -import { WindowService } from '../../services/window.service'; -import { BarChartComponent } from './bar-chart.component'; - -describe('BarChartComponent', () => { - let component: BarChartComponent; - let fixture: ComponentFixture; - - beforeEach(async(() => { - TestBed.configureTestingModule({ - declarations: [BarChartComponent], - providers: [{ provide: WindowService, useInstance: {} }] - }).compileComponents(); - })); - - beforeEach(() => { - fixture = TestBed.createComponent(BarChartComponent); - component = fixture.componentInstance; - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); - - it('should initially go to loading state', () => { - expect(component.loading).toBeTruthy(); - }); -}); diff --git a/webui/src/app/charts/bar-chart/bar-chart.component.ts b/webui/src/app/charts/bar-chart/bar-chart.component.ts deleted file mode 100644 index 47b6d2307..000000000 --- a/webui/src/app/charts/bar-chart/bar-chart.component.ts +++ /dev/null @@ -1,141 +0,0 @@ -import { - Component, - ElementRef, - Input, - OnChanges, - OnInit, - SimpleChanges -} from '@angular/core'; -import { axisBottom, axisLeft, max, scaleBand, scaleLinear, select } from 'd3'; -import { format } from 'd3-format'; -import * as _ from 'lodash'; -import { WindowService } from '../../services/window.service'; - -@Component({ - selector: 'app-bar-chart', - templateUrl: './bar-chart.component.html' -}) -export class BarChartComponent implements OnInit, OnChanges { - @Input() value: any; - - barChartEl: HTMLElement; - svg: any; - x: any; - y: any; - g: any; - width: number; - height: number; - margin = { top: 40, right: 40, bottom: 40, left: 40 }; - loading: boolean; - data: any[]; - previousData: any[]; - - constructor( - public elementRef: ElementRef, - public windowService: WindowService - ) { - this.loading = true; - } - - ngOnInit() { - this.barChartEl = this.elementRef.nativeElement.querySelector('.bar-chart'); - this.setup(); - setTimeout(() => (this.loading = false), 1000); - - this.windowService.resize.subscribe(w => this.draw()); - } - - ngOnChanges(changes: SimpleChanges) { - if (!this.value || !this.svg) { - return; - } - - if (!_.isEqual(this.previousData, this.value)) { - this.previousData = _.cloneDeep(this.value); - this.data = this.value; - - this.draw(); - } - } - - setup(): void { - this.width = - 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) - .append('svg') - .attr('width', this.width + this.margin.left + this.margin.right) - .attr('height', this.height + this.margin.top + this.margin.bottom); - - this.g = this.svg - .append('g') - .attr('transform', `translate(${this.margin.left}, ${this.margin.top})`); - - this.x = scaleBand().padding(0.05); - this.y = scaleLinear(); - - this.g.append('g').attr('class', 'axis axis--x'); - - this.g.append('g').attr('class', 'axis axis--y'); - } - - draw(): void { - if ( - this.barChartEl.clientWidth === 0 || - this.barChartEl.clientHeight === 0 - ) { - this.previousData = []; - } else { - this.width = - 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.y.domain([0, max(this.data, (d: any) => d.count)]); - - this.svg - .attr('width', this.width + this.margin.left + this.margin.right) - .attr('height', this.height + this.margin.top + this.margin.bottom); - - this.x.rangeRound([0, this.width]); - this.y.rangeRound([this.height, 0]); - - this.g - .select('.axis--x') - .attr('transform', `translate(0, ${this.height})`) - .call(axisBottom(this.x)); - - this.g.select('.axis--y').call( - axisLeft(this.y) - .tickFormat(format('~s')) - .tickSize(-this.width) - ); - - // Clean previous graph - this.g.selectAll('.bar').remove(); - - const bars = this.g.selectAll('.bar').data(this.data); - - bars - .enter() - .append('rect') - .attr('class', 'bar') - .style( - 'fill', - (d: any) => - 'hsl(' + Math.floor(((d.code - 100) * 310) / 427 + 50) + ', 50%, 50%)' - ) - .attr('x', (d: any) => this.x(d.code)) - .attr('y', (d: any) => this.y(d.count)) - .attr('width', this.x.bandwidth()) - .attr('height', (d: any) => - this.height - this.y(d.count) < 0 ? 0 : this.height - this.y(d.count) - ); - - bars.exit().remove(); - } -} diff --git a/webui/src/app/charts/line-chart/line-chart.component.html b/webui/src/app/charts/line-chart/line-chart.component.html deleted file mode 100644 index 99c13f597..000000000 --- a/webui/src/app/charts/line-chart/line-chart.component.html +++ /dev/null @@ -1,7 +0,0 @@ -
-
- - Loading, please wait... - - -
diff --git a/webui/src/app/charts/line-chart/line-chart.component.ts b/webui/src/app/charts/line-chart/line-chart.component.ts deleted file mode 100644 index 0ecbb7067..000000000 --- a/webui/src/app/charts/line-chart/line-chart.component.ts +++ /dev/null @@ -1,240 +0,0 @@ -import { - Component, - ElementRef, - Input, - OnChanges, - OnInit, - SimpleChanges -} from '@angular/core'; -import { - axisBottom, - axisLeft, - curveLinear, - easeLinear, - line, - max, - min, - range, - scaleLinear, - scaleTime, - select, - timeFormat, - timeSecond -} from 'd3'; -import { WindowService } from '../../services/window.service'; - -@Component({ - selector: 'app-line-chart', - templateUrl: 'line-chart.component.html' -}) -export class LineChartComponent implements OnChanges, OnInit { - @Input() value: { count: number; date: string }; - - firstDisplay: boolean; - dirty: boolean; - lineChartEl: HTMLElement; - loadingEl: HTMLElement; - svg: any; - g: any; - line: any; - path: any; - x: any; - y: any; - data: number[]; - now: Date; - duration: number; - limit: number; - options: any; - xAxis: any; - yAxis: any; - height: number; - width: number; - margin = { top: 40, right: 40, bottom: 60, left: 60 }; - loading = true; - - constructor( - private elementRef: ElementRef, - public windowService: WindowService - ) {} - - ngOnInit() { - this.lineChartEl = this.elementRef.nativeElement.querySelector( - '.line-chart' - ); - this.loadingEl = this.elementRef.nativeElement.querySelector( - '.line-chart-loading' - ); - this.limit = 40; - - // related to the Observable.timer(0, 3000) in health component - this.duration = 3000; - - this.now = new Date(Date.now() - this.duration); - - this.options = { - title: '', - color: '#3A84C5' - }; - - this.firstDisplay = true; - this.render(); - - this.windowService.resize.subscribe(w => { - if (this.svg) { - this.dirty = true; - this.loading = true; - this.render(); - } - }); - } - - render() { - // When the lineChartEl is not displayed (is-hidden), width and length are equal to 0. - let elt; - if ( - this.lineChartEl.clientWidth === 0 || - this.lineChartEl.clientHeight === 0 - ) { - elt = this.loadingEl; - } else { - elt = this.lineChartEl; - } - this.width = elt.clientWidth - this.margin.left - this.margin.right; - this.height = elt.clientHeight - this.margin.top - this.margin.bottom; - - const el = this.lineChartEl.querySelector('svg'); - if (el) { - el.parentNode.removeChild(el); - } - - this.svg = select(this.lineChartEl) - .append('svg') - .attr('width', this.width + this.margin.left + this.margin.right) - .attr('height', this.height + this.margin.top + this.margin.bottom) - .append('g') - .attr('transform', `translate(${this.margin.left}, ${this.margin.top})`); - - if (!this.data) { - this.data = range(this.limit).map(i => 0); - } - - this.x = scaleTime().range([0, this.width - 10]); - this.y = scaleLinear().range([this.height, 0]); - - 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.line = line() - .x((d: any, i: number) => - this.x((this.now as any) - (this.limit - 1 - i) * this.duration) - ) - .y((d: any) => this.y(d)) - .curve(curveLinear); - - this.svg - .append('defs') - .append('clipPath') - .attr('id', 'clip') - .append('rect') - .attr('width', this.width) - .attr('height', this.height); - - this.xAxis = this.svg - .append('g') - .attr('class', 'x axis') - .attr('transform', `translate(0, ${this.height})`) - .call( - axisBottom(this.x) - .tickSize(-this.height) - .ticks(timeSecond, 5) - .tickFormat(timeFormat('%H:%M:%S')) - ); - - this.yAxis = this.svg - .append('g') - .attr('class', 'y axis') - .call(axisLeft(this.y).tickSize(-this.width)); - - this.path = this.svg - .append('g') - .attr('clip-path', 'url(#clip)') - .append('path') - .data([this.data]) - .attr('class', 'line'); - } - - ngOnChanges(changes: SimpleChanges) { - if (!this.value || !this.svg) { - return; - } - - this.updateData(this.value.count); - } - - updateData(value: number) { - this.data.push(value * 1000000); - this.now = new Date(); - - this.x.domain([ - (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; - this.y.domain([minv, maxv]); - - this.xAxis - .transition() - .duration(this.firstDisplay || this.dirty ? 0 : this.duration) - .ease(easeLinear) - .call( - axisBottom(this.x) - .tickSize(-this.height) - .ticks(timeSecond, 5) - .tickFormat(timeFormat('%H:%M:%S')) - ); - - this.xAxis - .transition() - .duration(0) - .selectAll('text') - .style('text-anchor', 'end') - .attr('dx', '-.8em') - .attr('dy', '.15em') - .attr('transform', 'rotate(-65)'); - - this.yAxis - .transition() - .duration(500) - .ease(easeLinear) - .call(axisLeft(this.y).tickSize(-this.width)); - - this.path - .transition() - .duration(0) - .attr('d', this.line(this.data)) - .attr('transform', null) - .transition() - .duration(this.duration) - .ease(easeLinear) - .attr( - 'transform', - `translate(${this.x( - (this.now as any) - (this.limit - 1) * this.duration - )})` - ); - - this.firstDisplay = false; - this.dirty = false; - - if (this.loading) { - this.loading = false; - } - - this.data.shift(); - } -} diff --git a/webui/src/app/components/header/header.component.html b/webui/src/app/components/header/header.component.html deleted file mode 100644 index f41266a64..000000000 --- a/webui/src/app/components/header/header.component.html +++ /dev/null @@ -1,57 +0,0 @@ - diff --git a/webui/src/app/components/header/header.component.ts b/webui/src/app/components/header/header.component.ts deleted file mode 100644 index 52960be9b..000000000 --- a/webui/src/app/components/header/header.component.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { Component, OnInit } from '@angular/core'; -import { ApiService } from '../../services/api.service'; - -@Component({ - selector: 'app-header', - templateUrl: 'header.component.html' -}) -export class HeaderComponent implements OnInit { - version: string; - codename: string; - releaseLink: string; - burger: boolean; - - constructor(private apiService: ApiService) {} - - ngOnInit() { - this.apiService.fetchVersion().subscribe(data => { - this.version = data.Version; - this.codename = data.Codename; - this.releaseLink = - 'https://github.com/containous/traefik/tree/' + data.Version; - }); - } -} diff --git a/webui/src/app/components/health/health.component.html b/webui/src/app/components/health/health.component.html deleted file mode 100644 index 4125d3985..000000000 --- a/webui/src/app/components/health/health.component.html +++ /dev/null @@ -1,116 +0,0 @@ -
-
-
-
-
-
-
-
-
- Total Response Time - {{ - totalResponseTime - }} -
-
-
-
- Total Code Count - {{ totalCodeCount }} -
-
-
-
- Uptime Since
{{ uptimeSince }}
- {{ uptime }} -
-
-
-
-
-
-
-
-
-
- Average Response Time - {{ - averageResponseTime - }} -
-
-
-
- Code Count - {{ codeCount }} -
-
-
-
- PID - {{ pid }} -
-
-
-
-
-
- -
-
-
-
-

Average Response Time (µs)

- -
-
-
-
-

Total Status Code Count

- -
-
-
-
-
-
- -
-
-

Recent HTTP Errors

- - - - - - - - - - - - - - -
StatusRequestTime
- {{ - entry.status_code - }} {{ - entry.status - }} - - {{ entry.method }} {{ entry.host }}{{ entry.path }} - - {{ - entry.time | date: 'yyyy-MM-dd HH:mm:ss a z' - }} -
-

No entries

-
-
-
-
diff --git a/webui/src/app/components/health/health.component.ts b/webui/src/app/components/health/health.component.ts deleted file mode 100644 index ff290fa16..000000000 --- a/webui/src/app/components/health/health.component.ts +++ /dev/null @@ -1,83 +0,0 @@ -import { Component, OnDestroy, OnInit } from '@angular/core'; -import { distanceInWordsStrict, format, subSeconds } from 'date-fns'; -import * as _ from 'lodash'; -import { Subscription, timer } from 'rxjs'; -import { mergeMap, timeInterval } from 'rxjs/operators'; -import { ApiService } from '../../services/api.service'; - -@Component({ - selector: 'app-health', - templateUrl: 'health.component.html' -}) -export class HealthComponent implements OnInit, OnDestroy { - sub: Subscription; - recentErrors: any; - previousRecentErrors: any; - pid: number; - uptime: string; - uptimeSince: string; - averageResponseTime: string; - exactAverageResponseTime: string; - totalResponseTime: string; - exactTotalResponseTime: string; - codeCount: number; - totalCodeCount: number; - chartValue: any; - statusCodeValue: any; - - constructor(private apiService: ApiService) {} - - ngOnInit() { - this.sub = timer(0, 3000) - .pipe( - timeInterval(), - mergeMap(() => this.apiService.fetchHealthStatus()) - ) - .subscribe(data => { - if (data) { - if (!_.isEqual(this.previousRecentErrors, data.recent_errors)) { - this.previousRecentErrors = _.cloneDeep(data.recent_errors); - this.recentErrors = data.recent_errors; - } - - this.chartValue = { - count: data.average_response_time_sec, - 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.uptime = distanceInWordsStrict( - subSeconds(new Date(), data.uptime_sec), - 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.averageResponseTime = - Math.floor(data.average_response_time_sec * 1000) + ' ms'; - this.exactAverageResponseTime = data.average_response_time; - this.codeCount = data.count; - this.totalCodeCount = data.total_count; - } - }); - } - - ngOnDestroy() { - if (this.sub) { - this.sub.unsubscribe(); - } - } - - trackRecentErrors(index, item): string { - return item.status_code + item.method + item.host + item.path + item.time; - } -} diff --git a/webui/src/app/components/providers/providers.component.html b/webui/src/app/components/providers/providers.component.html deleted file mode 100644 index ab243adf0..000000000 --- a/webui/src/app/components/providers/providers.component.html +++ /dev/null @@ -1,1289 +0,0 @@ -
-
-
-
-
- - - -
- -
- -
- -
-
- -
-

- {{ frontends.length }}Frontends -

- -
-
-
-

- -
- {{ p.id }} -
-

-
- -
-
- -
- - -
-
-
-

Route Rule

-
- - - - - - -
- {{ route.rule }} -
-
- -
-
-
-
-

Entry Points

-
-
-
-
-
- {{ ep }} -
-
-
-
-
-
- -
-
-
-
-

Backend

-
-
-
- - {{ - p.backend - }} -
-
-
-
-
- - -
-
-
-
-

Misc.

-
-
-
-
-
- Priority - {{ - p.priority - }} -
-
-
-
- Host Header - {{ - !!p.passHostHeader - }} -
-
-
-
- TLS Cert - {{ - p.passTLSCert - }} -
-
-
-
-
-
- -
-
-
-
-

Redirect

-
-
-
-
-
- {{ - p.redirect.permanent - ? 'Permanent' - : 'Temporary' - }} - to - {{ - p.redirect.entryPoint - }} -
-
-
-
-
- {{ - p.redirect.permanent - ? 'Permanent' - : 'Temporary' - }} -
-
- From - {{ - p.redirect.regex - }} -
-
- To - {{ - p.redirect.replacement - }} -
-
-
-
-
- -
-
-
-
-

- Basic Authentication -

- - - - - - - - - - - - - - - - - - - -
- Users File - - {{ - p.auth.basic.usersFile - }} -
- Header Field - - {{ - p.auth.headerField - }} -
- Remove Auth Header - - {{ - !!p.auth.basic.removeHeader - }} -
- Users - -
- {{ - user - }} -
-
-
-
-

- Digest Authentication -

- - - - - - - - - - - - - - - - - - - -
- Users File - - {{ - p.auth.digest.usersFile - }} -
- Header Field - - {{ - p.auth.headerField - }} -
- Remove Auth Header - - {{ - !!p.auth.digest.removeHeader - }} -
- Users - -
- {{ - user - }} -
-
-
-
-

- Forward Authentication -

- - - - - - - - - - - - - - - -
- Address - - {{ - p.auth.forward.address - }} -
- Trust Forward Header - - {{ - p.auth.forward.trustForwardHeader - }} -
- Response Headers - -
- {{ - respHeader - }} -
-
-
-
-
- -
-
-
-

Error Pages

- - - - - - - - - - - - - -
BackendQueryStatus
- {{ - entry.backend - }} - - {{ - entry.query - }} - - {{ state }} -
-
-
- -
-
-
-
-

Whitelist

-
-
-
-
-
- useXForwardedFor - {{ - !!p.whiteList.useXForwardedFor - }} -
-
-
-
-
-
-
-
- {{ wlRange }} -
-
-
-
-
-
- -
-
-
-

Headers

-
-
- - - - - - - - - - -
Custom Request Headers
- {{ - header.name - }} - - {{ - header.value - }} -
-
- -
- - - - - - - - - - -
Custom Response Headers
- {{ - header.name - }} - - {{ - header.value - }} -
-
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Secure
- Browser XSS Filter - - {{ - p.headers.browserXssFilter - }} -
- Content Security Policy - - {{ - p.headers.contentSecurityPolicy - }} -
- Content Type (No sniff) - - {{ - p.headers.contentTypeNoSniff - }} -
- Custom Frame Options Value - - {{ - p.headers.customFrameOptionsValue - }} -
- Force STS Header - - {{ - p.headers.forceSTSHeader - }} -
- Frame Deny - - {{ - p.headers.frameDeny - }} -
- Is Development - - {{ - p.headers.isDevelopment - }} -
- Public Key - - {{ - p.headers.publicKey - }} -
- Referrer Policy - - {{ - p.headers.referrerPolicy - }} -
- SSL Host - - {{ - p.headers.sslHost - }} -
- SSL Force Host - - {{ - p.headers.sslForceHost - }} -
- SSL Redirect - - {{ - p.headers.sslRedirect - }} -
- SSL Temporary Redirect - - {{ - p.headers.sslTemporaryRedirect - }} -
- STS Include Subdomains - - {{ - p.headers.stsIncludeSubdomains - }} -
- STS Preload - - {{ - p.headers.stsPreload - }} -
- STS Seconds - - {{ - p.headers.stsSeconds - }} -
-
- -
- - - - - - - - - - -
SSL Proxy Headers
- {{ - header.name - }} - - {{ - header.value - }} -
-
- -
-

Allowed Hosts

-
- {{ host }} -
-
- -
-

Hosts Proxy Headers

-
- {{ h }} -
-
-
-
-
- - -
-
-
-
-
-

Rate Limiting

-
-
-
-
- Extractor Function - {{ - p.ratelimit.extractorFunc - }} -
-
- - - - - - - - - - - - - - - -
RatesetPeriodAverageBurst
{{ rateset.id }} - {{ rateset.period | humanreadable }} - {{ rateset.average }}{{ rateset.burst }}
-
-
-
-
-
-
-
-
-
-
-
- Too many frontends to display, please add a filter. -
-
-
-
- - -
-

- {{ backends.length }}Backends -

- -
-
-
-

- -
- {{ p.id }} -
-

-
-
-
- -
- - -
-
- - - - - - - - - - - - - - - -
ServerWeight
- {{ server.url }} - - {{ - server.weight - }} -
-
-
- - -
-
-
-
-

Load Balancer

-
-
-
-
-
- Method - {{ - p.loadBalancer.method - }} -
-
-
-
- Stickiness - true -
-
-
-
- Cookie Name - {{ - p.loadBalancer.stickiness.cookieName - }} -
-
-
-
-
-
- -
-
-
-
-

Max Connections

-
-
-
-
-
- Amount - {{ - p.maxConn.amount - }} -
-
-
-
- Extractor Function - {{ - p.maxConn.extractorFunc - }} -
-
-
-
-
-
- -
-
-
-
-

Circuit Breaker

-
-
-
-
-
- Expression - {{ - p.circuitBreaker.expression - }} -
-
-
-
-
-
- -
-
-
-
-

Health Check

-
-
-
-
-
- Path - {{ - p.healthCheck.path - }} -
-
-
-
- Interval - {{ - p.healthCheck.interval - }} -
-
-
-
- Timeout - {{ - p.healthCheck.timeout - }} -
-
-
-
- Port - {{ - p.healthCheck.port - }} -
-
-
-
- Scheme - {{ - p.healthCheck.scheme - }} -
-
-
-
- Hostname - {{ - p.healthCheck.hostname - }} -
-
-
-
-
-
- -
-
-
-

Buffering

- - - - - - - - - - - - - - - - - -
- Request Body Bytes - -
-
-
- Max - {{ - p.buffering.maxRequestBodyBytes - }} -
-
-
-
-
-
-
- Mem - {{ - p.buffering.memRequestBodyBytes - }} -
-
-
-
- Response Body Bytes - -
-
-
- Max - {{ - p.buffering.maxResponseBodyBytes - }} -
-
-
-
-
-
-
- Mem - {{ - p.buffering.memResponseBodyBytes - }} -
-
-
-
Retry Expression - {{ - p.buffering.retryExpression - }} -
-
-
-
-
-
-
-
-
-
- Too many backends to display, please add a filter. -
-
-
-
-
-
-
-
- -
-
-
- No providers found. -
-
-
-
-
diff --git a/webui/src/app/components/providers/providers.component.ts b/webui/src/app/components/providers/providers.component.ts deleted file mode 100644 index 37790bf20..000000000 --- a/webui/src/app/components/providers/providers.component.ts +++ /dev/null @@ -1,58 +0,0 @@ -import { Component, OnDestroy, OnInit } from '@angular/core'; -import * as _ from 'lodash'; -import { Subscription, timer } from 'rxjs'; -import { mergeMap, timeInterval } from 'rxjs/operators'; -import { ApiService } from '../../services/api.service'; - -@Component({ - selector: 'app-providers', - templateUrl: 'providers.component.html' -}) -export class ProvidersComponent implements OnInit, OnDestroy { - sub: Subscription; - maxItem: number; - keys: string[]; - previousKeys: string[]; - previousData: any; - providers: any; - tab: string; - keyword: string; - - constructor(private apiService: ApiService) {} - - ngOnInit() { - this.maxItem = 100; - this.keyword = ''; - this.sub = timer(0, 2000) - .pipe( - timeInterval(), - mergeMap(() => this.apiService.fetchProviders()) - ) - .subscribe(data => { - if (!_.isEqual(this.previousData, data)) { - this.previousData = _.cloneDeep(data); - this.providers = data; - - const keys = Object.keys(this.providers); - if (!_.isEqual(this.previousKeys, keys)) { - this.keys = keys; - - // keep current tab or set to the first tab - if (!this.tab || (this.tab && !this.keys.includes(this.tab))) { - this.tab = this.keys[0]; - } - } - } - }); - } - - trackItem(tab): (index, item) => string { - return (index, item): string => tab + '-' + item.id; - } - - ngOnDestroy() { - if (this.sub) { - this.sub.unsubscribe(); - } - } -} diff --git a/webui/src/app/directives/let.directive.ts b/webui/src/app/directives/let.directive.ts deleted file mode 100644 index 36c827501..000000000 --- a/webui/src/app/directives/let.directive.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { Directive, Input, TemplateRef, ViewContainerRef } from '@angular/core'; - -interface LetContext { - appLet: T; -} - -@Directive({ - selector: '[appLet]' -}) -export class LetDirective { - private _context: LetContext = { appLet: null }; - - constructor( - _viewContainer: ViewContainerRef, - _templateRef: TemplateRef> - ) { - _viewContainer.createEmbeddedView(_templateRef, this._context); - } - - @Input() - set appLet(value: T) { - this._context.appLet = value; - } -} diff --git a/webui/src/app/pipes/backend.filter.pipe.ts b/webui/src/app/pipes/backend.filter.pipe.ts deleted file mode 100644 index 0eb0debeb..000000000 --- a/webui/src/app/pipes/backend.filter.pipe.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { Pipe, PipeTransform } from '@angular/core'; - -@Pipe({ - name: 'backendFilter', - pure: false -}) -export class BackendFilterPipe implements PipeTransform { - transform(items: any[], filter: string): any { - if (!items || !filter) { - return items; - } - - const keyword = filter.toLowerCase(); - return items.filter( - d => - d.id.toLowerCase().includes(keyword) || - d.servers.some(r => r.url.toLowerCase().includes(keyword)) - ); - } -} diff --git a/webui/src/app/pipes/frontend.filter.pipe.ts b/webui/src/app/pipes/frontend.filter.pipe.ts deleted file mode 100644 index 566c635bc..000000000 --- a/webui/src/app/pipes/frontend.filter.pipe.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { Pipe, PipeTransform } from '@angular/core'; - -@Pipe({ - name: 'frontendFilter', - pure: false -}) -export class FrontendFilterPipe implements PipeTransform { - transform(items: any[], filter: string): any { - if (!items || !filter) { - return items; - } - - const keyword = filter.toLowerCase(); - return items.filter( - d => - d.id.toLowerCase().includes(keyword) || - d.backend.toLowerCase().includes(keyword) || - d.routes.some(r => r.rule.toLowerCase().includes(keyword)) - ); - } -} diff --git a/webui/src/app/pipes/humanreadable.filter.pipe.spec.ts b/webui/src/app/pipes/humanreadable.filter.pipe.spec.ts deleted file mode 100644 index 40e990136..000000000 --- a/webui/src/app/pipes/humanreadable.filter.pipe.spec.ts +++ /dev/null @@ -1,46 +0,0 @@ -import { HumanReadableFilterPipe } from './humanreadable.filter.pipe'; - -describe('HumanReadableFilterPipe', () => { - const pipe = new HumanReadableFilterPipe(); - - const datatable = [ - { - given: '180000000000', - expected: '180s' - }, - { - given: '4096.0', - expected: '4096ns' - }, - { - given: '7200000000000', - expected: '120m' - }, - { - given: '1337', - expected: '1337ns' - }, - { - given: 'traefik', - expected: 'traefik' - }, - { - given: '-23', - expected: '-23' - }, - { - given: '0', - expected: '0' - } - ]; - - datatable.forEach(item => { - it(item.given + ' should be transformed to ' + item.expected, () => { - expect(pipe.transform(item.given)).toEqual(item.expected); - }); - }); - - it('create an instance', () => { - expect(pipe).toBeTruthy(); - }); -}); diff --git a/webui/src/app/pipes/humanreadable.filter.pipe.ts b/webui/src/app/pipes/humanreadable.filter.pipe.ts deleted file mode 100644 index 0dac2895b..000000000 --- a/webui/src/app/pipes/humanreadable.filter.pipe.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { Pipe, PipeTransform } from '@angular/core'; - -/** - * HumanReadableFilterPipe converts a time period in nanoseconds to a human-readable - * string. - */ -@Pipe({ name: 'humanreadable' }) -export class HumanReadableFilterPipe implements PipeTransform { - transform(value): any { - let result = ''; - const powerOf10 = Math.floor(Math.log10(value)); - - if (powerOf10 > 11) { - result = value / (60 * Math.pow(10, 9)) + 'm'; - } else if (powerOf10 > 9) { - result = value / Math.pow(10, 9) + 's'; - } else if (powerOf10 > 6) { - result = value / Math.pow(10, 6) + 'ms'; - } else if (value > 0) { - result = Math.floor(value) + 'ns'; - } else { - result = value; - } - - return result; - } -} diff --git a/webui/src/app/pipes/keys.pipe.ts b/webui/src/app/pipes/keys.pipe.ts deleted file mode 100644 index 0429745a2..000000000 --- a/webui/src/app/pipes/keys.pipe.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { Pipe, PipeTransform } from '@angular/core'; - -@Pipe({ name: 'keys' }) -export class KeysPipe implements PipeTransform { - transform(value, args: string[]): any { - return Object.keys(value); - } -} diff --git a/webui/src/app/services/api.service.ts b/webui/src/app/services/api.service.ts deleted file mode 100644 index 4cf518afa..000000000 --- a/webui/src/app/services/api.service.ts +++ /dev/null @@ -1,119 +0,0 @@ -import { - HttpClient, - HttpErrorResponse, - HttpHeaders -} from '@angular/common/http'; -import { Injectable } from '@angular/core'; -import { Observable, EMPTY, of } from 'rxjs'; -import { catchError, map, retry } from 'rxjs/operators'; - -export interface ProviderType { - [provider: string]: { - backends: any; - frontends: any; - }; -} - -@Injectable() -export class ApiService { - headers: HttpHeaders; - - constructor(private http: HttpClient) { - this.headers = new HttpHeaders({ - 'Access-Control-Allow-Origin': '*' - }); - } - - fetchVersion(): Observable { - return this.http.get('../api/version', { headers: this.headers }).pipe( - retry(4), - catchError((err: HttpErrorResponse) => { - console.error( - `[version] returned code ${err.status}, body was: ${err.error}` - ); - return EMPTY; - }) - ); - } - - fetchHealthStatus(): Observable { - return this.http.get('../health', { headers: this.headers }).pipe( - retry(2), - catchError((err: HttpErrorResponse) => { - console.error( - `[health] returned code ${err.status}, body was: ${err.error}` - ); - return EMPTY; - }) - ); - } - - fetchProviders(): Observable { - return this.http.get('../api/providers', { headers: this.headers }).pipe( - retry(2), - catchError((err: HttpErrorResponse) => { - console.error( - `[providers] returned code ${err.status}, body was: ${err.error}` - ); - return of({}); - }), - map((data: any): ProviderType => this.parseProviders(data)) - ); - } - - parseProviders(data: any): ProviderType { - return Object.keys(data) - .filter(value => value !== 'acme' && value !== 'ACME') - .reduce((acc, curr) => { - acc[curr] = {}; - - acc[curr].frontends = this.toArray(data[curr].frontends, 'id').map( - frontend => { - frontend.routes = this.toArray(frontend.routes, 'id'); - frontend.errors = this.toArray(frontend.errors, 'id'); - if (frontend.headers) { - frontend.headers.customRequestHeaders = this.toHeaderArray( - frontend.headers.customRequestHeaders - ); - frontend.headers.customResponseHeaders = this.toHeaderArray( - frontend.headers.customResponseHeaders - ); - frontend.headers.sslProxyHeaders = this.toHeaderArray( - frontend.headers.sslProxyHeaders - ); - } - if (frontend.ratelimit && frontend.ratelimit.rateset) { - frontend.ratelimit.rateset = this.toArray( - frontend.ratelimit.rateset, - 'id' - ); - } - return frontend; - } - ); - - acc[curr].backends = this.toArray(data[curr].backends, 'id').map( - backend => { - backend.servers = this.toArray(backend.servers, 'id'); - return backend; - } - ); - - return acc; - }, {}); - } - - toHeaderArray(data: any): any[] { - return Object.keys(data || {}).map(key => ({ - name: key, - value: data[key] - })); - } - - toArray(data: any, fieldKeyName: string): any[] { - return Object.keys(data || {}).map(key => { - data[key][fieldKeyName] = key; - return data[key]; - }); - } -} diff --git a/webui/src/app/services/window.service.ts b/webui/src/app/services/window.service.ts deleted file mode 100644 index b54e993e5..000000000 --- a/webui/src/app/services/window.service.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { Injectable } from '@angular/core'; -import { EventManager } from '@angular/platform-browser'; -import { Subject } from 'rxjs'; - -@Injectable() -export class WindowService { - resize: Subject; - - constructor(private eventManager: EventManager) { - this.resize = new Subject(); - this.eventManager.addGlobalEventListener( - 'window', - 'resize', - (event: UIEvent) => { - this.resize.next(event.target); - } - ); - } -}