Device search is working. Working on The multidevice seach, WIP
This commit is contained in:
@@ -0,0 +1,133 @@
|
||||
import difflib
|
||||
|
||||
from textual.containers import Horizontal
|
||||
from textual.css.query import NoMatches
|
||||
from textual.message import Message
|
||||
from textual.widget import Widget
|
||||
from textual.widgets import Button, SelectionList, Static, Switch, TextArea
|
||||
|
||||
|
||||
class MultiAgentSelector(Widget):
|
||||
class AgentsSelected(Message):
|
||||
def __init__(self, selected_agents):
|
||||
super().__init__()
|
||||
self.selected_agents = selected_agents
|
||||
|
||||
def __init__(self, all_agents: list[dict]):
|
||||
super().__init__()
|
||||
self.all_agents = all_agents
|
||||
self._match_type = "fuzzy"
|
||||
|
||||
@property
|
||||
def match_type(self):
|
||||
return self._match_type
|
||||
|
||||
@match_type.setter
|
||||
def match_type(self, value):
|
||||
self._match_type = value
|
||||
|
||||
def compose(self):
|
||||
yield Static("🔍 Multi-Agent Selector", id="selector_title")
|
||||
text_area = TextArea(
|
||||
id="device_input",
|
||||
placeholder="Paste device names here (one per line)",
|
||||
)
|
||||
text_area.styles.height = 10
|
||||
text_area.styles.overflow_y = "auto"
|
||||
yield text_area
|
||||
|
||||
with Horizontal() as switch_container:
|
||||
switch_container.styles.height = "auto"
|
||||
switch_container.styles.align = ("left", "middle")
|
||||
switch_label = Static("Match Type: Fuzzy", id="match_switch_label")
|
||||
switch_label.styles.width = "auto"
|
||||
switch_label.styles.padding = (0, 1)
|
||||
yield switch_label
|
||||
|
||||
switch = Switch(value=False, id="match_switch")
|
||||
switch.styles.width = "auto"
|
||||
yield switch
|
||||
|
||||
yield Button("Search", id="search_button")
|
||||
|
||||
yield SelectionList(id="match_results")
|
||||
yield Static(id="unmatched_label")
|
||||
|
||||
yield Horizontal(
|
||||
Button("Select All", id="select_all"),
|
||||
Button("Select None", id="select_none"),
|
||||
)
|
||||
|
||||
yield Button("Continue with Selected", id="submit_selection", variant="primary")
|
||||
|
||||
def on_switch_changed(self, event: Switch.Changed):
|
||||
self.match_type = "exact" if event.value else "fuzzy"
|
||||
self.query_one("#match_switch_label", Static).update(
|
||||
f"Match Type: {self.match_type.capitalize()}"
|
||||
)
|
||||
|
||||
def on_button_pressed(self, event: Button.Pressed):
|
||||
btn_id = event.button.id # can be None for internal buttons
|
||||
|
||||
# Only query when needed
|
||||
try:
|
||||
match_list = self.query_one("#match_results", SelectionList)
|
||||
except NoMatches:
|
||||
# UI not mounted yet or id changed—just ignore gracefully
|
||||
return
|
||||
|
||||
if btn_id == "select_all":
|
||||
match_list.select_all()
|
||||
event.stop()
|
||||
elif btn_id == "select_none":
|
||||
match_list.deselect_all()
|
||||
event.stop()
|
||||
elif btn_id == "submit_selection":
|
||||
selected = list(match_list.selected)
|
||||
self.post_message(self.AgentsSelected(selected))
|
||||
event.stop()
|
||||
elif btn_id == "search_button":
|
||||
self.update_matches()
|
||||
event.stop()
|
||||
|
||||
def update_matches(self):
|
||||
raw_input = self.query_one("#device_input", TextArea).text.strip()
|
||||
device_names = [line.strip() for line in raw_input.split("\n") if line.strip()]
|
||||
matched, unmatched = self.match_devices(device_names)
|
||||
match_list = self.query_one("#match_results", SelectionList)
|
||||
match_list.clear_options()
|
||||
for name in matched:
|
||||
match_list.add_option((name, name))
|
||||
unmatched_label = self.query_one("#unmatched_label", Static)
|
||||
if unmatched:
|
||||
unmatched_label.update(f"⚠️ No matches for: {', '.join(unmatched)}")
|
||||
else:
|
||||
unmatched_label.update("")
|
||||
|
||||
def match_devices(self, device_names: list[str]) -> tuple[list[str], list[str]]:
|
||||
if not self.all_agents or not device_names:
|
||||
return [], device_names
|
||||
agent_names = [agent["hostname"] for agent in self.all_agents]
|
||||
matched = set()
|
||||
unmatched = []
|
||||
for name in device_names:
|
||||
if self.match_type == "exact":
|
||||
# Case-insensitive exact match
|
||||
name_lower = name.lower()
|
||||
exact_match = None
|
||||
for agent_name in agent_names:
|
||||
if agent_name.lower() == name_lower:
|
||||
exact_match = agent_name
|
||||
break
|
||||
if exact_match:
|
||||
matched.add(exact_match)
|
||||
else:
|
||||
unmatched.append(name)
|
||||
else:
|
||||
# Fuzzy match
|
||||
matches = difflib.get_close_matches(name, agent_names, n=5, cutoff=0.5)
|
||||
if matches:
|
||||
matched.update(matches)
|
||||
else:
|
||||
unmatched.append(name)
|
||||
return sorted(matched), unmatched
|
||||
Reference in New Issue
Block a user