diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml
index ec0c32edb..95e921ebf 100644
--- a/.github/workflows/build.yaml
+++ b/.github/workflows/build.yaml
@@ -20,9 +20,21 @@ jobs:
with:
fetch-depth: 0
+ - name: Setup node
+ uses: actions/setup-node@v4
+ with:
+ node-version-file: webui/.nvmrc
+ cache: yarn
+ cache-dependency-path: webui/yarn.lock
+
- name: Build webui
+ working-directory: ./webui
+ run: |
+ yarn install
+ yarn build
+
+ - name: Package webui
run: |
- make clean-webui generate-webui
tar czvf webui.tar.gz ./webui/static/
- name: Artifact webui
diff --git a/.github/workflows/experimental.yaml b/.github/workflows/experimental.yaml
index b20d31124..fa91bfda5 100644
--- a/.github/workflows/experimental.yaml
+++ b/.github/workflows/experimental.yaml
@@ -25,9 +25,18 @@ jobs:
with:
fetch-depth: 0
+ - name: Setup node
+ uses: actions/setup-node@v4
+ with:
+ node-version-file: webui/.nvmrc
+ cache: yarn
+ cache-dependency-path: webui/yarn.lock
+
- name: Build webui
+ working-directory: ./webui
run: |
- make clean-webui generate-webui
+ yarn install
+ yarn build
- name: Set up Go ${{ env.GO_VERSION }}
uses: actions/setup-go@v5
diff --git a/.github/workflows/test-integration.yaml b/.github/workflows/test-integration.yaml
index a8c6a4f64..d121c6df4 100644
--- a/.github/workflows/test-integration.yaml
+++ b/.github/workflows/test-integration.yaml
@@ -39,7 +39,7 @@ jobs:
fail-fast: true
matrix:
parallel: [12]
- index: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 , 11]
+ index: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]
steps:
- name: Check out code
diff --git a/.github/workflows/test-unit.yaml b/.github/workflows/test-unit.yaml
index 36721ba85..43ea42853 100644
--- a/.github/workflows/test-unit.yaml
+++ b/.github/workflows/test-unit.yaml
@@ -29,3 +29,24 @@ jobs:
- name: Tests
run: make test-unit
+
+ test-ui-unit:
+ runs-on: ubuntu-latest
+
+ steps:
+ - name: Check out code
+ uses: actions/checkout@v4
+ with:
+ fetch-depth: 0
+
+ - name: Set up Node.js ${{ env.NODE_VERSION }}
+ uses: actions/setup-node@v4
+ with:
+ node-version-file: webui/.nvmrc
+ cache: 'yarn'
+ cache-dependency-path: webui/yarn.lock
+
+ - name: UI unit tests
+ run: |
+ yarn --cwd webui install
+ yarn --cwd webui test:unit:ci
diff --git a/.github/workflows/validate.yaml b/.github/workflows/validate.yaml
index 2b249da47..641d5b7b9 100644
--- a/.github/workflows/validate.yaml
+++ b/.github/workflows/validate.yaml
@@ -8,7 +8,7 @@ on:
env:
GO_VERSION: '1.22'
GOLANGCI_LINT_VERSION: v1.59.0
- MISSSPELL_VERSION: v0.4.1
+ MISSSPELL_VERSION: v0.6.0
jobs:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index f4d550ccc..569a25c67 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,32 @@
+## [v3.0.2](https://github.com/traefik/traefik/tree/v3.0.2) (2024-06-10)
+[All Commits](https://github.com/traefik/traefik/compare/v3.0.1...v3.0.2)
+
+**Bug fixes:**
+- **[logs]** Bump OTel dependencies ([#10763](https://github.com/traefik/traefik/pull/10763) by [DrFaust92](https://github.com/DrFaust92))
+- **[logs]** Append to log file if it exists ([#10756](https://github.com/traefik/traefik/pull/10756) by [lbenguigui](https://github.com/lbenguigui))
+- **[metrics]** Fix service name label_replace in Grafana ([#10758](https://github.com/traefik/traefik/pull/10758) by [xdavidwu](https://github.com/xdavidwu))
+- **[middleware]** Forward the correct status code when compression is disabled within the Brotli handler ([#10780](https://github.com/traefik/traefik/pull/10780) by [rtribotte](https://github.com/rtribotte))
+- **[middleware]** Support Accept-Encoding header weights with Compress middleware ([#10777](https://github.com/traefik/traefik/pull/10777) by [ldez](https://github.com/ldez))
+
+**Documentation:**
+- Update v2 > v3 migration guide ([#10728](https://github.com/traefik/traefik/pull/10728) by [0anas01](https://github.com/0anas01))
+
+**Misc:**
+- Merge current v2.11 into v3.0 ([#10796](https://github.com/traefik/traefik/pull/10796) by [kevinpollet](https://github.com/kevinpollet))
+- Merge current v2.11 into v3.0 ([#10781](https://github.com/traefik/traefik/pull/10781) by [ldez](https://github.com/ldez))
+
+## [v2.11.4](https://github.com/traefik/traefik/tree/v2.11.4) (2024-06-10)
+[All Commits](https://github.com/traefik/traefik/compare/v2.11.3...v2.11.4)
+
+**Bug fixes:**
+- **[acme]** Update go-acme/lego to v4.17.3 ([#10768](https://github.com/traefik/traefik/pull/10768) by [ldez](https://github.com/ldez))
+
+**Documentation:**
+- **[acme]** Fix .com and .org domain examples ([#10635](https://github.com/traefik/traefik/pull/10635) by [rptaylor](https://github.com/rptaylor))
+- **[middleware]** Add a note about the Ratelimit middleware's behavior when the sourceCriterion header is missing ([#10752](https://github.com/traefik/traefik/pull/10752) by [dgutzmann](https://github.com/dgutzmann))
+- Add user guides link to getting started ([#10785](https://github.com/traefik/traefik/pull/10785) by [norlinhenrik](https://github.com/norlinhenrik))
+- Remove helm default repo warning as repo has been long deprecated ([#10772](https://github.com/traefik/traefik/pull/10772) by [corneliusroemer](https://github.com/corneliusroemer))
+
## [v3.0.1](https://github.com/traefik/traefik/tree/v3.0.1) (2024-05-22)
[All Commits](https://github.com/traefik/traefik/compare/v3.0.0...v3.0.1)
diff --git a/Makefile b/Makefile
index b006ce68e..8ba03926d 100644
--- a/Makefile
+++ b/Makefile
@@ -88,7 +88,7 @@ crossbinary-default: generate generate-webui
.PHONY: test
#? test: Run the unit and integration tests
-test: test-unit test-integration
+test: test-ui-unit test-unit test-integration
.PHONY: test-unit
#? test-unit: Run the unit tests
@@ -105,6 +105,13 @@ test-integration: binary
test-gateway-api-conformance: build-image-dirty
GOOS=$(GOOS) GOARCH=$(GOARCH) go test ./integration -v -test.run K8sConformanceSuite -k8sConformance $(TESTFLAGS)
+.PHONY: test-ui-unit
+#? test-ui-unit: Run the unit tests for the webui
+test-ui-unit:
+ $(MAKE) build-webui-image
+ docker run --rm -v "$(PWD)/webui/static":'/src/webui/static' traefik-webui yarn --cwd webui install
+ docker run --rm -v "$(PWD)/webui/static":'/src/webui/static' traefik-webui yarn --cwd webui test:unit:ci
+
.PHONY: pull-images
#? pull-images: Pull all Docker images to avoid timeout during integration tests
pull-images:
diff --git a/docs/check.Dockerfile b/docs/check.Dockerfile
index d33a46af3..824a97081 100644
--- a/docs/check.Dockerfile
+++ b/docs/check.Dockerfile
@@ -1,4 +1,4 @@
-FROM alpine:3.18 as alpine
+FROM alpine:3.20
RUN apk --no-cache --no-progress add \
build-base \
diff --git a/docs/content/getting-started/quick-start.md b/docs/content/getting-started/quick-start.md
index 122ff530d..67a74036e 100644
--- a/docs/content/getting-started/quick-start.md
+++ b/docs/content/getting-started/quick-start.md
@@ -119,6 +119,6 @@ IP: 172.27.0.4
!!! question "Where to Go Next?"
- Now that you have a basic understanding of how Traefik can automatically create the routes to your services and load balance them, it is time to dive into [the documentation](/ "Link to the docs landing page") and let Traefik work for you!
+ Now that you have a basic understanding of how Traefik can automatically create the routes to your services and load balance them, it is time to dive into [the user guides](../../user-guides/docker-compose/basic-example/ "Link to the user guides") and [the documentation](/ "Link to the docs landing page") and let Traefik work for you!
{!traefik-for-business-applications.md!}
diff --git a/docs/docs.Dockerfile b/docs/docs.Dockerfile
index ee10a5302..2ed4d0528 100644
--- a/docs/docs.Dockerfile
+++ b/docs/docs.Dockerfile
@@ -1,10 +1,12 @@
-FROM alpine:3.14
+FROM alpine:3.20
-ENV PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/root/.local/bin
+ENV PATH="${PATH}:/venv/bin"
COPY requirements.txt /mkdocs/
WORKDIR /mkdocs
VOLUME /mkdocs
RUN apk --no-cache --no-progress add py3-pip gcc musl-dev python3-dev \
- && pip3 install --user -r requirements.txt
+ && python3 -m venv /venv \
+ && source /venv/bin/activate \
+ && pip3 install -r requirements.txt
diff --git a/docs/requirements.txt b/docs/requirements.txt
index 8274f4d68..68126a411 100644
--- a/docs/requirements.txt
+++ b/docs/requirements.txt
@@ -1,45 +1,23 @@
-mkdocs==1.2.2
+markdown-include==0.5.1
+mkdocs==1.2.4
+mkdocs-exclude==1.0.2
mkdocs-traefiklabs>=100.0.7
-appdirs==1.4.4
-CacheControl==0.12.6
-certifi==2020.12.5
-chardet==4.0.0
-click==8.0.4
-colorama==0.4.4
-contextlib2==0.6.0
-distlib==0.3.1
-distro==1.5.0
-ghp-import==2.0.2
-html5lib==1.1
-idna==3.2
-importlib-metadata==4.11.3
-Jinja2==3.0.0
-lockfile==0.12.2
+click==8.1.7
+colorama==0.4.6
+ghp-import==2.1.0
+importlib_metadata==7.1.0
+Jinja2==3.1.3
Markdown==3.3.6
-markdown-include==0.5.1
-MarkupSafe==2.1.1
+MarkupSafe==2.1.5
mergedeep==1.3.4
-mkdocs-bootswatch==1.0
-mkdocs-exclude==1.0.2
-mkdocs-material-extensions==1.0.3
-msgpack==1.0.2
-ordered-set==4.0.2
-packaging==20.9
-pep517==0.10.0
-progress==1.5
-Pygments==2.11.2
+mkdocs-material-extensions==1.3.1
+packaging==24.0
+Pygments==2.18.0
pymdown-extensions==7.0
-pyparsing==2.4.7
-python-dateutil==2.8.2
+python-dateutil==2.9.0.post0
PyYAML==6.0.1
-pyyaml-env-tag==0.1
-requests==2.25.1
-retrying==1.3.3
-six==1.15.0
-toml==0.10.2
-urllib3==1.26.5
-watchdog==2.1.7
-webencodings==0.5.1
-zipp==3.7.0
-
+pyyaml_env_tag==0.1
+six==1.16.0
+watchdog==4.0.0
+zipp==3.18.1
diff --git a/docs/runtime.txt b/docs/runtime.txt
deleted file mode 100644
index 475ba515c..000000000
--- a/docs/runtime.txt
+++ /dev/null
@@ -1 +0,0 @@
-3.7
diff --git a/script/gcg/traefik-bugfix.toml b/script/gcg/traefik-bugfix.toml
index b6c09ed2b..f88cb01d7 100644
--- a/script/gcg/traefik-bugfix.toml
+++ b/script/gcg/traefik-bugfix.toml
@@ -4,11 +4,11 @@ RepositoryName = "traefik"
OutputType = "file"
FileName = "traefik_changelog.md"
-# example new bugfix v3.0.1
+# example new bugfix v3.0.2
CurrentRef = "v3.0"
-PreviousRef = "v3.0.0"
+PreviousRef = "v3.0.1"
BaseBranch = "v3.0"
-FutureCurrentRefName = "v3.0.1"
+FutureCurrentRefName = "v3.0.2"
ThresholdPreviousRef = 10
ThresholdCurrentRef = 10
diff --git a/webui/Dockerfile b/webui/Dockerfile
index 3939d56b0..df6ca6167 100644
--- a/webui/Dockerfile
+++ b/webui/Dockerfile
@@ -1,4 +1,4 @@
-FROM node:20.11
+FROM node:20.14
# Current Active LTS release according to (https://nodejs.org/en/about/releases/)
ENV WEBUI_DIR /src/webui
diff --git a/webui/package.json b/webui/package.json
index 3e367b9fd..23cbbba7c 100644
--- a/webui/package.json
+++ b/webui/package.json
@@ -8,10 +8,10 @@
"scripts": {
"transfer": "node dev/scripts/transfer.js",
"lint": "eslint --ext .js,.vue src",
- "dev": "export APP_ENV='development' && quasar dev",
+ "dev": "APP_ENV=development quasar dev",
"build-quasar": "quasar build",
- "build-staging": "export NODE_ENV='production' && export APP_ENV='development' && yarn build-quasar",
- "build": "export NODE_ENV='production' && export APP_ENV='production' && yarn build-quasar && yarn transfer spa",
+ "build-staging": "NODE_ENV=production APP_ENV=development yarn build-quasar",
+ "build": "NODE_ENV=production APP_ENV=production yarn build-quasar && yarn transfer spa",
"build:nc": "yarn build",
"test": "echo \"See package.json => scripts for available tests.\" && exit 0",
"test:unit": "vitest",
@@ -56,6 +56,7 @@
"engines": {
"node": "^20 || ^18 || ^16",
"npm": ">= 6.13.4",
- "yarn": ">= 1.21.1"
- }
+ "yarn": ">= 1.22.22"
+ },
+ "packageManager": "yarn@1.22.22"
}
diff --git a/webui/readme.md b/webui/readme.md
index e621e4080..812cbdf5a 100644
--- a/webui/readme.md
+++ b/webui/readme.md
@@ -20,7 +20,7 @@ make clean-webui generate-webui # Generate static contents in `webui/static/` fo
## How to build (only for frontend developer)
-- prerequisite: [Node 20.11+](https://nodejs.org) [Yarn 1.22.19](https://yarnpkg.com/)
+- prerequisite: [Node 20.14+](https://nodejs.org) [Yarn 1.22.22](https://yarnpkg.com/)
- Go to the `webui/` directory
diff --git a/webui/src/components/_commons/AvatarState.vue b/webui/src/components/_commons/AvatarState.vue
index e74e73104..1b99f976e 100644
--- a/webui/src/components/_commons/AvatarState.vue
+++ b/webui/src/components/_commons/AvatarState.vue
@@ -24,7 +24,7 @@ import { defineComponent } from 'vue'
export default defineComponent({
name: 'AvatarState',
props: {
- state: String
+ state: { type: String, default: undefined, required: false }
}
})
diff --git a/webui/src/components/_commons/MainTable.vue b/webui/src/components/_commons/MainTable.vue
index 1907215eb..468f21d31 100644
--- a/webui/src/components/_commons/MainTable.vue
+++ b/webui/src/components/_commons/MainTable.vue
@@ -128,13 +128,14 @@ export default defineComponent({
QPageScroller
},
props: {
- data: Object,
+ data: { type: Object, default: undefined, required: false },
columns: Array[Object],
loading: Boolean,
- onLoadMore: Function,
+ onLoadMore: { type: Function, default: undefined, required: false },
endReached: Boolean,
- onRowClick: Function
+ onRowClick: { type: Function, default: undefined, required: false }
},
+ emits: ['update:currentSort', 'update:currentSortDir'],
data () {
return {
currentSort: 'name',
diff --git a/webui/src/components/_commons/PanelHealthCheck.vue b/webui/src/components/_commons/PanelHealthCheck.vue
index 7513cded0..0d1689d0d 100644
--- a/webui/src/components/_commons/PanelHealthCheck.vue
+++ b/webui/src/components/_commons/PanelHealthCheck.vue
@@ -137,7 +137,7 @@ export default {
filters: {
},
props: {
- data: Object,
+ data: { type: Object, default: undefined, required: false },
dense: Boolean
},
computed: {
diff --git a/webui/src/components/_commons/PanelMiddlewares.vue b/webui/src/components/_commons/PanelMiddlewares.vue
index 4921a0b54..78ce95fb2 100644
--- a/webui/src/components/_commons/PanelMiddlewares.vue
+++ b/webui/src/components/_commons/PanelMiddlewares.vue
@@ -75,8 +75,8 @@
ERRORS
{{ errorMsg }}
diff --git a/webui/src/components/_commons/PanelMirroringServices.vue b/webui/src/components/_commons/PanelMirroringServices.vue
index 3a3fa3319..a526f3f0f 100644
--- a/webui/src/components/_commons/PanelMirroringServices.vue
+++ b/webui/src/components/_commons/PanelMirroringServices.vue
@@ -70,7 +70,7 @@
export default {
name: 'PanelMirroringServices',
props: {
- data: Object,
+ data: { type: Object, default: undefined, required: false },
dense: Boolean
},
computed: {
diff --git a/webui/src/components/_commons/PanelRouterDetails.vue b/webui/src/components/_commons/PanelRouterDetails.vue
index 7a17fe5de..a605cd393 100644
--- a/webui/src/components/_commons/PanelRouterDetails.vue
+++ b/webui/src/components/_commons/PanelRouterDetails.vue
@@ -146,8 +146,8 @@ export default defineComponent({
AvatarState
},
props: {
- data: Object,
- protocol: String
+ data: { type: Object, default: undefined, required: false },
+ protocol: { type: String, default: undefined, required: false }
},
computed: {
getProviderLogoPath () {
diff --git a/webui/src/components/_commons/PanelServers.vue b/webui/src/components/_commons/PanelServers.vue
index 5b07eda44..5c3e8f54b 100644
--- a/webui/src/components/_commons/PanelServers.vue
+++ b/webui/src/components/_commons/PanelServers.vue
@@ -102,7 +102,7 @@ export default defineComponent({
AvatarState
},
props: {
- data: Object,
+ data: { type: Object, default: undefined, required: false },
dense: Boolean,
hasStatus: Boolean
},
diff --git a/webui/src/components/_commons/PanelServiceDetails.vue b/webui/src/components/_commons/PanelServiceDetails.vue
index 98d26d19e..0eddba463 100644
--- a/webui/src/components/_commons/PanelServiceDetails.vue
+++ b/webui/src/components/_commons/PanelServiceDetails.vue
@@ -155,7 +155,7 @@ export default defineComponent({
StickyServiceDetails
},
props: {
- data: Object,
+ data: { type: Object, default: undefined, required: false },
dense: Boolean
},
computed: {
diff --git a/webui/src/components/_commons/PanelTLS.vue b/webui/src/components/_commons/PanelTLS.vue
index eb7af91a8..905d8878f 100644
--- a/webui/src/components/_commons/PanelTLS.vue
+++ b/webui/src/components/_commons/PanelTLS.vue
@@ -77,12 +77,12 @@
{{ domain.main }}
- {{ domain }}
+ {{ sanDomain }}
@@ -130,8 +130,8 @@ export default defineComponent({
BooleanState
},
props: {
- data: Object,
- protocol: String
+ data: { type: Object, default: undefined, required: false },
+ protocol: { type: String, default: undefined, required: false }
}
})
diff --git a/webui/src/components/_commons/PanelWeightedServices.vue b/webui/src/components/_commons/PanelWeightedServices.vue
index 178bc8d4b..b8eeaa72a 100644
--- a/webui/src/components/_commons/PanelWeightedServices.vue
+++ b/webui/src/components/_commons/PanelWeightedServices.vue
@@ -66,7 +66,7 @@ export default defineComponent({
name: 'PanelWeightedServices',
components: {},
props: {
- data: Object,
+ data: { type: Object, default: undefined, required: false },
dense: Boolean
},
computed: {
diff --git a/webui/src/components/_commons/ProviderIcon.vue b/webui/src/components/_commons/ProviderIcon.vue
index 9be76c746..4511b696b 100644
--- a/webui/src/components/_commons/ProviderIcon.vue
+++ b/webui/src/components/_commons/ProviderIcon.vue
@@ -9,7 +9,7 @@ import { defineComponent } from 'vue'
export default defineComponent({
props: {
- name: String
+ name: { type: String, default: undefined, required: false }
},
computed: {
getLogoPath () {
diff --git a/webui/src/components/_commons/SidePanel.vue b/webui/src/components/_commons/SidePanel.vue
index b13979739..f20d5bfe6 100644
--- a/webui/src/components/_commons/SidePanel.vue
+++ b/webui/src/components/_commons/SidePanel.vue
@@ -23,6 +23,7 @@ export default defineComponent({
props: {
isOpen: Boolean
},
+ emits: ['onClose'],
methods: {
close () {
this.$emit('onClose')
diff --git a/webui/src/components/_commons/SkeletonBox.vue b/webui/src/components/_commons/SkeletonBox.vue
index de6f3d4dd..f51612923 100644
--- a/webui/src/components/_commons/SkeletonBox.vue
+++ b/webui/src/components/_commons/SkeletonBox.vue
@@ -10,20 +10,20 @@ export default {
name: 'SkeletonBox',
props: {
maxWidth: {
- default: 100,
- type: Number
+ type: Number,
+ default: 100
},
minWidth: {
- default: 80,
- type: Number
+ type: Number,
+ default: 80
},
height: {
- default: '2em',
- type: String
+ type: String,
+ default: '2em'
},
width: {
- default: null,
- type: String
+ type: String,
+ default: null
}
},
computed: {
diff --git a/webui/src/components/_commons/StickyServiceDetails.vue b/webui/src/components/_commons/StickyServiceDetails.vue
index f2e53f4a9..de81f1119 100644
--- a/webui/src/components/_commons/StickyServiceDetails.vue
+++ b/webui/src/components/_commons/StickyServiceDetails.vue
@@ -55,7 +55,7 @@ export default defineComponent({
BooleanState
},
props: {
- sticky: Object,
+ sticky: { type: Object, default: undefined, required: false },
dense: Boolean
}
})
diff --git a/webui/src/components/_commons/ToolBarTable.vue b/webui/src/components/_commons/ToolBarTable.vue
index 03d4b6a52..65a6e3d6b 100644
--- a/webui/src/components/_commons/ToolBarTable.vue
+++ b/webui/src/components/_commons/ToolBarTable.vue
@@ -42,9 +42,10 @@ import Helps from '../../_helpers/Helps'
export default defineComponent({
name: 'ToolBarTable',
props: {
- status: String,
- filter: String
+ status: { type: String, default: undefined, required: false },
+ filter: { type: String, default: undefined, required: false }
},
+ emits: ['update:status', 'update:filter'],
computed: {
getStatus: {
get () {
diff --git a/webui/src/components/dashboard/PanelChart.vue b/webui/src/components/dashboard/PanelChart.vue
index 411de01e6..3f2bc0a8f 100644
--- a/webui/src/components/dashboard/PanelChart.vue
+++ b/webui/src/components/dashboard/PanelChart.vue
@@ -118,9 +118,9 @@ export default defineComponent({
AvatarState
},
props: {
- name: String,
- data: Object,
- type: String
+ name: { type: String, default: undefined, required: false },
+ data: { type: Object, default: undefined, required: false },
+ type: { type: String, default: undefined, required: false }
},
data () {
return {
diff --git a/webui/src/components/dashboard/PanelEntry.vue b/webui/src/components/dashboard/PanelEntry.vue
index 969c321b3..3077ce157 100644
--- a/webui/src/components/dashboard/PanelEntry.vue
+++ b/webui/src/components/dashboard/PanelEntry.vue
@@ -28,11 +28,11 @@ import { defineComponent } from 'vue'
export default defineComponent({
name: 'PanelEntry',
props: {
- address: String,
- name: String,
- type: String,
+ address: { type: String, default: undefined, required: false },
+ name: { type: String, default: undefined, required: false },
+ type: { type: String, default: undefined, required: false },
focus: Boolean,
- exSize: Number
+ exSize: { type: Number, default: undefined, required: false }
}
})
diff --git a/webui/src/components/dashboard/PanelFeature.vue b/webui/src/components/dashboard/PanelFeature.vue
index 9edff71c8..1b3ee5b64 100644
--- a/webui/src/components/dashboard/PanelFeature.vue
+++ b/webui/src/components/dashboard/PanelFeature.vue
@@ -28,7 +28,10 @@