Updates to Allowlist Selection
This commit is contained in:
@@ -23,14 +23,13 @@
|
|||||||
|
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
import tempfile
|
|
||||||
|
|
||||||
import dotenv
|
|
||||||
import urllib3
|
import urllib3
|
||||||
|
|
||||||
from services.API import AirlockAPIWrapper
|
from services.API import AirlockAPIWrapper
|
||||||
from services.security import getAPI
|
from services.security import getAPI
|
||||||
from TUI.TUI import run_Loxide
|
from TUI.TUI import run_Loxide
|
||||||
|
from utils.configmanager import get_system_value
|
||||||
from utils.setup import get_base_directory, setup
|
from utils.setup import get_base_directory, setup
|
||||||
from utils.utils import irtang
|
from utils.utils import irtang
|
||||||
|
|
||||||
@@ -38,24 +37,14 @@ urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
|
|||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
|
|
||||||
if "NUITKA_ONEFILE_PARENT" in os.environ:
|
|
||||||
splash_filename = os.path.join(
|
|
||||||
tempfile.gettempdir(),
|
|
||||||
f"onefile_{int(os.environ['NUITKA_ONEFILE_PARENT'])}_splash_feedback.tmp",
|
|
||||||
)
|
|
||||||
if os.path.exists(splash_filename):
|
|
||||||
os.unlink(splash_filename)
|
|
||||||
|
|
||||||
irtang()
|
irtang()
|
||||||
# Determine working directory, setup directory, configure logging, sent env, get API and URL if not already stored
|
# Determine working directory, setup directory, configure logging, sent env, get API and URL if not already stored
|
||||||
setup()
|
setup()
|
||||||
base_dir = get_base_directory()
|
base_dir = get_base_directory()
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
dotenv.load_dotenv(dotenv_path=base_dir / ".env")
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
url = os.getenv("URL")
|
url = get_system_value("URL")
|
||||||
username = os.getenv("USERNAME")
|
username = os.getenv("USERNAME")
|
||||||
|
|
||||||
if not url:
|
if not url:
|
||||||
@@ -75,7 +64,7 @@ def main():
|
|||||||
raise ValueError("API key for Loxide is missing.")
|
raise ValueError("API key for Loxide is missing.")
|
||||||
|
|
||||||
api = AirlockAPIWrapper(
|
api = AirlockAPIWrapper(
|
||||||
base_url=str(os.getenv("URL")),
|
base_url=str(url),
|
||||||
api_key=api_key,
|
api_key=api_key,
|
||||||
)
|
)
|
||||||
run_Loxide(api)
|
run_Loxide(api)
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ class AllowlistSelectionWidget(Static):
|
|||||||
layout: vertical;
|
layout: vertical;
|
||||||
}
|
}
|
||||||
#allowlist_main {
|
#allowlist_main {
|
||||||
height: 100%;
|
height: 1fr;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
#left_panel {
|
#left_panel {
|
||||||
@@ -61,7 +61,8 @@ class AllowlistSelectionWidget(Static):
|
|||||||
margin: 1 0;
|
margin: 1 0;
|
||||||
}
|
}
|
||||||
#action_buttons {
|
#action_buttons {
|
||||||
height: 10%;
|
height: auto;
|
||||||
|
min-height: 3;
|
||||||
padding: 1;
|
padding: 1;
|
||||||
content-align: center middle;
|
content-align: center middle;
|
||||||
}
|
}
|
||||||
@@ -149,7 +150,7 @@ class AllowlistSelectionWidget(Static):
|
|||||||
|
|
||||||
# Action buttons at bottom
|
# Action buttons at bottom
|
||||||
with Horizontal(id="action_buttons"):
|
with Horizontal(id="action_buttons"):
|
||||||
self.back_btn = Button("← Back", id="back_btn")
|
self.back_btn = Button("⬅ Back", id="back_btn")
|
||||||
self.add_btn = Button("➕ Add to Allowlist", id="add_to_allowlist_btn")
|
self.add_btn = Button("➕ Add to Allowlist", id="add_to_allowlist_btn")
|
||||||
|
|
||||||
self.back_btn.styles.width = "50%"
|
self.back_btn.styles.width = "50%"
|
||||||
@@ -176,7 +177,8 @@ class AllowlistSelectionWidget(Static):
|
|||||||
# First, try to get the host's policy if hostname is provided
|
# First, try to get the host's policy if hostname is provided
|
||||||
host_policy_allowlists = []
|
host_policy_allowlists = []
|
||||||
host_policy_ids = set()
|
host_policy_ids = set()
|
||||||
policy_name = None
|
policy_name = "Unknown Policy" # Default value
|
||||||
|
group_id = None
|
||||||
|
|
||||||
if self.hostname:
|
if self.hostname:
|
||||||
try:
|
try:
|
||||||
@@ -185,8 +187,23 @@ class AllowlistSelectionWidget(Static):
|
|||||||
if not agents_df.empty:
|
if not agents_df.empty:
|
||||||
# Get the policy group ID for this host
|
# Get the policy group ID for this host
|
||||||
group_id = agents_df.iloc[0].get("groupid")
|
group_id = agents_df.iloc[0].get("groupid")
|
||||||
policy_name = agents_df.iloc[0].get(
|
|
||||||
"groupname", "Unknown Policy"
|
# Look up the policy name from app's cached policies
|
||||||
|
if (
|
||||||
|
group_id
|
||||||
|
and hasattr(self.app, "policies")
|
||||||
|
and self.app.policies
|
||||||
|
):
|
||||||
|
for policy in self.app.policies:
|
||||||
|
if policy.groupid == group_id:
|
||||||
|
policy_name = policy.name
|
||||||
|
logger.info(
|
||||||
|
f"Found policy name: '{policy_name}' for group_id: {group_id}"
|
||||||
|
)
|
||||||
|
break
|
||||||
|
|
||||||
|
logger.info(
|
||||||
|
f"Found host '{self.hostname}' in policy '{policy_name}' (group_id: {group_id})"
|
||||||
)
|
)
|
||||||
|
|
||||||
if group_id:
|
if group_id:
|
||||||
@@ -208,6 +225,33 @@ class AllowlistSelectionWidget(Static):
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.warning(f"Could not get host's policy allowlists: {e}")
|
logger.warning(f"Could not get host's policy allowlists: {e}")
|
||||||
|
|
||||||
|
# If we still don't have a policy name, try to get it from the first allowlist or use a default
|
||||||
|
if not policy_name:
|
||||||
|
# Get all policies and try to find which one has allowlists
|
||||||
|
try:
|
||||||
|
all_policies_df = self.api.policy_find_all()
|
||||||
|
if not all_policies_df.empty:
|
||||||
|
# If we have a group_id from somewhere, use it
|
||||||
|
if group_id:
|
||||||
|
policy_row = all_policies_df[
|
||||||
|
all_policies_df["groupid"] == group_id
|
||||||
|
]
|
||||||
|
if not policy_row.empty:
|
||||||
|
policy_name = policy_row.iloc[0].get(
|
||||||
|
"groupname", "Unknown Policy"
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
# Use the first policy as fallback
|
||||||
|
policy_name = all_policies_df.iloc[0].get(
|
||||||
|
"groupname", "Default Policy"
|
||||||
|
)
|
||||||
|
logger.info(f"Using first available policy: {policy_name}")
|
||||||
|
else:
|
||||||
|
policy_name = "Unknown Policy"
|
||||||
|
except Exception as e:
|
||||||
|
logger.warning(f"Could not fetch policies: {e}")
|
||||||
|
policy_name = "Unknown Policy"
|
||||||
|
|
||||||
# Get all allowlists
|
# Get all allowlists
|
||||||
all_allowlists_df = self.api.allowlist_find_all()
|
all_allowlists_df = self.api.allowlist_find_all()
|
||||||
|
|
||||||
@@ -243,7 +287,7 @@ class AllowlistSelectionWidget(Static):
|
|||||||
# Add policy-associated allowlists if any
|
# Add policy-associated allowlists if any
|
||||||
if host_policy_allowlists:
|
if host_policy_allowlists:
|
||||||
# Add section header
|
# Add section header
|
||||||
header_text = f"━━━ Policy: {policy_name or 'Host Policy'} ━━━"
|
header_text = f"=== Policy: {policy_name or 'Host Policy'} ==="
|
||||||
self.allowlist_table.add_row(header_text, "", "", key="header_policy")
|
self.allowlist_table.add_row(header_text, "", "", key="header_policy")
|
||||||
current_row += 1
|
current_row += 1
|
||||||
|
|
||||||
@@ -270,7 +314,7 @@ class AllowlistSelectionWidget(Static):
|
|||||||
current_row += 1
|
current_row += 1
|
||||||
|
|
||||||
self.allowlist_table.add_row(
|
self.allowlist_table.add_row(
|
||||||
"━━━ Other Available Allowlists ━━━", "", "", key="header_other"
|
"=== Other Available Allowlists ===", "", "", key="header_other"
|
||||||
)
|
)
|
||||||
current_row += 1
|
current_row += 1
|
||||||
|
|
||||||
@@ -336,9 +380,9 @@ class AllowlistSelectionWidget(Static):
|
|||||||
|
|
||||||
if found_col:
|
if found_col:
|
||||||
self.hash_column = found_col
|
self.hash_column = found_col
|
||||||
preview_lines.append(f"✓ Found hash column: **{found_col}**\n")
|
preview_lines.append(f"âÅâ Found hash column: **{found_col}**\n")
|
||||||
else:
|
else:
|
||||||
preview_lines.append("⚠️ **No hash column found**\n")
|
preview_lines.append("⚠︠**No hash column found**\n")
|
||||||
preview_lines.append("Available columns:\n")
|
preview_lines.append("Available columns:\n")
|
||||||
for col in self.selected_data.columns:
|
for col in self.selected_data.columns:
|
||||||
if col != "_row_id":
|
if col != "_row_id":
|
||||||
@@ -420,7 +464,7 @@ class AllowlistSelectionWidget(Static):
|
|||||||
self.selected_allowlist = self.allowlists[actual_allowlist_index]
|
self.selected_allowlist = self.allowlists[actual_allowlist_index]
|
||||||
self.add_btn.disabled = False
|
self.add_btn.disabled = False
|
||||||
self.add_btn.label = (
|
self.add_btn.label = (
|
||||||
f"➕ Add to '{self.selected_allowlist.get('name', 'Unknown')}'"
|
f"â Add to '{self.selected_allowlist.get('name', 'Unknown')}'"
|
||||||
)
|
)
|
||||||
|
|
||||||
# Update preview with selection
|
# Update preview with selection
|
||||||
@@ -523,7 +567,7 @@ class AllowlistSelectionWidget(Static):
|
|||||||
|
|
||||||
# Success notification
|
# Success notification
|
||||||
self.app.notify(
|
self.app.notify(
|
||||||
f"✅ Successfully added {len(self.hashes_to_add)} hashes to '{allowlist_name}'",
|
f"â
Successfully added {len(self.hashes_to_add)} hashes to '{allowlist_name}'",
|
||||||
title="Success",
|
title="Success",
|
||||||
severity="information",
|
severity="information",
|
||||||
timeout=5,
|
timeout=5,
|
||||||
@@ -531,7 +575,7 @@ class AllowlistSelectionWidget(Static):
|
|||||||
|
|
||||||
# Update preview to show success
|
# Update preview to show success
|
||||||
self.preview_area.text = (
|
self.preview_area.text = (
|
||||||
f"## ✅ SUCCESS\n\n"
|
f"## â
SUCCESS\n\n"
|
||||||
f"Added **{len(self.hashes_to_add)} hashes** to allowlist:\n"
|
f"Added **{len(self.hashes_to_add)} hashes** to allowlist:\n"
|
||||||
f"**{allowlist_name}** (ID: {app_id})\n\n"
|
f"**{allowlist_name}** (ID: {app_id})\n\n"
|
||||||
f"### Operation Details:\n"
|
f"### Operation Details:\n"
|
||||||
@@ -551,7 +595,7 @@ class AllowlistSelectionWidget(Static):
|
|||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
logger.exception(f"Failed to add hashes to allowlist: {exc}")
|
logger.exception(f"Failed to add hashes to allowlist: {exc}")
|
||||||
self.app.notify(
|
self.app.notify(
|
||||||
f"❌ Failed to add hashes: {str(exc)}",
|
f"âÂÅ Failed to add hashes: {str(exc)}",
|
||||||
title="Error",
|
title="Error",
|
||||||
severity="error",
|
severity="error",
|
||||||
timeout=10,
|
timeout=10,
|
||||||
@@ -559,7 +603,7 @@ class AllowlistSelectionWidget(Static):
|
|||||||
|
|
||||||
# Re-enable button
|
# Re-enable button
|
||||||
self.add_btn.disabled = False
|
self.add_btn.disabled = False
|
||||||
self.add_btn.label = "➕ Retry Add to Allowlist"
|
self.add_btn.label = "â Retry Add to Allowlist"
|
||||||
|
|
||||||
|
|
||||||
class AllowlistSelectionScreen(Screen):
|
class AllowlistSelectionScreen(Screen):
|
||||||
|
|||||||
+13
-10
@@ -56,12 +56,13 @@ class OTPActivitiesWidget(Static):
|
|||||||
layout: vertical;
|
layout: vertical;
|
||||||
}
|
}
|
||||||
#activity_preview_container {
|
#activity_preview_container {
|
||||||
height: 75%;
|
height: 1fr;
|
||||||
border: none;
|
border: none;
|
||||||
padding: 1 1;
|
padding: 1 1;
|
||||||
}
|
}
|
||||||
#activity_buttons {
|
#activity_buttons {
|
||||||
height: 25%;
|
height: auto;
|
||||||
|
min-height: 3;
|
||||||
padding: 1 1;
|
padding: 1 1;
|
||||||
content-align: center middle;
|
content-align: center middle;
|
||||||
}
|
}
|
||||||
@@ -481,16 +482,18 @@ class ActivityDetailWidget(Static):
|
|||||||
layout: vertical;
|
layout: vertical;
|
||||||
}
|
}
|
||||||
#detail_table_container {
|
#detail_table_container {
|
||||||
height: 75%;
|
height: 1fr;
|
||||||
padding: 1 1;
|
padding: 1 1;
|
||||||
}
|
}
|
||||||
#selection_buttons {
|
#selection_buttons {
|
||||||
height: 10%;
|
height: auto;
|
||||||
|
min-height: 3;
|
||||||
padding: 1 1;
|
padding: 1 1;
|
||||||
content-align: center middle;
|
content-align: center middle;
|
||||||
}
|
}
|
||||||
#detail_buttons {
|
#detail_buttons {
|
||||||
height: 15%;
|
height: auto;
|
||||||
|
min-height: 3;
|
||||||
padding: 1 1;
|
padding: 1 1;
|
||||||
content-align: center middle;
|
content-align: center middle;
|
||||||
}
|
}
|
||||||
@@ -649,7 +652,7 @@ class ActivityDetailWidget(Static):
|
|||||||
logger.exception("Failed to sort by column %s: %s", column_key, exc)
|
logger.exception("Failed to sort by column %s: %s", column_key, exc)
|
||||||
return
|
return
|
||||||
|
|
||||||
# ✅ Only refresh rows, not columns
|
# ✅ Only refresh rows, not columns
|
||||||
await self._build_table(rebuild=False)
|
await self._build_table(rebuild=False)
|
||||||
|
|
||||||
async def on_button_pressed(self, event) -> None:
|
async def on_button_pressed(self, event) -> None:
|
||||||
@@ -728,13 +731,13 @@ class ActivityDetailWidget(Static):
|
|||||||
if self.activities_df is None or self.activities_df.empty:
|
if self.activities_df is None or self.activities_df.empty:
|
||||||
logger.info("No activities to export.")
|
logger.info("No activities to export.")
|
||||||
await self.mount(
|
await self.mount(
|
||||||
Static("❌ No activities to export.", classes="notification")
|
Static("⌠No activities to export.", classes="notification")
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
if not self.selected_row_ids:
|
if not self.selected_row_ids:
|
||||||
logger.info("No rows selected for export.")
|
logger.info("No rows selected for export.")
|
||||||
await self.mount(
|
await self.mount(
|
||||||
Static("❌ No rows selected for export.", classes="notification")
|
Static("⌠No rows selected for export.", classes="notification")
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
try:
|
try:
|
||||||
@@ -755,12 +758,12 @@ class ActivityDetailWidget(Static):
|
|||||||
logger.exception("Failed to export detail activities: %s", exc)
|
logger.exception("Failed to export detail activities: %s", exc)
|
||||||
await self.mount(
|
await self.mount(
|
||||||
Static(
|
Static(
|
||||||
"❌ Failed to export activities; check logs.",
|
"⌠Failed to export activities; check logs.",
|
||||||
classes="notification",
|
classes="notification",
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
# ✅ Helper methods
|
# ✅ Helper methods
|
||||||
def get_selected_data(self) -> pd.DataFrame:
|
def get_selected_data(self) -> pd.DataFrame:
|
||||||
"""Return a DataFrame of the selected rows."""
|
"""Return a DataFrame of the selected rows."""
|
||||||
if not self.selected_row_ids:
|
if not self.selected_row_ids:
|
||||||
|
|||||||
Reference in New Issue
Block a user