Release v0.7.2

Automated sync from private repository.
Built with obfuscation enabled.
This commit is contained in:
GitHub Actions Bot
2025-12-29 21:15:42 +00:00
parent 81491c7bd0
commit 26effe192b
35 changed files with 72 additions and 368 deletions
+1 -1
View File
@@ -1 +1 @@
0.7.1
0.7.2
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1 +1 @@
import{j as e}from"./index-D3k5VxbW.js";import{b as o,c as t}from"./recharts-bmHCCfxU.js";const l={hero:{mobile:12},feature:{mobile:12,md:6,lg:4},standard:{mobile:12,sm:6,lg:4,xl:3},compact:{mobile:6,lg:3},widget:{mobile:6,sm:4,md:3,lg:2},auto:{mobile:12}},a={hero:"bento-row-hero",feature:"bento-row-feature",standard:"bento-row-standard",compact:"bento-row-compact",widget:"bento-row-widget",auto:"bento-row-auto"};function n({template:n,children:s,className:r,gap:c}){const i=l[n],d=a[n],b="none"===c?"gap-0":"sm"===c?"bento-gap-sm":"lg"===c?"bento-gap-lg":"bento-gap",g=o.Children.map(s,t=>o.isValidElement(t)?t.type===m?t:e.jsx(m,{span:i.mobile,sm:i.sm,md:i.md,lg:i.lg,xl:i.xl,children:t}):t);return e.jsx("div",{className:t("bento-row",d,b,r),children:g})}function s(e,o=""){const t=o?`${o}:`:"";return"full"===e?`${t}col-span-full`:"auto"===e?`${t}col-auto`:1.5===e?`${t}bento-col-1-5`:`${t}bento-col-${e}`}function m({children:o,span:l,sm:a,md:n,lg:m,xl:r,className:c}){const i=t("bento-cell",s(l),a&&s(a,"sm"),n&&s(n,"md"),m&&s(m,"lg"),r&&s(r,"xl"),c);return e.jsx("div",{className:i,children:o})}export{m as C,n as R};
import{j as e}from"./index-CV7v97nI.js";import{b as o,c as t}from"./recharts-bmHCCfxU.js";const l={hero:{mobile:12},feature:{mobile:12,md:6,lg:4},standard:{mobile:12,sm:6,lg:4,xl:3},compact:{mobile:6,lg:3},widget:{mobile:6,sm:4,md:3,lg:2},auto:{mobile:12}},a={hero:"bento-row-hero",feature:"bento-row-feature",standard:"bento-row-standard",compact:"bento-row-compact",widget:"bento-row-widget",auto:"bento-row-auto"};function n({template:n,children:s,className:r,gap:c}){const i=l[n],d=a[n],b="none"===c?"gap-0":"sm"===c?"bento-gap-sm":"lg"===c?"bento-gap-lg":"bento-gap",g=o.Children.map(s,t=>o.isValidElement(t)?t.type===m?t:e.jsx(m,{span:i.mobile,sm:i.sm,md:i.md,lg:i.lg,xl:i.xl,children:t}):t);return e.jsx("div",{className:t("bento-row",d,b,r),children:g})}function s(e,o=""){const t=o?`${o}:`:"";return"full"===e?`${t}col-span-full`:"auto"===e?`${t}col-auto`:1.5===e?`${t}bento-col-1-5`:`${t}bento-col-${e}`}function m({children:o,span:l,sm:a,md:n,lg:m,xl:r,className:c}){const i=t("bento-cell",s(l),a&&s(a,"sm"),n&&s(n,"md"),m&&s(m,"lg"),r&&s(r,"xl"),c);return e.jsx("div",{className:i,children:o})}export{m as C,n as R};
@@ -1 +1 @@
import{c as e,j as t}from"./index-D3k5VxbW.js";import{b as o,c as n}from"./recharts-bmHCCfxU.js";const s=e("check",[["path",{d:"M20 6 9 17l-5-5",key:"1gmf2c"}]]),a=e("copy",[["rect",{width:"14",height:"14",x:"8",y:"8",rx:"2",ry:"2",key:"17jyea"}],["path",{d:"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2",key:"zix9uf"}]]),r=e("map-pin",[["path",{d:"M20 10c0 4.993-5.539 10.193-7.399 11.799a1 1 0 0 1-1.202 0C9.539 20.193 4 14.993 4 10a8 8 0 0 1 16 0",key:"1r0f0z"}],["circle",{cx:"12",cy:"10",r:"3",key:"ilqhr7"}]]);function c({hash:e,prefixLength:r=8,suffixLength:c=6,full:i=!1,className:l,size:p="md"}){const[d,u]=o.useState(!1),[x,h]=o.useState(!1),y=o.useRef(null),m=o.useCallback(t=>{t.preventDefault(),t.stopPropagation(),y.current&&clearTimeout(y.current),function(e){if("undefined"!=typeof window&&(window.isSecureContext||"https:"===window.location.protocol||"localhost"===window.location.hostname)&&navigator.clipboard&&"function"==typeof navigator.clipboard.writeText)return navigator.clipboard.writeText(e).catch(()=>{}),!0;try{const t=window.scrollX,o=window.scrollY,n=document.createElement("textarea");n.value=e,n.style.position="fixed",n.style.left="-9999px",n.style.top="-9999px",n.style.opacity="0",n.style.pointerEvents="none",n.setAttribute("readonly",""),document.body.appendChild(n),n.focus({preventScroll:!0}),n.select(),n.setSelectionRange(0,e.length);const s=document.execCommand("copy");return document.body.removeChild(n),window.scrollTo(t,o),s}catch(t){return!1}}(e),u(!0),y.current=setTimeout(()=>u(!1),2e3)},[e]),w=i||e.length<=r+c+3?e:`${e.slice(0,r)}${e.slice(-c)}`,f={xs:"text-[9px] px-1 py-0.5 gap-0.5",sm:"text-[10px] px-1.5 py-0.5 gap-1",md:"text-xs px-2 py-1 gap-1.5"}[p];return t.jsxs("button",{type:"button",onClick:m,onMouseEnter:()=>h(!0),onMouseLeave:()=>h(!1),className:n("inline-flex items-center font-mono rounded-md transition-all duration-200","bg-bg-subtle/80 hover:bg-bg-elevated border border-border-subtle hover:border-border-strong","cursor-pointer select-none",f,l),title:`Click to copy: ${e}`,"aria-label":`Copy hash ${e}`,children:[t.jsx("span",{className:n("transition-colors duration-200",d?"text-accent-success":"text-text-secondary"),children:w}),t.jsx("span",{className:n("flex items-center justify-center transition-all duration-200","xs"===p?"w-2.5 h-2.5":"sm"===p?"w-3 h-3":"w-3.5 h-3.5"),children:d?t.jsx(s,{className:n("text-accent-success","xs"===p?"w-2 h-2":"sm"===p?"w-2.5 h-2.5":"w-3 h-3")}):t.jsx(a,{className:n("transition-opacity duration-200",x?"opacity-70":"opacity-40","xs"===p?"w-2 h-2":"sm"===p?"w-2.5 h-2.5":"w-3 h-3")})})]})}export{a as C,c as H,r as M,s as a};
import{c as e,j as t}from"./index-CV7v97nI.js";import{b as o,c as n}from"./recharts-bmHCCfxU.js";const s=e("check",[["path",{d:"M20 6 9 17l-5-5",key:"1gmf2c"}]]),a=e("copy",[["rect",{width:"14",height:"14",x:"8",y:"8",rx:"2",ry:"2",key:"17jyea"}],["path",{d:"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2",key:"zix9uf"}]]),r=e("map-pin",[["path",{d:"M20 10c0 4.993-5.539 10.193-7.399 11.799a1 1 0 0 1-1.202 0C9.539 20.193 4 14.993 4 10a8 8 0 0 1 16 0",key:"1r0f0z"}],["circle",{cx:"12",cy:"10",r:"3",key:"ilqhr7"}]]);function c({hash:e,prefixLength:r=8,suffixLength:c=6,full:i=!1,className:l,size:p="md"}){const[d,u]=o.useState(!1),[x,h]=o.useState(!1),y=o.useRef(null),m=o.useCallback(t=>{t.preventDefault(),t.stopPropagation(),y.current&&clearTimeout(y.current),function(e){if("undefined"!=typeof window&&(window.isSecureContext||"https:"===window.location.protocol||"localhost"===window.location.hostname)&&navigator.clipboard&&"function"==typeof navigator.clipboard.writeText)return navigator.clipboard.writeText(e).catch(()=>{}),!0;try{const t=window.scrollX,o=window.scrollY,n=document.createElement("textarea");n.value=e,n.style.position="fixed",n.style.left="-9999px",n.style.top="-9999px",n.style.opacity="0",n.style.pointerEvents="none",n.setAttribute("readonly",""),document.body.appendChild(n),n.focus({preventScroll:!0}),n.select(),n.setSelectionRange(0,e.length);const s=document.execCommand("copy");return document.body.removeChild(n),window.scrollTo(t,o),s}catch(t){return!1}}(e),u(!0),y.current=setTimeout(()=>u(!1),2e3)},[e]),w=i||e.length<=r+c+3?e:`${e.slice(0,r)}${e.slice(-c)}`,f={xs:"text-[9px] px-1 py-0.5 gap-0.5",sm:"text-[10px] px-1.5 py-0.5 gap-1",md:"text-xs px-2 py-1 gap-1.5"}[p];return t.jsxs("button",{type:"button",onClick:m,onMouseEnter:()=>h(!0),onMouseLeave:()=>h(!1),className:n("inline-flex items-center font-mono rounded-md transition-all duration-200","bg-bg-subtle/80 hover:bg-bg-elevated border border-border-subtle hover:border-border-strong","cursor-pointer select-none",f,l),title:`Click to copy: ${e}`,"aria-label":`Copy hash ${e}`,children:[t.jsx("span",{className:n("transition-colors duration-200",d?"text-accent-success":"text-text-secondary"),children:w}),t.jsx("span",{className:n("flex items-center justify-center transition-all duration-200","xs"===p?"w-2.5 h-2.5":"sm"===p?"w-3 h-3":"w-3.5 h-3.5"),children:d?t.jsx(s,{className:n("text-accent-success","xs"===p?"w-2 h-2":"sm"===p?"w-2.5 h-2.5":"w-3 h-3")}):t.jsx(a,{className:n("transition-opacity duration-200",x?"opacity-70":"opacity-40","xs"===p?"w-2 h-2":"sm"===p?"w-2.5 h-2.5":"w-3 h-3")})})]})}export{a as C,c as H,r as M,s as a};
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1 +1 @@
import{j as e}from"./index-D3k5VxbW.js";import{c as s}from"./recharts-bmHCCfxU.js";function a({children:a,className:l}){return e.jsx("div",{className:s("section-gap",l),children:a})}function l({title:s,icon:a,controls:l,subtitle:c}){return e.jsxs("div",{className:"flex flex-col gap-2",children:[e.jsxs("div",{className:"flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between",children:[e.jsxs("h1",{className:"type-title text-text-primary flex items-center gap-3",children:[a&&e.jsx("span",{className:"w-6 h-6 text-accent-primary flex-shrink-0",children:a}),s]}),l&&e.jsx("div",{className:"flex items-center gap-2 sm:gap-3",children:l})]}),c&&e.jsx("div",{children:c})]})}const c={sm:"card-sm",md:"card-md",lg:"card-lg",hero:"card-hero",auto:"card-auto"};function t({children:a,size:l,compact:t,noPadding:r,className:i,style:n,onClick:d}){return e.jsx("div",{className:s("glass-card h-full flex flex-col",!r&&(t?"card-padding-sm":"card-padding"),l&&c[l],i),style:n,onClick:d,children:a})}function r({icon:a,title:l,badge:c,subtitle:t,actions:r,iconColor:i="text-accent-primary",largeTitle:n=!1,listHeader:d=!1}){return e.jsxs("div",{className:s("flex flex-col gap-1 flex-shrink-0",d?"px-4 py-3 border-b border-border-subtle bg-bg-elevated/20":"mb-3"),children:[e.jsxs("div",{className:"flex items-center justify-between gap-2",children:[e.jsxs("div",{className:"flex items-center gap-2",children:[a&&e.jsx("span",{className:s("icon-md",i),children:a}),e.jsx("span",{className:n?"type-subheading text-text-primary":"type-micro",children:l}),c&&e.jsx("span",{className:"pill-tag",children:c})]}),r&&e.jsx("div",{className:"flex items-center gap-2 flex-wrap",children:r})]}),t&&e.jsx("p",{className:"type-label text-text-muted ml-7",children:t})]})}export{t as C,a as P,l as a,r as b};
import{j as e}from"./index-CV7v97nI.js";import{c as s}from"./recharts-bmHCCfxU.js";function a({children:a,className:l}){return e.jsx("div",{className:s("section-gap",l),children:a})}function l({title:s,icon:a,controls:l,subtitle:c}){return e.jsxs("div",{className:"flex flex-col gap-2",children:[e.jsxs("div",{className:"flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between",children:[e.jsxs("h1",{className:"type-title text-text-primary flex items-center gap-3",children:[a&&e.jsx("span",{className:"w-6 h-6 text-accent-primary flex-shrink-0",children:a}),s]}),l&&e.jsx("div",{className:"flex items-center gap-2 sm:gap-3",children:l})]}),c&&e.jsx("div",{children:c})]})}const c={sm:"card-sm",md:"card-md",lg:"card-lg",hero:"card-hero",auto:"card-auto"};function t({children:a,size:l,compact:t,noPadding:r,className:i,style:n,onClick:d}){return e.jsx("div",{className:s("glass-card h-full flex flex-col",!r&&(t?"card-padding-sm":"card-padding"),l&&c[l],i),style:n,onClick:d,children:a})}function r({icon:a,title:l,badge:c,subtitle:t,actions:r,iconColor:i="text-accent-primary",largeTitle:n=!1,listHeader:d=!1}){return e.jsxs("div",{className:s("flex flex-col gap-1 flex-shrink-0",d?"px-4 py-3 border-b border-border-subtle bg-bg-elevated/20":"mb-3"),children:[e.jsxs("div",{className:"flex items-center justify-between gap-2",children:[e.jsxs("div",{className:"flex items-center gap-2",children:[a&&e.jsx("span",{className:s("icon-md",i),children:a}),e.jsx("span",{className:n?"type-subheading text-text-primary":"type-micro",children:l}),c&&e.jsx("span",{className:"pill-tag",children:c})]}),r&&e.jsx("div",{className:"flex items-center gap-2 flex-wrap",children:r})]}),t&&e.jsx("p",{className:"type-label text-text-muted ml-7",children:t})]})}export{t as C,a as P,l as a,r as b};
@@ -1 +1 @@
import{j as e}from"./index-D3k5VxbW.js";import{b as t}from"./recharts-bmHCCfxU.js";import{M as o,S as n,L as r,a as i,P as s}from"./maplibre-gl-Dk-KchcY.js";import"./maplibre-gl-CFO9X1Ek.js";const a="#4F46E5",l="#F9D26F",d="#B49DFF",c="#39D98A",u="#B49DFF";function p(e,t,o,n=!1,r=!1,i=!1){let s;s=r?c:i?u:e?a:t?"#6366F1":o?"transparent":l;const p=e||t||!o||r||i;return`<div style="\n width: 14px;\n height: 14px;\n background: ${s};\n border-radius: 50%;\n border: ${p?0:5}px solid ${p?"transparent":"#4338CA"};\n box-sizing: border-box;\n transition: box-shadow 0.15s ease;\n ${n?`box-shadow: 0 0 0 3px ${d}40, 0 0 8px ${d}60;`:""}\n "></div>`}const m={id:"path-line",type:"line",paint:{"line-color":"#3B3F4A","line-width":2,"line-opacity":.7},layout:{"line-cap":"round","line-join":"round"}};function x({resolvedPath:d,localNode:x,hubNodes:h=[],hoveredHopIndex:f,onHoverHop:b}){const g=t.useRef(null),y=t.useMemo(()=>new Set(h),[h]),[j,k]=t.useState(null),{positions:v,markers:C,pathLineGeoJSON:N}=t.useMemo(()=>{const e=[],t=[],o=[];d.hops.forEach((n,r)=>{const i=n.candidates.filter(e=>{return t=e.latitude,o=e.longitude,0!==t||0!==o;var t,o});if(0===i.length)return;const s=[...i].sort((e,t)=>t.probability-e.probability)[0];o.push([s.longitude,s.latitude]);const a=!0===n.isSource,l=!0===n.isDestination;i.forEach((o,s)=>{const d=[o.latitude,o.longitude];e.push(d);const c=0===s;t.push({position:d,prefix:n.prefix,confidence:n.confidence,candidateCount:i.length,hopIndex:r,candidate:o,isHub:y.has(o.hash),isPrimary:c,isSource:a,isDestination:l})})});const n={type:"FeatureCollection",features:o.length>=2?[{type:"Feature",properties:{},geometry:{type:"LineString",coordinates:o}}]:[]};return{positions:e,markers:t,pathLineGeoJSON:n}},[d,y]),{center:S,zoom:F}=t.useMemo(()=>{if(0===v.length)return x?{center:[x.longitude,x.latitude],zoom:10}:{center:[0,0],zoom:2};let e=1/0,t=-1/0,o=1/0,n=-1/0;for(const[c,u]of v)c<e&&(e=c),c>t&&(t=c),u<o&&(o=u),u>n&&(n=u);const r=(e+t)/2,i=(o+n)/2,s=t-e,a=n-o,l=Math.max(s,a);let d;return d=l<.01?15:l<.05?13:l<.1?12:l<.5?10:l<1?9:l<5?7:5,{center:[i,r],zoom:d}},[v,x]);t.useEffect(()=>{if(!g.current||v.length<2)return;let e=1/0,t=-1/0,o=1/0,n=-1/0;for(const[r,i]of v)r<e&&(e=r),r>t&&(t=r),i<o&&(o=i),i>n&&(n=i);g.current.fitBounds([[o,e],[n,t]],{padding:30,maxZoom:16,duration:0})},[v]);const L=t.useCallback(e=>{k({longitude:e.position[1],latitude:e.position[0],marker:e})},[]);return 0===v.length?e.jsx("div",{className:"h-[200px] flex items-center justify-center text-text-muted text-sm bg-bg-elevated",children:"No mappable path data"}):e.jsxs(o,{ref:g,initialViewState:{longitude:S[0],latitude:S[1],zoom:F},style:{height:"200px",width:"100%"},mapStyle:"https://basemaps.cartocdn.com/gl/dark-matter-gl-style/style.json",attributionControl:!1,children:[N.features.length>0&&e.jsx(n,{id:"path-line-source",type:"geojson",data:N,children:e.jsx(r,{...m})}),C.map(t=>{const o=f===t.hopIndex;return e.jsx(i,{longitude:t.position[1],latitude:t.position[0],anchor:"center",onClick:()=>L(t),children:e.jsx("div",{style:{cursor:"pointer",opacity:t.isPrimary?1:.5},onMouseEnter:()=>null==b?void 0:b(t.hopIndex),onMouseLeave:()=>null==b?void 0:b(null),dangerouslySetInnerHTML:{__html:p(t.candidate.isLocal||!1,t.isHub,t.isPrimary,o,t.isSource,t.isDestination)}})},`${t.hopIndex}-${t.candidate.hash}`)}),j&&e.jsx(s,{longitude:j.longitude,latitude:j.latitude,anchor:"bottom",offset:[0,-12],closeOnClick:!1,onClose:()=>k(null),className:"maplibre-popup",children:e.jsxs("div",{className:"text-xs",children:[e.jsxs("div",{className:"flex items-center gap-1.5",children:[e.jsx("span",{className:"font-semibold",children:j.marker.candidate.name}),j.marker.isSource&&e.jsx("span",{className:"px-1 py-0.5 text-[8px] font-bold rounded",style:{backgroundColor:c,color:"#000"},children:"SRC"}),j.marker.isDestination&&e.jsx("span",{className:"px-1 py-0.5 text-[8px] font-bold rounded",style:{backgroundColor:u,color:"#000"},children:"DST"}),j.marker.isHub&&e.jsx("span",{className:"px-1 py-0.5 text-[8px] font-bold rounded",style:{backgroundColor:"#FBBF24",color:"#000"},children:"HUB"}),j.marker.candidate.isLocal&&e.jsx("span",{className:"px-1 py-0.5 text-[8px] font-bold rounded",style:{backgroundColor:a,color:"#fff"},children:"LOCAL"})]}),e.jsxs("div",{className:"text-text-muted font-mono text-[10px]",children:[j.marker.prefix," • ",j.marker.candidate.hash.slice(0,10),"..."]}),!j.marker.isPrimary&&j.marker.candidateCount>1&&e.jsxs("div",{style:{color:l},children:["Alternative (",(100*j.marker.candidate.probability).toFixed(0),"%)"]}),j.marker.isPrimary&&j.marker.candidateCount>1&&e.jsxs("div",{className:"text-text-muted",children:[j.marker.candidateCount," candidates"]})]})})]})}export{x as default};
import{j as e}from"./index-CV7v97nI.js";import{b as t}from"./recharts-bmHCCfxU.js";import{M as o,S as n,L as r,a as i,P as s}from"./maplibre-gl-DPqA1aeF.js";import"./maplibre-gl-CFO9X1Ek.js";const a="#4F46E5",l="#F9D26F",d="#B49DFF",c="#39D98A",u="#B49DFF";function p(e,t,o,n=!1,r=!1,i=!1){let s;s=r?c:i?u:e?a:t?"#6366F1":o?"transparent":l;const p=e||t||!o||r||i;return`<div style="\n width: 14px;\n height: 14px;\n background: ${s};\n border-radius: 50%;\n border: ${p?0:5}px solid ${p?"transparent":"#4338CA"};\n box-sizing: border-box;\n transition: box-shadow 0.15s ease;\n ${n?`box-shadow: 0 0 0 3px ${d}40, 0 0 8px ${d}60;`:""}\n "></div>`}const m={id:"path-line",type:"line",paint:{"line-color":"#3B3F4A","line-width":2,"line-opacity":.7},layout:{"line-cap":"round","line-join":"round"}};function x({resolvedPath:d,localNode:x,hubNodes:h=[],hoveredHopIndex:f,onHoverHop:b}){const g=t.useRef(null),y=t.useMemo(()=>new Set(h),[h]),[j,k]=t.useState(null),{positions:v,markers:C,pathLineGeoJSON:N}=t.useMemo(()=>{const e=[],t=[],o=[];d.hops.forEach((n,r)=>{const i=n.candidates.filter(e=>{return t=e.latitude,o=e.longitude,0!==t||0!==o;var t,o});if(0===i.length)return;const s=[...i].sort((e,t)=>t.probability-e.probability)[0];o.push([s.longitude,s.latitude]);const a=!0===n.isSource,l=!0===n.isDestination;i.forEach((o,s)=>{const d=[o.latitude,o.longitude];e.push(d);const c=0===s;t.push({position:d,prefix:n.prefix,confidence:n.confidence,candidateCount:i.length,hopIndex:r,candidate:o,isHub:y.has(o.hash),isPrimary:c,isSource:a,isDestination:l})})});const n={type:"FeatureCollection",features:o.length>=2?[{type:"Feature",properties:{},geometry:{type:"LineString",coordinates:o}}]:[]};return{positions:e,markers:t,pathLineGeoJSON:n}},[d,y]),{center:S,zoom:F}=t.useMemo(()=>{if(0===v.length)return x?{center:[x.longitude,x.latitude],zoom:10}:{center:[0,0],zoom:2};let e=1/0,t=-1/0,o=1/0,n=-1/0;for(const[c,u]of v)c<e&&(e=c),c>t&&(t=c),u<o&&(o=u),u>n&&(n=u);const r=(e+t)/2,i=(o+n)/2,s=t-e,a=n-o,l=Math.max(s,a);let d;return d=l<.01?15:l<.05?13:l<.1?12:l<.5?10:l<1?9:l<5?7:5,{center:[i,r],zoom:d}},[v,x]);t.useEffect(()=>{if(!g.current||v.length<2)return;let e=1/0,t=-1/0,o=1/0,n=-1/0;for(const[r,i]of v)r<e&&(e=r),r>t&&(t=r),i<o&&(o=i),i>n&&(n=i);g.current.fitBounds([[o,e],[n,t]],{padding:30,maxZoom:16,duration:0})},[v]);const L=t.useCallback(e=>{k({longitude:e.position[1],latitude:e.position[0],marker:e})},[]);return 0===v.length?e.jsx("div",{className:"h-[200px] flex items-center justify-center text-text-muted text-sm bg-bg-elevated",children:"No mappable path data"}):e.jsxs(o,{ref:g,initialViewState:{longitude:S[0],latitude:S[1],zoom:F},style:{height:"200px",width:"100%"},mapStyle:"https://basemaps.cartocdn.com/gl/dark-matter-gl-style/style.json",attributionControl:!1,children:[N.features.length>0&&e.jsx(n,{id:"path-line-source",type:"geojson",data:N,children:e.jsx(r,{...m})}),C.map(t=>{const o=f===t.hopIndex;return e.jsx(i,{longitude:t.position[1],latitude:t.position[0],anchor:"center",onClick:()=>L(t),children:e.jsx("div",{style:{cursor:"pointer",opacity:t.isPrimary?1:.5},onMouseEnter:()=>null==b?void 0:b(t.hopIndex),onMouseLeave:()=>null==b?void 0:b(null),dangerouslySetInnerHTML:{__html:p(t.candidate.isLocal||!1,t.isHub,t.isPrimary,o,t.isSource,t.isDestination)}})},`${t.hopIndex}-${t.candidate.hash}`)}),j&&e.jsx(s,{longitude:j.longitude,latitude:j.latitude,anchor:"bottom",offset:[0,-12],closeOnClick:!1,onClose:()=>k(null),className:"maplibre-popup",children:e.jsxs("div",{className:"text-xs",children:[e.jsxs("div",{className:"flex items-center gap-1.5",children:[e.jsx("span",{className:"font-semibold",children:j.marker.candidate.name}),j.marker.isSource&&e.jsx("span",{className:"px-1 py-0.5 text-[8px] font-bold rounded",style:{backgroundColor:c,color:"#000"},children:"SRC"}),j.marker.isDestination&&e.jsx("span",{className:"px-1 py-0.5 text-[8px] font-bold rounded",style:{backgroundColor:u,color:"#000"},children:"DST"}),j.marker.isHub&&e.jsx("span",{className:"px-1 py-0.5 text-[8px] font-bold rounded",style:{backgroundColor:"#FBBF24",color:"#000"},children:"HUB"}),j.marker.candidate.isLocal&&e.jsx("span",{className:"px-1 py-0.5 text-[8px] font-bold rounded",style:{backgroundColor:a,color:"#fff"},children:"LOCAL"})]}),e.jsxs("div",{className:"text-text-muted font-mono text-[10px]",children:[j.marker.prefix," • ",j.marker.candidate.hash.slice(0,10),"..."]}),!j.marker.isPrimary&&j.marker.candidateCount>1&&e.jsxs("div",{style:{color:l},children:["Alternative (",(100*j.marker.candidate.probability).toFixed(0),"%)"]}),j.marker.isPrimary&&j.marker.candidateCount>1&&e.jsxs("div",{className:"text-text-muted",children:[j.marker.candidateCount," candidates"]})]})})]})}export{x as default};
File diff suppressed because one or more lines are too long
@@ -1 +1 @@
import{c as e,j as t}from"./index-D3k5VxbW.js";import{c as s,b as a}from"./recharts-bmHCCfxU.js";const n=e("signal-high",[["path",{d:"M2 20h.01",key:"4haj6o"}],["path",{d:"M7 20v-4",key:"j294jx"}],["path",{d:"M12 20v-8",key:"i3yub9"}],["path",{d:"M17 20V8",key:"1tkaf5"}]]),r=e("signal-low",[["path",{d:"M2 20h.01",key:"4haj6o"}],["path",{d:"M7 20v-4",key:"j294jx"}]]),c=e("signal-medium",[["path",{d:"M2 20h.01",key:"4haj6o"}],["path",{d:"M7 20v-4",key:"j294jx"}],["path",{d:"M12 20v-8",key:"i3yub9"}]]),o=e("signal-zero",[["path",{d:"M2 20h.01",key:"4haj6o"}]]),i=e("signal",[["path",{d:"M2 20h.01",key:"4haj6o"}],["path",{d:"M7 20v-4",key:"j294jx"}],["path",{d:"M12 20v-8",key:"i3yub9"}],["path",{d:"M17 20V8",key:"1tkaf5"}],["path",{d:"M22 4v16",key:"sih9yq"}]]);function d(e){return e>=-90?"excellent":e>=-100?"good":e>=-110?"fair":e>=-120?"weak":"poor"}function l(e,t){if(!t)return"bg-white/10";switch(e){case"excellent":return"bg-accent-success";case"good":return"bg-[#71F8E5]";case"fair":return"bg-[#F9D26F]";case"weak":return"bg-[#FB923C]";case"poor":return"bg-accent-danger";default:return"bg-white/20"}}function x({rssi:e,className:a="w-4 h-4"}){const l=d(e),x=function(e){switch(e){case"excellent":return"text-accent-success";case"good":return"text-[#71F8E5]";case"fair":return"text-[#F9D26F]";case"weak":return"text-[#FB923C]";case"poor":return"text-accent-danger";default:return"text-text-muted"}}(l),h=s(x,a);switch(l){case"excellent":return t.jsx(i,{className:h});case"good":return t.jsx(n,{className:h});case"fair":return t.jsx(c,{className:h});case"weak":return t.jsx(r,{className:h});default:return t.jsx(o,{className:h})}}const h=a.memo(function({rssi:e,snr:a,compact:n=!1,showValues:r=!0}){const c=d(e),o={excellent:4,good:3,fair:2,weak:1,poor:0}[c];return n?t.jsxs("div",{className:"flex items-center justify-end gap-1.5",children:[r&&t.jsx("span",{className:"text-[10px] font-mono text-text-secondary",children:e}),t.jsx("div",{className:"flex items-end gap-[2px] h-3",children:Array.from({length:4}).map((e,a)=>t.jsx("div",{className:s("w-[3px] rounded-[1px] transition-colors",l(c,a<o)),style:{height:(a+1)/4*100+"%"}},a))})]}):t.jsxs("div",{className:"flex items-center justify-end gap-2",children:[r&&t.jsxs("div",{className:"flex flex-col items-end",children:[t.jsxs("span",{className:"text-xs font-mono text-text-secondary leading-tight",children:[e," dBm"]}),void 0!==a&&t.jsxs("span",{className:"text-[10px] font-mono text-text-muted leading-tight",children:[a.toFixed(1)," dB"]})]}),t.jsx("div",{className:"flex items-end gap-[2px] h-3.5",children:Array.from({length:4}).map((e,a)=>t.jsx("div",{className:s("w-[3px] rounded-[1px] transition-colors",l(c,a<o)),style:{height:(a+1)/4*100+"%"}},a))})]})});function u(e){const t=d(e);return t.charAt(0).toUpperCase()+t.slice(1)}export{h as S,x as a,u as g};
import{c as e,j as t}from"./index-CV7v97nI.js";import{c as s,b as a}from"./recharts-bmHCCfxU.js";const n=e("signal-high",[["path",{d:"M2 20h.01",key:"4haj6o"}],["path",{d:"M7 20v-4",key:"j294jx"}],["path",{d:"M12 20v-8",key:"i3yub9"}],["path",{d:"M17 20V8",key:"1tkaf5"}]]),r=e("signal-low",[["path",{d:"M2 20h.01",key:"4haj6o"}],["path",{d:"M7 20v-4",key:"j294jx"}]]),c=e("signal-medium",[["path",{d:"M2 20h.01",key:"4haj6o"}],["path",{d:"M7 20v-4",key:"j294jx"}],["path",{d:"M12 20v-8",key:"i3yub9"}]]),o=e("signal-zero",[["path",{d:"M2 20h.01",key:"4haj6o"}]]),i=e("signal",[["path",{d:"M2 20h.01",key:"4haj6o"}],["path",{d:"M7 20v-4",key:"j294jx"}],["path",{d:"M12 20v-8",key:"i3yub9"}],["path",{d:"M17 20V8",key:"1tkaf5"}],["path",{d:"M22 4v16",key:"sih9yq"}]]);function d(e){return e>=-90?"excellent":e>=-100?"good":e>=-110?"fair":e>=-120?"weak":"poor"}function l(e,t){if(!t)return"bg-white/10";switch(e){case"excellent":return"bg-accent-success";case"good":return"bg-[#71F8E5]";case"fair":return"bg-[#F9D26F]";case"weak":return"bg-[#FB923C]";case"poor":return"bg-accent-danger";default:return"bg-white/20"}}function x({rssi:e,className:a="w-4 h-4"}){const l=d(e),x=function(e){switch(e){case"excellent":return"text-accent-success";case"good":return"text-[#71F8E5]";case"fair":return"text-[#F9D26F]";case"weak":return"text-[#FB923C]";case"poor":return"text-accent-danger";default:return"text-text-muted"}}(l),h=s(x,a);switch(l){case"excellent":return t.jsx(i,{className:h});case"good":return t.jsx(n,{className:h});case"fair":return t.jsx(c,{className:h});case"weak":return t.jsx(r,{className:h});default:return t.jsx(o,{className:h})}}const h=a.memo(function({rssi:e,snr:a,compact:n=!1,showValues:r=!0}){const c=d(e),o={excellent:4,good:3,fair:2,weak:1,poor:0}[c];return n?t.jsxs("div",{className:"flex items-center justify-end gap-1.5",children:[r&&t.jsx("span",{className:"text-[10px] font-mono text-text-secondary",children:e}),t.jsx("div",{className:"flex items-end gap-[2px] h-3",children:Array.from({length:4}).map((e,a)=>t.jsx("div",{className:s("w-[3px] rounded-[1px] transition-colors",l(c,a<o)),style:{height:(a+1)/4*100+"%"}},a))})]}):t.jsxs("div",{className:"flex items-center justify-end gap-2",children:[r&&t.jsxs("div",{className:"flex flex-col items-end",children:[t.jsxs("span",{className:"text-xs font-mono text-text-secondary leading-tight",children:[e," dBm"]}),void 0!==a&&t.jsxs("span",{className:"text-[10px] font-mono text-text-muted leading-tight",children:[a.toFixed(1)," dB"]})]}),t.jsx("div",{className:"flex items-end gap-[2px] h-3.5",children:Array.from({length:4}).map((e,a)=>t.jsx("div",{className:s("w-[3px] rounded-[1px] transition-colors",l(c,a<o)),style:{height:(a+1)/4*100+"%"}},a))})]})});function u(e){const t=d(e);return t.charAt(0).toUpperCase()+t.slice(1)}export{h as S,x as a,u as g};
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1 +1 @@
import{b as e}from"./recharts-bmHCCfxU.js";import{j as t}from"./index-D3k5VxbW.js";function o(t,o){const[s,r]=e.useState(t);return e.useEffect(()=>{const e=setTimeout(()=>r(t),o);return()=>clearTimeout(e)},[t,o]),s}const s=e.memo(function({ranges:e,selectedIndex:o,onSelect:s}){return t.jsx("div",{className:"toggle-group flex-shrink-0 overflow-x-auto",children:e.map((e,r)=>t.jsx("button",{onClick:()=>s(r),className:"toggle-group-item "+(o===r?"active":""),children:e.label},e.label))})});export{s as T,o as u};
import{b as e}from"./recharts-bmHCCfxU.js";import{j as t}from"./index-CV7v97nI.js";function o(t,o){const[s,r]=e.useState(t);return e.useEffect(()=>{const e=setTimeout(()=>r(t),o);return()=>clearTimeout(e)},[t,o]),s}const s=e.memo(function({ranges:e,selectedIndex:o,onSelect:s}){return t.jsx("div",{className:"toggle-group flex-shrink-0 overflow-x-auto",children:e.map((e,r)=>t.jsx("button",{onClick:()=>s(r),className:"toggle-group-item "+(o===r?"active":""),children:e.label},e.label))})});export{s as T,o as u};
@@ -1 +1 @@
import{c as a}from"./index-D3k5VxbW.js";const t=a("activity",[["path",{d:"M22 12h-2.48a2 2 0 0 0-1.93 1.46l-2.35 8.36a.25.25 0 0 1-.48 0L9.24 2.18a.25.25 0 0 0-.48 0l-2.35 8.36A2 2 0 0 1 4.49 12H2",key:"169zse"}]]);export{t as A};
import{c as a}from"./index-CV7v97nI.js";const t=a("activity",[["path",{d:"M22 12h-2.48a2 2 0 0 0-1.93 1.46l-2.35 8.36a.25.25 0 0 1-.48 0L9.24 2.18a.25.25 0 0 0-.48 0l-2.35 8.36A2 2 0 0 1 4.49 12H2",key:"169zse"}]]);export{t as A};
+1
View File
@@ -0,0 +1 @@
import{c}from"./index-CV7v97nI.js";const r=c("circle",[["circle",{cx:"12",cy:"12",r:"10",key:"1mglay"}]]);export{r as C};
-1
View File
@@ -1 +0,0 @@
import{c}from"./index-D3k5VxbW.js";const r=c("circle",[["circle",{cx:"12",cy:"12",r:"10",key:"1mglay"}]]);export{r as C};
@@ -1 +1 @@
import{c as a}from"./index-D3k5VxbW.js";const s=a("house",[["path",{d:"M15 21v-8a1 1 0 0 0-1-1h-4a1 1 0 0 0-1 1v8",key:"5wwlr5"}],["path",{d:"M3 10a2 2 0 0 1 .709-1.528l7-6a2 2 0 0 1 2.582 0l7 6A2 2 0 0 1 21 10v9a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z",key:"r6nss1"}]]);export{s as H};
import{c as a}from"./index-CV7v97nI.js";const s=a("house",[["path",{d:"M15 21v-8a1 1 0 0 0-1-1h-4a1 1 0 0 0-1 1v8",key:"5wwlr5"}],["path",{d:"M3 10a2 2 0 0 1 .709-1.528l7-6a2 2 0 0 1 2.582 0l7 6A2 2 0 0 1 21 10v9a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z",key:"r6nss1"}]]);export{s as H};
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1 +1 @@
import{c as e}from"./index-D3k5VxbW.js";const c=e("info",[["circle",{cx:"12",cy:"12",r:"10",key:"1mglay"}],["path",{d:"M12 16v-4",key:"1dtifu"}],["path",{d:"M12 8h.01",key:"e9boi3"}]]);export{c as I};
import{c as e}from"./index-CV7v97nI.js";const c=e("info",[["circle",{cx:"12",cy:"12",r:"10",key:"1mglay"}],["path",{d:"M12 16v-4",key:"1dtifu"}],["path",{d:"M12 8h.01",key:"e9boi3"}]]);export{c as I};
@@ -1 +1 @@
import{c as a}from"./index-D3k5VxbW.js";const e=a("loader-circle",[["path",{d:"M21 12a9 9 0 1 1-6.219-8.56",key:"13zald"}]]);export{e as L};
import{c as a}from"./index-CV7v97nI.js";const e=a("loader-circle",[["path",{d:"M21 12a9 9 0 1 1-6.219-8.56",key:"13zald"}]]);export{e as L};
File diff suppressed because one or more lines are too long
@@ -1 +1 @@
import{c as a}from"./index-D3k5VxbW.js";const e=a("refresh-cw",[["path",{d:"M3 12a9 9 0 0 1 9-9 9.75 9.75 0 0 1 6.74 2.74L21 8",key:"v9h5vc"}],["path",{d:"M21 3v5h-5",key:"1q7to0"}],["path",{d:"M21 12a9 9 0 0 1-9 9 9.75 9.75 0 0 1-6.74-2.74L3 16",key:"3uifl3"}],["path",{d:"M8 16H3v5",key:"1cv678"}]]);export{e as R};
import{c as a}from"./index-CV7v97nI.js";const e=a("refresh-cw",[["path",{d:"M3 12a9 9 0 0 1 9-9 9.75 9.75 0 0 1 6.74 2.74L21 8",key:"v9h5vc"}],["path",{d:"M21 3v5h-5",key:"1q7to0"}],["path",{d:"M21 12a9 9 0 0 1-9 9 9.75 9.75 0 0 1-6.74-2.74L3 16",key:"3uifl3"}],["path",{d:"M8 16H3v5",key:"1cv678"}]]);export{e as R};
@@ -1 +1 @@
import{c as t}from"./index-D3k5VxbW.js";const e=t("network",[["rect",{x:"16",y:"16",width:"6",height:"6",rx:"1",key:"4q2zg0"}],["rect",{x:"2",y:"16",width:"6",height:"6",rx:"1",key:"8cvhb9"}],["rect",{x:"9",y:"2",width:"6",height:"6",rx:"1",key:"1egb70"}],["path",{d:"M5 16v-3a1 1 0 0 1 1-1h12a1 1 0 0 1 1 1v3",key:"1jsf9p"}],["path",{d:"M12 12V8",key:"2874zd"}]]),h=t("trending-up",[["path",{d:"M16 7h6v6",key:"box55l"}],["path",{d:"m22 7-8.5 8.5-5-5L2 17",key:"1t1m79"}]]);export{e as N,h as T};
import{c as t}from"./index-CV7v97nI.js";const e=t("network",[["rect",{x:"16",y:"16",width:"6",height:"6",rx:"1",key:"4q2zg0"}],["rect",{x:"2",y:"16",width:"6",height:"6",rx:"1",key:"8cvhb9"}],["rect",{x:"9",y:"2",width:"6",height:"6",rx:"1",key:"1egb70"}],["path",{d:"M5 16v-3a1 1 0 0 1 1-1h12a1 1 0 0 1 1 1v3",key:"1jsf9p"}],["path",{d:"M12 12V8",key:"2874zd"}]]),h=t("trending-up",[["path",{d:"M16 7h6v6",key:"box55l"}],["path",{d:"m22 7-8.5 8.5-5-5L2 17",key:"1t1m79"}]]);export{e as N,h as T};
@@ -1 +1 @@
import{c as a}from"./index-D3k5VxbW.js";const e=a("triangle-alert",[["path",{d:"m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3",key:"wmoenq"}],["path",{d:"M12 9v4",key:"juzpu7"}],["path",{d:"M12 17h.01",key:"p32p05"}]]);export{e as T};
import{c as a}from"./index-CV7v97nI.js";const e=a("triangle-alert",[["path",{d:"m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3",key:"wmoenq"}],["path",{d:"M12 9v4",key:"juzpu7"}],["path",{d:"M12 17h.01",key:"p32p05"}]]);export{e as T};
@@ -1 +1 @@
import{c as a}from"./index-D3k5VxbW.js";const e=a("users",[["path",{d:"M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2",key:"1yyitq"}],["path",{d:"M16 3.128a4 4 0 0 1 0 7.744",key:"16gr8j"}],["path",{d:"M22 21v-2a4 4 0 0 0-3-3.87",key:"kshegd"}],["circle",{cx:"9",cy:"7",r:"4",key:"nufk8"}]]);export{e as U};
import{c as a}from"./index-CV7v97nI.js";const e=a("users",[["path",{d:"M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2",key:"1yyitq"}],["path",{d:"M16 3.128a4 4 0 0 1 0 7.744",key:"16gr8j"}],["path",{d:"M22 21v-2a4 4 0 0 0-3-3.87",key:"kshegd"}],["circle",{cx:"9",cy:"7",r:"4",key:"nufk8"}]]);export{e as U};
@@ -1 +1 @@
import{c as a}from"./index-D3k5VxbW.js";const t=a("minus",[["path",{d:"M5 12h14",key:"1ays0h"}]]),d=a("trending-down",[["path",{d:"M16 17h6v-6",key:"t6n2it"}],["path",{d:"m22 17-8.5-8.5-5 5L2 7",key:"x473p"}]]),h=a("zap",[["path",{d:"M4 14a1 1 0 0 1-.78-1.63l9.9-10.2a.5.5 0 0 1 .86.46l-1.92 6.02A1 1 0 0 0 13 10h7a1 1 0 0 1 .78 1.63l-9.9 10.2a.5.5 0 0 1-.86-.46l1.92-6.02A1 1 0 0 0 11 14z",key:"1xq2db"}]]);export{t as M,d as T,h as Z};
import{c as a}from"./index-CV7v97nI.js";const t=a("minus",[["path",{d:"M5 12h14",key:"1ays0h"}]]),d=a("trending-down",[["path",{d:"M16 17h6v-6",key:"t6n2it"}],["path",{d:"m22 17-8.5-8.5-5 5L2 7",key:"x473p"}]]),h=a("zap",[["path",{d:"M4 14a1 1 0 0 1-.78-1.63l9.9-10.2a.5.5 0 0 1 .86.46l-1.92 6.02A1 1 0 0 0 13 10h7a1 1 0 0 1 .78 1.63l-9.9 10.2a.5.5 0 0 1-.86-.46l1.92-6.02A1 1 0 0 0 11 14z",key:"1xq2db"}]]);export{t as M,d as T,h as Z};
+2 -2
View File
@@ -38,10 +38,10 @@
--font-data: 'JetBrains Mono', 'SF Mono', Monaco, monospace;
}
</style>
<script type="module" crossorigin src="/assets/index-D3k5VxbW.js"></script>
<script type="module" crossorigin src="/assets/index-CV7v97nI.js"></script>
<link rel="modulepreload" crossorigin href="/assets/maplibre-gl-CFO9X1Ek.js">
<link rel="modulepreload" crossorigin href="/assets/recharts-bmHCCfxU.js">
<link rel="stylesheet" crossorigin href="/assets/index-Cy8Uf58w.css">
<link rel="stylesheet" crossorigin href="/assets/index-Bmz50KcY.css">
</head>
<body>
<div id="root"></div>
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "pymc_console",
"version": "0.7.1",
"version": "0.7.2",
"description": "Vite + React Dashboard for pyMC_Repeater",
"private": true,
"type": "module",
+36 -332
View File
@@ -902,11 +902,10 @@ do_install() {
# Apply patches to /opt/pymc_repeater (the installed location, not the clone)
print_info "Patching installed files..."
patch_api_endpoints "$INSTALL_DIR" # PATCH 1: Radio config API endpoint
# PATCH 1 & 5 removed - merged upstream in PR #36 (feat/identity branch)
patch_logging_section "$INSTALL_DIR" # PATCH 2: Ensure logging section exists
patch_log_level_api "$INSTALL_DIR" # PATCH 3: Log level toggle API
patch_mesh_cli "$INSTALL_DIR" # PATCH 4: MeshCore CLI parity
patch_stats_api "$INSTALL_DIR" # PATCH 5: Extend stats API with MeshCore config
patch_private_key_api "$INSTALL_DIR" # PATCH 6: Private key get/set API
# =========================================================================
@@ -2273,22 +2272,20 @@ run_upstream_installer() {
}
# ============================================================================
# UPSTREAM PATCHES
# PATCH REGISTRY
# ============================================================================
# These patches modify pyMC_Repeater to support pymc_console features.
# They are applied during install/upgrade and should be converted to a
# clean PR for https://github.com/rightup/pyMC_Repeater once stable.
# Core patches that enhance pyMC_Repeater with pyMC Console features.
# These patches are candidates for upstream PR submission.
#
# PATCH REGISTRY:
# ---------------
# 1. patch_api_endpoints (api_endpoints.py)
# - Adds POST /api/update_radio_config endpoint
# - Allows web UI to update radio settings and save to config.yaml
# - PR Status: Pending
# REMOVED (Merged Upstream in PR #36 - feat/identity branch):
# - patch_api_endpoints - /api/update_radio_config endpoint
# - patch_stats_api - Extended /api/stats with max_flood_hops, advert_interval_minutes, rx_delay_base
#
# Remaining Patches:
#
# 2. patch_logging_section (main.py)
# - Ensures config['logging'] exists before setting level from --log-level
# - Prevents KeyError when config.yaml lacks 'logging' section (affects DEBUG arg)
# - Ensures config['logging'] exists before setting level from --log-level arg
# - Fixes KeyError when service starts with --log-level DEBUG
# - PR Status: Pending
#
# 3. patch_log_level_api (api_endpoints.py)
@@ -2302,10 +2299,11 @@ run_upstream_installer() {
# - Implemented via external Python patch script (patches/mesh_cli_enhancements.py)
# - PR Status: Pending
#
# 5. patch_stats_api (engine.py)
# - Extends /api/stats config with MeshCore CLI-accessible values
# - Adds: max_flood_hops, advert_interval_minutes, rx_delay_base to config.repeater
# - Enables Terminal `get flood.max`, `get advert.interval`, `get rxdelay` commands
# 6. patch_private_key_api (mesh_cli.py)
# - Adds get/set prv.key for private key management via Terminal
# - Stores key in config['mesh']['identity_key']
# - PR Status: Pending
#
# - PR Status: Pending
#
# NOTE: patch_static_file_serving was removed in v0.4.0 (SPA migration).
@@ -2324,286 +2322,20 @@ run_upstream_installer() {
# ============================================================================
# ------------------------------------------------------------------------------
# PATCH 1: Radio Configuration API Endpoint
# PATCH 1: Radio Configuration API Endpoint [REMOVED - MERGED UPSTREAM PR #36]
# ------------------------------------------------------------------------------
# File: repeater/web/api_endpoints.py
# Purpose: Allow web UI to update radio settings without SSH/CLI
# Changes:
# - Add POST /api/update_radio_config endpoint
# - Accepts: frequency_mhz, bandwidth_khz, spreading_factor, coding_rate, tx_power
# - Validates input ranges (SF 5-12, CR 5-8, power 2-22 dBm)
# - Saves to config.yaml via existing _save_config_to_file()
# - Returns restart_required: true (live radio update not yet supported)
# This patch was merged upstream in PR #36 to the feat/identity branch.
# The patch is preserved below (commented out) for reference and for users
# who may still be on main/dev branches before the PR is merged there.
# ------------------------------------------------------------------------------
# Patch removed - see PR #36: https://github.com/rightup/pyMC_Repeater/pull/36
patch_api_endpoints() {
local target_dir="${1:-$CLONE_DIR}"
local api_file="$target_dir/repeater/web/api_endpoints.py"
if [ ! -f "$api_file" ]; then
print_warning "api_endpoints.py not found, skipping patch"
return 0
fi
# Patch version - increment when patch content changes
local PATCH_VERSION="5" # v1: radio only, v2: +delay settings, v3: TX power 30dBm, v4: +repeater config, v5: live updates
# Check if current version is already applied
if grep -q "PYMC_CONSOLE_PATCH_V${PATCH_VERSION}" "$api_file" 2>/dev/null; then
print_info "API endpoints patch v${PATCH_VERSION} already applied"
return 0
fi
# Remove any existing patch (versioned or legacy) before applying new one
if grep -q 'def update_radio_config' "$api_file" 2>/dev/null; then
local old_ver="legacy"
if grep -q 'PYMC_CONSOLE_PATCH_V' "$api_file" 2>/dev/null; then
old_ver=$(grep -o 'PYMC_CONSOLE_PATCH_V[0-9]*' "$api_file" | head -1 | sed 's/PYMC_CONSOLE_PATCH_V//')
fi
print_info "Upgrading API patch v${old_ver} → v${PATCH_VERSION}..."
python3 << REMOVEPATCH
import re
with open("$api_file", 'r') as f:
content = f.read()
# Remove update_radio_config method entirely (handles both versioned and legacy)
pattern = r'\n( # PYMC_CONSOLE_PATCH[^\n]*\n)? @cherrypy\.expose\n @cherrypy\.tools\.json_out\(\)\n @cherrypy\.tools\.json_in\(\)\n def update_radio_config\(self\):.*?return self\._error\(str\(e\)\)'
content = re.sub(pattern, '', content, flags=re.DOTALL)
with open("$api_file", 'w') as f:
f.write(content)
print("Removed old patch")
REMOVEPATCH
fi
# Use Python to add the endpoint (note: no quotes around PATCHEOF to allow variable expansion)
python3 << PATCHEOF
import re
api_file = "$api_file"
with open(api_file, 'r') as f:
content = f.read()
# Add update_radio_config endpoint after save_cad_settings
update_radio_config_code = '''
# PYMC_CONSOLE_PATCH_V5 - Radio, delay, and repeater config API with live updates
@cherrypy.expose
@cherrypy.tools.json_out()
@cherrypy.tools.json_in()
def update_radio_config(self):
"""Update radio configuration and save to config.yaml
POST /api/update_radio_config
Body: {
"frequency_mhz": 906.875,
"bandwidth_khz": 250,
"spreading_factor": 10,
"coding_rate": 5,
"tx_power": 22
}
Returns: {"success": true, "data": {"applied": [...], "persisted": true, "restart_required": true}}
"""
try:
self._require_post()
data = cherrypy.request.json or {}
if not data:
return self._error("No configuration provided")
applied = []
# Ensure radio config section exists
if "radio" not in self.config:
self.config["radio"] = {}
# Update frequency (convert MHz to Hz for storage)
if "frequency_mhz" in data:
freq_hz = int(float(data["frequency_mhz"]) * 1_000_000)
self.config["radio"]["frequency"] = freq_hz
applied.append(f"frequency={data['frequency_mhz']}MHz")
# Update bandwidth (convert kHz to Hz for storage)
if "bandwidth_khz" in data:
bw_hz = int(float(data["bandwidth_khz"]) * 1000)
self.config["radio"]["bandwidth"] = bw_hz
applied.append(f"bandwidth={data['bandwidth_khz']}kHz")
# Update spreading factor
if "spreading_factor" in data:
sf = int(data["spreading_factor"])
if sf < 5 or sf > 12:
return self._error("Spreading factor must be 5-12")
self.config["radio"]["spreading_factor"] = sf
applied.append(f"SF={sf}")
# Update coding rate
if "coding_rate" in data:
cr = int(data["coding_rate"])
if cr < 5 or cr > 8:
return self._error("Coding rate must be 5-8 (4/5 to 4/8)")
self.config["radio"]["coding_rate"] = cr
applied.append(f"CR=4/{cr}")
# Update TX power (up to 30 dBm for high-power radios like E22-900M30S)
if "tx_power" in data:
power = int(data["tx_power"])
if power < 2 or power > 30:
return self._error("TX power must be 2-30 dBm")
self.config["radio"]["tx_power"] = power
applied.append(f"power={power}dBm")
# Ensure delays config section exists
if "delays" not in self.config:
self.config["delays"] = {}
# Update TX delay factor (af/txdelay)
if "tx_delay_factor" in data:
tdf = float(data["tx_delay_factor"])
if tdf < 0.0 or tdf > 5.0:
return self._error("TX delay factor must be 0.0-5.0")
self.config["delays"]["tx_delay_factor"] = tdf
applied.append(f"txdelay={tdf}")
# Update direct TX delay factor
if "direct_tx_delay_factor" in data:
dtdf = float(data["direct_tx_delay_factor"])
if dtdf < 0.0 or dtdf > 5.0:
return self._error("Direct TX delay factor must be 0.0-5.0")
self.config["delays"]["direct_tx_delay_factor"] = dtdf
applied.append(f"direct.txdelay={dtdf}")
# Update RX delay base
if "rx_delay_base" in data:
rxd = float(data["rx_delay_base"])
if rxd < 0.0:
return self._error("RX delay cannot be negative")
self.config["delays"]["rx_delay_base"] = rxd
applied.append(f"rxdelay={rxd}")
# Ensure repeater config section exists
if "repeater" not in self.config:
self.config["repeater"] = {}
# Update node name
if "node_name" in data:
name = str(data["node_name"]).strip()
if not name:
return self._error("Node name cannot be empty")
self.config["repeater"]["node_name"] = name
applied.append(f"name={name}")
# Update latitude
if "latitude" in data:
lat = float(data["latitude"])
if lat < -90 or lat > 90:
return self._error("Latitude must be -90 to 90")
self.config["repeater"]["latitude"] = lat
applied.append(f"lat={lat}")
# Update longitude
if "longitude" in data:
lon = float(data["longitude"])
if lon < -180 or lon > 180:
return self._error("Longitude must be -180 to 180")
self.config["repeater"]["longitude"] = lon
applied.append(f"lon={lon}")
# Update max flood hops
if "max_flood_hops" in data:
hops = int(data["max_flood_hops"])
if hops < 0 or hops > 64:
return self._error("Max flood hops must be 0-64")
self.config["repeater"]["max_flood_hops"] = hops
applied.append(f"flood.max={hops}")
# Update flood advert interval (hours)
if "flood_advert_interval_hours" in data:
hours = int(data["flood_advert_interval_hours"])
if hours != 0 and (hours < 3 or hours > 48):
return self._error("Flood advert interval must be 0 (off) or 3-48 hours")
self.config["repeater"]["send_advert_interval_hours"] = hours
applied.append(f"flood.advert.interval={hours}h")
# Update local advert interval (minutes)
if "advert_interval_minutes" in data:
mins = int(data["advert_interval_minutes"])
if mins != 0 and (mins < 1 or mins > 10080):
return self._error("Advert interval must be 0 (off) or 1-10080 minutes")
self.config["repeater"]["advert_interval_minutes"] = mins
applied.append(f"advert.interval={mins}m")
if not applied:
return self._error("No valid settings provided")
# Save to config file
config_path = getattr(self, '_config_path', '/etc/pymc_repeater/config.yaml')
self._save_config_to_file(config_path)
# Live update: Also update daemon's in-memory config for immediate effect
live_updated = False
if self.daemon_instance and hasattr(self.daemon_instance, 'config'):
try:
daemon_config = self.daemon_instance.config
# Update repeater section in daemon config
if 'repeater' not in daemon_config:
daemon_config['repeater'] = {}
for key in ['node_name', 'latitude', 'longitude', 'max_flood_hops',
'advert_interval_minutes', 'send_advert_interval_hours']:
if key in self.config.get('repeater', {}):
daemon_config['repeater'][key] = self.config['repeater'][key]
# Update delays section in daemon config
if 'delays' not in daemon_config:
daemon_config['delays'] = {}
for key in ['tx_delay_factor', 'direct_tx_delay_factor', 'rx_delay_base']:
if key in self.config.get('delays', {}):
daemon_config['delays'][key] = self.config['delays'][key]
live_updated = True
logger.info("Live updated daemon config")
except Exception as e:
logger.warning(f"Could not live update daemon config: {e}")
logger.info(f"Radio config updated: {', '.join(applied)}")
return self._success({
"applied": applied,
"persisted": True,
"live_update": live_updated,
"restart_required": not live_updated,
"message": "Settings applied immediately." if live_updated else "Settings saved. Restart service to apply changes."
})
except cherrypy.HTTPError:
raise
except Exception as e:
logger.error(f"Error updating radio config: {e}")
return self._error(str(e))
'''
# Find the save_cad_settings method and insert after it
# Look for the end of save_cad_settings (the except block)
pattern = r'( def save_cad_settings\(self\):.*?return self\._error\(e\))'
match = re.search(pattern, content, re.DOTALL)
if match:
insert_pos = match.end()
content = content[:insert_pos] + update_radio_config_code + content[insert_pos:]
with open(api_file, 'w') as f:
f.write(content)
print("Patched api_endpoints.py with update_radio_config")
else:
print("Could not find insertion point for update_radio_config")
PATCHEOF
# Verify patch was applied
if grep -q 'def update_radio_config' "$api_file" 2>/dev/null; then
print_success "Patched api_endpoints.py with update_radio_config"
else
print_warning "API patch may not have applied correctly"
fi
# DISABLED: This patch was merged upstream in PR #36 (feat/identity branch)
# See: https://github.com/rightup/pyMC_Repeater/pull/36
# The /api/update_radio_config endpoint is now part of upstream pyMC_Repeater
print_info "API endpoints patch skipped (merged upstream in PR #36)"
return 0
}
# ------------------------------------------------------------------------------
@@ -2847,46 +2579,18 @@ patch_mesh_cli() {
}
# ------------------------------------------------------------------------------
# PATCH 5: Stats API Extension (engine.py)
# PATCH 5: Stats API Extension [REMOVED - MERGED UPSTREAM PR #36]
# ------------------------------------------------------------------------------
# File: repeater/engine.py
# Purpose: Expose additional config values in /api/stats for Terminal CLI
# Changes:
# - Add max_flood_hops to config.repeater (for 'get flood.max')
# - Add advert_interval_minutes to config.repeater (for 'get advert.interval')
# - Add rx_delay_base to config.delays (for 'get rxdelay')
# PR Status: Pending upstream submission
# This patch was merged upstream in PR #36 to the feat/identity branch.
# The /api/stats endpoint now includes max_flood_hops, advert_interval_minutes,
# and rx_delay_base in the response.
# ------------------------------------------------------------------------------
# Patch removed - see PR #36: https://github.com/rightup/pyMC_Repeater/pull/36
patch_stats_api() {
local target_dir="${1:-$CLONE_DIR}"
local engine_file="$target_dir/repeater/engine.py"
local script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
local patch_script="$script_dir/patches/stats_api_extension.py"
if [ ! -f "$engine_file" ]; then
print_warning "engine.py not found, skipping stats API patch"
return 0
fi
# Check if already patched
if grep -q 'PYMC_CONSOLE_STATS_PATCH' "$engine_file" 2>/dev/null; then
print_info "Stats API patch already applied"
return 0
fi
# Check if patch script exists
if [ ! -f "$patch_script" ]; then
print_warning "Patch script not found: $patch_script"
print_info "Skipping stats API extension"
return 0
fi
# Run the Python patch script
if python3 "$patch_script" "$engine_file" 2>/dev/null; then
print_success "Applied stats API extension patch"
else
print_warning "Stats API patch may not have applied correctly"
fi
# Patch disabled - merged upstream in PR #36 (feat/identity branch)
print_info "Stats API patch skipped (merged upstream in PR #36)"
return 0
}
# ------------------------------------------------------------------------------