RustImplementation #28

Merged
mysticmomba merged 37 commits from RustImplementation into master 2025-11-18 14:51:42 -05:00
2 changed files with 73 additions and 49 deletions
Showing only changes of commit ed07c79b74 - Show all commits
+4 -5
View File
@@ -232,9 +232,8 @@ class OTPGenerator(Widget):
api = self.app.api api = self.app.api
output_lines = [ output_lines = [
"=" * 60, "Requested OTP Codes:",
"OTP GENERATION RESULTS", "=" * 25,
"=" * 60,
] ]
otp_dict = {} otp_dict = {}
@@ -250,9 +249,9 @@ class OTPGenerator(Widget):
) )
for hostname, otp_code in otp_dict.items(): for hostname, otp_code in otp_dict.items():
output_lines.append(f"{hostname:30} | {otp_code}") output_lines.append(f"{hostname} | {otp_code}")
output_lines.append("=" * 60) output_lines.append("=" * 25)
result_text = "\n".join(output_lines) result_text = "\n".join(output_lines)
self._show_result(result_text) self._show_result(result_text)
+69 -44
View File
@@ -1,3 +1,4 @@
from collections import defaultdict
import logging import logging
from rich.text import Text from rich.text import Text
@@ -17,14 +18,13 @@ class PolicyTreeWidget(Widget):
self.policies = policies self.policies = policies
self.devices = devices self.devices = devices
self.last_highlighted_node = None self.last_highlighted_node = None
self.leaf_counts = defaultdict(int)
def compose(self): def compose(self):
# Left: Policy Tree policy_tree = Tree("", id="policy_tree") # Label set in on_mount
policy_tree = Tree("Policies", id="policy_tree")
policy_tree.styles.width = "2fr" policy_tree.styles.width = "2fr"
policy_tree.styles.height = "100%" policy_tree.styles.height = "100%"
# Right: Search + Details
label = Static("Device Search:") label = Static("Device Search:")
search_box = Input( search_box = Input(
placeholder="Search policies or devices...", id="tree_search" placeholder="Search policies or devices...", id="tree_search"
@@ -40,44 +40,87 @@ class PolicyTreeWidget(Widget):
yield details_pane yield details_pane
def on_mount(self) -> None: def on_mount(self) -> None:
"""Build the tree after mounting.""" self._precompute_leaf_counts()
# Update root label with total leaf count
total_leaves = sum(
self.leaf_counts.get(policy.groupid, 0)
for policy in self.policies
if policy.parent == "global-policy-settings"
)
policy_tree = self.query_one("#policy_tree", Tree)
policy_tree.root.set_label(f"Agents in Policies: ({total_leaves})")
self._build_tree() self._build_tree()
def _build_tree(self) -> None: def _precompute_leaf_counts(self):
"""Build the policy tree structure.""" """Precompute leaf counts for each policy group."""
device_counts = defaultdict(int)
for device in self.devices:
device_counts[device.groupid] += 1
child_map = defaultdict(list)
for policy in self.policies:
child_map[policy.parent].append(policy.groupid)
def count_leaves(groupid):
count = device_counts[groupid]
for child_id in child_map.get(groupid, []):
count += count_leaves(child_id)
self.leaf_counts[groupid] = count
return count
for policy in self.policies:
if policy.parent == "global-policy-settings":
count_leaves(policy.groupid)
def _build_tree(self):
policy_tree = self.query_one("#policy_tree", Tree) policy_tree = self.query_one("#policy_tree", Tree)
node_map = {} node_map = {}
# Top-level policies # Sort top-level policies
for policy in self.policies: top_policies = [
if policy.parent == "global-policy-settings": p for p in self.policies if p.parent == "global-policy-settings"
node = policy_tree.root.add(label=policy.name, data=policy) ]
node_map[policy.groupid] = node top_policies.sort(
key=lambda p: self.leaf_counts.get(p.groupid, 0), reverse=True
)
# Child policies for policy in top_policies:
for policy in self.policies: label = f"{policy.name} ({self.leaf_counts.get(policy.groupid, 0)})"
parent_id = policy.parent node = policy_tree.root.add(label=label, data=policy)
if parent_id in node_map: node_map[policy.groupid] = node
parent_node = node_map[parent_id]
node = parent_node.add(label=policy.name, data=policy)
node_map[policy.groupid] = node
# Devices under policies # Sort and add child policies
children_by_parent = defaultdict(list)
for policy in self.policies:
if policy.parent != "global-policy-settings":
children_by_parent[policy.parent].append(policy)
for parent_id, children in children_by_parent.items():
children.sort(
key=lambda p: self.leaf_counts.get(p.groupid, 0), reverse=True
)
parent_node = node_map.get(parent_id)
if parent_node:
for policy in children:
label = f"{policy.name} ({self.leaf_counts.get(policy.groupid, 0)})"
node = parent_node.add(label=label, data=policy)
node_map[policy.groupid] = node
# Add devices (leaf nodes)
for device in self.devices: for device in self.devices:
group_id = device.groupid group_id = device.groupid
if group_id in node_map: parent_node = node_map.get(group_id)
parent_node = node_map[group_id] if parent_node:
label = device.hostname parent_node.add(label=device.hostname, data=device)
parent_node.add(label=label, data=device)
def _collect_tree_nodes(self, node, all_nodes): def _collect_tree_nodes(self, node, all_nodes):
"""Helper to recursively collect all nodes from a tree."""
all_nodes.append(node) all_nodes.append(node)
for child in node.children: for child in node.children:
self._collect_tree_nodes(child, all_nodes) self._collect_tree_nodes(child, all_nodes)
def _remove_match_selector(self): def _remove_match_selector(self):
"""Safely remove match selector widgets."""
try: try:
existing = self.query("#match_selector") existing = self.query("#match_selector")
for widget in existing: for widget in existing:
@@ -87,20 +130,16 @@ class PolicyTreeWidget(Widget):
logger.debug("Failed to remove match_selector: %s", exc) logger.debug("Failed to remove match_selector: %s", exc)
def on_tree_node_selected(self, message: Tree.NodeSelected) -> None: def on_tree_node_selected(self, message: Tree.NodeSelected) -> None:
"""Handle tree node selection."""
node = message.node node = message.node
data = node.data data = node.data
details_pane = self.query_one("#details_pane", Static) details_pane = self.query_one("#details_pane", Static)
# Reset previous highlight
if self.last_highlighted_node is not None: if self.last_highlighted_node is not None:
original_label = str(self.last_highlighted_node.label).strip() original_label = str(self.last_highlighted_node.label).strip()
# Remove any styling
if isinstance(self.last_highlighted_node.label, Text): if isinstance(self.last_highlighted_node.label, Text):
original_label = self.last_highlighted_node.label.plain original_label = self.last_highlighted_node.label.plain
self.last_highlighted_node.set_label(original_label) self.last_highlighted_node.set_label(original_label)
# Apply highlight to current node
label_text = str(node.label).strip() label_text = str(node.label).strip()
if isinstance(node.label, Text): if isinstance(node.label, Text):
label_text = node.label.plain label_text = node.label.plain
@@ -108,9 +147,7 @@ class PolicyTreeWidget(Widget):
node.set_label(highlighted_label) node.set_label(highlighted_label)
self.last_highlighted_node = node self.last_highlighted_node = node
# Update details pane
if data: if data:
# Work with dataclass objects using __dict__
details = "\n".join( details = "\n".join(
f"{key}: {value}" for key, value in data.__dict__.items() f"{key}: {value}" for key, value in data.__dict__.items()
) )
@@ -118,12 +155,9 @@ class PolicyTreeWidget(Widget):
details = f"Selected: {node.label}" details = f"Selected: {node.label}"
details_pane.update(details) details_pane.update(details)
# Stop event from bubbling
message.stop() message.stop()
def on_input_submitted(self, message: Input.Submitted) -> None: def on_input_submitted(self, message: Input.Submitted) -> None:
"""Handle search input submission."""
# Remove existing match selector FIRST
self._remove_match_selector() self._remove_match_selector()
query = message.value.strip().lower() query = message.value.strip().lower()
@@ -138,7 +172,6 @@ class PolicyTreeWidget(Widget):
label_text = str(node.label).lower() label_text = str(node.label).lower()
label_to_node[label_text] = node label_to_node[label_text] = node
if node.data: if node.data:
# Use __dict__ for dataclass objects
data_dict = ( data_dict = (
node.data.__dict__ if hasattr(node.data, "__dict__") else node.data node.data.__dict__ if hasattr(node.data, "__dict__") else node.data
) )
@@ -146,18 +179,15 @@ class PolicyTreeWidget(Widget):
if isinstance(value, str): if isinstance(value, str):
label_to_node[value.lower()] = node label_to_node[value.lower()] = node
# Wildcard-style substring match
matches = sorted([label for label in label_to_node if query in label]) matches = sorted([label for label in label_to_node if query in label])
if matches: if matches:
# Try to reuse existing match_selector or create new one
try: try:
option_list = self.query_one("#match_selector", OptionList) option_list = self.query_one("#match_selector", OptionList)
option_list.clear_options() option_list.clear_options()
option_list.display = True # Ensure it's visible option_list.display = True
except: except:
option_list = OptionList(id="match_selector") option_list = OptionList(id="match_selector")
# Mount to the details pane's parent (the Vertical container)
details_pane.parent.mount(option_list) details_pane.parent.mount(option_list)
for label in matches: for label in matches:
@@ -165,17 +195,14 @@ class PolicyTreeWidget(Widget):
details_pane.update(f"Found {len(matches)} matches. Select one below.") details_pane.update(f"Found {len(matches)} matches. Select one below.")
else: else:
# Hide or remove the match_selector when no matches
self._remove_match_selector() self._remove_match_selector()
details_pane.update("No matches found.") details_pane.update("No matches found.")
def on_option_list_option_selected(self, event: OptionList.OptionSelected) -> None: def on_option_list_option_selected(self, event: OptionList.OptionSelected) -> None:
"""Handle selection from search results."""
selected_id = event.option.id.replace("match_", "") selected_id = event.option.id.replace("match_", "")
tree = self.query_one("#policy_tree", Tree) tree = self.query_one("#policy_tree", Tree)
details_pane = self.query_one("#details_pane", Static) details_pane = self.query_one("#details_pane", Static)
# Find the node
all_nodes = [] all_nodes = []
self._collect_tree_nodes(tree.root, all_nodes) self._collect_tree_nodes(tree.root, all_nodes)
@@ -183,7 +210,6 @@ class PolicyTreeWidget(Widget):
match_node = label_to_node.get(selected_id.lower()) match_node = label_to_node.get(selected_id.lower())
if match_node: if match_node:
# Expand path (original working logic)
node = match_node node = match_node
path = [] path = []
while node: while node:
@@ -198,7 +224,6 @@ class PolicyTreeWidget(Widget):
match_node.set_label(Text(str(match_node.label), style="reverse bold")) match_node.set_label(Text(str(match_node.label), style="reverse bold"))
details_pane.update(f"Selected: {match_node.label}") details_pane.update(f"Selected: {match_node.label}")
# Remove the match_selector after selection
try: try:
option_list = self.query_one("#match_selector", OptionList) option_list = self.query_one("#match_selector", OptionList)
option_list.remove() option_list.remove()