Add profile stats feature and menu option

This commit is contained in:
thePR0M3TH3AN
2025-07-03 14:22:05 -04:00
parent cf1bb67b5b
commit ff71c60410
3 changed files with 110 additions and 1 deletions

View File

@@ -254,6 +254,8 @@ Back in the Settings menu you can:
* Choose `9` to set an additional backup location.
* Select `10` to change the inactivity timeout.
* Choose `11` to lock the vault and require re-entry of your password.
* Select `12` to return to the main menu.
* Choose `13` to view seed profile stats.
## Running Tests

View File

@@ -222,6 +222,17 @@ def handle_display_npub(password_manager: PasswordManager):
print(colored(f"Error: Failed to display npub: {e}", "red"))
def handle_display_stats(password_manager: PasswordManager) -> None:
"""Print seed profile statistics."""
try:
display_fn = getattr(password_manager, "display_stats", None)
if callable(display_fn):
display_fn()
except Exception as e: # pragma: no cover - display best effort
logging.error(f"Failed to display stats: {e}", exc_info=True)
print(colored(f"Error: Failed to display stats: {e}", "red"))
def handle_post_to_nostr(
password_manager: PasswordManager, alt_summary: str | None = None
):
@@ -556,6 +567,7 @@ def handle_settings(password_manager: PasswordManager) -> None:
print("10. Set inactivity timeout")
print("11. Lock Vault")
print("12. Back")
print("13. Stats")
choice = input("Select an option: ").strip()
if choice == "1":
handle_profiles_menu(password_manager)
@@ -585,6 +597,8 @@ def handle_settings(password_manager: PasswordManager) -> None:
password_manager.unlock_vault()
elif choice == "12":
break
elif choice == "13":
handle_display_stats(password_manager)
else:
print(colored("Invalid choice.", "red"))
@@ -606,6 +620,9 @@ def display_menu(
5. Settings
6. Exit
"""
display_fn = getattr(password_manager, "display_stats", None)
if callable(display_fn):
display_fn()
while True:
if time.time() - password_manager.last_activity > inactivity_timeout:
print(colored("Session timed out. Vault locked.", "yellow"))

View File

@@ -34,7 +34,7 @@ from utils.key_derivation import (
derive_index_key,
EncryptionMode,
)
from utils.checksum import calculate_checksum, verify_checksum
from utils.checksum import calculate_checksum, verify_checksum, json_checksum
from utils.password_prompt import (
prompt_for_password,
prompt_existing_password,
@@ -1826,3 +1826,93 @@ class PasswordManager:
except Exception as e:
logging.error(f"Failed to change password: {e}", exc_info=True)
print(colored(f"Error: Failed to change password: {e}", "red"))
def get_profile_stats(self) -> dict:
"""Return various statistics about the current seed profile."""
if not all([self.entry_manager, self.config_manager, self.backup_manager]):
return {}
stats: dict[str, object] = {}
# Entry counts by type
data = self.entry_manager.vault.load_index()
entries = data.get("entries", {})
counts: dict[str, int] = {}
for entry in entries.values():
etype = entry.get("type", EntryType.PASSWORD.value)
counts[etype] = counts.get(etype, 0) + 1
stats["entries"] = counts
stats["total_entries"] = len(entries)
# Schema version and checksum status
stats["schema_version"] = data.get("schema_version")
current_checksum = json_checksum(data)
chk_path = self.entry_manager.checksum_file
if chk_path.exists():
stored = chk_path.read_text().strip()
stats["checksum_ok"] = stored == current_checksum
else:
stored = None
stats["checksum_ok"] = False
stats["checksum"] = stored
# Relay info
cfg = self.config_manager.load_config(require_pin=False)
relays = cfg.get("relays", [])
stats["relays"] = relays
stats["relay_count"] = len(relays)
# Backup info
backups = list(
self.backup_manager.backup_dir.glob("entries_db_backup_*.json.enc")
)
stats["backup_count"] = len(backups)
stats["backup_dir"] = str(self.backup_manager.backup_dir)
stats["additional_backup_path"] = (
self.config_manager.get_additional_backup_path()
)
# Nostr sync info
manifest = getattr(self.nostr_client, "current_manifest", None)
if manifest is not None:
stats["chunk_count"] = len(manifest.chunks)
stats["delta_since"] = manifest.delta_since
else:
stats["chunk_count"] = 0
stats["delta_since"] = None
stats["pending_deltas"] = len(getattr(self.nostr_client, "_delta_events", []))
return stats
def display_stats(self) -> None:
"""Print a summary of :meth:`get_profile_stats` to the console."""
stats = self.get_profile_stats()
if not stats:
print(colored("No statistics available.", "red"))
return
print(colored("\n=== Seed Profile Stats ===", "yellow"))
print(colored(f"Total entries: {stats['total_entries']}", "cyan"))
for etype, count in stats["entries"].items():
print(colored(f" {etype}: {count}", "cyan"))
print(colored(f"Relays configured: {stats['relay_count']}", "cyan"))
print(
colored(
f"Backups: {stats['backup_count']} (dir: {stats['backup_dir']})", "cyan"
)
)
if stats.get("additional_backup_path"):
print(
colored(f"Additional backup: {stats['additional_backup_path']}", "cyan")
)
print(colored(f"Schema version: {stats['schema_version']}", "cyan"))
print(
colored(
f"Checksum ok: {'yes' if stats['checksum_ok'] else 'no'}",
"cyan",
)
)
print(colored(f"Snapshot chunks: {stats['chunk_count']}", "cyan"))
print(colored(f"Pending deltas: {stats['pending_deltas']}", "cyan"))
if stats.get("delta_since"):
print(colored(f"Latest delta id: {stats['delta_since']}", "cyan"))