Spaces:
Sleeping
Sleeping
Update src/streamlit_app.py
Browse files- src/streamlit_app.py +30 -19
src/streamlit_app.py
CHANGED
|
@@ -8,7 +8,7 @@ st.title("π Simple Password Lookup")
|
|
| 8 |
st.caption("Upload your Excel/CSV once β it will be saved for future sessions β search to reveal passwords.")
|
| 9 |
|
| 10 |
# ---------------- Config ----------------
|
| 11 |
-
#
|
| 12 |
PERSIST_FILE = Path("/data/creds.xlsx") if Path("/data").exists() else Path("creds.xlsx")
|
| 13 |
|
| 14 |
ALIASES = {
|
|
@@ -22,34 +22,38 @@ EXPECTED = ["name", "username", "url", "password", "note"]
|
|
| 22 |
|
| 23 |
def standardize_columns(df: pd.DataFrame) -> pd.DataFrame:
|
| 24 |
"""Map common header aliases -> EXPECTED, create missing columns, clean strings."""
|
|
|
|
| 25 |
df.columns = [str(c).strip().lower() for c in df.columns]
|
| 26 |
colmap = {}
|
|
|
|
| 27 |
for target, alias_list in ALIASES.items():
|
| 28 |
for c in df.columns:
|
| 29 |
if c in alias_list:
|
| 30 |
colmap[target] = c
|
| 31 |
break
|
|
|
|
| 32 |
for col in EXPECTED:
|
| 33 |
if col not in colmap:
|
| 34 |
df[col] = ""
|
| 35 |
colmap[col] = col
|
|
|
|
| 36 |
out = df[[colmap[c] for c in EXPECTED]].rename(columns={colmap[c]: c for c in colmap})
|
|
|
|
| 37 |
for c in EXPECTED:
|
| 38 |
-
out[c] = out[c].astype(str).fillna("")
|
| 39 |
-
|
| 40 |
-
|
| 41 |
-
|
| 42 |
-
|
| 43 |
-
|
| 44 |
-
|
| 45 |
-
)
|
| 46 |
-
return out[mask].reset_index(drop=True)
|
| 47 |
|
| 48 |
def read_any(file) -> pd.DataFrame:
|
| 49 |
-
"""Read CSV or Excel from an uploaded file-like."""
|
| 50 |
name = (getattr(file, "name", "") or "").lower()
|
| 51 |
if name.endswith(".csv"):
|
| 52 |
return pd.read_csv(file)
|
|
|
|
| 53 |
return pd.read_excel(file)
|
| 54 |
|
| 55 |
# ---------------- Load or Upload once ----------------
|
|
@@ -72,6 +76,7 @@ if st.session_state.creds is None:
|
|
| 72 |
try:
|
| 73 |
df = standardize_columns(read_any(up))
|
| 74 |
st.session_state.creds = df
|
|
|
|
| 75 |
try:
|
| 76 |
PERSIST_FILE.parent.mkdir(parents=True, exist_ok=True)
|
| 77 |
df.to_excel(PERSIST_FILE, index=False)
|
|
@@ -82,7 +87,7 @@ if st.session_state.creds is None:
|
|
| 82 |
st.error(f"Failed to read file: {e}")
|
| 83 |
st.stop()
|
| 84 |
else:
|
| 85 |
-
# Optional: allow replacing the saved file
|
| 86 |
with st.expander("Replace saved file (optional)"):
|
| 87 |
new_up = st.file_uploader("Upload new Excel/CSV to replace saved file", type=["xlsx", "xls", "csv"], key="replacer")
|
| 88 |
if new_up is not None:
|
|
@@ -105,10 +110,11 @@ q = st.text_input("Search by platform/site/username", placeholder="e.g., netflix
|
|
| 105 |
if q.strip():
|
| 106 |
Q = q.lower().strip()
|
| 107 |
df = st.session_state.creds
|
|
|
|
| 108 |
mask = (
|
| 109 |
-
df["name"].str.lower().str.contains(Q)
|
| 110 |
-
| df["username"].str.lower().str.contains(Q)
|
| 111 |
-
| df["url"].str.lower().str.contains(Q)
|
| 112 |
)
|
| 113 |
results = df[mask]
|
| 114 |
if results.empty:
|
|
@@ -116,12 +122,17 @@ if q.strip():
|
|
| 116 |
else:
|
| 117 |
st.caption(f"Matches: {len(results)} (showing up to 50)")
|
| 118 |
for idx, row in results.head(50).iterrows():
|
| 119 |
-
title = row["name"] or row["username"] or row["url"]
|
| 120 |
with st.expander(f"{title} β {row['username']} | {row['url']}"):
|
| 121 |
show = st.checkbox("Show password", key=f"show_{idx}")
|
| 122 |
-
st.text_input(
|
| 123 |
-
|
| 124 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 125 |
else:
|
| 126 |
st.caption("Type a keyword above to search.")
|
| 127 |
|
|
|
|
| 8 |
st.caption("Upload your Excel/CSV once β it will be saved for future sessions β search to reveal passwords.")
|
| 9 |
|
| 10 |
# ---------------- Config ----------------
|
| 11 |
+
# Hugging Face Spaces mounts /data as persistent storage; locally fall back to ./creds.xlsx
|
| 12 |
PERSIST_FILE = Path("/data/creds.xlsx") if Path("/data").exists() else Path("creds.xlsx")
|
| 13 |
|
| 14 |
ALIASES = {
|
|
|
|
| 22 |
|
| 23 |
def standardize_columns(df: pd.DataFrame) -> pd.DataFrame:
|
| 24 |
"""Map common header aliases -> EXPECTED, create missing columns, clean strings."""
|
| 25 |
+
# Normalize headers
|
| 26 |
df.columns = [str(c).strip().lower() for c in df.columns]
|
| 27 |
colmap = {}
|
| 28 |
+
# Map aliases
|
| 29 |
for target, alias_list in ALIASES.items():
|
| 30 |
for c in df.columns:
|
| 31 |
if c in alias_list:
|
| 32 |
colmap[target] = c
|
| 33 |
break
|
| 34 |
+
# Ensure all expected columns exist
|
| 35 |
for col in EXPECTED:
|
| 36 |
if col not in colmap:
|
| 37 |
df[col] = ""
|
| 38 |
colmap[col] = col
|
| 39 |
+
|
| 40 |
out = df[[colmap[c] for c in EXPECTED]].rename(columns={colmap[c]: c for c in colmap})
|
| 41 |
+
# Clean to strings
|
| 42 |
for c in EXPECTED:
|
| 43 |
+
out[c] = out[c].astype(str).fillna("").replace("nan", "")
|
| 44 |
+
|
| 45 |
+
# β
Keep rows that have at least one non-empty (after strip) among key fields
|
| 46 |
+
key_cols = ["name", "username", "url", "password"]
|
| 47 |
+
mask = out[key_cols].applymap(lambda x: str(x).strip() != "").any(axis=1)
|
| 48 |
+
out = out[mask].reset_index(drop=True)
|
| 49 |
+
return out
|
|
|
|
|
|
|
| 50 |
|
| 51 |
def read_any(file) -> pd.DataFrame:
|
| 52 |
+
"""Read CSV or Excel from an uploaded file-like object."""
|
| 53 |
name = (getattr(file, "name", "") or "").lower()
|
| 54 |
if name.endswith(".csv"):
|
| 55 |
return pd.read_csv(file)
|
| 56 |
+
# Default to Excel
|
| 57 |
return pd.read_excel(file)
|
| 58 |
|
| 59 |
# ---------------- Load or Upload once ----------------
|
|
|
|
| 76 |
try:
|
| 77 |
df = standardize_columns(read_any(up))
|
| 78 |
st.session_state.creds = df
|
| 79 |
+
# Try to persist for future sessions
|
| 80 |
try:
|
| 81 |
PERSIST_FILE.parent.mkdir(parents=True, exist_ok=True)
|
| 82 |
df.to_excel(PERSIST_FILE, index=False)
|
|
|
|
| 87 |
st.error(f"Failed to read file: {e}")
|
| 88 |
st.stop()
|
| 89 |
else:
|
| 90 |
+
# Optional: allow replacing the saved file
|
| 91 |
with st.expander("Replace saved file (optional)"):
|
| 92 |
new_up = st.file_uploader("Upload new Excel/CSV to replace saved file", type=["xlsx", "xls", "csv"], key="replacer")
|
| 93 |
if new_up is not None:
|
|
|
|
| 110 |
if q.strip():
|
| 111 |
Q = q.lower().strip()
|
| 112 |
df = st.session_state.creds
|
| 113 |
+
# Robust contains with na=False to avoid NaN issues
|
| 114 |
mask = (
|
| 115 |
+
df["name"].str.lower().str.contains(Q, na=False)
|
| 116 |
+
| df["username"].str.lower().str.contains(Q, na=False)
|
| 117 |
+
| df["url"].str.lower().str.contains(Q, na=False)
|
| 118 |
)
|
| 119 |
results = df[mask]
|
| 120 |
if results.empty:
|
|
|
|
| 122 |
else:
|
| 123 |
st.caption(f"Matches: {len(results)} (showing up to 50)")
|
| 124 |
for idx, row in results.head(50).iterrows():
|
| 125 |
+
title = (row["name"] or row["username"] or row["url"]).strip()
|
| 126 |
with st.expander(f"{title} β {row['username']} | {row['url']}"):
|
| 127 |
show = st.checkbox("Show password", key=f"show_{idx}")
|
| 128 |
+
st.text_input(
|
| 129 |
+
"Password",
|
| 130 |
+
value=row["password"],
|
| 131 |
+
type=("default" if show else "password"),
|
| 132 |
+
key=f"pw_{idx}",
|
| 133 |
+
)
|
| 134 |
+
if str(row.get("note", "")).strip():
|
| 135 |
+
st.caption("Note: " + str(row["note"]))
|
| 136 |
else:
|
| 137 |
st.caption("Type a keyword above to search.")
|
| 138 |
|