Spaces:
Sleeping
Sleeping
Update src/streamlit_app.py
Browse files- src/streamlit_app.py +63 -26
src/streamlit_app.py
CHANGED
|
@@ -1,11 +1,16 @@
|
|
|
|
|
| 1 |
import streamlit as st
|
| 2 |
import pandas as pd
|
|
|
|
| 3 |
|
| 4 |
st.set_page_config(page_title="Simple Password Lookup", page_icon="๐", layout="centered")
|
| 5 |
st.title("๐ Simple Password Lookup")
|
| 6 |
-
st.caption("Upload your Excel/CSV once โ
|
|
|
|
|
|
|
|
|
|
|
|
|
| 7 |
|
| 8 |
-
# ---------------- Helpers ----------------
|
| 9 |
ALIASES = {
|
| 10 |
"name": ["name", "title", "site", "account", "platform", "service", "app"],
|
| 11 |
"username": ["username", "user", "login", "email", "userid", "id"],
|
|
@@ -15,51 +20,83 @@ ALIASES = {
|
|
| 15 |
}
|
| 16 |
EXPECTED = ["name", "username", "url", "password", "note"]
|
| 17 |
|
| 18 |
-
|
| 19 |
def standardize_columns(df: pd.DataFrame) -> pd.DataFrame:
|
| 20 |
-
|
| 21 |
df.columns = [str(c).strip().lower() for c in df.columns]
|
| 22 |
colmap = {}
|
| 23 |
-
# map aliases
|
| 24 |
for target, alias_list in ALIASES.items():
|
| 25 |
for c in df.columns:
|
| 26 |
if c in alias_list:
|
| 27 |
colmap[target] = c
|
| 28 |
break
|
| 29 |
-
# ensure all expected exist
|
| 30 |
for col in EXPECTED:
|
| 31 |
if col not in colmap:
|
| 32 |
df[col] = ""
|
| 33 |
colmap[col] = col
|
| 34 |
out = df[[colmap[c] for c in EXPECTED]].rename(columns={colmap[c]: c for c in colmap})
|
| 35 |
-
# sanitize to strings
|
| 36 |
for c in EXPECTED:
|
| 37 |
out[c] = out[c].astype(str).fillna("")
|
| 38 |
-
#
|
| 39 |
-
mask = (
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 40 |
return out[mask].reset_index(drop=True)
|
| 41 |
|
| 42 |
-
|
| 43 |
def read_any(file) -> pd.DataFrame:
|
| 44 |
-
|
|
|
|
| 45 |
if name.endswith(".csv"):
|
| 46 |
return pd.read_csv(file)
|
| 47 |
return pd.read_excel(file)
|
| 48 |
|
| 49 |
-
# ---------------- Upload once ----------------
|
| 50 |
if "creds" not in st.session_state:
|
| 51 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 52 |
up = st.file_uploader("Choose file", type=["xlsx", "xls", "csv"], accept_multiple_files=False)
|
| 53 |
-
if up
|
| 54 |
st.stop()
|
| 55 |
try:
|
| 56 |
-
df = read_any(up)
|
| 57 |
-
df = standardize_columns(df)
|
| 58 |
st.session_state.creds = df
|
| 59 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 60 |
except Exception as e:
|
| 61 |
st.error(f"Failed to read file: {e}")
|
| 62 |
st.stop()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 63 |
|
| 64 |
# ---------------- Search ----------------
|
| 65 |
st.subheader("2) Find your password")
|
|
@@ -75,25 +112,25 @@ if q.strip():
|
|
| 75 |
)
|
| 76 |
results = df[mask]
|
| 77 |
if results.empty:
|
| 78 |
-
st.warning("No matches
|
| 79 |
else:
|
| 80 |
st.caption(f"Matches: {len(results)} (showing up to 50)")
|
| 81 |
for idx, row in results.head(50).iterrows():
|
| 82 |
-
title = row[
|
| 83 |
with st.expander(f"{title} โ {row['username']} | {row['url']}"):
|
| 84 |
show = st.checkbox("Show password", key=f"show_{idx}")
|
| 85 |
st.text_input("Password", value=row["password"], type=("default" if show else "password"), key=f"pw_{idx}")
|
| 86 |
-
if
|
| 87 |
-
st.caption("Note: " +
|
| 88 |
else:
|
| 89 |
st.caption("Type a keyword above to search.")
|
| 90 |
|
| 91 |
-
# ----------------
|
| 92 |
-
with st.expander("
|
| 93 |
st.markdown(
|
| 94 |
"""
|
| 95 |
-
-
|
| 96 |
-
-
|
| 97 |
-
-
|
| 98 |
"""
|
| 99 |
)
|
|
|
|
| 1 |
+
# simple_lookup_persistent.py
|
| 2 |
import streamlit as st
|
| 3 |
import pandas as pd
|
| 4 |
+
from pathlib import Path
|
| 5 |
|
| 6 |
st.set_page_config(page_title="Simple Password Lookup", page_icon="๐", layout="centered")
|
| 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 |
+
# On Hugging Face Spaces, /data is persistent; locally we fall back to ./creds.xlsx
|
| 12 |
+
PERSIST_FILE = Path("/data/creds.xlsx") if Path("/data").exists() else Path("creds.xlsx")
|
| 13 |
|
|
|
|
| 14 |
ALIASES = {
|
| 15 |
"name": ["name", "title", "site", "account", "platform", "service", "app"],
|
| 16 |
"username": ["username", "user", "login", "email", "userid", "id"],
|
|
|
|
| 20 |
}
|
| 21 |
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 |
+
# keep rows with at least one useful field
|
| 40 |
+
mask = (
|
| 41 |
+
out["name"].str.strip() != ""
|
| 42 |
+
| out["username"].str.strip() != ""
|
| 43 |
+
| out["url"].str.strip() != ""
|
| 44 |
+
| out["password"].str.strip() != ""
|
| 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 ----------------
|
| 56 |
if "creds" not in st.session_state:
|
| 57 |
+
if PERSIST_FILE.exists():
|
| 58 |
+
try:
|
| 59 |
+
st.session_state.creds = standardize_columns(pd.read_excel(PERSIST_FILE))
|
| 60 |
+
st.info(f"Loaded saved credentials from: {PERSIST_FILE}")
|
| 61 |
+
except Exception as e:
|
| 62 |
+
st.error(f"Found {PERSIST_FILE} but failed to read it: {e}")
|
| 63 |
+
st.session_state.creds = None
|
| 64 |
+
else:
|
| 65 |
+
st.session_state.creds = None
|
| 66 |
+
|
| 67 |
+
if st.session_state.creds is None:
|
| 68 |
+
st.subheader("1) Upload your Excel/CSV (only once)")
|
| 69 |
up = st.file_uploader("Choose file", type=["xlsx", "xls", "csv"], accept_multiple_files=False)
|
| 70 |
+
if not up:
|
| 71 |
st.stop()
|
| 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)
|
| 78 |
+
st.success(f"Saved to {PERSIST_FILE}. You won't need to upload again.")
|
| 79 |
+
except Exception as e:
|
| 80 |
+
st.warning(f"Loaded for this session, but could not save for persistence: {e}")
|
| 81 |
except Exception as e:
|
| 82 |
st.error(f"Failed to read file: {e}")
|
| 83 |
st.stop()
|
| 84 |
+
else:
|
| 85 |
+
# Optional: allow replacing the saved file if needed
|
| 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:
|
| 89 |
+
try:
|
| 90 |
+
df_new = standardize_columns(read_any(new_up))
|
| 91 |
+
st.session_state.creds = df_new
|
| 92 |
+
try:
|
| 93 |
+
PERSIST_FILE.parent.mkdir(parents=True, exist_ok=True)
|
| 94 |
+
df_new.to_excel(PERSIST_FILE, index=False)
|
| 95 |
+
st.success(f"Replaced saved file at {PERSIST_FILE}.")
|
| 96 |
+
except Exception as e:
|
| 97 |
+
st.warning(f"Replaced in memory only, could not save: {e}")
|
| 98 |
+
except Exception as e:
|
| 99 |
+
st.error(f"Failed to read new file: {e}")
|
| 100 |
|
| 101 |
# ---------------- Search ----------------
|
| 102 |
st.subheader("2) Find your password")
|
|
|
|
| 112 |
)
|
| 113 |
results = df[mask]
|
| 114 |
if results.empty:
|
| 115 |
+
st.warning("No matches found.")
|
| 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("Password", value=row["password"], type=("default" if show else "password"), key=f"pw_{idx}")
|
| 123 |
+
if row.get("note", "").strip():
|
| 124 |
+
st.caption("Note: " + row["note"])
|
| 125 |
else:
|
| 126 |
st.caption("Type a keyword above to search.")
|
| 127 |
|
| 128 |
+
# ---------------- Footer ----------------
|
| 129 |
+
with st.expander("Notes"):
|
| 130 |
st.markdown(
|
| 131 |
"""
|
| 132 |
+
- Accepted columns (case-insensitive; aliases auto-mapped): **name**, **username**, **url**, **password**, *(optional)* **note**.
|
| 133 |
+
- Your first upload is saved to a persistent file so you **wonโt have to re-upload again**.
|
| 134 |
+
- Use the โReplace saved fileโ section above if you need to update your credentials later.
|
| 135 |
"""
|
| 136 |
)
|