mirror of
https://github.com/containous/traefik.git
synced 2024-12-22 13:34:03 +03:00
feat: use dedicated entrypoint for the tunnels
Co-authored-by: Fernandez Ludovic <[ldez@users.noreply.github.com](mailto:ldez@users.noreply.github.com)>
This commit is contained in:
parent
619621f239
commit
86cc6df374
@ -34,6 +34,7 @@ import (
|
||||
"github.com/traefik/traefik/v2/pkg/pilot"
|
||||
"github.com/traefik/traefik/v2/pkg/provider/acme"
|
||||
"github.com/traefik/traefik/v2/pkg/provider/aggregator"
|
||||
"github.com/traefik/traefik/v2/pkg/provider/hub"
|
||||
"github.com/traefik/traefik/v2/pkg/provider/traefik"
|
||||
"github.com/traefik/traefik/v2/pkg/safe"
|
||||
"github.com/traefik/traefik/v2/pkg/server"
|
||||
@ -215,8 +216,6 @@ func setupServer(staticConfiguration *static.Configuration) (*server.Server, err
|
||||
}
|
||||
|
||||
if staticConfiguration.Pilot != nil {
|
||||
log.WithoutContext().Warn("Traefik Pilot is deprecated and will be removed soon. Please check our Blog for migration instructions later this year")
|
||||
|
||||
version.PilotEnabled = staticConfiguration.Pilot.Dashboard
|
||||
}
|
||||
|
||||
@ -363,7 +362,7 @@ func getDefaultsEntrypoints(staticConfiguration *static.Configuration) []string
|
||||
var defaultEntryPoints []string
|
||||
for name, cfg := range staticConfiguration.EntryPoints {
|
||||
// Traefik Hub entryPoint should not be part of the set of default entryPoints.
|
||||
if staticConfiguration.Hub != nil && staticConfiguration.Hub.EntryPoint == name {
|
||||
if hub.APIEntrypoint == name || hub.TunnelEntrypoint == name {
|
||||
continue
|
||||
}
|
||||
|
||||
|
@ -457,10 +457,3 @@ the value for the method label becomes `EXTENSION_METHOD`, instead of the reques
|
||||
### Tracing
|
||||
|
||||
In `v2.6.1`, the Datadog tags added to a span changed from `service.name` to `traefik.service.name` and from `router.name` to `traefik.router.name`.
|
||||
|
||||
## v2.7
|
||||
|
||||
### Traefik Pilot
|
||||
|
||||
In `v2.7`, the `pilot.token` and `pilot.dashboard` options are deprecated.
|
||||
Please check the [feature deprecation page](../deprecation/features.md) and our Blog for migration instructions later this year.
|
||||
|
@ -5,11 +5,6 @@ description: "Learn how to connect Traefik Proxy with Pilot, a SaaS platform tha
|
||||
|
||||
# Plugins and Traefik Pilot
|
||||
|
||||
!!! warning "Traefik Pilot Deprecation"
|
||||
|
||||
Traefik Pilot is deprecated and will be removed soon.
|
||||
Please check our Blog for migration instructions later this year.
|
||||
|
||||
Traefik Pilot is a software-as-a-service (SaaS) platform that connects to Traefik to extend its capabilities.
|
||||
It offers a number of features to enhance observability and control of Traefik through a global control plane and dashboard, including:
|
||||
|
||||
|
@ -222,9 +222,6 @@ The maximal depth of DNS recursive resolving (Default: ```5```)
|
||||
`--hub`:
|
||||
Traefik Hub configuration. (Default: ```false```)
|
||||
|
||||
`--hub.entrypoint`:
|
||||
Entrypoint that exposes data for Traefik Hub. It should be a dedicated one, and not used by any router. (Default: ```traefik-hub```)
|
||||
|
||||
`--hub.tls.ca`:
|
||||
The certificate authority authenticates the Traefik Hub Agent certificate.
|
||||
|
||||
|
@ -222,9 +222,6 @@ The maximal depth of DNS recursive resolving (Default: ```5```)
|
||||
`TRAEFIK_HUB`:
|
||||
Traefik Hub configuration. (Default: ```false```)
|
||||
|
||||
`TRAEFIK_HUB_ENTRYPOINT`:
|
||||
Entrypoint that exposes data for Traefik Hub. It should be a dedicated one, and not used by any router. (Default: ```traefik-hub```)
|
||||
|
||||
`TRAEFIK_HUB_TLS_CA`:
|
||||
The certificate authority authenticates the Traefik Hub Agent certificate.
|
||||
|
||||
|
@ -426,7 +426,6 @@
|
||||
dashboard = true
|
||||
|
||||
[hub]
|
||||
entrypoint = "foobar"
|
||||
[hub.tls]
|
||||
insecure = true
|
||||
ca = "foobar"
|
||||
|
@ -447,7 +447,6 @@ pilot:
|
||||
token: foobar
|
||||
dashboard: true
|
||||
hub:
|
||||
entrypoint: foobar
|
||||
tls:
|
||||
insecure: true
|
||||
ca: foobar
|
||||
|
@ -1,295 +0,0 @@
|
||||
# Traefik Hub (Experimental)
|
||||
|
||||
## Overview
|
||||
|
||||
Once the Traefik Hub Experimental feature is enabled in Traefik,
|
||||
Traefik and its local agent communicate together.
|
||||
This agent can:
|
||||
|
||||
* get the Traefik metrics to display them in the Traefik Hub UI
|
||||
* secure the Traefik routers
|
||||
* provide ACME certificates to Traefik
|
||||
* transfer requests from the SaaS Platform to Traefik (and then avoid the users to expose directly their infrastructure on the internet)
|
||||
|
||||
!!! warning "Traefik Hub EntryPoint"
|
||||
|
||||
When the Traefik Hub feature is enabled, Traefik exposes some services meant for the Traefik Hub Agent on a dedicated entryPoint (on port `9900` by default).
|
||||
Given their sensitive nature, those services should not be publicly exposed.
|
||||
Also this dedicated entryPoint, regardless of how it is created (default, or user-defined), should not be used by anything other than the Hub Agent.
|
||||
|
||||
!!! important "Learn More About Traefik Hub"
|
||||
|
||||
This section is intended only as a brief overview for Traefik users who are not familiar with Traefik Hub.
|
||||
To explore all that Traefik Hub has to offer, please consult the [Traefik Hub Documentation](https://doc.traefik.io/traefik-hub).
|
||||
|
||||
!!! Note "Prerequisites"
|
||||
|
||||
* Traefik Hub is compatible with Traefik Proxy 2.7 or later.
|
||||
* The Traefik Hub Agent must be installed to connect to the Traefik Hub platform.
|
||||
* Activate this feature in the experimental section of the static configuration.
|
||||
|
||||
!!! example "Minimal Static Configuration to Activate Traefik Hub"
|
||||
|
||||
```yaml tab="File (YAML)"
|
||||
experimental:
|
||||
hub: true
|
||||
|
||||
hub:
|
||||
tls:
|
||||
insecure: true
|
||||
|
||||
metrics:
|
||||
prometheus: {}
|
||||
```
|
||||
|
||||
```toml tab="File (TOML)"
|
||||
[experimental]
|
||||
hub = true
|
||||
|
||||
[hub]
|
||||
[hub.tls]
|
||||
insecure = true
|
||||
|
||||
[metrics]
|
||||
[metrics.prometheus]
|
||||
```
|
||||
|
||||
```bash tab="CLI"
|
||||
--experimental.hub
|
||||
--hub.tls.insecure=true
|
||||
--metrics.prometheus=true
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
### `entryPoint`
|
||||
|
||||
_Optional, Default="traefik-hub"_
|
||||
|
||||
Defines the entryPoint that exposes data for Traefik Hub Agent.
|
||||
|
||||
!!! info
|
||||
|
||||
* If no entryPoint is defined, a default `traefik-hub` entryPoint is created (on port `9900`).
|
||||
* If defined, the value must match an existing entryPoint name.
|
||||
* This dedicated Traefik Hub entryPoint should not be used by anything other than Traefik Hub.
|
||||
|
||||
```yaml tab="File (YAML)"
|
||||
entryPoints:
|
||||
hub-ep: ":8000"
|
||||
|
||||
hub:
|
||||
entryPoint: "hub-ep"
|
||||
```
|
||||
|
||||
```toml tab="File (TOML)"
|
||||
[entryPoints.hub-ep]
|
||||
address = ":8000"
|
||||
|
||||
[hub]
|
||||
entryPoint = "hub-ep"
|
||||
```
|
||||
|
||||
```bash tab="CLI"
|
||||
--entrypoints.hub-ep.address=:8000
|
||||
--hub.entrypoint=hub-ep
|
||||
```
|
||||
|
||||
### `tls`
|
||||
|
||||
_Required, Default=None_
|
||||
|
||||
This section allows configuring mutual TLS connection between Traefik Proxy and the Traefik Hub Agent.
|
||||
The key and the certificate are the credentials for Traefik Proxy as a TLS client.
|
||||
The certificate authority authenticates the Traefik Hub Agent certificate.
|
||||
|
||||
!!! note "Certificate Domain"
|
||||
|
||||
The certificate must be valid for the `proxy.traefik` domain.
|
||||
|
||||
!!! note "Certificates Definition"
|
||||
|
||||
Certificates can be defined either by their content or their path.
|
||||
|
||||
!!! note "Insecure Mode"
|
||||
|
||||
The `insecure` option is mutually exclusive with any other option.
|
||||
|
||||
```yaml tab="File (YAML)"
|
||||
hub:
|
||||
tls:
|
||||
ca: /path/to/ca.pem
|
||||
cert: /path/to/cert.pem
|
||||
key: /path/to/key.pem
|
||||
```
|
||||
|
||||
```toml tab="File (TOML)"
|
||||
[hub.tls]
|
||||
ca= "/path/to/ca.pem"
|
||||
cert= "/path/to/cert.pem"
|
||||
key= "/path/to/key.pem"
|
||||
```
|
||||
|
||||
```bash tab="CLI"
|
||||
--hub.tls.ca=/path/to/ca.pem
|
||||
--hub.tls.cert=/path/to/cert.pem
|
||||
--hub.tls.key=/path/to/key.pem
|
||||
```
|
||||
|
||||
### `tls.ca`
|
||||
|
||||
The certificate authority authenticates the Traefik Hub Agent certificate.
|
||||
|
||||
```yaml tab="File (YAML)"
|
||||
hub:
|
||||
tls:
|
||||
ca: |-
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIBcjCCARegAwIBAgIQaewCzGdRz5iNnjAiEoO5AzAKBggqhkjOPQQDAjASMRAw
|
||||
DgYDVQQKEwdBY21lIENvMCAXDTIyMDMyMTE2MTY0NFoYDzIxMjIwMjI1MTYxNjQ0
|
||||
WjASMRAwDgYDVQQKEwdBY21lIENvMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE
|
||||
ZaKYPj2G8Hnmju6jbHt+vODwKqNDVQMH5nxhtAgSUZS61mLWwZvvUhIYLNPwHz8a
|
||||
x8C7+cuihEC6Tzvn8DeGeKNNMEswDgYDVR0PAQH/BAQDAgeAMBMGA1UdJQQMMAoG
|
||||
CCsGAQUFBwMBMAwGA1UdEwEB/wQCMAAwFgYDVR0RBA8wDYILZXhhbXBsZS5jb20w
|
||||
CgYIKoZIzj0EAwIDSQAwRgIhAO8sucDGY+JOrNgQg1a9ZqqYvbxPFnYsSZr7F/vz
|
||||
aUX2AiEAilZ+M5eX4RiMFc3nlm9qVs1LZhV3dZW/u80/mPQ/oaY=
|
||||
-----END CERTIFICATE-----
|
||||
```
|
||||
|
||||
```toml tab="File (TOML)"
|
||||
[hub.tls]
|
||||
ca = """-----BEGIN CERTIFICATE-----
|
||||
MIIBcjCCARegAwIBAgIQaewCzGdRz5iNnjAiEoO5AzAKBggqhkjOPQQDAjASMRAw
|
||||
DgYDVQQKEwdBY21lIENvMCAXDTIyMDMyMTE2MTY0NFoYDzIxMjIwMjI1MTYxNjQ0
|
||||
WjASMRAwDgYDVQQKEwdBY21lIENvMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE
|
||||
ZaKYPj2G8Hnmju6jbHt+vODwKqNDVQMH5nxhtAgSUZS61mLWwZvvUhIYLNPwHz8a
|
||||
x8C7+cuihEC6Tzvn8DeGeKNNMEswDgYDVR0PAQH/BAQDAgeAMBMGA1UdJQQMMAoG
|
||||
CCsGAQUFBwMBMAwGA1UdEwEB/wQCMAAwFgYDVR0RBA8wDYILZXhhbXBsZS5jb20w
|
||||
CgYIKoZIzj0EAwIDSQAwRgIhAO8sucDGY+JOrNgQg1a9ZqqYvbxPFnYsSZr7F/vz
|
||||
aUX2AiEAilZ+M5eX4RiMFc3nlm9qVs1LZhV3dZW/u80/mPQ/oaY=
|
||||
-----END CERTIFICATE-----"""
|
||||
```
|
||||
|
||||
```bash tab="CLI"
|
||||
--hub.tls.ca=-----BEGIN CERTIFICATE-----
|
||||
MIIBcjCCARegAwIBAgIQaewCzGdRz5iNnjAiEoO5AzAKBggqhkjOPQQDAjASMRAw
|
||||
DgYDVQQKEwdBY21lIENvMCAXDTIyMDMyMTE2MTY0NFoYDzIxMjIwMjI1MTYxNjQ0
|
||||
WjASMRAwDgYDVQQKEwdBY21lIENvMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE
|
||||
ZaKYPj2G8Hnmju6jbHt+vODwKqNDVQMH5nxhtAgSUZS61mLWwZvvUhIYLNPwHz8a
|
||||
x8C7+cuihEC6Tzvn8DeGeKNNMEswDgYDVR0PAQH/BAQDAgeAMBMGA1UdJQQMMAoG
|
||||
CCsGAQUFBwMBMAwGA1UdEwEB/wQCMAAwFgYDVR0RBA8wDYILZXhhbXBsZS5jb20w
|
||||
CgYIKoZIzj0EAwIDSQAwRgIhAO8sucDGY+JOrNgQg1a9ZqqYvbxPFnYsSZr7F/vz
|
||||
aUX2AiEAilZ+M5eX4RiMFc3nlm9qVs1LZhV3dZW/u80/mPQ/oaY=
|
||||
-----END CERTIFICATE-----
|
||||
```
|
||||
|
||||
### `tls.cert`
|
||||
|
||||
The TLS certificate for Traefik Proxy as a TLS client.
|
||||
|
||||
!!! note "Certificate Domain"
|
||||
|
||||
The certificate must be valid for the `proxy.traefik` domain.
|
||||
|
||||
```yaml tab="File (YAML)"
|
||||
hub:
|
||||
tls:
|
||||
cert: |-
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIBcjCCARegAwIBAgIQaewCzGdRz5iNnjAiEoO5AzAKBggqhkjOPQQDAjASMRAw
|
||||
DgYDVQQKEwdBY21lIENvMCAXDTIyMDMyMTE2MTY0NFoYDzIxMjIwMjI1MTYxNjQ0
|
||||
WjASMRAwDgYDVQQKEwdBY21lIENvMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE
|
||||
ZaKYPj2G8Hnmju6jbHt+vODwKqNDVQMH5nxhtAgSUZS61mLWwZvvUhIYLNPwHz8a
|
||||
x8C7+cuihEC6Tzvn8DeGeKNNMEswDgYDVR0PAQH/BAQDAgeAMBMGA1UdJQQMMAoG
|
||||
CCsGAQUFBwMBMAwGA1UdEwEB/wQCMAAwFgYDVR0RBA8wDYILZXhhbXBsZS5jb20w
|
||||
CgYIKoZIzj0EAwIDSQAwRgIhAO8sucDGY+JOrNgQg1a9ZqqYvbxPFnYsSZr7F/vz
|
||||
aUX2AiEAilZ+M5eX4RiMFc3nlm9qVs1LZhV3dZW/u80/mPQ/oaY=
|
||||
-----END CERTIFICATE-----
|
||||
```
|
||||
|
||||
```toml tab="File (TOML)"
|
||||
[hub.tls]
|
||||
cert = """-----BEGIN CERTIFICATE-----
|
||||
MIIBcjCCARegAwIBAgIQaewCzGdRz5iNnjAiEoO5AzAKBggqhkjOPQQDAjASMRAw
|
||||
DgYDVQQKEwdBY21lIENvMCAXDTIyMDMyMTE2MTY0NFoYDzIxMjIwMjI1MTYxNjQ0
|
||||
WjASMRAwDgYDVQQKEwdBY21lIENvMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE
|
||||
ZaKYPj2G8Hnmju6jbHt+vODwKqNDVQMH5nxhtAgSUZS61mLWwZvvUhIYLNPwHz8a
|
||||
x8C7+cuihEC6Tzvn8DeGeKNNMEswDgYDVR0PAQH/BAQDAgeAMBMGA1UdJQQMMAoG
|
||||
CCsGAQUFBwMBMAwGA1UdEwEB/wQCMAAwFgYDVR0RBA8wDYILZXhhbXBsZS5jb20w
|
||||
CgYIKoZIzj0EAwIDSQAwRgIhAO8sucDGY+JOrNgQg1a9ZqqYvbxPFnYsSZr7F/vz
|
||||
aUX2AiEAilZ+M5eX4RiMFc3nlm9qVs1LZhV3dZW/u80/mPQ/oaY=
|
||||
-----END CERTIFICATE-----"""
|
||||
```
|
||||
|
||||
```bash tab="CLI"
|
||||
--hub.tls.cert=-----BEGIN CERTIFICATE-----
|
||||
MIIBcjCCARegAwIBAgIQaewCzGdRz5iNnjAiEoO5AzAKBggqhkjOPQQDAjASMRAw
|
||||
DgYDVQQKEwdBY21lIENvMCAXDTIyMDMyMTE2MTY0NFoYDzIxMjIwMjI1MTYxNjQ0
|
||||
WjASMRAwDgYDVQQKEwdBY21lIENvMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE
|
||||
ZaKYPj2G8Hnmju6jbHt+vODwKqNDVQMH5nxhtAgSUZS61mLWwZvvUhIYLNPwHz8a
|
||||
x8C7+cuihEC6Tzvn8DeGeKNNMEswDgYDVR0PAQH/BAQDAgeAMBMGA1UdJQQMMAoG
|
||||
CCsGAQUFBwMBMAwGA1UdEwEB/wQCMAAwFgYDVR0RBA8wDYILZXhhbXBsZS5jb20w
|
||||
CgYIKoZIzj0EAwIDSQAwRgIhAO8sucDGY+JOrNgQg1a9ZqqYvbxPFnYsSZr7F/vz
|
||||
aUX2AiEAilZ+M5eX4RiMFc3nlm9qVs1LZhV3dZW/u80/mPQ/oaY=
|
||||
-----END CERTIFICATE-----
|
||||
```
|
||||
|
||||
### `tls.key`
|
||||
|
||||
The TLS key for Traefik Proxy as a TLS client.
|
||||
|
||||
```yaml tab="File (YAML)"
|
||||
hub:
|
||||
tls:
|
||||
key: |-
|
||||
-----BEGIN PRIVATE KEY-----
|
||||
MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgm+XJ3LVrTbbirJea
|
||||
O+Crj2ADVsVHjMuiyd72VE3lgxihRANCAARlopg+PYbweeaO7qNse3684PAqo0NV
|
||||
AwfmfGG0CBJRlLrWYtbBm+9SEhgs0/AfPxrHwLv5y6KEQLpPO+fwN4Z4
|
||||
-----END PRIVATE KEY-----
|
||||
```
|
||||
|
||||
```toml tab="File (TOML)"
|
||||
[hub.tls]
|
||||
key = """-----BEGIN PRIVATE KEY-----
|
||||
MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgm+XJ3LVrTbbirJea
|
||||
O+Crj2ADVsVHjMuiyd72VE3lgxihRANCAARlopg+PYbweeaO7qNse3684PAqo0NV
|
||||
AwfmfGG0CBJRlLrWYtbBm+9SEhgs0/AfPxrHwLv5y6KEQLpPO+fwN4Z4
|
||||
-----END PRIVATE KEY-----"""
|
||||
```
|
||||
|
||||
```bash tab="CLI"
|
||||
--hub.tls.key=-----BEGIN PRIVATE KEY-----
|
||||
MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgm+XJ3LVrTbbirJea
|
||||
O+Crj2ADVsVHjMuiyd72VE3lgxihRANCAARlopg+PYbweeaO7qNse3684PAqo0NV
|
||||
AwfmfGG0CBJRlLrWYtbBm+9SEhgs0/AfPxrHwLv5y6KEQLpPO+fwN4Z4
|
||||
-----END PRIVATE KEY-----
|
||||
```
|
||||
|
||||
### `tls.insecure`
|
||||
|
||||
_Optional, Default=false_
|
||||
|
||||
Enables an insecure TLS connection that uses default credentials,
|
||||
and which has no peer authentication between Traefik Proxy and the Traefik Hub Agent.
|
||||
The `insecure` option is mutually exclusive with any other option.
|
||||
|
||||
!!! warning "Security Consideration"
|
||||
|
||||
Do not use this setup in production.
|
||||
This option implies sensitive data can be exposed to potential malicious third-party programs.
|
||||
|
||||
```yaml tab="File (YAML)"
|
||||
hub:
|
||||
tls:
|
||||
insecure: true
|
||||
```
|
||||
|
||||
```toml tab="File (TOML)"
|
||||
[hub.tls]
|
||||
insecure = true
|
||||
```
|
||||
|
||||
```bash tab="CLI"
|
||||
--hub.tls.insecure=true
|
||||
```
|
@ -136,7 +136,6 @@ nav:
|
||||
- 'InFlightConn': 'middlewares/tcp/inflightconn.md'
|
||||
- 'IpWhitelist': 'middlewares/tcp/ipwhitelist.md'
|
||||
- 'Plugins & Traefik Pilot': 'plugins/index.md'
|
||||
- 'Traefik Hub': 'traefik-hub/index.md'
|
||||
- 'Operations':
|
||||
- 'CLI': 'operations/cli.md'
|
||||
- 'Dashboard' : 'operations/dashboard.md'
|
||||
|
53
pkg/config/static/hub.go
Normal file
53
pkg/config/static/hub.go
Normal file
@ -0,0 +1,53 @@
|
||||
package static
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/traefik/traefik/v2/pkg/log"
|
||||
"github.com/traefik/traefik/v2/pkg/provider/hub"
|
||||
)
|
||||
|
||||
func (c *Configuration) initHubProvider() error {
|
||||
// Hub provider is an experimental feature. It requires the experimental flag to be enabled before continuing.
|
||||
if c.Experimental == nil || !c.Experimental.Hub {
|
||||
return errors.New("the experimental flag for Hub is not set")
|
||||
}
|
||||
|
||||
if _, ok := c.EntryPoints[hub.TunnelEntrypoint]; !ok {
|
||||
var ep EntryPoint
|
||||
ep.SetDefaults()
|
||||
ep.Address = ":9901"
|
||||
c.EntryPoints[hub.TunnelEntrypoint] = &ep
|
||||
log.WithoutContext().Infof("The entryPoint %q is created on port 9901 to allow exposition of services.", hub.TunnelEntrypoint)
|
||||
}
|
||||
|
||||
if c.Hub.TLS == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if c.Hub.TLS.Insecure && (c.Hub.TLS.CA != "" || c.Hub.TLS.Cert != "" || c.Hub.TLS.Key != "") {
|
||||
return errors.New("mTLS configuration for Hub and insecure TLS for Hub are mutually exclusive")
|
||||
}
|
||||
|
||||
if !c.Hub.TLS.Insecure && (c.Hub.TLS.CA == "" || c.Hub.TLS.Cert == "" || c.Hub.TLS.Key == "") {
|
||||
return errors.New("incomplete mTLS configuration for Hub")
|
||||
}
|
||||
|
||||
if c.Hub.TLS.Insecure {
|
||||
log.WithoutContext().Warn("Hub is in `insecure` mode. Do not run in production with this setup.")
|
||||
}
|
||||
|
||||
if _, ok := c.EntryPoints[hub.APIEntrypoint]; !ok {
|
||||
var ep EntryPoint
|
||||
ep.SetDefaults()
|
||||
ep.Address = ":9900"
|
||||
c.EntryPoints[hub.APIEntrypoint] = &ep
|
||||
log.WithoutContext().Infof("The entryPoint %q is created on port 9900 to allow Traefik to communicate with the Hub Agent for Traefik.", hub.APIEntrypoint)
|
||||
}
|
||||
|
||||
c.EntryPoints[hub.APIEntrypoint].HTTP.TLS = &TLSConfig{
|
||||
Options: "traefik-hub",
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
@ -1,7 +1,6 @@
|
||||
package static
|
||||
|
||||
// Pilot Configuration related to Traefik Pilot.
|
||||
// Deprecated.
|
||||
type Pilot struct {
|
||||
Token string `description:"Traefik Pilot token." json:"token,omitempty" toml:"token,omitempty" yaml:"token,omitempty" loggable:"false"`
|
||||
Dashboard bool `description:"Enable Traefik Pilot in the dashboard." json:"dashboard,omitempty" toml:"dashboard,omitempty" yaml:"dashboard,omitempty"`
|
||||
|
@ -1,7 +1,6 @@
|
||||
package static
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
stdlog "log"
|
||||
"strings"
|
||||
@ -78,7 +77,6 @@ type Configuration struct {
|
||||
|
||||
CertificatesResolvers map[string]CertificateResolver `description:"Certificates resolvers configuration." json:"certificatesResolvers,omitempty" toml:"certificatesResolvers,omitempty" yaml:"certificatesResolvers,omitempty" export:"true"`
|
||||
|
||||
// Deprecated.
|
||||
Pilot *Pilot `description:"Traefik Pilot configuration." json:"pilot,omitempty" toml:"pilot,omitempty" yaml:"pilot,omitempty" export:"true"`
|
||||
|
||||
Hub *hub.Provider `description:"Traefik Hub configuration." json:"hub,omitempty" toml:"hub,omitempty" yaml:"hub,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"`
|
||||
@ -201,7 +199,7 @@ type Providers struct {
|
||||
// It also takes care of maintaining backwards compatibility.
|
||||
func (c *Configuration) SetEffectiveConfiguration() {
|
||||
// Creates the default entry point if needed
|
||||
if len(c.EntryPoints) == 0 || (c.Hub != nil && len(c.EntryPoints) == 1 && c.EntryPoints[c.Hub.EntryPoint] != nil) {
|
||||
if !c.hasUserDefinedEntrypoint() {
|
||||
ep := &EntryPoint{Address: ":80"}
|
||||
ep.SetDefaults()
|
||||
// TODO: double check this tomorrow
|
||||
@ -287,6 +285,21 @@ func (c *Configuration) SetEffectiveConfiguration() {
|
||||
c.initACMEProvider()
|
||||
}
|
||||
|
||||
func (c *Configuration) hasUserDefinedEntrypoint() bool {
|
||||
if len(c.EntryPoints) == 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
switch len(c.EntryPoints) {
|
||||
case 1:
|
||||
return c.EntryPoints[hub.TunnelEntrypoint] == nil
|
||||
case 2:
|
||||
return c.EntryPoints[hub.TunnelEntrypoint] == nil || c.EntryPoints[hub.APIEntrypoint] == nil
|
||||
default:
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Configuration) initACMEProvider() {
|
||||
for _, resolver := range c.CertificatesResolvers {
|
||||
if resolver.ACME != nil {
|
||||
@ -297,46 +310,6 @@ func (c *Configuration) initACMEProvider() {
|
||||
legolog.Logger = stdlog.New(log.WithoutContext().WriterLevel(logrus.DebugLevel), "legolog: ", 0)
|
||||
}
|
||||
|
||||
func (c *Configuration) initHubProvider() error {
|
||||
// Hub provider is an experimental feature. Require the experimental flag to be enabled before continuing.
|
||||
if c.Experimental == nil || !c.Experimental.Hub {
|
||||
return errors.New("experimental flag for Hub not set")
|
||||
}
|
||||
|
||||
if c.Hub.TLS == nil {
|
||||
return errors.New("no TLS configuration defined for Hub")
|
||||
}
|
||||
|
||||
if c.Hub.TLS.Insecure && (c.Hub.TLS.CA != "" || c.Hub.TLS.Cert != "" || c.Hub.TLS.Key != "") {
|
||||
return errors.New("mTLS configuration for Hub and insecure TLS for Hub are mutually exclusive")
|
||||
}
|
||||
|
||||
if !c.Hub.TLS.Insecure && (c.Hub.TLS.CA == "" || c.Hub.TLS.Cert == "" || c.Hub.TLS.Key == "") {
|
||||
return errors.New("incomplete mTLS configuration for Hub")
|
||||
}
|
||||
|
||||
if c.Hub.TLS.Insecure {
|
||||
log.WithoutContext().Warn("Hub is in `insecure` mode. Do not run in production with this setup.")
|
||||
}
|
||||
|
||||
// Creates the internal Hub entry point if needed.
|
||||
if c.Hub.EntryPoint == hub.DefaultEntryPointName {
|
||||
if _, ok := c.EntryPoints[hub.DefaultEntryPointName]; !ok {
|
||||
var ep EntryPoint
|
||||
ep.SetDefaults()
|
||||
ep.Address = ":9900"
|
||||
c.EntryPoints[hub.DefaultEntryPointName] = &ep
|
||||
log.WithoutContext().Infof("The entryPoint %q is created on port 9900 to allow Traefik to communicate with the Hub Agent for Traefik.", hub.DefaultEntryPointName)
|
||||
}
|
||||
}
|
||||
|
||||
c.EntryPoints[c.Hub.EntryPoint].HTTP.TLS = &TLSConfig{
|
||||
Options: "traefik-hub",
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ValidateConfiguration validate that configuration is coherent.
|
||||
func (c *Configuration) ValidateConfiguration() error {
|
||||
var acmeEmail string
|
||||
|
88
pkg/config/static/static_config_test.go
Normal file
88
pkg/config/static/static_config_test.go
Normal file
@ -0,0 +1,88 @@
|
||||
package static
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/traefik/traefik/v2/pkg/provider/hub"
|
||||
)
|
||||
|
||||
func TestHasEntrypoint(t *testing.T) {
|
||||
tests := []struct {
|
||||
desc string
|
||||
entryPoints map[string]*EntryPoint
|
||||
assert assert.BoolAssertionFunc
|
||||
}{
|
||||
{
|
||||
desc: "no user defined entryPoints",
|
||||
assert: assert.False,
|
||||
},
|
||||
{
|
||||
desc: "user defined entryPoints",
|
||||
entryPoints: map[string]*EntryPoint{
|
||||
"foo": {},
|
||||
},
|
||||
assert: assert.True,
|
||||
},
|
||||
{
|
||||
desc: "user defined entryPoints + hub entryPoint (tunnel)",
|
||||
entryPoints: map[string]*EntryPoint{
|
||||
"foo": {},
|
||||
hub.TunnelEntrypoint: {},
|
||||
},
|
||||
assert: assert.True,
|
||||
},
|
||||
{
|
||||
desc: "hub entryPoint (tunnel)",
|
||||
entryPoints: map[string]*EntryPoint{
|
||||
hub.TunnelEntrypoint: {},
|
||||
},
|
||||
assert: assert.False,
|
||||
},
|
||||
{
|
||||
desc: "user defined entryPoints + hub entryPoint (api)",
|
||||
entryPoints: map[string]*EntryPoint{
|
||||
"foo": {},
|
||||
hub.APIEntrypoint: {},
|
||||
},
|
||||
assert: assert.True,
|
||||
},
|
||||
{
|
||||
desc: "hub entryPoint (api)",
|
||||
entryPoints: map[string]*EntryPoint{
|
||||
hub.APIEntrypoint: {},
|
||||
},
|
||||
assert: assert.True,
|
||||
},
|
||||
{
|
||||
desc: "user defined entryPoints + hub entryPoints (tunnel, api)",
|
||||
entryPoints: map[string]*EntryPoint{
|
||||
"foo": {},
|
||||
hub.TunnelEntrypoint: {},
|
||||
hub.APIEntrypoint: {},
|
||||
},
|
||||
assert: assert.True,
|
||||
},
|
||||
{
|
||||
desc: "hub entryPoints (tunnel, api)",
|
||||
entryPoints: map[string]*EntryPoint{
|
||||
hub.TunnelEntrypoint: {},
|
||||
hub.APIEntrypoint: {},
|
||||
},
|
||||
assert: assert.False,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
test := test
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
cfg := &Configuration{
|
||||
EntryPoints: test.entryPoints,
|
||||
}
|
||||
|
||||
test.assert(t, cfg.hasUserDefinedEntrypoint())
|
||||
})
|
||||
}
|
||||
}
|
@ -17,13 +17,15 @@ import (
|
||||
|
||||
var _ provider.Provider = (*Provider)(nil)
|
||||
|
||||
// DefaultEntryPointName is the name of the default internal entry point.
|
||||
const DefaultEntryPointName = "traefik-hub"
|
||||
// Entrypoints created for Hub.
|
||||
const (
|
||||
APIEntrypoint = "traefikhub-api"
|
||||
TunnelEntrypoint = "traefikhub-tunl"
|
||||
)
|
||||
|
||||
// Provider holds configurations of the provider.
|
||||
type Provider struct {
|
||||
EntryPoint string `description:"Entrypoint that exposes data for Traefik Hub. It should be a dedicated one, and not used by any router." json:"entryPoint,omitempty" toml:"entryPoint,omitempty" yaml:"entryPoint,omitempty" export:"true"`
|
||||
TLS *TLS `description:"TLS configuration for mTLS communication between Traefik and Hub Agent." json:"tls,omitempty" toml:"tls,omitempty" yaml:"tls,omitempty" export:"true"`
|
||||
TLS *TLS `description:"TLS configuration for mTLS communication between Traefik and Hub Agent." json:"tls,omitempty" toml:"tls,omitempty" yaml:"tls,omitempty" export:"true"`
|
||||
|
||||
server *http.Server
|
||||
}
|
||||
@ -36,11 +38,6 @@ type TLS struct {
|
||||
Key ttls.FileOrContent `description:"The TLS key for Traefik Proxy as a TLS client." json:"key,omitempty" toml:"key,omitempty" yaml:"key,omitempty" loggable:"false"`
|
||||
}
|
||||
|
||||
// SetDefaults sets the default values.
|
||||
func (p *Provider) SetDefaults() {
|
||||
p.EntryPoint = DefaultEntryPointName
|
||||
}
|
||||
|
||||
// Init the provider.
|
||||
func (p *Provider) Init() error {
|
||||
return nil
|
||||
@ -59,7 +56,7 @@ func (p *Provider) Provide(configurationChan chan<- dynamic.Message, _ *safe.Poo
|
||||
return fmt.Errorf("creating Hub Agent HTTP client: %w", err)
|
||||
}
|
||||
|
||||
p.server = &http.Server{Handler: newHandler(p.EntryPoint, port, configurationChan, p.TLS, client)}
|
||||
p.server = &http.Server{Handler: newHandler(APIEntrypoint, port, configurationChan, p.TLS, client)}
|
||||
|
||||
// TODO: this is going to be leaky (because no context to make it terminate)
|
||||
// if/when Provide lifecycle differs with Traefik lifecycle.
|
||||
@ -70,7 +67,7 @@ func (p *Provider) Provide(configurationChan chan<- dynamic.Message, _ *safe.Poo
|
||||
}
|
||||
}()
|
||||
|
||||
exposeAPIAndMetrics(configurationChan, p.EntryPoint, port, p.TLS)
|
||||
exposeAPIAndMetrics(configurationChan, APIEntrypoint, port, p.TLS)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
@ -2,6 +2,8 @@ FROM node:14.16
|
||||
# Current Active LTS release according to (https://nodejs.org/en/about/releases/)
|
||||
|
||||
ENV WEBUI_DIR /src/webui
|
||||
ARG ARG_PLATFORM_URL=https://pilot.traefik.io
|
||||
ENV PLATFORM_URL=${ARG_PLATFORM_URL}
|
||||
RUN mkdir -p $WEBUI_DIR
|
||||
|
||||
COPY package.json $WEBUI_DIR/
|
||||
|
@ -118,11 +118,13 @@ module.exports = function (ctx) {
|
||||
env: process.env.APP_ENV === 'development'
|
||||
? { // staging:
|
||||
APP_ENV: JSON.stringify(process.env.APP_ENV),
|
||||
APP_API: JSON.stringify(process.env.APP_API || '/api')
|
||||
APP_API: JSON.stringify(process.env.APP_API || '/api'),
|
||||
PLATFORM_URL: JSON.stringify(process.env.PLATFORM_URL || 'https://pilot.traefik.io')
|
||||
}
|
||||
: { // production:
|
||||
APP_ENV: JSON.stringify(process.env.APP_ENV),
|
||||
APP_API: JSON.stringify(process.env.APP_API || '/api')
|
||||
APP_API: JSON.stringify(process.env.APP_API || '/api'),
|
||||
PLATFORM_URL: JSON.stringify(process.env.PLATFORM_URL || 'https://pilot.traefik.io')
|
||||
},
|
||||
uglifyOptions: {
|
||||
compress: {
|
||||
|
@ -1,17 +1,26 @@
|
||||
<template>
|
||||
<div id="q-app">
|
||||
<router-view />
|
||||
<platform-panel
|
||||
v-if="pilotEnabled" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { APP } from './_helpers/APP'
|
||||
import PlatformPanel from './components/platform/PlatformPanel'
|
||||
import { mapGetters } from 'vuex'
|
||||
|
||||
export default {
|
||||
name: 'App',
|
||||
components: {
|
||||
PlatformPanel
|
||||
},
|
||||
computed: {
|
||||
...mapGetters('core', { coreVersion: 'version' })
|
||||
...mapGetters('core', { coreVersion: 'version' }),
|
||||
pilotEnabled () {
|
||||
return this.coreVersion.pilotEnabled
|
||||
}
|
||||
},
|
||||
beforeCreate () {
|
||||
// Set vue instance
|
||||
|
@ -1,7 +1,8 @@
|
||||
const APP = {
|
||||
config: {
|
||||
env: process.env.APP_ENV,
|
||||
apiUrl: process.env.APP_API
|
||||
apiUrl: process.env.APP_API,
|
||||
platformUrl: process.env.PLATFORM_URL
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -15,7 +15,6 @@
|
||||
</q-tabs>
|
||||
<div class="right-menu">
|
||||
<q-tabs>
|
||||
<q-btn type="a" href="https://hub.traefik.io/" target="_blank" flat no-caps label="Go to Hub Dashboard →" class="btn-menu btn-hub" />
|
||||
<q-btn @click="$q.dark.toggle()" stretch flat no-caps icon="invert_colors" :label="`${$q.dark.isActive ? 'Light' : 'Dark'} theme`" class="btn-menu" />
|
||||
<q-btn stretch flat icon="eva-question-mark-circle-outline">
|
||||
<q-menu anchor="bottom left" auto-close>
|
||||
@ -29,6 +28,8 @@
|
||||
</q-menu>
|
||||
</q-btn>
|
||||
</q-tabs>
|
||||
<platform-auth-state
|
||||
v-if="pilotEnabled" />
|
||||
</div>
|
||||
</q-toolbar>
|
||||
</div>
|
||||
@ -44,10 +45,12 @@
|
||||
|
||||
<script>
|
||||
import config from '../../../package'
|
||||
import PlatformAuthState from '../platform/PlatformAuthState'
|
||||
import { mapActions, mapGetters } from 'vuex'
|
||||
|
||||
export default {
|
||||
name: 'NavBar',
|
||||
components: { PlatformAuthState },
|
||||
computed: {
|
||||
...mapGetters('core', { coreVersion: 'version' }),
|
||||
version () {
|
||||
@ -56,6 +59,9 @@ export default {
|
||||
? this.coreVersion.Version
|
||||
: this.coreVersion.Version.substring(0, 7)
|
||||
},
|
||||
pilotEnabled () {
|
||||
return this.coreVersion.pilotEnabled
|
||||
},
|
||||
parsedVersion () {
|
||||
if (!this.version) {
|
||||
return 'master'
|
||||
@ -138,11 +144,6 @@ export default {
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.btn-hub {
|
||||
color: #0e204c;
|
||||
background: #deea48;
|
||||
}
|
||||
|
||||
.q-item {
|
||||
padding: 0;
|
||||
}
|
||||
|
82
webui/src/components/platform/PlatformAuthState.vue
Normal file
82
webui/src/components/platform/PlatformAuthState.vue
Normal file
@ -0,0 +1,82 @@
|
||||
<template>
|
||||
<div class="iframe-wrapper" v-if="isOnline">
|
||||
<iframe
|
||||
id="platform-auth-state"
|
||||
:src="iFrameUrl"
|
||||
v-if="renderIrame"
|
||||
v-resize="resizeOpts"
|
||||
height="64px"
|
||||
frameBorder="0"
|
||||
/>
|
||||
</div>
|
||||
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapActions, mapGetters } from 'vuex'
|
||||
import qs from 'query-string'
|
||||
import '../../_directives/resize'
|
||||
|
||||
export default {
|
||||
name: 'PlatformPanel',
|
||||
data () {
|
||||
return {
|
||||
renderIrame: true,
|
||||
resizeOpts: {
|
||||
log: false,
|
||||
onMessage: ({ iframe, message }) => {
|
||||
if (typeof message === 'string') {
|
||||
// 1st condition for backward compatibility
|
||||
if (message === 'open:profile') {
|
||||
this.openPlatform('/')
|
||||
} else if (message.includes('open:')) {
|
||||
this.openPlatform(message.split('open:')[1])
|
||||
} else if (message === 'logout') {
|
||||
this.closePlatform()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
created () {
|
||||
this.getInstanceInfos()
|
||||
},
|
||||
computed: {
|
||||
...mapGetters('platform', { isPlatformOpen: 'isOpen', platformPath: 'path' }),
|
||||
...mapGetters('core', { instanceInfos: 'version' }),
|
||||
isOnline () {
|
||||
return window.navigator.onLine
|
||||
},
|
||||
iFrameUrl () {
|
||||
const instanceInfos = JSON.stringify(this.instanceInfos)
|
||||
const authRedirectUrl = `${window.location.href.split('?')[0]}?platform=${this.platformPath}`
|
||||
|
||||
return qs.stringifyUrl({ url: `${this.platformUrl}/partials/auth-state`, query: { authRedirectUrl, instanceInfos } })
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
...mapActions('platform', { openPlatform: 'open' }, { closePlatform: 'close' }),
|
||||
...mapActions('core', { getInstanceInfos: 'getVersion' })
|
||||
},
|
||||
watch: {
|
||||
isPlatformOpen (isOpen, wasOpen) {
|
||||
if (!isOpen && wasOpen) {
|
||||
this.renderIrame = false
|
||||
this.$nextTick().then(() => {
|
||||
this.renderIrame = true
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import "../../css/sass/variables";
|
||||
|
||||
#platform-auth-state {
|
||||
width: 1px;
|
||||
min-width: 296px;
|
||||
}
|
||||
</style>
|
112
webui/src/components/platform/PlatformPanel.vue
Normal file
112
webui/src/components/platform/PlatformPanel.vue
Normal file
@ -0,0 +1,112 @@
|
||||
<template>
|
||||
<side-panel
|
||||
:isOpen="isPlatformOpen"
|
||||
@onClose="closePlatform()"
|
||||
v-if="isOnline"
|
||||
>
|
||||
<div class="iframe-wrapper">
|
||||
<iframe
|
||||
id="platform-iframe"
|
||||
:src="iFrameUrl"
|
||||
v-resize="resizeOpts"
|
||||
style="position: relative; height: 100%; width: 100%;"
|
||||
frameBorder="0"
|
||||
scrolling="yes"
|
||||
@load="onIFrameLoad"
|
||||
/>
|
||||
</div>
|
||||
</side-panel>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapActions, mapGetters } from 'vuex'
|
||||
import qs from 'query-string'
|
||||
import SidePanel from '../_commons/SidePanel'
|
||||
import Helps from '../../_helpers/Helps'
|
||||
import '../../_directives/resize'
|
||||
|
||||
export default {
|
||||
name: 'PlatformPanel',
|
||||
components: {
|
||||
SidePanel
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
resizeOpts: {
|
||||
log: false,
|
||||
resize: false,
|
||||
scrolling: true,
|
||||
onMessage: ({ iframe, message }) => {
|
||||
if (typeof message === 'string') {
|
||||
// 1st condition for backward compatibility
|
||||
if (message === 'open:profile') {
|
||||
this.openPlatform('/')
|
||||
} else if (message.includes('open:')) {
|
||||
this.openPlatform(message.split('open:')[1])
|
||||
} else if (message === 'logout') {
|
||||
this.closePlatform()
|
||||
}
|
||||
} else if (message.type) {
|
||||
switch (message.type) {
|
||||
case 'copy-to-clipboard':
|
||||
navigator.clipboard.writeText(message.value)
|
||||
break
|
||||
}
|
||||
} else if (message.isAuthenticated) {
|
||||
this.isAuthenticated = message.isAuthenticated
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
created () {
|
||||
this.getInstanceInfos()
|
||||
},
|
||||
computed: {
|
||||
...mapGetters('platform', { isPlatformOpen: 'isOpen', platformPath: 'path' }),
|
||||
...mapGetters('core', { instanceInfos: 'version' }),
|
||||
iFrameUrl () {
|
||||
const instanceInfos = JSON.stringify(this.instanceInfos)
|
||||
const authRedirectUrl = `${window.location.href.split('?')[0]}?platform=${this.platformPath}`
|
||||
|
||||
return qs.stringifyUrl({ url: `${this.platformUrl}${this.platformPath}`, query: { authRedirectUrl, instanceInfos } })
|
||||
},
|
||||
isOnline () {
|
||||
return window.navigator.onLine
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
...mapActions('platform', { openPlatform: 'open', closePlatform: 'close' }),
|
||||
...mapActions('core', { getInstanceInfos: 'getVersion' })
|
||||
},
|
||||
watch: {
|
||||
$route (to, from) {
|
||||
const wasOpen = from.query && from.query.platform
|
||||
const isOpen = to.query && to.query.platform
|
||||
|
||||
if (!wasOpen && isOpen) {
|
||||
this.openPlatform(to.query.platform)
|
||||
}
|
||||
},
|
||||
isPlatformOpen (newValue, oldValue) {
|
||||
if (newValue !== oldValue) {
|
||||
document.querySelector('body').style.overflow = newValue ? 'hidden' : 'visible'
|
||||
|
||||
this.$router.push({
|
||||
path: this.$route.path,
|
||||
query: Helps.removeEmptyObjects({
|
||||
...this.$route.query,
|
||||
platform: this.platformPath ? this.platformPath : undefined
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.iframe-wrapper {
|
||||
height: 100%;
|
||||
}
|
||||
</style>
|
Loading…
Reference in New Issue
Block a user