Post Black Linting

This commit is contained in:
2025-11-06 11:04:59 -05:00
parent f33b041ac0
commit b538f12e9a
20 changed files with 1106 additions and 618 deletions
+23 -14
View File
@@ -29,14 +29,15 @@ PROTECTED_KEYS = [
"PATH_EXCLUSION_CONST",
"MIN_FILES_FOR_PATH",
"VT_THREAT_TOLERANCE",
"POLICY_MAP_ENF_AUD"
"POLICY_MAP_ENF_AUD",
]
_protected_config = {}
def get_system_config_path() -> Path:
# Check inside bundled EXE directory first
bundled_dir = Path(getattr(sys, '_MEIPASS', ''))
bundled_dir = Path(getattr(sys, "_MEIPASS", ""))
bundled_path = bundled_dir / "system_config.json"
if bundled_path.exists():
return bundled_path
@@ -44,6 +45,7 @@ def get_system_config_path() -> Path:
# Fallback to external location
return Path(__file__).parent.parent / "system_config.json"
def load_protected_config() -> dict:
global _protected_config
try:
@@ -56,15 +58,16 @@ def load_protected_config() -> dict:
"PATH_EXCLUSION_CONST": 4,
"MIN_FILES_FOR_PATH": 4,
"VT_THREAT_TOLERANCE": 4,
"POLICY_MAP_ENF_AUD": {
"enforced_id": "audit_id"
}
"POLICY_MAP_ENF_AUD": {"enforced_id": "audit_id"},
}
_protected_config = {key: system_config[key] for key in PROTECTED_KEYS}
return _protected_config
def get_protected_value(key: str, cast_type: Callable[[str], T] = str, default: Optional[T] = None) -> Optional[T]:
def get_protected_value(
key: str, cast_type: Callable[[str], T] = str, default: Optional[T] = None
) -> Optional[T]:
value = _protected_config.get(key)
if value is None:
logging.warning(f"Protected config key '{key}' not found.")
@@ -74,9 +77,12 @@ def get_protected_value(key: str, cast_type: Callable[[str], T] = str, default:
value = value.strip("'\"")
return cast_type(value)
except (ValueError, TypeError):
logging.warning(f"Invalid value for protected key '{key}': {value}. Expected type {cast_type.__name__}.")
logging.warning(
f"Invalid value for protected key '{key}': {value}. Expected type {cast_type.__name__}."
)
return default
def get_protected_json(key: str, default: str = "{}") -> dict:
raw = _protected_config.get(key, default)
if isinstance(raw, dict):
@@ -85,13 +91,11 @@ def get_protected_json(key: str, default: str = "{}") -> dict:
return json.loads(raw)
except json.JSONDecodeError:
try:
escaped = raw.encode('unicode_escape').decode('utf-8')
escaped = raw.encode("unicode_escape").decode("utf-8")
return json.loads(escaped)
except Exception as e:
logging.error(f"Failed to parse protected JSON key '{key}': {e}")
return json.loads(default)
def load_env_json(key: str, default: str):
@@ -100,13 +104,16 @@ def load_env_json(key: str, default: str):
return json.loads(raw)
except json.JSONDecodeError:
try:
escaped = raw.encode('unicode_escape').decode('utf-8')
escaped = raw.encode("unicode_escape").decode("utf-8")
return json.loads(escaped)
except Exception as e:
logging.error(f"Failed to parse {key}: {e}")
return json.loads(default)
def load_env(key: str, cast_type: Callable[[str], T] = str, default: Optional[T] = None) -> Optional[T]:
def load_env(
key: str, cast_type: Callable[[str], T] = str, default: Optional[T] = None
) -> Optional[T]:
"""
Safely retrieves an environment variable and casts it to the desired type.
@@ -126,5 +133,7 @@ def load_env(key: str, cast_type: Callable[[str], T] = str, default: Optional[T]
value = value.strip("'\"") # Strip surrounding quotes
return cast_type(value)
except (ValueError, TypeError):
logger.warning(f"Invalid value for env var '{key}': {value}. Expected type {cast_type.__name__}.")
return default
logger.warning(
f"Invalid value for env var '{key}': {value}. Expected type {cast_type.__name__}."
)
return default
+53 -34
View File
@@ -21,9 +21,12 @@ from utils.utils import colorText, get_sanitized_input
logger = logging.getLogger(__name__)
class Selector:
@staticmethod
def _get_sorted_items(items: List[Any], label_func: Callable[[Any], str]) -> List[Any]:
def _get_sorted_items(
items: List[Any], label_func: Callable[[Any], str]
) -> List[Any]:
return sorted(items, key=lambda item: label_func(item).lower())
@staticmethod
@@ -31,10 +34,10 @@ class Selector:
items: List[Any],
label_func: Callable[[Any], str],
num_columns: int = 4,
header: str = "Available Choices:"
header: str = "Available Choices:",
) -> None:
# Force single column if items are DataFrame rows
if items and isinstance(items[0], (pd.Series, dict)):
num_columns = 1
@@ -51,9 +54,7 @@ class Selector:
@staticmethod
def _display_selected_items(
selected: List[Any],
label_func: Callable[[Any], str],
num_columns: int = 4
selected: List[Any], label_func: Callable[[Any], str], num_columns: int = 4
) -> None:
print(colorText("\nCurrent selections:", "cyan"))
if not selected:
@@ -92,7 +93,7 @@ class Selector:
allow_multiple: bool = False,
prompt_each: bool = False,
header: str = "Available Choices:",
num_columns: int = 4
num_columns: int = 4,
) -> Union[Optional[Any], List[Any]]:
if not items:
logger.warning("No items available for selection.")
@@ -104,9 +105,19 @@ class Selector:
if allow_multiple:
while True:
Selector._display_choices(remaining_items, label_func, num_columns=num_columns, header=header)
Selector._display_selected_items(selected, label_func, num_columns=num_columns)
choice = get_sanitized_input("Select item(s) by number (e.g. 1,3-5), R to reset, Q to finish: ").strip().lower()
Selector._display_choices(
remaining_items, label_func, num_columns=num_columns, header=header
)
Selector._display_selected_items(
selected, label_func, num_columns=num_columns
)
choice = (
get_sanitized_input(
"Select item(s) by number (e.g. 1,3-5), R to reset, Q to finish: "
)
.strip()
.lower()
)
if choice == "q":
break
elif choice == "r":
@@ -125,10 +136,14 @@ class Selector:
logger.info(f"Selected: {label_func(item)}")
else:
logger.warning("Item already selected.")
remaining_items = [item for item in remaining_items if item not in newly_selected]
remaining_items = [
item for item in remaining_items if item not in newly_selected
]
return selected if selected else None
else:
Selector._display_choices(full_sorted_items, label_func, num_columns=num_columns, header=header)
Selector._display_choices(
full_sorted_items, label_func, num_columns=num_columns, header=header
)
try:
choice = int(get_sanitized_input("Select one item by number: "))
if 1 <= choice <= len(full_sorted_items):
@@ -145,9 +160,14 @@ class Selector:
def select_with_mode(
items: List[Any],
label_func: Callable[[Any], str],
header: str = "Available Choices:"
header: str = "Available Choices:",
) -> List[Any]:
print(colorText("Choose selection mode: [I]nclude only selected, [E]xclude selected, [A]ll (skip):", "white"))
print(
colorText(
"Choose selection mode: [I]nclude only selected, [E]xclude selected, [A]ll (skip):",
"white",
)
)
mode = get_sanitized_input("").strip().lower()
if mode == "a":
return items
@@ -156,7 +176,7 @@ class Selector:
label_func=label_func,
allow_multiple=True,
prompt_each=False,
header=header
header=header,
)
if not selected:
return items
@@ -172,44 +192,38 @@ class Selector:
@staticmethod
def select_objects(
objects: List[Any],
allow_multiple: bool = False,
prompt_each: bool = False
objects: List[Any], allow_multiple: bool = False, prompt_each: bool = False
) -> Union[Optional[Any], List[Any]]:
return Selector._select_from_list(
objects,
label_func=lambda obj: getattr(obj, "name", str(obj)),
allow_multiple=allow_multiple,
prompt_each=prompt_each,
header="Available Objects:"
header="Available Objects:",
)
@staticmethod
def select_string(
options: List[str],
allow_multiple: bool = False,
prompt_each: bool = False
options: List[str], allow_multiple: bool = False, prompt_each: bool = False
) -> Union[Optional[str], List[str]]:
return Selector._select_from_list(
options,
label_func=str,
allow_multiple=allow_multiple,
prompt_each=prompt_each,
header="Available Options:"
header="Available Options:",
)
@staticmethod
def select_int(
options: List[int],
allow_multiple: bool = False,
prompt_each: bool = False
options: List[int], allow_multiple: bool = False, prompt_each: bool = False
) -> Union[Optional[int], List[int]]:
return Selector._select_from_list(
options,
label_func=lambda x: str(x),
allow_multiple=allow_multiple,
prompt_each=prompt_each,
header="Available Integers:"
header="Available Integers:",
)
@staticmethod
@@ -217,7 +231,7 @@ class Selector:
prompt: str,
value_type: type = int,
valid_range: Optional[tuple] = None,
allow_quit: bool = False
allow_quit: bool = False,
) -> Optional[Any]:
while True:
user_input = get_sanitized_input(prompt).strip().lower()
@@ -255,7 +269,7 @@ class Selector:
columns: Optional[List[str]] = None,
allow_multiple: bool = False,
prompt_each: bool = False,
header: str = "Available Rows:"
header: str = "Available Rows:",
) -> List[pd.Series]:
if df.empty:
print("DataFrame is empty.")
@@ -272,7 +286,7 @@ class Selector:
label_func=label_func,
allow_multiple=allow_multiple,
prompt_each=prompt_each,
header=header
header=header,
)
if isinstance(result, pd.Series):
@@ -286,7 +300,7 @@ class Selector:
def select_dataframe_with_mode(
df: pd.DataFrame,
columns: Optional[List[str]] = None,
header: str = "Available Rows:"
header: str = "Available Rows:",
) -> List[pd.Series]:
if df.empty:
print("⚠️ DataFrame is empty.")
@@ -305,7 +319,12 @@ class Selector:
print(f"{i}: {label_func(row)}")
# Prompt for mode once
print(colorText("\nChoose selection mode: [I]nclude only selected, [E]xclude selected, [A]ll (skip):", "white"))
print(
colorText(
"\nChoose selection mode: [I]nclude only selected, [E]xclude selected, [A]ll (skip):",
"white",
)
)
mode = get_sanitized_input("").strip().lower()
if mode == "a":
@@ -317,7 +336,7 @@ class Selector:
label_func=label_func,
allow_multiple=True,
prompt_each=False,
header=header
header=header,
)
if not selected:
@@ -331,4 +350,4 @@ class Selector:
return [pd.Series(row) for row in items if row not in selected]
else:
print(colorText("⚠️ Invalid mode. Returning no rows.", "yellow"))
return []
return []
+36 -32
View File
@@ -30,12 +30,12 @@ from utils.configmanager import PROTECTED_KEYS, load_protected_config
def get_base_directory() -> Path:
system = platform.system()
home = Path.home()
if system == 'Windows':
return Path(os.getenv('APPDATA', home / 'AppData' / 'Roaming')) / "Loxide"
elif system == 'Darwin':
return home / 'Library' / 'Application Support' / "Loxide"
if system == "Windows":
return Path(os.getenv("APPDATA", home / "AppData" / "Roaming")) / "Loxide"
elif system == "Darwin":
return home / "Library" / "Application Support" / "Loxide"
else:
return home / '.local' / 'share' / "Loxide"
return home / ".local" / "share" / "Loxide"
def configure_logging(log_dir: Path, log_level: str = "DEBUG"):
@@ -58,17 +58,17 @@ def configure_logging(log_dir: Path, log_level: str = "DEBUG"):
"file": {
"class": "logging.handlers.TimedRotatingFileHandler",
"filename": str(log_file),
"when": "midnight", # Rotate logs at midnight
"interval": 1, # Every 1 day
"backupCount": 7, # Keep 7 days of logs
"encoding": "utf-8", # Ensure UTF-8 encoding
"level": "DEBUG", # Always log DEBUG and above
"formatter": "detailed", # Use detailed format
"when": "midnight", # Rotate logs at midnight
"interval": 1, # Every 1 day
"backupCount": 7, # Keep 7 days of logs
"encoding": "utf-8", # Ensure UTF-8 encoding
"level": "DEBUG", # Always log DEBUG and above
"formatter": "detailed", # Use detailed format
},
"console": {
"class": "logging.StreamHandler",
"level": log_level.upper(), # Configurable log level
"formatter": "simple", # Use simple format
"formatter": "simple", # Use simple format
},
},
"root": {
@@ -83,8 +83,8 @@ def configure_logging(log_dir: Path, log_level: str = "DEBUG"):
config["handlers"]["eventlog"] = {
"class": "logging.handlers.NTEventLogHandler",
"appname": "Loxide", # Event log source name
"level": "CRITICAL", # Only log critical errors
"formatter": "simple", # Use simple format
"level": "CRITICAL", # Only log critical errors
"formatter": "simple", # Use simple format
}
config["root"]["handlers"].append("eventlog")
except Exception as e:
@@ -94,9 +94,11 @@ def configure_logging(log_dir: Path, log_level: str = "DEBUG"):
logging.config.dictConfig(config)
logging.getLogger().debug("✅ Logging configured.")
def get_system_config_path() -> Path:
base_path = Path(getattr(sys, '_MEIPASS', os.path.dirname(os.path.abspath(__file__))))
base_path = Path(
getattr(sys, "_MEIPASS", os.path.dirname(os.path.abspath(__file__)))
)
return base_path.parent / "system_config.json"
@@ -113,40 +115,40 @@ def load_system_config() -> dict:
"PATH_EXCLUSION_CONST": 4,
"MIN_FILES_FOR_PATH": 4,
"VT_THREAT_TOLERANCE": 4,
"POLICY_MAP_ENF_AUD": {
"enforced_id": "audit_id"
}
"POLICY_MAP_ENF_AUD": {"enforced_id": "audit_id"},
}
def load_user_config(config_dir: Path) -> dict:
user_config_path = config_dir / "user_config.json"
if not user_config_path.exists():
default_user_config = {
"URL": "",
"LOG_LEVEL": "INFO"
}
default_user_config = {"URL": "", "LOG_LEVEL": "INFO"}
with open(user_config_path, "w") as f:
json.dump(default_user_config, f, indent=4)
logging.debug(f"Created user config at {user_config_path}")
with open(user_config_path, "r") as f:
return json.load(f)
def write_config_to_env(config: dict, env_path: Path):
for key, value in config.items():
if key in PROTECTED_KEYS:
continue # Skip protected keys
try:
serialized = json.dumps(value) if isinstance(value, (list, dict)) else str(value)
serialized = (
json.dumps(value) if isinstance(value, (list, dict)) else str(value)
)
set_key(env_path, key, serialized)
except Exception as e:
logging.warning(f"Failed to write {key} to .env: {e}")
def setup():
base_dir = get_base_directory()
dirs = {
'config': base_dir / 'config',
'cache': base_dir / 'cache',
'logs': base_dir / 'logs',
"config": base_dir / "config",
"cache": base_dir / "cache",
"logs": base_dir / "logs",
}
for name, path in dirs.items():
@@ -154,7 +156,7 @@ def setup():
logging.debug(f"{name.capitalize()} directory ensured at: {path}")
system_config = load_system_config()
configure_logging(dirs['logs'], system_config.get("LOG_LEVEL", "DEBUG"))
configure_logging(dirs["logs"], system_config.get("LOG_LEVEL", "DEBUG"))
env_path = base_dir / ".env"
if not env_path.exists():
@@ -171,7 +173,7 @@ def setup():
"Approved": [],
"Needs_Review": ["Review_First", "Review_Second", "HTML"],
"Preflight": ["HTML"],
"Archived": []
"Archived": [],
}
for folder_name, subfolders in folders_structure.items():
@@ -183,7 +185,7 @@ def setup():
subfolder_path.mkdir(parents=True, exist_ok=True)
logging.debug(f" └─ '{subfolder}' subfolder created at: {subfolder_path}")
user_config = load_user_config(dirs['config'])
user_config = load_user_config(dirs["config"])
merged_config = {**system_config, **user_config}
protected_config = load_protected_config()
@@ -194,10 +196,12 @@ def setup():
if not url:
url = os.getenv("URL")
if not url:
url = input("🌐 Enter the service URL (e.g., https://example.com/api): ").strip()
url = input(
"🌐 Enter the service URL (e.g., https://example.com/api): "
).strip()
merged_config["URL"] = url
set_key(env_path, "URL", url)
os.environ["URL"] = url
logging.debug(f"Service URL set to: {url}")
write_config_to_env(merged_config, env_path)
write_config_to_env(merged_config, env_path)
+15 -20
View File
@@ -58,7 +58,9 @@ def _persist_user_theme(theme_name: str) -> None:
config_dir.mkdir(parents=True, exist_ok=True)
if not user_config_path.exists():
# minimal default like your load_user_config does
user_config_path.write_text('{"URL": "", "LOG_LEVEL": "INFO"}\n', encoding="utf-8")
user_config_path.write_text(
'{"URL": "", "LOG_LEVEL": "INFO"}\n', encoding="utf-8"
)
# load existing user config
user_conf = load_user_config(config_dir)
@@ -86,8 +88,6 @@ def _persist_user_theme(theme_name: str) -> None:
logger.debug("Reloaded .env from %s", env_path)
# ---------------------------------------------------------------------------
# 1) SCREEN
# ---------------------------------------------------------------------------
@@ -138,7 +138,6 @@ class MainMenuScreen(Screen):
wd = os.getcwd()
self.working_dir = wd
def _make_buttons_for(self, tab_id: str) -> Vertical:
defs = self.BUTTON_DEFS.get(tab_id, [])
buttons = []
@@ -148,9 +147,6 @@ class MainMenuScreen(Screen):
buttons.append(btn)
return Vertical(*buttons)
def compose(self) -> ComposeResult:
yield Header(show_clock=True, icon="")
@@ -208,8 +204,7 @@ class MainMenuScreen(Screen):
new_index = current + direction
if 0 <= new_index < len(buttons):
buttons[new_index].focus()
def switch_tab(self, tab_id: str) -> None:
self.current_tab = tab_id
content = self.query_one("#content", Vertical)
@@ -230,7 +225,9 @@ class MainMenuScreen(Screen):
layout.mount(policy_tree)
# Right: Details pane
details_pane = Static("Select a policy or device to view details", id="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)
@@ -240,7 +237,9 @@ class MainMenuScreen(Screen):
# 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 = policy_tree.root.add(
label=policy["name"], data=policy.to_dict()
)
node_map[policy["groupid"]] = node
# Child policies
@@ -259,7 +258,6 @@ class MainMenuScreen(Screen):
label = device["hostname"] # Keep tree clean
parent_node.add(label=label, data=device.to_dict())
elif tab_id == "settings":
# Create and mount the horizontal container
horizontal_container = Horizontal(id="settings_grid")
@@ -279,7 +277,7 @@ class MainMenuScreen(Screen):
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%"
# button.styles.width = "100%"
column.mount(button) # Mount each button
else:
@@ -300,9 +298,10 @@ class MainMenuScreen(Screen):
details = f"Selected: {node.label}"
details_pane.update(details)
def on_directory_tree_file_selected(self, event: DirectoryTree.FileSelected) -> None:
def on_directory_tree_file_selected(
self, event: DirectoryTree.FileSelected
) -> None:
path = event.path
logger.debug("Directory file selected: %s", path)
try:
@@ -359,10 +358,6 @@ class MainMenuScreen(Screen):
self.app.exit()
# ---------------------------------------------------------------------------
# 2) APP
# ---------------------------------------------------------------------------
@@ -407,7 +402,6 @@ class Loxide(App):
screen.switch_tab("dir")
# ---------------------------------------------------------------------------
# 3) TERMINAL + LEGACY
# ---------------------------------------------------------------------------
@@ -422,6 +416,7 @@ def _restore_terminal_for_legacy() -> None:
if os.name == "nt":
try:
import ctypes
kernel32 = ctypes.windll.kernel32
handle = kernel32.GetStdHandle(-11)
mode = ctypes.c_ulong()
+57 -22
View File
@@ -28,8 +28,6 @@ import pandas as pd
logger = logging.getLogger(__name__)
def import_to_dataframe(file_path: str) -> pd.DataFrame:
df = pd.DataFrame()
@@ -96,17 +94,17 @@ def choose_file(initial_directory=None, required_substring=None):
return file_path
def get_sanitized_input(prompt: str) -> str:
while True:
user_input = input(prompt)
if user_input.strip() == "":
return user_input # Allow blank lines
if re.match(r'^[a-zA-Z0-9_\- .]+$', user_input.strip()):
if re.match(r"^[a-zA-Z0-9_\- .]+$", user_input.strip()):
return user_input
else:
print("Invalid input. Only letters, numbers, underscores, spaces, hyphens, and periods are allowed.")
print(
"Invalid input. Only letters, numbers, underscores, spaces, hyphens, and periods are allowed."
)
def regulator(paths, case_insensitive=True):
@@ -119,8 +117,10 @@ def regulator(paths, case_insensitive=True):
pattern = "(?i)" + pattern # Add inline case-insensitive flag
print(f"Regulator is providing: {pattern}")
return pattern
def irtang():
print(
print(
colorText(
r"""
███
@@ -149,6 +149,8 @@ def irtang():
"yellow",
)
)
def displayIntro():
print(
@@ -164,6 +166,8 @@ def displayIntro():
"cyan",
)
)
def welcome():
print(
colorText(
@@ -184,11 +188,21 @@ def welcome():
)
)
def section_header(title):
print(colorText("\n --------------------------------------------------------------------", "cyan"))
print(colorText(f" ------------- {title} -------------", "cyan"))
print(colorText(" --------------------------------------------------------------------", "cyan"))
def section_header(title):
print(
colorText(
"\n --------------------------------------------------------------------",
"cyan",
)
)
print(colorText(f" ------------- {title} -------------", "cyan"))
print(
colorText(
" --------------------------------------------------------------------",
"cyan",
)
)
def areYouSure():
@@ -348,7 +362,11 @@ def printDeviceEnforceChecklist():
"cyan",
)
)
print(colorText(" When complete, save the csv file to the directory 'approved'", "cyan"))
print(
colorText(
" When complete, save the csv file to the directory 'approved'", "cyan"
)
)
print(
colorText(
" Do the same process with the list of publishers forthe same directories",
@@ -357,20 +375,38 @@ def printDeviceEnforceChecklist():
)
print(colorText(" Preflight Lists will be generated", "cyan"))
print(colorText("5. Choose the destination policy and parent and child allow list", "cyan"))
print(
colorText(
"5. Choose the destination policy and parent and child allow list", "cyan"
)
)
print(colorText("6. Test ------------------------------------------------------", "cyan"))
print(
colorText(
"6. Test ------------------------------------------------------", "cyan"
)
)
print(colorText(" Print rather than apply selected data.", "cyan"))
print(colorText("7. Liftoff ------------------------------------------------------", "cyan"))
print(
colorText(
"7. Liftoff ------------------------------------------------------", "cyan"
)
)
print(
colorText(
" Apply path exclusions according to allowed and approved paths",
"cyan",
)
)
print(colorText(" Apply signed or attested hashes to Parent Allow List", "cyan"))
print(colorText(" Apply approved, but unsigned hashes to the Child Allow List", "cyan"))
print(
colorText(" Apply signed or attested hashes to Parent Allow List", "cyan")
)
print(
colorText(
" Apply approved, but unsigned hashes to the Child Allow List", "cyan"
)
)
print(
colorText(
@@ -523,10 +559,9 @@ def formatHTML(df, output_html_path=None, overwrite=True):
return styled_html
def open_directory(path):
system = platform.system()
if system == "Windows":
os.startfile(path)
elif system == "Linux":
@@ -537,9 +572,9 @@ def open_directory(path):
def print_x_wide(items: list, width: int):
for i in range(0, len(items), width):
row = items[i:i+width]
row = items[i : i + width]
print(" | ".join(row))
def clear_screen():
os.system('cls' if os.name == 'nt' else 'clear')
os.system("cls" if os.name == "nt" else "clear")