Builds on the maglev-frontend component introduced in 284b4cc with
quality-of-life improvements, an authenticated /admin surface, a
live-action control plane, and Debian packaging cleanup.
- Backend state now renders live: maglevd's FrontendEvent synthetic
from==to replay hydrates FrontendSnapshot.State on WatchEvents
subscribe, and live transitions update both the in-process cache
and every connected browser via a new applyFrontendTransition
reducer. Shown as a StatusBadge next to the frontend name.
- VPP connection state surfaces in the VPP zippy title as a
green/red badge. Driven by vpp-connect / vpp-disconnect and by
the steady stream of vpp-api-send/recv debug heartbeats so a
silent VPP drop is caught within one debug-log tick.
- Probe heartbeat dot becomes ❤️ while a probe is in flight and
reverts to · on probe-done. Fixed-size wrapper so the emoji swap
doesn't jiggle the row; both states share the same font-size.
- Flash component replaced its subtle background-only fade with a
scale-pop + yellow halo box-shadow + longer duration so
weight/effective/state changes are unmissable on tiny numeric
cells. Initial mount still skipped via defer so no flash on load.
- Last-transition age is now a live countdown driven by a global
1-second ticker signal (one timer, many subscribers). Two most
significant units: 10m30s / 1h12m / 1d16h. Sub-second ages
render as "now" to absorb clock skew between maglevd and the
browser.
- Event stream is now chronological (oldest at top) with tail-
style auto-scroll, pause/resume, and the toolbar moved below the
list. Row separators removed. Also shown only in /admin (see
below) so /view stays a focused read-only surface.
- Table nowrap so backend names like nginx0-frggh0 and the
"last transition" header don't wrap. Frontends render in the
order returned by ListFrontends instead of Go map iteration
order so reload doesn't shuffle VIP order.
- IPng logo in the header, clickable, links to the git repo.
Header padding reduced so the logo can fill the bar up to the
separator. Version + commit + build date shown in the brand area
(fetched once from /view/api/version).
- "view" / "admin" mode tag moved to sit just left of the admin
toggle button so it reads as a pair.
- Prettier wired in as the web-side fixstyle via a new
fixstyle-web Make target that also runs from `make fixstyle`.
Added .prettierrc.json and .prettierignore; 8 existing files
were normalized in place.
- Fixed a "20555d ago" rendering bug: maglevd's synthetic
backend-replay events (from==to, at_unix_ns=0) were corrupting
the local cache's LastTransition via applyBackendTransition.
Backend synthetic events are now skipped entirely (refreshAll
covers initial hydration for backends), while frontend synthetic
events are still applied because FrontendInfo doesn't carry
state — the event is the only source.
- New MAGLEV_FRONTEND_USER / MAGLEV_FRONTEND_PASSWORD env vars.
When both are set and non-empty, /admin/ becomes a basic-auth-
protected SPA shell backed by the same embedded index.html as
/view/. The SPA detects its base path via a new stores/mode.ts
isAdmin constant and conditionally renders admin-only sections
(currently: the Event Stream / DebugPanel). When disabled,
/admin/ returns 404 (not 501) so operators who didn't configure
it see no teasing affordance, and the SPA's admin-toggle button
is hidden entirely via the admin_enabled flag on
/view/api/version.
- basicAuth uses crypto/subtle.ConstantTimeCompare for both user
and password so timing can't distinguish a wrong username from
a wrong password.
- New POST /admin/api/{maglevd}/backend/{name}/{pause|resume|
enable|disable} endpoint, gated by the same basic-auth
middleware as the SPA shell. maglevClient.BackendAction wraps
the four matching gRPC RPCs and returns a fresh BackendSnapshot;
the same transition lands via WatchEvents so every connected
browser converges through the normal reducer path.
- BackendActionsMenu Solid component: kebab (⋮) button in a new
trailing column rendered only in /admin. Click-outside and
Escape close the popover (document listeners installed only
while open). Actions are state-aware: up/down/unknown → pause,
disable; paused → resume, disable; disabled → enable;
removed → menu suppressed entirely. Busy indicator per action;
errors render inline under the item list.
- Structured audit log: every mutation logs an
admin-backend-action record with maglevd / backend / action /
resulting state.
- Renamed debian/vpp-maglevd.service → debian/vpp-maglev.service
to align naming with the new vpp-maglev-frontend.service
sibling. postinst handles upgrades by stopping + disabling any
lingering vpp-maglevd.service before enabling the renamed unit;
prerm stops both (the frontend unit is installed but not
enabled by default — operators opt in with systemctl enable).
- New debian/vpp-maglev-frontend.service (hardened:
NoNewPrivileges, ProtectSystem=strict, ProtectHome, PrivateTmp,
no capabilities). Reads the same /etc/default/vpp-maglev
conffile and expands MAGLEV_FRONTEND_ARGS via
`ExecStart=/usr/bin/maglev-frontend $MAGLEV_FRONTEND_ARGS` so
word-splitting works.
- docs/maglev-frontend.8 manpage documenting flags, endpoints,
and SSE reverse-proxy requirements.
- build-deb.sh: drops the commit hash from the .deb filename
(now vpp-maglev_<version>_<arch>.deb) and no longer takes the
commit as a CLI arg. Binaries continue to carry the commit via
-ldflags so `maglevd --version` et al are the authoritative
"which build is running" answer.
2 lines
28 KiB
JavaScript
2 lines
28 KiB
JavaScript
(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 l of s)if(l.type==="childList")for(const i of l.addedNodes)i.tagName==="LINK"&&i.rel==="modulepreload"&&r(i)}).observe(document,{childList:!0,subtree:!0});function n(s){const l={};return s.integrity&&(l.integrity=s.integrity),s.referrerPolicy&&(l.referrerPolicy=s.referrerPolicy),s.crossOrigin==="use-credentials"?l.credentials="include":s.crossOrigin==="anonymous"?l.credentials="omit":l.credentials="same-origin",l}function r(s){if(s.ep)return;s.ep=!0;const l=n(s);fetch(s.href,l)}})();const Je=!1,Xe=(e,t)=>e===t,I=Symbol("solid-proxy"),pe=Symbol("solid-track"),re={equals:Xe};let Ce=Te;const D=1,se=2,Oe={owned:null,cleanups:null,context:null,owner:null};var v=null;let he=null,ze=null,y=null,w=null,N=null,de=0;function ne(e,t){const n=y,r=v,s=e.length===0,l=t===void 0?r:t,i=s?Oe:{owned:null,cleanups:null,context:l?l.context:null,owner:l},o=s?e:()=>e(()=>L(()=>z(i)));v=i,y=null;try{return q(o,!0)}finally{y=n,v=r}}function _(e,t){t=t?Object.assign({},re,t):re;const n={value:e,observers:null,observerSlots:null,comparator:t.equals||void 0},r=s=>(typeof s=="function"&&(s=s(n.value)),Le(n,s));return[Ne.bind(n),r]}function S(e,t,n){const r=ve(e,t,!1,D);Z(r)}function Y(e,t,n){Ce=nt;const r=ve(e,t,!1,D);r.user=!0,N?N.push(r):Z(r)}function R(e,t,n){n=n?Object.assign({},re,n):re;const r=ve(e,t,!0,0);return r.observers=null,r.observerSlots=null,r.comparator=n.equals||void 0,Z(r),Ne.bind(r)}function Qe(e){return q(e,!1)}function L(e){if(y===null)return e();const t=y;y=null;try{return e()}finally{y=t}}function Ye(e,t,n){const r=Array.isArray(e);let s,l=n&&n.defer;return i=>{let o;if(r){o=Array(e.length);for(let f=0;f<e.length;f++)o[f]=e[f]()}else o=e();if(l)return l=!1,i;const a=L(()=>t(o,s,i));return s=o,a}}function Ze(e){Y(()=>L(e))}function Pe(e){return v===null||(v.cleanups===null?v.cleanups=[e]:v.cleanups.push(e)),e}function $e(){return y}function Ne(){if(this.sources&&this.state)if(this.state===D)Z(this);else{const e=w;w=null,q(()=>ie(this),!1),w=e}if(y){const e=this.observers?this.observers.length:0;y.sources?(y.sources.push(this),y.sourceSlots.push(e)):(y.sources=[this],y.sourceSlots=[e]),this.observers?(this.observers.push(y),this.observerSlots.push(y.sources.length-1)):(this.observers=[y],this.observerSlots=[y.sources.length-1])}return this.value}function Le(e,t,n){let r=e.value;return(!e.comparator||!e.comparator(r,t))&&(e.value=t,e.observers&&e.observers.length&&q(()=>{for(let s=0;s<e.observers.length;s+=1){const l=e.observers[s],i=he&&he.running;i&&he.disposed.has(l),(i?!l.tState:!l.state)&&(l.pure?w.push(l):N.push(l),l.observers&&je(l)),i||(l.state=D)}if(w.length>1e6)throw w=[],new Error},!1)),t}function Z(e){if(!e.fn)return;z(e);const t=de;et(e,e.value,t)}function et(e,t,n){let r;const s=v,l=y;y=v=e;try{r=e.fn(t)}catch(i){return e.pure&&(e.state=D,e.owned&&e.owned.forEach(z),e.owned=null),e.updatedAt=n+1,Be(i)}finally{y=l,v=s}(!e.updatedAt||e.updatedAt<=n)&&(e.updatedAt!=null&&"observers"in e?Le(e,r):e.value=r,e.updatedAt=n)}function ve(e,t,n,r=D,s){const l={fn:e,state:r,updatedAt:null,owned:null,sources:null,sourceSlots:null,cleanups:null,value:t,owner:v,context:v?v.context:null,pure:n};return v===null||v!==Oe&&(v.owned?v.owned.push(l):v.owned=[l]),l}function le(e){if(e.state===0)return;if(e.state===se)return ie(e);if(e.suspense&&L(e.suspense.inFallback))return e.suspense.effects.push(e);const t=[e];for(;(e=e.owner)&&(!e.updatedAt||e.updatedAt<de);)e.state&&t.push(e);for(let n=t.length-1;n>=0;n--)if(e=t[n],e.state===D)Z(e);else if(e.state===se){const r=w;w=null,q(()=>ie(e,t[0]),!1),w=r}}function q(e,t){if(w)return e();let n=!1;t||(w=[]),N?n=!0:N=[],de++;try{const r=e();return tt(n),r}catch(r){n||(N=null),w=null,Be(r)}}function tt(e){if(w&&(Te(w),w=null),e)return;const t=N;N=null,t.length&&q(()=>Ce(t),!1)}function Te(e){for(let t=0;t<e.length;t++)le(e[t])}function nt(e){let t,n=0;for(t=0;t<e.length;t++){const r=e[t];r.user?e[n++]=r:le(r)}for(t=0;t<n;t++)le(e[t])}function ie(e,t){e.state=0;for(let n=0;n<e.sources.length;n+=1){const r=e.sources[n];if(r.sources){const s=r.state;s===D?r!==t&&(!r.updatedAt||r.updatedAt<de)&&le(r):s===se&&ie(r,t)}}}function je(e){for(let t=0;t<e.observers.length;t+=1){const n=e.observers[t];n.state||(n.state=se,n.pure?w.push(n):N.push(n),n.observers&&je(n))}}function z(e){let t;if(e.sources)for(;e.sources.length;){const n=e.sources.pop(),r=e.sourceSlots.pop(),s=n.observers;if(s&&s.length){const l=s.pop(),i=n.observerSlots.pop();r<s.length&&(l.sourceSlots[i]=r,s[r]=l,n.observerSlots[r]=i)}}if(e.tOwned){for(t=e.tOwned.length-1;t>=0;t--)z(e.tOwned[t]);delete e.tOwned}if(e.owned){for(t=e.owned.length-1;t>=0;t--)z(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 rt(e){return e instanceof Error?e:new Error(typeof e=="string"?e:"Unknown error",{cause:e})}function Be(e,t=v){throw rt(e)}const st=Symbol("fallback");function ke(e){for(let t=0;t<e.length;t++)e[t]()}function lt(e,t,n={}){let r=[],s=[],l=[],i=0,o=t.length>1?[]:null;return Pe(()=>ke(l)),()=>{let a=e()||[],f=a.length,u,c;return a[pe],L(()=>{let p,$,m,C,M,k,A,x,T;if(f===0)i!==0&&(ke(l),l=[],r=[],s=[],i=0,o&&(o=[])),n.fallback&&(r=[st],s[0]=ne(te=>(l[0]=te,n.fallback())),i=1);else if(i===0){for(s=new Array(f),c=0;c<f;c++)r[c]=a[c],s[c]=ne(b);i=f}else{for(m=new Array(f),C=new Array(f),o&&(M=new Array(f)),k=0,A=Math.min(i,f);k<A&&r[k]===a[k];k++);for(A=i-1,x=f-1;A>=k&&x>=k&&r[A]===a[x];A--,x--)m[x]=s[A],C[x]=l[A],o&&(M[x]=o[A]);for(p=new Map,$=new Array(x+1),c=x;c>=k;c--)T=a[c],u=p.get(T),$[c]=u===void 0?-1:u,p.set(T,c);for(u=k;u<=A;u++)T=r[u],c=p.get(T),c!==void 0&&c!==-1?(m[c]=s[u],C[c]=l[u],o&&(M[c]=o[u]),c=$[c],p.set(T,c)):l[u]();for(c=k;c<f;c++)c in m?(s[c]=m[c],l[c]=C[c],o&&(o[c]=M[c],o[c](c))):s[c]=ne(b);s=s.slice(0,i=f),r=a.slice(0)}return s});function b(p){if(l[c]=p,o){const[$,m]=_(c);return o[c]=m,t(a[c],$)}return t(a[c])}}}function g(e,t){return L(()=>e(t||{}))}const it=e=>`Stale read from <${e}>.`;function V(e){const t="fallback"in e&&{fallback:()=>e.fallback};return R(lt(()=>e.each,e.children,t||void 0))}function E(e){const t=e.keyed,n=R(()=>e.when,void 0,void 0),r=t?n:R(n,void 0,{equals:(s,l)=>!s==!l});return R(()=>{const s=r();if(s){const l=e.children;return typeof l=="function"&&l.length>0?L(()=>l(t?s:()=>{if(!L(r))throw it("Show");return n()})):l}return e.fallback},void 0,void 0)}const B=e=>R(()=>e());function ot(e,t,n){let r=n.length,s=t.length,l=r,i=0,o=0,a=t[s-1].nextSibling,f=null;for(;i<s||o<l;){if(t[i]===n[o]){i++,o++;continue}for(;t[s-1]===n[l-1];)s--,l--;if(s===i){const u=l<r?o?n[o-1].nextSibling:n[l-o]:a;for(;o<l;)e.insertBefore(n[o++],u)}else if(l===o)for(;i<s;)(!f||!f.has(t[i]))&&t[i].remove(),i++;else if(t[i]===n[l-1]&&n[o]===t[s-1]){const u=t[--s].nextSibling;e.insertBefore(n[o++],t[i++].nextSibling),e.insertBefore(n[--l],u),t[s]=n[l]}else{if(!f){f=new Map;let c=o;for(;c<l;)f.set(n[c],c++)}const u=f.get(t[i]);if(u!=null)if(o<u&&u<l){let c=i,b=1,p;for(;++c<s&&c<l&&!((p=f.get(t[c]))==null||p!==u+b);)b++;if(b>u-o){const $=t[i];for(;o<u;)e.insertBefore(n[o++],$)}else e.replaceChild(n[o++],t[i++])}else i++;else t[i++].remove()}}}const Ae="_$DX_DELEGATE";function at(e,t,n,r={}){let s;return ne(l=>{s=l,t===document?e():d(t,e(),t.firstChild?null:void 0,n)},r.owner),()=>{s(),t.textContent=""}}function h(e,t,n,r){let s;const l=()=>{const o=document.createElement("template");return o.innerHTML=e,o.content.firstChild},i=()=>(s||(s=l())).cloneNode(!0);return i.cloneNode=i,i}function we(e,t=window.document){const n=t[Ae]||(t[Ae]=new Set);for(let r=0,s=e.length;r<s;r++){const l=e[r];n.has(l)||(n.add(l),t.addEventListener(l,ut))}}function P(e,t,n){n==null?e.removeAttribute(t):e.setAttribute(t,n)}function ct(e,t){t==null?e.removeAttribute("class"):e.className=t}function _e(e,t,n){return L(()=>e(t,n))}function d(e,t,n,r){if(n!==void 0&&!r&&(r=[]),typeof t!="function")return oe(e,t,r,n);S(s=>oe(e,t(),s,n),r)}function ut(e){let t=e.target;const n=`$$${e.type}`,r=e.target,s=e.currentTarget,l=a=>Object.defineProperty(e,"target",{configurable:!0,value:a}),i=()=>{const a=t[n];if(a&&!t.disabled){const f=t[`${n}Data`];if(f!==void 0?a.call(t,f,e):a.call(t,e),e.cancelBubble)return}return t.host&&typeof t.host!="string"&&!t.host._$host&&t.contains(e.target)&&l(t.host),!0},o=()=>{for(;i()&&(t=t._$host||t.parentNode||t.host););};if(Object.defineProperty(e,"currentTarget",{configurable:!0,get(){return t||document}}),e.composedPath){const a=e.composedPath();l(a[0]);for(let f=0;f<a.length-2&&(t=a[f],!!i());f++){if(t._$host){t=t._$host,o();break}if(t.parentNode===s)break}}else o();l(r)}function oe(e,t,n,r,s){for(;typeof n=="function";)n=n();if(t===n)return n;const l=typeof t,i=r!==void 0;if(e=i&&n[0]&&n[0].parentNode||e,l==="string"||l==="number"){if(l==="number"&&(t=t.toString(),t===n))return n;if(i){let o=n[0];o&&o.nodeType===3?o.data!==t&&(o.data=t):o=document.createTextNode(t),n=F(e,n,r,o)}else n!==""&&typeof n=="string"?n=e.firstChild.data=t:n=e.textContent=t}else if(t==null||l==="boolean")n=F(e,n,r);else{if(l==="function")return S(()=>{let o=t();for(;typeof o=="function";)o=o();n=oe(e,o,n,r)}),()=>n;if(Array.isArray(t)){const o=[],a=n&&Array.isArray(n);if(me(o,t,n,s))return S(()=>n=oe(e,o,n,r,!0)),()=>n;if(o.length===0){if(n=F(e,n,r),i)return n}else a?n.length===0?xe(e,o,r):ot(e,n,o):(n&&F(e),xe(e,o));n=o}else if(t.nodeType){if(Array.isArray(n)){if(i)return n=F(e,n,r,t);F(e,n,null,t)}else n==null||n===""||!e.firstChild?e.appendChild(t):e.replaceChild(t,e.firstChild);n=t}}return n}function me(e,t,n,r){let s=!1;for(let l=0,i=t.length;l<i;l++){let o=t[l],a=n&&n[e.length],f;if(!(o==null||o===!0||o===!1))if((f=typeof o)=="object"&&o.nodeType)e.push(o);else if(Array.isArray(o))s=me(e,o,a)||s;else if(f==="function")if(r){for(;typeof o=="function";)o=o();s=me(e,Array.isArray(o)?o:[o],Array.isArray(a)?a:[a])||s}else e.push(o),s=!0;else{const u=String(o);a&&a.nodeType===3&&a.data===u?e.push(a):e.push(document.createTextNode(u))}}return s}function xe(e,t,n=null){for(let r=0,s=t.length;r<s;r++)e.insertBefore(t[r],n)}function F(e,t,n,r){if(n===void 0)return e.textContent="";const s=r||document.createTextNode("");if(t.length){let l=!1;for(let i=t.length-1;i>=0;i--){const o=t[i];if(s!==o){const a=o.parentNode===e;!l&&!i?a?e.replaceChild(s,o):e.insertBefore(s,n):a&&o.remove()}else l=!0}}else e.insertBefore(s,n);return[s]}async function De(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 Ie(){return De("/view/api/state")}function ft(){return De("/view/api/version")}const ae=Symbol("store-raw"),U=Symbol("store-node"),O=Symbol("store-has"),Me=Symbol("store-self");function Fe(e){let t=e[I];if(!t&&(Object.defineProperty(e,I,{value:t=new Proxy(e,ht)}),!Array.isArray(e))){const n=Object.keys(e),r=Object.getOwnPropertyDescriptors(e);for(let s=0,l=n.length;s<l;s++){const i=n[s];r[i].get&&Object.defineProperty(e,i,{enumerable:r[i].enumerable,get:r[i].get.bind(t)})}}return t}function W(e){let t;return e!=null&&typeof e=="object"&&(e[I]||!(t=Object.getPrototypeOf(e))||t===Object.prototype||Array.isArray(e))}function K(e,t=new Set){let n,r,s,l;if(n=e!=null&&e[ae])return n;if(!W(e)||t.has(e))return e;if(Array.isArray(e)){Object.isFrozen(e)?e=e.slice(0):t.add(e);for(let i=0,o=e.length;i<o;i++)s=e[i],(r=K(s,t))!==s&&(e[i]=r)}else{Object.isFrozen(e)?e=Object.assign({},e):t.add(e);const i=Object.keys(e),o=Object.getOwnPropertyDescriptors(e);for(let a=0,f=i.length;a<f;a++)l=i[a],!o[l].get&&(s=e[l],(r=K(s,t))!==s&&(e[l]=r))}return e}function ce(e,t){let n=e[t];return n||Object.defineProperty(e,t,{value:n=Object.create(null)}),n}function Q(e,t,n){if(e[t])return e[t];const[r,s]=_(n,{equals:!1,internal:!0});return r.$=s,e[t]=r}function dt(e,t){const n=Reflect.getOwnPropertyDescriptor(e,t);return!n||n.get||!n.configurable||t===I||t===U||(delete n.value,delete n.writable,n.get=()=>e[I][t]),n}function Re(e){$e()&&Q(ce(e,U),Me)()}function gt(e){return Re(e),Reflect.ownKeys(e)}const ht={get(e,t,n){if(t===ae)return e;if(t===I)return n;if(t===pe)return Re(e),n;const r=ce(e,U),s=r[t];let l=s?s():e[t];if(t===U||t===O||t==="__proto__")return l;if(!s){const i=Object.getOwnPropertyDescriptor(e,t);$e()&&(typeof l!="function"||e.hasOwnProperty(t))&&!(i&&i.get)&&(l=Q(r,t,l)())}return W(l)?Fe(l):l},has(e,t){return t===ae||t===I||t===pe||t===U||t===O||t==="__proto__"?!0:($e()&&Q(ce(e,O),t)(),t in e)},set(){return!0},deleteProperty(){return!0},ownKeys:gt,getOwnPropertyDescriptor:dt};function H(e,t,n,r=!1){if(!r&&e[t]===n)return;const s=e[t],l=e.length;n===void 0?(delete e[t],e[O]&&e[O][t]&&s!==void 0&&e[O][t].$()):(e[t]=n,e[O]&&e[O][t]&&s===void 0&&e[O][t].$());let i=ce(e,U),o;if((o=Q(i,t,s))&&o.$(()=>n),Array.isArray(e)&&e.length!==l){for(let a=e.length;a<l;a++)(o=i[a])&&o.$();(o=Q(i,"length",l))&&o.$(e.length)}(o=i[Me])&&o.$()}function Ue(e,t){const n=Object.keys(t);for(let r=0;r<n.length;r+=1){const s=n[r];H(e,s,t[s])}}function bt(e,t){if(typeof t=="function"&&(t=t(e)),t=K(t),Array.isArray(t)){if(e===t)return;let n=0,r=t.length;for(;n<r;n++){const s=t[n];e[n]!==s&&H(e,n,s)}H(e,"length",r)}else Ue(e,t)}function J(e,t,n=[]){let r,s=e;if(t.length>1){r=t.shift();const i=typeof r,o=Array.isArray(e);if(Array.isArray(r)){for(let a=0;a<r.length;a++)J(e,[r[a]].concat(t),n);return}else if(o&&i==="function"){for(let a=0;a<e.length;a++)r(e[a],a)&&J(e,[a].concat(t),n);return}else if(o&&i==="object"){const{from:a=0,to:f=e.length-1,by:u=1}=r;for(let c=a;c<=f;c+=u)J(e,[c].concat(t),n);return}else if(t.length>1){J(e[r],t,[r].concat(n));return}s=e[r],n=[r].concat(n)}let l=t[0];typeof l=="function"&&(l=l(s,n),l===s)||r===void 0&&l==null||(l=K(l),r===void 0||W(s)&&W(l)&&!Array.isArray(l)?Ue(s,l):H(e,r,l))}function pt(...[e,t]){const n=K(e||{}),r=Array.isArray(n),s=Fe(n);function l(...i){Qe(()=>{r&&i.length===1?bt(n,i[0]):J(n,i)})}return[s,l]}const ue=new WeakMap,Ve={get(e,t){if(t===ae)return e;const n=e[t];let r;return W(n)?ue.get(n)||(ue.set(n,r=new Proxy(n,Ve)),r):n},set(e,t,n){return H(e,t,K(n)),!0},deleteProperty(e,t){return H(e,t,void 0,!0),!0}};function ee(e){return t=>{if(W(t)){let n;(n=ue.get(t))||ue.set(t,n=new Proxy(t,Ve)),e(n)}return t}}const[$t,mt]=_(0);setInterval(()=>mt(e=>e+1),5e3);const[fe,G]=pt({byName:{}});function We(e){const t={};for(const n of e)t[n.maglevd.name]=n;G({byName:t})}function yt(e,t){G(ee(n=>{const r=n.byName[e];if(!r)return;const s=r.backends.find(l=>l.name===t.backend);s&&(s.state=t.transition.to,s.last_transition=t.transition,s.transitions||(s.transitions=[]),s.transitions.push(t.transition),s.transitions.length>20&&(s.transitions=s.transitions.slice(s.transitions.length-20)))}))}function vt(e,t){G(ee(n=>{const r=n.byName[e];if(!r)return;const s=r.frontends.find(l=>l.name===t.frontend);s&&(s.state=t.transition.to)}))}function wt(e,t){G(ee(n=>{const r=n.byName[e];r&&(r.vpp_state=t)}))}function _t(e,t){G(ee(n=>{const r=n.byName[e];r&&(r.maglevd.connected=t.connected,r.maglevd.last_error=t.last_error)}))}function be(e,t,n){G(ee(r=>{const s=r.byName[e];if(!s)return;const l=s.backends.find(i=>i.address===t);if(l)for(const i of s.frontends)for(const o of i.pools)for(const a of o.backends)a.name===l.name&&(a.effective_weight=n)}))}function St(e){if($t(),!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 l=s%60,i=Math.floor(s/60);if(i<1)return`${l}m${r}s ago`;const o=i%24,a=Math.floor(i/24);return a<1?`${i}h${l}m ago`:`${a}d${o}h ago`}const Ee=500,[ye,kt]=_([]);function At(e){kt(t=>{const n=[...t,e];return n.length>Ee?n.slice(n.length-Ee):n})}function xt(){const e=new EventSource("/view/api/events");return e.onmessage=t=>{try{const n=JSON.parse(t.data);Et(n)}catch(n){console.error("sse parse error",n,t.data)}},e.addEventListener("resync",async()=>{try{const t=await Ie();We(t)}catch(t){console.error("resync refetch failed",t)}}),e.onerror=t=>{console.debug("sse error, browser will reconnect",t)},e}function Et(e){switch(At(e),e.type){case"backend":yt(e.maglevd,e.payload);break;case"frontend":vt(e.maglevd,e.payload);break;case"maglevd-status":_t(e.maglevd,e.payload);break;case"vpp-status":wt(e.maglevd,e.payload.state);break;case"log":Ct(e.maglevd,e.payload);break}}function Ct(e,t){if(!t.msg.startsWith("vpp-lb-sync-as-"))return;const n=t.attrs??{},r=n.address;if(r)switch(t.msg){case"vpp-lb-sync-as-added":be(e,r,Number(n.weight??0));break;case"vpp-lb-sync-as-removed":be(e,r,0);break;case"vpp-lb-sync-as-weight-updated":be(e,r,Number(n.to??0));break}}const[ge,Ke]=_(void 0);var Ot=h("<nav class=scope-selector>"),Pt=h("<button class=scope-tab><span class=dot>");const Nt=()=>{const e=()=>Object.keys(fe.byName).sort();return(()=>{var t=Ot();return d(t,g(V,{get each(){return e()},children:n=>{const r=()=>fe.byName[n],s=()=>r()?.maglevd.connected??!1;return(()=>{var l=Pt();return l.firstChild,l.$$click=()=>Ke(n),d(l,n,null),S(i=>{var o=ge()===n,a=!!s(),f=!s(),u=r()?.maglevd.address??"";return o!==i.e&&l.classList.toggle("active",i.e=o),a!==i.t&&l.classList.toggle("connected",i.t=a),f!==i.a&&l.classList.toggle("disconnected",i.a=f),u!==i.o&&P(l,"title",i.o=u),i},{e:void 0,t:void 0,a:void 0,o:void 0}),l})()}})),t})()};we(["click"]);var Lt=h("<span class=status-badge>");const He=e=>(()=>{var t=Lt();return d(t,()=>e.label??e.state),S(()=>P(t,"data-state",e.state)),t})();var Tt=h("<span class=probe-heartbeat>");const jt=e=>{const[t,n]=_(!1);return Y(()=>{const r=ye();if(r.length===0)return;const s=r[r.length-1];if(s.type!=="log"||s.maglevd!==e.maglevd)return;const l=s.payload;l.attrs?.backend===e.backend&&(l.msg==="probe-start"?n(!0):l.msg==="probe-done"&&n(!1))}),(()=>{var r=Tt();return d(r,()=>t()?"❤️":"·"),S(()=>r.classList.toggle("in-flight",!!t())),r})()};var Bt=h("<span class=flash-target>");const X=e=>{let t;return Y(Ye(()=>e.value,()=>{t?.animate([{transform:"scale(1)",backgroundColor:"#facc15",boxShadow:"0 0 0 2px #facc15",offset:0},{transform:"scale(1.35)",backgroundColor:"#facc15",boxShadow:"0 0 0 4px #facc15",offset:.18},{transform:"scale(1)",backgroundColor:"#facc15",boxShadow:"0 0 0 2px #facc15",offset:.5},{transform:"scale(1)",backgroundColor:"transparent",boxShadow:"0 0 0 0 transparent",offset:1}],{duration:1500,easing:"ease-out"})},{defer:!0})),(()=>{var n=Bt(),r=t;return typeof r=="function"?_e(r,n):t=n,d(n,()=>e.children??e.value),n})()};async function Dt(e,t,n){const r=`/admin/api/${encodeURIComponent(e)}/backend/${encodeURIComponent(t)}/${n}`,s=await fetch(r,{method:"POST",credentials:"same-origin"});if(!s.ok){const l=(await s.text()).trim();throw new Error(l||`${s.status} ${s.statusText}`)}return await s.json()}var It=h("<p class=kebab-error>"),Mt=h("<div class=kebab-menu role=menu>"),Ft=h('<div class=kebab-wrap><button type=button class=kebab-btn aria-haspopup=menu title="backend actions">⋮'),Rt=h("<button type=button class=kebab-item role=menuitem>");function Ut(e){switch(e){case"up":case"down":case"unknown":return[{label:"pause",action:"pause"},{label:"disable",action:"disable"}];case"paused":return[{label:"resume",action:"resume"},{label:"disable",action:"disable"}];case"disabled":return[{label:"enable",action:"enable"}];default:return[]}}const Vt=e=>{const[t,n]=_(!1),[r,s]=_(),[l,i]=_();let o;const a=()=>Ut(e.state);Y(()=>{if(!t())return;const u=b=>{o&&(o.contains(b.target)||n(!1))},c=b=>{b.key==="Escape"&&n(!1)};document.addEventListener("mousedown",u),document.addEventListener("keydown",c),Pe(()=>{document.removeEventListener("mousedown",u),document.removeEventListener("keydown",c)})});const f=async u=>{s(u),i(void 0);try{await Dt(e.maglevd,e.backend,u),n(!1)}catch(c){i(`${c}`)}finally{s(void 0)}};return g(E,{get when(){return a().length>0},get children(){var u=Ft(),c=u.firstChild,b=o;return typeof b=="function"?_e(b,u):o=u,c.$$click=p=>{p.stopPropagation(),n($=>!$)},d(u,g(E,{get when(){return t()},get children(){var p=Mt();return d(p,g(V,{get each(){return a()},children:$=>(()=>{var m=Rt();return m.$$click=()=>f($.action),d(m,(()=>{var C=B(()=>r()===$.action);return()=>C()?`${$.label}…`:$.label})()),S(()=>m.disabled=r()!==void 0),m})()}),null),d(p,g(E,{get when(){return l()},get children(){var $=It();return d($,l),$}}),null),p}}),null),S(()=>P(c,"aria-expanded",t())),u}})};we(["click"]);const j=window.location.pathname.startsWith("/admin");var Wt=h("<td class=actions>"),Kt=h("<tr class=backend-row><td class=backend-name></td><td class=backend-address></td><td></td><td class=numeric></td><td class=numeric></td><td class=age>"),Ht=h("<span class=tag>[disabled]");const qt=e=>{const t=()=>e.backend;return(()=>{var n=Kt(),r=n.firstChild,s=r.nextSibling,l=s.nextSibling,i=l.nextSibling,o=i.nextSibling,a=o.nextSibling;return d(r,g(jt,{get maglevd(){return e.maglevd},get backend(){return t().name}}),null),d(r,()=>t().name,null),d(r,(()=>{var f=B(()=>!t().enabled);return()=>f()&&Ht()})(),null),d(s,()=>t().address),d(l,g(X,{get value(){return t().state},get children(){return g(He,{get state(){return t().state}})}})),d(i,g(X,{get value(){return e.poolBackend.weight}})),d(o,g(X,{get value(){return e.poolBackend.effective_weight}})),d(a,()=>St(t().last_transition)),d(n,g(E,{when:j,get children(){var f=Wt();return d(f,g(Vt,{get maglevd(){return e.maglevd},get backend(){return t().name},get state(){return t().state}})),f}}),null),S(()=>P(n,"data-state",t().state)),n})()};var Gt=h("<section class=frontend-card><header class=frontend-header><h2></h2><div class=frontend-meta><span class=addr>:</span><span class=proto>"),Jt=h("<span class=tag>sticky"),Xt=h("<p class=frontend-desc>"),zt=h("<th class=actions>"),Qt=h("<div class=pool-block><h3 class=pool-name>pool: </h3><table class=backend-table><thead><tr><th>backend</th><th>address</th><th>state</th><th class=numeric>weight</th><th class=numeric>effective</th><th>last transition</th></tr></thead><tbody>");const Yt=e=>{const t=()=>Object.fromEntries(e.snap.backends.map(r=>[r.name,r])),n=()=>e.frontend;return(()=>{var r=Gt(),s=r.firstChild,l=s.firstChild,i=l.nextSibling,o=i.firstChild,a=o.firstChild,f=o.nextSibling;return d(l,()=>n().name,null),d(l,g(X,{get value(){return n().state??"unknown"},get children(){return g(He,{get state(){return n().state??"unknown"}})}}),null),d(o,()=>n().address,a),d(o,()=>n().port,null),d(f,()=>n().protocol.toUpperCase()),d(i,(()=>{var u=B(()=>!!n().src_ip_sticky);return()=>u()&&Jt()})(),null),d(s,(()=>{var u=B(()=>!!n().description);return()=>u()&&(()=>{var c=Xt();return d(c,()=>n().description),c})()})(),null),d(r,g(V,{get each(){return n().pools},children:u=>(()=>{var c=Qt(),b=c.firstChild;b.firstChild;var p=b.nextSibling,$=p.firstChild,m=$.firstChild,C=m.firstChild,M=C.nextSibling,k=M.nextSibling,A=k.nextSibling,x=A.nextSibling;x.nextSibling;var T=$.nextSibling;return d(b,()=>u.name,null),d(m,g(E,{when:j,get children(){return zt()}}),null),d(T,g(V,{get each(){return u.backends},children:te=>{const Se=t()[te.name];return Se?g(qt,{get maglevd(){return e.snap.maglevd.name},backend:Se,poolBackend:te}):null}})),c})()}),null),r})()};var Zt=h("<details class=zippy><summary></summary><div class=zippy-body>");const qe=e=>(()=>{var t=Zt(),n=t.firstChild,r=n.nextSibling;return d(n,()=>e.title),d(r,()=>e.children),S(()=>t.open=e.open),t})();var en=h("<span class=vpp-badge>"),tn=h("<span class=zippy-title>VPP"),nn=h("<p class=empty>No VPP information available."),rn=h("<dl class=kv><dt>version</dt><dd></dd><dt>build date</dt><dd></dd><dt>pid</dt><dd></dd><dt>booted</dt><dd></dd><dt>connected</dt><dd>");const sn=e=>{const t=()=>e.info?.boottime_ns?new Date(e.info.boottime_ns/1e6).toISOString():"",n=()=>e.info?.connecttime_ns?new Date(e.info.connecttime_ns/1e6).toISOString():"",r=()=>e.state==="connected"?"connected":"disconnected",s=(()=>{var l=tn();return l.firstChild,d(l,g(X,{get value(){return r()},get children(){var i=en();return d(i,r),S(()=>P(i,"data-state",r())),i}}),null),l})();return g(qe,{title:s,get children(){return g(E,{get when(){return e.info},get fallback(){return nn()},children:l=>(()=>{var i=rn(),o=i.firstChild,a=o.nextSibling,f=a.nextSibling,u=f.nextSibling,c=u.nextSibling,b=c.nextSibling,p=b.nextSibling,$=p.nextSibling,m=$.nextSibling,C=m.nextSibling;return d(a,()=>l().version),d(u,()=>l().build_date),d(b,()=>l().pid),d($,t),d(C,n),i})()})}})};var ln=h("<main class=overview>"),on=h("<p class=empty>No maglevd selected."),an=h('<div class="banner warn"> disconnected'),cn=h("<div class=frontend-grid>");const un=()=>{const e=()=>{const t=ge();return t?fe.byName[t]:void 0};return(()=>{var t=ln();return d(t,g(E,{get when(){return e()},get fallback(){return on()},children:n=>[g(E,{get when(){return!n().maglevd.connected},get children(){var r=an(),s=r.firstChild;return d(r,()=>n().maglevd.name,s),d(r,(()=>{var l=B(()=>!!n().maglevd.last_error);return()=>l()&&`: ${n().maglevd.last_error}`})(),null),r}}),(()=>{var r=cn();return d(r,g(V,{get each(){return n().frontends},children:s=>g(Yt,{get snap(){return n()},frontend:s})})),r})(),g(sn,{get info(){return n().vpp_info},get state(){return n().vpp_state}})]})),t})()};var fn=h("<ol class=event-tail>"),dn=h("<div class=debug-toolbar><label><input type=checkbox>all maglevds</label><button></button><span class=count> events"),gn=h("<li>");const hn=()=>{const[e,t]=_(!1),[n,r]=_(!1),[s,l]=_([]),i=R(()=>{const f=n()?s():ye();if(e())return f;const u=ge();return u?f.filter(c=>c.maglevd===u):f}),o=()=>{n()?r(!1):(l([...ye()]),r(!0))};let a;return Y(()=>{i(),!n()&&a&&(a.scrollTop=a.scrollHeight)}),g(qe,{title:"Event stream",get children(){return[(()=>{var f=fn(),u=a;return typeof u=="function"?_e(u,f):a=f,d(f,g(V,{get each(){return i()},children:c=>(()=>{var b=gn();return d(b,()=>$n(c)),S(p=>{var $=`event-row event-${c.type}`,m=!!bn(c);return $!==p.e&&ct(b,p.e=$),m!==p.t&&b.classList.toggle("event-sync",p.t=m),p},{e:void 0,t:void 0}),b})()})),f})(),(()=>{var f=dn(),u=f.firstChild,c=u.firstChild,b=u.nextSibling,p=b.nextSibling,$=p.firstChild;return c.addEventListener("change",m=>t(m.currentTarget.checked)),b.$$click=o,d(b,()=>n()?"resume":"pause"),d(p,()=>i().length,$),S(()=>c.checked=e()),f})()]}})};function bn(e){return e.type!=="log"?!1:e.payload.msg.startsWith("vpp-lb-sync-")}function pn(e){if(!e)return"";const t=["vip","protocol","port","address","weight","from","to","encap","src-ip-sticky","flush"],n=[],r=new Set;for(const s of t)s in e&&(n.push(`${s}=${e[s]}`),r.add(s));for(const[s,l]of Object.entries(e))r.has(s)||n.push(`${s}=${l}`);return n.join(" ")}function $n(e){const t=new Date(e.at_unix_ns/1e6).toISOString().substring(11,23),n=`[${e.maglevd}]`;switch(e.type){case"backend":{const r=e.payload;return`${t} ${n} backend ${r.backend}: ${r.transition.from} → ${r.transition.to}`}case"frontend":{const r=e.payload;return`${t} ${n} frontend ${r.frontend}: ${r.transition.from} → ${r.transition.to}`}case"log":{const r=e.payload;if(r.msg.startsWith("vpp-lb-sync-"))return`${t} ${n} ${r.msg} ${pn(r.attrs)}`.trimEnd();const s=r.attrs?Object.entries(r.attrs).map(([l,i])=>`${l}=${i}`).join(" "):"";return`${t} ${n} ${r.level} ${r.msg} ${s}`.trimEnd()}case"maglevd-status":return`${t} ${n} maglevd status: ${JSON.stringify(e.payload)}`;default:return`${t} ${n} ${e.type}`}}we(["click"]);const mn="/view/assets/logo-bimi-Bguc6E_L.svg";var yn=h("<span class=mode-tag>"),vn=h("<a class=admin-toggle>"),wn=h('<div class=app><header class=app-header><div class=brand><a class=brand-logo href=https://git.ipng.ch/ipng/vpp-maglev/ target=_blank rel=noopener title="vpp-maglev on git.ipng.ch"><img alt=IPng></a><strong>maglev'),_n=h("<span class=version> (<!>)"),Sn=h('<div class="banner err">'),kn=h("<p class=loading>Loading…");const An=()=>{const[e,t]=_(),[n,r]=_();Ze(async()=>{try{const[l,i]=await Promise.all([Ie(),ft()]);We(l),r(i),!ge()&&l.length>0&&Ke(l[0].maglevd.name),xt()}catch(l){t(`${l}`)}});const s=()=>n()?.admin_enabled===!0;return(()=>{var l=wn(),i=l.firstChild,o=i.firstChild,a=o.firstChild,f=a.firstChild;return a.nextSibling,P(f,"src",mn),d(o,(()=>{var u=B(()=>!!n());return()=>u()&&(()=>{var c=_n(),b=c.firstChild,p=b.nextSibling;return p.nextSibling,d(c,()=>n().version,b),d(c,()=>n().commit,p),S(()=>P(c,"title",`commit ${n().commit} · built ${n().date}`)),c})()})(),null),d(i,g(Nt,{}),null),d(i,g(E,{get when(){return j||s()},get children(){var u=yn();return d(u,j?"admin":"view"),u}}),null),d(i,g(E,{get when(){return s()},get children(){var u=vn();return P(u,"href",j?"/view/":"/admin/"),P(u,"title",j?"exit admin mode":"enter admin mode"),d(u,j?"exit admin":"admin…"),u}}),null),d(l,(()=>{var u=B(()=>!!e());return()=>u()&&(()=>{var c=Sn();return d(c,e),c})()})(),null),d(l,(()=>{var u=B(()=>!e()&&Object.keys(fe.byName).length===0);return()=>u()&&kn()})(),null),d(l,g(un,{}),null),d(l,g(E,{when:j,get children(){return g(hn,{})}}),null),l})()},Ge=document.getElementById("root");if(!Ge)throw new Error("no #root element");at(()=>g(An,{}),Ge);
|