Skip to content

Commit 44381b6

Browse files
Fix Edge3 provider navigation with webserver base_url configuration (apache#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 <95105677+jscheffl@users.noreply.github.com> --------- Co-authored-by: Jens Scheffler <95105677+jscheffl@users.noreply.github.com>
1 parent 0ec6653 commit 44381b6

File tree

5 files changed

+56
-30
lines changed

5 files changed

+56
-30
lines changed

providers/edge3/src/airflow/providers/edge3/plugins/edge_executor_plugin.py

Lines changed: 26 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -227,6 +227,22 @@ def change_maintenance_comment(self, worker_name: str):
227227
RUNNING_ON_APISERVER = "gunicorn" in sys.argv[0] and "airflow-webserver" in sys.argv
228228

229229

230+
def _get_base_url_path(path: str) -> str:
231+
"""Construct URL path with webserver base_url prefix."""
232+
base_url = conf.get("api", "base_url", fallback="/")
233+
# Extract pathname from base_url (handles both full URLs and path-only)
234+
if base_url.startswith(("http://", "https://")):
235+
from urllib.parse import urlparse
236+
237+
base_path = urlparse(base_url).path
238+
else:
239+
base_path = base_url
240+
241+
# Normalize paths: remove trailing slash from base, ensure leading slash on path
242+
base_path = base_path.rstrip("/")
243+
return base_path + path
244+
245+
230246
class EdgeExecutorPlugin(AirflowPlugin):
231247
"""EdgeExecutor Plugin - provides API endpoints for Edge Workers in Webserver."""
232248

@@ -237,30 +253,30 @@ class EdgeExecutorPlugin(AirflowPlugin):
237253
react_apps = [
238254
{
239255
"name": "Edge Worker",
240-
"bundle_url": "/edge_worker/static/main.umd.cjs",
256+
"bundle_url": _get_base_url_path("/edge_worker/static/main.umd.cjs"),
241257
"destination": "nav",
242258
"url_route": "edge_worker",
243259
"category": "admin",
244-
"icon": "/edge_worker/res/cloud-computer.svg",
245-
"icon_dark_mode": "/edge_worker/res/cloud-computer-dark.svg",
260+
"icon": _get_base_url_path("/edge_worker/res/cloud-computer.svg"),
261+
"icon_dark_mode": _get_base_url_path("/edge_worker/res/cloud-computer-dark.svg"),
246262
},
247263
{
248264
"name": "Edge Worker Jobs",
249-
"bundle_url": "/edge_worker/static/main.umd.cjs",
265+
"bundle_url": _get_base_url_path("/edge_worker/static/main.umd.cjs"),
250266
"url_route": "edge_jobs",
251267
"category": "admin",
252-
"icon": "/edge_worker/res/cloud-computer.svg",
253-
"icon_dark_mode": "/edge_worker/res/cloud-computer-dark.svg",
268+
"icon": _get_base_url_path("/edge_worker/res/cloud-computer.svg"),
269+
"icon_dark_mode": _get_base_url_path("/edge_worker/res/cloud-computer-dark.svg"),
254270
},
255271
]
256272
external_views = [
257273
{
258274
"name": "Edge Worker API docs",
259-
"href": "/edge_worker/docs",
275+
"href": _get_base_url_path("/edge_worker/docs"),
260276
"destination": "nav",
261277
"category": "docs",
262-
"icon": "/edge_worker/res/cloud-computer.svg",
263-
"icon_dark_mode": "/edge_worker/res/cloud-computer-dark.svg",
278+
"icon": _get_base_url_path("/edge_worker/res/cloud-computer.svg"),
279+
"icon_dark_mode": _get_base_url_path("/edge_worker/res/cloud-computer-dark.svg"),
264280
"url_route": "edge_worker_api_docs",
265281
}
266282
]
@@ -271,7 +287,7 @@ class EdgeExecutorPlugin(AirflowPlugin):
271287
appbuilder_menu_items = [
272288
{
273289
"name": "Edge Worker API docs",
274-
"href": "/edge_worker/v1/ui",
290+
"href": _get_base_url_path("/edge_worker/v1/ui"),
275291
"category": "Docs",
276292
}
277293
]

providers/edge3/src/airflow/providers/edge3/plugins/www/dist/main.umd.cjs

Lines changed: 18 additions & 18 deletions
Large diffs are not rendered by default.

providers/edge3/src/airflow/providers/edge3/plugins/www/src/layouts/EdgeLayout.tsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,13 @@ export const EdgeLayout = () => {
3030
{ label: "Edge Jobs", value: "plugin/edge_jobs" },
3131
];
3232

33+
// Extract base URL from HTML base element to handle webserver.base_url configuration
34+
const baseUrl = document.querySelector("base")?.href ?? "http://localhost:8080/";
35+
const basename = new URL(baseUrl).pathname;
36+
3337
return (
3438
<Box p={2} /* Compensate for parent padding from ExternalView */>
35-
<BrowserRouter>
39+
<BrowserRouter basename={basename}>
3640
<NavTabs tabs={tabs} />
3741
<Routes>
3842
<Route path="plugin/edge_worker" element={<WorkerPage />} />

providers/edge3/src/airflow/providers/edge3/plugins/www/src/main.tsx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import { ChakraProvider } from "@chakra-ui/react";
2020
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
2121
import axios from "axios";
22+
import { OpenAPI } from "openapi/requests/core/OpenAPI";
2223
import { FC } from "react";
2324

2425
import { ColorModeProvider } from "src/context/colorMode";
@@ -33,6 +34,11 @@ export type PluginComponentProps = object;
3334
* Main plugin component
3435
*/
3536
const PluginComponent: FC<PluginComponentProps> = () => {
37+
// Set the base URL for OpenAPI client from the HTML base tag
38+
const baseHref = document.querySelector("head > base")?.getAttribute("href") ?? "";
39+
const baseUrl = new URL(baseHref, globalThis.location.origin);
40+
OpenAPI.BASE = baseUrl.pathname.replace(/\/$/, ""); // Remove trailing slash
41+
3642
// ensure HTTP API calls are authenticated with current session token
3743
axios.interceptors.request.use(tokenHandler);
3844

providers/edge3/www-hash.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
719ccadaebac924011168dd33e1a46f7128362bfdd487ac43cefba7c365d717b
1+
b9d4cc61d6d7979b1b1cd1144d0a16663367137bb2e265a4fdd90a126dbbc78d

0 commit comments

Comments
 (0)