Add Traefik Hub button and deprecate Pilot
Co-authored-by: Tom Moulard <tom.moulard@traefik.io>
This commit is contained in:
parent
aa0b5466a9
commit
91f4ccf087
14 changed files with 21 additions and 250 deletions
|
@ -216,7 +216,7 @@ func setupServer(staticConfiguration *static.Configuration) (*server.Server, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if staticConfiguration.Pilot != nil {
|
if staticConfiguration.Pilot != nil {
|
||||||
version.PilotEnabled = staticConfiguration.Pilot.Dashboard
|
log.WithoutContext().Warn("Traefik Pilot is deprecated and will be removed soon. Please check our Blog for migration instructions later this year.")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Plugins
|
// Plugins
|
||||||
|
|
|
@ -468,3 +468,8 @@ This option available in the ForwardAuth middleware, as well as in the HTTP, Con
|
||||||
### Consul Enterprise Namespaces
|
### Consul Enterprise Namespaces
|
||||||
|
|
||||||
In `v2.8`, the `namespace` option of Consul and Consul Catalog providers is deprecated, please use the `namespaces` options instead.
|
In `v2.8`, the `namespace` option of Consul and Consul Catalog providers is deprecated, please use the `namespaces` options instead.
|
||||||
|
|
||||||
|
### Traefik Pilot
|
||||||
|
|
||||||
|
In `v2.8`, the `pilot.token` and `pilot.dashboard` options are deprecated.
|
||||||
|
Please check our Blog for migration instructions later this year.
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package static
|
package static
|
||||||
|
|
||||||
// Pilot Configuration related to Traefik Pilot.
|
// Pilot Configuration related to Traefik Pilot.
|
||||||
|
// Deprecated.
|
||||||
type Pilot struct {
|
type Pilot struct {
|
||||||
Token string `description:"Traefik Pilot token." json:"token,omitempty" toml:"token,omitempty" yaml:"token,omitempty" loggable:"false"`
|
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"`
|
Dashboard bool `description:"Enable Traefik Pilot in the dashboard." json:"dashboard,omitempty" toml:"dashboard,omitempty" yaml:"dashboard,omitempty"`
|
||||||
|
|
|
@ -78,6 +78,7 @@ type Configuration struct {
|
||||||
|
|
||||||
CertificatesResolvers map[string]CertificateResolver `description:"Certificates resolvers configuration." json:"certificatesResolvers,omitempty" toml:"certificatesResolvers,omitempty" yaml:"certificatesResolvers,omitempty" export:"true"`
|
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"`
|
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"`
|
Hub *hub.Provider `description:"Traefik Hub configuration." json:"hub,omitempty" toml:"hub,omitempty" yaml:"hub,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"`
|
||||||
|
|
|
@ -194,8 +194,6 @@ func (c *client) SendInstanceInfo(ctx context.Context, pilotMetrics []metrics.Pi
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to create UUID: %w", err)
|
return fmt.Errorf("failed to create UUID: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
version.UUID = c.uuid
|
|
||||||
}
|
}
|
||||||
|
|
||||||
info := instanceInfo{
|
info := instanceInfo{
|
||||||
|
|
|
@ -23,9 +23,6 @@ var (
|
||||||
// StartDate holds the start date of traefik.
|
// StartDate holds the start date of traefik.
|
||||||
StartDate = time.Now()
|
StartDate = time.Now()
|
||||||
// UUID instance uuid.
|
// UUID instance uuid.
|
||||||
UUID string
|
|
||||||
// PilotEnabled activate integration of pilot into the dashboard.
|
|
||||||
PilotEnabled bool
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Handler expose version routes.
|
// Handler expose version routes.
|
||||||
|
@ -46,11 +43,9 @@ func (v Handler) Append(router *mux.Router) {
|
||||||
UUID string `json:"uuid,omitempty"`
|
UUID string `json:"uuid,omitempty"`
|
||||||
PilotEnabled bool `json:"pilotEnabled"`
|
PilotEnabled bool `json:"pilotEnabled"`
|
||||||
}{
|
}{
|
||||||
Version: Version,
|
Version: Version,
|
||||||
Codename: Codename,
|
Codename: Codename,
|
||||||
StartDate: StartDate,
|
StartDate: StartDate,
|
||||||
UUID: UUID,
|
|
||||||
PilotEnabled: PilotEnabled,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := templatesRenderer.JSON(response, http.StatusOK, v); err != nil {
|
if err := templatesRenderer.JSON(response, http.StatusOK, v); err != nil {
|
||||||
|
|
|
@ -2,8 +2,6 @@ FROM node:14.16
|
||||||
# Current Active LTS release according to (https://nodejs.org/en/about/releases/)
|
# Current Active LTS release according to (https://nodejs.org/en/about/releases/)
|
||||||
|
|
||||||
ENV WEBUI_DIR /src/webui
|
ENV WEBUI_DIR /src/webui
|
||||||
ARG ARG_PLATFORM_URL=https://pilot.traefik.io
|
|
||||||
ENV PLATFORM_URL=${ARG_PLATFORM_URL}
|
|
||||||
RUN mkdir -p $WEBUI_DIR
|
RUN mkdir -p $WEBUI_DIR
|
||||||
|
|
||||||
COPY package.json $WEBUI_DIR/
|
COPY package.json $WEBUI_DIR/
|
||||||
|
|
|
@ -118,13 +118,11 @@ module.exports = function (ctx) {
|
||||||
env: process.env.APP_ENV === 'development'
|
env: process.env.APP_ENV === 'development'
|
||||||
? { // staging:
|
? { // staging:
|
||||||
APP_ENV: JSON.stringify(process.env.APP_ENV),
|
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:
|
: { // production:
|
||||||
APP_ENV: JSON.stringify(process.env.APP_ENV),
|
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: {
|
uglifyOptions: {
|
||||||
compress: {
|
compress: {
|
||||||
|
|
|
@ -1,26 +1,17 @@
|
||||||
<template>
|
<template>
|
||||||
<div id="q-app">
|
<div id="q-app">
|
||||||
<router-view />
|
<router-view />
|
||||||
<platform-panel
|
|
||||||
v-if="pilotEnabled" />
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { APP } from './_helpers/APP'
|
import { APP } from './_helpers/APP'
|
||||||
import PlatformPanel from './components/platform/PlatformPanel'
|
|
||||||
import { mapGetters } from 'vuex'
|
import { mapGetters } from 'vuex'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'App',
|
name: 'App',
|
||||||
components: {
|
|
||||||
PlatformPanel
|
|
||||||
},
|
|
||||||
computed: {
|
computed: {
|
||||||
...mapGetters('core', { coreVersion: 'version' }),
|
...mapGetters('core', { coreVersion: 'version' })
|
||||||
pilotEnabled () {
|
|
||||||
return this.coreVersion.pilotEnabled
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
beforeCreate () {
|
beforeCreate () {
|
||||||
// Set vue instance
|
// Set vue instance
|
||||||
|
|
|
@ -1,8 +1,7 @@
|
||||||
const APP = {
|
const APP = {
|
||||||
config: {
|
config: {
|
||||||
env: process.env.APP_ENV,
|
env: process.env.APP_ENV,
|
||||||
apiUrl: process.env.APP_API,
|
apiUrl: process.env.APP_API
|
||||||
platformUrl: process.env.PLATFORM_URL
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -30,6 +30,7 @@
|
||||||
</q-tabs>
|
</q-tabs>
|
||||||
<div class="right-menu">
|
<div class="right-menu">
|
||||||
<q-tabs>
|
<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 @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-btn stretch flat icon="eva-question-mark-circle-outline">
|
||||||
<q-menu anchor="bottom left" auto-close>
|
<q-menu anchor="bottom left" auto-close>
|
||||||
|
@ -43,8 +44,6 @@
|
||||||
</q-menu>
|
</q-menu>
|
||||||
</q-btn>
|
</q-btn>
|
||||||
</q-tabs>
|
</q-tabs>
|
||||||
<platform-auth-state
|
|
||||||
v-if="pilotEnabled" />
|
|
||||||
</div>
|
</div>
|
||||||
</q-toolbar>
|
</q-toolbar>
|
||||||
</div>
|
</div>
|
||||||
|
@ -60,12 +59,10 @@
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import config from '../../../package'
|
import config from '../../../package'
|
||||||
import PlatformAuthState from '../platform/PlatformAuthState'
|
|
||||||
import { mapActions, mapGetters } from 'vuex'
|
import { mapActions, mapGetters } from 'vuex'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'NavBar',
|
name: 'NavBar',
|
||||||
components: { PlatformAuthState },
|
|
||||||
computed: {
|
computed: {
|
||||||
...mapGetters('core', { coreVersion: 'version' }),
|
...mapGetters('core', { coreVersion: 'version' }),
|
||||||
version () {
|
version () {
|
||||||
|
@ -74,9 +71,6 @@ export default {
|
||||||
? this.coreVersion.Version
|
? this.coreVersion.Version
|
||||||
: this.coreVersion.Version.substring(0, 7)
|
: this.coreVersion.Version.substring(0, 7)
|
||||||
},
|
},
|
||||||
pilotEnabled () {
|
|
||||||
return this.coreVersion.pilotEnabled
|
|
||||||
},
|
|
||||||
parsedVersion () {
|
parsedVersion () {
|
||||||
if (!this.version) {
|
if (!this.version) {
|
||||||
return 'master'
|
return 'master'
|
||||||
|
@ -159,6 +153,11 @@ export default {
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.btn-hub {
|
||||||
|
color: #0e204c;
|
||||||
|
background: #deea48;
|
||||||
|
}
|
||||||
|
|
||||||
.q-item {
|
.q-item {
|
||||||
padding: 0;
|
padding: 0;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,20 +0,0 @@
|
||||||
<template>
|
|
||||||
<q-btn color="accent" class="btn" @click="$emit('click', $event)" :bind="$attrs">{{label}}</q-btn>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
|
|
||||||
export default {
|
|
||||||
name: 'ConnectButton',
|
|
||||||
props: ['label']
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
.btn {
|
|
||||||
font-family: 'Nunito', 'Roboto', sans-serif;
|
|
||||||
font-weight: bold;
|
|
||||||
font-size: 14px;
|
|
||||||
text-transform: inherit;
|
|
||||||
}
|
|
||||||
</style>
|
|
|
@ -1,82 +0,0 @@
|
||||||
<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>
|
|
|
@ -1,112 +0,0 @@
|
||||||
<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 a new issue