From 154a7efcc818111f4a8fbfa0e367a18f00787ee8 Mon Sep 17 00:00:00 2001 From: Zarithas Date: Fri, 5 Dec 2025 15:30:24 -0500 Subject: [PATCH] Bug fix for Revoke OTP resolved, no longer crashes when select all is chosen when there are no active sessions, Swapped Revoke OTP and Quiet Hosts locations on menus --- TUI/Screens/otprevokescreen.py | 61 ++++++++++++------------- TUI/Screens/quietagentworkflowscreen.py | 2 +- TUI/TUI.py | 8 ++-- 3 files changed, 35 insertions(+), 36 deletions(-) diff --git a/TUI/Screens/otprevokescreen.py b/TUI/Screens/otprevokescreen.py index 5dc142d..93c7a23 100644 --- a/TUI/Screens/otprevokescreen.py +++ b/TUI/Screens/otprevokescreen.py @@ -58,9 +58,14 @@ class OTPRevokeWidget(Static): } #button_container { height: auto; + width: 100%; padding: 1; align: center middle; } + #button_container Button { + min-width: 16; + margin: 0 1; + } #result_container { height: auto; max-height: 10; @@ -87,29 +92,10 @@ class OTPRevokeWidget(Static): # Action buttons with Horizontal(id="button_container"): - self.refresh_button = Button("🔄 Refresh", id="refresh_btn") - self.refresh_button.styles.width = "15%" - self.refresh_button.styles.margin = (1, 1, 1, 1) - yield self.refresh_button - - self.select_all_button = Button("☑️ Select All", id="select_all_btn") - self.select_all_button.styles.width = "15%" - self.select_all_button.styles.margin = (1, 1, 1, 1) - yield self.select_all_button - - self.select_none_button = Button( - "❌ Clear Selection", id="select_none_btn" - ) - self.select_none_button.styles.width = "20%" - self.select_none_button.styles.margin = (1, 1, 1, 1) - yield self.select_none_button - - self.revoke_button = Button( - "🛑 Revoke Selected", id="revoke_btn", variant="error" - ) - self.revoke_button.styles.width = "20%" - self.revoke_button.styles.margin = (1, 1, 1, 1) - yield self.revoke_button + yield Button("Refresh", id="refresh_btn") + yield Button("Select All", id="select_all_btn") + yield Button("Clear Selection", id="select_none_btn") + yield Button("Revoke Selected", id="revoke_btn", variant="error") # Results display with Vertical(id="result_container"): @@ -122,7 +108,7 @@ class OTPRevokeWidget(Static): # Configure sessions table self.sessions_table.clear() self.sessions_table.add_columns( - "☐", "OTP ID", "Hostname", "Status", "Purpose", "Granted" + "", "OTP ID", "Hostname", "Status", "Purpose", "Granted" ) # Enable row selection with checkbox column @@ -213,7 +199,11 @@ class OTPRevokeWidget(Static): elif btn.id == "select_all_btn": # Select all visible rows - if self._filtered_df is not None: + if ( + self._filtered_df is not None + and not self._filtered_df.empty + and "otpid" in self._filtered_df.columns + ): self._selected_otpids = set(str(x) for x in self._filtered_df["otpid"]) await self._refresh_table() @@ -235,7 +225,12 @@ class OTPRevokeWidget(Static): # Get the row index from the cursor row row_index = self.sessions_table.cursor_row - if self._filtered_df is not None and row_index < len(self._filtered_df): + if ( + self._filtered_df is not None + and not self._filtered_df.empty + and "otpid" in self._filtered_df.columns + and row_index < len(self._filtered_df) + ): # Get the OTP ID for this row otpid = str(self._filtered_df.iloc[row_index]["otpid"]) @@ -257,12 +252,12 @@ class OTPRevokeWidget(Static): async def _revoke_selected(self) -> None: """Revoke the selected OTP sessions.""" if not self._selected_otpids: - self.results_display.update("❌ No sessions selected for revocation") + self.results_display.update("No sessions selected for revocation") return api = getattr(self.app, "api", None) if not api: - self.results_display.update("❌ API not available") + self.results_display.update("API not available") return # Collect results @@ -303,13 +298,13 @@ class OTPRevokeWidget(Static): else "No response" ) results.append( - f"❌ Failed to revoke OTP {otpid} for {hostname}: {error_msg}" + f"Failed to revoke OTP {otpid} for {hostname}: {error_msg}" ) logger.error(f"Failed to revoke OTP {otpid}: {error_msg}") except Exception as e: failure_count += 1 - results.append(f"❌ Error revoking OTP {otpid}: {str(e)}") + results.append(f"Error revoking OTP {otpid}: {str(e)}") logger.exception(f"Exception revoking OTP {otpid}: {e}") # Update results display @@ -368,7 +363,11 @@ class OTPRevokeScreen(Screen): async def action_select_all(self) -> None: """Select all visible sessions.""" - if self.widget._filtered_df is not None: + if ( + self.widget._filtered_df is not None + and not self.widget._filtered_df.empty + and "otpid" in self.widget._filtered_df.columns + ): self.widget._selected_otpids = set( str(x) for x in self.widget._filtered_df["otpid"] ) diff --git a/TUI/Screens/quietagentworkflowscreen.py b/TUI/Screens/quietagentworkflowscreen.py index 1532b1d..bca4b31 100644 --- a/TUI/Screens/quietagentworkflowscreen.py +++ b/TUI/Screens/quietagentworkflowscreen.py @@ -411,7 +411,7 @@ class QuietAgentWorkflowScreen(Screen): # Delay the analysis start to ensure UI refresh completes first # This prevents Rust output from starting before the screen is cleared - self.set_timer(0.2, self._perform_analysis_worker) + self.set_timer(0.5, self._perform_analysis_worker) def _perform_analysis_worker(self) -> None: """Perform the actual agent activity analysis.""" diff --git a/TUI/TUI.py b/TUI/TUI.py index 1a8ce68..1a6f603 100644 --- a/TUI/TUI.py +++ b/TUI/TUI.py @@ -92,15 +92,15 @@ class MainMenuScreen(Screen): BUTTON_DEFS = { "agent_actions": [ ( - "🖥️ - Find, Move, or Generate OTP for Agents", + "🖥️ - Find agent, Move agent, or Generate One Time Pass", "move_agent_workflow_button", ), - ("🎫 - Review and appove OTP Activities", "otp_activities_button"), - ("🔕 - Find and Move Quiet Hosts to Enforcement", "find_quiet_button"), + ("🎫 - Review and approve OTP Activities", "otp_activities_button"), + ("🛑 - Revoke Active OTP Session", "otp_revoke_button"), ], "policy": [ ("⚖️ - Prepare Policy For Enforcement", "policy_prep_button"), - ("🛑 - Revoke OTPs", "otp_revoke_button"), + ("🔕 - Find and Move Quiet Hosts to Enforcement", "find_quiet_button"), ], }