Compare commits

..

26 Commits

Author SHA1 Message Date
SALLEYRON Julien
e3671cbb04 Prepare release 1.6.2 2018-05-22 17:06:04 +02:00
Ludovic Fernandez
a525d02cc5 Force to use ACME v02 endpoint. 2018-05-22 16:08:03 +02:00
Ludovic Fernandez
1cc1a4e6e2 WebUI typo into the buffering section. 2018-05-22 12:28:03 +02:00
SALLEYRON Julien
3f0af3fe09 No template parsing on traefik configuration file 2018-05-22 12:02:03 +02:00
Ludovic Fernandez
bc26d9f0de Enhance Load-balancing method validation log. 2018-05-22 09:20:03 +02:00
Florian Woerner
0ba28bbc8b Update caServer to letsencrypt one in examples 2018-05-18 18:16:03 +02:00
Michael Ermer
550184275a Add basicAuth example for KV 2018-05-18 17:54:03 +02:00
DeamonMV
c376083ecb Add command for basic auth with Docker Compose 2018-05-18 17:34:04 +02:00
Ludovic Fernandez
1db5fcf200 Removes ambiguity with the word 'default' 2018-05-18 17:12:03 +02:00
Ludovic Fernandez
16b2555ab3 Fix error pages content. 2018-05-18 16:38:03 +02:00
Joni Collinge
d9a5258f40 Update docs to match SF provider labels 2018-05-17 11:04:03 +02:00
Daniel Tomcej
190ebbed27 Update docs to reflect Provider wording 2018-05-16 19:48:03 +02:00
Ludovic Fernandez
a0872c9e31 Route rules overlaps in UI 2018-05-16 13:44:03 +02:00
Daniel Tomcej
f5b306e7ff Add redirect-permanent to kubernetes template 2018-05-16 10:46:03 +02:00
Ludovic Fernandez
7a1feb3c51 fix: acme errors management. 2018-05-15 17:28:02 +02:00
Ludovic Fernandez
1e8df9f245 Prepare release v1.6.1 2018-05-14 21:08:03 +02:00
Ludovic Fernandez
b72937e8fb Fix webui 2018-05-14 19:46:03 +02:00
Ludovic Fernandez
67847c3117 Error when accesslog and error pages 2018-05-14 10:38:03 +02:00
Ludovic Fernandez
a2a0c80acb Fix segment backend name 2018-05-14 10:18:03 +02:00
Ludovic Fernandez
b3fd06fb45 Add missing deprecation info in CLI help. 2018-05-14 09:52:03 +02:00
Ludovic Fernandez
c5db8d903c Enhance entry point TLS CLI reference. 2018-05-13 17:12:03 +02:00
Daniel Tomcej
8fcd242494 Add Documentation update for Kubernetes Ingress 2018-05-11 12:52:03 +02:00
Blake Patton
ebd9af900e Changed "is could" to should 2018-05-09 17:48:03 +02:00
Michael
b02381c2d5 Fix wrong tag in forward span in tracing middleware 2018-05-08 12:00:03 +02:00
Ludovic Fernandez
9b199ea756 fix: 1.6 change log. 2018-05-04 16:08:03 +02:00
Ludovic Fernandez
ec3b913ee4 fix: change log v1.6.0 2018-05-01 00:46:03 +02:00
85 changed files with 1429 additions and 1201 deletions

View File

@@ -1,20 +1,55 @@
# Change Log
## [v1.6.2](https://github.com/containous/traefik/tree/v1.6.2) (2018-05-22)
[All Commits](https://github.com/containous/traefik/compare/v1.6.1...v1.6.2)
**Bug fixes:**
- **[acme]** fix: acme errors management. ([#3329](https://github.com/containous/traefik/pull/3329) by [ldez](https://github.com/ldez))
- **[acme]** Force to use ACME v02 endpoint. ([#3358](https://github.com/containous/traefik/pull/3358) by [ldez](https://github.com/ldez))
- **[file]** No template parsing on traefik configuration file ([#3347](https://github.com/containous/traefik/pull/3347) by [Juliens](https://github.com/Juliens))
- **[k8s]** Add redirect-permanent to kubernetes template ([#3332](https://github.com/containous/traefik/pull/3332) by [dtomcej](https://github.com/dtomcej))
- **[logs]** Enhance Load-balancing method validation log. ([#3361](https://github.com/containous/traefik/pull/3361) by [ldez](https://github.com/ldez))
- **[middleware]** Fix error pages content. ([#3337](https://github.com/containous/traefik/pull/3337) by [ldez](https://github.com/ldez))
- **[webui]** Route rules overlaps in UI ([#3333](https://github.com/containous/traefik/pull/3333) by [ldez](https://github.com/ldez))
- **[webui]** WebUI typo into the buffering section. ([#3363](https://github.com/containous/traefik/pull/3363) by [ldez](https://github.com/ldez))
**Documentation:**
- **[acme]** Update caServer to letsencrypt one in examples ([#3339](https://github.com/containous/traefik/pull/3339) by [woernfl](https://github.com/woernfl))
- **[docker]** Add command for basic auth with Docker Compose ([#3346](https://github.com/containous/traefik/pull/3346) by [DeamonMV](https://github.com/DeamonMV))
- **[docker]** Removes ambiguity with the word 'default' ([#3344](https://github.com/containous/traefik/pull/3344) by [ldez](https://github.com/ldez))
- **[kv]** Add basicAuth example for KV ([#3274](https://github.com/containous/traefik/pull/3274) by [MichaelErmer](https://github.com/MichaelErmer))
- **[provider]** Update docs to reflect Provider wording ([#3331](https://github.com/containous/traefik/pull/3331) by [dtomcej](https://github.com/dtomcej))
- **[servicefabric]** Update docs to match SF provider labels ([#3335](https://github.com/containous/traefik/pull/3335) by [jjcollinge](https://github.com/jjcollinge))
## [v1.6.1](https://github.com/containous/traefik/tree/v1.6.1) (2018-05-14)
[All Commits](https://github.com/containous/traefik/compare/v1.6.0...v1.6.1)
**Bug fixes:**
- **[acme]** Add missing deprecation info in CLI help. ([#3291](https://github.com/containous/traefik/pull/3291) by [ldez](https://github.com/ldez))
- **[docker,marathon,rancher]** Fix segment backend name ([#3317](https://github.com/containous/traefik/pull/3317) by [ldez](https://github.com/ldez))
- **[logs,middleware]** Error when accesslog and error pages ([#3314](https://github.com/containous/traefik/pull/3314) by [ldez](https://github.com/ldez))
- **[middleware,tracing]** Fix wrong tag in forward span in tracing middleware ([#3279](https://github.com/containous/traefik/pull/3279) by [mmatur](https://github.com/mmatur))
- **[webui]** Fix webui ([#3299](https://github.com/containous/traefik/pull/3299) by [ldez](https://github.com/ldez))
**Documentation:**
- **[k8s]** Add Documentation update for Kubernetes Ingress ([#3294](https://github.com/containous/traefik/pull/3294) by [dtomcej](https://github.com/dtomcej))
- **[tls]** Enhance entry point TLS CLI reference. ([#3290](https://github.com/containous/traefik/pull/3290) by [ldez](https://github.com/ldez))
- Typo in documentation ([#3261](https://github.com/containous/traefik/pull/3261) by [blakethepatton](https://github.com/blakethepatton))
## [v1.6.0](https://github.com/containous/traefik/tree/v1.6.0) (2018-04-30)
[All Commits](https://github.com/containous/traefik/compare/v1.5.0-rc1...v1.6.0)
[Commits](https://github.com/containous/traefik/compare/v1.5.0-rc1...v1.6.0)
[Commits pre RC](https://github.com/containous/traefik/compare/v1.5.0-rc1...v1.6.0-rc1)
**Enhancements:**
- **[acme]** Create backup file during migration from ACME V1 to ACME V2 ([#3191](https://github.com/containous/traefik/pull/3191) by [nmengin](https://github.com/nmengin))
- **[acme]** Generate wildcard certificate with SANs in ACME ([#3167](https://github.com/containous/traefik/pull/3167) by [nmengin](https://github.com/nmengin))
- **[acme]** ACME V2 Integration ([#3063](https://github.com/containous/traefik/pull/3063) by [nmengin](https://github.com/nmengin))
- **[acme]** Create ACME Provider ([#2889](https://github.com/containous/traefik/pull/2889) by [nmengin](https://github.com/nmengin))
- **[acme]** Update Lego (Gandi API v5, cloudxns, ...) ([#2844](https://github.com/containous/traefik/pull/2844) by [ldez](https://github.com/ldez))
- **[acme]** Simplify storing renewed acme certificate ([#2614](https://github.com/containous/traefik/pull/2614) by [ferhatelmas](https://github.com/ferhatelmas))
- **[acme]** ACME V2 Integration ([#3063](https://github.com/containous/traefik/pull/3063) by [nmengin](https://github.com/nmengin))
- **[acme]** Bump Lego Version for GoDaddy DNS Provider ([#2482](https://github.com/containous/traefik/pull/2482) by [sjawhar](https://github.com/sjawhar))
- **[acme]** Delete TLS-SNI-01 challenge from ACME ([#2971](https://github.com/containous/traefik/pull/2971) by [nmengin](https://github.com/nmengin))
- **[acme]** Simplify storing renewed acme certificate ([#2614](https://github.com/containous/traefik/pull/2614) by [ferhatelmas](https://github.com/ferhatelmas))
- **[acme]** Update Lego (Gandi API v5, cloudxns, ...) ([#2844](https://github.com/containous/traefik/pull/2844) by [ldez](https://github.com/ldez))
- **[acme]** Remove acme provider dependency in server ([#3225](https://github.com/containous/traefik/pull/3225) by [Juliens](https://github.com/Juliens))
- **[acme]** Create backup file during migration from ACME V1 to ACME V2 ([#3191](https://github.com/containous/traefik/pull/3191) by [nmengin](https://github.com/nmengin))
- **[acme]** Generate wildcard certificate with SANs in ACME ([#3167](https://github.com/containous/traefik/pull/3167) by [nmengin](https://github.com/nmengin))
- **[api,cluster]** Added cluster/leader endpoint ([#3009](https://github.com/containous/traefik/pull/3009) by [aantono](https://github.com/aantono))
- **[api,cluster]** Improved cluster api to include the current leader node ([#3100](https://github.com/containous/traefik/pull/3100) by [aantono](https://github.com/aantono))
- **[authentication]** Forward Authentication: add X-Forwarded-Uri ([#2398](https://github.com/containous/traefik/pull/2398) by [sebastianbauer](https://github.com/sebastianbauer))
- **[boltdb,consul,etcd,kv,zk]** Add all available configuration to KV Backend ([#2652](https://github.com/containous/traefik/pull/2652) by [ldez](https://github.com/ldez))
- **[boltdb,consul,etcd,kv,zk]** homogenization of templates: KV ([#2661](https://github.com/containous/traefik/pull/2661) by [ldez](https://github.com/ldez))
@@ -22,76 +57,72 @@
- **[consul,consulcatalog]** Homogenization of templates: Consul Catalog ([#2668](https://github.com/containous/traefik/pull/2668) by [ldez](https://github.com/ldez))
- **[consul,consulcatalog]** Split consul and consul catalog. ([#2655](https://github.com/containous/traefik/pull/2655) by [ldez](https://github.com/ldez))
- **[consulcatalog,ecs,mesos]** Factorize labels managements. ([#3099](https://github.com/containous/traefik/pull/3099) by [ldez](https://github.com/ldez))
- **[consulcatalog]** TLS Support for ConsulCatalog ([#2900](https://github.com/containous/traefik/pull/2900) by [mmatur](https://github.com/mmatur))
- **[consulcatalog]** Check for endpoints while detecting Consul service changes ([#2882](https://github.com/containous/traefik/pull/2882) by [caseycs](https://github.com/caseycs))
- **[consulcatalog]** TLS Support for ConsulCatalog ([#2900](https://github.com/containous/traefik/pull/2900) by [mmatur](https://github.com/mmatur))
- **[consulcatalog]** Add all available tags to Consul Catalog Backend ([#2646](https://github.com/containous/traefik/pull/2646) by [ldez](https://github.com/ldez))
- **[docker,docker/swarm]** Fix support for macvlan driver in docker provider ([#2827](https://github.com/containous/traefik/pull/2827) by [mmatur](https://github.com/mmatur))
- **[docker,marathon,rancher]** Segments Labels: Rancher & Marathon ([#3073](https://github.com/containous/traefik/pull/3073) by [ldez](https://github.com/ldez))
- **[docker]** Custom headers by service labels for docker backends ([#2514](https://github.com/containous/traefik/pull/2514) by [Tiscs](https://github.com/Tiscs))
- **[docker]** Homogenization of templates: Docker ([#2659](https://github.com/containous/traefik/pull/2659) by [ldez](https://github.com/ldez))
- **[docker]** Add all available labels to Docker Backend ([#2584](https://github.com/containous/traefik/pull/2584) by [ldez](https://github.com/ldez))
- **[docker]** Homogenization of templates: Docker ([#2659](https://github.com/containous/traefik/pull/2659) by [ldez](https://github.com/ldez))
- **[docker]** Custom headers by service labels for docker backends ([#2514](https://github.com/containous/traefik/pull/2514) by [Tiscs](https://github.com/Tiscs))
- **[docker]** Segment labels: Docker ([#3055](https://github.com/containous/traefik/pull/3055) by [ldez](https://github.com/ldez))
- **[dynamodb,ecs]** Upgrade AWS SKD to version v1.13.1 ([#2908](https://github.com/containous/traefik/pull/2908) by [mmatur](https://github.com/mmatur))
- **[ecs]** Factorize labels managements. ([#3159](https://github.com/containous/traefik/pull/3159) by [ldez](https://github.com/ldez))
- **[ecs]** Homogenization of templates: ECS ([#2663](https://github.com/containous/traefik/pull/2663) by [ldez](https://github.com/ldez))
- **[ecs]** Add all available labels to ECS Backend ([#2605](https://github.com/containous/traefik/pull/2605) by [ldez](https://github.com/ldez))
- **[ecs]** Homogenization of templates: ECS ([#2663](https://github.com/containous/traefik/pull/2663) by [ldez](https://github.com/ldez))
- **[ecs]** Factorize labels managements. ([#3159](https://github.com/containous/traefik/pull/3159) by [ldez](https://github.com/ldez))
- **[eureka]** Homogenization of templates: Eureka ([#2846](https://github.com/containous/traefik/pull/2846) by [ldez](https://github.com/ldez))
- **[eureka]** Replace Delay by RefreshSecond in Eureka ([#2972](https://github.com/containous/traefik/pull/2972) by [ldez](https://github.com/ldez))
- **[file]** Added support for templates to file provider ([#2991](https://github.com/containous/traefik/pull/2991) by [aantono](https://github.com/aantono))
- **[healthcheck]** Toggle /ping to artificially return unhealthy response on SIGTERM during requestAcceptGraceTimeout interval ([#3062](https://github.com/containous/traefik/pull/3062) by [ravilr](https://github.com/ravilr))
- **[healthcheck]** Add HTTP headers to healthcheck. ([#3047](https://github.com/containous/traefik/pull/3047) by [zetaab](https://github.com/zetaab))
- **[healthcheck]** Improve logging output for failing healthchecks ([#2443](https://github.com/containous/traefik/pull/2443) by [marco-jantke](https://github.com/marco-jantke))
- **[k8s,tls]** Add support for fetching k8s Ingress TLS data from secrets ([#2439](https://github.com/containous/traefik/pull/2439) by [gopenguin](https://github.com/gopenguin))
- **[k8s]** Builders in k8s tests ([#2513](https://github.com/containous/traefik/pull/2513) by [ldez](https://github.com/ldez))
- **[k8s]** Support multi-port services. ([#3121](https://github.com/containous/traefik/pull/3121) by [timoreimann](https://github.com/timoreimann))
- **[k8s]** Bump kubernetes/client-go ([#2848](https://github.com/containous/traefik/pull/2848) by [yue9944882](https://github.com/yue9944882))
- **[k8s]** Add all available annotations to k8s Backend ([#2612](https://github.com/containous/traefik/pull/2612) by [ldez](https://github.com/ldez))
- **[k8s]** Allow custom value for kubernetes.io/ingress.class annotation ([#2222](https://github.com/containous/traefik/pull/2222) by [yuvipanda](https://github.com/yuvipanda))
- **[k8s]** Introduce k8s informer factory ([#2867](https://github.com/containous/traefik/pull/2867) by [yue9944882](https://github.com/yue9944882))
- **[k8s]** Add all available annotations to k8s Backend ([#2612](https://github.com/containous/traefik/pull/2612) by [ldez](https://github.com/ldez))
- **[k8s]** Bump kubernetes/client-go ([#2848](https://github.com/containous/traefik/pull/2848) by [yue9944882](https://github.com/yue9944882))
- **[k8s]** Add app-root annotation support for kubernetes ingress ([#2522](https://github.com/containous/traefik/pull/2522) by [yue9944882](https://github.com/yue9944882))
- **[k8s]** Builders in k8s tests ([#2513](https://github.com/containous/traefik/pull/2513) by [ldez](https://github.com/ldez))
- **[k8s]** Allow custom value for kubernetes.io/ingress.class annotation ([#2222](https://github.com/containous/traefik/pull/2222) by [yuvipanda](https://github.com/yuvipanda))
- **[logs,middleware]** Add access log filter for retry attempts ([#3042](https://github.com/containous/traefik/pull/3042) by [marco-jantke](https://github.com/marco-jantke))
- **[logs,middleware]** Ultimate Access log filter ([#2988](https://github.com/containous/traefik/pull/2988) by [mmatur](https://github.com/mmatur))
- **[logs,middleware]** Add username in accesslog ([#2111](https://github.com/containous/traefik/pull/2111) by [bastiaanb](https://github.com/bastiaanb))
- **[logs]** Display file log when test fails. ([#2801](https://github.com/containous/traefik/pull/2801) by [ldez](https://github.com/ldez))
- **[logs,middleware]** Ultimate Access log filter ([#2988](https://github.com/containous/traefik/pull/2988) by [mmatur](https://github.com/mmatur))
- **[logs]** Allow overriding the log level in debug mode. ([#3050](https://github.com/containous/traefik/pull/3050) by [timoreimann](https://github.com/timoreimann))
- **[logs]** Display file log when test fails. ([#2801](https://github.com/containous/traefik/pull/2801) by [ldez](https://github.com/ldez))
- **[marathon]** Remove health check filter from Marathon tasks. ([#2817](https://github.com/containous/traefik/pull/2817) by [timoreimann](https://github.com/timoreimann))
- **[marathon]** homogenization of templates: Marathon ([#2665](https://github.com/containous/traefik/pull/2665) by [ldez](https://github.com/ldez))
- **[marathon]** Add all available labels to Marathon Backend ([#2602](https://github.com/containous/traefik/pull/2602) by [ldez](https://github.com/ldez))
- **[marathon]** homogenization of templates: Marathon ([#2665](https://github.com/containous/traefik/pull/2665) by [ldez](https://github.com/ldez))
- **[mesos]** Add all available labels to Mesos Backend ([#2687](https://github.com/containous/traefik/pull/2687) by [ldez](https://github.com/ldez))
- **[metrics]** Added missing metrics to registry for DataDog and StatsD ([#2890](https://github.com/containous/traefik/pull/2890) by [aantono](https://github.com/aantono))
- **[metrics]** Extend metrics and rebuild prometheus exporting logic ([#2567](https://github.com/containous/traefik/pull/2567) by [marco-jantke](https://github.com/marco-jantke))
- **[metrics]** Remove unnecessary conversion ([#2850](https://github.com/containous/traefik/pull/2850) by [ferhatelmas](https://github.com/ferhatelmas))
- **[metrics]** Added entrypoint metrics to influxdb ([#2992](https://github.com/containous/traefik/pull/2992) by [adityacs](https://github.com/adityacs))
- **[metrics]** Remove unnecessary conversion ([#2850](https://github.com/containous/traefik/pull/2850) by [ferhatelmas](https://github.com/ferhatelmas))
- **[metrics]** Extend metrics and rebuild prometheus exporting logic ([#2567](https://github.com/containous/traefik/pull/2567) by [marco-jantke](https://github.com/marco-jantke))
- **[metrics]** Added missing metrics to registry for DataDog and StatsD ([#2890](https://github.com/containous/traefik/pull/2890) by [aantono](https://github.com/aantono))
- **[middleware,consul,consulcatalog,docker,ecs,k8s,marathon,mesos,rancher]** New option in secure middleware ([#2958](https://github.com/containous/traefik/pull/2958) by [mmatur](https://github.com/mmatur))
- **[middleware,consulcatalog,docker,ecs,k8s,kv,marathon,mesos,rancher]** Ability to use "X-Forwarded-For" as a source of IP for white list. ([#3070](https://github.com/containous/traefik/pull/3070) by [ldez](https://github.com/ldez))
- **[middleware,docker]** Use pointer of error pages ([#2607](https://github.com/containous/traefik/pull/2607) by [ldez](https://github.com/ldez))
- **[middleware,provider]** Redirection: permanent move option. ([#2774](https://github.com/containous/traefik/pull/2774) by [ldez](https://github.com/ldez))
- **[middleware]** Add tests on IPWhiteLister. ([#3106](https://github.com/containous/traefik/pull/3106) by [ldez](https://github.com/ldez))
- **[middleware]** Add new options to the CLI entrypoint definition. ([#2799](https://github.com/containous/traefik/pull/2799) by [ldez](https://github.com/ldez))
- **[middleware]** Change port of traefik for error pages integration test ([#2907](https://github.com/containous/traefik/pull/2907) by [mmatur](https://github.com/mmatur))
- **[middleware]** Request buffering middleware ([#2217](https://github.com/containous/traefik/pull/2217) by [harnash](https://github.com/harnash))
- **[middleware]** Remove unnecessary returns in tracing setup ([#2880](https://github.com/containous/traefik/pull/2880) by [ferhatelmas](https://github.com/ferhatelmas))
- **[middleware]** Extract internal router creation from server ([#3204](https://github.com/containous/traefik/pull/3204) by [Juliens](https://github.com/Juliens))
- **[provider]** Homogenization of the providers (part 1) ([#2518](https://github.com/containous/traefik/pull/2518) by [ldez](https://github.com/ldez))
- **[middleware]** Request buffering middleware ([#2217](https://github.com/containous/traefik/pull/2217) by [harnash](https://github.com/harnash))
- **[middleware]** Add new options to the CLI entrypoint definition. ([#2799](https://github.com/containous/traefik/pull/2799) by [ldez](https://github.com/ldez))
- **[provider]** No error pages must return nil. ([#2610](https://github.com/containous/traefik/pull/2610) by [ldez](https://github.com/ldez))
- **[provider]** Homogenization of the providers (part 1) ([#2518](https://github.com/containous/traefik/pull/2518) by [ldez](https://github.com/ldez))
- **[rancher]** Add all available labels to Rancher Backend ([#2601](https://github.com/containous/traefik/pull/2601) by [ldez](https://github.com/ldez))
- **[rancher]** Homogenization of templates: Rancher ([#2662](https://github.com/containous/traefik/pull/2662) by [ldez](https://github.com/ldez))
- **[rules]** Externalize Træfik rules in a dedicated package ([#2933](https://github.com/containous/traefik/pull/2933) by [nmengin](https://github.com/nmengin))
- **[servicefabric]** Use shared label system ([#3197](https://github.com/containous/traefik/pull/3197) by [ldez](https://github.com/ldez))
- **[servicefabric]** Add white list for Service Fabric ([#3079](https://github.com/containous/traefik/pull/3079) by [ldez](https://github.com/ldez))
- **[servicefabric]** Update Service Fabric backend. ([#3064](https://github.com/containous/traefik/pull/3064) by [ldez](https://github.com/ldez))
- **[servicefabric]** Add HTTP headers to healthcheck. ([#3205](https://github.com/containous/traefik/pull/3205) by [ldez](https://github.com/ldez))
- **[tls]** Support TLS MinVersion and CipherSuite as CLI option. ([#3107](https://github.com/containous/traefik/pull/3107) by [ldez](https://github.com/ldez))
- **[servicefabric]** Add white list for Service Fabric ([#3079](https://github.com/containous/traefik/pull/3079) by [ldez](https://github.com/ldez))
- **[tls]** Use default entryPoints when certificates are added with no entryPoints. ([#2534](https://github.com/containous/traefik/pull/2534) by [nmengin](https://github.com/nmengin))
- **[tracing]** Opentracing support ([#2587](https://github.com/containous/traefik/pull/2587) by [mmatur](https://github.com/mmatur))
- **[tracing]** Handle zipkin collector creation ([#2860](https://github.com/containous/traefik/pull/2860) by [ferhatelmas](https://github.com/ferhatelmas))
- **[tracing]** Opentracing support ([#2587](https://github.com/containous/traefik/pull/2587) by [tcolgate](https://github.com/tcolgate) and [mmatur](https://github.com/mmatur))
- **[webui]** New web ui ([#2226](https://github.com/containous/traefik/pull/2226) by [jkuri](https://github.com/jkuri))
- **[webui]** Add status code text to webui bar chart tooltip ([#2639](https://github.com/containous/traefik/pull/2639) by [wader](https://github.com/wader))
- Separate command from the main package ([#2951](https://github.com/containous/traefik/pull/2951) by [Juliens](https://github.com/Juliens))
- Logger and Leaks ([#2847](https://github.com/containous/traefik/pull/2847) by [ldez](https://github.com/ldez))
- Separate command from the main package ([#2951](https://github.com/containous/traefik/pull/2951) by [Juliens](https://github.com/Juliens))
- Use context in Server ([#3007](https://github.com/containous/traefik/pull/3007) by [Juliens](https://github.com/Juliens))
**Bug fixes:**
- **[acme]** Check all the C/N and SANs of provided certificates before generating ACME certificates in ACME provider ([#2970](https://github.com/containous/traefik/pull/2970) by [nmengin](https://github.com/nmengin))
- **[acme]** Update lego. ([#3158](https://github.com/containous/traefik/pull/3158) by [ldez](https://github.com/ldez))
- **[acme]** Fix panic with wrong ACME configuration ([#3084](https://github.com/containous/traefik/pull/3084) by [nmengin](https://github.com/nmengin))
- **[acme]** Minor updates to dumpcerts.sh ([#3116](https://github.com/containous/traefik/pull/3116) by [mathuin](https://github.com/mathuin))
@@ -99,87 +130,84 @@
- **[acme]** Add TTL and custom Timeout in DigitalOcean DNS provider ([#3143](https://github.com/containous/traefik/pull/3143) by [ldez](https://github.com/ldez))
- **[acme]** Fix acme.json file automatic creation ([#3156](https://github.com/containous/traefik/pull/3156) by [nmengin](https://github.com/nmengin))
- **[acme]** Fix wildcard match to ACME domains in cluster mode ([#3080](https://github.com/containous/traefik/pull/3080) by [oldmantaiter](https://github.com/oldmantaiter))
- **[acme]** Check all the C/N and SANs of provided certificates before generating ACME certificates in ACME provider ([#2970](https://github.com/containous/traefik/pull/2970) by [nmengin](https://github.com/nmengin))
- **[acme]** Does not generate ACME certificate if domain is checked by dynamic certificate ([#3238](https://github.com/containous/traefik/pull/3238) by [Juliens](https://github.com/Juliens))
- **[api,cluster]** Moved /api/cluster/leadership handler under public routes (requires no authentication) ([#3101](https://github.com/containous/traefik/pull/3101) by [aantono](https://github.com/aantono))
- **[authentication,middleware]** Forward auth: copy response headers when auth failed. ([#3207](https://github.com/containous/traefik/pull/3207) by [ldez](https://github.com/ldez))
- **[consul,docker,ecs,eureka,k8s,kv,marathon,mesos,rancher]** Server weight zero ([#3130](https://github.com/containous/traefik/pull/3130) by [ldez](https://github.com/ldez))
- **[docker,marathon,mesos,rancher]** Fix: label 'traefik.domain' ([#3201](https://github.com/containous/traefik/pull/3201) by [ldez](https://github.com/ldez))
- **[docker,k8s,marathon]** Fix custom headers template ([#2622](https://github.com/containous/traefik/pull/2622) by [ldez](https://github.com/ldez))
- **[docker,rancher]** Ignore server for container with empty IP address. ([#3213](https://github.com/containous/traefik/pull/3213) by [ldez](https://github.com/ldez))
- **[docker,marathon,mesos,rancher]** Fix: label 'traefik.domain' ([#3201](https://github.com/containous/traefik/pull/3201) by [ldez](https://github.com/ldez))
- **[docker,rancher]** Frontend rule and segment labels. ([#3091](https://github.com/containous/traefik/pull/3091) by [ldez](https://github.com/ldez))
- **[docker,rancher]** Ignore server for container with empty IP address. ([#3213](https://github.com/containous/traefik/pull/3213) by [ldez](https://github.com/ldez))
- **[docker]** Fix multiple frontends with docker-compose --scale ([#3190](https://github.com/containous/traefik/pull/3190) by [jbdoumenjou](https://github.com/jbdoumenjou))
- **[k8s]** Fixes prefixed annotations support. ([#3110](https://github.com/containous/traefik/pull/3110) by [ldez](https://github.com/ldez))
- **[k8s]** Limit label selector to Ingress factory. ([#3137](https://github.com/containous/traefik/pull/3137) by [timoreimann](https://github.com/timoreimann))
- **[healthcheck]** Remove unnecessary mutex usage in health checks ([#2726](https://github.com/containous/traefik/pull/2726) by [marco-jantke](https://github.com/marco-jantke))
- **[k8s]** Missing annotation prefix support. ([#2915](https://github.com/containous/traefik/pull/2915) by [ldez](https://github.com/ldez))
- **[k8s]** Remove hardcoded frontend prefix in Kubernetes template ([#2914](https://github.com/containous/traefik/pull/2914) by [psalaberria002](https://github.com/psalaberria002))
- **[k8s]** Limit label selector to Ingress factory. ([#3137](https://github.com/containous/traefik/pull/3137) by [timoreimann](https://github.com/timoreimann))
- **[k8s]** Fixes prefixed annotations support. ([#3110](https://github.com/containous/traefik/pull/3110) by [ldez](https://github.com/ldez))
- **[logs,middleware]** Fix bad access log ([#2682](https://github.com/containous/traefik/pull/2682) by [mmatur](https://github.com/mmatur))
- **[logs]** Add missing argument in log. ([#3188](https://github.com/containous/traefik/pull/3188) by [chemidy](https://github.com/chemidy))
- **[marathon]** Several apps with same backend name in Marathon. ([#3109](https://github.com/containous/traefik/pull/3109) by [ldez](https://github.com/ldez))
- **[mesos]** fix: overflow on 32 bits arch. ([#3127](https://github.com/containous/traefik/pull/3127) by [ldez](https://github.com/ldez))
- **[metrics]** Fix duplicated tags in InfluxDB ([#3189](https://github.com/containous/traefik/pull/3189) by [mmatur](https://github.com/mmatur))
- **[middleware,consul,consulcatalog,docker,ecs,kv,marathon,mesos,rancher]** Fix: error pages ([#3138](https://github.com/containous/traefik/pull/3138) by [ldez](https://github.com/ldez))
- **[middleware,tracing]** Fix nil value when tracing is enabled ([#3192](https://github.com/containous/traefik/pull/3192) by [mmatur](https://github.com/mmatur))
- **[middleware,tracing]** Fix <nil> tracer value in KV ([#2911](https://github.com/containous/traefik/pull/2911) by [mmatur](https://github.com/mmatur))
- **[middleware]** Fix panic in atomic on ARM and x86-32 platforms ([#3195](https://github.com/containous/traefik/pull/3195) by [mmatur](https://github.com/mmatur))
- **[middleware]** Redirect to HTTPS first before basic auth if header redirect (secure) is set ([#3187](https://github.com/containous/traefik/pull/3187) by [SantoDE](https://github.com/SantoDE))
- **[middleware]** Fix error pages redirect and headers. ([#3217](https://github.com/containous/traefik/pull/3217) by [ldez](https://github.com/ldez))
- **[middleware]** Fix whitelist and XFF. ([#3211](https://github.com/containous/traefik/pull/3211) by [ldez](https://github.com/ldez))
- **[middleware,tracing]** Fix nil value when tracing is enabled ([#3192](https://github.com/containous/traefik/pull/3192) by [mmatur](https://github.com/mmatur))
- **[middleware]** Use responseModifier to override secure headers ([#2946](https://github.com/containous/traefik/pull/2946) by [mmatur](https://github.com/mmatur))
- **[middleware]** Correct conditional setting of buffering retry expression. ([#2865](https://github.com/containous/traefik/pull/2865) by [ldez](https://github.com/ldez))
- **[middleware]** Fix high memory usage in retry middleware ([#2740](https://github.com/containous/traefik/pull/2740) by [marco-jantke](https://github.com/marco-jantke))
- **[middleware]** Fix whitelist and XFF. ([#3211](https://github.com/containous/traefik/pull/3211) by [ldez](https://github.com/ldez))
- **[middleware]** Fix panic in atomic on ARM and x86-32 platforms ([#3195](https://github.com/containous/traefik/pull/3195) by [mmatur](https://github.com/mmatur))
- **[middleware]** Redirect to HTTPS first before basic auth if header redirect (secure) is set ([#3187](https://github.com/containous/traefik/pull/3187) by [SantoDE](https://github.com/SantoDE))
- **[middleware]** Fix error pages redirect and headers. ([#3217](https://github.com/containous/traefik/pull/3217) by [ldez](https://github.com/ldez))
- **[provider]** Add some missing quotes in templates ([#2973](https://github.com/containous/traefik/pull/2973) by [ldez](https://github.com/ldez))
- **[servicefabric]** Fix backend name for stateful service and more. ([#3183](https://github.com/containous/traefik/pull/3183) by [ldez](https://github.com/ldez))
- **[tracing]** Tracing statusCodeTracker need to implement CloseNotify ([#2733](https://github.com/containous/traefik/pull/2733) by [mmatur](https://github.com/mmatur))
- **[tracing]** Fix missing configuration for jaeger reporter ([#2720](https://github.com/containous/traefik/pull/2720) by [mmatur](https://github.com/mmatur))
- **[tracing]** Tracing statusCodeTracker need to implement CloseNotify ([#2733](https://github.com/containous/traefik/pull/2733) by [mmatur](https://github.com/mmatur))
- **[tracing]** Fix integration tests in tracing ([#2759](https://github.com/containous/traefik/pull/2759) by [mmatur](https://github.com/mmatur))
- **[webui]** Remove useless ACME tab from UI. ([#3154](https://github.com/containous/traefik/pull/3154) by [ldez](https://github.com/ldez))
- **[webui]** Add redirect section. ([#3243](https://github.com/containous/traefik/pull/3243) by [ldez](https://github.com/ldez))
- Remove unnecessary mutex usage in health checks ([#2726](https://github.com/containous/traefik/pull/2726) by [marco-jantke](https://github.com/marco-jantke))
- Add missing argument in log. ([#3188](https://github.com/containous/traefik/pull/3188) by [chemidy](https://github.com/chemidy))
**Documentation:**
- **[docker]** Add default values for some Docker labels ([#2604](https://github.com/containous/traefik/pull/2604) by [ldez](https://github.com/ldez))
- **[file]** Add documentation about Templating in backend file ([#3223](https://github.com/containous/traefik/pull/3223) by [nmengin](https://github.com/nmengin))
- **[k8s]** Update kubernetes.md ([#3093](https://github.com/containous/traefik/pull/3093) by [rdrgporto](https://github.com/rdrgporto))
- **[k8s]** Update kubernetes.md ([#3171](https://github.com/containous/traefik/pull/3171) by [andreyfedoseev](https://github.com/andreyfedoseev))
- **[k8s]** Document custom k8s ingress class usage in guide. ([#3242](https://github.com/containous/traefik/pull/3242) by [timoreimann](https://github.com/timoreimann))
- **[k8s]** Update traefik-ds.yaml with --api command line parameter ([#2803](https://github.com/containous/traefik/pull/2803) by [maniankara](https://github.com/maniankara))
- **[k8s]** Remove web provider in example ([#2807](https://github.com/containous/traefik/pull/2807) by [pigletfly](https://github.com/pigletfly))
- **[k8s]** Drop capabilities in Kubernetes DaemonSet example ([#3028](https://github.com/containous/traefik/pull/3028) by [nogoegst](https://github.com/nogoegst))
- **[k8s]** Docs: Fix typos in k8s user-guide ([#2898](https://github.com/containous/traefik/pull/2898) by [cez81](https://github.com/cez81))
- **[k8s]** Change boolean annotation values to string ([#2839](https://github.com/containous/traefik/pull/2839) by [hobti01](https://github.com/hobti01))
- **[provider]** Fix template version documentation. ([#3184](https://github.com/containous/traefik/pull/3184) by [ldez](https://github.com/ldez))
- **[provider]** Cleaning labels/annotations documentation. ([#3245](https://github.com/containous/traefik/pull/3245) by [ldez](https://github.com/ldez))
- **[k8s]** Update kubernetes.md ([#3093](https://github.com/containous/traefik/pull/3093) by [rdrgporto](https://github.com/rdrgporto))
- **[k8s]** Document custom k8s ingress class usage in guide. ([#3242](https://github.com/containous/traefik/pull/3242) by [timoreimann](https://github.com/timoreimann))
- **[k8s]** Update kubernetes.md ([#3171](https://github.com/containous/traefik/pull/3171) by [andreyfedoseev](https://github.com/andreyfedoseev))
- **[provider]** Split security labels and custom labels documentation. ([#2872](https://github.com/containous/traefik/pull/2872) by [ldez](https://github.com/ldez))
- **[provider]** Remove non-supported label. ([#3065](https://github.com/containous/traefik/pull/3065) by [ldez](https://github.com/ldez))
- **[provider]** Remove obsolete paragraph about error pages. ([#2608](https://github.com/containous/traefik/pull/2608) by [ldez](https://github.com/ldez))
- **[servicefabric]** Update SF white list documentation section. ([#3082](https://github.com/containous/traefik/pull/3082) by [ldez](https://github.com/ldez))
- **[provider]** Cleaning labels/annotations documentation. ([#3245](https://github.com/containous/traefik/pull/3245) by [ldez](https://github.com/ldez))
- **[provider]** Fix template version documentation. ([#3184](https://github.com/containous/traefik/pull/3184) by [ldez](https://github.com/ldez))
- **[servicefabric]** Add SF to supported backends in docs ([#3033](https://github.com/containous/traefik/pull/3033) by [lawrencegripper](https://github.com/lawrencegripper))
- **[servicefabric]** Update SF white list documentation section. ([#3082](https://github.com/containous/traefik/pull/3082) by [ldez](https://github.com/ldez))
- **[tracing]** Fix typo in doc for rate limit label ([#2790](https://github.com/containous/traefik/pull/2790) by [mmatur](https://github.com/mmatur))
- **[tracing]** Add Tracing entry in the documentation. ([#2713](https://github.com/containous/traefik/pull/2713) by [ldez](https://github.com/ldez))
- **[tracing]** Fix documentation for tracing with Jaeger ([#3227](https://github.com/containous/traefik/pull/3227) by [mmatur](https://github.com/mmatur))
- **[webui]** doc: update Traefik images. ([#3241](https://github.com/containous/traefik/pull/3241) by [ldez](https://github.com/ldez))
- Fix typo in doc for rate limit label ([#2790](https://github.com/containous/traefik/pull/2790) by [mmatur](https://github.com/mmatur))
- Fix typo in documentation ([#3215](https://github.com/containous/traefik/pull/3215) by [arnaslu](https://github.com/arnaslu))
- Minor improvements to documentation ([#3221](https://github.com/containous/traefik/pull/3221) by [colincoller](https://github.com/colincoller))
- Update some examples ([#3150](https://github.com/containous/traefik/pull/3150) by [zaporylie](https://github.com/zaporylie))
- Normalize parameter names in configs ([#3132](https://github.com/containous/traefik/pull/3132) by [kachkaev](https://github.com/kachkaev))
- Fixed documentation urls on README.md ([#3102](https://github.com/containous/traefik/pull/3102) by [emir](https://github.com/emir))
- Minor improvements to documentation ([#3221](https://github.com/containous/traefik/pull/3221) by [colincoller](https://github.com/colincoller))
- Fix basic documentation ([#3086](https://github.com/containous/traefik/pull/3086) by [mmatur](https://github.com/mmatur))
- Fix typo and tweak formatting in quickstart ([#3250](https://github.com/containous/traefik/pull/3250) by [alexymik](https://github.com/alexymik))
- Prepare release v1.6.0-rc5 ([#3179](https://github.com/containous/traefik/pull/3179) by [Juliens](https://github.com/Juliens))
- Fix basic documentation ([#3086](https://github.com/containous/traefik/pull/3086) by [mmatur](https://github.com/mmatur))
- Prepare release v1.6.0-rc6 ([#3199](https://github.com/containous/traefik/pull/3199) by [mmatur](https://github.com/mmatur))
- Prepare release v1.6.0-rc5 ([#3179](https://github.com/containous/traefik/pull/3179) by [Juliens](https://github.com/Juliens))
- Prepare release v1.6.0-rc4 ([#3126](https://github.com/containous/traefik/pull/3126) by [ldez](https://github.com/ldez))
- Prepare release v1.6.0-rc3 ([#3096](https://github.com/containous/traefik/pull/3096) by [ldez](https://github.com/ldez))
- Prepare release v1.6.0-rc2 ([#3087](https://github.com/containous/traefik/pull/3087) by [nmengin](https://github.com/nmengin))
- Prepare release v1.6.0-rc1 ([#3078](https://github.com/containous/traefik/pull/3078) by [Juliens](https://github.com/Juliens))
- Prepare release v1.6.0 ([#3251](https://github.com/containous/traefik/pull/3251) by [Juliens](https://github.com/Juliens))
**Misc:**
- **[oxy]** Disable closeNotify when method GET for http pipelining ([#3108](https://github.com/containous/traefik/pull/3108) by [Juliens](https://github.com/Juliens))
- **[boltdb,consul,etcd,kv,zk]** Migrate from libkv to valkeyrie library ([#2743](https://github.com/containous/traefik/pull/2743) by [nmengin](https://github.com/nmengin))
- Fix Service Fabric docs to use v1.6 labels ([#3209](https://github.com/containous/traefik/pull/3209) by [jjcollinge](https://github.com/jjcollinge))
- Merge v1.6.0-rc6 into master ([#3203](https://github.com/containous/traefik/pull/3203) by [ldez](https://github.com/ldez))
- Merge v1.6.0-rc5 into master ([#3180](https://github.com/containous/traefik/pull/3180) by [ldez](https://github.com/ldez))
- Merge v1.6.0-rc4 into master ([#3129](https://github.com/containous/traefik/pull/3129) by [ldez](https://github.com/ldez))
- Drop unnecessary type conversions ([#2583](https://github.com/containous/traefik/pull/2583) by [ferhatelmas](https://github.com/ferhatelmas))
- Code simplification ([#2516](https://github.com/containous/traefik/pull/2516) by [ferhatelmas](https://github.com/ferhatelmas))
- Merge v1.5.4 into master ([#3024](https://github.com/containous/traefik/pull/3024) by [ldez](https://github.com/ldez))
- Merge v1.5.3 into master ([#2943](https://github.com/containous/traefik/pull/2943) by [ldez](https://github.com/ldez))
- Merge v1.5.2 into master ([#2843](https://github.com/containous/traefik/pull/2843) by [ldez](https://github.com/ldez))
@@ -187,8 +215,6 @@
- Merge v1.5.0-rc5 into master ([#2708](https://github.com/containous/traefik/pull/2708) by [ldez](https://github.com/ldez))
- Merge v1.5.0-rc3 into master ([#2600](https://github.com/containous/traefik/pull/2600) by [ldez](https://github.com/ldez))
- Merge v1.5.0-rc2 into master ([#2536](https://github.com/containous/traefik/pull/2536) by [ldez](https://github.com/ldez))
- Drop unnecessary type conversions ([#2583](https://github.com/containous/traefik/pull/2583) by [ferhatelmas](https://github.com/ferhatelmas))
- Code simplification ([#2516](https://github.com/containous/traefik/pull/2516) by [ferhatelmas](https://github.com/ferhatelmas))
## [v1.6.0-rc6](https://github.com/containous/traefik/tree/v1.6.0-rc6) (2018-04-17)
[All Commits](https://github.com/containous/traefik/compare/v1.6.0-rc5...v1.6.0-rc6)

2
Gopkg.lock generated
View File

@@ -1278,7 +1278,7 @@
"providers/dns/route53",
"providers/dns/vultr"
]
revision = "2817d2131186742bc98830c73a5d9c255b3f4537"
revision = "3d653ee2ee38f1d71beb5f09b37b23344eff0ab3"
source = "github.com/containous/lego"
[[projects]]

View File

@@ -14,7 +14,7 @@
Træfik is a modern HTTP reverse proxy and load balancer that makes deploying microservices easy.
Træfik integrates with your existing infrastructure components ([Docker](https://www.docker.com/), [Swarm mode](https://docs.docker.com/engine/swarm/), [Kubernetes](https://kubernetes.io), [Marathon](https://mesosphere.github.io/marathon/), [Consul](https://www.consul.io/), [Etcd](https://coreos.com/etcd/), [Rancher](https://rancher.com), [Amazon ECS](https://aws.amazon.com/ecs), ...) and configures itself automatically and dynamically.
Telling Træfik where your orchestrator is could be the _only_ configuration step you need to do.
Pointing Træfik at your orchestrator should be the _only_ configuration step you need.
---

View File

@@ -41,15 +41,15 @@ type ACME struct {
Email string `description:"Email address used for registration"`
Domains []types.Domain `description:"SANs (alternative domains) to each main domain using format: --acme.domains='main.com,san1.com,san2.com' --acme.domains='main.net,san1.net,san2.net'"`
Storage string `description:"File or key used for certificates storage."`
StorageFile string // deprecated
OnDemand bool `description:"Enable on demand certificate generation. This will request a certificate from Let's Encrypt during the first TLS handshake for a hostname that does not yet have a certificate."` //deprecated
StorageFile string // Deprecated
OnDemand bool `description:"(Deprecated) Enable on demand certificate generation. This will request a certificate from Let's Encrypt during the first TLS handshake for a hostname that does not yet have a certificate."` //deprecated
OnHostRule bool `description:"Enable certificate generation on frontends Host rules."`
CAServer string `description:"CA server to use."`
EntryPoint string `description:"Entrypoint to proxy acme challenge to."`
DNSChallenge *acmeprovider.DNSChallenge `description:"Activate DNS-01 Challenge"`
HTTPChallenge *acmeprovider.HTTPChallenge `description:"Activate HTTP-01 Challenge"`
DNSProvider string `description:"Activate DNS-01 Challenge (Deprecated)"` // deprecated
DelayDontCheckDNS flaeg.Duration `description:"Assume DNS propagates after a delay in seconds rather than finding and querying nameservers."` // deprecated
DNSProvider string `description:"(Deprecated) Activate DNS-01 Challenge"` // Deprecated
DelayDontCheckDNS flaeg.Duration `description:"(Deprecated) Assume DNS propagates after a delay in seconds rather than finding and querying nameservers."` // Deprecated
ACMELogging bool `description:"Enable debug logging of ACME actions."`
client *acme.Client
defaultCertificate *tls.Certificate
@@ -611,11 +611,13 @@ func (a *ACME) getDomainsCertificates(domains []string) (*Certificate, error) {
domains = fun.Map(types.CanonicalDomain, domains).([]string)
log.Debugf("Loading ACME certificates %s...", domains)
bundle := true
certificate, failures := a.client.ObtainCertificate(domains, bundle, nil, OSCPMustStaple)
if len(failures) > 0 {
log.Error(failures)
return nil, fmt.Errorf("cannot obtain certificates %+v", failures)
certificate, err := a.client.ObtainCertificate(domains, bundle, nil, OSCPMustStaple)
if err != nil {
log.Error(err)
return nil, fmt.Errorf("cannot obtain certificates: %+v", err)
}
log.Debugf("Loaded ACME certificates %s", domains)
return &Certificate{
Domain: certificate.Domain,

View File

@@ -1089,6 +1089,7 @@ var _templatesKubernetesTmpl = []byte(`[backends]
entryPoint = "{{ $frontend.Redirect.EntryPoint }}"
regex = "{{ $frontend.Redirect.Regex }}"
replacement = "{{ $frontend.Redirect.Replacement }}"
permanent = {{ $frontend.Redirect.Permanent }}
{{end}}
{{if $frontend.Errors }}

View File

@@ -50,6 +50,9 @@ const (
// DefaultGraceTimeout controls how long Traefik serves pending requests
// prior to shutting down.
DefaultGraceTimeout = 10 * time.Second
// DefaultAcmeCAServer is the default ACME API endpoint
DefaultAcmeCAServer = "https://acme-v02.api.letsencrypt.org/directory"
)
// GlobalConfiguration holds global configuration (with providers, etc.).
@@ -105,13 +108,13 @@ type GlobalConfiguration struct {
// WebCompatibility is a configuration to handle compatibility with deprecated web provider options
type WebCompatibility struct {
Address string `description:"Web administration port" export:"true"`
CertFile string `description:"SSL certificate" export:"true"`
KeyFile string `description:"SSL certificate" export:"true"`
ReadOnly bool `description:"Enable read only API" export:"true"`
Statistics *types.Statistics `description:"Enable more detailed statistics" export:"true"`
Metrics *types.Metrics `description:"Enable a metrics exporter" export:"true"`
Path string `description:"Root path for dashboard and API" export:"true"`
Address string `description:"(Deprecated) Web administration port" export:"true"`
CertFile string `description:"(Deprecated) SSL certificate" export:"true"`
KeyFile string `description:"(Deprecated) SSL certificate" export:"true"`
ReadOnly bool `description:"(Deprecated) Enable read only API" export:"true"`
Statistics *types.Statistics `description:"(Deprecated) Enable more detailed statistics" export:"true"`
Metrics *types.Metrics `description:"(Deprecated) Enable a metrics exporter" export:"true"`
Path string `description:"(Deprecated) Root path for dashboard and API" export:"true"`
Auth *types.Auth `export:"true"`
Debug bool `export:"true"`
}
@@ -304,14 +307,8 @@ func (gc *GlobalConfiguration) SetEffectiveConfiguration(configFile string) {
gc.Web.Path += "/"
}
// Try to fallback to traefik config file in case the file provider is enabled
// but has no file name configured and is not in a directory mode.
if gc.File != nil && len(gc.File.Filename) == 0 && len(gc.File.Directory) == 0 {
if len(configFile) > 0 {
gc.File.Filename = configFile
} else {
log.Errorln("Error using file configuration backend, no filename defined")
}
if gc.File != nil {
gc.File.TraefikFile = configFile
}
gc.initACMEProvider()
@@ -356,7 +353,14 @@ func (gc *GlobalConfiguration) initTracing() {
func (gc *GlobalConfiguration) initACMEProvider() {
if gc.ACME != nil {
// TODO: to remove in the futurs
gc.ACME.CAServer = getSafeACMECAServer(gc.ACME.CAServer)
if gc.ACME.DNSChallenge != nil && gc.ACME.HTTPChallenge != nil {
log.Warn("Unable to use DNS challenge and HTTP challenge at the same time. Fallback to DNS challenge.")
gc.ACME.HTTPChallenge = nil
}
// TODO: to remove in the future
if len(gc.ACME.StorageFile) > 0 && len(gc.ACME.Storage) == 0 {
log.Warn("ACME.StorageFile is deprecated, use ACME.Storage instead")
gc.ACME.Storage = gc.ACME.StorageFile
@@ -391,6 +395,26 @@ func (gc *GlobalConfiguration) initACMEProvider() {
}
}
func getSafeACMECAServer(caServerSrc string) string {
if len(caServerSrc) == 0 {
return DefaultAcmeCAServer
}
if strings.HasPrefix(caServerSrc, "https://acme-v01.api.letsencrypt.org") {
caServer := strings.Replace(caServerSrc, "v01", "v02", 1)
log.Warnf("The CA server %[1]q refers to a v01 endpoint of the ACME API, please change to %[2]q. Fallback to %[2]q.", caServerSrc, caServer)
return caServer
}
if strings.HasPrefix(caServerSrc, "https://acme-staging.api.letsencrypt.org") {
caServer := strings.Replace(caServerSrc, "https://acme-staging.api.letsencrypt.org", "https://acme-staging-v02.api.letsencrypt.org", 1)
log.Warnf("The CA server %[1]q refers to a v01 endpoint of the ACME API, please change to %[2]q. Fallback to %[2]q.", caServerSrc, caServer)
return caServer
}
return caServerSrc
}
// ValidateConfiguration validate that configuration is coherent
func (gc *GlobalConfiguration) ValidateConfiguration() {
if gc.ACME != nil {

View File

@@ -65,24 +65,28 @@ func TestSetEffectiveConfigurationGraceTimeout(t *testing.T) {
func TestSetEffectiveConfigurationFileProviderFilename(t *testing.T) {
testCases := []struct {
desc string
fileProvider *file.Provider
wantFileProviderFilename string
desc string
fileProvider *file.Provider
wantFileProviderFilename string
wantFileProviderTraefikFile string
}{
{
desc: "no filename for file provider given",
fileProvider: &file.Provider{},
wantFileProviderFilename: defaultConfigFile,
desc: "no filename for file provider given",
fileProvider: &file.Provider{},
wantFileProviderFilename: "",
wantFileProviderTraefikFile: defaultConfigFile,
},
{
desc: "filename for file provider given",
fileProvider: &file.Provider{BaseProvider: provider.BaseProvider{Filename: "other.toml"}},
wantFileProviderFilename: "other.toml",
desc: "filename for file provider given",
fileProvider: &file.Provider{BaseProvider: provider.BaseProvider{Filename: "other.toml"}},
wantFileProviderFilename: "other.toml",
wantFileProviderTraefikFile: defaultConfigFile,
},
{
desc: "directory for file provider given",
fileProvider: &file.Provider{Directory: "/"},
wantFileProviderFilename: "",
desc: "directory for file provider given",
fileProvider: &file.Provider{Directory: "/"},
wantFileProviderFilename: "",
wantFileProviderTraefikFile: defaultConfigFile,
},
}
@@ -98,6 +102,7 @@ func TestSetEffectiveConfigurationFileProviderFilename(t *testing.T) {
gc.SetEffectiveConfiguration(defaultConfigFile)
assert.Equal(t, test.wantFileProviderFilename, gc.File.Filename)
assert.Equal(t, test.wantFileProviderTraefikFile, gc.File.TraefikFile)
})
}
}

View File

@@ -170,7 +170,7 @@ func TestEntryPoints_Set(t *testing.T) {
name: "all parameters camelcase",
expression: "Name:foo " +
"Address::8000 " +
"TLS:goo,gii " +
"TLS:goo,gii;foo,fii " +
"TLS " +
"CA:car " +
"CA.Optional:true " +
@@ -203,6 +203,10 @@ func TestEntryPoints_Set(t *testing.T) {
CertFile: tls.FileOrContent("goo"),
KeyFile: tls.FileOrContent("gii"),
},
{
CertFile: tls.FileOrContent("foo"),
KeyFile: tls.FileOrContent("fii"),
},
},
ClientCA: tls.ClientCA{
Files: []string{"car"},
@@ -272,7 +276,7 @@ func TestEntryPoints_Set(t *testing.T) {
name: "all parameters lowercase",
expression: "Name:foo " +
"address::8000 " +
"tls:goo,gii " +
"tls:goo,gii;foo,fii " +
"tls " +
"ca:car " +
"ca.Optional:true " +
@@ -303,6 +307,10 @@ func TestEntryPoints_Set(t *testing.T) {
CertFile: tls.FileOrContent("goo"),
KeyFile: tls.FileOrContent("gii"),
},
{
CertFile: tls.FileOrContent("foo"),
KeyFile: tls.FileOrContent("fii"),
},
},
ClientCA: tls.ClientCA{
Files: []string{"car"},

View File

@@ -1,13 +1,13 @@
# BoltDB Backend
# BoltDB Provider
Træfik can be configured to use BoltDB as a backend configuration.
Træfik can be configured to use BoltDB as a provider.
```toml
################################################################
# BoltDB configuration backend
# BoltDB Provider
################################################################
# Enable BoltDB configuration backend.
# Enable BoltDB Provider.
[boltdb]
# BoltDB file.
@@ -56,4 +56,4 @@ filename = "boltdb.tmpl"
# insecureSkipVerify = true
```
To enable constraints see [backend-specific constraints section](/configuration/commons/#backend-specific).
To enable constraints see [provider-specific constraints section](/configuration/commons/#provider-specific).

View File

@@ -1,13 +1,13 @@
# Consul Key-Value Backend
# Consul Key-Value Provider
Træfik can be configured to use Consul as a backend configuration.
Træfik can be configured to use Consul as a provider.
```toml
################################################################
# Consul KV configuration backend
# Consul KV Provider
################################################################
# Enable Consul KV configuration backend.
# Enable Consul KV Provider.
[consul]
# Consul server endpoint.
@@ -56,6 +56,6 @@ prefix = "traefik"
# insecureSkipVerify = true
```
To enable constraints see [backend-specific constraints section](/configuration/commons/#backend-specific).
To enable constraints see [provider-specific constraints section](/configuration/commons/#provider-specific).
Please refer to the [Key Value storage structure](/user-guide/kv-config/#key-value-storage-structure) section to get documentation on Traefik KV structure.

View File

@@ -1,13 +1,13 @@
# Consul Catalog backend
# Consul Catalog Provider
Træfik can be configured to use service discovery catalog of Consul as a backend configuration.
Træfik can be configured to use service discovery catalog of Consul as a provider.
```toml
################################################################
# Consul Catalog configuration backend
# Consul Catalog Provider
################################################################
# Enable Consul Catalog configuration backend.
# Enable Consul Catalog Provider.
[consulCatalog]
# Consul server endpoint.
@@ -76,9 +76,9 @@ prefix = "traefik"
# templateVersion = 2
```
This backend will create routes matching on hostname based on the service name used in Consul.
This provider will create routes matching on hostname based on the service name used in Consul.
To enable constraints see [backend-specific constraints section](/configuration/commons/#backend-specific).
To enable constraints see [provider-specific constraints section](/configuration/commons/#provider-specific).
## Tags

View File

@@ -1,16 +1,16 @@
# Docker Backend
# Docker Provider
Træfik can be configured to use Docker as a backend configuration.
Træfik can be configured to use Docker as a provider.
## Docker
```toml
################################################################
# Docker configuration backend
# Docker Provider
################################################################
# Enable Docker configuration backend.
# Enable Docker Provider.
[docker]
# Docker server endpoint. Can be a tcp or a unix socket endpoint.
@@ -82,17 +82,17 @@ swarmMode = false
# insecureSkipVerify = true
```
To enable constraints see [backend-specific constraints section](/configuration/commons/#backend-specific).
To enable constraints see [provider-specific constraints section](/configuration/commons/#provider-specific).
## Docker Swarm Mode
```toml
################################################################
# Docker Swarm Mode configuration backend
# Docker Swarm Mode Provider
################################################################
# Enable Docker configuration backend.
# Enable Docker Provider.
[docker]
# Docker server endpoint.
@@ -159,7 +159,7 @@ exposedByDefault = false
# insecureSkipVerify = true
```
To enable constraints see [backend-specific constraints section](/configuration/commons/#backend-specific).
To enable constraints see [provider-specific constraints section](/configuration/commons/#provider-specific).
## Labels: overriding default behavior
@@ -218,7 +218,7 @@ Labels can be used on containers to override default behavior.
| `traefik.backend.loadbalancer.swarm=true` | Use Swarm's inbuilt load balancer (only relevant under Swarm Mode). |
| `traefik.backend.maxconn.amount=10` | Set a maximum number of connections to the backend.<br>Must be used in conjunction with the below label to take effect. |
| `traefik.backend.maxconn.extractorfunc=client.ip` | Set the function to be used against the request to determine what to limit maximum connections to the backend by.<br>Must be used in conjunction with the above label to take effect. |
| `traefik.frontend.auth.basic=EXPR` | Sets basic authentication for that frontend in CSV format: `User:Hash,User:Hash` |
| `traefik.frontend.auth.basic=EXPR` | Sets basic authentication for that frontend in CSV format: `User:Hash,User:Hash` [2] |
| `traefik.frontend.entryPoints=http,https` | Assign this frontend to entry points `http` and `https`.<br>Overrides `defaultEntryPoints` |
| `traefik.frontend.errors.<name>.backend=NAME` | See [custom error pages](/configuration/commons/#custom-error-pages) section. |
| `traefik.frontend.errors.<name>.query=PATH` | See [custom error pages](/configuration/commons/#custom-error-pages) section. |
@@ -243,6 +243,10 @@ If a container is linked to several networks, be sure to set the proper network
For instance when deploying docker `stack` from compose files, the compose defined networks will be prefixed with the `stack` name.
Or if your service references external network use it's name instead.
[2] `traefik.frontend.auth.basic=EXPR`:
To create `user:password` pair, it's possible to use this command `echo $(htpasswd -nb user password) | sed -e s/\\$/\\$\\$/g`.
The result will be `user:$$apr1$$9Cv/OMGj$$ZomWQzuQbL.3TRCS81A1g/`, note additional symbol `$` makes escaping.
#### Custom Headers
| Label | Description |
@@ -284,12 +288,12 @@ Segment labels override the default behavior.
| Label | Description |
|---------------------------------------------------------------------------|-------------------------------------------------------------|
| `traefik.<segment_name>.backend=BACKEND` | Same as `traefik.backend` |
| `traefik.<segment_name>.domain=DOMAIN` | Same as `traefik.domain` |
| `traefik.<segment_name>.port=PORT` | Same as `traefik.port` |
| `traefik.<segment_name>.protocol=http` | Same as `traefik.protocol` |
| `traefik.<segment_name>.weight=10` | Same as `traefik.weight` |
| `traefik.<segment_name>.frontend.auth.basic=EXPR` | Same as `traefik.frontend.auth.basic` |
| `traefik.<segment_name>.frontend.backend=BACKEND` | Same as `traefik.frontend.backend` |
| `traefik.<segment_name>.frontend.entryPoints=https` | Same as `traefik.frontend.entryPoints` |
| `traefik.<segment_name>.frontend.errors.<name>.backend=NAME` | Same as `traefik.frontend.errors.<name>.backend` |
| `traefik.<segment_name>.frontend.errors.<name>.query=PATH` | Same as `traefik.frontend.errors.<name>.query` |

View File

@@ -1,15 +1,15 @@
# DynamoDB Backend
# DynamoDB Provider
Træfik can be configured to use Amazon DynamoDB as a backend configuration.
Træfik can be configured to use Amazon DynamoDB as a provider.
## Configuration
```toml
################################################################
# DynamoDB configuration backend
# DynamoDB Provider
################################################################
# Enable DynamoDB configuration backend.
# Enable DynamoDB Provider.
[dynamodb]
# Region to use when connecting to AWS.
@@ -68,4 +68,3 @@ Items in the `dynamodb` table must have three attributes:
See `types/types.go` for details.
The presence or absence of this attribute determines its type.
So an item should never have both a `frontend` and a `backend` attribute.

View File

@@ -1,15 +1,15 @@
# ECS Backend
# ECS Provider
Træfik can be configured to use Amazon ECS as a backend configuration.
Træfik can be configured to use Amazon ECS as a provider.
## Configuration
```toml
################################################################
# ECS configuration backend
# ECS Provider
################################################################
# Enable ECS configuration backend.
# Enable ECS Provider.
[ecs]
# ECS Cluster Name.

View File

@@ -1,13 +1,13 @@
# Etcd Backend
# Etcd Provider
Træfik can be configured to use Etcd as a backend configuration.
Træfik can be configured to use Etcd as a provider.
```toml
################################################################
# Etcd configuration backend
# Etcd Provider
################################################################
# Enable Etcd configuration backend.
# Enable Etcd Provider.
[etcd]
# Etcd server endpoint.
@@ -66,7 +66,7 @@ useAPIV3 = true
# insecureSkipVerify = true
```
To enable constraints see [backend-specific constraints section](/configuration/commons/#backend-specific).
To enable constraints see [provider-specific constraints section](/configuration/commons/#provider-specific).
Please refer to the [Key Value storage structure](/user-guide/kv-config/#key-value-storage-structure) section to get documentation on Traefik KV structure.

View File

@@ -1,13 +1,13 @@
# Eureka Backend
# Eureka Provider
Træfik can be configured to use Eureka as a backend configuration.
Træfik can be configured to use Eureka as a provider.
```toml
################################################################
# Eureka configuration backend
# Eureka Provider
################################################################
# Enable Eureka configuration backend.
# Enable Eureka Provider.
[eureka]
# Eureka server endpoint.

View File

@@ -1,4 +1,4 @@
# File Backends
# File Provider
Træfik can be configured with a file.

View File

@@ -1,6 +1,6 @@
# Kubernetes Ingress Backend
# Kubernetes Ingress Provider
Træfik can be configured to use Kubernetes Ingress as a backend configuration.
Træfik can be configured to use Kubernetes Ingress as a provider.
See also [Kubernetes user guide](/user-guide/kubernetes).
@@ -8,10 +8,10 @@ See also [Kubernetes user guide](/user-guide/kubernetes).
```toml
################################################################
# Kubernetes Ingress configuration backend
# Kubernetes Ingress Provider
################################################################
# Enable Kubernetes Ingress configuration backend.
# Enable Kubernetes Ingress Provider.
[kubernetes]
# Kubernetes server endpoint.
@@ -112,7 +112,7 @@ Although traefik will connect directly to the endpoints (pods), it still checks
If the service port defined in the ingress spec is 443, then the backend communication protocol is assumed to be TLS, and will connect via TLS automatically.
!!! note
Please note that by enabling TLS communication between traefik and your pods, you will have to have trusted certificates that have the proper trust chain and IP subject name.
Please note that by enabling TLS communication between traefik and your pods, you will have to have trusted certificates that have the proper trust chain and IP subject name.
If this is not an option, you may need to skip TLS certificate verification.
See the [insecureSkipVerify](/configuration/commons/#main-section) setting for more details.
@@ -137,7 +137,7 @@ The following general annotations are applicable on the Ingress object:
| `traefik.ingress.kubernetes.io/redirect-replacement: http://mydomain/$1` | Redirect to another URL for that frontend. Must be set with `traefik.ingress.kubernetes.io/redirect-regex`. |
| `traefik.ingress.kubernetes.io/rewrite-target: /users` | Replaces each matched Ingress path with the specified one, and adds the old path to the `X-Replaced-Path` header. |
| `traefik.ingress.kubernetes.io/rule-type: PathPrefixStrip` | Override the default frontend rule type. Default: `PathPrefix`. |
| `traefik.ingress.kubernetes.io/whitelist-source-range: "1.2.3.0/24, fe80::/16"` | A comma-separated list of IP ranges permitted for access. all source IPs are permitted if the list is empty or a single range is ill-formatted. |
| `traefik.ingress.kubernetes.io/whitelist-source-range: "1.2.3.0/24, fe80::/16"` | A comma-separated list of IP ranges permitted for access. all source IPs are permitted if the list is empty or a single range is ill-formatted. Please note, you may have to set `service.spec.externalTrafficPolicy` to the value `Local` to preserve the source IP of the request for filtering. Please see [this link](https://kubernetes.io/docs/tutorials/services/source-ip/) for more information.|
| `traefik.ingress.kubernetes.io/app-root: "/index.html"` | Redirects all requests for `/` to the defined path. (4) |
<1> `traefik.ingress.kubernetes.io/error-pages` example:
@@ -261,4 +261,4 @@ More information are available in the [User Guide](/user-guide/kubernetes/#add-
!!! note
Only TLS certificates provided by users can be stored in Kubernetes Secrets.
[Let's Encrypt](https://letsencrypt.org) certificates cannot be managed in Kubernets Secrets yet.
[Let's Encrypt](https://letsencrypt.org) certificates cannot be managed in Kubernets Secrets yet.

View File

@@ -1,6 +1,6 @@
# Marathon Backend
# Marathon Provider
Træfik can be configured to use Marathon as a backend configuration.
Træfik can be configured to use Marathon as a provider.
See also [Marathon user guide](/user-guide/marathon).
@@ -9,10 +9,10 @@ See also [Marathon user guide](/user-guide/marathon).
```toml
################################################################
# Mesos/Marathon configuration backend
# Mesos/Marathon Provider
################################################################
# Enable Marathon configuration backend.
# Enable Marathon Provider.
[marathon]
# Marathon server endpoint.
@@ -157,7 +157,7 @@ domain = "marathon.localhost"
# respectReadinessChecks = true
```
To enable constraints see [backend-specific constraints section](/configuration/commons/#backend-specific).
To enable constraints see [provider-specific constraints section](/configuration/commons/#provider-specific).
## Labels: overriding default behavior
@@ -255,13 +255,13 @@ Segment labels override the default behavior.
| Label | Description |
|---------------------------------------------------------------------------|-------------------------------------------------------------|
| `traefik.<segment_name>.backend=BACKEND` | Same as `traefik.backend` |
| `traefik.<segment_name>.domain=DOMAIN` | Same as `traefik.domain` |
| `traefik.<segment_name>.portIndex=1` | Same as `traefik.portIndex` |
| `traefik.<segment_name>.port=PORT` | Same as `traefik.port` |
| `traefik.<segment_name>.protocol=http` | Same as `traefik.protocol` |
| `traefik.<segment_name>.weight=10` | Same as `traefik.weight` |
| `traefik.<segment_name>.frontend.auth.basic=EXPR` | Same as `traefik.frontend.auth.basic` |
| `traefik.<segment_name>.frontend.backend=BACKEND` | Same as `traefik.frontend.backend` |
| `traefik.<segment_name>.frontend.entryPoints=https` | Same as `traefik.frontend.entryPoints` |
| `traefik.<segment_name>.frontend.errors.<name>.backend=NAME` | Same as `traefik.frontend.errors.<name>.backend` |
| `traefik.<segment_name>.frontend.errors.<name>.query=PATH` | Same as `traefik.frontend.errors.<name>.query` |

View File

@@ -1,13 +1,13 @@
# Mesos Generic Backend
# Mesos Generic Provider
Træfik can be configured to use Mesos as a backend configuration.
Træfik can be configured to use Mesos as a provider.
```toml
################################################################
# Mesos configuration backend
# Mesos Provider
################################################################
# Enable Mesos configuration backend.
# Enable Mesos Provider.
[mesos]
# Mesos server endpoint.

View File

@@ -1,15 +1,15 @@
# Rancher Backend
# Rancher Provider
Træfik can be configured to use Rancher as a backend configuration.
Træfik can be configured to use Rancher as a provider.
## Global Configuration
```toml
################################################################
# Rancher configuration backend
# Rancher Provider
################################################################
# Enable Rancher configuration backend.
# Enable Rancher Provider.
[rancher]
# Default domain used.
@@ -64,13 +64,13 @@ enableServiceHealthFilter = true
# templateVersion = 2
```
To enable constraints see [backend-specific constraints section](/configuration/commons/#backend-specific).
To enable constraints see [provider-specific constraints section](/configuration/commons/#provider-specific).
## Rancher Metadata Service
```toml
# Enable Rancher metadata service configuration backend instead of the API
# configuration backend.
# Enable Rancher metadata service provider instead of the API
# provider.
#
# Optional
# Default: false
@@ -97,7 +97,7 @@ prefix = "/2016-07-29"
## Rancher API
```toml
# Enable Rancher API configuration backend.
# Enable Rancher API provider.
#
# Optional
# Default: true
@@ -222,12 +222,12 @@ Segment labels override the default behavior.
| Label | Description |
|---------------------------------------------------------------------------|-------------------------------------------------------------|
| `traefik.<segment_name>.backend=BACKEND` | Same as `traefik.backend` |
| `traefik.<segment_name>.domain=DOMAIN` | Same as `traefik.domain` |
| `traefik.<segment_name>.port=PORT` | Same as `traefik.port` |
| `traefik.<segment_name>.protocol=http` | Same as `traefik.protocol` |
| `traefik.<segment_name>.weight=10` | Same as `traefik.weight` |
| `traefik.<segment_name>.frontend.auth.basic=EXPR` | Same as `traefik.frontend.auth.basic` |
| `traefik.<segment_name>.frontend.backend=BACKEND` | Same as `traefik.frontend.backend` |
| `traefik.<segment_name>.frontend.entryPoints=https` | Same as `traefik.frontend.entryPoints` |
| `traefik.<segment_name>.frontend.errors.<name>.backend=NAME` | Same as `traefik.frontend.errors.<name>.backend` |
| `traefik.<segment_name>.frontend.errors.<name>.query=PATH` | Same as `traefik.frontend.errors.<name>.query` |

View File

@@ -1,4 +1,4 @@
# Rest Backend
# Rest Provider
Træfik can be configured:
@@ -7,7 +7,7 @@ Træfik can be configured:
## Configuration
```toml
# Enable rest backend.
# Enable REST Provider.
[rest]
# Name of the related entry point
#

View File

@@ -1,6 +1,6 @@
# Azure Service Fabric Backend
# Azure Service Fabric Provider
Træfik can be configured to use Azure Service Fabric as a backend configuration.
Træfik can be configured to use Azure Service Fabric as a provider.
See [this repository for an example deployment package and further documentation.](https://aka.ms/traefikonsf)
@@ -8,10 +8,10 @@ See [this repository for an example deployment package and further documentation
```toml
################################################################
# Azure Service Fabric provider
# Azure Service Fabric Provider
################################################################
# Enable Azure Service Fabric configuration backend
# Enable Azure Service Fabric Provider
[serviceFabric]
# Azure Service Fabric Management Endpoint
@@ -61,7 +61,7 @@ Here is an example of an extension setting Træfik labels:
<Extension Name="Traefik">
<Labels xmlns="http://schemas.microsoft.com/2015/03/fabact-no-schema">
<Label Key="traefik.frontend.rule.example2">PathPrefixStrip: /a/path/to/strip</Label>
<Label Key="traefik.enable">true</Label>
<Label Key="traefik.enable">true</Label>
<Label Key="traefik.frontend.passHostHeader">true</Label>
</Labels>
</Extension>
@@ -98,8 +98,9 @@ Labels, set through extensions or the property manager, can be used on services
|------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `traefik.enable=false` | Disable this container in Træfik |
| `traefik.backend.circuitbreaker.expression=EXPR` | Create a [circuit breaker](/basics/#backends) to be used against the backend |
| `traefik.backend.group.name` | Group all services with the same name into a single backend in Træfik |
| `traefik.backend.group.weight` | Set the weighting of the current services nodes in the backend group |
| `traefik.servicefabric.groupname` | Group all services with the same name into a single backend in Træfik |
| `traefik.servicefabric.groupweight` | Set the weighting of the current services nodes in the backend group |
| `traefik.servicefabric.enablelabeloverrides` | Toggle whether labels can be overridden using the Service Fabric Property Manager API |
| `traefik.backend.healthcheck.path=/health` | Enable health check for the backend, hitting the container at `path`. |
| `traefik.backend.healthcheck.port=8080` | Allow to use a different port for the health check. |
| `traefik.backend.healthcheck.interval=1s` | Define the health check interval. |

View File

@@ -1,4 +1,4 @@
# Web Backend
# Web Provider
!!! danger "DEPRECATED"
The web provider is deprecated, please use the [api](/configuration/api.md), the [ping](/configuration/ping.md), the [metrics](/configuration/metrics) and the [rest](/configuration/backends/rest.md) provider.
@@ -12,7 +12,7 @@ Træfik can be configured:
## Configuration
```toml
# Enable web backend.
# Enable Web Provider.
[web]
# Web administration port.

View File

@@ -1,13 +1,13 @@
# Zookeeper Backend
# Zookeeper Provider
Træfik can be configured to use Zookeeper as a backend configuration.
Træfik can be configured to use Zookeeper as a provider.
```toml
################################################################
# Zookeeper configuration backend
# Zookeeper Provider
################################################################
# Enable Zookeeperconfiguration backend.
# Enable Zookeeper Provider.
[zookeeper]
# Zookeeper server endpoint.
@@ -56,6 +56,6 @@ prefix = "traefik"
# insecureSkipVerify = true
```
To enable constraints see [backend-specific constraints section](/configuration/commons/#backend-specific).
To enable constraints see [provider-specific constraints section](/configuration/commons/#provider-specific).
Please refer to the [Key Value storage structure](/user-guide/kv-config/#key-value-storage-structure) section to get documentation on Traefik KV structure.

View File

@@ -33,7 +33,7 @@
#
# checkNewVersion = false
# Backends throttle duration.
# Providers throttle duration.
#
# Optional
# Default: "2s"
@@ -85,7 +85,7 @@ Can be provided in a format supported by [time.ParseDuration](https://golang.org
If no units are provided, the value is parsed assuming seconds.
**Note:** in this time frame no new requests are accepted.
- `providersThrottleDuration`: Backends throttle duration: minimum duration in seconds between 2 events from providers before applying a new configuration.
- `providersThrottleDuration`: Providers throttle duration: minimum duration in seconds between 2 events from providers before applying a new configuration.
It avoids unnecessary reloads if multiples events are sent in a short amount of time.
Can be provided in a format supported by [time.ParseDuration](https://golang.org/pkg/time/#ParseDuration) or as raw values (digits).
If no units are provided, the value is parsed assuming seconds.
@@ -108,7 +108,7 @@ Each frontend can specify its own entrypoints.
In a micro-service architecture, with a central service discovery, setting constraints limits Træfik scope to a smaller number of routes.
Træfik filters services according to service attributes/tags set in your configuration backends.
Træfik filters services according to service attributes/tags set in your providers.
Supported filters:
@@ -136,9 +136,9 @@ constraints = ["tag==us-*"]
constraints = ["tag!=us-*", "tag!=asia-*"]
```
### Backend-specific
### provider-specific
Supported backends:
Supported Providers:
- Docker
- Consul K/V
@@ -151,12 +151,12 @@ Supported backends:
- Kubernetes (using a provider-specific mechanism based on label selectors)
```toml
# Backend-specific constraint
# Provider-specific constraint
[consulCatalog]
# ...
constraints = ["tag==api"]
# Backend-specific constraint
# Provider-specific constraint
[marathon]
# ...
constraints = ["tag==api", "tag!=v*-beta"]
@@ -421,12 +421,12 @@ idleTimeout = "360s"
!!! warning
For advanced users only.
Supported by all backends except: File backend, Web backend and DynamoDB backend.
Supported by all providers except: File Provider, Web Provider and DynamoDB Provider.
```toml
[backend_name]
[provider_name]
# Override default configuration template. For advanced users :)
# Override default provider configuration template. For advanced users :)
#
# Optional
# Default: ""

View File

@@ -106,7 +106,7 @@ traefik:
```ini
Name:foo
Address::80
TLS:goo,gii
TLS:/my/path/foo.cert,/my/path/foo.key;/my/path/goo.cert,/my/path/goo.key;/my/path/hoo.cert,/my/path/hoo.key
TLS
CA:car
CA.Optional:true

View File

@@ -54,7 +54,7 @@ Træfik supports two backends: Jaeger and Zipkin.
```
!!! warning
Træfik is only able to send data over compact thrift protocol to the [Jaeger agent](https://www.jaegertracing.io/docs/deployment/#agent).
Træfik is only able to send data over compact thrift protocol to the [Jaeger agent](https://www.jaegertracing.io/docs/deployment/#agent).
## Zipkin

Binary file not shown.

Before

Width:  |  Height:  |  Size: 189 KiB

After

Width:  |  Height:  |  Size: 208 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 218 KiB

After

Width:  |  Height:  |  Size: 274 KiB

View File

@@ -12,7 +12,7 @@
Træfik is a modern HTTP reverse proxy and load balancer that makes deploying microservices easy.
Træfik integrates with your existing infrastructure components ([Docker](https://www.docker.com/), [Swarm mode](https://docs.docker.com/engine/swarm/), [Kubernetes](https://kubernetes.io), [Marathon](https://mesosphere.github.io/marathon/), [Consul](https://www.consul.io/), [Etcd](https://coreos.com/etcd/), [Rancher](https://rancher.com), [Amazon ECS](https://aws.amazon.com/ecs), ...) and configures itself automatically and dynamically.
Telling Træfik where your orchestrator is could be the _only_ configuration step you need to do.
Pointing Træfik at your orchestrator should be the _only_ configuration step you need.
## Overview
@@ -47,7 +47,7 @@ _(But if you'd rather configure some of your routes manually, Træfik supports t
- Packaged as a single binary file (made with :heart: with go) and available as a [tiny](https://microbadger.com/images/traefik) [official](https://hub.docker.com/r/_/traefik/) docker image
## Supported backends
## Supported Providers
- [Docker](/configuration/backends/docker/) / [Swarm mode](/configuration/backends/docker/#docker-swarm-mode)
- [Kubernetes](/configuration/backends/kubernetes/)
@@ -166,7 +166,7 @@ IP: 172.27.0.4
### 4 — Enjoy Træfik's Magic
Now that you have a basic understanding of how Træfik can automatically create the routes to your services and load balance them, it might be time to dive into [the documentation](/) and let Træfik work for you!
Whatever your infrastructure is, there is probably [an available Træfik backend](/#supported-backends) that will do the job.
Whatever your infrastructure is, there is probably [an available Træfik provider](/#supported-providers) that will do the job.
Our recommendation would be to see for yourself how simple it is to enable HTTPS with [Træfik's let's encrypt integration](/user-guide/examples/#lets-encrypt-support) using the dedicated [user guide](/user-guide/docker-and-lets-encrypt/).

View File

@@ -113,7 +113,7 @@ This is the minimum configuration required to do the following:
- Log `ERROR`-level messages (or more severe) to the console, but silence `DEBUG`-level messages
- Check for new versions of Træfik periodically
- Create two entry points, namely an `HTTP` endpoint on port `80`, and an `HTTPS` endpoint on port `443` where all incoming traffic on port `80` will immediately get redirected to `HTTPS`.
- Enable the Docker configuration backend and listen for container events on the Docker unix socket we've mounted earlier. However, **new containers will not be exposed by Træfik by default, we'll get into this in a bit!**
- Enable the Docker provider and listen for container events on the Docker unix socket we've mounted earlier. However, **new containers will not be exposed by Træfik by default, we'll get into this in a bit!**
- Enable automatic request and configuration of SSL certificates using Let's Encrypt.
These certificates will be stored in the `acme.json` file, which you can back-up yourself and store off-premises.
@@ -123,7 +123,7 @@ Alright, let's boot the container. From the `/opt/traefik` directory, run `docke
Now that we've fully configured and started Træfik, it's time to get our applications running!
Let's take a simple example of a micro-service project consisting of various services, where some will be exposed to the outside world and some will not.
Let's take a simple example of a micro-service project consisting of various services, where some will be exposed to the outside world and some will not.
The `docker-compose.yml` of our project looks like this:
@@ -145,12 +145,11 @@ services:
expose:
- "9000"
labels:
- "traefik.backend=my-awesome-app-app"
- "traefik.docker.network=web"
- "traefik.frontend.rule=Host:app.my-awesome-app.org"
- "traefik.enable=true"
- "traefik.port=9000"
- "traefik.default.protocol=http"
- "traefik.basic.frontend.rule=Host:app.my-awesome-app.org"
- "traefik.basic.port=9000"
- "traefik.basic.protocol=http"
- "traefik.admin.frontend.rule=Host:admin-app.my-awesome-app.org"
- "traefik.admin.protocol=https"
- "traefik.admin.port=9443"
@@ -204,12 +203,11 @@ Thanks to Docker labels, we can tell Træfik how to create its internal routing
Let's take a look at the labels themselves for the `app` service, which is a HTTP webservice listing on port 9000:
```yaml
- "traefik.backend=my-awesome-app-app"
- "traefik.docker.network=web"
- "traefik.frontend.rule=Host:app.my-awesome-app.org"
- "traefik.enable=true"
- "traefik.port=9000"
- "traefik.default.protocol=http"
- "traefik.basic.frontend.rule=Host:app.my-awesome-app.org"
- "traefik.basic.port=9000"
- "traefik.basic.protocol=http"
- "traefik.admin.frontend.rule=Host:admin-app.my-awesome-app.org"
- "traefik.admin.protocol=https"
- "traefik.admin.port=9443"
@@ -221,11 +219,11 @@ We use both `container labels` and `service labels`.
First, we specify the `backend` name which corresponds to the actual service we're routing **to**.
We also tell Træfik to use the `web` network to route HTTP traffic to this container.
We also tell Træfik to use the `web` network to route HTTP traffic to this container.
With the `traefik.enable` label, we tell Træfik to include this container in its internal configuration.
With the `frontend.rule` label, we tell Træfik that we want to route to this container if the incoming HTTP request contains the `Host` `app.my-awesome-app.org`.
Essentially, this is the actual rule used for Layer-7 load balancing.
Essentially, this is the actual rule used for Layer-7 load balancing.
Finally but not unimportantly, we tell Træfik to route **to** port `9000`, since that is the actual TCP/IP port the container actually listens on.
@@ -236,11 +234,11 @@ Finally but not unimportantly, we tell Træfik to route **to** port `9000`, sinc
When both `container labels` and `service labels` are defined, `container labels` are just used as default values for missing `service labels` but no frontend/backend are going to be defined only with these labels.
Obviously, labels `traefik.frontend.rule` and `traefik.port` described above, will only be used to complete information set in `service labels` during the container frontends/bakends creation.
In the example, two service names are defined : `default` and `admin`.
In the example, two service names are defined : `basic` and `admin`.
They allow creating two frontends and two backends.
- `default` has only one `service label` : `traefik.default.protocol`.
Træfik will use values set in `traefik.frontend.rule` and `traefik.port` to create the `default` frontend and backend.
- `basic` has only one `service label` : `traefik.basic.protocol`.
Træfik will use values set in `traefik.frontend.rule` and `traefik.port` to create the `basic` frontend and backend.
The frontend listens to incoming HTTP requests which contain the `Host` `app.my-awesome-app.org` and redirect them in `HTTP` to the port `9000` of the backend.
- `admin` has all the `services labels` needed to create the `admin` frontend and backend (`traefik.admin.frontend.rule`, `traefik.admin.protocol`, `traefik.admin.port`).
Træfik will create a frontend to listen to incoming HTTP requests which contain the `Host` `admin-app.my-awesome-app.org` and redirect them in `HTTPS` to the port `9443` of the backend.

View File

@@ -68,7 +68,7 @@ defaultEntryPoints = ["http", "https"]
[acme]
email = "test@traefik.io"
storage = "acme.json"
caServer = "http://172.18.0.1:4000/directory"
caServer = "https://acme-staging-v02.api.letsencrypt.org/directory"
entryPoint = "https"
[acme.httpChallenge]
entryPoint = "http"
@@ -103,7 +103,7 @@ Træfik generates these certificates when it starts and it needs to be restart i
email = "test@traefik.io"
storage = "acme.json"
onHostRule = true
caServer = "http://172.18.0.1:4000/directory"
caServer = "https://acme-staging-v02.api.letsencrypt.org/directory"
entryPoint = "https"
[acme.httpChallenge]
entryPoint = "http"
@@ -140,7 +140,7 @@ If a backend is added with a `onHost` rule, Træfik will automatically generate
email = "test@traefik.io"
storage = "acme.json"
onDemand = true
caServer = "http://172.18.0.1:4000/directory"
caServer = "https://acme-staging-v02.api.letsencrypt.org/directory"
entryPoint = "https"
[acme.httpChallenge]
entryPoint = "http"
@@ -167,7 +167,7 @@ This configuration allows generating a Let's Encrypt certificate (thanks to `HTT
[acme]
email = "test@traefik.io"
storage = "acme.json"
caServer = "http://172.18.0.1:4000/directory"
caServer = "https://acme-staging-v02.api.letsencrypt.org/directory"
entryPoint = "https"
[acme.dnsChallenge]
provider = "digitalocean" # DNS Provider name (cloudflare, OVH, gandi...)

View File

@@ -76,7 +76,7 @@ defaultEntryPoints = ["http", "https"]
address = ":80"
[entryPoints.https]
address = ":443"
[entryPoints.https.tls]
[[entryPoints.https.tls.certificates]]
certFile = "integration/fixtures/https/snitest.com.cert"
@@ -164,7 +164,7 @@ If a Consul ACL is used to restrict Træfik read/write access, one of the follow
key "traefik" {
policy = "write"
},
session "" {
policy = "write"
}
@@ -266,6 +266,10 @@ Here is the toml configuration we would like to store in the store :
backend = "backend1"
passHostHeader = true
priority = 10
basicAuth = [
"test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/",
"test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0",
]
entrypoints = ["https"] # overrides defaultEntryPoints
[frontends.frontend2.routes.test_1]
rule = "Host:{subdomain:[a-z]+}.localhost"
@@ -325,13 +329,15 @@ And there, the same dynamic configuration in a KV Store (using `prefix = "traefi
- frontend 2
| Key | Value |
|----------------------------------------------------|--------------------|
| `/traefik/frontends/frontend2/backend` | `backend1` |
| `/traefik/frontends/frontend2/passhostheader` | `true` |
| `/traefik/frontends/frontend2/priority` | `10` |
| `/traefik/frontends/frontend2/entrypoints` | `http,https` |
| `/traefik/frontends/frontend2/routes/test_2/rule` | `PathPrefix:/test` |
| Key | Value |
|----------------------------------------------------|-----------------------------------------------|
| `/traefik/frontends/frontend2/backend` | `backend1` |
| `/traefik/frontends/frontend2/passhostheader` | `true` |
| `/traefik/frontends/frontend2/priority` | `10` |
| `/traefik/frontends/frontend2/basicauth/0` | `test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/` |
| `/traefik/frontends/frontend2/basicauth/1` | `test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0` |
| `/traefik/frontends/frontend2/entrypoints` | `http,https` |
| `/traefik/frontends/frontend2/routes/test_2/rule` | `PathPrefix:/test` |
- certificate 1
@@ -422,7 +428,7 @@ Træfik will not start but the [static configuration](/basics/#static-trfik-conf
If you configured ACME (Let's Encrypt), your registration account and your certificates will also be uploaded.
If you configured a file backend `[file]`, all your dynamic configuration (backends, frontends...) will be uploaded to the Key-value store.
If you configured a file provider `[file]`, all your dynamic configuration (backends, frontends...) will be uploaded to the Key-value store.
To upload your ACME certificates to the KV store, get your Traefik TOML file and add the new `storage` option in the `acme` section:

View File

@@ -101,7 +101,7 @@ Let's explain this command:
| `--constraint=node.role==manager` | we ask docker to schedule Træfik on a manager node. |
| `--mount type=bind,source=/var/run/docker.sock,target=/var/run/docker.sock` | we bind mount the docker socket where Træfik is scheduled to be able to speak to the daemon. |
| `--network traefik-net` | we attach the Træfik service (and thus the underlying container) to the `traefik-net` network. |
| `--docker` | enable docker backend, and `--docker.swarmMode` to enable the swarm mode on Træfik. |
| `--docker` | enable docker provider, and `--docker.swarmMode` to enable the swarm mode on Træfik. |
| `--api | activate the webUI on port 8080 |

View File

@@ -104,7 +104,7 @@ Let's explain this command:
| `--net=my-net` | run the container on the network my-net |
| `-v /var/lib/boot2docker/:/ssl` | mount the ssl keys generated by docker-machine |
| `-c /dev/null` | empty config file |
| `--docker` | enable docker backend |
| `--docker` | enable docker provider |
| `--docker.endpoint=tcp://172.18.0.1:2376` | connect to the swarm master using the docker_gwbridge network |
| `--docker.tls` | enable TLS using the docker-machine keys |
| `--api` | activate the webUI on port 8080 |

View File

@@ -101,19 +101,25 @@ func openAccessLogFile(filePath string) (*os.File, error) {
return file, nil
}
// GetLogDataTable gets the request context object that contains logging data. This accretes
// data as the request passes through the middleware chain.
// GetLogDataTable gets the request context object that contains logging data.
// This creates data as the request passes through the middleware chain.
func GetLogDataTable(req *http.Request) *LogData {
return req.Context().Value(DataTableKey).(*LogData)
if ld, ok := req.Context().Value(DataTableKey).(*LogData); ok {
return ld
}
log.Errorf("%s is nil", DataTableKey)
return &LogData{Core: make(CoreLogData)}
}
func (l *LogHandler) ServeHTTP(rw http.ResponseWriter, req *http.Request, next http.HandlerFunc) {
now := time.Now().UTC()
core := make(CoreLogData)
core := CoreLogData{
StartUTC: now,
StartLocal: now.Local(),
}
logDataTable := &LogData{Core: core, Request: req.Header}
core[StartUTC] = now
core[StartLocal] = now.Local()
reqWithDataTable := req.WithContext(context.WithValue(req.Context(), DataTableKey, logDataTable))

View File

@@ -43,8 +43,6 @@ func (sb *SaveBackend) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
table.Core[OriginContentSize] = crw.Size()
}
//-------------------------------------------------------------------------------------------------
// SaveFrontend sends the frontend name to the logger. These are sometimes used with a corresponding
// SaveBackend handler, but not always. For example, redirected requests don't reach a backend.
type SaveFrontend struct {

View File

@@ -92,14 +92,23 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, req *http.Request, next http.
if err != nil {
log.Error(err)
w.WriteHeader(recorder.GetCode())
w.Write([]byte(http.StatusText(recorder.GetCode())))
fmt.Fprint(w, http.StatusText(recorder.GetCode()))
return
}
recorderErrorPage := newResponseRecorder(w)
utils.CopyHeaders(pageReq.Header, req.Header)
h.backendHandler.ServeHTTP(recorderErrorPage, pageReq.WithContext(req.Context()))
utils.CopyHeaders(w.Header(), recorder.Header())
for key := range recorderErrorPage.Header() {
w.Header().Del(key)
}
utils.CopyHeaders(w.Header(), recorderErrorPage.Header())
w.WriteHeader(recorder.GetCode())
h.backendHandler.ServeHTTP(w, pageReq)
w.Write(recorderErrorPage.GetBody().Bytes())
return
}
}

View File

@@ -33,7 +33,7 @@ func (f *forwarderMiddleware) ServeHTTP(w http.ResponseWriter, r *http.Request,
span.SetTag("frontend.name", f.frontend)
span.SetTag("backend.name", f.backend)
ext.HTTPMethod.Set(span, r.Method)
ext.HTTPUrl.Set(span, r.URL.String())
ext.HTTPUrl.Set(span, fmt.Sprintf("%s%s", r.URL.String(), r.RequestURI))
span.SetTag("http.host", r.Host)
InjectRequestHeaders(r)

View File

@@ -69,27 +69,27 @@ pages:
- 'Logs': 'configuration/logs.md'
- 'EntryPoints': 'configuration/entrypoints.md'
- 'Let''s Encrypt': 'configuration/acme.md'
- 'Backend: Web': 'configuration/backends/web.md'
- 'Backend: BoltDB': 'configuration/backends/boltdb.md'
- 'Backend: Consul': 'configuration/backends/consul.md'
- 'Backend: Consul Catalog': 'configuration/backends/consulcatalog.md'
- 'Backend: Docker': 'configuration/backends/docker.md'
- 'Backend: DynamoDB': 'configuration/backends/dynamodb.md'
- 'Backend: ECS': 'configuration/backends/ecs.md'
- 'Backend: Etcd': 'configuration/backends/etcd.md'
- 'Backend: Eureka': 'configuration/backends/eureka.md'
- 'Backend: File': 'configuration/backends/file.md'
- 'Backend: Kubernetes Ingress': 'configuration/backends/kubernetes.md'
- 'Backend: Marathon': 'configuration/backends/marathon.md'
- 'Backend: Mesos': 'configuration/backends/mesos.md'
- 'Backend: Rancher': 'configuration/backends/rancher.md'
- 'Backend: Rest': 'configuration/backends/rest.md'
- 'Backend: Azure Service Fabric': 'configuration/backends/servicefabric.md'
- 'Backend: Zookeeper': 'configuration/backends/zookeeper.md'
- 'API / Dashboard': 'configuration/api.md'
- 'BoltDB': 'configuration/backends/boltdb.md'
- 'Consul': 'configuration/backends/consul.md'
- 'Consul Catalog': 'configuration/backends/consulcatalog.md'
- 'Docker': 'configuration/backends/docker.md'
- 'DynamoDB': 'configuration/backends/dynamodb.md'
- 'ECS': 'configuration/backends/ecs.md'
- 'Etcd': 'configuration/backends/etcd.md'
- 'Eureka': 'configuration/backends/eureka.md'
- 'File': 'configuration/backends/file.md'
- 'Kubernetes Ingress': 'configuration/backends/kubernetes.md'
- 'Marathon': 'configuration/backends/marathon.md'
- 'Mesos': 'configuration/backends/mesos.md'
- 'Rancher': 'configuration/backends/rancher.md'
- 'Rest': 'configuration/backends/rest.md'
- 'Azure Service Fabric': 'configuration/backends/servicefabric.md'
- 'Zookeeper': 'configuration/backends/zookeeper.md'
- 'Ping': 'configuration/ping.md'
- 'Metrics': 'configuration/metrics.md'
- 'Tracing': 'configuration/tracing.md'
- 'Web (Deprecated)': 'configuration/backends/web.md'
- User Guides:
- 'Configuration Examples': 'user-guide/examples.md'
- 'Swarm Mode Cluster': 'user-guide/swarm-mode.md'

View File

@@ -226,9 +226,9 @@ func (p *Provider) resolveCertificate(domain types.Domain, domainFromConfigurati
bundle := true
certificate, failures := client.ObtainCertificate(uncheckedDomains, bundle, nil, OSCPMustStaple)
if len(failures) > 0 {
return nil, fmt.Errorf("cannot obtain certificates %+v", failures)
certificate, err := client.ObtainCertificate(uncheckedDomains, bundle, nil, OSCPMustStaple)
if err != nil {
return nil, fmt.Errorf("cannot obtain certificates: %+v", err)
}
if len(certificate.Certificate) == 0 || len(certificate.PrivateKey) == 0 {

View File

@@ -262,7 +262,7 @@ func isBackendLBSwarm(container dockerData) bool {
}
func getSegmentBackendName(container dockerData) string {
if value := label.GetStringValue(container.SegmentLabels, label.TraefikFrontendBackend, ""); len(value) > 0 {
if value := label.GetStringValue(container.SegmentLabels, label.TraefikBackend, ""); len(value) > 0 {
return provider.Normalize(container.ServiceName + "-" + value)
}

View File

@@ -251,7 +251,7 @@ func TestSegmentBuildConfiguration(t *testing.T) {
"traefik.sauternes.port": "2503",
"traefik.sauternes.protocol": "https",
"traefik.sauternes.weight": "80",
"traefik.sauternes.frontend.backend": "foobar",
"traefik.sauternes.backend": "foobar",
"traefik.sauternes.frontend.passHostHeader": "false",
"traefik.sauternes.frontend.rule": "Path:/mypath",
"traefik.sauternes.frontend.priority": "5000",

View File

@@ -88,7 +88,7 @@ func extractServicePortV1(labelName string) []string {
// Extract backend from labels for a given service and a given docker container
// Deprecated
func getServiceBackendNameV1(container dockerData, serviceName string) string {
if value, ok := getServiceLabelsV1(container, serviceName)[label.SuffixFrontendBackend]; ok {
if value, ok := getServiceLabelsV1(container, serviceName)[label.SuffixBackend]; ok {
return provider.Normalize(container.ServiceName + "-" + value)
}
return provider.Normalize(container.ServiceName + "-" + getBackendNameV1(container) + "-" + serviceName)

View File

@@ -162,7 +162,7 @@ func TestDockerServiceBuildConfigurationV1(t *testing.T) {
"traefik.service.port": "2503",
"traefik.service.protocol": "https",
"traefik.service.weight": "80",
"traefik.service.frontend.backend": "foobar",
"traefik.service.backend": "foobar",
"traefik.service.frontend.passHostHeader": "false",
"traefik.service.frontend.rule": "Path:/mypath",
"traefik.service.frontend.priority": "5000",
@@ -595,7 +595,7 @@ func TestDockerGetServiceBackendNameV1(t *testing.T) {
},
{
container: containerJSON(labels(map[string]string{
"traefik.myservice.frontend.backend": "custom-backend",
"traefik.myservice.backend": "custom-backend",
})),
expected: "fake-custom-backend",
},

View File

@@ -14,6 +14,7 @@ import (
"github.com/containous/traefik/safe"
"github.com/containous/traefik/tls"
"github.com/containous/traefik/types"
"github.com/pkg/errors"
"gopkg.in/fsnotify.v1"
)
@@ -23,6 +24,7 @@ var _ provider.Provider = (*Provider)(nil)
type Provider struct {
provider.BaseProvider `mapstructure:",squash" export:"true"`
Directory string `description:"Load configuration from one or more .toml files in a directory" export:"true"`
TraefikFile string
}
// Provide allows the file provider to provide configurations to traefik
@@ -37,10 +39,12 @@ func (p *Provider) Provide(configurationChan chan<- types.ConfigMessage, pool *s
if p.Watch {
var watchItem string
if p.Directory != "" {
if len(p.Directory) > 0 {
watchItem = p.Directory
} else {
} else if len(p.Filename) > 0 {
watchItem = filepath.Dir(p.Filename)
} else {
watchItem = filepath.Dir(p.TraefikFile)
}
if err := p.addWatcher(pool, watchItem, configurationChan, p.watcherCallback); err != nil {
@@ -55,10 +59,19 @@ func (p *Provider) Provide(configurationChan chan<- types.ConfigMessage, pool *s
// BuildConfiguration loads configuration either from file or a directory specified by 'Filename'/'Directory'
// and returns a 'Configuration' object
func (p *Provider) BuildConfiguration() (*types.Configuration, error) {
if p.Directory != "" {
if len(p.Directory) > 0 {
return p.loadFileConfigFromDirectory(p.Directory, nil)
}
return p.loadFileConfig(p.Filename)
if len(p.Filename) > 0 {
return p.loadFileConfig(p.Filename, true)
}
if len(p.TraefikFile) > 0 {
return p.loadFileConfig(p.TraefikFile, false)
}
return nil, errors.New("Error using file configuration backend, no filename defined")
}
func (p *Provider) addWatcher(pool *safe.Pool, directory string, configurationChan chan<- types.ConfigMessage, callback func(chan<- types.ConfigMessage, fsnotify.Event)) error {
@@ -67,6 +80,11 @@ func (p *Provider) addWatcher(pool *safe.Pool, directory string, configurationCh
return fmt.Errorf("error creating file watcher: %s", err)
}
err = watcher.Add(directory)
if err != nil {
return fmt.Errorf("error adding file watcher: %s", err)
}
// Process events
pool.Go(func(stop chan bool) {
defer watcher.Close()
@@ -76,8 +94,15 @@ func (p *Provider) addWatcher(pool *safe.Pool, directory string, configurationCh
return
case evt := <-watcher.Events:
if p.Directory == "" {
var filename string
if len(p.Filename) > 0 {
filename = p.Filename
} else {
filename = p.TraefikFile
}
_, evtFileName := filepath.Split(evt.Name)
_, confFileName := filepath.Split(p.Filename)
_, confFileName := filepath.Split(filename)
if evtFileName == confFileName {
callback(configurationChan, evt)
}
@@ -89,18 +114,15 @@ func (p *Provider) addWatcher(pool *safe.Pool, directory string, configurationCh
}
}
})
err = watcher.Add(directory)
if err != nil {
return fmt.Errorf("error adding file watcher: %s", err)
}
return nil
}
func (p *Provider) watcherCallback(configurationChan chan<- types.ConfigMessage, event fsnotify.Event) {
watchItem := p.Filename
if p.Directory != "" {
watchItem := p.TraefikFile
if len(p.Directory) > 0 {
watchItem = p.Directory
} else if len(p.Filename) > 0 {
watchItem = p.Filename
}
if _, err := os.Stat(watchItem); err != nil {
@@ -136,12 +158,19 @@ func readFile(filename string) (string, error) {
return "", fmt.Errorf("invalid filename: %s", filename)
}
func (p *Provider) loadFileConfig(filename string) (*types.Configuration, error) {
func (p *Provider) loadFileConfig(filename string, parseTemplate bool) (*types.Configuration, error) {
fileContent, err := readFile(filename)
if err != nil {
return nil, fmt.Errorf("error reading configuration file: %s - %s", filename, err)
}
configuration, err := p.CreateConfiguration(fileContent, template.FuncMap{}, false)
var configuration *types.Configuration
if parseTemplate {
configuration, err = p.CreateConfiguration(fileContent, template.FuncMap{}, false)
} else {
configuration, err = p.DecodeConfiguration(fileContent)
}
if err != nil {
return nil, err
}
@@ -182,7 +211,7 @@ func (p *Provider) loadFileConfigFromDirectory(directory string, configuration *
}
var c *types.Configuration
c, err = p.loadFileConfig(path.Join(directory, item.Name()))
c, err = p.loadFileConfig(path.Join(directory, item.Name()), true)
if err != nil {
return configuration, err

View File

@@ -14,216 +14,6 @@ import (
"github.com/stretchr/testify/assert"
)
func TestProvideSingleFileAndWatch(t *testing.T) {
tempDir := createTempDir(t, "testfile")
defer os.RemoveAll(tempDir)
expectedNumFrontends := 2
expectedNumBackends := 2
expectedNumTLSConf := 2
tempFile := createFile(t,
tempDir, "simple.toml",
createFrontendConfiguration(expectedNumFrontends),
createBackendConfiguration(expectedNumBackends),
createTLS(expectedNumTLSConf))
configurationChan, signal := createConfigurationRoutine(t, &expectedNumFrontends, &expectedNumBackends, &expectedNumTLSConf)
provide(configurationChan, watch, withFile(tempFile))
// Wait for initial message to be tested
err := waitForSignal(signal, 2*time.Second, "initial config")
assert.NoError(t, err)
// Now test again with single frontend and backend
expectedNumFrontends = 1
expectedNumBackends = 1
expectedNumTLSConf = 1
createFile(t,
tempDir, "simple.toml",
createFrontendConfiguration(expectedNumFrontends),
createBackendConfiguration(expectedNumBackends),
createTLS(expectedNumTLSConf))
err = waitForSignal(signal, 2*time.Second, "single frontend, backend, TLS configuration")
assert.NoError(t, err)
}
func TestProvideSingleFileAndNotWatch(t *testing.T) {
tempDir := createTempDir(t, "testfile")
defer os.RemoveAll(tempDir)
expectedNumFrontends := 2
expectedNumBackends := 2
expectedNumTLSConf := 2
tempFile := createFile(t,
tempDir, "simple.toml",
createFrontendConfiguration(expectedNumFrontends),
createBackendConfiguration(expectedNumBackends),
createTLS(expectedNumTLSConf))
configurationChan, signal := createConfigurationRoutine(t, &expectedNumFrontends, &expectedNumBackends, &expectedNumTLSConf)
provide(configurationChan, withFile(tempFile))
// Wait for initial message to be tested
err := waitForSignal(signal, 2*time.Second, "initial config")
assert.NoError(t, err)
// Now test again with single frontend and backend
expectedNumFrontends = 1
expectedNumBackends = 1
expectedNumTLSConf = 1
createFile(t,
tempDir, "simple.toml",
createFrontendConfiguration(expectedNumFrontends),
createBackendConfiguration(expectedNumBackends),
createTLS(expectedNumTLSConf))
// Must fail because we don't watch the changes
err = waitForSignal(signal, 2*time.Second, "single frontend, backend and TLS configuration")
assert.Error(t, err)
}
func TestProvideDirectoryAndWatch(t *testing.T) {
tempDir := createTempDir(t, "testdir")
defer os.RemoveAll(tempDir)
expectedNumFrontends := 2
expectedNumBackends := 2
expectedNumTLSConf := 2
tempFile1 := createRandomFile(t, tempDir, createFrontendConfiguration(expectedNumFrontends))
tempFile2 := createRandomFile(t, tempDir, createBackendConfiguration(expectedNumBackends))
tempFile3 := createRandomFile(t, tempDir, createTLS(expectedNumTLSConf))
configurationChan, signal := createConfigurationRoutine(t, &expectedNumFrontends, &expectedNumBackends, &expectedNumTLSConf)
provide(configurationChan, watch, withDirectory(tempDir))
// Wait for initial config message to be tested
err := waitForSignal(signal, 2*time.Second, "initial config")
assert.NoError(t, err)
// Now remove the backends file
expectedNumFrontends = 2
expectedNumBackends = 0
expectedNumTLSConf = 2
os.Remove(tempFile2.Name())
err = waitForSignal(signal, 2*time.Second, "remove the backends file")
assert.NoError(t, err)
// Now remove the frontends file
expectedNumFrontends = 0
expectedNumBackends = 0
expectedNumTLSConf = 2
os.Remove(tempFile1.Name())
err = waitForSignal(signal, 2*time.Second, "remove the frontends file")
assert.NoError(t, err)
// Now remove the TLS configuration file
expectedNumFrontends = 0
expectedNumBackends = 0
expectedNumTLSConf = 0
os.Remove(tempFile3.Name())
err = waitForSignal(signal, 2*time.Second, "remove the TLS configuration file")
assert.NoError(t, err)
}
func TestProvideDirectoryAndNotWatch(t *testing.T) {
tempDir := createTempDir(t, "testdir")
tempTLSDir := createSubDir(t, tempDir, "tls")
defer os.RemoveAll(tempDir)
expectedNumFrontends := 2
expectedNumBackends := 2
expectedNumTLSConf := 2
createRandomFile(t, tempDir, createFrontendConfiguration(expectedNumFrontends))
tempFile2 := createRandomFile(t, tempDir, createBackendConfiguration(expectedNumBackends))
createRandomFile(t, tempTLSDir, createTLS(expectedNumTLSConf))
configurationChan, signal := createConfigurationRoutine(t, &expectedNumFrontends, &expectedNumBackends, &expectedNumTLSConf)
provide(configurationChan, withDirectory(tempDir))
// Wait for initial config message to be tested
err := waitForSignal(signal, 2*time.Second, "initial config")
assert.NoError(t, err)
// Now remove the backends file
expectedNumFrontends = 2
expectedNumBackends = 0
expectedNumTLSConf = 2
os.Remove(tempFile2.Name())
// Must fail because we don't watch the changes
err = waitForSignal(signal, 2*time.Second, "remove the backends file")
assert.Error(t, err)
}
func createConfigurationRoutine(t *testing.T, expectedNumFrontends *int, expectedNumBackends *int, expectedNumTLSes *int) (chan types.ConfigMessage, chan interface{}) {
configurationChan := make(chan types.ConfigMessage)
signal := make(chan interface{})
safe.Go(func() {
for {
data := <-configurationChan
assert.Equal(t, "file", data.ProviderName)
assert.Len(t, data.Configuration.Frontends, *expectedNumFrontends)
assert.Len(t, data.Configuration.Backends, *expectedNumBackends)
assert.Len(t, data.Configuration.TLS, *expectedNumTLSes)
signal <- nil
}
})
return configurationChan, signal
}
func waitForSignal(signal chan interface{}, timeout time.Duration, caseName string) error {
timer := time.NewTimer(timeout)
defer timer.Stop()
select {
case <-signal:
case <-timer.C:
return fmt.Errorf("Timed out waiting for assertions to be tested: %s", caseName)
}
return nil
}
func provide(configurationChan chan types.ConfigMessage, builders ...func(p *Provider)) {
pvd := &Provider{}
for _, builder := range builders {
builder(pvd)
}
pvd.Provide(configurationChan, safe.NewPool(context.Background()), nil)
}
func watch(pvd *Provider) {
pvd.Watch = true
}
func withDirectory(name string) func(*Provider) {
return func(pvd *Provider) {
pvd.Directory = name
}
}
func withFile(tempFile *os.File) func(*Provider) {
return func(p *Provider) {
p.Filename = tempFile.Name()
}
}
// createRandomFile Helper
func createRandomFile(t *testing.T, tempDir string, contents ...string) *os.File {
return createFile(t, tempDir, fmt.Sprintf("temp%d.toml", time.Now().UnixNano()), contents...)
@@ -264,25 +54,12 @@ func createTempDir(t *testing.T, dir string) string {
return d
}
// createDir Helper
func createSubDir(t *testing.T, rootDir, dir string) string {
t.Helper()
err := os.Mkdir(rootDir+"/"+dir, 0775)
if err != nil {
t.Fatal(err)
}
return rootDir + "/" + dir
}
// createFrontendConfiguration Helper
func createFrontendConfiguration(n int) string {
conf := "{{$home := env \"HOME\"}}\n[frontends]\n"
conf := "[frontends]\n"
for i := 1; i <= n; i++ {
conf += fmt.Sprintf(` [frontends."frontend%[1]d"]
backend = "backend%[1]d"
`, i)
conf += fmt.Sprintf(` [frontends."frontend%[1]d".headers]
"PublicKey" = "{{$home}}/pub.key"
`, i)
}
return conf
@@ -313,3 +90,240 @@ func createTLS(n int) string {
}
return conf
}
type ProvideTestCase struct {
desc string
directoryContent []string
fileContent string
traefikFileContent string
expectedNumFrontend int
expectedNumBackend int
expectedNumTLSConf int
}
func getTestCases() []ProvideTestCase {
return []ProvideTestCase{
{
desc: "simple file",
fileContent: createFrontendConfiguration(2) + createBackendConfiguration(3) + createTLS(4),
expectedNumFrontend: 2,
expectedNumBackend: 3,
expectedNumTLSConf: 4,
},
{
desc: "simple file and a traefik file",
fileContent: createFrontendConfiguration(2) + createBackendConfiguration(3) + createTLS(4),
traefikFileContent: `
debug=true
`,
expectedNumFrontend: 2,
expectedNumBackend: 3,
expectedNumTLSConf: 4,
},
{
desc: "template file",
fileContent: `
[frontends]
{{ range $i, $e := until 20 }}
[frontends.frontend{{ $e }}]
backend = "backend"
{{ end }}
`,
expectedNumFrontend: 20,
},
{
desc: "simple directory",
directoryContent: []string{
createFrontendConfiguration(2),
createBackendConfiguration(3),
createTLS(4),
},
expectedNumFrontend: 2,
expectedNumBackend: 3,
expectedNumTLSConf: 4,
},
{
desc: "template in directory",
directoryContent: []string{
`
[frontends]
{{ range $i, $e := until 20 }}
[frontends.frontend{{ $e }}]
backend = "backend"
{{ end }}
`,
`
[backends]
{{ range $i, $e := until 20 }}
[backends.backend{{ $e }}]
[backends.backend{{ $e }}.servers.server1]
url="http://127.0.0.1"
{{ end }}
`,
},
expectedNumFrontend: 20,
expectedNumBackend: 20,
},
{
desc: "simple traefik file",
traefikFileContent: `
debug=true
[file]
` + createFrontendConfiguration(2) + createBackendConfiguration(3) + createTLS(4),
expectedNumFrontend: 2,
expectedNumBackend: 3,
expectedNumTLSConf: 4,
},
{
desc: "simple traefik file with templating",
traefikFileContent: `
temp="{{ getTag \"test\" }}"
[file]
` + createFrontendConfiguration(2) + createBackendConfiguration(3) + createTLS(4),
expectedNumFrontend: 2,
expectedNumBackend: 3,
expectedNumTLSConf: 4,
},
}
}
func TestProvideWithoutWatch(t *testing.T) {
for _, test := range getTestCases() {
test := test
t.Run(test.desc+" without watch", func(t *testing.T) {
t.Parallel()
provider, clean := createProvider(t, test, false)
defer clean()
configChan := make(chan types.ConfigMessage)
go func() {
err := provider.Provide(configChan, safe.NewPool(context.Background()), types.Constraints{})
assert.NoError(t, err)
}()
timeout := time.After(time.Second)
select {
case config := <-configChan:
assert.Len(t, config.Configuration.Backends, test.expectedNumBackend)
assert.Len(t, config.Configuration.Frontends, test.expectedNumFrontend)
assert.Len(t, config.Configuration.TLS, test.expectedNumTLSConf)
case <-timeout:
t.Errorf("timeout while waiting for config")
}
})
}
}
func TestProvideWithWatch(t *testing.T) {
for _, test := range getTestCases() {
test := test
t.Run(test.desc+" with watch", func(t *testing.T) {
t.Parallel()
provider, clean := createProvider(t, test, true)
defer clean()
configChan := make(chan types.ConfigMessage)
go func() {
err := provider.Provide(configChan, safe.NewPool(context.Background()), types.Constraints{})
assert.NoError(t, err)
}()
timeout := time.After(time.Second)
select {
case config := <-configChan:
assert.Len(t, config.Configuration.Backends, 0)
assert.Len(t, config.Configuration.Frontends, 0)
assert.Len(t, config.Configuration.TLS, 0)
case <-timeout:
t.Errorf("timeout while waiting for config")
}
if len(test.fileContent) > 0 {
ioutil.WriteFile(provider.Filename, []byte(test.fileContent), 0755)
}
if len(test.traefikFileContent) > 0 {
ioutil.WriteFile(provider.TraefikFile, []byte(test.traefikFileContent), 0755)
}
if len(test.directoryContent) > 0 {
for _, fileContent := range test.directoryContent {
createRandomFile(t, provider.Directory, fileContent)
}
}
timeout = time.After(time.Second * 1)
success := false
for !success {
select {
case config := <-configChan:
success = assert.Len(t, config.Configuration.Backends, test.expectedNumBackend)
success = success && assert.Len(t, config.Configuration.Frontends, test.expectedNumFrontend)
success = success && assert.Len(t, config.Configuration.TLS, test.expectedNumTLSConf)
case <-timeout:
t.Errorf("timeout while waiting for config")
return
}
}
})
}
}
func TestErrorWhenEmptyConfig(t *testing.T) {
provider := &Provider{}
configChan := make(chan types.ConfigMessage)
errorChan := make(chan struct{})
go func() {
err := provider.Provide(configChan, safe.NewPool(context.Background()), types.Constraints{})
assert.Error(t, err)
close(errorChan)
}()
timeout := time.After(time.Second)
select {
case <-configChan:
t.Fatal("We should not receive config message")
case <-timeout:
t.Fatal("timeout while waiting for config")
case <-errorChan:
}
}
func createProvider(t *testing.T, test ProvideTestCase, watch bool) (*Provider, func()) {
tempDir := createTempDir(t, "testdir")
provider := &Provider{}
provider.Watch = watch
if len(test.directoryContent) > 0 {
if !watch {
for _, fileContent := range test.directoryContent {
createRandomFile(t, tempDir, fileContent)
}
}
provider.Directory = tempDir
}
if len(test.fileContent) > 0 {
if watch {
test.fileContent = ""
}
filename := createRandomFile(t, tempDir, test.fileContent)
provider.Filename = filename.Name()
}
if len(test.traefikFileContent) > 0 {
if watch {
test.traefikFileContent = ""
}
filename := createRandomFile(t, tempDir, test.traefikFileContent)
provider.TraefikFile = filename.Name()
}
return provider, func() {
os.Remove(tempDir)
}
}

View File

@@ -27,11 +27,6 @@ const (
DefaultBackendMaxconnExtractorFunc = "request.host"
DefaultBackendLoadbalancerStickinessCookieName = ""
DefaultBackendHealthCheckPort = 0
// TODO need to be remove in extra-service-fabric
DefaultWeightInt = 1 // Deprecated
DefaultPassHostHeaderBool = true // Deprecated
DefaultFrontendPriorityInt = 0 // Deprecated
)
var (
@@ -64,6 +59,7 @@ func GetBoolValue(labels map[string]string, labelName string, defaultValue bool)
if err == nil {
return v
}
log.Errorf("Unable to parse %q: %q, falling back to %v. %v", labelName, rawValue, defaultValue, err)
}
return defaultValue
}

View File

@@ -32,7 +32,6 @@ const (
SuffixBackendBufferingRetryExpression = SuffixBackendBuffering + ".retryExpression"
SuffixFrontend = "frontend"
SuffixFrontendAuthBasic = "frontend.auth.basic"
SuffixFrontendBackend = "frontend.backend"
SuffixFrontendEntryPoints = "frontend.entryPoints"
SuffixFrontendHeaders = "frontend.headers."
SuffixFrontendRequestHeaders = SuffixFrontendHeaders + "customRequestHeaders"
@@ -98,7 +97,6 @@ const (
TraefikBackendBufferingRetryExpression = Prefix + SuffixBackendBufferingRetryExpression
TraefikFrontend = Prefix + SuffixFrontend
TraefikFrontendAuthBasic = Prefix + SuffixFrontendAuthBasic
TraefikFrontendBackend = Prefix + SuffixFrontendBackend
TraefikFrontendEntryPoints = Prefix + SuffixFrontendEntryPoints
TraefikFrontendPassHostHeader = Prefix + SuffixFrontendPassHostHeader
TraefikFrontendPassTLSCert = Prefix + SuffixFrontendPassTLSCert

View File

@@ -62,8 +62,6 @@ func (p *BaseProvider) GetConfiguration(defaultTemplate string, funcMap template
// CreateConfiguration create a provider configuration from content using templating
func (p *BaseProvider) CreateConfiguration(tmplContent string, funcMap template.FuncMap, templateObjects interface{}) (*types.Configuration, error) {
configuration := new(types.Configuration)
var defaultFuncMap = sprig.TxtFuncMap()
// tolower is deprecated in favor of sprig's lower function
defaultFuncMap["tolower"] = strings.ToLower
@@ -91,7 +89,13 @@ func (p *BaseProvider) CreateConfiguration(tmplContent string, funcMap template.
log.Debugf("Template content: %s", tmplContent)
log.Debugf("Rendering results: %s", renderedTemplate)
}
if _, err := toml.Decode(renderedTemplate, configuration); err != nil {
return p.DecodeConfiguration(renderedTemplate)
}
// DecodeConfiguration Decode a *types.Configuration from a content
func (p *BaseProvider) DecodeConfiguration(content string) (*types.Configuration, error) {
configuration := new(types.Configuration)
if _, err := toml.Decode(content, configuration); err != nil {
return nil, err
}
return configuration, nil

View File

@@ -150,7 +150,7 @@ func getBackendName(service rancherData) string {
}
func getSegmentBackendName(service rancherData) string {
if value := label.GetStringValue(service.SegmentLabels, label.TraefikFrontendBackend, ""); len(value) > 0 {
if value := label.GetStringValue(service.SegmentLabels, label.TraefikBackend, ""); len(value) > 0 {
return provider.Normalize(service.Name + "-" + value)
}

View File

@@ -1438,7 +1438,7 @@ func configureBackends(backends map[string]*types.Backend) {
}
}
} else {
log.Debugf("Validation of load balancer method for backend %s failed: %s. Using default method wrr.", backendName, err)
log.Debugf("Backend %s: %v", backendName, err)
var stickiness *types.Stickiness
if backend.LoadBalancer != nil {

View File

@@ -69,6 +69,7 @@
entryPoint = "{{ $frontend.Redirect.EntryPoint }}"
regex = "{{ $frontend.Redirect.Regex }}"
replacement = "{{ $frontend.Redirect.Replacement }}"
permanent = {{ $frontend.Redirect.Permanent }}
{{end}}
{{if $frontend.Errors }}

View File

@@ -211,16 +211,21 @@ var loadBalancerMethodNames = []string{
// NewLoadBalancerMethod create a new LoadBalancerMethod from a given LoadBalancer.
func NewLoadBalancerMethod(loadBalancer *LoadBalancer) (LoadBalancerMethod, error) {
var method string
if loadBalancer != nil {
method = loadBalancer.Method
for i, name := range loadBalancerMethodNames {
if strings.EqualFold(name, method) {
return LoadBalancerMethod(i), nil
}
if loadBalancer == nil {
return Wrr, errors.New("no load-balancer defined, fallback to 'wrr' method")
}
if len(loadBalancer.Method) == 0 {
return Wrr, errors.New("no load-balancing method defined, fallback to 'wrr' method")
}
for i, name := range loadBalancerMethodNames {
if strings.EqualFold(name, loadBalancer.Method) {
return LoadBalancerMethod(i), nil
}
}
return Wrr, fmt.Errorf("invalid load-balancing method '%s'", method)
return Wrr, fmt.Errorf("invalid load-balancing method %q, fallback to 'wrr' method", loadBalancer.Method)
}
// Configurations is for currentConfigurations Map

View File

@@ -189,7 +189,7 @@ func (c *Client) ResolveAccountByKey() (*RegistrationResource, error) {
logf("[INFO] acme: Trying to resolve account by key")
acc := accountMessage{OnlyReturnExisting: true}
hdr, err := postJSON(c.jws, c.directory.NewAccountURL, acc, &acc)
hdr, err := postJSON(c.jws, c.directory.NewAccountURL, acc, nil)
if err != nil {
return nil, err
}
@@ -265,7 +265,7 @@ func (c *Client) QueryRegistration() (*RegistrationResource, error) {
// your issued certificate as a bundle.
// This function will never return a partial certificate. If one domain in the list fails,
// the whole certificate will fail.
func (c *Client) ObtainCertificateForCSR(csr x509.CertificateRequest, bundle bool) (CertificateResource, map[string]error) {
func (c *Client) ObtainCertificateForCSR(csr x509.CertificateRequest, bundle bool) (CertificateResource, error) {
// figure out what domains it concerns
// start with the common name
domains := []string{csr.Subject.CommonName}
@@ -292,30 +292,26 @@ DNSNames:
order, err := c.createOrderForIdentifiers(domains)
if err != nil {
identErrors := make(map[string]error)
for _, auth := range order.Identifiers {
identErrors[auth.Value] = err
}
return CertificateResource{}, identErrors
return CertificateResource{}, err
}
authz, failures := c.getAuthzForOrder(order)
// If any challenge fails - return. Do not generate partial SAN certificates.
if len(failures) > 0 {
authz, err := c.getAuthzForOrder(order)
if err != nil {
// If any challenge fails, return. Do not generate partial SAN certificates.
/*for _, auth := range authz {
c.disableAuthz(auth)
}*/
return CertificateResource{}, failures
return CertificateResource{}, err
}
errs := c.solveChallengeForAuthz(authz)
// If any challenge fails - return. Do not generate partial SAN certificates.
if len(errs) > 0 {
return CertificateResource{}, errs
err = c.solveChallengeForAuthz(authz)
if err != nil {
// If any challenge fails, return. Do not generate partial SAN certificates.
return CertificateResource{}, err
}
logf("[INFO][%s] acme: Validations succeeded; requesting certificates", strings.Join(domains, ", "))
failures := make(ObtainError)
cert, err := c.requestCertificateForCsr(order, bundle, csr.Raw, nil)
if err != nil {
for _, chln := range authz {
@@ -326,7 +322,12 @@ DNSNames:
// Add the CSR to the certificate so that it can be used for renewals.
cert.CSR = pemEncode(&csr)
return cert, failures
// do not return an empty failures map, because
// it would still be a non-nil error value
if len(failures) > 0 {
return cert, failures
}
return cert, nil
}
// ObtainCertificate tries to obtain a single certificate using all domains passed into it.
@@ -338,7 +339,11 @@ DNSNames:
// your issued certificate as a bundle.
// This function will never return a partial certificate. If one domain in the list fails,
// the whole certificate will fail.
func (c *Client) ObtainCertificate(domains []string, bundle bool, privKey crypto.PrivateKey, mustStaple bool) (CertificateResource, map[string]error) {
func (c *Client) ObtainCertificate(domains []string, bundle bool, privKey crypto.PrivateKey, mustStaple bool) (CertificateResource, error) {
if len(domains) == 0 {
return CertificateResource{}, errors.New("No domains to obtain a certificate for")
}
if bundle {
logf("[INFO][%s] acme: Obtaining bundled SAN certificate", strings.Join(domains, ", "))
} else {
@@ -347,30 +352,26 @@ func (c *Client) ObtainCertificate(domains []string, bundle bool, privKey crypto
order, err := c.createOrderForIdentifiers(domains)
if err != nil {
identErrors := make(map[string]error)
for _, auth := range order.Identifiers {
identErrors[auth.Value] = err
}
return CertificateResource{}, identErrors
return CertificateResource{}, err
}
authz, failures := c.getAuthzForOrder(order)
// If any challenge fails - return. Do not generate partial SAN certificates.
if len(failures) > 0 {
authz, err := c.getAuthzForOrder(order)
if err != nil {
// If any challenge fails, return. Do not generate partial SAN certificates.
/*for _, auth := range authz {
c.disableAuthz(auth)
}*/
return CertificateResource{}, failures
return CertificateResource{}, err
}
errs := c.solveChallengeForAuthz(authz)
// If any challenge fails - return. Do not generate partial SAN certificates.
if len(errs) > 0 {
return CertificateResource{}, errs
err = c.solveChallengeForAuthz(authz)
if err != nil {
// If any challenge fails, return. Do not generate partial SAN certificates.
return CertificateResource{}, err
}
logf("[INFO][%s] acme: Validations succeeded; requesting certificates", strings.Join(domains, ", "))
failures := make(ObtainError)
cert, err := c.requestCertificateForOrder(order, bundle, privKey, mustStaple)
if err != nil {
for _, auth := range authz {
@@ -378,7 +379,12 @@ func (c *Client) ObtainCertificate(domains []string, bundle bool, privKey crypto
}
}
return cert, failures
// do not return an empty failures map, because
// it would still be a non-nil error value
if len(failures) > 0 {
return cert, failures
}
return cert, nil
}
// RevokeCertificate takes a PEM encoded certificate or bundle and tries to revoke it at the CA.
@@ -433,7 +439,7 @@ func (c *Client) RenewCertificate(cert CertificateResource, bundle, mustStaple b
return CertificateResource{}, err
}
newCert, failures := c.ObtainCertificateForCSR(*csr, bundle)
return newCert, failures[cert.Domain]
return newCert, failures
}
var privKey crypto.PrivateKey
@@ -445,7 +451,6 @@ func (c *Client) RenewCertificate(cert CertificateResource, bundle, mustStaple b
}
var domains []string
var failures map[string]error
// check for SAN certificate
if len(x509Cert.DNSNames) > 1 {
domains = append(domains, x509Cert.Subject.CommonName)
@@ -459,8 +464,8 @@ func (c *Client) RenewCertificate(cert CertificateResource, bundle, mustStaple b
domains = append(domains, x509Cert.Subject.CommonName)
}
newCert, failures := c.ObtainCertificate(domains, bundle, privKey, mustStaple)
return newCert, failures[cert.Domain]
newCert, err := c.ObtainCertificate(domains, bundle, privKey, mustStaple)
return newCert, err
}
func (c *Client) createOrderForIdentifiers(domains []string) (orderResource, error) {
@@ -490,9 +495,10 @@ func (c *Client) createOrderForIdentifiers(domains []string) (orderResource, err
// Looks through the challenge combinations to find a solvable match.
// Then solves the challenges in series and returns.
func (c *Client) solveChallengeForAuthz(authorizations []authorization) map[string]error {
func (c *Client) solveChallengeForAuthz(authorizations []authorization) error {
failures := make(ObtainError)
// loop through the resources, basically through the domains.
failures := make(map[string]error)
for _, authz := range authorizations {
if authz.Status == "valid" {
// Boulder might recycle recent validated authz (see issue #267)
@@ -513,7 +519,12 @@ func (c *Client) solveChallengeForAuthz(authorizations []authorization) map[stri
}
}
return failures
// be careful not to return an empty failures map, for
// even an empty ObtainError is a non-nil error value
if len(failures) > 0 {
return failures
}
return nil
}
// Checks all challenges from the server in order and returns the first matching solver.
@@ -528,7 +539,7 @@ func (c *Client) chooseSolver(auth authorization, domain string) (int, solver) {
}
// Get the challenges needed to proof our identifier to the ACME server.
func (c *Client) getAuthzForOrder(order orderResource) ([]authorization, map[string]error) {
func (c *Client) getAuthzForOrder(order orderResource) ([]authorization, error) {
resc, errc := make(chan authorization), make(chan domainError)
delay := time.Second / overallRequestLimit
@@ -549,7 +560,7 @@ func (c *Client) getAuthzForOrder(order orderResource) ([]authorization, map[str
}
var responses []authorization
failures := make(map[string]error)
failures := make(ObtainError)
for i := 0; i < len(order.Authorizations); i++ {
select {
case res := <-resc:
@@ -564,7 +575,12 @@ func (c *Client) getAuthzForOrder(order orderResource) ([]authorization, map[str
close(resc)
close(errc)
return responses, failures
// be careful to not return an empty failures map;
// even if empty, they become non-nil error values
if len(failures) > 0 {
return responses, failures
}
return responses, nil
}
func logAuthz(order orderResource) {

View File

@@ -1,6 +1,7 @@
package acmev2
import (
"bytes"
"encoding/json"
"fmt"
"io/ioutil"
@@ -13,6 +14,18 @@ const (
invalidNonceError = "urn:ietf:params:acme:error:badNonce"
)
// ObtainError is returned when there are specific errors available
// per domain. For example in ObtainCertificate
type ObtainError map[string]error
func (e ObtainError) Error() string {
buffer := bytes.NewBufferString("acme: Error -> One or more domains had a problem:\n")
for dom, err := range e {
buffer.WriteString(fmt.Sprintf("[%s] %s\n", dom, err))
}
return buffer.String()
}
// RemoteError is the base type for all errors specific to the ACME protocol.
type RemoteError struct {
StatusCode int `json:"status,omitempty"`

View File

@@ -8,7 +8,7 @@
"root": "src",
"outDir": "dist",
"assets": [
"assets",
"assets/images",
"favicon.ico"
],
"index": "index.html",
@@ -19,7 +19,7 @@
"testTsconfig": "tsconfig.spec.json",
"prefix": "app",
"styles": [
"styles/app.sass"
"app.sass"
],
"scripts": [
"../node_modules/@fortawesome/fontawesome/index.js",

View File

@@ -27,9 +27,10 @@
"@angular/router": "^5.2.0",
"@fortawesome/fontawesome": "^1.1.5",
"@fortawesome/fontawesome-free-solid": "^5.0.10",
"bulma": "^0.6.2",
"bulma": "^0.7.0",
"core-js": "^2.4.1",
"d3": "^4.13.0",
"d3-format": "^1.3.0",
"date-fns": "^1.29.0",
"lodash": "^4.17.5",
"rxjs": "^5.5.6",

27
webui/src/app.sass Normal file
View File

@@ -0,0 +1,27 @@
@charset "utf-8"
@import 'styles/typography'
@import 'styles/variables'
@import 'styles/colors'
@import '~bulma/sass/utilities/all'
@import '~bulma/sass/base/all'
@import '~bulma/sass/grid/all'
@import '~bulma/sass/elements/container'
@import '~bulma/sass/elements/tag'
@import '~bulma/sass/elements/other'
@import '~bulma/sass/elements/box'
@import '~bulma/sass/elements/form'
@import '~bulma/sass/elements/table'
@import '~bulma/sass/components/navbar'
@import '~bulma/sass/components/tabs'
@import '~bulma/sass/elements/notification'
@import 'styles/nav'
@import 'styles/content'
@import 'styles/message'
@import 'styles/charts'
@import 'styles/helper'
html
font-family: $open-sans
height: 100%
background: $background

View File

@@ -1,4 +1,4 @@
import { TestBed, async } from '@angular/core/testing';
import { async, TestBed } from '@angular/core/testing';
import { AppComponent } from './app.component';
describe('AppComponent', () => {

View File

@@ -1,18 +1,21 @@
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 { 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 { KeysPipe } from './pipes/keys.pipe';
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: [
@@ -22,7 +25,10 @@ import { KeysPipe } from './pipes/keys.pipe';
HealthComponent,
LineChartComponent,
BarChartComponent,
KeysPipe
KeysPipe,
FrontendFilterPipe,
BackendFilterPipe,
LetDirective
],
imports: [
BrowserModule,
@@ -30,8 +36,8 @@ import { KeysPipe } from './pipes/keys.pipe';
HttpClientModule,
FormsModule,
RouterModule.forRoot([
{ path: '', component: ProvidersComponent, pathMatch: 'full' },
{ path: 'status', component: HealthComponent }
{path: '', component: ProvidersComponent, pathMatch: 'full'},
{path: 'status', component: HealthComponent}
])
],
providers: [

View File

@@ -1,15 +1,8 @@
import { Component, Input, OnInit, ElementRef, OnChanges, SimpleChanges } from '@angular/core';
import { Component, ElementRef, Input, OnChanges, OnInit, SimpleChanges } from '@angular/core';
import { axisBottom, axisLeft, easeLinear, max, min, scaleBand, scaleLinear, select } from 'd3';
import { format } from 'd3-format';
import * as _ from 'lodash';
import { WindowService } from '../../services/window.service';
import {
min,
max,
easeLinear,
select,
axisLeft,
axisBottom,
scaleBand,
scaleLinear
} from 'd3';
@Component({
selector: 'app-bar-chart',
@@ -23,12 +16,12 @@ export class BarChartComponent implements OnInit, OnChanges {
x: any;
y: any;
g: any;
bars: any;
width: number;
height: number;
margin = { top: 40, right: 40, bottom: 40, left: 40 };
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;
@@ -37,7 +30,7 @@ export class BarChartComponent implements OnInit, OnChanges {
ngOnInit() {
this.barChartEl = this.elementRef.nativeElement.querySelector('.bar-chart');
this.setup();
setTimeout(() => this.loading = false, 4000);
setTimeout(() => this.loading = false, 1000);
this.windowService.resize.subscribe(w => this.draw());
}
@@ -47,15 +40,20 @@ export class BarChartComponent implements OnInit, OnChanges {
return;
}
this.data = this.value;
this.draw();
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')
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);
@@ -73,11 +71,16 @@ export class BarChartComponent implements OnInit, OnChanges {
}
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.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)
@@ -91,19 +94,18 @@ export class BarChartComponent implements OnInit, OnChanges {
.call(axisBottom(this.x));
this.g.select('.axis--y')
.call(axisLeft(this.y).tickSize(-this.width));
.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')
.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))
.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));

View File

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

View File

@@ -1,20 +1,20 @@
import { Component, Input, OnInit, ElementRef, OnChanges, SimpleChanges } from '@angular/core';
import { WindowService } from '../../services/window.service';
import { Component, ElementRef, Input, OnChanges, OnInit, SimpleChanges } from '@angular/core';
import {
range,
scaleTime,
scaleLinear,
min,
max,
curveLinear,
line,
easeLinear,
select,
axisLeft,
axisBottom,
timeSecond,
timeFormat
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',
@@ -23,7 +23,10 @@ import {
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;
@@ -39,15 +42,19 @@ export class LineChartComponent implements OnChanges, OnInit {
yAxis: any;
height: number;
width: number;
margin = { top: 40, right: 40, bottom: 60, left: 60 };
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 = {
@@ -55,22 +62,37 @@ export class LineChartComponent implements OnChanges, OnInit {
color: '#3A84C5'
};
this.firstDisplay = true;
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.dirty = true;
this.loading = true;
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;
// 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;
this.svg = select(this.lineChartEl).append('svg')
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')
@@ -80,7 +102,7 @@ export class LineChartComponent implements OnChanges, OnInit {
this.data = range(this.limit).map(i => 0);
}
this.x = scaleTime().range([0, this.width]);
this.x = scaleTime().range([0, this.width - 10]);
this.y = scaleLinear().range([this.height, 0]);
this.x.domain([<any>this.now - (this.limit - 2), <any>this.now - this.duration]);
@@ -91,7 +113,9 @@ export class LineChartComponent implements OnChanges, OnInit {
.y((d: any) => this.y(d))
.curve(curveLinear);
this.svg.append('defs').append('clipPath')
this.svg
.append('defs')
.append('clipPath')
.attr('id', 'clip')
.append('rect')
.attr('width', this.width)
@@ -121,7 +145,7 @@ export class LineChartComponent implements OnChanges, OnInit {
this.updateData(this.value.count);
}
updateData = (value: number) => {
updateData(value: number) {
this.data.push(value * 1000000);
this.now = new Date();
@@ -132,9 +156,13 @@ export class LineChartComponent implements OnChanges, OnInit {
this.xAxis
.transition()
.duration(this.duration)
.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')))
.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')
@@ -157,6 +185,13 @@ export class LineChartComponent implements OnChanges, OnInit {
.ease(easeLinear)
.attr('transform', `translate(${this.x(<any>this.now - (this.limit - 1) * this.duration)})`);
this.firstDisplay = false;
this.dirty = false;
if (this.loading) {
this.loading = false;
}
this.data.shift();
}
}

View File

@@ -1,22 +1,27 @@
<nav class="navbar is-fixed-top" 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="navbar-menu">
<div class="navbar-brand">
<a class="navbar-item" routerLink="/">
<img src="./assets/images/traefik.logo.svg" alt="Traefik" class="navbar-logo">
<div class="navbar-brand">
<a class="navbar-item" routerLink="/" (click)="burger = false">
<img src="./assets/images/traefik.logo.svg" alt="Traefik" class="navbar-logo">
</a>
<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>
</div>
</div>
<div id="navbarMain" class="navbar-menu" [class.is-active]="burger">
<div class="navbar-start">
<a class="navbar-item" routerLink="/" routerLinkActive="is-active" [routerLinkActiveOptions]="{ exact: true }" (click)="burger = false">
Providers
</a>
<a class="navbar-item" routerLink="/status" routerLinkActive="is-active" (click)="burger = false">
Health
</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">
<div class="navbar-end">
<a class="navbar-item" [href]="releaseLink" target="_blank">
{{ version }} / {{ codename }}
</a>
@@ -25,5 +30,6 @@
</a>
</div>
</div>
</div>
</nav>

View File

@@ -9,6 +9,7 @@ export class HeaderComponent implements OnInit {
version: string;
codename: string;
releaseLink: string;
burger: boolean;
constructor(private apiService: ApiService) { }

View File

@@ -9,7 +9,7 @@
<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>
<span class="data-blue" [title]="exactTotalResponseTime">{{ totalResponseTime }}</span>
</div>
</div>
<div class="column is-4">
@@ -33,7 +33,7 @@
<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>
<span class="data-blue" [title]="exactAverageResponseTime">{{ averageResponseTime }}</span>
</div>
</div>
<div class="column is-4">
@@ -82,15 +82,15 @@
<td>Request</td>
<td>Time</td>
</tr>
<tr *ngFor="let entry of recentErrors">
<tr *ngFor="let entry of recentErrors; trackBy: trackRecentErrors;">
<td>
<span class="tag is-info">{{ entry.status_code }}</span>&nbsp;<span>{{ 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>
<span class="tag">{{ entry.method }}</span>&nbsp;<a>{{ entry.host }}{{ entry.path }}</a>
<span class="tag">{{ entry.method }}</span>&nbsp;<span>{{ entry.host }}{{ entry.path }}</span>
</td>
<td>
<span>{{ entry.time }}</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>
</tr>
<tr *ngIf="!recentErrors?.length">

View File

@@ -1,12 +1,13 @@
import { Component, OnInit, OnDestroy } from '@angular/core';
import { ApiService } from '../../services/api.service';
import { Component, OnDestroy, OnInit } from '@angular/core';
import { distanceInWordsStrict, format, subSeconds } from 'date-fns';
import * as _ from 'lodash';
import 'rxjs/add/observable/timer';
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/mergeMap';
import 'rxjs/add/operator/timeInterval';
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';
import { ApiService } from '../../services/api.service';
@Component({
selector: 'app-health',
@@ -15,11 +16,14 @@ import { format, distanceInWordsStrict, subSeconds } from 'date-fns';
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;
@@ -33,16 +37,22 @@ export class HealthComponent implements OnInit, OnDestroy {
.mergeMap(() => this.apiService.fetchHealthStatus())
.subscribe(data => {
if (data) {
this.recentErrors = data.recent_errors;
this.chartValue = { count: data.average_response_time_sec, date: data.time };
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] }));
.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.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;
}
@@ -54,4 +64,8 @@ export class HealthComponent implements OnInit, OnDestroy {
this.sub.unsubscribe();
}
}
trackRecentErrors(index, item): string {
return item.status_code + item.method + item.host + item.path + item.time;
}
}

View File

@@ -5,8 +5,9 @@
<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()">
<span class="icon search-button" *ngIf="!keyword"><i class="fas fa-search"></i></span>
<a class="delete search-button" *ngIf="keyword" (click)="keyword = ''"></a>
<input type="text" placeholder="Filter by name or id ..." [(ngModel)]="keyword">
</div>
<div class="tabs" *ngIf="keys?.length">
@@ -20,30 +21,17 @@
<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">
<div class="column is-6" *appLet="providers[tab]?.frontends | frontendFilter:keyword as frontends">
<h2 class="subtitle"><span class="tag is-info">{{ frontends.length }}</span><span class="subtitle-name">Frontends</span></h2>
<div *ngIf="frontends.length < maxItem">
<div class="message" *ngFor="let p of frontends; trackBy: trackItem(tab)">
<div class="message-header" [class.has-background-info]="p.backend" [class.has-background-danger]="!p.backend">
<h2>
<i class="icon fas fa-globe has-text-white"></i>
<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>
<span class="has-text-white" [class.is-info]="p.backend" [class.is-danger]="!p.backend">{{ p.id }}</span>
</div>
</h2>
</div>
@@ -57,16 +45,16 @@
</div>
<!-- Main -->
<div *ngIf="p.section !== 'details'">
<div *ngIf="p.section !== 'details'" class="section-container">
<div *ngIf="p.routes && p.routes.length">
<table class="table is-fullwidth is-hoverable">
<div *ngIf="p.routes && p.routes.length" class="section-line">
<div>
<h2>Route Rule</h2>
</div>
<table class="table is-fullwidth is-hoverable table-fixed-break">
<tbody>
<tr>
<td>Route Rule</td>
</tr>
<tr *ngFor="let route of p.routes; let ri = index;">
<td><code class="has-text-grey" title="{{ route.title }}">{{ route.rule }}</code></td>
<tr *ngFor="let route of p.routes">
<td><code class="has-text-grey" [title]="route.id">{{ route.rule }}</code></td>
</tr>
</tbody>
</table>
@@ -74,15 +62,15 @@
<div *ngIf="p.entryPoints && p.entryPoints.length">
<hr>
<div class="columns">
<div class="columns section-line">
<div class="column is-3">
<h2>Entry Points</h2>
<h2 class="section-line-header">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>
<span class="tag is-info" *ngFor="let ep of p.entryPoints">{{ ep }}</span>
</div>
</div>
</div>
@@ -90,19 +78,34 @@
</div>
</div>
<div *ngIf="p.backend">
<hr>
<div class="columns section-line">
<div class="column is-2">
<h2 class="section-line-header">Backend</h2>
</div>
<div class="column is-10">
<div class="field">
<i class="icon fas fa-server has-text-primary" title="Backend"></i>
<span class="has-text-primary">{{ p.backend }}</span>
</div>
</div>
</div>
</div>
</div>
<!-- Details -->
<div *ngIf="p.section === 'details'">
<div *ngIf="p.section === 'details'" class="section-container">
<div>
<div class="section-line">
<div class="columns">
<div class="column is-3">
<h2>Misc.</h2>
<h2 class="section-line-header">Misc.</h2>
</div>
<div class="column is-9">
<div class="field is-grouped is-grouped-multiline">
<div class="control">
<div class="control" *ngIf="p.priority">
<div class="tags has-addons">
<span class="tag is-light">Priority</span>
<span class="tag is-info">{{ p.priority }}</span>
@@ -111,7 +114,7 @@
<div class="control">
<div class="tags has-addons">
<span class="tag is-light">Host Header</span>
<span class="tag is-info">{{ p.passHostHeader }}</span>
<span class="tag is-info">{{ !!p.passHostHeader }}</span>
</div>
</div>
<div class="control" *ngIf="p.passTLSCert">
@@ -127,9 +130,9 @@
<div *ngIf="p.redirect">
<hr>
<div class="columns">
<div class="columns section-line">
<div class="column is-3">
<h2>Redirect</h2>
<h2 class="section-line-header">Redirect</h2>
</div>
<div class="column is-9">
<div class="field is-grouped is-grouped-multiline" *ngIf="p.redirect.entryPoint">
@@ -160,45 +163,49 @@
<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 class="section-line">
<h2 class="section-line-header">Basic Authentication</h2>
<div class="tags padding-5-10">
<span class="tag is-info" *ngFor="let auth of p.basicAuth">{{ auth }}</span>
</div>
</div>
</div>
<div *ngIf="p.errors">
<div *ngIf="p.errors?.length">
<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 class="section-line">
<h2 class="section-line-header">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 entry of p.errors">
<td><span class="has-text-grey-light">{{ entry.backend }}</span></td>
<td><span class="has-text-grey">{{ entry.query }}</span></td>
<td>
<span class="tag is-light" *ngFor="let state of entry.status">{{ state }}</span>
</td>
</tr>
</tbody>
</table>
</div>
</div>
<div *ngIf="p.whiteList">
<hr/>
<div class="columns is-gapless is-multiline is-mobile">
<div class="columns is-gapless is-multiline is-mobile section-line">
<div class="column is-half">
<h2>Whitelist</h2>
<h2 class="section-line-header">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>
<span class="tag is-info">{{ !!p.whiteList.useXForwardedFor }}</span>
</div>
</div>
</div>
@@ -207,7 +214,7 @@
<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>
<span class="tag is-info" *ngFor="let wlRange of p.whiteList.sourceRange">{{ wlRange }}</span>
</div>
</div>
</div>
@@ -217,126 +224,137 @@
<div *ngIf="p.headers">
<hr/>
<h2>Headers</h2>
<div class="columns is-multiline">
<div class="section-line">
<h2 class="section-line-header">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.customRequestHeaders?.length">
<table class="table is-fullwidth is-hoverable table-fixed-break">
<tbody>
<tr>
<td colspan="2">Custom Request Headers</td>
</tr>
<tr *ngFor="let header of p.headers.customRequestHeaders">
<td><span class="has-text-grey-light">{{ header.name }}</span></td>
<td><span class="has-text-grey">{{ header.value }}</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" *ngIf="p.headers.customResponseHeaders?.length">
<table class="table is-fullwidth is-hoverable table-fixed-break">
<tbody>
<tr>
<td colspan="2">Custom Response Headers</td>
</tr>
<tr *ngFor="let header of p.headers.customResponseHeaders">
<td><span class="has-text-grey-light">{{ header.name }}</span></td>
<td><span class="has-text-grey">{{ header.value }}</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">
<table class="table is-fullwidth is-hoverable table-fixed-break">
<tbody>
<tr>
<td colspan="2">Secure</td>
</tr>
<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 class="column is-12" *ngIf="p.headers.sslProxyHeaders?.length">
<table class="table is-fullwidth is-hoverable table-fixed-break">
<tbody>
<tr>
<td colspan="2">SSL Proxy Headers</td>
</tr>
<tr *ngFor="let header of p.headers.sslProxyHeaders">
<td><span class="has-text-grey-light">{{ header.name }}</span></td>
<td><span class="has-text-grey">{{ header.value }}</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.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 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>
@@ -344,23 +362,33 @@
</div>
</div>
</div>
<div *ngIf="frontends.length > maxItem">
<div class="message">
<div class="message-header has-background-warning has-text-black">
Too many frontends to display, please add a filter.
</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 class="column is-6" *appLet="providers[tab]?.backends | backendFilter:keyword as backends">
<h2 class="subtitle"><span class="tag is-primary">{{ backends.length }}</span><span class="subtitle-name">Backends</span></h2>
<div *ngIf="backends.length < maxItem">
<div class="message" *ngFor="let p of backends; trackBy: trackItem(tab);">
<div class="message-header" [class.has-background-primary]="p.servers?.length" [class.has-background-danger]="!p.servers?.length">
<h2>
<i class="icon fas fa-server has-text-white"></i>
<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>
<span class="has-text-white">{{ p.id }}</span>
</div>
</h2>
</div>
@@ -374,28 +402,34 @@
</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 *ngIf="p.section !== 'details'" class="section-container">
<div class="section-line">
<table class="table is-fullwidth is-hoverable table-fixed">
<colgroup>
<col class="table-col-75">
<col>
</colgroup>
<tbody>
<tr>
<td>Server</td>
<td>Weight</td>
</tr>
<tr *ngFor="let server of p.servers">
<td class="table-cell-limited"><a href="{{ server.url }}" [title]="server.id">{{ server.url }}</a></td>
<td><span class="has-text-grey">{{ server.weight }}</span></td>
</tr>
</tbody>
</table>
</div>
</div>
<!-- Details -->
<div *ngIf="p.section === 'details'">
<div *ngIf="p.section === 'details'" class="section-container">
<div *ngIf="p.loadBalancer">
<div *ngIf="p.loadBalancer" class="section-line">
<div class="columns">
<div class="column is-3">
<h2>Load Balancer</h2>
<h2 class="section-line-header">Load Balancer</h2>
</div>
<div class="column is-9">
<div class="field is-grouped is-grouped-multiline">
@@ -424,9 +458,9 @@
<div *ngIf="p.maxConn">
<hr/>
<div class="columns">
<div class="columns section-line">
<div class="column is-3">
<h2>Max Connections</h2>
<h2 class="section-line-header">Max Connections</h2>
</div>
<div class="column is-9">
<div class="field is-grouped is-grouped-multiline">
@@ -449,9 +483,9 @@
<div *ngIf="p.circuitBreaker">
<hr/>
<div class="columns">
<div class="columns section-line">
<div class="column is-3">
<h2>Circuit Breaker</h2>
<h2 class="section-line-header">Circuit Breaker</h2>
</div>
<div class="column is-9">
<div class="field is-grouped is-grouped-multiline">
@@ -468,9 +502,9 @@
<div *ngIf="p.healthCheck">
<hr/>
<div class="columns">
<div class="columns section-line">
<div class="column is-3">
<h2>Health Check</h2>
<h2 class="section-line-header">Health Check</h2>
</div>
<div class="column is-9">
<div class="field is-grouped is-grouped-multiline">
@@ -505,81 +539,79 @@
<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 class="section-line">
<h2 class="section-line-header">Buffering</h2>
<table class="table is-fullwidth is-hoverable table-fixedd">
<tbody>
<tr>
<td><span class="has-text-grey">Request Body Bytes</span></td>
<td>
<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>
</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>
</td>
<td>
<div class="field is-grouped is-grouped-multiline">
<div class="control">
<div class="tags has-addons">
<span class="tag is-light">Mem</span>
<span class="tag is-info">{{ p.buffering.memRequestBodyBytes }}</span>
</div>
</div>
</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>
</td>
</tr>
<tr>
<td><span class="has-text-grey">Response Body Bytes</span></td>
<td>
<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>
</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>
</td>
<td>
<div class="field is-grouped is-grouped-multiline">
<div class="control">
<div class="tags has-addons">
<span class="tag is-light">Mem</span>
<span class="tag is-info">{{ p.buffering.memResponseBodyBytes }}</span>
</div>
</div>
</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>
</td>
</tr>
<tr>
<td class="has-text-grey">Retry Expression</td>
<td colspan="2"><span class="tag is-info">{{ p.buffering.retryExpression }}</span></td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
</div>
<div *ngIf="backends.length > maxItem">
<div class="message">
<div class="message-header has-background-warning has-text-black">
Too many backends to display, please add a filter.
</div>
</div>
</div>
</div>
</div>

View File

@@ -1,8 +1,8 @@
import { Component, OnInit, OnDestroy } from '@angular/core';
import { ApiService } from '../../services/api.service';
import { Subscription } from 'rxjs/Subscription';
import { Component, OnDestroy, OnInit } from '@angular/core';
import * as _ from 'lodash';
import { Observable } from 'rxjs/Observable';
import * as _ from "lodash";
import { Subscription } from 'rxjs/Subscription';
import { ApiService } from '../../services/api.service';
@Component({
selector: 'app-providers',
@@ -10,8 +10,9 @@ import * as _ from "lodash";
})
export class ProvidersComponent implements OnInit, OnDestroy {
sub: Subscription;
maxItem: number;
keys: string[];
data: any;
previousKeys: string[];
previousData: any;
providers: any;
tab: string;
@@ -20,6 +21,7 @@ export class ProvidersComponent implements OnInit, OnDestroy {
constructor(private apiService: ApiService) { }
ngOnInit() {
this.maxItem = 100;
this.keyword = '';
this.sub = Observable.timer(0, 2000)
.timeInterval()
@@ -27,28 +29,23 @@ export class ProvidersComponent implements OnInit, OnDestroy {
.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];
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];
}
}
}
});
}
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);
})
}
});
}, {});
trackItem(tab): (index, item) => string {
return (index, item): string => tab + '-' + item.id;
}
ngOnDestroy() {

View File

@@ -0,0 +1,21 @@
import { Directive, Input, TemplateRef, ViewContainerRef } from '@angular/core';
interface LetContext<T> {
appLet: T;
}
@Directive({
selector: '[appLet]'
})
export class LetDirective<T> {
private _context: LetContext<T> = {appLet: null};
constructor(_viewContainer: ViewContainerRef, _templateRef: TemplateRef<LetContext<T>>) {
_viewContainer.createEmbeddedView(_templateRef, this._context);
}
@Input()
set appLet(value: T) {
this._context.appLet = value;
}
}

View File

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

View File

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

View File

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

View File

@@ -1,11 +1,11 @@
import { HttpClient, HttpErrorResponse, HttpHeaders } from '@angular/common/http';
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/map';
import 'rxjs/add/operator/retry';
import { Observable } from 'rxjs/Observable';
export interface ProviderType {
[provider: string]: {
@@ -25,7 +25,7 @@ export class ApiService {
}
fetchVersion(): Observable<any> {
return this.http.get(`/api/version`, { headers: this.headers })
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}`);
@@ -34,7 +34,7 @@ export class ApiService {
}
fetchHealthStatus(): Observable<any> {
return this.http.get(`/health`, { headers: this.headers })
return this.http.get('../health', {headers: this.headers})
.retry(2)
.catch((err: HttpErrorResponse) => {
console.error(`[health] returned code ${err.status}, body was: ${err.error}`);
@@ -43,46 +43,53 @@ export class ApiService {
}
fetchProviders(): Observable<any> {
return this.http.get(`/api/providers`, { headers: this.headers })
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);
.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] = {
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
};
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);
}
return frontend;
});
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
};
acc[curr].backends = this.toArray(data[curr].backends, 'id')
.map(backend => {
backend.servers = this.toArray(backend.servers, 'id');
return backend;
});
return data[curr].frontends[key];
}),
};
return acc;
}, {});
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];
});
}
}

View File

@@ -1,26 +0,0 @@
@charset "utf-8"
@import 'typography'
@import 'colors'
@import '../../node_modules/bulma/sass/utilities/all'
@import '../../node_modules/bulma/sass/base/all'
@import '../../node_modules/bulma/sass/grid/all'
@import '../../node_modules/bulma/sass/elements/container'
@import '../../node_modules/bulma/sass/elements/tag'
@import '../../node_modules/bulma/sass/elements/box'
@import '../../node_modules/bulma/sass/elements/form'
@import '../../node_modules/bulma/sass/elements/table'
@import '../../node_modules/bulma/sass/components/navbar'
@import '../../node_modules/bulma/sass/components/tabs'
@import '../../node_modules/bulma/sass/elements/notification'
@import 'nav'
@import 'content'
@import 'message'
@import 'label'
@import 'charts'
@import 'helper'
html
font-family: $open-sans
height: 100%
background: $background

View File

@@ -30,12 +30,6 @@
height: 320px
background-color: $white
.bar
fill: rgba($blue, 0.91)
&:hover
fill: lighten($blue, 10)
.axis text
fill: $text
font: 10px sans-serif

View File

@@ -1,46 +1,21 @@
.content
background: transparent
margin: 40px 0
margin: 2rem 0
.subtitle
font-size: 15px
text-transform: uppercase
color: $black
font-size: 0.9rem
font-weight: $weight-bold
text-transform: uppercase
margin: 10px 0 0 0
.list-title
color: $text-dark
weight: $weight-semibold
margin: 5px 0 0 0
.list-item
width: 100%
display: block
align-items: center
font-size: 12px
padding: 6px 10px
border-top: 1px solid $border-light
.columns
.column
display: flex
align-items: center
.icon
width: 22px
height: 22px
display: block
float: left
margin-right: 10px
.subtitle-name
padding-left: 0.5rem
.content-item
background: $white
border: 1px solid $border-secondary
margin: 10px 0
border-radius: 4px
border-radius: $traefik-border-radius
box-shadow: 1px 2px 5px rgba($border, 0.4)
h2
@@ -82,7 +57,7 @@
img
width: 40px
heught: 40px
height: 40px
display: block
float: left
margin-right: 10px
@@ -106,37 +81,27 @@
margin: 15px auto
.search-container
height: 50px
background: $white
border-radius: 4px
color: $black
margin: 10px 0
display: flex
align-items: center
position: relative
border-radius: $traefik-border-radius
box-shadow: 1px 2px 5px rgba($border, 0.4)
border: 1px solid $border-secondary
position: relative
height: 3rem
.icon
.search-button
position: absolute
left: 10px
top: 13px
left: 1rem
top: 0.8rem
input
font-size: 16px
color: $text
width: 100%
height: 48px
padding-left: 50px
border: none
border-radius: $traefik-border-radius
outline: none
font-size: 1rem
font-weight: $weight-light
border-radius: 4px
.notification
background: $white
border-radius: 4px
color: $text
font-size: 16px
box-shadow: 1px 2px 5px rgba($border, 0.4)
border: 1px solid $border-secondary
width: 100%
padding-left: 2.8rem

View File

@@ -1,29 +0,0 @@
.label
padding: 5px 10px
background: $white
color: $color
font-size: 12px
font-family: $weight-semibold
width: 100%
display: flex
align-items: center
justify-content: center
border: 1px solid $border
background: linear-gradient(0deg, #F2F4F7 0%, #FFFFFF 100%)
&.green
background: $green-secondary
&.red
background: $red-secondary
&.yellow
background: $yellow-secondary
&.blue
background: $blue-secondary
span
display: inline-flex
float: left
align-items: center

View File

@@ -1,89 +1,65 @@
.message
display: block
font-size: 14px
margin: 20px 0 30px 0
font-size: 0.8rem
margin: 1rem 0 1.5rem 0
padding-bottom: 0.3rem
border: 1px solid $border
background: $white
border-radius: 4px
border-radius: $traefik-border-radius
box-shadow: 1px 2px 5px rgba($border, 0.4)
.message-header
color: $color-secondary
border-bottom: 1px solid $border-secondary
padding: 20px 10px
background: #f8f9fa
border-top-left-radius: 4px
border-top-right-radius: 4px
padding: 0.6rem
border-top-left-radius: $traefik-border-radius
border-top-right-radius: $traefik-border-radius
.icon
display: block
float: left
width: 1.4rem
height: 1.4rem
margin-right: 0.5rem
h2
font-size: 14px
weight: $weight-bold
display: flex
justify-content: space-between
&.red
background: rgba($red-secondary, 0.4)
border-bottom: 1px solid $red-secondary
color: $red-secondary
p
color: $red-secondary
&.green
background-color: rgba($green-secondary, 0.4)
border-bottom: 1px solid $green-secondary
color: $green-secondary
p
color: darken($green-secondary, 10) !important
&.orange
background-color: rgba($orange-secondary, 0.4)
border-bottom: 1px solid $orange-secondary
color: $orange-secondary
p
color: $orange-secondary
&.blue
background-color: rgba($blue-background, 0.4)
border-bottom: 1px solid $blue-background
color: $blue-background
p
color: $blue-background !important
img
margin-right: 15px
.message-body
.field
margin: 5px 10px
padding-bottom: 10px
.tabs
margin-bottom: 0.5rem
.tags-list
margin: 5px 10px
.section-container
padding: 0.3em 0 0 0
.control
width: 100%
margin: 5px 0
.section-line
padding: 0 0.75em
.tags
width: 100%
.section-line-header
padding: 0.2em 0 0 0
.tag
width: 50%
// required for small screen (without -> table overlapping)
.table-fixed
table-layout: fixed
// required for small screen (without -> table overlapping)
.table-fixed-break
table-layout: fixed
word-wrap: break-word
.table-cell-limited
overflow: hidden
text-overflow: ellipsis
.table-col-75
width: 75%
h2
margin: 10px 10px 0 10px
color: $black
hr
margin: 5px 0
.message-subheader
border-bottom: 1px solid $border-secondary
padding: 10px
margin-bottom: 5px

View File

@@ -1,16 +1,12 @@
.navbar
border-bottom: 1px solid $border
box-shadow: 1px 2px 5px rgba($border, 0.4)
height: 60px
.navbar-item
font-size: 13px
font-size: 0.8rem
text-transform: uppercase
font-weight: $weight-semibold
.navbar-logo
width: 40px
min-height: 40px
&:hover
background: transparent

View File

@@ -1,14 +1,14 @@
=font-face($family, $path, $weight: normal, $style: normal)
@font-face
font-family: $family
src: url('#{$path}.ttf') format('truetype')
src: url('./#{$path}.ttf') format('truetype')
font-weight: $weight
font-style: $style
+font-face('Open Sans', '/assets/fonts/OpenSans-Light', 300, 'light')
+font-face('Open Sans', '/assets/fonts/OpenSans-Regular', 400, 'regular')
+font-face('Open Sans', '/assets/fonts/OpenSans-Semibold', 600, 'semibold')
+font-face('Open Sans', '/assets/fonts/OpenSans-Bold', 700, 'bold')
+font-face('Open Sans', '/assets/fonts/OpenSans-ExtraBold', 800, 'extrabold')
+font-face('Open Sans', 'assets/fonts/OpenSans-Light', 300, 'light')
+font-face('Open Sans', 'assets/fonts/OpenSans-Regular', 400, 'regular')
+font-face('Open Sans', 'assets/fonts/OpenSans-Semibold', 600, 'semibold')
+font-face('Open Sans', 'assets/fonts/OpenSans-Bold', 700, 'bold')
+font-face('Open Sans', 'assets/fonts/OpenSans-ExtraBold', 800, 'extrabold')
$open-sans: 'Open Sans', sans-serif

View File

@@ -0,0 +1 @@
$traefik-border-radius: 4px

View File

@@ -1031,9 +1031,9 @@ builtin-status-codes@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz#85982878e21b98e1c66425e03d0174788f569ee8"
bulma@^0.6.2:
version "0.6.2"
resolved "https://registry.yarnpkg.com/bulma/-/bulma-0.6.2.tgz#f4b1d11d5acc51a79644eb0a2b0b10649d3d71f5"
bulma@^0.7.0:
version "0.7.1"
resolved "https://registry.yarnpkg.com/bulma/-/bulma-0.7.1.tgz#73c2e3b2930c90cc272029cbd19918b493fca486"
bytes@3.0.0:
version "3.0.0"
@@ -1698,6 +1698,10 @@ d3-format@1, d3-format@1.2.2:
version "1.2.2"
resolved "https://registry.yarnpkg.com/d3-format/-/d3-format-1.2.2.tgz#1a39c479c8a57fe5051b2e67a3bee27061a74e7a"
d3-format@^1.3.0:
version "1.3.0"
resolved "https://registry.yarnpkg.com/d3-format/-/d3-format-1.3.0.tgz#a3ac44269a2011cdb87c7b5693040c18cddfff11"
d3-geo@1.9.1:
version "1.9.1"
resolved "https://registry.yarnpkg.com/d3-geo/-/d3-geo-1.9.1.tgz#157e3b0f917379d0f73bebfff3be537f49fa7356"