mirror of
https://github.com/PR0M3TH3AN/SeedPass.git
synced 2025-09-09 07:48:57 +00:00
Add profile stats feature and menu option
This commit is contained in:
@@ -254,6 +254,8 @@ Back in the Settings menu you can:
|
|||||||
* Choose `9` to set an additional backup location.
|
* Choose `9` to set an additional backup location.
|
||||||
* Select `10` to change the inactivity timeout.
|
* Select `10` to change the inactivity timeout.
|
||||||
* Choose `11` to lock the vault and require re-entry of your password.
|
* 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
|
## Running Tests
|
||||||
|
|
||||||
|
17
src/main.py
17
src/main.py
@@ -222,6 +222,17 @@ def handle_display_npub(password_manager: PasswordManager):
|
|||||||
print(colored(f"Error: Failed to display npub: {e}", "red"))
|
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(
|
def handle_post_to_nostr(
|
||||||
password_manager: PasswordManager, alt_summary: str | None = None
|
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("10. Set inactivity timeout")
|
||||||
print("11. Lock Vault")
|
print("11. Lock Vault")
|
||||||
print("12. Back")
|
print("12. Back")
|
||||||
|
print("13. Stats")
|
||||||
choice = input("Select an option: ").strip()
|
choice = input("Select an option: ").strip()
|
||||||
if choice == "1":
|
if choice == "1":
|
||||||
handle_profiles_menu(password_manager)
|
handle_profiles_menu(password_manager)
|
||||||
@@ -585,6 +597,8 @@ def handle_settings(password_manager: PasswordManager) -> None:
|
|||||||
password_manager.unlock_vault()
|
password_manager.unlock_vault()
|
||||||
elif choice == "12":
|
elif choice == "12":
|
||||||
break
|
break
|
||||||
|
elif choice == "13":
|
||||||
|
handle_display_stats(password_manager)
|
||||||
else:
|
else:
|
||||||
print(colored("Invalid choice.", "red"))
|
print(colored("Invalid choice.", "red"))
|
||||||
|
|
||||||
@@ -606,6 +620,9 @@ def display_menu(
|
|||||||
5. Settings
|
5. Settings
|
||||||
6. Exit
|
6. Exit
|
||||||
"""
|
"""
|
||||||
|
display_fn = getattr(password_manager, "display_stats", None)
|
||||||
|
if callable(display_fn):
|
||||||
|
display_fn()
|
||||||
while True:
|
while True:
|
||||||
if time.time() - password_manager.last_activity > inactivity_timeout:
|
if time.time() - password_manager.last_activity > inactivity_timeout:
|
||||||
print(colored("Session timed out. Vault locked.", "yellow"))
|
print(colored("Session timed out. Vault locked.", "yellow"))
|
||||||
|
@@ -34,7 +34,7 @@ from utils.key_derivation import (
|
|||||||
derive_index_key,
|
derive_index_key,
|
||||||
EncryptionMode,
|
EncryptionMode,
|
||||||
)
|
)
|
||||||
from utils.checksum import calculate_checksum, verify_checksum
|
from utils.checksum import calculate_checksum, verify_checksum, json_checksum
|
||||||
from utils.password_prompt import (
|
from utils.password_prompt import (
|
||||||
prompt_for_password,
|
prompt_for_password,
|
||||||
prompt_existing_password,
|
prompt_existing_password,
|
||||||
@@ -1826,3 +1826,93 @@ class PasswordManager:
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
logging.error(f"Failed to change password: {e}", exc_info=True)
|
logging.error(f"Failed to change password: {e}", exc_info=True)
|
||||||
print(colored(f"Error: Failed to change password: {e}", "red"))
|
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"))
|
||||||
|
Reference in New Issue
Block a user