1
0
Fork 0

New web ui

This commit is contained in:
Jan Kuri 2018-04-27 13:12:04 +02:00 committed by Traefiker Bot
parent e09d5cb4ec
commit 9c651ae913
105 changed files with 7314 additions and 5514 deletions

View file

@ -0,0 +1,32 @@
import { TestBed, async } from '@angular/core/testing';
import { AppComponent } from './app.component';
describe('AppComponent', () => {
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [
AppComponent
],
}).compileComponents();
}));
it('should create the app', async(() => {
const fixture = TestBed.createComponent(AppComponent);
const app = fixture.debugElement.componentInstance;
expect(app).toBeTruthy();
}));
it(`should have as title 'app'`, async(() => {
const fixture = TestBed.createComponent(AppComponent);
const app = fixture.debugElement.componentInstance;
expect(app.title).toEqual('app');
}));
it('should render title in a h1 tag', async(() => {
const fixture = TestBed.createComponent(AppComponent);
fixture.detectChanges();
const compiled = fixture.debugElement.nativeElement;
expect(compiled.querySelector('h1').textContent).toContain('Welcome to app!');
}));
});

View file

@ -0,0 +1,10 @@
import { Component } from '@angular/core';
@Component({
selector: 'app-root',
template: `
<app-header></app-header>
<router-outlet></router-outlet>
`
})
export class AppComponent { }

View file

@ -0,0 +1,43 @@
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { CommonModule } from '@angular/common';
import { RouterModule } from '@angular/router';
import { HttpClientModule } from '@angular/common/http';
import { FormsModule } from '@angular/forms';
import { ApiService } from './services/api.service';
import { WindowService } from './services/window.service';
import { AppComponent } from './app.component';
import { HeaderComponent } from './components/header/header.component';
import { ProvidersComponent } from './components/providers/providers.component';
import { HealthComponent } from './components/health/health.component';
import { LineChartComponent } from './charts/line-chart/line-chart.component';
import { BarChartComponent } from './charts/bar-chart/bar-chart.component';
import { KeysPipe } from './pipes/keys.pipe';
@NgModule({
declarations: [
AppComponent,
HeaderComponent,
ProvidersComponent,
HealthComponent,
LineChartComponent,
BarChartComponent,
KeysPipe
],
imports: [
BrowserModule,
CommonModule,
HttpClientModule,
FormsModule,
RouterModule.forRoot([
{ path: '', component: ProvidersComponent, pathMatch: 'full' },
{ path: 'status', component: HealthComponent }
])
],
providers: [
ApiService,
WindowService
],
bootstrap: [AppComponent]
})
export class AppModule { }

View file

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

View file

@ -0,0 +1,25 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { BarChartComponent } from './bar-chart.component';
describe('BarChartComponent', () => {
let component: BarChartComponent;
let fixture: ComponentFixture<BarChartComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ BarChartComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(BarChartComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View file

@ -0,0 +1,114 @@
import { Component, Input, OnInit, ElementRef, OnChanges, SimpleChanges } from '@angular/core';
import { WindowService } from '../../services/window.service';
import {
min,
max,
easeLinear,
select,
axisLeft,
axisBottom,
scaleBand,
scaleLinear
} from 'd3';
@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;
bars: any;
width: number;
height: number;
margin = { top: 40, right: 40, bottom: 40, left: 40 };
loading: boolean;
data: 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, 4000);
this.windowService.resize.subscribe(w => this.draw());
}
ngOnChanges(changes: SimpleChanges) {
if (!this.value || !this.svg) {
return;
}
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 {
this.x.domain(this.data.map((d: any) => d.code));
this.y.domain([0, max(this.data, (d: any) => d.count)]);
this.width = this.barChartEl.clientWidth - this.margin.left - this.margin.right;
this.height = this.barChartEl.clientHeight - this.margin.top - this.margin.bottom;
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).tickSize(-this.width));
const bars = this.g.selectAll('.bar').data(this.data);
bars.enter()
.append('rect')
.attr('class', 'bar')
.attr('x', (d: any) => d.code)
.attr('y', (d: any) => 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.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();
}
}

View file

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

View file

@ -0,0 +1,162 @@
import { Component, Input, OnInit, ElementRef, OnChanges, SimpleChanges } from '@angular/core';
import { WindowService } from '../../services/window.service';
import {
range,
scaleTime,
scaleLinear,
min,
max,
curveLinear,
line,
easeLinear,
select,
axisLeft,
axisBottom,
timeSecond,
timeFormat
} from 'd3';
@Component({
selector: 'app-line-chart',
templateUrl: 'line-chart.component.html'
})
export class LineChartComponent implements OnChanges, OnInit {
@Input() value: { count: number, date: string };
lineChartEl: 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.limit = 40;
this.duration = 3000;
this.now = new Date(Date.now() - this.duration);
this.options = {
title: '',
color: '#3A84C5'
};
this.render();
setTimeout(() => this.loading = false, 4000);
this.windowService.resize.subscribe(w => {
if (this.svg) {
const el = this.lineChartEl.querySelector('svg');
el.parentNode.removeChild(el);
this.render();
}
});
}
render() {
this.width = this.lineChartEl.clientWidth - this.margin.left - this.margin.right;
this.height = this.lineChartEl.clientHeight - this.margin.top - this.margin.bottom;
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]);
this.y = scaleLinear().range([this.height, 0]);
this.x.domain([<any>this.now - (this.limit - 2), <any>this.now - this.duration]);
this.y.domain([0, max(this.data, (d: any) => d)]);
this.line = line()
.x((d: any, i: number) => this.x(<any>this.now - (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([<any>this.now - (this.limit - 2) * this.duration, <any>this.now - 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.duration)
.ease(easeLinear)
.call(axisBottom(this.x).tickSize(-this.height).ticks(timeSecond, 5).tickFormat(timeFormat('%H:%M:%S')))
.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(<any>this.now - (this.limit - 1) * this.duration)})`);
this.data.shift();
}
}

View file

@ -0,0 +1,29 @@
<nav class="navbar is-fixed-top" role="navigation" aria-label="main navigation">
<div class="container">
<div class="navbar-menu">
<div class="navbar-brand">
<a class="navbar-item" routerLink="/">
<img src="./assets/images/traefik.logo.svg" alt="Traefik" class="navbar-logo">
</a>
</div>
<div class="navbar-start">
<div class="navbar-menu">
<a class="navbar-item" routerLink="/" routerLinkActive="is-active" [routerLinkActiveOptions]="{ exact: true }">
Providers
</a>
<a class="navbar-item" routerLink="/status" routerLinkActive="is-active">
Health
</a>
</div>
</div>
<div class="navbar-end is-hidden-mobile">
<a class="navbar-item" [href]="releaseLink" target="_blank">
{{ version }} / {{ codename }}
</a>
<a class="navbar-item" href="https://docs.traefik.io" target="_blank">
Documentation
</a>
</div>
</div>
</div>
</nav>

View file

@ -0,0 +1,23 @@
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;
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;
});
}
}

View file

@ -0,0 +1,104 @@
<div class="container">
<div class="content">
<div class="columns is-multiline">
<div class="column is-12">
<div class="content-item">
<div class="content-item-data">
<div class="columns">
<div class="column is-4">
<div class="item-data border-right">
<span class="data-grey">Total Response Time</span>
<span class="data-blue">{{ totalResponseTime }}</span>
</div>
</div>
<div class="column is-4">
<div class="item-data border-right">
<span class="data-grey">Total Code Count</span>
<span class="data-blue">{{ totalCodeCount }}</span>
</div>
</div>
<div class="column is-4">
<div class="item-data">
<span class="data-grey">Uptime Since <br/>{{ uptimeSince }}</span>
<span class="data-blue">{{ uptime }}</span>
</div>
</div>
</div>
</div>
</div>
<div class="content-item">
<div class="content-item-data">
<div class="columns">
<div class="column is-4">
<div class="item-data border-right">
<span class="data-grey">Average Response Time</span>
<span class="data-blue">{{ averageResponseTime }}</span>
</div>
</div>
<div class="column is-4">
<div class="item-data border-right">
<span class="data-grey">Code Count</span>
<span class="data-blue">{{ codeCount }}</span>
</div>
</div>
<div class="column is-4">
<div class="item-data">
<span class="data-grey">PID</span>
<span class="data-blue">{{ pid }}</span>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="column is-12">
<div class="columns">
<div class="column is-6">
<div class="content-item">
<h2>Average Response Time (µs)</h2>
<app-line-chart [value]="chartValue"></app-line-chart>
</div>
</div>
<div class="column is-6">
<div class="content-item">
<h2>Total Status Code Count</h2>
<app-bar-chart [value]="statusCodeValue"></app-bar-chart>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="content" *ngIf="recentErrors">
<div class="content-item">
<h2>Recent HTTP Errors</h2>
<table class="table is-fullwidth">
<tr>
<td>Status</td>
<td>Request</td>
<td>Time</td>
</tr>
<tr *ngFor="let entry of recentErrors">
<td>
<span class="tag is-info">{{ entry.status_code }}</span>&nbsp;<span>{{ entry.status }}</span>
</td>
<td>
<span class="tag">{{ entry.method }}</span>&nbsp;<a>{{ entry.host }}{{ entry.path }}</a>
</td>
<td>
<span>{{ entry.time }}</span>
</td>
</tr>
<tr *ngIf="!recentErrors?.length">
<td colspan="3">
<p class="text-muted text-center">No entries</p>
</td>
</tr>
</table>
</div>
</div>
</div>

View file

@ -0,0 +1,57 @@
import { Component, OnInit, OnDestroy } from '@angular/core';
import { ApiService } from '../../services/api.service';
import { Observable } from 'rxjs/Observable';
import { Subscription } from 'rxjs/Subscription';
import 'rxjs/add/observable/timer';
import 'rxjs/add/operator/timeInterval';
import 'rxjs/add/operator/mergeMap';
import 'rxjs/add/operator/map';
import { format, distanceInWordsStrict, subSeconds } from 'date-fns';
@Component({
selector: 'app-health',
templateUrl: 'health.component.html'
})
export class HealthComponent implements OnInit, OnDestroy {
sub: Subscription;
recentErrors: any;
pid: number;
uptime: string;
uptimeSince: string;
averageResponseTime: string;
totalResponseTime: string;
codeCount: number;
totalCodeCount: number;
chartValue: any;
statusCodeValue: any;
constructor(private apiService: ApiService) { }
ngOnInit() {
this.sub = Observable.timer(0, 3000)
.timeInterval()
.mergeMap(() => this.apiService.fetchHealthStatus())
.subscribe(data => {
if (data) {
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), 'MM/DD/YYYY HH:mm:ss');
this.totalResponseTime = data.total_response_time;
this.averageResponseTime = data.average_response_time;
this.codeCount = data.count;
this.totalCodeCount = data.total_count;
}
});
}
ngOnDestroy() {
if (this.sub) {
this.sub.unsubscribe();
}
}
}

View file

@ -0,0 +1,566 @@
<div class="container">
<div class="content">
<div class="columns is-multiline" *ngIf="keys?.length">
<div class="column is-12">
<div class="search-container">
<span class="icon"><i class="fas fa-search"></i></span>
<input type="text" placeholder="Filter by name or id ..." [(ngModel)]="keyword" (ngModelChange)="filter()">
</div>
<div class="tabs" *ngIf="keys?.length">
<ul>
<li *ngFor="let provider of keys" [class.is-active]="tab === provider" (click)="tab = provider">
<a>{{ provider }}</a>
</li>
</ul>
</div>
<div *ngIf="keys?.length">
<div class="columns">
<!-- Frontends -->
<div class="column is-6">
<h2 class="subtitle"><span class="tag is-info">{{ providers[tab]?.frontends.length }}</span> Frontends</h2>
<div class="message" *ngFor="let p of providers[tab]?.frontends; let i = index;">
<div class="message-header">
<h2>
<div>
<i class="icon fas fa-globe"></i>
<div class="field is-grouped is-grouped-multiline">
<div class="control">
<div class="tags has-addons">
<span class="tag is-info">{{ p.id }}</span>
</div>
</div>
</div>
</div>
<div *ngIf="p.backend">
<div class="field is-grouped is-grouped-multiline">
<div class="control">
<a class="tags has-addons" [href]="'#' + p.backend">
<span class="tag is-light">Backend</span>
<span class="tag is-primary">{{ p.backend }}</span>
</a>
</div>
</div>
</div>
</h2>
</div>
<div class="message-body">
<div class="tabs is-fullwidth is-small is-boxed">
<ul>
<li [class.is-active]="p.section !== 'details'" (click)="p.section = 'main'"><a>Main</a></li>
<li [class.is-active]="p.section === 'details'" (click)="p.section = 'details'"><a>Details</a></li>
</ul>
</div>
<!-- Main -->
<div *ngIf="p.section !== 'details'">
<div *ngIf="p.routes && p.routes.length">
<table class="table is-fullwidth is-hoverable">
<tbody>
<tr>
<td>Route Rule</td>
</tr>
<tr *ngFor="let route of p.routes; let ri = index;">
<td><span class="has-text-grey" title="{{ route.title }}">{{ route.rule }}</span></td>
</tr>
</tbody>
</table>
</div>
<div *ngIf="p.entryPoints && p.entryPoints.length">
<hr>
<div class="columns">
<div class="column is-3">
<h2>Entry Points</h2>
</div>
<div class="column is-9">
<div class="field is-grouped is-grouped-multiline">
<div class="control">
<div class="tags">
<span class="tag is-info" *ngFor="let ep of p.entryPoints; let ri = index;">{{ ep }}</span>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Details -->
<div *ngIf="p.section === 'details'">
<div>
<div class="columns">
<div class="column is-3">
<h2>Misc.</h2>
</div>
<div class="column is-9">
<div class="field is-grouped is-grouped-multiline">
<div class="control">
<div class="tags has-addons">
<span class="tag is-light">Priority</span>
<span class="tag is-info">{{ p.priority }}</span>
</div>
</div>
<div class="control">
<div class="tags has-addons">
<span class="tag is-light">Host Header</span>
<span class="tag is-info">{{ p.passHostHeader }}</span>
</div>
</div>
<div class="control" *ngIf="p.passTLSCert">
<div class="tags has-addons">
<span class="tag is-light">TLS Cert</span>
<span class="tag is-info">{{ p.passTLSCert }}</span>
</div>
</div>
</div>
</div>
</div>
</div>
<div *ngIf="p.basicAuth && p.basicAuth.length">
<hr/>
<h2>Basic Authentication</h2>
<div class="tags padding-5-10">
<span class="tag is-info" *ngFor="let auth of p.basicAuth; let ri = index;">{{ auth }}</span>
</div>
</div>
<div *ngIf="p.errors">
<hr/>
<h2>Error Pages</h2>
<table class="table is-fullwidth is-hoverable">
<tbody>
<tr>
<td>Backend</td>
<td>Query</td>
<td>Status</td>
</tr>
<tr *ngFor="let key of p.errors | keys">
<td><span class="has-text-grey-light">{{ p.errors[key].backend }}</span></td>
<td><span class="has-text-grey">{{ p.errors[key].query }}</span></td>
<td>
<span class="tag is-light" *ngFor="let state of p.errors[key].status">{{ state }}</span>
</td>
</tr>
</tbody>
</table>
</div>
<div *ngIf="p.whiteList">
<hr/>
<div class="columns is-gapless is-multiline is-mobile">
<div class="column is-half">
<h2>Whitelist</h2>
</div>
<div class="column is-half">
<div class="field">
<div class="control">
<div class="tags has-addons">
<span class="tag is-light">useXForwardedFor</span>
<span class="tag is-info">{{ p.whiteList.useXForwardedFor }}</span>
</div>
</div>
</div>
</div>
<div class="column">
<div class="field is-grouped is-grouped-multiline">
<div class="control">
<div class="tags">
<span class="tag is-info" *ngFor="let wlRange of p.whiteList.sourceRange; let ri = index;">{{ wlRange }}</span>
</div>
</div>
</div>
</div>
</div>
</div>
<div *ngIf="p.headers">
<hr/>
<h2>Headers</h2>
<div class="columns is-multiline">
<div class="column is-12" *ngIf="p.headers.customRequestHeaders">
<h2>Custom Request Headers</h2>
<table class="table is-fullwidth is-hoverable">
<tbody>
<tr *ngFor="let key of p.headers.customRequestHeaders | keys">
<td><span class="has-text-grey-light">{{ key }}</span></td>
<td><span class="has-text-grey">{{ p.headers.customRequestHeaders[key] }}</span></td>
</tr>
</tbody>
</table>
</div>
<div class="column is-12" *ngIf="p.headers.customResponseHeaders">
<h2>Custom Response Headers</h2>
<table class="table is-fullwidth is-hoverable">
<tbody>
<tr *ngFor="let key of p.headers.customResponseHeaders | keys">
<td><span class="has-text-grey-light">{{ key }}</span></td>
<td><span class="has-text-grey">{{ p.headers.customResponseHeaders[key] }}</span></td>
</tr>
</tbody>
</table>
</div>
<div class="column is-12">
<h2>Secure</h2>
<table class="table is-fullwidth is-hoverable">
<tbody>
<tr *ngIf="p.headers.browserXssFilter">
<td><span class="has-text-grey">Browser XSS Filter</span></td>
<td><span class="has-text-grey">{{ p.headers.browserXssFilter }}</span></td>
</tr>
<tr *ngIf="p.headers.contentSecurityPolicy">
<td><span class="has-text-grey">Content Security Policy</span></td>
<td><span class="has-text-grey">{{ p.headers.contentSecurityPolicy }}</span></td>
</tr>
<tr *ngIf="p.headers.contentTypeNoSniff">
<td><span class="has-text-grey">Content Type (No sniff)</span></td>
<td><span class="has-text-grey">{{ p.headers.contentTypeNoSniff }}</span></td>
</tr>
<tr *ngIf="p.headers.customFrameOptionsValue">
<td><span class="has-text-grey">Custom Frame Options Value</span></td>
<td><span class="has-text-grey">{{ p.headers.customFrameOptionsValue }}</span></td>
</tr>
<tr *ngIf="p.headers.forceSTSHeader">
<td><span class="has-text-grey">Force STS Header</span></td>
<td><span class="has-text-grey">{{ p.headers.forceSTSHeader }}</span></td>
</tr>
<tr *ngIf="p.headers.frameDeny">
<td><span class="has-text-grey">Frame Deny</span></td>
<td><span class="has-text-grey">{{ p.headers.frameDeny }}</span></td>
</tr>
<tr *ngIf="p.headers.isDevelopment">
<td><span class="has-text-grey">Is Development</span></td>
<td><span class="has-text-grey">{{ p.headers.isDevelopment }}</span></td>
</tr>
<tr *ngIf="p.headers.publicKey">
<td><span class="has-text-grey">Public Key</span></td>
<td><span class="has-text-grey">{{ p.headers.publicKey }}</span></td>
</tr>
<tr *ngIf="p.headers.referrerPolicy">
<td><span class="has-text-grey">Referrer Policy</span></td>
<td><span class="has-text-grey">{{ p.headers.referrerPolicy }}</span></td>
</tr>
<tr *ngIf="p.headers.sslHost">
<td><span class="has-text-grey">SSL Host</span></td>
<td><span class="has-text-grey">{{ p.headers.sslHost }}</span></td>
</tr>
<tr *ngIf="p.headers.sslRedirect">
<td><span class="has-text-grey">SSL Redirect</span></td>
<td><span class="has-text-grey">{{ p.headers.sslRedirect }}</span></td>
</tr>
<tr *ngIf="p.headers.sslTemporaryRedirect">
<td><span class="has-text-grey">SSL Temporary Redirect</span></td>
<td><span class="has-text-grey">{{ p.headers.sslTemporaryRedirect }}</span></td>
</tr>
<tr *ngIf="p.headers.stsIncludeSubdomains">
<td><span class="has-text-grey">STS Include Subdomains</span></td>
<td><span class="has-text-grey">{{ p.headers.stsIncludeSubdomains }}</span></td>
</tr>
<tr *ngIf="p.headers.stsPreload">
<td><span class="has-text-grey">STS Preload</span></td>
<td><span class="has-text-grey">{{ p.headers.stsPreload }}</span></td>
</tr>
<tr *ngIf="p.headers.stsSeconds">
<td><span class="has-text-grey">STS Seconds</span></td>
<td><span class="has-text-grey">{{ p.headers.stsSeconds }}</span></td>
</tr>
</tbody>
</table>
</div>
<div class="column is-12" *ngIf="p.headers.allowedHosts">
<h2>Allowed Hosts</h2>
<div class="tags-list">
<span class="tag is-light" *ngFor="let host of p.headers.allowedHosts">{{ host }}</span>
</div>
</div>
<div class="column is-12" *ngIf="p.headers.sslProxyHeaders">
<h2>SSL Proxy Headers</h2>
<table class="table is-fullwidth is-hoverable">
<tbody>
<tr *ngFor="let key of p.headers.sslProxyHeaders | keys">
<td><span class="has-text-grey-light">{{ key }}</span></td>
<td><span class="has-text-grey">{{ p.headers.sslProxyHeaders[key] }}</span></td>
</tr>
</tbody>
</table>
</div>
<div class="column is-12" *ngIf="p.headers.hostsProxyHeaders">
<h2>Hosts Proxy Headers</h2>
<div class="tags-list">
<span class="tag is-light" *ngFor="let h of p.headers.hostsProxyHeaders">{{ h }}</span>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Backends -->
<div class="column is-6">
<h2 class="subtitle"><span class="tag is-primary">{{ providers[tab]?.backends.length }}</span> Backends</h2>
<div class="message" *ngFor="let p of providers[tab]?.backends; let i = index;">
<div class="message-header">
<h2 [id]="p.id">
<div>
<i class="icon fas fa-server"></i>
<div class="field is-grouped is-grouped-multiline">
<div class="control">
<div class="tags has-addons">
<span class="tag is-primary">{{ p.id }}</span>
</div>
</div>
</div>
</div>
</h2>
</div>
<div class="message-body">
<div class="tabs is-fullwidth is-small is-boxed">
<ul>
<li [class.is-active]="p.section !== 'details'" (click)="p.section = 'main'"><a>Main</a></li>
<li [class.is-active]="p.section === 'details'" (click)="p.section = 'details'"><a>Details</a></li>
</ul>
</div>
<!-- Main -->
<div *ngIf="p.section !== 'details'">
<table class="table is-fullwidth is-hoverable">
<tbody>
<tr>
<td>Server</td>
<td>Weight</td>
</tr>
<tr *ngFor="let server of p.servers; let ri = index;">
<td><a href="{{ server.url }}" title="{{ server.title }}">{{ server.url }}</a></td>
<td><span class="has-text-grey">{{ server.weight }}</span></td>
</tr>
</tbody>
</table>
</div>
<!-- Details -->
<div *ngIf="p.section === 'details'">
<div *ngIf="p.loadBalancer">
<div class="columns">
<div class="column is-3">
<h2>Load Balancer</h2>
</div>
<div class="column is-9">
<div class="field is-grouped is-grouped-multiline">
<div class="control">
<div class="tags has-addons">
<span class="tag is-light">Method</span>
<span class="tag is-info">{{ p.loadBalancer.method }}</span>
</div>
</div>
<div class="control">
<div class="tags has-addons" *ngIf="p.loadBalancer.stickiness || p.loadBalancer.sticky">
<span class="tag is-light">Stickiness</span>
<span class="tag is-info">true</span>
</div>
</div>
<div class="control" *ngIf="p.loadBalancer.stickiness">
<div class="tags has-addons">
<span class="tag is-light">Cookie Name</span>
<span class="tag is-info">{{ p.loadBalancer.stickiness.cookieName }}</span>
</div>
</div>
</div>
</div>
</div>
</div>
<div *ngIf="p.maxConn">
<hr/>
<div class="columns">
<div class="column is-3">
<h2>Max Connections</h2>
</div>
<div class="column is-9">
<div class="field is-grouped is-grouped-multiline">
<div class="control">
<div class="tags has-addons">
<span class="tag is-light">Amount</span>
<span class="tag is-info">{{ p.maxConn.amount }}</span>
</div>
</div>
<div class="control">
<div class="tags has-addons">
<span class="tag is-light">Extractor Function</span>
<span class="tag is-info">{{ p.maxConn.extractorFunc }}</span>
</div>
</div>
</div>
</div>
</div>
</div>
<div *ngIf="p.circuitBreaker">
<hr/>
<div class="columns">
<div class="column is-3">
<h2>Circuit Breaker</h2>
</div>
<div class="column is-9">
<div class="field is-grouped is-grouped-multiline">
<div class="control">
<div class="tags has-addons">
<span class="tag is-light">Expression</span>
<span class="tag is-info">{{ p.circuitBreaker.expression }}</span>
</div>
</div>
</div>
</div>
</div>
</div>
<div *ngIf="p.healthCheck">
<hr/>
<div class="columns">
<div class="column is-3">
<h2>Health Check</h2>
</div>
<div class="column is-9">
<div class="field is-grouped is-grouped-multiline">
<div class="control">
<div class="tags has-addons">
<span class="tag is-light">Path</span>
<span class="tag is-info">{{ p.healthCheck.path }}</span>
</div>
</div>
<div class="control" *ngIf="p.healthCheck.port">
<div class="tags has-addons">
<span class="tag is-light">Port</span>
<span class="tag is-info">{{ p.healthCheck.port }}</span>
</div>
</div>
<div class="control" *ngIf="p.healthCheck.interval">
<div class="tags has-addons">
<span class="tag is-light">Interval</span>
<span class="tag is-info">{{ p.healthCheck.interval }}</span>
</div>
</div>
<div class="control" *ngIf="p.healthCheck.hostname">
<div class="tags has-addons">
<span class="tag is-light">Hostname</span>
<span class="tag is-info">{{ p.healthCheck.hostname }}</span>
</div>
</div>
</div>
</div>
</div>
</div>
<div *ngIf="p.buffering">
<hr>
<div class="columns list-title">
<div class="column is-12">
<h2>Buffering</h2>
</div>
</div>
<div class="list-item">
<div class="columns">
<div class="column is-4">
<span>Request Body Bytes</span>
</div>
<div class="column is-4">
<div class="field is-grouped is-grouped-multiline">
<div class="control">
<div class="tags has-addons">
<span class="tag is-light">Max</span>
<span class="tag is-info">{{ p.buffering.maxRequestBodyBytes }}</span>
</div>
</div>
</div>
</div>
<div class="column is-4">
<div class="field is-grouped is-grouped-multiline">
<div class="control">
<div class="tags has-addons">
<span class="tag is-light">Men</span>
<span class="tag is-info">{{ p.buffering.memRequestBodyBytes }}</span>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="list-item">
<div class="columns">
<div class="column is-4">
<span>Response Body Bytes</span>
</div>
<div class="column is-4">
<div class="field is-grouped is-grouped-multiline">
<div class="control">
<div class="tags has-addons">
<span class="tag is-light">Max</span>
<span class="tag is-info">{{ p.buffering.maxResponseBodyBytes }}</span>
</div>
</div>
</div>
</div>
<div class="column is-4">
<div class="field is-grouped is-grouped-multiline">
<div class="control">
<div class="tags has-addons">
<span class="tag is-light">Men</span>
<span class="tag is-info">{{ p.buffering.memResponseBodyBytes }}</span>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="list-item">
<div class="columns">
<div class="column is-4">
<span>Retry Expression</span>
</div>
<div class="column is-8">
<span class="tag is-info">{{ p.buffering.retryExpression }}</span>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="columns" *ngIf="!keys || !keys.length">
<div class="column is-12">
<div class="notification">
No providers found.
</div>
</div>
</div>
</div>
</div>

View file

@ -0,0 +1,59 @@
import { Component, OnInit, OnDestroy } from '@angular/core';
import { ApiService } from '../../services/api.service';
import { Subscription } from 'rxjs/Subscription';
import { Observable } from 'rxjs/Observable';
import * as _ from "lodash";
@Component({
selector: 'app-providers',
templateUrl: 'providers.component.html'
})
export class ProvidersComponent implements OnInit, OnDestroy {
sub: Subscription;
keys: string[];
data: any;
previousData: any;
providers: any;
tab: string;
keyword: string;
constructor(private apiService: ApiService) { }
ngOnInit() {
this.keyword = '';
this.sub = Observable.timer(0, 2000)
.timeInterval()
.mergeMap(() => this.apiService.fetchProviders())
.subscribe(data => {
if (!_.isEqual(this.previousData, data)) {
this.previousData = _.cloneDeep(data);
this.data = data;
this.providers = data;
this.keys = Object.keys(this.providers);
this.tab = this.keys[0];
}
});
}
filter(): void {
const keyword = this.keyword.toLowerCase();
this.providers = Object.keys(this.data)
.filter(value => value !== 'acme' && value !== 'ACME')
.reduce((acc, curr) => {
return Object.assign(acc, {
[curr]: {
backends: this.data[curr].backends.filter(d => d.id.toLowerCase().includes(keyword)),
frontends: this.data[curr].frontends.filter(d => {
return d.id.toLowerCase().includes(keyword) || d.backend.toLowerCase().includes(keyword);
})
}
});
}, {});
}
ngOnDestroy() {
if (this.sub) {
this.sub.unsubscribe();
}
}
}

View file

@ -1,14 +0,0 @@
'use strict';
var angular = require('angular');
var traefikCoreHealth = 'traefik.core.health';
module.exports = traefikCoreHealth;
angular
.module(traefikCoreHealth, ['ngResource'])
.factory('Health', Health);
/** @ngInject */
function Health($resource) {
return $resource('../health');
}

View file

@ -1,52 +0,0 @@
'use strict';
var angular = require('angular');
var traefikCoreProvider = 'traefik.core.provider';
module.exports = traefikCoreProvider;
angular
.module(traefikCoreProvider, ['ngResource'])
.factory('Providers', Providers);
/** @ngInject */
function Providers($resource, $q) {
const resourceProvider = $resource('../api/providers');
return {
get: function () {
return $q((resolve, reject) => {
resourceProvider.get()
.$promise
.then((rawProviders) => {
delete rawProviders.acme;
delete rawProviders.ACME;
for (let providerName in rawProviders) {
if (rawProviders.hasOwnProperty(providerName)) {
if (!providerName.startsWith('$')) {
// BackEnds mapping
let bckends = rawProviders[providerName].backends || {};
rawProviders[providerName].backends = Object.keys(bckends)
.map(key => {
const goodBackend = bckends[key];
goodBackend.backendId = key;
return goodBackend;
});
// FrontEnds mapping
let frtends = rawProviders[providerName].frontends || {};
rawProviders[providerName].frontends = Object.keys(frtends)
.map(key => {
const goodFrontend = frtends[key];
goodFrontend.frontendId = key;
return goodFrontend;
});
}
}
}
resolve(rawProviders);
})
.catch(reject);
});
}
};
}

View file

@ -1,14 +0,0 @@
'use strict';
var angular = require('angular');
var traefikCoreVersion = 'traefik.core.version';
module.exports = traefikCoreVersion;
angular
.module(traefikCoreVersion, ['ngResource'])
.factory('Version', Version);
/** @ngInject */
function Version($resource) {
return $resource('../api/version');
}

View file

@ -1,34 +0,0 @@
/**
* If you want to override some bootstrap variables, you have to change values here.
* The list of variables are listed here bower_components/bootstrap-sass/assets/stylesheets/bootstrap/_variables.scss
*/
$navbar-inverse-link-color: #5AADBB;
/**
* Do not remove the comments below. It's the markers used by wiredep to inject
* sass dependencies when defined in the bower.json of your dependencies
*/
// bower:scss
// endbower
.browsehappy {
margin: 0.2em 0;
background: #ccc;
color: #000;
padding: 0.2em 0;
}
.thumbnail {
height: 200px;
img.pull-right {
width: 50px;
}
}
/**
* Do not remove the comments below. It's the markers used by gulp-inject to inject
* all your sass files automatically
*/
// injector
// endinjector

View file

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

View file

@ -1,238 +0,0 @@
'use strict';
var d3 = require('d3'),
moment = require('moment'),
HttpStatus = require('http-status-codes');
/** @ngInject */
function HealthController($scope, $interval, $log, Health) {
var vm = this;
vm.graph = {
averageResponseTime: {},
totalStatusCodeCount: {}
};
vm.graph.totalStatusCodeCount.options = {
"chart": {
type: 'discreteBarChart',
tooltip: {
contentGenerator: function (e) {
var d = e.data;
return d.label + " " + d.text;
}
},
height: 200,
margin: {
top: 20,
right: 20,
bottom: 40,
left: 55
},
x: function (d) {
return d.label;
},
y: function (d) {
return d.value;
},
showValues: true,
valueFormat: function (d) {
return d3.format('d')(d);
},
yAxis: {
axisLabelDistance: 30,
tickFormat: d3.format('d')
}
},
"title": {
"enable": true,
"text": "Total Status Code Count",
"css": {
"textAlign": "center"
}
}
};
vm.graph.totalStatusCodeCount.data = [
{
key: "Total Status Code Count",
values: [
{
"label": "200",
"value": 0
}
]
}
];
/**
* Update Total Status Code Count graph
*
* @param {Object} totalStatusCodeCount Object from API
*/
function updateTotalStatusCodeCount(totalStatusCodeCount) {
// extract values
vm.graph.totalStatusCodeCount.data[0].values = [];
for (var code in totalStatusCodeCount) {
if (totalStatusCodeCount.hasOwnProperty(code)) {
var statusCodeText = "";
try {
statusCodeText = HttpStatus.getStatusText(code);
} catch (e) {
// HttpStatus.getStatusText throws error on unknown codes
statusCodeText = "Unknown status code";
}
vm.graph.totalStatusCodeCount.data[0].values.push({
label: code,
value: totalStatusCodeCount[code],
text: statusCodeText
});
}
}
// Update Total Status Code Count graph render
if (vm.graph.totalStatusCodeCount.api) {
vm.graph.totalStatusCodeCount.api.update();
} else {
$log.error('fail');
}
}
vm.graph.averageResponseTime.options = {
chart: {
type: 'lineChart',
height: 200,
margin: {
top: 20,
right: 40,
bottom: 40,
left: 55
},
x: function (d) {
return d.x;
},
y: function (d) {
return d.y;
},
useInteractiveGuideline: true,
xAxis: {
tickFormat: function (d) {
return d3.time.format('%X')(new Date(d));
}
},
yAxis: {
tickFormat: function (d) {
return d3.format(',.1f')(d);
}
},
forceY: [0., 1.], // This prevents the chart from showing -1 on Oy when all the input data points
// have y = 0. It won't disable the automatic adjustment of the max value.
duration: 0 // Bug: Markers will not be drawn if you set this to some other value...
},
"title": {
"enable": true,
"text": "Average response time",
"css": {
"textAlign": "center"
}
}
};
var initialPoint = {
x: Date.now() - 3000,
y: 0
};
vm.graph.averageResponseTime.data = [
{
values: [initialPoint],
key: 'Average response time (ms)',
type: 'line',
color: '#2ca02c'
}
];
/**
* Update average response time graph
*
* @param {Number} x Coordinate X
* @param {Number} y Coordinate Y
*/
function updateAverageResponseTimeGraph(x, y) {
// x multiply 1000 by because unix time is in seconds and JS Date are in milliseconds
var data = {
x: x * 1000,
y: y * 1000
};
vm.graph.averageResponseTime.data[0].values.push(data);
// limit graph entries
if (vm.graph.averageResponseTime.data[0].values.length > 100) {
vm.graph.averageResponseTime.data[0].values.shift();
}
// Update Average Response Time graph render
if (vm.graph.averageResponseTime.api) {
vm.graph.averageResponseTime.api.update();
}
}
/**
* Format the timestamp as "x seconds ago", etc.
*
* @param {String} t Timestamp returned from the API
*/
function formatTimestamp(t) {
return moment(t, "YYYY-MM-DDTHH:mm:ssZ").fromNow();
}
/**
* Load all graph's datas
*
* @param {Object} health Health data from server
*/
function loadData(health) {
// Load datas and update Average Response Time graph render
updateAverageResponseTimeGraph(health.unixtime, health.average_response_time_sec);
// Load datas and update Total Status Code Count graph render
updateTotalStatusCodeCount(health.total_status_code_count);
// Format the timestamps
if (health.recent_errors) {
angular.forEach(health.recent_errors, function(i) {
i.time_formatted = formatTimestamp(i.time);
});
}
// set data's view
vm.health = health;
}
/**
* Action when load datas failed
*
* @param {Object} error Error state object
*/
function erroData(error) {
vm.health = {};
$log.error(error);
}
// first load
Health.get(loadData, erroData);
// Auto refresh data
var intervalId = $interval(function () {
Health.get(loadData, erroData);
}, 3000);
// Stop auto refresh when page change
$scope.$on('$destroy', function () {
$interval.cancel(intervalId);
});
}
module.exports = HealthController;

View file

@ -1,73 +0,0 @@
<div>
<h1 class="text-danger">
<span class="glyphicon glyphicon-heart" aria-hidden="true"></span> Health
</h1>
<div class="row">
<div class="col-md-6">
<div>
<nvd3 options="healthCtrl.graph.averageResponseTime.options" data="healthCtrl.graph.averageResponseTime.data" api="healthCtrl.graph.averageResponseTime.api"></nvd3>
</div>
<ul class="list-group">
<li class="list-group-item">
<span>Total response time :</span><span class="badge">{{healthCtrl.health.total_response_time}}</span>
</li>
</ul>
<ul class="list-group">
<li class="list-group-item">
<span>PID :</span><span class="badge">{{healthCtrl.health.pid}}</span>
</li>
<li class="list-group-item">
<span>Uptime :</span><span class="badge">{{healthCtrl.health.uptime}}</span>
</li>
</ul>
</div>
<div class="col-md-6">
<div>
<nvd3 options="healthCtrl.graph.totalStatusCodeCount.options" data="healthCtrl.graph.totalStatusCodeCount.data" api="healthCtrl.graph.totalStatusCodeCount.api"></nvd3>
</div>
<ul class="list-group">
<li class="list-group-item">
<span>Total count :</span><span class="badge">{{healthCtrl.health.total_count}}</span>
</li>
<li class="list-group-item">
<span>Count :</span><span class="badge">{{healthCtrl.health.count}}</span>
</li>
</ul>
</div>
</div>
<div ng-if="healthCtrl.health.recent_errors">
<h3>Recent HTTP Errors</h3>
<table class="table table-striped table-bordered">
<tr>
<td>Status</td>
<td>Request</td>
<td>Time</td>
</tr>
<tr ng-repeat="entry in healthCtrl.health.recent_errors"
ng-class="{'text-danger': entry.status_code >= 500}">
<td>{{ entry.status_code }} &mdash; {{ entry.status }}</td>
<td>
<span class="badge">{{ entry.method }}</span>
&nbsp;
{{ entry.host }}{{ entry.path }}
</td>
<td>
<span title="{{ entry.time }}">
{{ entry.time_formatted }}
</span>
</td>
</tr>
<tr ng-if="healthCtrl.health.recent_errors.length == 0">
<td colspan="3">
<p class="text-muted text-center">No entries</p>
</td>
</tr>
</table>
</div>
</div>

View file

@ -1,24 +0,0 @@
'use strict';
var angular = require('angular');
var traefikCoreHealth = require('../../core/health.resource');
var HealthController = require('./health.controller');
var traefikSectionHealth = 'traefik.section.health';
module.exports = traefikSectionHealth;
angular
.module(traefikSectionHealth, [traefikCoreHealth])
.controller('HealthController', HealthController)
.config(config);
/** @ngInject */
function config($stateProvider) {
$stateProvider.state('health', {
url: '/health',
template: require('./health.html'),
controller: 'HealthController',
controllerAs: 'healthCtrl'
});
}

View file

@ -1,20 +0,0 @@
'use strict';
function backendMonitor() {
return {
restrict: 'EA',
template: require('./backend-monitor.html'),
controller: BackendMonitorController,
controllerAs: 'backendCtrl',
bindToController: true,
scope: {
backend: '='
}
};
}
function BackendMonitorController() {
// Nothing
}
module.exports = backendMonitor;

View file

@ -1,23 +0,0 @@
<div class="panel panel-success">
<div class="panel-heading">
<strong><span class="glyphicon glyphicon-tasks" aria-hidden="true"></span> {{backendCtrl.backend.backendId}}</strong>
</div>
<div class="panel-body">
<table class="panel-table__servers table table-striped table-hover">
<tr>
<td><em>Server</em></td>
<td><em>URL</em></td>
<td><em>Weight</em></td>
</tr>
<tr data-ng-repeat="(serverId, server) in backendCtrl.backend.servers">
<td>{{serverId}}</td>
<td><code><a data-ng-href="{{server.url}}">{{server.url}}</a></code></td>
<td>{{server.weight}}</td>
</tr>
</table>
</div>
<div class="panel-footer" data-ng-show="backendCtrl.backend.loadBalancer || backendCtrl.backend.circuitBreaker">
<span data-ng-show="backendCtrl.backend.loadBalancer" class="label label-success">Load Balancer: {{backendCtrl.backend.loadBalancer.method}}</span>
<span data-ng-show="backendCtrl.backend.circuitBreaker" class="label label-success">Circuit Breaker: {{backendCtrl.backend.circuitBreaker.expression}}</span>
</div>
</div>

View file

@ -1,10 +0,0 @@
'use strict';
var angular = require('angular');
var backendMonitor = require('./backend-monitor.directive');
var traefikBackendMonitor = 'traefik.section.providers.backend-monitor';
module.exports = traefikBackendMonitor;
angular
.module(traefikBackendMonitor, [])
.directive('backendMonitor', backendMonitor);

View file

@ -1,20 +0,0 @@
'use strict';
function frontendMonitor() {
return {
restrict: 'EA',
template: require('./frontend-monitor.html'),
controller: FrontendMonitorController,
controllerAs: 'frontendCtrl',
bindToController: true,
scope: {
frontend: '='
}
};
}
function FrontendMonitorController() {
// Nothing
}
module.exports = frontendMonitor;

View file

@ -1,29 +0,0 @@
<div class="panel panel-warning">
<div class="panel-heading">
<strong><span class="glyphicon glyphicon-globe" aria-hidden="true"></span> {{frontendCtrl.frontend.frontendId}}</strong>
</div>
<div class="panel-body">
<table class="panel-table__routes table table-striped table-hover">
<tr>
<td><em>Route</em></td>
<td><em>Rule</em></td>
</tr>
<tr data-ng-repeat="(routeId, route) in frontendCtrl.frontend.routes">
<td>{{routeId}}</td>
<td><code>{{route.rule}}</code></td>
</tr>
</table>
</div>
<div data-bg-show="frontendCtrl.frontend.backend" class="panel-footer">
<span data-ng-repeat="entryPoint in frontendCtrl.frontend.entryPoints">
<span class="label label-primary">{{entryPoint}}</span><span data-ng-hide="$last">&nbsp;</span>
</span>
<span data-ng-show="frontendCtrl.frontend.redirect" class="label label-success">Redirect to {{frontendCtrl.frontend.redirect}}</span>
<span class="label label-warning" role="button" data-toggle="collapse" href="#{{frontendCtrl.frontend.backend}}" aria-expanded="false">Backend:{{frontendCtrl.frontend.backend}}</span>
<span data-ng-show="frontendCtrl.frontend.passHostHeader" class="label label-warning">PassHostHeader</span>
<span data-ng-repeat="whitelistSourceRange in frontendCtrl.frontend.whitelistSourceRange">
<span class="label label-warning">Whitelist {{ whitelistSourceRange }}</span>
</span>
<span data-ng-show="frontendCtrl.frontend.priority" class="label label-warning">Priority:{{frontendCtrl.frontend.priority}}</span>
</div>
</div>

View file

@ -1,10 +0,0 @@
'use strict';
var angular = require('angular');
var frontendMonitor = require('./frontend-monitor.directive');
var traefikFrontendMonitor = 'traefik.section.providers.frontend-monitor';
module.exports = traefikFrontendMonitor;
angular
.module(traefikFrontendMonitor, [])
.directive('frontendMonitor', frontendMonitor);

View file

@ -1,33 +0,0 @@
'use strict';
var _ = require('lodash');
/** @ngInject */
function ProvidersController($scope, $interval, $log, Providers) {
const vm = this;
function loadProviders() {
Providers
.get()
.then(providers => {
if (!_.isEqual(vm.previousProviders, providers)) {
vm.providers = providers;
vm.previousProviders = _.cloneDeep(providers);
}
})
.catch(error => {
vm.providers = {};
$log.error(error);
});
}
loadProviders();
const intervalId = $interval(loadProviders, 2000);
$scope.$on('$destroy', function () {
$interval.cancel(intervalId);
});
}
module.exports = ProvidersController;

View file

@ -1,22 +0,0 @@
<div>
<div><input type="text" data-ng-model="providersCtrl.providerFilter" placeholder="Filter" class="form-control"></div>
<br>
<uib-tabset>
<uib-tab data-ng-repeat="(providerId, provider) in providersCtrl.providers" heading="{{providerId}}">
<div class="row tabset-row__providers">
<div class="col-md-6">
<div data-ng-repeat="frontend in provider.frontends | filter: providersCtrl.providerFilter ">
<frontend-monitor data-provider-id="providerId" data-frontend="frontend"></frontend-monitor>
</div>
</div>
<div class="col-md-6">
<div data-ng-repeat="backend in provider.backends | filter: providersCtrl.providerFilter">
<backend-monitor data-provider-id="providerId" data-backend="backend"></backend-monitor>
</div>
</div>
</div>
</uib-tab>
</uib-tabset>
</div>

View file

@ -1,30 +0,0 @@
'use strict';
var angular = require('angular');
var traefikCoreProvider = require('../../core/providers.resource');
var ProvidersController = require('./providers.controller');
var traefikBackendMonitor = require('./backend-monitor/backend-monitor.module');
var traefikFrontendMonitor = require('./frontend-monitor/frontend-monitor.module');
var traefikSectionProviders = 'traefik.section.providers';
module.exports = traefikSectionProviders;
angular
.module(traefikSectionProviders, [
traefikCoreProvider,
traefikBackendMonitor,
traefikFrontendMonitor
])
.config(config)
.controller('ProvidersController', ProvidersController);
/** @ngInject */
function config($stateProvider) {
$stateProvider.state('provider', {
url: '/',
template: require('./providers.html'),
controller: 'ProvidersController',
controllerAs: 'providersCtrl'
});
}

View file

@ -1,24 +0,0 @@
'use strict';
var angular = require('angular');
require('nvd3');
var ndv3 = require('angular-nvd3');
var traefikSectionHealth = require('./health/health.module');
var traefikSectionProviders = require('./providers/providers.module');
var traefikSection = 'traefik.section';
module.exports = traefikSection;
angular
.module(traefikSection, [
'ui.router',
'ui.bootstrap',
ndv3,
traefikSectionProviders,
traefikSectionHealth
])
.config(config);
/** @ngInject */
function config($urlRouterProvider) {
$urlRouterProvider.otherwise('/');
}

View file

@ -0,0 +1,88 @@
import { Injectable } from '@angular/core';
import { HttpClient, HttpHeaders, HttpErrorResponse } from '@angular/common/http';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/operator/map';
import 'rxjs/add/observable/empty';
import 'rxjs/add/observable/of';
import 'rxjs/add/operator/catch';
import 'rxjs/add/operator/retry';
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<any> {
return this.http.get(`/api/version`, { headers: this.headers })
.retry(4)
.catch((err: HttpErrorResponse) => {
console.error(`[version] returned code ${err.status}, body was: ${err.error}`);
return Observable.empty<any>();
});
}
fetchHealthStatus(): Observable<any> {
return this.http.get(`/health`, { headers: this.headers })
.retry(2)
.catch((err: HttpErrorResponse) => {
console.error(`[health] returned code ${err.status}, body was: ${err.error}`);
return Observable.empty<any>();
});
}
fetchProviders(): Observable<any> {
return this.http.get(`/api/providers`, { headers: this.headers })
.retry(2)
.catch((err: HttpErrorResponse) => {
console.error(`[providers] returned code ${err.status}, body was: ${err.error}`);
return Observable.of<any>({});
})
.map(this.parseProviders);
}
parseProviders(data: any): ProviderType {
return Object.keys(data)
.filter(value => value !== 'acme' && value !== 'ACME')
.reduce((acc, curr) => {
acc[curr] = {
backends: Object.keys(data[curr].backends || {}).map(key => {
data[curr].backends[key].id = key;
data[curr].backends[key].servers = Object.keys(data[curr].backends[key].servers || {}).map(server => {
return {
title: server,
url: data[curr].backends[key].servers[server].url,
weight: data[curr].backends[key].servers[server].weight
};
});
return data[curr].backends[key];
}),
frontends: Object.keys(data[curr].frontends || {}).map(key => {
data[curr].frontends[key].id = key;
data[curr].frontends[key].routes = Object.keys(data[curr].frontends[key].routes || {}).map(route => {
return {
title: route,
rule: data[curr].frontends[key].routes[route].rule
};
});
return data[curr].frontends[key];
}),
};
return acc;
}, {});
}
}

View file

@ -0,0 +1,17 @@
import { Injectable } from '@angular/core';
import { EventManager } from '@angular/platform-browser';
import { Subject } from 'rxjs/Subject';
@Injectable()
export class WindowService {
resize: Subject<any>;
constructor(private eventManager: EventManager) {
this.resize = new Subject();
this.eventManager.addGlobalEventListener('window', 'resize', this.onResize);
}
onResize = (event: UIEvent) => {
this.resize.next(event.target);
}
}

View file

@ -1,34 +0,0 @@
@font-face {
font-family: 'charterregular';
src: url('./assets/fonts/charter_regular-webfont.eot');
src: url('./assets/fonts/charter_regular-webfont.eot?#iefix') format('embedded-opentype'),
url('./assets/fonts/charter_regular-webfont.woff') format('woff');
font-weight: normal;
font-style: normal;
}
.traefik-blue {
color: #00B1FF;
}
.traefik-text {
font-family: 'charterregular', Arial, sans-serif;
}
.panel-body .panel-table__servers,
.panel-body .panel-table__routes {
margin-bottom: 0;
}
.tabset-row__providers {
margin-top: 3rem;
}
table {
table-layout: fixed;
}
td, th {
word-wrap: break-word;
}

View file

@ -1,10 +0,0 @@
'use strict';
/** @ngInject */
function VersionController($scope, Version) {
Version.get(function (version) {
$scope.version = version;
});
}
module.exports = VersionController;

View file

@ -1,11 +0,0 @@
'use strict';
var angular = require('angular');
var traefikCoreVersion = require('../core/version.resource');
var VersionController = require('./version.controller');
var traefikVersion = 'traefik.version';
module.exports = traefikVersion;
angular
.module(traefikVersion, [traefikCoreVersion])
.controller('VersionController', VersionController);