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

kaxilnaik 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 068f3fce84e Feature/edge maintenance plugin beautification (#55348)
068f3fce84e is described below

commit 068f3fce84e1d75752a852ba714e12d8f33f6f4f
Author: Jens Scheffler <95105677+jsche...@users.noreply.github.com>
AuthorDate: Tue Sep 9 00:13:54 2025 +0200

    Feature/edge maintenance plugin beautification (#55348)
    
    @dheerajturaga somehow I was not able to pugh to your repo directly git 
gave me an error :-(
    ```
    error: Authentication error: Authentication required: You must have push 
access to verify locks
    error: failed to push some refs to 'github.com:dheerajturaga/airflow.git'
    ```
    ...anyway, another way around, openeing a draft PR here, can you 
cherry-pick the commits down to your PR?
    
    The last commit [Rework maintenance dialog to be a 
dialog](https://github.com/apache/airflow/commit/7e6d13cf316711f286e3262fa8552b2d62ede965)
 has a problem though: Dialog is not opening, it makes a JS script error which 
is not much helpful:
    ```
    Uncaught TypeError: can't access property "flushSync", ne is undefined
        R http://localhost:28080/edge_worker/static/main.umd.cjs:16
    ```
---
 airflow-core/src/airflow/ui/src/main.tsx           |   4 +-
 .../providers/edge3/plugins/www/dist/main.umd.cjs  |  51 ++++---
 .../www/src/components/MaintenanceEnterButton.tsx  | 101 ++++++++++++++
 .../www/src/components/MaintenanceExitButton.tsx   |  61 +++++++++
 .../plugins/www/src/components/OperationsCell.tsx  | 147 ---------------------
 .../www/src/components/WorkerOperations.tsx        |  65 +++++++++
 .../components/ui/{index.ts => createToaster.ts}   |   6 +-
 .../edge3/plugins/www/src/components/ui/index.ts   |   1 +
 .../edge3/plugins/www/src/pages/WorkerPage.tsx     |  54 +-------
 .../providers/edge3/plugins/www/vite.config.ts     |   1 +
 .../providers/edge3/worker_api/routes/ui.py        |  12 +-
 providers/edge3/www-hash.txt                       |   2 +-
 12 files changed, 282 insertions(+), 223 deletions(-)

diff --git a/airflow-core/src/airflow/ui/src/main.tsx 
b/airflow-core/src/airflow/ui/src/main.tsx
index 2768d84809a..41f5d1bd0f6 100644
--- a/airflow-core/src/airflow/ui/src/main.tsx
+++ b/airflow-core/src/airflow/ui/src/main.tsx
@@ -21,6 +21,7 @@ import { QueryClientProvider } from "@tanstack/react-query";
 import axios, { type AxiosError } from "axios";
 import { StrictMode } from "react";
 import React from "react";
+import * as ReactDOM from "react-dom";
 import { createRoot } from "react-dom/client";
 import { I18nextProvider } from "react-i18next";
 import { RouterProvider } from "react-router-dom";
@@ -37,10 +38,11 @@ import { client } from "./queryClient";
 import { system } from "./theme";
 import { clearToken, tokenHandler } from "./utils/tokenHandler";
 
-// Set React and ReactJSXRuntime on globalThis to share them with the 
dynamically imported React plugins.
+// Set React, ReactDOM, and ReactJSXRuntime on globalThis to share them with 
the dynamically imported React plugins.
 // Only one instance of React should be used.
 // Reflect will avoid type checking.
 Reflect.set(globalThis, "React", React);
+Reflect.set(globalThis, "ReactDOM", ReactDOM);
 Reflect.set(globalThis, "ReactJSXRuntime", ReactJSXRuntime);
 
 // redirect to login page if the API responds with unauthorized or forbidden 
errors
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 871159e89c8..766376f7f11 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(T,Y){typeof exports=="object"&&typeof 
module<"u"?module.exports=Y(require("react"),require("react-dom")):typeof 
define=="function"&&define.amd?define(["react","react-dom"],Y):(T=typeof 
globalThis<"u"?globalThis:T||self,T.AirflowPlugin=Y(T.React))})(this,function(T){"use
 strict";var ZT=Object.defineProperty;var uv=T=>{throw TypeError(T)};var 
eN=(T,Y,w)=>Y in 
T?ZT(T,Y,{enumerable:!0,configurable:!0,writable:!0,value:w}):T[Y]=w;var 
Ke=(T,Y,w)=>eN(T,typeof Y!="symbol"?Y+"":Y,w),Vl= [...]
+(function(E,te){typeof exports=="object"&&typeof 
module<"u"?module.exports=te(require("react"),require("react-dom")):typeof 
define=="function"&&define.amd?define(["react","react-dom"],te):(E=typeof 
globalThis<"u"?globalThis:E||self,E.AirflowPlugin=te(E.React,E.ReactDOM))})(this,function(E,te){"use
 strict";var YA=Object.defineProperty;var hb=E=>{throw TypeError(E)};var 
QA=(E,te,ve)=>te in 
E?YA(E,te,{enumerable:!0,configurable:!0,writable:!0,value:ve}):E[te]=ve;var 
Je=(E,te,ve)=>QA(E,typeo [...]
  * @license React
  * react-jsx-runtime.production.min.js
  *
@@ -6,27 +6,27 @@
  *
  * This source code is licensed under the MIT license found in the
  * LICENSE file in the root directory of this source tree.
- */var 
pv=T,mv=Symbol.for("react.element"),vv=Symbol.for("react.fragment"),bv=Object.prototype.hasOwnProperty,yv=pv.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.ReactCurrentOwner,xv={key:!0,ref:!0,__self:!0,__source:!0};function
 Wl(e,t,n){var r,i={},o=null,s=null;n!==void 0&&(o=""+n),t.key!==void 
0&&(o=""+t.key),t.ref!==void 0&&(s=t.ref);for(r in 
t)bv.call(t,r)&&!xv.hasOwnProperty(r)&&(i[r]=t[r]);if(e&&e.defaultProps)for(r 
in t=e.defaultProps,t)i[r]===void 0&&(i[r]=t[r]);return{$$t [...]
+ */var 
vb=E,bb=Symbol.for("react.element"),yb=Symbol.for("react.fragment"),xb=Object.prototype.hasOwnProperty,Cb=vb.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.ReactCurrentOwner,Sb={key:!0,ref:!0,__self:!0,__source:!0};function
 kc(e,t,n){var r,i={},o=null,s=null;n!==void 0&&(o=""+n),t.key!==void 
0&&(o=""+t.key),t.ref!==void 0&&(s=t.ref);for(r in 
t)xb.call(t,r)&&!Sb.hasOwnProperty(r)&&(i[r]=t[r]);if(e&&e.defaultProps)for(r 
in t=e.defaultProps,t)i[r]===void 0&&(i[r]=t[r]);return{$$t [...]
  * 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 xe=typeof 
Symbol=="function"&&Symbol.for,Os=xe?Symbol.for("react.element"):60103,Is=xe?Symbol.for("react.portal"):60106,$i=xe?Symbol.for("react.fragment"):60107,Bi=xe?Symbol.for("react.strict_mode"):60108,ji=xe?Symbol.for("react.profiler"):60114,Wi=xe?Symbol.for("react.provider"):60109,Hi=xe?Symbol.for("react.context"):60110,Rs=xe?Symbol.for("react.async_mode"):60111,Ui=xe?Symbol.for("react.concurrent_mode"):60111,Gi=xe?Symbol.for("react.forward_ref"):60112,qi=xe?Symbol.for("react
 [...]
+ */var ke=typeof 
Symbol=="function"&&Symbol.for,Js=ke?Symbol.for("react.element"):60103,Zs=ke?Symbol.for("react.portal"):60106,Zi=ke?Symbol.for("react.fragment"):60107,eo=ke?Symbol.for("react.strict_mode"):60108,to=ke?Symbol.for("react.profiler"):60114,no=ke?Symbol.for("react.provider"):60109,ro=ke?Symbol.for("react.context"):60110,ea=ke?Symbol.for("react.async_mode"):60111,io=ke?Symbol.for("react.concurrent_mode"):60111,oo=ke?Symbol.for("react.forward_ref"):60112,so=ke?Symbol.for("react
 [...]
       <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 
MS(e){if(!(!e||e.ownerDocument.activeElement!==e))try{const{selectionStart:t,selectionEnd:n,value:r}=e,i=r.substring(0,t),o=r.substring(n);return{start:t,end:n,value:r,beforeTxt:i,afterTxt:o}}catch{}}function
 
$S(e,t){if(!(!e||e.ownerDocument.activeElement!==e)){if(!t){e.setSelectionRange(e.value.length,e.value.length);return}try{const{value:n}=e,{beforeTxt:r="",afterTxt:i="",start:o}=t;let
 s=n.length;if(n.endsWith(i))s=n.length-i.length;else  [...]
+      </svg>`,n.body.appendChild(r)};function 
pE(e){if(!(!e||e.ownerDocument.activeElement!==e))try{const{selectionStart:t,selectionEnd:n,value:r}=e,i=r.substring(0,t),o=r.substring(n);return{start:t,end:n,value:r,beforeTxt:i,afterTxt:o}}catch{}}function
 
mE(e,t){if(!(!e||e.ownerDocument.activeElement!==e)){if(!t){e.setSelectionRange(e.value.length,e.value.length);return}try{const{value:n}=e,{beforeTxt:r="",afterTxt:i="",start:o}=t;let
 s=n.length;if(n.endsWith(i))s=n.length-i.length;else  [...]
 )+\\(\\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")}),Dw=mh("width"),Mw=mh("height"),vh=e=>({isMin:wh(e.minMax,e.maxMin,e.min),isMax:wh(e.maxMin,e.minMax,e.max)}),{isMin:Ga,isMax:bh}=vh(Dw),{isMin:qa,isMax:yh}=vh(Mw),xh=/print/i,Ch=/^print$/i,$w=/(-?\d*\.?\d+)(ch|em|ex|px|rem)/,Bw=/(\d)/,di=Number.MAX_VALUE,jw={ch:8.8984375,em:16,rem:16,ex:8.296875,px:1};function
 Sh(e){const t=$w.exec(e)||(Ga(e)||qa(e)?Bw.exec(e):null);if(!t)return 
di;if(t[0]==="0")return 0; [...]
-`).forEach(function(s){i=s.indexOf(":"),n=s.substring(0,i).trim().toLowerCase(),r=s.substring(i+1).trim(),!(!n||t[n]&&e2[n])&&(n==="set-cookie"?t[n]?t[n].push(r):t[n]=[r]:t[n]=t[n]?t[n]+",
 "+r:r)}),t},lg=Symbol("internals");function yi(e){return 
e&&String(e).trim().toLowerCase()}function Yo(e){return 
e===!1||e==null?e:k.isArray(e)?e.map(Yo):String(e)}function n2(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 r2=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(i=>r.set(i)),r}static accessor(t){const 
r=(this[lg]=this[lg]={accessors:{}}).accessors,i=this.prototype;function 
o(s){const a=yi(s);r[a]||(o2(i,s),r[a]=!0)}return 
k.isArray(t)?t.forEach(o):o(t),this}};Ue.accessor(["Content-Type","Content-Length","Accept","Accept-Encoding","User-A
 [...]
-`+o.map(xg).join(`
-`):" "+xg(o[0]):"as no adapter specified";throw new W("There is no suitable 
adapter to dispatch the request "+s,"ERR_NOT_SUPPORT")}return 
r},adapters:xl};function 
Cl(e){if(e.cancelToken&&e.cancelToken.throwIfRequested(),e.signal&&e.signal.aborted)throw
 new Cr(null,e)}function Sg(e){return 
Cl(e),e.headers=Ue.from(e.headers),e.data=bl.call(e,e.transformRequest),["post","put","patch"].indexOf(e.method)!==-1&&e.headers.setContentType("application/x-www-form-urlencoded",!1),Cg.getAdapter(e.ad
 [...]
-`+o):r.stack=o}catch{}}throw r}}_request(t,n){typeof 
t=="string"?(n=n||{},n.url=t):n=t||{},n=zn(this.defaults,n);const{transitional:r,paramsSerializer:i,headers:o}=n;r!==void
 
0&&ts.assertOptions(r,{silentJSONParsing:Pt.transitional(Pt.boolean),forcedJSONParsing:Pt.transitional(Pt.boolean),clarifyTimeoutError:Pt.transitional(Pt.boolean)},!1),i!=null&&(k.isFunction(i)?n.paramsSerializer={serialize:i}:ts.assertOptions(i,{encode:Pt.function,serialize:Pt.function},!0)),n.allowAbsoluteUrls!==v
 [...]
+)+\\(\\s*min(-device)?-${e}`,"i"),max:new 
RegExp(`\\(\\s*max(-device)?-${e}`,"i")}),Sk=Cf("width"),wk=Cf("height"),Sf=e=>({isMin:Pf(e.minMax,e.maxMin,e.min),isMax:Pf(e.maxMin,e.minMax,e.max)}),{isMin:kl,isMax:wf}=Sf(Sk),{isMin:Ol,isMax:Ef}=Sf(wk),kf=/print/i,Of=/^print$/i,Ek=/(-?\d*\.?\d+)(ch|em|ex|px|rem)/,kk=/(\d)/,Oi=Number.MAX_VALUE,Ok={ch:8.8984375,em:16,rem:16,ex:8.296875,px:1};function
 If(e){const t=Ek.exec(e)||(kl(e)||Ol(e)?kk.exec(e):null);if(!t)return 
Oi;if(t[0]==="0")return 0; [...]
+`).forEach(function(s){i=s.indexOf(":"),n=s.substring(0,i).trim().toLowerCase(),r=s.substring(i+1).trim(),!(!n||t[n]&&QT[n])&&(n==="set-cookie"?t[n]?t[n].push(r):t[n]=[r]:t[n]=t[n]?t[n]+",
 "+r:r)}),t},up=Symbol("internals");function _i(e){return 
e&&String(e).trim().toLowerCase()}function Cs(e){return 
e===!1||e==null?e:k.isArray(e)?e.map(Cs):String(e)}function ZT(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 e2=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(i=>r.set(i)),r}static accessor(t){const 
r=(this[up]=this[up]={accessors:{}}).accessors,i=this.prototype;function 
o(s){const a=_i(s);r[a]||(n2(i,s),r[a]=!0)}return 
k.isArray(t)?t.forEach(o):o(t),this}};Xe.accessor(["Content-Type","Content-Length","Accept","Accept-Encoding","User-A
 [...]
+`+o.map(Sp).join(`
+`):" "+Sp(o[0]):"as no adapter specified";throw new Q("There is no suitable 
adapter to dispatch the request "+s,"ERR_NOT_SUPPORT")}return 
r},adapters:tc};function 
nc(e){if(e.cancelToken&&e.cancelToken.throwIfRequested(),e.signal&&e.signal.aborted)throw
 new Vr(null,e)}function Ep(e){return 
nc(e),e.headers=Xe.from(e.headers),e.data=Zl.call(e,e.transformRequest),["post","put","patch"].indexOf(e.method)!==-1&&e.headers.setContentType("application/x-www-form-urlencoded",!1),wp.getAdapter(e.ad
 [...]
+`+o):r.stack=o}catch{}}throw r}}_request(t,n){typeof 
t=="string"?(n=n||{},n.url=t):n=t||{},n=qn(this.defaults,n);const{transitional:r,paramsSerializer:i,headers:o}=n;r!==void
 
0&&Os.assertOptions(r,{silentJSONParsing:Vt.transitional(Vt.boolean),forcedJSONParsing:Vt.transitional(Vt.boolean),clarifyTimeoutError:Vt.transitional(Vt.boolean)},!1),i!=null&&(k.isFunction(i)?n.paramsSerializer={serialize:i}:Os.assertOptions(i,{encode:Vt.function,serialize:Vt.function},!0)),n.allowAbsoluteUrls!==v
 [...]
  * @remix-run/router v1.23.0
  *
  * Copyright (c) Remix Software Inc.
@@ -35,7 +35,7 @@
  * LICENSE.md file in the root directory of this source tree.
  *
  * @license MIT
- */function xi(){return 
xi=Object.assign?Object.assign.bind():function(e){for(var 
t=1;t<arguments.length;t++){var n=arguments[t];for(var r in 
n)Object.prototype.hasOwnProperty.call(n,r)&&(e[r]=n[r])}return 
e},xi.apply(this,arguments)}var 
tn;(function(e){e.Pop="POP",e.Push="PUSH",e.Replace="REPLACE"})(tn||(tn={}));const
 Pg="popstate";function V2(e){e===void 0&&(e={});function 
t(r,i){let{pathname:o,search:s,hash:a}=r.location;return 
kl("",{pathname:o,search:s,hash:a},i.state&&i.state.usr|| [...]
+ */function Vi(){return 
Vi=Object.assign?Object.assign.bind():function(e){for(var 
t=1;t<arguments.length;t++){var n=arguments[t];for(var r in 
n)Object.prototype.hasOwnProperty.call(n,r)&&(e[r]=n[r])}return 
e},Vi.apply(this,arguments)}var 
cn;(function(e){e.Pop="POP",e.Push="PUSH",e.Replace="REPLACE"})(cn||(cn={}));const
 Np="popstate";function N2(e){e===void 0&&(e={});function 
t(r,i){let{pathname:o,search:s,hash:a}=r.location;return 
oc("",{pathname:o,search:s,hash:a},i.state&&i.state.usr|| [...]
  * React Router v6.30.1
  *
  * Copyright (c) Remix Software Inc.
@@ -44,7 +44,7 @@
  * LICENSE.md file in the root directory of this source tree.
  *
  * @license MIT
- */function Ci(){return 
Ci=Object.assign?Object.assign.bind():function(e){for(var 
t=1;t<arguments.length;t++){var n=arguments[t];for(var r in 
n)Object.prototype.hasOwnProperty.call(n,r)&&(e[r]=n[r])}return 
e},Ci.apply(this,arguments)}const 
is=w.createContext(null),Mg=w.createContext(null),rn=w.createContext(null),os=w.createContext(null),Mn=w.createContext({outlet:null,matches:[],isDataRoute:!1}),$g=w.createContext(null);function
 oP(e,t){let{relative:n}=t===void 0?{}:t;Si()||de(!1);let{b [...]
+ */function Fi(){return 
Fi=Object.assign?Object.assign.bind():function(e){for(var 
t=1;t<arguments.length;t++){var n=arguments[t];for(var r in 
n)Object.prototype.hasOwnProperty.call(n,r)&&(e[r]=n[r])}return 
e},Fi.apply(this,arguments)}const 
Rs=O.createContext(null),Bp=O.createContext(null),dn=O.createContext(null),Ts=O.createContext(null),Xn=O.createContext({outlet:null,matches:[],isDataRoute:!1}),jp=O.createContext(null);function
 nN(e,t){let{relative:n}=t===void 0?{}:t;Li()||pe(!1);let{b [...]
  * React Router DOM v6.30.1
  *
  * Copyright (c) Remix Software Inc.
@@ -53,7 +53,7 @@
  * LICENSE.md file in the root directory of this source tree.
  *
  * @license MIT
- */function as(){return 
as=Object.assign?Object.assign.bind():function(e){for(var 
t=1;t<arguments.length;t++){var n=arguments[t];for(var r in 
n)Object.prototype.hasOwnProperty.call(n,r)&&(e[r]=n[r])}return 
e},as.apply(this,arguments)}function Gg(e,t){if(e==null)return{};var 
n={},r=Object.keys(e),i,o;for(o=0;o<r.length;o++)i=r[o],!(t.indexOf(i)>=0)&&(n[i]=e[i]);return
 n}function kP(e){return!!(e.metaKey||e.altKey||e.ctrlKey||e.shiftKey)}function 
EP(e,t){return e.button===0&&(!t||t==="_sel [...]
+ */function As(){return 
As=Object.assign?Object.assign.bind():function(e){for(var 
t=1;t<arguments.length;t++){var n=arguments[t];for(var r in 
n)Object.prototype.hasOwnProperty.call(n,r)&&(e[r]=n[r])}return 
e},As.apply(this,arguments)}function Kp(e,t){if(e==null)return{};var 
n={},r=Object.keys(e),i,o;for(o=0;o<r.length;o++)i=r[o],!(t.indexOf(i)>=0)&&(n[i]=e[i]);return
 n}function CN(e){return!!(e.metaKey||e.altKey||e.ctrlKey||e.shiftKey)}function 
SN(e,t){return e.button===0&&(!t||t==="_sel [...]
  * 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
@@ -70,7 +70,7 @@
  * KIND, either express or implied.  See the License for the
  * specific language governing permissions and limitations
  * under the License.
- */const ap=5e3;/*!
+ */const 
mA=zE({pauseOnPageIdle:!0,placement:"bottom-end"}),sm=({error:e})=>{var i;const 
t=e;if(!t)return;const n=(i=t.body)==null?void 0:i.detail;let r;return n!==void 
0&&(typeof n=="string"?r=n:Array.isArray(n)?r=n.map(o=>`${o.loc.join(".")} 
${o.msg}`):r=Object.keys(n).map(o=>`${o}: 
${n[o]}`)),y.jsx(pA,{status:"error",children:y.jsxs(fR,{align:"start",flexDirection:"column",gap:2,mt:-1,children:[t.status,"
 ",t.message,r===t.message?void 
0:y.jsx(Wx,{whiteSpace:"preserve",wordBreak:"brea [...]
  * 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
@@ -87,7 +87,7 @@
  * KIND, either express or implied.  See the License for the
  * specific language governing permissions and limitations
  * under the License.
- */const RT=e=>{const[t,n]=T.useState(0);return 
T.useEffect(()=>{if(!e.current)return;const r=new ResizeObserver(i=>{for(const 
o of i)n(o.contentRect.width)});return 
r.observe(e.current),()=>{r.disconnect()}},[e]),t};/*!
+ */const cm=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
@@ -104,7 +104,7 @@
  * KIND, either express or implied.  See the License for the
  * specific language governing permissions and limitations
  * under the License.
- */const lp="token",PT=()=>{const e=document.cookie.split(";");for(const t of 
e){const[n,r]=t.split("=");if((n==null?void 0:n.trim())==="_token"&&r!==void 
0)return localStorage.setItem(lp,r),document.cookie="_token=; expires=Sat, 01 
Jan 2000 00:00:00 UTC; path=/;",r}},TT=e=>{const 
t=localStorage.getItem(lp)??PT();return t!==void 
0&&(e.headers.Authorization=`Bearer 
${t}`),e},NT=()=>{const{data:e,error:t}=nT(void 
0,{enabled:!0,refetchInterval:ap});return e?S.jsx(At,{p:2,children:S.jsxs(mf, 
[...]
+ */const OA=e=>{const[t,n]=E.useState(0);return 
E.useEffect(()=>{if(!e.current)return;const r=new ResizeObserver(i=>{for(const 
o of i)n(o.contentRect.width)});return 
r.observe(e.current),()=>{r.disconnect()}},[e]),t};/*!
  * 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
@@ -121,4 +121,21 @@
  * KIND, either express or implied.  See the License for the
  * specific language governing permissions and limitations
  * under the License.
- */const 
on=(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}`}}}),qT=ja({theme:{tokens:{colors:{suc
 [...]
+ */const um="token",IA=()=>{const e=document.cookie.split(";");for(const t of 
e){const[n,r]=t.split("=");if((n==null?void 0:n.trim())==="_token"&&r!==void 
0)return localStorage.setItem(um,r),document.cookie="_token=; expires=Sat, 01 
Jan 2000 00:00:00 UTC; path=/;",r}},PA=e=>{const 
t=localStorage.getItem(um)??IA();return t!==void 
0&&(e.headers.Authorization=`Bearer 
${t}`),e},RA=()=>{const{data:e,error:t}=ZN(void 
0,{enabled:!0,refetchInterval:cm});return e?y.jsx(zt,{p:2,children:y.jsxs(bg, 
[...]
+ * 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
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.
+ */const 
hn=(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}`}}}),HA=Cl({theme:{tokens:{colors:{suc
 [...]
diff --git 
a/providers/edge3/src/airflow/providers/edge3/plugins/www/src/components/MaintenanceEnterButton.tsx
 
b/providers/edge3/src/airflow/providers/edge3/plugins/www/src/components/MaintenanceEnterButton.tsx
new file mode 100644
index 00000000000..a7d526c0095
--- /dev/null
+++ 
b/providers/edge3/src/airflow/providers/edge3/plugins/www/src/components/MaintenanceEnterButton.tsx
@@ -0,0 +1,101 @@
+/*!
+ * 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
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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 { Button, CloseButton, Dialog, IconButton, Portal, Textarea, 
useDisclosure } from "@chakra-ui/react";
+import { useUiServiceRequestWorkerMaintenance } from "openapi/queries";
+import { useState } from "react";
+import { HiOutlineWrenchScrewdriver } from "react-icons/hi2";
+
+interface MaintenanceEnterButtonProps {
+  onEnterMaintenance: (toast: Record<string, string>) => void;
+  workerName: string;
+}
+
+export const MaintenanceEnterButton = ({ onEnterMaintenance, workerName }: 
MaintenanceEnterButtonProps) => {
+  const { onClose, onOpen, open } = useDisclosure();
+  const [comment, setComment] = useState("");
+
+  const enterMaintenanceMutation = useUiServiceRequestWorkerMaintenance({
+    onError: (error) => {
+      onEnterMaintenance({
+        description: `Unable to set worker ${workerName} to maintenance mode: 
${error}`,
+        title: "Setting Maintenance Mode failed",
+        type: "error",
+      });
+    },
+    onSuccess: () => {
+      onEnterMaintenance({
+        description: `Worker ${workerName} was requested to be in maintenance 
mode.`,
+        title: "Maintenance Mode activated",
+        type: "success",
+      });
+    },
+  });
+
+  const enterMaintenance = () => {
+    enterMaintenanceMutation.mutate({ requestBody: { maintenance_comment: 
comment }, workerName });
+  };
+
+  return (
+    <>
+      <IconButton
+        size="sm"
+        variant="ghost"
+        aria-label="Enter Maintenance"
+        title="Enter Maintenance"
+        onClick={onOpen}
+      >
+        <HiOutlineWrenchScrewdriver />
+      </IconButton>
+
+      <Dialog.Root onOpenChange={onClose} open={open} size="md">
+        <Portal>
+          <Dialog.Backdrop />
+          <Dialog.Positioner>
+            <Dialog.Content>
+              <Dialog.Header>
+                <Dialog.Title>Set maintenance for worker 
{workerName}</Dialog.Title>
+              </Dialog.Header>
+              <Dialog.Body>
+                <Textarea
+                  placeholder="Enter maintenance comment (required)"
+                  value={comment}
+                  onChange={(e) => setComment(e.target.value)}
+                  required
+                  maxLength={1024}
+                  size="sm"
+                />
+              </Dialog.Body>
+              <Dialog.Footer>
+                <Dialog.ActionTrigger asChild>
+                  <Button variant="outline">Cancel</Button>
+                </Dialog.ActionTrigger>
+                <Button onClick={enterMaintenance} disabled={!comment.trim()}>
+                  Confirm Maintenance
+                </Button>
+              </Dialog.Footer>
+              <Dialog.CloseTrigger asChild>
+                <CloseButton size="sm" />
+              </Dialog.CloseTrigger>
+            </Dialog.Content>
+          </Dialog.Positioner>
+        </Portal>
+      </Dialog.Root>
+    </>
+  );
+};
diff --git 
a/providers/edge3/src/airflow/providers/edge3/plugins/www/src/components/MaintenanceExitButton.tsx
 
b/providers/edge3/src/airflow/providers/edge3/plugins/www/src/components/MaintenanceExitButton.tsx
new file mode 100644
index 00000000000..7ebb04c1572
--- /dev/null
+++ 
b/providers/edge3/src/airflow/providers/edge3/plugins/www/src/components/MaintenanceExitButton.tsx
@@ -0,0 +1,61 @@
+/*!
+ * 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
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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 { IconButton } from "@chakra-ui/react";
+import { useUiServiceExitWorkerMaintenance } from "openapi/queries";
+import { IoMdExit } from "react-icons/io";
+
+interface MaintenanceExitButtonProps {
+  onExitMaintenance: (toast: Record<string, string>) => void;
+  workerName: string;
+}
+
+export const MaintenanceExitButton = ({ onExitMaintenance, workerName }: 
MaintenanceExitButtonProps) => {
+  const exitMaintenanceMutation = useUiServiceExitWorkerMaintenance({
+    onError: (error) => {
+      onExitMaintenance({
+        description: `Unable to exit ${workerName} from maintenance mode: 
${error}`,
+        title: "Exit Maintenance Mode failed",
+        type: "error",
+      });
+    },
+    onSuccess: () => {
+      onExitMaintenance({
+        description: `Worker ${workerName} was requested to exit maintenance 
mode.`,
+        title: "Maintenance Mode deactivated",
+        type: "success",
+      });
+    },
+  });
+
+  const exitMaintenance = () => {
+    exitMaintenanceMutation.mutate({ workerName });
+  };
+
+  return (
+    <IconButton
+      size="sm"
+      variant="ghost"
+      onClick={() => exitMaintenance()}
+      aria-label="Exit Maintenance"
+      title="Exit Maintenance"
+    >
+      <IoMdExit />
+    </IconButton>
+  );
+};
diff --git 
a/providers/edge3/src/airflow/providers/edge3/plugins/www/src/components/OperationsCell.tsx
 
b/providers/edge3/src/airflow/providers/edge3/plugins/www/src/components/OperationsCell.tsx
deleted file mode 100644
index bf7fe3b4d94..00000000000
--- 
a/providers/edge3/src/airflow/providers/edge3/plugins/www/src/components/OperationsCell.tsx
+++ /dev/null
@@ -1,147 +0,0 @@
-/*!
- * 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
- * regarding copyright ownership.  The ASF licenses this file
- * to you 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 { Box, Flex, HStack, IconButton, Textarea, VStack } from 
"@chakra-ui/react";
-import type { Worker } from "openapi/requests/types.gen";
-import { useState } from "react";
-import { FcCheckmark } from "react-icons/fc";
-import { HiOutlineWrenchScrewdriver } from "react-icons/hi2";
-import { ImCross } from "react-icons/im";
-import { IoMdExit } from "react-icons/io";
-
-interface MaintenanceFormProps {
-  onSubmit: (comment: string) => void;
-  onCancel: () => void;
-}
-
-const MaintenanceForm = ({ onCancel, onSubmit }: MaintenanceFormProps) => {
-  const [comment, setComment] = useState("");
-
-  const handleSubmit = () => {
-    if (comment.trim()) {
-      onSubmit(comment.trim());
-    }
-  };
-
-  return (
-    <VStack gap={2} align="stretch">
-      <Textarea
-        placeholder="Enter maintenance comment (required)"
-        value={comment}
-        onChange={(e) => setComment(e.target.value)}
-        required
-        maxLength={1024}
-        size="sm"
-      />
-      <HStack gap={2}>
-        <IconButton
-          size="sm"
-          colorScheme="green"
-          onClick={handleSubmit}
-          disabled={!comment.trim()}
-          aria-label="Confirm Maintenance"
-          title="Confirm Maintenance"
-        >
-          <FcCheckmark />
-        </IconButton>
-        <IconButton
-          size="sm"
-          colorScheme="red"
-          variant="outline"
-          onClick={onCancel}
-          aria-label="Cancel"
-          title="Cancel"
-        >
-          <ImCross />
-        </IconButton>
-      </HStack>
-    </VStack>
-  );
-};
-
-interface OperationsCellProps {
-  worker: Worker;
-  activeMaintenanceForm: string | null;
-  onSetActiveMaintenanceForm: (workerName: string | null) => void;
-  onRequestMaintenance: (workerName: string, comment: string) => void;
-  onExitMaintenance: (workerName: string) => void;
-}
-
-export const OperationsCell = ({
-  activeMaintenanceForm,
-  onExitMaintenance,
-  onRequestMaintenance,
-  onSetActiveMaintenanceForm,
-  worker,
-}: OperationsCellProps) => {
-  const workerName = worker.worker_name;
-  const state = worker.state;
-
-  let cellContent = null;
-
-  if (state === "idle" || state === "running") {
-    if (activeMaintenanceForm === workerName) {
-      cellContent = (
-        <MaintenanceForm
-          onSubmit={(comment) => onRequestMaintenance(workerName, comment)}
-          onCancel={() => onSetActiveMaintenanceForm(null)}
-        />
-      );
-    } else {
-      cellContent = (
-        <Flex justifyContent="end">
-          <IconButton
-            size="sm"
-            variant="ghost"
-            onClick={() => onSetActiveMaintenanceForm(workerName)}
-            aria-label="Enter Maintenance"
-            title="Enter Maintenance"
-          >
-            <HiOutlineWrenchScrewdriver />
-          </IconButton>
-        </Flex>
-      );
-    }
-  } else if (
-    state === "maintenance pending" ||
-    state === "maintenance mode" ||
-    state === "maintenance request" ||
-    state === "offline maintenance"
-  ) {
-    cellContent = (
-      <VStack gap={2} align="stretch">
-        <Box fontSize="sm" whiteSpace="pre-wrap">
-          {worker.maintenance_comments || "No comment"}
-        </Box>
-        <Flex justifyContent="end">
-          <IconButton
-            size="sm"
-            variant="ghost"
-            onClick={() => onExitMaintenance(workerName)}
-            aria-label="Exit Maintenance"
-            title="Exit Maintenance"
-          >
-            <IoMdExit />
-          </IconButton>
-        </Flex>
-      </VStack>
-    );
-  }
-
-  return cellContent;
-};
diff --git 
a/providers/edge3/src/airflow/providers/edge3/plugins/www/src/components/WorkerOperations.tsx
 
b/providers/edge3/src/airflow/providers/edge3/plugins/www/src/components/WorkerOperations.tsx
new file mode 100644
index 00000000000..34ce5fd6676
--- /dev/null
+++ 
b/providers/edge3/src/airflow/providers/edge3/plugins/www/src/components/WorkerOperations.tsx
@@ -0,0 +1,65 @@
+/*!
+ * 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
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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 { Box, Flex, VStack } from "@chakra-ui/react";
+import type { Worker } from "openapi/requests/types.gen";
+
+import { toaster } from "src/components/ui";
+
+import { MaintenanceEnterButton } from "./MaintenanceEnterButton";
+import { MaintenanceExitButton } from "./MaintenanceExitButton";
+
+interface WorkerOperationsProps {
+  onOperations: () => void;
+  worker: Worker;
+}
+
+export const WorkerOperations = ({ onOperations, worker }: 
WorkerOperationsProps) => {
+  const workerName = worker.worker_name;
+  const state = worker.state;
+
+  const onWorkerChange = (toast: Record<string, string>) => {
+    toaster.create(toast);
+    onOperations();
+  };
+
+  if (state === "idle" || state === "running") {
+    return (
+      <Flex justifyContent="end">
+        <MaintenanceEnterButton onEnterMaintenance={onWorkerChange} 
workerName={workerName} />
+      </Flex>
+    );
+  } else if (
+    state === "maintenance pending" ||
+    state === "maintenance mode" ||
+    state === "maintenance request" ||
+    state === "offline maintenance"
+  ) {
+    return (
+      <VStack gap={2} align="stretch">
+        <Box fontSize="sm" whiteSpace="pre-wrap">
+          {worker.maintenance_comments || "No comment"}
+        </Box>
+        <Flex justifyContent="end">
+          <MaintenanceExitButton onExitMaintenance={onWorkerChange} 
workerName={workerName} />
+        </Flex>
+      </VStack>
+    );
+  }
+  return null;
+};
diff --git 
a/providers/edge3/src/airflow/providers/edge3/plugins/www/src/components/ui/index.ts
 
b/providers/edge3/src/airflow/providers/edge3/plugins/www/src/components/ui/createToaster.ts
similarity index 84%
copy from 
providers/edge3/src/airflow/providers/edge3/plugins/www/src/components/ui/index.ts
copy to 
providers/edge3/src/airflow/providers/edge3/plugins/www/src/components/ui/createToaster.ts
index 62ff4eda180..9fa1c393c77 100644
--- 
a/providers/edge3/src/airflow/providers/edge3/plugins/www/src/components/ui/index.ts
+++ 
b/providers/edge3/src/airflow/providers/edge3/plugins/www/src/components/ui/createToaster.ts
@@ -16,5 +16,9 @@
  * specific language governing permissions and limitations
  * under the License.
  */
+import { createToaster } from "@chakra-ui/react";
 
-export * from "./Alert";
+export const toaster = createToaster({
+  pauseOnPageIdle: true,
+  placement: "bottom-end",
+});
diff --git 
a/providers/edge3/src/airflow/providers/edge3/plugins/www/src/components/ui/index.ts
 
b/providers/edge3/src/airflow/providers/edge3/plugins/www/src/components/ui/index.ts
index 62ff4eda180..95ef0889d86 100644
--- 
a/providers/edge3/src/airflow/providers/edge3/plugins/www/src/components/ui/index.ts
+++ 
b/providers/edge3/src/airflow/providers/edge3/plugins/www/src/components/ui/index.ts
@@ -18,3 +18,4 @@
  */
 
 export * from "./Alert";
+export * from "./createToaster";
diff --git 
a/providers/edge3/src/airflow/providers/edge3/plugins/www/src/pages/WorkerPage.tsx
 
b/providers/edge3/src/airflow/providers/edge3/plugins/www/src/pages/WorkerPage.tsx
index ff6a1745532..0351b2d608e 100644
--- 
a/providers/edge3/src/airflow/providers/edge3/plugins/www/src/pages/WorkerPage.tsx
+++ 
b/providers/edge3/src/airflow/providers/edge3/plugins/www/src/pages/WorkerPage.tsx
@@ -17,15 +17,10 @@
  * under the License.
  */
 import { Box, Table } from "@chakra-ui/react";
-import {
-  useUiServiceWorker,
-  useUiServiceRequestWorkerMaintenance,
-  useUiServiceExitWorkerMaintenance,
-} from "openapi/queries";
-import { useState } from "react";
+import { useUiServiceWorker } from "openapi/queries";
 
 import { ErrorAlert } from "src/components/ErrorAlert";
-import { OperationsCell } from "src/components/OperationsCell";
+import { WorkerOperations } from "src/components/WorkerOperations";
 import { WorkerStateBadge } from "src/components/WorkerStateBadge";
 import { autoRefreshInterval } from "src/utils";
 
@@ -34,43 +29,6 @@ export const WorkerPage = () => {
     enabled: true,
     refetchInterval: autoRefreshInterval,
   });
-  const [activeMaintenanceForm, setActiveMaintenanceForm] = useState<string | 
null>(null);
-
-  const requestMaintenanceMutation = useUiServiceRequestWorkerMaintenance({
-    onError: (error) => {
-      console.error("Error requesting maintenance:", error);
-      alert(`Error requesting maintenance: ${error}`);
-    },
-    onSuccess: () => {
-      console.log("Maintenance request successful");
-      setActiveMaintenanceForm(null);
-      refetch();
-    },
-  });
-
-  const exitMaintenanceMutation = useUiServiceExitWorkerMaintenance({
-    onError: (error) => {
-      console.error("Error exiting maintenance:", error);
-      alert(`Error exiting maintenance: ${error}`);
-    },
-    onSuccess: () => {
-      console.log("Exit maintenance successful");
-      refetch();
-    },
-  });
-
-  const requestMaintenance = (workerName: string, comment: string) => {
-    console.log(`Requesting maintenance for worker: ${workerName}, comment: 
${comment}`);
-    requestMaintenanceMutation.mutate({
-      requestBody: { maintenance_comment: comment },
-      workerName,
-    });
-  };
-
-  const exitMaintenance = (workerName: string) => {
-    console.log(`Exiting maintenance for worker: ${workerName}`);
-    exitMaintenanceMutation.mutate({ workerName });
-  };
 
   // TODO to make it proper
   // Use DataTable as component from Airflow-Core UI
@@ -129,13 +87,7 @@ export const WorkerPage = () => {
                   )}
                 </Table.Cell>
                 <Table.Cell>
-                  <OperationsCell
-                    worker={worker}
-                    activeMaintenanceForm={activeMaintenanceForm}
-                    onSetActiveMaintenanceForm={setActiveMaintenanceForm}
-                    onRequestMaintenance={requestMaintenance}
-                    onExitMaintenance={exitMaintenance}
-                  />
+                  <WorkerOperations worker={worker} onOperations={refetch} />
                 </Table.Cell>
               </Table.Row>
             ))}
diff --git 
a/providers/edge3/src/airflow/providers/edge3/plugins/www/vite.config.ts 
b/providers/edge3/src/airflow/providers/edge3/plugins/www/vite.config.ts
index e364349872f..37ebf1eca9e 100644
--- a/providers/edge3/src/airflow/providers/edge3/plugins/www/vite.config.ts
+++ b/providers/edge3/src/airflow/providers/edge3/plugins/www/vite.config.ts
@@ -42,6 +42,7 @@ export default defineConfig(({ command }) => {
             output: {
               globals: {
                 react: "React",
+                "react-dom": "ReactDOM",
                 "react/jsx-runtime": "ReactJSXRuntime",
               },
             },
diff --git 
a/providers/edge3/src/airflow/providers/edge3/worker_api/routes/ui.py 
b/providers/edge3/src/airflow/providers/edge3/worker_api/routes/ui.py
index 11027310768..0b452691765 100644
--- a/providers/edge3/src/airflow/providers/edge3/worker_api/routes/ui.py
+++ b/providers/edge3/src/airflow/providers/edge3/worker_api/routes/ui.py
@@ -19,7 +19,7 @@ from __future__ import annotations
 
 from datetime import datetime
 
-from fastapi import Depends, HTTPException
+from fastapi import Depends, HTTPException, status
 from sqlalchemy import select
 
 from airflow.api_fastapi.auth.managers.models.resource_details import 
AccessView
@@ -122,7 +122,9 @@ def request_worker_maintenance(
     worker_query = select(EdgeWorkerModel).where(EdgeWorkerModel.worker_name 
== worker_name)
     worker = session.scalar(worker_query)
     if not worker:
-        raise HTTPException(status_code=404, detail=f"Worker {worker_name} not 
found")
+        raise HTTPException(status.HTTP_404_NOT_FOUND, detail=f"Worker 
{worker_name} not found")
+    if not maintenance_request.maintenance_comment:
+        raise HTTPException(status.HTTP_400_BAD_REQUEST, detail="Maintenance 
comment is required")
 
     # Format the comment with timestamp and username (username will be added 
by plugin layer)
     formatted_comment = f"[{datetime.now().strftime('%Y-%m-%d %H:%M')}] - 
{user.get_name()} put node into maintenance mode\nComment: 
{maintenance_request.maintenance_comment}"
@@ -130,7 +132,7 @@ def request_worker_maintenance(
     try:
         request_maintenance(worker_name, formatted_comment, session=session)
     except Exception as e:
-        raise HTTPException(status_code=400, detail=str(e))
+        raise HTTPException(status.HTTP_400_BAD_REQUEST, detail=str(e))
 
 
 @ui_router.delete(
@@ -148,9 +150,9 @@ def exit_worker_maintenance(
     worker_query = select(EdgeWorkerModel).where(EdgeWorkerModel.worker_name 
== worker_name)
     worker = session.scalar(worker_query)
     if not worker:
-        raise HTTPException(status_code=404, detail=f"Worker {worker_name} not 
found")
+        raise HTTPException(status.HTTP_404_NOT_FOUND, detail=f"Worker 
{worker_name} not found")
 
     try:
         exit_maintenance(worker_name, session=session)
     except Exception as e:
-        raise HTTPException(status_code=400, detail=str(e))
+        raise HTTPException(status.HTTP_400_BAD_REQUEST, detail=str(e))
diff --git a/providers/edge3/www-hash.txt b/providers/edge3/www-hash.txt
index 0e053e9262d..e77d9fccb14 100644
--- a/providers/edge3/www-hash.txt
+++ b/providers/edge3/www-hash.txt
@@ -1 +1 @@
-ed85f7d6558cdc8c5edf498fcd96e187484327b877e021314f94ae640d4634f2
+d3d458dbc15ae801bb6bf8f128e38a1a65fd81e3ecc8c87dd30a79acc9a4041a


Reply via email to