Post Black Linting
This commit is contained in:
+23
-14
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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")
|
||||
|
||||
Reference in New Issue
Block a user