Phase 5: Final integration — fix imports, full module discovery, interactive menu
- Fix pyserial import error in cellular_backup.py (lazy import with fallback) - Replace stealth-only module discovery with discover_all_modules() across all 5 categories (stealth, passive, active, intel, connectivity) - Wire up interactive menu options 5-8: - View Credentials: query credential_db SQLite, Rich table with stats - View Intelligence: host count, cred stats, security posture summary - Network Topology: host table from topology_mapper state data - Timeline: recent entries from operator_audit HMAC-chained log - Update start command to discover+register all enabled modules (not just stealth) - Update modules/activate/selftest commands to use full module discovery - Add is_module_enabled() config-aware helper for per-category defaults - Fix modules.yaml module count headers (16->17 passive, 8->9 active/intel) - Set bettercap_mgr enabled=false (all active modules disabled by default) - Add tests/test_all_modules.py: 179 parametrized tests covering import, BaseModule subclass, attributes, and instantiation for all 55 modules across passive, active, intel, and connectivity categories - All 256 tests passing
This commit is contained in:
+416
-35
@@ -12,6 +12,9 @@ import signal
|
||||
import logging
|
||||
import inspect
|
||||
import importlib
|
||||
import json
|
||||
import sqlite3
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
from typing import Dict, List, Optional, Type
|
||||
|
||||
@@ -50,25 +53,28 @@ logger = logging.getLogger("bb.cli")
|
||||
# PID file for daemon mode
|
||||
PID_FILE = "/tmp/.bb.pid"
|
||||
|
||||
# All module categories to discover
|
||||
MODULE_CATEGORIES = ["stealth", "passive", "active", "intel", "connectivity"]
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Module discovery
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def discover_stealth_modules() -> Dict[str, Type[BaseModule]]:
|
||||
"""Scan modules/stealth/ for BaseModule subclasses."""
|
||||
def discover_modules_in_category(category: str) -> Dict[str, Type[BaseModule]]:
|
||||
"""Scan modules/<category>/ for BaseModule subclasses."""
|
||||
modules: Dict[str, Type[BaseModule]] = {}
|
||||
stealth_dir = os.path.join(PROJECT_ROOT, "modules", "stealth")
|
||||
cat_dir = os.path.join(PROJECT_ROOT, "modules", category)
|
||||
|
||||
if not os.path.isdir(stealth_dir):
|
||||
if not os.path.isdir(cat_dir):
|
||||
return modules
|
||||
|
||||
for filename in sorted(os.listdir(stealth_dir)):
|
||||
for filename in sorted(os.listdir(cat_dir)):
|
||||
if filename.startswith("_") or not filename.endswith(".py"):
|
||||
continue
|
||||
|
||||
module_name = filename[:-3]
|
||||
dotted = f"modules.stealth.{module_name}"
|
||||
dotted = f"modules.{category}.{module_name}"
|
||||
|
||||
try:
|
||||
mod = importlib.import_module(dotted)
|
||||
@@ -90,18 +96,53 @@ def discover_stealth_modules() -> Dict[str, Type[BaseModule]]:
|
||||
return modules
|
||||
|
||||
|
||||
def discover_all_modules() -> Dict[str, Type[BaseModule]]:
|
||||
"""Discover all modules across every category."""
|
||||
all_modules: Dict[str, Type[BaseModule]] = {}
|
||||
for category in MODULE_CATEGORIES:
|
||||
all_modules.update(discover_modules_in_category(category))
|
||||
return all_modules
|
||||
|
||||
|
||||
def discover_stealth_modules() -> Dict[str, Type[BaseModule]]:
|
||||
"""Scan modules/stealth/ for BaseModule subclasses (backward compat)."""
|
||||
return discover_modules_in_category("stealth")
|
||||
|
||||
|
||||
def get_module_config(module_name: str, config: dict) -> dict:
|
||||
"""Extract module-specific config from the master config."""
|
||||
# Check for module-specific section
|
||||
# Check for module-specific section in each category
|
||||
for category in MODULE_CATEGORIES:
|
||||
cat_cfg = config.get(category, {})
|
||||
if module_name in cat_cfg:
|
||||
merged = dict(config)
|
||||
merged.update(cat_cfg[module_name])
|
||||
return merged
|
||||
|
||||
# Fallback: check top-level modules section
|
||||
mod_cfg = config.get("modules", {}).get(module_name, {})
|
||||
if mod_cfg:
|
||||
# Merge with top-level config for access to device, network, etc.
|
||||
merged = dict(config)
|
||||
merged.update(mod_cfg)
|
||||
return merged
|
||||
return dict(config)
|
||||
|
||||
|
||||
def is_module_enabled(module_name: str, module_type: str, config: dict) -> bool:
|
||||
"""Check if a module is enabled in the config (modules.yaml)."""
|
||||
# Look in the category-level config
|
||||
cat_cfg = config.get(module_type, {})
|
||||
if module_name in cat_cfg:
|
||||
return cat_cfg[module_name].get("enabled", True)
|
||||
# Default: stealth/passive/intel enabled, active disabled
|
||||
if module_type in ("stealth", "passive", "intel"):
|
||||
return True
|
||||
if module_type == "connectivity":
|
||||
# Connectivity has mixed defaults; check config
|
||||
return cat_cfg.get(module_name, {}).get("enabled", False)
|
||||
return False # active modules default to disabled
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Click CLI
|
||||
# ---------------------------------------------------------------------------
|
||||
@@ -145,15 +186,26 @@ def start(daemon, passive_only):
|
||||
tool_manager = ToolManager(bus=bus)
|
||||
tool_manager.start_monitoring()
|
||||
|
||||
# Discover and register stealth modules
|
||||
discovered = discover_stealth_modules()
|
||||
console.print(f" Discovered [cyan]{len(discovered)}[/cyan] stealth modules")
|
||||
|
||||
enabled_modules = config.get("modules", {}).get("enabled", list(discovered.keys()))
|
||||
# Discover all modules across every category
|
||||
discovered = discover_all_modules()
|
||||
by_type: Dict[str, int] = {}
|
||||
for name, cls in discovered.items():
|
||||
if name in enabled_modules or not config.get("modules", {}).get("enabled"):
|
||||
mod_type = getattr(cls, "module_type", "unknown")
|
||||
by_type[mod_type] = by_type.get(mod_type, 0) + 1
|
||||
|
||||
console.print(f" Discovered [cyan]{len(discovered)}[/cyan] modules: " +
|
||||
", ".join(f"{t}={c}" for t, c in sorted(by_type.items())))
|
||||
|
||||
# Register enabled modules
|
||||
registered_count = 0
|
||||
for name, cls in discovered.items():
|
||||
mod_type = getattr(cls, "module_type", "unknown")
|
||||
if is_module_enabled(name, mod_type, config):
|
||||
mod_config = get_module_config(name, config)
|
||||
engine.register(cls, mod_config)
|
||||
registered_count += 1
|
||||
|
||||
console.print(f" Registered [cyan]{registered_count}[/cyan] enabled modules")
|
||||
|
||||
# Start modules in dependency order
|
||||
results = engine.start_all()
|
||||
@@ -272,7 +324,7 @@ def activate(module_name):
|
||||
console.print(f"[red]Config error:[/red] {exc}")
|
||||
return
|
||||
|
||||
discovered = discover_stealth_modules()
|
||||
discovered = discover_all_modules()
|
||||
if module_name not in discovered:
|
||||
console.print(f"[red]Module '{module_name}' not found.[/red]")
|
||||
console.print(f"Available: {', '.join(sorted(discovered.keys()))}")
|
||||
@@ -443,10 +495,11 @@ def selftest():
|
||||
else:
|
||||
console.print(f" [yellow]SKIP[/yellow] Binary not found: {binary}")
|
||||
|
||||
# 7. Stealth modules
|
||||
discovered = discover_stealth_modules()
|
||||
console.print(f" [green]PASS[/green] Stealth modules discovered: {len(discovered)}")
|
||||
passed += 1
|
||||
# 7. Module discovery — all categories
|
||||
for category in MODULE_CATEGORIES:
|
||||
discovered = discover_modules_in_category(category)
|
||||
console.print(f" [green]PASS[/green] {category.capitalize()} modules: {len(discovered)}")
|
||||
passed += 1
|
||||
|
||||
console.print(f"\n[bold]Results: {passed} passed, {failed} failed[/bold]")
|
||||
|
||||
@@ -459,8 +512,7 @@ def list_modules():
|
||||
except Exception:
|
||||
config = {}
|
||||
|
||||
discovered = discover_stealth_modules()
|
||||
enabled_list = config.get("modules", {}).get("enabled")
|
||||
discovered = discover_all_modules()
|
||||
|
||||
table = Table(title="Available Modules", show_lines=True)
|
||||
table.add_column("Module", style="cyan", min_width=22)
|
||||
@@ -477,16 +529,340 @@ def list_modules():
|
||||
requires_root = "Yes" if getattr(cls, "requires_root", False) else "No"
|
||||
deps = ", ".join(getattr(cls, "dependencies", [])) or "-"
|
||||
|
||||
if enabled_list is None:
|
||||
enabled = "[green]Yes[/green]"
|
||||
elif name in enabled_list:
|
||||
enabled = "[green]Yes[/green]"
|
||||
else:
|
||||
enabled = "[dim]No[/dim]"
|
||||
enabled = is_module_enabled(name, mod_type, config)
|
||||
enabled_str = "[green]Yes[/green]" if enabled else "[dim]No[/dim]"
|
||||
|
||||
table.add_row(name, mod_type, priority, requires_root, enabled, deps)
|
||||
table.add_row(name, mod_type, priority, requires_root, enabled_str, deps)
|
||||
|
||||
console.print(table)
|
||||
console.print(f"\n Total: [cyan]{len(discovered)}[/cyan] modules across "
|
||||
f"{len(MODULE_CATEGORIES)} categories")
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Interactive menu — view functions
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def _view_credentials():
|
||||
"""Query the credential database and display a Rich table."""
|
||||
base_dir = os.path.expanduser("~/.bigbrother")
|
||||
db_path = os.path.join(base_dir, "credentials.db")
|
||||
|
||||
if not os.path.isfile(db_path):
|
||||
console.print("\n[yellow]No credential database found.[/yellow]")
|
||||
console.print(f" Expected: {db_path}")
|
||||
console.print(" (Credentials are stored when modules capture them at runtime)")
|
||||
return
|
||||
|
||||
try:
|
||||
conn = sqlite3.connect(db_path)
|
||||
conn.row_factory = sqlite3.Row
|
||||
|
||||
# Summary stats
|
||||
total = conn.execute("SELECT COUNT(*) FROM credentials").fetchone()[0]
|
||||
cracked = conn.execute(
|
||||
"SELECT COUNT(*) FROM credentials WHERE crack_status='cracked'"
|
||||
).fetchone()[0]
|
||||
unique_users = conn.execute(
|
||||
"SELECT COUNT(DISTINCT username) FROM credentials"
|
||||
).fetchone()[0]
|
||||
unique_hosts = conn.execute(
|
||||
"SELECT COUNT(DISTINCT target_ip) FROM credentials WHERE target_ip != ''"
|
||||
).fetchone()[0]
|
||||
|
||||
console.print(f"\n Total credentials: [cyan]{total}[/cyan] | "
|
||||
f"Cracked: [green]{cracked}[/green] | "
|
||||
f"Unique users: [yellow]{unique_users}[/yellow] | "
|
||||
f"Unique hosts: [magenta]{unique_hosts}[/magenta]")
|
||||
|
||||
if total == 0:
|
||||
conn.close()
|
||||
return
|
||||
|
||||
# Show recent credentials
|
||||
rows = conn.execute(
|
||||
"""SELECT id, timestamp, source_module, target_ip, service,
|
||||
username, domain, cred_type, crack_status, cracked_value
|
||||
FROM credentials
|
||||
ORDER BY timestamp DESC LIMIT 50"""
|
||||
).fetchall()
|
||||
|
||||
table = Table(title="Credentials (most recent 50)", show_lines=True)
|
||||
table.add_column("ID", justify="right", min_width=4)
|
||||
table.add_column("Time", min_width=16)
|
||||
table.add_column("Source", min_width=14)
|
||||
table.add_column("Target", min_width=14)
|
||||
table.add_column("Service", min_width=10)
|
||||
table.add_column("Domain\\User", min_width=20)
|
||||
table.add_column("Type", min_width=10)
|
||||
table.add_column("Status", min_width=8)
|
||||
|
||||
for row in rows:
|
||||
ts = datetime.fromtimestamp(row["timestamp"]).strftime("%Y-%m-%d %H:%M")
|
||||
domain_user = row["username"]
|
||||
if row["domain"]:
|
||||
domain_user = f"{row['domain']}\\{row['username']}"
|
||||
|
||||
crack_status = row["crack_status"]
|
||||
if crack_status == "cracked":
|
||||
status_str = f"[green]cracked[/green]"
|
||||
elif crack_status == "cracking":
|
||||
status_str = f"[yellow]cracking[/yellow]"
|
||||
else:
|
||||
status_str = f"[dim]uncracked[/dim]"
|
||||
|
||||
table.add_row(
|
||||
str(row["id"]),
|
||||
ts,
|
||||
row["source_module"] or "-",
|
||||
row["target_ip"] or "-",
|
||||
row["service"] or "-",
|
||||
domain_user,
|
||||
row["cred_type"] or "-",
|
||||
status_str,
|
||||
)
|
||||
|
||||
console.print(table)
|
||||
|
||||
# Type breakdown
|
||||
type_rows = conn.execute(
|
||||
"SELECT cred_type, COUNT(*) as cnt FROM credentials GROUP BY cred_type ORDER BY cnt DESC"
|
||||
).fetchall()
|
||||
if type_rows:
|
||||
console.print("\n By type: " + " | ".join(
|
||||
f"{r['cred_type']}={r['cnt']}" for r in type_rows
|
||||
))
|
||||
|
||||
conn.close()
|
||||
|
||||
except Exception as exc:
|
||||
console.print(f"\n[red]Error reading credential database:[/red] {exc}")
|
||||
|
||||
|
||||
def _view_intel():
|
||||
"""Show an intelligence summary: hosts, credentials, modules, security tools."""
|
||||
base_dir = os.path.expanduser("~/.bigbrother")
|
||||
state = StateManager()
|
||||
|
||||
console.print("\n")
|
||||
intel_table = Table(title="Intelligence Summary", show_lines=True, min_width=60)
|
||||
intel_table.add_column("Category", style="cyan", min_width=25)
|
||||
intel_table.add_column("Value", min_width=30)
|
||||
|
||||
# Host count from topology mapper state
|
||||
nodes_json = state.get("topology_mapper", "nodes")
|
||||
host_count = 0
|
||||
if nodes_json:
|
||||
try:
|
||||
nodes = json.loads(nodes_json)
|
||||
host_count = len(nodes)
|
||||
except (json.JSONDecodeError, TypeError):
|
||||
pass
|
||||
intel_table.add_row("Discovered Hosts", str(host_count))
|
||||
|
||||
# Credential count
|
||||
cred_db_path = os.path.join(base_dir, "credentials.db")
|
||||
cred_count = 0
|
||||
cracked_count = 0
|
||||
if os.path.isfile(cred_db_path):
|
||||
try:
|
||||
conn = sqlite3.connect(cred_db_path)
|
||||
cred_count = conn.execute("SELECT COUNT(*) FROM credentials").fetchone()[0]
|
||||
cracked_count = conn.execute(
|
||||
"SELECT COUNT(*) FROM credentials WHERE crack_status='cracked'"
|
||||
).fetchone()[0]
|
||||
conn.close()
|
||||
except Exception:
|
||||
pass
|
||||
intel_table.add_row("Credentials (total/cracked)", f"{cred_count} / {cracked_count}")
|
||||
|
||||
# Active modules
|
||||
all_status = state.get_all_module_status()
|
||||
running_modules = [n for n, s in all_status.items() if s.get("status") == "running"]
|
||||
intel_table.add_row("Running Modules", str(len(running_modules)))
|
||||
if running_modules:
|
||||
intel_table.add_row(" Active", ", ".join(sorted(running_modules)[:10]))
|
||||
|
||||
# Security posture
|
||||
risk_level = state.get("security_posture", "risk_level")
|
||||
if risk_level:
|
||||
risk_color = {"low": "green", "medium": "yellow", "high": "red", "critical": "bold red"
|
||||
}.get(risk_level, "dim")
|
||||
intel_table.add_row("Security Risk Level", f"[{risk_color}]{risk_level}[/{risk_color}]")
|
||||
else:
|
||||
intel_table.add_row("Security Risk Level", "[dim]not assessed[/dim]")
|
||||
|
||||
has_edr = state.get("security_posture", "has_edr")
|
||||
if has_edr:
|
||||
intel_table.add_row("EDR Detected", "Yes" if json.loads(has_edr) else "No")
|
||||
|
||||
has_siem = state.get("security_posture", "has_siem")
|
||||
if has_siem:
|
||||
intel_table.add_row("SIEM Detected", "Yes" if json.loads(has_siem) else "No")
|
||||
|
||||
has_nac = state.get("security_posture", "has_nac")
|
||||
if has_nac:
|
||||
intel_table.add_row("NAC Detected", "Yes" if json.loads(has_nac) else "No")
|
||||
|
||||
has_honeypot = state.get("security_posture", "has_honeypot")
|
||||
if has_honeypot:
|
||||
intel_table.add_row("Honeypots Detected", "Yes" if json.loads(has_honeypot) else "No")
|
||||
|
||||
# VLANs
|
||||
vlans_json = state.get("topology_mapper", "vlans")
|
||||
if vlans_json:
|
||||
try:
|
||||
vlans = json.loads(vlans_json)
|
||||
intel_table.add_row("VLANs Discovered", str(len(vlans)))
|
||||
except (json.JSONDecodeError, TypeError):
|
||||
pass
|
||||
|
||||
console.print(intel_table)
|
||||
|
||||
|
||||
def _view_topology():
|
||||
"""Show network topology from topology_mapper state data."""
|
||||
state = StateManager()
|
||||
nodes_json = state.get("topology_mapper", "nodes")
|
||||
edges_json = state.get("topology_mapper", "edges")
|
||||
vlans_json = state.get("topology_mapper", "vlans")
|
||||
|
||||
nodes = {}
|
||||
edges = {}
|
||||
vlans = {}
|
||||
|
||||
if nodes_json:
|
||||
try:
|
||||
nodes = json.loads(nodes_json)
|
||||
except (json.JSONDecodeError, TypeError):
|
||||
pass
|
||||
|
||||
if edges_json:
|
||||
try:
|
||||
edges = json.loads(edges_json)
|
||||
except (json.JSONDecodeError, TypeError):
|
||||
pass
|
||||
|
||||
if vlans_json:
|
||||
try:
|
||||
vlans = json.loads(vlans_json)
|
||||
except (json.JSONDecodeError, TypeError):
|
||||
pass
|
||||
|
||||
if not nodes:
|
||||
console.print("\n[yellow]No topology data available yet.[/yellow]")
|
||||
console.print(" Topology is built from passive host discovery and network observation.")
|
||||
return
|
||||
|
||||
# Host table
|
||||
table = Table(title=f"Network Topology ({len(nodes)} hosts, {len(edges)} links, {len(vlans)} VLANs)",
|
||||
show_lines=True)
|
||||
table.add_column("IP", style="cyan", min_width=15)
|
||||
table.add_column("Hostname", min_width=18)
|
||||
table.add_column("MAC", min_width=17)
|
||||
table.add_column("OS", min_width=10)
|
||||
table.add_column("Role", min_width=12)
|
||||
table.add_column("VLAN", justify="right", min_width=5)
|
||||
table.add_column("Ports", min_width=15)
|
||||
|
||||
for ip in sorted(nodes.keys()):
|
||||
node = nodes[ip]
|
||||
hostname = node.get("hostname", "")
|
||||
mac = node.get("mac", "")
|
||||
os_family = node.get("os_family", "unknown")
|
||||
role = node.get("role", "unknown")
|
||||
vlan = str(node.get("vlan", "")) if node.get("vlan") is not None else "-"
|
||||
ports = node.get("open_ports", [])
|
||||
if isinstance(ports, list):
|
||||
ports_str = ", ".join(str(p) for p in sorted(ports)[:8])
|
||||
if len(ports) > 8:
|
||||
ports_str += f" (+{len(ports) - 8})"
|
||||
else:
|
||||
ports_str = str(ports)
|
||||
|
||||
table.add_row(ip, hostname or "-", mac or "-", os_family, role, vlan, ports_str or "-")
|
||||
|
||||
console.print(f"\n")
|
||||
console.print(table)
|
||||
|
||||
# VLAN summary
|
||||
if vlans:
|
||||
console.print("\n VLANs:")
|
||||
for vid, vinfo in sorted(vlans.items(), key=lambda x: str(x[0])):
|
||||
name = vinfo.get("name", "")
|
||||
subnet = vinfo.get("subnet", "")
|
||||
console.print(f" VLAN {vid}: {name} ({subnet})" if name
|
||||
else f" VLAN {vid}: {subnet}")
|
||||
|
||||
|
||||
def _view_timeline():
|
||||
"""Show recent events from the operator audit log."""
|
||||
base_dir = os.path.expanduser("~/.bigbrother")
|
||||
db_path = os.path.join(base_dir, "operator_audit.db")
|
||||
|
||||
if not os.path.isfile(db_path):
|
||||
console.print("\n[yellow]No audit log found.[/yellow]")
|
||||
console.print(f" Expected: {db_path}")
|
||||
console.print(" (Audit log is created when operator_audit module starts)")
|
||||
return
|
||||
|
||||
try:
|
||||
conn = sqlite3.connect(db_path)
|
||||
conn.row_factory = sqlite3.Row
|
||||
|
||||
total = conn.execute("SELECT COUNT(*) FROM audit_log").fetchone()[0]
|
||||
|
||||
rows = conn.execute(
|
||||
"""SELECT id, timestamp, action, operator, details
|
||||
FROM audit_log
|
||||
ORDER BY id DESC LIMIT 50"""
|
||||
).fetchall()
|
||||
|
||||
if not rows:
|
||||
console.print("\n[yellow]Audit log is empty.[/yellow]")
|
||||
conn.close()
|
||||
return
|
||||
|
||||
console.print(f"\n Total audit entries: [cyan]{total}[/cyan] (showing most recent 50)")
|
||||
|
||||
table = Table(title="Operator Timeline", show_lines=True)
|
||||
table.add_column("ID", justify="right", min_width=5)
|
||||
table.add_column("Time", min_width=19)
|
||||
table.add_column("Action", min_width=16)
|
||||
table.add_column("Operator", min_width=16)
|
||||
table.add_column("Details", min_width=30)
|
||||
|
||||
for row in rows:
|
||||
ts = datetime.fromtimestamp(row["timestamp"]).strftime("%Y-%m-%d %H:%M:%S")
|
||||
action = row["action"]
|
||||
|
||||
# Color code actions
|
||||
action_colors = {
|
||||
"module_start": "green",
|
||||
"module_stop": "yellow",
|
||||
"kill_switch": "bold red",
|
||||
"config_change": "magenta",
|
||||
"ssh_session": "cyan",
|
||||
"cli_command": "blue",
|
||||
"cred_export": "green",
|
||||
"data_exfil": "green",
|
||||
}
|
||||
color = action_colors.get(action, "dim")
|
||||
action_str = f"[{color}]{action}[/{color}]"
|
||||
|
||||
table.add_row(
|
||||
str(row["id"]),
|
||||
ts,
|
||||
action_str,
|
||||
row["operator"] or "-",
|
||||
(row["details"] or "-")[:60],
|
||||
)
|
||||
|
||||
console.print(table)
|
||||
conn.close()
|
||||
|
||||
except Exception as exc:
|
||||
console.print(f"\n[red]Error reading audit log:[/red] {exc}")
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
@@ -521,10 +897,10 @@ def interactive_menu():
|
||||
console.print(" [bold]2.[/bold] Stop all modules")
|
||||
console.print(" [bold]3.[/bold] Module status")
|
||||
console.print(" [bold]4.[/bold] List modules")
|
||||
console.print(" [bold]5.[/bold] Credentials [dim](Available in Phase 2)[/dim]")
|
||||
console.print(" [bold]6.[/bold] Intelligence [dim](Available in Phase 2)[/dim]")
|
||||
console.print(" [bold]7.[/bold] Topology [dim](Available in Phase 2)[/dim]")
|
||||
console.print(" [bold]8.[/bold] Timeline [dim](Available in Phase 2)[/dim]")
|
||||
console.print(" [bold]5.[/bold] View Credentials")
|
||||
console.print(" [bold]6.[/bold] View Intelligence")
|
||||
console.print(" [bold]7.[/bold] Network Topology")
|
||||
console.print(" [bold]8.[/bold] Timeline")
|
||||
console.print(" [bold]9.[/bold] Self-test")
|
||||
console.print(" [bold]C.[/bold] Configuration")
|
||||
console.print(" [bold]K.[/bold] Kill switch")
|
||||
@@ -546,9 +922,14 @@ def interactive_menu():
|
||||
elif choice == "4":
|
||||
ctx = click.Context(list_modules)
|
||||
ctx.invoke(list_modules)
|
||||
elif choice in ("5", "6", "7", "8"):
|
||||
labels = {"5": "Credentials", "6": "Intelligence", "7": "Topology", "8": "Timeline"}
|
||||
console.print(f"\n[yellow]{labels[choice]} viewer is available in Phase 2.[/yellow]")
|
||||
elif choice == "5":
|
||||
_view_credentials()
|
||||
elif choice == "6":
|
||||
_view_intel()
|
||||
elif choice == "7":
|
||||
_view_topology()
|
||||
elif choice == "8":
|
||||
_view_timeline()
|
||||
elif choice == "9":
|
||||
ctx = click.Context(selftest)
|
||||
ctx.invoke(selftest)
|
||||
|
||||
Reference in New Issue
Block a user