From dfbb7ef16daada45c1e51b2dd5cde77fc5cfaf55 Mon Sep 17 00:00:00 2001 From: Mannu Date: Thu, 13 Aug 2026 02:13:53 +0530 Subject: [PATCH] feat: live service status dots, +4 services, richer markets widget --- index.html | 96 ++++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 72 insertions(+), 24 deletions(-) diff --git a/index.html b/index.html index 54760dd..f652b48 100644 --- a/index.html +++ b/index.html @@ -59,6 +59,7 @@ const SECTIONS=[ {id:"miniflux",name:"Miniflux",desc:"RSS reader",emoji:"๐Ÿ“ฐ",url:"https://feeds.manohargupta.com"}, {id:"changedetect",name:"ChangeDetection",desc:"Page change monitor",emoji:"๐Ÿ‘",url:"https://watch.manohargupta.com"}, {id:"openwebui",name:"Open WebUI",desc:"AI chat interface",emoji:"๐Ÿ’ฌ",url:"https://chat.manohargupta.com"}, + {id:"notes",name:"Notes",desc:"Obsidian vault portal",emoji:"๐Ÿชถ",url:"https://notes.manohargupta.com"}, ]}, {id:"dev",label:"Dev",services:[ {id:"forgejo",name:"Forgejo",desc:"Self-hosted Git",emoji:"๐ŸŒฟ",url:"https://git.manohargupta.com"}, @@ -70,6 +71,11 @@ const SECTIONS=[ {id:"tiger",name:"Tiger Agent",desc:"AI orchestration",emoji:"๐Ÿฏ",url:"https://agent.manohargupta.com"}, {id:"ladder",name:"Ladder",desc:"Paywall bypass ยท CORS proxy",emoji:"๐Ÿชœ",url:"https://ladder.manohargupta.com"}, {id:"angel",name:"Angel",desc:"Portfolio tracker",emoji:"",url:"https://angel.manohargupta.com"}, + {id:"remodel",name:"REmodel",desc:"RE project finance model",emoji:"โ˜€๏ธ",url:"https://model.manohargupta.com"}, + ]}, + {id:"projects",label:"Projects",services:[ + {id:"tia",name:"TIA",desc:"Baby growth tracker",emoji:"๐Ÿ‘ถ",url:"https://tia.manohargupta.com"}, + {id:"awadhgully",name:"Awadhgully",desc:"Storefront ยท Vendure",emoji:"๐Ÿ›",url:"https://awadhgully.com"}, ]}, ]; @@ -88,7 +94,9 @@ function useTime(){const[n,s]=useState(new Date());useEffect(()=>{const t=setInt function greet(h){return h<12?"Good morning":h<17?"Good afternoon":"Good evening"} function Dot({status}){ - const c={online:"#22c55e",running:"#22c55e",warning:"#f59e0b",offline:"#ef4444",exited:"#ef4444"}[status]||"#94a3b8"; + // "degraded" = reachable but 5xx. "unknown"/undefined = grey, meaning the API + // hasn't reported on this id yet โ€” never assume green. + const c={online:"#22c55e",running:"#22c55e",warning:"#f59e0b",degraded:"#f59e0b",offline:"#ef4444",exited:"#ef4444"}[status]||"#94a3b8"; return ; } @@ -176,40 +184,67 @@ function TigerTasks({data}){ function Markets({data}){ if(!data)return Loading...; - const live=data.nifty?.close!=null; + const n=data.nifty||{}, x=data.iex_dam||{}; + const nOk=n.close!=null, xOk=x.weighted_avg_price_inr!=null; + // Green/red on NIFTY change; IEX shows the day's volume-weighted MCP. + const nc=(n.change_pct??0)>=0?"#22c55e":"#ef4444"; + const sign=(n.change_pct??0)>=0?"+":""; return( - - {live - ?
-
+ LIVE + : closed}> + {(nOk||xOk) + ?
+
NIFTY 50
-
{data.nifty.close?.toLocaleString()}
+ {nOk + ?<> +
{n.close.toLocaleString("en-IN")}
+
{sign}{n.change_pts?.toFixed(2)} ({sign}{n.change_pct?.toFixed(2)}%)
+ + :
โ€”
}
-
-
IEX DAM
-
โ‚น{data.iex_dam?.weighted_avg_price_inr??"โ€”"}
+
+
IEX DAM ยท avg
+ {xOk + ?<> +
โ‚น{x.weighted_avg_price_inr.toLocaleString("en-IN")}
+
โ‚น{x.min_price_inr}โ€“{x.max_price_inr} ยท {x.total_volume_mu} MU
+ + :
โ€”
}
- :
๐Ÿ“ก Data pipeline coming soon
+ :
โš ๏ธ Sources unreachable
} + {xOk&&
Rs/MWh ยท DAM {x.as_of} ยท {x.blocks_parsed} blocks
} ); } -function StatCell({val,lbl}){ +function StatCell({val,lbl,alert}){ const[h,sH]=useState(false); + // `alert` overrides the normal text colour so a threshold breach is visible + // at a glance. A number you look at daily that never changes colour is a + // number you stop reading. + const col=alert||(h?"var(--c-accent)":"var(--c-text)"); return(
sH(true)} onMouseLeave={()=>sH(false)} style={{textAlign:"center",padding:"8px 20px",background:h?"var(--c-accent-faint)":"transparent",transition:"background 0.15s",cursor:"default"}}> -
{val}
+
{val}
{lbl}
); } -function Card({svc}){ +function Card({svc,health}){ const[h,sH]=useState(false); + // health is {status, code, ms} from /api/all -> health.services[svc.id], + // or undefined if the API is unreachable / this id isn't probed yet. + const st=health?.status; + const tip=health + ? `${st}${health.code?` ยท HTTP ${health.code}`:""}${health.ms!=null?` ยท ${health.ms}ms`:""}` + : "status unknown"; return(
window.open(svc.url,"_blank")} onMouseEnter={()=>sH(true)} onMouseLeave={()=>sH(false)} style={{background:h?"var(--c-surface2)":"var(--c-surface)",border:`1px solid ${h?"var(--c-accent)":"var(--c-border)"}`,borderRadius:4,padding:"14px 16px",cursor:"pointer",transform:h?"translateY(-1px)":"none",boxShadow:h?"0 4px 16px rgba(0,0,0,0.08)":"none",transition:"all 0.15s",display:"flex",alignItems:"center",gap:12,position:"relative"}}> @@ -220,21 +255,28 @@ function Card({svc}){
{svc.name}
{svc.desc}
- + {h&&
OPEN โ†’
}
); } -function Section({sec}){ +function Section({sec,health}){ const[open,setOpen]=useState(true); + // Surface faults on the section header too โ€” otherwise collapsing a section + // hides the very thing you need to see. + const bad=sec.services.filter(s=>{ + const st=health?.[s.id]?.status; + return st==="offline"||st==="degraded"; + }).length; return(
- {open&&
{sec.services.map(s=>)}
} + {open&&
{sec.services.map(s=>)}
}
); } @@ -282,11 +324,17 @@ function App(){ },[dark]); const srv=live?.server||{}; + // Threshold colours: amber = worth noticing, red = act now. + const AMBER="#f59e0b", RED="#ef4444"; + const dSt=srv.disk?.status; // "ok" | "warning" | "critical" const stats=[ - [srv.cpu_percent!=null?`${srv.cpu_percent}%`:"โ€”","CPU"], - [srv.ram_used_gb!=null?`${srv.ram_used_gb} GB`:"โ€”","RAM used"], - [srv.disk?.free_gb!=null?`${srv.disk.free_gb} GB`:"โ€”","Disk free"], - [srv.uptime_days!=null?`${srv.uptime_days}d`:"โ€”","Uptime"], + [srv.cpu_percent!=null?`${srv.cpu_percent}%`:"โ€”","CPU", + srv.cpu_percent>=90?RED:srv.cpu_percent>=70?AMBER:null], + [srv.ram_used_gb!=null?`${srv.ram_used_gb} GB`:"โ€”","RAM used", + srv.ram_percent>=90?RED:srv.ram_percent>=80?AMBER:null], + [srv.disk?.free_gb!=null?`${srv.disk.free_gb} GB`:"โ€”","Disk free", + dSt==="critical"?RED:dSt==="warning"?AMBER:null], + [srv.uptime_days!=null?`${srv.uptime_days}d`:"โ€”","Uptime",null], ]; return( @@ -311,9 +359,9 @@ function App(){
- {stats.map(([val,lbl],i,arr)=>( + {stats.map(([val,lbl,alert],i,arr)=>( - + {i} ))} @@ -328,7 +376,7 @@ function App(){
- {SECTIONS.map(s=>
)} + {SECTIONS.map(s=>
)}