feat: GitHub + Vercel auto-scan, fix XServer/Cloudflare data

This commit is contained in:
posimai 2026-03-29 22:59:12 +09:00
parent 81c09e9819
commit d39c0d58fd
1 changed files with 147 additions and 0 deletions

View File

@ -933,6 +933,38 @@
<div id="tailscale-scan-status" class="scan-status"></div>
</div>
</div>
<div>
<div class="settings-group-label">GitHub</div>
<div class="settings-item" style="flex-direction:column;align-items:flex-start;gap:8px">
<div style="font-size:11px;color:var(--text3);line-height:1.5">
Personal Access Token でリポジトリ・連携サービスを検出しますread:user, repo スコープ)
</div>
<input class="form-input" id="github-token-input" type="password"
placeholder="ghp_..." style="font-size:12px">
<input class="form-input" id="github-org-input" type="text"
placeholder="org 名(例: posimai" style="font-size:12px">
<button class="btn btn-secondary" id="btnGithubScan" style="width:100%;font-size:12px">
<i data-lucide="github" style="width:13px;height:13px;stroke-width:1.75"></i>
GitHub スキャン
</button>
<div id="github-scan-status" class="scan-status"></div>
</div>
</div>
<div>
<div class="settings-group-label">Vercel</div>
<div class="settings-item" style="flex-direction:column;align-items:flex-start;gap:8px">
<div style="font-size:11px;color:var(--text3);line-height:1.5">
API トークンでデプロイ中のプロジェクトを自動検出します
</div>
<input class="form-input" id="vercel-token-input" type="password"
placeholder="vercel token..." style="font-size:12px">
<button class="btn btn-secondary" id="btnVercelScan" style="width:100%;font-size:12px">
<i data-lucide="triangle" style="width:13px;height:13px;stroke-width:1.75"></i>
Vercel スキャン
</button>
<div id="vercel-scan-status" class="scan-status"></div>
</div>
</div>
</div>
</aside>
<div class="overlay" id="overlay" aria-hidden="true"></div>
@ -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');