Add list entries feature

This commit is contained in:
thePR0M3TH3AN
2025-07-05 09:24:26 -04:00
parent 20f3e7bc80
commit ce90e7348f
3 changed files with 165 additions and 92 deletions

View File

@@ -701,10 +701,11 @@ def display_menu(
1. Add Entry
2. Retrieve Entry
3. Search Entries
4. Modify an Existing Entry
5. 2FA Codes
6. Settings
7. Exit
4. List Entries
5. Modify an Existing Entry
6. 2FA Codes
7. Settings
8. Exit
"""
display_fn = getattr(password_manager, "display_stats", None)
if callable(display_fn):
@@ -729,7 +730,7 @@ def display_menu(
print(colored(menu, "cyan"))
try:
choice = timed_input(
"Enter your choice (1-7): ", inactivity_timeout
"Enter your choice (1-8): ", inactivity_timeout
).strip()
except TimeoutError:
print(colored("Session timed out. Vault locked.", "yellow"))
@@ -740,7 +741,7 @@ def display_menu(
if not choice:
print(
colored(
"No input detected. Please enter a number between 1 and 7.",
"No input detected. Please enter a number between 1 and 8.",
"yellow",
)
)
@@ -787,14 +788,17 @@ def display_menu(
password_manager.handle_search_entries()
elif choice == "4":
password_manager.update_activity()
password_manager.handle_modify_entry()
password_manager.handle_list_entries()
elif choice == "5":
password_manager.update_activity()
password_manager.handle_display_totp_codes()
password_manager.handle_modify_entry()
elif choice == "6":
password_manager.update_activity()
handle_settings(password_manager)
password_manager.handle_display_totp_codes()
elif choice == "7":
password_manager.update_activity()
handle_settings(password_manager)
elif choice == "8":
logging.info("Exiting the program.")
print(colored("Exiting the program.", "green"))
password_manager.nostr_client.close_client_pool()

View File

@@ -841,3 +841,39 @@ class EntryManager:
logger.error(f"Failed to list all entries: {e}", exc_info=True)
print(colored(f"Error: Failed to list all entries: {e}", "red"))
return
def get_entry_summaries(
self, filter_kind: str | None = None
) -> list[tuple[int, str]]:
"""Return a list of entry index and display labels."""
try:
data = self.vault.load_index()
entries_data = data.get("entries", {})
summaries: list[tuple[int, str]] = []
for idx_str, entry in entries_data.items():
etype = entry.get("type", entry.get("kind", EntryType.PASSWORD.value))
if filter_kind and etype != filter_kind:
continue
if etype == EntryType.PASSWORD.value:
label = entry.get("website", "")
elif etype == EntryType.TOTP.value:
label = entry.get("label", "")
elif etype == EntryType.SSH.value:
label = "SSH Key"
elif etype == EntryType.SEED.value:
label = "Seed Phrase"
elif etype == EntryType.NOSTR.value:
label = entry.get("label", "Nostr Key")
elif etype == EntryType.PGP.value:
label = "PGP Key"
else:
label = etype
summaries.append((int(idx_str), label))
summaries.sort(key=lambda x: x[0])
return summaries
except Exception as e:
logger.error(f"Failed to get entry summaries: {e}", exc_info=True)
print(colored(f"Error: Failed to get entry summaries: {e}", "red"))
return []

View File

@@ -1752,93 +1752,126 @@ class PasswordManager:
print(colored("\n[+] Search Results:\n", "green"))
for match in results:
index, website, username, url, blacklisted = match
entry = self.entry_manager.retrieve_entry(index)
if not entry:
continue
etype = entry.get("type", entry.get("kind", EntryType.PASSWORD.value))
print(colored(f"Index: {index}", "cyan"))
if etype == EntryType.TOTP.value:
print(colored(f" Label: {entry.get('label', website)}", "cyan"))
print(
colored(
f" Derivation Index: {entry.get('index', index)}", "cyan"
)
)
print(
colored(
f" Period: {entry.get('period', 30)}s Digits: {entry.get('digits', 6)}",
"cyan",
)
)
notes = entry.get("notes", "")
if notes:
print(colored(f" Notes: {notes}", "cyan"))
elif etype == EntryType.SEED.value:
print(colored(" Type: Seed Phrase", "cyan"))
print(colored(f" Words: {entry.get('words', 24)}", "cyan"))
print(
colored(
f" Derivation Index: {entry.get('index', index)}", "cyan"
)
)
notes = entry.get("notes", "")
if notes:
print(colored(f" Notes: {notes}", "cyan"))
elif etype == EntryType.SSH.value:
print(colored(" Type: SSH Key", "cyan"))
print(
colored(
f" Derivation Index: {entry.get('index', index)}", "cyan"
)
)
notes = entry.get("notes", "")
if notes:
print(colored(f" Notes: {notes}", "cyan"))
elif etype == EntryType.PGP.value:
print(colored(" Type: PGP Key", "cyan"))
print(
colored(
f" Key Type: {entry.get('key_type', 'ed25519')}", "cyan"
)
)
uid = entry.get("user_id", "")
if uid:
print(colored(f" User ID: {uid}", "cyan"))
print(
colored(
f" Derivation Index: {entry.get('index', index)}", "cyan"
)
)
notes = entry.get("notes", "")
if notes:
print(colored(f" Notes: {notes}", "cyan"))
elif etype == EntryType.NOSTR.value:
print(colored(" Type: Nostr Key", "cyan"))
print(colored(f" Label: {entry.get('label', '')}", "cyan"))
print(
colored(
f" Derivation Index: {entry.get('index', index)}", "cyan"
)
)
notes = entry.get("notes", "")
if notes:
print(colored(f" Notes: {notes}", "cyan"))
else:
print(colored(f" Website: {website}", "cyan"))
print(colored(f" Username: {username or 'N/A'}", "cyan"))
print(colored(f" URL: {url or 'N/A'}", "cyan"))
print(
colored(
f" Blacklisted: {'Yes' if blacklisted else 'No'}", "cyan"
)
)
print("-" * 40)
self.display_entry_details(match[0])
except Exception as e:
logging.error(f"Failed to search entries: {e}", exc_info=True)
print(colored(f"Error: Failed to search entries: {e}", "red"))
def display_entry_details(self, index: int) -> None:
"""Print detailed information for a single entry."""
entry = self.entry_manager.retrieve_entry(index)
if not entry:
return
etype = entry.get("type", entry.get("kind", EntryType.PASSWORD.value))
print(colored(f"Index: {index}", "cyan"))
if etype == EntryType.TOTP.value:
print(colored(f" Label: {entry.get('label', '')}", "cyan"))
print(colored(f" Derivation Index: {entry.get('index', index)}", "cyan"))
print(
colored(
f" Period: {entry.get('period', 30)}s Digits: {entry.get('digits', 6)}",
"cyan",
)
)
notes = entry.get("notes", "")
if notes:
print(colored(f" Notes: {notes}", "cyan"))
elif etype == EntryType.SEED.value:
print(colored(" Type: Seed Phrase", "cyan"))
print(colored(f" Words: {entry.get('words', 24)}", "cyan"))
print(colored(f" Derivation Index: {entry.get('index', index)}", "cyan"))
notes = entry.get("notes", "")
if notes:
print(colored(f" Notes: {notes}", "cyan"))
elif etype == EntryType.SSH.value:
print(colored(" Type: SSH Key", "cyan"))
print(colored(f" Derivation Index: {entry.get('index', index)}", "cyan"))
notes = entry.get("notes", "")
if notes:
print(colored(f" Notes: {notes}", "cyan"))
elif etype == EntryType.PGP.value:
print(colored(" Type: PGP Key", "cyan"))
print(colored(f" Key Type: {entry.get('key_type', 'ed25519')}", "cyan"))
uid = entry.get("user_id", "")
if uid:
print(colored(f" User ID: {uid}", "cyan"))
print(colored(f" Derivation Index: {entry.get('index', index)}", "cyan"))
notes = entry.get("notes", "")
if notes:
print(colored(f" Notes: {notes}", "cyan"))
elif etype == EntryType.NOSTR.value:
print(colored(" Type: Nostr Key", "cyan"))
print(colored(f" Label: {entry.get('label', '')}", "cyan"))
print(colored(f" Derivation Index: {entry.get('index', index)}", "cyan"))
notes = entry.get("notes", "")
if notes:
print(colored(f" Notes: {notes}", "cyan"))
else:
website = entry.get("website", "")
username = entry.get("username", "")
url = entry.get("url", "")
blacklisted = entry.get("blacklisted", False)
print(colored(f" Website: {website}", "cyan"))
print(colored(f" Username: {username or 'N/A'}", "cyan"))
print(colored(f" URL: {url or 'N/A'}", "cyan"))
print(colored(f" Blacklisted: {'Yes' if blacklisted else 'No'}", "cyan"))
print("-" * 40)
def handle_list_entries(self) -> None:
"""List entries and optionally show details."""
try:
while True:
print("\nList Entries:")
print("1. All")
print("2. Passwords")
print("3. 2FA (TOTP)")
print("4. SSH Key")
print("5. Seed Phrase")
print("6. Nostr Key Pair")
print("7. PGP")
print("8. Back")
choice = input("Select entry type: ").strip()
if choice == "1":
filter_kind = None
elif choice == "2":
filter_kind = EntryType.PASSWORD.value
elif choice == "3":
filter_kind = EntryType.TOTP.value
elif choice == "4":
filter_kind = EntryType.SSH.value
elif choice == "5":
filter_kind = EntryType.SEED.value
elif choice == "6":
filter_kind = EntryType.NOSTR.value
elif choice == "7":
filter_kind = EntryType.PGP.value
elif choice == "8":
return
else:
print(colored("Invalid choice.", "red"))
continue
summaries = self.entry_manager.get_entry_summaries(filter_kind)
if not summaries:
continue
print(colored("\n[+] Entries:\n", "green"))
for idx, label in summaries:
print(colored(f"{idx}. {label}", "cyan"))
idx_input = input(
"Enter index to view details or press Enter to return: "
).strip()
if not idx_input:
return
if not idx_input.isdigit():
print(colored("Invalid index.", "red"))
continue
self.display_entry_details(int(idx_input))
return
except Exception as e:
logging.error(f"Failed to list entries: {e}", exc_info=True)
print(colored(f"Error: Failed to list entries: {e}", "red"))
def delete_entry(self) -> None:
"""Deletes an entry from the password index."""
try: