mirror of
https://github.com/Ed1s0nZ/CyberStrikeAI.git
synced 2026-08-08 11:58:50 +02:00
Add files via upload
This commit is contained in:
@@ -45,12 +45,20 @@
|
||||
return { typeLabel: '目标', typeEn: 'TARGET', accent: '#4F46E5', bgEnd: '#F5F3FF', icon: 'target' };
|
||||
case 'finding':
|
||||
return { typeLabel: '发现', typeEn: 'FINDING', accent: '#E11D48', bgEnd: '#FFF1F2', icon: 'vulnerability' };
|
||||
case 'exploit':
|
||||
return { typeLabel: '利用', typeEn: 'EXPLOIT', accent: '#B45309', bgEnd: '#FFFBEB', icon: 'vulnerability' };
|
||||
case 'vulnerability':
|
||||
return { typeLabel: '漏洞', typeEn: 'VULN', accent: '#BE123C', bgEnd: '#FFF1F2', icon: 'vulnerability' };
|
||||
case 'auth':
|
||||
return { typeLabel: '认证', typeEn: 'AUTH', accent: '#0D9488', bgEnd: '#F0FDFA', icon: 'default' };
|
||||
case 'infra':
|
||||
return { typeLabel: '基础设施', typeEn: 'INFRA', accent: '#64748B', bgEnd: '#F8FAFC', icon: 'default' };
|
||||
case 'chain':
|
||||
return { typeLabel: '攻击链', typeEn: 'CHAIN', accent: '#7C3AED', bgEnd: '#F5F3FF', icon: 'vulnerability' };
|
||||
case 'poc':
|
||||
return { typeLabel: 'POC', typeEn: 'POC', accent: '#C2410C', bgEnd: '#FFEDD5', icon: 'vulnerability' };
|
||||
case 'business':
|
||||
return { typeLabel: '业务', typeEn: 'BUSINESS', accent: '#0369A1', bgEnd: '#F0F9FF', icon: 'default' };
|
||||
case 'missing':
|
||||
return { typeLabel: '缺失', typeEn: 'MISSING', accent: '#CBD5E1', bgEnd: '#F1F5F9', icon: 'default' };
|
||||
default:
|
||||
@@ -261,6 +269,24 @@
|
||||
}
|
||||
}
|
||||
|
||||
// ELK 分层(仅影响节点纵向位置,不修改边的 source/target)
|
||||
function pathGraphNodeLayer(type, factKey) {
|
||||
const key = (factKey || '').toLowerCase();
|
||||
if (key.startsWith('vuln:')) return '4';
|
||||
if (key.startsWith('target/')) return '0';
|
||||
if (key.startsWith('infra/') || key.startsWith('auth/') || key.startsWith('business/')) return '1';
|
||||
if (key.startsWith('exploit/') || key.startsWith('evidence/')) return '3';
|
||||
if (key.startsWith('poc/')) return '3';
|
||||
if (key.startsWith('chain/')) return '2';
|
||||
if (key.startsWith('finding/')) return '2';
|
||||
const t = (type || '').toLowerCase();
|
||||
if (t === 'target') return '0';
|
||||
if (t === 'infra' || t === 'auth') return '1';
|
||||
if (t === 'exploit' || t === 'poc') return '3';
|
||||
if (t === 'chain' || t === 'finding' || t === 'vulnerability') return '2';
|
||||
return '2';
|
||||
}
|
||||
|
||||
function applyElkLayout(validEdges, isComplex) {
|
||||
const layoutOptions = {
|
||||
name: 'breadthfirst',
|
||||
@@ -290,7 +316,15 @@
|
||||
const n = _cy ? _cy.getElementById(node.id) : null;
|
||||
const w = n.length ? n.data('nodeWidth') : node.type === 'target' ? CARD_TARGET_W : CARD_MIN_W;
|
||||
const h = n.length ? n.data('nodeHeight') : CARD_MIN_H;
|
||||
return { id: node.id, width: w, height: h };
|
||||
const nodeKey = node.fact_key || node.id;
|
||||
return {
|
||||
id: node.id,
|
||||
width: w,
|
||||
height: h,
|
||||
layoutOptions: {
|
||||
'org.eclipse.elk.layered.layering.layerId': pathGraphNodeLayer(node.type, nodeKey),
|
||||
},
|
||||
};
|
||||
}),
|
||||
edges: validEdges.map((edge) => ({
|
||||
id: edge.id,
|
||||
@@ -543,6 +577,23 @@
|
||||
}
|
||||
}
|
||||
|
||||
/** 与后端 GraphNodeType 一致:优先 fact_key 前缀,再 category/type。 */
|
||||
function resolveGraphNodeType(node) {
|
||||
if (!node) return 'note';
|
||||
const key = String(node.fact_key || node.id || '').toLowerCase();
|
||||
if (key.startsWith('target/')) return 'target';
|
||||
if (key.startsWith('exploit/') || key.startsWith('poc/') || key.startsWith('evidence/')) return 'exploit';
|
||||
if (key.startsWith('chain/')) return 'chain';
|
||||
if (key.startsWith('finding/')) return 'finding';
|
||||
if (key.startsWith('auth/')) return 'auth';
|
||||
if (key.startsWith('infra/')) return 'infra';
|
||||
if (key.startsWith('business/')) return 'business';
|
||||
if (key.startsWith('vuln:')) return 'vulnerability';
|
||||
const t = String(node.type || node.category || 'note').toLowerCase();
|
||||
if (t === 'vuln') return 'vulnerability';
|
||||
return t || 'note';
|
||||
}
|
||||
|
||||
global.ProjectFactGraph = {
|
||||
render,
|
||||
destroy,
|
||||
@@ -551,5 +602,7 @@
|
||||
setConnectMode,
|
||||
selectEdge,
|
||||
clearEdgeSelection,
|
||||
nodeTheme,
|
||||
resolveGraphNodeType,
|
||||
};
|
||||
})(typeof window !== 'undefined' ? window : globalThis);
|
||||
|
||||
+27
-17
@@ -64,7 +64,7 @@ Host: ...
|
||||
## 关联
|
||||
- related_vulnerability_id: <可选>
|
||||
- 依赖事实: <fact_key,如 auth/session_cookie>
|
||||
- 结构化出边(自动同步):
|
||||
- 结构化关系边(自动同步;links 文本格式 type: source_fact_key):
|
||||
- discovered_on: target/primary_domain
|
||||
|
||||
## 备注与不确定性
|
||||
@@ -798,13 +798,14 @@ async function handleGraphConnectNodePick(factKey) {
|
||||
loadProjectFacts();
|
||||
}
|
||||
|
||||
function formatOutgoingLinksForModal(links) {
|
||||
function formatIncomingLinksForModal(links) {
|
||||
if (!links || !links.length) return '';
|
||||
return links
|
||||
.map((e) => `${e.edge_type || e.type}: ${e.target_fact_key || e.to}`)
|
||||
.map((e) => `${e.edge_type || e.type}: ${e.source_fact_key || e.from}`)
|
||||
.join('\n');
|
||||
}
|
||||
|
||||
|
||||
async function loadProjectFactGraph() {
|
||||
const container = document.getElementById('project-fact-graph-container');
|
||||
const statsEl = document.getElementById('project-fact-graph-stats');
|
||||
@@ -887,9 +888,9 @@ function renderGraphEdgesListHtml(factKey, graphData, selectedEdgeId) {
|
||||
return edges
|
||||
.map((e) => {
|
||||
const isOut = e.source === factKey;
|
||||
const dirLabel = isOut ? tp('projects.graphEdgeOutgoing') : tp('projects.graphEdgeIncoming');
|
||||
const other = isOut ? e.target : e.source;
|
||||
const arrow = isOut ? '→' : '←';
|
||||
const dirLabel = isOut ? tp('projects.graphEdgeFromSelf') : tp('projects.graphEdgeToSelf');
|
||||
const src = e.source || '';
|
||||
const tgt = e.target || '';
|
||||
const selected = e.id === selectedEdgeId ? ' is-selected' : '';
|
||||
const synthetic = isSyntheticGraphEdge(e);
|
||||
const deleteBtn = synthetic
|
||||
@@ -898,8 +899,7 @@ function renderGraphEdgesListHtml(factKey, graphData, selectedEdgeId) {
|
||||
return `<div class="project-fact-graph-edge-item${selected}" data-edge-id="${escapeHtml(e.id)}" onclick="focusProjectFactGraphEdge(${JSON.stringify(e.id)})">
|
||||
<span class="project-fact-graph-edge-dir">${escapeHtml(dirLabel)}</span>
|
||||
<span class="project-fact-graph-edge-type">${escapeHtml(e.type || '')}</span>
|
||||
<span class="project-fact-graph-edge-arrow">${arrow}</span>
|
||||
<span class="project-fact-graph-edge-peer" title="${escapeHtml(other)}">${escapeHtml(other)}</span>
|
||||
<span class="project-fact-graph-edge-peer" title="${escapeHtml(src + ' → ' + tgt)}">${escapeHtml(src)} → ${escapeHtml(tgt)}</span>
|
||||
${deleteBtn}
|
||||
</div>`;
|
||||
})
|
||||
@@ -935,17 +935,25 @@ function showProjectFactGraphNode(factKey, graphData, selectedEdgeId) {
|
||||
if (!sidebar || !titleEl || !metaEl) return;
|
||||
titleEl.textContent = factKey;
|
||||
if (categoryEl) {
|
||||
const cat = node?.category || node?.type || '';
|
||||
categoryEl.textContent = cat;
|
||||
categoryEl.hidden = !cat;
|
||||
categoryEl.className = 'project-fact-graph-node-category project-fact-graph-node-category--' + (cat || 'note');
|
||||
const visualType =
|
||||
typeof ProjectFactGraph !== 'undefined' && ProjectFactGraph.resolveGraphNodeType
|
||||
? ProjectFactGraph.resolveGraphNodeType(node)
|
||||
: node?.type || node?.category || 'note';
|
||||
const theme =
|
||||
typeof ProjectFactGraph !== 'undefined' && ProjectFactGraph.nodeTheme
|
||||
? ProjectFactGraph.nodeTheme(visualType)
|
||||
: { typeEn: String(visualType).toUpperCase(), typeLabel: visualType };
|
||||
categoryEl.textContent = theme.typeEn || String(visualType).toUpperCase();
|
||||
categoryEl.hidden = false;
|
||||
categoryEl.className = 'project-fact-graph-node-category project-fact-graph-node-category--' + visualType;
|
||||
categoryEl.title = theme.typeLabel || visualType;
|
||||
}
|
||||
const conf = node?.confidence || '';
|
||||
const label = node?.label || '';
|
||||
if (label || conf) {
|
||||
const summary = (node?.summary || node?.label || '').trim();
|
||||
if (summary || conf) {
|
||||
const parts = [];
|
||||
if (label) {
|
||||
parts.push(`<span class="project-fact-graph-node-summary">${escapeHtml(label)}</span>`);
|
||||
if (summary) {
|
||||
parts.push(`<span class="project-fact-graph-node-summary">${escapeHtml(summary)}</span>`);
|
||||
}
|
||||
if (conf) {
|
||||
parts.push(formatConfidenceBadge(conf));
|
||||
@@ -1806,6 +1814,8 @@ function resetFactModalForm() {
|
||||
if (rel) rel.value = '';
|
||||
const linksEl = document.getElementById('fact-modal-links');
|
||||
if (linksEl) linksEl.value = '';
|
||||
const incomingWrap = document.getElementById('fact-modal-incoming-links-wrap');
|
||||
if (incomingWrap) incomingWrap.hidden = true;
|
||||
updateFactFormHints();
|
||||
}
|
||||
|
||||
@@ -1838,7 +1848,7 @@ function fillFactModalForm(f) {
|
||||
const rel = document.getElementById('fact-modal-related-vuln');
|
||||
if (rel) rel.value = f.related_vulnerability_id || '';
|
||||
const linksEl = document.getElementById('fact-modal-links');
|
||||
if (linksEl) linksEl.value = formatOutgoingLinksForModal(f.outgoing_links);
|
||||
if (linksEl) linksEl.value = formatIncomingLinksForModal(f.incoming_links);
|
||||
const pinEl = document.getElementById('fact-modal-pinned');
|
||||
if (pinEl) pinEl.checked = !!f.pinned;
|
||||
updateFactFormHints();
|
||||
|
||||
Reference in New Issue
Block a user