This is an automated email from the ASF dual-hosted git repository.

jscheffl pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/airflow.git


The following commit(s) were added to refs/heads/main by this push:
     new 44381b6282b Fix Edge3 provider navigation with webserver base_url 
configuration (#56189)
44381b6282b is described below

commit 44381b6282b351fce16c05317d7f2ad89ab7a782
Author: Dheeraj Turaga <[email protected]>
AuthorDate: Wed Oct 8 11:04:31 2025 -0500

    Fix Edge3 provider navigation with webserver base_url configuration (#56189)
    
    * Fix Edge3 provider navigation with webserver base_url configuration
    
      The Edge3 provider's BrowserRouter was not respecting the Airflow
      webserver.base_url configuration, causing navigation links to generate
      absolute paths from root instead of properly prefixed paths. This
      resulted in 404 errors when Airflow is deployed with a base URL prefix
      (e.g., my_company.com/airflow).
    
      **Problem:**
      - Edge3 provider pages were navigating to /plugin/edge_worker instead
        of /airflow/plugin/edge_worker
      - This broke navigation in deployments where nginx redirects traffic
        from my_company.com/airflow to localhost:8080
      - The issue was in JobsPage.tsx:94 where RouterLink generated links
        without the base URL prefix
    
    * Fix: Added _get_base_url_path() helper function and updated all URLs to 
use it
    
    * Fix Edge3 provider frontend API calls with webserver base_url 
configuration
    
      The Edge3 provider's frontend was not respecting Airflow's webserver
      base_url configuration, causing API calls to fail when the webserver
      was configured with a subpath (e.g., localhost:8080/airflow).
    
      The OpenAPI-generated client was using an empty BASE configuration,
      resulting in API calls to /edge_worker/ui/jobs instead of
      /airflow/edge_worker/ui/jobs when base_url was set.
    
      This fix initializes OpenAPI.BASE from the HTML <base> tag, following
      the same pattern used in Airflow core, ensuring all API calls are
      correctly prefixed with the configured base URL path
    
    * Update 
providers/edge3/src/airflow/providers/edge3/plugins/edge_executor_plugin.py
    
    Co-authored-by: Jens Scheffler <[email protected]>
    
    ---------
    
    Co-authored-by: Jens Scheffler <[email protected]>
---
 .../edge3/plugins/edge_executor_plugin.py          | 36 ++++++++++++++++------
 .../providers/edge3/plugins/www/dist/main.umd.cjs  | 36 +++++++++++-----------
 .../edge3/plugins/www/src/layouts/EdgeLayout.tsx   |  6 +++-
 .../providers/edge3/plugins/www/src/main.tsx       |  6 ++++
 providers/edge3/www-hash.txt                       |  2 +-
 5 files changed, 56 insertions(+), 30 deletions(-)

diff --git 
a/providers/edge3/src/airflow/providers/edge3/plugins/edge_executor_plugin.py 
b/providers/edge3/src/airflow/providers/edge3/plugins/edge_executor_plugin.py
index 6c8d5ebeda0..cd24e3e68dd 100644
--- 
a/providers/edge3/src/airflow/providers/edge3/plugins/edge_executor_plugin.py
+++ 
b/providers/edge3/src/airflow/providers/edge3/plugins/edge_executor_plugin.py
@@ -227,6 +227,22 @@ else:
     RUNNING_ON_APISERVER = "gunicorn" in sys.argv[0] and "airflow-webserver" 
in sys.argv
 
 
+def _get_base_url_path(path: str) -> str:
+    """Construct URL path with webserver base_url prefix."""
+    base_url = conf.get("api", "base_url", fallback="/")
+    # Extract pathname from base_url (handles both full URLs and path-only)
+    if base_url.startswith(("http://";, "https://";)):
+        from urllib.parse import urlparse
+
+        base_path = urlparse(base_url).path
+    else:
+        base_path = base_url
+
+    # Normalize paths: remove trailing slash from base, ensure leading slash 
on path
+    base_path = base_path.rstrip("/")
+    return base_path + path
+
+
 class EdgeExecutorPlugin(AirflowPlugin):
     """EdgeExecutor Plugin - provides API endpoints for Edge Workers in 
Webserver."""
 
@@ -237,30 +253,30 @@ class EdgeExecutorPlugin(AirflowPlugin):
             react_apps = [
                 {
                     "name": "Edge Worker",
-                    "bundle_url": "/edge_worker/static/main.umd.cjs",
+                    "bundle_url": 
_get_base_url_path("/edge_worker/static/main.umd.cjs"),
                     "destination": "nav",
                     "url_route": "edge_worker",
                     "category": "admin",
-                    "icon": "/edge_worker/res/cloud-computer.svg",
-                    "icon_dark_mode": 
"/edge_worker/res/cloud-computer-dark.svg",
+                    "icon": 
_get_base_url_path("/edge_worker/res/cloud-computer.svg"),
+                    "icon_dark_mode": 
_get_base_url_path("/edge_worker/res/cloud-computer-dark.svg"),
                 },
                 {
                     "name": "Edge Worker Jobs",
-                    "bundle_url": "/edge_worker/static/main.umd.cjs",
+                    "bundle_url": 
_get_base_url_path("/edge_worker/static/main.umd.cjs"),
                     "url_route": "edge_jobs",
                     "category": "admin",
-                    "icon": "/edge_worker/res/cloud-computer.svg",
-                    "icon_dark_mode": 
"/edge_worker/res/cloud-computer-dark.svg",
+                    "icon": 
_get_base_url_path("/edge_worker/res/cloud-computer.svg"),
+                    "icon_dark_mode": 
_get_base_url_path("/edge_worker/res/cloud-computer-dark.svg"),
                 },
             ]
             external_views = [
                 {
                     "name": "Edge Worker API docs",
-                    "href": "/edge_worker/docs",
+                    "href": _get_base_url_path("/edge_worker/docs"),
                     "destination": "nav",
                     "category": "docs",
-                    "icon": "/edge_worker/res/cloud-computer.svg",
-                    "icon_dark_mode": 
"/edge_worker/res/cloud-computer-dark.svg",
+                    "icon": 
_get_base_url_path("/edge_worker/res/cloud-computer.svg"),
+                    "icon_dark_mode": 
_get_base_url_path("/edge_worker/res/cloud-computer-dark.svg"),
                     "url_route": "edge_worker_api_docs",
                 }
             ]
@@ -271,7 +287,7 @@ class EdgeExecutorPlugin(AirflowPlugin):
             appbuilder_menu_items = [
                 {
                     "name": "Edge Worker API docs",
-                    "href": "/edge_worker/v1/ui",
+                    "href": _get_base_url_path("/edge_worker/v1/ui"),
                     "category": "Docs",
                 }
             ]
diff --git 
a/providers/edge3/src/airflow/providers/edge3/plugins/www/dist/main.umd.cjs 
b/providers/edge3/src/airflow/providers/edge3/plugins/www/dist/main.umd.cjs
index 8677681e230..be28996e0ec 100644
--- a/providers/edge3/src/airflow/providers/edge3/plugins/www/dist/main.umd.cjs
+++ b/providers/edge3/src/airflow/providers/edge3/plugins/www/dist/main.umd.cjs
@@ -1,4 +1,4 @@
-(function(P,Ft){typeof exports=="object"&&typeof 
module<"u"?module.exports=Ft(require("react"),require("react-dom")):typeof 
define=="function"&&define.amd?define(["react","react-dom"],Ft):(P=typeof 
globalThis<"u"?globalThis:P||self,P.AirflowPlugin=Ft(P.React,P.ReactDOM))})(this,(function(P,Ft){"use
 strict";function Sp(e){const 
t=Object.create(null,{[Symbol.toStringTag]:{value:"Module"}});if(e){for(const n 
in e)if(n!=="default"){const 
r=Object.getOwnPropertyDescriptor(e,n);Object.definePr [...]
+(function(P,Lt){typeof exports=="object"&&typeof 
module<"u"?module.exports=Lt(require("react"),require("react-dom")):typeof 
define=="function"&&define.amd?define(["react","react-dom"],Lt):(P=typeof 
globalThis<"u"?globalThis:P||self,P.AirflowPlugin=Lt(P.React,P.ReactDOM))})(this,(function(P,Lt){"use
 strict";function Ep(e){const 
t=Object.create(null,{[Symbol.toStringTag]:{value:"Module"}});if(e){for(const n 
in e)if(n!=="default"){const 
r=Object.getOwnPropertyDescriptor(e,n);Object.definePr [...]
  * @license React
  * react-jsx-runtime.production.js
  *
@@ -6,28 +6,28 @@
  *
  * This source code is licensed under the MIT license found in the
  * LICENSE file in the root directory of this source tree.
- */var ml;function Op(){if(ml)return hr;ml=1;var 
e=Symbol.for("react.transitional.element"),t=Symbol.for("react.fragment");function
 n(r,o,i){var s=null;if(i!==void 0&&(s=""+i),o.key!==void 
0&&(s=""+o.key),"key"in o){i={};for(var a in o)a!=="key"&&(i[a]=o[a])}else 
i=o;return o=i.ref,{$$typeof:e,type:r,key:s,ref:o!==void 
0?o:null,props:i}}return hr.Fragment=t,hr.jsx=n,hr.jsxs=n,hr}var vl;function 
Pp(){return vl||(vl=1,qi.exports=Op()),qi.exports}var g=Pp();function bl(e){var 
t=Object.creat [...]
+ */var ml;function Pp(){if(ml)return hr;ml=1;var 
e=Symbol.for("react.transitional.element"),t=Symbol.for("react.fragment");function
 n(r,o,i){var s=null;if(i!==void 0&&(s=""+i),o.key!==void 
0&&(s=""+o.key),"key"in o){i={};for(var a in o)a!=="key"&&(i[a]=o[a])}else 
i=o;return o=i.ref,{$$typeof:e,type:r,key:s,ref:o!==void 
0?o:null,props:i}}return hr.Fragment=t,hr.jsx=n,hr.jsxs=n,hr}var vl;function 
Rp(){return vl||(vl=1,qi.exports=Pp()),qi.exports}var g=Rp();function bl(e){var 
t=Object.creat [...]
  * react-is.production.min.js
  *
  * Copyright (c) Facebook, Inc. and its affiliates.
  *
  * This source code is licensed under the MIT license found in the
  * LICENSE file in the root directory of this source tree.
- */var Il;function im(){if(Il)return Q;Il=1;var e=typeof 
Symbol=="function"&&Symbol.for,t=e?Symbol.for("react.element"):60103,n=e?Symbol.for("react.portal"):60106,r=e?Symbol.for("react.fragment"):60107,o=e?Symbol.for("react.strict_mode"):60108,i=e?Symbol.for("react.profiler"):60114,s=e?Symbol.for("react.provider"):60109,a=e?Symbol.for("react.context"):60110,l=e?Symbol.for("react.async_mode"):60111,c=e?Symbol.for("react.concurrent_mode"):60111,u=e?Symbol.for("react.forward_ref"):60112,h=e
 [...]
+ */var Il;function sm(){if(Il)return Q;Il=1;var e=typeof 
Symbol=="function"&&Symbol.for,t=e?Symbol.for("react.element"):60103,n=e?Symbol.for("react.portal"):60106,r=e?Symbol.for("react.fragment"):60107,o=e?Symbol.for("react.strict_mode"):60108,i=e?Symbol.for("react.profiler"):60114,s=e?Symbol.for("react.provider"):60109,a=e?Symbol.for("react.context"):60110,l=e?Symbol.for("react.async_mode"):60111,c=e?Symbol.for("react.concurrent_mode"):60111,u=e?Symbol.for("react.forward_ref"):60112,h=e
 [...]
       <svg width="46" height="15" style="left: -15.5px; position: absolute; 
top: 0; filter: drop-shadow(rgba(0, 0, 0, 0.4) 0px 1px 1.1px);">
         <g transform="translate(2 3)">
           <path fill-rule="evenodd" d="M 15 4.5L 15 2L 11.5 5.5L 15 9L 15 6.5L 
31 6.5L 31 9L 34.5 5.5L 31 2L 31 4.5Z" style="stroke-width: 2px; stroke: 
white;"></path>
           <path fill-rule="evenodd" d="M 15 4.5L 15 2L 11.5 5.5L 15 9L 15 6.5L 
31 6.5L 31 9L 34.5 5.5L 31 2L 31 4.5Z"></path>
         </g>
-      </svg>`,n.body.appendChild(r)};function 
fk(e,t){if(!(!e||!t.isActiveElement(e)))try{const{selectionStart:n,selectionEnd:r,value:o}=e,i=o.substring(0,n),s=o.substring(r);return{start:n,end:r,value:o,beforeTxt:i,afterTxt:s}}catch{}}function
 
gk(e,t,n){if(!(!e||!n.isActiveElement(e))){if(!t){e.setSelectionRange(e.value.length,e.value.length);return}try{const{value:r}=e,{beforeTxt:o="",afterTxt:i="",start:s}=t;let
 a=r.length;if(r.endsWith(i))a=r.length-i.length;else if(r.startsWith(o))a [...]
+      </svg>`,n.body.appendChild(r)};function 
gk(e,t,n){if(!(!e||!n.isActiveElement(e))){if(!t){const 
r=e.value.length;e.setSelectionRange(r,r);return}try{const 
r=e.value,{start:o,end:i,value:s}=t;if(r===s){e.setSelectionRange(o,i);return}const
 
a=Vd(s,r,o),l=o===i?a:Vd(s,r,i),c=Math.max(0,Math.min(a,r.length)),u=Math.max(c,Math.min(l,r.length));e.setSelectionRange(c,u)}catch{const
 r=e.value.length;e.setSelectionRange(r,r)}}}function Vd(e,t,n){const 
r=e.slice(0,n),o=e.slice(n);let i=0;con [...]
 )+\\(\\s*max(-device)?-${e}`,"i"),min:new 
RegExp(`\\(\\s*min(-device)?-${e}`,"i"),maxMin:new 
RegExp(`(!?\\(\\s*max(-device)?-${e})(.|
-)+\\(\\s*min(-device)?-${e}`,"i"),max:new 
RegExp(`\\(\\s*max(-device)?-${e}`,"i")}),yC=uh("width"),xC=uh("height"),dh=e=>({isMin:vh(e.minMax,e.maxMin,e.min),isMax:vh(e.maxMin,e.minMax,e.max)}),{isMin:Sa,isMax:hh}=dh(yC),{isMin:Ea,isMax:fh}=dh(xC),gh=/print/i,ph=/^print$/i,kC=/(-?\d*\.?\d+)(ch|em|ex|px|rem)/,CC=/(\d)/,$r=Number.MAX_VALUE,wC={ch:8.8984375,em:16,rem:16,ex:8.296875,px:1};function
 mh(e){const t=kC.exec(e)||(Sa(e)||Ea(e)?CC.exec(e):null);if(!t)return 
$r;if(t[0]==="0")return 0; [...]
-`).forEach(function(s){o=s.indexOf(":"),n=s.substring(0,o).trim().toLowerCase(),r=s.substring(o+1).trim(),!(!n||t[n]&&BO[n])&&(n==="set-cookie"?t[n]?t[n].push(r):t[n]=[r]:t[n]=t[n]?t[n]+",
 "+r:r)}),t},og=Symbol("internals");function Kr(e){return 
e&&String(e).trim().toLowerCase()}function Ei(e){return 
e===!1||e==null?e:E.isArray(e)?e.map(Ei):String(e)}function HO(e){const 
t=Object.create(null),n=/([^\s,;=]+)\s*(?:=\s*([^,;]+))?/g;let 
r;for(;r=n.exec(e);)t[r[1]]=r[2];return t}const UO=e=>/ [...]
-`)}getSetCookie(){return 
this.get("set-cookie")||[]}get[Symbol.toStringTag](){return"AxiosHeaders"}static
 from(t){return t instanceof this?t:new this(t)}static concat(t,...n){const 
r=new this(t);return n.forEach(o=>r.set(o)),r}static accessor(t){const 
r=(this[og]=this[og]={accessors:{}}).accessors,o=this.prototype;function 
i(s){const a=Kr(s);r[a]||(qO(o,s),r[a]=!0)}return 
E.isArray(t)?t.forEach(i):i(t),this}};Me.accessor(["Content-Type","Content-Length","Accept","Accept-Encoding","User-A
 [...]
-`+s.map(bg).join(`
-`):" "+bg(s[0]):"as no adapter specified";throw new H("There is no suitable 
adapter to dispatch the request "+a,"ERR_NOT_SUPPORT")}return 
o},adapters:Xa};function 
Qa(e){if(e.cancelToken&&e.cancelToken.throwIfRequested(),e.signal&&e.signal.aborted)throw
 new lr(null,e)}function xg(e){return 
Qa(e),e.headers=Me.from(e.headers),e.data=Ya.call(e,e.transformRequest),["post","put","patch"].indexOf(e.method)!==-1&&e.headers.setContentType("application/x-www-form-urlencoded",!1),yg.getAdapter(e.ad
 [...]
-`+i):r.stack=i}catch{}}throw r}}_request(t,n){typeof 
t=="string"?(n=n||{},n.url=t):n=t||{},n=In(this.defaults,n);const{transitional:r,paramsSerializer:o,headers:i}=n;r!==void
 
0&&Ii.assertOptions(r,{silentJSONParsing:mt.transitional(mt.boolean),forcedJSONParsing:mt.transitional(mt.boolean),clarifyTimeoutError:mt.transitional(mt.boolean)},!1),o!=null&&(E.isFunction(o)?n.paramsSerializer={serialize:o}:Ii.assertOptions(o,{encode:mt.function,serialize:mt.function},!0)),n.allowAbsoluteUrls!==v
 [...]
- * react-router v7.9.2
+)+\\(\\s*min(-device)?-${e}`,"i"),max:new 
RegExp(`\\(\\s*max(-device)?-${e}`,"i")}),yC=dh("width"),xC=dh("height"),hh=e=>({isMin:bh(e.minMax,e.maxMin,e.min),isMax:bh(e.maxMin,e.minMax,e.max)}),{isMin:Sa,isMax:fh}=hh(yC),{isMin:Ea,isMax:gh}=hh(xC),ph=/print/i,mh=/^print$/i,kC=/(-?\d*\.?\d+)(ch|em|ex|px|rem)/,CC=/(\d)/,$r=Number.MAX_VALUE,wC={ch:8.8984375,em:16,rem:16,ex:8.296875,px:1};function
 vh(e){const t=kC.exec(e)||(Sa(e)||Ea(e)?CC.exec(e):null);if(!t)return 
$r;if(t[0]==="0")return 0; [...]
+`).forEach(function(s){o=s.indexOf(":"),n=s.substring(0,o).trim().toLowerCase(),r=s.substring(o+1).trim(),!(!n||t[n]&&BO[n])&&(n==="set-cookie"?t[n]?t[n].push(r):t[n]=[r]:t[n]=t[n]?t[n]+",
 "+r:r)}),t},ig=Symbol("internals");function Kr(e){return 
e&&String(e).trim().toLowerCase()}function Ei(e){return 
e===!1||e==null?e:E.isArray(e)?e.map(Ei):String(e)}function HO(e){const 
t=Object.create(null),n=/([^\s,;=]+)\s*(?:=\s*([^,;]+))?/g;let 
r;for(;r=n.exec(e);)t[r[1]]=r[2];return t}const UO=e=>/ [...]
+`)}getSetCookie(){return 
this.get("set-cookie")||[]}get[Symbol.toStringTag](){return"AxiosHeaders"}static
 from(t){return t instanceof this?t:new this(t)}static concat(t,...n){const 
r=new this(t);return n.forEach(o=>r.set(o)),r}static accessor(t){const 
r=(this[ig]=this[ig]={accessors:{}}).accessors,o=this.prototype;function 
i(s){const a=Kr(s);r[a]||(qO(o,s),r[a]=!0)}return 
E.isArray(t)?t.forEach(i):i(t),this}};Me.accessor(["Content-Type","Content-Length","Accept","Accept-Encoding","User-A
 [...]
+`+s.map(yg).join(`
+`):" "+yg(s[0]):"as no adapter specified";throw new H("There is no suitable 
adapter to dispatch the request "+a,"ERR_NOT_SUPPORT")}return 
o},adapters:Xa};function 
Qa(e){if(e.cancelToken&&e.cancelToken.throwIfRequested(),e.signal&&e.signal.aborted)throw
 new lr(null,e)}function kg(e){return 
Qa(e),e.headers=Me.from(e.headers),e.data=Ya.call(e,e.transformRequest),["post","put","patch"].indexOf(e.method)!==-1&&e.headers.setContentType("application/x-www-form-urlencoded",!1),xg.getAdapter(e.ad
 [...]
+`+i):r.stack=i}catch{}}throw r}}_request(t,n){typeof 
t=="string"?(n=n||{},n.url=t):n=t||{},n=In(this.defaults,n);const{transitional:r,paramsSerializer:o,headers:i}=n;r!==void
 
0&&Ii.assertOptions(r,{silentJSONParsing:mt.transitional(mt.boolean),forcedJSONParsing:mt.transitional(mt.boolean),clarifyTimeoutError:mt.transitional(mt.boolean)},!1),o!=null&&(E.isFunction(o)?n.paramsSerializer={serialize:o}:Ii.assertOptions(o,{encode:mt.function,serialize:mt.function},!0)),n.allowAbsoluteUrls!==v
 [...]
+ * react-router v7.9.3
  *
  * Copyright (c) Remix Software Inc.
  *
@@ -35,9 +35,9 @@
  * LICENSE.md file in the root directory of this source tree.
  *
  * @license MIT
- */var Pg="popstate";function wP(e={}){function 
t(r,o){let{pathname:i,search:s,hash:a}=r.location;return 
el("",{pathname:i,search:s,hash:a},o.state&&o.state.usr||null,o.state&&o.state.key||"default")}function
 n(r,o){return typeof o=="string"?o:Yr(o)}return EP(t,n,null,e)}function 
le(e,t){if(e===!1||e===null||typeof e>"u")throw new Error(t)}function 
vt(e,t){if(!e){typeof console<"u"&&console.warn(t);try{throw new 
Error(t)}catch{}}}function SP(){return Math.random().toString(36).substring( 
[...]
+ */var Ig="popstate";function wP(e={}){function 
t(r,o){let{pathname:i,search:s,hash:a}=r.location;return 
el("",{pathname:i,search:s,hash:a},o.state&&o.state.usr||null,o.state&&o.state.key||"default")}function
 n(r,o){return typeof o=="string"?o:Yr(o)}return EP(t,n,null,e)}function 
le(e,t){if(e===!1||e===null||typeof e>"u")throw new Error(t)}function 
bt(e,t){if(!e){typeof console<"u"&&console.warn(t);try{throw new 
Error(t)}catch{}}}function SP(){return Math.random().toString(36).substring( 
[...]
 
-Please change the parent <Route path="${y}"> to <Route 
path="${y==="/"?"*":`${y}/*`}">.`)}let d=Nn(),m;if(t){let y=typeof 
t=="string"?cr(t):t;le(u==="/"||y.pathname?.startsWith(u),`When overriding the 
location using \`<Routes location>\` or \`useRoutes(routes, location)\`, the 
location pathname must begin with the portion of the URL pathname that was 
matched by all parent routes. The current pathname base is "${u}" but pathname 
"${y.pathname}" was given in the \`location\` prop.`),m=y}el [...]
+Please change the parent <Route path="${y}"> to <Route 
path="${y==="/"?"*":`${y}/*`}">.`)}let d=Nn(),m;if(t){let y=typeof 
t=="string"?cr(t):t;le(u==="/"||y.pathname?.startsWith(u),`When overriding the 
location using \`<Routes location>\` or \`useRoutes(routes, location)\`, the 
location pathname must begin with the portion of the URL pathname that was 
matched by all parent routes. The current pathname base is "${u}" but pathname 
"${y.pathname}" was given in the \`location\` prop.`),m=y}el [...]
  * Licensed to the Apache Software Foundation (ASF) under one
  * or more contributor license agreements.  See the NOTICE file
  * distributed with this work for additional information
@@ -54,7 +54,7 @@ Please change the parent <Route path="${y}"> to <Route 
path="${y==="/"?"*":`${y}
  * KIND, either express or implied.  See the License for the
  * specific language governing permissions and limitations
  * under the License.
- */const _I=Fk({pauseOnPageIdle:!0,placement:"bottom-end"});/*!
+ */const _I=Lk({pauseOnPageIdle:!0,placement:"bottom-end"});/*!
  * Licensed to the Apache Software Foundation (ASF) under one
  * or more contributor license agreements.  See the NOTICE file
  * distributed with this work for additional information
@@ -71,7 +71,7 @@ Please change the parent <Route path="${y}"> to <Route 
path="${y==="/"?"*":`${y}
  * KIND, either express or implied.  See the License for the
  * specific language governing permissions and limitations
  * under the License.
- */const 
VI=({block:e="start",inline:t="nearest"})=>{const[n,r]=P.useState(()=>window.location.hash);return
 P.useEffect(()=>{const o=()=>r(window.location.hash);return 
window.addEventListener("hashchange",o),()=>window.removeEventListener("hashchange",o)},[]),P.useEffect(()=>{if(n){const
 
o=document.getElementById(n.slice(1));o&&o.scrollIntoView({behavior:"auto",block:e,inline:t})}},[n,e,t]),null},lp=({error:e})=>{const
 t=e;if(!t)return;const n=t.body?.detail;let r;return n!==void 0&&(typ [...]
+ */const 
VI=({block:e="start",inline:t="nearest"})=>{const[n,r]=P.useState(()=>window.location.hash);return
 P.useEffect(()=>{const o=()=>r(window.location.hash);return 
window.addEventListener("hashchange",o),()=>window.removeEventListener("hashchange",o)},[]),P.useEffect(()=>{if(n){const
 
o=document.getElementById(n.slice(1));o&&o.scrollIntoView({behavior:"auto",block:e,inline:t})}},[n,e,t]),null},cp=({error:e})=>{const
 t=e;if(!t)return;const n=t.body?.detail;let r;return n!==void 0&&(typ [...]
  * Licensed to the Apache Software Foundation (ASF) under one
  * or more contributor license agreements.  See the NOTICE file
  * distributed with this work for additional information
@@ -88,7 +88,7 @@ Please change the parent <Route path="${y}"> to <Route 
path="${y==="/"?"*":`${y}
  * KIND, either express or implied.  See the License for the
  * specific language governing permissions and limitations
  * under the License.
- */const dp=5e3;/*!
+ */const hp=5e3;/*!
  * Licensed to the Apache Software Foundation (ASF) under one
  * or more contributor license agreements.  See the NOTICE file
  * distributed with this work for additional information
@@ -122,7 +122,7 @@ Please change the parent <Route path="${y}"> to <Route 
path="${y==="/"?"*":`${y}
  * KIND, either express or implied.  See the License for the
  * specific language governing permissions and limitations
  * under the License.
- */const hp="token",GI=()=>{const e=document.cookie.split(";");for(const t of 
e){const[n,r]=t.split("=");if(n?.trim()==="_token"&&r!==void 0)return 
localStorage.setItem(hp,r),document.cookie="_token=; expires=Sat, 01 Jan 2000 
00:00:00 UTC; path=/;",r}},qI=e=>{const t=localStorage.getItem(hp)??GI();return 
t!==void 0&&(e.headers.Authorization=`Bearer 
${t}`),e},KI=()=>{const{data:e,error:t}=cI(void 
0,{enabled:!0,refetchInterval:dp});return 
e?.jobs&&e.jobs.length>0?g.jsx(xr,{p:2,children:g.j [...]
+ */const fp="token",GI=()=>{const e=document.cookie.split(";");for(const t of 
e){const[n,r]=t.split("=");if(n?.trim()==="_token"&&r!==void 0)return 
localStorage.setItem(fp,r),document.cookie="_token=; expires=Sat, 01 Jan 2000 
00:00:00 UTC; path=/;",r}},qI=e=>{const t=localStorage.getItem(fp)??GI();return 
t!==void 0&&(e.headers.Authorization=`Bearer 
${t}`),e},KI=()=>{const{data:e,error:t}=cI(void 
0,{enabled:!0,refetchInterval:hp});return 
e?.jobs&&e.jobs.length>0?g.jsx(xr,{p:2,children:g.j [...]
  * Licensed to the Apache Software Foundation (ASF) under one
  * or more contributor license agreements.  See the NOTICE file
  * distributed with this work for additional information
@@ -139,4 +139,4 @@ Please change the parent <Route path="${y}"> to <Route 
path="${y==="/"?"*":`${y}
  * KIND, either express or implied.  See the License for the
  * specific language governing permissions and limitations
  * under the License.
- */const 
U=(e,t="white")=>({solid:{value:`{colors.${e}.600}`},contrast:{value:{_light:"white",_dark:t}},fg:{value:{_light:`{colors.${e}.800}`,_dark:`{colors.${e}.200}`}},muted:{value:{_light:`{colors.${e}.200}`,_dark:`{colors.${e}.800}`}},subtle:{value:{_light:`{colors.${e}.100}`,_dark:`{colors.${e}.900}`}},emphasized:{value:{_light:`{colors.${e}.300}`,_dark:`{colors.${e}.700}`}},focusRing:{value:{_light:`{colors.${e}.800}`,_dark:`{colors.${e}.200}`}}}),fT=ka({theme:{tokens:{colors:{blac
 [...]
+ */const 
U=(e,t="white")=>({solid:{value:`{colors.${e}.600}`},contrast:{value:{_light:"white",_dark:t}},fg:{value:{_light:`{colors.${e}.800}`,_dark:`{colors.${e}.200}`}},muted:{value:{_light:`{colors.${e}.200}`,_dark:`{colors.${e}.800}`}},subtle:{value:{_light:`{colors.${e}.100}`,_dark:`{colors.${e}.900}`}},emphasized:{value:{_light:`{colors.${e}.300}`,_dark:`{colors.${e}.700}`}},focusRing:{value:{_light:`{colors.${e}.800}`,_dark:`{colors.${e}.200}`}}}),fT=ka({theme:{tokens:{colors:{blac
 [...]
diff --git 
a/providers/edge3/src/airflow/providers/edge3/plugins/www/src/layouts/EdgeLayout.tsx
 
b/providers/edge3/src/airflow/providers/edge3/plugins/www/src/layouts/EdgeLayout.tsx
index 00d04b0e225..8d75fdec30a 100644
--- 
a/providers/edge3/src/airflow/providers/edge3/plugins/www/src/layouts/EdgeLayout.tsx
+++ 
b/providers/edge3/src/airflow/providers/edge3/plugins/www/src/layouts/EdgeLayout.tsx
@@ -30,9 +30,13 @@ export const EdgeLayout = () => {
     { label: "Edge Jobs", value: "plugin/edge_jobs" },
   ];
 
+  // Extract base URL from HTML base element to handle webserver.base_url 
configuration
+  const baseUrl = document.querySelector("base")?.href ?? 
"http://localhost:8080/";;
+  const basename = new URL(baseUrl).pathname;
+
   return (
     <Box p={2} /* Compensate for parent padding from ExternalView */>
-      <BrowserRouter>
+      <BrowserRouter basename={basename}>
         <NavTabs tabs={tabs} />
         <Routes>
           <Route path="plugin/edge_worker" element={<WorkerPage />} />
diff --git 
a/providers/edge3/src/airflow/providers/edge3/plugins/www/src/main.tsx 
b/providers/edge3/src/airflow/providers/edge3/plugins/www/src/main.tsx
index 6d06ef6a8c5..71d23e738c1 100644
--- a/providers/edge3/src/airflow/providers/edge3/plugins/www/src/main.tsx
+++ b/providers/edge3/src/airflow/providers/edge3/plugins/www/src/main.tsx
@@ -19,6 +19,7 @@
 import { ChakraProvider } from "@chakra-ui/react";
 import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
 import axios from "axios";
+import { OpenAPI } from "openapi/requests/core/OpenAPI";
 import { FC } from "react";
 
 import { ColorModeProvider } from "src/context/colorMode";
@@ -33,6 +34,11 @@ export type PluginComponentProps = object;
  * Main plugin component
  */
 const PluginComponent: FC<PluginComponentProps> = () => {
+  // Set the base URL for OpenAPI client from the HTML base tag
+  const baseHref = document.querySelector("head > base")?.getAttribute("href") 
?? "";
+  const baseUrl = new URL(baseHref, globalThis.location.origin);
+  OpenAPI.BASE = baseUrl.pathname.replace(/\/$/, ""); // Remove trailing slash
+
   // ensure HTTP API calls are authenticated with current session token
   axios.interceptors.request.use(tokenHandler);
 
diff --git a/providers/edge3/www-hash.txt b/providers/edge3/www-hash.txt
index 7d7c111098a..ce30fba4ffd 100644
--- a/providers/edge3/www-hash.txt
+++ b/providers/edge3/www-hash.txt
@@ -1 +1 @@
-719ccadaebac924011168dd33e1a46f7128362bfdd487ac43cefba7c365d717b
+b9d4cc61d6d7979b1b1cd1144d0a16663367137bb2e265a4fdd90a126dbbc78d

Reply via email to