mirror of
https://github.com/jtang613/IDAssist
synced 2026-08-09 12:43:55 +00:00
Merge branch 'master' into pr-6
This commit is contained in:
+1
-1
@@ -3,7 +3,7 @@
|
||||
"plugin": {
|
||||
"name": "IDAssist",
|
||||
"entryPoint": "idassist_plugin.py",
|
||||
"version": "1.5.0",
|
||||
"version": "1.6.0",
|
||||
"description": "AI-powered reverse engineering plugin with LLM analysis, semantic knowledge graphs, RAG search, and SymGraph collaboration",
|
||||
"license": "MIT",
|
||||
"urls": {
|
||||
|
||||
@@ -1785,6 +1785,7 @@ Analyze the specific instruction/line of code marked with >>> below. Provide a d
|
||||
return
|
||||
self.view.update_security_info(
|
||||
node.risk_level,
|
||||
node.category,
|
||||
node.activity_profile,
|
||||
node.security_flags,
|
||||
node.network_apis,
|
||||
|
||||
@@ -481,13 +481,24 @@ class SemanticGraphController:
|
||||
results = []
|
||||
for edge in edges:
|
||||
target_id = edge.target_id if edge.source_id == node_id else edge.source_id
|
||||
target_node = self.graph_store.get_node_by_id(str(target_id))
|
||||
results.append({
|
||||
"type": edge.edge_type,
|
||||
"target": str(target_id),
|
||||
"target_id": str(target_id),
|
||||
"target_label": self._format_node_display_name(target_node, str(target_id)),
|
||||
"weight": edge.weight or 1.0,
|
||||
})
|
||||
return results
|
||||
|
||||
@staticmethod
|
||||
def _format_node_display_name(node, node_id: str) -> str:
|
||||
if node:
|
||||
if node.name:
|
||||
return node.name
|
||||
if node.address is not None:
|
||||
return f"0x{int(node.address):x}"
|
||||
return f"{node_id[:8]}..." if len(node_id) > 8 else node_id
|
||||
|
||||
def _collect_graph(self, binary_hash: str, center_id: int, n_hops: int, edge_types: Set[str]):
|
||||
max_nodes = 50
|
||||
visited = {center_id}
|
||||
|
||||
@@ -593,6 +593,7 @@ class ApplySymbolsWorker(QThread):
|
||||
file_paths=self._coerce_list(props.get("file_paths")),
|
||||
domains=self._coerce_list(props.get("domains")),
|
||||
registry_keys=self._coerce_list(props.get("registry_keys")),
|
||||
category=props.get("category"),
|
||||
risk_level=props.get("risk_level"),
|
||||
activity_profile=props.get("activity_profile"),
|
||||
analysis_depth=int(props.get("analysis_depth", 0) or 0),
|
||||
@@ -1672,6 +1673,8 @@ class SymGraphController(QObject):
|
||||
result['domains'] = list(node.domains)
|
||||
if node.registry_keys:
|
||||
result['registry_keys'] = list(node.registry_keys)
|
||||
if node.category:
|
||||
result['category'] = node.category
|
||||
|
||||
if node.risk_level:
|
||||
result['risk_level'] = node.risk_level
|
||||
|
||||
@@ -143,6 +143,9 @@ class AnalysisDBService:
|
||||
"address": "INTEGER",
|
||||
"binary_id": "TEXT",
|
||||
"name": "TEXT",
|
||||
"signature": "TEXT",
|
||||
"decompiled_code": "TEXT",
|
||||
"disassembly": "TEXT",
|
||||
"raw_content": "TEXT",
|
||||
"llm_summary": "TEXT",
|
||||
"confidence": "REAL",
|
||||
@@ -155,6 +158,7 @@ class AnalysisDBService:
|
||||
"file_paths": "TEXT",
|
||||
"domains": "TEXT",
|
||||
"registry_keys": "TEXT",
|
||||
"category": "TEXT",
|
||||
"risk_level": "TEXT",
|
||||
"activity_profile": "TEXT",
|
||||
"analysis_depth": "INTEGER",
|
||||
@@ -192,6 +196,7 @@ class AnalysisDBService:
|
||||
expected_fts = [
|
||||
"id",
|
||||
"name",
|
||||
"signature",
|
||||
"llm_summary",
|
||||
"security_flags",
|
||||
]
|
||||
@@ -209,6 +214,9 @@ class AnalysisDBService:
|
||||
address INTEGER,
|
||||
binary_id TEXT NOT NULL,
|
||||
name TEXT,
|
||||
signature TEXT,
|
||||
decompiled_code TEXT,
|
||||
disassembly TEXT,
|
||||
raw_content TEXT,
|
||||
llm_summary TEXT,
|
||||
confidence REAL DEFAULT 0.0,
|
||||
@@ -221,6 +229,7 @@ class AnalysisDBService:
|
||||
file_paths TEXT,
|
||||
domains TEXT,
|
||||
registry_keys TEXT,
|
||||
category TEXT,
|
||||
risk_level TEXT,
|
||||
activity_profile TEXT,
|
||||
analysis_depth INTEGER DEFAULT 0,
|
||||
@@ -306,6 +315,7 @@ class AnalysisDBService:
|
||||
CREATE VIRTUAL TABLE IF NOT EXISTS node_fts USING fts5(
|
||||
id,
|
||||
name,
|
||||
signature,
|
||||
llm_summary,
|
||||
security_flags,
|
||||
content='graph_nodes',
|
||||
@@ -315,22 +325,22 @@ class AnalysisDBService:
|
||||
self._drop_graph_nodes_triggers(cursor)
|
||||
cursor.execute('''
|
||||
CREATE TRIGGER IF NOT EXISTS graph_nodes_ai AFTER INSERT ON graph_nodes BEGIN
|
||||
INSERT INTO node_fts(rowid, id, name, llm_summary, security_flags)
|
||||
VALUES (new.rowid, new.id, new.name, new.llm_summary, new.security_flags);
|
||||
INSERT INTO node_fts(rowid, id, name, signature, llm_summary, security_flags)
|
||||
VALUES (new.rowid, new.id, new.name, new.signature, new.llm_summary, new.security_flags);
|
||||
END;
|
||||
''')
|
||||
cursor.execute('''
|
||||
CREATE TRIGGER IF NOT EXISTS graph_nodes_ad AFTER DELETE ON graph_nodes BEGIN
|
||||
INSERT INTO node_fts(node_fts, rowid, id, name, llm_summary, security_flags)
|
||||
VALUES ('delete', old.rowid, old.id, old.name, old.llm_summary, old.security_flags);
|
||||
INSERT INTO node_fts(node_fts, rowid, id, name, signature, llm_summary, security_flags)
|
||||
VALUES ('delete', old.rowid, old.id, old.name, old.signature, old.llm_summary, old.security_flags);
|
||||
END;
|
||||
''')
|
||||
cursor.execute('''
|
||||
CREATE TRIGGER IF NOT EXISTS graph_nodes_au AFTER UPDATE ON graph_nodes BEGIN
|
||||
INSERT INTO node_fts(node_fts, rowid, id, name, llm_summary, security_flags)
|
||||
VALUES ('delete', old.rowid, old.id, old.name, old.llm_summary, old.security_flags);
|
||||
INSERT INTO node_fts(rowid, id, name, llm_summary, security_flags)
|
||||
VALUES (new.rowid, new.id, new.name, new.llm_summary, new.security_flags);
|
||||
INSERT INTO node_fts(node_fts, rowid, id, name, signature, llm_summary, security_flags)
|
||||
VALUES ('delete', old.rowid, old.id, old.name, old.signature, old.llm_summary, old.security_flags);
|
||||
INSERT INTO node_fts(rowid, id, name, signature, llm_summary, security_flags)
|
||||
VALUES (new.rowid, new.id, new.name, new.signature, new.llm_summary, new.security_flags);
|
||||
END;
|
||||
''')
|
||||
except Exception as e:
|
||||
@@ -447,8 +457,11 @@ class AnalysisDBService:
|
||||
cursor.execute('CREATE INDEX IF NOT EXISTS idx_line_explanations_lookup ON BNLineExplanations(binary_hash, line_address, view_type)')
|
||||
cursor.execute('CREATE INDEX IF NOT EXISTS idx_line_explanations_function ON BNLineExplanations(binary_hash, function_start)')
|
||||
|
||||
# Note: GraphRAG tables (nodes, edges, communities, FTS) are now created
|
||||
# by database migrations in db_migrations.py (migrations 004-006)
|
||||
# Reconcile GraphRAG tables against the current expected schema even if
|
||||
# the stored schema_version is stale or older plugin runs partially
|
||||
# applied migrations. This prevents runtime read failures on missing
|
||||
# graph columns such as category/risk_level.
|
||||
self._ensure_graphrag_schema(cursor)
|
||||
|
||||
conn.commit()
|
||||
log.log_info("AnalysisDB schema created successfully")
|
||||
@@ -472,6 +485,14 @@ class AnalysisDBService:
|
||||
try:
|
||||
from .db_migrations import DatabaseMigrations
|
||||
DatabaseMigrations.migrate_analysis_db(self._db_path)
|
||||
with self._db_lock:
|
||||
conn = self._get_connection()
|
||||
try:
|
||||
cursor = conn.cursor()
|
||||
self._ensure_graphrag_schema(cursor)
|
||||
conn.commit()
|
||||
finally:
|
||||
conn.close()
|
||||
except Exception as e:
|
||||
log.log_warn(f"Migration failed: {e}")
|
||||
|
||||
|
||||
@@ -258,7 +258,7 @@ class DatabaseMigrations:
|
||||
"id", "type", "address", "binary_id", "name", "signature", "decompiled_code", "disassembly", "raw_content",
|
||||
"llm_summary", "confidence", "embedding", "security_flags",
|
||||
"network_apis", "file_io_apis", "ip_addresses", "urls",
|
||||
"file_paths", "domains", "registry_keys", "risk_level",
|
||||
"file_paths", "domains", "registry_keys", "category", "risk_level",
|
||||
"activity_profile", "analysis_depth", "created_at", "updated_at",
|
||||
"is_stale", "user_edited"
|
||||
}
|
||||
@@ -299,6 +299,7 @@ class DatabaseMigrations:
|
||||
file_paths TEXT,
|
||||
domains TEXT,
|
||||
registry_keys TEXT,
|
||||
category TEXT,
|
||||
risk_level TEXT,
|
||||
activity_profile TEXT,
|
||||
analysis_depth INTEGER DEFAULT 0,
|
||||
|
||||
@@ -49,6 +49,23 @@ class GraphStore:
|
||||
except Exception:
|
||||
return []
|
||||
|
||||
@staticmethod
|
||||
def _normalize_semantic_fields(category: Optional[str], security_flags: Optional[List[str]]) -> Tuple[Optional[str], List[str]]:
|
||||
normalized_category = category.strip().lower().replace(" ", "_") if isinstance(category, str) and category.strip() else None
|
||||
filtered_flags: List[str] = []
|
||||
for flag in security_flags or []:
|
||||
if not isinstance(flag, str):
|
||||
continue
|
||||
trimmed = flag.strip()
|
||||
if not trimmed or trimmed == "LLM_FLAGGED":
|
||||
continue
|
||||
if trimmed.startswith("CATEGORY_"):
|
||||
if normalized_category is None:
|
||||
normalized_category = trimmed[len("CATEGORY_"):].lower()
|
||||
continue
|
||||
filtered_flags.append(trimmed)
|
||||
return normalized_category, filtered_flags
|
||||
|
||||
@staticmethod
|
||||
def _now_ms() -> int:
|
||||
return int(time.time() * 1000)
|
||||
@@ -61,12 +78,13 @@ class GraphStore:
|
||||
f"{qualifier}signature, {qualifier}decompiled_code, {qualifier}disassembly, {qualifier}raw_content, "
|
||||
f"{qualifier}llm_summary, {qualifier}confidence, {qualifier}embedding, {qualifier}security_flags, "
|
||||
f"{qualifier}network_apis, {qualifier}file_io_apis, {qualifier}ip_addresses, {qualifier}urls, "
|
||||
f"{qualifier}file_paths, {qualifier}domains, {qualifier}registry_keys, {qualifier}risk_level, "
|
||||
f"{qualifier}file_paths, {qualifier}domains, {qualifier}registry_keys, {qualifier}category, {qualifier}risk_level, "
|
||||
f"{qualifier}activity_profile, {qualifier}analysis_depth, {qualifier}created_at, {qualifier}updated_at, "
|
||||
f"{qualifier}is_stale, {qualifier}user_edited"
|
||||
)
|
||||
|
||||
def _row_to_node(self, row: Tuple[Any, ...]) -> GraphNode:
|
||||
category, security_flags = self._normalize_semantic_fields(row[20], self._deserialize_list(row[12]))
|
||||
return GraphNode(
|
||||
id=row[0],
|
||||
binary_hash=row[1],
|
||||
@@ -80,7 +98,7 @@ class GraphStore:
|
||||
llm_summary=row[9],
|
||||
confidence=row[10] if row[10] is not None else 0.0,
|
||||
embedding=row[11],
|
||||
security_flags=self._deserialize_list(row[12]),
|
||||
security_flags=security_flags,
|
||||
network_apis=self._deserialize_list(row[13]),
|
||||
file_io_apis=self._deserialize_list(row[14]),
|
||||
ip_addresses=self._deserialize_list(row[15]),
|
||||
@@ -88,13 +106,14 @@ class GraphStore:
|
||||
file_paths=self._deserialize_list(row[17]),
|
||||
domains=self._deserialize_list(row[18]),
|
||||
registry_keys=self._deserialize_list(row[19]),
|
||||
risk_level=row[20],
|
||||
activity_profile=row[21],
|
||||
analysis_depth=row[22] if row[22] is not None else 0,
|
||||
created_at=row[23],
|
||||
updated_at=row[24],
|
||||
is_stale=bool(row[25]),
|
||||
user_edited=bool(row[26]),
|
||||
category=category,
|
||||
risk_level=row[21],
|
||||
activity_profile=row[22],
|
||||
analysis_depth=row[23] if row[23] is not None else 0,
|
||||
created_at=row[24],
|
||||
updated_at=row[25],
|
||||
is_stale=bool(row[26]),
|
||||
user_edited=bool(row[27]),
|
||||
)
|
||||
|
||||
def get_node_by_address(self, binary_hash: str, node_type: str, address: int) -> Optional[GraphNode]:
|
||||
@@ -151,6 +170,8 @@ class GraphStore:
|
||||
existing = self.get_node_by_address(node.binary_hash, node.node_type, node.address)
|
||||
node.id = existing.id if existing else str(uuid.uuid4())
|
||||
|
||||
node.category, node.security_flags = self._normalize_semantic_fields(node.category, node.security_flags)
|
||||
|
||||
now_ms = self._now_ms()
|
||||
security_flags = self._serialize_list(node.security_flags)
|
||||
network_apis = self._serialize_list(node.network_apis)
|
||||
@@ -169,10 +190,10 @@ class GraphStore:
|
||||
INSERT INTO graph_nodes (
|
||||
id, type, address, binary_id, name, signature, decompiled_code, disassembly, raw_content, llm_summary,
|
||||
confidence, embedding, security_flags, network_apis, file_io_apis,
|
||||
ip_addresses, urls, file_paths, domains, registry_keys, risk_level,
|
||||
ip_addresses, urls, file_paths, domains, registry_keys, category, risk_level,
|
||||
activity_profile, analysis_depth, created_at, updated_at, is_stale,
|
||||
user_edited
|
||||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
ON CONFLICT(id) DO UPDATE SET
|
||||
-- STRUCTURAL DATA: Always update from fresh extraction
|
||||
type = excluded.type,
|
||||
@@ -191,6 +212,7 @@ class GraphStore:
|
||||
file_paths = excluded.file_paths,
|
||||
domains = excluded.domains,
|
||||
registry_keys = excluded.registry_keys,
|
||||
category = excluded.category,
|
||||
risk_level = excluded.risk_level,
|
||||
activity_profile = excluded.activity_profile,
|
||||
updated_at = excluded.updated_at,
|
||||
@@ -223,6 +245,7 @@ class GraphStore:
|
||||
file_paths,
|
||||
domains,
|
||||
registry_keys,
|
||||
node.category,
|
||||
node.risk_level,
|
||||
node.activity_profile,
|
||||
node.analysis_depth,
|
||||
@@ -609,6 +632,8 @@ class GraphStore:
|
||||
# Cache the node ID
|
||||
self._node_cache[cache_key] = node.id
|
||||
|
||||
node.category, node.security_flags = self._normalize_semantic_fields(node.category, node.security_flags)
|
||||
|
||||
# Always queue for batch insert/update (even existing nodes need is_stale updated)
|
||||
with self._batch_lock:
|
||||
self._node_batch.append(node)
|
||||
@@ -685,6 +710,7 @@ class GraphStore:
|
||||
self._serialize_list(node.file_paths),
|
||||
self._serialize_list(node.domains),
|
||||
self._serialize_list(node.registry_keys),
|
||||
node.category,
|
||||
node.risk_level,
|
||||
node.activity_profile,
|
||||
node.analysis_depth,
|
||||
@@ -702,10 +728,10 @@ class GraphStore:
|
||||
INSERT INTO graph_nodes (
|
||||
id, type, address, binary_id, name, signature, decompiled_code, disassembly, raw_content, llm_summary,
|
||||
confidence, embedding, security_flags, network_apis, file_io_apis,
|
||||
ip_addresses, urls, file_paths, domains, registry_keys, risk_level,
|
||||
ip_addresses, urls, file_paths, domains, registry_keys, category, risk_level,
|
||||
activity_profile, analysis_depth, created_at, updated_at, is_stale,
|
||||
user_edited
|
||||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
ON CONFLICT(id) DO UPDATE SET
|
||||
-- STRUCTURAL DATA: Always update from fresh extraction
|
||||
type = excluded.type,
|
||||
@@ -724,6 +750,7 @@ class GraphStore:
|
||||
file_paths = excluded.file_paths,
|
||||
domains = excluded.domains,
|
||||
registry_keys = excluded.registry_keys,
|
||||
category = excluded.category,
|
||||
risk_level = excluded.risk_level,
|
||||
activity_profile = excluded.activity_profile,
|
||||
updated_at = excluded.updated_at,
|
||||
|
||||
@@ -214,6 +214,7 @@ class GraphNode:
|
||||
file_paths: List[str] = field(default_factory=list)
|
||||
domains: List[str] = field(default_factory=list)
|
||||
registry_keys: List[str] = field(default_factory=list)
|
||||
category: Optional[str] = None
|
||||
activity_profile: Optional[str] = None
|
||||
risk_level: Optional[str] = None
|
||||
analysis_depth: int = 0
|
||||
|
||||
@@ -41,7 +41,7 @@ class GraphRAGQueryEngine:
|
||||
"has_structure_data": has_structure,
|
||||
"summary": summary,
|
||||
"security_flags": node.security_flags or [],
|
||||
"category": self._extract_category(node.llm_summary),
|
||||
"category": node.category or self._extract_category(node.llm_summary),
|
||||
"confidence": 0.8 if has_semantic else 0.0,
|
||||
"callers": callers,
|
||||
"callees": callees,
|
||||
|
||||
@@ -128,7 +128,10 @@ class SemanticExtractor:
|
||||
if not response:
|
||||
return False
|
||||
|
||||
from .extraction_prompts import extract_category
|
||||
|
||||
node.llm_summary = response.strip()
|
||||
node.category = extract_category(response) or None
|
||||
node.confidence = 0.85 # LLM-generated summary confidence
|
||||
node.is_stale = False
|
||||
node.user_edited = False
|
||||
|
||||
@@ -283,6 +283,8 @@ class GraphNode:
|
||||
properties['domains'] = data.get('domains', [])
|
||||
if 'registry_keys' in data and 'registry_keys' not in properties:
|
||||
properties['registry_keys'] = data.get('registry_keys', [])
|
||||
if 'category' in data and 'category' not in properties:
|
||||
properties['category'] = data.get('category')
|
||||
if 'risk_level' in data and 'risk_level' not in properties:
|
||||
properties['risk_level'] = data.get('risk_level')
|
||||
if 'activity_profile' in data and 'activity_profile' not in properties:
|
||||
@@ -320,6 +322,8 @@ class GraphNode:
|
||||
result['disassembly'] = self.properties.get('disassembly')
|
||||
if self.properties.get('decompiled_code') or self.properties.get('raw_content'):
|
||||
result['raw_content'] = self.properties.get('decompiled_code') or self.properties.get('raw_content')
|
||||
if self.properties.get('category'):
|
||||
result['category'] = self.properties.get('category')
|
||||
if self.properties:
|
||||
result['properties'] = self.properties
|
||||
return result
|
||||
|
||||
@@ -233,6 +233,7 @@ class ExplainTabView(QWidget):
|
||||
grid.setVerticalSpacing(2)
|
||||
|
||||
self.risk_label = QLabel("Risk: —")
|
||||
self.category_label = QLabel("Category: —")
|
||||
self.activity_label = QLabel("Activity: —")
|
||||
self.flags_label = QLabel("Flags: None")
|
||||
self.flags_label.setWordWrap(True)
|
||||
@@ -252,17 +253,18 @@ class ExplainTabView(QWidget):
|
||||
self.file_text.setLineWrapMode(QTextEdit.NoWrap)
|
||||
|
||||
grid.addWidget(self.risk_label, 0, 0)
|
||||
grid.addWidget(self.activity_label, 0, 1)
|
||||
grid.addWidget(self.flags_label, 1, 0, 1, 2)
|
||||
grid.addWidget(self.network_label, 2, 0)
|
||||
grid.addWidget(self.file_label, 2, 1)
|
||||
grid.addWidget(self.network_text, 3, 0)
|
||||
grid.addWidget(self.file_text, 3, 1)
|
||||
grid.addWidget(self.category_label, 0, 1)
|
||||
grid.addWidget(self.activity_label, 1, 0, 1, 2)
|
||||
grid.addWidget(self.flags_label, 2, 0, 1, 2)
|
||||
grid.addWidget(self.network_label, 3, 0)
|
||||
grid.addWidget(self.file_label, 3, 1)
|
||||
grid.addWidget(self.network_text, 4, 0)
|
||||
grid.addWidget(self.file_text, 4, 1)
|
||||
|
||||
# Let the API text areas absorb extra vertical space from the splitter
|
||||
for r in range(0, 3):
|
||||
for r in range(0, 4):
|
||||
grid.setRowStretch(r, 0)
|
||||
grid.setRowStretch(3, 1)
|
||||
grid.setRowStretch(4, 1)
|
||||
grid.setColumnStretch(0, 1)
|
||||
grid.setColumnStretch(1, 1)
|
||||
|
||||
@@ -356,9 +358,10 @@ class ExplainTabView(QWidget):
|
||||
else:
|
||||
self.explain_editor.setPlainText(markdown_text)
|
||||
|
||||
def update_security_info(self, risk_level, activity_profile, security_flags, network_apis, file_io_apis):
|
||||
def update_security_info(self, risk_level, category, activity_profile, security_flags, network_apis, file_io_apis):
|
||||
"""Update the security analysis panel."""
|
||||
self.risk_label.setText(f"Risk: {risk_level or '—'}")
|
||||
self.category_label.setText(f"Category: {category or '—'}")
|
||||
self.activity_label.setText(f"Activity: {activity_profile or '—'}")
|
||||
|
||||
flags_text = ", ".join(security_flags) if security_flags else "None"
|
||||
@@ -367,12 +370,13 @@ class ExplainTabView(QWidget):
|
||||
self.network_text.setPlainText("\n".join(network_apis) if network_apis else "(none detected)")
|
||||
self.file_text.setPlainText("\n".join(file_io_apis) if file_io_apis else "(none detected)")
|
||||
|
||||
has_data = bool(risk_level or activity_profile or security_flags or network_apis or file_io_apis)
|
||||
has_data = bool(risk_level or category or activity_profile or security_flags or network_apis or file_io_apis)
|
||||
self.security_group.setVisible(has_data)
|
||||
|
||||
def clear_security_info(self):
|
||||
"""Clear and hide the security analysis panel."""
|
||||
self.risk_label.setText("Risk: —")
|
||||
self.category_label.setText("Category: —")
|
||||
self.activity_label.setText("Activity: —")
|
||||
self.flags_label.setText("Flags: None")
|
||||
self.network_text.clear()
|
||||
|
||||
@@ -432,10 +432,10 @@ class SemanticGraphListView(QWidget):
|
||||
row = self.edges_table.rowCount()
|
||||
self.edges_table.insertRow(row)
|
||||
self.edges_table.setItem(row, 0, QTableWidgetItem(edge["type"]))
|
||||
self.edges_table.setItem(row, 1, QTableWidgetItem(edge["target"]))
|
||||
self.edges_table.setItem(row, 1, QTableWidgetItem(edge["target_label"]))
|
||||
self.edges_table.setItem(row, 2, QTableWidgetItem(f"{edge.get('weight', 1.0):.2f}"))
|
||||
button = QPushButton("View")
|
||||
button.clicked.connect(lambda _=None, target=edge["target"]: self.edge_clicked.emit(target))
|
||||
button.clicked.connect(lambda _=None, target_id=edge["target_id"]: self.edge_clicked.emit(target_id))
|
||||
self.edges_table.setCellWidget(row, 3, button)
|
||||
|
||||
def set_security_flags(self, flags: List[str]):
|
||||
|
||||
Reference in New Issue
Block a user