Skip to content

Commit b0db819

Browse files
authored
Merge pull request #57 from pirate/claude/issue-56-20251206-0018
Make Active Monitors list clickable to toggle individual monitors
2 parents 83c3aed + 4d86ba2 commit b0db819

File tree

1 file changed

+87
-51
lines changed

1 file changed

+87
-51
lines changed

security-growler.30s.py

Lines changed: 87 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -55,22 +55,60 @@
5555
MAX_EVENTS = 50
5656
MAX_LOG_LINES = 1000
5757

58-
# Environment variable configuration (set by xbar)
59-
SHOW_NOTIFICATIONS = os.environ.get("SHOW_NOTIFICATIONS", "true").lower() == "true"
60-
MONITOR_SSH = os.environ.get("MONITOR_SSH", "true").lower() == "true"
61-
MONITOR_SUDO = os.environ.get("MONITOR_SUDO", "true").lower() == "true"
62-
MONITOR_PORTSCAN = os.environ.get("MONITOR_PORTSCAN", "true").lower() == "true"
63-
MONITOR_VNC = os.environ.get("MONITOR_VNC", "true").lower() == "true"
64-
MONITOR_PORTS = os.environ.get("MONITOR_PORTS", "true").lower() == "true"
58+
# Monitor toggle management
59+
def get_monitor_overrides() -> Dict[str, bool]:
60+
"""Load monitor toggle overrides from state file."""
61+
STATE_DIR.mkdir(parents=True, exist_ok=True)
62+
overrides_file = STATE_DIR / "monitor_overrides.json"
63+
if overrides_file.exists():
64+
try:
65+
with open(overrides_file, "r") as f:
66+
return json.load(f)
67+
except (json.JSONDecodeError, IOError):
68+
pass
69+
return {}
70+
71+
def toggle_monitor(monitor_name: str) -> None:
72+
"""Toggle a monitor on/off by storing override in state."""
73+
STATE_DIR.mkdir(parents=True, exist_ok=True)
74+
overrides_file = STATE_DIR / "monitor_overrides.json"
75+
76+
# Load current overrides
77+
overrides = get_monitor_overrides()
78+
79+
# Get current effective state (env var default, then override)
80+
env_default = os.environ.get(monitor_name, "true").lower() == "true"
81+
current_state = overrides.get(monitor_name, env_default)
82+
83+
# Toggle it
84+
overrides[monitor_name] = not current_state
85+
86+
# Save
87+
with open(overrides_file, "w") as f:
88+
json.dump(overrides, f, indent=2)
89+
90+
def is_monitor_enabled(monitor_name: str, env_default: str = "true") -> bool:
91+
"""Check if a monitor is enabled, considering both env vars and overrides."""
92+
env_value = os.environ.get(monitor_name, env_default).lower() == "true"
93+
overrides = get_monitor_overrides()
94+
return overrides.get(monitor_name, env_value)
95+
96+
# Environment variable configuration (set by xbar, can be overridden by user toggles)
97+
SHOW_NOTIFICATIONS = is_monitor_enabled("SHOW_NOTIFICATIONS")
98+
MONITOR_SSH = is_monitor_enabled("MONITOR_SSH")
99+
MONITOR_SUDO = is_monitor_enabled("MONITOR_SUDO")
100+
MONITOR_PORTSCAN = is_monitor_enabled("MONITOR_PORTSCAN")
101+
MONITOR_VNC = is_monitor_enabled("MONITOR_VNC")
102+
MONITOR_PORTS = is_monitor_enabled("MONITOR_PORTS")
65103
MONITORED_PORTS = os.environ.get("MONITORED_PORTS", "21,445,548,3306,3689,5432")
66-
MONITOR_LISTENING = os.environ.get("MONITOR_LISTENING", "true").lower() == "true"
67-
MONITOR_DOTENV = os.environ.get("MONITOR_DOTENV", "true").lower() == "true"
68-
MONITOR_DANGEROUS_COMMANDS = os.environ.get("MONITOR_DANGEROUS_COMMANDS", "true").lower() == "true"
69-
MONITOR_DNS = os.environ.get("MONITOR_DNS", "true").lower() == "true"
70-
MONITOR_PUBLIC_IP = os.environ.get("MONITOR_PUBLIC_IP", "true").lower() == "true"
71-
MONITOR_LOCAL_IP = os.environ.get("MONITOR_LOCAL_IP", "true").lower() == "true"
72-
MONITOR_MDM = os.environ.get("MONITOR_MDM", "true").lower() == "true"
73-
MONITOR_ARP_SPOOF = os.environ.get("MONITOR_ARP_SPOOF", "true").lower() == "true"
104+
MONITOR_LISTENING = is_monitor_enabled("MONITOR_LISTENING")
105+
MONITOR_DOTENV = is_monitor_enabled("MONITOR_DOTENV")
106+
MONITOR_DANGEROUS_COMMANDS = is_monitor_enabled("MONITOR_DANGEROUS_COMMANDS")
107+
MONITOR_DNS = is_monitor_enabled("MONITOR_DNS")
108+
MONITOR_PUBLIC_IP = is_monitor_enabled("MONITOR_PUBLIC_IP")
109+
MONITOR_LOCAL_IP = is_monitor_enabled("MONITOR_LOCAL_IP")
110+
MONITOR_MDM = is_monitor_enabled("MONITOR_MDM")
111+
MONITOR_ARP_SPOOF = is_monitor_enabled("MONITOR_ARP_SPOOF")
74112

75113
# Listening port range to monitor
76114
LISTENING_PORT_MIN = 21
@@ -1575,43 +1613,34 @@ def format_xbar_output(state: Dict[str, Any], new_events: List[Tuple[str, str, s
15751613
print(f"Last check: {datetime.now().strftime('%H:%M:%S')} | color=#666666 size=11")
15761614
print("---")
15771615

1578-
# Active monitors
1616+
# Active monitors - clickable to toggle
15791617
print("Active Monitors | color=#333333")
1580-
monitors = []
1581-
if MONITOR_SSH:
1582-
monitors.append("SSH")
1583-
if MONITOR_SUDO:
1584-
monitors.append("Sudo")
1585-
if MONITOR_PORTSCAN:
1586-
monitors.append("Port Scans")
1587-
if MONITOR_VNC:
1588-
monitors.append("VNC")
1589-
if MONITOR_PORTS:
1590-
port_list = ", ".join(str(p) for p in PORTS_TO_MONITOR[:3])
1591-
if len(PORTS_TO_MONITOR) > 3:
1592-
port_list += f" (+{len(PORTS_TO_MONITOR) - 3})"
1593-
monitors.append(f"Ports: {port_list}")
1594-
if MONITOR_LISTENING:
1595-
monitors.append(f"Listening ({LISTENING_PORT_MIN}-{LISTENING_PORT_MAX})")
1596-
if MONITOR_DOTENV:
1597-
monitors.append(".env Files")
1598-
if MONITOR_DANGEROUS_COMMANDS:
1599-
monitors.append(f"Commands: {', '.join(DANGEROUS_COMMANDS)}")
1600-
if MONITOR_DNS:
1601-
monitors.append("DNS Resolvers")
1602-
if MONITOR_PUBLIC_IP:
1603-
public_ip = state.get("known_public_ip", "?")
1604-
monitors.append(f"Public IP ({public_ip})")
1605-
if MONITOR_LOCAL_IP:
1606-
monitors.append("Local IPs")
1607-
if MONITOR_MDM:
1608-
monitors.append("Kandji/MDM")
1609-
if MONITOR_ARP_SPOOF:
1610-
gateway_mac = state.get("known_gateway_mac", "?")[:8] if state.get("known_gateway_mac") else "?"
1611-
monitors.append(f"ARP Spoofing (GW: {gateway_mac}...)")
1612-
1613-
for m in monitors:
1614-
print(f"--✓ {m} | color=#228B22 size=12")
1618+
script_path = os.path.abspath(__file__)
1619+
1620+
# Define all monitors with their display names and environment variable names
1621+
monitor_configs = [
1622+
("MONITOR_SSH", MONITOR_SSH, "SSH"),
1623+
("MONITOR_SUDO", MONITOR_SUDO, "Sudo"),
1624+
("MONITOR_PORTSCAN", MONITOR_PORTSCAN, "Port Scans"),
1625+
("MONITOR_VNC", MONITOR_VNC, "VNC"),
1626+
("MONITOR_PORTS", MONITOR_PORTS, f"Ports: {', '.join(str(p) for p in PORTS_TO_MONITOR[:3])}{f' (+{len(PORTS_TO_MONITOR) - 3})' if len(PORTS_TO_MONITOR) > 3 else ''}"),
1627+
("MONITOR_LISTENING", MONITOR_LISTENING, f"Listening ({LISTENING_PORT_MIN}-{LISTENING_PORT_MAX})"),
1628+
("MONITOR_DOTENV", MONITOR_DOTENV, ".env Files"),
1629+
("MONITOR_DANGEROUS_COMMANDS", MONITOR_DANGEROUS_COMMANDS, f"Commands: {', '.join(DANGEROUS_COMMANDS)}"),
1630+
("MONITOR_DNS", MONITOR_DNS, "DNS Resolvers"),
1631+
("MONITOR_PUBLIC_IP", MONITOR_PUBLIC_IP, f"Public IP ({state.get('known_public_ip', '?')})"),
1632+
("MONITOR_LOCAL_IP", MONITOR_LOCAL_IP, "Local IPs"),
1633+
("MONITOR_MDM", MONITOR_MDM, "Kandji/MDM"),
1634+
("MONITOR_ARP_SPOOF", MONITOR_ARP_SPOOF, f"ARP Spoofing (GW: {state.get('known_gateway_mac', '?')[:8] if state.get('known_gateway_mac') else '?'}...)"),
1635+
]
1636+
1637+
for var_name, is_enabled, display_name in monitor_configs:
1638+
if is_enabled:
1639+
# Show enabled monitors with checkmark - clickable to disable
1640+
print(f"--✓ {display_name} | color=#228B22 size=12 bash={script_path} param1=toggle param2={var_name} terminal=false refresh=true")
1641+
else:
1642+
# Show disabled monitors with gray text - clickable to enable
1643+
print(f"--○ {display_name} | color=#999999 size=12 bash={script_path} param1=toggle param2={var_name} terminal=false refresh=true")
16151644

16161645
print("---")
16171646

@@ -1669,6 +1698,13 @@ def format_xbar_output(state: Dict[str, Any], new_events: List[Tuple[str, str, s
16691698

16701699
def main():
16711700
"""Main entry point for the xbar plugin."""
1701+
# Check if we're being called to toggle a monitor
1702+
if len(sys.argv) > 1 and sys.argv[1] == "toggle":
1703+
if len(sys.argv) > 2:
1704+
monitor_name = sys.argv[2]
1705+
toggle_monitor(monitor_name)
1706+
sys.exit(0)
1707+
16721708
try:
16731709
# Load state
16741710
state = load_state()

0 commit comments

Comments
 (0)