From 45183288546eac5dc80a6415ecaa04872f3eab87 Mon Sep 17 00:00:00 2001 From: Josh Kim Date: Fri, 19 Jun 2026 10:49:16 -0700 Subject: [PATCH 1/2] frontend: Fix /auth proxy path to use trailing slash prefix /auth is a React route that the Go backend's /oidc-callback handler redirects users to after a successful OIDC login. Without the trailing slash in the dev proxy config, requests to /auth (and sub-paths like /auth/...) were being forwarded to the backend instead of being served by the frontend, resulting in 404s after the OIDC callback. Also adds the Apache license header to rsbuild.config.ts and normalises minor style inconsistencies between the two config files. --- frontend/rsbuild.config.ts | 33 ++++++++++++++++++++++++++------- frontend/vite.config.ts | 24 ++++++++++++------------ 2 files changed, 38 insertions(+), 19 deletions(-) diff --git a/frontend/rsbuild.config.ts b/frontend/rsbuild.config.ts index a93b2770be4..b208535edf8 100644 --- a/frontend/rsbuild.config.ts +++ b/frontend/rsbuild.config.ts @@ -1,15 +1,34 @@ +/* + * Copyright 2025 The Kubernetes Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + import { defineConfig } from '@rsbuild/core'; +import { pluginNodePolyfill } from '@rsbuild/plugin-node-polyfill'; import { pluginReact } from '@rsbuild/plugin-react'; import { pluginSvgr } from '@rsbuild/plugin-svgr'; -import { pluginNodePolyfill } from '@rsbuild/plugin-node-polyfill'; // Dynamically inject REACT_APP_ environment variables const reactAppEnvVars = Object.entries(process.env) .filter(([key, value]) => key.startsWith('REACT_APP_') && value !== undefined) - .reduce((env, [key, value]) => { - env[`import.meta.env.${key}`] = JSON.stringify(value); - return env; - }, { 'import.meta.env': '{}' }); + .reduce( + (env, [key, value]) => { + env[`import.meta.env.${key}`] = JSON.stringify(value); + return env; + }, + { 'import.meta.env': '{}' } + ); // Use environment variable for backend port, defaulting to 4466 const backendPort = process.env.HEADLAMP_PORT || '4466'; @@ -44,7 +63,7 @@ export default defineConfig({ '/clusters', '/plugins', '/config', - '/auth', + '/auth/', '/oidc', '/oidc-callback', '/externalproxy', @@ -138,7 +157,7 @@ export default defineConfig({ pluginReact({ swcReactOptions: { throwIfNamespace: false, - } + }, }), pluginSvgr({ svgrOptions: { diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts index ad879207039..a9a51e72eef 100644 --- a/frontend/vite.config.ts +++ b/frontend/vite.config.ts @@ -17,19 +17,19 @@ import react from '@vitejs/plugin-react'; import { defineConfig } from 'vite'; import { nodePolyfills } from 'vite-plugin-node-polyfills'; -import svgr from 'vite-plugin-svgr'; import { viteStaticCopy } from "vite-plugin-static-copy"; +import svgr from 'vite-plugin-svgr'; -// Use environment variable for backend port, defaulting to 4466 -const backendPort = process.env.HEADLAMP_PORT || '4466'; -const backendTarget = `http://localhost:${backendPort}`; -const underTest = process.env.UNDER_TEST === 'true' || process.env.VITEST === 'true'; +// Use environment variable for backend port, defaulting to 4466 +const backendPort = process.env.HEADLAMP_PORT || '4466'; +const backendTarget = `http://localhost:${backendPort}`; +const underTest = process.env.UNDER_TEST === 'true' || process.env.VITEST === 'true'; export default defineConfig({ - define: { - global: 'globalThis', - 'import.meta.env.UNDER_TEST': JSON.stringify(underTest), - }, + define: { + global: 'globalThis', + 'import.meta.env.UNDER_TEST': JSON.stringify(underTest), + }, envPrefix: 'REACT_APP_', base: process.env.PUBLIC_URL, server: { @@ -51,7 +51,7 @@ export default defineConfig({ target: backendTarget, changeOrigin: true, }, - '/auth': { + '/auth/': { target: backendTarget, changeOrigin: true, }, @@ -117,8 +117,8 @@ export default defineConfig({ viteStaticCopy({ targets: [ { - src: "node_modules/monaco-editor/min/vs", - dest: "assets", // copies to assets/vs + src: 'node_modules/monaco-editor/min/vs', + dest: 'assets', // copies to assets/vs }, ], }), From 1d55ee2b4c03361842cd6427ea6adfe5fde0f672 Mon Sep 17 00:00:00 2001 From: Josh Kim Date: Fri, 19 Jun 2026 10:49:18 -0700 Subject: [PATCH 2/2] frontend: Enforce LF line endings for TS/JS/JSON files Add .gitattributes rules so Git normalises *.ts, *.tsx, *.js, *.jsx, and *.json to LF on checkout, matching the existing *.go rule. Also converts vite.config.ts from CRLF to LF to align with the new policy. --- .gitattributes | 7 +- frontend/vite.config.ts | 316 ++++++++++++++++++++-------------------- 2 files changed, 164 insertions(+), 159 deletions(-) diff --git a/.gitattributes b/.gitattributes index d207b1802b2..0d6d68cde01 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1 +1,6 @@ -*.go text eol=lf +*.go text eol=lf +*.ts text eol=lf +*.tsx text eol=lf +*.js text eol=lf +*.jsx text eol=lf +*.json text eol=lf diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts index a9a51e72eef..5a4b647d137 100644 --- a/frontend/vite.config.ts +++ b/frontend/vite.config.ts @@ -1,158 +1,158 @@ -/* - * Copyright 2025 The Kubernetes Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import react from '@vitejs/plugin-react'; -import { defineConfig } from 'vite'; -import { nodePolyfills } from 'vite-plugin-node-polyfills'; -import { viteStaticCopy } from "vite-plugin-static-copy"; -import svgr from 'vite-plugin-svgr'; - -// Use environment variable for backend port, defaulting to 4466 -const backendPort = process.env.HEADLAMP_PORT || '4466'; -const backendTarget = `http://localhost:${backendPort}`; -const underTest = process.env.UNDER_TEST === 'true' || process.env.VITEST === 'true'; - -export default defineConfig({ - define: { - global: 'globalThis', - 'import.meta.env.UNDER_TEST': JSON.stringify(underTest), - }, - envPrefix: 'REACT_APP_', - base: process.env.PUBLIC_URL, - server: { - port: 3000, - proxy: { - '/api': { - target: backendTarget, - changeOrigin: true, - }, - '/clusters': { - target: backendTarget, - changeOrigin: true, - }, - '/plugins': { - target: backendTarget, - changeOrigin: true, - }, - '/config': { - target: backendTarget, - changeOrigin: true, - }, - '/auth/': { - target: backendTarget, - changeOrigin: true, - }, - '/oidc': { - target: backendTarget, - changeOrigin: true, - }, - '/oidc-callback': { - target: backendTarget, - changeOrigin: true, - }, - '/wsMultiplexer': { - target: backendTarget, - changeOrigin: true, - ws: true, - }, - '/externalproxy': { - target: backendTarget, - changeOrigin: true, - }, - '/drain-node': { - target: backendTarget, - changeOrigin: true, - }, - '/drain-node-status': { - target: backendTarget, - changeOrigin: true, - }, - '/parseKubeConfig': { - target: backendTarget, - changeOrigin: true, - }, - '/cluster': { - target: backendTarget, - changeOrigin: true, - }, - '/metrics': { - target: backendTarget, - changeOrigin: true, - }, - }, - cors: true, - }, - plugins: [ - svgr({ - svgrOptions: { - prettier: false, - svgo: false, - svgoConfig: { - plugins: [{ removeViewBox: false }], - }, - titleProp: true, - ref: true, - }, - }), - react(), - nodePolyfills({ - include: ['process', 'buffer', 'stream'], - }), - // Make sure we copy the minified monaco-editor source into the static folder - // since it's loaded dynamically and not bundled via ESM. We do it this way - // to support setting the localization language - viteStaticCopy({ - targets: [ - { - src: 'node_modules/monaco-editor/min/vs', - dest: 'assets', // copies to assets/vs - }, - ], - }), - ], - build: { - outDir: 'build', - commonjsOptions: { - transformMixedEsModules: true, - }, - rollupOptions: { - // Exclude @axe-core from production bundle - external: ['@axe-core/react'], - output: { - manualChunks(id: string) { - // Build smaller chunks for @mui, lodash, xterm, recharts - if (id.includes('node_modules')) { - if (id.includes('lodash')) { - return 'vendor-lodash'; - } - - if (id.includes('@mui/material')) { - return 'vendor-mui'; - } - - if (id.includes('xterm')) { - return 'vendor-xterm'; - } - - if (id.includes('recharts')) { - return 'vendor-recharts'; - } - } - }, - }, - }, - }, -}); +/* + * Copyright 2025 The Kubernetes Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import react from '@vitejs/plugin-react'; +import { defineConfig } from 'vite'; +import { nodePolyfills } from 'vite-plugin-node-polyfills'; +import { viteStaticCopy } from "vite-plugin-static-copy"; +import svgr from 'vite-plugin-svgr'; + +// Use environment variable for backend port, defaulting to 4466 +const backendPort = process.env.HEADLAMP_PORT || '4466'; +const backendTarget = `http://localhost:${backendPort}`; +const underTest = process.env.UNDER_TEST === 'true' || process.env.VITEST === 'true'; + +export default defineConfig({ + define: { + global: 'globalThis', + 'import.meta.env.UNDER_TEST': JSON.stringify(underTest), + }, + envPrefix: 'REACT_APP_', + base: process.env.PUBLIC_URL, + server: { + port: 3000, + proxy: { + '/api': { + target: backendTarget, + changeOrigin: true, + }, + '/clusters': { + target: backendTarget, + changeOrigin: true, + }, + '/plugins': { + target: backendTarget, + changeOrigin: true, + }, + '/config': { + target: backendTarget, + changeOrigin: true, + }, + '/auth/': { + target: backendTarget, + changeOrigin: true, + }, + '/oidc': { + target: backendTarget, + changeOrigin: true, + }, + '/oidc-callback': { + target: backendTarget, + changeOrigin: true, + }, + '/wsMultiplexer': { + target: backendTarget, + changeOrigin: true, + ws: true, + }, + '/externalproxy': { + target: backendTarget, + changeOrigin: true, + }, + '/drain-node': { + target: backendTarget, + changeOrigin: true, + }, + '/drain-node-status': { + target: backendTarget, + changeOrigin: true, + }, + '/parseKubeConfig': { + target: backendTarget, + changeOrigin: true, + }, + '/cluster': { + target: backendTarget, + changeOrigin: true, + }, + '/metrics': { + target: backendTarget, + changeOrigin: true, + }, + }, + cors: true, + }, + plugins: [ + svgr({ + svgrOptions: { + prettier: false, + svgo: false, + svgoConfig: { + plugins: [{ removeViewBox: false }], + }, + titleProp: true, + ref: true, + }, + }), + react(), + nodePolyfills({ + include: ['process', 'buffer', 'stream'], + }), + // Make sure we copy the minified monaco-editor source into the static folder + // since it's loaded dynamically and not bundled via ESM. We do it this way + // to support setting the localization language + viteStaticCopy({ + targets: [ + { + src: 'node_modules/monaco-editor/min/vs', + dest: 'assets', // copies to assets/vs + }, + ], + }), + ], + build: { + outDir: 'build', + commonjsOptions: { + transformMixedEsModules: true, + }, + rollupOptions: { + // Exclude @axe-core from production bundle + external: ['@axe-core/react'], + output: { + manualChunks(id: string) { + // Build smaller chunks for @mui, lodash, xterm, recharts + if (id.includes('node_modules')) { + if (id.includes('lodash')) { + return 'vendor-lodash'; + } + + if (id.includes('@mui/material')) { + return 'vendor-mui'; + } + + if (id.includes('xterm')) { + return 'vendor-xterm'; + } + + if (id.includes('recharts')) { + return 'vendor-recharts'; + } + } + }, + }, + }, + }, +});