/* Bee Smart Operations Portal — shared kit: tokens, Icon, app shell, primitives. ADD-TO the Bee Smart design system. Loaded after React + Babel + Lucide. */ const O = { azure:'#1F6FE0', azure6:'#1657BE', azure7:'#14479B', azure4:'#3D82E6', azure1:'#CFE2FB', azure05:'#EAF2FD', leaf:'#1B8E54', leaf4:'#2FA968', leaf1:'#C7EBD3', leaf05:'#E7F6EC', cyan:'#15B5DE', teal:'#0FA89A', gold:'#C49A22', goldDeep:'#8F6E14', // status / severity ok:'#1B8E54', okBg:'#E7F6EC', warn:'#C77D12', warnBg:'#FBEFD6', crit:'#C8402B', critBg:'#FBE3DD', info:'#1F6FE0', infoBg:'#EAF2FD', idle:'#6E8298', idleBg:'#EDF1F6', // neutrals ink:'#16202B', fg1:'#16202B', fg2:'#3A4B5E', fg3:'#6E8298', faint:'#9DAEC2', bg:'#F6F8FB', raised:'#FFFFFF', sunken:'#EDF1F6', line:'#DDE4EE', lineS:'#C4D0DE', navy:'#0E1C33', navy2:'#16202B', sans:"'Hanken Grotesk', system-ui, sans-serif", disp:"'Archivo', system-ui, sans-serif", mono:"'IBM Plex Mono', monospace", }; const STATUS = { online: { c:O.ok, bg:O.okBg, label:'Online' }, offline: { c:O.crit, bg:O.critBg, label:'Offline' }, stale: { c:O.warn, bg:O.warnBg, label:'Stale' }, // s356 user ruling: STALE = late data (device reporting late) degraded: { c:O.goldDeep, bg:'#FAF1D8', label:'Degraded' }, // s356 user ruling: DEGRADED = network or platform performance (no device in this state yet) }; const SEV = { critical:{ c:O.crit, bg:O.critBg, label:'Critical', icon:'alert-octagon' }, warning: { c:O.warn, bg:O.warnBg, label:'Warning', icon:'alert-triangle' }, info: { c:O.info, bg:O.infoBg, label:'Info', icon:'info' }, }; const KIND = { gateway:{icon:'router', label:'Gateway'}, router:{icon:'share-2', label:'Router'}, sensor:{icon:'radio', label:'Sensor'} }; function OIcon({ name, size=18, color, style }){ const ref = React.useRef(null); React.useLayoutEffect(()=>{ const n=ref.current; if(!n||!window.lucide) return; n.innerHTML=''; const i=document.createElement('i'); i.setAttribute('data-lucide',name); n.appendChild(i); window.lucide.createIcons(); const s=n.querySelector('svg'); if(s){s.setAttribute('width',size);s.setAttribute('height',size);} }); return ; } function StatusPill({ status, sm }){ const s = STATUS[status]||STATUS.stale; return {s.label}; } function Battery({ pct }){ const c = pct<=25?O.crit:pct<50?O.warn:O.fg2; return {pct}% ; } // Sparkline (area) function Spark({ data, color=O.azure, w=120, h=34, fill=true }){ const max=Math.max(...data), min=Math.min(...data), rng=(max-min)||1; const pts=data.map((v,i)=>[i/(data.length-1)*w, h-((v-min)/rng)*(h-4)-2]); const d=pts.map((p,i)=>(i?'L':'M')+p[0].toFixed(1)+' '+p[1].toFixed(1)).join(' '); const area=d+` L ${w} ${h} L 0 ${h} Z`; const gid='sg'+Math.random().toString(36).slice(2,7); return {fill && } ; } // Donut for fleet composition / online ratio function Donut({ value, total, color=O.leaf, size=92, label }){ const r=size/2-8, C=2*Math.PI*r, pct=total?value/total:0; return
80?20:16,color:O.fg1,lineHeight:1}}>{Math.round(pct*100)}% {label&&{label}}
; } function Bars({ data, color=O.azure, h=56 }){ const max=Math.max(...data)||1; return
{data.map((v,i)=>
)}
; } function Card({ children, style, pad=18, onClick, className }){ const float = !!className && className.split(' ').includes('clk'); // .clk owns the shadow; drop the inline one so the :hover lift works return
{children}
; } function CardHead({ title, sub, right, icon }){ return
{icon&&}
{title}
{sub&&
{sub}
}
{right}
; } // Live freshness indicator (PM-167): "Live . updated Ns ago" + manual refresh. // Self-contained 1s tick so only this pill re-renders (no app-wide flicker). The // 30s auto-poll lives in OpsApp; this shows the age of the last successful load. function fmtAgoSec(s){ if(s<5) return 'just now'; if(s<60) return s+'s ago'; const m=Math.floor(s/60); if(m<60) return m+'m ago'; const h=Math.floor(m/60); return h+'h ago'; } function LiveIndicator({ lastUpdated, refreshing, onRefresh }){ const [,tick]=React.useState(0); React.useEffect(()=>{ const id=setInterval(()=>tick(t=>t+1),1000); return ()=>clearInterval(id); },[]); const ageSec = lastUpdated!=null ? Math.max(0,Math.floor((Date.now()-lastUpdated)/1000)) : null; const stale = ageSec!=null && ageSec>=600; // 10 min since the last successful fetch = data delayed (green->red; raises a Critical alert) const dot = refreshing ? O.azure : stale ? O.crit : O.leaf; const txt = refreshing ? 'Updating...' : ageSec==null ? 'Connecting...' : (stale?'Delayed':'Live')+' . updated '+fmtAgoSec(ageSec); return
{txt}
; } // ---- App shell: collapsible sidebar + topbar ---- const NAV = [ { id:'overview', label:'Overview', icon:'layout-dashboard' }, { id:'growers', label:'Growers', icon:'sprout' }, { id:'devices', label:'Devices', icon:'cpu' }, { id:'topology', label:'Network', icon:'share-2' }, { id:'integrations', label:'Integrations', icon:'plug' }, { id:'alerts', label:'Alerts', icon:'bell' }, ]; function Shell({ route, onNav, alertCount, children, siteFilter, onSite, lastUpdated, refreshing, onRefresh }){ const [open,setOpen]=React.useState(false); // mobile drawer return
{/* sidebar */} {open&&
setOpen(false)} className="ops-scrim" style={{position:'fixed',inset:0,background:'rgba(14,28,51,0.4)',zIndex:40}} />}
{/* topbar */}
{onRefresh && }
OT
Ops Team
NetOps · admin
{/* vNext canon ribbon: prototype-only chrome, hidden when served as production (PROD_PORTAL). */} {!PROD_PORTAL &&
Prototype - vNext Canon Planned next version of portal.tbdcloud.com - the sign-off surface. Live fleet via /api/fleet-snapshot, auto-refreshed every 30s. Not the production portal.
}
{children}
; } function PageTitle({ title, sub, right }){ return

{title}

{sub&&

{sub}

}
{right}
; } Object.assign(window,{ O, STATUS, SEV, KIND, OIcon, StatusPill, Battery, Spark, Donut, Bars, Card, CardHead, Shell, PageTitle, NAV });