Added OTP Activity Review WIP

This commit is contained in:
2025-11-13 11:45:13 -05:00
parent 5cb079dad7
commit f0e77db414
11 changed files with 787 additions and 74 deletions
+41 -15
View File
@@ -1,11 +1,13 @@
import logging
import os
import sys
from typing import Optional
import dotenv
from dotenv import set_key
from textual.app import App, ComposeResult
from textual.containers import Vertical
from textual.message import Message
from textual.reactive import reactive
from textual.screen import Screen
from textual.widgets import (
@@ -18,25 +20,26 @@ from textual.widgets import (
Tabs,
)
from flows.otp import otp_activities_by_agent, otp_revoke
from flows.otp import otp_revoke
from flows.prepPolicy import menu_policy_enforce
from flows.quietAgent import findQuietAgents
from models.agent import Agent
from models.policy import Policy
from screens.moveagentworkflowscreen import MoveAgentWorkflowScreen
from screens.otpactivityscreen import OTPActivitiesScreen
from screens.otpworkflowscreen import OTPWorkflowScreen
from services.API import AirlockAPIWrapper
from services.policyhandler import confirmUpdateAfromE
from themes.amber_terminal_theme import get_amber_terminal_theme
from themes.retro_terminal_theme import get_retro_terminal_theme
from utils.configmanager import load_env
from utils.setup import get_base_directory, load_user_config
from utils.utils import open_directory
from widgets.agentmoveoperations import AgentMoveOperations
from widgets.amber_terminal_theme import get_amber_terminal_theme
from widgets.multiagentselector import MultiAgentSelector
from widgets.OTP_generate import OTPGenerator
from widgets.policytreewidget import PolicyTreeWidget
from widgets.resultsdisplay import ResultsDisplay
from widgets.retro_terminal_theme import get_retro_terminal_theme
from widgets.themeselector import ThemeSelector
dotenv.load_dotenv()
@@ -102,6 +105,7 @@ def _persist_user_theme(theme_name: str) -> None:
# 1) SCREEN
# ---------------------------------------------------------------------------
class MainMenuScreen(Screen):
api: AirlockAPIWrapper
current_tab = reactive("")
BUTTON_DEFS = {
@@ -120,9 +124,8 @@ class MainMenuScreen(Screen):
],
}
def __init__(self, api: AirlockAPIWrapper) -> None:
def __init__(self) -> None:
super().__init__()
self.api = api
self.extras = load_env("EXTRAS")
wd = load_env("WORKING_DIR") or os.getcwd()
if not os.path.isdir(wd):
@@ -156,6 +159,7 @@ class MainMenuScreen(Screen):
yield Footer()
def on_mount(self) -> None:
api = self.app.api
self.switch_tab("agent_actions")
# focus helpers
@@ -321,34 +325,55 @@ class MainMenuScreen(Screen):
self.app.push_screen(MoveAgentWorkflowScreen(self.app.devices))
event.stop()
return # Don't exit the app
case "otp_generate_button":
# NEW: Push OTP workflow screen instead of legacy function
self.app.push_screen(OTPWorkflowScreen(self.app.devices))
event.stop()
return # Don't exit the app
case "find_quiet_button":
_PENDING_JOB = ("legacy", findQuietAgents, (self.app.api,), {})
case "otp_activities_button":
_PENDING_JOB = ("legacy", otp_activities_by_agent, (self.app.api,), {})
# === FIXED: push the Textual OTPActivitiesScreen and return immediately ===
# This must return so we don't fall through to the code that exits the app.
self.app.push_screen(OTPActivitiesScreen())
event.stop()
return
case "otp_revoke_button":
_PENDING_JOB = ("legacy", otp_revoke, (self.app.api,), {})
case "policy_prep_button":
_PENDING_JOB = ("legacy", menu_policy_enforce, (self.app.api,), {})
case "policy_audit_update_button":
_PENDING_JOB = ("legacy", confirmUpdateAfromE, (self.app.api,), {})
case _:
self.app.bell()
logger.warning("Unknown button pressed: %s", button_id)
return
# Only exit the UI loop when we explicitly queued a legacy job.
# The original flow used `self.app.exit()` after setting _PENDING_JOB so
# the outer loop could run legacy code. Keep that behavior only for legacy jobs.
logger.debug("Set _PENDING_JOB = %r", _PENDING_JOB)
self.app.exit()
if _PENDING_JOB and _PENDING_JOB[0] == "legacy":
# let the main loop pick up the legacy job
self.app.exit()
# ---------------------------------------------------------------------------
# 2) APP
# ---------------------------------------------------------------------------
class Loxide(App):
class Loxide(App[Message]):
api: AirlockAPIWrapper
working_dir: str
policies: Optional[list[Policy]]
devices: Optional[list[Agent]]
CSS = """
#logo {
width: 100%;
@@ -398,7 +423,7 @@ class Loxide(App):
self.register_theme(get_retro_terminal_theme())
self.register_theme(get_amber_terminal_theme())
self.theme = self._textual_theme
self.push_screen(MainMenuScreen(api))
self.push_screen(MainMenuScreen())
def action_quit(self) -> None:
global _PENDING_JOB
@@ -406,12 +431,13 @@ class Loxide(App):
self.exit()
def action_open_dir(self) -> None:
# Refresh data before proceeding
self.refresh_data()
screen = self.screen_stack[-1]
if isinstance(screen, MainMenuScreen):
if screen.current_tab != "dir":
screen.switch_tab("dir")
"""Open the working directory in the OS file manager (footer binding)."""
path_to_open = self.working_dir or os.getcwd()
try:
open_directory(path_to_open)
except Exception as exc:
logger.error("Failed to open directory %s: %s", path_to_open, exc)
self.bell() # optional feedback
# ---------------------------------------------------------------------------