{helper}
} -{helper}
} -{helper}
} -{helper}
} -{helper}
} -Lower = more notifications. "Warning" recommended for most rules.
-- {rule.trigger_type === 'schedule' - ? 'Send reports on a schedule (daily briefings, weekly digests)' - : 'React to alert conditions (fires, outages, weather warnings)'} -
-- {messageTypeOptions.find(m => m.value === rule.message_type)?.description} -
-- {deliveryOptions.find(d => d.value === (rule.delivery_type || ''))?.description} -
-{getExampleMessage()}
-This is an example of what this rule would send.
-- Alert delivery and scheduled reports. Rules define what triggers a notification and where it gets sent. -
-- Emergency alerts and rules with "Override Quiet Hours" enabled always deliver. -
- > - )} -{helper}
} +{helper}
} +{helper}
} +{helper}
} +{helper}
} +Lower = more notifications. "Warning" recommended for most rules.
++ {rule.trigger_type === 'schedule' + ? 'Send reports on a schedule (daily briefings, weekly digests)' + : 'React to alert conditions (fires, outages, weather warnings)'} +
++ {messageTypeOptions.find(m => m.value === rule.message_type)?.description} +
++ {deliveryOptions.find(d => d.value === (rule.delivery_type || ''))?.description} +
+{getExampleMessage()}
+This is an example of what this rule would send.
++ Alert delivery and scheduled reports. Rules define what triggers a notification and where it gets sent. +
++ Emergency alerts and rules with "Override Quiet Hours" enabled always deliver. +
+ > + )} +| {h} | + ))} +
|---|
| {cell} | + ))} +
{children}
+}
+
+// Topic section wrapper
+function TopicSection({ id, title, children }: { id: string; title: string; children: React.ReactNode }) {
+ return (
+ + Everything you need to understand and configure MeshAI's monitoring and alerting systems. +
+ + {/* Stream Gauges */} ++ MeshAI watches river and stream levels at gauges you configure. Each gauge reports two things: +
++ Water Level (Gage Height) — how high the water is, measured in feet. Important: this is NOT the depth of the river. It's the height above a fixed measuring point that's different at every gauge. A reading of "10 feet" at one gauge means something completely different than "10 feet" at another. You can only compare readings from the SAME gauge over time. +
++ Flow (Discharge) — how much water is moving past the gauge, in cubic feet per second (CFS). Think of it as the river's "throughput." For scale: +
++ Flood levels are set by the National Weather Service, not USGS. NWS looks at each specific gauge location and decides "at what water level does the road flood? At what level do buildings get water?" Those levels are different everywhere. +
+Action Stage — water is rising, time to start paying attention. Usually still inside the riverbanks.
+Minor Flood — low-lying roads start getting water on them. NWS issues a Flood Advisory.
+Moderate Flood — water in buildings near the river. Some people need to evacuate. NWS issues a Flood Warning.
+Major Flood — widespread flooding. Many people evacuating. Serious property damage.
++ MeshAI automatically looks up the flood levels for your gauge from NWS when you add a site. Some remote gauges don't have flood levels assigned — for those, you set them manually if you know what water levels cause problems in your area. +
+ ++ There's no official "drought stage" for most gauges. If you need to monitor low water (irrigation, fish habitat), set a manual low-water threshold based on what you know about your local river. +
+ +If NWS flood levels don't populate, your gauge may not have them. Set manual thresholds if you know your local conditions.
+ ++ MeshAI tracks active wildfire perimeters from the National Interagency Fire Center (NIFC). For each fire, you see the name, size, how much is contained, and how far it is from your mesh nodes. +
+ +For reference, 1,000 acres is about 1.5 square miles.
+ ++ Containment means the percentage of the fire's edge where firefighters have built a control line (a cleared strip to stop the fire from spreading further). It does NOT mean the fire is out inside that line. +
++ How fast can a fire travel? In grass with wind: up to 14 mph. In heavy timber: 1-6 mph. A fire 10 miles away could theoretically reach you in 1-2 hours under worst-case conditions, but typical spread is much slower. +
+ ++ Distance is the immediate concern. A small uncontained fire 10 km away is more dangerous right now than a huge fire 50 km away. But big fires have more energy and can grow fast under wind shifts — keep watching them. +
+ +
+ Just configure your state code (like
+ NASA's VIIRS satellites orbit the Earth and look for heat signatures on the ground. When they see something hot — a fire, a factory, a sunlit building — they flag it as a "hotspot." MeshAI checks these detections for your area. +
++ Why this matters: satellite hotspots show up hours before official fire perimeters are mapped. If a new fire starts near your mesh, the satellite might see it before anyone on the ground reports it. +
+ +Each detection gets a confidence rating:
++ Recommendation: Set the filter to "Nominal + High." If you include "Low" you'll get alerts for every hot parking lot on a summer day. +
+ +FRP (Fire Radiative Power) measures the heat output in megawatts. Think of it as "how hot is this thing":
+Setting the minimum FRP to 5 MW filters out most industrial and agricultural false alarms.
+ ++ MeshAI cross-references satellite hotspots against known NIFC fire perimeters. If a hotspot is NOT near any known fire, it gets flagged as a potential new ignition — maybe a new fire just started. These get elevated priority regardless of confidence level. +
+ ++ Satellite data arrives 1-3 hours after the satellite passes overhead. Each location gets observed about 6 times per day across all satellites, so there are multi-hour gaps. This is not real-time — it's "pretty recent." +
+ ++ MeshAI watches for NWS (National Weather Service) alerts affecting your area — warnings, watches, and advisories. +
+ ++ A single alert has all three. A hurricane warning for next week is "Severe + Future + Likely." A tornado spotted on the ground is "Extreme + Immediate + Observed." An air quality advisory is "Minor + Expected + Possible." +
+ ++ Moderate is recommended. It catches Watches (advance warning that conditions may worsen) and Advisories (conditions exist but aren't severe) while filtering out the informational stuff. +
+ ++ NWS wants to know who's using their API — not for approval, just so they can contact you if something breaks. You make it up: +
+No registration. No waiting. Just type it in.
+ ++ MeshAI tracks space weather — solar activity and its effects on Earth's magnetic field. This matters for radio operators because the sun directly controls how well HF radio works, and major solar events can affect all radio communications. +
+ +Think of SFI as a "how active is the sun" number. Higher = better for HF radio, but also higher risk of solar flares.
+Quick rule: SFI above 90 and Kp below 4 = good day for HF radio.
+ +Kp measures how disturbed Earth's magnetic field is, on a 0-9 scale. Higher = more disturbance = worse for HF radio but better for aurora viewing.
+NOAA's shorthand for three types of space weather events:
++ Bz measures the direction of the solar wind's magnetic field. When it points south (negative values), the solar wind can dump energy into Earth's magnetic field, causing storms. +
+Bz can change fast — minute to minute. What matters is whether it stays negative for hours, not brief dips.
+ ++ Sometimes the atmosphere creates an invisible "pipe" that traps radio signals and carries them much farther than normal. This is called tropospheric ducting. It mostly affects VHF and UHF frequencies. +
++ MeshAI watches for these conditions by analyzing weather data (temperature and humidity at different altitudes) over your mesh area. +
+ +MeshAI reports a "condition" based on the atmospheric profile:
+When ducting happens on your mesh:
+The dashboard shows a "dM/dz" value in "M-units/km." You don't need to understand the math — just know:
++ Just configure the latitude and longitude of the center of your mesh area in Config → Environmental → Ducting. MeshAI checks the atmospheric conditions there every 3 hours using free weather model data. No API key needed. +
+ ++ MeshAI pulls avalanche forecasts from your regional avalanche center during winter months. The danger scale has 5 levels and it's the same across all of North America. +
+ ++ Level 3 (Considerable) kills more people than any other level. People look at "3 out of 5" and think "middle of the road, probably okay." In reality, the risk roughly doubles at each step up the scale. Level 3 is where dangerous conditions overlap with people thinking they can handle it. +
+ ++ MeshAI only checks avalanche conditions during winter months (configurable, default December through April). Outside season, it shows "off season" and saves API calls. +
+ +
+ Go to
+ MeshAI monitors traffic speed on road segments you configure, using data from TomTom (real vehicles with navigation apps reporting their speed). +
+ +MeshAI compares current speed to "free-flow speed" (what traffic normally does when the road is empty). The ratio tells you how congested it is:
++ Note: "free-flow speed" is NOT the speed limit. It's what traffic actually does on that road when nobody's in the way. Drivers often exceed speed limits on open highways. +
+ +TomTom's confidence score tells you how much of the reading comes from real vehicles right now vs historical averages:
+Set minimum confidence to 0.7 to avoid false congestion alerts at night or on rural roads where few probe vehicles drive.
+ +Each "corridor" is a point on a road you want to monitor. To add one:
++ 511 systems report road closures, construction, weather events, mountain pass conditions, and incidents. Every state runs their own 511 system — there is no national API. +
+ ++ You need to find YOUR state's 511 developer API. MeshAI does not include a default URL because every state is different. Some states have free public APIs, some require registration, and some don't have developer APIs at all. +
+Configure in Config → Environmental → 511:
+Check your state's 511 or DOT website for developer information.
+MeshAI computes a 0-100 health score for your mesh network by looking at five areas:
++ Meshtastic radios share one LoRa channel. If too many nodes are transmitting too often, they step on each other and messages get lost. +
++ ⚠️ "Packet flooding" means a node sending too many RADIO PACKETS. This has nothing to do with water flooding. +
++ A normal Meshtastic node sends a packet every few minutes (announcing itself, reporting telemetry, updating position). If a node starts blasting packets every few seconds, something is wrong — firmware bug, stuck transmitter, or misconfiguration. +
++ Most Meshtastic radios (T-Beam, RAK4631, Heltec V3) use a single lithium battery cell. The voltage tells you how much charge is left: +
++ USB-powered nodes report 100% battery even if there's no battery installed. Battery alerts only matter for nodes actually running on battery power. +
+ ++ MeshAI marks a node as "offline" when it hasn't been heard for a configurable time period. Different node types need different thresholds: +
++ Rule of thumb: set the threshold to about 4× the node's beacon interval. Too tight and nodes will constantly flap "offline/online" from normal gaps. Too loose and real outages go unnoticed. +
+Each rule answers three questions:
++ Use "Add from Template" to start with a pre-built rule and customize it, or build from scratch with "Add Rule." +
+ ++ "Warning" is the sweet spot for most rules. You get alerted when something actually needs your attention without being overwhelmed by every minor event. +
+ ++ When enabled, non-emergency notifications are held during sleeping hours (default 10pm-6am). Emergency alerts and rules marked "Override Quiet Hours" always get through. +
+You can turn quiet hours off entirely if you don't want them.
+ ++ A webhook sends your alert as an HTTP POST to any URL. This one delivery method works with: +
++ MeshAI doesn't need to know what's on the other end. Give it the URL and it works. +
+
+ All commands use the
+ MeshAI isn't just commands — you can ask it questions in plain English. "How's the mesh doing?" "Is there any ducting?" "What's the fire situation?" "How's traffic on I-84?" It uses the live environmental data and mesh health data to answer. +
+
+ MeshAI's REST API is available at
s>=80?"#22c55e":s>=60?"#f59e0b":"#ef4444")(t),a=2*Math.PI*45,o=t/100*a;return _.jsx("div",{className:"flex flex-col items-center",children:_.jsxs("svg",{width:"140",height:"140",viewBox:"0 0 100 100",children:[_.jsx("circle",{cx:"50",cy:"50",r:"45",fill:"none",stroke:"#1e2a3a",strokeWidth:"8"}),_.jsx("circle",{cx:"50",cy:"50",r:"45",fill:"none",stroke:i,strokeWidth:"8",strokeLinecap:"round",strokeDasharray:a,strokeDashoffset:a-o,transform:"rotate(-90 50 50)",className:"transition-all duration-500"}),_.jsx("text",{x:"50",y:"46",textAnchor:"middle",className:"fill-slate-100 font-mono text-2xl font-bold",style:{fontSize:"24px"},children:t.toFixed(1)}),_.jsx("text",{x:"50",y:"62",textAnchor:"middle",className:"fill-slate-400 text-xs",style:{fontSize:"10px"},children:r})]})})}function xx({label:e,value:t}){const r=n=>n>=80?"bg-green-500":n>=60?"bg-amber-500":"bg-red-500";return _.jsxs("div",{className:"flex items-center gap-3",children:[_.jsx("div",{className:"w-24 text-xs text-slate-400 truncate",children:e}),_.jsx("div",{className:"flex-1 h-2 bg-border rounded-full overflow-hidden",children:_.jsx("div",{className:`h-full ${r(t)} transition-all duration-300`,style:{width:`${t}%`}})}),_.jsx("div",{className:"w-12 text-right text-xs font-mono text-slate-300",children:t.toFixed(1)})]})}function YDe({alert:e}){const r=(i=>{switch(i.toLowerCase()){case"critical":case"emergency":return{bg:"bg-red-500/10",border:"border-red-500",icon:Hy,iconColor:"text-red-500"};case"warning":return{bg:"bg-amber-500/10",border:"border-amber-500",icon:Ds,iconColor:"text-amber-500"};default:return{bg:"bg-green-500/10",border:"border-green-500",icon:bm,iconColor:"text-green-500"}}})(e.severity),n=r.icon;return _.jsxs("div",{className:`p-3 rounded-lg ${r.bg} border-l-2 ${r.border} flex items-start gap-3`,children:[_.jsx(n,{size:16,className:r.iconColor}),_.jsxs("div",{className:"flex-1 min-w-0",children:[_.jsx("div",{className:"text-sm text-slate-200",children:e.message}),_.jsx("div",{className:"text-xs text-slate-500 mt-1",children:e.timestamp||"Just now"})]})]})}function XDe({source:e}){const t=()=>e.is_loaded?e.last_error?"bg-amber-500":"bg-green-500":"bg-red-500";return _.jsxs("div",{className:"flex items-center gap-3 p-3 rounded-lg bg-bg-hover",children:[_.jsx("div",{className:`w-2 h-2 rounded-full ${t()}`}),_.jsxs("div",{className:"flex-1 min-w-0",children:[_.jsx("div",{className:"text-sm text-slate-200 truncate",children:e.name}),_.jsxs("div",{className:"text-xs text-slate-500",children:[e.node_count," nodes · ",e.type]})]})]})}function _x({icon:e,label:t,value:r,subvalue:n}){return _.jsxs("div",{className:"bg-bg-card border border-border rounded-lg p-4",children:[_.jsxs("div",{className:"flex items-center gap-2 text-slate-400 mb-2",children:[_.jsx(e,{size:14}),_.jsx("span",{className:"text-xs",children:t})]}),_.jsx("div",{className:"font-mono text-xl text-slate-100",children:r}),n&&_.jsx("div",{className:"text-xs text-slate-500 mt-1",children:n})]})}function kC({label:e,value:t}){const r=()=>t===0?"bg-green-500/20 text-green-400 border-green-500/50":t<=2?"bg-amber-500/20 text-amber-400 border-amber-500/50":"bg-red-500/20 text-red-400 border-red-500/50";return _.jsxs("span",{className:`px-2 py-1 rounded text-xs font-mono font-medium border ${r()}`,children:[e,t]})}function K$({label:e,value:t,unit:r,getColor:n}){const i=t!==void 0?n(t):"text-slate-400";return _.jsxs("div",{className:"text-center",children:[_.jsx("div",{className:"text-xs text-slate-500 mb-1",children:e}),_.jsx("div",{className:`font-mono text-3xl font-bold ${i}`,children:(t==null?void 0:t.toFixed(0))??"—"}),r&&_.jsx("div",{className:"text-xs text-slate-500",children:r})]})}function qDe({history:e}){var a;const t=W.useMemo(()=>!e||e.length===0?[]:e.slice(-16).map((o,s)=>({idx:s,value:o.value,time:o.time})),[e]);if(t.length===0)return null;const r=Math.max(...t.map(o=>o.value),5),n=((a=t[t.length-1])==null?void 0:a.value)??0,i=()=>r>5?"kpGradientRed":r>3?"kpGradientAmber":"kpGradientGreen";return _.jsxs("div",{className:"h-20 w-full",children:[_.jsx(KY,{width:"100%",height:"100%",children:_.jsxs(UDe,{data:t,margin:{top:5,right:5,bottom:5,left:5},children:[_.jsxs("defs",{children:[_.jsxs("linearGradient",{id:"kpGradientGreen",x1:"0",y1:"0",x2:"0",y2:"1",children:[_.jsx("stop",{offset:"0%",stopColor:"#22c55e",stopOpacity:.4}),_.jsx("stop",{offset:"100%",stopColor:"#22c55e",stopOpacity:.05})]}),_.jsxs("linearGradient",{id:"kpGradientAmber",x1:"0",y1:"0",x2:"0",y2:"1",children:[_.jsx("stop",{offset:"0%",stopColor:"#f59e0b",stopOpacity:.4}),_.jsx("stop",{offset:"100%",stopColor:"#f59e0b",stopOpacity:.05})]}),_.jsxs("linearGradient",{id:"kpGradientRed",x1:"0",y1:"0",x2:"0",y2:"1",children:[_.jsx("stop",{offset:"0%",stopColor:"#ef4444",stopOpacity:.4}),_.jsx("stop",{offset:"100%",stopColor:"#ef4444",stopOpacity:.05})]})]}),_.jsx(Mv,{domain:[0,Math.ceil(r)],hide:!0}),_.jsx(Cv,{dataKey:"idx",hide:!0}),_.jsx(ty,{y:3,stroke:"#f59e0b",strokeDasharray:"3 3",strokeOpacity:.5}),_.jsx(ty,{y:5,stroke:"#ef4444",strokeDasharray:"3 3",strokeOpacity:.5}),_.jsx(vu,{type:"monotone",dataKey:"value",stroke:n>5?"#ef4444":n>3?"#f59e0b":"#22c55e",fill:`url(#${i()})`,strokeWidth:2})]})}),_.jsxs("div",{className:"flex justify-between text-xs text-slate-600 px-1",children:[_.jsx("span",{children:"48h ago"}),_.jsx("span",{children:"now"})]})]})}function KDe({profile:e}){const t=W.useMemo(()=>!e||e.length===0?[]:[...e].sort((r,n)=>r.height_m-n.height_m).map(r=>({height:r.height_m,M:r.M})),[e]);return t.length===0?null:_.jsxs("div",{className:"h-24 w-full",children:[_.jsx(KY,{width:"100%",height:"100%",children:_.jsxs(HDe,{data:t,margin:{top:5,right:10,bottom:5,left:5},children:[_.jsx(Cv,{dataKey:"M",type:"number",domain:["dataMin - 20","dataMax + 20"],tick:{fontSize:10,fill:"#64748b"},tickLine:!1,axisLine:{stroke:"#334155"}}),_.jsx(Mv,{dataKey:"height",type:"number",domain:[0,"dataMax"],tick:{fontSize:10,fill:"#64748b"},tickLine:!1,axisLine:{stroke:"#334155"},tickFormatter:r=>`${(r/1e3).toFixed(1)}k`}),_.jsx(Jy,{type:"monotone",dataKey:"M",stroke:"#3b82f6",strokeWidth:2,dot:{r:3,fill:"#3b82f6"}})]})}),_.jsx("div",{className:"text-center text-xs text-slate-600",children:"M-units vs Height (km)"})]})}function QDe({swpc:e,ducting:t}){const r=a=>a>=120?"text-green-400":a>=80?"text-amber-400":"text-red-400",n=a=>a<=3?"text-green-400":a<=5?"text-amber-400":"text-red-400",i=a=>{if(!a)return null;const o={normal:"bg-green-500/20 text-green-400 border-green-500/50",super_refraction:"bg-amber-500/20 text-amber-400 border-amber-500/50",surface_duct:"bg-blue-500/20 text-blue-400 border-blue-500/50",elevated_duct:"bg-blue-500/20 text-blue-400 border-blue-500/50"},s={normal:"Normal",super_refraction:"Super Refraction",surface_duct:"Surface Duct",elevated_duct:"Elevated Duct"};return _.jsx("span",{className:`px-2 py-1 rounded text-xs font-medium border ${o[a]||o.normal}`,children:s[a]||a})};return _.jsxs("div",{className:"bg-bg-card border border-border rounded-lg p-4 flex flex-col h-full",children:[_.jsxs("h2",{className:"text-sm font-medium text-slate-400 mb-4 flex items-center gap-2",children:[_.jsx(wm,{size:14}),"RF Propagation"]}),_.jsxs("div",{className:"flex justify-around mb-4",children:[_.jsx(K$,{label:"SFI",value:e==null?void 0:e.sfi,getColor:r}),_.jsx("div",{className:"w-px bg-border"}),_.jsx(K$,{label:"Kp",value:e==null?void 0:e.kp_current,getColor:n})]}),_.jsxs("div",{className:"flex justify-center gap-2 mb-4",children:[_.jsx(kC,{label:"R",value:(e==null?void 0:e.r_scale)??0}),_.jsx(kC,{label:"S",value:(e==null?void 0:e.s_scale)??0}),_.jsx(kC,{label:"G",value:(e==null?void 0:e.g_scale)??0})]}),(e==null?void 0:e.kp_history)&&e.kp_history.length>0&&_.jsxs("div",{className:"mb-4",children:[_.jsx("div",{className:"text-xs text-slate-500 mb-1",children:"Kp Trend (48h)"}),_.jsx(qDe,{history:e.kp_history})]}),_.jsx("div",{className:"border-t border-border my-3"}),_.jsxs("div",{className:"flex items-center gap-2 mb-2",children:[_.jsx(Zc,{size:14,className:"text-slate-400"}),_.jsx("span",{className:"text-xs text-slate-500",children:"Tropospheric"}),i(t==null?void 0:t.condition)]}),(t==null?void 0:t.min_gradient)!==void 0&&_.jsxs("div",{className:"text-xs text-slate-400 font-mono mb-2",children:["dM/dz: ",t.min_gradient.toFixed(1)," M-units/km"]}),(t==null?void 0:t.profile)&&t.profile.length>0&&_.jsx(KDe,{profile:t.profile}),(e==null?void 0:e.active_warnings)&&e.active_warnings.length>0&&_.jsxs("div",{className:"mt-auto pt-3 border-t border-border",children:[_.jsx("div",{className:"text-xs text-slate-500 mb-1",children:"SWPC Alerts"}),_.jsx("div",{className:"flex flex-wrap gap-1",children:e.active_warnings.slice(0,3).map((a,o)=>_.jsx("span",{className:"px-2 py-0.5 rounded text-xs bg-amber-500/20 text-amber-400 border border-amber-500/30 truncate max-w-full",children:a.replace("Space Weather Message Code: ","")},o))})]})]})}const JDe={nws:{icon:Zc,color:"text-blue-400",label:"NWS"},swpc:{icon:Fb,color:"text-yellow-400",label:"SWPC"},ducting:{icon:eu,color:"text-cyan-400",label:"Tropo"},nifc:{icon:VE,color:"text-orange-400",label:"NIFC"},firms:{icon:WE,color:"text-red-400",label:"FIRMS"},avalanche:{icon:GE,color:"text-slate-300",label:"Avy"},usgs:{icon:zE,color:"text-blue-300",label:"USGS"},traffic:{icon:BE,color:"text-purple-400",label:"Traffic"},roads:{icon:gZ,color:"text-amber-400",label:"511"}},Q$={info:"bg-blue-500/20 text-blue-400 border-blue-500/30",advisory:"bg-amber-500/20 text-amber-400 border-amber-500/30",moderate:"bg-amber-500/20 text-amber-400 border-amber-500/30",watch:"bg-orange-500/20 text-orange-400 border-orange-500/30",warning:"bg-red-500/20 text-red-400 border-red-500/30",critical:"bg-red-600/20 text-red-300 border-red-600/30",emergency:"bg-red-700/20 text-red-200 border-red-700/30"};function eNe({event:e}){var a;const t=JDe[e.source]||{icon:bm,color:"text-slate-400",label:e.source},r=t.icon,n=Q$[(a=e.severity)==null?void 0:a.toLowerCase()]||Q$.info,i=o=>{const s=new Date(o*1e3),u=new Date().getTime()-s.getTime(),c=Math.floor(u/6e4);return c<1?"just now":c<60?`${c}m ago`:c<1440?`${Math.floor(c/60)}h ago`:s.toLocaleDateString(void 0,{month:"short",day:"numeric"})};return _.jsxs("div",{className:"flex items-start gap-2 py-2 border-b border-border/50 last:border-0",children:[_.jsx(r,{size:14,className:`mt-0.5 flex-shrink-0 ${t.color}`}),_.jsxs("div",{className:"flex-1 min-w-0",children:[_.jsxs("div",{className:"flex items-center gap-2 mb-0.5",children:[_.jsx("span",{className:`px-1.5 py-0.5 rounded text-xs border ${n}`,children:e.severity||"info"}),_.jsx("span",{className:"text-xs text-slate-500",children:t.label}),_.jsx("span",{className:"text-xs text-slate-600 ml-auto",children:i(e.fetched_at)})]}),_.jsx("div",{className:"text-sm text-slate-200 truncate",children:e.headline})]})]})}function tNe({events:e,envStatus:t}){const r=W.useMemo(()=>[...e].sort((i,a)=>(a.fetched_at||0)-(i.fetched_at||0)),[e]),n=W.useMemo(()=>{if(!(t!=null&&t.feeds))return null;const i=t.feeds.length,a=t.feeds.filter(u=>u.is_loaded&&!u.last_error).length,o=t.feeds.filter(u=>u.last_error).map(u=>u.source),s=Math.max(...t.feeds.map(u=>u.last_fetch||0)),l=s?Math.floor(Date.now()/1e3-s):null;return{total:i,active:a,errors:o,secAgo:l}},[t]);return _.jsxs("div",{className:"bg-bg-card border border-border rounded-lg p-4 flex flex-col h-full",children:[_.jsxs("h2",{className:"text-sm font-medium text-slate-400 mb-3 flex items-center gap-2",children:[_.jsx(Gy,{size:14}),"Live Event Feed"]}),r.length>0?_.jsx("div",{className:"flex-1 overflow-y-auto max-h-80 pr-1 -mr-1",children:r.map((i,a)=>_.jsx(eNe,{event:i},i.event_id||a))}):_.jsx("div",{className:"flex-1 flex items-center justify-center",children:_.jsxs("div",{className:"text-center py-8",children:[_.jsx(Kh,{size:24,className:"text-green-500 mx-auto mb-2"}),_.jsx("div",{className:"text-slate-400",children:"No active events"}),_.jsx("div",{className:"text-xs text-slate-500",children:"All clear"})]})}),n&&_.jsxs("div",{className:`text-xs mt-3 pt-3 border-t border-border ${n.errors.length>0?"text-amber-400":"text-slate-500"}`,children:[n.active," of ",n.total," feeds active",n.secAgo!==null&&` · Last update ${n.secAgo}s ago`,n.errors.length>0&&_.jsxs("span",{className:"text-amber-400",children:[" · ",n.errors.join(", "),": error"]})]})]})}function rNe(){var S,T,A,M,P;const[e,t]=W.useState(null),[r,n]=W.useState([]),[i,a]=W.useState([]),[o,s]=W.useState(null),[l,u]=W.useState([]),[c,f]=W.useState(null),[h,d]=W.useState(null),[v,g]=W.useState(!0),[m,y]=W.useState(null),{lastHealth:x,lastMessage:b}=ZE();return W.useEffect(()=>{Promise.all([ace(),lce(),PZ(),LZ(),kZ().catch(()=>[]),IZ().catch(()=>null),OZ().catch(()=>null)]).then(([k,I,O,D,N,B,$])=>{t(k),n(I),a(O),s(D),u(N),f(B),d($),g(!1),document.title="Dashboard — MeshAI"}).catch(k=>{y(k.message),g(!1),document.title="Dashboard — MeshAI"})},[]),W.useEffect(()=>{x&&t(x)},[x]),W.useEffect(()=>{(b==null?void 0:b.type)==="env_update"&&b.event&&u(k=>{const I=b.event,O=k.filter(D=>D.event_id!==I.event_id);return[I,...O].slice(0,100)})},[b]),v?_.jsx("div",{className:"flex items-center justify-center h-64",children:_.jsx("div",{className:"text-slate-400",children:"Loading..."})}):m?_.jsx("div",{className:"flex items-center justify-center h-64",children:_.jsxs("div",{className:"text-red-400",children:["Error: ",m]})}):_.jsxs("div",{className:"space-y-6",children:[_.jsxs("div",{className:"grid grid-cols-1 lg:grid-cols-3 gap-6",children:[_.jsxs("div",{className:"bg-bg-card border border-border rounded-lg p-6",children:[_.jsx("h2",{className:"text-sm font-medium text-slate-400 mb-4",children:"Mesh Health"}),e&&_.jsxs(_.Fragment,{children:[_.jsx(ZDe,{health:e}),_.jsxs("div",{className:"mt-6 space-y-3",children:[_.jsx(xx,{label:"Infrastructure",value:((S=e.pillars)==null?void 0:S.infrastructure)??0}),_.jsx(xx,{label:"Utilization",value:((T=e.pillars)==null?void 0:T.utilization)??0}),_.jsx(xx,{label:"Behavior",value:((A=e.pillars)==null?void 0:A.behavior)??0}),_.jsx(xx,{label:"Power",value:((M=e.pillars)==null?void 0:M.power)??0})]})]})]}),_.jsxs("div",{className:"lg:col-span-2 space-y-6",children:[_.jsxs("div",{className:"bg-bg-card border border-border rounded-lg p-6",children:[_.jsx("h2",{className:"text-sm font-medium text-slate-400 mb-4",children:"Active Alerts"}),i.length>0?_.jsx("div",{className:"space-y-3 max-h-48 overflow-y-auto",children:i.map((k,I)=>_.jsx(YDe,{alert:k},I))}):_.jsxs("div",{className:"flex items-center gap-2 text-slate-500 py-4",children:[_.jsx(Kh,{size:16,className:"text-green-500"}),_.jsx("span",{children:"No active alerts"})]})]}),_.jsxs("div",{className:"grid grid-cols-2 lg:grid-cols-4 gap-4",children:[_.jsx(_x,{icon:eu,label:"Nodes Online",value:(e==null?void 0:e.total_nodes)||0,subvalue:`${(e==null?void 0:e.unlocated_count)||0} unlocated`}),_.jsx(_x,{icon:mZ,label:"Infrastructure",value:`${(e==null?void 0:e.infra_online)||0}/${(e==null?void 0:e.infra_total)||0}`,subvalue:(e==null?void 0:e.infra_online)===(e==null?void 0:e.infra_total)?"All online":"Some offline"}),_.jsx(_x,{icon:Gy,label:"Utilization",value:`${((P=e==null?void 0:e.util_percent)==null?void 0:P.toFixed(1))||0}%`,subvalue:`${(e==null?void 0:e.flagged_nodes)||0} flagged`}),_.jsx(_x,{icon:bZ,label:"Regions",value:(e==null?void 0:e.total_regions)||0,subvalue:`${(e==null?void 0:e.battery_warnings)||0} battery warnings`})]})]})]}),_.jsxs("div",{className:"grid grid-cols-1 lg:grid-cols-3 gap-6",children:[_.jsxs("div",{className:"bg-bg-card border border-border rounded-lg p-6",children:[_.jsxs("h2",{className:"text-sm font-medium text-slate-400 mb-4",children:["Mesh Sources (",r.length,")"]}),r.length>0?_.jsx("div",{className:"space-y-2",children:r.map((k,I)=>_.jsx(XDe,{source:k},I))}):_.jsx("div",{className:"text-slate-500 py-4",children:"No sources configured"})]}),_.jsx(QDe,{swpc:c,ducting:h}),_.jsx(tNe,{events:l,envStatus:o})]})]})}/*! *****************************************************************************
+Copyright (c) Microsoft Corporation.
+
+Permission to use, copy, modify, and/or distribute this software for any
+purpose with or without fee is hereby granted.
+
+THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
+REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
+INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
+OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+PERFORMANCE OF THIS SOFTWARE.
+***************************************************************************** */var Ck=function(e,t){return Ck=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(r,n){r.__proto__=n}||function(r,n){for(var i in n)Object.prototype.hasOwnProperty.call(n,i)&&(r[i]=n[i])},Ck(e,t)};function q(e,t){if(typeof t!="function"&&t!==null)throw new TypeError("Class extends value "+String(t)+" is not a constructor or null");Ck(e,t);function r(){this.constructor=e}e.prototype=t===null?Object.create(t):(r.prototype=t.prototype,new r)}var Rg=function(){return Rg=Object.assign||function(t){for(var r,n=1,i=arguments.length;n0&&a[a.length-1])&&(u[0]===6||u[0]===2)){r=0;continue}if(u[0]===3&&(!a||u[1]>a[0]&&u[1]"u"&&typeof self<"u"?nt.worker=!0:!nt.hasGlobalWindow||"Deno"in window||typeof navigator<"u"&&typeof navigator.userAgent=="string"&&navigator.userAgent.indexOf("Node.js")>-1?(nt.node=!0,nt.svgSupported=!0):oNe(navigator.userAgent,nt);function oNe(e,t){var r=t.browser,n=e.match(/Firefox\/([\d.]+)/),i=e.match(/MSIE\s([\d.]+)/)||e.match(/Trident\/.+?rv:(([\d.]+))/),a=e.match(/Edge?\/([\d.]+)/),o=/micromessenger/i.test(e);n&&(r.firefox=!0,r.version=n[1]),i&&(r.ie=!0,r.version=i[1]),a&&(r.edge=!0,r.version=a[1],r.newEdge=+a[1].split(".")[0]>18),o&&(r.weChat=!0),t.svgSupported=typeof SVGRect<"u",t.touchEventsSupported="ontouchstart"in window&&!r.ie&&!r.edge,t.pointerEventsSupported="onpointerdown"in window&&(r.edge||r.ie&&+r.version>=11);var s=t.domSupported=typeof document<"u";if(s){var l=document.documentElement.style;t.transform3dSupported=(r.ie&&"transition"in l||r.edge||"WebKitCSSMatrix"in window&&"m11"in new WebKitCSSMatrix||"MozPerspective"in l)&&!("OTransition"in l),t.transformSupported=t.transform3dSupported||r.ie&&+r.version>=9}}var XD=12,dK="sans-serif",Bs=XD+"px "+dK,sNe=20,lNe=100,uNe="007LLmW'55;N0500LLLLLLLLLL00NNNLzWW\\\\WQb\\0FWLg\\bWb\\WQ\\WrWWQ000CL5LLFLL0LL**F*gLLLL5F0LF\\FFF5.5N";function cNe(e){var t={};if(typeof JSON>"u")return t;for(var r=0;r1&&n&&n.length>1){var a=aF(n)/aF(i);!isFinite(a)&&(a=1),t.pinchScale=a;var o=VNe(n);return t.pinchX=o[0],t.pinchY=o[1],{type:"pinch",target:e[0].target,event:t}}}}};function Wr(){return[1,0,0,1,0,0]}function n0(e){return e[0]=1,e[1]=0,e[2]=0,e[3]=1,e[4]=0,e[5]=0,e}function i0(e,t){return e[0]=t[0],e[1]=t[1],e[2]=t[2],e[3]=t[3],e[4]=t[4],e[5]=t[5],e}function Fa(e,t,r){var n=t[0]*r[0]+t[2]*r[1],i=t[1]*r[0]+t[3]*r[1],a=t[0]*r[2]+t[2]*r[3],o=t[1]*r[2]+t[3]*r[3],s=t[0]*r[4]+t[2]*r[5]+t[4],l=t[1]*r[4]+t[3]*r[5]+t[5];return e[0]=n,e[1]=i,e[2]=a,e[3]=o,e[4]=s,e[5]=l,e}function Ua(e,t,r){return e[0]=t[0],e[1]=t[1],e[2]=t[2],e[3]=t[3],e[4]=t[4]+r[0],e[5]=t[5]+r[1],e}function qs(e,t,r,n){n===void 0&&(n=[0,0]);var i=t[0],a=t[2],o=t[4],s=t[1],l=t[3],u=t[5],c=Math.sin(r),f=Math.cos(r);return e[0]=i*f+s*c,e[1]=-i*c+s*f,e[2]=a*f+l*c,e[3]=-a*c+f*l,e[4]=f*(o-n[0])+c*(u-n[1])+n[0],e[5]=f*(u-n[1])-c*(o-n[0])+n[1],e}function dT(e,t,r){var n=r[0],i=r[1];return e[0]=t[0]*n,e[1]=t[1]*i,e[2]=t[2]*n,e[3]=t[3]*i,e[4]=t[4]*n,e[5]=t[5]*i,e}function da(e,t){var r=t[0],n=t[2],i=t[4],a=t[1],o=t[3],s=t[5],l=r*o-a*n;return l?(l=1/l,e[0]=o*l,e[1]=-a*l,e[2]=-n*l,e[3]=r*l,e[4]=(n*s-o*i)*l,e[5]=(a*i-r*s)*l,e):null}function TK(e){var t=Wr();return i0(t,e),t}const GNe=Object.freeze(Object.defineProperty({__proto__:null,clone:TK,copy:i0,create:Wr,identity:n0,invert:da,mul:Fa,rotate:qs,scale:dT,translate:Ua},Symbol.toStringTag,{value:"Module"}));var Ie=function(){function e(t,r){this.x=t||0,this.y=r||0}return e.prototype.copy=function(t){return this.x=t.x,this.y=t.y,this},e.prototype.clone=function(){return new e(this.x,this.y)},e.prototype.set=function(t,r){return this.x=t,this.y=r,this},e.prototype.equal=function(t){return t.x===this.x&&t.y===this.y},e.prototype.add=function(t){return this.x+=t.x,this.y+=t.y,this},e.prototype.scale=function(t){this.x*=t,this.y*=t},e.prototype.scaleAndAdd=function(t,r){this.x+=t.x*r,this.y+=t.y*r},e.prototype.sub=function(t){return this.x-=t.x,this.y-=t.y,this},e.prototype.dot=function(t){return this.x*t.x+this.y*t.y},e.prototype.len=function(){return Math.sqrt(this.x*this.x+this.y*this.y)},e.prototype.lenSquare=function(){return this.x*this.x+this.y*this.y},e.prototype.normalize=function(){var t=this.len();return this.x/=t,this.y/=t,this},e.prototype.distance=function(t){var r=this.x-t.x,n=this.y-t.y;return Math.sqrt(r*r+n*n)},e.prototype.distanceSquare=function(t){var r=this.x-t.x,n=this.y-t.y;return r*r+n*n},e.prototype.negate=function(){return this.x=-this.x,this.y=-this.y,this},e.prototype.transform=function(t){if(t){var r=this.x,n=this.y;return this.x=t[0]*r+t[2]*n+t[4],this.y=t[1]*r+t[3]*n+t[5],this}},e.prototype.toArray=function(t){return t[0]=this.x,t[1]=this.y,t},e.prototype.fromArray=function(t){this.x=t[0],this.y=t[1]},e.set=function(t,r,n){t.x=r,t.y=n},e.copy=function(t,r){t.x=r.x,t.y=r.y},e.len=function(t){return Math.sqrt(t.x*t.x+t.y*t.y)},e.lenSquare=function(t){return t.x*t.x+t.y*t.y},e.dot=function(t,r){return t.x*r.x+t.y*r.y},e.add=function(t,r,n){t.x=r.x+n.x,t.y=r.y+n.y},e.sub=function(t,r,n){t.x=r.x-n.x,t.y=r.y-n.y},e.scale=function(t,r,n){t.x=r.x*n,t.y=r.y*n},e.scaleAndAdd=function(t,r,n,i){t.x=r.x+n.x*i,t.y=r.y+n.y*i},e.lerp=function(t,r,n,i){var a=1-i;t.x=a*r.x+i*n.x,t.y=a*r.y+i*n.y},e}(),wc=Math.min,Rh=Math.max,Ek=Math.abs,oF=["x","y"],WNe=["width","height"],Mu=new Ie,Pu=new Ie,Lu=new Ie,ku=new Ie,bi=AK(),cg=bi.minTv,Dk=bi.maxTv,Fg=[0,0],Oe=function(){function e(t,r,n,i){e.set(this,t,r,n,i)}return e.set=function(t,r,n,i,a){return i<0&&(r=r+i,i=-i),a<0&&(n=n+a,a=-a),t.x=r,t.y=n,t.width=i,t.height=a,t},e.prototype.union=function(t){var r=wc(t.x,this.x),n=wc(t.y,this.y);isFinite(this.x)&&isFinite(this.width)?this.width=Rh(t.x+t.width,this.x+this.width)-r:this.width=t.width,isFinite(this.y)&&isFinite(this.height)?this.height=Rh(t.y+t.height,this.y+this.height)-n:this.height=t.height,this.x=r,this.y=n},e.prototype.applyTransform=function(t){e.applyTransform(this,this,t)},e.prototype.calculateTransform=function(t){var r=this,n=t.width/r.width,i=t.height/r.height,a=Wr();return Ua(a,a,[-r.x,-r.y]),dT(a,a,[n,i]),Ua(a,a,[t.x,t.y]),a},e.prototype.intersect=function(t,r,n){return e.intersect(this,t,r,n)},e.intersect=function(t,r,n,i){n&&Ie.set(n,0,0);var a=i&&i.outIntersectRect||null,o=i&&i.clamp;if(a&&(a.x=a.y=a.width=a.height=NaN),!t||!r)return!1;t instanceof e||(t=e.set(HNe,t.x,t.y,t.width,t.height)),r instanceof e||(r=e.set(UNe,r.x,r.y,r.width,r.height));var s=!!n;bi.reset(i,s);var l=bi.touchThreshold,u=t.x+l,c=t.x+t.width-l,f=t.y+l,h=t.y+t.height-l,d=r.x+l,v=r.x+r.width-l,g=r.y+l,m=r.y+r.height-l;if(u>c||f>h||d>v||g>m)return!1;var y=!(c0;)o=l,l=(l<<1)+1,l<=0&&(l=s);l>s&&(l=s),o+=i,l+=i}else{for(s=i+1;ls&&(l=s);var u=o;o=i-l,l=i-u}for(o++;os&&(l=s);var u=o;o=i-l,l=i-u}else{for(s=n-i;l=0;)o=l,l=(l<<1)+1,l<=0&&(l=s);l>s&&(l=s),o+=i,l+=i}for(o++;or);c++);c=h(c-1,s-2)}v=o[c+1],d=o[c]}if(d&&v){this._lastFr=c,this._lastFrP=r;var m=v.percent-d.percent,y=m===0?1:h((r-d.percent)/m,1);v.easingFunc&&(y=v.easingFunc(y));var x=n?this._additiveValue:u?Pp:t[l];if((Lx(a)||u)&&!x&&(x=this._additiveValue=[]),this.discrete)t[l]=y<1?d.rawValue:v.rawValue;else if(Lx(a))a===K_?zC(x,d[i],v[i],y):wje(x,d[i],v[i],y);else if(_F(a)){var b=d[i],S=v[i],T=a===$k;t[l]={type:T?"linear":"radial",x:ss(b.x,S.x,y),y:ss(b.y,S.y,y),colorStops:se(b.colorStops,function(M,P){var k=S.colorStops[P];return{offset:ss(M.offset,k.offset,y),color:q_(zC([],M.color,k.color,y))}}),global:S.global},T?(t[l].x2=ss(b.x2,S.x2,y),t[l].y2=ss(b.y2,S.y2,y)):t[l].r=ss(b.r,S.r,y)}else if(u)zC(x,d[i],v[i],y),n||(t[l]=q_(x));else{var A=ss(d[i],v[i],y);n?this._additiveValue=A:t[l]=A}n&&this._addToTarget(t)}}},e.prototype._addToTarget=function(t){var r=this.valType,n=this.propName,i=this._additiveValue;r===Px?t[n]=t[n]+i:r===hg?(En(t[n],Pp),Mx(Pp,Pp,i,1),t[n]=q_(Pp)):r===K_?Mx(t[n],t[n],i,1):r===WK&&yF(t[n],t[n],i,1)},e}(),oN=function(){function e(t,r,n,i){if(this._tracks={},this._trackKeys=[],this._maxTime=0,this._started=0,this._clip=null,this._target=t,this._loop=r,r&&i){uT("Can' use additive animation on looped animation.");return}this._additiveAnimators=i,this._allowDiscrete=n}return e.prototype.getMaxTime=function(){return this._maxTime},e.prototype.getDelay=function(){return this._delay},e.prototype.getLoop=function(){return this._loop},e.prototype.getTarget=function(){return this._target},e.prototype.changeTarget=function(t){this._target=t},e.prototype.when=function(t,r,n){return this.whenWithKeys(t,r,it(r),n)},e.prototype.whenWithKeys=function(t,r,n,i){for(var a=this._tracks,o=0;o