# app.py # Minimal Streamlit status board using ONLY built-in components (no Altair). # - Connects directly to Postgres # - Orders rows: ORDER BY ref_x DESC, ref_12 DESC, ref_16 DESC # - Fixed column order: ref_x, ref_12, ref_16 # - Each "dot" is a clickable link button to your File Browser URL # - Two generic action buttons per row (function placeholders provided) import os from urllib.parse import quote import pandas as pd import streamlit as st from sqlalchemy import create_engine, text # ----------------------------- # Configuration # ----------------------------- st.set_page_config(page_title="File Processing Status", layout="wide") # Hide chrome for a minimal look st.markdown( """ """, unsafe_allow_html=True, ) # DB config (fill via st.secrets or env) DB_HOST = "postgres" DB_PORT = "5432" DB_NAME = "mydatabase" DB_USER = "myuser" DB_PASSWORD = "mypassword" TABLE_NAME = "decorrupt" # Optional: restrict columns; leave empty to auto-detect REF_PREFIX = "ref_" # File Browser base (adjust to your host/IP) FILEBROWSER_BASE = "http://192.168.1.229:8081/files" # Expected ref columns (fixed order) REF_COLS = ["ref_x", "ref_12", "ref_16"] # Dot icons for statuses (built-in emoji; no custom CSS/JS) DOTS = { -1: "🔴", # unknown/error 0: "⚪", # not processing (gray/white) 1: "🟠", # processing (orange) 2: "🟢", # processed (green) } LABELS = {0: "not processing", 1: "processing", 2: "processed", -1: "unknown"} def update_status(filename: str, column_name: str, new_value: int) -> bool: """ Update a single column for a given filename in the specified table. Args: filename (str): The filename to update. table_name (str): The table where the row exists. column_name (str): The column to update (e.g., 'ref_x'). new_value (int): The new status value (e.g., 0, 1, 2). Returns: bool: True if update succeeded, False otherwise. """ sql = text(f'UPDATE "decorrupt" SET "{column_name}" = :new_value WHERE filename = :filename') try: engine = get_engine() # reuse your existing get_engine() with engine.begin() as conn: # auto-commits or rolls back result = conn.execute(sql, {"new_value": new_value, "filename": filename}) return result.rowcount > 0 except Exception as e: st.error(f"Failed to update {filename}.{column_name}: {e}") return False # ----------------------------- # DB helpers # ----------------------------- def get_engine(): url = f"postgresql+psycopg2://{DB_USER}:{DB_PASSWORD}@{DB_HOST}:{DB_PORT}/{DB_NAME}" return create_engine(url, pool_pre_ping=True) def load_data(table_name: str) -> pd.DataFrame: query = text(f'SELECT * FROM "{table_name}" WHERE isdeleted = 0 ORDER BY filename') with get_engine().connect() as conn: return pd.read_sql(query, conn) # ----------------------------- # Actions (function placeholders) # ----------------------------- def resolve_record(filename: str): update_status(filename = filename, column_name = "isresolved", new_value = 1) st.toast(f"{filename} resolved") def delete_record(filename: str): update_status(filename = filename, column_name = "isdeleted", new_value = 1) st.toast(f"{filename} deleted") # ----------------------------- # Load & validate # ----------------------------- st.title("File Processing Status") st.caption("0 = not processing · 1 = processing · 2 = processed") if "filename', unsafe_allow_html=True) c[1].markdown(f'
{REF_COLS[0]}
', unsafe_allow_html=True) c[2].markdown(f'
{REF_COLS[1]}
', unsafe_allow_html=True) c[3].markdown(f'
{REF_COLS[2]}
', unsafe_allow_html=True) c[4].markdown('
actions
', unsafe_allow_html=True) # ----------------------------- # Grid rows (built-in components) # ----------------------------- for idx, fname in enumerate(filenames_in_order): row = df.iloc[idx] col_widths = [6, 2, 2, 2, 3] c = st.columns(col_widths, vertical_alignment="center") # Filename with c[0]: st.markdown(f'
{fname}
', unsafe_allow_html=True) # For each ref column, draw a link button with a colored dot for j, ref in enumerate(REF_COLS, start=1): status = int(row.get(ref, -1)) dot = DOTS.get(status, DOTS[-1]) url = f"{FILEBROWSER_BASE}/{quote(str(fname))}_fixed-s1_{ref}.MP4" with c[j]: st.link_button(dot, url, use_container_width=True) # Actions (only show if resolved != 1) if int(row.get("isresolved", 0)) != 1: with c[4]: aa, bb = st.columns(2, vertical_alignment="center") if aa.button("resolve", key=f"{fname}-resolve"): resolve_record(fname) if bb.button("delete", key=f"{fname}-delete"): delete_record(fname) else: with c[4]: st.markdown('Resolved', unsafe_allow_html=True) # ----------------------------- # Optional: Raw data view # ----------------------------- with st.expander("Raw data"): st.dataframe(df[["filename"] + present_refs], use_container_width=True)