brakit 0.10.0 → 0.10.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/api.d.ts +6 -5
- package/dist/api.js +23 -15
- package/dist/bin/brakit.js +120 -25
- package/dist/dashboard-client.global.js +470 -424
- package/dist/dashboard.html +558 -472
- package/dist/mcp/server.js +98 -22
- package/dist/runtime/index.js +361 -269
- package/package.json +1 -1
|
@@ -1,90 +1,90 @@
|
|
|
1
|
-
(function(){'use strict';var
|
|
2
|
-
\f\r]`,
|
|
3
|
-
\f\r"'\`<>=]|("|')|))|$)`,"g"),ms=/'/g,fs=/"/g,Es=/^(?:script|style|textarea|title)$/i,Ae=o=>(s,...t)=>({_$litType$:o,strings:s,values:t}),a=Ae(1),O=Ae(2),Y=Symbol.for("lit-noChange"),p=Symbol.for("lit-nothing"),vs=new WeakMap,it=ot.createTreeWalker(ot,129);function ys(o,s){if(!Re(o)||!o.hasOwnProperty("raw"))throw Error("invalid template strings array");return ps!==void 0?ps.createHTML(s):s}var Pr=(o,s)=>{let t=o.length-1,e=[],r,i=s===2?"<svg>":s===3?"<math>":"",n=Ot;for(let l=0;l<t;l++){let c=o[l],d,h,m=-1,v=0;for(;v<c.length&&(n.lastIndex=v,h=n.exec(c),h!==null);)v=n.lastIndex,n===Ot?h[1]==="!--"?n=hs:h[1]!==void 0?n=us:h[2]!==void 0?(Es.test(h[2])&&(r=RegExp("</"+h[2],"g")),n=rt):h[3]!==void 0&&(n=rt):n===rt?h[0]===">"?(n=r??Ot,m=-1):h[1]===void 0?m=-2:(m=n.lastIndex-h[2].length,d=h[1],n=h[3]===void 0?rt:h[3]==='"'?fs:ms):n===fs||n===ms?n=rt:n===hs||n===us?n=Ot:(n=rt,r=void 0);let x=n===rt&&o[l+1].startsWith("/>")?" ":"";i+=n===Ot?c+Dr:m>=0?(e.push(d),c.slice(0,m)+gs+c.slice(m)+z+x):c+z+(m===-2?l:x);}return [ys(o,i+(o[t]||"<?>")+(s===2?"</svg>":s===3?"</math>":"")),e]},Pt=class o{constructor({strings:s,_$litType$:t},e){let r;this.parts=[];let i=0,n=0,l=s.length-1,c=this.parts,[d,h]=Pr(s,t);if(this.el=o.createElement(d,e),it.currentNode=this.el.content,t===2||t===3){let m=this.el.content.firstChild;m.replaceWith(...m.childNodes);}for(;(r=it.nextNode())!==null&&c.length<l;){if(r.nodeType===1){if(r.hasAttributes())for(let m of r.getAttributeNames())if(m.endsWith(gs)){let v=h[n++],x=r.getAttribute(m).split(z),y=/([.?@])?(.*)/.exec(v);c.push({type:1,index:i,name:y[2],strings:x,ctor:y[1]==="."?xe:y[1]==="?"?Se:y[1]==="@"?Te:ht}),r.removeAttribute(m);}else m.startsWith(z)&&(c.push({type:6,index:i}),r.removeAttribute(m));if(Es.test(r.tagName)){let m=r.textContent.split(z),v=m.length-1;if(v>0){r.textContent=ee?ee.emptyScript:"";for(let x=0;x<v;x++)r.append(m[x],Dt()),it.nextNode(),c.push({type:2,index:++i});r.append(m[v],Dt());}}}else if(r.nodeType===8)if(r.data===bs)c.push({type:2,index:i});else {let m=-1;for(;(m=r.data.indexOf(z,m+1))!==-1;)c.push({type:7,index:i}),m+=z.length-1;}i++;}}static createElement(s,t){let e=ot.createElement("template");return e.innerHTML=s,e}};function pt(o,s,t=o,e){if(s===Y)return s;let r=e!==void 0?t._$Co?.[e]:t._$Cl,i=Ht(s)?void 0:s._$litDirective$;return r?.constructor!==i&&(r?._$AO?.(false),i===void 0?r=void 0:(r=new i(o),r._$AT(o,t,e)),e!==void 0?(t._$Co??(t._$Co=[]))[e]=r:t._$Cl=r),r!==void 0&&(s=pt(o,r._$AS(o,s.values),r,e)),s}var $e=class{constructor(s,t){this._$AV=[],this._$AN=void 0,this._$AD=s,this._$AM=t;}get parentNode(){return this._$AM.parentNode}get _$AU(){return this._$AM._$AU}u(s){let{el:{content:t},parts:e}=this._$AD,r=(s?.creationScope??ot).importNode(t,true);it.currentNode=r;let i=it.nextNode(),n=0,l=0,c=e[0];for(;c!==void 0;){if(n===c.index){let d;c.type===2?d=new qt(i,i.nextSibling,this,s):c.type===1?d=new c.ctor(i,c.name,c.strings,this,s):c.type===6&&(d=new we(i,this,s)),this._$AV.push(d),c=e[++l];}n!==c?.index&&(i=it.nextNode(),n++);}return it.currentNode=ot,r}p(s){let t=0;for(let e of this._$AV)e!==void 0&&(e.strings!==void 0?(e._$AI(s,e,t),t+=e.strings.length-2):e._$AI(s[t])),t++;}},qt=class o{get _$AU(){return this._$AM?._$AU??this._$Cv}constructor(s,t,e,r){this.type=2,this._$AH=p,this._$AN=void 0,this._$AA=s,this._$AB=t,this._$AM=e,this.options=r,this._$Cv=r?.isConnected??true;}get parentNode(){let s=this._$AA.parentNode,t=this._$AM;return t!==void 0&&s?.nodeType===11&&(s=t.parentNode),s}get startNode(){return this._$AA}get endNode(){return this._$AB}_$AI(s,t=this){s=pt(this,s,t),Ht(s)?s===p||s==null||s===""?(this._$AH!==p&&this._$AR(),this._$AH=p):s!==this._$AH&&s!==Y&&this._(s):s._$litType$!==void 0?this.$(s):s.nodeType!==void 0?this.T(s):Hr(s)?this.k(s):this._(s);}O(s){return this._$AA.parentNode.insertBefore(s,this._$AB)}T(s){this._$AH!==s&&(this._$AR(),this._$AH=this.O(s));}_(s){this._$AH!==p&&Ht(this._$AH)?this._$AA.nextSibling.data=s:this.T(ot.createTextNode(s)),this._$AH=s;}$(s){let{values:t,_$litType$:e}=s,r=typeof e=="number"?this._$AC(s):(e.el===void 0&&(e.el=Pt.createElement(ys(e.h,e.h[0]),this.options)),e);if(this._$AH?._$AD===r)this._$AH.p(t);else {let i=new $e(r,this),n=i.u(this.options);i.p(t),this.T(n),this._$AH=i;}}_$AC(s){let t=vs.get(s.strings);return t===void 0&&vs.set(s.strings,t=new Pt(s)),t}k(s){Re(this._$AH)||(this._$AH=[],this._$AR());let t=this._$AH,e,r=0;for(let i of s)r===t.length?t.push(e=new o(this.O(Dt()),this.O(Dt()),this,this.options)):e=t[r],e._$AI(i),r++;r<t.length&&(this._$AR(e&&e._$AB.nextSibling,r),t.length=r);}_$AR(s=this._$AA.nextSibling,t){for(this._$AP?.(false,true,t);s!==this._$AB;){let e=ds(s).nextSibling;ds(s).remove(),s=e;}}setConnected(s){this._$AM===void 0&&(this._$Cv=s,this._$AP?.(s));}},ht=class{get tagName(){return this.element.tagName}get _$AU(){return this._$AM._$AU}constructor(s,t,e,r,i){this.type=1,this._$AH=p,this._$AN=void 0,this.element=s,this.name=t,this._$AM=r,this.options=i,e.length>2||e[0]!==""||e[1]!==""?(this._$AH=Array(e.length-1).fill(new String),this.strings=e):this._$AH=p;}_$AI(s,t=this,e,r){let i=this.strings,n=false;if(i===void 0)s=pt(this,s,t,0),n=!Ht(s)||s!==this._$AH&&s!==Y,n&&(this._$AH=s);else {let l=s,c,d;for(s=i[0],c=0;c<i.length-1;c++)d=pt(this,l[e+c],t,c),d===Y&&(d=this._$AH[c]),n||(n=!Ht(d)||d!==this._$AH[c]),d===p?s=p:s!==p&&(s+=(d??"")+i[c+1]),this._$AH[c]=d;}n&&!r&&this.j(s);}j(s){s===p?this.element.removeAttribute(this.name):this.element.setAttribute(this.name,s??"");}},xe=class extends ht{constructor(){super(...arguments),this.type=3;}j(s){this.element[this.name]=s===p?void 0:s;}},Se=class extends ht{constructor(){super(...arguments),this.type=4;}j(s){this.element.toggleAttribute(this.name,!!s&&s!==p);}},Te=class extends ht{constructor(s,t,e,r,i){super(s,t,e,r,i),this.type=5;}_$AI(s,t=this){if((s=pt(this,s,t,0)??p)===Y)return;let e=this._$AH,r=s===p&&e!==p||s.capture!==e.capture||s.once!==e.once||s.passive!==e.passive,i=s!==p&&(e===p||r);r&&this.element.removeEventListener(this.name,this,e),i&&this.element.addEventListener(this.name,this,s),this._$AH=s;}handleEvent(s){typeof this._$AH=="function"?this._$AH.call(this.options?.host??this.element,s):this._$AH.handleEvent(s);}},we=class{constructor(s,t,e){this.element=s,this.type=6,this._$AN=void 0,this._$AM=t,this.options=e;}get _$AU(){return this._$AM._$AU}_$AI(s){pt(this,s);}};var qr=kt.litHtmlPolyfillSupport;qr?.(Pt,qt),(kt.litHtmlVersions??(kt.litHtmlVersions=[])).push("3.3.2");var _s=(o,s,t)=>{let e=t?.renderBefore??s,r=e._$litPart$;if(r===void 0){let i=t?.renderBefore??null;e._$litPart$=r=new qt(s.insertBefore(Dt(),i),i,void 0,t??{});}return r._$AI(o),r};var Ut=globalThis,E=class extends j{constructor(){super(...arguments),this.renderOptions={host:this},this._$Do=void 0;}createRenderRoot(){var t;let s=super.createRenderRoot();return (t=this.renderOptions).renderBefore??(t.renderBefore=s.firstChild),s}update(s){let t=this.render();this.hasUpdated||(this.renderOptions.isConnected=this.isConnected),super.update(s),this._$Do=_s(t,this.renderRoot,this.renderOptions);}connectedCallback(){super.connectedCallback(),this._$Do?.setConnected(true);}disconnectedCallback(){super.disconnectedCallback(),this._$Do?.setConnected(false);}render(){return Y}};E._$litElement$=true,E.finalized=true,Ut.litElementHydrateSupport?.({LitElement:E});var Ur=Ut.litElementPolyfillSupport;Ur?.({LitElement:E});(Ut.litElementVersions??(Ut.litElementVersions=[])).push("4.2.2");var S=o=>(s,t)=>{t!==void 0?t.addInitializer(()=>{customElements.define(o,s);}):customElements.define(o,s);};var Fr={attribute:true,type:String,converter:Nt,reflect:false,hasChanged:te},Gr=(o=Fr,s,t)=>{let{kind:e,metadata:r}=t,i=globalThis.litPropertyMetadata.get(r);if(i===void 0&&globalThis.litPropertyMetadata.set(r,i=new Map),e==="setter"&&((o=Object.create(o)).wrapped=true),i.set(t.name,o),e==="accessor"){let{name:n}=t;return {set(l){let c=s.get.call(this);s.set.call(this,l),this.requestUpdate(n,c,o,true,l);},init(l){return l!==void 0&&this.C(n,void 0,o,l),l}}}if(e==="setter"){let{name:n}=t;return function(l){let c=this[n];s.call(this,l),this.requestUpdate(n,c,o,true,l);}}throw Error("Unsupported decorator location: "+e)};function L(o){return (s,t)=>typeof t=="object"?Gr(o,s,t):((e,r,i)=>{let n=r.hasOwnProperty(i);return r.constructor.createProperty(i,e),n?Object.getOwnPropertyDescriptor(r,i):void 0})(o,s,t)}function b(o){return L({...o,state:true,attribute:false})}var Ft=class extends E{constructor(){super(...arguments);this.method="";}createRenderRoot(){return this}render(){let t=this.method.toUpperCase();return a`<span class="method-badge method-badge-${t}">${t}</span>`}};u([L()],Ft.prototype,"method",2),Ft=u([S("bk-method-badge")],Ft);var P="/__brakit/api",F="/__brakit",w={flows:`${P}/flows`,requests:`${P}/requests`,events:`${P}/events`,clear:`${P}/clear`,fetches:`${P}/fetches`,errors:`${P}/errors`,logs:`${P}/logs`,queries:`${P}/queries`,metricsLive:`${P}/metrics/live`,insights:`${P}/insights`,tab:`${P}/tab`,activity:`${P}/activity`,graph:`${P}/graph`};var ut="polling",re="static",Br="auth-handshake",Wr="auth-check",Qr="middleware",Gt={[Br]:1,[Wr]:1,[Qr]:1};var ie="fetch";var oe="error_event",ne="query",ae="issues";var Ie={overview:"Overview",actions:"Actions",requests:"Requests",fetches:"Server Fetches",queries:"Queries",errors:"Errors",logs:"Logs",performance:"Performance",security:"Security",graph:"Graph"},Ce={overview:"Live summary of your application",actions:"User actions captured as sequences of HTTP requests",requests:"All HTTP requests proxied through brakit",fetches:"Outbound HTTP calls made by your server to external services",queries:"Database queries executed during request handling",errors:"Unhandled exceptions and errors thrown by your application",logs:"Console output from your application",performance:"Endpoint health and response time trends",security:"Security findings and recommendations",graph:"Runtime dependency graph of your application"};var Me=100,mt=300,ft=800,Ne=2e3,Oe=100,ke=50,De=500;var J="__all__",pe={SELECT:"var(--blue)",INSERT:"var(--green)",UPDATE:"var(--amber)",DELETE:"var(--red)",COUNT:"var(--text-muted)"},As={error:"var(--red)",warn:"var(--amber)",info:"var(--blue)",debug:"var(--text-muted)",log:"var(--text-dim)"},qe=["#2563eb","#7c3aed","#16a34a","#d97706","#dc2626","#0891b2","#ea580c","#c026d3","#059669","#db2777"],Bt={green:"#4ade80",amber:"#fbbf24",red:"#f87171"},gt=[{max:Me,label:"Fast",color:"var(--green)",bg:"rgba(22,163,74,0.08)",border:"rgba(22,163,74,0.2)"},{max:mt,label:"Good",color:"var(--green)",bg:"rgba(22,163,74,0.06)",border:"rgba(22,163,74,0.15)"},{max:ft,label:"OK",color:"var(--amber)",bg:"rgba(217,119,6,0.06)",border:"rgba(217,119,6,0.15)"},{max:Ne,label:"Slow",color:"var(--red)",bg:"rgba(220,38,38,0.06)",border:"rgba(220,38,38,0.15)"},{max:1/0,label:"Critical",color:"var(--red)",bg:"rgba(220,38,38,0.08)",border:"rgba(220,38,38,0.2)"}],Is="rgba(228,228,231,0.8)",Ue="rgba(113,113,122,0.7)",Cs="10px monospace",Fe="9px monospace";var Ls={top:16,right:16,bottom:28,left:52},Ms={fetch:"var(--blue)",log:"var(--text-muted)",error:"var(--red)",query:"var(--accent)"},Ns={fetch:"FETCH",log:"LOG",error:"ERROR",query:"QUERY"},Os=new Set(["cookie","set-cookie","authorization","proxy-authorization","x-api-key","x-auth-token"]),ks={400:"Bad Request",401:"Unauthorized",403:"Forbidden",404:"Not Found",405:"Method Not Allowed",408:"Timeout",409:"Conflict",422:"Unprocessable",429:"Too Many Requests",500:"Internal Server Error",502:"Bad Gateway",503:"Service Unavailable",504:"Gateway Timeout"},Ds=new Set(["host","connection","accept-encoding"]),Z={critical:{icon:"\u2717",cls:"critical",sort:0},warning:{icon:"\u26A0",cls:"warning",sort:1},info:{icon:"\u2139",cls:"info",sort:2}};function _(o){return o<1e3?o+"ms":(o/1e3).toFixed(1)+"s"}function et(o){return !o||o===0?"":o<1024?o+"b":(o/1024).toFixed(1)+"kb"}function tt(o){return o?o.replaceAll("&","&").replaceAll("<","<").replaceAll(">",">").replaceAll('"',"""):""}function bt(o){return o>=500?"status-pill-5xx":o>=400?"status-pill-4xx":o>=300?"status-pill-3xx":"status-pill-2xx"}function Hs(o){return ks[o]||(o>=500?"Server Error":o>=400?"Client Error":"OK")}function jr(o,s){if(Os.has(o.toLowerCase())){let t=String(s);return t.length<=8?"****":t.slice(0,4)+"..."+t.slice(-4)+" ("+t.length+" chars)"}return String(s)}function Et(o){return !o||Object.keys(o).length===0?'<span style="color:var(--text-muted)">No headers</span>':Object.entries(o).map(([s,t])=>'<span class="json-key">'+tt(s)+"</span>: "+tt(jr(s,t))).join(`
|
|
4
|
-
`)}function
|
|
1
|
+
(function(){'use strict';var kr=Object.defineProperty;var Or=Object.getOwnPropertyDescriptor;var u=(o,r,t,e)=>{for(var s=e>1?void 0:e?Or(r,t):r,i=o.length-1,n;i>=0;i--)(n=o[i])&&(s=(e?n(r,t,s):n(s))||s);return e&&s&&kr(r,t,s),s};var oe=globalThis,ae=oe.ShadowRoot&&(oe.ShadyCSS===void 0||oe.ShadyCSS.nativeShadow)&&"adoptedStyleSheets"in Document.prototype&&"replace"in CSSStyleSheet.prototype,ms=Symbol(),us=new WeakMap,ne=class{constructor(r,t,e){if(this._$cssResult$=true,e!==ms)throw Error("CSSResult is not constructable. Use `unsafeCSS` or `css` instead.");this.cssText=r,this.t=t;}get styleSheet(){let r=this.o,t=this.t;if(ae&&r===void 0){let e=t!==void 0&&t.length===1;e&&(r=us.get(t)),r===void 0&&((this.o=r=new CSSStyleSheet).replaceSync(this.cssText),e&&us.set(t,r));}return r}toString(){return this.cssText}},fs=o=>new ne(typeof o=="string"?o:o+"",void 0,ms);var vs=(o,r)=>{if(ae)o.adoptedStyleSheets=r.map(t=>t instanceof CSSStyleSheet?t:t.styleSheet);else for(let t of r){let e=document.createElement("style"),s=oe.litNonce;s!==void 0&&e.setAttribute("nonce",s),e.textContent=t.cssText,o.appendChild(e);}},Ae=ae?o=>o:o=>o instanceof CSSStyleSheet?(r=>{let t="";for(let e of r.cssRules)t+=e.cssText;return fs(t)})(o):o;var{is:Dr,defineProperty:Pr,getOwnPropertyDescriptor:Hr,getOwnPropertyNames:qr,getOwnPropertySymbols:Ur,getPrototypeOf:Fr}=Object,et=globalThis,gs=et.trustedTypes,Gr=gs?gs.emptyScript:"",Br=et.reactiveElementPolyfillSupport,Pt=(o,r)=>o,Ht={toAttribute(o,r){switch(r){case Boolean:o=o?Gr:null;break;case Object:case Array:o=o==null?o:JSON.stringify(o);}return o},fromAttribute(o,r){let t=o;switch(r){case Boolean:t=o!==null;break;case Number:t=o===null?null:Number(o);break;case Object:case Array:try{t=JSON.parse(o);}catch{t=null;}}return t}},le=(o,r)=>!Dr(o,r),Es={attribute:true,type:String,converter:Ht,reflect:false,useDefault:false,hasChanged:le};Symbol.metadata??(Symbol.metadata=Symbol("metadata")),et.litPropertyMetadata??(et.litPropertyMetadata=new WeakMap);var K=class extends HTMLElement{static addInitializer(r){this._$Ei(),(this.l??(this.l=[])).push(r);}static get observedAttributes(){return this.finalize(),this._$Eh&&[...this._$Eh.keys()]}static createProperty(r,t=Es){if(t.state&&(t.attribute=false),this._$Ei(),this.prototype.hasOwnProperty(r)&&((t=Object.create(t)).wrapped=true),this.elementProperties.set(r,t),!t.noAccessor){let e=Symbol(),s=this.getPropertyDescriptor(r,e,t);s!==void 0&&Pr(this.prototype,r,s);}}static getPropertyDescriptor(r,t,e){let{get:s,set:i}=Hr(this.prototype,r)??{get(){return this[t]},set(n){this[t]=n;}};return {get:s,set(n){let a=s?.call(this);i?.call(this,n),this.requestUpdate(r,a,e);},configurable:true,enumerable:true}}static getPropertyOptions(r){return this.elementProperties.get(r)??Es}static _$Ei(){if(this.hasOwnProperty(Pt("elementProperties")))return;let r=Fr(this);r.finalize(),r.l!==void 0&&(this.l=[...r.l]),this.elementProperties=new Map(r.elementProperties);}static finalize(){if(this.hasOwnProperty(Pt("finalized")))return;if(this.finalized=true,this._$Ei(),this.hasOwnProperty(Pt("properties"))){let t=this.properties,e=[...qr(t),...Ur(t)];for(let s of e)this.createProperty(s,t[s]);}let r=this[Symbol.metadata];if(r!==null){let t=litPropertyMetadata.get(r);if(t!==void 0)for(let[e,s]of t)this.elementProperties.set(e,s);}this._$Eh=new Map;for(let[t,e]of this.elementProperties){let s=this._$Eu(t,e);s!==void 0&&this._$Eh.set(s,t);}this.elementStyles=this.finalizeStyles(this.styles);}static finalizeStyles(r){let t=[];if(Array.isArray(r)){let e=new Set(r.flat(1/0).reverse());for(let s of e)t.unshift(Ae(s));}else r!==void 0&&t.push(Ae(r));return t}static _$Eu(r,t){let e=t.attribute;return e===false?void 0:typeof e=="string"?e:typeof r=="string"?r.toLowerCase():void 0}constructor(){super(),this._$Ep=void 0,this.isUpdatePending=false,this.hasUpdated=false,this._$Em=null,this._$Ev();}_$Ev(){this._$ES=new Promise(r=>this.enableUpdating=r),this._$AL=new Map,this._$E_(),this.requestUpdate(),this.constructor.l?.forEach(r=>r(this));}addController(r){(this._$EO??(this._$EO=new Set)).add(r),this.renderRoot!==void 0&&this.isConnected&&r.hostConnected?.();}removeController(r){this._$EO?.delete(r);}_$E_(){let r=new Map,t=this.constructor.elementProperties;for(let e of t.keys())this.hasOwnProperty(e)&&(r.set(e,this[e]),delete this[e]);r.size>0&&(this._$Ep=r);}createRenderRoot(){let r=this.shadowRoot??this.attachShadow(this.constructor.shadowRootOptions);return vs(r,this.constructor.elementStyles),r}connectedCallback(){this.renderRoot??(this.renderRoot=this.createRenderRoot()),this.enableUpdating(true),this._$EO?.forEach(r=>r.hostConnected?.());}enableUpdating(r){}disconnectedCallback(){this._$EO?.forEach(r=>r.hostDisconnected?.());}attributeChangedCallback(r,t,e){this._$AK(r,e);}_$ET(r,t){let e=this.constructor.elementProperties.get(r),s=this.constructor._$Eu(r,e);if(s!==void 0&&e.reflect===true){let i=(e.converter?.toAttribute!==void 0?e.converter:Ht).toAttribute(t,e.type);this._$Em=r,i==null?this.removeAttribute(s):this.setAttribute(s,i),this._$Em=null;}}_$AK(r,t){let e=this.constructor,s=e._$Eh.get(r);if(s!==void 0&&this._$Em!==s){let i=e.getPropertyOptions(s),n=typeof i.converter=="function"?{fromAttribute:i.converter}:i.converter?.fromAttribute!==void 0?i.converter:Ht;this._$Em=s;let a=n.fromAttribute(t,i.type);this[s]=a??this._$Ej?.get(s)??a,this._$Em=null;}}requestUpdate(r,t,e,s=false,i){if(r!==void 0){let n=this.constructor;if(s===false&&(i=this[r]),e??(e=n.getPropertyOptions(r)),!((e.hasChanged??le)(i,t)||e.useDefault&&e.reflect&&i===this._$Ej?.get(r)&&!this.hasAttribute(n._$Eu(r,e))))return;this.C(r,t,e);}this.isUpdatePending===false&&(this._$ES=this._$EP());}C(r,t,{useDefault:e,reflect:s,wrapped:i},n){e&&!(this._$Ej??(this._$Ej=new Map)).has(r)&&(this._$Ej.set(r,n??t??this[r]),i!==true||n!==void 0)||(this._$AL.has(r)||(this.hasUpdated||e||(t=void 0),this._$AL.set(r,t)),s===true&&this._$Em!==r&&(this._$Eq??(this._$Eq=new Set)).add(r));}async _$EP(){this.isUpdatePending=true;try{await this._$ES;}catch(t){Promise.reject(t);}let r=this.scheduleUpdate();return r!=null&&await r,!this.isUpdatePending}scheduleUpdate(){return this.performUpdate()}performUpdate(){if(!this.isUpdatePending)return;if(!this.hasUpdated){if(this.renderRoot??(this.renderRoot=this.createRenderRoot()),this._$Ep){for(let[s,i]of this._$Ep)this[s]=i;this._$Ep=void 0;}let e=this.constructor.elementProperties;if(e.size>0)for(let[s,i]of e){let{wrapped:n}=i,a=this[s];n!==true||this._$AL.has(s)||a===void 0||this.C(s,void 0,i,a);}}let r=false,t=this._$AL;try{r=this.shouldUpdate(t),r?(this.willUpdate(t),this._$EO?.forEach(e=>e.hostUpdate?.()),this.update(t)):this._$EM();}catch(e){throw r=false,this._$EM(),e}r&&this._$AE(t);}willUpdate(r){}_$AE(r){this._$EO?.forEach(t=>t.hostUpdated?.()),this.hasUpdated||(this.hasUpdated=true,this.firstUpdated(r)),this.updated(r);}_$EM(){this._$AL=new Map,this.isUpdatePending=false;}get updateComplete(){return this.getUpdateComplete()}getUpdateComplete(){return this._$ES}shouldUpdate(r){return true}update(r){this._$Eq&&(this._$Eq=this._$Eq.forEach(t=>this._$ET(t,this[t]))),this._$EM();}updated(r){}firstUpdated(r){}};K.elementStyles=[],K.shadowRootOptions={mode:"open"},K[Pt("elementProperties")]=new Map,K[Pt("finalized")]=new Map,Br?.({ReactiveElement:K}),(et.reactiveElementVersions??(et.reactiveElementVersions=[])).push("2.1.2");var Ut=globalThis,bs=o=>o,ce=Ut.trustedTypes,ys=ce?ce.createPolicy("lit-html",{createHTML:o=>o}):void 0,ws="$lit$",st=`lit$${Math.random().toFixed(9).slice(2)}$`,Rs="?"+st,Wr=`<${Rs}>`,ct=document,Ft=()=>ct.createComment(""),Gt=o=>o===null||typeof o!="object"&&typeof o!="function",Oe=Array.isArray,Qr=o=>Oe(o)||typeof o?.[Symbol.iterator]=="function",Ie=`[
|
|
2
|
+
\f\r]`,qt=/<(?:(!--|\/[^a-zA-Z])|(\/?[a-zA-Z][^>\s]*)|(\/?$))/g,_s=/-->/g,$s=/>/g,at=RegExp(`>|${Ie}(?:([^\\s"'>=/]+)(${Ie}*=${Ie}*(?:[^
|
|
3
|
+
\f\r"'\`<>=]|("|')|))|$)`,"g"),xs=/'/g,Ss=/"/g,As=/^(?:script|style|textarea|title)$/i,De=o=>(r,...t)=>({_$litType$:o,strings:r,values:t}),l=De(1),k=De(2),z=Symbol.for("lit-noChange"),p=Symbol.for("lit-nothing"),Ts=new WeakMap,lt=ct.createTreeWalker(ct,129);function Is(o,r){if(!Oe(o)||!o.hasOwnProperty("raw"))throw Error("invalid template strings array");return ys!==void 0?ys.createHTML(r):r}var jr=(o,r)=>{let t=o.length-1,e=[],s,i=r===2?"<svg>":r===3?"<math>":"",n=qt;for(let a=0;a<t;a++){let c=o[a],d,h,m=-1,f=0;for(;f<c.length&&(n.lastIndex=f,h=n.exec(c),h!==null);)f=n.lastIndex,n===qt?h[1]==="!--"?n=_s:h[1]!==void 0?n=$s:h[2]!==void 0?(As.test(h[2])&&(s=RegExp("</"+h[2],"g")),n=at):h[3]!==void 0&&(n=at):n===at?h[0]===">"?(n=s??qt,m=-1):h[1]===void 0?m=-2:(m=n.lastIndex-h[2].length,d=h[1],n=h[3]===void 0?at:h[3]==='"'?Ss:xs):n===Ss||n===xs?n=at:n===_s||n===$s?n=qt:(n=at,s=void 0);let S=n===at&&o[a+1].startsWith("/>")?" ":"";i+=n===qt?c+Wr:m>=0?(e.push(d),c.slice(0,m)+ws+c.slice(m)+st+S):c+st+(m===-2?a:S);}return [Is(o,i+(o[t]||"<?>")+(r===2?"</svg>":r===3?"</math>":"")),e]},Bt=class o{constructor({strings:r,_$litType$:t},e){let s;this.parts=[];let i=0,n=0,a=r.length-1,c=this.parts,[d,h]=jr(r,t);if(this.el=o.createElement(d,e),lt.currentNode=this.el.content,t===2||t===3){let m=this.el.content.firstChild;m.replaceWith(...m.childNodes);}for(;(s=lt.nextNode())!==null&&c.length<a;){if(s.nodeType===1){if(s.hasAttributes())for(let m of s.getAttributeNames())if(m.endsWith(ws)){let f=h[n++],S=s.getAttribute(m).split(st),R=/([.?@])?(.*)/.exec(f);c.push({type:1,index:i,name:R[2],strings:S,ctor:R[1]==="."?Le:R[1]==="?"?Me:R[1]==="@"?Ne:gt}),s.removeAttribute(m);}else m.startsWith(st)&&(c.push({type:6,index:i}),s.removeAttribute(m));if(As.test(s.tagName)){let m=s.textContent.split(st),f=m.length-1;if(f>0){s.textContent=ce?ce.emptyScript:"";for(let S=0;S<f;S++)s.append(m[S],Ft()),lt.nextNode(),c.push({type:2,index:++i});s.append(m[f],Ft());}}}else if(s.nodeType===8)if(s.data===Rs)c.push({type:2,index:i});else {let m=-1;for(;(m=s.data.indexOf(st,m+1))!==-1;)c.push({type:7,index:i}),m+=st.length-1;}i++;}}static createElement(r,t){let e=ct.createElement("template");return e.innerHTML=r,e}};function vt(o,r,t=o,e){if(r===z)return r;let s=e!==void 0?t._$Co?.[e]:t._$Cl,i=Gt(r)?void 0:r._$litDirective$;return s?.constructor!==i&&(s?._$AO?.(false),i===void 0?s=void 0:(s=new i(o),s._$AT(o,t,e)),e!==void 0?(t._$Co??(t._$Co=[]))[e]=s:t._$Cl=s),s!==void 0&&(r=vt(o,s._$AS(o,r.values),s,e)),r}var Ce=class{constructor(r,t){this._$AV=[],this._$AN=void 0,this._$AD=r,this._$AM=t;}get parentNode(){return this._$AM.parentNode}get _$AU(){return this._$AM._$AU}u(r){let{el:{content:t},parts:e}=this._$AD,s=(r?.creationScope??ct).importNode(t,true);lt.currentNode=s;let i=lt.nextNode(),n=0,a=0,c=e[0];for(;c!==void 0;){if(n===c.index){let d;c.type===2?d=new Wt(i,i.nextSibling,this,r):c.type===1?d=new c.ctor(i,c.name,c.strings,this,r):c.type===6&&(d=new ke(i,this,r)),this._$AV.push(d),c=e[++a];}n!==c?.index&&(i=lt.nextNode(),n++);}return lt.currentNode=ct,s}p(r){let t=0;for(let e of this._$AV)e!==void 0&&(e.strings!==void 0?(e._$AI(r,e,t),t+=e.strings.length-2):e._$AI(r[t])),t++;}},Wt=class o{get _$AU(){return this._$AM?._$AU??this._$Cv}constructor(r,t,e,s){this.type=2,this._$AH=p,this._$AN=void 0,this._$AA=r,this._$AB=t,this._$AM=e,this.options=s,this._$Cv=s?.isConnected??true;}get parentNode(){let r=this._$AA.parentNode,t=this._$AM;return t!==void 0&&r?.nodeType===11&&(r=t.parentNode),r}get startNode(){return this._$AA}get endNode(){return this._$AB}_$AI(r,t=this){r=vt(this,r,t),Gt(r)?r===p||r==null||r===""?(this._$AH!==p&&this._$AR(),this._$AH=p):r!==this._$AH&&r!==z&&this._(r):r._$litType$!==void 0?this.$(r):r.nodeType!==void 0?this.T(r):Qr(r)?this.k(r):this._(r);}O(r){return this._$AA.parentNode.insertBefore(r,this._$AB)}T(r){this._$AH!==r&&(this._$AR(),this._$AH=this.O(r));}_(r){this._$AH!==p&&Gt(this._$AH)?this._$AA.nextSibling.data=r:this.T(ct.createTextNode(r)),this._$AH=r;}$(r){let{values:t,_$litType$:e}=r,s=typeof e=="number"?this._$AC(r):(e.el===void 0&&(e.el=Bt.createElement(Is(e.h,e.h[0]),this.options)),e);if(this._$AH?._$AD===s)this._$AH.p(t);else {let i=new Ce(s,this),n=i.u(this.options);i.p(t),this.T(n),this._$AH=i;}}_$AC(r){let t=Ts.get(r.strings);return t===void 0&&Ts.set(r.strings,t=new Bt(r)),t}k(r){Oe(this._$AH)||(this._$AH=[],this._$AR());let t=this._$AH,e,s=0;for(let i of r)s===t.length?t.push(e=new o(this.O(Ft()),this.O(Ft()),this,this.options)):e=t[s],e._$AI(i),s++;s<t.length&&(this._$AR(e&&e._$AB.nextSibling,s),t.length=s);}_$AR(r=this._$AA.nextSibling,t){for(this._$AP?.(false,true,t);r!==this._$AB;){let e=bs(r).nextSibling;bs(r).remove(),r=e;}}setConnected(r){this._$AM===void 0&&(this._$Cv=r,this._$AP?.(r));}},gt=class{get tagName(){return this.element.tagName}get _$AU(){return this._$AM._$AU}constructor(r,t,e,s,i){this.type=1,this._$AH=p,this._$AN=void 0,this.element=r,this.name=t,this._$AM=s,this.options=i,e.length>2||e[0]!==""||e[1]!==""?(this._$AH=Array(e.length-1).fill(new String),this.strings=e):this._$AH=p;}_$AI(r,t=this,e,s){let i=this.strings,n=false;if(i===void 0)r=vt(this,r,t,0),n=!Gt(r)||r!==this._$AH&&r!==z,n&&(this._$AH=r);else {let a=r,c,d;for(r=i[0],c=0;c<i.length-1;c++)d=vt(this,a[e+c],t,c),d===z&&(d=this._$AH[c]),n||(n=!Gt(d)||d!==this._$AH[c]),d===p?r=p:r!==p&&(r+=(d??"")+i[c+1]),this._$AH[c]=d;}n&&!s&&this.j(r);}j(r){r===p?this.element.removeAttribute(this.name):this.element.setAttribute(this.name,r??"");}},Le=class extends gt{constructor(){super(...arguments),this.type=3;}j(r){this.element[this.name]=r===p?void 0:r;}},Me=class extends gt{constructor(){super(...arguments),this.type=4;}j(r){this.element.toggleAttribute(this.name,!!r&&r!==p);}},Ne=class extends gt{constructor(r,t,e,s,i){super(r,t,e,s,i),this.type=5;}_$AI(r,t=this){if((r=vt(this,r,t,0)??p)===z)return;let e=this._$AH,s=r===p&&e!==p||r.capture!==e.capture||r.once!==e.once||r.passive!==e.passive,i=r!==p&&(e===p||s);s&&this.element.removeEventListener(this.name,this,e),i&&this.element.addEventListener(this.name,this,r),this._$AH=r;}handleEvent(r){typeof this._$AH=="function"?this._$AH.call(this.options?.host??this.element,r):this._$AH.handleEvent(r);}},ke=class{constructor(r,t,e){this.element=r,this.type=6,this._$AN=void 0,this._$AM=t,this.options=e;}get _$AU(){return this._$AM._$AU}_$AI(r){vt(this,r);}};var Yr=Ut.litHtmlPolyfillSupport;Yr?.(Bt,Wt),(Ut.litHtmlVersions??(Ut.litHtmlVersions=[])).push("3.3.2");var Cs=(o,r,t)=>{let e=t?.renderBefore??r,s=e._$litPart$;if(s===void 0){let i=t?.renderBefore??null;e._$litPart$=s=new Wt(r.insertBefore(Ft(),i),i,void 0,t??{});}return s._$AI(o),s};var Qt=globalThis,b=class extends K{constructor(){super(...arguments),this.renderOptions={host:this},this._$Do=void 0;}createRenderRoot(){var t;let r=super.createRenderRoot();return (t=this.renderOptions).renderBefore??(t.renderBefore=r.firstChild),r}update(r){let t=this.render();this.hasUpdated||(this.renderOptions.isConnected=this.isConnected),super.update(r),this._$Do=Cs(t,this.renderRoot,this.renderOptions);}connectedCallback(){super.connectedCallback(),this._$Do?.setConnected(true);}disconnectedCallback(){super.disconnectedCallback(),this._$Do?.setConnected(false);}render(){return z}};b._$litElement$=true,b.finalized=true,Qt.litElementHydrateSupport?.({LitElement:b});var Vr=Qt.litElementPolyfillSupport;Vr?.({LitElement:b});(Qt.litElementVersions??(Qt.litElementVersions=[])).push("4.2.2");var $=o=>(r,t)=>{t!==void 0?t.addInitializer(()=>{customElements.define(o,r);}):customElements.define(o,r);};var Xr={attribute:true,type:String,converter:Ht,reflect:false,hasChanged:le},Kr=(o=Xr,r,t)=>{let{kind:e,metadata:s}=t,i=globalThis.litPropertyMetadata.get(s);if(i===void 0&&globalThis.litPropertyMetadata.set(s,i=new Map),e==="setter"&&((o=Object.create(o)).wrapped=true),i.set(t.name,o),e==="accessor"){let{name:n}=t;return {set(a){let c=r.get.call(this);r.set.call(this,a),this.requestUpdate(n,c,o,true,a);},init(a){return a!==void 0&&this.C(n,void 0,o,a),a}}}if(e==="setter"){let{name:n}=t;return function(a){let c=this[n];r.call(this,a),this.requestUpdate(n,c,o,true,a);}}throw Error("Unsupported decorator location: "+e)};function L(o){return (r,t)=>typeof t=="object"?Kr(o,r,t):((e,s,i)=>{let n=s.hasOwnProperty(i);return s.constructor.createProperty(i,e),n?Object.getOwnPropertyDescriptor(s,i):void 0})(o,r,t)}function E(o){return L({...o,state:true,attribute:false})}var jt=class extends b{constructor(){super(...arguments);this.method="";}createRenderRoot(){return this}render(){let t=this.method.toUpperCase();return l`<span class="method-badge method-badge-${t}">${t}</span>`}};u([L()],jt.prototype,"method",2),jt=u([$("bk-method-badge")],jt);var q="/__brakit/api",U="/__brakit",T={flows:`${q}/flows`,requests:`${q}/requests`,events:`${q}/events`,clear:`${q}/clear`,fetches:`${q}/fetches`,errors:`${q}/errors`,logs:`${q}/logs`,queries:`${q}/queries`,metricsLive:`${q}/metrics/live`,insights:`${q}/insights`,tab:`${q}/tab`,activity:`${q}/activity`,graph:`${q}/graph`};var Et="polling",pe="static",zr="auth-handshake",Jr="auth-check",Zr="middleware",Yt={[zr]:1,[Jr]:1,[Zr]:1};var he="fetch";var ue="error_event",me="query",fe="issues";var Pe={overview:"Overview",actions:"Actions",insights:"Insights",performance:"Performance",graph:"Graph",explorer:"Explorer",requests:"Requests",fetches:"Server Fetches",queries:"Queries",errors:"Errors",logs:"Logs",security:"Security"},He={overview:"Live summary of your application",actions:"User actions captured as sequences of HTTP requests",insights:"Security findings, error patterns, and issue tracking",performance:"Endpoint health and response time trends",graph:"Runtime dependency graph of your application",explorer:"Browse raw requests, fetches, queries, logs, and errors",requests:"All HTTP requests proxied through brakit",fetches:"Outbound HTTP calls made by your server to external services",queries:"Database queries executed during request handling",errors:"Unhandled exceptions and errors thrown by your application",logs:"Console output from your application",security:"Security findings and recommendations"},ve=[{key:"requests",label:"Requests"},{key:"fetches",label:"Fetches"},{key:"queries",label:"Queries"},{key:"logs",label:"Logs"},{key:"errors",label:"Errors"}];var Ls=new Set(["exposed-secret","token-in-url","stack-trace-leak","error-info-leak","insecure-cookie","sensitive-logs","cors-credentials","response-pii-leak"]),Ms=new Set(["slow-endpoint","n1","high-query-count"]);function Vt(o){let r=o.issue.rule||o.issue.type||"";return Ls.has(r)?"security":Ms.has(r)?"performance":"quality"}var O={CLEAR_CONFIRM:"This will clear all data including performance metrics history. Continue?",CLEARED_TOAST:"Cleared",EMPTY_TITLE:"Waiting for requests...",EMPTY_SUBTITLE_OVERVIEW:"Start using your app to see your dashboard here",ALL_CLEAR:"All clear",ALL_CLEAR_DETAIL:"No issues detected",NO_ACTIONS:"No actions captured yet",NO_REQUEST_DATA:"No request data yet",ALL_FAST:"All fast",NO_SLOW_ENDPOINTS:"No slow endpoints detected",ZERO_ERRORS:"0 errors",ALL_REQUESTS_OK:"All requests successful",BUILD_GRAPH:"Navigate your app to build the graph",REVIEW_RECOMMENDED:"review recommended"};var Fe=100,yt=300,_t=800,Ge=2e3,Be=100,We=50,Qe=500;var rt="__all__",Ee={SELECT:"var(--blue)",INSERT:"var(--green)",UPDATE:"var(--amber)",DELETE:"var(--red)",COUNT:"var(--text-muted)"},qs={error:"var(--red)",warn:"var(--amber)",info:"var(--blue)",debug:"var(--text-muted)",log:"var(--text-dim)"},Ve=["#2563eb","#7c3aed","#16a34a","#d97706","#dc2626","#0891b2","#ea580c","#c026d3","#059669","#db2777"],Xt={green:"#4ade80",amber:"#fbbf24",red:"#f87171"},xt=[{max:Fe,label:"Fast",color:"var(--green)",bg:"var(--green-bg)",border:"var(--green-border)"},{max:yt,label:"Good",color:"var(--green)",bg:"var(--green-bg-subtle)",border:"var(--green-border-subtle)"},{max:_t,label:"OK",color:"var(--amber)",bg:"var(--amber-bg)",border:"var(--amber-border)"},{max:Ge,label:"Slow",color:"var(--red)",bg:"var(--red-bg)",border:"var(--red-border)"},{max:1/0,label:"Critical",color:"var(--red)",bg:"var(--red-bg)",border:"var(--red-border)"}],Us="rgba(228,228,231,0.8)",Xe="rgba(113,113,122,0.7)",Fs="10px monospace",Ke="9px monospace";var Gs={top:16,right:16,bottom:28,left:52},Bs={fetch:"var(--blue)",log:"var(--text-muted)",error:"var(--red)",query:"var(--accent)"},Ws={fetch:"FETCH",log:"LOG",error:"ERROR",query:"QUERY"},Qs=new Set(["cookie","set-cookie","authorization","proxy-authorization","x-api-key","x-auth-token"]),js={400:"Bad Request",401:"Unauthorized",403:"Forbidden",404:"Not Found",405:"Method Not Allowed",408:"Timeout",409:"Conflict",422:"Unprocessable",429:"Too Many Requests",500:"Internal Server Error",502:"Bad Gateway",503:"Service Unavailable",504:"Gateway Timeout"},Ys=new Set(["host","connection","accept-encoding"]),Y={critical:{icon:"\u2717",cls:"critical",sort:0},warning:{icon:"\u26A0",cls:"warning",sort:1},info:{icon:"\u2139",cls:"info",sort:2}};function y(o){return o<1e3?o+"ms":(o/1e3).toFixed(1)+"s"}function ot(o){return !o||o===0?"":o<1024?o+"b":(o/1024).toFixed(1)+"kb"}function it(o){return o?o.replaceAll("&","&").replaceAll("<","<").replaceAll(">",">").replaceAll('"',"""):""}function St(o){return o>=500?"status-pill-5xx":o>=400?"status-pill-4xx":o>=300?"status-pill-3xx":"status-pill-2xx"}function Vs(o){return js[o]||(o>=500?"Server Error":o>=400?"Client Error":"OK")}function ti(o,r){if(Qs.has(o.toLowerCase())){let t=String(r);return t.length<=8?"****":t.slice(0,4)+"..."+t.slice(-4)+" ("+t.length+" chars)"}return String(r)}function Tt(o){return !o||Object.keys(o).length===0?'<span style="color:var(--text-muted)">No headers</span>':Object.entries(o).map(([r,t])=>'<span class="json-key">'+it(r)+"</span>: "+it(ti(r,t))).join(`
|
|
4
|
+
`)}function dt(o){if(!o)return '<span style="color:var(--text-muted)">No body</span>';try{let r=JSON.parse(o);return ei(JSON.stringify(r,null,2))}catch{return it(o)}}function ei(o){return it(o).replace(/("(?:[^"\\]|\\.)*")(\s*:)?|\b(true|false)\b|\bnull\b|(-?\d+\.?\d*(?:[eE][+-]?\d+)?)/g,(r,t,e,s,i)=>t?e?'<span class="json-key">'+t+"</span>"+e:'<span class="json-str">'+t+"</span>":s?'<span class="json-bool">'+r+"</span>":i?'<span class="json-num">'+r+"</span>":r==="null"?'<span class="json-null">null</span>':r)}var Kt=class extends b{constructor(){super(...arguments);this.code=0;}createRenderRoot(){return this}render(){let t=St(this.code);return l`<span class="status-pill ${t}">${this.code}</span>`}};u([L({type:Number})],Kt.prototype,"code",2),Kt=u([$("bk-status-pill")],Kt);var zt=class extends b{constructor(){super(...arguments);this.ms=0;}createRenderRoot(){return this}render(){return l`<span class="req-duration">${y(this.ms)}</span>`}};u([L({type:Number})],zt.prototype,"ms",2),zt=u([$("bk-duration-label")],zt);var wt=class extends b{constructor(){super(...arguments);this.title="";this.subtitle="";}createRenderRoot(){return this}render(){return l`
|
|
5
5
|
<div class="empty">
|
|
6
6
|
<span class="empty-title">${this.title}</span>
|
|
7
7
|
<span class="empty-sub">${this.subtitle}</span>
|
|
8
8
|
</div>
|
|
9
|
-
`}};u([L()],
|
|
9
|
+
`}};u([L()],wt.prototype,"title",2),u([L()],wt.prototype,"subtitle",2),wt=u([$("bk-empty-state")],wt);var D=class extends b{constructor(){super(...arguments);this.message="";this.visible=false;}createRenderRoot(){return this}static show(t){let e=document.querySelector("bk-toast");e&&e.showMessage(t);}showMessage(t){this.hideTimer&&clearTimeout(this.hideTimer),this.message=t,this.visible=true,this.hideTimer=setTimeout(()=>{this.visible=false;},2e3);}render(){return l`<div class="toast ${this.visible?"show":""}">${this.message}</div>`}};u([E()],D.prototype,"message",2),u([E()],D.prototype,"visible",2),D=u([$("bk-toast")],D);var pt=class extends b{constructor(){super(...arguments);this.text="";this.label="Copy";this.toastMessage="Copied";}createRenderRoot(){return this}async copy(t){t.stopPropagation();try{await navigator.clipboard.writeText(this.text),D.show(this.toastMessage);}catch{}}render(){return l`<button class="query-detail-copy" @click=${this.copy}>${this.label}</button>`}};u([L()],pt.prototype,"text",2),u([L()],pt.prototype,"label",2),u([L({attribute:"toast-message"})],pt.prototype,"toastMessage",2),pt=u([$("bk-copy-button")],pt);var ht=class extends b{constructor(){super(...arguments);this.value="";this.label="";this.color="";}createRenderRoot(){return this}render(){return l`
|
|
10
10
|
<div class="fetch-stat">
|
|
11
11
|
<span class="fetch-stat-value" style="color:${this.color}">${this.value}</span>
|
|
12
12
|
<span class="fetch-stat-label">${this.label}</span>
|
|
13
13
|
</div>
|
|
14
|
-
`}};u([L()],
|
|
14
|
+
`}};u([L()],ht.prototype,"value",2),u([L()],ht.prototype,"label",2),u([L()],ht.prototype,"color",2),ht=u([$("bk-stat-card")],ht);var nt=class extends Event{constructor(r,t,e,s){super("context-request",{bubbles:true,composed:true}),this.context=r,this.contextTarget=t,this.callback=e,this.subscribe=s??false;}};var Rt=class{constructor(r,t,e,s){if(this.subscribe=false,this.provided=false,this.value=void 0,this.t=(i,n)=>{this.unsubscribe&&(this.unsubscribe!==n&&(this.provided=false,this.unsubscribe()),this.subscribe||this.unsubscribe()),this.value=i,this.host.requestUpdate(),this.provided&&!this.subscribe||(this.provided=true,this.callback&&this.callback(i,n)),this.unsubscribe=n;},this.host=r,t.context!==void 0){let i=t;this.context=i.context,this.callback=i.callback,this.subscribe=i.subscribe??false;}else this.context=t,this.callback=e,this.subscribe=s??false;this.host.addController(this);}hostConnected(){this.dispatchRequest();}hostDisconnected(){this.unsubscribe&&(this.unsubscribe(),this.unsubscribe=void 0);}dispatchRequest(){this.host.dispatchEvent(new nt(this.context,this.host,this.t,this.subscribe));}};var be=class{get value(){return this.o}set value(r){this.setValue(r);}setValue(r,t=false){let e=t||!Object.is(r,this.o);this.o=r,e&&this.updateObservers();}constructor(r){this.subscriptions=new Map,this.updateObservers=()=>{for(let[t,{disposer:e}]of this.subscriptions)t(this.o,e);},r!==void 0&&(this.value=r);}addCallback(r,t,e){if(!e)return void r(this.value);this.subscriptions.has(r)||this.subscriptions.set(r,{disposer:()=>{this.subscriptions.delete(r);},consumerHost:t});let{disposer:s}=this.subscriptions.get(r);r(this.value,s);}clearCallbacks(){this.subscriptions.clear();}};var ze=class extends Event{constructor(r,t){super("context-provider",{bubbles:true,composed:true}),this.context=r,this.contextTarget=t;}},At=class extends be{constructor(r,t,e){super(t.context!==void 0?t.initialValue:e),this.onContextRequest=s=>{if(s.context!==this.context)return;let i=s.contextTarget??s.composedPath()[0];i!==this.host&&(s.stopPropagation(),this.addCallback(s.callback,i,s.subscribe));},this.onProviderRequest=s=>{if(s.context!==this.context||(s.contextTarget??s.composedPath()[0])===this.host)return;let i=new Set;for(let[n,{consumerHost:a}]of this.subscriptions)i.has(n)||(i.add(n),a.dispatchEvent(new nt(this.context,a,n,true)));s.stopPropagation();},this.host=r,t.context!==void 0?this.context=t.context:this.context=t,this.attachListeners(),this.host.addController?.(this);}attachListeners(){this.host.addEventListener("context-request",this.onContextRequest),this.host.addEventListener("context-provider",this.onProviderRequest);}hostConnected(){this.host.dispatchEvent(new ze(this.context,this.host));}};function Je({context:o}){return (r,t)=>{let e=new WeakMap;if(typeof t=="object")return {get(){return r.get.call(this)},set(s){return e.get(this).setValue(s),r.set.call(this,s)},init(s){return e.set(this,new At(this,{context:o,initialValue:s})),s}};{r.constructor.addInitializer((n=>{e.set(n,new At(n,{context:o}));}));let s=Object.getOwnPropertyDescriptor(r,t),i;if(s===void 0){let n=new WeakMap;i={get(){return n.get(this)},set(a){e.get(this).setValue(a),n.set(this,a);},configurable:true,enumerable:true};}else {let n=s.set;i={...s,set(a){e.get(this).setValue(a),n?.call(this,a);}};}return void Object.defineProperty(r,t,i)}}}function I({context:o,subscribe:r}){return (t,e)=>{typeof e=="object"?e.addInitializer((function(){new Rt(this,{context:o,callback:s=>{t.set.call(this,s);},subscribe:r});})):t.constructor.addInitializer((s=>{new Rt(s,{context:o,callback:i=>{s[e]=i;},subscribe:r});}));}}var A="dashboard-store",ye=class extends EventTarget{constructor(){super(...arguments);this._state={flows:[],requests:[],fetches:[],errors:[],logs:[],queries:[],issues:[],metrics:[],viewMode:"simple",activeView:"overview"};}get state(){return this._state}setState(t,e){this._state={...this._state,[t]:e},this.notify(t);}setFlows(t){this.setState("flows",t);}setRequests(t){this.setState("requests",t);}setFetches(t){this.setState("fetches",t);}setErrors(t){this.setState("errors",t);}setLogs(t){this.setState("logs",t);}setQueries(t){this.setState("queries",t);}setIssues(t){this.setState("issues",t);}setMetrics(t){this.setState("metrics",t);}prependRequest(t){let e=[t,...this._state.requests.slice(0,999)];this._state={...this._state,requests:e},this.notify("requests");}prependFetch(t){let e=[t,...this._state.fetches.slice(0,999)];this._state={...this._state,fetches:e},this.notify("fetches");}prependError(t){let e=[t,...this._state.errors.slice(0,999)];this._state={...this._state,errors:e},this.notify("errors");}prependLog(t){let e=[t,...this._state.logs.slice(0,999)];this._state={...this._state,logs:e},this.notify("logs");}prependQuery(t){let e=[t,...this._state.queries.slice(0,999)];this._state={...this._state,queries:e},this.notify("queries");}setActiveView(t){this.setState("activeView",t);}setViewMode(t){this.setState("viewMode",t);}clearAll(){this._state={...this._state,flows:[],requests:[],fetches:[],errors:[],logs:[],queries:[],issues:[],metrics:[]},this.notify("all");}notify(t){this.dispatchEvent(new CustomEvent("state-changed",{detail:t}));}};var It=class extends b{constructor(){super(...arguments);this.expandedIdx=-1;}createRenderRoot(){return this}connectedCallback(){super.connectedCallback(),this.store.addEventListener("state-changed",()=>this.requestUpdate());}toggleError(t){this.expandedIdx=this.expandedIdx===t?-1:t;}renderErrorRow(t,e){let s=new Date(t.timestamp).toLocaleTimeString(),i=this.expandedIdx===e;return l`
|
|
15
15
|
<div
|
|
16
16
|
class="req-row tel-clickable ${i?"expanded":""}"
|
|
17
17
|
@click=${()=>this.toggleError(e)}
|
|
18
18
|
>
|
|
19
19
|
<span class="tel-error-name" title=${t.name}>${t.name}</span>
|
|
20
20
|
<span class="tel-message" title=${t.message}>${t.message}</span>
|
|
21
|
-
<span class="tel-timestamp">${
|
|
21
|
+
<span class="tel-timestamp">${s}</span>
|
|
22
22
|
</div>
|
|
23
|
-
${i&&t.stack?
|
|
24
|
-
`}render(){let t=this.store.state.errors;return t.length===0?
|
|
23
|
+
${i&&t.stack?l`<div class="error-stack">${t.stack}</div>`:p}
|
|
24
|
+
`}render(){let t=this.store.state.errors;return t.length===0?l`<bk-empty-state
|
|
25
25
|
title="No errors"
|
|
26
26
|
subtitle="No errors have been captured yet"
|
|
27
|
-
></bk-empty-state>`:
|
|
27
|
+
></bk-empty-state>`:l`
|
|
28
28
|
<div class="col-header">
|
|
29
29
|
<span style="width:180px">Type</span>
|
|
30
30
|
<span style="flex:1">Message</span>
|
|
31
31
|
<span style="width:130px;text-align:right">Time</span>
|
|
32
32
|
</div>
|
|
33
33
|
<div id="error-list">
|
|
34
|
-
${t.map((e,
|
|
34
|
+
${t.map((e,s)=>this.renderErrorRow(e,s))}
|
|
35
35
|
</div>
|
|
36
|
-
`}};u([I({context:A})],
|
|
36
|
+
`}};u([I({context:A})],It.prototype,"store",2),u([E()],It.prototype,"expandedIdx",2),It=u([$("bk-errors-view")],It);var Jt=class extends b{createRenderRoot(){return this}connectedCallback(){super.connectedCallback(),this.store.addEventListener("state-changed",()=>this.requestUpdate());}renderAnalysis(r){if(r.length===0)return p;let t={error:0,warn:0,info:0,debug:0,log:0};for(let e of r)t[e.level]!==void 0&&t[e.level]++;return l`
|
|
37
37
|
<div id="log-analysis">
|
|
38
38
|
<div class="fetch-summary">
|
|
39
|
-
<bk-stat-card value=${String(
|
|
40
|
-
${t.error>0?
|
|
41
|
-
${t.warn>0?
|
|
39
|
+
<bk-stat-card value=${String(r.length)} label="Total Logs"></bk-stat-card>
|
|
40
|
+
${t.error>0?l`<bk-stat-card value=${String(t.error)} label="Errors" color="var(--red)"></bk-stat-card>`:p}
|
|
41
|
+
${t.warn>0?l`<bk-stat-card value=${String(t.warn)} label="Warnings" color="var(--amber)"></bk-stat-card>`:p}
|
|
42
42
|
<bk-stat-card value=${String(t.info)} label="Info"></bk-stat-card>
|
|
43
|
-
${t.debug>0?
|
|
44
|
-
${t.log>0?
|
|
43
|
+
${t.debug>0?l`<bk-stat-card value=${String(t.debug)} label="Debug"></bk-stat-card>`:p}
|
|
44
|
+
${t.log>0?l`<bk-stat-card value=${String(t.log)} label="Log"></bk-stat-card>`:p}
|
|
45
45
|
</div>
|
|
46
46
|
</div>
|
|
47
|
-
`}renderLogRow(
|
|
47
|
+
`}renderLogRow(r){let t=new Date(r.timestamp).toLocaleTimeString();return l`
|
|
48
48
|
<div class="req-row">
|
|
49
|
-
<span class="tel-level tel-level-${
|
|
50
|
-
<span class="tel-message tel-mono" title=${
|
|
49
|
+
<span class="tel-level tel-level-${r.level}">${r.level.toUpperCase()}</span>
|
|
50
|
+
<span class="tel-message tel-mono" title=${r.message}>${r.message}</span>
|
|
51
51
|
<span class="tel-timestamp">${t}</span>
|
|
52
52
|
</div>
|
|
53
|
-
`}render(){let
|
|
53
|
+
`}render(){let r=this.store.state.logs;return r.length===0?l`<bk-empty-state
|
|
54
54
|
title="No logs"
|
|
55
55
|
subtitle="No console output has been captured yet"
|
|
56
|
-
></bk-empty-state>`:
|
|
57
|
-
${this.renderAnalysis(
|
|
56
|
+
></bk-empty-state>`:l`
|
|
57
|
+
${this.renderAnalysis(r)}
|
|
58
58
|
<div class="col-header">
|
|
59
59
|
<span style="width:52px">Level</span>
|
|
60
60
|
<span style="flex:1">Message</span>
|
|
61
61
|
<span style="width:130px;text-align:right">Time</span>
|
|
62
62
|
</div>
|
|
63
63
|
<div id="log-list">
|
|
64
|
-
${
|
|
64
|
+
${r.map(t=>this.renderLogRow(t))}
|
|
65
65
|
</div>
|
|
66
|
-
`}};u([I({context:A})],
|
|
66
|
+
`}};u([I({context:A})],Jt.prototype,"store",2),Jt=u([$("bk-logs-view")],Jt);var Xs={CHILD:2},Ks=o=>(...r)=>({_$litDirective$:o,values:r}),_e=class{constructor(r){}get _$AU(){return this._$AM._$AU}_$AT(r,t,e){this._$Ct=r,this._$AM=t,this._$Ci=e;}_$AS(r,t){return this.update(r,t)}update(r,t){return this.render(...t)}};var Zt=class extends _e{constructor(r){if(super(r),this.it=p,r.type!==Xs.CHILD)throw Error(this.constructor.directiveName+"() can only be used in child bindings")}render(r){if(r===p||r==null)return this._t=void 0,this.it=r;if(r===z)return r;if(typeof r!="string")throw Error(this.constructor.directiveName+"() called with a non-string value");if(r===this.it)return this._t;this.it=r;let t=[r];return t.raw=t,this._t={_$litType$:this.constructor.resultType,strings:t,values:[]}}};Zt.directiveName="unsafeHTML",Zt.resultType=1;var G=Ks(Zt);var ri=new Set(["SELECT","FROM","WHERE","AND","OR","INSERT","INTO","VALUES","UPDATE","SET","DELETE","JOIN","LEFT","RIGHT","INNER","OUTER","ON","GROUP","BY","ORDER","HAVING","LIMIT","OFFSET","AS","IN","NOT","NULL","IS","LIKE","BETWEEN","EXISTS","CASE","WHEN","THEN","ELSE","END","COUNT","SUM","AVG","MIN","MAX","DISTINCT","UNION","ALL","CREATE","TABLE","ALTER","DROP","INDEX","RETURNING","WITH","RECURSIVE","OVER","PARTITION","WINDOW","FETCH","NEXT","ROWS","ONLY","CAST","COALESCE","NULLIF","EXTRACT","INTERVAL","TRUE","FALSE","ASC","DESC","USING","NATURAL","CROSS","FULL","ROLLBACK","COMMIT","BEGIN","TRANSACTION","SAVEPOINT","RELEASE"]);function zs(o){let r=o.trimStart().split(/\s/)[0];return r?r.toUpperCase():"?"}function Js(o){let r=o.replace(/\s+/g," ").trim(),t=r.match(/\bFROM\s+["'`]?(\w+)["'`]?/i);if(t)return t[1];let e=r.match(/\bINTO\s+["'`]?(\w+)["'`]?/i);if(e)return e[1];let s=r.match(/\bUPDATE\s+["'`]?(\w+)["'`]?/i);return s?s[1]:""}function Zs(o){return it(o).replace(/\b\w+\b/g,r=>ri.has(r.toUpperCase())?'<span class="sql-kw">'+r+"</span>":r)}var Ct=class extends b{constructor(){super(...arguments);this.expandedIdx=-1;}createRenderRoot(){return this}connectedCallback(){super.connectedCallback(),this.store.addEventListener("state-changed",()=>this.requestUpdate());}toggleQuery(t){this.expandedIdx=this.expandedIdx===t?-1:t;}queryDuration(t){return t===0?"<1ms":y(t)}getQueryInfo(t){let e=(t.normalizedOp||t.operation||(t.sql?zs(t.sql):"?")).toUpperCase(),s=t.table||t.model||(t.sql?Js(t.sql):""),i=t.sql||e+" "+s;return {op:e,table:s,sqlText:i}}renderQueryRow(t,e){let{op:s,table:i,sqlText:n}=this.getQueryInfo(t),a=Ee[s]||"var(--text-dim)",c=t.durationMs>Be,d=t.sql||s+" "+i,h=this.expandedIdx===e;return l`
|
|
67
67
|
<div>
|
|
68
68
|
<div
|
|
69
69
|
class="req-row query-row tel-clickable ${h?"expanded":""}"
|
|
70
70
|
@click=${()=>this.toggleQuery(e)}
|
|
71
71
|
>
|
|
72
|
-
<span class="query-op" title=${
|
|
72
|
+
<span class="query-op" title=${s} style="color:${a}">${s}</span>
|
|
73
73
|
<span class="query-table" title=${i}>${i}</span>
|
|
74
74
|
<span class="query-preview" title=${d}>${d}</span>
|
|
75
75
|
<span class="query-dur${c?" query-slow":""}">${this.queryDuration(t.durationMs)}</span>
|
|
76
76
|
</div>
|
|
77
77
|
<div class="query-detail ${h?"open":""}">
|
|
78
|
-
${h?
|
|
79
|
-
<pre class="query-detail-sql">${
|
|
78
|
+
${h?l`
|
|
79
|
+
<pre class="query-detail-sql">${G(Zs(n))}</pre>
|
|
80
80
|
<bk-copy-button .text=${n} label="Copy"></bk-copy-button>
|
|
81
81
|
`:p}
|
|
82
82
|
</div>
|
|
83
83
|
</div>
|
|
84
|
-
`}render(){let t=this.store.state.queries;return t.length===0?
|
|
84
|
+
`}render(){let t=this.store.state.queries;return t.length===0?l`<bk-empty-state
|
|
85
85
|
title="No queries"
|
|
86
86
|
subtitle="No database queries have been captured yet"
|
|
87
|
-
></bk-empty-state>`:
|
|
87
|
+
></bk-empty-state>`:l`
|
|
88
88
|
<div class="col-header">
|
|
89
89
|
<span style="width:70px;border-right:1px solid var(--border);padding-right:16px">Operation</span>
|
|
90
90
|
<span style="width:170px;border-right:1px solid var(--border);padding-right:16px">Table</span>
|
|
@@ -92,37 +92,37 @@
|
|
|
92
92
|
<span style="width:60px;text-align:right">Time</span>
|
|
93
93
|
</div>
|
|
94
94
|
<div id="query-list">
|
|
95
|
-
${t.map((e,
|
|
95
|
+
${t.map((e,s)=>this.renderQueryRow(e,s))}
|
|
96
96
|
</div>
|
|
97
|
-
`}};u([I({context:A})],
|
|
97
|
+
`}};u([I({context:A})],Ct.prototype,"store",2),u([E()],Ct.prototype,"expandedIdx",2),Ct=u([$("bk-queries-view")],Ct);function Ze(o){return o.replaceAll("'","'\\''")}function ii(o,r){let t=Object.entries(o.headers||{}).filter(([i])=>!Ys.has(i)).map(([i,n])=>`-H '${Ze(i)}: ${Ze(n)}'`).join(" "),e=o.requestBody?` -d '${Ze(o.requestBody)}'`:"",s=r?`http://localhost:${r}`:"";return `curl -X ${o.method} ${t}${e} '${s}${o.url}'`}function Lt(o){let r=window.__BRAKIT_CONFIG__?.port??"",t=ii(o,r);navigator.clipboard.writeText(t).then(()=>D.show("Copied cURL command"));}var Mt=class extends b{constructor(){super(...arguments);this.expandedId=null;}createRenderRoot(){return this}connectedCallback(){super.connectedCallback(),this.store.addEventListener("state-changed",()=>this.requestUpdate());}toggleRequest(t){this.expandedId=this.expandedId===t?null:t;}handleCopyAsCurl(t,e){e.stopPropagation(),Lt(t);}renderDetail(t){return l`
|
|
98
98
|
<div class="detail-meta">
|
|
99
99
|
<span><bk-method-badge .method=${t.method}></bk-method-badge> ${t.url}</span>
|
|
100
100
|
<span><bk-status-pill .code=${t.statusCode}></bk-status-pill></span>
|
|
101
101
|
<span>${t.durationMs}ms</span>
|
|
102
|
-
${t.responseSize?
|
|
102
|
+
${t.responseSize?l`<span>${ot(t.responseSize)}</span>`:p}
|
|
103
103
|
</div>
|
|
104
104
|
<div class="request-timeline tl-hidden" data-request-id=${t.id} data-request-started=${String(t.startedAt)}></div>
|
|
105
105
|
<div class="detail-grid">
|
|
106
|
-
<div class="detail-section"><h4>Request Headers</h4><pre>${
|
|
107
|
-
<div class="detail-section"><h4>Response Headers</h4><pre>${
|
|
108
|
-
<div class="detail-section"><h4>Request Body</h4><pre>${
|
|
109
|
-
<div class="detail-section"><h4>Response Body</h4><pre>${
|
|
106
|
+
<div class="detail-section"><h4>Request Headers</h4><pre>${G(Tt(t.headers))}</pre></div>
|
|
107
|
+
<div class="detail-section"><h4>Response Headers</h4><pre>${G(Tt(t.responseHeaders))}</pre></div>
|
|
108
|
+
<div class="detail-section"><h4>Request Body</h4><pre>${G(dt(t.requestBody))}</pre></div>
|
|
109
|
+
<div class="detail-section"><h4>Response Body</h4><pre>${G(dt(t.responseBody))}</pre></div>
|
|
110
110
|
</div>
|
|
111
111
|
<div class="detail-actions">
|
|
112
112
|
<button class="btn btn-curl" @click=${e=>this.handleCopyAsCurl(t,e)}>Copy cURL</button>
|
|
113
113
|
</div>
|
|
114
|
-
`}renderRequestRow(t){let e=this.expandedId===t.id;return
|
|
114
|
+
`}renderRequestRow(t){let e=this.expandedId===t.id;return l`
|
|
115
115
|
<div class="req-row ${e?"expanded":""}" @click=${()=>this.toggleRequest(t.id)}>
|
|
116
116
|
<div class="req-summary">
|
|
117
117
|
<bk-method-badge .method=${t.method}></bk-method-badge>
|
|
118
118
|
<span class="req-url">${t.url}</span>
|
|
119
119
|
<bk-status-pill .code=${t.statusCode}></bk-status-pill>
|
|
120
120
|
<bk-duration-label .ms=${t.durationMs}></bk-duration-label>
|
|
121
|
-
<span class="req-size">${
|
|
121
|
+
<span class="req-size">${ot(t.responseSize)}</span>
|
|
122
122
|
</div>
|
|
123
123
|
</div>
|
|
124
124
|
<div class="req-detail ${e?"open":""}">${e?this.renderDetail(t):p}</div>
|
|
125
|
-
`}render(){let t=this.store.state.requests.filter(e=>!e.path?.startsWith(
|
|
125
|
+
`}render(){let t=this.store.state.requests.filter(e=>!e.path?.startsWith(U));return t.length===0?l`<bk-empty-state title="No requests" subtitle="No HTTP requests have been captured yet"></bk-empty-state>`:l`
|
|
126
126
|
<div class="col-header">
|
|
127
127
|
<span style="width:60px">Method</span>
|
|
128
128
|
<span style="flex:1">URL</span>
|
|
@@ -131,53 +131,53 @@
|
|
|
131
131
|
<span style="width:60px;text-align:right">Size</span>
|
|
132
132
|
</div>
|
|
133
133
|
<div id="request-list">${t.map(e=>this.renderRequestRow(e))}</div>
|
|
134
|
-
`}};u([I({context:A})],
|
|
134
|
+
`}};u([I({context:A})],Mt.prototype,"store",2),u([E()],Mt.prototype,"expandedId",2),Mt=u([$("bk-requests-view")],Mt);var te=class extends b{createRenderRoot(){return this}connectedCallback(){super.connectedCallback(),this.store.addEventListener("state-changed",()=>this.requestUpdate());}buildGroups(r,t){let e=new Map;for(let i of t)e.set(i.id,i);let s={};for(let i of r){let n=i.method+" "+i.url;s[n]||(s[n]={method:i.method,url:i.url,count:0,totalDur:0,maxDur:0,errors:0,callers:{},statusCodes:{},firstTs:i.timestamp,lastTs:i.timestamp});let a=s[n];if(a.count++,a.totalDur+=i.durationMs,i.durationMs>a.maxDur&&(a.maxDur=i.durationMs),i.statusCode>=400&&a.errors++,a.statusCodes[i.statusCode]=(a.statusCodes[i.statusCode]||0)+1,i.timestamp<a.firstTs&&(a.firstTs=i.timestamp),i.timestamp>a.lastTs&&(a.lastTs=i.timestamp),i.parentRequestId){let c=e.get(i.parentRequestId);c&&(a.callers[c.method+" "+(c.path||c.url)]=1);}}return Object.values(s).sort((i,n)=>n.count-i.count)}renderSummary(r){let t=new Set,e=0,s=0;for(let n of r)t.add(n.url),n.statusCode>=400&&e++,s+=n.durationMs;let i=Math.round(s/r.length);return l`
|
|
135
135
|
<div class="fetch-summary">
|
|
136
|
-
<bk-stat-card value=${String(
|
|
136
|
+
<bk-stat-card value=${String(r.length)} label="Total Fetches"></bk-stat-card>
|
|
137
137
|
<bk-stat-card value=${String(t.size)} label="Unique URLs"></bk-stat-card>
|
|
138
138
|
<bk-stat-card value=${String(e)} label="Errors" color=${e>0?"var(--red)":""}></bk-stat-card>
|
|
139
|
-
<bk-stat-card value=${
|
|
139
|
+
<bk-stat-card value=${y(i)} label="Avg Duration"></bk-stat-card>
|
|
140
140
|
</div>
|
|
141
|
-
`}formatTime(
|
|
141
|
+
`}formatTime(r){return new Date(r).toLocaleTimeString([],{hour:"2-digit",minute:"2-digit",second:"2-digit"})}renderGroup(r){let t=Math.round(r.totalDur/r.count),e=r.count>0?Math.round(r.errors/r.count*100):0,s=Object.keys(r.callers),i=Object.entries(r.statusCodes),n=i.length>0?Number(i.sort((a,c)=>c[1]-a[1])[0][0]):0;return l`
|
|
142
142
|
<div class="fetch-group">
|
|
143
143
|
<div class="fetch-group-header">
|
|
144
|
-
<bk-method-badge .method=${
|
|
145
|
-
<span class="fetch-group-url" title=${
|
|
146
|
-
${n>0?
|
|
147
|
-
<span class="fetch-group-count">${
|
|
144
|
+
<bk-method-badge .method=${r.method}></bk-method-badge>
|
|
145
|
+
<span class="fetch-group-url" title=${r.url}>${r.url}</span>
|
|
146
|
+
${n>0?l`<bk-status-pill .code=${n}></bk-status-pill>`:p}
|
|
147
|
+
<span class="fetch-group-count">${r.count}x</span>
|
|
148
148
|
</div>
|
|
149
149
|
<div class="fetch-group-meta">
|
|
150
|
-
<span>avg ${
|
|
150
|
+
<span>avg ${y(t)}</span>
|
|
151
151
|
<span class="fetch-group-sep">\u00b7</span>
|
|
152
|
-
<span>max ${
|
|
152
|
+
<span>max ${y(r.maxDur)}</span>
|
|
153
153
|
<span class="fetch-group-sep">\u00b7</span>
|
|
154
|
-
${e>0?
|
|
154
|
+
${e>0?l`<span class="fetch-group-err">${e}% errors</span>`:l`<span class="fetch-group-ok">0% errors</span>`}
|
|
155
155
|
</div>
|
|
156
|
-
${
|
|
156
|
+
${r.firstTs>0?l`
|
|
157
157
|
<div class="fetch-group-timeline">
|
|
158
158
|
<span class="fetch-group-timeline-dot"></span>
|
|
159
159
|
<span class="fetch-group-timeline-range">
|
|
160
|
-
${this.formatTime(
|
|
160
|
+
${this.formatTime(r.firstTs)}${r.firstTs!==r.lastTs?l` \u2192 ${this.formatTime(r.lastTs)}`:p}
|
|
161
161
|
</span>
|
|
162
162
|
</div>`:p}
|
|
163
|
-
${
|
|
163
|
+
${s.length>0?l`
|
|
164
164
|
<div class="fetch-group-callers">
|
|
165
165
|
<span class="fetch-group-callers-label">Called by</span>
|
|
166
|
-
${
|
|
166
|
+
${s.map(a=>l`<span class="fetch-group-caller-pill">${a}</span>`)}
|
|
167
167
|
</div>`:p}
|
|
168
168
|
</div>
|
|
169
|
-
`}render(){let
|
|
169
|
+
`}render(){let r=this.store.state.fetches,t=this.store.state.requests;if(r.length===0)return l`<bk-empty-state title="No fetches" subtitle="No outbound HTTP calls have been captured yet"></bk-empty-state>`;let e=this.buildGroups(r,t);return l`
|
|
170
170
|
<div class="fetch-analysis" id="fetch-analysis">
|
|
171
|
-
${this.renderSummary(
|
|
172
|
-
${e.length>0?
|
|
171
|
+
${this.renderSummary(r)}
|
|
172
|
+
${e.length>0?l`
|
|
173
173
|
<div class="fetch-groups-title">Grouped by URL (${e.length})</div>
|
|
174
|
-
<div class="fetch-groups">${e.map(
|
|
174
|
+
<div class="fetch-groups">${e.map(s=>this.renderGroup(s))}</div>
|
|
175
175
|
`:p}
|
|
176
176
|
</div>
|
|
177
|
-
`}};u([I({context:A})],
|
|
177
|
+
`}};u([I({context:A})],te.prototype,"store",2),te=u([$("bk-fetches-view")],te);function tr(o,r){if(r>=400)return "var(--red)";switch(o){case "GET":return "var(--green)";case "POST":return "var(--blue)";case "PUT":case "PATCH":return "var(--amber)";case "DELETE":return "var(--red)";default:return "var(--text-muted)"}}function ts(o){return o==="query"?"var(--accent)":"var(--cyan)"}function li(o){return o.type==="query"||o.type==="fetch"}function ci(o){let r=(o.normalizedOp||o.operation||"QUERY").toUpperCase(),t=o.table||o.model||"";return {label:`${r} ${t}`,tooltip:o.sql||`${r} ${t}`}}function di(o){return {label:`${o.method} ${o.url}`,tooltip:`${o.method} ${o.url}`}}function pi(o,r,t,e,s,i){let n=o.data.durationMs||0,a,c;if(i){let d=Math.max(o.timestamp-e,0);a=Math.min(d/s*100,95),c=Math.max(n/s*100,1.5);}else {let d=t[0].timestamp,m=t[t.length-1].timestamp-d;a=m>0?(o.timestamp-d)/m*85:r/Math.max(t.length-1,1)*85,c=Math.max(n/s*100,1.5);}return a+c>100&&(c=Math.max(100-a,1.5)),{leftPct:a,widthPct:c}}function hi(o,r){let t=o.timeline.filter(li);if(t.length===0)return [];let e=r.startedAt,s=r.durationMs||1,i=Math.abs(t[0].timestamp-e)<s*10;return t.map((n,a)=>{let c=n.data.durationMs||0,{leftPct:d,widthPct:h}=pi(n,a,t,e,s,i),m=n.type==="query"?ci(n.data):di(n.data);return {type:n.type,label:m.label,durMs:c,durLabel:y(c),tooltip:m.tooltip,leftPct:d,widthPct:h}})}function sr(o,r){let t=o.requests.filter(a=>!a.isStrictModeDupe);if(t.length===0)return {rows:[],totalMs:0};let e=Math.min(...t.map(a=>a.startedAt)),i=Math.max(...t.map(a=>a.startedAt+a.durationMs))-e;return i===0?{rows:[],totalMs:0}:{rows:t.map(a=>{let c=(a.startedAt-e)/i*100,d=Math.max(a.durationMs/i*100,.5),h=r?.activities?.[a.id],m=h?hi(h,a):[];return {label:`${a.method} ${a.label}`,leftPct:c,widthPct:d,color:tr(a.method,a.statusCode),durMs:a.durationMs,durLabel:y(a.durationMs),tooltip:`${a.method} ${a.label} (${y(a.durationMs)})`,subEvents:m}}),totalMs:i}}function rr(o){let r=o.requests,t=[],e=[],s=[],i=[],n=new Map;for(let d of r){let h=d.label,m=d.pollingDurationMs||d.durationMs;if(!Yt[d.category||""]){if(d.isDuplicate){let f=n.get(h);f?(f.count++,f.wastedMs+=m):n.set(h,{name:h,count:2,wastedMs:m});continue}if(d.statusCode>=400){e.push(h+" ("+Vs(d.statusCode)+")");continue}d.responseSize>51200&&s.push("Large response: "+h+" returned "+ot(d.responseSize)),t.push(h);}}for(let d of n.values())i.push(d);let a="";if(i.length>0){let d=i.map(m=>m.name).join(", "),h=i.reduce((m,f)=>m+f.wastedMs,0);a="Your app fetches "+d+" multiple times on this page. This wastes ~"+y(h)+". Try caching these calls, deduplicating with React Query/SWR, or moving them to a shared layout.";}else e.length>0&&(a="Some requests are failing. Check your API routes and make sure the endpoints exist.");let c=r.filter(d=>d.durationMs>2e3&&d.category!==Et);return c.length>0&&!a&&(a=c.map(d=>d.label).join(", ")+` is taking over ${y(2e3)}. Consider adding caching or optimizing the backend query.`),{successes:t,errors:e,warnings:s,duplicates:i,tip:a}}var V=class extends b{constructor(){super(...arguments);this.expandedFlowIdx=-1;this.expandedSubReqIdx=-1;this.flowDetailTab="insights";this.flowTimeline=null;this.flowTimelineLoading=false;}createRenderRoot(){return this}connectedCallback(){super.connectedCallback(),this.store.addEventListener("state-changed",()=>this.requestUpdate());}get flows(){return this.store.state.flows}get viewMode(){return this.store.state.viewMode}flowDotClass(t){return t.hasErrors?"dot-error":t.redundancyPct>0?"dot-warn":"dot-clean"}flowBadgeInfo(t){if(t.hasErrors){let e=t.requests.filter(s=>s.statusCode>=400).length;return {text:e+" error"+(e!==1?"s":""),cls:"badge-error"}}return t.redundancyPct>0?{text:t.redundancyPct+"% redundant",cls:"badge-warn"}:{text:"clean",cls:"badge-clean"}}toggleFlow(t){this.expandedFlowIdx===t?this.expandedFlowIdx=-1:(this.expandedFlowIdx=t,this.expandedSubReqIdx=-1,this.flowDetailTab="insights",this.flowTimeline=null);}toggleSubReq(t,e){e.stopPropagation(),this.expandedSubReqIdx=this.expandedSubReqIdx===t?-1:t;}toggleBodyBlock(t){t.stopPropagation();let e=t.currentTarget,s=e.parentElement;if(!s)return;e.classList.toggle("open");let i=s.querySelector("pre");i&&i.classList.toggle("open");}switchTab(t,e,s){s.stopPropagation(),this.flowDetailTab=t,t==="timeline"&&!this.flowTimeline&&this.loadFlowTimeline(e);}async loadFlowTimeline(t){if(this.flowTimelineLoading)return;let e=t.requests.map(s=>s.id).filter(Boolean);if(e.length!==0){this.flowTimelineLoading=true;try{let s=await fetch(`${T.activity}?requestIds=${e.join(",")}`);if(!s.ok){this.flowTimelineLoading=!1;return}this.flowTimeline=await s.json();}catch{}this.flowTimelineLoading=false;}}loadTimelineForContainer(t){let e=t.querySelectorAll(".request-timeline");for(let s of e){let i=s.getAttribute("data-request-id");if(i&&!s.hasAttribute("data-loaded")){s.setAttribute("data-loaded","1");let n=document.createElement("bk-timeline-panel");n.setAttribute("request-id",i),n.setAttribute("request-started",s.getAttribute("data-request-started")||"0"),s.appendChild(n),s.classList.remove("tl-hidden");}}}updated(){if(this.expandedFlowIdx>=0&&this.flowDetailTab==="insights"){let t=this.querySelector(".flow-expand.open");t&&this.loadTimelineForContainer(t);}}render(){let t=this.flows;return t.length===0?l`<bk-empty-state
|
|
178
178
|
title="No actions yet"
|
|
179
179
|
subtitle="Start using your app to see user action flows here"
|
|
180
|
-
></bk-empty-state>`:
|
|
180
|
+
></bk-empty-state>`:l`
|
|
181
181
|
<div id="flow-col-header" class="col-header">
|
|
182
182
|
<span style="width:8px"></span>
|
|
183
183
|
<span style="flex:1">Action</span>
|
|
@@ -186,11 +186,11 @@
|
|
|
186
186
|
<span style="width:70px;text-align:right">Time</span>
|
|
187
187
|
</div>
|
|
188
188
|
<div id="flow-list">
|
|
189
|
-
${t.map((e,
|
|
189
|
+
${t.map((e,s)=>this.renderFlowRow(e,s))}
|
|
190
190
|
</div>
|
|
191
|
-
`}renderFlowRow(t,e){let
|
|
191
|
+
`}renderFlowRow(t,e){let s=this.expandedFlowIdx===e,i=this.flowDotClass(t),n=this.flowBadgeInfo(t);return l`
|
|
192
192
|
<div
|
|
193
|
-
class="flow-row ${
|
|
193
|
+
class="flow-row ${s?"expanded":""}"
|
|
194
194
|
@click=${()=>this.toggleFlow(e)}
|
|
195
195
|
>
|
|
196
196
|
<div class="flow-summary-row">
|
|
@@ -202,39 +202,39 @@
|
|
|
202
202
|
>
|
|
203
203
|
<span class="flow-badge-pill ${n.cls}">${n.text}</span>
|
|
204
204
|
<span class="flow-duration"
|
|
205
|
-
>${
|
|
205
|
+
>${y(t.totalDurationMs)}</span
|
|
206
206
|
>
|
|
207
207
|
</div>
|
|
208
208
|
</div>
|
|
209
|
-
<div class="flow-expand ${
|
|
210
|
-
${
|
|
209
|
+
<div class="flow-expand ${s?"open":""}">
|
|
210
|
+
${s?this.renderFlowDetail(t):p}
|
|
211
211
|
</div>
|
|
212
|
-
`}renderFlowDetail(t){let e=this.viewMode==="simple"?"Insights":"Details";return
|
|
212
|
+
`}renderFlowDetail(t){let e=this.viewMode==="simple"?"Insights":"Details";return l`
|
|
213
213
|
<div class="flow-detail-tabs">
|
|
214
214
|
<button
|
|
215
215
|
class="flow-tab ${this.flowDetailTab==="insights"?"active":""}"
|
|
216
|
-
@click=${
|
|
216
|
+
@click=${s=>this.switchTab("insights",t,s)}
|
|
217
217
|
>
|
|
218
218
|
${e}
|
|
219
219
|
</button>
|
|
220
220
|
<button
|
|
221
221
|
class="flow-tab ${this.flowDetailTab==="timeline"?"active":""}"
|
|
222
|
-
@click=${
|
|
222
|
+
@click=${s=>this.switchTab("timeline",t,s)}
|
|
223
223
|
>
|
|
224
224
|
Timeline
|
|
225
225
|
</button>
|
|
226
226
|
</div>
|
|
227
227
|
${this.flowDetailTab==="insights"?this.viewMode==="simple"?this.renderFlowInsights(t):this.renderFlowSubReqs(t):this.renderFlowWaterfall(t)}
|
|
228
|
-
`}renderFlowWaterfall(t){if(this.flowTimelineLoading)return
|
|
228
|
+
`}renderFlowWaterfall(t){if(this.flowTimelineLoading)return l`<div class="wf-loading">Loading timeline...</div>`;let{rows:e,totalMs:s}=sr(t,this.flowTimeline);if(e.length===0)return p;let i=[];for(let n=0;n<=5;n++)i.push(y(s/5*n));return l`
|
|
229
229
|
<div class="flow-waterfall">
|
|
230
230
|
<div class="wf-time-axis">
|
|
231
|
-
${i.map(n=>
|
|
231
|
+
${i.map(n=>l`<span>${n}</span>`)}
|
|
232
232
|
</div>
|
|
233
233
|
<div class="wf-rows">
|
|
234
234
|
${e.map(n=>this.renderWaterfallGroup(n))}
|
|
235
235
|
</div>
|
|
236
236
|
</div>
|
|
237
|
-
`}renderWaterfallGroup(t){return
|
|
237
|
+
`}renderWaterfallGroup(t){return l`
|
|
238
238
|
<div class="wf-request-group">
|
|
239
239
|
<div class="wf-req-row" title="${t.tooltip}">
|
|
240
240
|
<div class="wf-req-label">${t.label}</div>
|
|
@@ -246,48 +246,48 @@
|
|
|
246
246
|
</div>
|
|
247
247
|
<div class="wf-req-dur">${t.durLabel}</div>
|
|
248
248
|
</div>
|
|
249
|
-
${t.subEvents.length>0?t.subEvents.map(e=>
|
|
249
|
+
${t.subEvents.length>0?t.subEvents.map(e=>l`
|
|
250
250
|
<div class="wf-sub-row" title="${e.tooltip}">
|
|
251
251
|
<div class="wf-sub-label">
|
|
252
252
|
<span
|
|
253
253
|
class="wf-sub-dot"
|
|
254
|
-
style="background:${
|
|
254
|
+
style="background:${ts(e.type)}"
|
|
255
255
|
></span>
|
|
256
256
|
${e.label}
|
|
257
257
|
</div>
|
|
258
258
|
<div class="wf-bar-track">
|
|
259
259
|
<div
|
|
260
260
|
class="wf-bar wf-sub-bar-sized"
|
|
261
|
-
style="left:${t.leftPct+e.leftPct/100*t.widthPct}%;width:${e.widthPct/100*t.widthPct}%;background:${
|
|
261
|
+
style="left:${t.leftPct+e.leftPct/100*t.widthPct}%;width:${e.widthPct/100*t.widthPct}%;background:${ts(e.type)}"
|
|
262
262
|
></div>
|
|
263
263
|
</div>
|
|
264
264
|
<div class="wf-sub-dur">${e.durLabel}</div>
|
|
265
265
|
</div>
|
|
266
266
|
`):p}
|
|
267
267
|
</div>
|
|
268
|
-
`}renderFlowInsights(t){let e=
|
|
268
|
+
`}renderFlowInsights(t){let e=rr(t),s=e.errors.length>0||e.duplicates.length>0||e.warnings.length>0||!!e.tip;return l`
|
|
269
269
|
<div>
|
|
270
270
|
<div class="flow-traffic">
|
|
271
271
|
${t.requests.map(i=>this.renderTrafficCard(i))}
|
|
272
272
|
</div>
|
|
273
|
-
${
|
|
273
|
+
${s?l`
|
|
274
274
|
<div class="flow-divider"></div>
|
|
275
275
|
<div class="flow-insights">
|
|
276
|
-
${e.errors.map(i=>
|
|
276
|
+
${e.errors.map(i=>l`<div class="insight-line insight-error">
|
|
277
277
|
✗ ${i}
|
|
278
278
|
</div>`)}
|
|
279
|
-
${e.duplicates.map(i=>
|
|
279
|
+
${e.duplicates.map(i=>l`<div class="insight-line insight-warn">
|
|
280
280
|
⚠ ${i.name} — loaded ${i.count}x (wasting
|
|
281
|
-
~${
|
|
281
|
+
~${y(i.wastedMs)})
|
|
282
282
|
</div>`)}
|
|
283
|
-
${e.warnings.map(i=>
|
|
284
|
-
${e.tip?
|
|
283
|
+
${e.warnings.map(i=>l`<div class="insight-line insight-warn">⚠ ${i}</div>`)}
|
|
284
|
+
${e.tip?l`<div class="insight-line insight-tip">
|
|
285
285
|
Tip: ${e.tip}
|
|
286
286
|
</div>`:p}
|
|
287
287
|
</div>
|
|
288
288
|
`:p}
|
|
289
289
|
</div>
|
|
290
|
-
`}renderTrafficCard(t){if(
|
|
290
|
+
`}renderTrafficCard(t){if(Yt[t.category||""])return p;let e=St(t.statusCode),s=y(t.pollingDurationMs||t.durationMs),i=!t.isDuplicate&&t.category!==pe&&t.category!==Et||t.requestBody&&t.method!=="GET"||!!t.responseBody;return l`
|
|
291
291
|
<div
|
|
292
292
|
class="traffic-card ${t.isStrictModeDupe?"strict-mode-dupe":""}"
|
|
293
293
|
>
|
|
@@ -297,15 +297,15 @@
|
|
|
297
297
|
>${t.label}</span
|
|
298
298
|
>
|
|
299
299
|
<span class="status-pill ${e}">${t.statusCode}</span>
|
|
300
|
-
<span class="traffic-card-dur">${
|
|
301
|
-
${t.isDuplicate?
|
|
302
|
-
>${
|
|
300
|
+
<span class="traffic-card-dur">${s}</span>
|
|
301
|
+
${t.isDuplicate?l`<span class="traffic-card-dup">duplicate</span>`:l`<span class="traffic-card-size"
|
|
302
|
+
>${ot(t.responseSize)}</span
|
|
303
303
|
>`}
|
|
304
304
|
</div>
|
|
305
|
-
${t.isStrictModeDupe?
|
|
305
|
+
${t.isStrictModeDupe?l`<div class="strict-mode-banner">
|
|
306
306
|
React Strict Mode duplicate — does not happen in production
|
|
307
307
|
</div>`:p}
|
|
308
|
-
${!t.isDuplicate&&t.category!==
|
|
308
|
+
${!t.isDuplicate&&t.category!==pe&&t.category!==Et?l`<div
|
|
309
309
|
class="request-timeline tl-hidden"
|
|
310
310
|
data-request-id=${t.id}
|
|
311
311
|
data-request-started=${String(t.startedAt)}
|
|
@@ -313,42 +313,42 @@
|
|
|
313
313
|
${t.requestBody&&t.method!=="GET"?this.renderBodyToggle("out","Request Body",t.requestBody):p}
|
|
314
314
|
${t.responseBody?this.renderBodyToggle("in","Response Body",t.responseBody):p}
|
|
315
315
|
</div>
|
|
316
|
-
`}renderBodyToggle(t,e,
|
|
316
|
+
`}renderBodyToggle(t,e,s){let i=t==="out"?"\u2192":"\u2190";return l`
|
|
317
317
|
<div class="traffic-body">
|
|
318
318
|
<button class="traffic-body-toggle" @click=${this.toggleBodyBlock}>
|
|
319
319
|
<span class="chevron">▸</span
|
|
320
320
|
><span class="arrow-${t}">${i}</span> ${e}
|
|
321
321
|
</button>
|
|
322
|
-
<pre>${
|
|
322
|
+
<pre>${G(dt(s))}</pre>
|
|
323
323
|
</div>
|
|
324
|
-
`}renderFlowSubReqs(t){return
|
|
325
|
-
${t.requests.map((e,
|
|
326
|
-
</div>`}renderSubReqRow(t,e){let
|
|
324
|
+
`}renderFlowSubReqs(t){return l`<div class="flow-subreqs">
|
|
325
|
+
${t.requests.map((e,s)=>this.renderSubReqRow(e,s))}
|
|
326
|
+
</div>`}renderSubReqRow(t,e){let s=this.expandedSubReqIdx===e,i=St(t.statusCode),n=t.pollingDurationMs?y(t.pollingDurationMs):y(t.durationMs);return l`
|
|
327
327
|
<div
|
|
328
|
-
class="flow-subreq ${
|
|
329
|
-
@click=${
|
|
328
|
+
class="flow-subreq ${s?"expanded":""}"
|
|
329
|
+
@click=${a=>this.toggleSubReq(e,a)}
|
|
330
330
|
>
|
|
331
331
|
<bk-method-badge .method=${t.method}></bk-method-badge>
|
|
332
332
|
<span class="subreq-label ${t.isDuplicate?"is-dup":""}"
|
|
333
333
|
>${t.path||t.url}</span
|
|
334
334
|
>
|
|
335
|
-
${t.isDuplicate?
|
|
335
|
+
${t.isDuplicate?l`<span class="subreq-dup-tag">duplicate</span>`:p}
|
|
336
336
|
<span class="status-pill ${i}">${t.statusCode}</span>
|
|
337
337
|
<span class="subreq-dur">${n}</span>
|
|
338
338
|
</div>
|
|
339
|
-
<div class="flow-subreq-detail ${
|
|
340
|
-
${
|
|
339
|
+
<div class="flow-subreq-detail ${s?"open":""}">
|
|
340
|
+
${s?this.renderSubReqDetail(t):p}
|
|
341
341
|
</div>
|
|
342
|
-
`}renderSubReqDetail(t){let e=
|
|
342
|
+
`}renderSubReqDetail(t){let e=St(t.statusCode);return l`
|
|
343
343
|
<div class="detail-meta">
|
|
344
344
|
<span
|
|
345
|
-
><bk-method-badge .method=${t.method}></bk-method-badge> ${
|
|
345
|
+
><bk-method-badge .method=${t.method}></bk-method-badge> ${it(t.url)}</span
|
|
346
346
|
>
|
|
347
347
|
<span
|
|
348
348
|
><span class="status-pill ${e}">${t.statusCode}</span></span
|
|
349
349
|
>
|
|
350
350
|
<span>${t.durationMs}ms</span>
|
|
351
|
-
${t.responseSize?
|
|
351
|
+
${t.responseSize?l`<span>${ot(t.responseSize)}</span>`:p}
|
|
352
352
|
</div>
|
|
353
353
|
<div
|
|
354
354
|
class="request-timeline tl-hidden"
|
|
@@ -358,30 +358,30 @@
|
|
|
358
358
|
<div class="detail-grid">
|
|
359
359
|
<div class="detail-section">
|
|
360
360
|
<h4>Request Headers</h4>
|
|
361
|
-
<pre>${
|
|
361
|
+
<pre>${G(Tt(t.headers))}</pre>
|
|
362
362
|
</div>
|
|
363
363
|
<div class="detail-section">
|
|
364
364
|
<h4>Response Headers</h4>
|
|
365
|
-
<pre>${
|
|
365
|
+
<pre>${G(Tt(t.responseHeaders))}</pre>
|
|
366
366
|
</div>
|
|
367
367
|
<div class="detail-section">
|
|
368
368
|
<h4>Request Body</h4>
|
|
369
|
-
<pre>${
|
|
369
|
+
<pre>${G(dt(t.requestBody))}</pre>
|
|
370
370
|
</div>
|
|
371
371
|
<div class="detail-section">
|
|
372
372
|
<h4>Response Body</h4>
|
|
373
|
-
<pre>${
|
|
373
|
+
<pre>${G(dt(t.responseBody))}</pre>
|
|
374
374
|
</div>
|
|
375
375
|
</div>
|
|
376
376
|
<div class="detail-actions">
|
|
377
377
|
<button
|
|
378
378
|
class="btn btn-curl"
|
|
379
|
-
@click=${
|
|
379
|
+
@click=${s=>{s.stopPropagation(),Lt(t);}}
|
|
380
380
|
>
|
|
381
381
|
Copy cURL
|
|
382
382
|
</button>
|
|
383
383
|
</div>
|
|
384
|
-
`}};u([I({context:A})],
|
|
384
|
+
`}};u([I({context:A})],V.prototype,"store",2),u([E()],V.prototype,"expandedFlowIdx",2),u([E()],V.prototype,"expandedSubReqIdx",2),u([E()],V.prototype,"flowDetailTab",2),u([E()],V.prototype,"flowTimeline",2),u([E()],V.prototype,"flowTimelineLoading",2),V=u([$("bk-flows-view")],V);var ee=class extends b{createRenderRoot(){return this}connectedCallback(){super.connectedCallback(),this.store.addEventListener("state-changed",()=>this.requestUpdate());}render(){let r=(this.store.state.issues||[]).slice(),t=r.filter(a=>a.state==="open"||a.state==="fixing"||a.state==="regressed"),e=r.filter(a=>a.state==="resolved");if(t.length===0&&e.length===0)return this.store.state.requests.length>0||this.store.state.logs.length>0||this.store.state.queries.length>0?l`
|
|
385
385
|
<div class="sec-clear">
|
|
386
386
|
<span class="sec-clear-icon">\u2713</span>
|
|
387
387
|
<div class="sec-clear-text">
|
|
@@ -389,10 +389,10 @@
|
|
|
389
389
|
<div class="sec-clear-sub">No security or quality issues detected this session</div>
|
|
390
390
|
</div>
|
|
391
391
|
</div>
|
|
392
|
-
`:
|
|
392
|
+
`:l`<bk-empty-state title="Waiting for requests..." subtitle="Start using your app to see security findings here"></bk-empty-state>`;let s=0,i=0,n=0;for(let a of t){let c=a.issue.severity;c==="critical"?s++:c==="info"?n++:i++;}return l`
|
|
393
393
|
<div id="security-content">
|
|
394
|
-
${this.renderSummary(t.length,e.length,
|
|
395
|
-
${t.length===0&&e.length>0?
|
|
394
|
+
${this.renderSummary(t.length,e.length,s,i,n)}
|
|
395
|
+
${t.length===0&&e.length>0?l`
|
|
396
396
|
<div class="sec-clear">
|
|
397
397
|
<span class="sec-clear-icon">\u2713</span>
|
|
398
398
|
<div class="sec-clear-text">
|
|
@@ -404,148 +404,110 @@
|
|
|
404
404
|
${t.length>0?this.renderOpenGroups(t):p}
|
|
405
405
|
${e.length>0?this.renderResolved(e):p}
|
|
406
406
|
</div>
|
|
407
|
-
`}renderSummary(
|
|
407
|
+
`}renderSummary(r,t,e,s,i){return l`
|
|
408
408
|
<div class="sec-summary">
|
|
409
409
|
<div class="sec-summary-left">
|
|
410
|
-
<span class="sec-summary-count">${
|
|
411
|
-
<span class="sec-summary-label">open issue${
|
|
412
|
-
${t>0?
|
|
410
|
+
<span class="sec-summary-count">${r}</span>
|
|
411
|
+
<span class="sec-summary-label">open issue${r!==1?"s":""}</span>
|
|
412
|
+
${t>0?l`<span class="sec-resolved-badge">${t} resolved</span>`:p}
|
|
413
413
|
</div>
|
|
414
414
|
<div class="sec-summary-right">
|
|
415
|
-
${e>0?
|
|
416
|
-
${
|
|
417
|
-
${i>0?
|
|
415
|
+
${e>0?l`<span class="sec-badge critical">${e} critical</span>`:p}
|
|
416
|
+
${s>0?l`<span class="sec-badge warning">${s} warning</span>`:p}
|
|
417
|
+
${i>0?l`<span class="sec-badge info">${i} info</span>`:p}
|
|
418
418
|
</div>
|
|
419
419
|
</div>
|
|
420
|
-
`}renderOpenGroups(
|
|
420
|
+
`}renderOpenGroups(r){let t={},e=[];for(let s of r){let i=s.issue,n=i.rule||i.type;t[n]||(t[n]={rule:n,title:i.title,severity:i.severity,hint:i.hint,items:[]},e.push(n)),t[n].items.push(s);}return e.sort((s,i)=>{let n=Y[t[s].severity]?.sort??2,a=Y[t[i].severity]?.sort??2;return n!==a?n-a:t[i].items.length-t[s].items.length}),l`${e.map(s=>this.renderGroup(t[s]))}`}renderGroup(r){let t=Y[r.severity]||Y.info;return l`
|
|
421
421
|
<div class="sec-group">
|
|
422
422
|
<div class="sec-group-header">
|
|
423
423
|
<span class="sec-group-icon ${t.cls}">${t.icon}</span>
|
|
424
|
-
<span class="sec-group-title">${
|
|
425
|
-
<span class="sec-group-count">${
|
|
424
|
+
<span class="sec-group-title">${r.title}</span>
|
|
425
|
+
<span class="sec-group-count">${r.items.length}</span>
|
|
426
426
|
</div>
|
|
427
|
-
${
|
|
428
|
-
<div class="sec-items">${
|
|
427
|
+
${r.hint?l`<div class="sec-hint">${r.hint}</div>`:p}
|
|
428
|
+
<div class="sec-items">${r.items.map(e=>this.renderIssueItem(e))}</div>
|
|
429
429
|
</div>
|
|
430
|
-
`}renderIssueItem(
|
|
430
|
+
`}renderIssueItem(r){let t=r.issue;return l`
|
|
431
431
|
<div class="sec-item">
|
|
432
432
|
<div class="sec-item-desc">${t.desc}</div>
|
|
433
|
-
${
|
|
434
|
-
${
|
|
435
|
-
${
|
|
433
|
+
${r.occurrences>1?l`<span class="sec-item-count">${r.occurrences}x</span>`:p}
|
|
434
|
+
${r.state==="fixing"&&r.aiStatus==="fixed"?l`<span class="sec-ai-badge sec-ai-fixing">AI fixed \u2014 awaiting verification</span>`:r.aiStatus==="wont_fix"?l`<span class="sec-ai-badge sec-ai-wontfix">AI: won\u2019t fix</span>`:r.state==="regressed"?l`<span class="sec-ai-badge sec-ai-fixing" style="background:var(--red)">regressed</span>`:p}
|
|
435
|
+
${r.aiNotes?l`<div class="sec-ai-notes">${r.aiNotes}</div>`:p}
|
|
436
436
|
</div>
|
|
437
|
-
`}renderResolved(
|
|
437
|
+
`}renderResolved(r){return l`
|
|
438
438
|
<div class="sec-resolved-title">
|
|
439
439
|
<span class="sec-resolved-check">\u2713</span> Resolved
|
|
440
|
-
<span class="sec-resolved-count">${
|
|
440
|
+
<span class="sec-resolved-count">${r.length}</span>
|
|
441
441
|
</div>
|
|
442
442
|
<div class="sec-group sec-group-resolved">
|
|
443
443
|
<div class="sec-items">
|
|
444
|
-
${
|
|
444
|
+
${r.map(t=>l`
|
|
445
445
|
<div class="sec-item sec-item-resolved">
|
|
446
446
|
<span class="sec-resolved-item-icon">\u2713</span>
|
|
447
447
|
<div class="sec-item-desc">${t.issue.title} \u2014 ${t.issue.endpoint||"global"}</div>
|
|
448
|
-
${t.aiStatus==="fixed"?
|
|
449
|
-
${t.aiNotes?
|
|
448
|
+
${t.aiStatus==="fixed"?l`<span class="sec-ai-badge sec-ai-verified">Verified fix</span>`:p}
|
|
449
|
+
${t.aiNotes?l`<div class="sec-ai-notes">${t.aiNotes}</div>`:p}
|
|
450
450
|
</div>
|
|
451
451
|
`)}
|
|
452
452
|
</div>
|
|
453
453
|
</div>
|
|
454
|
-
`}};u([I({context:A})],
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
</div>`:p}
|
|
463
|
-
${m.length===0&&x.length>0?a`<div class="ov-clear">
|
|
464
|
-
<span class="ov-clear-icon">\u2713</span>All issues resolved \u2014
|
|
465
|
-
${x.length} finding${x.length!==1?"s were":" was"} detected and
|
|
466
|
-
fixed
|
|
467
|
-
</div>`:p}
|
|
468
|
-
${m.length>0?this.renderOpenIssues(m):p}
|
|
469
|
-
${v.length>0?this.renderVerifying(v):p}
|
|
470
|
-
${x.length>0?this.renderResolvedIssues(x):p}
|
|
471
|
-
</div>
|
|
472
|
-
`}renderSummary(t,e,r,i,n,l){return a`
|
|
473
|
-
<div class="ov-summary">
|
|
474
|
-
<div class="ov-stat"><span class="ov-stat-value">${t}</span><span class="ov-stat-label">Requests</span></div>
|
|
475
|
-
<div class="ov-stat"><span class="ov-stat-value">${e}</span><span class="ov-stat-label">Actions</span></div>
|
|
476
|
-
<div class="ov-stat"><span class="ov-stat-value">${_(r)}</span><span class="ov-stat-label">Avg Response</span></div>
|
|
477
|
-
<div class="ov-stat"><span class="ov-stat-value">${i}</span><span class="ov-stat-label">Queries</span></div>
|
|
478
|
-
<div class="ov-stat"><span class="ov-stat-value" style="color:${n>0?"var(--red)":"var(--green)"}">${n}</span><span class="ov-stat-label">Errors</span></div>
|
|
479
|
-
<div class="ov-stat"><span class="ov-stat-value">${l}</span><span class="ov-stat-label">Fetches</span></div>
|
|
480
|
-
</div>
|
|
481
|
-
`}renderOpenIssues(t){return a`
|
|
482
|
-
<div class="ov-section-title">Issues Found <span class="ov-issue-count">${t.length}</span></div>
|
|
483
|
-
<div class="ov-cards">${t.map((e,r)=>this.renderIssueCard(e,r))}</div>
|
|
484
|
-
`}renderIssueCard(t,e){let r=t.issue,i=Z[r.severity]||Z.info,n=this.expandedCardIdx===e,l=t.aiStatus==="wont_fix"?a`<span class="sec-ai-badge sec-ai-wontfix">AI: won\u2019t fix</span>`:t.state==="regressed"?a`<span class="sec-ai-badge sec-ai-fixing" style="background:var(--red)">regressed</span>`:p,c=t.cleanHitsSinceLastSeen>0?a`<div class="ov-card-resolving">Resolving\u2026 ${t.cleanHitsSinceLastSeen}/${5} clean requests</div>`:p;return a`
|
|
485
|
-
<div class="ov-card ${n?"expanded":""}" @click=${()=>this.toggleCard(e)}>
|
|
486
|
-
<span class="ov-card-icon ${i.cls}">${i.icon}</span>
|
|
487
|
-
<div class="ov-card-body">
|
|
488
|
-
<div class="ov-card-title">${r.title}${l}</div>
|
|
489
|
-
<div class="ov-card-desc">${r.desc}</div>
|
|
490
|
-
${r.detail?a`<div class="ov-card-detail">${r.detail}</div>`:p}
|
|
491
|
-
${c}
|
|
492
|
-
${n&&r.hint?a`<div class="ov-card-hint">${r.hint}</div>`:p}
|
|
454
|
+
`}};u([I({context:A})],ee.prototype,"store",2),ee=u([$("bk-security-view")],ee);var J=class extends b{constructor(){super(...arguments);this.handleStateChanged=()=>this.requestUpdate();}createRenderRoot(){return this}connectedCallback(){super.connectedCallback(),this.store.addEventListener("state-changed",this.handleStateChanged);}disconnectedCallback(){super.disconnectedCallback(),this.store.removeEventListener("state-changed",this.handleStateChanged);}};u([I({context:A})],J.prototype,"store",2);async function Q(o){let r=await fetch(o);if(!r.ok)throw new Error(`HTTP ${r.status}: ${o}`);return r.json()}function B(o,r,t){return o===1?r:t??`${r}s`}function ir(o){return o.state!=="stale"}function $e(o){return (o.state==="open"||o.state==="regressed")&&o.aiStatus!=="wont_fix"}var se=class extends J{constructor(){super(...arguments);this.graphSummary=null;}connectedCallback(){super.connectedCallback(),this.loadGraphSummary();}async loadGraphSummary(){try{let e=(await Q(`${T.graph}?level=endpoints`)).nodes||[];this.graphSummary={endpoints:e.filter(s=>s.type==="endpoint").length,tables:e.filter(s=>s.type==="table").length,externals:e.filter(s=>s.type==="external").length};}catch{}}navigateTo(t){this.store.setActiveView(t);}navigateToExplorerErrors(){this.store.setActiveView("explorer"),window.dispatchEvent(new CustomEvent("navigate-explorer",{detail:"errors"}));}render(){let t=this.store.state,e=t.requests.filter(i=>!i.isStatic&&!i.isHealthCheck&&(!i.path||i.path.indexOf(U)!==0));return e.length>0||t.queries.length>0?l`
|
|
455
|
+
<div class="ov-container">
|
|
456
|
+
<div class="ov-grid">
|
|
457
|
+
${this.renderActionsCard(t.flows)}
|
|
458
|
+
${this.renderInsightsCard(t.issues)}
|
|
459
|
+
${this.renderPerformanceCard(e)}
|
|
460
|
+
${this.renderErrorsCard(e)}
|
|
461
|
+
${this.renderGraphCard()}
|
|
493
462
|
</div>
|
|
494
|
-
${r.hint?a`<span class="ov-card-arrow">${n?"\u2193":"\u2192"}</span>`:p}
|
|
495
|
-
</div>
|
|
496
|
-
`}renderVerifying(t){return a`
|
|
497
|
-
<div class="ov-section-title ov-resolved-title">
|
|
498
|
-
<span style="color:var(--yellow,#f5a623)">\u29d7</span> Awaiting Verification
|
|
499
|
-
<span class="ov-issue-count">${t.length}</span>
|
|
500
|
-
</div>
|
|
501
|
-
<div class="ov-cards">
|
|
502
|
-
${t.map(e=>{let r=e.issue,i=e.cleanHitsSinceLastSeen>0?a`<div class="ov-card-resolving">Verifying\u2026 ${e.cleanHitsSinceLastSeen}/${5} clean requests</div>`:p;return a`
|
|
503
|
-
<div class="ov-card ov-card-resolved">
|
|
504
|
-
<span class="ov-card-icon resolved">\u29d7</span>
|
|
505
|
-
<div class="ov-card-body">
|
|
506
|
-
<div class="ov-card-title" style="color:var(--text-muted)">
|
|
507
|
-
${r.title}
|
|
508
|
-
<span class="sec-ai-badge sec-ai-fixing">AI fixed \u2014 awaiting verification</span>
|
|
509
|
-
</div>
|
|
510
|
-
<div class="ov-card-desc">${r.desc}</div>
|
|
511
|
-
${i}
|
|
512
|
-
</div>
|
|
513
|
-
</div>
|
|
514
|
-
`})}
|
|
515
463
|
</div>
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
<div class="ov-card
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
464
|
+
`:l`<bk-empty-state
|
|
465
|
+
title="${O.EMPTY_TITLE}"
|
|
466
|
+
subtitle="${O.EMPTY_SUBTITLE_OVERVIEW}"
|
|
467
|
+
></bk-empty-state>`}renderActionsCard(t){let e=t.length;if(e===0)return this.renderEmptyCard("\u25B6","Actions",O.NO_ACTIONS,"actions");let s=Math.round(t.reduce((n,a)=>n+a.totalDurationMs,0)/e),i=t[0]?.label||"";return this.renderCard("\u25B6",`${e} ${B(e,"action")}`,`avg ${y(s)}${i?` \xB7 "${i}"`:""}`,"actions","var(--accent)")}renderInsightsCard(t){let e=(t||[]).filter($e);if(e.length===0)return this.renderCard("\u2713",O.ALL_CLEAR,O.ALL_CLEAR_DETAIL,"insights","var(--green)");let s=e.filter(a=>a.issue.severity==="critical").length,i=e.filter(a=>a.issue.severity==="warning").length,n=[];return s>0&&n.push(`${s} critical`),i>0&&n.push(`${i} warning`),this.renderCard("\u{1F4A1}",`${e.length} ${B(e.length,"issue")}`,n.join(" \xB7 ")||O.REVIEW_RECOMMENDED,"insights","var(--amber)")}renderPerformanceCard(t){if(t.length===0)return this.renderEmptyCard("\u26A1","Performance",O.NO_REQUEST_DATA,"performance");let e=new Map;for(let n of t){let a=`${n.method} ${n.path}`,c=e.get(a);c||(c=[],e.set(a,c)),c.push(n.durationMs);}let s=[];for(let[n,a]of e){let c=Math.round(a.reduce((d,h)=>d+h,0)/a.length);c>2e3&&s.push({key:n,avg:c});}if(s.sort((n,a)=>a.avg-n.avg),s.length===0)return this.renderCard("\u26A1",O.ALL_FAST,O.NO_SLOW_ENDPOINTS,"performance","var(--green)");let i=s[0];return this.renderCard("\u26A1",`${s.length} slow ${B(s.length,"endpoint")}`,`worst: ${i.key} \xB7 ${y(i.avg)}`,"performance","var(--amber)")}renderErrorsCard(t){let e=t.filter(n=>n.statusCode>=400);if(e.length===0)return this.renderCard("\u2713",O.ZERO_ERRORS,O.ALL_REQUESTS_OK,"explorer","var(--green)");let s=new Map;for(let n of e)s.set(n.statusCode,(s.get(n.statusCode)||0)+1);let i=[...s.entries()].sort((n,a)=>a[1]-n[1]).slice(0,3).map(([n,a])=>`${a}\xD7 ${n}`);return l`
|
|
468
|
+
<div class="ov-card-nav" @click=${()=>this.navigateToExplorerErrors()}>
|
|
469
|
+
<div class="ov-card-icon-lg" style="color:var(--red)">\u2715</div>
|
|
470
|
+
<div class="ov-card-content">
|
|
471
|
+
<div class="ov-card-headline">${e.length} ${B(e.length,"error")}</div>
|
|
472
|
+
<div class="ov-card-context">${i.join(" \xB7 ")}</div>
|
|
473
|
+
</div>
|
|
474
|
+
<span class="ov-card-arrow">\u2192</span>
|
|
475
|
+
</div>
|
|
476
|
+
`}renderGraphCard(){let t=this.graphSummary;if(!t||t.endpoints===0&&t.tables===0)return this.renderEmptyCard("\u25CE","Graph",O.BUILD_GRAPH,"graph");let e=[];t.endpoints>0&&e.push(`${t.endpoints} ${B(t.endpoints,"endpoint")}`),t.tables>0&&e.push(`${t.tables} ${B(t.tables,"table")}`);let s=t.externals>0?`${t.externals} external ${B(t.externals,"service")}`:"";return this.renderCard("\u25CE",e.join(" \xB7 "),s,"graph","var(--accent)")}renderCard(t,e,s,i,n){return l`
|
|
477
|
+
<div class="ov-card-nav" @click=${()=>this.navigateTo(i)}>
|
|
478
|
+
<div class="ov-card-icon-lg" style="color:${n}">${t}</div>
|
|
479
|
+
<div class="ov-card-content">
|
|
480
|
+
<div class="ov-card-headline">${e}</div>
|
|
481
|
+
${s?l`<div class="ov-card-context">${s}</div>`:p}
|
|
482
|
+
</div>
|
|
483
|
+
<span class="ov-card-arrow">\u2192</span>
|
|
484
|
+
</div>
|
|
485
|
+
`}renderEmptyCard(t,e,s,i){return l`
|
|
486
|
+
<div class="ov-card-nav ov-card-empty" @click=${()=>this.navigateTo(i)}>
|
|
487
|
+
<div class="ov-card-icon-lg">${t}</div>
|
|
488
|
+
<div class="ov-card-content">
|
|
489
|
+
<div class="ov-card-headline">${e}</div>
|
|
490
|
+
<div class="ov-card-context">${s}</div>
|
|
491
|
+
</div>
|
|
492
|
+
<span class="ov-card-arrow">\u2192</span>
|
|
531
493
|
</div>
|
|
532
|
-
`}};u([
|
|
494
|
+
`}};u([E()],se.prototype,"graphSummary",2),se=u([$("bk-overview-view")],se);function or(o){return o<1?"<1ms":o<1e3?Math.round(o)+"ms":(o/1e3).toFixed(1)+"s"}function ui(o){return o<yt?Xt.green:o<_t?Xt.amber:Xt.red}function mi(o){return o.statusCode>=400?Xt.red:ui(o.durationMs)}function nr(o){return [parseInt(o.slice(1,3),16),parseInt(o.slice(3,5),16),parseInt(o.slice(5,7),16)]}function fi(o){let r=o.getContext("2d");if(!r)return null;let t=window.devicePixelRatio||1,e=o.clientWidth,s=o.clientHeight;return o.width=e*t,o.height=s*t,r.scale(t,t),{ctx:r,w:e,h:s}}function vi(o,r,t,e,s){let[i,n,a]=nr(s);o.beginPath(),o.arc(r,t,e+2,0,Math.PI*2),o.fillStyle=`rgba(${i},${n},${a},0.25)`,o.fill(),o.beginPath(),o.arc(r,t,e,0,Math.PI*2),o.fillStyle=s,o.fill();}function gi(o,r,t,e,s,i){let[n,a,c]=nr(s);o.strokeStyle=`rgba(${n},${a},${c},0.3)`,o.lineWidth=i+2,o.beginPath(),o.moveTo(r-e,t-e),o.lineTo(r+e,t+e),o.moveTo(r+e,t-e),o.lineTo(r-e,t+e),o.stroke(),o.strokeStyle=s,o.lineWidth=i,o.beginPath(),o.moveTo(r-e,t-e),o.lineTo(r+e,t+e),o.moveTo(r+e,t-e),o.lineTo(r-e,t+e),o.stroke();}function ar(o,r){let t=[],e=fi(o);if(!e||r.length===0)return t;let{ctx:s,w:i,h:n}=e,a=Gs,c=i-a.left-a.right,d=n-a.top-a.bottom,h=0,m=r[0].timestamp,f=r[0].timestamp;for(let _ of r)_.durationMs>h&&(h=_.durationMs),_.timestamp<m&&(m=_.timestamp),_.timestamp>f&&(f=_.timestamp);h=Math.max(h,10),h=Math.ceil(h*1.15/10)*10;let S=f-m||1;s.strokeStyle=Us,s.lineWidth=1;let R=4;for(let _=0;_<=R;_++){let v=a.top+d-_/R*d;s.beginPath(),s.moveTo(a.left,v),s.lineTo(a.left+c,v),s.stroke(),s.fillStyle=Xe,s.font=Fs,s.textAlign="right",s.fillText(or(Math.round(_/R*h)),a.left-8,v+3);}for(let _ of [{ms:yt},{ms:_t}]){if(_.ms>=h)continue;let v=a.top+d-_.ms/h*d;s.beginPath(),s.setLineDash([4,4]),s.strokeStyle="rgba(113,113,122,0.3)",s.lineWidth=1,s.moveTo(a.left,v),s.lineTo(a.left+c,v),s.stroke(),s.setLineDash([]),s.fillStyle="rgba(113,113,122,0.5)",s.font=Ke,s.textAlign="left",s.fillText(or(_.ms),a.left+c+2,v+3);}for(let _=0;_<r.length;_++){let v=r[_],g=r.length===1?a.left+c/2:a.left+(v.timestamp-m)/S*c,x=a.top+d-v.durationMs/h*d,w=mi(v);t.push({x:g,y:x,idx:_,r:v}),v.statusCode>=400?gi(s,g,x,4,w,2):vi(s,g,x,4,w);}s.fillStyle=Xe,s.font=Ke,s.textAlign="center";let P=[m,m+S/2,f];for(let _=0;_<P.length;_++){let v=a.left+_/2*c,g=new Date(P[_]);s.fillText(g.toLocaleTimeString([],{hour:"2-digit",minute:"2-digit",second:"2-digit"}),v,a.top+d+14);}return t}var xi={max:1/0,label:"Pending",color:"var(--text-muted)",bg:"var(--bg-muted)",border:"var(--border)"};function lr(o,r,t){return t>=20?o:r}function ss(o,r){if(!r||r<=0)return xi;let t=o/r;return t<.7?xt[0]:t<1.2?xt[1]:t<2?xt[2]:t<3?xt[3]:xt[4]}var X=class extends b{constructor(){super(...arguments);this.selectedEndpoint=rt;this.graphData=[];this.loadError=false;this.queryBreakdown=[];this.queryBreakdownLoading=false;this.scatterDots=[];}createRenderRoot(){return this}connectedCallback(){super.connectedCallback(),this.store.addEventListener("state-changed",()=>this.requestUpdate()),this.loadMetrics();}async loadMetrics(){try{let e=await(await fetch(T.metricsLive)).json();this.graphData=e.endpoints||[],this.loadError=!1,(!this.selectedEndpoint||this.selectedEndpoint===rt)&&(this.selectedEndpoint=rt);}catch{this.loadError=true;}}healthGradeForEndpoint(t){let e=lr(t.summary.p95Ms,t.summary.medianMs,t.summary.totalRequests);return ss(e,t.baselineP95Ms)}healthGradeForDuration(t,e){return ss(t,e)}getCallers(t){let e=this.store.state.flows,s=new Map;for(let i of e)for(let n of i.requests)if(`${n.method} ${n.path}`===t||this.normalizeEndpoint(n)===t){let c=s.get(i.label);c?(c.count++,c.totalMs+=n.durationMs):s.set(i.label,{count:1,totalMs:n.durationMs});}return [...s.entries()].map(([i,n])=>({label:i,count:n.count,avgMs:Math.round(n.totalMs/n.count)})).sort((i,n)=>n.count-i.count)}normalizeEndpoint(t){let s=t.path.split("?")[0].split("/").map(i=>{if(!i)return i;let n=i.length>0;for(let a=0;a<i.length;a++){let c=i.charCodeAt(a);if(c<48||c>57){n=false;break}}return n||i.length===36&&i[8]==="-"&&i[13]==="-"&&i[18]==="-"&&i[23]==="-"?":id":i}).join("/");return `${t.method} ${s}`}async loadQueryBreakdown(t){if(this.queryBreakdownLoading)return;let s=this.store.state.requests.filter(i=>`${i.method} ${i.path}`===t||this.normalizeEndpoint(i)===t).slice(-20).map(i=>i.id).filter(Boolean);if(s.length===0){this.queryBreakdown=[];return}this.queryBreakdownLoading=true;try{let i=await fetch(`${T.activity}?requestIds=${s.join(",")}`);if(!i.ok){this.queryBreakdownLoading=!1;return}let n=await i.json(),a=new Map;for(let c of Object.values(n.activities))for(let d of c.timeline){if(d.type!=="query")continue;let h=d.data,m=(h.normalizedOp||h.operation||"QUERY").toUpperCase(),f=h.table||h.model||"",S=`${m} ${f}`.trim(),R=a.get(S);R?(R.totalMs+=h.durationMs,R.count++):a.set(S,{label:S,totalMs:h.durationMs,count:1});}this.queryBreakdown=[...a.values()].map(c=>({...c,avgMs:Math.round(c.totalMs/c.count)})).sort((c,d)=>d.totalMs-c.totalMs);}catch{}this.queryBreakdownLoading=false;}renderScatterChart(t,e){this.scatterDots=ar(t,e),t.style.cursor="pointer",t.onclick=s=>{let i=t.getBoundingClientRect(),n=s.clientX-i.left,a=s.clientY-i.top,c=null,d=1/0;for(let h of this.scatterDots){let m=Math.sqrt((h.x-n)**2+(h.y-a)**2);m<d&&(d=m,c=h);}c&&d<16&&this.highlightRow(c.idx);};}highlightRow(t){let e=this.querySelector(".perf-hist-row-hl");e&&e.classList.remove("perf-hist-row-hl");let s=this.querySelector(`[data-req-idx="${t}"]`);s&&(s.classList.add("perf-hist-row-hl"),s.scrollIntoView({behavior:"smooth",block:"center"}));}updated(){if(this.selectedEndpoint===rt)return;let t=this.querySelector("#perf-detail-canvas");if(t){let e=this.graphData.find(s=>s.endpoint===this.selectedEndpoint);e&&this.renderScatterChart(t,e.requests);}}render(){return !this.graphData||this.graphData.length===0?l`<bk-empty-state title="No performance data yet" subtitle="Hit some endpoints and data will appear here"></bk-empty-state>`:l`
|
|
533
495
|
<div id="graph-content">
|
|
534
496
|
${this.renderSelector()}
|
|
535
|
-
${this.selectedEndpoint===
|
|
497
|
+
${this.selectedEndpoint===rt?this.renderOverview():this.renderDetail()}
|
|
536
498
|
</div>
|
|
537
|
-
`}renderSelector(){return
|
|
499
|
+
`}renderSelector(){return l`
|
|
538
500
|
<div class="perf-selector">
|
|
539
|
-
<button class="perf-selector-btn ${this.selectedEndpoint===
|
|
540
|
-
@click=${()=>{this.selectedEndpoint=
|
|
541
|
-
${this.graphData.map((t,e)=>
|
|
501
|
+
<button class="perf-selector-btn ${this.selectedEndpoint===rt?"active":""}"
|
|
502
|
+
@click=${()=>{this.selectedEndpoint=rt;}}>Overview</button>
|
|
503
|
+
${this.graphData.map((t,e)=>l`
|
|
542
504
|
<button class="perf-selector-btn ${t.endpoint===this.selectedEndpoint?"active":""}"
|
|
543
505
|
@click=${()=>{this.selectedEndpoint=t.endpoint,this.queryBreakdown=[],this.loadQueryBreakdown(t.endpoint);}}>
|
|
544
|
-
<span class="perf-dot" style="background:${
|
|
506
|
+
<span class="perf-dot" style="background:${Ve[e%Ve.length]}"></span>${t.endpoint}
|
|
545
507
|
</button>
|
|
546
508
|
`)}
|
|
547
509
|
</div>
|
|
548
|
-
`}renderOverview(){let t=this.graphData.filter(c=>c.requests.length>0);if(t.length===0)return p;let e=t.reduce((c,d)=>c+d.summary.totalRequests,0),
|
|
510
|
+
`}renderOverview(){let t=this.graphData.filter(c=>c.requests.length>0);if(t.length===0)return p;let e=t.reduce((c,d)=>c+d.summary.totalRequests,0),s=e>0?Math.round(t.reduce((c,d)=>c+d.summary.p95Ms*d.summary.totalRequests,0)/e):0,i=t.reduce((c,d)=>c+Math.round(d.summary.errorRate*d.summary.totalRequests),0),n=e>0?i/e:0,a=t[0];return l`
|
|
549
511
|
<div class="perf-overview">
|
|
550
512
|
<div class="perf-summary-row">
|
|
551
513
|
<div class="perf-summary-card">
|
|
@@ -554,7 +516,7 @@
|
|
|
554
516
|
</div>
|
|
555
517
|
<div class="perf-summary-card">
|
|
556
518
|
<span class="perf-summary-label">Avg P95</span>
|
|
557
|
-
<span class="perf-summary-value" style="color:${this.healthGradeForDuration(
|
|
519
|
+
<span class="perf-summary-value" style="color:${this.healthGradeForDuration(s).color}">${y(s)}</span>
|
|
558
520
|
</div>
|
|
559
521
|
<div class="perf-summary-card">
|
|
560
522
|
<span class="perf-summary-label">Error Rate</span>
|
|
@@ -562,7 +524,7 @@
|
|
|
562
524
|
</div>
|
|
563
525
|
<div class="perf-summary-card">
|
|
564
526
|
<span class="perf-summary-label">Slowest</span>
|
|
565
|
-
<span class="perf-summary-value perf-summary-value-sm">${
|
|
527
|
+
<span class="perf-summary-value perf-summary-value-sm">${a?.endpoint??"-"}</span>
|
|
566
528
|
</div>
|
|
567
529
|
</div>
|
|
568
530
|
|
|
@@ -582,50 +544,50 @@
|
|
|
582
544
|
</tbody>
|
|
583
545
|
</table>
|
|
584
546
|
</div>
|
|
585
|
-
`}renderHeatmapRow(t){let e=t.summary,
|
|
547
|
+
`}renderHeatmapRow(t){let e=t.summary,s=this.healthGradeForEndpoint(t),i=Math.round(e.errorRate*e.totalRequests),n=(e.avgQueryTimeMs||0)+(e.avgFetchTimeMs||0)+(e.avgAppTimeMs||0),a=0,c=0,d=100;return n>0&&(a=Math.round((e.avgQueryTimeMs||0)/n*100),c=Math.round((e.avgFetchTimeMs||0)/n*100),d=Math.max(0,100-a-c)),l`
|
|
586
548
|
<tr class="perf-table-row" @click=${()=>{this.selectedEndpoint=t.endpoint,this.queryBreakdown=[],this.loadQueryBreakdown(t.endpoint);}}>
|
|
587
549
|
<td class="perf-td-name">${t.endpoint}</td>
|
|
588
550
|
<td class="perf-td-right">${e.totalRequests}</td>
|
|
589
551
|
<td class="perf-td-center">
|
|
590
|
-
<span class="perf-hm-p95" style="color:${
|
|
552
|
+
<span class="perf-hm-p95" style="color:${s.color};background:${s.bg};border-color:${s.border}">${y(e.p95Ms)}</span>
|
|
591
553
|
</td>
|
|
592
554
|
<td class="perf-td-center" style="color:${i>0?"var(--red)":"var(--text-muted)"}">${i>0?i:"-"}</td>
|
|
593
555
|
<td class="perf-td-center" style="color:${e.avgQueryCount>5?"var(--amber)":"var(--text-muted)"}">${e.avgQueryCount}</td>
|
|
594
556
|
<td>
|
|
595
557
|
<span class="perf-hm-split-bar">
|
|
596
|
-
${
|
|
597
|
-
${c>0?
|
|
598
|
-
${d>0?
|
|
558
|
+
${a>0?l`<span class="perf-breakdown-seg perf-breakdown-db" style="width:${a}%"></span>`:p}
|
|
559
|
+
${c>0?l`<span class="perf-breakdown-seg perf-breakdown-fetch" style="width:${c}%"></span>`:p}
|
|
560
|
+
${d>0?l`<span class="perf-breakdown-seg perf-breakdown-app" style="width:${d}%"></span>`:p}
|
|
599
561
|
</span>
|
|
600
562
|
</td>
|
|
601
563
|
</tr>
|
|
602
|
-
`}renderDetail(){let t=this.graphData.find(n=>n.endpoint===this.selectedEndpoint);if(!t?.requests?.length)return
|
|
603
|
-
${this.renderDetailHeader(t,
|
|
604
|
-
${this.renderDetailMetrics(e,
|
|
564
|
+
`}renderDetail(){let t=this.graphData.find(n=>n.endpoint===this.selectedEndpoint);if(!t?.requests?.length)return l`<bk-empty-state subtitle="No data for this endpoint"></bk-empty-state>`;let e=t.summary,s=this.healthGradeForEndpoint(t),i=Math.round(e.errorRate*e.totalRequests);return l`
|
|
565
|
+
${this.renderDetailHeader(t,s)}
|
|
566
|
+
${this.renderDetailMetrics(e,s,i)}
|
|
605
567
|
${this.renderDetailBreakdown(e)}
|
|
606
568
|
${this.renderCallers(t.endpoint)}
|
|
607
569
|
${this.renderQueryBreakdown()}
|
|
608
570
|
${this.renderTrends(t)}
|
|
609
571
|
${this.renderDetailChart()}
|
|
610
572
|
${this.renderDetailHistory(t)}
|
|
611
|
-
`}renderDetailHeader(t,e){return
|
|
573
|
+
`}renderDetailHeader(t,e){return l`
|
|
612
574
|
<div class="perf-detail-header">
|
|
613
575
|
<div class="perf-detail-title">
|
|
614
576
|
<span class="perf-badge perf-badge-lg" style="color:${e.color};background:${e.bg};border-color:${e.border}">${e.label}</span>
|
|
615
577
|
<span>${t.endpoint}</span>
|
|
616
|
-
${t.baselineP95Ms?
|
|
578
|
+
${t.baselineP95Ms?l`<span class="perf-baseline-hint">Baseline: ${y(t.baselineP95Ms)}</span>`:p}
|
|
617
579
|
</div>
|
|
618
580
|
</div>
|
|
619
|
-
`}renderDetailMetrics(t,e,
|
|
581
|
+
`}renderDetailMetrics(t,e,s){return l`
|
|
620
582
|
<div class="perf-metric-row">
|
|
621
583
|
<div class="perf-metric-card">
|
|
622
584
|
<span class="perf-metric-label">P95</span>
|
|
623
|
-
<span class="perf-metric-value" style="color:${e.color}">${
|
|
585
|
+
<span class="perf-metric-value" style="color:${e.color}">${y(t.p95Ms)}</span>
|
|
624
586
|
</div>
|
|
625
587
|
<div class="perf-metric-card">
|
|
626
588
|
<span class="perf-metric-label">Errors</span>
|
|
627
|
-
<span class="perf-metric-value" style="color:${
|
|
628
|
-
${
|
|
589
|
+
<span class="perf-metric-value" style="color:${s>0?"var(--red)":"var(--green)"}">
|
|
590
|
+
${s>0?s+" ("+Math.round(t.errorRate*100)+"%)":"0"}
|
|
629
591
|
</span>
|
|
630
592
|
</div>
|
|
631
593
|
<div class="perf-metric-card">
|
|
@@ -633,71 +595,71 @@
|
|
|
633
595
|
<span class="perf-metric-value" style="color:${t.avgQueryCount>5?"var(--amber)":"var(--text)"}">${t.avgQueryCount}</span>
|
|
634
596
|
</div>
|
|
635
597
|
</div>
|
|
636
|
-
`}renderDetailBreakdown(t){let e=(t.avgQueryTimeMs||0)+(t.avgFetchTimeMs||0)+(t.avgAppTimeMs||0);if(e<=0)return p;let
|
|
598
|
+
`}renderDetailBreakdown(t){let e=(t.avgQueryTimeMs||0)+(t.avgFetchTimeMs||0)+(t.avgAppTimeMs||0);if(e<=0)return p;let s=Math.round((t.avgQueryTimeMs||0)/e*100),i=Math.round((t.avgFetchTimeMs||0)/e*100),n=Math.max(0,100-s-i);return l`
|
|
637
599
|
<div class="perf-breakdown">
|
|
638
600
|
<div class="perf-section-title">Time Breakdown</div>
|
|
639
601
|
<div class="perf-breakdown-bar">
|
|
640
|
-
${
|
|
641
|
-
${i>0?
|
|
642
|
-
${n>0?
|
|
602
|
+
${s>0?l`<div class="perf-breakdown-seg perf-breakdown-db" style="width:${s}%"></div>`:p}
|
|
603
|
+
${i>0?l`<div class="perf-breakdown-seg perf-breakdown-fetch" style="width:${i}%"></div>`:p}
|
|
604
|
+
${n>0?l`<div class="perf-breakdown-seg perf-breakdown-app" style="width:${n}%"></div>`:p}
|
|
643
605
|
</div>
|
|
644
606
|
<div class="perf-breakdown-legend">
|
|
645
|
-
<span class="perf-breakdown-item"><span class="perf-breakdown-dot perf-breakdown-db"></span>DB ${
|
|
646
|
-
<span class="perf-breakdown-item"><span class="perf-breakdown-dot perf-breakdown-fetch"></span>Fetch ${
|
|
647
|
-
<span class="perf-breakdown-item"><span class="perf-breakdown-dot perf-breakdown-app"></span>App ${
|
|
607
|
+
<span class="perf-breakdown-item"><span class="perf-breakdown-dot perf-breakdown-db"></span>DB ${y(t.avgQueryTimeMs||0)} (${s}%)</span>
|
|
608
|
+
<span class="perf-breakdown-item"><span class="perf-breakdown-dot perf-breakdown-fetch"></span>Fetch ${y(t.avgFetchTimeMs||0)} (${i}%)</span>
|
|
609
|
+
<span class="perf-breakdown-item"><span class="perf-breakdown-dot perf-breakdown-app"></span>App ${y(t.avgAppTimeMs||0)} (${n}%)</span>
|
|
648
610
|
</div>
|
|
649
611
|
</div>
|
|
650
|
-
`}renderCallers(t){let e=this.getCallers(t);return e.length===0?p:
|
|
612
|
+
`}renderCallers(t){let e=this.getCallers(t);return e.length===0?p:l`
|
|
651
613
|
<div class="perf-callers">
|
|
652
614
|
<div class="perf-section-title">Called By</div>
|
|
653
615
|
<div class="perf-callers-list">
|
|
654
|
-
${e.map(
|
|
616
|
+
${e.map(s=>l`
|
|
655
617
|
<div class="perf-caller-row">
|
|
656
|
-
<span class="perf-caller-name">${
|
|
657
|
-
<span class="perf-caller-count">${
|
|
658
|
-
<span class="perf-caller-avg">avg ${
|
|
618
|
+
<span class="perf-caller-name">${s.label}</span>
|
|
619
|
+
<span class="perf-caller-count">${s.count} call${s.count!==1?"s":""}</span>
|
|
620
|
+
<span class="perf-caller-avg">avg ${y(s.avgMs)}</span>
|
|
659
621
|
</div>
|
|
660
622
|
`)}
|
|
661
623
|
</div>
|
|
662
624
|
</div>
|
|
663
|
-
`}renderQueryBreakdown(){return this.queryBreakdownLoading?
|
|
625
|
+
`}renderQueryBreakdown(){return this.queryBreakdownLoading?l`<div class="perf-queries"><div class="perf-section-title">DB Queries</div><div class="perf-queries-loading">Loading...</div></div>`:this.queryBreakdown.length===0?p:l`
|
|
664
626
|
<div class="perf-queries">
|
|
665
627
|
<div class="perf-section-title">DB Queries</div>
|
|
666
628
|
<div class="perf-queries-list">
|
|
667
|
-
${this.queryBreakdown.map(t=>
|
|
629
|
+
${this.queryBreakdown.map(t=>l`
|
|
668
630
|
<div class="perf-query-row">
|
|
669
631
|
<span class="perf-query-label">${t.label}</span>
|
|
670
|
-
<span class="perf-query-avg">avg ${
|
|
632
|
+
<span class="perf-query-avg">avg ${y(t.avgMs)}</span>
|
|
671
633
|
<span class="perf-query-count">${t.count} call${t.count!==1?"s":""}</span>
|
|
672
634
|
</div>
|
|
673
635
|
`)}
|
|
674
636
|
</div>
|
|
675
637
|
</div>
|
|
676
|
-
`}renderTrends(t){let e=t.sessions;if(!e||e.length===0)return p;let
|
|
638
|
+
`}renderTrends(t){let e=t.sessions;if(!e||e.length===0)return p;let s=e.slice(-10);return l`
|
|
677
639
|
<div class="perf-trends">
|
|
678
640
|
<div class="perf-section-title">Session Trend</div>
|
|
679
641
|
<div class="perf-trends-list">
|
|
680
|
-
${
|
|
642
|
+
${s.map((i,n)=>{let a=n>0?s[n-1].p95DurationMs:null,c=a!==null?i.p95DurationMs>a*1.2?"slower":i.p95DurationMs<a*.8?"faster":"":"",d=this.formatTimeAgo(i.startedAt),h=n===s.length-1,m=this.healthGradeForDuration(i.p95DurationMs,t.baselineP95Ms);return l`
|
|
681
643
|
<div class="perf-trend-row ${h?"perf-trend-current":""}">
|
|
682
644
|
<span class="perf-trend-time">${h?"Current":d}</span>
|
|
683
645
|
<span class="perf-trend-p95">
|
|
684
646
|
<span class="perf-hm-p95" style="color:${m.color};background:${m.bg};border-color:${m.border}">
|
|
685
|
-
p95: ${
|
|
647
|
+
p95: ${y(i.p95DurationMs)}
|
|
686
648
|
</span>
|
|
687
649
|
</span>
|
|
688
650
|
<span class="perf-trend-reqs">${i.requestCount} req${i.requestCount!==1?"s":""}</span>
|
|
689
651
|
<span class="perf-trend-errs" style="color:${i.errorCount>0?"var(--red)":"var(--text-dim)"}">${i.errorCount} err${i.errorCount!==1?"s":""}</span>
|
|
690
|
-
${c?
|
|
652
|
+
${c?l`<span class="perf-trend-arrow ${c==="slower"?"perf-trend-slower":"perf-trend-faster"}">${c==="slower"?"\u2191 slower":"\u2193 faster"}</span>`:p}
|
|
691
653
|
</div>
|
|
692
654
|
`})}
|
|
693
655
|
</div>
|
|
694
656
|
</div>
|
|
695
|
-
`}formatTimeAgo(t){let e=Date.now()-t,
|
|
657
|
+
`}formatTimeAgo(t){let e=Date.now()-t,s=Math.round(e/6e4);if(s<1)return "just now";if(s<60)return `${s}m ago`;let i=Math.round(s/60);return i<24?`${i}h ago`:`${Math.round(i/24)}d ago`}renderDetailChart(){return l`
|
|
696
658
|
<div class="perf-chart-wrap">
|
|
697
659
|
<div class="perf-section-title">Response Time</div>
|
|
698
660
|
<canvas id="perf-detail-canvas" class="perf-canvas" style="width:100%;height:240px"></canvas>
|
|
699
661
|
</div>
|
|
700
|
-
`}renderDetailHistory(t){if(t.requests.length===0)return p;let e=[];for(let
|
|
662
|
+
`}renderDetailHistory(t){if(t.requests.length===0)return p;let e=[];for(let s=t.requests.length-1;s>=0&&e.length<50;s--)e.push({point:t.requests[s],originalIndex:s});return l`
|
|
701
663
|
<div class="perf-history-wrap">
|
|
702
664
|
<table class="perf-table">
|
|
703
665
|
<thead>
|
|
@@ -711,39 +673,39 @@
|
|
|
711
673
|
</tr>
|
|
712
674
|
</thead>
|
|
713
675
|
<tbody>
|
|
714
|
-
${e.map(
|
|
676
|
+
${e.map(s=>this.renderHistoryRow(s.point,s.originalIndex,t.baselineP95Ms))}
|
|
715
677
|
</tbody>
|
|
716
678
|
</table>
|
|
717
679
|
</div>
|
|
718
|
-
`}renderHistoryRow(t,e,
|
|
719
|
-
<tr class="perf-table-row ${
|
|
680
|
+
`}renderHistoryRow(t,e,s){let i=this.healthGradeForDuration(t.durationMs,s),n=new Date(t.timestamp).toLocaleTimeString([],{hour:"2-digit",minute:"2-digit",second:"2-digit"}),a=t.statusCode>=400,c=t.queryTimeMs||0,d=t.fetchTimeMs||0,h=Math.max(0,t.durationMs-c-d);return l`
|
|
681
|
+
<tr class="perf-table-row ${a?"perf-row-err":""}" data-req-idx=${e}>
|
|
720
682
|
<td class="perf-td-muted">${n}</td>
|
|
721
683
|
<td>
|
|
722
684
|
<span class="perf-badge perf-badge-sm" style="color:${i.color};background:${i.bg};border-color:${i.border}">${i.label}</span>
|
|
723
685
|
</td>
|
|
724
|
-
<td>${
|
|
686
|
+
<td>${y(t.durationMs)}</td>
|
|
725
687
|
<td>
|
|
726
|
-
${c>0?
|
|
727
|
-
${d>0?
|
|
728
|
-
<span class="perf-bd-tag perf-bd-tag-app">App ${
|
|
688
|
+
${c>0?l`<span class="perf-bd-tag perf-bd-tag-db">DB ${y(c)}</span>`:p}
|
|
689
|
+
${d>0?l`<span class="perf-bd-tag perf-bd-tag-fetch">Fetch ${y(d)}</span>`:p}
|
|
690
|
+
<span class="perf-bd-tag perf-bd-tag-app">App ${y(h)}</span>
|
|
729
691
|
</td>
|
|
730
|
-
<td class="perf-td-center" style="color:${
|
|
692
|
+
<td class="perf-td-center" style="color:${a?"var(--red)":"var(--text-muted)"}">${t.statusCode}</td>
|
|
731
693
|
<td class="perf-td-right perf-td-muted">${t.queryCount}</td>
|
|
732
694
|
</tr>
|
|
733
|
-
`}};u([I({context:A})],Q.prototype,"store",2),u([b()],Q.prototype,"selectedEndpoint",2),u([b()],Q.prototype,"graphData",2),u([b()],Q.prototype,"loadError",2),u([b()],Q.prototype,"queryBreakdown",2),u([b()],Q.prototype,"queryBreakdownLoading",2),Q=u([S("bk-performance-view")],Q);var Xe={auth:{label:"Auth",color:"#059669",icon:"\u{1F6E1}",tooltip:"Highlight which endpoints require authentication and which are unprotected"},security:{label:"Security",color:"#dc2626",icon:"\u26A0",tooltip:"Show security findings like exposed secrets, token leaks, and PII exposure"},performance:{label:"Perf",color:"#2563eb",icon:"\u26A1",tooltip:"Color endpoints by P95 latency \u2014 green (fast) to red (slow)"},issues:{label:"Issues",color:"#d97706",icon:"\u25CF",tooltip:"Badge endpoints with open issues like N+1 queries or redundant calls"},heat:{label:"Heat",color:"#ef4444",icon:"\u{1F525}",tooltip:"Color nodes and edges by traffic volume \u2014 blue (low) to red (hot)"}},At={action:{fill:"#faf5ff",stroke:"#a855f7",icon:"\u25B6",columnHeader:"ACTIONS"},endpoint:{fill:"#f8fafc",stroke:"#6366f1",icon:"\u26A1",columnHeader:"ENDPOINTS"},table:{fill:"#f0fdf4",stroke:"#16a34a",icon:"\u229E",columnHeader:"TABLES"},external:{fill:"#fffbeb",stroke:"#d97706",icon:"\u25C6",columnHeader:"EXTERNAL"}},fe={triggers:"#a855f7",reads:"#6366f1",writes:"#ef4444",fetches:"#f59e0b",calls:"#22c55e"},yi=100,_i=300,$i=800;function It(o){return o<yi?"#22c55e":o<_i?"#3b82f6":o<$i?"#eab308":"#ef4444"}var xi=.25,Si=.5,Ti=.75;function Ve(o){return o<xi?"#3b82f6":o<Si?"#22c55e":o<Ti?"#eab308":"#ef4444"}var zs="#4338ca",Js="#e0e7ff",Zs="#818cf8",tr="#eef2ff",Ke="#7c3aed",er="#6366f1",ve="#f97316",sr="#ecfdf5",ze="#059669",rr="#fef2f2",Je="#dc2626",ir="#fffbeb",Ze="#d97706",ct="#ef4444",or=.2,nr=3,ts=1.2,Kt=40;var ar=800,lr=500;function Ci(o){return Math.max(140,o.length*7.2+36)}function es(o,s){return Math.round(36+o.stats.requestCount/s*28)}function Li(o){return o.length>0?Math.max(140,...o.map(s=>Ci(s.label))):0}function dr(o,s,t){let e=[];for(let r of s){let i=r.source===o?r.target:r.target===o?r.source:null;if(i){let n=t.get(i);n&&e.push(n.y+n.h/2);}}return e.length>0?e.reduce((r,i)=>r+i,0)/e.length:1/0}function hr(o,s){let t=f=>o.filter(g=>g.type===f).sort((g,T)=>T.stats.requestCount-g.stats.requestCount),e=t("action"),r=t("endpoint"),i=t("table"),n=t("external"),l=Math.max(1,...o.map(f=>f.stats.requestCount)),c=new Map,d=[],h=[],m=50,v=[{type:"action",items:e},{type:"endpoint",items:r},{type:"table",items:i},{type:"external",items:n}];for(let f of v)if(f.items.length>0){let g=Li(f.items);h.push({type:f.type,items:f.items,width:g,x:m}),m+=g+160;}let x=h[0];if(x){let f=74;for(let g of x.items){let T=es(g,l),R={id:g.id,x:x.x,y:f,w:x.width,h:T,label:g.label,type:g.type,stats:g.stats,annotations:g.annotations};d.push(R),c.set(g.id,R),f+=T+10;}}for(let f=1;f<h.length;f++){let g=h[f],T=[...g.items].sort((C,q)=>dr(C.id,s,c)-dr(q.id,s,c)),R=d.filter(C=>C.x===h[f-1].x),H=Math.min(...R.map(C=>C.y)),B=Math.max(...R.map(C=>C.y+C.h))-H,V=T.reduce((C,q)=>C+es(q,l)+10,-10),G=H+Math.max(0,(B-V)/2);for(let C of T){let q=es(C,l),rs={id:C.id,x:g.x,y:G,w:g.width,h:q,label:C.label,type:C.type,stats:C.stats,annotations:C.annotations};d.push(rs),c.set(C.id,rs),G+=q+10;}}let y=[];for(let f of s){let g=c.get(f.source),T=c.get(f.target);if(!g||!T)continue;let R=g.x<T.x,H=R?g.x+g.w:g.x,X=g.y+g.h/2,B=R?T.x:T.x+T.w,V=T.y+T.h/2,G=[];f.stats.frequency>1&&G.push(`${f.stats.frequency}\xD7`),G.push(f.type),f.stats.avgLatencyMs>0&&G.push(`${f.stats.avgLatencyMs}ms`),y.push({key:f.id,sx:H,sy:X,tx:B,ty:V,label:G.join(" \xB7 "),color:fe[f.type]||"#94a3b8",thickness:Math.min(.75+Math.log2(f.stats.frequency+1)*.35,2.5),dashed:f.type==="reads"||f.type==="writes",data:f});}let k=d.reduce((f,g)=>Math.max(f,g.x+g.w),0),$=d.reduce((f,g)=>Math.max(f,g.y+g.h),0);return {nodes:d,edges:y,width:k+100,height:Math.max($+50,250)}}function ur(o){let s=o.charCodeAt(0);return s>=48&&s<=57||s>=65&&s<=70||s>=97&&s<=102}function Mi(o){if(o.length!==36)return false;for(let s=0;s<o.length;s++)if(s===8||s===13||s===18||s===23){if(o[s]!=="-")return false}else if(!ur(o[s]))return false;return true}function Ni(o){if(!o.length)return false;for(let s=0;s<o.length;s++){let t=o.charCodeAt(s);if(t<48||t>57)return false}return true}function Oi(o){if(o.length<12)return false;for(let s=0;s<o.length;s++)if(!ur(o[s]))return false;return true}function ki(o){if(o.length<8)return false;let s=false,t=false;for(let e=0;e<o.length;e++){let r=o.charCodeAt(e);if(r>=65&&r<=90||r>=97&&r<=122)s=true;else if(r>=48&&r<=57)t=true;else if(r!==95&&r!==45)return false}return s&&t}var Di=":id";function Hi(o){return Mi(o)||Ni(o)||Oi(o)||ki(o)?Di:o}function ss(o,s){let t=s.split("?")[0];return `${o} ${t.split("/").map(e=>e&&Hi(e)).join("/")}`}function mr(o,s,t){let e=[...o],r=[...s],i=new Set(e.map(l=>l.id)),n=new Map;for(let l of t){let c=l.label||"Unknown",d=n.get(c);d||(d={endpointKeys:new Set,count:0,totalMs:0},n.set(c,d)),d.count++,d.totalMs+=l.totalDurationMs;for(let h of l.requests)h.path?.startsWith(F)||d.endpointKeys.add(ss(h.method,h.path));}for(let[l,c]of n){let d=`action:${l}`;i.has(d)||(e.push({id:d,type:"action",label:l,stats:{requestCount:c.count,avgLatencyMs:c.count>0?Math.round(c.totalMs/c.count):0,errorRate:0,avgQueryCount:0}}),i.add(d));for(let h of c.endpointKeys){let m=`endpoint:${h}`;if(i.has(m)){let v=`${d} -> ${m}`;r.find(x=>x.id===v)||r.push({id:v,source:d,target:m,type:"triggers",stats:{frequency:c.count,avgLatencyMs:0}});}}}return {nodes:e,edges:r}}function fr(o){let s=new Map;for(let t of o){let e=t.label||"Unknown",r=s.get(e);r||(r={keys:new Set,count:0,totalMs:0},s.set(e,r)),r.count++,r.totalMs+=t.totalDurationMs;for(let i of t.requests)i.path?.startsWith(F)||r.keys.add(ss(i.method,i.path));}return [...s.entries()].map(([t,e])=>({label:t,occurrences:e.count,endpointKeys:e.keys,avgDurationMs:e.count>0?Math.round(e.totalMs/e.count):0}))}function be(o,s){let t=`event=${encodeURIComponent(o)}${s?`&detail=${encodeURIComponent(s)}`:""}`;fetch(`${w.tab}?${t}`).catch(()=>{});}var M=class extends E{constructor(){super(...arguments);this.graphNodes=[];this.graphEdges=[];this.locked=null;this.hovered=null;this.loading=true;this.activeLayers=new Set;this.searchQuery="";this.viewTransform={x:0,y:0,scale:1};this.isPanning=false;this.panStart={x:0,y:0,vtx:0,vty:0};this.dragging=null;this.wasDragging=false;this.nodePositionOverrides=new Map;this.detailTab="overview";this.consolidatedFlows=[];this.activeFlowIdx=-1;this.focusIdx=-1;this.refreshTimer=null;}createRenderRoot(){return this}connectedCallback(){super.connectedCallback(),this.loadData(),this.refreshTimer=setInterval(()=>this.loadData(),4e3);}disconnectedCallback(){super.disconnectedCallback(),this.refreshTimer&&clearInterval(this.refreshTimer);}async loadData(){try{let[t,e]=await Promise.all([fetch(`${w.graph}?level=endpoints`),fetch(w.flows)]),r=await t.json(),i=await e.json(),n=mr(r.nodes||[],r.edges||[],i.flows||[]);this.graphNodes=n.nodes,this.graphEdges=n.edges,this.consolidatedFlows=fr(i.flows||[]),this.nodePositionOverrides.size===0&&this.graphNodes.length>0&&this.tryLoadPersistedPositions(),this.loading=!1;}catch{this.loading=false;}}getPositionStorageKey(){let t=this.graphNodes.map(r=>r.id).sort().join(","),e=0;for(let r=0;r<t.length;r++)e=(e<<5)-e+t.charCodeAt(r)|0;return `bk-graph-pos-${e}`}persistPositions(){if(this.nodePositionOverrides.size!==0)try{let t=Object.fromEntries(this.nodePositionOverrides);localStorage.setItem(this.getPositionStorageKey(),JSON.stringify(t));}catch{}}tryLoadPersistedPositions(){try{let t=localStorage.getItem(this.getPositionStorageKey());if(t){let e=JSON.parse(t);this.nodePositionOverrides=new Map(Object.entries(e));}}catch{}}get activeNodeId(){return this.locked??this.hovered}getHighlightedNodeIds(){let t=this.activeNodeId;if(!t)return null;let e=new Set([t]),r=true;for(;r;){r=false;for(let i of this.graphEdges)e.has(i.source)&&!e.has(i.target)&&(e.add(i.target),r=true);}return e}getFlowTraceNodeIds(){if(this.activeFlowIdx<0||this.activeFlowIdx>=this.consolidatedFlows.length)return null;let t=this.consolidatedFlows[this.activeFlowIdx],e=new Set;e.add(`action:${t.label}`);for(let r of t.endpointKeys)e.add(`endpoint:${r}`);for(let r of this.graphEdges)e.has(r.source)&&e.add(r.target);return e}getFlowTraceEdgeIds(){let t=this.getFlowTraceNodeIds();if(!t)return null;let e=new Set;for(let r of this.graphEdges)t.has(r.source)&&t.has(r.target)&&e.add(r.id);return e}matchesSearch(t){return this.searchQuery?t.toLowerCase().includes(this.searchQuery.toLowerCase()):true}handlePanStart(t){t.button===0&&(t.target.closest(".graph-g")||(this.isPanning=true,this.panStart={x:t.clientX,y:t.clientY,vtx:this.viewTransform.x,vty:this.viewTransform.y}));}handlePanMove(t){if(this.dragging){let e=t.currentTarget.getBoundingClientRect(),r=(t.clientX-e.left-this.viewTransform.x)/this.viewTransform.scale,i=(t.clientY-e.top-this.viewTransform.y)/this.viewTransform.scale;this.nodePositionOverrides.set(this.dragging.nodeId,{x:r-this.dragging.offsetX,y:i-this.dragging.offsetY}),this.requestUpdate();return}this.isPanning&&(this.viewTransform={...this.viewTransform,x:this.panStart.vtx+(t.clientX-this.panStart.x),y:this.panStart.vty+(t.clientY-this.panStart.y)});}handlePanEnd(){this.dragging&&(this.persistPositions(),this.dragging=null,this.wasDragging=true,requestAnimationFrame(()=>{this.wasDragging=false;})),this.isPanning=false;}resetView(){this.viewTransform={x:0,y:0,scale:1};}zoomIn(){this.viewTransform={...this.viewTransform,scale:Math.min(nr,this.viewTransform.scale*ts)};}zoomOut(){this.viewTransform={...this.viewTransform,scale:Math.max(or,this.viewTransform.scale/ts)};}resetLayout(){this.nodePositionOverrides.clear();try{localStorage.removeItem(this.getPositionStorageKey());}catch{}this.viewTransform={x:0,y:0,scale:1},this.requestUpdate(),be("layout_reset");}startNodeDrag(t,e,r){t.stopPropagation();let i=t.currentTarget.closest("svg").getBoundingClientRect(),n=(t.clientX-i.left-this.viewTransform.x)/this.viewTransform.scale,l=(t.clientY-i.top-this.viewTransform.y)/this.viewTransform.scale;this.dragging={nodeId:e,offsetX:n-r.x,offsetY:l-r.y};}handleKeyDown(t){let e=this.graphNodes;if(t.key==="/"){t.preventDefault(),this.querySelector(".graph-search-input")?.focus();return}if(t.key==="Escape"){this.locked=null,this.searchQuery="",this.focusIdx=-1,this.activeFlowIdx=-1;return}if(t.key==="Tab"){t.preventDefault(),this.focusIdx=t.shiftKey?this.focusIdx<=0?e.length-1:this.focusIdx-1:(this.focusIdx+1)%e.length,this.hovered=e[this.focusIdx]?.id??null;return}if(t.key==="Enter"&&this.focusIdx>=0){let r=e[this.focusIdx];r&&(this.locked=this.locked===r.id?null:r.id);return}if(t.key==="+"||t.key==="="){this.zoomIn();return}if(t.key==="-"){this.zoomOut();return}if(t.key==="ArrowUp"){this.viewTransform={...this.viewTransform,y:this.viewTransform.y+Kt};return}if(t.key==="ArrowDown"){this.viewTransform={...this.viewTransform,y:this.viewTransform.y-Kt};return}if(t.key==="ArrowLeft"){this.viewTransform={...this.viewTransform,x:this.viewTransform.x+Kt};return}if(t.key==="ArrowRight"){this.viewTransform={...this.viewTransform,x:this.viewTransform.x-Kt};return}}render(){if(this.loading&&this.graphNodes.length===0)return a`<div class="graph-loading">Loading graph…</div>`;if(this.graphNodes.length===0)return a`
|
|
695
|
+
`}};u([I({context:A})],X.prototype,"store",2),u([E()],X.prototype,"selectedEndpoint",2),u([E()],X.prototype,"graphData",2),u([E()],X.prototype,"loadError",2),u([E()],X.prototype,"queryBreakdown",2),u([E()],X.prototype,"queryBreakdownLoading",2),X=u([$("bk-performance-view")],X);var rs={auth:{label:"Auth",color:"#059669",icon:"\u{1F6E1}",tooltip:"Highlight which endpoints require authentication and which are unprotected"},security:{label:"Security",color:"#dc2626",icon:"\u26A0",tooltip:"Show security findings like exposed secrets, token leaks, and PII exposure"},performance:{label:"Perf",color:"#2563eb",icon:"\u26A1",tooltip:"Color endpoints by P95 latency \u2014 green (fast) to red (slow)"},issues:{label:"Issues",color:"#d97706",icon:"\u25CF",tooltip:"Badge endpoints with open issues like N+1 queries or redundant calls"},heat:{label:"Heat",color:"#ef4444",icon:"\u{1F525}",tooltip:"Color nodes and edges by traffic volume \u2014 blue (low) to red (hot)"}},Nt={action:{fill:"#faf5ff",stroke:"#a855f7",icon:"\u25B6",columnHeader:"ACTIONS"},endpoint:{fill:"#f8fafc",stroke:"#6366f1",icon:"\u26A1",columnHeader:"ENDPOINTS"},table:{fill:"#f0fdf4",stroke:"#16a34a",icon:"\u229E",columnHeader:"TABLES"},external:{fill:"#fffbeb",stroke:"#d97706",icon:"\u25C6",columnHeader:"EXTERNAL"}},xe={triggers:"#a855f7",reads:"#6366f1",writes:"#ef4444",fetches:"#f59e0b",calls:"#22c55e"},Ai=100,Ii=300,Ci=800;function kt(o){return o<Ai?"#22c55e":o<Ii?"#3b82f6":o<Ci?"#eab308":"#ef4444"}var Li=.25,Mi=.5,Ni=.75;function is(o){return o<Li?"#3b82f6":o<Mi?"#22c55e":o<Ni?"#eab308":"#ef4444"}var cr="#4338ca",dr="#e0e7ff",pr="#818cf8",hr="#eef2ff",os="#7c3aed",ur="#6366f1",Se="#f97316",mr="#ecfdf5",ns="#059669",fr="#fef2f2",as="#dc2626",vr="#fffbeb",ls="#d97706",ut="#ef4444",gr=.2,Er=3,cs=1.2,re=40;var br=800,yr=500;function Hi(o){return Math.max(140,o.length*7.2+36)}function ds(o,r){return Math.round(36+o.stats.requestCount/r*28)}function qi(o){return o.length>0?Math.max(140,...o.map(r=>Hi(r.label))):0}function $r(o,r,t){let e=[];for(let s of r){let i=s.source===o?s.target:s.target===o?s.source:null;if(i){let n=t.get(i);n&&e.push(n.y+n.h/2);}}return e.length>0?e.reduce((s,i)=>s+i,0)/e.length:1/0}function Sr(o,r){let t=v=>o.filter(g=>g.type===v).sort((g,x)=>x.stats.requestCount-g.stats.requestCount),e=t("action"),s=t("endpoint"),i=t("table"),n=t("external"),a=Math.max(1,...o.map(v=>v.stats.requestCount)),c=new Map,d=[],h=[],m=50,f=[{type:"action",items:e},{type:"endpoint",items:s},{type:"table",items:i},{type:"external",items:n}];for(let v of f)if(v.items.length>0){let g=qi(v.items);h.push({type:v.type,items:v.items,width:g,x:m}),m+=g+160;}let S=h[0];if(S){let v=74;for(let g of S.items){let x=ds(g,a),w={id:g.id,x:S.x,y:v,w:S.width,h:x,label:g.label,type:g.type,stats:g.stats,annotations:g.annotations};d.push(w),c.set(g.id,w),v+=x+10;}}for(let v=1;v<h.length;v++){let g=h[v],x=[...g.items].sort((C,F)=>$r(C.id,r,c)-$r(F.id,r,c)),w=d.filter(C=>C.x===h[v-1].x),H=Math.min(...w.map(C=>C.y)),j=Math.max(...w.map(C=>C.y+C.h))-H,tt=x.reduce((C,F)=>C+ds(F,a)+10,-10),W=H+Math.max(0,(j-tt)/2);for(let C of x){let F=ds(C,a),hs={id:C.id,x:g.x,y:W,w:g.width,h:F,label:C.label,type:C.type,stats:C.stats,annotations:C.annotations};d.push(hs),c.set(C.id,hs),W+=F+10;}}let R=[];for(let v of r){let g=c.get(v.source),x=c.get(v.target);if(!g||!x)continue;let w=g.x<x.x,H=w?g.x+g.w:g.x,Z=g.y+g.h/2,j=w?x.x:x.x+x.w,tt=x.y+x.h/2,W=[];v.stats.frequency>1&&W.push(`${v.stats.frequency}\xD7`),W.push(v.type),v.stats.avgLatencyMs>0&&W.push(`${v.stats.avgLatencyMs}ms`),R.push({key:v.id,sx:H,sy:Z,tx:j,ty:tt,label:W.join(" \xB7 "),color:xe[v.type]||"#94a3b8",thickness:Math.min(.75+Math.log2(v.stats.frequency+1)*.35,2.5),dashed:v.type==="reads"||v.type==="writes",data:v});}let P=d.reduce((v,g)=>Math.max(v,g.x+g.w),0),_=d.reduce((v,g)=>Math.max(v,g.y+g.h),0);return {nodes:d,edges:R,width:P+100,height:Math.max(_+50,250)}}function Tr(o){let r=o.charCodeAt(0);return r>=48&&r<=57||r>=65&&r<=70||r>=97&&r<=102}function Ui(o){if(o.length!==36)return false;for(let r=0;r<o.length;r++)if(r===8||r===13||r===18||r===23){if(o[r]!=="-")return false}else if(!Tr(o[r]))return false;return true}function Fi(o){if(!o.length)return false;for(let r=0;r<o.length;r++){let t=o.charCodeAt(r);if(t<48||t>57)return false}return true}function Gi(o){if(o.length<12)return false;for(let r=0;r<o.length;r++)if(!Tr(o[r]))return false;return true}function Bi(o){if(o.length<8)return false;let r=false,t=false;for(let e=0;e<o.length;e++){let s=o.charCodeAt(e);if(s>=65&&s<=90||s>=97&&s<=122)r=true;else if(s>=48&&s<=57)t=true;else if(s!==95&&s!==45)return false}return r&&t}var Wi=":id";function Qi(o){return Ui(o)||Fi(o)||Gi(o)||Bi(o)?Wi:o}function ps(o,r){let t=r.split("?")[0];return `${o} ${t.split("/").map(e=>e&&Qi(e)).join("/")}`}function wr(o,r,t){let e=[...o],s=[...r],i=new Set(e.map(a=>a.id)),n=new Map;for(let a of t){let c=a.label||"Unknown",d=n.get(c);d||(d={endpointKeys:new Set,count:0,totalMs:0},n.set(c,d)),d.count++,d.totalMs+=a.totalDurationMs;for(let h of a.requests)h.path?.startsWith(U)||d.endpointKeys.add(ps(h.method,h.path));}for(let[a,c]of n){let d=`action:${a}`;i.has(d)||(e.push({id:d,type:"action",label:a,stats:{requestCount:c.count,avgLatencyMs:c.count>0?Math.round(c.totalMs/c.count):0,errorRate:0,avgQueryCount:0}}),i.add(d));for(let h of c.endpointKeys){let m=`endpoint:${h}`;if(i.has(m)){let f=`${d} -> ${m}`;s.find(S=>S.id===f)||s.push({id:f,source:d,target:m,type:"triggers",stats:{frequency:c.count,avgLatencyMs:0}});}}}return {nodes:e,edges:s}}function Rr(o){let r=new Map;for(let t of o){let e=t.label||"Unknown",s=r.get(e);s||(s={keys:new Set,count:0,totalMs:0},r.set(e,s)),s.count++,s.totalMs+=t.totalDurationMs;for(let i of t.requests)i.path?.startsWith(U)||s.keys.add(ps(i.method,i.path));}return [...r.entries()].map(([t,e])=>({label:t,occurrences:e.count,endpointKeys:e.keys,avgDurationMs:e.count>0?Math.round(e.totalMs/e.count):0}))}function we(o,r){let t=`event=${encodeURIComponent(o)}${r?`&detail=${encodeURIComponent(r)}`:""}`;fetch(`${T.tab}?${t}`).catch(()=>{});}var M=class extends b{constructor(){super(...arguments);this.graphNodes=[];this.graphEdges=[];this.locked=null;this.hovered=null;this.loading=true;this.activeLayers=new Set;this.searchQuery="";this.viewTransform={x:0,y:0,scale:1};this.isPanning=false;this.panStart={x:0,y:0,vtx:0,vty:0};this.dragging=null;this.wasDragging=false;this.nodePositionOverrides=new Map;this.detailTab="overview";this.consolidatedFlows=[];this.activeFlowIdx=-1;this.focusIdx=-1;this.refreshTimer=null;}createRenderRoot(){return this}connectedCallback(){super.connectedCallback(),this.loadData(),this.refreshTimer=setInterval(()=>this.loadData(),4e3);}disconnectedCallback(){super.disconnectedCallback(),this.refreshTimer&&clearInterval(this.refreshTimer);}async loadData(){try{let[t,e]=await Promise.all([fetch(`${T.graph}?level=endpoints`),fetch(T.flows)]),s=await t.json(),i=await e.json(),n=wr(s.nodes||[],s.edges||[],i.flows||[]);this.graphNodes=n.nodes,this.graphEdges=n.edges,this.consolidatedFlows=Rr(i.flows||[]),this.nodePositionOverrides.size===0&&this.graphNodes.length>0&&this.tryLoadPersistedPositions(),this.loading=!1;}catch{this.loading=false;}}getPositionStorageKey(){let t=this.graphNodes.map(s=>s.id).sort().join(","),e=0;for(let s=0;s<t.length;s++)e=(e<<5)-e+t.charCodeAt(s)|0;return `bk-graph-pos-${e}`}persistPositions(){if(this.nodePositionOverrides.size!==0)try{let t=Object.fromEntries(this.nodePositionOverrides);localStorage.setItem(this.getPositionStorageKey(),JSON.stringify(t));}catch{}}tryLoadPersistedPositions(){try{let t=localStorage.getItem(this.getPositionStorageKey());if(t){let e=JSON.parse(t);this.nodePositionOverrides=new Map(Object.entries(e));}}catch{}}get activeNodeId(){return this.locked??this.hovered}getHighlightedNodeIds(){let t=this.activeNodeId;if(!t)return null;let e=new Set([t]),s=true;for(;s;){s=false;for(let i of this.graphEdges)e.has(i.source)&&!e.has(i.target)&&(e.add(i.target),s=true);}return e}getFlowTraceNodeIds(){if(this.activeFlowIdx<0||this.activeFlowIdx>=this.consolidatedFlows.length)return null;let t=this.consolidatedFlows[this.activeFlowIdx],e=new Set;e.add(`action:${t.label}`);for(let s of t.endpointKeys)e.add(`endpoint:${s}`);for(let s of this.graphEdges)e.has(s.source)&&e.add(s.target);return e}getFlowTraceEdgeIds(){let t=this.getFlowTraceNodeIds();if(!t)return null;let e=new Set;for(let s of this.graphEdges)t.has(s.source)&&t.has(s.target)&&e.add(s.id);return e}matchesSearch(t){return this.searchQuery?t.toLowerCase().includes(this.searchQuery.toLowerCase()):true}handlePanStart(t){t.button===0&&(t.target.closest(".graph-g")||(this.isPanning=true,this.panStart={x:t.clientX,y:t.clientY,vtx:this.viewTransform.x,vty:this.viewTransform.y}));}handlePanMove(t){if(this.dragging){let e=t.currentTarget.getBoundingClientRect(),s=(t.clientX-e.left-this.viewTransform.x)/this.viewTransform.scale,i=(t.clientY-e.top-this.viewTransform.y)/this.viewTransform.scale;this.nodePositionOverrides.set(this.dragging.nodeId,{x:s-this.dragging.offsetX,y:i-this.dragging.offsetY}),this.requestUpdate();return}this.isPanning&&(this.viewTransform={...this.viewTransform,x:this.panStart.vtx+(t.clientX-this.panStart.x),y:this.panStart.vty+(t.clientY-this.panStart.y)});}handlePanEnd(){this.dragging&&(this.persistPositions(),this.dragging=null,this.wasDragging=true,requestAnimationFrame(()=>{this.wasDragging=false;})),this.isPanning=false;}resetView(){this.viewTransform={x:0,y:0,scale:1};}zoomIn(){this.viewTransform={...this.viewTransform,scale:Math.min(Er,this.viewTransform.scale*cs)};}zoomOut(){this.viewTransform={...this.viewTransform,scale:Math.max(gr,this.viewTransform.scale/cs)};}resetLayout(){this.nodePositionOverrides.clear();try{localStorage.removeItem(this.getPositionStorageKey());}catch{}this.viewTransform={x:0,y:0,scale:1},this.requestUpdate(),we("layout_reset");}startNodeDrag(t,e,s){t.stopPropagation();let i=t.currentTarget.closest("svg").getBoundingClientRect(),n=(t.clientX-i.left-this.viewTransform.x)/this.viewTransform.scale,a=(t.clientY-i.top-this.viewTransform.y)/this.viewTransform.scale;this.dragging={nodeId:e,offsetX:n-s.x,offsetY:a-s.y};}handleKeyDown(t){let e=this.graphNodes;if(t.key==="/"){t.preventDefault(),this.querySelector(".graph-search-input")?.focus();return}if(t.key==="Escape"){this.locked=null,this.searchQuery="",this.focusIdx=-1,this.activeFlowIdx=-1;return}if(t.key==="Tab"){t.preventDefault(),this.focusIdx=t.shiftKey?this.focusIdx<=0?e.length-1:this.focusIdx-1:(this.focusIdx+1)%e.length,this.hovered=e[this.focusIdx]?.id??null;return}if(t.key==="Enter"&&this.focusIdx>=0){let s=e[this.focusIdx];s&&(this.locked=this.locked===s.id?null:s.id);return}if(t.key==="+"||t.key==="="){this.zoomIn();return}if(t.key==="-"){this.zoomOut();return}if(t.key==="ArrowUp"){this.viewTransform={...this.viewTransform,y:this.viewTransform.y+re};return}if(t.key==="ArrowDown"){this.viewTransform={...this.viewTransform,y:this.viewTransform.y-re};return}if(t.key==="ArrowLeft"){this.viewTransform={...this.viewTransform,x:this.viewTransform.x+re};return}if(t.key==="ArrowRight"){this.viewTransform={...this.viewTransform,x:this.viewTransform.x-re};return}}render(){if(this.loading&&this.graphNodes.length===0)return l`<div class="graph-loading">Loading graph…</div>`;if(this.graphNodes.length===0)return l`
|
|
734
696
|
<div class="graph-empty">
|
|
735
697
|
<div class="graph-empty-icon">◎</div>
|
|
736
698
|
<div class="graph-empty-title">No data yet</div>
|
|
737
699
|
<div class="graph-empty-desc">Navigate your app to build the dependency graph.</div>
|
|
738
700
|
</div>
|
|
739
|
-
`;let t=
|
|
701
|
+
`;let t=Sr(this.graphNodes,this.graphEdges);this.applyPositionOverrides(t);let e=this.getSelectedNodeDetail(),s=this.getHighlightedNodeIds(),i=this.getFlowTraceNodeIds(),n=this.getFlowTraceEdgeIds(),a=this.deduplicateColumnHeaders(t.nodes),c=Math.max(1,...this.graphNodes.map(h=>h.stats.requestCount)),d=this.viewTransform;return l`
|
|
740
702
|
<div class="graph-wrapper" tabindex="0" @keydown=${this.handleKeyDown}
|
|
741
703
|
@click=${h=>{let m=h.target;m.closest(".graph-detail")||m.closest(".graph-toolbar")||m.closest(".graph-float")||m.closest(".graph-g")||(this.locked=null);}}>
|
|
742
704
|
${this.renderToolbar()}
|
|
743
705
|
<div class="graph-body">
|
|
744
706
|
<div class="graph-canvas" style="position:relative">
|
|
745
707
|
<svg width="100%" height="100%"
|
|
746
|
-
viewBox="0 0 ${Math.max(t.width,
|
|
708
|
+
viewBox="0 0 ${Math.max(t.width,br)} ${Math.max(t.height,yr)}"
|
|
747
709
|
class="graph-svg"
|
|
748
710
|
style="cursor:${this.isPanning?"grabbing":"grab"}"
|
|
749
711
|
@mousedown=${this.handlePanStart}
|
|
@@ -752,12 +714,12 @@
|
|
|
752
714
|
@mouseleave=${this.handlePanEnd}>
|
|
753
715
|
|
|
754
716
|
<g transform="translate(${d.x},${d.y}) scale(${d.scale})">
|
|
755
|
-
${
|
|
717
|
+
${k`${a.map(h=>k`
|
|
756
718
|
<text x="${h.x}" y="${58}" class="graph-col-header">${h.label}</text>
|
|
757
719
|
`)}`}
|
|
758
720
|
|
|
759
|
-
${t.edges.map(h=>this.renderEdge(h,
|
|
760
|
-
${t.nodes.map(h=>this.renderNode(h,
|
|
721
|
+
${t.edges.map(h=>this.renderEdge(h,s,n,c))}
|
|
722
|
+
${t.nodes.map(h=>this.renderNode(h,s,i,c))}
|
|
761
723
|
</g>
|
|
762
724
|
</svg>
|
|
763
725
|
${this.renderFloatingControls()}
|
|
@@ -765,12 +727,12 @@
|
|
|
765
727
|
${e?this.renderDetailPanel(e):p}
|
|
766
728
|
</div>
|
|
767
729
|
</div>
|
|
768
|
-
`}applyPositionOverrides(t){for(let
|
|
730
|
+
`}applyPositionOverrides(t){for(let s of t.nodes){let i=this.nodePositionOverrides.get(s.id);i&&(s.x=i.x,s.y=i.y);}let e=new Map(t.nodes.map(s=>[s.id,s]));for(let s of t.edges){let i=e.get(s.data.source),n=e.get(s.data.target);if(i&&n){let a=i.x<n.x;s.sx=a?i.x+i.w:i.x,s.sy=i.y+i.h/2,s.tx=a?n.x:n.x+n.w,s.ty=n.y+n.h/2;}}}deduplicateColumnHeaders(t){let e=[],s=new Set;for(let i of t)s.has(i.type)||(s.add(i.type),e.push({x:i.x,label:Nt[i.type]?.columnHeader||i.type.toUpperCase()}));return e}renderToolbar(){return l`
|
|
769
731
|
<div class="graph-toolbar">
|
|
770
732
|
<div class="graph-layer-toggles">
|
|
771
|
-
${Object.keys(
|
|
772
|
-
<button class="graph-layer-btn ${
|
|
773
|
-
style="${
|
|
733
|
+
${Object.keys(rs).map(t=>{let e=rs[t],s=this.activeLayers.has(t);return l`
|
|
734
|
+
<button class="graph-layer-btn ${s?"active":""}"
|
|
735
|
+
style="${s?`border-color:${e.color};color:${e.color}`:""}"
|
|
774
736
|
@click=${()=>this.toggleLayer(t)}
|
|
775
737
|
title="${e.tooltip}">
|
|
776
738
|
${e.icon} ${e.label}
|
|
@@ -783,35 +745,35 @@
|
|
|
783
745
|
<input class="graph-search-input" type="text" placeholder="Search nodes… ( / )"
|
|
784
746
|
.value=${this.searchQuery}
|
|
785
747
|
@input=${t=>{this.searchQuery=t.target.value;}}>
|
|
786
|
-
${this.searchQuery?
|
|
748
|
+
${this.searchQuery?l`
|
|
787
749
|
<button class="graph-search-clear" @click=${()=>{this.searchQuery="";}}>✕</button>
|
|
788
750
|
`:p}
|
|
789
751
|
</div>
|
|
790
752
|
|
|
791
|
-
${this.consolidatedFlows.length>0?
|
|
792
|
-
<select class="graph-flow-picker" @change=${t=>{this.activeFlowIdx=parseInt(t.target.value,10),this.activeFlowIdx>=0&&
|
|
753
|
+
${this.consolidatedFlows.length>0?l`
|
|
754
|
+
<select class="graph-flow-picker" @change=${t=>{this.activeFlowIdx=parseInt(t.target.value,10),this.activeFlowIdx>=0&&we("flow_traced");}}>
|
|
793
755
|
<option value="-1">Trace flow…</option>
|
|
794
|
-
${this.consolidatedFlows.map((t,e)=>
|
|
756
|
+
${this.consolidatedFlows.map((t,e)=>l`
|
|
795
757
|
<option value="${e}" ?selected=${this.activeFlowIdx===e}>${t.label} → ${t.endpointKeys.size} ep · ${t.occurrences}×</option>
|
|
796
758
|
`)}
|
|
797
759
|
</select>
|
|
798
760
|
`:p}
|
|
799
761
|
|
|
800
|
-
${this.activeLayers.has("auth")?
|
|
762
|
+
${this.activeLayers.has("auth")?l`
|
|
801
763
|
<div class="graph-auth-legend">
|
|
802
|
-
<span class="graph-auth-legend-item"><span style="color:${
|
|
803
|
-
<span class="graph-auth-legend-item"><span style="color:${
|
|
764
|
+
<span class="graph-auth-legend-item"><span style="color:${ns}">🛡</span> protected</span>
|
|
765
|
+
<span class="graph-auth-legend-item"><span style="color:${Se};font-weight:700">!</span> no auth</span>
|
|
804
766
|
</div>
|
|
805
767
|
`:p}
|
|
806
768
|
</div>
|
|
807
|
-
`}renderFloatingControls(){let t=this.nodePositionOverrides.size>0,e=Math.round(this.viewTransform.scale*100);return
|
|
769
|
+
`}renderFloatingControls(){let t=this.nodePositionOverrides.size>0,e=Math.round(this.viewTransform.scale*100);return l`
|
|
808
770
|
<div class="graph-float">
|
|
809
771
|
<button class="graph-float-btn" @click=${this.zoomOut} title="Zoom out (-)">−</button>
|
|
810
772
|
<span class="graph-float-zoom">${e}%</span>
|
|
811
773
|
<button class="graph-float-btn" @click=${this.zoomIn} title="Zoom in (+)">+</button>
|
|
812
774
|
<span class="graph-float-sep"></span>
|
|
813
775
|
<button class="graph-float-btn" @click=${this.resetView} title="Reset pan & zoom">⊙</button>
|
|
814
|
-
${t?
|
|
776
|
+
${t?l`
|
|
815
777
|
<span class="graph-float-sep"></span>
|
|
816
778
|
<button class="graph-float-btn graph-float-btn-accent" @click=${()=>this.resetLayout()} title="Reset layout to auto-arrange">
|
|
817
779
|
⟲ Reformat
|
|
@@ -822,185 +784,185 @@
|
|
|
822
784
|
📷
|
|
823
785
|
</button>
|
|
824
786
|
</div>
|
|
825
|
-
`}async captureScreenshot(){
|
|
787
|
+
`}async captureScreenshot(){we("screenshot_captured");let t=this.querySelector(".graph-svg");if(!t)return;let e=t.cloneNode(true);e.setAttribute("xmlns","http://www.w3.org/2000/svg"),e.style.cssText="",e.removeAttribute("class");let s=document.createElement("style");s.textContent=`
|
|
826
788
|
text { font-family: 'Inter', system-ui, -apple-system, sans-serif; }
|
|
827
789
|
.graph-col-header { fill: #c4c4cc; font-size: 9px; font-weight: 600; letter-spacing: 1.5px; }
|
|
828
790
|
.graph-flow-edge { stroke-dasharray: 6,4; }
|
|
829
791
|
.graph-pulse { }
|
|
830
|
-
`,e.insertBefore(
|
|
831
|
-
<g class="graph-g" transform="translate(${t.x},${t.y})" style="opacity:${
|
|
832
|
-
@click=${
|
|
792
|
+
`,e.insertBefore(s,e.firstChild);let n=document.createElementNS("http://www.w3.org/2000/svg","rect");n.setAttribute("width","100%"),n.setAttribute("height","100%"),n.setAttribute("fill","#ffffff"),e.insertBefore(n,e.firstChild);let a=e.getAttribute("viewBox")||"0 0 800 500",[,,c,d]=a.split(" ").map(Number),h=2,m=c*h,f=d*h,S=new XMLSerializer().serializeToString(e),R=new Blob([S],{type:"image/svg+xml;charset=utf-8"}),P=URL.createObjectURL(R),_=new Image;_.onload=()=>{let v=document.createElement("canvas");v.width=m,v.height=f;let g=v.getContext("2d");g.fillStyle="#ffffff",g.fillRect(0,0,m,f),g.drawImage(_,0,0,m,f),URL.revokeObjectURL(P),v.toBlob(x=>{if(!x)return;let w=document.createElement("a");w.href=URL.createObjectURL(x),w.download=`brakit-graph-${new Date().toISOString().slice(0,19).replace(/[T:]/g,"-")}.png`,document.body.appendChild(w),w.click(),document.body.removeChild(w),URL.revokeObjectURL(w.href);},"image/png");},_.src=P;}toggleLayer(t){let e=new Set(this.activeLayers);e.has(t)?e.delete(t):(e.add(t),we("layer_toggled",t)),this.activeLayers=e;}renderNode(t,e,s,i){let n=this.activeNodeId,a=e===null||e.has(t.id),c=n===t.id,d=this.locked===t.id,h=s?.has(t.id)??false,m=this.matchesSearch(t.label),f=Nt[t.type]||Nt.endpoint,S=d?cr:c?pr:f.stroke,R=d?dr:c?hr:f.fill,P=d?2:c?1.5:.75,_=a?1:.08;this.searchQuery&&!m&&(_=.05),s&&!h&&(_=Math.min(_,.08)),h&&(_=1,S=os);let v=this.searchQuery&&m,g=t.annotations,x=this.activeLayers;x.has("performance")&&g?.p95Ms!==void 0&&t.type==="endpoint"&&(R=kt(g.p95Ms)+"18"),x.has("heat")&&(R=is(t.stats.requestCount/i)+"20");let w=x.has("auth")&&t.type==="endpoint"&&!g?.hasAuth,H=x.has("auth")&&g?.hasAuth,Z=x.has("security")?g?.securityFindings?.length??0:0,j=g?.securityFindings?.some(F=>F.severity==="critical"),tt=x.has("issues")?g?.openIssueCount??0:0,W=this.nodeSubtitle(t),C=x.has("performance")&&g?.p95Ms!==void 0&&t.type==="endpoint";return k`
|
|
793
|
+
<g class="graph-g" transform="translate(${t.x},${t.y})" style="opacity:${_};cursor:pointer;transition:opacity .15s,transform .1s"
|
|
794
|
+
@click=${F=>{F.stopPropagation(),!this.wasDragging&&(this.locked=this.locked===t.id?null:t.id,this.detailTab="overview");}}
|
|
833
795
|
@mouseenter=${()=>{this.hovered=t.id;}}
|
|
834
796
|
@mouseleave=${()=>{this.hovered=null;}}
|
|
835
|
-
@mousedown=${
|
|
797
|
+
@mousedown=${F=>{F.detail>=2||this.startNodeDrag(F,t.id,t);}}>
|
|
836
798
|
|
|
837
|
-
${
|
|
799
|
+
${v?k`
|
|
838
800
|
<rect x="-3" y="-3" width="${t.w+6}" height="${t.h+6}" rx="9" fill="none"
|
|
839
|
-
stroke="${
|
|
801
|
+
stroke="${ur}" stroke-width="2" stroke-dasharray="4,2"/>
|
|
840
802
|
`:p}
|
|
841
803
|
|
|
842
|
-
${
|
|
843
|
-
<rect width="${t.w}" height="${t.h}" rx="8" fill="${
|
|
804
|
+
${w?k`
|
|
805
|
+
<rect width="${t.w}" height="${t.h}" rx="8" fill="${R}" stroke="${Se}"
|
|
844
806
|
stroke-width="1.2" stroke-dasharray="5,3"/>
|
|
845
|
-
`:
|
|
846
|
-
<rect width="${t.w}" height="${t.h}" rx="8" fill="${
|
|
807
|
+
`:k`
|
|
808
|
+
<rect width="${t.w}" height="${t.h}" rx="8" fill="${R}" stroke="${S}" stroke-width="${P}"/>
|
|
847
809
|
`}
|
|
848
810
|
|
|
849
811
|
<text x="12" y="${t.h/2-4}" fill="#1e293b" font-size="11.5" font-weight="600"
|
|
850
|
-
font-family="'Inter',system-ui,sans-serif">${
|
|
812
|
+
font-family="'Inter',system-ui,sans-serif">${f.icon} ${t.label}</text>
|
|
851
813
|
<text x="12" y="${t.h/2+10}" fill="#a1a1aa" font-size="9"
|
|
852
|
-
font-family="ui-monospace,monospace">${
|
|
814
|
+
font-family="ui-monospace,monospace">${W}</text>
|
|
853
815
|
|
|
854
|
-
${C?
|
|
855
|
-
<text x="${t.w-8}" y="${t.h-6}" fill="${
|
|
816
|
+
${C?k`
|
|
817
|
+
<text x="${t.w-8}" y="${t.h-6}" fill="${kt(g.p95Ms)}" font-size="9"
|
|
856
818
|
font-family="ui-monospace,monospace" text-anchor="end">p95: ${g.p95Ms}ms</text>
|
|
857
819
|
`:p}
|
|
858
820
|
|
|
859
|
-
${H?
|
|
821
|
+
${H?k`
|
|
860
822
|
<g transform="translate(${t.w-24},3)">
|
|
861
823
|
<title>Auth protected</title>
|
|
862
|
-
<rect width="18" height="18" rx="3" fill="${
|
|
824
|
+
<rect width="18" height="18" rx="3" fill="${mr}" stroke="${ns}" stroke-width="0.5"/>
|
|
863
825
|
<text x="9" y="13" text-anchor="middle" font-size="10">🛡</text>
|
|
864
826
|
</g>
|
|
865
827
|
`:p}
|
|
866
828
|
|
|
867
|
-
${
|
|
829
|
+
${w?k`
|
|
868
830
|
<g transform="translate(${t.w-24},3)">
|
|
869
831
|
<title>No auth detected — this endpoint may be unprotected</title>
|
|
870
|
-
<rect width="18" height="18" rx="3" fill="#fff7ed" stroke="${
|
|
832
|
+
<rect width="18" height="18" rx="3" fill="#fff7ed" stroke="${Se}" stroke-width="0.5"/>
|
|
871
833
|
<text x="9" y="13" text-anchor="middle" font-size="10" fill="#ea580c">!</text>
|
|
872
834
|
</g>
|
|
873
835
|
`:p}
|
|
874
836
|
|
|
875
|
-
${
|
|
876
|
-
<g transform="translate(${t.w-(H||
|
|
877
|
-
<rect width="18" height="18" rx="3" fill="${
|
|
878
|
-
stroke="${
|
|
879
|
-
class="${
|
|
837
|
+
${Z>0?k`
|
|
838
|
+
<g transform="translate(${t.w-(H||w?46:24)},3)">
|
|
839
|
+
<rect width="18" height="18" rx="3" fill="${j?fr:vr}"
|
|
840
|
+
stroke="${j?as:ls}" stroke-width="0.5"
|
|
841
|
+
class="${j?"graph-pulse":""}"/>
|
|
880
842
|
<text x="9" y="13" text-anchor="middle" font-size="9" font-weight="600"
|
|
881
|
-
fill="${
|
|
843
|
+
fill="${j?as:ls}">${Z}</text>
|
|
882
844
|
</g>
|
|
883
845
|
`:p}
|
|
884
846
|
|
|
885
|
-
${
|
|
886
|
-
<circle cx="${t.w-8}" cy="8" r="5" fill="${
|
|
887
|
-
<text x="${t.w-8}" y="11" text-anchor="middle" font-size="7" fill="white" font-weight="700">${
|
|
888
|
-
`:t.stats.errorRate>.05?
|
|
847
|
+
${tt>0?k`
|
|
848
|
+
<circle cx="${t.w-8}" cy="8" r="5" fill="${ut}" stroke="white" stroke-width="1"/>
|
|
849
|
+
<text x="${t.w-8}" y="11" text-anchor="middle" font-size="7" fill="white" font-weight="700">${tt}</text>
|
|
850
|
+
`:t.stats.errorRate>.05?k`<circle cx="${t.w-12}" cy="12" r="4" fill="${ut}"/>`:p}
|
|
889
851
|
|
|
890
|
-
${g?.isMiddleware&&
|
|
852
|
+
${g?.isMiddleware&&x.has("auth")?k`
|
|
891
853
|
<text x="${t.w}" y="${t.h+12}" text-anchor="end" font-size="8" fill="#6b7280"
|
|
892
854
|
font-family="ui-monospace,monospace">middleware</text>
|
|
893
855
|
`:p}
|
|
894
856
|
</g>
|
|
895
|
-
`}nodeSubtitle(t){switch(t.type){case "endpoint":return `${t.stats.requestCount} req \xB7 ${t.stats.avgLatencyMs}ms${t.stats.avgQueryCount>0?` \xB7 ${t.stats.avgQueryCount}q`:""}`;case "action":return `${t.stats.requestCount}\xD7 \xB7 ${t.stats.avgLatencyMs}ms`;case "table":return "table";default:return "service"}}renderEdge(t,e,
|
|
857
|
+
`}nodeSubtitle(t){switch(t.type){case "endpoint":return `${t.stats.requestCount} req \xB7 ${t.stats.avgLatencyMs}ms${t.stats.avgQueryCount>0?` \xB7 ${t.stats.avgQueryCount}q`:""}`;case "action":return `${t.stats.requestCount}\xD7 \xB7 ${t.stats.avgLatencyMs}ms`;case "table":return "table";default:return "service"}}renderEdge(t,e,s,i){let n=this.activeNodeId,a=e===null||e.has(t.data.source)&&e.has(t.data.target),c=s?.has(t.key)??false,d=a?n===null?.25:.6:.04,h=a&&n!==null,m=t.color,f=t.thickness;if(s&&!c&&(d=.04),c&&(d=.85,m=os,f=Math.max(f,1.8)),this.activeLayers.has("heat")&&!c){let tt=Math.max(1,...this.graphEdges.map(C=>C.stats.frequency)),W=t.data.stats.frequency/tt;m=is(W),a&&(d=Math.max(d,.4));}let S=this.activeLayers.has("issues")&&t.data.annotations?.hasIssue,R=Math.abs(t.tx-t.sx),P=Math.min(R*.45,120),_=t.sx<t.tx,v=_?t.sx+P:t.sx-P,g=_?t.tx-P:t.tx+P,x=(t.sx+t.tx)/2,w=(t.sy+t.ty)/2,H=4,Z=S?ut:m,j=S?Math.max(f,1.5):f;return k`
|
|
896
858
|
<g style="transition:opacity .15s">
|
|
897
|
-
<path d="M${t.sx},${t.sy} C${
|
|
898
|
-
fill="none" stroke="${
|
|
859
|
+
<path d="M${t.sx},${t.sy} C${v},${t.sy} ${g},${t.ty} ${t.tx},${t.ty}"
|
|
860
|
+
fill="none" stroke="${Z}" stroke-width="${j}"
|
|
899
861
|
stroke-opacity="${d}" stroke-linecap="round"
|
|
900
862
|
stroke-dasharray="${c?"6,4":t.dashed?"3,3":"none"}"
|
|
901
863
|
class="${c?"graph-flow-edge":""}"/>
|
|
902
|
-
<polygon points="${t.tx},${t.ty} ${t.tx+(
|
|
903
|
-
fill="${
|
|
904
|
-
${h?
|
|
905
|
-
<rect x="${
|
|
864
|
+
<polygon points="${t.tx},${t.ty} ${t.tx+(_?-H*1.5:H*1.5)},${t.ty-H} ${t.tx+(_?-H*1.5:H*1.5)},${t.ty+H}"
|
|
865
|
+
fill="${Z}" fill-opacity="${d}"/>
|
|
866
|
+
${h?k`
|
|
867
|
+
<rect x="${x-t.label.length*2.8-2}" y="${w-7}" width="${t.label.length*5.6+8}" height="14"
|
|
906
868
|
rx="4" fill="white" fill-opacity="0.92" stroke="${m}" stroke-width="0.4" stroke-opacity="0.15"/>
|
|
907
|
-
<text x="${
|
|
869
|
+
<text x="${x}" y="${w+3.5}" fill="${m}" font-size="8" font-weight="500"
|
|
908
870
|
font-family="ui-monospace,monospace" text-anchor="middle" opacity="0.85">${t.label}</text>
|
|
909
871
|
`:p}
|
|
910
|
-
${
|
|
911
|
-
<text x="${
|
|
872
|
+
${S?k`
|
|
873
|
+
<text x="${x}" y="${w-10}" fill="${ut}" font-size="8" font-weight="600"
|
|
912
874
|
text-anchor="middle">⚠ N+1</text>
|
|
913
875
|
`:p}
|
|
914
876
|
</g>
|
|
915
|
-
`}renderDetailPanel(t){let{node:e,edges:
|
|
877
|
+
`}renderDetailPanel(t){let{node:e,edges:s}=t,i=Nt[e.type]||Nt.endpoint,n=e.annotations,a=(n?.securityFindings?.length??0)>0,c=(n?.openIssueCount??0)>0,d=n?.p95Ms!==void 0,m=[{key:"overview",label:"Overview",show:true},{key:"security",label:`Security${a?` (${n.securityFindings.length})`:""}`,show:a},{key:"performance",label:"Perf",show:d},{key:"issues",label:`Issues${c?` (${n.openIssueCount})`:""}`,show:c}].filter(f=>f.show);return l`
|
|
916
878
|
<div class="graph-detail">
|
|
917
879
|
<div class="graph-detail-head">
|
|
918
880
|
<div>
|
|
919
881
|
<div class="graph-detail-badge" style="color:${i.stroke}">${i.icon} ${e.type}</div>
|
|
920
882
|
<div class="graph-detail-name">${e.label}</div>
|
|
921
|
-
${n?.hasAuth?
|
|
922
|
-
${n?.isMiddleware?
|
|
883
|
+
${n?.hasAuth?l`<span class="graph-detail-auth-badge">🛡 Authenticated</span>`:p}
|
|
884
|
+
${n?.isMiddleware?l`<span class="graph-detail-mw-badge">middleware</span>`:p}
|
|
923
885
|
</div>
|
|
924
886
|
<button class="graph-detail-close" @click=${()=>{this.locked=null;}}>✕</button>
|
|
925
887
|
</div>
|
|
926
888
|
|
|
927
|
-
${m.length>1?
|
|
889
|
+
${m.length>1?l`
|
|
928
890
|
<div class="graph-detail-tabs">
|
|
929
|
-
${m.map(
|
|
930
|
-
<button class="graph-detail-tab ${this.detailTab===
|
|
931
|
-
@click=${()=>{this.detailTab=
|
|
891
|
+
${m.map(f=>l`
|
|
892
|
+
<button class="graph-detail-tab ${this.detailTab===f.key?"active":""}"
|
|
893
|
+
@click=${()=>{this.detailTab=f.key;}}>${f.label}</button>
|
|
932
894
|
`)}
|
|
933
895
|
</div>
|
|
934
896
|
`:p}
|
|
935
897
|
|
|
936
|
-
${this.detailTab==="overview"?this.renderOverviewTab(e,
|
|
898
|
+
${this.detailTab==="overview"?this.renderOverviewTab(e,s):p}
|
|
937
899
|
${this.detailTab==="security"?this.renderSecurityTab(n):p}
|
|
938
900
|
${this.detailTab==="performance"?this.renderPerformanceTab(e,n):p}
|
|
939
901
|
${this.detailTab==="issues"?this.renderIssuesTab(n):p}
|
|
940
902
|
</div>
|
|
941
|
-
`}renderOverviewTab(t,e){return
|
|
903
|
+
`}renderOverviewTab(t,e){return l`
|
|
942
904
|
<div class="graph-detail-stats">
|
|
943
905
|
<div class="graph-detail-stat">
|
|
944
906
|
<div class="graph-detail-val">${t.stats.requestCount}</div>
|
|
945
907
|
<div class="graph-detail-lbl">${t.type==="action"?"OCCURRENCES":"REQUESTS"}</div>
|
|
946
908
|
</div>
|
|
947
909
|
<div class="graph-detail-stat">
|
|
948
|
-
<div class="graph-detail-val" style="color:${
|
|
910
|
+
<div class="graph-detail-val" style="color:${kt(t.stats.avgLatencyMs)}">${t.stats.avgLatencyMs}ms</div>
|
|
949
911
|
<div class="graph-detail-lbl">AVG LATENCY</div>
|
|
950
912
|
</div>
|
|
951
|
-
${t.stats.avgQueryCount>0?
|
|
913
|
+
${t.stats.avgQueryCount>0?l`
|
|
952
914
|
<div class="graph-detail-stat">
|
|
953
915
|
<div class="graph-detail-val">${t.stats.avgQueryCount}</div>
|
|
954
916
|
<div class="graph-detail-lbl">QUERIES/REQ</div>
|
|
955
917
|
</div>
|
|
956
918
|
`:p}
|
|
957
|
-
${t.stats.errorRate>.01?
|
|
919
|
+
${t.stats.errorRate>.01?l`
|
|
958
920
|
<div class="graph-detail-stat">
|
|
959
|
-
<div class="graph-detail-val" style="color:${
|
|
921
|
+
<div class="graph-detail-val" style="color:${ut}">${Math.round(t.stats.errorRate*100)}%</div>
|
|
960
922
|
<div class="graph-detail-lbl">ERRORS</div>
|
|
961
923
|
</div>
|
|
962
924
|
`:p}
|
|
963
925
|
</div>
|
|
964
926
|
|
|
965
|
-
${e.length>0?
|
|
927
|
+
${e.length>0?l`
|
|
966
928
|
<div class="graph-detail-sec">Connections</div>
|
|
967
|
-
${e.map(
|
|
929
|
+
${e.map(s=>{let i=s.source===this.locked,n=(i?s.target:s.source).replace(/^(action|endpoint|table|external):/,"");return l`
|
|
968
930
|
<div class="graph-detail-conn">
|
|
969
|
-
<span class="graph-detail-edge-dot" style="background:${
|
|
970
|
-
<span class="graph-detail-edge-type">${
|
|
931
|
+
<span class="graph-detail-edge-dot" style="background:${xe[s.type]}"></span>
|
|
932
|
+
<span class="graph-detail-edge-type">${s.type}</span>
|
|
971
933
|
<span>${i?"\u2192":"\u2190"} ${n}</span>
|
|
972
|
-
<span class="graph-detail-dim">${
|
|
934
|
+
<span class="graph-detail-dim">${s.stats.frequency}× · ${s.stats.avgLatencyMs}ms</span>
|
|
973
935
|
</div>
|
|
974
936
|
`})}
|
|
975
937
|
`:p}
|
|
976
938
|
|
|
977
|
-
${e.some(
|
|
939
|
+
${e.some(s=>s.patterns?.length)?l`
|
|
978
940
|
<div class="graph-detail-sec">SQL Patterns</div>
|
|
979
|
-
${e.filter(
|
|
980
|
-
<pre class="graph-detail-sql">${
|
|
941
|
+
${e.filter(s=>s.patterns).flatMap(s=>s.patterns).map(s=>l`
|
|
942
|
+
<pre class="graph-detail-sql">${s.length>200?s.slice(0,200)+"\u2026":s}</pre>
|
|
981
943
|
`)}
|
|
982
944
|
`:p}
|
|
983
|
-
`}renderSecurityTab(t){return t?.securityFindings?.length?
|
|
984
|
-
${t.securityFindings.map(e=>
|
|
945
|
+
`}renderSecurityTab(t){return t?.securityFindings?.length?l`
|
|
946
|
+
${t.securityFindings.map(e=>l`
|
|
985
947
|
<div class="graph-detail-finding">
|
|
986
948
|
<span class="graph-detail-severity graph-detail-severity-${e.severity}">${e.severity}</span>
|
|
987
949
|
<div class="graph-detail-finding-title">${e.title}</div>
|
|
988
950
|
<div class="graph-detail-finding-meta">${e.rule} · ${e.count} occurrence${e.count!==1?"s":""}</div>
|
|
989
951
|
</div>
|
|
990
952
|
`)}
|
|
991
|
-
`:
|
|
953
|
+
`:l`<div class="graph-detail-empty">No security findings</div>`}renderPerformanceTab(t,e){return l`
|
|
992
954
|
<div class="graph-detail-stats">
|
|
993
|
-
${e?.p95Ms!==void 0?
|
|
955
|
+
${e?.p95Ms!==void 0?l`
|
|
994
956
|
<div class="graph-detail-stat">
|
|
995
|
-
<div class="graph-detail-val" style="color:${
|
|
957
|
+
<div class="graph-detail-val" style="color:${kt(e.p95Ms)}">${e.p95Ms}ms</div>
|
|
996
958
|
<div class="graph-detail-lbl">P95 LATENCY</div>
|
|
997
959
|
</div>
|
|
998
960
|
`:p}
|
|
999
961
|
<div class="graph-detail-stat">
|
|
1000
|
-
<div class="graph-detail-val" style="color:${
|
|
962
|
+
<div class="graph-detail-val" style="color:${kt(t.stats.avgLatencyMs)}">${t.stats.avgLatencyMs}ms</div>
|
|
1001
963
|
<div class="graph-detail-lbl">AVG LATENCY</div>
|
|
1002
964
|
</div>
|
|
1003
|
-
${t.stats.avgQueryCount>0?
|
|
965
|
+
${t.stats.avgQueryCount>0?l`
|
|
1004
966
|
<div class="graph-detail-stat">
|
|
1005
967
|
<div class="graph-detail-val">${t.stats.avgQueryCount}</div>
|
|
1006
968
|
<div class="graph-detail-lbl">QUERIES/REQ</div>
|
|
@@ -1012,97 +974,193 @@
|
|
|
1012
974
|
</div>
|
|
1013
975
|
</div>
|
|
1014
976
|
|
|
1015
|
-
${(e?.insights?.length??0)>0?
|
|
977
|
+
${(e?.insights?.length??0)>0?l`
|
|
1016
978
|
<div class="graph-detail-sec">Performance Insights</div>
|
|
1017
|
-
${e.insights.map(
|
|
979
|
+
${e.insights.map(s=>l`
|
|
1018
980
|
<div class="graph-detail-finding">
|
|
1019
|
-
<span class="graph-detail-severity graph-detail-severity-${
|
|
1020
|
-
<div class="graph-detail-finding-title">${
|
|
1021
|
-
<div class="graph-detail-finding-meta">${
|
|
981
|
+
<span class="graph-detail-severity graph-detail-severity-${s.severity}">${s.severity}</span>
|
|
982
|
+
<div class="graph-detail-finding-title">${s.title}</div>
|
|
983
|
+
<div class="graph-detail-finding-meta">${s.type}</div>
|
|
1022
984
|
</div>
|
|
1023
985
|
`)}
|
|
1024
986
|
`:p}
|
|
1025
|
-
`}renderIssuesTab(t){let e=t?.openIssueCount??0;return e===0?
|
|
987
|
+
`}renderIssuesTab(t){let e=t?.openIssueCount??0;return e===0?l`<div class="graph-detail-empty">No open issues</div>`:l`
|
|
1026
988
|
<div class="graph-detail-issue-summary">
|
|
1027
989
|
<div class="graph-detail-stat">
|
|
1028
|
-
<div class="graph-detail-val" style="color:${
|
|
990
|
+
<div class="graph-detail-val" style="color:${ut}">${e}</div>
|
|
1029
991
|
<div class="graph-detail-lbl">OPEN ISSUES</div>
|
|
1030
992
|
</div>
|
|
1031
993
|
</div>
|
|
1032
994
|
<p class="graph-detail-hint">View the Issues tab for full details and remediation hints.</p>
|
|
1033
|
-
`}getSelectedNodeDetail(){if(!this.locked)return null;let t=this.graphNodes.find(
|
|
995
|
+
`}getSelectedNodeDetail(){if(!this.locked)return null;let t=this.graphNodes.find(s=>s.id===this.locked);if(!t)return null;let e=this.graphEdges.filter(s=>s.source===this.locked||s.target===this.locked);return {node:t,edges:e}}};u([I({context:A})],M.prototype,"store",2),u([E()],M.prototype,"graphNodes",2),u([E()],M.prototype,"graphEdges",2),u([E()],M.prototype,"locked",2),u([E()],M.prototype,"hovered",2),u([E()],M.prototype,"loading",2),u([E()],M.prototype,"activeLayers",2),u([E()],M.prototype,"searchQuery",2),u([E()],M.prototype,"viewTransform",2),u([E()],M.prototype,"dragging",2),u([E()],M.prototype,"detailTab",2),u([E()],M.prototype,"consolidatedFlows",2),u([E()],M.prototype,"activeFlowIdx",2),u([E()],M.prototype,"focusIdx",2),M=u([$("bk-graph-view")],M);var ie=class extends J{constructor(){super(...arguments);this.activeTab="requests";this.handleNavigateExplorer=t=>{let e=t.detail;ve.some(s=>s.key===e)&&(this.activeTab=e);};}connectedCallback(){super.connectedCallback(),window.addEventListener("navigate-explorer",this.handleNavigateExplorer);}disconnectedCallback(){super.disconnectedCallback(),window.removeEventListener("navigate-explorer",this.handleNavigateExplorer);}switchTab(t){this.activeTab=t,fetch(`${T.tab}?event=explorer.${t}`).catch(()=>{});}getCount(t){let e=this.store.state;switch(t){case "requests":return e.requests.filter(s=>!s.path?.startsWith(U)).length;case "fetches":return e.fetches.length;case "queries":return e.queries.length;case "logs":return e.logs.length;case "errors":return e.errors.length}}render(){return l`
|
|
996
|
+
<div class="explorer-tabs">
|
|
997
|
+
${ve.map(t=>l`
|
|
998
|
+
<button class="explorer-tab ${this.activeTab===t.key?"active":""}"
|
|
999
|
+
@click=${()=>this.switchTab(t.key)}>
|
|
1000
|
+
${t.label}
|
|
1001
|
+
<span class="explorer-tab-count">${this.getCount(t.key)}</span>
|
|
1002
|
+
</button>
|
|
1003
|
+
`)}
|
|
1004
|
+
</div>
|
|
1005
|
+
<div style="display:${this.activeTab==="requests"?"block":"none"}">
|
|
1006
|
+
<bk-requests-view></bk-requests-view>
|
|
1007
|
+
</div>
|
|
1008
|
+
<div style="display:${this.activeTab==="fetches"?"block":"none"}">
|
|
1009
|
+
<bk-fetches-view></bk-fetches-view>
|
|
1010
|
+
</div>
|
|
1011
|
+
<div style="display:${this.activeTab==="queries"?"block":"none"}">
|
|
1012
|
+
<bk-queries-view></bk-queries-view>
|
|
1013
|
+
</div>
|
|
1014
|
+
<div style="display:${this.activeTab==="logs"?"block":"none"}">
|
|
1015
|
+
<bk-logs-view></bk-logs-view>
|
|
1016
|
+
</div>
|
|
1017
|
+
<div style="display:${this.activeTab==="errors"?"block":"none"}">
|
|
1018
|
+
<bk-errors-view></bk-errors-view>
|
|
1019
|
+
</div>
|
|
1020
|
+
`}};u([E()],ie.prototype,"activeTab",2),ie=u([$("bk-explorer-view")],ie);var Yi=[{key:"all",label:"All"},{key:"security",label:"Security"},{key:"performance",label:"Performance"},{key:"quality",label:"Quality"}],mt=class extends J{constructor(){super(...arguments);this.filter="all";this.expandedIdx=-1;this.showDismissed=false;}getFilteredIssues(t){return (this.filter==="all"?t:t.filter(s=>Vt(s)===this.filter)).sort((s,i)=>{let n=h=>h==="open"||h==="regressed"?0:h==="fixing"?1:2,a=n(s.state)-n(i.state);if(a!==0)return a;let c=Y[s.issue.severity]?.sort??3,d=Y[i.issue.severity]?.sort??3;return c-d})}getCounts(t){let e={all:t.length,security:0,performance:0,quality:0};for(let s of t)e[Vt(s)]++;return e}render(){if(!(this.store.state.requests.length>0||this.store.state.queries.length>0))return l`<bk-empty-state
|
|
1021
|
+
title="Waiting for requests..."
|
|
1022
|
+
subtitle="Start using your app to see insights here"
|
|
1023
|
+
></bk-empty-state>`;let e=(this.store.state.issues||[]).filter(ir),s=this.getFilteredIssues(e),i=this.getCounts(e),n=s.filter(f=>f.state==="open"&&f.aiStatus!=="wont_fix"),a=s.filter(f=>f.state==="regressed"&&f.aiStatus!=="wont_fix"),c=s.filter(f=>f.state==="fixing"),d=s.filter(f=>f.state==="resolved"),h=s.filter(f=>f.aiStatus==="wont_fix"),m=0;return l`
|
|
1024
|
+
<div class="insights-filters">
|
|
1025
|
+
${Yi.map(f=>l`
|
|
1026
|
+
<button class="insights-chip ${this.filter===f.key?"active":""}"
|
|
1027
|
+
@click=${()=>{this.filter=f.key,this.expandedIdx=-1;}}>
|
|
1028
|
+
${f.label}
|
|
1029
|
+
${i[f.key]>0?l`<span class="insights-chip-count">${i[f.key]}</span>`:p}
|
|
1030
|
+
</button>
|
|
1031
|
+
`)}
|
|
1032
|
+
</div>
|
|
1033
|
+
|
|
1034
|
+
<div class="insights-list">
|
|
1035
|
+
${n.length===0&&a.length===0&&c.length===0&&d.length===0&&h.length===0?l`<div class="insights-empty"><span class="insights-empty-icon">\u2713</span>${this.filter==="all"?"All clear \u2014 no issues detected":`No ${this.filter} issues`}</div>`:p}
|
|
1036
|
+
|
|
1037
|
+
${a.length>0?l`
|
|
1038
|
+
<div class="insights-section insights-section-regressed">
|
|
1039
|
+
<span class="insights-section-icon">\u21A9</span> Regressed
|
|
1040
|
+
<span class="insights-section-count">${a.length}</span>
|
|
1041
|
+
</div>
|
|
1042
|
+
${a.map(f=>this.renderIssueCard(f,m++))}
|
|
1043
|
+
`:p}
|
|
1044
|
+
|
|
1045
|
+
${n.length>0?l`
|
|
1046
|
+
<div class="insights-section">
|
|
1047
|
+
<span class="insights-section-icon">\u25CF</span> Open
|
|
1048
|
+
<span class="insights-section-count">${n.length}</span>
|
|
1049
|
+
</div>
|
|
1050
|
+
${n.map(f=>this.renderIssueCard(f,m++))}
|
|
1051
|
+
`:p}
|
|
1052
|
+
|
|
1053
|
+
${c.length>0?l`
|
|
1054
|
+
<div class="insights-section insights-section-verifying">
|
|
1055
|
+
<span class="insights-section-icon">\u29D7</span> Verifying
|
|
1056
|
+
<span class="insights-section-count">${c.length}</span>
|
|
1057
|
+
</div>
|
|
1058
|
+
${c.map(f=>this.renderIssueCard(f,m++))}
|
|
1059
|
+
`:p}
|
|
1060
|
+
|
|
1061
|
+
${d.length>0?l`
|
|
1062
|
+
<div class="insights-section insights-section-resolved">
|
|
1063
|
+
<span class="insights-section-icon">\u2713</span> Resolved
|
|
1064
|
+
<span class="insights-section-count">${d.length}</span>
|
|
1065
|
+
</div>
|
|
1066
|
+
${d.map(f=>this.renderIssueCard(f,m++))}
|
|
1067
|
+
`:p}
|
|
1068
|
+
|
|
1069
|
+
${h.length>0?l`
|
|
1070
|
+
<div class="insights-section insights-section-dismissed" @click=${()=>{this.showDismissed=!this.showDismissed;}}>
|
|
1071
|
+
<span class="insights-section-icon">${this.showDismissed?"\u25BE":"\u25B8"}</span> Won't Fix
|
|
1072
|
+
<span class="insights-section-count">${h.length}</span>
|
|
1073
|
+
</div>
|
|
1074
|
+
${this.showDismissed?h.map(f=>this.renderIssueCard(f,m++)):p}
|
|
1075
|
+
`:p}
|
|
1076
|
+
</div>
|
|
1077
|
+
`}renderIssueCard(t,e){let s=t.issue,i=Y[s.severity]||Y.info,n=this.expandedIdx===e,a=t.state==="resolved",c=t.state==="fixing",d=Vt(t);return l`
|
|
1078
|
+
<div class="insights-card ${n?"expanded":""} ${a?"resolved":""}"
|
|
1079
|
+
@click=${()=>{this.expandedIdx=this.expandedIdx===e?-1:e;}}>
|
|
1080
|
+
<div class="insights-card-left">
|
|
1081
|
+
<span class="insights-sev ${i.cls}">${i.icon}</span>
|
|
1082
|
+
</div>
|
|
1083
|
+
<div class="insights-card-body">
|
|
1084
|
+
<div class="insights-card-header">
|
|
1085
|
+
<span class="insights-card-title ${a?"resolved":""}">${s.title}</span>
|
|
1086
|
+
<span class="insights-card-cat">${d}</span>
|
|
1087
|
+
${s.count?l`<span class="insights-card-count">${s.count}\u00D7</span>`:p}
|
|
1088
|
+
${t.state==="regressed"?l`<span class="insights-badge-regressed">regressed</span>`:p}
|
|
1089
|
+
${c?l`<span class="insights-badge-verifying">verifying</span>`:p}
|
|
1090
|
+
${a?l`<span class="insights-badge-resolved">resolved</span>`:p}
|
|
1091
|
+
</div>
|
|
1092
|
+
<div class="insights-card-desc">${s.desc}</div>
|
|
1093
|
+
${s.detail?l`<div class="insights-card-detail">${s.detail}</div>`:p}
|
|
1094
|
+
${t.cleanHitsSinceLastSeen>0?l`
|
|
1095
|
+
<div class="insights-card-progress">${t.cleanHitsSinceLastSeen}/${5} clean requests</div>
|
|
1096
|
+
`:p}
|
|
1097
|
+
${n&&s.hint?l`<div class="insights-card-hint">${s.hint}</div>`:p}
|
|
1098
|
+
</div>
|
|
1099
|
+
${s.hint?l`<span class="insights-card-arrow">${n?"\u2193":"\u2192"}</span>`:p}
|
|
1100
|
+
</div>
|
|
1101
|
+
`}};u([E()],mt.prototype,"filter",2),u([E()],mt.prototype,"expandedIdx",2),u([E()],mt.prototype,"showDismissed",2),mt=u([$("bk-insights-view")],mt);function Vi(o){return o===0?"<1ms":y(o)}var N=class extends b{constructor(){super(...arguments);this.requestId="";this.requestStarted=0;this.data=null;this.loading=false;this.failed=false;this.expandedSqlIdx=-1;}createRenderRoot(){return this}connectedCallback(){super.connectedCallback(),this.store.addEventListener("state-changed",()=>this.requestUpdate()),this.requestId&&this.loadTimeline();}async loadTimeline(){if(!this.requestId)return;let t=N.cache.get(this.requestId);if(t){this.data=t;return}this.loading=true;try{let e=await fetch(`${T.activity}?requestId=${this.requestId}`);if(!e.ok){this.failed=!0,this.loading=!1;return}let s=await e.json();if(N.cache.size>=We){let i=N.cache.keys().next().value;i!==void 0&&N.cache.delete(i);}N.cache.set(this.requestId,s),this.data=s,this.loading=!1;}catch(e){console.debug("[brakit] timeline load failed:",e),this.failed=true,this.loading=false;}}toggleSql(t,e){e.stopPropagation(),this.expandedSqlIdx=this.expandedSqlIdx===t?-1:t;}copySql(t,e){e.stopPropagation(),navigator.clipboard.writeText(t).then(()=>D.show("SQL copied")).catch(()=>D.show("Copy failed"));}render(){if(this.loading)return l`<div class="tl-loading">Loading activity...</div>`;if(this.failed||!this.data||this.data.total===0)return p;let t=this.data,e=t.timeline[0]?.timestamp??0;return l`
|
|
1034
1102
|
<div class="tl-header">
|
|
1035
1103
|
<span class="tl-title">Activity Timeline</span>
|
|
1036
1104
|
<span class="tl-counts">
|
|
1037
|
-
${t.counts.queries>0?
|
|
1038
|
-
${t.counts.fetches>0?
|
|
1039
|
-
${t.counts.logs>0?
|
|
1040
|
-
${t.counts.errors>0?
|
|
1105
|
+
${t.counts.queries>0?l`<span class="tl-count tl-count-query">${t.counts.queries} quer${t.counts.queries===1?"y":"ies"}</span>`:p}
|
|
1106
|
+
${t.counts.fetches>0?l`<span class="tl-count tl-count-fetch">${t.counts.fetches} fetch${t.counts.fetches===1?"":"es"}</span>`:p}
|
|
1107
|
+
${t.counts.logs>0?l`<span class="tl-count tl-count-log">${t.counts.logs} log${t.counts.logs===1?"":"s"}</span>`:p}
|
|
1108
|
+
${t.counts.errors>0?l`<span class="tl-count tl-count-error">${t.counts.errors} error${t.counts.errors===1?"":"s"}</span>`:p}
|
|
1041
1109
|
</span>
|
|
1042
1110
|
</div>
|
|
1043
1111
|
<div class="tl-events">${this.renderTimeline(t.timeline,e)}</div>
|
|
1044
|
-
`}renderTimeline(t,e){let
|
|
1045
|
-
${this.renderEvent(
|
|
1112
|
+
`}renderTimeline(t,e){let s=new Map,i=[];for(let a of t){let c=a.type==="query"?a.data.parentFetchId:void 0;if(a.type==="query"&&c){let d=s.get(c);d||(d=[],s.set(c,d)),d.push(a);}else i.push(a);}let n=0;return i.map(a=>{let c=n++,d=a.type==="fetch"?a.data.fetchId:void 0,h=d?s.get(d):void 0;if(h&&h.length>0){let m=h.length;return l`
|
|
1113
|
+
${this.renderEvent(a,c,e)}
|
|
1046
1114
|
<div class="tl-nested">
|
|
1047
1115
|
<span class="tl-nested-label">${m} nested quer${m===1?"y":"ies"}</span>
|
|
1048
|
-
${h.map(
|
|
1116
|
+
${h.map(f=>{let S=n++;return this.renderEvent(f,S,e,true)})}
|
|
1049
1117
|
</div>
|
|
1050
|
-
`}return this.renderEvent(
|
|
1118
|
+
`}return this.renderEvent(a,c,e)})}renderEvent(t,e,s,i=false){let n=Bs[t.type]||"var(--text-dim)",a=Ws[t.type]||t.type,c="+"+y(Math.round(t.timestamp-s)),d=t.type==="query"?t.data.sql:void 0,h=!!d,m=this.expandedSqlIdx===e;return l`
|
|
1051
1119
|
<div class="tl-event ${h?"tl-clickable":""} ${i?"tl-nested-event":""}"
|
|
1052
1120
|
style="${h?"":`border-left-color:${n}`}"
|
|
1053
|
-
@click=${h?
|
|
1121
|
+
@click=${h?f=>this.toggleSql(e,f):p}>
|
|
1054
1122
|
<span class="tl-event-time">${c}</span>
|
|
1055
|
-
<span class="tl-event-type" style="color:${n}">${
|
|
1123
|
+
<span class="tl-event-type" style="color:${n}">${a}</span>
|
|
1056
1124
|
${this.renderEventContent(t)}
|
|
1057
|
-
${d?
|
|
1125
|
+
${d?l`
|
|
1058
1126
|
<div class="tl-event-sql ${m?"open":""}">
|
|
1059
|
-
<button class="tl-sql-copy" @click=${
|
|
1127
|
+
<button class="tl-sql-copy" @click=${f=>this.copySql(d,f)}>Copy</button>
|
|
1060
1128
|
${d}
|
|
1061
1129
|
</div>`:p}
|
|
1062
1130
|
</div>
|
|
1063
|
-
`}renderEventContent(t){switch(t.type){case "fetch":{let e=t.data,
|
|
1131
|
+
`}renderEventContent(t){switch(t.type){case "fetch":{let e=t.data,s=e.statusCode>=400;return l`
|
|
1064
1132
|
<span class="tl-event-summary">${e.method} ${e.url}</span>
|
|
1065
|
-
<span class="tl-event-status" style="${
|
|
1066
|
-
<span class="tl-event-dur">${
|
|
1067
|
-
`}case "query":{let e=t.data,
|
|
1068
|
-
<span class="tl-event-summary"><span style="color:${n};font-weight:600">${
|
|
1069
|
-
<span class="tl-event-dur">${
|
|
1070
|
-
`}case "log":{let e=t.data,
|
|
1133
|
+
<span class="tl-event-status" style="${s?"color:var(--red)":""}">${e.statusCode}</span>
|
|
1134
|
+
<span class="tl-event-dur">${y(e.durationMs)}</span>
|
|
1135
|
+
`}case "query":{let e=t.data,s=(e.normalizedOp||e.operation||"?").toUpperCase(),i=e.table||e.model||"",n=Ee[s]||"var(--text-dim)";return l`
|
|
1136
|
+
<span class="tl-event-summary"><span style="color:${n};font-weight:600">${s}</span> ${i}</span>
|
|
1137
|
+
<span class="tl-event-dur">${Vi(e.durationMs)}</span>
|
|
1138
|
+
`}case "log":{let e=t.data,s=qs[e.level]||"var(--text-dim)";return l`<span class="tl-event-summary"><span style="color:${s}">${e.level.toUpperCase()}</span> ${e.message}</span>`}case "error":{let e=t.data;return l`<span class="tl-event-summary" style="color:var(--red)">${e.name}: ${e.message}</span>`}default:return p}}};N.cache=new Map,u([I({context:A})],N.prototype,"store",2),u([L({attribute:"request-id"})],N.prototype,"requestId",2),u([L({attribute:"request-started",type:Number})],N.prototype,"requestStarted",2),u([E()],N.prototype,"data",2),u([E()],N.prototype,"loading",2),u([E()],N.prototype,"failed",2),u([E()],N.prototype,"expandedSqlIdx",2),N=u([$("bk-timeline-panel")],N);function Dt(o){try{return JSON.parse(o)}catch{return null}}var Re=class{constructor(r,t){this.host=r;this.store=t;this.retryCount=0;this.boundHandlers={fetch:r=>{let t=Dt(r.data);t&&this.store.prependFetch(t);},log:r=>{let t=Dt(r.data);t&&this.store.prependLog(t);},error:r=>{let t=Dt(r.data);t&&this.store.prependError(t);},query:r=>{let t=Dt(r.data);t&&this.store.prependQuery(t);},issues:r=>{let t=Dt(r.data);t&&this.store.setIssues(t);}};r.addController(this);}hostConnected(){this.connect();}hostDisconnected(){this.removeListeners(),this.eventSource?.close(),clearTimeout(this.reloadTimer),clearTimeout(this.perfReloadTimer),clearTimeout(this.reconnectTimer);}removeListeners(){this.eventSource&&(this.eventSource.removeEventListener(he,this.boundHandlers.fetch),this.eventSource.removeEventListener("log",this.boundHandlers.log),this.eventSource.removeEventListener(ue,this.boundHandlers.error),this.eventSource.removeEventListener(me,this.boundHandlers.query),this.eventSource.removeEventListener(fe,this.boundHandlers.issues));}connect(){this.removeListeners(),this.eventSource?.close(),this.eventSource=new EventSource(T.events),this.eventSource.onopen=()=>{this.retryCount=0;},this.eventSource.onerror=()=>{this.eventSource?.close(),this.scheduleReconnect();},this.eventSource.onmessage=r=>{let t=Dt(r.data);t&&(t.path?.startsWith(U)||(this.store.prependRequest(t),clearTimeout(this.reloadTimer),this.reloadTimer=setTimeout(()=>this.reloadFlows(),300),this.store.state.activeView==="performance"&&(clearTimeout(this.perfReloadTimer),this.perfReloadTimer=setTimeout(()=>this.reloadMetrics(),Qe))));},this.eventSource.addEventListener(he,this.boundHandlers.fetch),this.eventSource.addEventListener("log",this.boundHandlers.log),this.eventSource.addEventListener(ue,this.boundHandlers.error),this.eventSource.addEventListener(me,this.boundHandlers.query),this.eventSource.addEventListener(fe,this.boundHandlers.issues);}scheduleReconnect(){if(this.retryCount>=10)return;let r=Math.min(1e3*2**this.retryCount,3e4);this.retryCount++,this.reconnectTimer=setTimeout(()=>this.connect(),r);}async reloadFlows(){try{let t=await(await fetch(T.flows)).json();this.store.setFlows(t.flows);}catch{}}async reloadMetrics(){try{let t=await(await fetch(T.metricsLive)).json();this.store.setMetrics(t.endpoints||[]);}catch{}}};function Ar(){return l`<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="7" height="7"/><rect x="14" y="3" width="7" height="7"/><rect x="14" y="14" width="7" height="7"/><rect x="3" y="14" width="7" height="7"/></svg>`}function Ir(){return l`<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polygon points="13 2 3 14 12 14 11 22 21 10 12 10 13 2"/></svg>`}function Cr(){return l`<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="20" x2="18" y2="10"/><line x1="12" y1="20" x2="12" y2="4"/><line x1="6" y1="20" x2="6" y2="14"/></svg>`}function Lr(){return l`<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M9 18h6"/><path d="M10 22h4"/><path d="M12 2a7 7 0 0 0-4 12.7V17h8v-2.3A7 7 0 0 0 12 2z"/></svg>`}function Mr(){return l`<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/></svg>`}function Nr(){return l`<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="6" cy="6" r="3"/><circle cx="18" cy="6" r="3"/><circle cx="6" cy="18" r="3"/><circle cx="18" cy="18" r="3"/><line x1="9" y1="6" x2="15" y2="6"/><line x1="6" y1="9" x2="6" y2="15"/><line x1="18" y1="9" x2="18" y2="15"/><line x1="9" y1="18" x2="15" y2="18"/></svg>`}var ft=class extends b{constructor(){super(...arguments);this.store=new ye;this.activeView="overview";this.viewMode="simple";this.sse=new Re(this,this.store);this.handleStateChanged=t=>{t.detail==="activeView"&&(this.activeView=this.store.state.activeView),this.requestUpdate();};}createRenderRoot(){return this}connectedCallback(){super.connectedCallback(),this.loadInitialData(),this.store.addEventListener("state-changed",this.handleStateChanged);}disconnectedCallback(){super.disconnectedCallback(),this.store.removeEventListener("state-changed",this.handleStateChanged);}async loadInitialData(){try{let[t,e]=await Promise.all([Q(T.flows),Q(T.requests)]);this.store.setFlows(t.flows),this.store.setRequests(e.requests);}catch(t){console.warn("[brakit]",t);}try{let[t,e,s,i,n]=await Promise.all([Q(T.fetches),Q(T.errors),Q(T.logs),Q(T.queries),Q(T.metricsLive)]);this.store.setFetches(t.entries),this.store.setErrors(e.entries),this.store.setLogs(s.entries),this.store.setQueries(i.entries),this.store.setMetrics(n.endpoints||[]);}catch(t){console.warn("[brakit]",t);}try{let t=await Q(T.insights);this.store.setIssues(t.issues||[]);}catch(t){console.warn("[brakit]",t);}}switchView(t){t!==this.activeView&&(this.activeView=t,this.store.setActiveView(t),fetch(`${T.tab}?tab=${encodeURIComponent(t)}`).catch(()=>{}),t==="performance"&&this.sse.reloadMetrics());}async handleClear(){confirm(O.CLEAR_CONFIRM)&&(await fetch(T.clear,{method:"POST"}),this.store.clearAll(),D.show(O.CLEARED_TOAST));}handleCopyAsCurl(t){Lt(t);}render(){let t=this.store.state,e=t.requests.filter(c=>!c.path?.startsWith(U)),s=e.filter(c=>c.statusCode>=400).length,i=e.length>0?Math.round(e.reduce((c,d)=>c+d.durationMs,0)/e.length):0,n=(t.issues||[]).filter($e).length,a=window.__BRAKIT_CONFIG__;return l`
|
|
1071
1139
|
<div class="app" id="app">
|
|
1072
1140
|
<aside class="sidebar">
|
|
1073
1141
|
<div class="sidebar-logo">
|
|
1074
1142
|
<span class="logo-text">brakit</span>
|
|
1075
|
-
<span class="logo-version">v${
|
|
1143
|
+
<span class="logo-version">v${a?.version??""}</span>
|
|
1076
1144
|
</div>
|
|
1077
1145
|
<nav class="sidebar-nav">
|
|
1078
|
-
${this.renderSidebarItem("overview","Overview",
|
|
1079
|
-
|
|
1080
|
-
${this.renderSidebarItem("
|
|
1081
|
-
${this.renderSidebarItem("
|
|
1082
|
-
${this.renderSidebarItem("
|
|
1083
|
-
<div class="sidebar-
|
|
1084
|
-
${this.renderSidebarItem("
|
|
1085
|
-
${this.renderSidebarItem("errors","Errors",_r(),t.errors.length)}
|
|
1086
|
-
${this.renderSidebarItem("logs","Logs",$r(),t.logs.length)}
|
|
1087
|
-
${this.renderSidebarItem("security","Security",xr(),n,n===0)}
|
|
1088
|
-
${this.renderSidebarItem("performance","Performance",Sr(),void 0)}
|
|
1089
|
-
<div class="sidebar-section">Topology</div>
|
|
1090
|
-
<button class="sidebar-item ${this.activeView==="graph"?"active":""}" @click=${()=>this.switchView("graph")}>
|
|
1091
|
-
<span class="item-icon">${Tr()}</span>
|
|
1092
|
-
<span class="item-label">Graph</span>
|
|
1093
|
-
<span class="sidebar-beta">beta</span>
|
|
1094
|
-
</button>
|
|
1146
|
+
${this.renderSidebarItem("overview","Overview",Ar(),void 0)}
|
|
1147
|
+
${this.renderSidebarItem("actions","Actions",Ir(),t.flows.length)}
|
|
1148
|
+
${this.renderSidebarItem("insights","Insights",Lr(),n,n===0)}
|
|
1149
|
+
${this.renderSidebarItem("performance","Performance",Cr(),void 0)}
|
|
1150
|
+
${this.renderSidebarItem("graph","Graph",Nr(),void 0)}
|
|
1151
|
+
<div class="sidebar-divider"></div>
|
|
1152
|
+
${this.renderSidebarItem("explorer","Explorer",Mr(),e.length+t.fetches.length+t.queries.length+t.logs.length+t.errors.length)}
|
|
1095
1153
|
</nav>
|
|
1096
|
-
<div class="sidebar-footer">:${
|
|
1154
|
+
<div class="sidebar-footer">:${a?.port??""}</div>
|
|
1097
1155
|
</aside>
|
|
1098
1156
|
<div class="main-panel">
|
|
1099
1157
|
<div class="header">
|
|
1100
1158
|
<div class="header-left">
|
|
1101
|
-
<span class="header-title" id="header-title">${
|
|
1102
|
-
<span class="header-sub" id="header-sub">${
|
|
1159
|
+
<span class="header-title" id="header-title">${Pe[this.activeView]||this.activeView}</span>
|
|
1160
|
+
<span class="header-sub" id="header-sub">${He[this.activeView]||""}</span>
|
|
1103
1161
|
</div>
|
|
1104
1162
|
<div class="header-right">
|
|
1105
|
-
${this.activeView==="actions"?
|
|
1163
|
+
${this.activeView==="actions"?l`
|
|
1106
1164
|
<div class="segmented-control" id="mode-toggle">
|
|
1107
1165
|
<button class="segmented-btn ${this.viewMode==="simple"?"active":""}" @click=${()=>{this.viewMode="simple",this.store.setViewMode("simple");}}>Quick</button>
|
|
1108
1166
|
<button class="segmented-btn ${this.viewMode==="detailed"?"active":""}" @click=${()=>{this.viewMode="detailed",this.store.setViewMode("detailed");}}>Detailed</button>
|
|
@@ -1112,53 +1170,41 @@
|
|
|
1112
1170
|
</div>
|
|
1113
1171
|
</div>
|
|
1114
1172
|
<div class="main-content">
|
|
1115
|
-
<div
|
|
1173
|
+
<div style="display:${this.activeView==="overview"?"block":"none"}">
|
|
1116
1174
|
<bk-overview-view></bk-overview-view>
|
|
1117
1175
|
</div>
|
|
1118
|
-
<div
|
|
1176
|
+
<div style="display:${this.activeView==="actions"?"block":"none"}">
|
|
1119
1177
|
<bk-flows-view></bk-flows-view>
|
|
1120
1178
|
</div>
|
|
1121
|
-
<div
|
|
1122
|
-
<bk-
|
|
1179
|
+
<div style="display:${this.activeView==="insights"?"block":"none"}">
|
|
1180
|
+
<bk-insights-view></bk-insights-view>
|
|
1123
1181
|
</div>
|
|
1124
|
-
<div
|
|
1125
|
-
<bk-fetches-view></bk-fetches-view>
|
|
1126
|
-
</div>
|
|
1127
|
-
<div class="view-telemetry" id="query-container" style="display:${this.activeView==="queries"?"block":"none"}">
|
|
1128
|
-
<bk-queries-view></bk-queries-view>
|
|
1129
|
-
</div>
|
|
1130
|
-
<div class="view-telemetry" id="error-container" style="display:${this.activeView==="errors"?"block":"none"}">
|
|
1131
|
-
<bk-errors-view></bk-errors-view>
|
|
1132
|
-
</div>
|
|
1133
|
-
<div class="view-telemetry" id="log-container" style="display:${this.activeView==="logs"?"block":"none"}">
|
|
1134
|
-
<bk-logs-view></bk-logs-view>
|
|
1135
|
-
</div>
|
|
1136
|
-
<div class="view-telemetry" id="security-container" style="display:${this.activeView==="security"?"block":"none"}">
|
|
1137
|
-
<bk-security-view></bk-security-view>
|
|
1138
|
-
</div>
|
|
1139
|
-
<div class="view-telemetry" id="performance-container" style="display:${this.activeView==="performance"?"block":"none"}">
|
|
1182
|
+
<div style="display:${this.activeView==="performance"?"block":"none"}">
|
|
1140
1183
|
<bk-performance-view></bk-performance-view>
|
|
1141
1184
|
</div>
|
|
1142
|
-
<div
|
|
1185
|
+
<div style="display:${this.activeView==="graph"?"block":"none"}">
|
|
1143
1186
|
<bk-graph-view></bk-graph-view>
|
|
1144
1187
|
</div>
|
|
1188
|
+
<div style="display:${this.activeView==="explorer"?"block":"none"}">
|
|
1189
|
+
<bk-explorer-view></bk-explorer-view>
|
|
1190
|
+
</div>
|
|
1145
1191
|
</div>
|
|
1146
1192
|
<div class="footer">
|
|
1147
|
-
<span id="stat-total">${e.length}
|
|
1148
|
-
<span id="stat-flows">${t.flows.length}
|
|
1149
|
-
<span id="stat-errors" class="error-count">${
|
|
1193
|
+
<span id="stat-total">${e.length} ${B(e.length,"request")}</span>
|
|
1194
|
+
<span id="stat-flows">${t.flows.length} ${B(t.flows.length,"action")}</span>
|
|
1195
|
+
<span id="stat-errors" class="error-count">${s} ${B(s,"error")}</span>
|
|
1150
1196
|
<span id="stat-avg">Avg: ${i}ms</span>
|
|
1151
1197
|
</div>
|
|
1152
1198
|
</div>
|
|
1153
1199
|
</div>
|
|
1154
1200
|
<bk-toast></bk-toast>
|
|
1155
|
-
`}renderSidebarItem(t,e,
|
|
1201
|
+
`}renderSidebarItem(t,e,s,i,n=false){return l`
|
|
1156
1202
|
<button class="sidebar-item ${this.activeView===t?"active":""}" @click=${()=>this.switchView(t)}>
|
|
1157
|
-
<span class="item-icon">${
|
|
1203
|
+
<span class="item-icon">${s}</span>
|
|
1158
1204
|
<span class="item-label">${e}</span>
|
|
1159
|
-
${i!==void 0?
|
|
1205
|
+
${i!==void 0?l`<span class="item-count" style="display:${n?"none":""}">${i}</span>`:p}
|
|
1160
1206
|
</button>
|
|
1161
|
-
`}};u([
|
|
1207
|
+
`}};u([Je({context:A})],ft.prototype,"store",2),u([E()],ft.prototype,"activeView",2),u([E()],ft.prototype,"viewMode",2),ft=u([$("bk-dashboard")],ft);
|
|
1162
1208
|
/*! Bundled license information:
|
|
1163
1209
|
|
|
1164
1210
|
@lit/reactive-element/css-tag.js:
|