From 224167ce396c4441070dc3705e74579933c16e3a Mon Sep 17 00:00:00 2001 From: Pim van Pelt Date: Tue, 14 Apr 2026 14:39:52 +0200 Subject: [PATCH] Dataplane reconcile fixes; LB counters cleanup; SPA scope cookie MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Checker / reload: - Reload's update-in-place branch now mirrors b.Address onto the runtime health.Backend. Without this, GetBackend kept returning the pre-reload address indefinitely after a config edit that touched addresses but not healthcheck settings — the VPP sync path reads cfg.Backends directly so the dataplane moved on while the gRPC and SPA view stayed wedged on the old IPv4/IPv6. Sync (internal/vpp/lbsync.go): - reconcileVIP now detects encap mismatch in addition to src-ip-sticky mismatch and takes the full tear-down / re-add path via a new shared recreateVIP helper. Triggered when every backend flips address family (gre4 <-> gre6) and the existing VIP can no longer accept new ASes — previously the sync wedged with 'Invalid address family' until a full maglevd restart. - setASWeight is issued whenever the state machine requests flush (a.Flush=true), not only on the weight-value transition edge. Fixes the case where a backend reached StateDisabled after its effective weight had already been drained to 0 by pool failover — the sticky-cache entries pointing at it were previously never cleared. maglev-frontend: - signal.Ignore(SIGHUP) so a controlling-terminal disconnect doesn't kill the daemon. - debian/vpp-maglev.service grants CAP_SYS_ADMIN in addition to CAP_NET_RAW so setns(CLONE_NEWNET) can join the healthcheck netns. Comment documents the 'operation not permitted' symptom and notes the knob can be dropped if the deployment doesn't use the 'netns:' healthcheck option. LB plugin counters (internal/vpp/lbstats.go + friends): - Fix the VIP counter regex: the LB plugin registers vlib_simple_counter_main_t names without a leading '/' (vlib_validate_simple_counter in counter.c:50 uses cm->name verbatim; only entries that set cm->stat_segment_name get a slash). first/next/untracked/no-server now read through as live values instead of zero. - Drop the per-backend FIB counter block end-to-end (proto, grpcapi, metrics, vpp.Client, lbstats, maglevc). Traced from lb/node.c:558 into ip{4,6}_forward.h:141 — the LB plugin forwards by writing adj_index[VLIB_TX] directly and bypassing ip{4,6}_lookup_inline, which is the only path that increments lbm_to_counters. The backend's FIB load_balance stats_index literally never ticks for LB-forwarded traffic, so the column was always zero and misleading. docs/implementation/TODO records the full investigation and the recommended upstream path (new lb_as_stats_dump API message) for when we're ready to carry that VPP patch. - maglevc show vpp lb counters: plain-text tabular headers. label() wraps strings in ANSI escapes (~11 bytes of overhead), but tabwriter counts bytes, not rendered width — so a header row with label()'d cells and data rows with plain cells drifts column alignment on every row. color.go comment now spells out the constraint: label() only works when column N is wrapped identically in every row (key-value layouts are fine, multi-column tables with header-only labelling are not). SPA: - stores/scope.ts is cookie-backed (maglev_scope, 1 year, SameSite=Lax). App.tsx hydrates from the cookie then validates against the fetched snapshots: a cookie referencing a maglevd that no longer exists falls through to snaps[0] instead of leaving the user on a ghost selection. - components/Flash.tsx wraps props.value in createMemo. Solid's on() fires its callback on every dep notification, not on value change — source is right in solid-js/dist/solid.js:460, no equality check. Without the memo, flipping scope between two 'connected' maglevds (or any other cross-store reactive re-eval that doesn't actually change the concrete string) replays the animation every time. createMemo's default === dedupe fixes it in one place for every Flash consumer, superseding the local createMemo workaround we'd added in BackendRow earlier. --- cmd/frontend/main.go | 6 + .../web/dist/assets/index-3m4Pjc8_.js | 1 + .../web/dist/assets/index-DCJJqBMY.js | 1 - cmd/frontend/web/dist/index.html | 2 +- cmd/frontend/web/src/App.tsx | 9 +- cmd/frontend/web/src/components/Flash.tsx | 21 +- cmd/frontend/web/src/stores/scope.ts | 46 ++- cmd/frontend/web/src/stores/state.ts | 7 +- cmd/frontend/web/src/stores/zippy.ts | 4 +- cmd/maglevc/color.go | 17 +- cmd/maglevc/commands.go | 64 ++- debian/vpp-maglev.service | 17 +- internal/checker/checker.go | 14 + internal/grpcapi/maglev.pb.go | 364 +++++++----------- internal/grpcapi/server.go | 26 +- internal/metrics/metrics.go | 50 +-- internal/vpp/client.go | 18 - internal/vpp/lbstats.go | 102 ++--- internal/vpp/lbsync.go | 117 ++++-- proto/maglev.proto | 20 +- 20 files changed, 435 insertions(+), 471 deletions(-) create mode 100644 cmd/frontend/web/dist/assets/index-3m4Pjc8_.js delete mode 100644 cmd/frontend/web/dist/assets/index-DCJJqBMY.js diff --git a/cmd/frontend/main.go b/cmd/frontend/main.go index bd15371..707bc68 100644 --- a/cmd/frontend/main.go +++ b/cmd/frontend/main.go @@ -94,6 +94,12 @@ func run() error { ReadHeaderTimeout: 10 * time.Second, } + // Ignore SIGHUP so a controlling-terminal disconnect (or any + // stray process-group SIGHUP) doesn't kill the daemon — the + // default Go handler terminates the process with "Hangup", + // which is the wrong behaviour for a long-running network + // service. SIGTERM / SIGINT remain the clean-shutdown signals. + signal.Ignore(syscall.SIGHUP) sigCh := make(chan os.Signal, 1) signal.Notify(sigCh, syscall.SIGTERM, syscall.SIGINT) diff --git a/cmd/frontend/web/dist/assets/index-3m4Pjc8_.js b/cmd/frontend/web/dist/assets/index-3m4Pjc8_.js new file mode 100644 index 0000000..7f1bfe1 --- /dev/null +++ b/cmd/frontend/web/dist/assets/index-3m4Pjc8_.js @@ -0,0 +1 @@ +(function(){const t=document.createElement("link").relList;if(t&&t.supports&&t.supports("modulepreload"))return;for(const s of document.querySelectorAll('link[rel="modulepreload"]'))r(s);new MutationObserver(s=>{for(const i of s)if(i.type==="childList")for(const o of i.addedNodes)o.tagName==="LINK"&&o.rel==="modulepreload"&&r(o)}).observe(document,{childList:!0,subtree:!0});function n(s){const i={};return s.integrity&&(i.integrity=s.integrity),s.referrerPolicy&&(i.referrerPolicy=s.referrerPolicy),s.crossOrigin==="use-credentials"?i.credentials="include":s.crossOrigin==="anonymous"?i.credentials="omit":i.credentials="same-origin",i}function r(s){if(s.ep)return;s.ep=!0;const i=n(s);fetch(s.href,i)}})();const gt=!1,ht=(e,t)=>e===t,G=Symbol("solid-proxy"),Ce=Symbol("solid-track"),de={equals:ht};let Ke=Ye;const V=1,ge=2,Ge={owned:null,cleanups:null,context:null,owner:null};var k=null;let Ee=null,bt=null,_=null,O=null,R=null,_e=0;function ie(e,t){const n=_,r=k,s=e.length===0,i=t===void 0?r:t,o=s?Ge:{owned:null,cleanups:null,context:i?i.context:null,owner:i},l=s?e:()=>e(()=>U(()=>oe(o)));k=o,_=null;try{return Y(l,!0)}finally{_=n,k=r}}function A(e,t){t=t?Object.assign({},de,t):de;const n={value:e,observers:null,observerSlots:null,comparator:t.equals||void 0},r=s=>(typeof s=="function"&&(s=s(n.value)),Xe(n,s));return[qe.bind(n),r]}function E(e,t,n){const r=Le(e,t,!1,V);ce(r)}function K(e,t,n){Ke=kt;const r=Le(e,t,!1,V);(!n||!n.render)&&(r.user=!0),R?R.push(r):ce(r)}function B(e,t,n){n=n?Object.assign({},de,n):de;const r=Le(e,t,!0,0);return r.observers=null,r.observerSlots=null,r.comparator=n.equals||void 0,ce(r),qe.bind(r)}function mt(e){return Y(e,!1)}function U(e){if(_===null)return e();const t=_;_=null;try{return e()}finally{_=t}}function pt(e,t,n){const r=Array.isArray(e);let s,i=n&&n.defer;return o=>{let l;if(r){l=Array(e.length);for(let c=0;ct(l,s,o));return s=l,a}}function $t(e){K(()=>U(e))}function q(e){return k===null||(k.cleanups===null?k.cleanups=[e]:k.cleanups.push(e)),e}function Oe(){return _}function vt(){return k}function wt(e,t){const n=k,r=_;k=e,_=null;try{return Y(t,!0)}catch(s){Ne(s)}finally{k=n,_=r}}function qe(){if(this.sources&&this.state)if(this.state===V)ce(this);else{const e=O;O=null,Y(()=>be(this),!1),O=e}if(_){const e=this.observers?this.observers.length:0;_.sources?(_.sources.push(this),_.sourceSlots.push(e)):(_.sources=[this],_.sourceSlots=[e]),this.observers?(this.observers.push(_),this.observerSlots.push(_.sources.length-1)):(this.observers=[_],this.observerSlots=[_.sources.length-1])}return this.value}function Xe(e,t,n){let r=e.value;return(!e.comparator||!e.comparator(r,t))&&(e.value=t,e.observers&&e.observers.length&&Y(()=>{for(let s=0;s1e6)throw O=[],new Error},!1)),t}function ce(e){if(!e.fn)return;oe(e);const t=_e;yt(e,e.value,t)}function yt(e,t,n){let r;const s=k,i=_;_=k=e;try{r=e.fn(t)}catch(o){return e.pure&&(e.state=V,e.owned&&e.owned.forEach(oe),e.owned=null),e.updatedAt=n+1,Ne(o)}finally{_=i,k=s}(!e.updatedAt||e.updatedAt<=n)&&(e.updatedAt!=null&&"observers"in e?Xe(e,r):e.value=r,e.updatedAt=n)}function Le(e,t,n,r=V,s){const i={fn:e,state:r,updatedAt:null,owned:null,sources:null,sourceSlots:null,cleanups:null,value:t,owner:k,context:k?k.context:null,pure:n};return k===null||k!==Ge&&(k.owned?k.owned.push(i):k.owned=[i]),i}function he(e){if(e.state===0)return;if(e.state===ge)return be(e);if(e.suspense&&U(e.suspense.inFallback))return e.suspense.effects.push(e);const t=[e];for(;(e=e.owner)&&(!e.updatedAt||e.updatedAt<_e);)e.state&&t.push(e);for(let n=t.length-1;n>=0;n--)if(e=t[n],e.state===V)ce(e);else if(e.state===ge){const r=O;O=null,Y(()=>be(e,t[0]),!1),O=r}}function Y(e,t){if(O)return e();let n=!1;t||(O=[]),R?n=!0:R=[],_e++;try{const r=e();return _t(n),r}catch(r){n||(R=null),O=null,Ne(r)}}function _t(e){if(O&&(Ye(O),O=null),e)return;const t=R;R=null,t.length&&Y(()=>Ke(t),!1)}function Ye(e){for(let t=0;t=0;t--)oe(e.tOwned[t]);delete e.tOwned}if(e.owned){for(t=e.owned.length-1;t>=0;t--)oe(e.owned[t]);e.owned=null}if(e.cleanups){for(t=e.cleanups.length-1;t>=0;t--)e.cleanups[t]();e.cleanups=null}e.state=0}function St(e){return e instanceof Error?e:new Error(typeof e=="string"?e:"Unknown error",{cause:e})}function Ne(e,t=k){throw St(e)}const xt=Symbol("fallback");function Ue(e){for(let t=0;t1?[]:null;return q(()=>Ue(i)),()=>{let a=e()||[],c=a.length,u,f;return a[Ce],U(()=>{let g,w,v,T,C,p,m,y,S;if(c===0)o!==0&&(Ue(i),i=[],r=[],s=[],o=0,l&&(l=[])),n.fallback&&(r=[xt],s[0]=ie(x=>(i[0]=x,n.fallback())),o=1);else if(o===0){for(s=new Array(c),f=0;f=p&&y>=p&&r[m]===a[y];m--,y--)v[y]=s[m],T[y]=i[m],l&&(C[y]=l[m]);for(g=new Map,w=new Array(y+1),f=y;f>=p;f--)S=a[f],u=g.get(S),w[f]=u===void 0?-1:u,g.set(S,f);for(u=p;u<=m;u++)S=r[u],f=g.get(S),f!==void 0&&f!==-1?(v[f]=s[u],T[f]=i[u],l&&(C[f]=l[u]),f=w[f],g.set(S,f)):i[u]();for(f=p;fe(t||{}))}const Et=e=>`Stale read from <${e}>.`;function Q(e){const t="fallback"in e&&{fallback:()=>e.fallback};return B(At(()=>e.each,e.children,t||void 0))}function L(e){const t=e.keyed,n=B(()=>e.when,void 0,void 0),r=t?n:B(n,void 0,{equals:(s,i)=>!s==!i});return B(()=>{const s=r();if(s){const i=e.children;return typeof i=="function"&&i.length>0?U(()=>i(t?s:()=>{if(!U(r))throw Et("Show");return n()})):i}return e.fallback},void 0,void 0)}const I=e=>B(()=>e());function Ct(e,t,n){let r=n.length,s=t.length,i=r,o=0,l=0,a=t[s-1].nextSibling,c=null;for(;ou-l){const w=t[o];for(;l{s=i,t===document?e():d(t,e(),t.firstChild?null:void 0,n)},r.owner),()=>{s(),t.textContent=""}}function b(e,t,n,r){let s;const i=()=>{const l=document.createElement("template");return l.innerHTML=e,l.content.firstChild},o=()=>(s||(s=i())).cloneNode(!0);return o.cloneNode=o,o}function ke(e,t=window.document){const n=t[We]||(t[We]=new Set);for(let r=0,s=e.length;re(t,n))}function d(e,t,n,r){if(n!==void 0&&!r&&(r=[]),typeof t!="function")return me(e,t,r,n);E(s=>me(e,t(),s,n),r)}function Lt(e){let t=e.target;const n=`$$${e.type}`,r=e.target,s=e.currentTarget,i=a=>Object.defineProperty(e,"target",{configurable:!0,value:a}),o=()=>{const a=t[n];if(a&&!t.disabled){const c=t[`${n}Data`];if(c!==void 0?a.call(t,c,e):a.call(t,e),e.cancelBubble)return}return t.host&&typeof t.host!="string"&&!t.host._$host&&t.contains(e.target)&&i(t.host),!0},l=()=>{for(;o()&&(t=t._$host||t.parentNode||t.host););};if(Object.defineProperty(e,"currentTarget",{configurable:!0,get(){return t||document}}),e.composedPath){const a=e.composedPath();i(a[0]);for(let c=0;c{let l=t();for(;typeof l=="function";)l=l();n=me(e,l,n,r)}),()=>n;if(Array.isArray(t)){const l=[],a=n&&Array.isArray(n);if(Te(l,t,n,s))return E(()=>n=me(e,l,n,r,!0)),()=>n;if(l.length===0){if(n=J(e,n,r),o)return n}else a?n.length===0?He(e,l,r):Ct(e,n,l):(n&&J(e),He(e,l));n=l}else if(t.nodeType){if(Array.isArray(n)){if(o)return n=J(e,n,r,t);J(e,n,null,t)}else n==null||n===""||!e.firstChild?e.appendChild(t):e.replaceChild(t,e.firstChild);n=t}}return n}function Te(e,t,n,r){let s=!1;for(let i=0,o=t.length;i=0;o--){const l=t[o];if(s!==l){const a=l.parentNode===e;!i&&!o?a?e.replaceChild(s,l):e.insertBefore(s,n):a&&l.remove()}else i=!0}}else e.insertBefore(s,n);return[s]}const Nt="http://www.w3.org/2000/svg";function It(e,t=!1,n=void 0){return t?document.createElementNS(Nt,e):document.createElement(e,{is:n})}function Mt(e){const{useShadow:t}=e,n=document.createTextNode(""),r=()=>e.mount||document.body,s=vt();let i;return K(()=>{i||(i=wt(s,()=>B(()=>e.children)));const o=r();if(o instanceof HTMLHeadElement){const[l,a]=A(!1),c=()=>a(!0);ie(u=>d(o,()=>l()?u():i(),null)),q(c)}else{const l=It(e.isSVG?"g":"div",e.isSVG),a=t&&l.attachShadow?l.attachShadow({mode:"open"}):l;Object.defineProperty(l,"_$host",{get(){return n.parentNode},configurable:!0}),d(a,i),o.appendChild(l),e.ref&&e.ref(l),q(()=>o.removeChild(l))}},void 0,{render:!0}),n}async function Ze(e){const t=await fetch(e,{credentials:"same-origin"});if(!t.ok)throw new Error(`${e}: ${t.status} ${t.statusText}`);return await t.json()}function ze(){return Ze("/view/api/state")}function jt(){return Ze("/view/api/version")}const pe=Symbol("store-raw"),z=Symbol("store-node"),D=Symbol("store-has"),Qe=Symbol("store-self");function et(e){let t=e[G];if(!t&&(Object.defineProperty(e,G,{value:t=new Proxy(e,Bt)}),!Array.isArray(e))){const n=Object.keys(e),r=Object.getOwnPropertyDescriptors(e);for(let s=0,i=n.length;se[G][t]),n}function tt(e){Oe()&&ae($e(e,z),Qe)()}function Rt(e){return tt(e),Reflect.ownKeys(e)}const Bt={get(e,t,n){if(t===pe)return e;if(t===G)return n;if(t===Ce)return tt(e),n;const r=$e(e,z),s=r[t];let i=s?s():e[t];if(t===z||t===D||t==="__proto__")return i;if(!s){const o=Object.getOwnPropertyDescriptor(e,t);Oe()&&(typeof i!="function"||e.hasOwnProperty(t))&&!(o&&o.get)&&(i=ae(r,t,i)())}return ee(i)?et(i):i},has(e,t){return t===pe||t===G||t===Ce||t===z||t===D||t==="__proto__"?!0:(Oe()&&ae($e(e,D),t)(),t in e)},set(){return!0},deleteProperty(){return!0},ownKeys:Rt,getOwnPropertyDescriptor:Dt};function ne(e,t,n,r=!1){if(!r&&e[t]===n)return;const s=e[t],i=e.length;n===void 0?(delete e[t],e[D]&&e[D][t]&&s!==void 0&&e[D][t].$()):(e[t]=n,e[D]&&e[D][t]&&s===void 0&&e[D][t].$());let o=$e(e,z),l;if((l=ae(o,t,s))&&l.$(()=>n),Array.isArray(e)&&e.length!==i){for(let a=e.length;a1){r=t.shift();const o=typeof r,l=Array.isArray(e);if(Array.isArray(r)){for(let a=0;a1){se(e[r],t,[r].concat(n));return}s=e[r],n=[r].concat(n)}let i=t[0];typeof i=="function"&&(i=i(s,n),i===s)||r===void 0&&i==null||(i=te(i),r===void 0||ee(s)&&ee(i)&&!Array.isArray(i)?nt(s,i):ne(e,r,i))}function Wt(...[e,t]){const n=te(e||{}),r=Array.isArray(n),s=et(n);function i(...o){mt(()=>{r&&o.length===1?Ut(n,o[0]):se(n,o)})}return[s,i]}const ve=new WeakMap,rt={get(e,t){if(t===pe)return e;const n=e[t];let r;return ee(n)?ve.get(n)||(ve.set(n,r=new Proxy(n,rt)),r):n},set(e,t,n){return ne(e,t,te(n)),!0},deleteProperty(e,t){return ne(e,t,void 0,!0),!0}};function F(e){return t=>{if(ee(t)){let n;(n=ve.get(t))||ve.set(t,n=new Proxy(t,rt)),e(n)}return t}}const[Ht,Ft]=A(0);setInterval(()=>Ft(e=>e+1),5e3);function Ie(e){const t={};for(const n of e.backends)t[n.name]=n.state;for(const n of e.frontends){let r=0;for(let a=0;a0){c=!0;break}if(c){r=a;break}}let s=!1,i=!1,o=!0;const l=new Set;for(let a=0;a0&&(s=!0),l.has(c.name)||(l.add(c.name),i=!0,u!=="unknown"&&(o=!1))}!i||o?n.state="unknown":s?n.state="up":n.state="down"}}const[X,W]=Wt({byName:{},settling:{}}),Vt=2e3,le=new Map;function Kt(e,t){return`${e}\0${t}`}function st(e,t){W(F(s=>{s.settling[e]||(s.settling[e]={}),s.settling[e][t]=!0}));const n=Kt(e,t),r=le.get(n);r&&clearTimeout(r),le.set(n,setTimeout(()=>{le.delete(n),W(F(s=>{s.settling[e]&&delete s.settling[e][t]}))},Vt))}function Gt(e){for(const[t,n]of le)t.startsWith(e+"\0")&&(clearTimeout(n),le.delete(t));W(F(t=>{t.settling[e]&&(t.settling[e]={})}))}function it(e){const t={};for(const n of e)Ie(n),t[n.maglevd.name]=n;W({byName:t})}function qt(e,t){W(F(r=>{const s=r.byName[e];if(!s)return;const i=s.backends.find(o=>o.name===t.backend);i&&(i.state=t.transition.to,i.enabled=t.transition.to!=="disabled",i.last_transition=t.transition,i.transitions||(i.transitions=[]),i.transitions.push(t.transition),i.transitions.length>20&&(i.transitions=i.transitions.slice(i.transitions.length-20)),Ie(s))}));const n=X.byName[e];if(n)for(const r of n.frontends)r.pools.some(s=>s.backends.some(i=>i.name===t.backend))&&st(e,r.name)}function Xt(e,t){W(F(n=>{const r=n.byName[e];if(!r)return;const s=t.per_frontend;if(!s||Object.keys(s).length===0){r.lb_state!==void 0&&(r.lb_state=void 0);return}r.lb_state||(r.lb_state={per_frontend:{}});const o=r.lb_state.per_frontend;for(const l of Object.keys(s)){o[l]||(o[l]={});const a=o[l],c=s[l];for(const u of Object.keys(c))a[u]!==c[u]&&(a[u]=c[u]);for(const u of Object.keys(a))u in c||delete a[u]}for(const l of Object.keys(o))l in s||delete o[l]})),Gt(e)}function Yt(e,t,n){return e?.lb_state?.per_frontend?.[t]?.[n]}function Jt(e,t){W(F(n=>{const r=n.byName[e];r&&(r.vpp_state=t)}))}function Zt(e,t){W(F(n=>{const r=n.byName[e];r&&(r.maglevd.connected=t.connected,r.maglevd.last_error=t.last_error)}))}function zt(e,t,n,r,s){W(F(i=>{const o=i.byName[e];if(!o)return;const l=o.frontends.find(u=>u.name===t);if(!l)return;const a=l.pools.find(u=>u.name===n);if(!a)return;const c=a.backends.find(u=>u.name===r);c&&(c.weight=s,Ie(o))})),st(e,t)}function Qt(e,t){const n={};for(const f of e.backends)n[f.name]=f.state;const r=!!e.lb_state,s=e.lb_state?.per_frontend?.[t.name],i=!!X.settling[e.maglevd.name]?.[t.name];let o=!1,l=!1;for(const f of t.pools)for(const $ of f.backends)if(n[$.name]!=="up"&&(o=!0),!i&&r&&$.effective_weight>0){const g=s?.[$.name];(g===void 0||g===0)&&(l=!0)}const a=t.pools[0],c=!!a&&a.backends.some(f=>f.weight>0),u=!a||a.backends.every(f=>f.effective_weight===0);return!o&&c&&!l?"ok":l?"bug-buckets":u?"primary-drained":o?"degraded":"unknown"}function en(e,t){switch(Qt(e,t)){case"ok":return"✅";case"bug-buckets":return"‼️";case"primary-drained":return"❗";case"degraded":return"⚠️";case"unknown":return"❓"}}function tn(e,t){return e.includes(":")?`[${e}]:${t}`:`${e}:${t}`}function nn(e){if(Ht(),!e||!e.at_unix_ns||e.at_unix_ns<=0)return"";const t=Date.now()-e.at_unix_ns/1e6,n=Math.floor(t/1e3);if(n<=1)return"now";const r=n%60,s=Math.floor(n/60);if(s<1)return`${n}s ago`;const i=s%60,o=Math.floor(s/60);if(o<1)return`${i}m${r}s ago`;const l=o%24,a=Math.floor(o/24);return a<1?`${o}h${i}m ago`:`${a}d${l}h ago`}const Fe=500,[we,rn]=A([]);function sn(e){rn(t=>{const n=[...t,e];return n.length>Fe?n.slice(n.length-Fe):n})}const ln=1e4,on=3e4;function an(){let e,t=!1;const n=l=>{try{const a=JSON.parse(l.data);cn(a)}catch(a){console.error("sse parse error",a,l.data)}},r=async()=>{try{const l=await ze();it(l)}catch(l){console.error("resync refetch failed",l)}},s=()=>{e&&(e.close(),e=void 0),e=new EventSource("/view/api/events"),e.onmessage=n,e.addEventListener("resync",r),e.onerror=l=>{console.debug("sse error, browser will reconnect",l)}},i=l=>{t||(t=!0,console.info("sse reconnecting:",l),s(),setTimeout(()=>{t=!1},1e3))};let o=Date.now();setInterval(()=>{const l=Date.now(),a=l-o;o=l,a>on&&i(`wake detected (${Math.round(a/1e3)}s gap)`)},ln),s()}function cn(e){switch(sn(e),e.type){case"backend":qt(e.maglevd,e.payload);break;case"frontend":e.maglevd,e.payload;break;case"maglevd-status":Zt(e.maglevd,e.payload);break;case"vpp-status":Jt(e.maglevd,e.payload.state);break;case"lb-state":Xt(e.maglevd,e.payload);break}}const ye="maglev_scope",un=60*60*24*365;function fn(){try{const e=document.cookie.split("; ").find(n=>n.startsWith(ye+"="));return e&&decodeURIComponent(e.slice(ye.length+1))||void 0}catch{return}}function dn(e){try{if(!e){document.cookie=`${ye}=; Path=/; Max-Age=0; SameSite=Lax`;return}const t=encodeURIComponent(e);document.cookie=`${ye}=${t}; Path=/; Max-Age=${un}; SameSite=Lax`}catch{}}const[xe,gn]=A(fn());function lt(e){gn(e),dn(e)}var hn=b("