diff --git a/.env.example b/.env.example index 777277a..3dedbd2 100644 --- a/.env.example +++ b/.env.example @@ -2,7 +2,7 @@ # Nonempty GETBIBLE_* application settings override values saved by the menu. # Empty optional values leave the saved setting/default in control. GETBIBLE_IMAGE_REPOSITORY=ghcr.io/getbible/api -GETBIBLE_IMAGE_TAG=3.2.8 +GETBIBLE_IMAGE_TAG=3.2.9 GETBIBLE_DATA_ROOT=/srv/getbible-data GETBIBLE_HOSTNAME=getbible TZ=UTC diff --git a/Dockerfile b/Dockerfile index f36e6da..5a524bb 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,7 +1,7 @@ # syntax=docker/dockerfile:1 FROM ubuntu:24.04 -ARG GETBIBLE_VERSION=3.2.8 +ARG GETBIBLE_VERSION=3.2.9 ARG GETBIBLE_REVISION=unknown ARG GETBIBLE_IMAGE_PYTHONS= LABEL org.opencontainers.image.title="getBible API" \ diff --git a/VERSION b/VERSION index f092941..e650c01 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -3.2.8 +3.2.9 diff --git a/compose.yaml b/compose.yaml index f2e5a17..100c09a 100644 --- a/compose.yaml +++ b/compose.yaml @@ -4,7 +4,7 @@ services: getbible: # Production deployment only pulls the prebuilt image. No checkout # or Dockerfile is required on the server. See docs/DOCKER.md for login. - image: ${GETBIBLE_IMAGE_REPOSITORY:-ghcr.io/getbible/api}:${GETBIBLE_IMAGE_TAG:-3.2.8} + image: ${GETBIBLE_IMAGE_REPOSITORY:-ghcr.io/getbible/api}:${GETBIBLE_IMAGE_TAG:-3.2.9} restart: unless-stopped hostname: ${GETBIBLE_HOSTNAME:-getbible} user: "0:0" diff --git a/docs/LOGGING.md b/docs/LOGGING.md index 5ecc2fd..92ee89b 100644 --- a/docs/LOGGING.md +++ b/docs/LOGGING.md @@ -256,11 +256,31 @@ reminder or sustained recovery rather than resending an unchanged message at every short cooldown. Missing/stale measurements never imply recovery. Inspect open incidents and the collector journal when capacity is still saturated. +If unread bytes are zero while committed closed transport occupies the allowance, +ingestion has caught up and reclamation needs attention. The capacity report shows +these bytes separately and labels reclamation as pending, blocked or stale. The +last `spool_cleanup` observation records attempted/removed/retained bytes, reason +counts and bounded failure details including errno. An open writer points to a +producer that has not closed its rotated file; inspect nginx reopen failures. +The collector retries nginx reopening for writer-held nginx archives. For an idle +runtime producer, Gunicorn's USR1 handling reopens application log files in its +master and workers. Administrative event producers close their append streams +after each event, so idle dashboard auditing no longer holds rotated files open. +Unsupported leases or denied permissions point to the mounted filesystem or +service identity rather than a need for a larger budget. Inspect current evidence +with `getbible capacity --json` and `journalctl -u getbible-telemetry --since '1 hour ago'`. +Safe cleanup retries automatically; do not delete a transport file merely because +the unread count is zero, since a producer may still append to it. + Recommendations use measured demand/high-water usage, saturation observations and available headroom, and explain their headroom assumptions. A growing or stalled unread backlog requires fixing collection before relying on a larger spool allowance. Advice is withheld when evidence/headroom is insufficient; no limit is automatically raised and no unread history is discarded to silence an alarm. +Transport sizing uses the peak active/pending demand after subtracting committed +closed spools; the total occupied high-water remains visible as pressure evidence. +Older observations that lack this distinction do not justify an increased +allowance: sizing waits for at least 60 observed seconds and six new samples. Configured values controlled by deployment environment or host cgroups are shown as such. Collector restart retains the incident, while real recovery clears it after the configured stability interval. diff --git a/src/apps/dashboard/static/assets/index-BlYHjtRi.js b/src/apps/dashboard/static/assets/index-BvBBoPHR.js similarity index 81% rename from src/apps/dashboard/static/assets/index-BlYHjtRi.js rename to src/apps/dashboard/static/assets/index-BvBBoPHR.js index b9fc418..84cafeb 100644 --- a/src/apps/dashboard/static/assets/index-BlYHjtRi.js +++ b/src/apps/dashboard/static/assets/index-BvBBoPHR.js @@ -7,4 +7,4 @@ var e=Object.create,t=Object.defineProperty,n=Object.getOwnPropertyDescriptor,r= Error generating stack: `+e.message+` `+e.stack}}var ze=Object.prototype.hasOwnProperty,Be=t.unstable_scheduleCallback,Ve=t.unstable_cancelCallback,He=t.unstable_shouldYield,Ue=t.unstable_requestPaint,We=t.unstable_now,Ge=t.unstable_getCurrentPriorityLevel,Ke=t.unstable_ImmediatePriority,qe=t.unstable_UserBlockingPriority,Je=t.unstable_NormalPriority,Ye=t.unstable_LowPriority,Xe=t.unstable_IdlePriority,Ze=t.log,Qe=t.unstable_setDisableYieldValue,$e=null,et=null;function tt(e){if(typeof Ze==`function`&&Qe(e),et&&typeof et.setStrictMode==`function`)try{et.setStrictMode($e,e)}catch{}}var nt=Math.clz32?Math.clz32:at,rt=Math.log,it=Math.LN2;function at(e){return e>>>=0,e===0?32:31-(rt(e)/it|0)|0}var ot=256,st=262144,ct=4194304;function lt(e){var t=e&42;if(t!==0)return t;switch(e&-e){case 1:return 1;case 2:return 2;case 4:return 4;case 8:return 8;case 16:return 16;case 32:return 32;case 64:return 64;case 128:return 128;case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:return e&-e;case 262144:case 524288:case 1048576:case 2097152:return e&3932160;case 4194304:case 8388608:case 16777216:case 33554432:return e&62914560;case 67108864:return 67108864;case 134217728:return 134217728;case 268435456:return 268435456;case 536870912:return 536870912;case 1073741824:return 0;default:return e}}function ut(e,t,n){var r=e.pendingLanes;if(r===0)return 0;var i=0,a=e.suspendedLanes,o=e.pingedLanes;e=e.warmLanes;var s=r&134217727;return s===0?(s=r&~a,s===0?o===0?n||(n=r&~e,n!==0&&(i=lt(n))):i=lt(o):i=lt(s)):(r=s&~a,r===0?(o&=s,o===0?n||(n=s&~e,n!==0&&(i=lt(n))):i=lt(o)):i=lt(r)),i===0?0:t!==0&&t!==i&&(t&a)===0&&(a=i&-i,n=t&-t,a>=n||a===32&&n&4194048)?t:i}function dt(e,t){return(e.pendingLanes&~(e.suspendedLanes&~e.pingedLanes)&t)===0}function ft(e,t){t&8&&(t|=t&32);var n=e.entangledLanes;if(n!==0)for(e=e.entanglements,n&=t;0n;n++)t.push(e);return t}function gt(e,t){e.pendingLanes|=t,t!==268435456&&(e.suspendedLanes=0,e.pingedLanes=0,e.warmLanes=0)}function _t(e,t,n,r,i,a){var o=e.pendingLanes;e.pendingLanes=n,e.suspendedLanes=0,e.pingedLanes=0,e.warmLanes=0,e.expiredLanes&=n,e.entangledLanes&=n,e.errorRecoveryDisabledLanes&=n,e.shellSuspendCounter=0;var s=e.entanglements,c=e.expirationTimes,l=e.hiddenUpdates;for(n=o&~n;0=gr),yr=` `,br=!1;function xr(e,t){switch(e){case`keyup`:return mr.indexOf(t.keyCode)!==-1;case`keydown`:return t.keyCode!==229;case`keypress`:case`mousedown`:case`focusout`:return!0;default:return!1}}function Sr(e){return e=e.detail,typeof e==`object`&&`data`in e?e.data:null}var Cr=!1;function wr(e,t){switch(e){case`compositionend`:return Sr(t);case`keypress`:return t.which===32?(br=!0,yr):null;case`textInput`:return e=t.data,e===yr&&br?null:e;default:return null}}function Tr(e,t){if(Cr)return e===`compositionend`||!hr&&xr(e,t)?(e=Ln(),In=Fn=Pn=null,Cr=!1,e):null;switch(e){case`paste`:return null;case`keypress`:if(!(t.ctrlKey||t.altKey||t.metaKey)||t.ctrlKey&&t.altKey){if(t.char&&1=t)return{node:n,offset:t-e};e=r}a:{for(;n;){if(n.nextSibling){n=n.nextSibling;break a}n=n.parentNode}n=void 0}n=Jr(n)}}function Xr(e,t){return e&&t?e===t?!0:e&&e.nodeType===3?!1:t&&t.nodeType===3?Xr(e,t.parentNode):`contains`in e?e.contains(t):e.compareDocumentPosition?!!(e.compareDocumentPosition(t)&16):!1:!1}function Zr(e){e=e!=null&&e.ownerDocument!=null&&e.ownerDocument.defaultView!=null?e.ownerDocument.defaultView:window;for(var t=qr(e.document);t instanceof e.HTMLIFrameElement;){try{var n=typeof t.contentWindow.location.href==`string`}catch{n=!1}if(n)e=t.contentWindow;else break;t=qr(e.document)}return t}function Qr(e){var t=e&&e.nodeName&&e.nodeName.toLowerCase();return t&&(t===`input`&&(e.type===`text`||e.type===`search`||e.type===`tel`||e.type===`url`||e.type===`password`)||t===`textarea`||e.contentEditable===`true`)}var $r=jn&&`documentMode`in document&&11>=document.documentMode,ei=null,ti=null,ni=null,ri=!1;function ii(e,t,n){var r=n.window===n?n.document:n.nodeType===9?n:n.ownerDocument;ri||ei==null||ei!==qr(r)||(r=ei,`selectionStart`in r&&Qr(r)?r={start:r.selectionStart,end:r.selectionEnd}:(r=(r.ownerDocument&&r.ownerDocument.defaultView||window).getSelection(),r={anchorNode:r.anchorNode,anchorOffset:r.anchorOffset,focusNode:r.focusNode,focusOffset:r.focusOffset}),ni&&Kr(ni,r)||(ni=r,r=Jf(ti,`onSelect`),0>=o,i-=o,ea=1<<32-nt(t)+i|n<h?(g=d,d=null):g=d.sibling;var _=p(i,d,s[h],c);if(_===null){d===null&&(d=g);break}e&&d&&_.alternate===null&&t(i,d),a=o(_,a,h),u===null?l=_:u.sibling=_,u=_,d=g}if(h===s.length)return n(i,d),L&&na(i,h),l;if(d===null){for(;hg?(_=h,h=null):_=h.sibling;var y=p(a,h,v.value,l);if(y===null){h===null&&(h=_);break}e&&h&&y.alternate===null&&t(a,h),s=o(y,s,g),d===null?u=y:d.sibling=y,d=y,h=_}if(v.done)return n(a,h),L&&na(a,g),u;if(h===null){for(;!v.done;g++,v=c.next())v=f(a,v.value,l),v!==null&&(s=o(v,s,g),d===null?u=v:d.sibling=v,d=v);return L&&na(a,g),u}for(h=r(h);!v.done;g++,v=c.next())v=m(h,a,g,v.value,l),v!==null&&(e&&(_=v.alternate,_!==null&&h.delete(_.key===null?g:_.key)),s=o(v,s,g),d===null?u=v:d.sibling=v,d=v);return e&&h.forEach(function(e){return t(a,e)}),L&&na(a,g),u}function _(e,r,o,c){if(typeof o==`object`&&o&&o.type===ie&&o.key===null&&o.props.ref===void 0&&(o=o.props.children),typeof o==`object`&&o){switch(o.$$typeof){case E:a:{for(var l=o.key;r!==null;){if(r.key===l){if(l=o.type,l===ie){if(r.tag===7){n(e,r.sibling),c=a(r,o.props.children),fo(c,o),c.return=e,e=c;break a}}else if(r.elementType===l||typeof l==`object`&&l&&l.$$typeof===k&&io(l)===r.type){n(e,r.sibling),c=a(r,o.props),fo(c,o),c.return=e,e=c;break a}n(e,r);break}t(e,r),r=r.sibling}o.type===ie?(c=Vi(o.props.children,e.mode,c,o.key),fo(c,o),c.return=e,e=c):(c=Bi(o.type,o.key,o.props,null,e.mode,c),fo(c,o),c.return=e,e=c)}return s(e);case re:a:{for(l=o.key;r!==null;){if(r.key===l){if(r.tag===4&&r.stateNode.containerInfo===o.containerInfo&&r.stateNode.implementation===o.implementation){n(e,r.sibling),c=a(r,o.children||[]),c.return=e,e=c;break a}n(e,r);break}t(e,r),r=r.sibling}c=Wi(o,e.mode,c),c.return=e,e=c}return s(e);case k:return o=io(o),_(e,r,o,c)}if(ye(o))return h(e,r,o,c);if(_e(o)){if(l=_e(o),typeof l!=`function`)throw Error(i(150));return o=l.call(o),g(e,r,o,c)}if(typeof o.then==`function`)return _(e,r,uo(o),c);if(o.$$typeof===D)return _(e,r,ka(e,o),c);po(e,o)}return typeof o==`string`&&o!==``||typeof o==`number`||typeof o==`bigint`?(o=``+o,r!==null&&r.tag===6?(n(e,r.sibling),c=a(r,o),c.return=e,e=c):(n(e,r),c=Hi(o,e.mode,c),c.return=e,e=c),s(e)):n(e,r)}return function(e,t,n,r){try{lo=0;var i=_(e,t,n,r);return co=null,i}catch(t){if(t===Qa||t===eo)throw t;var a=Ii(29,t,null,e.mode);return a.lanes=r,a.return=e,a}}}var ho=mo(!0),go=mo(!1),_o=!1;function vo(e){e.updateQueue={baseState:e.memoizedState,firstBaseUpdate:null,lastBaseUpdate:null,shared:{pending:null,lanes:0,hiddenCallbacks:null},callbacks:null}}function yo(e,t){e=e.updateQueue,t.updateQueue===e&&(t.updateQueue={baseState:e.baseState,firstBaseUpdate:e.firstBaseUpdate,lastBaseUpdate:e.lastBaseUpdate,shared:e.shared,callbacks:null})}function bo(e){return{lane:e,tag:0,payload:null,callback:null,next:null}}function xo(e,t,n){var r=e.updateQueue;if(r===null)return null;if(r=r.shared,W&2){var i=r.pending;return i===null?t.next=t:(t.next=i.next,i.next=t),r.pending=t,t=Ni(e),Mi(e,null,n),t}return ki(e,r,t,n),Ni(e)}function So(e,t,n){if(t=t.updateQueue,t!==null&&(t=t.shared,n&4194048)){var r=t.lanes;r&=e.pendingLanes,n|=r,t.lanes=n,yt(e,n)}}function Co(e,t){var n=e.updateQueue,r=e.alternate;if(r!==null&&(r=r.updateQueue,n===r)){var i=null,a=null;if(n=n.firstBaseUpdate,n!==null){do{var o={lane:n.lane,tag:n.tag,payload:n.payload,callback:null,next:null};a===null?i=a=o:a=a.next=o,n=n.next}while(n!==null);a===null?i=a=t:a=a.next=t}else i=a=t;n={baseState:r.baseState,firstBaseUpdate:i,lastBaseUpdate:a,shared:r.shared,callbacks:r.callbacks},e.updateQueue=n;return}e=n.lastBaseUpdate,e===null?n.firstBaseUpdate=t:e.next=t,n.lastBaseUpdate=t}var wo=!1;function To(){if(wo){var e=Ua;if(e!==null)throw e}}function Eo(e,t,n,r){wo=!1;var i=e.updateQueue;_o=!1;var a=i.firstBaseUpdate,o=i.lastBaseUpdate,s=i.shared.pending;if(s!==null){i.shared.pending=null;var c=s,l=c.next;c.next=null,o===null?a=l:o.next=l,o=c;var u=e.alternate;u!==null&&(u=u.updateQueue,s=u.lastBaseUpdate,s!==o&&(s===null?u.firstBaseUpdate=l:s.next=l,u.lastBaseUpdate=c))}if(a!==null){var d=i.baseState;o=0,u=l=c=null,s=a;do{var f=s.lane&-536870913,p=f!==s.lane;if(p?(q&f)===f:(r&f)===f){f!==0&&f===Ha&&(wo=!0),u!==null&&(u=u.next={lane:0,tag:s.tag,payload:s.payload,callback:null,next:null});a:{var m=e,h=s;f=t;var g=n;switch(h.tag){case 1:if(m=h.payload,typeof m==`function`){d=m.call(g,d,f);break a}d=m;break a;case 3:m.flags=m.flags&-65537|128;case 0:if(m=h.payload,f=typeof m==`function`?m.call(g,d,f):m,f==null)break a;d=T({},d,f);break a;case 2:_o=!0}}f=s.callback,f!==null&&(e.flags|=64,p&&(e.flags|=8192),p=i.callbacks,p===null?i.callbacks=[f]:p.push(f))}else p={lane:f,tag:s.tag,payload:s.payload,callback:s.callback,next:null},u===null?(l=u=p,c=d):u=u.next=p,o|=f;if(s=s.next,s===null){if(s=i.shared.pending,s===null)break;p=s,s=p.next,p.next=null,i.lastBaseUpdate=p,i.shared.pending=null}}while(1);u===null&&(c=d),i.baseState=c,i.firstBaseUpdate=l,i.lastBaseUpdate=u,a===null&&(i.shared.lanes=0),sd|=o,e.lanes=o,e.memoizedState=d}}function Do(e,t){if(typeof e!=`function`)throw Error(i(191,e));e.call(t)}function Oo(e,t){var n=e.callbacks;if(n!==null)for(e.callbacks=null,e=0;ea?a:8;var o=j.T,s={};s.types=o===null?null:o.types,j.T=s,pc(e,!1,t,n);try{var c=i(),l=j.S;l!==null&&l(s,c),typeof c==`object`&&c&&typeof c.then==`function`?fc(e,t,Ka(c,r),jd(e)):fc(e,t,r,jd(e))}catch(n){fc(e,t,{then:function(){},status:`rejected`,reason:n},jd())}finally{M.p=a,o!==null&&s.types!==null&&(o.types=s.types),j.T=o}}function nc(){}function rc(e,t,n,r){if(e.tag!==5)throw Error(i(476));var a=ic(e).queue;tc(e,a,t,be,n===null?nc:function(){return ac(e),n(r)})}function ic(e){var t=e.memoizedState;if(t!==null)return t;t={memoizedState:be,baseState:be,baseQueue:null,queue:{pending:null,lanes:0,dispatch:null,lastRenderedReducer:ms,lastRenderedState:be},next:null};var n={};return t.next={memoizedState:n,baseState:n,baseQueue:null,queue:{pending:null,lanes:0,dispatch:null,lastRenderedReducer:ms,lastRenderedState:n},next:null},e.memoizedState=t,e=e.alternate,e!==null&&(e.memoizedState=t),t}function ac(e){var t=ic(e);t.next===null&&(t=e.alternate.memoizedState),fc(e,t.next.queue,{},jd())}function oc(){return Oa(sh)}function sc(){return ls().memoizedState}function cc(){return ls().memoizedState}function lc(e){for(var t=e.return;t!==null;){switch(t.tag){case 24:case 3:var n=jd();e=bo(n);var r=xo(t,e,n);r!==null&&(Pd(r,t,n),So(r,t,n)),t={cache:Fa()},e.payload=t;return}t=t.return}}function uc(e,t,n){var r=jd();n={lane:r,revertLane:0,gesture:null,action:n,hasEagerState:!1,eagerState:null,next:null},mc(e)?hc(t,n):(n=Ai(e,t,n,r),n!==null&&(Pd(n,e,r),gc(n,t,r)))}function dc(e,t,n){fc(e,t,n,jd())}function fc(e,t,n,r){var i={lane:r,revertLane:0,gesture:null,action:n,hasEagerState:!1,eagerState:null,next:null};if(mc(e))hc(t,i);else{var a=e.alternate;if(e.lanes===0&&(a===null||a.lanes===0)&&(a=t.lastRenderedReducer,a!==null))try{var o=t.lastRenderedState,s=a(o,n);if(i.hasEagerState=!0,i.eagerState=s,Gr(s,o))return ki(e,t,i,0),G===null&&Oi(),!1}catch{}if(n=Ai(e,t,i,r),n!==null)return Pd(n,e,r),gc(n,t,r),!0}return!1}function pc(e,t,n,r){if(r={lane:2,revertLane:Pf(),gesture:null,action:r,hasEagerState:!1,eagerState:null,next:null},mc(e)){if(t)throw Error(i(479))}else t=Ai(e,n,r,2),t!==null&&Pd(t,e,2)}function mc(e){var t=e.alternate;return e===R||t!==null&&t===R}function hc(e,t){Jo=qo=!0;var n=e.pending;n===null?t.next=t:(t.next=n.next,n.next=t),e.pending=t}function gc(e,t,n){if(n&4194048){var r=t.lanes;r&=e.pendingLanes,n|=r,t.lanes=n,yt(e,n)}}var _c={readContext:Oa,use:fs,useCallback:B,useContext:B,useEffect:B,useImperativeHandle:B,useLayoutEffect:B,useInsertionEffect:B,useMemo:B,useReducer:B,useRef:B,useState:B,useDebugValue:B,useDeferredValue:B,useTransition:B,useSyncExternalStore:B,useId:B,useHostTransitionStatus:B,useFormState:B,useActionState:B,useOptimistic:B,useMemoCache:B,useCacheRefresh:B,useEffectEvent:B},vc={readContext:Oa,use:fs,useCallback:function(e,t){return cs().memoizedState=[e,t===void 0?null:t],e},useContext:Oa,useEffect:Hs,useImperativeHandle:function(e,t,n){n=n==null?null:n.concat([e]),Bs(4194308,4,Js.bind(null,t,e),n)},useLayoutEffect:function(e,t){return Bs(4194308,4,e,t)},useInsertionEffect:function(e,t){Bs(4,2,e,t)},useMemo:function(e,t){var n=cs();t=t===void 0?null:t;var r=e();if(Yo){tt(!0);try{e()}finally{tt(!1)}}return n.memoizedState=[r,t],r},useReducer:function(e,t,n){var r=cs();if(n!==void 0){var i=n(t);if(Yo){tt(!0);try{n(t)}finally{tt(!1)}}}else i=t;return r.memoizedState=r.baseState=i,e={pending:null,lanes:0,dispatch:null,lastRenderedReducer:e,lastRenderedState:i},r.queue=e,e=e.dispatch=uc.bind(null,R,e),[r.memoizedState,e]},useRef:function(e){var t=cs();return e={current:e},t.memoizedState=e},useState:function(e){e=ws(e);var t=e.queue,n=dc.bind(null,R,t);return t.dispatch=n,[e.memoizedState,n]},useDebugValue:Xs,useDeferredValue:function(e,t){return $s(cs(),e,t)},useTransition:function(){var e=ws(!1);return e=tc.bind(null,R,e.queue,!0,!1),cs().memoizedState=e,[!1,e]},useSyncExternalStore:function(e,t,n){var r=R,a=cs();if(L){if(n===void 0)throw Error(i(407));n=n()}else{if(n=t(),G===null)throw Error(i(349));q&127||ys(r,t,n)}a.memoizedState=n;var o={value:n,getSnapshot:t};return a.queue=o,Hs(xs.bind(null,r,o,e),[e]),r.flags|=2048,Rs(9,{destroy:void 0},bs.bind(null,r,o,n,t),null),n},useId:function(){var e=cs(),t=G.identifierPrefix;if(L){var n=ta,r=ea;n=(r&~(1<<32-nt(r)-1)).toString(32)+n,t=`_`+t+`R_`+n,n=Xo++,0<\/script>`,o=o.removeChild(o.firstChild);break;case`select`:o=typeof r.is==`string`?s.createElement(`select`,{is:r.is}):s.createElement(`select`),r.multiple?o.multiple=!0:r.size&&(o.size=r.size);break;default:o=typeof r.is==`string`?s.createElement(a,{is:r.is}):s.createElement(a)}}o[Et]=t,o[Dt]=r;a:for(s=t.child;s!==null;){if(s.tag===5||s.tag===6)o.appendChild(s.stateNode);else if(s.tag!==4&&s.tag!==27&&s.child!==null){s.child.return=s,s=s.child;continue}if(s===t)break a;for(;s.sibling===null;){if(s.return===null||s.return===t)break a;s=s.return}s.sibling.return=s.return,s=s.sibling}t.stateNode=o;a:switch(np(o,a,r),a){case`button`:case`input`:case`select`:case`textarea`:r=!!r.autoFocus;break a;case`img`:r=!0;break a;default:r=!1}r&&gl(t)}}return V(t),t.subtreeFlags&=-33554433,_l(t,t.type,e===null?null:e.memoizedProps,t.pendingProps,n),null;case 6:if(e&&t.stateNode!=null)e.memoizedProps!==r&&gl(t);else{if(typeof r!=`string`&&t.stateNode===null)throw Error(i(166));if(e=Ee.current,ma(t)){if(e=t.stateNode,n=t.memoizedProps,r=null,a=sa,a!==null)switch(a.tag){case 27:case 5:r=a.memoizedProps}e[Et]=t,e=!!(e.nodeValue===n||r!==null&&!0===r.suppressHydrationWarning||ep(e.nodeValue,n)),e||da(t,!0)}else e=lp(e).createTextNode(r),e[Et]=t,t.stateNode=e}return V(t),null;case 31:if(n=t.memoizedState,e===null||e.memoizedState!==null){if(r=ma(t),n!==null){if(e===null){if(!r)throw Error(i(318));if(e=t.memoizedState,e=e===null?null:e.dehydrated,!e)throw Error(i(557));e[Et]=t}else ha(),!(t.flags&128)&&(t.memoizedState=null),t.flags|=4;V(t),e=!1}else n=ga(),e!==null&&e.memoizedState!==null&&(e.memoizedState.hydrationErrors=n),e=!0;if(!e)return t.flags&256?(Bo(t),t):(Bo(t),null);if(t.flags&128)throw Error(i(558))}return V(t),null;case 13:if(r=t.memoizedState,e===null||e.memoizedState!==null&&e.memoizedState.dehydrated!==null){if(a=ma(t),r!==null&&r.dehydrated!==null){if(e===null){if(!a)throw Error(i(318));if(a=t.memoizedState,a=a===null?null:a.dehydrated,!a)throw Error(i(317));a[Et]=t}else ha(),!(t.flags&128)&&(t.memoizedState=null),t.flags|=4;V(t),a=!1}else a=ga(),e!==null&&e.memoizedState!==null&&(e.memoizedState.hydrationErrors=a),a=!0;if(!a)return t.flags&256?(Bo(t),t):(Bo(t),null)}return Bo(t),t.flags&128?(t.lanes=n,t):(n=r!==null,e=e!==null&&e.memoizedState!==null,n&&(r=t.child,a=null,r.alternate!==null&&r.alternate.memoizedState!==null&&r.alternate.memoizedState.cachePool!==null&&(a=r.alternate.memoizedState.cachePool.pool),o=null,r.memoizedState!==null&&r.memoizedState.cachePool!==null&&(o=r.memoizedState.cachePool.pool),o!==a&&(r.flags|=2048)),n!==e&&n&&(t.child.flags|=8192),yl(t,t.updateQueue),V(t),null);case 4:return ke(),e===null&&Wf(t.stateNode.containerInfo),t.flags|=67108864,V(t),null;case 10:return Sa(t.type),V(t),null;case 19:if(Uo(t),r=t.memoizedState,r===null)return V(t),null;if(a=!!(t.flags&128),o=r.rendering,o===null){if(a)bl(r,!1);else{if(Y!==0||e!==null&&e.flags&128)for(e=t.child;e!==null;){if(o=Wo(e),o!==null){for(t.flags|=128,bl(r,!1),e=o.updateQueue,t.updateQueue=e,yl(t,e),t.subtreeFlags=0,e=n,n=t.child;n!==null;)zi(n,e),n=n.sibling;return Ho(t,Vo.current&1|2),L&&na(t,r.treeForkCount),t.child}e=e.sibling}r.tail!==null&&We()>_d&&(t.flags|=128,a=!0,bl(r,!1),t.lanes=4194304)}}else{if(!a){if(e=Wo(o),e!==null){if(t.flags|=128,a=!0,e=e.updateQueue,t.updateQueue=e,yl(t,e),bl(r,!0),r.tail===null&&r.tailMode!==`collapsed`&&r.tailMode!==`visible`&&!o.alternate&&!L)return V(t),null}else 2*We()-r.renderingStartTime>_d&&n!==536870912&&(t.flags|=128,a=!0,bl(r,!1),t.lanes=4194304)}r.isBackwards?(o.sibling=t.child,t.child=o):(e=r.last,e===null?t.child=o:e.sibling=o,r.last=o)}if(r.tail!==null){e=r.tail;a:{for(n=e;n!==null;){if(n.alternate!==null){n=!1;break a}n=n.sibling}n=!0}return r.rendering=e,r.tail=e.sibling,r.renderingStartTime=We(),e.sibling=null,o=Vo.current,o=a?o&1|2:o&1,r.tailMode===`visible`||r.tailMode===`collapsed`||!n||L?Ho(t,o):(n=o,P(Po,t),P(Vo,n),Fo===null&&(Fo=t)),L&&na(t,r.treeForkCount),e}return V(t),null;case 22:case 23:return Bo(t),No(),r=t.memoizedState!==null,e===null?r&&(t.flags|=8192):e.memoizedState!==null!==r&&(t.flags|=8192),r?n&536870912&&!(t.flags&128)&&(V(t),t.subtreeFlags&6&&(t.flags|=8192)):V(t),n=t.updateQueue,n!==null&&yl(t,n.retryQueue),n=null,e!==null&&e.memoizedState!==null&&e.memoizedState.cachePool!==null&&(n=e.memoizedState.cachePool.pool),r=null,t.memoizedState!==null&&t.memoizedState.cachePool!==null&&(r=t.memoizedState.cachePool.pool),r!==n&&(t.flags|=2048),e!==null&&N(Ja),null;case 24:return n=null,e!==null&&(n=e.memoizedState.cache),t.memoizedState.cache!==n&&(t.flags|=2048),Sa(Pa),V(t),null;case 25:return null;case 30:return t.flags|=33554432,V(t),null}throw Error(i(156,t.tag))}function Sl(e,t){switch(aa(t),t.tag){case 1:return e=t.flags,e&65536?(t.flags=e&-65537|128,t):null;case 3:return Sa(Pa),ke(),e=t.flags,e&65536&&!(e&128)?(t.flags=e&-65537|128,t):null;case 26:case 27:case 5:return je(t),null;case 31:if(t.memoizedState!==null){if(Bo(t),t.alternate===null)throw Error(i(340));ha()}return e=t.flags,e&65536?(t.flags=e&-65537|128,t):null;case 13:if(Bo(t),e=t.memoizedState,e!==null&&e.dehydrated!==null){if(t.alternate===null)throw Error(i(340));ha()}return e=t.flags,e&65536?(t.flags=e&-65537|128,t):null;case 19:return Uo(t),e=t.flags,e&65536?(t.flags=e&-65537|128,e=t.memoizedState,e!==null&&(e.rendering=null,e.tail=null),t.flags|=4,t):null;case 4:return ke(),null;case 10:return Sa(t.type),null;case 22:case 23:return Bo(t),No(),e!==null&&N(Ja),e=t.flags,e&65536?(t.flags=e&-65537|128,t):null;case 24:return Sa(Pa),null;case 25:return null;default:return null}}function Cl(e,t){switch(aa(t),t.tag){case 3:Sa(Pa),ke();break;case 26:case 27:case 5:je(t);break;case 4:ke();break;case 31:t.memoizedState!==null&&Bo(t);break;case 13:Bo(t);break;case 19:Uo(t);break;case 10:Sa(t.type);break;case 22:case 23:Bo(t),No(),e!==null&&N(Ja);break;case 24:Sa(Pa)}}function wl(e,t){try{var n=t.updateQueue,r=n===null?null:n.lastEffect;if(r!==null){var i=r.next;n=i;do{if((n.tag&e)===e){r=void 0;var a=n.create,o=n.inst;r=a(),o.destroy=r}n=n.next}while(n!==i)}}catch(e){Z(t,t.return,e)}}function Tl(e,t,n){try{var r=t.updateQueue,i=r===null?null:r.lastEffect;if(i!==null){var a=i.next;r=a;do{if((r.tag&e)===e){var o=r.inst,s=o.destroy;if(s!==void 0){o.destroy=void 0,i=t;var c=n,l=s;try{l()}catch(e){Z(i,c,e)}}}r=r.next}while(r!==a)}}catch(e){Z(t,t.return,e)}}function El(e){var t=e.updateQueue;if(t!==null){var n=e.stateNode;try{Oo(t,n)}catch(t){Z(e,e.return,t)}}}function Dl(e,t,n){n.props=Tc(e.type,e.memoizedProps),n.state=e.memoizedState;try{n.componentWillUnmount()}catch(n){Z(e,t,n)}}function Ol(e,t){try{var n=e.ref;if(n!==null){switch(e.tag){case 26:case 27:case 5:var r=e.stateNode;break;case 30:var i=e.stateNode,a=xi(e.memoizedProps,i);(i.ref===null||i.ref.name!==a)&&(i.ref=Pp(a)),r=i.ref;break;case 7:if(e.stateNode===null){var o=new Fp(e);h(e.child,!1,Qp,o,void 0,void 0),e.stateNode=o}r=e.stateNode;break;default:r=e.stateNode}typeof n==`function`?e.refCleanup=n(r):n.current=r}}catch(n){Z(e,t,n)}}function kl(e,t){var n=e.ref,r=e.refCleanup;if(n!==null){if(typeof r==`function`)try{r()}catch(n){Z(e,t,n)}finally{e.refCleanup=null,e=e.alternate,e!=null&&(e.refCleanup=null)}else if(typeof n==`function`)try{n(null)}catch(n){Z(e,t,n)}else n.current=null}}function Al(e,t){if((e.tag===5||e.tag===27||e.tag===6)&&e.alternate===null&&t!==null)for(var n=0;n title`))),np(r,t,n),r[Et]=e,Bt(r),t=r;break a;case`link`:if(o=Gm(`link`,`href`,a).get(t+(n.href||``))){for(s=0;sg&&(o=g,g=h,h=o);var _=Yr(s,h),v=Yr(s,g);if(_&&v&&(p.rangeCount!==1||p.anchorNode!==_.node||p.anchorOffset!==_.offset||p.focusNode!==v.node||p.focusOffset!==v.offset)){var y=d.createRange();y.setStart(_.node,_.offset),p.removeAllRanges(),h>g?(p.addRange(y),p.extend(v.node,v.offset)):(y.setEnd(v.node,v.offset),p.addRange(y))}}}}for(d=[],p=s;p=p.parentNode;)p.nodeType===1&&d.push({element:p,left:p.scrollLeft,top:p.scrollTop});for(typeof s.focus==`function`&&s.focus(),s=0;sn?32:n,j.T=null,n=wd,wd=null;var o=bd,s=Sd;if(X=0,xd=bd=null,Sd=0,W&6)throw Error(i(331));var c=W;if(W|=4,Zu(o.current),Uu(o,o.current,s,n),W=c,Df(0,!1),et&&typeof et.onPostCommitFiberRoot==`function`)try{et.onPostCommitFiberRoot($e,o)}catch{}return!0}finally{M.p=a,j.T=r,uf(e,t)}}function pf(e,t,n){t=Ki(n,t),t=jc(e.stateNode,t,2),e=xo(e,t,2),e!==null&&(gt(e,2),Ef(e))}function Z(e,t,n){if(e.tag===3)pf(e,e,n);else for(;t!==null;){if(t.tag===3){pf(t,e,n);break}if(t.tag===1){var r=t.stateNode;if(typeof t.type.getDerivedStateFromError==`function`||typeof r.componentDidCatch==`function`&&(yd===null||!yd.has(r))){e=Ki(n,e),n=Mc(2),r=xo(t,n,2),r!==null&&(Nc(n,r,t,e),gt(r,2),Ef(r));break}}t=t.return}}function mf(e,t,n){var r=e.pingCache;if(r===null){r=e.pingCache=new td;var i=new Set;r.set(t,i)}else i=r.get(t),i===void 0&&(i=new Set,r.set(t,i));i.has(n)||(ad=!0,i.add(n),e=hf.bind(null,e,t,n),t.then(e,e))}function hf(e,t,n){var r=e.pingCache;r!==null&&r.delete(t),e.pingedLanes|=e.suspendedLanes&n,e.warmLanes&=~n,G===e&&(q&n)===n&&(Y===4||Y===3&&(q&62914560)===q&&300>We()-hd?W&2?ld|=n:Vd(e,0):ld|=n,dd===q&&(dd=0)),Ef(e)}function gf(e,t){t===0&&(t=mt()),e=ji(e,t),e!==null&&(gt(e,t),Ef(e))}function _f(e){var t=e.memoizedState,n=0;t!==null&&(n=t.retryLane),gf(e,n)}function vf(e,t){var n=0;switch(e.tag){case 31:case 13:var r=e.stateNode,a=e.memoizedState;a!==null&&(n=a.retryLane);break;case 19:r=e.stateNode;break;case 22:r=e.stateNode._retryCache;break;default:throw Error(i(314))}r!==null&&r.delete(t),gf(e,n)}function yf(e,t){return Be(e,t)}var bf=null,xf=null,Sf=!1,Cf=!1,wf=!1,Tf=0;function Ef(e){e!==xf&&e.next===null&&(xf===null?bf=xf=e:xf=xf.next=e),Cf=!0,Sf||(Sf=!0,Nf())}function Df(e,t){if(!wf&&Cf){wf=!0;do for(var n=!1,r=bf;r!==null;){if(!t){if(e!==0){var i=r.pendingLanes;if(i===0)var a=0;else{var o=r.suspendedLanes,s=r.pingedLanes;a=(1<<31-nt(42|e)+1)-1,a&=i&~(o&~s),a=a&201326741?a&201326741|1:a?a|2:0}a!==0&&(n=!0,Mf(r,a))}else a=q,a=ut(r,r===G?a:0,r.cancelPendingCommit!==null||r.timeoutHandle!==-1),!(a&3)||dt(r,a)||(n=!0,Mf(r,a))}r=r.next}while(n);wf=!1}}function Of(){kf()}function kf(){Cf=Sf=!1;var e=0;Tf!==0&&hp()&&(e=Tf);for(var t=We(),n=null,r=bf;r!==null;){var i=r.next,a=Af(r,t);a===0?(r.next=null,n===null?bf=i:n.next=i,i===null&&(xf=n)):(n=r,(e!==0||a&3)&&(Cf=!0)),r=i}X!==0&&X!==5||Df(e,!1),Tf!==0&&(Tf=0)}function Af(e,t){for(var n=e.suspendedLanes,r=e.pingedLanes,i=e.expirationTimes,a=e.pendingLanes&-62914561;0s)break;var u=c.transferSize,d=c.initiatorType;u&&ap(d)&&(c=c.responseEnd,o+=u*(c$m){s.length=o;break}f=new Promise(jp.bind(f)),s.push(f)}}}if(0`u`?null:document;function Tm(e,t,n){var r=wm;if(r&&typeof t==`string`&&t){var i=sn(t);i=`link[rel="`+e+`"][href="`+i+`"]`,typeof n==`string`&&(i+=`[crossorigin="`+n+`"]`),ym.has(i)||(ym.add(i),e={rel:e,crossOrigin:n,href:t},r.querySelector(i)===null&&(t=r.createElement(`link`),np(t,`link`,e),Bt(t),r.head.appendChild(t)))}}function Em(e){xm.D(e),Tm(`dns-prefetch`,e,null)}function Dm(e,t){xm.C(e,t),Tm(`preconnect`,e,t)}function Om(e,t,n){xm.L(e,t,n);var r=wm;if(r&&e&&t){var i=`link[rel="preload"][as="`+sn(t)+`"]`;t===`image`&&n&&n.imageSrcSet?(i+=`[imagesrcset="`+sn(n.imageSrcSet)+`"]`,typeof n.imageSizes==`string`&&(i+=`[imagesizes="`+sn(n.imageSizes)+`"]`)):i+=`[href="`+sn(e)+`"]`;var a=i;switch(t){case`style`:a=Pm(e);break;case`script`:a=Rm(e)}if(!(vm.has(a)||(e=T({rel:`preload`,href:t===`image`&&n&&n.imageSrcSet?void 0:e,as:t},n),vm.set(a,e),r.querySelector(i)!==null||t===`style`&&r.querySelector(Fm(a))||t===`script`&&r.querySelector(zm(a))))){var o=r.createElement(`link`);np(o,`link`,e),t===`style`&&(o[Pt]=!0,o.onload=o.onerror=function(){Vt(o)}),Bt(o),r.head.appendChild(o)}}}function km(e,t){xm.m(e,t);var n=wm;if(n&&e){var r=t&&typeof t.as==`string`?t.as:`script`,i=`link[rel="modulepreload"][as="`+sn(r)+`"][href="`+sn(e)+`"]`,a=i;switch(r){case`audioworklet`:case`paintworklet`:case`serviceworker`:case`sharedworker`:case`worker`:case`script`:a=Rm(e)}if(!vm.has(a)&&(e=T({rel:`modulepreload`,href:e},t),vm.set(a,e),n.querySelector(i)===null)){switch(r){case`audioworklet`:case`paintworklet`:case`serviceworker`:case`sharedworker`:case`worker`:case`script`:if(n.querySelector(zm(a)))return}r=n.createElement(`link`),np(r,`link`,e),Bt(r),n.head.appendChild(r)}}}function Am(e,t,n){xm.S(e,t,n);var r=wm;if(r&&e){var i=zt(r).hoistableStyles,a=Pm(e);t||=`default`;var o=i.get(a);if(!o){var s={loading:0,preload:null};if(o=r.querySelector(Fm(a)))s.loading=5;else{e=T({rel:`stylesheet`,href:e,"data-precedence":t},n),(n=vm.get(a))&&Hm(e,n);var c=o=r.createElement(`link`);Bt(c),np(c,`link`,e),c._p=new Promise(function(e,t){c.onload=e,c.onerror=t}),c.addEventListener(`load`,function(){s.loading|=1}),c.addEventListener(`error`,function(){s.loading|=2}),s.loading|=4,Vm(o,t,r)}o={type:`stylesheet`,instance:o,count:1,state:s},i.set(a,o)}}}function jm(e,t){xm.X(e,t);var n=wm;if(n&&e){var r=zt(n).hoistableScripts,i=Rm(e),a=r.get(i);a||(a=n.querySelector(zm(i)),a||(e=T({src:e,async:!0},t),(t=vm.get(i))&&Um(e,t),a=n.createElement(`script`),Bt(a),np(a,`link`,e),n.head.appendChild(a)),a={type:`script`,instance:a,count:1,state:null},r.set(i,a))}}function Mm(e,t){xm.M(e,t);var n=wm;if(n&&e){var r=zt(n).hoistableScripts,i=Rm(e),a=r.get(i);a||(a=n.querySelector(zm(i)),a||(e=T({src:e,async:!0,type:`module`},t),(t=vm.get(i))&&Um(e,t),a=n.createElement(`script`),Bt(a),np(a,`link`,e),n.head.appendChild(a)),a={type:`script`,instance:a,count:1,state:null},r.set(i,a))}}function Nm(e,t,n,r){var a=(a=Ee.current)?bm(a):null;if(!a)throw Error(i(446));switch(e){case`meta`:case`title`:return null;case`style`:return typeof n.precedence==`string`&&typeof n.href==`string`?(n=Pm(n.href),t=zt(a).hoistableStyles,r=t.get(n),r||(r={type:`style`,instance:null,count:0,state:null},t.set(n,r)),r):{type:`void`,instance:null,count:0,state:null};case`link`:if(n.rel===`stylesheet`&&typeof n.href==`string`&&typeof n.precedence==`string`){e=Pm(n.href);var o=zt(a).hoistableStyles,s=o.get(e);if(s||(a=a.ownerDocument||a,s={type:`stylesheet`,instance:null,count:0,state:{loading:0,preload:null}},o.set(e,s),(o=a.querySelector(Fm(e)))?o._p||(s.instance=o,s.state.loading=5):(o=vm.get(e),o||(o={rel:`preload`,as:`style`,href:n.href,crossOrigin:n.crossOrigin,integrity:n.integrity,media:n.media,hrefLang:n.hrefLang,referrerPolicy:n.referrerPolicy},vm.set(e,o)),Lm(a,e,o,s.state))),t&&r===null)throw Error(i(528,``));return s}if(t&&r!==null)throw Error(i(529,``));return null;case`script`:return t=n.async,n=n.src,typeof n==`string`&&t&&typeof t!=`function`&&typeof t!=`symbol`?(n=Rm(n),t=zt(a).hoistableScripts,r=t.get(n),r||(r={type:`script`,instance:null,count:0,state:null},t.set(n,r)),r):{type:`void`,instance:null,count:0,state:null};default:throw Error(i(444,e))}}function Pm(e){return`href="`+sn(e)+`"`}function Fm(e){return`link[rel="stylesheet"][`+e+`]`}function Im(e){return T({},e,{"data-precedence":e.precedence,precedence:null})}function Lm(e,t,n,r){if(t=e.querySelector(`link[rel="preload"][as="style"][`+t+`]`)){if(!0!==t[Pt]){r.loading=1;return}}else t=e.createElement(`link`),t[Pt]=!0,t.onload=t.onerror=Vt.bind(null,t),np(t,`link`,n),Bt(t),e.head.appendChild(t);r.preload=t,t.addEventListener(`load`,function(){return r.loading|=1}),t.addEventListener(`error`,function(){return r.loading|=2})}function Rm(e){return`[src="`+sn(e)+`"]`}function zm(e){return`script[async]`+e}function Bm(e,t,n){if(t.count++,t.instance===null)switch(t.type){case`style`:var r=e.querySelector(`style[data-href~="`+sn(n.href)+`"]`);if(r)return t.instance=r,Bt(r),r;var a=T({},n,{"data-href":n.href,"data-precedence":n.precedence,href:null,precedence:null});return r=(e.ownerDocument||e).createElement(`style`),Bt(r),np(r,`style`,a),Vm(r,n.precedence,e),t.instance=r;case`stylesheet`:a=Pm(n.href);var o=e.querySelector(Fm(a));if(o)return t.state.loading|=4,t.instance=o,Bt(o),o;r=Im(n),(a=vm.get(a))&&Hm(r,a),o=(e.ownerDocument||e).createElement(`link`),Bt(o);var s=o;return s._p=new Promise(function(e,t){s.onload=e,s.onerror=t}),np(o,`link`,r),t.state.loading|=4,Vm(o,n.precedence,e),t.instance=o;case`script`:return o=Rm(n.src),(a=e.querySelector(zm(o)))?(t.instance=a,Bt(a),a):(r=n,(a=vm.get(o))&&(r=T({},n),Um(r,a)),e=e.ownerDocument||e,a=e.createElement(`script`),Bt(a),np(a,`link`,r),e.head.appendChild(a),t.instance=a);case`void`:return null;default:throw Error(i(443,t.type))}else t.type===`stylesheet`&&!(t.state.loading&4)&&(r=t.instance,t.state.loading|=4,Vm(r,n.precedence,e));return t.instance}function Vm(e,t,n){for(var r=n.querySelectorAll(`link[rel="stylesheet"][data-precedence],style[data-precedence]`),i=r.length?r[r.length-1]:null,a=i,o=0;o title`):null)}function qm(e,t,n){if(n===1||t.itemProp!=null)return!1;switch(e){case`meta`:case`title`:return!0;case`style`:if(typeof t.precedence!=`string`||typeof t.href!=`string`||t.href===``)break;return!0;case`link`:if(typeof t.rel!=`string`||typeof t.href!=`string`||t.href===``||t.onLoad||t.onError)break;switch(t.rel){case`stylesheet`:return e=t.disabled,typeof t.precedence==`string`&&e==null;default:return!0}case`script`:if(t.async&&typeof t.async!=`function`&&typeof t.async!=`symbol`&&!t.onLoad&&!t.onError&&t.src&&typeof t.src==`string`)return!0}return!1}function Jm(e,t){return e===`img`&&t.src!=null&&t.src!==``&&t.onLoad==null&&t.loading!==`lazy`}function Ym(e){return!(e.type===`stylesheet`&&!(e.state.loading&3))}function Xm(e){return(e.width||100)*(e.height||100)*(typeof devicePixelRatio==`number`?devicePixelRatio:1)*.25}function Zm(e,t){typeof t.decode==`function`&&(e.imgCount++,t.complete||(e.imgBytes+=Xm(t),e.suspenseyImages.push(t)),e=rh.bind(e),t.decode().then(e,e))}function Qm(e,t,n,r){if(n.type===`stylesheet`&&(typeof r.media!=`string`||!1!==matchMedia(r.media).matches)&&!(n.state.loading&4)){if(n.instance===null){var i=Pm(r.href),a=t.querySelector(Fm(i));if(a){t=a._p,typeof t==`object`&&t&&typeof t.then==`function`&&(e.count++,e=nh.bind(e),t.then(e,e)),n.state.loading|=4,n.instance=a,Bt(a);return}a=t.ownerDocument||t,r=Im(r),(i=vm.get(i))&&Hm(r,i),a=a.createElement(`link`),Bt(a);var o=a;o._p=new Promise(function(e,t){o.onload=e,o.onerror=t}),np(a,`link`,r),n.instance=a}e.stylesheets===null&&(e.stylesheets=new Map),e.stylesheets.set(n,t),(t=n.state.preload)&&!(n.state.loading&3)&&(e.count++,n=nh.bind(e),t.addEventListener(`load`,n),t.addEventListener(`error`,n))}}var $m=0;function eh(e,t){return e.stylesheets&&e.count===0&&ah(e,e.stylesheets),0$m?50:800)+t);return e.unsuspend=n,function(){e.unsuspend=null,clearTimeout(r),clearTimeout(i)}}:null}function th(e){if(e.count===0&&(e.imgCount===0||!e.waitingForImages)){if(e.stylesheets)ah(e,e.stylesheets);else if(e.unsuspend){var t=e.unsuspend;e.unsuspend=null,t()}}}function nh(){this.count--,th(this)}function rh(){this.imgCount--,th(this)}var ih=null;function ah(e,t){e.stylesheets=null,e.unsuspend!==null&&(e.count++,ih=new Map,t.forEach(oh,e),ih=null,nh.call(e))}function oh(e,t){if(!(t.state.loading&4)){var n=ih.get(e);if(n)var r=n.get(null);else{n=new Map,ih.set(e,n);for(var i=e.querySelectorAll(`link[data-precedence],style[data-precedence]`),a=0;a{function n(){if(typeof __REACT_DEVTOOLS_GLOBAL_HOOK__<`u`&&typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE==`function`)try{__REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE(n)}catch(e){console.error(e)}}n(),t.exports=h()})),_=c(u(),1),v=g(),y=``,b=class extends Error{constructor(e,t){super(t.detail||t.message||t.title||`Request failed (${e})`),this.status=e,this.problem=t}};function ee(e){y=e||``}async function x(e,{method:t=`GET`,body:n,signal:r}={}){let i=await fetch(`/api/${e}`,{method:t,credentials:`same-origin`,cache:`no-store`,signal:r,headers:{Accept:`application/json`,...n===void 0?{}:{"Content-Type":`application/json`},...t===`GET`?{}:{"X-CSRF-Token":y}},body:n===void 0?void 0:JSON.stringify(n)}),a=await i.json().catch(()=>({detail:`The server returned an unreadable response.`}));if(!i.ok)throw new b(i.status,a);return a.csrf_token&&ee(a.csrf_token),a}var te=(e,t={})=>x(e,{method:`POST`,body:t});function S(e){return new URLSearchParams(Object.entries(e).filter(([,e])=>e!==``&&e!=null)).toString()}var C=(e,t=0)=>e==null?`—`:Number(e).toLocaleString(void 0,{maximumFractionDigits:t});function w(e){if(e==null)return`—`;if(e===0)return`0 B`;let t=Math.min(Math.floor(Math.log(Math.max(1,e))/Math.log(1024)),4);return`${C(e/1024**t,+!!t)} ${[`B`,`KiB`,`MiB`,`GiB`,`TiB`][t]}`}var T=e=>e?new Date(typeof e==`number`?e*1e3:e).toLocaleString():`—`,ne=o((e=>{var t=Symbol.for(`react.transitional.element`),n=Symbol.for(`react.fragment`);function r(e,n,r){var i=null;if(r!==void 0&&(i=``+r),n.key!==void 0&&(i=``+n.key),`key`in n)for(var a in r={},n)a!==`key`&&(r[a]=n[a]);else r=n;return n=r.ref,{$$typeof:t,type:e,key:i,ref:n===void 0?null:n,props:r}}e.Fragment=n,e.jsx=r,e.jsxs=r})),E=o(((e,t)=>{t.exports=ne()}))();function re({option:e,height:t=260,label:n,onZoom:r}){let i=(0,_.useRef)(null),a=(0,_.useRef)(null),o=(0,_.useRef)(r),s=(0,_.useRef)(e);return o.current=r,s.current=e,(0,_.useEffect)(()=>{let t,n,r;function c(){if(!window.echarts){t=setTimeout(c,50);return}a.current=window.echarts.init(i.current,null,{renderer:`canvas`}),a.current.on(`datazoom`,e=>{if(!o.current)return;let t=e.batch?.[0]||e,r=s.current.series?.[0]?.data?.map(e=>e[0])||[];if(r.length<2)return;let i=Math.min(...r),a=Math.max(...r),c=Number(t.startValue??i+(a-i)*(t.start??0)/100),l=Number(t.endValue??i+(a-i)*(t.end??100)/100);clearTimeout(n),n=setTimeout(()=>o.current?.({start:Math.floor(c/1e3),end:Math.ceil(l/1e3)}),400)}),r=new ResizeObserver(()=>a.current?.resize()),r.observe(i.current),a.current.setOption(e)}return c(),()=>{clearTimeout(t),clearTimeout(n),r?.disconnect(),a.current?.dispose(),a.current=null}},[]),(0,_.useEffect)(()=>{a.current?.setOption(e,{notMerge:!0,lazyUpdate:!0})},[e]),(0,E.jsx)(`div`,{ref:i,className:`chart`,role:`img`,"aria-label":n,style:{height:t}})}var ie={animationDuration:250,color:[`#38bdf8`,`#34d399`,`#fbbf24`,`#fb7185`,`#a78bfa`],textStyle:{fontFamily:`system-ui, sans-serif`,color:`#8396ad`}};function ae(e,t,{zoom:n=!1}={}){return{...ie,tooltip:{trigger:`axis`,confine:!0},legend:{bottom:0,textStyle:{color:`#8396ad`},icon:`circle`,itemWidth:8},grid:{left:54,right:18,top:24,bottom:n?86:52},xAxis:{type:`time`,axisLine:{show:!1},splitLine:{show:!1}},yAxis:{type:`value`,splitLine:{lineStyle:{color:`#8396ad22`}},axisLabel:{formatter:`{value}`}},...n?{dataZoom:[{type:`inside`},{type:`slider`,bottom:28,height:18}]}:{},series:t.map(([t,n],r)=>({name:n,type:`line`,showSymbol:!1,smooth:.15,lineStyle:{width:2},areaStyle:r===0?{opacity:.09}:void 0,data:e.map(e=>[Number(e.stamp??e.timestamp??e.bucket??e.time)*1e3,e[t]??null])}))}}function oe(e){return{...ie,tooltip:{trigger:`item`,confine:!0},legend:{bottom:0,textStyle:{color:`#8396ad`},icon:`circle`,itemWidth:8},series:[{type:`pie`,radius:[`55%`,`76%`],center:[`50%`,`44%`],label:{show:!1},emphasis:{label:{show:!0,fontSize:16,fontWeight:`bold`,formatter:`{b} -{d}%`}},data:e.map(e=>({name:e.name??e.key??e.value??`Unknown`,value:e.calls??e.count??e.requests??0}))}]}}function se({error:e,dismiss:t}){return e?(0,E.jsxs)(`div`,{className:`alert alert-danger d-flex align-items-start gap-3`,role:`alert`,children:[(0,E.jsx)(`span`,{className:`flex-grow-1`,children:e.message||String(e)}),t&&(0,E.jsx)(`button`,{className:`btn-close`,"aria-label":`Dismiss error`,onClick:t})]}):null}function D({children:e=`No records in this time range.`}){return(0,E.jsx)(`div`,{className:`empty-state`,children:e})}function ce({children:e=`Loading…`}){return(0,E.jsxs)(`div`,{className:`empty-state`,children:[(0,E.jsx)(`span`,{className:`spinner-border spinner-border-sm me-2`,"aria-hidden":`true`}),e]})}function O({title:e,detail:t,children:n,className:r=``,actions:i}){return(0,E.jsxs)(`section`,{className:`panel ${r}`,children:[(0,E.jsxs)(`div`,{className:`panel-title`,children:[(0,E.jsxs)(`div`,{children:[(0,E.jsx)(`h2`,{children:e}),t&&(0,E.jsx)(`small`,{children:t})]}),i]}),n]})}function le({children:e,tone:t=`muted`}){return(0,E.jsx)(`span`,{className:`status-badge ${t}`,children:e})}function ue({value:e,max:t,label:n}){return(0,E.jsx)(`div`,{className:`progress`,role:`progressbar`,"aria-label":n,"aria-valuenow":e||0,"aria-valuemin":`0`,"aria-valuemax":t||100,children:(0,E.jsx)(`div`,{className:`progress-bar`,style:{width:`${Math.min(100,(e||0)*100/(t||100))}%`}})})}function k({columns:e,rows:t,onSelect:n,rowKey:r}){return t.length?(0,E.jsx)(`div`,{className:`table-responsive`,children:(0,E.jsxs)(`table`,{className:`table align-middle table-hover mb-0`,children:[(0,E.jsx)(`thead`,{children:(0,E.jsx)(`tr`,{children:e.map(e=>(0,E.jsx)(`th`,{scope:`col`,children:e.label},e.key))})}),(0,E.jsx)(`tbody`,{children:t.map((t,i)=>(0,E.jsx)(`tr`,{children:e.map((e,r)=>(0,E.jsx)(`td`,{children:r===0&&n?(0,E.jsx)(`button`,{className:`table-link`,onClick:()=>n(t),children:e.render?e.render(t):t[e.key]}):e.render?e.render(t):t[e.key]??`—`},e.key))},r?.(t)??i))})]})}):(0,E.jsx)(D,{})}function de(e,t=!1){return![`blocked`,`updating`].includes(e.status)&&(e.eligible||t)}function fe(e,t,{force:n=!1,retry:r=!1}={}){let i=new Set(t);return(e?.targets||[]).filter(e=>i.has(e.id)&&de(e,n)&&(!r||[`failed`,`interrupted`].includes(e.outcome))).map(e=>e.id)}function pe(e){return(e?.targets||[]).filter(e=>de(e)).map(e=>e.id)}function me(e,t,n={}){if(!e||!/^[a-f0-9]{64}$/.test(e.plan_id))throw Error(`Refresh the upgrade plan before applying.`);return{targets:fe(e,t,n),plan_id:e.plan_id,force:n.force===!0,retry:n.retry===!0}}function he(e,t=!1){let n=e?.recommendation?.value;return t||e?.recommendation?.status!==`suggested`||!Number.isFinite(n)||n<0||!e?.configuration?.editable||e.configuration.owner!==`saved`||![`TELEMETRY_SPOOL_MAX_GIB`,`TELEMETRY_MAX_GIB`].includes(e.setting)?null:{key:e.setting,value:String(n)}}function ge({execute:e,refresh:t,paused:n=!1}){let[r,i]=(0,_.useState)(null),[a,o]=(0,_.useState)(null),[s,c]=(0,_.useState)(0),[l,u]=(0,_.useState)(!0),[d,f]=(0,_.useState)([]),[p,m]=(0,_.useState)(!1),[h,g]=(0,_.useState)(!1);(0,_.useEffect)(()=>{let e=!1,t=new AbortController;return u(!0),o(null),x(`upgrades`,{signal:t.signal}).then(t=>{e||(i(t),f(pe(t)),m(!1),g(!1))}).catch(t=>{!e&&t.name!==`AbortError`&&o(t)}).finally(()=>{e||u(!1)}),()=>{e=!0,t.abort()}},[t,s]);let v=fe(r,d,{force:p,retry:h}),y=r&&!l&&!a&&!n,b=e=>f(t=>t.includes(e)?t.filter(t=>t!==e):[...t,e]);return(0,E.jsxs)(`div`,{className:`upgrade-plan`,children:[(0,E.jsx)(`p`,{children:`Only selected targets will be upgraded. Static targets update their software and configuration—not Bible files. Skipped changes remain pending.`}),(0,E.jsx)(se,{error:a}),(0,E.jsxs)(`div`,{className:`d-flex gap-2 flex-wrap mb-3`,children:[(0,E.jsx)(`button`,{type:`button`,className:`btn btn-outline-secondary`,disabled:l,onClick:()=>c(e=>e+1),children:`Refresh upgrade plan`}),(0,E.jsx)(`button`,{type:`button`,className:`btn btn-outline-secondary`,disabled:!y,onClick:()=>{f(pe(r)),g(!1)},children:`Select changed targets`}),(0,E.jsx)(`button`,{type:`button`,className:`btn btn-outline-secondary`,disabled:!y,onClick:()=>f([]),children:`Clear selection`}),(0,E.jsxs)(`label`,{className:`form-check`,children:[(0,E.jsx)(`input`,{type:`checkbox`,className:`form-check-input`,checked:p,disabled:!y,onChange:e=>m(e.target.checked)}),`Allow forced redeployment of unchanged targets`]}),(0,E.jsxs)(`label`,{className:`form-check`,children:[(0,E.jsx)(`input`,{type:`checkbox`,className:`form-check-input`,checked:h,disabled:!y,onChange:e=>{g(e.target.checked),e.target.checked&&f((r?.targets||[]).filter(e=>[`failed`,`interrupted`].includes(e.outcome)).map(e=>e.id))}}),`Retry failed or interrupted targets only`]})]}),l?(0,E.jsx)(ce,{children:`Inspecting installed releases and target readiness…`}):r?(0,E.jsxs)(E.Fragment,{children:[(0,E.jsxs)(`div`,{className:`alert alert-info`,role:`status`,children:[`Release `,r.version,`: `,r.pending,` target(s) pending. `,v.length,` selected. Serving health is separate from upgrade completion.`]}),(0,E.jsx)(k,{rowKey:e=>e.id,rows:r.targets||[],columns:[{key:`selected`,label:`Upgrade`,render:e=>(0,E.jsx)(`input`,{type:`checkbox`,"aria-label":`Upgrade ${e.id}`,checked:v.includes(e.id),disabled:!y||!de(e,p)||h&&![`failed`,`interrupted`].includes(e.outcome),onChange:()=>b(e.id)})},{key:`id`,label:`Target`,render:e=>(0,E.jsxs)(E.Fragment,{children:[(0,E.jsx)(`strong`,{children:e.id}),(0,E.jsx)(`small`,{className:`d-block`,children:e.reason}),e.last_error&&(0,E.jsx)(`small`,{className:`d-block text-warning`,children:e.last_error})]})},{key:`status`,label:`Upgrade state`,render:e=>(0,E.jsx)(le,{tone:e.status===`current`?`success`:e.status===`blocked`?`warning`:`info`,children:e.status})},{key:`serving`,label:`Serving`,render:e=>(0,E.jsx)(le,{tone:e.serving===`ready`?`success`:`warning`,children:e.serving})},{key:`outcome`,label:`Last result`,render:e=>(0,E.jsxs)(E.Fragment,{children:[e.outcome,(0,E.jsx)(`small`,{className:`d-block`,children:T(e.last_attempt)})]})},{key:`fingerprints`,label:`Implementation`,render:e=>(0,E.jsxs)(`details`,{children:[(0,E.jsx)(`summary`,{children:`Fingerprints`}),(0,E.jsxs)(`small`,{className:`d-block text-break`,children:[`Desired: `,e.desired_fingerprint||`Unavailable`]}),(0,E.jsxs)(`small`,{className:`d-block text-break`,children:[`Applied: `,e.applied_fingerprint||`Not recorded`]})]})}]}),(0,E.jsx)(`p`,{className:`text-secondary mt-3`,children:r.note})]}):(0,E.jsx)(D,{children:`The upgrade plan is unavailable. Refresh to retry.`}),(0,E.jsx)(`button`,{type:`button`,className:`btn btn-primary`,disabled:!y||!v.length,onClick:()=>{try{e(`system.update`,me(r,d,{force:p,retry:h}),{title:`Upgrade ${v.length} selected target(s)`,description:`The manager rechecks this plan before changing anything. Unselected generations remain running; capacity checks may reject an unsafe overlap. Whole-container replacement is a separate restart.`})}catch(e){o(e)}},children:`Review selected upgrades`}),!v.length&&!l&&(0,E.jsx)(`small`,{className:`d-block mt-2`,children:`Nothing selected. No upgrade will run.`})]})}var _e=[{id:`domains`,title:`Domains`,detail:`Status, endpoints, pages, access and runtime settings`},{id:`deploy`,title:`Deploy a new domain`,detail:`Prepare a static, runtime or MCP domain`},{id:`go-live`,title:`Go live`,detail:`Activate a staged domain`},{id:`self-update`,title:`Update manager`,detail:`Update the native manager checkout`},{id:`update`,title:`Upgrade targets`,detail:`Review changes, select targets and retry incomplete upgrades`},{id:`analytics`,title:`Traffic analytics`,detail:`Run a traffic report`},{id:`dashboard`,title:`Private dashboard`,detail:`Domain, password, sessions and blocked addresses`},{id:`logs`,title:`Logs`,detail:`Domain diagnostics, archives and rotation`},{id:`settings`,title:`Settings`,detail:`Environment, notifications, Cloudflare token and icons`},{id:`system`,title:`System`,detail:`Host checks, dependencies and resource allocation`}],A=e=>e.id||e.operation||e.name;function ve(e){let t=e?.fields||e?.parameters||[];return Array.isArray(t)?t:Object.entries(t).map(([e,t])=>({name:e,...t}))}function ye(e){let t=A(e);if(t.startsWith(`domain.deploy_`))return[[`deploy`,`New domain`]];if(t===`domain.go_live`)return[[`go-live`,`Activation`],[`domains`,`Publication`]];if(t===`system.self_update`)return[[`self-update`,`Manager checkout`]];if(t===`system.update`)return[[`update`,`Review eligible targets`]];if(t===`analytics`)return[[`analytics`,`Traffic report`]];if(t.startsWith(`dashboard.`))return[[`dashboard`,[`dashboard.sessions`,`dashboard.revoke`].includes(t)?`Sessions`:t===`dashboard.blocks`?`Blocked addresses`:`Dashboard settings`]];if(t.startsWith(`logs.`))return t===`logs.rotate`?[[`logs`,`Rotation`]]:t===`logs.reset`?[[`logs`,`History`]]:[[`logs`,`Domain logs`],[`domains`,`Logs`]];if(t.startsWith(`settings.`))return[[`settings`,`Environment and defaults`]];if(t.startsWith(`telegram.`))return[[`settings`,`Telegram notifications`]];if(t===`cloudflare.token`)return[[`settings`,`Cloudflare API token`]];if([`system.icons`,`system.favicon`,`system.logo`].includes(t))return[[`settings`,`Icons`]];if(t===`pages.upload_icon`)return[[`settings`,`Icons`],[`domains`,`Pages and OpenAPI`]];if([`cloudflare.verify`,`cloudflare.refresh_ips`].includes(t))return[[`system`,`Cloudflare origin tools`]];if(t.startsWith(`resources.`))return[[`system`,`Resource allocation`]];if(t===`runtime.versions`)return[[`system`,`Python versions`]];if(ve(e).some(e=>e.name===`domain`)){let e=`Other domain operations`;return[`domain.status`,`domain.verify`].includes(t)?e=`Status and health`:t===`domain.stage`?e=`Publication`:[`domain.apply`,`domain.update`].includes(t)?e=`Apply configuration`:t===`domain.remove`?e=`Remove domain`:t.startsWith(`endpoint.`)?e=`Endpoints and repositories`:t===`access.mode`?e=`Access mode`:t===`access.limits`?e=`Anonymous caller limits`:t.startsWith(`token.`)?e=`Bearer tokens`:t.startsWith(`certificate.`)?e=`Certificate`:t.startsWith(`cloudflare.`)?e=`Cloudflare`:t.startsWith(`pages.`)?e=`Pages and OpenAPI`:t.startsWith(`mcp.`)?e=`MCP service`:t===`runtime.set`?e=`Runtime settings`:t===`runtime.cache`?e=`Translation memory`:t.startsWith(`runtime.`)&&(e=`Runtime services`),[[`domains`,e]]}return[[`system`,t.startsWith(`system.`)?`Host maintenance`:e.category||`Other operations`]]}function j(e){let t=new Map((e?.domains||[]).map(e=>[e.domain,{...e,endpoints:[]}]));for(let n of e?.endpoints||[])t.has(n.domain)||t.set(n.domain,{...n,endpoints:[]}),t.get(n.domain).endpoints.push(n);return[...t.values()].sort((e,t)=>e.domain.localeCompare(t.domain))}function M(e,t){if(!t)return!0;let n=A(e),r=t.type===`runtime`,i=t.type===`static`;return Array.isArray(e.domain_types)&&!e.domain_types.includes(t.type)?!1:n.startsWith(`mcp.`)?t.type===`mcp`:n.startsWith(`runtime.`)||[`endpoint.add_runtime`,`endpoint.default`].includes(n)?r:[`endpoint.add_static`,`endpoint.change_source`,`endpoint.sync`,`endpoint.deploy_key`,`endpoint.repo_access`,`endpoint.filetypes`].includes(n)?i:[`endpoint.remove`,`pages.docs`,`pages.openapi`,`pages.write`].includes(n)?i||r:n===`domain.go_live`?!t.live:n!==`domain.stage`||t.live}function be(e,t,n){let r=new Map;for(let i of e){if(!M(i,n))continue;let e=ye(i).find(([e])=>e===t);e&&(r.has(e[1])||r.set(e[1],[]),r.get(e[1]).push(i))}return[...r].map(([e,t])=>({title:e,operations:t}))}function xe(e){return ve(e).some(e=>e.name===`domain`&&e.required)&&!A(e).startsWith(`domain.deploy_`)&&A(e)!==`dashboard.enable`}function Se(e){return ve(e).some(e=>e.name===`endpoint`)&&!A(e).startsWith(`domain.deploy_`)&&!A(e).startsWith(`endpoint.add_`)}function Ce(e){let t=A(e);return t.startsWith(`pages.`)?`Domain page`:t===`logs.view`?`Domain logs`:`All endpoints`}function N(e,t={},n){let r=e&&A(e);if(!e?.runtime_kinds||![`domain.deploy_runtime`,`endpoint.add_runtime`].includes(r))return null;let i=r===`endpoint.add_runtime`?n?.kind:t.kind,a=e.runtime_kinds[i],o=r===`endpoint.add_runtime`?new Set((n?.endpoints||[]).map(e=>e.label)):new Set,s=o.has(`root`),c=s?[]:(a?.versions||[]).filter(e=>!o.has(e)),l=c.includes(a?.default_version)?a.default_version:c[0]||``,u=r===`endpoint.add_runtime`?`endpoint`:`version`,d=t[u]||l;return{kind:i,kinds:Object.keys(e.runtime_kinds),versions:c,defaultVersion:l,versionField:u,version:d,rootEndpoint:s,repositories:(e.repositories||[]).filter(e=>e.version===d)}}function P(e,t,n){let r=N(e,t,n);return ve(e).map(e=>r?e.name===`kind`?{...e,choices:r.kinds}:e.name===r.versionField?{...e,label:`API version`,choices:r.versions,disabled:!r.kind||!r.versions.length,description:r.kind?r.rootEndpoint?`This domain serves a root endpoint, which cannot coexist with version folders.`:r.versions.length?e.description:`This domain already serves every available version.`:`Choose a runtime kind to see its API versions.`}:e.name===`repository`?{...e,type:`runtime_repository`,label:`Local Bible source`,repositories:r.repositories,disabled:!r.kind||!r.versions.length}:e:e)}function we(e,t,n,r,i){let a={...t,[n]:r},o=N(e,a,i);return o&&t[n]!==r&&[`kind`,`version`,`endpoint`,`domain`].includes(n)&&(delete a.repository,delete a._repositoryMode,(n===`kind`||n===`domain`)&&(a[o.versionField]=o.defaultVersion)),a}function Te(e,{domain:t,endpoint:n}={}){let r=ve(e),i=Object.fromEntries(r.filter(e=>e.default!==void 0).map(e=>[e.name,e.default]));t&&r.some(e=>e.name===`domain`)&&(i.domain=t.domain),n!==void 0&&r.some(e=>e.name===`endpoint`)&&(i.endpoint=n?.label||``);let a=t?.settings||{},o=n?.endpoint_settings||{},s=A(e),c=s===`access.mode`?{mode:a.ACCESS_MODE}:s===`access.limits`?{rate:a.RATE_PER_SECOND,burst:a.RATE_BURST,hour:a.QUOTA_HOUR,day:a.QUOTA_DAY,connections:a.CONN_LIMIT}:s===`endpoint.change_source`?{repository:o.REPO_URL,ref:o.REPO_REF,source_path:o.REPO_PATH}:s===`cloudflare.mode`?{value:a.CLOUDFLARE_MODE}:s===`cloudflare.cache`?{value:a.CLOUDFLARE_CACHE}:{},l={...i,...Object.fromEntries(Object.entries(c).filter(([,e])=>e!==void 0))},u=N(e,l,t);return u&&(l[u.versionField]=u.defaultVersion),l}function Ee(e,t){return Object.fromEntries(ve(e).filter(e=>t[e.name]!==void 0&&(t[e.name]!==``||e.allow_empty)).map(e=>[e.name,t[e.name]]))}var De=(e,t)=>Array.isArray(e)?e:e?.[t]||[],Oe=new Set([`queued`,`waiting`,`running`]);function ke({items:e,onSelect:t}){return(0,E.jsx)(`div`,{className:`manage-grid`,children:e.map(e=>(0,E.jsxs)(`button`,{type:`button`,className:`manage-card`,onClick:()=>t(e),children:[(0,E.jsx)(`strong`,{children:e.title}),e.detail&&(0,E.jsx)(`small`,{children:e.detail}),e.badge&&(0,E.jsx)(le,{tone:e.tone,children:e.badge})]},e.id||e.title))})}function Ae({field:e,values:t,setValues:n,onError:r,domains:i,currentSettings:a,spec:o,selectedDomain:s}){let c=t=>n(n=>({...we(o,n,e.name,t,s),...e.name===`key`&&a?{value:a[t]??``}:{}})),l={className:`form-control`,"aria-label":e.label||e.name,required:e.required&&!e.allow_empty,disabled:e.disabled},u;if(e.type===`runtime_repository`){let r=t._repositoryMode===`manual`;return(0,E.jsxs)(`div`,{children:[(0,E.jsxs)(`label`,{children:[e.label,(0,E.jsxs)(`select`,{...l,className:`form-select`,value:r?`__manual__`:t.repository||``,onChange:e=>{let t=e.target.value;n(e=>({...e,repository:t===`__manual__`?``:t,_repositoryMode:t===`__manual__`?`manual`:`published`}))},children:[(0,E.jsx)(`option`,{value:``,children:`Automatic local source`}),e.repositories.map(e=>(0,E.jsx)(`option`,{value:e.value,children:e.label},e.value)),(0,E.jsx)(`option`,{value:`__manual__`,children:`Enter a local path…`})]})]}),r&&(0,E.jsxs)(`label`,{children:[`Custom local path`,(0,E.jsx)(`input`,{className:`form-control`,"aria-label":`Custom local path`,type:`text`,required:!0,autoComplete:`off`,value:t.repository||``,onChange:e=>c(e.target.value)})]}),e.description&&(0,E.jsx)(`small`,{children:e.description})]})}return u=e.type===`file`?(0,E.jsx)(`input`,{...l,type:`file`,accept:`.png,.jpg,.jpeg,.webp,.ico,.svg`,onChange:t=>{let i=t.target.files?.[0];if(!i)return;if(i.size>1048576){r(Error(`Choose an image smaller than 1 MiB.`)),t.target.value=``;return}let a=new FileReader;a.onload=()=>n(t=>({...t,filename:i.name,[e.name]:String(a.result).split(`,`)[1]})),a.onerror=()=>r(Error(`The selected file could not be read.`)),a.readAsDataURL(i)}}):e.type===`multiline`?(0,E.jsx)(`textarea`,{...l,rows:12,spellCheck:`false`,value:t[e.name]||``,onChange:e=>c(e.target.value)}):e.type===`boolean`?(0,E.jsxs)(`select`,{"aria-label":e.label||e.name,className:`form-select`,value:String(t[e.name]??!1),onChange:e=>c(e.target.value===`true`),children:[(0,E.jsx)(`option`,{value:`false`,children:`No`}),(0,E.jsx)(`option`,{value:`true`,children:`Yes`})]}):e.name===`domain`&&!e.required?(0,E.jsxs)(`select`,{"aria-label":e.label||e.name,className:`form-select`,value:t.domain||``,onChange:e=>c(e.target.value),children:[(0,E.jsx)(`option`,{value:``,children:e.scopeLabel||`All domains`}),i.map(e=>(0,E.jsx)(`option`,{value:e.domain,children:e.domain},e.domain))]}):e.choices||e.enum?(0,E.jsxs)(`select`,{...l,className:`form-select`,value:t[e.name]??``,onChange:e=>c(e.target.value),children:[(0,E.jsx)(`option`,{value:``,children:`Select…`}),(e.choices||e.enum).map(e=>(0,E.jsx)(`option`,{value:e,children:e},e))]}):(0,E.jsx)(`input`,{...l,type:e.secret?`password`:e.type===`integer`?`number`:`text`,autoComplete:e.secret?`new-password`:`off`,value:t[e.name]??``,onChange:t=>c(e.type===`integer`&&t.target.value!==``?Number(t.target.value):t.target.value)}),(0,E.jsxs)(`label`,{className:e.type===`multiline`?`full-width`:void 0,children:[e.label||e.name,u,e.description&&(0,E.jsx)(`small`,{children:e.description})]})}function je({execute:e,onError:t,onDetail:n,refresh:r,initialSection:i=``}){let[a,o]=(0,_.useState)(null),[s,c]=(0,_.useState)(null),[l,u]=(0,_.useState)(null),[d,f]=(0,_.useState)(null),[p,m]=(0,_.useState)(null),[h,g]=(0,_.useState)({section:i,domain:``,group:``,operation:``,endpoint:void 0}),[v,y]=(0,_.useState)({});(0,_.useEffect)(()=>{let e=!1;return u(null),Promise.allSettled([x(`operations`),x(`endpoints`)]).then(n=>{if(e)return;n[0].status===`fulfilled`&&o(n[0].value),n[1].status===`fulfilled`&&c(n[1].value);let r=n.find(e=>e.status===`rejected`);r&&(u(r.reason),t(r.reason))}),()=>{e=!0}},[r]),(0,_.useEffect)(()=>{let e=!1,n;async function r(){try{let[t,n]=await Promise.all([x(`jobs`),x(`management/state`)]);e||(f(t),m(n))}catch(n){e||t(n)}e||(n=setTimeout(r,2e3))}return r(),()=>{e=!0,clearTimeout(n)}},[r]);let b=De(a,`operations`),ee=(0,_.useMemo)(()=>j(s),[s]),te=ee.find(e=>e.domain===h.domain),S=te?.endpoints.find(e=>e.label===h.endpoint),C=b.find(e=>A(e)===h.operation),w=P(C,v,te),ne=N(C,v,te),re=!(!ne||ne.kind&&ne.versions.length),ie=_e.find(e=>e.id===h.section),ae=be(b,h.section,te),oe=ae.find(e=>e.title===h.group),ue=[`domains`,`go-live`].includes(h.section),de=!!(h.section&&!h.domain&&(ue||C&&xe(C))),fe=!!(C&&!de&&Se(C)&&h.endpoint===void 0),pe=p?.accepting_jobs===!1,me=!!(h.domain&&s&&!te),he=!!(h.endpoint&&s&&!S),ve=w.filter(e=>!(e.name===`domain`&&h.domain)&&!(e.name===`endpoint`&&Se(C))&&!(e.name===`filename`&&w.some(e=>e.type===`file`)));function ye(e){g(e);let t=b.find(t=>A(t)===e.operation),n=ee.find(t=>t.domain===e.domain),r=e.endpoint===void 0?void 0:n?.endpoints.find(t=>t.label===e.endpoint)||null;y(t?Te(t,{domain:n,endpoint:r}):{})}function we(e){let t=be(b,e.id),n=t.length===1?t[0]:void 0;ye({section:e.id,domain:``,group:n?.title||``,operation:n?.operations.length===1?A(n.operations[0]):``,endpoint:void 0})}function je(e){ye({...h,domain:e.id,endpoint:void 0})}function Me(e){ye({...h,operation:e.id,endpoint:void 0})}function Ne(t){t.preventDefault(),!(!C||pe||me||he||re)&&e(A(C),Ee(C,v),{title:C.title,description:C.description})}let Pe=[{label:`Manage`,action:()=>ye({section:``,domain:``,group:``,operation:``,endpoint:void 0})}];ie&&Pe.push({label:ie.title,action:()=>we(ie)}),h.domain&&ue&&Pe.push({label:h.domain,action:()=>ye({...h,group:``,operation:``,endpoint:void 0})}),h.group&&Pe.push({label:h.group,action:()=>ye({...h,operation:``,endpoint:void 0})}),C&&Pe.push({label:C.title,action:()=>ye({...h,domain:ue?h.domain:``,endpoint:void 0})}),h.domain&&!ue&&Pe.push({label:h.domain,action:()=>ye({...h,endpoint:void 0})}),C&&h.endpoint!==void 0&&Pe.push({label:h.endpoint||Ce(C)});let Fe;if(!a)Fe=l?(0,E.jsx)(D,{children:`Management actions could not be loaded. Use Refresh all to retry.`}):(0,E.jsx)(ce,{});else if(!b.length)Fe=(0,E.jsx)(D,{children:`No management operations were returned by the broker.`});else if(!ie)Fe=(0,E.jsx)(ke,{items:_e.filter(e=>be(b,e.id).length),onSelect:we});else if(me||he)Fe=(0,E.jsxs)(D,{children:[`This `,me?`domain`:`endpoint`,` is no longer registered. Select another from the menu above.`]});else if(de){let e=ee.filter(e=>h.section===`go-live`?!e.live:!C||M(C,e));Fe=s?e.length?(0,E.jsx)(ke,{items:e.map(e=>({id:e.domain,title:e.domain,detail:`${e.type===`mcp`?`MCP at /`:`${e.kind||e.type} · ${e.endpoints.length} endpoints`} · ${e.settings?.ACCESS_MODE||`access not reported`}`,badge:e.live?`Live`:`Staged`,tone:e.live?`success`:`info`})),onSelect:je}):(0,E.jsx)(D,{children:h.section===`go-live`?`No staged domains are waiting to go live.`:`No matching domains are registered. Choose Deploy a new domain from Manage to begin.`}):l?(0,E.jsx)(D,{children:`The domain inventory could not be loaded. Use Refresh all to retry.`}):(0,E.jsx)(ce,{children:`Loading registered domains…`})}else if(!h.group)Fe=(0,E.jsx)(ke,{items:ae.map(e=>({id:e.title,title:e.title,detail:e.operations.map(e=>e.title).join(` · `)})),onSelect:e=>{let t=ae.find(t=>t.title===e.id);ye({...h,group:e.id,operation:t.operations.length===1?A(t.operations[0]):``,endpoint:void 0})}});else if(!C)Fe=(0,E.jsx)(ke,{items:(oe?.operations||[]).map(e=>({id:A(e),title:e.title,detail:e.description,badge:e.mutates===!1?`View`:void 0})),onSelect:Me});else if(fe){let e=w.find(e=>e.name===`endpoint`),t=(te?.endpoints||[]).map(e=>({id:e.label,title:e.label===`root`?`Domain root`:`/${e.label}/`,detail:e.repository||`${e.kind} endpoint`}));e.required||t.unshift({id:``,title:Ce(C),detail:`Use the domain scope for this operation`}),Fe=t.length?(0,E.jsx)(ke,{items:t,onSelect:e=>ye({...h,endpoint:e.id})}):(0,E.jsx)(D,{children:`No endpoints are registered for this domain. Add an endpoint from Endpoints and repositories.`})}else Fe=A(C)===`system.update`?(0,E.jsx)(ge,{execute:e,refresh:r,paused:pe}):(0,E.jsxs)(`form`,{className:`management-form`,onSubmit:Ne,children:[C.description&&(0,E.jsx)(`p`,{className:`text-secondary`,children:C.description}),(h.domain||h.endpoint!==void 0)&&(0,E.jsxs)(`div`,{className:`management-context`,children:[(0,E.jsx)(`strong`,{children:h.domain}),h.endpoint!==void 0&&(0,E.jsxs)(`span`,{children:[` · `,h.endpoint?h.endpoint===`root`?`Domain root`:`/${h.endpoint}/`:Ce(C)]})]}),(0,E.jsx)(`div`,{className:`filter-grid`,children:ve.map(e=>(0,E.jsx)(Ae,{field:e.name===`domain`&&A(C)===`pages.upload_icon`?{...e,scopeLabel:`System icons`}:e,values:v,setValues:y,domains:ee,spec:C,selectedDomain:te,currentSettings:A(C)===`runtime.set`?S?.endpoint_settings:void 0,onError:t},`${h.operation}-${h.domain}-${h.endpoint}-${e.name}`))}),!!S&&A(C)===`runtime.set`&&(0,E.jsxs)(`details`,{className:`current-settings`,children:[(0,E.jsx)(`summary`,{children:`Current endpoint settings`}),(0,E.jsx)(k,{rows:Object.entries(S.endpoint_settings||{}).map(([e,t])=>({key:e,value:t})),columns:[{key:`key`,label:`Setting`},{key:`value`,label:`Current value`}]})]}),(0,E.jsx)(`button`,{className:`btn btn-primary`,disabled:pe||re,children:`Review operation`})]});let Ie=De(d,`jobs`),Le=Ie.filter(e=>Oe.has(e.status));return(0,E.jsxs)(E.Fragment,{children:[p?.refresh?.pending&&(0,E.jsxs)(`div`,{className:`alert ${p.refresh.state===`failed`?`alert-warning`:`alert-info`}`,role:`status`,children:[(0,E.jsx)(`strong`,{children:p.refresh.state===`failed`?`Management service update needs attention`:`Management services are updating`}),(0,E.jsx)(`div`,{children:p.refresh.state===`failed`?p.refresh.last_error||`The service refresh could not be scheduled. Management remains available; retry the update or inspect the service from the CLI.`:`Accepted jobs will finish before management restarts. Existing results and issued credentials remain available.`})]}),(0,E.jsxs)(O,{title:C?.title||h.group||(de?`Choose a domain`:ie?.title)||`Server management`,detail:`Follow the CLI menu flow. Started jobs continue when this page closes.`,children:[(0,E.jsx)(`nav`,{"aria-label":`Management navigation`,className:`management-breadcrumb`,children:Pe.map((e,t)=>(0,E.jsxs)(_.Fragment,{children:[t>0&&(0,E.jsx)(`span`,{"aria-hidden":`true`,children:`/`}),e.action&&te.status===`waiting`)?`An operation is waiting for the current CLI or management operation to finish; it will continue automatically.`:`Select an operation below for live progress.`]}),(0,E.jsx)(O,{title:`Operation history`,detail:`Select a job to view live progress and output`,children:d?(0,E.jsx)(k,{rows:Ie,onSelect:async e=>{try{n(`Operation details`,await x(`jobs/${encodeURIComponent(e.id)}`))}catch(e){t(e)}},columns:[{key:`id`,label:`Job`},{key:`operation`,label:`Operation`,render:e=>b.find(t=>A(t)===e.operation)?.title||e.operation},{key:`status`,label:`State`,render:e=>(0,E.jsx)(le,{tone:[`failed`,`interrupted`].includes(e.status)?`warning`:e.status===`succeeded`?`success`:`info`,children:e.status})},{key:`created`,label:`Submitted`,render:e=>T(e.created_at??e.created)},{key:`started`,label:`Started`,render:e=>T(e.started_at??e.started)},{key:`finished`,label:`Finished`,render:e=>T(e.finished_at??e.finished)}]}):(0,E.jsx)(ce,{})})]})}var Me=e=>String(e||`unknown`).replaceAll(`_`,` `),Ne=e=>e==null?`Unknown`:`${e<0?`−`:``}${w(Math.abs(e))}/s`;function Pe({refresh:e,execute:t}){let[n,r]=(0,_.useState)(null),[i,a]=(0,_.useState)(null);(0,_.useEffect)(()=>{let e=!1,t,n=new AbortController;async function i(){try{let t=await x(`capacity`,{signal:n.signal});e||(r(t),a(null))}catch(t){!e&&t.name!==`AbortError`&&a(t)}e||(t=setTimeout(i,3e4))}return i(),()=>{e=!0,clearTimeout(t),n.abort()}},[e]);let o=!!(i||n?.stale||n?.state!==`observed`),s=n?.collection||{};return(0,E.jsxs)(O,{title:`Capacity and sizing advice`,detail:`Measured limits and repeated saturation; suggestions require operator review and never change settings automatically.`,children:[(0,E.jsx)(se,{error:i}),n?(0,E.jsxs)(E.Fragment,{children:[(0,E.jsxs)(`div`,{className:`alert ${o?`alert-warning`:`alert-info`}`,role:`status`,children:[(0,E.jsx)(`strong`,{children:Me(n.state)}),` · Last sample: `,T(n.sampled_at),(0,E.jsx)(`div`,{children:n.note})]}),(0,E.jsxs)(`div`,{className:`fact-list`,children:[(0,E.jsxs)(`div`,{children:[(0,E.jsx)(`span`,{children:`Collection`}),(0,E.jsx)(`strong`,{children:o?`Unknown — observations are not current`:Me(s.state)})]}),(0,E.jsxs)(`div`,{children:[(0,E.jsx)(`span`,{children:`Unread backlog`}),(0,E.jsxs)(`strong`,{children:[w(s.unread_bytes),` · `,C(s.unread_files),` files`]})]}),(0,E.jsxs)(`div`,{children:[(0,E.jsx)(`span`,{children:`Transport / retained archives`}),(0,E.jsxs)(`strong`,{children:[w(s.budgeted_spool_bytes),` / `,w(s.retained_archive_bytes)]})]}),(0,E.jsxs)(`div`,{children:[(0,E.jsx)(`span`,{children:`Producer / collector throughput`}),(0,E.jsxs)(`strong`,{children:[Ne(s.producer_bytes_per_second),` / `,Ne(s.collector_bytes_per_second)]})]}),(0,E.jsxs)(`div`,{children:[(0,E.jsx)(`span`,{children:`Backlog growth / continuous backlog`}),(0,E.jsxs)(`strong`,{children:[Ne(s.backlog_growth_bytes_per_second),` / `,C(s.backlog_observed_seconds),` seconds`]})]}),(0,E.jsxs)(`div`,{children:[(0,E.jsx)(`span`,{children:`Rate window / peak window`}),(0,E.jsxs)(`strong`,{children:[C(s.rate_window_seconds),` seconds / `,C(n.window_seconds/3600),` hours`]})]})]}),(0,E.jsx)(k,{rowKey:e=>e.id,rows:n.limits||[],columns:[{key:`label`,label:`Limit`,render:e=>(0,E.jsxs)(E.Fragment,{children:[(0,E.jsx)(`strong`,{children:e.label}),(0,E.jsxs)(`small`,{className:`d-block`,children:[e.setting||`Host capacity`,` · `,e.unit]})]})},{key:`used`,label:`Usage / effective limit`,render:e=>e.available?`${C(e.used,2)} / ${C(e.effective_limit,2)} ${e.unit}`:`Unavailable`},{key:`high_water`,label:`Observed peak`,render:e=>e.available?`${C(e.high_water,2)} ${e.unit}`:`Unknown`},{key:`episodes`,label:`Saturation`,render:e=>e.available?(0,E.jsxs)(E.Fragment,{children:[C(e.episodes),` episodes`,(0,E.jsxs)(`small`,{className:`d-block`,children:[C(e.saturated_samples),` / `,C(e.samples),` samples; `,C(e.saturated_seconds),` / `,C(e.observed_seconds),` observed seconds`]}),(0,E.jsxs)(`small`,{className:`d-block`,children:[`At or above `,C(e.saturation_threshold*100),`% of limit`]})]}):`Unknown`},{key:`configuration`,label:`Configured value / owner`,render:e=>(0,E.jsxs)(E.Fragment,{children:[e.configuration?.value??`Not observable`,(0,E.jsxs)(`small`,{className:`d-block`,children:[Me(e.configuration?.owner),e.configuration?.editable?` · editable`:` · change at source`]}),(0,E.jsx)(`small`,{className:`d-block`,children:e.configuration?.note})]})},{key:`recommendation`,label:`Recommendation`,render:e=>{let n=he(e,o);return(0,E.jsxs)(E.Fragment,{children:[(0,E.jsx)(le,{tone:e.recommendation?.status===`adequate`?`success`:`info`,children:Me(e.recommendation?.status)}),e.recommendation?.value!=null&&!o&&(0,E.jsxs)(`strong`,{className:`d-block`,children:[C(e.recommendation.value,2),` `,e.unit]}),(0,E.jsx)(`small`,{className:`d-block`,children:e.recommendation?.reason}),e.recommendation?.assumptions&&(0,E.jsx)(`small`,{className:`d-block`,children:e.recommendation.assumptions}),n&&(0,E.jsx)(`button`,{className:`btn btn-sm btn-outline-primary mt-2`,type:`button`,onClick:()=>t(`settings.set`,n,{title:`Review ${n.key} = ${n.value}`,description:`This is an observed-demand recommendation, not an automatic correction or a guarantee. Confirm free capacity and collection health before applying.`}),children:`Review suggested setting`})]})}}]}),!!n.incidents?.length&&(0,E.jsxs)(E.Fragment,{children:[(0,E.jsx)(`h3`,{className:`h6 mt-3`,children:`Open capacity incidents`}),(0,E.jsx)(k,{rows:n.incidents,rowKey:e=>e.id,columns:[{key:`id`,label:`Condition`},{key:`since`,label:`Since`,render:e=>T(e.since)},{key:`last_sent`,label:`Last notification`,render:e=>T(e.last_sent)},{key:`last_event`,label:`Last transition`,render:e=>Me(e.last_event)}]})]}),!!Object.keys(n.problems||{}).length&&(0,E.jsxs)(`details`,{className:`mt-3`,children:[(0,E.jsx)(`summary`,{children:`Collection and reclamation diagnostics`}),(0,E.jsx)(`pre`,{className:`record-detail`,children:JSON.stringify(n.problems,null,2)})]})]}):!i&&(0,E.jsx)(ce,{children:`Reading capacity observations…`})]})}function Fe(e,t,n,r=Date.now()/1e3){let i=e.cache||{},a=i.query_translations?.[t],o=i.search_corpora?.translations?.[t],s=i.translation_cache?.translations?.[t],c=a?.expired_chapters>0||[o,s].some(e=>e&&(e.stale||e.checked_at!=null&&i.ttl_seconds!=null&&e.checked_at+i.ttl_seconds<=r)),l=e.translation_status?.[t],u=!!(a||o||s),d=!c&&(l?.ready??(n===`search`?!!(o?.verses>0&&o.indexes?.some(e=>e.case_sensitive===!1&&e.fold_diacritics===!0)):a?.complete===!0));return{code:t,pid:e.pid,resident:u,ready:!!d,stale:c,reason:l?.reason||``,retention_limited:l?.retention_limited===!0,query_chapters:a?.chapters,query_bytes:a?.estimated_bytes,search_verses:o?.verses,search_bytes:o?.estimated_bytes,snapshot_bytes:s?.estimated_bytes,snapshot_present:!!s,ttl:i.ttl_seconds,rss_bytes:e.rss_bytes,private_bytes:e.private_bytes,details:{query:a,search:o,snapshot:s,readiness:l,activity:e.activity},worker:e}}function Ie(e,t=Date.now()/1e3){let n=e.workers||[],r=new Set;for(let e of n)for(let t of[...Object.keys(e.cache?.query_translations||{}),...Object.keys(e.cache?.search_corpora?.translations||{}),...Object.keys(e.cache?.translation_cache?.translations||{})])r.add(t);return[...r].sort().map(n=>Le(e,n,t))}function Le(e,t,n=Date.now()/1e3){let r=(e.workers||[]).map(r=>Fe(r,t,e.kind,n)),i=e.expected_workers??r.length,a=e.complete===!0&&r.length>0&&r.length===i,o=r.filter(e=>e.resident),s=r.filter(e=>e.ready).length,c=o.some(e=>e.stale),l=a&&s===i;return{code:t,workers:r,resident:o,expected:i,completeReport:a,readyCount:s,ready:l,stale:c,status:l?`Warm`:c?`Recheck on use`:o.length?`Partly resident`:a?`Not in memory`:`Not reported`,configured:(e.configured_warm_translations||[]).includes(t),reasons:[...new Set(r.filter(e=>e.resident&&!e.ready).map(e=>e.reason).filter(Boolean))]}}function Re(e,t){let n=e.map(e=>e[t]).filter(e=>Number.isFinite(e));return n.length?{min:Math.min(...n),max:Math.max(...n)}:null}function ze(e,t,n=C){let r=Re(e,t);return r?r.min===r.max?n(r.min):`${n(r.min)}–${n(r.max)}`:`—`}function Be(e,t,n){let r=Re(e,t);return`${ze(e,t)} ${n}${r?.min===1&&r?.max===1?``:`s`}`}function Ve({row:e}){return(0,E.jsx)(le,{tone:e.ready?`success`:e.resident.length?`warning`:`muted`,children:e.status})}function He({row:e,onBack:t,onDetail:n}){return(0,E.jsxs)(`div`,{className:`worker-layer`,children:[(0,E.jsxs)(`div`,{className:`worker-heading`,children:[(0,E.jsx)(`button`,{className:`btn btn-sm btn-outline-secondary`,onClick:t,children:`Back to translations`}),(0,E.jsxs)(`strong`,{children:[e.code.toUpperCase(),` · workers`]}),(0,E.jsx)(Ve,{row:e})]}),(0,E.jsxs)(`p`,{className:`panel-note`,children:[C(e.resident.length),` of `,C(e.expected),` workers currently retain this translation. Counts and estimates below describe each worker separately. A worker can retain query chapters alongside a complete search corpus.`]}),(0,E.jsx)(k,{rows:e.workers,rowKey:e=>e.pid,onSelect:t=>n(`${e.code.toUpperCase()} · worker ${t.pid}`,t.details),columns:[{key:`pid`,label:`Worker PID`},{key:`resident`,label:`Cache state`,render:e=>(0,E.jsxs)(`div`,{children:[(0,E.jsx)(le,{tone:e.ready?`success`:e.resident?`warning`:`muted`,children:e.ready?`Warm`:e.stale?`Recheck on use`:e.resident?`Partly resident`:`Not in memory`}),e.reason&&(0,E.jsx)(`small`,{className:`cell-note`,children:e.reason})]})},{key:`query_chapters`,label:`Query chapters`,render:e=>(0,E.jsxs)(`div`,{children:[C(e.query_chapters),(0,E.jsxs)(`small`,{className:`cell-note`,children:[w(e.query_bytes),` estimated`]})]})},{key:`search_verses`,label:`Search corpus / index`,render:e=>(0,E.jsxs)(`div`,{children:[e.search_verses==null?`—`:`${C(e.search_verses)} verses`,(0,E.jsxs)(`small`,{className:`cell-note`,children:[w(e.search_bytes),` estimated`]})]})},{key:`snapshot_bytes`,label:`Translation snapshot`,render:e=>e.snapshot_present?`${w(e.snapshot_bytes)} estimated`:`—`},{key:`ttl`,label:`Memory TTL`,render:e=>e.ttl==null?`—`:`${C(e.ttl/86400,1)} days`},{key:`rss_bytes`,label:`Worker RSS`,render:e=>w(e.rss_bytes)},{key:`private_bytes`,label:`Worker private memory`,render:e=>w(e.private_bytes)}]}),(0,E.jsx)(`p`,{className:`panel-note`,children:`RSS includes shared pages and the whole worker. Private memory also covers the whole worker. Neither is a per-translation measurement; cache estimates may overlap.`})]})}function Ue({onError:e,execute:t,onDetail:n,refresh:r}){let[i,a]=(0,_.useState)(null),[o,s]=(0,_.useState)(``),[c,l]=(0,_.useState)(`kjv`),[u,d]=(0,_.useState)({});(0,_.useEffect)(()=>{let t=!1,n;async function r(){try{let e=await x(`translations`);t||a(e)}catch(n){t||e(n)}t||(n=setTimeout(r,5e3))}return r(),()=>{t=!0,clearTimeout(n)}},[r]);let f=i?.endpoints||[],p=f.find(e=>`${e.domain}/${e.label}`===o),m=p?.available_translations||[],h=c.trim().toLowerCase(),g=p?Le(p,h):null,v=/^[a-z0-9][a-z0-9_-]{0,29}$/.test(h);function y(e,n,r){t(`runtime.cache`,{domain:e.domain,endpoint:e.label,action:r,translation:n})}return(0,E.jsxs)(E.Fragment,{children:[(0,E.jsxs)(O,{title:`Translation memory`,detail:`See what is in memory, then select a translation to inspect its workers. Current residency is refreshed every five seconds.`,children:[(0,E.jsxs)(`div`,{className:`filter-grid`,children:[(0,E.jsxs)(`label`,{children:[`Runtime endpoint`,(0,E.jsxs)(`select`,{"aria-label":`Runtime endpoint`,className:`form-select`,value:o,onChange:e=>s(e.target.value),children:[(0,E.jsx)(`option`,{value:``,children:`Select endpoint`}),f.map(e=>(0,E.jsxs)(`option`,{value:`${e.domain}/${e.label}`,children:[e.domain,` / `,e.label,` · `,e.kind]},`${e.domain}/${e.label}`))]})]}),(0,E.jsxs)(`label`,{children:[`Translation`,(0,E.jsx)(`input`,{list:`available-translations`,className:`form-control`,value:c,onChange:e=>l(e.target.value),placeholder:`kjv`}),(0,E.jsx)(`datalist`,{id:`available-translations`,children:m.map(e=>(0,E.jsx)(`option`,{value:e.translation,children:Le(p,e.translation).status},e.translation))})]}),(0,E.jsx)(`div`,{className:`d-flex gap-2 align-items-end`,children:(0,E.jsx)(`button`,{className:`btn btn-primary`,disabled:!p||!v||g?.ready,onClick:()=>y(p,h,`warm`),children:g?.ready?`Already warm`:`Warm translation`})})]}),g&&v&&(0,E.jsxs)(`div`,{className:`warm-selection`,"aria-live":`polite`,children:[(0,E.jsx)(Ve,{row:g}),(0,E.jsxs)(`span`,{children:[h.toUpperCase(),` · `,C(g.resident.length),` / `,C(g.expected),` workers retain data · `,C(g.readyCount),` ready`,g.configured?` · configured for startup warming`:``]}),g.reasons.map(e=>(0,E.jsx)(`small`,{children:e},e)),!g.completeReport&&(0,E.jsx)(`small`,{children:`Some workers have not reported; their memory state is unknown.`})]})]}),i?f.length?f.map(e=>{let t=`${e.domain}/${e.label}`,r=Ie(e),i=u[t]?Le(e,u[t]):null;return(0,E.jsxs)(O,{title:`${e.domain} / ${e.label}`,detail:`${e.kind} · ${r.length} translation${r.length===1?``:`s`} currently resident · ${e.workers?.length||0} worker${e.workers?.length===1?``:`s`} reported`,actions:(0,E.jsx)(le,{tone:e.complete?`success`:`warning`,children:e.complete?`All workers reported`:`Partial report`}),children:[i?(0,E.jsx)(He,{endpoint:e,row:i,onDetail:n,onBack:()=>d({...u,[t]:``})}):(0,E.jsxs)(E.Fragment,{children:[r.length?(0,E.jsx)(k,{rows:r,rowKey:e=>e.code,onSelect:e=>d({...u,[t]:e.code}),columns:[{key:`code`,label:`Translation`,render:e=>(0,E.jsxs)(`span`,{children:[e.code.toUpperCase(),(0,E.jsx)(`small`,{className:`cell-note`,children:e.configured?`Startup warming configured`:`Currently in memory`})]})},{key:`status`,label:`Readiness`,render:e=>(0,E.jsxs)(`div`,{children:[(0,E.jsx)(Ve,{row:e}),e.reasons.map(e=>(0,E.jsx)(`small`,{className:`cell-note`,children:e},e))]})},{key:`workers`,label:`Worker coverage`,render:e=>(0,E.jsxs)(`div`,{children:[C(e.resident.length),` / `,C(e.expected),` resident`,(0,E.jsxs)(`small`,{className:`cell-note`,children:[C(e.readyCount),` / `,C(e.expected),` ready`]})]})},{key:`query_chapters`,label:`Query data per worker`,render:e=>{let t=e.resident.filter(e=>e.query_chapters!=null);return t.length?(0,E.jsxs)(`div`,{children:[Be(t,`query_chapters`,`chapter`),(0,E.jsxs)(`small`,{className:`cell-note`,children:[ze(t,`query_bytes`,w),` estimated · `,t.length,` worker`,t.length===1?``:`s`]})]}):`—`}},{key:`search_verses`,label:`Search data per worker`,render:e=>{let t=e.resident.filter(e=>e.search_verses!=null);return t.length?(0,E.jsxs)(`div`,{children:[ze(t,`search_verses`),` verses`,(0,E.jsxs)(`small`,{className:`cell-note`,children:[ze(t,`search_bytes`,w),` corpus / index estimate · `,t.length,` worker`,t.length===1?``:`s`]})]}):`—`}},{key:`snapshot_bytes`,label:`Snapshot per worker`,render:e=>{let t=e.resident.filter(e=>e.snapshot_present);return t.length?(0,E.jsxs)(`div`,{children:[ze(t,`snapshot_bytes`,w),` estimated`,(0,E.jsxs)(`small`,{className:`cell-note`,children:[t.length,` worker`,t.length===1?``:`s`]})]}):`—`}},{key:`actions`,label:`All workers`,render:t=>(0,E.jsxs)(`div`,{className:`btn-group`,children:[!t.ready&&(0,E.jsx)(`button`,{className:`btn btn-sm btn-outline-primary`,onClick:()=>y(e,t.code,`warm`),children:`Warm`}),(0,E.jsx)(`button`,{className:`btn btn-sm btn-outline-primary`,onClick:()=>y(e,t.code,`reload`),children:`Reload`}),(0,E.jsx)(`button`,{className:`btn btn-sm btn-outline-danger`,onClick:()=>y(e,t.code,`drop`),children:`Drop`})]})}]}):(0,E.jsx)(D,{children:e.complete?`No translations are currently retained in worker memory.`:`No resident translations have been reported yet.`}),(0,E.jsx)(`p`,{className:`panel-note`,children:`Ranges show differences between workers, not a combined memory total. Select a translation for individual worker contents and measured memory.`})]}),(0,E.jsxs)(`details`,{className:`worker-details`,children:[(0,E.jsxs)(`summary`,{children:[C(e.available_translations?.length||0),` translations available on disk`]}),(0,E.jsx)(k,{rows:e.available_translations||[],onSelect:e=>n(`${e.translation.toUpperCase()} on disk`,e),columns:[{key:`translation`,label:`Translation`,render:e=>e.translation.toUpperCase()},{key:`memory`,label:`Current memory`,render:t=>(0,E.jsx)(Ve,{row:Le(e,t.translation)})},{key:`allocated_bytes`,label:`Allocated disk`,render:e=>w(e.allocated_bytes)},{key:`logical_bytes`,label:`File contents`,render:e=>w(e.logical_bytes)},{key:`files`,label:`Files`,render:e=>C(e.files)},{key:`warm`,label:`All workers`,render:t=>{let n=Le(e,t.translation);return(0,E.jsx)(`button`,{className:`btn btn-sm btn-outline-primary`,disabled:n.ready,onClick:()=>y(e,t.translation,`warm`),children:n.ready?`Already warm`:`Warm translation`})}}]})]}),!!e.configured_warm_translations?.length&&(0,E.jsxs)(`p`,{className:`panel-note`,children:[`Configured startup warm-ups: `,e.configured_warm_translations.map(e=>e.toUpperCase()).join(`, `),`. Configuration describes startup intent; the table shows current memory.`]}),e.inventory_error&&(0,E.jsx)(`div`,{className:`alert alert-warning m-3`,children:e.inventory_error}),!!e.errors?.length&&(0,E.jsx)(`div`,{className:`alert alert-warning m-3`,children:e.errors.map((e,t)=>(0,E.jsx)(`div`,{children:typeof e==`string`?e:e.error||`Worker did not report.`},t))})]},t)}):(0,E.jsx)(D,{children:`No runtime endpoints are configured.`}):(0,E.jsx)(ce,{})]})}function We(e,t,n,r={},i=Math.floor(Date.now()/1e3)){let a=n?i:t.end,o=n?a-900:t.start,s=n?5:Math.max(1,Math.ceil((a-o)/400)),c=S({...r,start:o,end:a,bucket_seconds:s});return e===`Overview`?{overview:`overview?${c}&dimensions=auth,endpoint,status,translation,search,reference,book,ip,referrer,user_agent`,history:`history?${c}`}:e===`Resources`?{metrics:`metrics?${S({start:o,end:a,bucket_seconds:s})}`}:e===`MCP traffic`?{mcp:`mcp?${c}`}:e===`Audience`?{audience:`audience?${c}`}:{}}var Ge=()=>({data:null,error:null,loading:!0,preparing:!1,progress:null});function Ke({paths:e,live:t=!1,interval:n=2e3,onChange:r,onError:i=()=>{},onUpdate:a=()=>{},load:o=x,schedule:s=setTimeout,cancel:c=clearTimeout}){let l=new AbortController,u=new Map,d=!1;async function f(c,p=Ge()){if(d)return;let m={...p,loading:!0};r(c,m);let h,g=t?n:null;try{let t=await o(e()[c],{signal:l.signal});if(d)return;t.state===`preparing`?(h={...m,error:null,preparing:!0,progress:t.progress||null},g=Math.max(500,Math.min(3e4,Number(t.retry_after)*1e3||2e3))):(h={data:t,error:null,loading:!1,preparing:!1,progress:null},a())}catch(e){if(d||e.name===`AbortError`)return;h={...m,error:e,loading:!1,preparing:!1,progress:null},e.status===401&&(g=null,i(e))}d||(r(c,h),g!==null&&u.set(c,s(()=>f(c,h),g)))}for(let t of Object.keys(e()))f(t);return()=>{d=!0,l.abort();for(let e of u.values())c(e);u.clear()}}function qe({page:e,range:t,live:n,filters:r,refresh:i,enabled:a=!0,interval:o=2e3,onError:s,onUpdate:c}){let l=JSON.stringify([e,t.start,t.end,n,r,i,a,o]),[u,d]=(0,_.useState)({key:null,values:{}});(0,_.useEffect)(()=>{if(d({key:l,values:{}}),a)return Ke({paths:()=>We(e,t,n,r),live:n,interval:o,onError:s,onUpdate:c,onChange:(e,t)=>d(n=>({key:l,values:{...n.key===l?n.values:{},[e]:t}}))})},[l]);let f=u.key===l?u.values:{};return Object.fromEntries(Object.keys(We(e,t,n,r)).map(e=>[e,f[e]||Ge()]))}function Je({report:e,label:t,children:n}){return(0,E.jsxs)(E.Fragment,{children:[(0,E.jsx)(se,{error:e.error&&Error(`${t}: ${e.error.message}`)}),e.preparing&&(0,E.jsxs)(`div`,{className:`alert alert-info py-2`,role:`status`,children:[`Preparing `,t.toLowerCase(),` for this time range…`]}),e.data?n:e.loading&&!e.preparing?(0,E.jsxs)(ce,{children:[`Loading `,t.toLowerCase(),`…`]}):null]})}function Ye({dimension:e,title:t,label:n,filters:r,range:i,live:a,refresh:o,onFilter:s,onError:c}){let[l,u]=(0,_.useState)(``),[d,f]=(0,_.useState)(``),{audience:p}=qe({page:`Audience`,range:i,live:a,refresh:o,onError:c,interval:5e3,filters:{...r,dimension:e,top:1e3,[`${e}_contains`]:d}}),m=p.data?.[e===`referrer`?`referrers`:`user_agents`]||[];return(0,E.jsxs)(O,{title:t,detail:`Origin requests, including errors. Select a row to explore its traffic.`,children:[(0,E.jsxs)(`form`,{className:`filter-grid`,onSubmit:e=>{e.preventDefault(),f(l)},children:[(0,E.jsxs)(`label`,{children:[`Find `,n.toLowerCase(),(0,E.jsx)(`input`,{className:`form-control`,value:l,onChange:e=>u(e.target.value),placeholder:`Part of a ${n.toLowerCase()}`})]}),(0,E.jsxs)(`div`,{className:`d-flex gap-2 align-items-end`,children:[(0,E.jsx)(`button`,{className:`btn btn-sm btn-primary`,children:`Search`}),(0,E.jsx)(`button`,{type:`button`,className:`btn btn-sm btn-outline-secondary`,onClick:()=>{u(``),f(``)},children:`Clear`})]})]}),(0,E.jsx)(Je,{report:p,label:t,children:(0,E.jsx)(k,{rows:m,rowKey:e=>e.value,onSelect:t=>s(e,t.value,t.filters),columns:[{key:`value`,label:n,render:e=>(0,E.jsx)(`span`,{className:`consumer-value`,title:e.value,children:e.value})},{key:`calls`,label:`Requests`,render:e=>C(e.calls)},{key:`errors`,label:`Errors`,render:e=>C(e.errors)},{key:`bytes`,label:`Transfer`,render:e=>w(e.bytes)}]})}),(0,E.jsx)(`div`,{className:`table-footer`,children:(0,E.jsxs)(`small`,{children:[m?.length===1e3?`Top 1,000 matching values. Search to narrow the results.`:`${C(m?.length||0)} matching values`,` · requests without a recorded `,n.toLowerCase(),` are omitted.`]})})]})}function Xe(e){return(0,E.jsxs)(E.Fragment,{children:[(0,E.jsx)(`p`,{className:`text-secondary`,children:`Referrers and user agents are recorded request fields. User agents may identify software, robots or browsers; these reported values do not establish a person's identity. Select a value to inspect matching requests in this time range.`}),(0,E.jsx)(Ye,{...e,dimension:`referrer`,title:`Referrers`,label:`Referrer`}),(0,E.jsx)(Ye,{...e,dimension:`user_agent`,title:`User agents and robots`,label:`User agent`})]})}function Ze(e,t,n={}){return{...n,...t.filters||{[e]:t.value}}}var Qe=e=>[`queued`,`waiting`,`running`].includes(e),$e=[[`mcp_method`,`MCP method`],[`mcp_tool`,`MCP tool`],[`mcp_client_name`,`Declared MCP client`],[`mcp_client_version`,`MCP client version`],[`upstream_service`,`Upstream service`],[`upstream_api_version`,`Upstream API version`],[`upstream_operation`,`Upstream operation`]],et=[`success`,`tool_error`,`protocol_error`,`transport_error`,`unknown`],tt=new Set([`tool_error`,`protocol_error`,`transport_error`]);function nt(e={}){return{...e,endpoint_kind:`mcp`,origin_only:`true`}}function rt(e={}){let t=nt(e);for(let e of[`version`,`translation`,`reference`,`search`,`book`,`usage`,`operation`])delete t[e];return e.endpoint_kind!==`mcp`&&delete t.endpoint,t}function it(e){return Number(e.status)>=400||e.mcp_error===!0||tt.has(e.mcp_outcome)}function at(e,t,n){if(t===`endpoint_kind`&&n===`mcp`)return rt(e);let r={...e,[t]:n};if((t===`successful`&&n!==`true`||t===`endpoint_kind`)&&delete r.usage,t===`endpoint_kind`&&n!==`mcp`){for(let[e]of $e)delete r[e];delete r.mcp_outcome,e.endpoint_kind===`mcp`&&delete r.endpoint}return t===`mcp_outcome`&&tt.has(n)&&(delete r.successful,delete r.usage),t===`successful`&&n===`true`&&tt.has(r.mcp_outcome)&&delete r.mcp_outcome,t===`status`&&/^\d{3}$/.test(String(n))&&!(Number(n)>=200&&Number(n)<300)&&Number(n)!==304&&(delete r.usage,delete r.successful),r}var ot=[[`endpoint`,`MCP domains`,`Origin requests to the protocol endpoint at /`,`Domain`],[`mcp_method`,`Protocol methods`,`Initialization, discovery, tool calls and notifications`,`Method`],[`mcp_tool`,`MCP tools`,`Recorded tool names, without tool arguments`,`Tool`],[`mcp_outcome`,`Protocol outcomes`,`Tool and protocol errors can have HTTP status 200`,`Outcome`],[`mcp_client_name`,`Declared clients`,`Client names supplied during initialization`,`Client`],[`mcp_client_version`,`Declared client versions`,`Client versions supplied during initialization`,`Client version`],[`upstream_service`,`Upstream API services`,`Service selected by a recorded API operation`,`Service`],[`upstream_api_version`,`Upstream API versions`,`API versions are tool inputs; the MCP host has no version paths`,`API version`],[`upstream_operation`,`Upstream API operations`,`Operation identifiers selected by recorded tool calls`,`Operation`],[`status`,`HTTP responses`,`Includes rejected and malformed origin requests`,`HTTP status`]];function st({filters:e,range:t,live:n,refresh:r,onFilter:i,onError:a,onZoom:o}){let s=(0,_.useMemo)(()=>nt(e),[e]),{mcp:c}=qe({page:`MCP traffic`,range:t,live:n,filters:s,refresh:r,onError:a}),l=c.data;function u(e,t,n){i(e,t,Ze(e,{value:t,filters:n},s))}if(!l)return(0,E.jsx)(Je,{report:c,label:`MCP traffic`});let d=[[`MCP requests`,C(l.mcp_requests??l.calls),`${C(l.unique_ips)} unique IPs`],[`Tool calls`,C(l.mcp_tool_calls),`Recorded tools/call requests`],[`Errors`,C(l.mcp_errors??l.errors),`${C(l.http_errors)} HTTP errors; includes protocol failures`],[`Average duration`,`${C(l.duration_ms,1)} ms`,`Observed origin request duration`],[`P95 duration`,l.latency_ms?.p95==null?`—`:`${C(l.latency_ms.p95,1)} ms`,`Histogram estimate`]],f=ae(l.series||[],[[`duration_ms`,`Average duration (ms)`]]);return f.xAxis.axisLabel={hideOverlap:!0},(0,E.jsxs)(E.Fragment,{children:[(0,E.jsx)(Je,{report:c,label:`MCP traffic`}),(0,E.jsx)(`p`,{className:`text-secondary`,children:`MCP origin requests are counted once, including requests rejected before protocol handling. Protocol methods and outcomes appear when captured; user agents remain available independently of declared client metadata.`}),(0,E.jsx)(`div`,{className:`metric-grid`,children:d.map(([e,t,n])=>(0,E.jsxs)(`div`,{className:`metric panel`,children:[(0,E.jsx)(`span`,{children:e}),(0,E.jsx)(`strong`,{children:t}),(0,E.jsx)(`small`,{children:n})]},e))}),(0,E.jsxs)(`div`,{className:`overview-grid`,children:[(0,E.jsx)(O,{className:`flow-panel`,title:`MCP requests and errors`,detail:`HTTP failures and captured MCP failures`,actions:(0,E.jsx)(`button`,{className:`btn btn-sm btn-outline-secondary`,onClick:()=>u(`endpoint_kind`,`mcp`,s),children:`Explore requests`}),children:(0,E.jsx)(re,{label:`MCP requests and errors over time`,height:300,onZoom:o,option:ae(l.series||[],[[`calls`,`Requests`],[`errors`,`Errors`]],{zoom:!0})})}),(0,E.jsx)(O,{title:`Request duration`,detail:`Includes the lifetime of streamed HTTP requests`,children:(0,E.jsx)(re,{label:`Average MCP origin request duration in milliseconds`,height:300,option:f})})]}),(0,E.jsx)(`div`,{className:`resource-grid`,children:ot.map(([e,t,n,r])=>(0,E.jsx)(O,{title:t,detail:n,children:(0,E.jsx)(k,{rows:l.breakdowns?.[e]||[],onSelect:t=>u(e,t.value,t.filters),columns:[{key:`value`,label:r,render:e=>(0,E.jsx)(`span`,{className:`consumer-value`,title:String(e.value),children:e.value||(0,E.jsx)(le,{children:`Unrecorded`})})},{key:`calls`,label:`Requests`,render:e=>C(e.calls)},{key:`errors`,label:`Errors`,render:e=>C(e.errors)},{key:`duration_ms`,label:`Avg. ms`,render:e=>C(e.duration_ms,1)}]})},e))}),(0,E.jsx)(Xe,{filters:s,range:t,live:n,refresh:r,onFilter:u,onError:a})]})}var ct=[`Overview`,`Traffic`,`MCP traffic`,`Audience`,`Translations`,`Resources`,`Events`,`Manage`,`Sessions`],lt={Live:900,"24 hours":86400,"7 days":604800,"30 days":2592e3,"6 months":15552e3},ut=(e,t)=>Array.isArray(e)?e:e?.[t]??e?.items??[];function dt({title:e,value:t,onClose:n,children:r}){let i=(0,_.useRef)(null),a=(0,_.useId)();return(0,_.useEffect)(()=>{let e=new window.bootstrap.Modal(i.current),t=()=>n();return i.current.addEventListener(`hidden.bs.modal`,t),e.show(),()=>{i.current?.removeEventListener(`hidden.bs.modal`,t),e.hide(),e.dispose()}},[]),(0,E.jsx)(`div`,{ref:i,className:`modal`,tabIndex:`-1`,"aria-labelledby":a,children:(0,E.jsx)(`div`,{className:`modal-dialog modal-xl modal-dialog-scrollable`,children:(0,E.jsxs)(`div`,{className:`modal-content`,children:[(0,E.jsxs)(`div`,{className:`modal-header`,children:[(0,E.jsx)(`h2`,{className:`modal-title fs-5`,id:a,children:e}),(0,E.jsx)(`button`,{className:`btn-close`,"data-bs-dismiss":`modal`,"aria-label":`Close details`})]}),(0,E.jsx)(`div`,{className:`modal-body`,children:r||(0,E.jsx)(`pre`,{className:`record-detail`,children:JSON.stringify(t,null,2)})})]})})})}function ft({onLogin:e}){let[t,n]=(0,_.useState)(``),[r,i]=(0,_.useState)(``),[a,o]=(0,_.useState)(null),[s,c]=(0,_.useState)(60),[l,u]=(0,_.useState)(null),[d,f]=(0,_.useState)(!1),[p,m]=(0,_.useState)(!1);(0,_.useEffect)(()=>{if(!a)return;let e=setInterval(()=>c(Math.max(0,Math.ceil((a.deadline-Date.now())/1e3))),250);return()=>clearInterval(e)},[a]);async function h(s){s.preventDefault(),u(null),f(!0);try{if(a)await te(`auth/token`,{challenge_id:a.challenge_id,token:r}),i(``),e(await x(`auth/status`));else{let e=await te(`auth/password`,{password:t});n(``),o({...e,deadline:Date.now()+e.expires_in*1e3})}}catch(e){u(e),(e.status===403||e.status===423)&&m(!0)}finally{f(!1)}}return(0,E.jsxs)(`main`,{className:`login-page`,children:[(0,E.jsxs)(`div`,{className:`login-brand`,children:[(0,E.jsx)(`img`,{src:`/favicon.png`,alt:``}),(0,E.jsx)(`span`,{children:`getBible`}),(0,E.jsx)(`small`,{children:`OPERATIONS`})]}),(0,E.jsxs)(`section`,{className:`login-box panel`,children:[(0,E.jsx)(`span`,{className:`eyebrow`,children:`PRIVATE CONSOLE`}),(0,E.jsx)(`h1`,{children:a?`Check Telegram`:`Sign in to your server`}),(0,E.jsx)(`p`,{children:a?`Enter the one-use code sent to your configured Telegram group.`:`Your password and a Telegram code protect access to this console.`}),(0,E.jsx)(se,{error:l}),(0,E.jsxs)(`form`,{onSubmit:h,children:[(0,E.jsx)(`label`,{className:`form-label`,htmlFor:`credential`,children:a?`Telegram code`:`Password`}),(0,E.jsx)(`input`,{autoFocus:!0,className:`form-control form-control-lg`,id:`credential`,type:a?`text`:`password`,autoComplete:a?`one-time-code`:`current-password`,spellCheck:`false`,autoCapitalize:`none`,value:a?r:t,disabled:d||p,onChange:e=>a?i(e.target.value):n(e.target.value),required:!0}),a&&(0,E.jsx)(`div`,{className:`mt-3 ${s<15?`text-danger`:`text-secondary`}`,"aria-live":`polite`,children:s?`${s}s remaining`:`Code expired. Unblock your IP through the CLI to try again.`}),(0,E.jsx)(`button`,{className:`btn btn-primary w-100 mt-4`,disabled:d||p||a&&!s,children:d?`Please wait…`:a?`Verify and open dashboard`:`Continue with Telegram`})]}),(0,E.jsx)(`p`,{className:`login-note`,children:a?`An incorrect or expired code blocks this IP until it is unblocked through the CLI.`:`Three incorrect passwords block this IP. Successful sessions last up to 30 days.`})]}),(0,E.jsx)(`small`,{className:`text-secondary`,children:`Traffic recording continues while this console is asleep.`})]})}function pt({rows:e=[],label:t,onSelect:n}){let r=Math.max(1,...e.map(e=>e.calls));return e.length?(0,E.jsx)(`div`,{className:`rankings`,children:e.slice(0,8).map((e,i)=>(0,E.jsxs)(`button`,{className:`ranking`,onClick:()=>n?.(e.value,e),children:[(0,E.jsx)(`span`,{className:`rank-index`,children:String(i+1).padStart(2,`0`)}),(0,E.jsxs)(`span`,{className:`rank-name`,title:String(e.label||e.value),children:[e.label||e.value||`Unspecified`,(0,E.jsx)(ue,{value:e.calls,max:r,label:`${t}: ${e.value}`})]}),(0,E.jsx)(`strong`,{children:C(e.calls)})]},`${e.value}-${i}`))}):(0,E.jsx)(D,{})}function mt({overview:e,timeline:t,onFilter:n,onDetail:r,onZoom:i}){let a=e.data||{},o=t.data||{},s=a.breakdowns||{},c=a.latest_metrics||{},l=[[`Origin requests`,C(a.calls),`${C(a.unique_ips)} unique IPs`],[`Request rate`,C(a.requests_per_second,1),`requests / second`],[`P95 latency`,a.latency_ms?.p95==null?`—`:`${C(a.latency_ms.p95,1)} ms`,`histogram estimate`],[`Cache hit rate`,a.cache_hit_ratio==null?`—`:`${C(a.cache_hit_ratio*100,1)}%`,`${C(a.cache_hits)} origin cache hits`],[`Errors`,C(a.errors),`${C(a.rate_limited)} rate limited`]];return(0,E.jsxs)(E.Fragment,{children:[(0,E.jsx)(Je,{report:e,label:`Request totals`,children:(0,E.jsx)(`div`,{className:`metric-grid`,children:l.map(([e,t,n])=>(0,E.jsxs)(`div`,{className:`metric panel`,children:[(0,E.jsx)(`span`,{children:e}),(0,E.jsx)(`strong`,{children:t}),(0,E.jsx)(`small`,{children:n})]},e))})}),(0,E.jsxs)(`div`,{className:`overview-grid`,children:[(0,E.jsx)(O,{className:`flow-panel`,title:`Request flow`,detail:`Origin traffic over the selected period`,actions:(0,E.jsxs)(le,{tone:`info`,children:[w(a.bytes),` transferred`]}),children:(0,E.jsx)(Je,{report:t,label:`Request flow`,children:(0,E.jsx)(re,{label:`Origin requests and errors by time`,height:310,onZoom:i,option:ae(o.series||[],[[`calls`,`Requests`],[`errors`,`Errors`]],{zoom:!0})})})}),e.data&&(0,E.jsxs)(E.Fragment,{children:[(0,E.jsx)(O,{title:`Access breakdown`,detail:`Credential state at the origin`,children:(0,E.jsx)(re,{label:`Anonymous, authenticated and rejected request breakdown`,option:oe(s.auth||[]),height:310})}),(0,E.jsx)(O,{className:`endpoints-panel`,title:`API endpoints`,detail:`Select an endpoint to inspect its traffic`,children:(0,E.jsx)(k,{rows:s.endpoint||s.domain||[],onSelect:e=>n(`endpoint`,e.value,e.filters),columns:[{key:`value`,label:`Endpoint`},{key:`calls`,label:`Requests`,render:e=>C(e.calls)},{key:`errors`,label:`Errors`,render:e=>C(e.errors)},{key:`bytes`,label:`Transfer`,render:e=>w(e.bytes)}]})}),(0,E.jsx)(O,{title:`Response codes`,detail:`Failures and rate limits`,children:(0,E.jsx)(pt,{rows:s.status||[],label:`Response status`,onSelect:(e,t)=>n(`status`,e,Ze(`status`,t))})}),(0,E.jsx)(O,{title:`Popular translations`,detail:`Successful origin requests`,children:(0,E.jsx)(pt,{rows:s.translation||[],label:`Translation`,onSelect:(e,t)=>n(`translation`,e,Ze(`translation`,t))})}),(0,E.jsx)(O,{title:`Frequent searches`,detail:`Successful origin requests`,children:(0,E.jsx)(pt,{rows:s.search||[],label:`Search`,onSelect:(e,t)=>n(`search`,e,Ze(`search`,t))})}),(0,E.jsx)(O,{title:`Scripture references`,detail:`Successful origin requests`,children:(0,E.jsx)(pt,{rows:s.reference||[],label:`Reference`,onSelect:(e,t)=>n(`reference`,e,Ze(`reference`,t))})}),(0,E.jsx)(O,{title:`Books of the Bible`,detail:`Successful origin requests`,children:(0,E.jsx)(pt,{rows:s.book||[],label:`Book`,onSelect:(e,t)=>n(`book`,e,Ze(`book`,t))})}),(0,E.jsx)(O,{title:`Active IP addresses`,detail:`Inspect high-volume origin consumers`,children:(0,E.jsx)(pt,{rows:s.ip||[],label:`Client IP`,onSelect:(e,t)=>n(`ip`,e,Ze(`ip`,t))})}),(0,E.jsx)(O,{title:`Referrers`,detail:`Origin requests, including errors`,children:(0,E.jsx)(pt,{rows:s.referrer||[],label:`Referrer`,onSelect:(e,t)=>n(`referrer`,e,t.filters)})}),(0,E.jsx)(O,{title:`User agents`,detail:`Origin requests, including errors`,children:(0,E.jsx)(pt,{rows:s.user_agent||[],label:`User agent`,onSelect:(e,t)=>n(`user_agent`,e,t.filters)})}),(0,E.jsx)(O,{title:`Collector health`,detail:`Continuous capture, independent of this dashboard`,children:(0,E.jsxs)(`div`,{className:`fact-list`,children:[(0,E.jsxs)(`div`,{children:[(0,E.jsx)(`span`,{children:`Available history`}),(0,E.jsx)(`strong`,{children:T(a.retention?.first_request)})]}),(0,E.jsxs)(`div`,{children:[(0,E.jsx)(`span`,{children:`Last sample`}),(0,E.jsx)(`strong`,{children:T(c.stamp)})]}),(0,E.jsxs)(`div`,{children:[(0,E.jsx)(`span`,{children:`Observed scope`}),(0,E.jsx)(`strong`,{children:`Origin only`})]}),(0,E.jsx)(`button`,{className:`btn btn-outline-secondary btn-sm`,onClick:()=>r(`Retention and capture health`,a.retention||{}),children:`Retention details`})]})})]})]})]})}function ht({filters:e,setFilters:t,range:n,live:r,onError:i,onDetail:a}){let[o,s]=(0,_.useState)(null),[c,l]=(0,_.useState)(``),[u,d]=(0,_.useState)([]),[f,p]=(0,_.useState)(n),[m,h]=(0,_.useState)(e);(0,_.useEffect)(()=>{h(e),l(``),d([])},[e]),(0,_.useEffect)(()=>{let t=new AbortController,a=!1,o;async function l(){let u=r&&!c?Math.floor(Date.now()/1e3):c?f.end:n.end,d=r&&!c?u-900:c?f.start:n.start;try{let i=await x(`requests?${S({start:d,end:u,...e,cursor:c,limit:100})}`,{signal:t.signal});a||(s(i),c||p({start:r?u-900:n.start,end:u}))}catch(e){!a&&e.name!==`AbortError`&&i(e)}!a&&r&&!c&&(o=setTimeout(l,2e3))}return l(),()=>{a=!0,clearTimeout(o),t.abort()}},[e,n.start,n.end,c,r]);function g(e){e.preventDefault(),t(m)}let v=m.endpoint_kind===`mcp`,y=ut(o,`requests`),b=e.endpoint_kind===`mcp`||y.some(e=>e.endpoint_kind===`mcp`),ee=[[`endpoint`,v?`MCP domain`:`Endpoint`],[`ip`,`Client IP`],...v?$e:[[`version`,`Version`],[`translation`,`Translation`],[`reference`,`Reference`],[`search`,`Search query`],[`book`,`Book number`]],[`status`,`HTTP status`],[`referrer`,`Exact referrer`],[`user_agent`,`Exact user agent`],[`referrer_contains`,`Referrer contains`],[`user_agent_contains`,`User agent contains`],[`path_contains`,`Request path contains`],[`q`,`Search recorded request data`]];return(0,E.jsxs)(O,{title:`Request explorer`,detail:`Inspect recorded request details; credentials are excluded`,children:[(0,E.jsxs)(`form`,{className:`filter-grid`,onSubmit:g,children:[ee.map(([e,t])=>(0,E.jsxs)(`label`,{children:[t,(0,E.jsx)(`input`,{className:`form-control form-control-sm`,value:m[e]||``,onChange:t=>h(at(m,e,t.target.value))})]},e)),(0,E.jsxs)(`label`,{children:[`Endpoint kind`,(0,E.jsxs)(`select`,{"aria-label":`Endpoint kind`,className:`form-select form-select-sm`,value:m.endpoint_kind||``,onChange:e=>h(at(m,`endpoint_kind`,e.target.value)),children:[(0,E.jsx)(`option`,{value:``,children:`All kinds`}),(0,E.jsx)(`option`,{value:`static`,children:`Static API`}),(0,E.jsx)(`option`,{value:`query`,children:`Query`}),(0,E.jsx)(`option`,{value:`search`,children:`Search`}),(0,E.jsx)(`option`,{value:`mcp`,children:`MCP`})]})]}),v&&(0,E.jsxs)(`label`,{children:[`MCP outcome`,(0,E.jsxs)(`select`,{"aria-label":`MCP outcome`,className:`form-select form-select-sm`,value:m.mcp_outcome||``,onChange:e=>h(at(m,`mcp_outcome`,e.target.value)),children:[(0,E.jsx)(`option`,{value:``,children:`All outcomes`}),et.map(e=>(0,E.jsx)(`option`,{value:e,children:e.replaceAll(`_`,` `)},e))]})]}),(0,E.jsxs)(`label`,{children:[`Responses`,(0,E.jsxs)(`select`,{"aria-label":`Responses`,className:`form-select form-select-sm`,value:m.successful||``,onChange:e=>h(at(m,`successful`,e.target.value)),children:[(0,E.jsx)(`option`,{value:``,children:`All responses`}),(0,E.jsx)(`option`,{value:`true`,children:`Successful responses`})]})]}),(0,E.jsxs)(`label`,{children:[`Authentication`,(0,E.jsxs)(`select`,{"aria-label":`Authentication`,className:`form-select form-select-sm`,value:m.auth||``,onChange:e=>h({...m,auth:e.target.value}),children:[(0,E.jsx)(`option`,{value:``,children:`All traffic`}),(0,E.jsx)(`option`,{value:`anonymous`,children:`Anonymous`}),(0,E.jsx)(`option`,{value:`valid`,children:`Authenticated`}),(0,E.jsx)(`option`,{value:`rejected`,children:`Rejected credential`})]})]}),(0,E.jsxs)(`div`,{className:`d-flex gap-2 align-items-end`,children:[(0,E.jsx)(`button`,{className:`btn btn-primary btn-sm`,children:`Apply filters`}),(0,E.jsx)(`button`,{type:`button`,className:`btn btn-outline-secondary btn-sm`,onClick:()=>{h({}),t({})},children:`Clear`})]})]}),o?(0,E.jsx)(k,{rows:y,onSelect:e=>a(`Request details`,e),columns:[{key:`timestamp`,label:`Time`,render:e=>T(e.stamp)},{key:`endpoint`,label:`Endpoint`},{key:`method`,label:`Method`},{key:`path`,label:`Request`,render:e=>(0,E.jsx)(`span`,{className:`truncate`,title:e.path,children:e.path})},{key:`status`,label:`Status`,render:e=>(0,E.jsx)(le,{tone:it(e)?`warning`:`success`,children:e.status})},{key:`duration_ms`,label:`Latency`,render:e=>`${C(e.duration_ms,1)} ms`},...b?[{key:`mcp_method`,label:`MCP operation`,render:e=>e.mcp_method?(0,E.jsxs)(`span`,{className:`consumer-value`,children:[e.mcp_method,e.mcp_tool&&(0,E.jsx)(`small`,{className:`d-block`,children:e.mcp_tool}),e.upstream_operation&&(0,E.jsx)(`small`,{className:`d-block`,children:[e.upstream_service,e.upstream_api_version,e.upstream_operation].filter(Boolean).join(` · `)})]}):`—`},{key:`mcp_outcome`,label:`MCP outcome`,render:e=>e.endpoint_kind===`mcp`?(0,E.jsx)(le,{tone:it(e)?`warning`:e.mcp_outcome===`success`?`success`:`muted`,children:(e.mcp_outcome||`unknown`).replaceAll(`_`,` `)}):`—`},{key:`mcp_client_name`,label:`Declared MCP client`,render:e=>e.mcp_client_name?(0,E.jsxs)(`span`,{className:`consumer-value`,children:[e.mcp_client_name,e.mcp_client_version&&(0,E.jsx)(`small`,{className:`d-block`,children:e.mcp_client_version})]}):`—`}]:[],{key:`remote_addr`,label:`Client IP`},{key:`referrer`,label:`Referrer`,render:n=>n.referrer?(0,E.jsx)(`button`,{className:`table-link truncate`,title:n.referrer,onClick:()=>t({...e,referrer:n.referrer}),children:n.referrer}):`—`},{key:`user_agent`,label:`User agent`,render:n=>n.user_agent?(0,E.jsx)(`button`,{className:`table-link truncate`,title:n.user_agent,onClick:()=>t({...e,user_agent:n.user_agent}),children:n.user_agent}):`—`},{key:`auth`,label:`Auth`}]}):(0,E.jsx)(ce,{}),(0,E.jsxs)(`div`,{className:`table-footer`,children:[(0,E.jsx)(`small`,{children:`Latest captures first · up to 100 requests per page`}),(0,E.jsxs)(`div`,{className:`btn-group`,children:[(0,E.jsx)(`button`,{className:`btn btn-outline-secondary btn-sm`,disabled:!u.length,onClick:()=>{l(u.at(-1)),d(u.slice(0,-1))},children:`Previous`}),(0,E.jsx)(`button`,{className:`btn btn-outline-secondary btn-sm`,disabled:!o?.next_cursor,onClick:()=>{d([...u,c]),l(o.next_cursor)},children:`Next`})]})]})]})}function gt({range:e,live:t,onError:n,onDetail:r}){let[i,a]=(0,_.useState)(null),[o,s]=(0,_.useState)(``),[c,l]=(0,_.useState)([]),[u,d]=(0,_.useState)(e);return(0,_.useEffect)(()=>{let r=!1,i,s=new AbortController;async function c(){let l=t&&!o?Math.floor(Date.now()/1e3):o?u.end:e.end;try{let n=await x(`events?${S({start:t?l-900:e.start,end:l,cursor:o,limit:100})}`,{signal:s.signal});r||(a(n),o||d({start:t?l-900:e.start,end:l}))}catch(e){!r&&e.name!==`AbortError`&&n(e)}!r&&t&&!o&&(i=setTimeout(c,2e3))}return c(),()=>{r=!0,clearTimeout(i),s.abort()}},[e.start,e.end,t,o]),(0,E.jsxs)(O,{title:`Operational events`,detail:`Service messages, alerts, synchronization and management audit records`,children:[i?(0,E.jsx)(k,{rows:ut(i,`items`),onSelect:e=>r(`Event details`,e),columns:[{key:`stamp`,label:`Time`,render:e=>T(e.stamp)},{key:`endpoint`,label:`Endpoint`},{key:`event`,label:`Event`,render:e=>e.event||e.payload?.event||e.source},{key:`level`,label:`Level`,render:e=>e.level||e.payload?.level||`info`},{key:`message`,label:`Message`,render:e=>(0,E.jsx)(`span`,{className:`truncate`,children:e.message||e.payload?.message||e.payload?.MESSAGE||``})}]}):(0,E.jsx)(ce,{}),(0,E.jsxs)(`div`,{className:`table-footer`,children:[(0,E.jsx)(`small`,{children:`Latest captures first`}),(0,E.jsxs)(`div`,{className:`btn-group`,children:[(0,E.jsx)(`button`,{className:`btn btn-sm btn-outline-secondary`,disabled:!c.length,onClick:()=>{s(c.at(-1)),l(c.slice(0,-1))},children:`Previous`}),(0,E.jsx)(`button`,{className:`btn btn-sm btn-outline-secondary`,disabled:!i?.next_cursor,onClick:()=>{l([...c,o]),s(i.next_cursor)},children:`Next`})]})]})]})}function _t({report:e,refresh:t,onError:n,onDetail:r,execute:i}){let a=e.data||{},[o,s]=(0,_.useState)(null);(0,_.useEffect)(()=>{let e=new AbortController,r=!1;return x(`storage?refresh=${t>0}`,{signal:e.signal}).then(e=>{r||s(e)}).catch(e=>{!r&&e.name!==`AbortError`&&n(e)}),()=>{r=!0,e.abort()}},[t]);let c=a.metrics?.at(-1)||{};return(0,E.jsxs)(E.Fragment,{children:[(0,E.jsx)(Pe,{refresh:t,execute:i}),(0,E.jsx)(Je,{report:e,label:`Resource metrics`}),(0,E.jsxs)(`div`,{className:`resource-grid`,children:[e.data&&(0,E.jsxs)(E.Fragment,{children:[(0,E.jsxs)(O,{title:`CPU`,detail:c.scope===`host`?`Host CPU utilization`:`Usage within the container’s effective CPU allowance`,children:[(0,E.jsx)(re,{label:`CPU usage over time`,option:ae((a.metrics||[]).map(e=>({...e,cpu_percent:e.cpu?.used_fraction==null?null:e.cpu.used_fraction*100})),[[`cpu_percent`,`CPU usage %`]])}),(0,E.jsxs)(`div`,{className:`fact-list`,children:[(0,E.jsxs)(`div`,{children:[(0,E.jsx)(`span`,{children:`Current utilization`}),(0,E.jsxs)(`strong`,{children:[C(c.cpu?.used_fraction==null?null:c.cpu.used_fraction*100,1),`%`]})]}),(0,E.jsxs)(`div`,{children:[(0,E.jsx)(`span`,{children:`Throttled time`}),(0,E.jsxs)(`strong`,{children:[C(c.cpu?.counters?.throttled_usec),` μs`]})]}),(0,E.jsxs)(`div`,{children:[(0,E.jsx)(`span`,{children:`Temperature`}),(0,E.jsx)(`strong`,{children:c.temperatures?.length?`${C(Math.max(...c.temperatures.map(e=>e.celsius)),1)} °C`:`Sensor unavailable`})]})]})]}),(0,E.jsxs)(O,{title:`Memory`,detail:c.scope===`host`?`Host memory usage, including other services`:`Measured container usage includes more than translation caches`,children:[(0,E.jsx)(re,{label:`Memory usage over time`,option:ae((a.metrics||[]).map(e=>({...e,memory_gib:e.memory?.current_bytes==null?null:e.memory.current_bytes/1073741824})),[[`memory_gib`,`Memory GiB`]])}),(0,E.jsxs)(`div`,{className:`fact-list`,children:[(0,E.jsxs)(`div`,{children:[(0,E.jsx)(`span`,{children:`Current / limit`}),(0,E.jsxs)(`strong`,{children:[w(c.memory?.current_bytes),` / `,w(c.memory?.limit_bytes)]})]}),(0,E.jsxs)(`div`,{children:[(0,E.jsx)(`span`,{children:`OOM events`}),(0,E.jsx)(`strong`,{children:C(c.memory?.events?.oom_kill)})]})]})]})]}),(0,E.jsx)(O,{className:`wide`,title:`Persistent storage`,detail:`Mounted data on the host; application budgets do not impose a filesystem quota`,actions:(0,E.jsx)(`button`,{className:`btn btn-sm btn-outline-secondary`,onClick:()=>r(`Storage accounting`,o),children:`Inspect accounting`}),children:o?(0,E.jsx)(k,{rows:ut(o,`components`),onSelect:e=>r(`Storage details`,e),columns:[{key:`name`,label:`Data`,render:e=>e.name||e.category||e.path},{key:`bytes`,label:`Allocated`,render:e=>w(e.bytes??e.allocated_bytes)},{key:`limit_bytes`,label:`Budget`,render:e=>e.limit_bytes?w(e.limit_bytes):`—`},{key:`path`,label:`Location`}]}):(0,E.jsx)(ce,{})}),(0,E.jsx)(O,{className:`wide`,title:`Pressure and collection details`,children:(0,E.jsxs)(`div`,{className:`fact-list`,children:[(0,E.jsx)(`button`,{className:`btn btn-outline-secondary`,onClick:()=>r(`Latest resource sample`,c),children:`Inspect latest resource sample`}),(0,E.jsx)(`button`,{className:`btn btn-outline-secondary`,onClick:()=>r(`Retention`,a.retention),children:`Inspect available history and pruning`})]})})]})]})}function vt({initial:e,onError:t,onComplete:n}){let[r,i]=(0,_.useState)(e),[a,o]=(0,_.useState)(null),[s,c]=(0,_.useState)(!1),[l,u]=(0,_.useState)(!1),d=(0,_.useRef)(!1),f=e.job_id||e.id;(0,_.useEffect)(()=>{let e=!1,r,a=1e3;async function o(){try{let t=await x(`jobs/${encodeURIComponent(f)}`);e||(i(t),u(!1),a=1e3,!Qe(t.status)&&!d.current&&(d.current=!0,n?.()),Qe(t.status)&&(r=setTimeout(o,1e3)))}catch(n){e||(!n.status||n.status>=500?(u(!0),r=setTimeout(o,a),a=Math.min(a*2,1e4)):t(n))}}return o(),()=>{e=!0,clearTimeout(r)}},[f]);async function p(){try{let e=await te(`jobs/${encodeURIComponent(f)}/reveal`);o(e.one_time_output||`The one-time output is no longer available.`)}catch(e){t(e)}}return(0,E.jsxs)(E.Fragment,{children:[l&&(0,E.jsx)(`div`,{className:`alert alert-info`,role:`status`,children:`Reconnecting to operation progress… The accepted job continues on the server.`}),(0,E.jsxs)(`div`,{className:`fact-list`,children:[(0,E.jsxs)(`div`,{children:[(0,E.jsx)(`span`,{children:`Operation`}),(0,E.jsx)(`strong`,{children:r.operation||`Starting…`})]}),(0,E.jsxs)(`div`,{children:[(0,E.jsx)(`span`,{children:`Status`}),(0,E.jsx)(le,{tone:r.status===`failed`?`warning`:`info`,children:r.status})]}),(0,E.jsxs)(`div`,{children:[(0,E.jsx)(`span`,{children:`Started`}),(0,E.jsx)(`strong`,{children:r.started?T(r.started):`Waiting to start`})]})]}),(0,E.jsx)(`pre`,{className:`record-detail`,children:r.output||`Waiting for operation output…`}),r.secret_available&&!a&&(0,E.jsx)(`button`,{className:`btn btn-warning`,onClick:p,children:`Reveal issued credential once`}),a&&(0,E.jsxs)(`div`,{className:`alert alert-warning`,children:[(0,E.jsx)(`p`,{children:`Copy this credential now. It is shown only once and is not saved in the job history.`}),(0,E.jsx)(`pre`,{className:`record-detail`,children:a}),(0,E.jsx)(`button`,{className:`btn btn-outline-secondary`,onClick:async()=>{try{await navigator.clipboard.writeText(a),c(!0)}catch(e){t(e)}},children:s?`Copied`:`Copy credential`})]})]})}function yt({onError:e,refresh:t,onRefresh:n}){let[r,i]=(0,_.useState)(null);(0,_.useEffect)(()=>{x(`sessions`).then(i).catch(e)},[t]);async function a(t){try{await te(`sessions/${encodeURIComponent(t)}/revoke`),n()}catch(t){e(t)}}return(0,E.jsx)(O,{title:`Browser sessions`,detail:`Sleep preserves sessions. Revoking one requires that browser to authenticate again.`,children:r?(0,E.jsx)(k,{rows:ut(r,`sessions`),columns:[{key:`ip`,label:`Client IP`},{key:`user_agent`,label:`Browser`,render:e=>(0,E.jsx)(`span`,{className:`truncate`,title:e.user_agent,children:e.user_agent})},{key:`created_at`,label:`Signed in`,render:e=>T(e.created_at)},{key:`expires_at`,label:`Expires`,render:e=>T(e.expires_at)},{key:`action`,label:`Access`,render:e=>(0,E.jsx)(`button`,{className:`btn btn-sm btn-outline-danger`,onClick:()=>a(e.id),children:`Revoke session`})}]}):(0,E.jsx)(ce,{})})}function bt(){let[e,t]=(0,_.useState)(null),[n,r]=(0,_.useState)(null),[i,a]=(0,_.useState)(`Overview`),[o,s]=(0,_.useState)(``),[c,l]=(0,_.useState)(0),[u,d]=(0,_.useState)(()=>localStorage.getItem(`getbible-theme`)||`system`),[f,p]=(0,_.useState)(`24 hours`),[m,h]=(0,_.useState)({start:``,end:``}),[g,v]=(0,_.useState)({}),[y,b]=(0,_.useState)(null),[S,C]=(0,_.useState)(null),[w,ne]=(0,_.useState)(!1),re=(0,_.useRef)(!1),[ie,ae]=(0,_.useState)(null),[oe,D]=(0,_.useState)(0),[ue,k]=(0,_.useState)(null),[de,fe]=(0,_.useState)(`initializing`),[pe,me]=(0,_.useState)(!1),[he,ge]=(0,_.useState)(()=>Math.floor(Date.now()/1e3)),_e=(0,_.useRef)(crypto.randomUUID()),A=(0,_.useCallback)(e=>{e.status===401?(ee(``),t({authenticated:!1})):r(e)},[]),ve=(0,_.useMemo)(()=>f===`Custom`&&m.start&&m.end?{start:Math.floor(new Date(m.start).getTime()/1e3),end:Math.floor(new Date(m.end).getTime()/1e3)}:{start:he-(lt[f]||86400),end:he},[f,he,m]);(0,_.useEffect)(()=>{x(`auth/status`).then(t).catch(A)},[]),(0,_.useEffect)(()=>{e?.authenticated||(b(null),C(null),fe(`initializing`),k(null))},[e?.authenticated]),(0,_.useEffect)(()=>{let e=matchMedia(`(prefers-color-scheme: dark)`),t=()=>{document.documentElement.dataset.bsTheme=u===`system`?e.matches?`dark`:`light`:u};return t(),localStorage.setItem(`getbible-theme`,u),e.addEventListener(`change`,t),()=>e.removeEventListener(`change`,t)},[u]),(0,_.useEffect)(()=>{if(!e?.authenticated)return;let t=!1,n;async function r(){try{let e=await te(`dashboard/heartbeat`,{viewer_id:_e.current});t||fe(e.state===`awake`?`ready`:e.state)}catch(e){t||A(e)}t||(n=setTimeout(r,15e3))}r();let i=()=>{document.visibilityState===`visible`&&(clearTimeout(n),r())};return document.addEventListener(`visibilitychange`,i),()=>{t=!0,clearTimeout(n),document.removeEventListener(`visibilitychange`,i)}},[e?.authenticated]),(0,_.useEffect)(()=>{if(!e?.authenticated||de===`ready`)return;let t=!1,n;async function i(){try{let e=await x(`dashboard/state`);if(t)return;if(e.state===`awake`){fe(`ready`),r(e=>e?.historyUnavailable?null:e);return}e.state===`unavailable`&&fe(`unavailable`),e.error&&r(Object.assign(Error(e.error),{historyUnavailable:e.state===`unavailable`}))}catch(e){t||A(e)}t||(n=setTimeout(i,1e3))}return i(),()=>{t=!0,clearTimeout(n)}},[e?.authenticated,de]);let ye=(0,_.useCallback)(()=>k(new Date),[]),j=qe({page:[`Overview`,`Resources`].includes(i)?i:``,range:ve,live:f===`Live`,filters:i===`Overview`?g:void 0,refresh:oe,enabled:e?.authenticated&&de===`ready`&&(f!==`Custom`||!!(m.start&&m.end)),onError:A,onUpdate:ye}),M=j.overview?.data;function be({start:e,end:t}){if(!(t>e))return;let n=e=>{let t=new Date(e*1e3);return new Date(t.getTime()-t.getTimezoneOffset()*6e4).toISOString().slice(0,19)};h({start:n(e),end:n(t)}),p(`Custom`)}function xe(e,t){b({title:e,value:t})}function Se(e,t,n){v({...g,...n||{[e]:t}}),a(`Traffic`)}function Ce(){ge(Math.floor(Date.now()/1e3)),D(e=>e+1)}async function N(){if(!re.current){re.current=!0,ne(!0),ae(null);try{let e=await te(`actions`,{operation:S.operation,arguments:S.arguments,confirm:!0});C(null),xe(`Operation details`,{...e,job_id:e.job_id||e.id}),Ce()}catch(e){ae(e),A(e)}finally{re.current=!1,ne(!1)}}}return e?e.authenticated?(0,E.jsxs)(`div`,{className:`console`,children:[(0,E.jsxs)(`aside`,{className:`sidebar ${pe?`expanded`:``}`,children:[(0,E.jsxs)(`a`,{className:`brand`,href:`#overview`,onClick:e=>{e.preventDefault(),a(`Overview`)},children:[(0,E.jsx)(`img`,{src:`/favicon.png`,alt:``}),(0,E.jsxs)(`span`,{children:[`getBible`,(0,E.jsx)(`small`,{children:`OPERATIONS`})]})]}),(0,E.jsx)(`div`,{className:`workspace-label`,children:`SERVER CONSOLE`}),(0,E.jsx)(`nav`,{"aria-label":`Main navigation`,children:ct.map((e,t)=>(0,E.jsxs)(`button`,{className:i===e?`active`:``,"aria-current":i===e?`page`:void 0,"aria-label":e,onClick:()=>{a(e),e===`MCP traffic`&&v(rt(g)),e===`Manage`&&(s(``),l(e=>e+1)),me(!1)},children:[(0,E.jsx)(`span`,{className:`nav-number`,children:String(t+1).padStart(2,`0`)}),e]},e))}),(0,E.jsxs)(`div`,{className:`sidebar-bottom`,children:[(0,E.jsx)(`span`,{className:`eyebrow`,children:`CONSOLE STATE`}),(0,E.jsx)(`strong`,{children:de===`ready`?`Connected`:de===`unavailable`?`History unavailable`:`Initializing`}),(0,E.jsx)(`small`,{children:`Traffic is recorded continuously.`}),(0,E.jsx)(`button`,{className:`btn btn-sm btn-outline-secondary`,onClick:async()=>{try{await te(`auth/logout`),t({authenticated:!1}),ee(``)}catch(e){A(e)}},children:`Sign out`})]})]}),(0,E.jsxs)(`div`,{className:`main-area`,children:[(0,E.jsxs)(`header`,{className:`topbar`,children:[(0,E.jsx)(`button`,{className:`btn btn-sm btn-outline-secondary mobile-menu`,onClick:()=>me(!pe),"aria-label":`Toggle navigation`,children:`Menu`}),(0,E.jsxs)(`span`,{className:`breadcrumb-text`,children:[`getBible `,(0,E.jsx)(`span`,{children:`/`}),` `,i]}),(0,E.jsxs)(`div`,{className:`topbar-controls`,children:[(0,E.jsx)(`small`,{className:`last-updated`,children:ue?`Updated ${ue.toLocaleTimeString()}`:`Initializing…`}),(0,E.jsx)(`button`,{className:`btn btn-sm btn-outline-secondary`,onClick:Ce,title:`Refresh all dashboard data and locally cached metadata`,children:`Refresh all`}),(0,E.jsx)(`label`,{className:`visually-hidden`,htmlFor:`theme`,children:`Color theme`}),(0,E.jsxs)(`select`,{id:`theme`,className:`form-select form-select-sm theme-select`,value:u,onChange:e=>d(e.target.value),children:[(0,E.jsx)(`option`,{value:`system`,children:`System theme`}),(0,E.jsx)(`option`,{value:`dark`,children:`Dark`}),(0,E.jsx)(`option`,{value:`light`,children:`Light`})]})]})]}),(0,E.jsxs)(`main`,{className:`content`,children:[(0,E.jsxs)(`div`,{className:`page-heading`,children:[(0,E.jsxs)(`div`,{children:[(0,E.jsx)(`span`,{className:`eyebrow`,children:i===`Overview`?`YOUR API AT A GLANCE`:`GETBIBLE OPERATIONS`}),(0,E.jsx)(`h1`,{children:i===`Overview`?`Traffic & performance`:i})]}),(0,E.jsxs)(`div`,{className:`range-picker`,children:[(0,E.jsx)(`label`,{className:`visually-hidden`,htmlFor:`period`,children:`Time range`}),(0,E.jsx)(`select`,{className:`form-select`,id:`period`,value:f,onChange:e=>{p(e.target.value),ge(Math.floor(Date.now()/1e3))},children:[...Object.keys(lt),`Custom`].map(e=>(0,E.jsx)(`option`,{children:e},e))}),f===`Live`&&(0,E.jsx)(le,{tone:`success`,children:`LIVE`})]})]}),f===`Custom`&&(0,E.jsxs)(`div`,{className:`custom-range`,children:[(0,E.jsxs)(`label`,{children:[`From`,(0,E.jsx)(`input`,{className:`form-control`,type:`datetime-local`,step:`1`,value:m.start,onChange:e=>h({...m,start:e.target.value})})]}),(0,E.jsxs)(`label`,{children:[`Until`,(0,E.jsx)(`input`,{className:`form-control`,type:`datetime-local`,step:`1`,value:m.end,onChange:e=>h({...m,end:e.target.value})})]}),(0,E.jsx)(`small`,{children:`Times use your browser's local time zone.`})]}),(0,E.jsx)(se,{error:n,dismiss:()=>r(null)}),M?.retention?.first_request>ve.start&&(0,E.jsxs)(`div`,{className:`alert alert-info py-2`,children:[`Available request history starts `,T(M.retention.first_request),`. Earlier time in this selection has no retained detail.`]}),Object.entries(g).filter(([,e])=>e).length>0&&(0,E.jsxs)(`div`,{className:`filter-chips`,children:[Object.entries(g).filter(([,e])=>e).map(([e,t])=>(0,E.jsxs)(`button`,{className:`btn btn-sm btn-outline-info`,onClick:()=>{let t={...g};delete t[e],v(t)},children:[e,`: `,String(t),` `,(0,E.jsx)(`span`,{"aria-label":`Remove filter`,children:`×`})]},e)),(0,E.jsx)(`button`,{className:`btn btn-sm btn-link`,onClick:()=>v({}),children:`Clear all`})]}),de!==`ready`&&![`Manage`,`Translations`,`Resources`,`Sessions`].includes(i)?de===`unavailable`?(0,E.jsx)(O,{title:`Traffic history needs attention`,detail:`Server management remains available while traffic history is unavailable.`,children:(0,E.jsxs)(`div`,{className:`fact-list`,children:[(0,E.jsx)(`p`,{children:`Review the reported history error. To start fresh after an incompatible history format, open Logs, then History, and review the reset operation.`}),(0,E.jsx)(`button`,{className:`btn btn-primary`,onClick:()=>{s(`logs`),a(`Manage`)},children:`Manage traffic history`})]})}):(0,E.jsx)(ce,{children:`Initializing dashboard…`}):i===`Overview`?(0,E.jsx)(mt,{overview:j.overview,timeline:j.history,onFilter:Se,onDetail:xe,onZoom:be}):i===`Traffic`?(0,E.jsx)(ht,{live:f===`Live`,filters:g,setFilters:v,range:ve,onError:A,onDetail:xe},JSON.stringify([ve,g,f,oe])):i===`MCP traffic`?(0,E.jsx)(st,{range:ve,live:f===`Live`,filters:g,refresh:oe,onFilter:Se,onError:A,onZoom:be}):i===`Audience`?(0,E.jsx)(Xe,{range:ve,live:f===`Live`,filters:g,refresh:oe,onFilter:Se,onError:A}):i===`Translations`?(0,E.jsx)(Ue,{onError:A,execute:(e,t,n)=>(ae(null),C({operation:e,arguments:t,review:n})),onDetail:xe,refresh:oe}):i===`Resources`?(0,E.jsx)(_t,{report:j.metrics,refresh:oe,onError:A,onDetail:xe,execute:(e,t,n)=>(ae(null),C({operation:e,arguments:t,review:n}))}):i===`Events`?(0,E.jsx)(gt,{range:ve,live:f===`Live`,onError:A,onDetail:xe},JSON.stringify([ve,f,oe])):i===`Manage`?(0,E.jsx)(je,{initialSection:o,execute:(e,t,n)=>(ae(null),C({operation:e,arguments:t,review:n})),onError:A,onDetail:xe,refresh:oe},`${o||`manage`}:${c}`):(0,E.jsx)(yt,{onError:A,refresh:oe,onRefresh:Ce}),(0,E.jsx)(`footer`,{children:`Origin observations · CDN cache hits served before this server are outside this view.`})]})]}),y&&(0,E.jsx)(dt,{title:y.title,value:y.value,onClose:()=>b(null),children:y.title===`Operation details`?(0,E.jsx)(vt,{initial:y.value,onError:A,onComplete:Ce}):void 0}),S&&(0,E.jsxs)(dt,{title:`Review operation`,onClose:()=>C(null),children:[(0,E.jsx)(se,{error:ie}),(0,E.jsx)(`p`,{children:S.review?.title||`The following operation will run on this server.`}),S.review?.description&&(0,E.jsx)(`p`,{className:`text-secondary`,children:S.review.description}),(0,E.jsx)(`pre`,{className:`record-detail`,children:JSON.stringify({operation:S.operation,arguments:Object.fromEntries(Object.entries(S.arguments).map(([e,t])=>[e,/password|token|secret|credential/i.test(e)?`[hidden]`:e===`content`?`[content supplied]`:t]))},null,2)}),(0,E.jsxs)(`div`,{className:`d-flex gap-2`,children:[(0,E.jsx)(`button`,{className:`btn btn-primary`,disabled:w,onClick:N,children:w?`Submitting…`:`Run operation`}),(0,E.jsx)(`button`,{className:`btn btn-outline-secondary`,"data-bs-dismiss":`modal`,children:`Cancel`})]})]})]}):(0,E.jsx)(ft,{onLogin:t}):(0,E.jsxs)(`main`,{className:`login-page`,children:[(0,E.jsx)(ce,{children:`Connecting to your server…`}),(0,E.jsx)(se,{error:n})]})}(0,v.createRoot)(document.getElementById(`root`)).render((0,E.jsx)(bt,{})); \ No newline at end of file +{d}%`}},data:e.map(e=>({name:e.name??e.key??e.value??`Unknown`,value:e.calls??e.count??e.requests??0}))}]}}function se({error:e,dismiss:t}){return e?(0,E.jsxs)(`div`,{className:`alert alert-danger d-flex align-items-start gap-3`,role:`alert`,children:[(0,E.jsx)(`span`,{className:`flex-grow-1`,children:e.message||String(e)}),t&&(0,E.jsx)(`button`,{className:`btn-close`,"aria-label":`Dismiss error`,onClick:t})]}):null}function D({children:e=`No records in this time range.`}){return(0,E.jsx)(`div`,{className:`empty-state`,children:e})}function ce({children:e=`Loading…`}){return(0,E.jsxs)(`div`,{className:`empty-state`,children:[(0,E.jsx)(`span`,{className:`spinner-border spinner-border-sm me-2`,"aria-hidden":`true`}),e]})}function O({title:e,detail:t,children:n,className:r=``,actions:i}){return(0,E.jsxs)(`section`,{className:`panel ${r}`,children:[(0,E.jsxs)(`div`,{className:`panel-title`,children:[(0,E.jsxs)(`div`,{children:[(0,E.jsx)(`h2`,{children:e}),t&&(0,E.jsx)(`small`,{children:t})]}),i]}),n]})}function le({children:e,tone:t=`muted`}){return(0,E.jsx)(`span`,{className:`status-badge ${t}`,children:e})}function ue({value:e,max:t,label:n}){return(0,E.jsx)(`div`,{className:`progress`,role:`progressbar`,"aria-label":n,"aria-valuenow":e||0,"aria-valuemin":`0`,"aria-valuemax":t||100,children:(0,E.jsx)(`div`,{className:`progress-bar`,style:{width:`${Math.min(100,(e||0)*100/(t||100))}%`}})})}function k({columns:e,rows:t,onSelect:n,rowKey:r}){return t.length?(0,E.jsx)(`div`,{className:`table-responsive`,children:(0,E.jsxs)(`table`,{className:`table align-middle table-hover mb-0`,children:[(0,E.jsx)(`thead`,{children:(0,E.jsx)(`tr`,{children:e.map(e=>(0,E.jsx)(`th`,{scope:`col`,children:e.label},e.key))})}),(0,E.jsx)(`tbody`,{children:t.map((t,i)=>(0,E.jsx)(`tr`,{children:e.map((e,r)=>(0,E.jsx)(`td`,{children:r===0&&n?(0,E.jsx)(`button`,{className:`table-link`,onClick:()=>n(t),children:e.render?e.render(t):t[e.key]}):e.render?e.render(t):t[e.key]??`—`},e.key))},r?.(t)??i))})]})}):(0,E.jsx)(D,{})}function de(e,t=!1){return![`blocked`,`updating`].includes(e.status)&&(e.eligible||t)}function fe(e,t,{force:n=!1,retry:r=!1}={}){let i=new Set(t);return(e?.targets||[]).filter(e=>i.has(e.id)&&de(e,n)&&(!r||[`failed`,`interrupted`].includes(e.outcome))).map(e=>e.id)}function pe(e){return(e?.targets||[]).filter(e=>de(e)).map(e=>e.id)}function me(e,t,n={}){if(!e||!/^[a-f0-9]{64}$/.test(e.plan_id))throw Error(`Refresh the upgrade plan before applying.`);return{targets:fe(e,t,n),plan_id:e.plan_id,force:n.force===!0,retry:n.retry===!0}}function he(e,t=!1){let n=e?.recommendation?.value;return t||e?.recommendation?.status!==`suggested`||!Number.isFinite(n)||n<0||!e?.configuration?.editable||e.configuration.owner!==`saved`||![`TELEMETRY_SPOOL_MAX_GIB`,`TELEMETRY_MAX_GIB`].includes(e.setting)?null:{key:e.setting,value:String(n)}}function ge({execute:e,refresh:t,paused:n=!1}){let[r,i]=(0,_.useState)(null),[a,o]=(0,_.useState)(null),[s,c]=(0,_.useState)(0),[l,u]=(0,_.useState)(!0),[d,f]=(0,_.useState)([]),[p,m]=(0,_.useState)(!1),[h,g]=(0,_.useState)(!1);(0,_.useEffect)(()=>{let e=!1,t=new AbortController;return u(!0),o(null),x(`upgrades`,{signal:t.signal}).then(t=>{e||(i(t),f(pe(t)),m(!1),g(!1))}).catch(t=>{!e&&t.name!==`AbortError`&&o(t)}).finally(()=>{e||u(!1)}),()=>{e=!0,t.abort()}},[t,s]);let v=fe(r,d,{force:p,retry:h}),y=r&&!l&&!a&&!n,b=e=>f(t=>t.includes(e)?t.filter(t=>t!==e):[...t,e]);return(0,E.jsxs)(`div`,{className:`upgrade-plan`,children:[(0,E.jsx)(`p`,{children:`Only selected targets will be upgraded. Static targets update their software and configuration—not Bible files. Skipped changes remain pending.`}),(0,E.jsx)(se,{error:a}),(0,E.jsxs)(`div`,{className:`d-flex gap-2 flex-wrap mb-3`,children:[(0,E.jsx)(`button`,{type:`button`,className:`btn btn-outline-secondary`,disabled:l,onClick:()=>c(e=>e+1),children:`Refresh upgrade plan`}),(0,E.jsx)(`button`,{type:`button`,className:`btn btn-outline-secondary`,disabled:!y,onClick:()=>{f(pe(r)),g(!1)},children:`Select changed targets`}),(0,E.jsx)(`button`,{type:`button`,className:`btn btn-outline-secondary`,disabled:!y,onClick:()=>f([]),children:`Clear selection`}),(0,E.jsxs)(`label`,{className:`form-check`,children:[(0,E.jsx)(`input`,{type:`checkbox`,className:`form-check-input`,checked:p,disabled:!y,onChange:e=>m(e.target.checked)}),`Allow forced redeployment of unchanged targets`]}),(0,E.jsxs)(`label`,{className:`form-check`,children:[(0,E.jsx)(`input`,{type:`checkbox`,className:`form-check-input`,checked:h,disabled:!y,onChange:e=>{g(e.target.checked),e.target.checked&&f((r?.targets||[]).filter(e=>[`failed`,`interrupted`].includes(e.outcome)).map(e=>e.id))}}),`Retry failed or interrupted targets only`]})]}),l?(0,E.jsx)(ce,{children:`Inspecting installed releases and target readiness…`}):r?(0,E.jsxs)(E.Fragment,{children:[(0,E.jsxs)(`div`,{className:`alert alert-info`,role:`status`,children:[`Release `,r.version,`: `,r.pending,` target(s) pending. `,v.length,` selected. Serving health is separate from upgrade completion.`]}),(0,E.jsx)(k,{rowKey:e=>e.id,rows:r.targets||[],columns:[{key:`selected`,label:`Upgrade`,render:e=>(0,E.jsx)(`input`,{type:`checkbox`,"aria-label":`Upgrade ${e.id}`,checked:v.includes(e.id),disabled:!y||!de(e,p)||h&&![`failed`,`interrupted`].includes(e.outcome),onChange:()=>b(e.id)})},{key:`id`,label:`Target`,render:e=>(0,E.jsxs)(E.Fragment,{children:[(0,E.jsx)(`strong`,{children:e.id}),(0,E.jsx)(`small`,{className:`d-block`,children:e.reason}),e.last_error&&(0,E.jsx)(`small`,{className:`d-block text-warning`,children:e.last_error})]})},{key:`status`,label:`Upgrade state`,render:e=>(0,E.jsx)(le,{tone:e.status===`current`?`success`:e.status===`blocked`?`warning`:`info`,children:e.status})},{key:`serving`,label:`Serving`,render:e=>(0,E.jsx)(le,{tone:e.serving===`ready`?`success`:`warning`,children:e.serving})},{key:`outcome`,label:`Last result`,render:e=>(0,E.jsxs)(E.Fragment,{children:[e.outcome,(0,E.jsx)(`small`,{className:`d-block`,children:T(e.last_attempt)})]})},{key:`fingerprints`,label:`Implementation`,render:e=>(0,E.jsxs)(`details`,{children:[(0,E.jsx)(`summary`,{children:`Fingerprints`}),(0,E.jsxs)(`small`,{className:`d-block text-break`,children:[`Desired: `,e.desired_fingerprint||`Unavailable`]}),(0,E.jsxs)(`small`,{className:`d-block text-break`,children:[`Applied: `,e.applied_fingerprint||`Not recorded`]})]})}]}),(0,E.jsx)(`p`,{className:`text-secondary mt-3`,children:r.note})]}):(0,E.jsx)(D,{children:`The upgrade plan is unavailable. Refresh to retry.`}),(0,E.jsx)(`button`,{type:`button`,className:`btn btn-primary`,disabled:!y||!v.length,onClick:()=>{try{e(`system.update`,me(r,d,{force:p,retry:h}),{title:`Upgrade ${v.length} selected target(s)`,description:`The manager rechecks this plan before changing anything. Unselected generations remain running; capacity checks may reject an unsafe overlap. Whole-container replacement is a separate restart.`})}catch(e){o(e)}},children:`Review selected upgrades`}),!v.length&&!l&&(0,E.jsx)(`small`,{className:`d-block mt-2`,children:`Nothing selected. No upgrade will run.`})]})}var _e=[{id:`domains`,title:`Domains`,detail:`Status, endpoints, pages, access and runtime settings`},{id:`deploy`,title:`Deploy a new domain`,detail:`Prepare a static, runtime or MCP domain`},{id:`go-live`,title:`Go live`,detail:`Activate a staged domain`},{id:`self-update`,title:`Update manager`,detail:`Update the native manager checkout`},{id:`update`,title:`Upgrade targets`,detail:`Review changes, select targets and retry incomplete upgrades`},{id:`analytics`,title:`Traffic analytics`,detail:`Run a traffic report`},{id:`dashboard`,title:`Private dashboard`,detail:`Domain, password, sessions and blocked addresses`},{id:`logs`,title:`Logs`,detail:`Domain diagnostics, archives and rotation`},{id:`settings`,title:`Settings`,detail:`Environment, notifications, Cloudflare token and icons`},{id:`system`,title:`System`,detail:`Host checks, dependencies and resource allocation`}],A=e=>e.id||e.operation||e.name;function ve(e){let t=e?.fields||e?.parameters||[];return Array.isArray(t)?t:Object.entries(t).map(([e,t])=>({name:e,...t}))}function ye(e){let t=A(e);if(t.startsWith(`domain.deploy_`))return[[`deploy`,`New domain`]];if(t===`domain.go_live`)return[[`go-live`,`Activation`],[`domains`,`Publication`]];if(t===`system.self_update`)return[[`self-update`,`Manager checkout`]];if(t===`system.update`)return[[`update`,`Review eligible targets`]];if(t===`analytics`)return[[`analytics`,`Traffic report`]];if(t.startsWith(`dashboard.`))return[[`dashboard`,[`dashboard.sessions`,`dashboard.revoke`].includes(t)?`Sessions`:t===`dashboard.blocks`?`Blocked addresses`:`Dashboard settings`]];if(t.startsWith(`logs.`))return t===`logs.rotate`?[[`logs`,`Rotation`]]:t===`logs.reset`?[[`logs`,`History`]]:[[`logs`,`Domain logs`],[`domains`,`Logs`]];if(t.startsWith(`settings.`))return[[`settings`,`Environment and defaults`]];if(t.startsWith(`telegram.`))return[[`settings`,`Telegram notifications`]];if(t===`cloudflare.token`)return[[`settings`,`Cloudflare API token`]];if([`system.icons`,`system.favicon`,`system.logo`].includes(t))return[[`settings`,`Icons`]];if(t===`pages.upload_icon`)return[[`settings`,`Icons`],[`domains`,`Pages and OpenAPI`]];if([`cloudflare.verify`,`cloudflare.refresh_ips`].includes(t))return[[`system`,`Cloudflare origin tools`]];if(t.startsWith(`resources.`))return[[`system`,`Resource allocation`]];if(t===`runtime.versions`)return[[`system`,`Python versions`]];if(ve(e).some(e=>e.name===`domain`)){let e=`Other domain operations`;return[`domain.status`,`domain.verify`].includes(t)?e=`Status and health`:t===`domain.stage`?e=`Publication`:[`domain.apply`,`domain.update`].includes(t)?e=`Apply configuration`:t===`domain.remove`?e=`Remove domain`:t.startsWith(`endpoint.`)?e=`Endpoints and repositories`:t===`access.mode`?e=`Access mode`:t===`access.limits`?e=`Anonymous caller limits`:t.startsWith(`token.`)?e=`Bearer tokens`:t.startsWith(`certificate.`)?e=`Certificate`:t.startsWith(`cloudflare.`)?e=`Cloudflare`:t.startsWith(`pages.`)?e=`Pages and OpenAPI`:t.startsWith(`mcp.`)?e=`MCP service`:t===`runtime.set`?e=`Runtime settings`:t===`runtime.cache`?e=`Translation memory`:t.startsWith(`runtime.`)&&(e=`Runtime services`),[[`domains`,e]]}return[[`system`,t.startsWith(`system.`)?`Host maintenance`:e.category||`Other operations`]]}function j(e){let t=new Map((e?.domains||[]).map(e=>[e.domain,{...e,endpoints:[]}]));for(let n of e?.endpoints||[])t.has(n.domain)||t.set(n.domain,{...n,endpoints:[]}),t.get(n.domain).endpoints.push(n);return[...t.values()].sort((e,t)=>e.domain.localeCompare(t.domain))}function M(e,t){if(!t)return!0;let n=A(e),r=t.type===`runtime`,i=t.type===`static`;return Array.isArray(e.domain_types)&&!e.domain_types.includes(t.type)?!1:n.startsWith(`mcp.`)?t.type===`mcp`:n.startsWith(`runtime.`)||[`endpoint.add_runtime`,`endpoint.default`].includes(n)?r:[`endpoint.add_static`,`endpoint.change_source`,`endpoint.sync`,`endpoint.deploy_key`,`endpoint.repo_access`,`endpoint.filetypes`].includes(n)?i:[`endpoint.remove`,`pages.docs`,`pages.openapi`,`pages.write`].includes(n)?i||r:n===`domain.go_live`?!t.live:n!==`domain.stage`||t.live}function be(e,t,n){let r=new Map;for(let i of e){if(!M(i,n))continue;let e=ye(i).find(([e])=>e===t);e&&(r.has(e[1])||r.set(e[1],[]),r.get(e[1]).push(i))}return[...r].map(([e,t])=>({title:e,operations:t}))}function xe(e){return ve(e).some(e=>e.name===`domain`&&e.required)&&!A(e).startsWith(`domain.deploy_`)&&A(e)!==`dashboard.enable`}function Se(e){return ve(e).some(e=>e.name===`endpoint`)&&!A(e).startsWith(`domain.deploy_`)&&!A(e).startsWith(`endpoint.add_`)}function Ce(e){let t=A(e);return t.startsWith(`pages.`)?`Domain page`:t===`logs.view`?`Domain logs`:`All endpoints`}function N(e,t={},n){let r=e&&A(e);if(!e?.runtime_kinds||![`domain.deploy_runtime`,`endpoint.add_runtime`].includes(r))return null;let i=r===`endpoint.add_runtime`?n?.kind:t.kind,a=e.runtime_kinds[i],o=r===`endpoint.add_runtime`?new Set((n?.endpoints||[]).map(e=>e.label)):new Set,s=o.has(`root`),c=s?[]:(a?.versions||[]).filter(e=>!o.has(e)),l=c.includes(a?.default_version)?a.default_version:c[0]||``,u=r===`endpoint.add_runtime`?`endpoint`:`version`,d=t[u]||l;return{kind:i,kinds:Object.keys(e.runtime_kinds),versions:c,defaultVersion:l,versionField:u,version:d,rootEndpoint:s,repositories:(e.repositories||[]).filter(e=>e.version===d)}}function P(e,t,n){let r=N(e,t,n);return ve(e).map(e=>r?e.name===`kind`?{...e,choices:r.kinds}:e.name===r.versionField?{...e,label:`API version`,choices:r.versions,disabled:!r.kind||!r.versions.length,description:r.kind?r.rootEndpoint?`This domain serves a root endpoint, which cannot coexist with version folders.`:r.versions.length?e.description:`This domain already serves every available version.`:`Choose a runtime kind to see its API versions.`}:e.name===`repository`?{...e,type:`runtime_repository`,label:`Local Bible source`,repositories:r.repositories,disabled:!r.kind||!r.versions.length}:e:e)}function we(e,t,n,r,i){let a={...t,[n]:r},o=N(e,a,i);return o&&t[n]!==r&&[`kind`,`version`,`endpoint`,`domain`].includes(n)&&(delete a.repository,delete a._repositoryMode,(n===`kind`||n===`domain`)&&(a[o.versionField]=o.defaultVersion)),a}function Te(e,{domain:t,endpoint:n}={}){let r=ve(e),i=Object.fromEntries(r.filter(e=>e.default!==void 0).map(e=>[e.name,e.default]));t&&r.some(e=>e.name===`domain`)&&(i.domain=t.domain),n!==void 0&&r.some(e=>e.name===`endpoint`)&&(i.endpoint=n?.label||``);let a=t?.settings||{},o=n?.endpoint_settings||{},s=A(e),c=s===`access.mode`?{mode:a.ACCESS_MODE}:s===`access.limits`?{rate:a.RATE_PER_SECOND,burst:a.RATE_BURST,hour:a.QUOTA_HOUR,day:a.QUOTA_DAY,connections:a.CONN_LIMIT}:s===`endpoint.change_source`?{repository:o.REPO_URL,ref:o.REPO_REF,source_path:o.REPO_PATH}:s===`cloudflare.mode`?{value:a.CLOUDFLARE_MODE}:s===`cloudflare.cache`?{value:a.CLOUDFLARE_CACHE}:{},l={...i,...Object.fromEntries(Object.entries(c).filter(([,e])=>e!==void 0))},u=N(e,l,t);return u&&(l[u.versionField]=u.defaultVersion),l}function Ee(e,t){return Object.fromEntries(ve(e).filter(e=>t[e.name]!==void 0&&(t[e.name]!==``||e.allow_empty)).map(e=>[e.name,t[e.name]]))}var De=(e,t)=>Array.isArray(e)?e:e?.[t]||[],Oe=new Set([`queued`,`waiting`,`running`]);function ke({items:e,onSelect:t}){return(0,E.jsx)(`div`,{className:`manage-grid`,children:e.map(e=>(0,E.jsxs)(`button`,{type:`button`,className:`manage-card`,onClick:()=>t(e),children:[(0,E.jsx)(`strong`,{children:e.title}),e.detail&&(0,E.jsx)(`small`,{children:e.detail}),e.badge&&(0,E.jsx)(le,{tone:e.tone,children:e.badge})]},e.id||e.title))})}function Ae({field:e,values:t,setValues:n,onError:r,domains:i,currentSettings:a,spec:o,selectedDomain:s}){let c=t=>n(n=>({...we(o,n,e.name,t,s),...e.name===`key`&&a?{value:a[t]??``}:{}})),l={className:`form-control`,"aria-label":e.label||e.name,required:e.required&&!e.allow_empty,disabled:e.disabled},u;if(e.type===`runtime_repository`){let r=t._repositoryMode===`manual`;return(0,E.jsxs)(`div`,{children:[(0,E.jsxs)(`label`,{children:[e.label,(0,E.jsxs)(`select`,{...l,className:`form-select`,value:r?`__manual__`:t.repository||``,onChange:e=>{let t=e.target.value;n(e=>({...e,repository:t===`__manual__`?``:t,_repositoryMode:t===`__manual__`?`manual`:`published`}))},children:[(0,E.jsx)(`option`,{value:``,children:`Automatic local source`}),e.repositories.map(e=>(0,E.jsx)(`option`,{value:e.value,children:e.label},e.value)),(0,E.jsx)(`option`,{value:`__manual__`,children:`Enter a local path…`})]})]}),r&&(0,E.jsxs)(`label`,{children:[`Custom local path`,(0,E.jsx)(`input`,{className:`form-control`,"aria-label":`Custom local path`,type:`text`,required:!0,autoComplete:`off`,value:t.repository||``,onChange:e=>c(e.target.value)})]}),e.description&&(0,E.jsx)(`small`,{children:e.description})]})}return u=e.type===`file`?(0,E.jsx)(`input`,{...l,type:`file`,accept:`.png,.jpg,.jpeg,.webp,.ico,.svg`,onChange:t=>{let i=t.target.files?.[0];if(!i)return;if(i.size>1048576){r(Error(`Choose an image smaller than 1 MiB.`)),t.target.value=``;return}let a=new FileReader;a.onload=()=>n(t=>({...t,filename:i.name,[e.name]:String(a.result).split(`,`)[1]})),a.onerror=()=>r(Error(`The selected file could not be read.`)),a.readAsDataURL(i)}}):e.type===`multiline`?(0,E.jsx)(`textarea`,{...l,rows:12,spellCheck:`false`,value:t[e.name]||``,onChange:e=>c(e.target.value)}):e.type===`boolean`?(0,E.jsxs)(`select`,{"aria-label":e.label||e.name,className:`form-select`,value:String(t[e.name]??!1),onChange:e=>c(e.target.value===`true`),children:[(0,E.jsx)(`option`,{value:`false`,children:`No`}),(0,E.jsx)(`option`,{value:`true`,children:`Yes`})]}):e.name===`domain`&&!e.required?(0,E.jsxs)(`select`,{"aria-label":e.label||e.name,className:`form-select`,value:t.domain||``,onChange:e=>c(e.target.value),children:[(0,E.jsx)(`option`,{value:``,children:e.scopeLabel||`All domains`}),i.map(e=>(0,E.jsx)(`option`,{value:e.domain,children:e.domain},e.domain))]}):e.choices||e.enum?(0,E.jsxs)(`select`,{...l,className:`form-select`,value:t[e.name]??``,onChange:e=>c(e.target.value),children:[(0,E.jsx)(`option`,{value:``,children:`Select…`}),(e.choices||e.enum).map(e=>(0,E.jsx)(`option`,{value:e,children:e},e))]}):(0,E.jsx)(`input`,{...l,type:e.secret?`password`:e.type===`integer`?`number`:`text`,autoComplete:e.secret?`new-password`:`off`,value:t[e.name]??``,onChange:t=>c(e.type===`integer`&&t.target.value!==``?Number(t.target.value):t.target.value)}),(0,E.jsxs)(`label`,{className:e.type===`multiline`?`full-width`:void 0,children:[e.label||e.name,u,e.description&&(0,E.jsx)(`small`,{children:e.description})]})}function je({execute:e,onError:t,onDetail:n,refresh:r,initialSection:i=``}){let[a,o]=(0,_.useState)(null),[s,c]=(0,_.useState)(null),[l,u]=(0,_.useState)(null),[d,f]=(0,_.useState)(null),[p,m]=(0,_.useState)(null),[h,g]=(0,_.useState)({section:i,domain:``,group:``,operation:``,endpoint:void 0}),[v,y]=(0,_.useState)({});(0,_.useEffect)(()=>{let e=!1;return u(null),Promise.allSettled([x(`operations`),x(`endpoints`)]).then(n=>{if(e)return;n[0].status===`fulfilled`&&o(n[0].value),n[1].status===`fulfilled`&&c(n[1].value);let r=n.find(e=>e.status===`rejected`);r&&(u(r.reason),t(r.reason))}),()=>{e=!0}},[r]),(0,_.useEffect)(()=>{let e=!1,n;async function r(){try{let[t,n]=await Promise.all([x(`jobs`),x(`management/state`)]);e||(f(t),m(n))}catch(n){e||t(n)}e||(n=setTimeout(r,2e3))}return r(),()=>{e=!0,clearTimeout(n)}},[r]);let b=De(a,`operations`),ee=(0,_.useMemo)(()=>j(s),[s]),te=ee.find(e=>e.domain===h.domain),S=te?.endpoints.find(e=>e.label===h.endpoint),C=b.find(e=>A(e)===h.operation),w=P(C,v,te),ne=N(C,v,te),re=!(!ne||ne.kind&&ne.versions.length),ie=_e.find(e=>e.id===h.section),ae=be(b,h.section,te),oe=ae.find(e=>e.title===h.group),ue=[`domains`,`go-live`].includes(h.section),de=!!(h.section&&!h.domain&&(ue||C&&xe(C))),fe=!!(C&&!de&&Se(C)&&h.endpoint===void 0),pe=p?.accepting_jobs===!1,me=!!(h.domain&&s&&!te),he=!!(h.endpoint&&s&&!S),ve=w.filter(e=>!(e.name===`domain`&&h.domain)&&!(e.name===`endpoint`&&Se(C))&&!(e.name===`filename`&&w.some(e=>e.type===`file`)));function ye(e){g(e);let t=b.find(t=>A(t)===e.operation),n=ee.find(t=>t.domain===e.domain),r=e.endpoint===void 0?void 0:n?.endpoints.find(t=>t.label===e.endpoint)||null;y(t?Te(t,{domain:n,endpoint:r}):{})}function we(e){let t=be(b,e.id),n=t.length===1?t[0]:void 0;ye({section:e.id,domain:``,group:n?.title||``,operation:n?.operations.length===1?A(n.operations[0]):``,endpoint:void 0})}function je(e){ye({...h,domain:e.id,endpoint:void 0})}function Me(e){ye({...h,operation:e.id,endpoint:void 0})}function Ne(t){t.preventDefault(),!(!C||pe||me||he||re)&&e(A(C),Ee(C,v),{title:C.title,description:C.description})}let Pe=[{label:`Manage`,action:()=>ye({section:``,domain:``,group:``,operation:``,endpoint:void 0})}];ie&&Pe.push({label:ie.title,action:()=>we(ie)}),h.domain&&ue&&Pe.push({label:h.domain,action:()=>ye({...h,group:``,operation:``,endpoint:void 0})}),h.group&&Pe.push({label:h.group,action:()=>ye({...h,operation:``,endpoint:void 0})}),C&&Pe.push({label:C.title,action:()=>ye({...h,domain:ue?h.domain:``,endpoint:void 0})}),h.domain&&!ue&&Pe.push({label:h.domain,action:()=>ye({...h,endpoint:void 0})}),C&&h.endpoint!==void 0&&Pe.push({label:h.endpoint||Ce(C)});let Fe;if(!a)Fe=l?(0,E.jsx)(D,{children:`Management actions could not be loaded. Use Refresh all to retry.`}):(0,E.jsx)(ce,{});else if(!b.length)Fe=(0,E.jsx)(D,{children:`No management operations were returned by the broker.`});else if(!ie)Fe=(0,E.jsx)(ke,{items:_e.filter(e=>be(b,e.id).length),onSelect:we});else if(me||he)Fe=(0,E.jsxs)(D,{children:[`This `,me?`domain`:`endpoint`,` is no longer registered. Select another from the menu above.`]});else if(de){let e=ee.filter(e=>h.section===`go-live`?!e.live:!C||M(C,e));Fe=s?e.length?(0,E.jsx)(ke,{items:e.map(e=>({id:e.domain,title:e.domain,detail:`${e.type===`mcp`?`MCP at /`:`${e.kind||e.type} · ${e.endpoints.length} endpoints`} · ${e.settings?.ACCESS_MODE||`access not reported`}`,badge:e.live?`Live`:`Staged`,tone:e.live?`success`:`info`})),onSelect:je}):(0,E.jsx)(D,{children:h.section===`go-live`?`No staged domains are waiting to go live.`:`No matching domains are registered. Choose Deploy a new domain from Manage to begin.`}):l?(0,E.jsx)(D,{children:`The domain inventory could not be loaded. Use Refresh all to retry.`}):(0,E.jsx)(ce,{children:`Loading registered domains…`})}else if(!h.group)Fe=(0,E.jsx)(ke,{items:ae.map(e=>({id:e.title,title:e.title,detail:e.operations.map(e=>e.title).join(` · `)})),onSelect:e=>{let t=ae.find(t=>t.title===e.id);ye({...h,group:e.id,operation:t.operations.length===1?A(t.operations[0]):``,endpoint:void 0})}});else if(!C)Fe=(0,E.jsx)(ke,{items:(oe?.operations||[]).map(e=>({id:A(e),title:e.title,detail:e.description,badge:e.mutates===!1?`View`:void 0})),onSelect:Me});else if(fe){let e=w.find(e=>e.name===`endpoint`),t=(te?.endpoints||[]).map(e=>({id:e.label,title:e.label===`root`?`Domain root`:`/${e.label}/`,detail:e.repository||`${e.kind} endpoint`}));e.required||t.unshift({id:``,title:Ce(C),detail:`Use the domain scope for this operation`}),Fe=t.length?(0,E.jsx)(ke,{items:t,onSelect:e=>ye({...h,endpoint:e.id})}):(0,E.jsx)(D,{children:`No endpoints are registered for this domain. Add an endpoint from Endpoints and repositories.`})}else Fe=A(C)===`system.update`?(0,E.jsx)(ge,{execute:e,refresh:r,paused:pe}):(0,E.jsxs)(`form`,{className:`management-form`,onSubmit:Ne,children:[C.description&&(0,E.jsx)(`p`,{className:`text-secondary`,children:C.description}),(h.domain||h.endpoint!==void 0)&&(0,E.jsxs)(`div`,{className:`management-context`,children:[(0,E.jsx)(`strong`,{children:h.domain}),h.endpoint!==void 0&&(0,E.jsxs)(`span`,{children:[` · `,h.endpoint?h.endpoint===`root`?`Domain root`:`/${h.endpoint}/`:Ce(C)]})]}),(0,E.jsx)(`div`,{className:`filter-grid`,children:ve.map(e=>(0,E.jsx)(Ae,{field:e.name===`domain`&&A(C)===`pages.upload_icon`?{...e,scopeLabel:`System icons`}:e,values:v,setValues:y,domains:ee,spec:C,selectedDomain:te,currentSettings:A(C)===`runtime.set`?S?.endpoint_settings:void 0,onError:t},`${h.operation}-${h.domain}-${h.endpoint}-${e.name}`))}),!!S&&A(C)===`runtime.set`&&(0,E.jsxs)(`details`,{className:`current-settings`,children:[(0,E.jsx)(`summary`,{children:`Current endpoint settings`}),(0,E.jsx)(k,{rows:Object.entries(S.endpoint_settings||{}).map(([e,t])=>({key:e,value:t})),columns:[{key:`key`,label:`Setting`},{key:`value`,label:`Current value`}]})]}),(0,E.jsx)(`button`,{className:`btn btn-primary`,disabled:pe||re,children:`Review operation`})]});let Ie=De(d,`jobs`),Le=Ie.filter(e=>Oe.has(e.status));return(0,E.jsxs)(E.Fragment,{children:[p?.refresh?.pending&&(0,E.jsxs)(`div`,{className:`alert ${p.refresh.state===`failed`?`alert-warning`:`alert-info`}`,role:`status`,children:[(0,E.jsx)(`strong`,{children:p.refresh.state===`failed`?`Management service update needs attention`:`Management services are updating`}),(0,E.jsx)(`div`,{children:p.refresh.state===`failed`?p.refresh.last_error||`The service refresh could not be scheduled. Management remains available; retry the update or inspect the service from the CLI.`:`Accepted jobs will finish before management restarts. Existing results and issued credentials remain available.`})]}),(0,E.jsxs)(O,{title:C?.title||h.group||(de?`Choose a domain`:ie?.title)||`Server management`,detail:`Follow the CLI menu flow. Started jobs continue when this page closes.`,children:[(0,E.jsx)(`nav`,{"aria-label":`Management navigation`,className:`management-breadcrumb`,children:Pe.map((e,t)=>(0,E.jsxs)(_.Fragment,{children:[t>0&&(0,E.jsx)(`span`,{"aria-hidden":`true`,children:`/`}),e.action&&te.status===`waiting`)?`An operation is waiting for the current CLI or management operation to finish; it will continue automatically.`:`Select an operation below for live progress.`]}),(0,E.jsx)(O,{title:`Operation history`,detail:`Select a job to view live progress and output`,children:d?(0,E.jsx)(k,{rows:Ie,onSelect:async e=>{try{n(`Operation details`,await x(`jobs/${encodeURIComponent(e.id)}`))}catch(e){t(e)}},columns:[{key:`id`,label:`Job`},{key:`operation`,label:`Operation`,render:e=>b.find(t=>A(t)===e.operation)?.title||e.operation},{key:`status`,label:`State`,render:e=>(0,E.jsx)(le,{tone:[`failed`,`interrupted`].includes(e.status)?`warning`:e.status===`succeeded`?`success`:`info`,children:e.status})},{key:`created`,label:`Submitted`,render:e=>T(e.created_at??e.created)},{key:`started`,label:`Started`,render:e=>T(e.started_at??e.started)},{key:`finished`,label:`Finished`,render:e=>T(e.finished_at??e.finished)}]}):(0,E.jsx)(ce,{})})]})}var Me=e=>String(e||`unknown`).replaceAll(`_`,` `),Ne=e=>e==null?`Unknown`:`${e<0?`−`:``}${w(Math.abs(e))}/s`;function Pe({refresh:e,execute:t}){let[n,r]=(0,_.useState)(null),[i,a]=(0,_.useState)(null);(0,_.useEffect)(()=>{let e=!1,t,n=new AbortController;async function i(){try{let t=await x(`capacity`,{signal:n.signal});e||(r(t),a(null))}catch(t){!e&&t.name!==`AbortError`&&a(t)}e||(t=setTimeout(i,3e4))}return i(),()=>{e=!0,clearTimeout(t),n.abort()}},[e]);let o=!!(i||n?.stale||n?.state!==`observed`),s=n?.collection||{};return(0,E.jsxs)(O,{title:`Capacity and sizing advice`,detail:`Measured limits and repeated saturation; suggestions require operator review and never change settings automatically.`,children:[(0,E.jsx)(se,{error:i}),n?(0,E.jsxs)(E.Fragment,{children:[(0,E.jsxs)(`div`,{className:`alert ${o?`alert-warning`:`alert-info`}`,role:`status`,children:[(0,E.jsx)(`strong`,{children:Me(n.state)}),` · Last sample: `,T(n.sampled_at),(0,E.jsx)(`div`,{children:n.note})]}),(0,E.jsxs)(`div`,{className:`fact-list`,children:[(0,E.jsxs)(`div`,{children:[(0,E.jsx)(`span`,{children:`Collection`}),(0,E.jsx)(`strong`,{children:o?`Unknown — observations are not current`:Me(s.state)})]}),(0,E.jsxs)(`div`,{children:[(0,E.jsx)(`span`,{children:`Unread backlog`}),(0,E.jsxs)(`strong`,{children:[w(s.unread_bytes),` · `,C(s.unread_files),` files`]})]}),(0,E.jsxs)(`div`,{children:[(0,E.jsx)(`span`,{children:`Transport / retained archives`}),(0,E.jsxs)(`strong`,{children:[w(s.budgeted_spool_bytes),` / `,w(s.retained_archive_bytes)]})]}),(0,E.jsxs)(`div`,{children:[(0,E.jsx)(`span`,{children:`Committed closed transport`}),(0,E.jsxs)(`strong`,{children:[w(s.consumed_rotated_bytes),` · already ingested`]})]}),(0,E.jsxs)(`div`,{children:[(0,E.jsx)(`span`,{children:`Reclamation`}),(0,E.jsxs)(`div`,{className:`text-end`,children:[(0,E.jsx)(`strong`,{children:o?`Unknown — observations are not current`:Me(s.reclamation?.state)}),(0,E.jsx)(`small`,{className:`d-block`,children:s.reclamation?.reason})]})]}),(0,E.jsxs)(`div`,{children:[(0,E.jsx)(`span`,{children:`Producer / collector throughput`}),(0,E.jsxs)(`strong`,{children:[Ne(s.producer_bytes_per_second),` / `,Ne(s.collector_bytes_per_second)]})]}),(0,E.jsxs)(`div`,{children:[(0,E.jsx)(`span`,{children:`Backlog growth / continuous backlog`}),(0,E.jsxs)(`strong`,{children:[Ne(s.backlog_growth_bytes_per_second),` / `,C(s.backlog_observed_seconds),` seconds`]})]}),(0,E.jsxs)(`div`,{children:[(0,E.jsx)(`span`,{children:`Rate window / peak window`}),(0,E.jsxs)(`strong`,{children:[C(s.rate_window_seconds),` seconds / `,C(n.window_seconds/3600),` hours`]})]})]}),(0,E.jsx)(k,{rowKey:e=>e.id,rows:n.limits||[],columns:[{key:`label`,label:`Limit`,render:e=>(0,E.jsxs)(E.Fragment,{children:[(0,E.jsx)(`strong`,{children:e.label}),(0,E.jsxs)(`small`,{className:`d-block`,children:[e.setting||`Host capacity`,` · `,e.unit]})]})},{key:`used`,label:`Usage / effective limit`,render:e=>e.available?`${C(e.used,2)} / ${C(e.effective_limit,2)} ${e.unit}`:`Unavailable`},{key:`high_water`,label:`Observed peak`,render:e=>e.available?`${C(e.high_water,2)} ${e.unit}`:`Unknown`},{key:`episodes`,label:`Saturation`,render:e=>e.available?(0,E.jsxs)(E.Fragment,{children:[C(e.episodes),` episodes`,(0,E.jsxs)(`small`,{className:`d-block`,children:[C(e.saturated_samples),` / `,C(e.samples),` samples; `,C(e.saturated_seconds),` / `,C(e.observed_seconds),` observed seconds`]}),(0,E.jsxs)(`small`,{className:`d-block`,children:[`At or above `,C(e.saturation_threshold*100),`% of limit`]})]}):`Unknown`},{key:`configuration`,label:`Configured value / owner`,render:e=>(0,E.jsxs)(E.Fragment,{children:[e.configuration?.value??`Not observable`,(0,E.jsxs)(`small`,{className:`d-block`,children:[Me(e.configuration?.owner),e.configuration?.editable?` · editable`:` · change at source`]}),(0,E.jsx)(`small`,{className:`d-block`,children:e.configuration?.note})]})},{key:`recommendation`,label:`Recommendation`,render:e=>{let n=he(e,o);return(0,E.jsxs)(E.Fragment,{children:[(0,E.jsx)(le,{tone:e.recommendation?.status===`adequate`?`success`:`info`,children:Me(e.recommendation?.status)}),e.recommendation?.value!=null&&!o&&(0,E.jsxs)(`strong`,{className:`d-block`,children:[C(e.recommendation.value,2),` `,e.unit]}),(0,E.jsx)(`small`,{className:`d-block`,children:e.recommendation?.reason}),e.recommendation?.assumptions&&(0,E.jsx)(`small`,{className:`d-block`,children:e.recommendation.assumptions}),n&&(0,E.jsx)(`button`,{className:`btn btn-sm btn-outline-primary mt-2`,type:`button`,onClick:()=>t(`settings.set`,n,{title:`Review ${n.key} = ${n.value}`,description:`This is an observed-demand recommendation, not an automatic correction or a guarantee. Confirm free capacity and collection health before applying.`}),children:`Review suggested setting`})]})}}]}),!!n.incidents?.length&&(0,E.jsxs)(E.Fragment,{children:[(0,E.jsx)(`h3`,{className:`h6 mt-3`,children:`Open capacity incidents`}),(0,E.jsx)(k,{rows:n.incidents,rowKey:e=>e.id,columns:[{key:`id`,label:`Condition`},{key:`since`,label:`Since`,render:e=>T(e.since)},{key:`last_sent`,label:`Last notification`,render:e=>T(e.last_sent)},{key:`last_event`,label:`Last transition`,render:e=>Me(e.last_event)}]})]}),!!Object.keys(n.problems||{}).length&&(0,E.jsxs)(`details`,{className:`mt-3`,children:[(0,E.jsx)(`summary`,{children:`Collection and reclamation diagnostics`}),(0,E.jsx)(`pre`,{className:`record-detail`,children:JSON.stringify(n.problems,null,2)})]})]}):!i&&(0,E.jsx)(ce,{children:`Reading capacity observations…`})]})}function Fe(e,t,n,r=Date.now()/1e3){let i=e.cache||{},a=i.query_translations?.[t],o=i.search_corpora?.translations?.[t],s=i.translation_cache?.translations?.[t],c=a?.expired_chapters>0||[o,s].some(e=>e&&(e.stale||e.checked_at!=null&&i.ttl_seconds!=null&&e.checked_at+i.ttl_seconds<=r)),l=e.translation_status?.[t],u=!!(a||o||s),d=!c&&(l?.ready??(n===`search`?!!(o?.verses>0&&o.indexes?.some(e=>e.case_sensitive===!1&&e.fold_diacritics===!0)):a?.complete===!0));return{code:t,pid:e.pid,resident:u,ready:!!d,stale:c,reason:l?.reason||``,retention_limited:l?.retention_limited===!0,query_chapters:a?.chapters,query_bytes:a?.estimated_bytes,search_verses:o?.verses,search_bytes:o?.estimated_bytes,snapshot_bytes:s?.estimated_bytes,snapshot_present:!!s,ttl:i.ttl_seconds,rss_bytes:e.rss_bytes,private_bytes:e.private_bytes,details:{query:a,search:o,snapshot:s,readiness:l,activity:e.activity},worker:e}}function Ie(e,t=Date.now()/1e3){let n=e.workers||[],r=new Set;for(let e of n)for(let t of[...Object.keys(e.cache?.query_translations||{}),...Object.keys(e.cache?.search_corpora?.translations||{}),...Object.keys(e.cache?.translation_cache?.translations||{})])r.add(t);return[...r].sort().map(n=>Le(e,n,t))}function Le(e,t,n=Date.now()/1e3){let r=(e.workers||[]).map(r=>Fe(r,t,e.kind,n)),i=e.expected_workers??r.length,a=e.complete===!0&&r.length>0&&r.length===i,o=r.filter(e=>e.resident),s=r.filter(e=>e.ready).length,c=o.some(e=>e.stale),l=a&&s===i;return{code:t,workers:r,resident:o,expected:i,completeReport:a,readyCount:s,ready:l,stale:c,status:l?`Warm`:c?`Recheck on use`:o.length?`Partly resident`:a?`Not in memory`:`Not reported`,configured:(e.configured_warm_translations||[]).includes(t),reasons:[...new Set(r.filter(e=>e.resident&&!e.ready).map(e=>e.reason).filter(Boolean))]}}function Re(e,t){let n=e.map(e=>e[t]).filter(e=>Number.isFinite(e));return n.length?{min:Math.min(...n),max:Math.max(...n)}:null}function ze(e,t,n=C){let r=Re(e,t);return r?r.min===r.max?n(r.min):`${n(r.min)}–${n(r.max)}`:`—`}function Be(e,t,n){let r=Re(e,t);return`${ze(e,t)} ${n}${r?.min===1&&r?.max===1?``:`s`}`}function Ve({row:e}){return(0,E.jsx)(le,{tone:e.ready?`success`:e.resident.length?`warning`:`muted`,children:e.status})}function He({row:e,onBack:t,onDetail:n}){return(0,E.jsxs)(`div`,{className:`worker-layer`,children:[(0,E.jsxs)(`div`,{className:`worker-heading`,children:[(0,E.jsx)(`button`,{className:`btn btn-sm btn-outline-secondary`,onClick:t,children:`Back to translations`}),(0,E.jsxs)(`strong`,{children:[e.code.toUpperCase(),` · workers`]}),(0,E.jsx)(Ve,{row:e})]}),(0,E.jsxs)(`p`,{className:`panel-note`,children:[C(e.resident.length),` of `,C(e.expected),` workers currently retain this translation. Counts and estimates below describe each worker separately. A worker can retain query chapters alongside a complete search corpus.`]}),(0,E.jsx)(k,{rows:e.workers,rowKey:e=>e.pid,onSelect:t=>n(`${e.code.toUpperCase()} · worker ${t.pid}`,t.details),columns:[{key:`pid`,label:`Worker PID`},{key:`resident`,label:`Cache state`,render:e=>(0,E.jsxs)(`div`,{children:[(0,E.jsx)(le,{tone:e.ready?`success`:e.resident?`warning`:`muted`,children:e.ready?`Warm`:e.stale?`Recheck on use`:e.resident?`Partly resident`:`Not in memory`}),e.reason&&(0,E.jsx)(`small`,{className:`cell-note`,children:e.reason})]})},{key:`query_chapters`,label:`Query chapters`,render:e=>(0,E.jsxs)(`div`,{children:[C(e.query_chapters),(0,E.jsxs)(`small`,{className:`cell-note`,children:[w(e.query_bytes),` estimated`]})]})},{key:`search_verses`,label:`Search corpus / index`,render:e=>(0,E.jsxs)(`div`,{children:[e.search_verses==null?`—`:`${C(e.search_verses)} verses`,(0,E.jsxs)(`small`,{className:`cell-note`,children:[w(e.search_bytes),` estimated`]})]})},{key:`snapshot_bytes`,label:`Translation snapshot`,render:e=>e.snapshot_present?`${w(e.snapshot_bytes)} estimated`:`—`},{key:`ttl`,label:`Memory TTL`,render:e=>e.ttl==null?`—`:`${C(e.ttl/86400,1)} days`},{key:`rss_bytes`,label:`Worker RSS`,render:e=>w(e.rss_bytes)},{key:`private_bytes`,label:`Worker private memory`,render:e=>w(e.private_bytes)}]}),(0,E.jsx)(`p`,{className:`panel-note`,children:`RSS includes shared pages and the whole worker. Private memory also covers the whole worker. Neither is a per-translation measurement; cache estimates may overlap.`})]})}function Ue({onError:e,execute:t,onDetail:n,refresh:r}){let[i,a]=(0,_.useState)(null),[o,s]=(0,_.useState)(``),[c,l]=(0,_.useState)(`kjv`),[u,d]=(0,_.useState)({});(0,_.useEffect)(()=>{let t=!1,n;async function r(){try{let e=await x(`translations`);t||a(e)}catch(n){t||e(n)}t||(n=setTimeout(r,5e3))}return r(),()=>{t=!0,clearTimeout(n)}},[r]);let f=i?.endpoints||[],p=f.find(e=>`${e.domain}/${e.label}`===o),m=p?.available_translations||[],h=c.trim().toLowerCase(),g=p?Le(p,h):null,v=/^[a-z0-9][a-z0-9_-]{0,29}$/.test(h);function y(e,n,r){t(`runtime.cache`,{domain:e.domain,endpoint:e.label,action:r,translation:n})}return(0,E.jsxs)(E.Fragment,{children:[(0,E.jsxs)(O,{title:`Translation memory`,detail:`See what is in memory, then select a translation to inspect its workers. Current residency is refreshed every five seconds.`,children:[(0,E.jsxs)(`div`,{className:`filter-grid`,children:[(0,E.jsxs)(`label`,{children:[`Runtime endpoint`,(0,E.jsxs)(`select`,{"aria-label":`Runtime endpoint`,className:`form-select`,value:o,onChange:e=>s(e.target.value),children:[(0,E.jsx)(`option`,{value:``,children:`Select endpoint`}),f.map(e=>(0,E.jsxs)(`option`,{value:`${e.domain}/${e.label}`,children:[e.domain,` / `,e.label,` · `,e.kind]},`${e.domain}/${e.label}`))]})]}),(0,E.jsxs)(`label`,{children:[`Translation`,(0,E.jsx)(`input`,{list:`available-translations`,className:`form-control`,value:c,onChange:e=>l(e.target.value),placeholder:`kjv`}),(0,E.jsx)(`datalist`,{id:`available-translations`,children:m.map(e=>(0,E.jsx)(`option`,{value:e.translation,children:Le(p,e.translation).status},e.translation))})]}),(0,E.jsx)(`div`,{className:`d-flex gap-2 align-items-end`,children:(0,E.jsx)(`button`,{className:`btn btn-primary`,disabled:!p||!v||g?.ready,onClick:()=>y(p,h,`warm`),children:g?.ready?`Already warm`:`Warm translation`})})]}),g&&v&&(0,E.jsxs)(`div`,{className:`warm-selection`,"aria-live":`polite`,children:[(0,E.jsx)(Ve,{row:g}),(0,E.jsxs)(`span`,{children:[h.toUpperCase(),` · `,C(g.resident.length),` / `,C(g.expected),` workers retain data · `,C(g.readyCount),` ready`,g.configured?` · configured for startup warming`:``]}),g.reasons.map(e=>(0,E.jsx)(`small`,{children:e},e)),!g.completeReport&&(0,E.jsx)(`small`,{children:`Some workers have not reported; their memory state is unknown.`})]})]}),i?f.length?f.map(e=>{let t=`${e.domain}/${e.label}`,r=Ie(e),i=u[t]?Le(e,u[t]):null;return(0,E.jsxs)(O,{title:`${e.domain} / ${e.label}`,detail:`${e.kind} · ${r.length} translation${r.length===1?``:`s`} currently resident · ${e.workers?.length||0} worker${e.workers?.length===1?``:`s`} reported`,actions:(0,E.jsx)(le,{tone:e.complete?`success`:`warning`,children:e.complete?`All workers reported`:`Partial report`}),children:[i?(0,E.jsx)(He,{endpoint:e,row:i,onDetail:n,onBack:()=>d({...u,[t]:``})}):(0,E.jsxs)(E.Fragment,{children:[r.length?(0,E.jsx)(k,{rows:r,rowKey:e=>e.code,onSelect:e=>d({...u,[t]:e.code}),columns:[{key:`code`,label:`Translation`,render:e=>(0,E.jsxs)(`span`,{children:[e.code.toUpperCase(),(0,E.jsx)(`small`,{className:`cell-note`,children:e.configured?`Startup warming configured`:`Currently in memory`})]})},{key:`status`,label:`Readiness`,render:e=>(0,E.jsxs)(`div`,{children:[(0,E.jsx)(Ve,{row:e}),e.reasons.map(e=>(0,E.jsx)(`small`,{className:`cell-note`,children:e},e))]})},{key:`workers`,label:`Worker coverage`,render:e=>(0,E.jsxs)(`div`,{children:[C(e.resident.length),` / `,C(e.expected),` resident`,(0,E.jsxs)(`small`,{className:`cell-note`,children:[C(e.readyCount),` / `,C(e.expected),` ready`]})]})},{key:`query_chapters`,label:`Query data per worker`,render:e=>{let t=e.resident.filter(e=>e.query_chapters!=null);return t.length?(0,E.jsxs)(`div`,{children:[Be(t,`query_chapters`,`chapter`),(0,E.jsxs)(`small`,{className:`cell-note`,children:[ze(t,`query_bytes`,w),` estimated · `,t.length,` worker`,t.length===1?``:`s`]})]}):`—`}},{key:`search_verses`,label:`Search data per worker`,render:e=>{let t=e.resident.filter(e=>e.search_verses!=null);return t.length?(0,E.jsxs)(`div`,{children:[ze(t,`search_verses`),` verses`,(0,E.jsxs)(`small`,{className:`cell-note`,children:[ze(t,`search_bytes`,w),` corpus / index estimate · `,t.length,` worker`,t.length===1?``:`s`]})]}):`—`}},{key:`snapshot_bytes`,label:`Snapshot per worker`,render:e=>{let t=e.resident.filter(e=>e.snapshot_present);return t.length?(0,E.jsxs)(`div`,{children:[ze(t,`snapshot_bytes`,w),` estimated`,(0,E.jsxs)(`small`,{className:`cell-note`,children:[t.length,` worker`,t.length===1?``:`s`]})]}):`—`}},{key:`actions`,label:`All workers`,render:t=>(0,E.jsxs)(`div`,{className:`btn-group`,children:[!t.ready&&(0,E.jsx)(`button`,{className:`btn btn-sm btn-outline-primary`,onClick:()=>y(e,t.code,`warm`),children:`Warm`}),(0,E.jsx)(`button`,{className:`btn btn-sm btn-outline-primary`,onClick:()=>y(e,t.code,`reload`),children:`Reload`}),(0,E.jsx)(`button`,{className:`btn btn-sm btn-outline-danger`,onClick:()=>y(e,t.code,`drop`),children:`Drop`})]})}]}):(0,E.jsx)(D,{children:e.complete?`No translations are currently retained in worker memory.`:`No resident translations have been reported yet.`}),(0,E.jsx)(`p`,{className:`panel-note`,children:`Ranges show differences between workers, not a combined memory total. Select a translation for individual worker contents and measured memory.`})]}),(0,E.jsxs)(`details`,{className:`worker-details`,children:[(0,E.jsxs)(`summary`,{children:[C(e.available_translations?.length||0),` translations available on disk`]}),(0,E.jsx)(k,{rows:e.available_translations||[],onSelect:e=>n(`${e.translation.toUpperCase()} on disk`,e),columns:[{key:`translation`,label:`Translation`,render:e=>e.translation.toUpperCase()},{key:`memory`,label:`Current memory`,render:t=>(0,E.jsx)(Ve,{row:Le(e,t.translation)})},{key:`allocated_bytes`,label:`Allocated disk`,render:e=>w(e.allocated_bytes)},{key:`logical_bytes`,label:`File contents`,render:e=>w(e.logical_bytes)},{key:`files`,label:`Files`,render:e=>C(e.files)},{key:`warm`,label:`All workers`,render:t=>{let n=Le(e,t.translation);return(0,E.jsx)(`button`,{className:`btn btn-sm btn-outline-primary`,disabled:n.ready,onClick:()=>y(e,t.translation,`warm`),children:n.ready?`Already warm`:`Warm translation`})}}]})]}),!!e.configured_warm_translations?.length&&(0,E.jsxs)(`p`,{className:`panel-note`,children:[`Configured startup warm-ups: `,e.configured_warm_translations.map(e=>e.toUpperCase()).join(`, `),`. Configuration describes startup intent; the table shows current memory.`]}),e.inventory_error&&(0,E.jsx)(`div`,{className:`alert alert-warning m-3`,children:e.inventory_error}),!!e.errors?.length&&(0,E.jsx)(`div`,{className:`alert alert-warning m-3`,children:e.errors.map((e,t)=>(0,E.jsx)(`div`,{children:typeof e==`string`?e:e.error||`Worker did not report.`},t))})]},t)}):(0,E.jsx)(D,{children:`No runtime endpoints are configured.`}):(0,E.jsx)(ce,{})]})}function We(e,t,n,r={},i=Math.floor(Date.now()/1e3)){let a=n?i:t.end,o=n?a-900:t.start,s=n?5:Math.max(1,Math.ceil((a-o)/400)),c=S({...r,start:o,end:a,bucket_seconds:s});return e===`Overview`?{overview:`overview?${c}&dimensions=auth,endpoint,status,translation,search,reference,book,ip,referrer,user_agent`,history:`history?${c}`}:e===`Resources`?{metrics:`metrics?${S({start:o,end:a,bucket_seconds:s})}`}:e===`MCP traffic`?{mcp:`mcp?${c}`}:e===`Audience`?{audience:`audience?${c}`}:{}}var Ge=()=>({data:null,error:null,loading:!0,preparing:!1,progress:null});function Ke({paths:e,live:t=!1,interval:n=2e3,onChange:r,onError:i=()=>{},onUpdate:a=()=>{},load:o=x,schedule:s=setTimeout,cancel:c=clearTimeout}){let l=new AbortController,u=new Map,d=!1;async function f(c,p=Ge()){if(d)return;let m={...p,loading:!0};r(c,m);let h,g=t?n:null;try{let t=await o(e()[c],{signal:l.signal});if(d)return;t.state===`preparing`?(h={...m,error:null,preparing:!0,progress:t.progress||null},g=Math.max(500,Math.min(3e4,Number(t.retry_after)*1e3||2e3))):(h={data:t,error:null,loading:!1,preparing:!1,progress:null},a())}catch(e){if(d||e.name===`AbortError`)return;h={...m,error:e,loading:!1,preparing:!1,progress:null},e.status===401&&(g=null,i(e))}d||(r(c,h),g!==null&&u.set(c,s(()=>f(c,h),g)))}for(let t of Object.keys(e()))f(t);return()=>{d=!0,l.abort();for(let e of u.values())c(e);u.clear()}}function qe({page:e,range:t,live:n,filters:r,refresh:i,enabled:a=!0,interval:o=2e3,onError:s,onUpdate:c}){let l=JSON.stringify([e,t.start,t.end,n,r,i,a,o]),[u,d]=(0,_.useState)({key:null,values:{}});(0,_.useEffect)(()=>{if(d({key:l,values:{}}),a)return Ke({paths:()=>We(e,t,n,r),live:n,interval:o,onError:s,onUpdate:c,onChange:(e,t)=>d(n=>({key:l,values:{...n.key===l?n.values:{},[e]:t}}))})},[l]);let f=u.key===l?u.values:{};return Object.fromEntries(Object.keys(We(e,t,n,r)).map(e=>[e,f[e]||Ge()]))}function Je({report:e,label:t,children:n}){return(0,E.jsxs)(E.Fragment,{children:[(0,E.jsx)(se,{error:e.error&&Error(`${t}: ${e.error.message}`)}),e.preparing&&(0,E.jsxs)(`div`,{className:`alert alert-info py-2`,role:`status`,children:[`Preparing `,t.toLowerCase(),` for this time range…`]}),e.data?n:e.loading&&!e.preparing?(0,E.jsxs)(ce,{children:[`Loading `,t.toLowerCase(),`…`]}):null]})}function Ye({dimension:e,title:t,label:n,filters:r,range:i,live:a,refresh:o,onFilter:s,onError:c}){let[l,u]=(0,_.useState)(``),[d,f]=(0,_.useState)(``),{audience:p}=qe({page:`Audience`,range:i,live:a,refresh:o,onError:c,interval:5e3,filters:{...r,dimension:e,top:1e3,[`${e}_contains`]:d}}),m=p.data?.[e===`referrer`?`referrers`:`user_agents`]||[];return(0,E.jsxs)(O,{title:t,detail:`Origin requests, including errors. Select a row to explore its traffic.`,children:[(0,E.jsxs)(`form`,{className:`filter-grid`,onSubmit:e=>{e.preventDefault(),f(l)},children:[(0,E.jsxs)(`label`,{children:[`Find `,n.toLowerCase(),(0,E.jsx)(`input`,{className:`form-control`,value:l,onChange:e=>u(e.target.value),placeholder:`Part of a ${n.toLowerCase()}`})]}),(0,E.jsxs)(`div`,{className:`d-flex gap-2 align-items-end`,children:[(0,E.jsx)(`button`,{className:`btn btn-sm btn-primary`,children:`Search`}),(0,E.jsx)(`button`,{type:`button`,className:`btn btn-sm btn-outline-secondary`,onClick:()=>{u(``),f(``)},children:`Clear`})]})]}),(0,E.jsx)(Je,{report:p,label:t,children:(0,E.jsx)(k,{rows:m,rowKey:e=>e.value,onSelect:t=>s(e,t.value,t.filters),columns:[{key:`value`,label:n,render:e=>(0,E.jsx)(`span`,{className:`consumer-value`,title:e.value,children:e.value})},{key:`calls`,label:`Requests`,render:e=>C(e.calls)},{key:`errors`,label:`Errors`,render:e=>C(e.errors)},{key:`bytes`,label:`Transfer`,render:e=>w(e.bytes)}]})}),(0,E.jsx)(`div`,{className:`table-footer`,children:(0,E.jsxs)(`small`,{children:[m?.length===1e3?`Top 1,000 matching values. Search to narrow the results.`:`${C(m?.length||0)} matching values`,` · requests without a recorded `,n.toLowerCase(),` are omitted.`]})})]})}function Xe(e){return(0,E.jsxs)(E.Fragment,{children:[(0,E.jsx)(`p`,{className:`text-secondary`,children:`Referrers and user agents are recorded request fields. User agents may identify software, robots or browsers; these reported values do not establish a person's identity. Select a value to inspect matching requests in this time range.`}),(0,E.jsx)(Ye,{...e,dimension:`referrer`,title:`Referrers`,label:`Referrer`}),(0,E.jsx)(Ye,{...e,dimension:`user_agent`,title:`User agents and robots`,label:`User agent`})]})}function Ze(e,t,n={}){return{...n,...t.filters||{[e]:t.value}}}var Qe=e=>[`queued`,`waiting`,`running`].includes(e),$e=[[`mcp_method`,`MCP method`],[`mcp_tool`,`MCP tool`],[`mcp_client_name`,`Declared MCP client`],[`mcp_client_version`,`MCP client version`],[`upstream_service`,`Upstream service`],[`upstream_api_version`,`Upstream API version`],[`upstream_operation`,`Upstream operation`]],et=[`success`,`tool_error`,`protocol_error`,`transport_error`,`unknown`],tt=new Set([`tool_error`,`protocol_error`,`transport_error`]);function nt(e={}){return{...e,endpoint_kind:`mcp`,origin_only:`true`}}function rt(e={}){let t=nt(e);for(let e of[`version`,`translation`,`reference`,`search`,`book`,`usage`,`operation`])delete t[e];return e.endpoint_kind!==`mcp`&&delete t.endpoint,t}function it(e){return Number(e.status)>=400||e.mcp_error===!0||tt.has(e.mcp_outcome)}function at(e,t,n){if(t===`endpoint_kind`&&n===`mcp`)return rt(e);let r={...e,[t]:n};if((t===`successful`&&n!==`true`||t===`endpoint_kind`)&&delete r.usage,t===`endpoint_kind`&&n!==`mcp`){for(let[e]of $e)delete r[e];delete r.mcp_outcome,e.endpoint_kind===`mcp`&&delete r.endpoint}return t===`mcp_outcome`&&tt.has(n)&&(delete r.successful,delete r.usage),t===`successful`&&n===`true`&&tt.has(r.mcp_outcome)&&delete r.mcp_outcome,t===`status`&&/^\d{3}$/.test(String(n))&&!(Number(n)>=200&&Number(n)<300)&&Number(n)!==304&&(delete r.usage,delete r.successful),r}var ot=[[`endpoint`,`MCP domains`,`Origin requests to the protocol endpoint at /`,`Domain`],[`mcp_method`,`Protocol methods`,`Initialization, discovery, tool calls and notifications`,`Method`],[`mcp_tool`,`MCP tools`,`Recorded tool names, without tool arguments`,`Tool`],[`mcp_outcome`,`Protocol outcomes`,`Tool and protocol errors can have HTTP status 200`,`Outcome`],[`mcp_client_name`,`Declared clients`,`Client names supplied during initialization`,`Client`],[`mcp_client_version`,`Declared client versions`,`Client versions supplied during initialization`,`Client version`],[`upstream_service`,`Upstream API services`,`Service selected by a recorded API operation`,`Service`],[`upstream_api_version`,`Upstream API versions`,`API versions are tool inputs; the MCP host has no version paths`,`API version`],[`upstream_operation`,`Upstream API operations`,`Operation identifiers selected by recorded tool calls`,`Operation`],[`status`,`HTTP responses`,`Includes rejected and malformed origin requests`,`HTTP status`]];function st({filters:e,range:t,live:n,refresh:r,onFilter:i,onError:a,onZoom:o}){let s=(0,_.useMemo)(()=>nt(e),[e]),{mcp:c}=qe({page:`MCP traffic`,range:t,live:n,filters:s,refresh:r,onError:a}),l=c.data;function u(e,t,n){i(e,t,Ze(e,{value:t,filters:n},s))}if(!l)return(0,E.jsx)(Je,{report:c,label:`MCP traffic`});let d=[[`MCP requests`,C(l.mcp_requests??l.calls),`${C(l.unique_ips)} unique IPs`],[`Tool calls`,C(l.mcp_tool_calls),`Recorded tools/call requests`],[`Errors`,C(l.mcp_errors??l.errors),`${C(l.http_errors)} HTTP errors; includes protocol failures`],[`Average duration`,`${C(l.duration_ms,1)} ms`,`Observed origin request duration`],[`P95 duration`,l.latency_ms?.p95==null?`—`:`${C(l.latency_ms.p95,1)} ms`,`Histogram estimate`]],f=ae(l.series||[],[[`duration_ms`,`Average duration (ms)`]]);return f.xAxis.axisLabel={hideOverlap:!0},(0,E.jsxs)(E.Fragment,{children:[(0,E.jsx)(Je,{report:c,label:`MCP traffic`}),(0,E.jsx)(`p`,{className:`text-secondary`,children:`MCP origin requests are counted once, including requests rejected before protocol handling. Protocol methods and outcomes appear when captured; user agents remain available independently of declared client metadata.`}),(0,E.jsx)(`div`,{className:`metric-grid`,children:d.map(([e,t,n])=>(0,E.jsxs)(`div`,{className:`metric panel`,children:[(0,E.jsx)(`span`,{children:e}),(0,E.jsx)(`strong`,{children:t}),(0,E.jsx)(`small`,{children:n})]},e))}),(0,E.jsxs)(`div`,{className:`overview-grid`,children:[(0,E.jsx)(O,{className:`flow-panel`,title:`MCP requests and errors`,detail:`HTTP failures and captured MCP failures`,actions:(0,E.jsx)(`button`,{className:`btn btn-sm btn-outline-secondary`,onClick:()=>u(`endpoint_kind`,`mcp`,s),children:`Explore requests`}),children:(0,E.jsx)(re,{label:`MCP requests and errors over time`,height:300,onZoom:o,option:ae(l.series||[],[[`calls`,`Requests`],[`errors`,`Errors`]],{zoom:!0})})}),(0,E.jsx)(O,{title:`Request duration`,detail:`Includes the lifetime of streamed HTTP requests`,children:(0,E.jsx)(re,{label:`Average MCP origin request duration in milliseconds`,height:300,option:f})})]}),(0,E.jsx)(`div`,{className:`resource-grid`,children:ot.map(([e,t,n,r])=>(0,E.jsx)(O,{title:t,detail:n,children:(0,E.jsx)(k,{rows:l.breakdowns?.[e]||[],onSelect:t=>u(e,t.value,t.filters),columns:[{key:`value`,label:r,render:e=>(0,E.jsx)(`span`,{className:`consumer-value`,title:String(e.value),children:e.value||(0,E.jsx)(le,{children:`Unrecorded`})})},{key:`calls`,label:`Requests`,render:e=>C(e.calls)},{key:`errors`,label:`Errors`,render:e=>C(e.errors)},{key:`duration_ms`,label:`Avg. ms`,render:e=>C(e.duration_ms,1)}]})},e))}),(0,E.jsx)(Xe,{filters:s,range:t,live:n,refresh:r,onFilter:u,onError:a})]})}var ct=[`Overview`,`Traffic`,`MCP traffic`,`Audience`,`Translations`,`Resources`,`Events`,`Manage`,`Sessions`],lt={Live:900,"24 hours":86400,"7 days":604800,"30 days":2592e3,"6 months":15552e3},ut=(e,t)=>Array.isArray(e)?e:e?.[t]??e?.items??[];function dt({title:e,value:t,onClose:n,children:r}){let i=(0,_.useRef)(null),a=(0,_.useId)();return(0,_.useEffect)(()=>{let e=new window.bootstrap.Modal(i.current),t=()=>n();return i.current.addEventListener(`hidden.bs.modal`,t),e.show(),()=>{i.current?.removeEventListener(`hidden.bs.modal`,t),e.hide(),e.dispose()}},[]),(0,E.jsx)(`div`,{ref:i,className:`modal`,tabIndex:`-1`,"aria-labelledby":a,children:(0,E.jsx)(`div`,{className:`modal-dialog modal-xl modal-dialog-scrollable`,children:(0,E.jsxs)(`div`,{className:`modal-content`,children:[(0,E.jsxs)(`div`,{className:`modal-header`,children:[(0,E.jsx)(`h2`,{className:`modal-title fs-5`,id:a,children:e}),(0,E.jsx)(`button`,{className:`btn-close`,"data-bs-dismiss":`modal`,"aria-label":`Close details`})]}),(0,E.jsx)(`div`,{className:`modal-body`,children:r||(0,E.jsx)(`pre`,{className:`record-detail`,children:JSON.stringify(t,null,2)})})]})})})}function ft({onLogin:e}){let[t,n]=(0,_.useState)(``),[r,i]=(0,_.useState)(``),[a,o]=(0,_.useState)(null),[s,c]=(0,_.useState)(60),[l,u]=(0,_.useState)(null),[d,f]=(0,_.useState)(!1),[p,m]=(0,_.useState)(!1);(0,_.useEffect)(()=>{if(!a)return;let e=setInterval(()=>c(Math.max(0,Math.ceil((a.deadline-Date.now())/1e3))),250);return()=>clearInterval(e)},[a]);async function h(s){s.preventDefault(),u(null),f(!0);try{if(a)await te(`auth/token`,{challenge_id:a.challenge_id,token:r}),i(``),e(await x(`auth/status`));else{let e=await te(`auth/password`,{password:t});n(``),o({...e,deadline:Date.now()+e.expires_in*1e3})}}catch(e){u(e),(e.status===403||e.status===423)&&m(!0)}finally{f(!1)}}return(0,E.jsxs)(`main`,{className:`login-page`,children:[(0,E.jsxs)(`div`,{className:`login-brand`,children:[(0,E.jsx)(`img`,{src:`/favicon.png`,alt:``}),(0,E.jsx)(`span`,{children:`getBible`}),(0,E.jsx)(`small`,{children:`OPERATIONS`})]}),(0,E.jsxs)(`section`,{className:`login-box panel`,children:[(0,E.jsx)(`span`,{className:`eyebrow`,children:`PRIVATE CONSOLE`}),(0,E.jsx)(`h1`,{children:a?`Check Telegram`:`Sign in to your server`}),(0,E.jsx)(`p`,{children:a?`Enter the one-use code sent to your configured Telegram group.`:`Your password and a Telegram code protect access to this console.`}),(0,E.jsx)(se,{error:l}),(0,E.jsxs)(`form`,{onSubmit:h,children:[(0,E.jsx)(`label`,{className:`form-label`,htmlFor:`credential`,children:a?`Telegram code`:`Password`}),(0,E.jsx)(`input`,{autoFocus:!0,className:`form-control form-control-lg`,id:`credential`,type:a?`text`:`password`,autoComplete:a?`one-time-code`:`current-password`,spellCheck:`false`,autoCapitalize:`none`,value:a?r:t,disabled:d||p,onChange:e=>a?i(e.target.value):n(e.target.value),required:!0}),a&&(0,E.jsx)(`div`,{className:`mt-3 ${s<15?`text-danger`:`text-secondary`}`,"aria-live":`polite`,children:s?`${s}s remaining`:`Code expired. Unblock your IP through the CLI to try again.`}),(0,E.jsx)(`button`,{className:`btn btn-primary w-100 mt-4`,disabled:d||p||a&&!s,children:d?`Please wait…`:a?`Verify and open dashboard`:`Continue with Telegram`})]}),(0,E.jsx)(`p`,{className:`login-note`,children:a?`An incorrect or expired code blocks this IP until it is unblocked through the CLI.`:`Three incorrect passwords block this IP. Successful sessions last up to 30 days.`})]}),(0,E.jsx)(`small`,{className:`text-secondary`,children:`Traffic recording continues while this console is asleep.`})]})}function pt({rows:e=[],label:t,onSelect:n}){let r=Math.max(1,...e.map(e=>e.calls));return e.length?(0,E.jsx)(`div`,{className:`rankings`,children:e.slice(0,8).map((e,i)=>(0,E.jsxs)(`button`,{className:`ranking`,onClick:()=>n?.(e.value,e),children:[(0,E.jsx)(`span`,{className:`rank-index`,children:String(i+1).padStart(2,`0`)}),(0,E.jsxs)(`span`,{className:`rank-name`,title:String(e.label||e.value),children:[e.label||e.value||`Unspecified`,(0,E.jsx)(ue,{value:e.calls,max:r,label:`${t}: ${e.value}`})]}),(0,E.jsx)(`strong`,{children:C(e.calls)})]},`${e.value}-${i}`))}):(0,E.jsx)(D,{})}function mt({overview:e,timeline:t,onFilter:n,onDetail:r,onZoom:i}){let a=e.data||{},o=t.data||{},s=a.breakdowns||{},c=a.latest_metrics||{},l=[[`Origin requests`,C(a.calls),`${C(a.unique_ips)} unique IPs`],[`Request rate`,C(a.requests_per_second,1),`requests / second`],[`P95 latency`,a.latency_ms?.p95==null?`—`:`${C(a.latency_ms.p95,1)} ms`,`histogram estimate`],[`Cache hit rate`,a.cache_hit_ratio==null?`—`:`${C(a.cache_hit_ratio*100,1)}%`,`${C(a.cache_hits)} origin cache hits`],[`Errors`,C(a.errors),`${C(a.rate_limited)} rate limited`]];return(0,E.jsxs)(E.Fragment,{children:[(0,E.jsx)(Je,{report:e,label:`Request totals`,children:(0,E.jsx)(`div`,{className:`metric-grid`,children:l.map(([e,t,n])=>(0,E.jsxs)(`div`,{className:`metric panel`,children:[(0,E.jsx)(`span`,{children:e}),(0,E.jsx)(`strong`,{children:t}),(0,E.jsx)(`small`,{children:n})]},e))})}),(0,E.jsxs)(`div`,{className:`overview-grid`,children:[(0,E.jsx)(O,{className:`flow-panel`,title:`Request flow`,detail:`Origin traffic over the selected period`,actions:(0,E.jsxs)(le,{tone:`info`,children:[w(a.bytes),` transferred`]}),children:(0,E.jsx)(Je,{report:t,label:`Request flow`,children:(0,E.jsx)(re,{label:`Origin requests and errors by time`,height:310,onZoom:i,option:ae(o.series||[],[[`calls`,`Requests`],[`errors`,`Errors`]],{zoom:!0})})})}),e.data&&(0,E.jsxs)(E.Fragment,{children:[(0,E.jsx)(O,{title:`Access breakdown`,detail:`Credential state at the origin`,children:(0,E.jsx)(re,{label:`Anonymous, authenticated and rejected request breakdown`,option:oe(s.auth||[]),height:310})}),(0,E.jsx)(O,{className:`endpoints-panel`,title:`API endpoints`,detail:`Select an endpoint to inspect its traffic`,children:(0,E.jsx)(k,{rows:s.endpoint||s.domain||[],onSelect:e=>n(`endpoint`,e.value,e.filters),columns:[{key:`value`,label:`Endpoint`},{key:`calls`,label:`Requests`,render:e=>C(e.calls)},{key:`errors`,label:`Errors`,render:e=>C(e.errors)},{key:`bytes`,label:`Transfer`,render:e=>w(e.bytes)}]})}),(0,E.jsx)(O,{title:`Response codes`,detail:`Failures and rate limits`,children:(0,E.jsx)(pt,{rows:s.status||[],label:`Response status`,onSelect:(e,t)=>n(`status`,e,Ze(`status`,t))})}),(0,E.jsx)(O,{title:`Popular translations`,detail:`Successful origin requests`,children:(0,E.jsx)(pt,{rows:s.translation||[],label:`Translation`,onSelect:(e,t)=>n(`translation`,e,Ze(`translation`,t))})}),(0,E.jsx)(O,{title:`Frequent searches`,detail:`Successful origin requests`,children:(0,E.jsx)(pt,{rows:s.search||[],label:`Search`,onSelect:(e,t)=>n(`search`,e,Ze(`search`,t))})}),(0,E.jsx)(O,{title:`Scripture references`,detail:`Successful origin requests`,children:(0,E.jsx)(pt,{rows:s.reference||[],label:`Reference`,onSelect:(e,t)=>n(`reference`,e,Ze(`reference`,t))})}),(0,E.jsx)(O,{title:`Books of the Bible`,detail:`Successful origin requests`,children:(0,E.jsx)(pt,{rows:s.book||[],label:`Book`,onSelect:(e,t)=>n(`book`,e,Ze(`book`,t))})}),(0,E.jsx)(O,{title:`Active IP addresses`,detail:`Inspect high-volume origin consumers`,children:(0,E.jsx)(pt,{rows:s.ip||[],label:`Client IP`,onSelect:(e,t)=>n(`ip`,e,Ze(`ip`,t))})}),(0,E.jsx)(O,{title:`Referrers`,detail:`Origin requests, including errors`,children:(0,E.jsx)(pt,{rows:s.referrer||[],label:`Referrer`,onSelect:(e,t)=>n(`referrer`,e,t.filters)})}),(0,E.jsx)(O,{title:`User agents`,detail:`Origin requests, including errors`,children:(0,E.jsx)(pt,{rows:s.user_agent||[],label:`User agent`,onSelect:(e,t)=>n(`user_agent`,e,t.filters)})}),(0,E.jsx)(O,{title:`Collector health`,detail:`Continuous capture, independent of this dashboard`,children:(0,E.jsxs)(`div`,{className:`fact-list`,children:[(0,E.jsxs)(`div`,{children:[(0,E.jsx)(`span`,{children:`Available history`}),(0,E.jsx)(`strong`,{children:T(a.retention?.first_request)})]}),(0,E.jsxs)(`div`,{children:[(0,E.jsx)(`span`,{children:`Last sample`}),(0,E.jsx)(`strong`,{children:T(c.stamp)})]}),(0,E.jsxs)(`div`,{children:[(0,E.jsx)(`span`,{children:`Observed scope`}),(0,E.jsx)(`strong`,{children:`Origin only`})]}),(0,E.jsx)(`button`,{className:`btn btn-outline-secondary btn-sm`,onClick:()=>r(`Retention and capture health`,a.retention||{}),children:`Retention details`})]})})]})]})]})}function ht({filters:e,setFilters:t,range:n,live:r,onError:i,onDetail:a}){let[o,s]=(0,_.useState)(null),[c,l]=(0,_.useState)(``),[u,d]=(0,_.useState)([]),[f,p]=(0,_.useState)(n),[m,h]=(0,_.useState)(e);(0,_.useEffect)(()=>{h(e),l(``),d([])},[e]),(0,_.useEffect)(()=>{let t=new AbortController,a=!1,o;async function l(){let u=r&&!c?Math.floor(Date.now()/1e3):c?f.end:n.end,d=r&&!c?u-900:c?f.start:n.start;try{let i=await x(`requests?${S({start:d,end:u,...e,cursor:c,limit:100})}`,{signal:t.signal});a||(s(i),c||p({start:r?u-900:n.start,end:u}))}catch(e){!a&&e.name!==`AbortError`&&i(e)}!a&&r&&!c&&(o=setTimeout(l,2e3))}return l(),()=>{a=!0,clearTimeout(o),t.abort()}},[e,n.start,n.end,c,r]);function g(e){e.preventDefault(),t(m)}let v=m.endpoint_kind===`mcp`,y=ut(o,`requests`),b=e.endpoint_kind===`mcp`||y.some(e=>e.endpoint_kind===`mcp`),ee=[[`endpoint`,v?`MCP domain`:`Endpoint`],[`ip`,`Client IP`],...v?$e:[[`version`,`Version`],[`translation`,`Translation`],[`reference`,`Reference`],[`search`,`Search query`],[`book`,`Book number`]],[`status`,`HTTP status`],[`referrer`,`Exact referrer`],[`user_agent`,`Exact user agent`],[`referrer_contains`,`Referrer contains`],[`user_agent_contains`,`User agent contains`],[`path_contains`,`Request path contains`],[`q`,`Search recorded request data`]];return(0,E.jsxs)(O,{title:`Request explorer`,detail:`Inspect recorded request details; credentials are excluded`,children:[(0,E.jsxs)(`form`,{className:`filter-grid`,onSubmit:g,children:[ee.map(([e,t])=>(0,E.jsxs)(`label`,{children:[t,(0,E.jsx)(`input`,{className:`form-control form-control-sm`,value:m[e]||``,onChange:t=>h(at(m,e,t.target.value))})]},e)),(0,E.jsxs)(`label`,{children:[`Endpoint kind`,(0,E.jsxs)(`select`,{"aria-label":`Endpoint kind`,className:`form-select form-select-sm`,value:m.endpoint_kind||``,onChange:e=>h(at(m,`endpoint_kind`,e.target.value)),children:[(0,E.jsx)(`option`,{value:``,children:`All kinds`}),(0,E.jsx)(`option`,{value:`static`,children:`Static API`}),(0,E.jsx)(`option`,{value:`query`,children:`Query`}),(0,E.jsx)(`option`,{value:`search`,children:`Search`}),(0,E.jsx)(`option`,{value:`mcp`,children:`MCP`})]})]}),v&&(0,E.jsxs)(`label`,{children:[`MCP outcome`,(0,E.jsxs)(`select`,{"aria-label":`MCP outcome`,className:`form-select form-select-sm`,value:m.mcp_outcome||``,onChange:e=>h(at(m,`mcp_outcome`,e.target.value)),children:[(0,E.jsx)(`option`,{value:``,children:`All outcomes`}),et.map(e=>(0,E.jsx)(`option`,{value:e,children:e.replaceAll(`_`,` `)},e))]})]}),(0,E.jsxs)(`label`,{children:[`Responses`,(0,E.jsxs)(`select`,{"aria-label":`Responses`,className:`form-select form-select-sm`,value:m.successful||``,onChange:e=>h(at(m,`successful`,e.target.value)),children:[(0,E.jsx)(`option`,{value:``,children:`All responses`}),(0,E.jsx)(`option`,{value:`true`,children:`Successful responses`})]})]}),(0,E.jsxs)(`label`,{children:[`Authentication`,(0,E.jsxs)(`select`,{"aria-label":`Authentication`,className:`form-select form-select-sm`,value:m.auth||``,onChange:e=>h({...m,auth:e.target.value}),children:[(0,E.jsx)(`option`,{value:``,children:`All traffic`}),(0,E.jsx)(`option`,{value:`anonymous`,children:`Anonymous`}),(0,E.jsx)(`option`,{value:`valid`,children:`Authenticated`}),(0,E.jsx)(`option`,{value:`rejected`,children:`Rejected credential`})]})]}),(0,E.jsxs)(`div`,{className:`d-flex gap-2 align-items-end`,children:[(0,E.jsx)(`button`,{className:`btn btn-primary btn-sm`,children:`Apply filters`}),(0,E.jsx)(`button`,{type:`button`,className:`btn btn-outline-secondary btn-sm`,onClick:()=>{h({}),t({})},children:`Clear`})]})]}),o?(0,E.jsx)(k,{rows:y,onSelect:e=>a(`Request details`,e),columns:[{key:`timestamp`,label:`Time`,render:e=>T(e.stamp)},{key:`endpoint`,label:`Endpoint`},{key:`method`,label:`Method`},{key:`path`,label:`Request`,render:e=>(0,E.jsx)(`span`,{className:`truncate`,title:e.path,children:e.path})},{key:`status`,label:`Status`,render:e=>(0,E.jsx)(le,{tone:it(e)?`warning`:`success`,children:e.status})},{key:`duration_ms`,label:`Latency`,render:e=>`${C(e.duration_ms,1)} ms`},...b?[{key:`mcp_method`,label:`MCP operation`,render:e=>e.mcp_method?(0,E.jsxs)(`span`,{className:`consumer-value`,children:[e.mcp_method,e.mcp_tool&&(0,E.jsx)(`small`,{className:`d-block`,children:e.mcp_tool}),e.upstream_operation&&(0,E.jsx)(`small`,{className:`d-block`,children:[e.upstream_service,e.upstream_api_version,e.upstream_operation].filter(Boolean).join(` · `)})]}):`—`},{key:`mcp_outcome`,label:`MCP outcome`,render:e=>e.endpoint_kind===`mcp`?(0,E.jsx)(le,{tone:it(e)?`warning`:e.mcp_outcome===`success`?`success`:`muted`,children:(e.mcp_outcome||`unknown`).replaceAll(`_`,` `)}):`—`},{key:`mcp_client_name`,label:`Declared MCP client`,render:e=>e.mcp_client_name?(0,E.jsxs)(`span`,{className:`consumer-value`,children:[e.mcp_client_name,e.mcp_client_version&&(0,E.jsx)(`small`,{className:`d-block`,children:e.mcp_client_version})]}):`—`}]:[],{key:`remote_addr`,label:`Client IP`},{key:`referrer`,label:`Referrer`,render:n=>n.referrer?(0,E.jsx)(`button`,{className:`table-link truncate`,title:n.referrer,onClick:()=>t({...e,referrer:n.referrer}),children:n.referrer}):`—`},{key:`user_agent`,label:`User agent`,render:n=>n.user_agent?(0,E.jsx)(`button`,{className:`table-link truncate`,title:n.user_agent,onClick:()=>t({...e,user_agent:n.user_agent}),children:n.user_agent}):`—`},{key:`auth`,label:`Auth`}]}):(0,E.jsx)(ce,{}),(0,E.jsxs)(`div`,{className:`table-footer`,children:[(0,E.jsx)(`small`,{children:`Latest captures first · up to 100 requests per page`}),(0,E.jsxs)(`div`,{className:`btn-group`,children:[(0,E.jsx)(`button`,{className:`btn btn-outline-secondary btn-sm`,disabled:!u.length,onClick:()=>{l(u.at(-1)),d(u.slice(0,-1))},children:`Previous`}),(0,E.jsx)(`button`,{className:`btn btn-outline-secondary btn-sm`,disabled:!o?.next_cursor,onClick:()=>{d([...u,c]),l(o.next_cursor)},children:`Next`})]})]})]})}function gt({range:e,live:t,onError:n,onDetail:r}){let[i,a]=(0,_.useState)(null),[o,s]=(0,_.useState)(``),[c,l]=(0,_.useState)([]),[u,d]=(0,_.useState)(e);return(0,_.useEffect)(()=>{let r=!1,i,s=new AbortController;async function c(){let l=t&&!o?Math.floor(Date.now()/1e3):o?u.end:e.end;try{let n=await x(`events?${S({start:t?l-900:e.start,end:l,cursor:o,limit:100})}`,{signal:s.signal});r||(a(n),o||d({start:t?l-900:e.start,end:l}))}catch(e){!r&&e.name!==`AbortError`&&n(e)}!r&&t&&!o&&(i=setTimeout(c,2e3))}return c(),()=>{r=!0,clearTimeout(i),s.abort()}},[e.start,e.end,t,o]),(0,E.jsxs)(O,{title:`Operational events`,detail:`Service messages, alerts, synchronization and management audit records`,children:[i?(0,E.jsx)(k,{rows:ut(i,`items`),onSelect:e=>r(`Event details`,e),columns:[{key:`stamp`,label:`Time`,render:e=>T(e.stamp)},{key:`endpoint`,label:`Endpoint`},{key:`event`,label:`Event`,render:e=>e.event||e.payload?.event||e.source},{key:`level`,label:`Level`,render:e=>e.level||e.payload?.level||`info`},{key:`message`,label:`Message`,render:e=>(0,E.jsx)(`span`,{className:`truncate`,children:e.message||e.payload?.message||e.payload?.MESSAGE||``})}]}):(0,E.jsx)(ce,{}),(0,E.jsxs)(`div`,{className:`table-footer`,children:[(0,E.jsx)(`small`,{children:`Latest captures first`}),(0,E.jsxs)(`div`,{className:`btn-group`,children:[(0,E.jsx)(`button`,{className:`btn btn-sm btn-outline-secondary`,disabled:!c.length,onClick:()=>{s(c.at(-1)),l(c.slice(0,-1))},children:`Previous`}),(0,E.jsx)(`button`,{className:`btn btn-sm btn-outline-secondary`,disabled:!i?.next_cursor,onClick:()=>{l([...c,o]),s(i.next_cursor)},children:`Next`})]})]})]})}function _t({report:e,refresh:t,onError:n,onDetail:r,execute:i}){let a=e.data||{},[o,s]=(0,_.useState)(null);(0,_.useEffect)(()=>{let e=new AbortController,r=!1;return x(`storage?refresh=${t>0}`,{signal:e.signal}).then(e=>{r||s(e)}).catch(e=>{!r&&e.name!==`AbortError`&&n(e)}),()=>{r=!0,e.abort()}},[t]);let c=a.metrics?.at(-1)||{};return(0,E.jsxs)(E.Fragment,{children:[(0,E.jsx)(Pe,{refresh:t,execute:i}),(0,E.jsx)(Je,{report:e,label:`Resource metrics`}),(0,E.jsxs)(`div`,{className:`resource-grid`,children:[e.data&&(0,E.jsxs)(E.Fragment,{children:[(0,E.jsxs)(O,{title:`CPU`,detail:c.scope===`host`?`Host CPU utilization`:`Usage within the container’s effective CPU allowance`,children:[(0,E.jsx)(re,{label:`CPU usage over time`,option:ae((a.metrics||[]).map(e=>({...e,cpu_percent:e.cpu?.used_fraction==null?null:e.cpu.used_fraction*100})),[[`cpu_percent`,`CPU usage %`]])}),(0,E.jsxs)(`div`,{className:`fact-list`,children:[(0,E.jsxs)(`div`,{children:[(0,E.jsx)(`span`,{children:`Current utilization`}),(0,E.jsxs)(`strong`,{children:[C(c.cpu?.used_fraction==null?null:c.cpu.used_fraction*100,1),`%`]})]}),(0,E.jsxs)(`div`,{children:[(0,E.jsx)(`span`,{children:`Throttled time`}),(0,E.jsxs)(`strong`,{children:[C(c.cpu?.counters?.throttled_usec),` μs`]})]}),(0,E.jsxs)(`div`,{children:[(0,E.jsx)(`span`,{children:`Temperature`}),(0,E.jsx)(`strong`,{children:c.temperatures?.length?`${C(Math.max(...c.temperatures.map(e=>e.celsius)),1)} °C`:`Sensor unavailable`})]})]})]}),(0,E.jsxs)(O,{title:`Memory`,detail:c.scope===`host`?`Host memory usage, including other services`:`Measured container usage includes more than translation caches`,children:[(0,E.jsx)(re,{label:`Memory usage over time`,option:ae((a.metrics||[]).map(e=>({...e,memory_gib:e.memory?.current_bytes==null?null:e.memory.current_bytes/1073741824})),[[`memory_gib`,`Memory GiB`]])}),(0,E.jsxs)(`div`,{className:`fact-list`,children:[(0,E.jsxs)(`div`,{children:[(0,E.jsx)(`span`,{children:`Current / limit`}),(0,E.jsxs)(`strong`,{children:[w(c.memory?.current_bytes),` / `,w(c.memory?.limit_bytes)]})]}),(0,E.jsxs)(`div`,{children:[(0,E.jsx)(`span`,{children:`OOM events`}),(0,E.jsx)(`strong`,{children:C(c.memory?.events?.oom_kill)})]})]})]})]}),(0,E.jsx)(O,{className:`wide`,title:`Persistent storage`,detail:`Mounted data on the host; application budgets do not impose a filesystem quota`,actions:(0,E.jsx)(`button`,{className:`btn btn-sm btn-outline-secondary`,onClick:()=>r(`Storage accounting`,o),children:`Inspect accounting`}),children:o?(0,E.jsx)(k,{rows:ut(o,`components`),onSelect:e=>r(`Storage details`,e),columns:[{key:`name`,label:`Data`,render:e=>e.name||e.category||e.path},{key:`bytes`,label:`Allocated`,render:e=>w(e.bytes??e.allocated_bytes)},{key:`limit_bytes`,label:`Budget`,render:e=>e.limit_bytes?w(e.limit_bytes):`—`},{key:`path`,label:`Location`}]}):(0,E.jsx)(ce,{})}),(0,E.jsx)(O,{className:`wide`,title:`Pressure and collection details`,children:(0,E.jsxs)(`div`,{className:`fact-list`,children:[(0,E.jsx)(`button`,{className:`btn btn-outline-secondary`,onClick:()=>r(`Latest resource sample`,c),children:`Inspect latest resource sample`}),(0,E.jsx)(`button`,{className:`btn btn-outline-secondary`,onClick:()=>r(`Retention`,a.retention),children:`Inspect available history and pruning`})]})})]})]})}function vt({initial:e,onError:t,onComplete:n}){let[r,i]=(0,_.useState)(e),[a,o]=(0,_.useState)(null),[s,c]=(0,_.useState)(!1),[l,u]=(0,_.useState)(!1),d=(0,_.useRef)(!1),f=e.job_id||e.id;(0,_.useEffect)(()=>{let e=!1,r,a=1e3;async function o(){try{let t=await x(`jobs/${encodeURIComponent(f)}`);e||(i(t),u(!1),a=1e3,!Qe(t.status)&&!d.current&&(d.current=!0,n?.()),Qe(t.status)&&(r=setTimeout(o,1e3)))}catch(n){e||(!n.status||n.status>=500?(u(!0),r=setTimeout(o,a),a=Math.min(a*2,1e4)):t(n))}}return o(),()=>{e=!0,clearTimeout(r)}},[f]);async function p(){try{let e=await te(`jobs/${encodeURIComponent(f)}/reveal`);o(e.one_time_output||`The one-time output is no longer available.`)}catch(e){t(e)}}return(0,E.jsxs)(E.Fragment,{children:[l&&(0,E.jsx)(`div`,{className:`alert alert-info`,role:`status`,children:`Reconnecting to operation progress… The accepted job continues on the server.`}),(0,E.jsxs)(`div`,{className:`fact-list`,children:[(0,E.jsxs)(`div`,{children:[(0,E.jsx)(`span`,{children:`Operation`}),(0,E.jsx)(`strong`,{children:r.operation||`Starting…`})]}),(0,E.jsxs)(`div`,{children:[(0,E.jsx)(`span`,{children:`Status`}),(0,E.jsx)(le,{tone:r.status===`failed`?`warning`:`info`,children:r.status})]}),(0,E.jsxs)(`div`,{children:[(0,E.jsx)(`span`,{children:`Started`}),(0,E.jsx)(`strong`,{children:r.started?T(r.started):`Waiting to start`})]})]}),(0,E.jsx)(`pre`,{className:`record-detail`,children:r.output||`Waiting for operation output…`}),r.secret_available&&!a&&(0,E.jsx)(`button`,{className:`btn btn-warning`,onClick:p,children:`Reveal issued credential once`}),a&&(0,E.jsxs)(`div`,{className:`alert alert-warning`,children:[(0,E.jsx)(`p`,{children:`Copy this credential now. It is shown only once and is not saved in the job history.`}),(0,E.jsx)(`pre`,{className:`record-detail`,children:a}),(0,E.jsx)(`button`,{className:`btn btn-outline-secondary`,onClick:async()=>{try{await navigator.clipboard.writeText(a),c(!0)}catch(e){t(e)}},children:s?`Copied`:`Copy credential`})]})]})}function yt({onError:e,refresh:t,onRefresh:n}){let[r,i]=(0,_.useState)(null);(0,_.useEffect)(()=>{x(`sessions`).then(i).catch(e)},[t]);async function a(t){try{await te(`sessions/${encodeURIComponent(t)}/revoke`),n()}catch(t){e(t)}}return(0,E.jsx)(O,{title:`Browser sessions`,detail:`Sleep preserves sessions. Revoking one requires that browser to authenticate again.`,children:r?(0,E.jsx)(k,{rows:ut(r,`sessions`),columns:[{key:`ip`,label:`Client IP`},{key:`user_agent`,label:`Browser`,render:e=>(0,E.jsx)(`span`,{className:`truncate`,title:e.user_agent,children:e.user_agent})},{key:`created_at`,label:`Signed in`,render:e=>T(e.created_at)},{key:`expires_at`,label:`Expires`,render:e=>T(e.expires_at)},{key:`action`,label:`Access`,render:e=>(0,E.jsx)(`button`,{className:`btn btn-sm btn-outline-danger`,onClick:()=>a(e.id),children:`Revoke session`})}]}):(0,E.jsx)(ce,{})})}function bt(){let[e,t]=(0,_.useState)(null),[n,r]=(0,_.useState)(null),[i,a]=(0,_.useState)(`Overview`),[o,s]=(0,_.useState)(``),[c,l]=(0,_.useState)(0),[u,d]=(0,_.useState)(()=>localStorage.getItem(`getbible-theme`)||`system`),[f,p]=(0,_.useState)(`24 hours`),[m,h]=(0,_.useState)({start:``,end:``}),[g,v]=(0,_.useState)({}),[y,b]=(0,_.useState)(null),[S,C]=(0,_.useState)(null),[w,ne]=(0,_.useState)(!1),re=(0,_.useRef)(!1),[ie,ae]=(0,_.useState)(null),[oe,D]=(0,_.useState)(0),[ue,k]=(0,_.useState)(null),[de,fe]=(0,_.useState)(`initializing`),[pe,me]=(0,_.useState)(!1),[he,ge]=(0,_.useState)(()=>Math.floor(Date.now()/1e3)),_e=(0,_.useRef)(crypto.randomUUID()),A=(0,_.useCallback)(e=>{e.status===401?(ee(``),t({authenticated:!1})):r(e)},[]),ve=(0,_.useMemo)(()=>f===`Custom`&&m.start&&m.end?{start:Math.floor(new Date(m.start).getTime()/1e3),end:Math.floor(new Date(m.end).getTime()/1e3)}:{start:he-(lt[f]||86400),end:he},[f,he,m]);(0,_.useEffect)(()=>{x(`auth/status`).then(t).catch(A)},[]),(0,_.useEffect)(()=>{e?.authenticated||(b(null),C(null),fe(`initializing`),k(null))},[e?.authenticated]),(0,_.useEffect)(()=>{let e=matchMedia(`(prefers-color-scheme: dark)`),t=()=>{document.documentElement.dataset.bsTheme=u===`system`?e.matches?`dark`:`light`:u};return t(),localStorage.setItem(`getbible-theme`,u),e.addEventListener(`change`,t),()=>e.removeEventListener(`change`,t)},[u]),(0,_.useEffect)(()=>{if(!e?.authenticated)return;let t=!1,n;async function r(){try{let e=await te(`dashboard/heartbeat`,{viewer_id:_e.current});t||fe(e.state===`awake`?`ready`:e.state)}catch(e){t||A(e)}t||(n=setTimeout(r,15e3))}r();let i=()=>{document.visibilityState===`visible`&&(clearTimeout(n),r())};return document.addEventListener(`visibilitychange`,i),()=>{t=!0,clearTimeout(n),document.removeEventListener(`visibilitychange`,i)}},[e?.authenticated]),(0,_.useEffect)(()=>{if(!e?.authenticated||de===`ready`)return;let t=!1,n;async function i(){try{let e=await x(`dashboard/state`);if(t)return;if(e.state===`awake`){fe(`ready`),r(e=>e?.historyUnavailable?null:e);return}e.state===`unavailable`&&fe(`unavailable`),e.error&&r(Object.assign(Error(e.error),{historyUnavailable:e.state===`unavailable`}))}catch(e){t||A(e)}t||(n=setTimeout(i,1e3))}return i(),()=>{t=!0,clearTimeout(n)}},[e?.authenticated,de]);let ye=(0,_.useCallback)(()=>k(new Date),[]),j=qe({page:[`Overview`,`Resources`].includes(i)?i:``,range:ve,live:f===`Live`,filters:i===`Overview`?g:void 0,refresh:oe,enabled:e?.authenticated&&de===`ready`&&(f!==`Custom`||!!(m.start&&m.end)),onError:A,onUpdate:ye}),M=j.overview?.data;function be({start:e,end:t}){if(!(t>e))return;let n=e=>{let t=new Date(e*1e3);return new Date(t.getTime()-t.getTimezoneOffset()*6e4).toISOString().slice(0,19)};h({start:n(e),end:n(t)}),p(`Custom`)}function xe(e,t){b({title:e,value:t})}function Se(e,t,n){v({...g,...n||{[e]:t}}),a(`Traffic`)}function Ce(){ge(Math.floor(Date.now()/1e3)),D(e=>e+1)}async function N(){if(!re.current){re.current=!0,ne(!0),ae(null);try{let e=await te(`actions`,{operation:S.operation,arguments:S.arguments,confirm:!0});C(null),xe(`Operation details`,{...e,job_id:e.job_id||e.id}),Ce()}catch(e){ae(e),A(e)}finally{re.current=!1,ne(!1)}}}return e?e.authenticated?(0,E.jsxs)(`div`,{className:`console`,children:[(0,E.jsxs)(`aside`,{className:`sidebar ${pe?`expanded`:``}`,children:[(0,E.jsxs)(`a`,{className:`brand`,href:`#overview`,onClick:e=>{e.preventDefault(),a(`Overview`)},children:[(0,E.jsx)(`img`,{src:`/favicon.png`,alt:``}),(0,E.jsxs)(`span`,{children:[`getBible`,(0,E.jsx)(`small`,{children:`OPERATIONS`})]})]}),(0,E.jsx)(`div`,{className:`workspace-label`,children:`SERVER CONSOLE`}),(0,E.jsx)(`nav`,{"aria-label":`Main navigation`,children:ct.map((e,t)=>(0,E.jsxs)(`button`,{className:i===e?`active`:``,"aria-current":i===e?`page`:void 0,"aria-label":e,onClick:()=>{a(e),e===`MCP traffic`&&v(rt(g)),e===`Manage`&&(s(``),l(e=>e+1)),me(!1)},children:[(0,E.jsx)(`span`,{className:`nav-number`,children:String(t+1).padStart(2,`0`)}),e]},e))}),(0,E.jsxs)(`div`,{className:`sidebar-bottom`,children:[(0,E.jsx)(`span`,{className:`eyebrow`,children:`CONSOLE STATE`}),(0,E.jsx)(`strong`,{children:de===`ready`?`Connected`:de===`unavailable`?`History unavailable`:`Initializing`}),(0,E.jsx)(`small`,{children:`Traffic is recorded continuously.`}),(0,E.jsx)(`button`,{className:`btn btn-sm btn-outline-secondary`,onClick:async()=>{try{await te(`auth/logout`),t({authenticated:!1}),ee(``)}catch(e){A(e)}},children:`Sign out`})]})]}),(0,E.jsxs)(`div`,{className:`main-area`,children:[(0,E.jsxs)(`header`,{className:`topbar`,children:[(0,E.jsx)(`button`,{className:`btn btn-sm btn-outline-secondary mobile-menu`,onClick:()=>me(!pe),"aria-label":`Toggle navigation`,children:`Menu`}),(0,E.jsxs)(`span`,{className:`breadcrumb-text`,children:[`getBible `,(0,E.jsx)(`span`,{children:`/`}),` `,i]}),(0,E.jsxs)(`div`,{className:`topbar-controls`,children:[(0,E.jsx)(`small`,{className:`last-updated`,children:ue?`Updated ${ue.toLocaleTimeString()}`:`Initializing…`}),(0,E.jsx)(`button`,{className:`btn btn-sm btn-outline-secondary`,onClick:Ce,title:`Refresh all dashboard data and locally cached metadata`,children:`Refresh all`}),(0,E.jsx)(`label`,{className:`visually-hidden`,htmlFor:`theme`,children:`Color theme`}),(0,E.jsxs)(`select`,{id:`theme`,className:`form-select form-select-sm theme-select`,value:u,onChange:e=>d(e.target.value),children:[(0,E.jsx)(`option`,{value:`system`,children:`System theme`}),(0,E.jsx)(`option`,{value:`dark`,children:`Dark`}),(0,E.jsx)(`option`,{value:`light`,children:`Light`})]})]})]}),(0,E.jsxs)(`main`,{className:`content`,children:[(0,E.jsxs)(`div`,{className:`page-heading`,children:[(0,E.jsxs)(`div`,{children:[(0,E.jsx)(`span`,{className:`eyebrow`,children:i===`Overview`?`YOUR API AT A GLANCE`:`GETBIBLE OPERATIONS`}),(0,E.jsx)(`h1`,{children:i===`Overview`?`Traffic & performance`:i})]}),(0,E.jsxs)(`div`,{className:`range-picker`,children:[(0,E.jsx)(`label`,{className:`visually-hidden`,htmlFor:`period`,children:`Time range`}),(0,E.jsx)(`select`,{className:`form-select`,id:`period`,value:f,onChange:e=>{p(e.target.value),ge(Math.floor(Date.now()/1e3))},children:[...Object.keys(lt),`Custom`].map(e=>(0,E.jsx)(`option`,{children:e},e))}),f===`Live`&&(0,E.jsx)(le,{tone:`success`,children:`LIVE`})]})]}),f===`Custom`&&(0,E.jsxs)(`div`,{className:`custom-range`,children:[(0,E.jsxs)(`label`,{children:[`From`,(0,E.jsx)(`input`,{className:`form-control`,type:`datetime-local`,step:`1`,value:m.start,onChange:e=>h({...m,start:e.target.value})})]}),(0,E.jsxs)(`label`,{children:[`Until`,(0,E.jsx)(`input`,{className:`form-control`,type:`datetime-local`,step:`1`,value:m.end,onChange:e=>h({...m,end:e.target.value})})]}),(0,E.jsx)(`small`,{children:`Times use your browser's local time zone.`})]}),(0,E.jsx)(se,{error:n,dismiss:()=>r(null)}),M?.retention?.first_request>ve.start&&(0,E.jsxs)(`div`,{className:`alert alert-info py-2`,children:[`Available request history starts `,T(M.retention.first_request),`. Earlier time in this selection has no retained detail.`]}),Object.entries(g).filter(([,e])=>e).length>0&&(0,E.jsxs)(`div`,{className:`filter-chips`,children:[Object.entries(g).filter(([,e])=>e).map(([e,t])=>(0,E.jsxs)(`button`,{className:`btn btn-sm btn-outline-info`,onClick:()=>{let t={...g};delete t[e],v(t)},children:[e,`: `,String(t),` `,(0,E.jsx)(`span`,{"aria-label":`Remove filter`,children:`×`})]},e)),(0,E.jsx)(`button`,{className:`btn btn-sm btn-link`,onClick:()=>v({}),children:`Clear all`})]}),de!==`ready`&&![`Manage`,`Translations`,`Resources`,`Sessions`].includes(i)?de===`unavailable`?(0,E.jsx)(O,{title:`Traffic history needs attention`,detail:`Server management remains available while traffic history is unavailable.`,children:(0,E.jsxs)(`div`,{className:`fact-list`,children:[(0,E.jsx)(`p`,{children:`Review the reported history error. To start fresh after an incompatible history format, open Logs, then History, and review the reset operation.`}),(0,E.jsx)(`button`,{className:`btn btn-primary`,onClick:()=>{s(`logs`),a(`Manage`)},children:`Manage traffic history`})]})}):(0,E.jsx)(ce,{children:`Initializing dashboard…`}):i===`Overview`?(0,E.jsx)(mt,{overview:j.overview,timeline:j.history,onFilter:Se,onDetail:xe,onZoom:be}):i===`Traffic`?(0,E.jsx)(ht,{live:f===`Live`,filters:g,setFilters:v,range:ve,onError:A,onDetail:xe},JSON.stringify([ve,g,f,oe])):i===`MCP traffic`?(0,E.jsx)(st,{range:ve,live:f===`Live`,filters:g,refresh:oe,onFilter:Se,onError:A,onZoom:be}):i===`Audience`?(0,E.jsx)(Xe,{range:ve,live:f===`Live`,filters:g,refresh:oe,onFilter:Se,onError:A}):i===`Translations`?(0,E.jsx)(Ue,{onError:A,execute:(e,t,n)=>(ae(null),C({operation:e,arguments:t,review:n})),onDetail:xe,refresh:oe}):i===`Resources`?(0,E.jsx)(_t,{report:j.metrics,refresh:oe,onError:A,onDetail:xe,execute:(e,t,n)=>(ae(null),C({operation:e,arguments:t,review:n}))}):i===`Events`?(0,E.jsx)(gt,{range:ve,live:f===`Live`,onError:A,onDetail:xe},JSON.stringify([ve,f,oe])):i===`Manage`?(0,E.jsx)(je,{initialSection:o,execute:(e,t,n)=>(ae(null),C({operation:e,arguments:t,review:n})),onError:A,onDetail:xe,refresh:oe},`${o||`manage`}:${c}`):(0,E.jsx)(yt,{onError:A,refresh:oe,onRefresh:Ce}),(0,E.jsx)(`footer`,{children:`Origin observations · CDN cache hits served before this server are outside this view.`})]})]}),y&&(0,E.jsx)(dt,{title:y.title,value:y.value,onClose:()=>b(null),children:y.title===`Operation details`?(0,E.jsx)(vt,{initial:y.value,onError:A,onComplete:Ce}):void 0}),S&&(0,E.jsxs)(dt,{title:`Review operation`,onClose:()=>C(null),children:[(0,E.jsx)(se,{error:ie}),(0,E.jsx)(`p`,{children:S.review?.title||`The following operation will run on this server.`}),S.review?.description&&(0,E.jsx)(`p`,{className:`text-secondary`,children:S.review.description}),(0,E.jsx)(`pre`,{className:`record-detail`,children:JSON.stringify({operation:S.operation,arguments:Object.fromEntries(Object.entries(S.arguments).map(([e,t])=>[e,/password|token|secret|credential/i.test(e)?`[hidden]`:e===`content`?`[content supplied]`:t]))},null,2)}),(0,E.jsxs)(`div`,{className:`d-flex gap-2`,children:[(0,E.jsx)(`button`,{className:`btn btn-primary`,disabled:w,onClick:N,children:w?`Submitting…`:`Run operation`}),(0,E.jsx)(`button`,{className:`btn btn-outline-secondary`,"data-bs-dismiss":`modal`,children:`Cancel`})]})]})]}):(0,E.jsx)(ft,{onLogin:t}):(0,E.jsxs)(`main`,{className:`login-page`,children:[(0,E.jsx)(ce,{children:`Connecting to your server…`}),(0,E.jsx)(se,{error:n})]})}(0,v.createRoot)(document.getElementById(`root`)).render((0,E.jsx)(bt,{})); \ No newline at end of file diff --git a/src/apps/dashboard/static/index.html b/src/apps/dashboard/static/index.html index a1aa937..f3713c5 100644 --- a/src/apps/dashboard/static/index.html +++ b/src/apps/dashboard/static/index.html @@ -10,7 +10,7 @@ - + diff --git a/src/apps/dashboard/ui/package-lock.json b/src/apps/dashboard/ui/package-lock.json index 5b66e36..f7158ad 100644 --- a/src/apps/dashboard/ui/package-lock.json +++ b/src/apps/dashboard/ui/package-lock.json @@ -12,10 +12,10 @@ "react-dom": "19.3.0" }, "devDependencies": { - "@vitejs/plugin-react": "6.1.1", + "@vitejs/plugin-react": "6.1.2", "bootstrap": "5.3.8", "echarts": "6.1.0", - "vite": "8.3.2" + "vite": "8.3.3" }, "engines": { "node": ">=22.12" @@ -324,9 +324,9 @@ "license": "MIT" }, "node_modules/@vitejs/plugin-react": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-6.1.1.tgz", - "integrity": "sha512-yxLaQV9gkhS8ezJqCM6+ndU7mDY6gqAg75NQ+0IjwEI8IYOmQCgkRwHKVSfWXW076DsqMo0Dk+0FK1U+M5RgFw==", + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-6.1.2.tgz", + "integrity": "sha512-fhJNGUdscQa4HkI0xFxwonWyhnVReCPDbXI7FH7jmzpnVCrFQI7PRrhWecO263610vOrs9tr2Y4xcf8uAdFxbw==", "dev": true, "license": "MIT", "dependencies": { @@ -338,7 +338,7 @@ "peerDependencies": { "@rolldown/plugin-babel": "^0.1.7 || ^0.2.0", "babel-plugin-react-compiler": "^1.0.0", - "oxc-transform-react": "^0.145.0", + "oxc-transform-react": "^0.152.0", "vite": "^8.0.0" }, "peerDependenciesMeta": { @@ -864,9 +864,9 @@ } }, "node_modules/vite": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/vite/-/vite-8.3.2.tgz", - "integrity": "sha512-SQr1x6W5vVSbROg7vsyXIaxK9b0G7zsT68acdWWRmnBUsgDieLCRG+Rep9WdZgcposvv/GSnr4GUUBqB3vXq6w==", + "version": "8.3.3", + "resolved": "https://registry.npmjs.org/vite/-/vite-8.3.3.tgz", + "integrity": "sha512-cTAldKPImjg6c+gk48U19POPn3GCBzZwpdsN8ZMEEcbpes+6/wvfqUd0C2y3qYY4wsj8PwgFwrZ/1jBPVttMSg==", "dev": true, "license": "MIT", "dependencies": { diff --git a/src/apps/dashboard/ui/package.json b/src/apps/dashboard/ui/package.json index 8c40622..f6e482c 100644 --- a/src/apps/dashboard/ui/package.json +++ b/src/apps/dashboard/ui/package.json @@ -17,10 +17,10 @@ "react-dom": "19.3.0" }, "devDependencies": { - "@vitejs/plugin-react": "6.1.1", + "@vitejs/plugin-react": "6.1.2", "bootstrap": "5.3.8", "echarts": "6.1.0", - "vite": "8.3.2" + "vite": "8.3.3" }, "engines": { "node": ">=22.12" diff --git a/src/apps/dashboard/ui/src/capacity.jsx b/src/apps/dashboard/ui/src/capacity.jsx index b58cfef..5fc8049 100644 --- a/src/apps/dashboard/ui/src/capacity.jsx +++ b/src/apps/dashboard/ui/src/capacity.jsx @@ -28,6 +28,8 @@ export default function Capacity({refresh, execute}) {
Collection{stale ? 'Unknown — observations are not current' : words(collection.state)}
Unread backlog{bytes(collection.unread_bytes)} · {number(collection.unread_files)} files
Transport / retained archives{bytes(collection.budgeted_spool_bytes)} / {bytes(collection.retained_archive_bytes)}
+
Committed closed transport{bytes(collection.consumed_rotated_bytes)} · already ingested
+
Reclamation
{stale ? 'Unknown — observations are not current' : words(collection.reclamation?.state)}{collection.reclamation?.reason}
Producer / collector throughput{rate(collection.producer_bytes_per_second)} / {rate(collection.collector_bytes_per_second)}
Backlog growth / continuous backlog{rate(collection.backlog_growth_bytes_per_second)} / {number(collection.backlog_observed_seconds)} seconds
Rate window / peak window{number(collection.rate_window_seconds)} seconds / {number(report.window_seconds / 3600)} hours
diff --git a/src/apps/telemetry/getbible_telemetry/capacity.py b/src/apps/telemetry/getbible_telemetry/capacity.py index eb6d856..060774a 100644 --- a/src/apps/telemetry/getbible_telemetry/capacity.py +++ b/src/apps/telemetry/getbible_telemetry/capacity.py @@ -17,6 +17,38 @@ HEADROOM = .25 +def reclamation_status(spool: dict[str, Any], *, now: float | None = None) -> dict[str, Any]: + """Explain retained committed transport independently of unread collection.""" + now = time.time() if now is None else now + committed = finite(spool.get("consumed_rotated_bytes")) + cleanup = spool.get("problems", {}).get("spool_cleanup", {}) + reasons = cleanup.get("reason_counts") or {reason: None for reason in cleanup.get("reasons", [])} + explanations = { + "writer_open": "A producer still has a committed spool open; inspect nginx log reopening and idle runtime producers.", + "writer_open_or_lease_unavailable": "A writer is open or the filesystem refused the safety lease.", + "lease_unsupported": "The filesystem does not support the safety lease required for reclamation.", + "permission_denied": "Lease or removal permission was denied; inspect service identity and mount ownership.", + "filesystem_error": "The filesystem refused reclamation; inspect the recorded errno and collector journal.", + "source_changed": "A source changed after collection; it is preserved for another ingestion pass.", + "source_changed_or_opening": "A source changed or a writer reopened it during reclamation.", + } + if committed is None: + state, reason = "unknown", "Committed transport usage has not been measured." + elif not committed: + state, reason = "caught_up", "No committed closed transport spools remain." + elif finite(cleanup.get("time")) is not None and now - cleanup["time"] > 60: + state, reason = "stale", "The last reclamation attempt is over 60 seconds old; inspect the collector journal." + elif cleanup.get("retained") or cleanup.get("error"): + state = "blocked" + reason = " ".join(explanations.get(key, key.replace("_", " ")) for key in reasons) + reason = reason or "The reclamation helper failed; inspect spool_cleanup and the collector journal." + else: + state, reason = "pending", "Committed closed transport spools await a safe cleanup pass." + return {"state": state, "reason": reason, "attempted_at": cleanup.get("time"), + "removed_bytes": cleanup.get("removed_bytes"), "retained_bytes": cleanup.get("retained_bytes"), + "reason_counts": reasons} + + def finite(value: Any) -> float | None: if isinstance(value, bool) or not isinstance(value, (int, float)): return None @@ -121,6 +153,13 @@ def observe(self, sample: dict[str, Any], spool: dict[str, Any], *, now: float | record["peak"] = max(record["peak"], value) record["samples"] += 1 record["seconds"] += duration + if key == "telemetry_spool" and finite(spool.get("consumed_rotated_bytes")) is not None: + # Committed transport occupying disk is reclamation pressure, + # not evidence that producers need a larger transport allowance. + record["sizing_peak"] = max(record.get("sizing_peak", 0), + max(0, value - spool["consumed_rotated_bytes"])) + record["sizing_samples"] = record.get("sizing_samples", 0) + 1 + record["sizing_seconds"] = record.get("sizing_seconds", 0) + duration if above: record["saturated_seconds"] += duration record["saturated_samples"] += 1 @@ -135,7 +174,12 @@ def observe(self, sample: dict[str, Any], spool: dict[str, Any], *, now: float | "saturated_seconds": sum(item["saturated_seconds"] for item in aggregate), "saturated_samples": sum(item["saturated_samples"] for item in aggregate), "episodes": sum(item["episodes"] for item in aggregate), "saturation_threshold": .9} - row["recommendation"] = self._recommend(row, sample, spool) + if key == "telemetry_spool": + sizing = [item for item in aggregate if "sizing_peak" in item] + row.update(sizing_high_water=max((item["sizing_peak"] for item in sizing), default=0) / scale, + sizing_samples=sum(item["sizing_samples"] for item in sizing), + sizing_observed_seconds=sum(item["sizing_seconds"] for item in sizing)) + row["recommendation"] = self._recommend(row, sample, spool, now=now) current.append(row) rates = {"producer_bytes_per_second": None, "collector_bytes_per_second": None, "backlog_growth_bytes_per_second": None, "collector_records_per_second": None} @@ -175,6 +219,8 @@ def observe(self, sample: dict[str, Any], spool: dict[str, Any], *, now: float | "limits": current, "collection": {"state": state, "backlog_observed_seconds": backlog_age, "unread_bytes": spool.get("unread_bytes"), "unread_files": spool.get("unread_files"), "budgeted_spool_bytes": usage, "retained_archive_bytes": spool.get("retained_archive_bytes"), + "consumed_rotated_bytes": spool.get("consumed_rotated_bytes"), + "reclamation": reclamation_status(spool, now=now), "compressed_pending_bytes": spool.get("compressed_pending_bytes"), "rate_window_seconds": 300, "rate_samples": len(observations), **rates}, "problems": spool.get("problems", {}), "note": "Suggested values use observed peak / 0.75. Capped demand is a lower bound, not an ideal-value guarantee. No setting is changed automatically."} @@ -187,20 +233,30 @@ def observe(self, sample: dict[str, Any], spool: dict[str, Any], *, now: float | return result @staticmethod - def _recommend(row: dict[str, Any], sample: dict[str, Any], spool: dict[str, Any]) -> dict[str, Any]: - if row["observed_seconds"] < 60 or row["samples"] < 6: + def _recommend(row: dict[str, Any], sample: dict[str, Any], spool: dict[str, Any], + *, now: float | None = None) -> dict[str, Any]: + if row["id"] == "telemetry_spool": + if spool.get("consumed_rotated_bytes", 0) > spool.get("spool_max_bytes", GIB) * .25: + cleanup = reclamation_status(spool, now=now) + return {"status": "investigate_cleanup", "value": None, + "reason": "Committed transport occupies the allowance but is already ingested. " + + cleanup["reason"] + " Inspect spool_cleanup before increasing the budget."} + seconds = row.get("sizing_observed_seconds", 0) + samples = row.get("sizing_samples", 0) + else: + seconds, samples = row["observed_seconds"], row["samples"] + if seconds < 60 or samples < 6: return {"status": "insufficient_data", "value": None, - "reason": "Collect at least 60 observed seconds and six samples before sizing."} + "reason": "Collect at least 60 observed seconds and six samples before sizing." + + (" Transport sizing requires fresh measurements separating committed spools from demand." + if row["id"] == "telemetry_spool" else "")} if row["setting"] is None: return {"status": "operator_review", "value": None, "reason": "This is a host/filesystem measurement, not an adjustable application allowance. Review physical headroom and workload."} - desired = max(row["effective_limit"], row["high_water"] / (1 - HEADROOM)) + peak = row.get("sizing_high_water", row["high_water"]) + desired = max(row["effective_limit"], peak / (1 - HEADROOM)) quantum = .1 if row["unit"] == "cores" else .25 if row["id"] == "memory" else 1 desired = math.ceil(desired / quantum) * quantum - if row["id"] == "telemetry_spool": - if spool.get("consumed_rotated_bytes", 0) > spool.get("spool_max_bytes", GIB) * .25 and spool.get("problems", {}).get("spool_cleanup", {}).get("retained", 0): - return {"status": "investigate_cleanup", "value": None, - "reason": "Committed transport files are awaiting safe closure or filesystem lease support; inspect spool_cleanup before increasing the budget."} if row["id"] in {"telemetry_spool", "telemetry_history"}: disks = sample.get("disks", []) if not disks: @@ -215,7 +271,9 @@ def _recommend(row: dict[str, Any], sample: dict[str, Any], spool: dict[str, Any return {"status": "headroom_unknown" if available is None else "insufficient_headroom", "value": None, "reason": "An increased container memory limit needs confirmed host headroom; reserve at least half of currently available host memory."} return {"status": "adequate" if desired <= row["effective_limit"] else "suggested", - "value": round(desired, 2), "reason": "Observed 24-hour peak with 25% headroom; never automatically lowers the current allowance.", + "value": round(desired, 2), "reason": "Observed 24-hour peak with 25% headroom; never automatically lowers the current allowance." + + (" Already committed closed transport is excluded from sizing demand." + if row["id"] == "telemetry_spool" else ""), "assumptions": "Observed demand may be censored by current limits. Confirm workload and physical/provider capacity before applying."} @@ -235,6 +293,10 @@ def capacity_report(store, *, now: float | None = None) -> dict[str, Any]: result["stale"] = result["age_seconds"] > 60 result["state"] = "stale" if result["stale"] else "observed" if result["stale"]: + collection = result.setdefault("collection", {}) + collection["state"] = "stale" + collection["reclamation"] = {**collection.get("reclamation", {}), "state": "stale", + "reason": "Collection is not providing current reclamation observations."} for item in result.get("limits", []): item["recommendation"] = {"status": "stale", "value": None, "reason": "Collection is not providing current observations."} return result diff --git a/src/apps/telemetry/getbible_telemetry/collector.py b/src/apps/telemetry/getbible_telemetry/collector.py index 6e14b99..2156332 100644 --- a/src/apps/telemetry/getbible_telemetry/collector.py +++ b/src/apps/telemetry/getbible_telemetry/collector.py @@ -65,6 +65,7 @@ def __init__(self, store: TelemetryStore, log_root: str, *, batch_size: int = 10 self._alert_processes: list[subprocess.Popen] = [] self._last_cleanup: dict[str, tuple[int, int, float]] = {} self._cleanup_cursor = 0 + self._cleanup_pending = False self._ingest_cursors = [0, 0] self._batch_pending = False self._pass_pending = False @@ -126,10 +127,17 @@ def ingest_file(self, path: Path, endpoint: str, source: str, *, rotated: bool = self._batch_pending = False signature = [stat.st_size, stat.st_mtime_ns, endpoint, source] complete = self.store.db.execute("SELECT value FROM metadata WHERE key=?", ("source_eof:" + identity,)).fetchone() - if complete and json.loads(complete[0]) == signature: + completed_signature = json.loads(complete[0]) if complete else None + if completed_signature == signature: return 0 opener = gzip.open if path.suffix == ".gz" else open with opener(path, "rb") as handle: + opened = os.fstat(handle.fileno()) + if (opened.st_dev, opened.st_ino) != (stat.st_dev, stat.st_ino): + # Rotation/replacement raced the open. Do not commit data + # under the identity of the pathname's previous inode. + self._batch_pending = True + return 0 row = self.store.db.execute("SELECT * FROM sources WHERE identity=?", (identity,)).fetchone() offset = row["offset"] if row else 0 generation = row["generation"] if row else 0 @@ -141,7 +149,13 @@ def ingest_file(self, path: Path, endpoint: str, source: str, *, rotated: bool = else: unchanged = True truncated = (path.suffix != ".gz" and stat.st_size < offset) or not unchanged - if truncated: + rewritten = (row is not None and completed_signature is not None + and completed_signature[0] == stat.st_size + and completed_signature[1] != stat.st_mtime_ns) + if truncated or rewritten: + # A matching short prefix cannot rule out a tail overwrite. + # Replay changed same-size completed inputs in bounded + # batches; stable content record keys deduplicate old lines. offset, fingerprint_bytes, fingerprint = 0, 0, "" generation += 1 handle.seek(offset) @@ -199,7 +213,8 @@ def ingest_file(self, path: Path, endpoint: str, source: str, *, rotated: bool = "updated=excluded.updated,closed=excluded.closed", (identity, str(path), offset, fingerprint, fingerprint_bytes, generation, time.time(), int(rotated))) after = os.fstat(handle.fileno()) - if eof and (after.st_size, after.st_mtime_ns) == (stat.st_size, stat.st_mtime_ns): + if eof and (after.st_dev, after.st_ino, after.st_size, after.st_mtime_ns) == ( + stat.st_dev, stat.st_ino, stat.st_size, stat.st_mtime_ns): self.store.set_metadata("source_eof:" + identity, signature) else: self.store.db.execute("DELETE FROM metadata WHERE key=?", ("source_eof:" + identity,)) @@ -298,55 +313,95 @@ def ingest_pass(self, *, time_budget: float = .25) -> int: return work def cleanup(self) -> int: + """Reclaim a bounded batch with committed EOF and kernel closure proof.""" candidates = [] - spools = [entry[0] for entry in self.files() if entry[3] and entry[0].name.endswith(".spool")] + spools = [entry for entry in self.files() if entry[3] and entry[0].name.endswith(".spool")] + self._cleanup_pending = False if spools: start = self._cleanup_cursor % len(spools) spools = spools[start:] + spools[:start] - checked = 0 + scanned = 0 live = set() - for path in spools: + awaiting_stability = 0 + now = time.monotonic() + for path, endpoint, source, _rotated in spools: + scanned += 1 try: stat = path.stat() identity = f"{stat.st_dev}:{stat.st_ino}" live.add(identity) row = self.store.db.execute("SELECT offset FROM sources WHERE identity=?", (identity,)).fetchone() - if row is None or row[0] != stat.st_size: + eof = self.store.db.execute("SELECT value FROM metadata WHERE key=?", + ("source_eof:" + identity,)).fetchone() + signature = [stat.st_size, stat.st_mtime_ns, endpoint, source] + # An offset alone is not proof: a writer can replace contents + # in place without changing file length. Only the observation + # committed with the complete input may authorize reclamation. + if row is None or row[0] != stat.st_size or eof is None or json.loads(eof[0]) != signature: self._last_cleanup.pop(identity, None) continue previous = self._last_cleanup.get(identity) if previous is None or previous[:2] != (stat.st_size, stat.st_mtime_ns): - self._last_cleanup[identity] = stat.st_size, stat.st_mtime_ns, time.monotonic() + self._last_cleanup[identity] = stat.st_size, stat.st_mtime_ns, now + awaiting_stability += 1 continue - if time.monotonic() - previous[2] < 5: + if now - previous[2] < 5: + awaiting_stability += 1 continue candidates.append({"path": str(path), "identity": identity, "size": stat.st_size, "mtime_ns": stat.st_mtime_ns}) - checked += 1 - if checked == 32: + if len(candidates) == 32: break except FileNotFoundError: continue - self._cleanup_cursor += max(1, checked) + # Move past everything inspected, including unread and settling files, + # so a blocked prefix cannot starve ready archives later in the list. + self._cleanup_cursor += max(1, scanned) + if scanned == len(spools): + self._last_cleanup = {identity: value for identity, value in self._last_cleanup.items() + if identity in live} + summary = {"time": time.time(), "attempted": len(candidates), "removed": 0, + "retained": len(candidates), "awaiting_stability": awaiting_stability, + "attempted_bytes": sum(item["size"] for item in candidates), "removed_bytes": 0, + "retained_bytes": sum(item["size"] for item in candidates), + "reasons": [], "reason_counts": {}, "failures": []} # No /proc visibility assumptions or added Docker capabilities: a read # lease proves that the committed archive has no writable descriptor. if not candidates: + with self.store.db: + self.store.set_metadata("spool_cleanup", summary) return 0 try: process = subprocess.run([sys.executable, str(Path(__file__).with_name("spools.py"))], input=json.dumps(candidates), capture_output=True, text=True, timeout=5) if process.returncode: - raise RuntimeError("spool reclamation helper failed") + raise RuntimeError("spool reclamation helper failed (exit " + str(process.returncode) + + "): " + process.stderr.strip()[:512]) results = json.loads(process.stdout) + if not isinstance(results, list) or len(results) != len(candidates) or any( + not isinstance(result, dict) or result.get("path") != item["path"] + or result.get("identity") != item["identity"] + for result, item in zip(results, candidates)): + raise ValueError("spool reclamation helper returned inconsistent results") except (OSError, ValueError, RuntimeError, subprocess.TimeoutExpired) as exc: with self.store.db: - self.store.set_metadata("spool_cleanup", {"time": time.time(), "removed": 0, - "retained": len(candidates), "error": str(exc)}) + self.store.set_metadata("spool_cleanup", {**summary, "error": str(exc)[:1024], + "reasons": ["helper_failed"], "reason_counts": {"helper_failed": len(candidates)}}) return 0 removed = 0 + reopen = False with self.store.db: - for result in results: + for result, item in zip(results, candidates): if not result.get("removed"): + reason = result.get("reason", "unknown") + summary["reason_counts"][reason] = summary["reason_counts"].get(reason, 0) + 1 + if len(summary["failures"]) < 8: + summary["failures"].append({key: result[key] for key in + ("path", "reason", "errno", "operation", "error") if key in result}) + # A failed earlier reopen otherwise waits indefinitely for + # another rotation. Signal only nginx's held archives. + reopen |= reason == "writer_open" and Path(item["path"]).name.startswith( + ("access.log-", "error.log-")) continue identity = result["identity"] self.store.db.execute("DELETE FROM sources WHERE identity=?", (identity,)) @@ -354,9 +409,20 @@ def cleanup(self) -> int: ("source_eof:" + identity, "blocked_source:" + identity)) self._last_cleanup.pop(identity, None) removed += 1 - self.store.set_metadata("spool_cleanup", {"time": time.time(), "removed": removed, - "retained": len(candidates) - removed, - "reasons": sorted({item["reason"] for item in results if item.get("reason")})}) + summary["removed_bytes"] += item["size"] + summary.update(removed=removed, retained=len(candidates) - removed, + retained_bytes=summary["attempted_bytes"] - summary["removed_bytes"], + reasons=sorted(summary["reason_counts"])) + if reopen: + summary["nginx_reopen_requested"] = self._nginx_reopen() + if summary["nginx_reopen_requested"]: + self.store.db.execute("DELETE FROM metadata WHERE key='nginx_reopen_failed'") + else: + self.store.set_metadata("nginx_reopen_failed", {"time": time.time(), "spools_retained": True}) + self.store.set_metadata("spool_cleanup", summary) + # Successful reclamation can drain accumulated committed archives at + # one bounded batch per second, without speeding up blocked retries. + self._cleanup_pending = len(candidates) == 32 and removed > 0 return removed def state(self) -> dict[str, Any]: @@ -377,7 +443,7 @@ def state(self) -> dict[str, Any]: active_files += 1 elif path.name.endswith(".spool"): rotated_bytes += stat.st_size - if offset == stat.st_size: + if offset == stat.st_size and complete: consumed += stat.st_size else: archives += stat.st_size @@ -389,6 +455,10 @@ def state(self) -> dict[str, Any]: unread_files += 1 else: remaining = max(0, stat.st_size - offset) + if rotated and remaining == 0 and identity in completed and not complete: + # Same-size overwrites invalidate the durable EOF + # observation even though the old offset still fits. + remaining = stat.st_size unread += remaining unread_files += bool(remaining) except FileNotFoundError: @@ -509,6 +579,13 @@ def health(self, sample: dict[str, Any], state: dict[str, Any], *, now: float | f"unread {state.get('unread_bytes', 0) / 1024**2:.1f} MiB, " f"committed closed spools {state.get('consumed_rotated_bytes', 0) / 1024**2:.1f} MiB. " "Unread/active files are preserved. Use getbible capacity for collection health and sizing advice.") + cleanup = state.get("problems", {}).get("spool_cleanup", {}) + if state.get("consumed_rotated_bytes", 0) and cleanup.get("retained", 0): + reasons = ", ".join(f"{reason}: {count}" for reason, count in + sorted(cleanup.get("reason_counts", {}).items())) + details += (f" Latest reclamation batch retained {cleanup['retained']} committed files" + + (f" ({reasons})" if reasons else "") + + "; getbible capacity --json includes paths and kernel errors.") conditions = { "memory": (memory, self.alert_settings["ALERT_MEMORY_PERCENT"] / 100, "Memory exceeds its configured threshold."), "cpu": (cpu, self.alert_settings["ALERT_CPU_PERCENT"] / 100, "CPU demand exceeds its configured threshold."), @@ -551,7 +628,7 @@ def run(self, *, flush_seconds: int = 1, metrics_seconds: int = 5, self._history_max_bytes = max_bytes sampler = MetricsSampler(cgroup_root=cgroup_root, disks=[str(self.root), str(self.store.path.parent)]) inspector = HealthInspector(systemctl) - next_metric = next_prune = next_cleanup = next_settings = 0.0 + next_metric = next_prune = next_cleanup = next_rotate = next_settings = 0.0 next_ingest = next_rollup = 0.0 try: while self.running: @@ -601,10 +678,12 @@ def run(self, *, flush_seconds: int = 1, metrics_seconds: int = 5, with self.store.db: self.store.set_metadata("last_prune", result) next_prune = now + 60 - if now >= next_cleanup: + if now >= next_rotate: self.rotate() + next_rotate = now + 10 + if now >= next_cleanup: self.cleanup() - next_cleanup = now + 10 + next_cleanup = now + (1 if self._cleanup_pending else 10) # Historical reporting work is resumable and follows each # bounded ingestion pass. It never delays source commits or # makes ordinary request handlers maintain the database. @@ -625,7 +704,7 @@ def run(self, *, flush_seconds: int = 1, metrics_seconds: int = 5, break # Wake for metrics/settings/shutdown at least once per second # without accidentally ingesting every time this loop wakes. - deadline = min(next_ingest, next_metric, next_prune, next_cleanup, + deadline = min(next_ingest, next_metric, next_prune, next_cleanup, next_rotate, next_settings if settings else float("inf")) time.sleep(min(1.0, max(.01, deadline - time.monotonic()))) finally: diff --git a/src/apps/telemetry/getbible_telemetry/diagnostics.py b/src/apps/telemetry/getbible_telemetry/diagnostics.py index a75533e..6afd802 100644 --- a/src/apps/telemetry/getbible_telemetry/diagnostics.py +++ b/src/apps/telemetry/getbible_telemetry/diagnostics.py @@ -15,7 +15,7 @@ from types import SimpleNamespace from typing import Any -from .capacity import capacity_report +from .capacity import capacity_report, GIB def read_config(path: str | os.PathLike[str]) -> dict[str, str]: @@ -71,10 +71,17 @@ def format_capacity(report: dict[str, Any]) -> str: lines = ["getBible capacity", "State: " + report["state"], report.get("note", ""), ""] collection = report.get("collection", {}) lines.append("Collection: " + collection.get("state", "unknown")) - for key in ("unread_bytes", "unread_files", "budgeted_spool_bytes", "retained_archive_bytes", "consumed_rotated_bytes", + for key in ("unread_bytes", "unread_files", "budgeted_spool_bytes", "retained_archive_bytes", "producer_bytes_per_second", "collector_bytes_per_second", "backlog_growth_bytes_per_second", "backlog_observed_seconds"): if collection.get(key) is not None: lines.append(f" {key}: {collection[key]}") + if collection.get("consumed_rotated_bytes") is not None: + lines.append(f" Committed closed transport: {collection['consumed_rotated_bytes'] / GIB:.3f} GiB (already ingested)") + cleanup = collection.get("reclamation", {}) + if cleanup: + lines.extend(["Reclamation: " + cleanup.get("state", "unknown"), " " + cleanup.get("reason", "")]) + if report.get("problems", {}).get("spool_cleanup"): + lines.append(" Last cleanup: " + json.dumps(report["problems"]["spool_cleanup"], ensure_ascii=False, sort_keys=True)) lines.append("") for row in report.get("limits", []): lines.append(row["label"]) diff --git a/src/apps/telemetry/getbible_telemetry/producer.py b/src/apps/telemetry/getbible_telemetry/producer.py index 9b63e6a..69e1e3c 100644 --- a/src/apps/telemetry/getbible_telemetry/producer.py +++ b/src/apps/telemetry/getbible_telemetry/producer.py @@ -4,13 +4,11 @@ import json from datetime import datetime, timezone -from logging.handlers import WatchedFileHandler from threading import Lock from typing import Any from .store import redact -_handlers: dict[str, WatchedFileHandler] = {} _lock = Lock() @@ -21,24 +19,13 @@ def emit_event(event: str, payload: dict[str, Any], *, The installer creates the parent directory with its service identity. Persistent session audit references use ``session_ref``; credential-bearing session IDs, passwords, OTPs and cookie fields are removed defensively. + These infrequent administrative events close their append stream before + returning, so an idle dashboard cannot pin a committed rotated spool. """ entry = {"time": datetime.now(timezone.utc).isoformat(timespec="milliseconds"), "event": event, "level": payload.get("level", "INFO"), **redact(payload)} message = json.dumps(entry, ensure_ascii=False, separators=(",", ":")) - with _lock: - handler = _handlers.get(path) - if handler is None: - handler = WatchedFileHandler(path, encoding="utf-8") - _handlers[path] = handler - # Logging.Handler.emit normally swallows disk errors. Administrative - # callers need a real error so they can expose missing audit durability. - handler.reopenIfNeeded() - handler.stream.write(message + "\n") - handler.flush() - - -def close_producers() -> None: - with _lock: - for handler in _handlers.values(): - handler.close() - _handlers.clear() + # Preserve serialized records and propagate write/flush/close failures to + # callers rather than letting logging.Handler hide missing audit durability. + with _lock, open(path, "a", encoding="utf-8") as stream: + stream.write(message + "\n") diff --git a/src/apps/telemetry/getbible_telemetry/spools.py b/src/apps/telemetry/getbible_telemetry/spools.py index fefa9e6..93e43ca 100644 --- a/src/apps/telemetry/getbible_telemetry/spools.py +++ b/src/apps/telemetry/getbible_telemetry/spools.py @@ -9,6 +9,7 @@ from __future__ import annotations import fcntl +import errno import json import os from pathlib import Path @@ -26,6 +27,7 @@ def reclaim(item: dict[str, Any]) -> dict[str, Any]: fd = None original_uid = os.geteuid() broken = False + operation = "open" def on_break(_signum: int, _frame: object) -> None: nonlocal broken @@ -43,12 +45,18 @@ def on_break(_signum: int, _frame: object) -> None: # root privilege to adopt only this file's owner; no container capability # such as SYS_PTRACE or LEASE needs to be granted. Restore it immediately. try: + operation = "set_owner" if original_uid != before.st_uid: os.seteuid(before.st_uid) + operation = "lease" fcntl.fcntl(fd, fcntl.F_SETLEASE, fcntl.F_RDLCK) finally: + previous_operation = operation if os.geteuid() != original_uid: + operation = "restore_owner" os.seteuid(original_uid) + operation = previous_operation + operation = "verify" current = path.lstat() if (broken or fcntl.fcntl(fd, fcntl.F_GETLEASE) != fcntl.F_RDLCK or (current.st_dev, current.st_ino, current.st_size, current.st_mtime_ns) @@ -56,12 +64,24 @@ def on_break(_signum: int, _frame: object) -> None: return {**result, "reason": "source_changed_or_opening"} # Producers open their active .log path, never a closed archive name. # Keep the lease until after unlink so no existing writer is overlooked. + operation = "unlink" path.unlink() return {**result, "removed": True} except FileNotFoundError: return {**result, "reason": "source_missing"} except (OSError, AttributeError) as exc: - return {**result, "reason": "writer_open_or_lease_unavailable", "errno": getattr(exc, "errno", None)} + code = getattr(exc, "errno", None) + if operation == "lease" and code in {errno.EAGAIN, errno.EWOULDBLOCK}: + reason = "writer_open" + elif operation == "lease" and (isinstance(exc, AttributeError) + or code in {errno.EINVAL, errno.ENOSYS, errno.EOPNOTSUPP}): + reason = "lease_unsupported" + elif code in {errno.EPERM, errno.EACCES}: + reason = "permission_denied" + else: + reason = "filesystem_error" + return {**result, "reason": reason, "errno": code, "operation": operation, + "error": str(exc)[:512]} finally: if os.geteuid() != original_uid: os.seteuid(original_uid) diff --git a/tests/python/test_capacity.py b/tests/python/test_capacity.py index 4687de1..d85cfd0 100644 --- a/tests/python/test_capacity.py +++ b/tests/python/test_capacity.py @@ -15,6 +15,7 @@ sys.path.insert(0, str(ROOT / 'src/apps/telemetry')) from getbible_telemetry import TelemetryStore from getbible_telemetry.capacity import CapacityTracker, capacity_report, incident_update, GIB +from getbible_telemetry.diagnostics import format_capacity from getbible_telemetry.collector import Collector from getbible_telemetry.spools import reclaim @@ -92,7 +93,17 @@ def test_kernel_lease_preserves_open_writer_then_reclaims_committed_spool(self): @unittest.skipUnless(os.geteuid() == 0, 'ownership transition requires root') def test_reclamation_of_other_uid_does_not_require_proc_or_new_capabilities(self): path = self.archive() - os.chown(path, 65534, 65534) + alternate_uid = None + for line in Path('/proc/self/uid_map').read_text().splitlines(): + first, _host, count = map(int, line.split()) + if first <= 65534 < first + count: + alternate_uid = 65534 + break + if first + count > max(1, first): + alternate_uid = max(1, first) + if alternate_uid is None: + self.skipTest('The execution environment maps only root; cross-owner reclamation requires another mapped UID') + os.chown(path, alternate_uid, -1) info = path.stat() payload = [{'path': str(path), 'identity': f'{info.st_dev}:{info.st_ino}', 'size': info.st_size, 'mtime_ns': info.st_mtime_ns}] @@ -173,6 +184,7 @@ def sample(self): def observe(self, tracker, t, **extra): spool = {'budgeted_spool_bytes': 2 * GIB, 'spool_max_bytes': GIB, 'unread_bytes': 0, + 'consumed_rotated_bytes': 0, 'unread_files': 0, 'collector_instance': 'a', 'committed_bytes': t * 100, 'committed_records': t, 'problems': {}, **extra} with self.store.db: @@ -196,6 +208,15 @@ def test_recommendations_have_evidence_units_and_survive_restart(self): stale = capacity_report(self.store, now=1000) self.assertTrue(stale['stale']) self.assertIsNone(stale['limits'][0]['recommendation']['value']) + self.assertEqual(stale['collection']['state'], 'stale') + self.assertEqual(stale['collection']['reclamation']['state'], 'stale') + self.assertIn('not providing current', stale['collection']['reclamation']['reason']) + self.assertEqual(stale['collection']['budgeted_spool_bytes'], 2 * GIB) + self.assertEqual(stale['limits'][0]['high_water'], 2) + with self.store.db: + current = capacity_report(self.store, now=181) + self.assertEqual(current['collection']['state'], result['collection']['state']) + self.assertEqual(current['collection']['reclamation']['state'], 'caught_up') def test_backlog_stall_is_not_an_instruction_to_raise_the_limit(self): tracker = CapacityTracker(self.store) @@ -205,6 +226,64 @@ def test_backlog_stall_is_not_an_instruction_to_raise_the_limit(self): self.assertEqual(result['limits'][0]['recommendation']['status'], 'investigate_collection') self.assertIsNone(result['limits'][0]['recommendation']['value']) + def test_committed_transport_pressure_reports_cleanup_separately_from_collection(self): + tracker = CapacityTracker(self.store) + cleanup = {'time': 100, 'retained': 4, 'retained_bytes': int(4.25 * GIB), + 'reason_counts': {'writer_open': 4}, 'failures': [{'reason': 'writer_open', 'errno': 11}]} + self.observe(tracker, 100, budgeted_spool_bytes=4.25 * GIB, + consumed_rotated_bytes=4.25 * GIB, problems={'spool_cleanup': cleanup}) + result = self.observe(tracker, 110, budgeted_spool_bytes=4.25 * GIB, + consumed_rotated_bytes=4.25 * GIB, problems={'spool_cleanup': cleanup}) + self.assertEqual(result['collection']['state'], 'caught_up') + self.assertEqual(result['collection']['reclamation']['state'], 'blocked') + self.assertEqual(result['collection']['consumed_rotated_bytes'], 4.25 * GIB) + advice = result['limits'][0]['recommendation'] + self.assertEqual(advice['status'], 'investigate_cleanup') + self.assertIsNone(advice['value']) + self.assertIn('nginx log reopening', advice['reason']) + rendered = format_capacity({'state': 'observed', **result}) + self.assertIn('Committed closed transport: 4.250 GiB (already ingested)', rendered) + self.assertIn('Reclamation: blocked', rendered) + self.assertIn('"errno": 11', rendered) + + def test_old_pressure_peaks_do_not_supply_transport_sizing_evidence(self): + tracker = CapacityTracker(self.store) + tracker.history['hours'] = {'0': {'telemetry_spool': {'peak': 10 * GIB, 'samples': 100, + 'seconds': 600, 'saturated_seconds': 600, 'saturated_samples': 100, 'episodes': 1}}} + first = self.observe(tracker, 1000, budgeted_spool_bytes=GIB / 4) + self.assertEqual(first['limits'][0]['high_water'], 10) + self.assertEqual(first['limits'][0]['recommendation']['status'], 'insufficient_data') + for t in range(1010, 1080, 10): + result = self.observe(tracker, t, budgeted_spool_bytes=GIB / 4) + row = result['limits'][0] + self.assertEqual(row['high_water'], 10) + self.assertEqual(row['sizing_high_water'], .25) + self.assertEqual(row['recommendation']['status'], 'adequate') + self.assertEqual(row['recommendation']['value'], 1) + + def test_committed_transport_is_excluded_from_sizing_after_reclamation(self): + tracker = CapacityTracker(self.store) + for t in range(100, 180, 10): + self.observe(tracker, t, budgeted_spool_bytes=4.25 * GIB, consumed_rotated_bytes=4 * GIB) + result = self.observe(tracker, 180, budgeted_spool_bytes=GIB / 4) + row = result['limits'][0] + self.assertEqual(row['high_water'], 4.25) + self.assertEqual(row['sizing_high_water'], .25) + self.assertEqual(row['recommendation']['status'], 'adequate') + self.assertEqual(row['recommendation']['value'], 1) + + def test_reclamation_diagnostics_distinguish_unsupported_leases_and_stale_attempts(self): + tracker = CapacityTracker(self.store) + cleanup = {'time': 100, 'retained': 1, 'reason_counts': {'lease_unsupported': 1}} + result = self.observe(tracker, 100, consumed_rotated_bytes=GIB, + problems={'spool_cleanup': cleanup}) + self.assertEqual(result['collection']['reclamation']['state'], 'blocked') + self.assertIn('filesystem does not support', result['limits'][0]['recommendation']['reason']) + result = self.observe(tracker, 200, consumed_rotated_bytes=GIB, + problems={'spool_cleanup': cleanup}) + self.assertEqual(result['collection']['reclamation']['state'], 'stale') + self.assertIn('over 60 seconds old', result['limits'][0]['recommendation']['reason']) + def test_observation_gaps_and_insufficient_headroom_are_not_filled_in(self): tracker = CapacityTracker(self.store) for t in range(100, 180, 10): diff --git a/tests/python/test_dashboard_server.py b/tests/python/test_dashboard_server.py index 9ab07b9..7fb0b48 100644 --- a/tests/python/test_dashboard_server.py +++ b/tests/python/test_dashboard_server.py @@ -24,6 +24,7 @@ from getbible_dashboard.analytics import ReportPreparing from tests.python.test_dashboard_auth import Clock, FakeTelegram from getbible_telemetry.store import TelemetrySchemaError +from getbible_telemetry.producer import emit_event class UnixConnection(HTTPConnection): @@ -452,5 +453,47 @@ def test_schema_failure_explains_preserved_history(self): self.assertIn("database migrations", message) +class AuditProducerTests(unittest.TestCase): + def test_idle_audit_releases_rotated_files_and_retains_redacted_events(self): + with tempfile.TemporaryDirectory() as directory: + path = Path(directory) / "dashboard.log" + archived = path.with_name("dashboard.log-old.spool") + streams = [] + builtin_open = open + + def record_stream(*args, **kwargs): + stream = builtin_open(*args, **kwargs) + streams.append(stream) + return stream + + with patch("getbible_telemetry.producer.open", side_effect=record_stream, create=True): + emit_event("dashboard.session_created", { + "session_ref": "public-session", "password": "private-password", + "session_id": "private-session", "cookie": "private-cookie", + }, path=str(path)) + self.assertTrue(streams[0].closed, "Idle auditing must not retain a spool writer") + path.rename(archived) + path.touch() + emit_event("dashboard.sleeping", {}, path=str(path)) + self.assertTrue(streams[1].closed) + + original = json.loads(archived.read_text()) + self.assertEqual(original["event"], "dashboard.session_created") + self.assertEqual(original["session_ref"], "public-session") + self.assertNotIn("private-", archived.read_text()) + self.assertEqual(json.loads(path.read_text())["event"], "dashboard.sleeping") + + def test_audit_write_failure_remains_visible_and_closes_the_stream(self): + class UnavailableStream(io.StringIO): + def write(self, message): + raise OSError("Audit spool unavailable") + + stream = UnavailableStream() + with patch("getbible_telemetry.producer.open", return_value=stream, create=True): + with self.assertRaises(OSError): + emit_event("dashboard.session_created", {}, path="dashboard.log") + self.assertTrue(stream.closed) + + if __name__ == "__main__": unittest.main() diff --git a/tests/python/test_telemetry.py b/tests/python/test_telemetry.py index f2b31e2..f702f35 100644 --- a/tests/python/test_telemetry.py +++ b/tests/python/test_telemetry.py @@ -6,6 +6,7 @@ import contextlib import io import os +import errno import sqlite3 import sys import tempfile @@ -24,6 +25,7 @@ from getbible_telemetry.producer import emit_event from getbible_telemetry.cli import main from getbible_telemetry.settings import numeric_setting +from getbible_telemetry.spools import reclaim from getbible_telemetry.store import SCHEMA_VERSION, TelemetrySchemaError, prepare_history @@ -438,6 +440,146 @@ def test_closed_spool_deleted_only_after_commit_and_no_open_writer(self): self.assertFalse(path.exists()) self.assertEqual(self.store.summary(0, 200)["calls"], 1) + def test_cleanup_preserves_rewritten_same_size_until_new_contents_are_committed(self): + path = self.root / "logs/bible.test/archive/access.log-1.spool" + path.parent.mkdir(parents=True) + path.write_text(json.dumps(edge("old")) + "\n") + collector = Collector(self.store, str(self.root / "logs")) + collector.ingest_file(path, "bible.test", "edge", rotated=True) + previous = path.stat() + path.write_text(json.dumps(edge("new")) + "\n") + os.utime(path, ns=(previous.st_atime_ns, previous.st_mtime_ns + 1_000_000)) + self.assertEqual(path.stat().st_size, previous.st_size) + changed = collector.state() + self.assertEqual(changed["consumed_rotated_bytes"], 0) + self.assertEqual(changed["unread_bytes"], previous.st_size) + with patch("getbible_telemetry.collector.time.monotonic", return_value=10**12): + self.assertEqual(collector.cleanup(), 0) + with patch("getbible_telemetry.collector.time.monotonic", return_value=2 * 10**12): + self.assertEqual(collector.cleanup(), 0) + self.assertTrue(path.exists()) + self.assertEqual(collector.ingest_file(path, "bible.test", "edge", rotated=True), 1) + self.assertEqual(collector.state()["consumed_rotated_bytes"], previous.st_size) + self.assertEqual(collector.state()["unread_bytes"], 0) + collector.cleanup() + with patch("getbible_telemetry.collector.time.monotonic", return_value=3 * 10**12): + self.assertEqual(collector.cleanup(), 1) + self.assertEqual(self.store.summary(0, 200)["calls"], 2) + + def test_cleanup_passes_unread_prefix_to_drain_bounded_committed_batches(self): + archive = self.root / "logs/bible.test/archive" + archive.mkdir(parents=True) + collector = Collector(self.store, str(self.root / "logs")) + unread, committed = [], [] + for index in range(144): + path = archive / f"access.log-{index:03}.spool" + path.write_text(json.dumps(edge(str(index))) + "\n") + os.utime(path, (index + 100, index + 100)) + if index < 80: + unread.append(path) + else: + committed.append(path) + collector.ingest_file(path, "bible.test", "edge", rotated=True) + with patch("getbible_telemetry.collector.time.monotonic", return_value=100): + self.assertEqual(collector.cleanup(), 0) + with patch("getbible_telemetry.collector.time.monotonic", return_value=106): + self.assertEqual(collector.cleanup(), 32) + self.assertTrue(collector._cleanup_pending) + # Cursor movement covers the 80 unread files and 32 candidates. With + # the first batch gone, the remaining committed files still follow. + self.assertEqual(collector._cleanup_cursor, 256) + with patch("getbible_telemetry.collector.time.monotonic", return_value=107): + self.assertEqual(collector.cleanup(), 32) + self.assertTrue(all(path.exists() for path in unread)) + self.assertTrue(all(not path.exists() for path in committed)) + self.assertEqual(self.store.summary(0, 200)["calls"], 64) + + def test_same_size_tail_rewrite_replays_before_authorizing_cleanup(self): + path = self.root / "logs/bible.test/archive/access.log-1.spool" + path.parent.mkdir(parents=True) + prefix = json.dumps(edge("same", padding="x" * 512)) + "\n" + path.write_text(prefix + json.dumps(edge("old1")) + "\n") + collector = Collector(self.store, str(self.root / "logs")) + self.assertEqual(collector.ingest_file(path, "bible.test", "edge", rotated=True), 2) + previous = path.stat() + path.write_text(prefix + json.dumps(edge("new1")) + "\n") + os.utime(path, ns=(previous.st_atime_ns, previous.st_mtime_ns + 1_000_000)) + self.assertEqual(path.stat().st_size, previous.st_size) + self.assertEqual(collector.cleanup(), 0) + self.assertEqual(collector.ingest_file(path, "bible.test", "edge", rotated=True), 2) + # The unchanged first record is idempotent; the new tail is retained + # alongside the previously committed history before deleting transport. + self.assertEqual(self.store.summary(0, 200)["calls"], 3) + self.assertEqual({row["request_id"] for row in self.store.requests(0, 200)["items"]}, + {"same", "old1", "new1"}) + collector.cleanup() + with patch("getbible_telemetry.collector.time.monotonic", return_value=10**12): + self.assertEqual(collector.cleanup(), 1) + self.assertFalse(path.exists()) + + def test_source_replacement_between_stat_and_open_does_not_commit_wrong_identity(self): + path = self.root / "logs/bible.test/archive/access.log-1.spool" + path.parent.mkdir(parents=True) + path.write_text(json.dumps(edge("old1")) + "\n") + replacement = path.with_name("replacement") + replacement.write_text(json.dumps(edge("new1")) + "\n") + collector = Collector(self.store, str(self.root / "logs")) + builtin_open = open + + def replace_then_open(filename, mode): + replacement.replace(path) + return builtin_open(filename, mode) + + with patch("getbible_telemetry.collector.open", side_effect=replace_then_open): + self.assertEqual(collector.ingest_file(path, "bible.test", "edge", rotated=True), 0) + self.assertTrue(collector._batch_pending) + self.assertEqual(self.store.db.execute("SELECT count(*) FROM sources").fetchone()[0], 0) + self.assertEqual(self.store.summary(0, 200)["calls"], 0) + self.assertEqual(collector.ingest_file(path, "bible.test", "edge", rotated=True), 1) + self.assertEqual(self.store.requests(0, 200)["items"][0]["request_id"], "new1") + + def test_spool_reclamation_exposes_kernel_failure_and_retries_nginx_reopen(self): + path = self.root / "logs/bible.test/archive/access.log-1.spool" + path.parent.mkdir(parents=True) + path.write_text(json.dumps(edge()) + "\n") + collector = Collector(self.store, str(self.root / "logs")) + collector.ingest_file(path, "bible.test", "edge", rotated=True) + collector.cleanup() + with path.open("ab"), patch.object(collector, "_nginx_reopen", return_value=True) as reopen, \ + patch("getbible_telemetry.collector.time.monotonic", return_value=10**12): + self.assertEqual(collector.cleanup(), 0) + reopen.assert_called_once_with() + cleanup = self.store.storage()["metadata"]["spool_cleanup"] + self.assertEqual(cleanup["reason_counts"], {"writer_open": 1}) + self.assertEqual(cleanup["retained_bytes"], path.stat().st_size) + self.assertEqual(cleanup["failures"][0]["errno"], errno.EAGAIN) + self.assertEqual(cleanup["failures"][0]["operation"], "lease") + self.assertFalse(collector._cleanup_pending) + with patch("getbible_telemetry.collector.time.monotonic", return_value=2 * 10**12): + self.assertEqual(collector.cleanup(), 1) + collector.cleanup() + cleared = self.store.storage()["metadata"]["spool_cleanup"] + self.assertEqual(cleared["retained"], 0) + self.assertEqual(cleared["failures"], []) + + def test_reclamation_separates_unsupported_leases_and_permission_errors(self): + path = self.root / "archive/access.log-1.spool" + path.parent.mkdir() + path.write_text(json.dumps(edge()) + "\n") + info = path.stat() + item = {"path": str(path), "identity": f"{info.st_dev}:{info.st_ino}", + "size": info.st_size, "mtime_ns": info.st_mtime_ns} + for code, reason in ((errno.EOPNOTSUPP, "lease_unsupported"), + (errno.EPERM, "permission_denied")): + with self.subTest(code=code), patch("getbible_telemetry.spools.fcntl.fcntl", + side_effect=OSError(code, os.strerror(code))): + result = reclaim(item) + self.assertFalse(result["removed"]) + self.assertEqual(result["reason"], reason) + self.assertEqual(result["errno"], code) + self.assertEqual(result["operation"], "lease") + self.assertTrue(path.exists()) + def test_malformed_logs_are_preserved_as_diagnostics(self): path = self.root / "access.log" path.write_text("bad request token=topsecret\n")