From d39c0d58fd99b38d09984605ba38ecf54dba376a Mon Sep 17 00:00:00 2001 From: posimai Date: Sun, 29 Mar 2026 22:59:12 +0900 Subject: [PATCH] feat: GitHub + Vercel auto-scan, fix XServer/Cloudflare data --- index.html | 147 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 147 insertions(+) diff --git a/index.html b/index.html index 843bd4d..4e79690 100644 --- a/index.html +++ b/index.html @@ -933,6 +933,38 @@
+
+
GitHub
+
+
+ Personal Access Token でリポジトリ・連携サービスを検出します(read:user, repo スコープ) +
+ + + +
+
+
+
+
Vercel
+
+
+ API トークンでデプロイ中のプロジェクトを自動検出します +
+ + +
+
+
@@ -2008,6 +2040,118 @@ async function runTailscaleScan() { } } +// ── GitHub scan ──────────────────────────────────────────────── +async function runGithubScan() { + const token = document.getElementById('github-token-input').value.trim(); + const org = document.getElementById('github-org-input').value.trim(); + const statusEl = document.getElementById('github-scan-status'); + const btn = document.getElementById('btnGithubScan'); + if (!token) { showToast('GitHub トークンを入力してください'); return; } + + statusEl.className = 'scan-status visible'; + statusEl.textContent = 'スキャン中...'; + btn.disabled = true; + + try { + const apiBase = 'https://api.soar-enrich.com/brain/api'; + const url = `${apiBase}/atlas/github-scan?token=${encodeURIComponent(token)}${org ? '&org=' + encodeURIComponent(org) : ''}`; + const res = await fetch(url); + if (!res.ok) { const e = await res.json().catch(() => ({})); throw new Error(e.error || `HTTP ${res.status}`); } + const repos = await res.json(); + + let added = 0; + // Add GitHub org/user as cloud node if not present + const githubId = 'github'; + repos.forEach(repo => { + const nodeId = `gh-${repo.name}`; + if (atlasData.nodes.find(n => n.id === nodeId)) return; + atlasData.nodes.push({ + id: nodeId, + label: repo.name, + type: 'app', + description: repo.description || `GitHub リポジトリ: ${repo.full_name}`, + url: repo.html_url, + status: repo.archived ? 'inactive' : 'active', + parent: githubId, + }); + // edge: github → vercel if homepage looks like vercel + if (repo.homepage && repo.homepage.includes('vercel.app')) { + const vercelId = 'vercel'; + if (!atlasData.edges.find(e => e.from === githubId && e.to === vercelId)) { + // already exists globally, skip per-repo + } + atlasData.edges.push({ from: nodeId, to: vercelId, type: 'hosts', label: 'deploy' }); + } + added++; + }); + + saveData(); + buildFilterBar(); + initGraph(); + setTimeout(fitGraph, 500); + statusEl.className = 'scan-status visible ok'; + statusEl.textContent = `${repos.length} リポジトリ検出 — ${added} 件追加`; + showToast(`GitHub: ${added} リポジトリを追加しました`); + } catch (e) { + statusEl.className = 'scan-status visible err'; + statusEl.textContent = `エラー: ${e.message}`; + } finally { + btn.disabled = false; + } +} + +// ── Vercel scan ───────────────────────────────────────────────── +async function runVercelScan() { + const token = document.getElementById('vercel-token-input').value.trim(); + const statusEl = document.getElementById('vercel-scan-status'); + const btn = document.getElementById('btnVercelScan'); + if (!token) { showToast('Vercel トークンを入力してください'); return; } + + statusEl.className = 'scan-status visible'; + statusEl.textContent = 'スキャン中...'; + btn.disabled = true; + + try { + const apiBase = 'https://api.soar-enrich.com/brain/api'; + const res = await fetch(`${apiBase}/atlas/vercel-scan?token=${encodeURIComponent(token)}`); + if (!res.ok) { const e = await res.json().catch(() => ({})); throw new Error(e.error || `HTTP ${res.status}`); } + const data = await res.json(); + const projects = data.projects || []; + + let added = 0; + projects.forEach(proj => { + const nodeId = `vercel-${proj.name}`; + if (atlasData.nodes.find(n => n.id === nodeId)) return; + const prodUrl = proj.targets?.production?.alias?.[0] + ? `https://${proj.targets.production.alias[0]}` + : null; + atlasData.nodes.push({ + id: nodeId, + label: proj.name, + type: 'app', + description: `Vercel プロジェクト: ${proj.framework || '静的'}`, + url: prodUrl, + status: 'active', + parent: 'vercel', + }); + added++; + }); + + saveData(); + buildFilterBar(); + initGraph(); + setTimeout(fitGraph, 500); + statusEl.className = 'scan-status visible ok'; + statusEl.textContent = `${projects.length} プロジェクト検出 — ${added} 件追加`; + showToast(`Vercel: ${added} プロジェクトを追加しました`); + } catch (e) { + statusEl.className = 'scan-status visible err'; + statusEl.textContent = `エラー: ${e.message}`; + } finally { + btn.disabled = false; + } +} + // ── Event bindings ───────────────────────────────────────────── function bindEvents() { document.getElementById('btn-fit').addEventListener('click', fitGraph); @@ -2080,6 +2224,9 @@ function bindEvents() { applyMonitorSetting(parseInt(e.target.value, 10)); }); + document.getElementById('btnGithubScan').addEventListener('click', runGithubScan); + document.getElementById('btnVercelScan').addEventListener('click', runVercelScan); + // File input (wizard + settings import) document.getElementById('fileInput').addEventListener('change', e => { const isWizard = !document.getElementById('wizard-overlay').hasAttribute('hidden');