Device search is working. Working on The multidevice seach, WIP

This commit is contained in:
2025-11-06 17:36:45 -05:00
parent c062532dd6
commit 26199c72cb
5 changed files with 423 additions and 105 deletions
+43 -102
View File
@@ -5,7 +5,7 @@ import sys
import dotenv
from dotenv import set_key
from textual.app import App, ComposeResult
from textual.containers import Horizontal, Vertical
from textual.containers import Vertical
from textual.reactive import reactive
from textual.screen import Screen
from textual.widgets import (
@@ -16,7 +16,6 @@ from textual.widgets import (
Static,
Tab,
Tabs,
Tree,
)
from flows.otp import otp_activities_by_agent, otp_generate, otp_revoke
@@ -28,6 +27,9 @@ from services.policyhandler import confirmUpdateAfromE
from utils.configmanager import load_env
from utils.setup import get_base_directory, load_user_config
from utils.utils import open_directory
from widgets.multiagentselector import MultiAgentSelector
from widgets.policytreewidget import PolicyTreeWidget
from widgets.themeselector import ThemeSelector
dotenv.load_dotenv()
@@ -115,21 +117,6 @@ class MainMenuScreen(Screen):
],
}
# textual themes to expose
THEME_BUTTONS = [
("textual-dark", "textual-dark"),
("textual-light", "textual-light"),
("nord", "nord"),
("gruvbox", "gruvbox"),
("catppuccin-mocha", "catppuccin-mocha"),
("dracula", "dracula"),
("tokyo-night", "tokyo-night"),
("monokai", "monokai"),
("flexoki", "flexoki"),
("catppuccin-latte", "catppuccin-latte"),
("solarized-light", "solarized-light"),
]
def __init__(self) -> None:
super().__init__()
self.extras = load_env("EXTRAS")
@@ -143,7 +130,7 @@ class MainMenuScreen(Screen):
buttons = []
for label, btn_id in defs:
btn = Button(label, id=btn_id)
btn.styles.width = "100%" # Make button span full width of parent
btn.styles.width = "100%"
buttons.append(btn)
return Vertical(*buttons)
@@ -157,6 +144,7 @@ class MainMenuScreen(Screen):
Tab("OTP", id="otp"),
Tab("Directory", id="dir"),
Tab("Settings", id="settings"),
Tab("Multi Select", id="multi_select"),
]
if self.extras == "POLICYPREP":
@@ -216,88 +204,36 @@ class MainMenuScreen(Screen):
elif tab_id == "dir":
content.mount(DirectoryTree(self.working_dir, id="dir_tree"))
elif tab_id == "p_tree":
layout = Horizontal()
content.mount(layout)
# Left: Policy Tree
policy_tree = Tree("Policies", id="policy_tree")
policy_tree.styles.width = "2fr"
layout.mount(policy_tree)
# Right: Details pane
details_pane = Static(
"Select a policy or device to view details", id="details-pane"
)
details_pane.styles.width = "3fr"
layout.mount(details_pane)
# Build the tree
node_map = {}
# Top-level policies
for _, policy in self.app.policies.iterrows():
if policy["parent"] == "global-policy-settings":
node = policy_tree.root.add(
label=policy["name"], data=policy.to_dict()
)
node_map[policy["groupid"]] = node
# Child policies
for _, policy in self.app.policies.iterrows():
parent_id = policy["parent"]
if parent_id in node_map:
parent_node = node_map[parent_id]
node = parent_node.add(label=policy["name"], data=policy.to_dict())
node_map[policy["groupid"]] = node
# Devices under policies
for _, device in self.app.devices.iterrows():
group_id = device["groupid"]
if group_id in node_map:
parent_node = node_map[group_id]
label = device["hostname"] # Keep tree clean
parent_node.add(label=label, data=device.to_dict())
content.mount(PolicyTreeWidget(self.app.policies, self.app.devices))
elif tab_id == "settings":
# Create and mount the horizontal container
horizontal_container = Horizontal(id="settings_grid")
horizontal_container.styles.layout = "horizontal"
horizontal_container.styles.height = "auto"
content.mount(Static("Theme Options"))
content.mount(horizontal_container) # Mount the horizontal container first
# Create 3 columns
for i in range(1):
column = Vertical()
column.styles.width = "1fr"
column.styles.height = "auto"
horizontal_container.mount(column) # Mount each column
for j in range(i, len(self.THEME_BUTTONS), 1):
if j < len(self.THEME_BUTTONS):
label, btn_id = self.THEME_BUTTONS[j]
button = Button(label, id=f"set_theme_{btn_id}", compact=True)
# button.styles.width = "100%"
column.mount(button) # Mount each button
content.mount(ThemeSelector())
elif tab_id == "multi_select":
content.mount(MultiAgentSelector(self.app.devices.to_dict("records")))
else:
content.mount(Static(f"Unknown tab: {tab_id}"))
def on_tabs_tab_activated(self, event: Tabs.TabActivated) -> None:
self.switch_tab(event.tab.id)
def on_tree_node_selected(self, message: Tree.NodeSelected) -> None:
node = message.node
data = node.data
def on_multi_agent_selector_agents_selected(
self, message: MultiAgentSelector.AgentsSelected
) -> None:
"""Handle selected agents from MultiAgentSelector."""
global _PENDING_JOB
selected_agents = message.selected_agents
logger.info("Selected agents: %s", selected_agents)
# TODO: Implement actual handling of selected agents
_PENDING_JOB = ("multi_agent_action", selected_agents)
self.app.exit()
details_pane = self.query_one("#details-pane", Static)
if data:
details = "\n".join(f"{key}: {value}" for key, value in data.items())
else:
details = f"Selected: {node.label}"
details_pane.update(details)
def on_theme_selector_theme_selected(
self, message: ThemeSelector.ThemeSelected
) -> None:
"""Handle theme selection from ThemeSelector."""
global _PENDING_JOB
_persist_user_theme(message.theme_name)
_PENDING_JOB = ("restart",)
self.app.exit()
def on_directory_tree_file_selected(
self, event: DirectoryTree.FileSelected
@@ -315,14 +251,6 @@ class MainMenuScreen(Screen):
button_id = event.button.id
logger.debug("Button pressed: %s", button_id)
# theme selection → user config
if button_id.startswith("set_theme_"):
theme_name = button_id.replace("set_theme_", "")
_persist_user_theme(theme_name)
_PENDING_JOB = ("restart",)
self.app.exit()
return
match button_id:
case "find_device_button":
_PENDING_JOB = ("legacy", findAgents, (self.app.api, False), {})
@@ -383,8 +311,15 @@ class Loxide(App):
if not os.path.isdir(wd):
wd = os.getcwd()
self.working_dir = wd
self.policies = api.policy_find_all()
self.devices = api.agent_find_all()
# Add error handling for API calls
try:
self.policies = api.policy_find_all()
self.devices = api.agent_find_all()
except Exception as exc:
logger.error("Failed to load policies/devices: %s", exc)
self.policies = None
self.devices = None
def on_mount(self) -> None:
self.theme = self._textual_theme
@@ -473,6 +408,12 @@ def run_Loxide(api: AirlockAPIWrapper) -> None:
# just loop again; fresh .env was already loaded at the top
continue
if job[0] == "multi_agent_action":
# Handle multi-agent selection
# TODO: Implement actual multi-agent action handling
logger.info("Multi-agent action with selected agents: %s", job[1])
continue
break