feat: health check / edge legend / keyboard shortcuts / remove AI hint
This commit is contained in:
parent
0252ee9315
commit
db605440af
156
index.html
156
index.html
|
|
@ -725,6 +725,65 @@
|
||||||
color: #F87171;
|
color: #F87171;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ── Health check button ───────────────────────────── */
|
||||||
|
#dp-health-btn {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
font-size: 12px;
|
||||||
|
padding: 5px 10px;
|
||||||
|
border-radius: 8px;
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
background: transparent;
|
||||||
|
color: var(--text3);
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.15s;
|
||||||
|
width: fit-content;
|
||||||
|
}
|
||||||
|
#dp-health-btn:hover { border-color: var(--accent); color: var(--accent); }
|
||||||
|
#dp-health-btn.checking { opacity: 0.6; pointer-events: none; }
|
||||||
|
.health-dot {
|
||||||
|
width: 6px; height: 6px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: var(--text3);
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
.health-dot.online { background: #4ADE80; box-shadow: 0 0 6px #4ADE8088; }
|
||||||
|
.health-dot.offline { background: #F87171; }
|
||||||
|
.health-dot.limited { background: #FB923C; }
|
||||||
|
|
||||||
|
/* ── Edge legend ────────────────────────────────────── */
|
||||||
|
#edge-legend {
|
||||||
|
position: fixed;
|
||||||
|
bottom: max(76px, calc(env(safe-area-inset-bottom) + 76px));
|
||||||
|
right: 16px;
|
||||||
|
z-index: 18;
|
||||||
|
background: rgba(12,18,33,0.88);
|
||||||
|
backdrop-filter: blur(16px);
|
||||||
|
-webkit-backdrop-filter: blur(16px);
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: 10px;
|
||||||
|
padding: 10px 12px;
|
||||||
|
display: none;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 5px;
|
||||||
|
min-width: 140px;
|
||||||
|
}
|
||||||
|
[data-theme="light"] #edge-legend { background: rgba(239,246,255,0.92); }
|
||||||
|
#edge-legend.open { display: flex; }
|
||||||
|
.legend-row {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
font-size: 11px;
|
||||||
|
color: var(--text2);
|
||||||
|
}
|
||||||
|
.legend-line {
|
||||||
|
width: 22px; height: 2px;
|
||||||
|
border-radius: 1px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
/* ── Edge label tooltip ────────────────────────────── */
|
/* ── Edge label tooltip ────────────────────────────── */
|
||||||
#edge-tooltip {
|
#edge-tooltip {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
|
|
@ -836,7 +895,13 @@
|
||||||
<div class="detail-body">
|
<div class="detail-body">
|
||||||
<div class="detail-desc" id="dp-desc"></div>
|
<div class="detail-desc" id="dp-desc"></div>
|
||||||
<div class="detail-url" id="dp-url"></div>
|
<div class="detail-url" id="dp-url"></div>
|
||||||
<div id="dp-status"></div>
|
<div style="display:flex;align-items:center;gap:8px;flex-wrap:wrap">
|
||||||
|
<div id="dp-status"></div>
|
||||||
|
<button id="dp-health-btn" style="display:none">
|
||||||
|
<span class="health-dot" id="dp-health-dot"></span>
|
||||||
|
<span id="dp-health-label">接続確認</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
<div id="dp-connections">
|
<div id="dp-connections">
|
||||||
<div class="detail-section-label">接続</div>
|
<div class="detail-section-label">接続</div>
|
||||||
<div class="detail-connections" id="dp-conn-list"></div>
|
<div class="detail-connections" id="dp-conn-list"></div>
|
||||||
|
|
@ -961,9 +1026,6 @@
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div style="padding:8px 16px;font-size:11px;color:var(--text3);border-bottom:1px solid var(--border);line-height:1.6">
|
|
||||||
Claude や Gemini のチャットに貼り付けると、AI があなたの環境を即座に把握します
|
|
||||||
</div>
|
|
||||||
<pre id="ai-context-text"></pre>
|
<pre id="ai-context-text"></pre>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -980,11 +1042,25 @@
|
||||||
<button class="tb-btn" id="btn-add-node" aria-label="ノードを追加" title="ノードを追加">
|
<button class="tb-btn" id="btn-add-node" aria-label="ノードを追加" title="ノードを追加">
|
||||||
<i data-lucide="plus" style="width:17px;height:17px;stroke-width:1.75"></i>
|
<i data-lucide="plus" style="width:17px;height:17px;stroke-width:1.75"></i>
|
||||||
</button>
|
</button>
|
||||||
|
<button class="tb-btn" id="btn-legend" aria-label="凡例" title="接続タイプ凡例">
|
||||||
|
<i data-lucide="info" style="width:15px;height:15px;stroke-width:1.75"></i>
|
||||||
|
</button>
|
||||||
<button class="tb-btn" id="btn-fit" aria-label="画面に合わせる" title="フィット">
|
<button class="tb-btn" id="btn-fit" aria-label="画面に合わせる" title="フィット">
|
||||||
<i data-lucide="maximize-2" style="width:15px;height:15px;stroke-width:1.75"></i>
|
<i data-lucide="maximize-2" style="width:15px;height:15px;stroke-width:1.75"></i>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Edge type legend -->
|
||||||
|
<div id="edge-legend" role="complementary" aria-label="接続タイプ凡例">
|
||||||
|
<div style="font-size:10px;font-weight:600;color:var(--text3);letter-spacing:.05em;margin-bottom:2px">接続タイプ</div>
|
||||||
|
<div class="legend-row"><span class="legend-line" style="background:rgba(34,211,238,0.5)"></span>push / calls</div>
|
||||||
|
<div class="legend-row"><span class="legend-line" style="background:rgba(192,132,252,0.5)"></span>trigger</div>
|
||||||
|
<div class="legend-row"><span class="legend-line" style="background:rgba(74,222,128,0.4)"></span>hosts</div>
|
||||||
|
<div class="legend-row"><span class="legend-line" style="background:rgba(251,146,60,0.4)"></span>runs-on</div>
|
||||||
|
<div class="legend-row"><span class="legend-line" style="background:rgba(129,140,248,0.5)"></span>dns</div>
|
||||||
|
<div class="legend-row"><span class="legend-line" style="background:rgba(255,255,255,0.15)"></span>connects</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Edge label tooltip -->
|
<!-- Edge label tooltip -->
|
||||||
<div id="edge-tooltip"></div>
|
<div id="edge-tooltip"></div>
|
||||||
|
|
||||||
|
|
@ -1324,6 +1400,20 @@ function showDetail(id, simNodes, simEdges) {
|
||||||
const s = node.status || 'unknown';
|
const s = node.status || 'unknown';
|
||||||
statusEl.innerHTML = `<span class="status-badge ${s}"><span class="status-dot"></span>${s}</span>`;
|
statusEl.innerHTML = `<span class="status-badge ${s}"><span class="status-dot"></span>${s}</span>`;
|
||||||
|
|
||||||
|
// Health check button
|
||||||
|
const healthBtn = document.getElementById('dp-health-btn');
|
||||||
|
const healthDot = document.getElementById('dp-health-dot');
|
||||||
|
const healthLabel = document.getElementById('dp-health-label');
|
||||||
|
if (node.url && !isReadOnly) {
|
||||||
|
healthBtn.style.display = 'flex';
|
||||||
|
healthBtn.classList.remove('checking');
|
||||||
|
healthDot.className = 'health-dot';
|
||||||
|
healthLabel.textContent = '接続確認';
|
||||||
|
healthBtn.onclick = () => checkNodeHealth(node.id, node.url);
|
||||||
|
} else {
|
||||||
|
healthBtn.style.display = 'none';
|
||||||
|
}
|
||||||
|
|
||||||
// Connections
|
// Connections
|
||||||
const connList = document.getElementById('dp-conn-list');
|
const connList = document.getElementById('dp-conn-list');
|
||||||
const related = atlasData.edges.filter(e => e.from === id || e.to === id);
|
const related = atlasData.edges.filter(e => e.from === id || e.to === id);
|
||||||
|
|
@ -1655,6 +1745,28 @@ function bindEvents() {
|
||||||
document.getElementById('fileInput').click();
|
document.getElementById('fileInput').click();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Legend toggle
|
||||||
|
document.getElementById('btn-legend').addEventListener('click', () => {
|
||||||
|
document.getElementById('edge-legend').classList.toggle('open');
|
||||||
|
});
|
||||||
|
document.addEventListener('click', e => {
|
||||||
|
if (!e.target.closest('#edge-legend') && !e.target.closest('#btn-legend')) {
|
||||||
|
document.getElementById('edge-legend').classList.remove('open');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Keyboard shortcuts
|
||||||
|
document.addEventListener('keydown', e => {
|
||||||
|
if (e.target.matches('input, textarea, select')) return;
|
||||||
|
if (e.key === 'Escape') {
|
||||||
|
if (document.getElementById('modal-overlay').classList.contains('open')) { closeModal('modal-overlay'); return; }
|
||||||
|
if (document.getElementById('edge-modal-overlay').classList.contains('open')) { closeModal('edge-modal-overlay'); return; }
|
||||||
|
if (document.getElementById('ai-modal-overlay').classList.contains('open')) { closeModal('ai-modal-overlay'); return; }
|
||||||
|
closeDetail();
|
||||||
|
}
|
||||||
|
if (e.key === 'f' || e.key === 'F') fitGraph();
|
||||||
|
});
|
||||||
|
|
||||||
window.addEventListener('resize', () => {
|
window.addEventListener('resize', () => {
|
||||||
if (simulation) {
|
if (simulation) {
|
||||||
const svg = d3.select('#graph-svg');
|
const svg = d3.select('#graph-svg');
|
||||||
|
|
@ -1672,6 +1784,42 @@ if ('serviceWorker' in navigator) {
|
||||||
navigator.serviceWorker.register('/sw.js');
|
navigator.serviceWorker.register('/sw.js');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── Health check ───────────────────────────────────────────────
|
||||||
|
async function checkNodeHealth(nodeId, url) {
|
||||||
|
const btn = document.getElementById('dp-health-btn');
|
||||||
|
const dot = document.getElementById('dp-health-dot');
|
||||||
|
const label = document.getElementById('dp-health-label');
|
||||||
|
btn.classList.add('checking');
|
||||||
|
label.textContent = '確認中...';
|
||||||
|
dot.className = 'health-dot';
|
||||||
|
|
||||||
|
let result = 'offline';
|
||||||
|
try {
|
||||||
|
const ctrl = new AbortController();
|
||||||
|
const timer = setTimeout(() => ctrl.abort(), 6000);
|
||||||
|
const res = await fetch(url, { method: 'HEAD', mode: 'no-cors', signal: ctrl.signal });
|
||||||
|
clearTimeout(timer);
|
||||||
|
// no-cors returns opaque (type='opaque', status=0) — server was reached
|
||||||
|
result = res.type === 'opaque' ? 'limited' : (res.ok ? 'online' : 'offline');
|
||||||
|
} catch (e) {
|
||||||
|
result = 'offline';
|
||||||
|
}
|
||||||
|
|
||||||
|
const labelMap = { online: 'online', limited: '到達可能', offline: '到達不可' };
|
||||||
|
dot.className = `health-dot ${result}`;
|
||||||
|
label.textContent = labelMap[result];
|
||||||
|
btn.classList.remove('checking');
|
||||||
|
|
||||||
|
// Update node status in data
|
||||||
|
const node = atlasData.nodes.find(n => n.id === nodeId);
|
||||||
|
if (node) {
|
||||||
|
node.status = result === 'offline' ? 'inactive' : 'active';
|
||||||
|
saveData();
|
||||||
|
document.getElementById('dp-status').innerHTML =
|
||||||
|
`<span class="status-badge ${node.status}"><span class="status-dot"></span>${node.status}</span>`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ── URL Sharing ────────────────────────────────────────────────
|
// ── URL Sharing ────────────────────────────────────────────────
|
||||||
function generateShareURL() {
|
function generateShareURL() {
|
||||||
const json = JSON.stringify(atlasData);
|
const json = JSON.stringify(atlasData);
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue