feat(market-intel): gate manual fetch behind mcp readiness
All checks were successful
CD Pipeline / deploy (push) Successful in 1m6s

This commit is contained in:
OoO
2026-05-18 15:40:56 +08:00
parent d990316d74
commit 921e9eeb15
11 changed files with 429 additions and 13 deletions

View File

@@ -468,6 +468,24 @@
</div>
</div>
<div class="market-intel-panel" data-market-intel-mcp-fetch-gate>
<div class="market-intel-preview-head">
<div>
<p class="market-intel-muted momo-mono mb-1">MCP / FETCH GATE</p>
<h2 class="market-intel-preview-title">人工 Fetch 安全閘門</h2>
</div>
<button class="market-intel-icon-button" type="button" title="重新整理人工 Fetch 安全閘門" data-market-intel-mcp-fetch-gate-refresh>
<i class="fas fa-rotate-right" aria-hidden="true"></i>
</button>
</div>
<div class="market-intel-preview-meta" data-market-intel-mcp-fetch-gate-meta>
<span class="market-intel-pill">loading</span>
</div>
<div data-market-intel-mcp-fetch-gate-body>
<div class="market-intel-empty">讀取人工 Fetch 安全閘門中...</div>
</div>
</div>
<div class="market-intel-panel" data-market-intel-migration>
<div class="market-intel-preview-head">
<div>
@@ -536,10 +554,11 @@
const mcpReadinessRoot = document.querySelector('[data-market-intel-mcp-readiness]');
const mcpPreflightRoot = document.querySelector('[data-market-intel-mcp-preflight]');
const mcpActivationRoot = document.querySelector('[data-market-intel-mcp-activation]');
const mcpFetchGateRoot = document.querySelector('[data-market-intel-mcp-fetch-gate]');
const migrationRoot = document.querySelector('[data-market-intel-migration]');
const approvalRoot = document.querySelector('[data-market-intel-approval]');
const deployRoot = document.querySelector('[data-market-intel-deploy]');
if (!root && !writerRoot && !cliRoot && !dbProbeRoot && !seedDiffRoot && !legacyBridgeRoot && !mcpReadinessRoot && !mcpPreflightRoot && !mcpActivationRoot && !migrationRoot && !approvalRoot && !deployRoot) return;
if (!root && !writerRoot && !cliRoot && !dbProbeRoot && !seedDiffRoot && !legacyBridgeRoot && !mcpReadinessRoot && !mcpPreflightRoot && !mcpActivationRoot && !mcpFetchGateRoot && !migrationRoot && !approvalRoot && !deployRoot) return;
const meta = root ? root.querySelector('[data-market-intel-preview-meta]') : null;
const body = root ? root.querySelector('[data-market-intel-preview-body]') : null;
@@ -577,6 +596,10 @@
const mcpActivationBody = mcpActivationRoot ? mcpActivationRoot.querySelector('[data-market-intel-mcp-activation-body]') : null;
const mcpActivationRefresh = mcpActivationRoot ? mcpActivationRoot.querySelector('[data-market-intel-mcp-activation-refresh]') : null;
const mcpActivationEndpoint = "{{ url_for('market_intel.market_intel_mcp_activation_runbook') }}";
const mcpFetchGateMeta = mcpFetchGateRoot ? mcpFetchGateRoot.querySelector('[data-market-intel-mcp-fetch-gate-meta]') : null;
const mcpFetchGateBody = mcpFetchGateRoot ? mcpFetchGateRoot.querySelector('[data-market-intel-mcp-fetch-gate-body]') : null;
const mcpFetchGateRefresh = mcpFetchGateRoot ? mcpFetchGateRoot.querySelector('[data-market-intel-mcp-fetch-gate-refresh]') : null;
const mcpFetchGateEndpoint = "{{ url_for('market_intel.market_intel_mcp_fetch_gate') }}?fetch=false&execute=false";
const migrationMeta = migrationRoot ? migrationRoot.querySelector('[data-market-intel-migration-meta]') : null;
const migrationBody = migrationRoot ? migrationRoot.querySelector('[data-market-intel-migration-body]') : null;
const migrationRefresh = migrationRoot ? migrationRoot.querySelector('[data-market-intel-migration-refresh]') : null;
@@ -1195,6 +1218,93 @@
}
};
const renderMcpFetchGateMeta = data => {
mcpFetchGateMeta.innerHTML = [
`mode=${data.mode || 'unknown'}`,
`fetch=${data.fetch_requested ? 'true' : 'false'}`,
`gate=${data.manual_fetch_gate_open ? 'open' : 'blocked'}`,
`network=${data.network_request_allowed ? 'allow' : 'block'}`,
`blocked=${(data.blocked_reasons || []).length}`
].map(item => `<span class="market-intel-pill">${escapeHtml(item)}</span>`).join('');
};
const renderMcpFetchGateBody = data => {
const blockers = (data.blocked_reasons || []).join(' / ');
const checks = Object.entries(data.gate_checks || {});
const sequence = data.required_sequence || [];
const readiness = data.mcp_readiness_summary || {};
mcpFetchGateBody.innerHTML = `
<div class="market-intel-empty mb-3">人工 fetch 目前先由 MCP gate 接管;頁面預設只做 planned preview不抓外站、不寫 DB、不掛 scheduler。${blockers ? `阻擋:${escapeHtml(blockers)}` : ''}</div>
<div class="market-intel-deploy-grid">
<div data-market-intel-mcp-fetch-gate-checks>
<p class="market-intel-deploy-section-title">FETCH GATE CHECKS</p>
<div class="market-intel-check-list">${
checks.length
? checks.map(([name, passed]) => `
<div class="market-intel-check">
<div>
<strong>${escapeHtml(name)}</strong>
</div>
<span>${passed ? 'PASS' : 'BLOCK'}</span>
</div>
`).join('')
: '<div class="market-intel-empty">尚未提供 fetch gate checks。</div>'
}</div>
</div>
<div data-market-intel-mcp-fetch-gate-sequence>
<p class="market-intel-deploy-section-title">REQUIRED SEQUENCE</p>
<div class="market-intel-check-list">${
sequence.length
? sequence.map((item, index) => `
<div class="market-intel-check">
<div>
<strong>${escapeHtml(`step_${index + 1}`)}</strong>
<small>${escapeHtml(item)}</small>
</div>
<span>REQUIRED</span>
</div>
`).join('')
: '<div class="market-intel-empty">尚未提供啟用順序。</div>'
}</div>
</div>
<div data-market-intel-mcp-fetch-gate-readiness>
<p class="market-intel-deploy-section-title">READINESS SUMMARY</p>
<div class="market-intel-check-list">
<div class="market-intel-check">
<div>
<strong>router</strong>
<small>${escapeHtml(readiness.mode || 'unknown')}</small>
</div>
<span>${readiness.router_enabled ? 'ON' : 'OFF'}</span>
</div>
<div class="market-intel-check">
<div>
<strong>external_mcp</strong>
<small>${escapeHtml((readiness.blocked_reasons || []).join(' / ') || 'ready')}</small>
</div>
<span>${readiness.external_mcp_complete ? 'READY' : 'PENDING'}</span>
</div>
</div>
</div>
</div>
`;
};
const loadMcpFetchGate = async () => {
if (!mcpFetchGateMeta || !mcpFetchGateBody) return;
mcpFetchGateBody.innerHTML = '<div class="market-intel-empty">讀取人工 Fetch 安全閘門中...</div>';
try {
const response = await fetch(mcpFetchGateEndpoint, { credentials: 'same-origin' });
if (!response.ok) throw new Error(`HTTP ${response.status}`);
const data = await response.json();
renderMcpFetchGateMeta(data);
renderMcpFetchGateBody(data);
} catch (error) {
mcpFetchGateMeta.innerHTML = '<span class="market-intel-pill">error</span>';
mcpFetchGateBody.innerHTML = `<div class="market-intel-empty">人工 Fetch 安全閘門讀取失敗:${escapeHtml(error.message)}</div>`;
}
};
const renderMigrationMeta = data => {
const seedWriter = data.command_plan && data.command_plan.seed_writer_command
? data.command_plan.seed_writer_command
@@ -1456,6 +1566,9 @@
if (mcpActivationRefresh) {
mcpActivationRefresh.addEventListener('click', loadMcpActivation);
}
if (mcpFetchGateRefresh) {
mcpFetchGateRefresh.addEventListener('click', loadMcpFetchGate);
}
if (migrationRefresh) {
migrationRefresh.addEventListener('click', loadMigration);
}
@@ -1474,6 +1587,7 @@
loadMcpReadiness();
loadMcpPreflight();
loadMcpActivation();
loadMcpFetchGate();
loadMigration();
loadApproval();
loadDeploy();