fix: atlas mixed-content, station footer URL, service card uptime+latency bar

- atlas: skip http:// health_url from https context
- station: dashboard footer link → posimai.soar-enrich.com
- station: service cards add uptime %, latency bar, updateLatencyBar fn

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
posimai 2026-03-31 21:42:58 +09:00
parent 0bd747ebd2
commit eb2d8877c5
1 changed files with 29 additions and 5 deletions

View File

@ -139,6 +139,12 @@
.svc-dot { width:5px;height:5px;border-radius:50%;background:var(--text3);opacity:0.25; } .svc-dot { width:5px;height:5px;border-radius:50%;background:var(--text3);opacity:0.25; }
.svc-dot.ok { background:var(--ok); opacity:0.85; } .svc-dot.ok { background:var(--ok); opacity:0.85; }
.svc-dot.crit { background:var(--crit); opacity:0.85; } .svc-dot.crit { background:var(--crit); opacity:0.85; }
.service-uptime { font-family:'JetBrains Mono',monospace;font-size:10px;font-weight:600; }
.service-uptime.full { color:var(--ok); }
.service-uptime.partial { color:#FB923C; }
.service-uptime.down { color:var(--crit); }
.latency-bar-wrap { height:3px;border-radius:2px;background:rgba(255,255,255,0.07);overflow:hidden;margin-top:4px; }
.latency-bar { height:100%;border-radius:2px;transition:width 0.6s ease; }
/* stream */ /* stream */
#stream-feed { flex:1;overflow:hidden;display:flex;flex-direction:column;gap:0; } #stream-feed { flex:1;overflow:hidden;display:flex;flex-direction:column;gap:0; }
@ -298,7 +304,7 @@
<div class="bottom-links"> <div class="bottom-links">
<a class="bottom-link" href="/" target="_blank" rel="noopener"><i data-lucide="terminal"></i>dev</a> <a class="bottom-link" href="/" target="_blank" rel="noopener"><i data-lucide="terminal"></i>dev</a>
<a class="bottom-link" href="https://posimai-atlas.vercel.app" target="_blank" rel="noopener"><i data-lucide="network"></i>atlas</a> <a class="bottom-link" href="https://posimai-atlas.vercel.app" target="_blank" rel="noopener"><i data-lucide="network"></i>atlas</a>
<a class="bottom-link" href="https://posimai-dashboard.vercel.app" target="_blank" rel="noopener"><i data-lucide="layout-dashboard"></i>dashboard</a> <a class="bottom-link" href="https://posimai.soar-enrich.com" target="_blank" rel="noopener"><i data-lucide="layout-dashboard"></i>dashboard</a>
</div> </div>
<div id="refresh-countdown">次の更新まで <span id="countdown">30</span>s</div> <div id="refresh-countdown">次の更新まで <span id="countdown">30</span>s</div>
</div> </div>
@ -440,11 +446,19 @@ function buildServiceCards(){
SERVICES.forEach(svc=>{ SERVICES.forEach(svc=>{
const card=document.createElement('div'); card.className='service-card'; card.id=`svc-${svc.id}`; const card=document.createElement('div'); card.className='service-card'; card.id=`svc-${svc.id}`;
const dots=Array(5).fill(0).map((_,i)=>`<div class="svc-dot" id="dot-${svc.id}-${i}"></div>`).join(''); const dots=Array(5).fill(0).map((_,i)=>`<div class="svc-dot" id="dot-${svc.id}-${i}"></div>`).join('');
card.innerHTML=`<div class="service-card-top"><span class="service-name">${svc.name}</span><span class="service-badge checking" id="badge-${svc.id}">...</span></div><span class="service-desc">${svc.desc}</span><div class="service-footer"><div class="service-dots">${dots}</div><span class="service-latency" id="lat-${svc.id}"></span></div>`; card.innerHTML=`<div class="service-card-top"><span class="service-name">${svc.name}</span><span class="service-badge checking" id="badge-${svc.id}">...</span></div><span class="service-desc">${svc.desc}</span><div class="latency-bar-wrap"><div class="latency-bar" id="lbar-${svc.id}" style="width:0%;background:var(--ok)"></div></div><div class="service-footer"><div class="service-dots">${dots}</div><span class="service-uptime" id="upt-${svc.id}"></span><span class="service-latency" id="lat-${svc.id}"></span></div>`;
grid.appendChild(card); grid.appendChild(card);
}); });
} }
function updateLatencyBar(id,ms){
const bar=document.getElementById(`lbar-${id}`); if(!bar)return;
if(ms===null){bar.style.width='100%';bar.style.background='var(--crit)';return;}
const w=ms<=50?100:ms<=200?80:ms<=500?55:35;
const color=ms<=200?'var(--ok)':ms<=500?'#FB923C':'var(--crit)';
bar.style.width=w+'%'; bar.style.background=color;
}
function pushSvcHistory(id,ok){ function pushSvcHistory(id,ok){
svcHist[id].push(ok); if(svcHist[id].length>5)svcHist[id].shift(); svcHist[id].push(ok); if(svcHist[id].length>5)svcHist[id].shift();
const h=svcHist[id]; const h=svcHist[id];
@ -453,6 +467,13 @@ function pushSvcHistory(id,ok){
const idx=i-(5-h.length); if(idx<0){dot.className='svc-dot';continue;} const idx=i-(5-h.length); if(idx<0){dot.className='svc-dot';continue;}
dot.className='svc-dot '+(h[idx]?'ok':'crit'); dot.className='svc-dot '+(h[idx]?'ok':'crit');
} }
// 稼働率表示
const uptEl=document.getElementById(`upt-${id}`);
if(uptEl&&h.length>0){
const pct=Math.round(h.filter(Boolean).length/h.length*100);
uptEl.textContent=`${pct}%`;
uptEl.className='service-uptime '+(pct===100?'full':pct>=60?'partial':'down');
}
} }
async function checkService(svc){ async function checkService(svc){
@ -462,20 +483,23 @@ async function checkService(svc){
try{ try{
const ctrl=new AbortController(),timer=setTimeout(()=>ctrl.abort(),7000); const ctrl=new AbortController(),timer=setTimeout(()=>ctrl.abort(),7000);
if(svc.proxy){ if(svc.proxy){
// サーバー経由プロキシチェックmixed-content 回避)
const r=await fetch(svc.url,{signal:ctrl.signal}); const r=await fetch(svc.url,{signal:ctrl.signal});
clearTimeout(timer); clearTimeout(timer);
const data=await r.json(); const data=await r.json();
const ok=data.ok||(data.status&&data.status<500); const ok=data.ok||(data.status&&data.status<500);
const ms=data.latency_ms||0;
badge.className='service-badge '+(ok?'ok':'crit'); badge.className='service-badge '+(ok?'ok':'crit');
badge.textContent=ok?'OK':'DOWN'; badge.textContent=ok?'OK':'DOWN';
latEl.textContent=data.latency_ms?`${data.latency_ms}ms`:''; latEl.textContent=ms?`${ms}ms`:'';
updateLatencyBar(svc.id,ok?ms:null);
pushSvcHistory(svc.id,!!ok); pushSvcHistory(svc.id,!!ok);
}else{ }else{
await fetch(svc.url,{method:'HEAD',mode:'no-cors',signal:ctrl.signal}); await fetch(svc.url,{method:'HEAD',mode:'no-cors',signal:ctrl.signal});
clearTimeout(timer); clearTimeout(timer);
const ms=Date.now()-t0;
badge.className='service-badge ok'; badge.textContent='OK'; badge.className='service-badge ok'; badge.textContent='OK';
latEl.textContent=`${Date.now()-t0}ms`; latEl.textContent=`${ms}ms`;
updateLatencyBar(svc.id,ms);
pushSvcHistory(svc.id,true); pushSvcHistory(svc.id,true);
} }
}catch(e){ }catch(e){