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 abdb7880d13 Add Links to Edge React UI (#55356)
abdb7880d13 is described below
commit abdb7880d130e8d63685516ff814ebd7a6402396
Author: Jens Scheffler <[email protected]>
AuthorDate: Wed Sep 10 17:29:21 2025 +0200
Add Links to Edge React UI (#55356)
* Add Links to Edge React UI
* Turn off live updates for time-ago
* Copilot feedback
---
.../providers/edge3/plugins/www/dist/main.umd.cjs | 53 ++++++++++++++--------
.../providers/edge3/plugins/www/package.json | 3 +-
.../providers/edge3/plugins/www/pnpm-lock.yaml | 12 +++++
.../www/src/components/WorkerStateBadge.tsx | 33 ++++++++++++++
.../www/src/components/ui/ScrollToAnchor.tsx | 49 ++++++++++++++++++++
.../edge3/plugins/www/src/components/ui/index.ts | 1 +
.../edge3/plugins/www/src/pages/JobsPage.tsx | 46 +++++++++++++++----
.../edge3/plugins/www/src/pages/WorkerPage.tsx | 18 ++++++--
providers/edge3/www-hash.txt | 2 +-
9 files changed, 182 insertions(+), 35 deletions(-)
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 766376f7f11..6a912664875 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(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 [...]
+(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 dA=Object.defineProperty;var Eb=E=>{throw TypeError(E)};var
hA=(E,te,ve)=>te in
E?dA(E,te,{enumerable:!0,configurable:!0,writable:!0,value:ve}):E[te]=ve;var
Je=(E,te,ve)=>hA(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
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 [...]
+ */var
Rb=E,Tb=Symbol.for("react.element"),Nb=Symbol.for("react.fragment"),_b=Object.prototype.hasOwnProperty,Ab=Rb.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.ReactCurrentOwner,Vb={key:!0,ref:!0,__self:!0,__source:!0};function
Vc(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)_b.call(t,r)&&!Vb.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 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
[...]
+ */var ke=typeof
Symbol=="function"&&Symbol.for,sa=ke?Symbol.for("react.element"):60103,aa=ke?Symbol.for("react.portal"):60106,ro=ke?Symbol.for("react.fragment"):60107,io=ke?Symbol.for("react.strict_mode"):60108,oo=ke?Symbol.for("react.profiler"):60114,so=ke?Symbol.for("react.provider"):60109,ao=ke?Symbol.for("react.context"):60110,la=ke?Symbol.for("react.async_mode"):60111,lo=ke?Symbol.for("react.concurrent_mode"):60111,co=ke?Symbol.for("react.forward_ref"):60112,uo=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
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 [...]
+ </svg>`,n.body.appendChild(r)};function
IE(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
PE(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")}),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
[...]
+)+\\(\\s*min(-device)?-${e}`,"i"),max:new
RegExp(`\\(\\s*max(-device)?-${e}`,"i")}),Vk=Tf("width"),Fk=Tf("height"),Nf=e=>({isMin:Df(e.minMax,e.maxMin,e.min),isMax:Df(e.maxMin,e.minMax,e.max)}),{isMin:Al,isMax:_f}=Nf(Vk),{isMin:Vl,isMax:Af}=Nf(Fk),Vf=/print/i,Ff=/^print$/i,Lk=/(-?\d*\.?\d+)(ch|em|ex|px|rem)/,Dk=/(\d)/,Ii=Number.MAX_VALUE,zk={ch:8.8984375,em:16,rem:16,ex:8.296875,px:1};function
Lf(e){const t=Lk.exec(e)||(Al(e)||Vl(e)?Dk.exec(e):null);if(!t)return
Ii;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]&&u2[n])&&(n==="set-cookie"?t[n]?t[n].push(r):t[n]=[r]:t[n]=t[n]?t[n]+",
"+r:r)}),t},yp=Symbol("internals");function Vi(e){return
e&&String(e).trim().toLowerCase()}function Os(e){return
e===!1||e==null?e:k.isArray(e)?e.map(Os):String(e)}function h2(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 f2=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[yp]=this[yp]={accessors:{}}).accessors,i=this.prototype;function
o(s){const a=Vi(s);r[a]||(p2(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(Np).join(`
+`):" "+Np(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:cc};function
uc(e){if(e.cancelToken&&e.cancelToken.throwIfRequested(),e.signal&&e.signal.aborted)throw
new Fr(null,e)}function Ap(e){return
uc(e),e.headers=Xe.from(e.headers),e.data=ac.call(e,e.transformRequest),["post","put","patch"].indexOf(e.method)!==-1&&e.headers.setContentType("application/x-www-form-urlencoded",!1),_p.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&&Ns.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}:Ns.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 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|| [...]
+ */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)}var
cn;(function(e){e.Pop="POP",e.Push="PUSH",e.Replace="REPLACE"})(cn||(cn={}));const
$p="popstate";function H2(e){e===void 0&&(e={});function
t(r,i){let{pathname:o,search:s,hash:a}=r.location;return
fc("",{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 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 [...]
+ */function Li(){return
Li=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},Li.apply(this,arguments)}const
Vs=O.createContext(null),Yp=O.createContext(null),dn=O.createContext(null),Fs=O.createContext(null),Xn=O.createContext({outlet:null,matches:[],isDataRoute:!1}),Qp=O.createContext(null);function
pN(e,t){let{relative:n}=t===void 0?{}:t;Di()||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 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 [...]
+ */function Ds(){return
Ds=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},Ds.apply(this,arguments)}function rm(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 VN(e){return!!(e.metaKey||e.altKey||e.ctrlKey||e.shiftKey)}function
FN(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
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 [...]
+ */const R_=YE({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
@@ -87,7 +87,7 @@
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
- */const cm=5e3;/*!
+ */const
T_=({block:e="start",inline:t="nearest"})=>{const[n,r]=E.useState(()=>window.location.hash);return
E.useEffect(()=>{const i=()=>r(window.location.hash);return
window.addEventListener("hashchange",i),()=>window.removeEventListener("hashchange",i)},[]),E.useEffect(()=>{if(n){const
i=document.getElementById(n.slice(1));i&&i.scrollIntoView({behavior:"auto",block:e,inline:t})}},[n,e,t]),null},bm=({error:e})=>{var
i;const t=e;if(!t)return;const n=(i=t.body)==null?void 0:i.detail;let r [...]
* 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 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};/*!
+ */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
@@ -121,7 +121,7 @@
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
- */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,
[...]
+ */const $_=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
@@ -138,4 +138,21 @@
* 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
[...]
+ */const Sm="token",B_=()=>{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(Sm,r),document.cookie="_token=; expires=Sat, 01
Jan 2000 00:00:00 UTC; path=/;",r}},j_=e=>{const
t=localStorage.getItem(Sm)??B_();return t!==void
0&&(e.headers.Authorization=`Bearer
${t}`),e},W_=()=>{const{data:e,error:t}=d_(void
0,{enabled:!0,refetchInterval:Cm});return e?v.jsx(zt,{p:2,children:v.jsxs(Ig,
[...]
+ * 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}`}}}),oA=Rl({theme:{tokens:{colors:{suc
[...]
diff --git
a/providers/edge3/src/airflow/providers/edge3/plugins/www/package.json
b/providers/edge3/src/airflow/providers/edge3/plugins/www/package.json
index 48ea8f420f9..aa9d61d0690 100644
--- a/providers/edge3/src/airflow/providers/edge3/plugins/www/package.json
+++ b/providers/edge3/src/airflow/providers/edge3/plugins/www/package.json
@@ -37,7 +37,8 @@
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-icons": "^5.5.0",
- "react-router-dom": "^6.30.0"
+ "react-router-dom": "^6.30.0",
+ "react-timeago": "^8.3.0"
},
"devDependencies": {
"@7nohe/openapi-react-query-codegen": "^1.6.2",
diff --git
a/providers/edge3/src/airflow/providers/edge3/plugins/www/pnpm-lock.yaml
b/providers/edge3/src/airflow/providers/edge3/plugins/www/pnpm-lock.yaml
index c8e8979ad51..800ead46d1d 100644
--- a/providers/edge3/src/airflow/providers/edge3/plugins/www/pnpm-lock.yaml
+++ b/providers/edge3/src/airflow/providers/edge3/plugins/www/pnpm-lock.yaml
@@ -35,6 +35,9 @@ importers:
react-router-dom:
specifier: ^6.30.0
version: 6.30.1([email protected]([email protected]))([email protected])
+ react-timeago:
+ specifier: ^8.3.0
+ version: 8.3.0([email protected])
devDependencies:
'@7nohe/openapi-react-query-codegen':
specifier: ^1.6.2
@@ -2562,6 +2565,11 @@ packages:
peerDependencies:
react: '>=16.8'
+ [email protected]:
+ resolution: {integrity:
sha512-BeR0hj/5qqTc2+zxzBSQZMky6MmqwOtKseU3CSmcjKR5uXerej2QY34v2d+cdz11PoeVfAdWLX+qjM/UdZkUUg==}
+ peerDependencies:
+ react: ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
+
[email protected]:
resolution: {integrity:
sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==}
engines: {node: '>=0.10.0'}
@@ -6039,6 +6047,10 @@ snapshots:
'@remix-run/router': 1.23.0
react: 18.3.1
+ [email protected]([email protected]):
+ dependencies:
+ react: 18.3.1
+
[email protected]:
dependencies:
loose-envify: 1.4.0
diff --git
a/providers/edge3/src/airflow/providers/edge3/plugins/www/src/components/WorkerStateBadge.tsx
b/providers/edge3/src/airflow/providers/edge3/plugins/www/src/components/WorkerStateBadge.tsx
index d4269b2d2ab..71ec46ea7ce 100644
---
a/providers/edge3/src/airflow/providers/edge3/plugins/www/src/components/WorkerStateBadge.tsx
+++
b/providers/edge3/src/airflow/providers/edge3/plugins/www/src/components/WorkerStateBadge.tsx
@@ -48,6 +48,38 @@ const state2Color = (state: EdgeWorkerState | null |
undefined) => {
}
};
+const state2TooltipText = (state: EdgeWorkerState | null | undefined) => {
+ switch (state) {
+ // see enum mapping from
providers/edge3/src/airflow/providers/edge3/models/edge_worker.py:EdgeWorkerState
+ case "starting":
+ return "Edge Worker is in initialization.";
+ case "running":
+ return "Edge Worker is actively running a task.";
+ case "idle":
+ return "Edge Worker is active and waiting for a task.";
+ case "shutdown request":
+ return "Request to shutdown Edge Worker.";
+ case "terminating":
+ return "Edge Worker is completing work and stopping.";
+ case "offline":
+ return "Edge Worker was shut down.";
+ case "unknown":
+ return "No heartbeat signal from worker for some time, Edge Worker
probably down.";
+ case "maintenance request":
+ return "Worker was requested to enter maintenance mode. Once worker
receives this it will pause fetching jobs.";
+ case "maintenance pending":
+ return "Edge worker received the request for maintenance, waiting for
jobs to finish. Once jobs are finished will move to 'maintenance mode'.";
+ case "maintenance mode":
+ return "Edge worker is in maintenance mode. It is online but pauses
fetching jobs.";
+ case "maintenance exit":
+ return "Request worker to exit maintenance mode. Once the worker
receives this state it will un-pause and fetch new jobs.";
+ case "offline maintenance":
+ return "Worker was shut down in maintenance mode. It will be in
maintenance mode when restarted.";
+ default:
+ return undefined;
+ }
+};
+
export type Props = {
readonly state?: EdgeWorkerState | null;
} & BadgeProps;
@@ -61,6 +93,7 @@ export const WorkerStateBadge =
React.forwardRef<HTMLDivElement, Props>(
px={children === undefined ? 1 : 2}
py={1}
ref={ref}
+ title={state2TooltipText(state)}
variant="solid"
{...rest}
>
diff --git
a/providers/edge3/src/airflow/providers/edge3/plugins/www/src/components/ui/ScrollToAnchor.tsx
b/providers/edge3/src/airflow/providers/edge3/plugins/www/src/components/ui/ScrollToAnchor.tsx
new file mode 100644
index 00000000000..c06766d6e48
--- /dev/null
+++
b/providers/edge3/src/airflow/providers/edge3/plugins/www/src/components/ui/ScrollToAnchor.tsx
@@ -0,0 +1,49 @@
+/*!
+ * 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 { useEffect, useState } from "react";
+
+interface ScrollToAnchorProps {
+ inline?: ScrollLogicalPosition;
+ block?: ScrollLogicalPosition;
+}
+
+export const ScrollToAnchor = ({ block = "start", inline = "nearest" }:
ScrollToAnchorProps): null => {
+ const [hash, setHash] = useState(() => window.location.hash);
+
+ useEffect(() => {
+ const onHashChange = () => setHash(window.location.hash);
+ window.addEventListener("hashchange", onHashChange);
+ return () => window.removeEventListener("hashchange", onHashChange);
+ }, []);
+
+ useEffect(() => {
+ if (hash) {
+ const element = document.getElementById(hash.slice(1));
+ if (element) {
+ element.scrollIntoView({
+ behavior: "auto",
+ block: block,
+ inline: inline,
+ });
+ }
+ }
+ }, [hash, block, inline]);
+
+ 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/index.ts
index 95ef0889d86..5f6d9ea424c 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
@@ -19,3 +19,4 @@
export * from "./Alert";
export * from "./createToaster";
+export * from "./ScrollToAnchor";
diff --git
a/providers/edge3/src/airflow/providers/edge3/plugins/www/src/pages/JobsPage.tsx
b/providers/edge3/src/airflow/providers/edge3/plugins/www/src/pages/JobsPage.tsx
index 5ace842f5dd..344860c5083 100644
---
a/providers/edge3/src/airflow/providers/edge3/plugins/www/src/pages/JobsPage.tsx
+++
b/providers/edge3/src/airflow/providers/edge3/plugins/www/src/pages/JobsPage.tsx
@@ -16,8 +16,10 @@
* specific language governing permissions and limitations
* under the License.
*/
-import { Box, Table } from "@chakra-ui/react";
+import { Box, Link, Table } from "@chakra-ui/react";
import { useUiServiceJobs } from "openapi/queries";
+import { Link as RouterLink } from "react-router-dom";
+import TimeAgo from "react-timeago";
import { ErrorAlert } from "src/components/ErrorAlert";
import { StateBadge } from "src/components/StateBadge";
@@ -33,8 +35,7 @@ export const JobsPage = () => {
// Use DataTable as component from Airflow-Core UI
// Add sorting
// Add filtering
- // Add links to see job details / jobs list
- // Translation
+ // Translation?
if (data)
return (
<Box p={2}>
@@ -58,18 +59,43 @@ export const JobsPage = () => {
<Table.Row
key={`${job.dag_id}.${job.run_id}.${job.task_id}.${job.map_index}.${job.try_number}`}
>
- <Table.Cell>{job.dag_id}</Table.Cell>
- <Table.Cell>{job.run_id}</Table.Cell>
- <Table.Cell>{job.task_id}</Table.Cell>
- <Table.Cell>{job.map_index}</Table.Cell>
+ <Table.Cell>
+ {/* TODO Check why <Link to={`/dags/${job.dag_id}`}> is not
working via react-router-dom! */}
+ <Link href={`../dags/${job.dag_id}`}>{job.dag_id}</Link>
+ </Table.Cell>
+ <Table.Cell>
+ <Link
href={`../dags/${job.dag_id}/runs/${job.run_id}`}>{job.run_id}</Link>
+ </Table.Cell>
+ <Table.Cell>
+ {job.map_index >= 0 ? (
+ <Link
+
href={`../dags/${job.dag_id}/runs/${job.run_id}/tasks/${job.task_id}/mapped/${job.map_index}?try_number=${job.try_number}`}
+ >
+ {job.task_id}
+ </Link>
+ ) : (
+ <Link
+
href={`../dags/${job.dag_id}/runs/${job.run_id}/tasks/${job.task_id}?try_number=${job.try_number}`}
+ >
+ {job.task_id}
+ </Link>
+ )}
+ </Table.Cell>
+ <Table.Cell>{job.map_index >= 0 ? job.map_index :
"-"}</Table.Cell>
<Table.Cell>{job.try_number}</Table.Cell>
<Table.Cell>
<StateBadge state={job.state}>{job.state}</StateBadge>
</Table.Cell>
<Table.Cell>{job.queue}</Table.Cell>
- <Table.Cell>{job.queued_dttm}</Table.Cell>
- <Table.Cell>{job.edge_worker}</Table.Cell>
- <Table.Cell>{job.last_update}</Table.Cell>
+ <Table.Cell>
+ {job.queued_dttm ? <TimeAgo date={job.queued_dttm}
live={false} /> : undefined}
+ </Table.Cell>
+ <Table.Cell>
+ <RouterLink
to={`/plugin/edge_worker#${job.edge_worker}`}>{job.edge_worker}</RouterLink>
+ </Table.Cell>
+ <Table.Cell>
+ {job.last_update ? <TimeAgo date={job.last_update}
live={false} /> : undefined}
+ </Table.Cell>
</Table.Row>
))}
</Table.Body>
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 0351b2d608e..dd1d059acb4 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
@@ -18,10 +18,12 @@
*/
import { Box, Table } from "@chakra-ui/react";
import { useUiServiceWorker } from "openapi/queries";
+import TimeAgo from "react-timeago";
import { ErrorAlert } from "src/components/ErrorAlert";
import { WorkerOperations } from "src/components/WorkerOperations";
import { WorkerStateBadge } from "src/components/WorkerStateBadge";
+import { ScrollToAnchor } from "src/components/ui";
import { autoRefreshInterval } from "src/utils";
export const WorkerPage = () => {
@@ -34,8 +36,9 @@ export const WorkerPage = () => {
// Use DataTable as component from Airflow-Core UI
// Add sorting
// Add filtering
- // Add links to see jobs on worker
- // Translation
+ // Add links with filter to see jobs on worker
+ // Add time zone support for time display
+ // Translation?
if (data)
return (
<Box p={2}>
@@ -54,7 +57,7 @@ export const WorkerPage = () => {
</Table.Header>
<Table.Body>
{data.workers.map((worker) => (
- <Table.Row key={worker.worker_name}>
+ <Table.Row key={worker.worker_name} id={worker.worker_name}>
<Table.Cell>{worker.worker_name}</Table.Cell>
<Table.Cell>
<WorkerStateBadge
state={worker.state}>{worker.state}</WorkerStateBadge>
@@ -70,8 +73,12 @@ export const WorkerPage = () => {
"(default)"
)}
</Table.Cell>
- <Table.Cell>{worker.first_online}</Table.Cell>
- <Table.Cell>{worker.last_heartbeat}</Table.Cell>
+ <Table.Cell>
+ {worker.first_online ? <TimeAgo date={worker.first_online}
live={false} /> : undefined}
+ </Table.Cell>
+ <Table.Cell>
+ {worker.last_heartbeat ? <TimeAgo
date={worker.last_heartbeat} live={false} /> : undefined}
+ </Table.Cell>
<Table.Cell>{worker.jobs_active}</Table.Cell>
<Table.Cell>
{worker.sysinfo ? (
@@ -93,6 +100,7 @@ export const WorkerPage = () => {
))}
</Table.Body>
</Table.Root>
+ <ScrollToAnchor />
</Box>
);
if (error) {
diff --git a/providers/edge3/www-hash.txt b/providers/edge3/www-hash.txt
index e77d9fccb14..f1107f40c8a 100644
--- a/providers/edge3/www-hash.txt
+++ b/providers/edge3/www-hash.txt
@@ -1 +1 @@
-d3d458dbc15ae801bb6bf8f128e38a1a65fd81e3ecc8c87dd30a79acc9a4041a
+597e7860a9617d30432916dd89ed0018eca967c71184b4454690e36de31d0120