CoolFace
Apppublic

JonoRens/gr_test_app

sourceHugging Faceupdated 11mo agoView on Hugging Face
0likes
intercom_module_hf.py517 linesDownload Raw Back to root
1import gradio as gr2import psycopg23import pandas as pd4import random5 6from pathlib import Path7import pickle8from cryptography.fernet import Fernet, InvalidToken9from typing import Optional, Dict, Any10from cryptography.fernet import Fernet, InvalidToken  # not just Fernet11import time12import json13import os14 15# ===== Hugging Face / app.py glue =====16_CACHED_NHOST_PARAMS: Dict[str, Any] = {}17 18def _params_from_env() -> Optional[Dict[str, Any]]:19    """20    Try NHOST_JSON first; otherwise look for individual env vars.21    Returns a dict suitable for psycopg2.connect or None.22    """23    # NHOST_JSON wins if present24    j = os.environ.get("NHOST_JSON", "").strip()25    if j:26        try:27            obj = json.loads(j)28            return {29                "host": obj.get("host"),30                "port": int(obj.get("port", 5432)) if obj.get("port") else None,31                "user": obj.get("user"),32                "password": obj.get("password"),33                "dbname": obj.get("dbname"),34                "sslmode": obj.get("sslmode", "require"),35            }36        except Exception:37            pass38 39    # Fallback: individual env vars40    host = os.environ.get("NHOST_HOST", "").strip()41    user = os.environ.get("NHOST_USER", "").strip()42    password = os.environ.get("NHOST_PASSWORD", "").strip()43    db = os.environ.get("NHOST_DB", "").strip() or os.environ.get("NHOST_DBNAME", "").strip()44    port = os.environ.get("NHOST_PORT", "").strip()45    sslmode = os.environ.get("NHOST_SSLMODE", "require").strip() or "require"46 47    if host and user and password and db:48        return {49            "host": host,50            "port": int(port) if port else 5432,51            "user": user,52            "password": password,53            "dbname": db,54            "sslmode": sslmode,55        }56    return None57 58 59def decrypt_nhost(enc_path: Path = None) -> Dict[str, Any]:60    """61    Mirrors property_module_hf.py:62    1) NHOST_JSON / env63    2) Decrypt nhost_params.enc with SECRET_KEY (Fernet)64    3) else raise RuntimeError (fail fast, so we don’t fall back to localhost socket)65    """66    global _CACHED_NHOST_PARAMS67    if _CACHED_NHOST_PARAMS:68        return _CACHED_NHOST_PARAMS69 70    # 1) environment71    env_params = _params_from_env()72    if env_params and env_params.get("host"):73        _CACHED_NHOST_PARAMS = env_params74        return _CACHED_NHOST_PARAMS75 76    # 2) encrypted file77    if enc_path is None:78        # locate next to app.py (cwd) or module file79        here = Path(__file__).parent80        enc_path = (Path.cwd() / "nhost_params.enc")81        if not enc_path.exists():82            enc_path = here / "nhost_params.enc"83 84    secret = os.environ.get("SECRET_KEY", "").strip()85    try:86        if not secret:87            raise RuntimeError("SECRET_KEY env var is missing.")88        if not enc_path.exists():89            raise RuntimeError("nhost_params.enc not found.")90        key = secret.encode("utf-8")91        f = Fernet(key)92        blob = enc_path.read_bytes()93        raw = f.decrypt(blob)94        obj = json.loads(raw.decode("utf-8"))95 96        _CACHED_NHOST_PARAMS = {97            "host": obj.get("host"),98            "port": int(obj.get("port", 5432)) if obj.get("port") else 5432,99            "user": obj.get("user"),100            "password": obj.get("password"),101            "dbname": obj.get("dbname"),102            "sslmode": obj.get("sslmode", "require"),103        }104        return _CACHED_NHOST_PARAMS105    except Exception as e:106        print("[WARN] decrypt_nhost(): could not decrypt nhost_params.enc →", e)107 108    # 3) fail fast109    raise RuntimeError(110        "No NHOST connection params found. "111        "Set NHOST_JSON (or NHOST_* env vars) or provide nhost_params.enc with SECRET_KEY."112    )113 114 115def get_connection() -> psycopg2.extensions.connection:116    params = decrypt_nhost()117    # sanity118    if not params.get("host"):119        raise RuntimeError("NHOST params invalid (no host).")120    return psycopg2.connect(**params)121 122 123# ─────────────────────────────────────────────124# Active User: same approach as property_module_hf.py125# ─────────────────────────────────────────────126 127# encrypted-pickle fallbacks (same names used in your other modules)128NEW_DATA_FILE = Path(__file__).parent / "openqr_active_user.pkl.enc"129NEW_KEY_FILE  = Path(__file__).parent / "openqr_secret.key"130LEGACY_DATA_FILE = Path(__file__).parent / "oqrdata.pkl.enc"131LEGACY_KEY_FILE  = Path(__file__).parent / "oqrdata.key"132 133def _read_encrypted_pickle(data_path: Path, key_path: Path):134    try:135        if not data_path.exists() or not key_path.exists():136            return None137        key = key_path.read_bytes()138        f = Fernet(key)139        blob = data_path.read_bytes()140        raw = f.decrypt(blob)141        import pickle142        return pickle.loads(raw)143    except (InvalidToken, Exception):144        return None145 146 147ActiveUserID: Optional[int] = None148 149def _coerce_uid(val) -> Optional[int]:150    try:151        iv = int(str(val).strip())152        return iv if iv > 0 else None153    except Exception:154        return None155 156def resolve_and_cache_uid(get_user_id=None) -> Optional[int]:157    global ActiveUserID158    if _coerce_uid(ActiveUserID):159        return ActiveUserID160 161    # 1) callback first162    if callable(get_user_id):163        try:164            v = _coerce_uid(get_user_id())165            if v:166                ActiveUserID = v167                return ActiveUserID168        except Exception:169            pass170 171    # 2) env next172    env_val = _coerce_uid(os.getenv("OPENQR_ACTIVE_USER_ID") or os.getenv("OQR_ACTIVE_USER_ID"))173    if env_val:174        ActiveUserID = env_val175        return ActiveUserID176 177    # 3) encrypted pickle last178    info = _read_encrypted_pickle(NEW_DATA_FILE, NEW_KEY_FILE) or _read_encrypted_pickle(LEGACY_DATA_FILE, LEGACY_KEY_FILE)179    if info:180        for k in ("ActiveUserID", "UserID", "MUserID"):181            v = _coerce_uid(info.get(k))182            if v:183                ActiveUserID = v184                return ActiveUserID185 186    ActiveUserID = None187    return None188 189 190def launch_intercom_module(get_user_id=None):191 192   # ========== UI ==========193   with gr.Blocks() as demo:194    195       with gr.Tab("Manager Intercoms at an access point"):    196 197     198            def _uid() -> Optional[int]:199                # convenience wrapper used throughout the module200                return resolve_and_cache_uid(get_user_id)201                           202            # ========== UTILS ==========203            def generate_id():204                return random.randint(100000000, 999999999)205        206            def load_property_options():207                uid =_uid()         208                if uid is None:209                    return []    210                conn = get_connection()211                cursor = conn.cursor()212                cursor.execute('SELECT "PropID", "Name", "Owner" FROM property WHERE "UserID" = %s AND "Active" = TRUE', (uid,))213                rows = cursor.fetchall()214                conn.close()215                seen = set()216                options = []217                for prop_id, name, owner in rows:218                    key = (prop_id, name, owner)219                    if key not in seen:220                        seen.add(key)221                        label = f"{name} ({owner}) [{prop_id}]"222                        value = f"{prop_id}|{name}|{owner}"223                        options.append((label, value))224                return options225        226            def sync_intercoms(prop_id):227                conn = get_connection()228                cursor = conn.cursor()229                cursor.execute('SELECT "ID", "PropID", "IntercomName", "IntercomID", "IPUnitName", "IPUnitNameMobileNo" FROM intercom WHERE "PropID" = %s', (int(prop_id),))230                rows = cursor.fetchall()231                conn.close()232                df = pd.DataFrame(rows, columns=["ID", "PropID", "IntercomName", "IntercomID", "IPUnitName", "IPUnitNameMobileNo"])233                df["IntercomID"] = df["IntercomID"].astype(str).str.replace(r"\.0$", "", regex=True)234                df_display = df.drop(columns=["ID", "PropID"])235                df_display.columns = ["Intercom's Access Point", "Access Point ID", "Unit connected to Intercom", "Mobile No for Unit"]236                return df, df_display237        238            def get_row_options(df):239                if df is None or df.empty:240                    return []241                return [f"{row['IntercomName']} ({row['IPUnitName']})" for _, row in df.iterrows()]242        243        244            def load_accesspoints(prop_id):245                conn = get_connection()246                cursor = conn.cursor()247                cursor.execute('SELECT "AccessPointID", "NameOfAccessPoint" FROM accesspoint WHERE "PropID" = %s', (int(prop_id),))248                rows = cursor.fetchall()249                conn.close()250                return [f"{name} ({ap_id})" for ap_id, name in rows]251        252            def set_from_accesspoint(ap_label):253                if not ap_label or "(" not in ap_label:254                    return "", ""255                name = ap_label.split(" (")[0]256                ap_id = ap_label.split("(")[-1].replace(")", "")257                return name, ap_id258        259            def select_property(value):260                if not value:261                    return (gr.update(visible=False), None, None, gr.update(choices=[]), gr.update(choices=[]),262                            gr.update(visible=False), gr.update(visible=False), gr.update(visible=False),263                            gr.update(value="", visible=False), gr.update(value="", visible=False),264                            gr.update(visible=False), gr.update(visible=False),265                            gr.update(choices=[], visible=False))266                prop_id, name, owner = value.split("|")267                df_full, df_display = sync_intercoms(prop_id)268                options = get_row_options(df_full)269                accesspoint_choices = load_accesspoints(prop_id)270                return (271                    gr.update(visible=True, value=df_display),272                    df_full,273                    prop_id,274                    gr.update(choices=options, value=None),275                    gr.update(choices=options, value=None),276                    gr.update(visible=True),277                    gr.update(visible=True),278                    gr.update(visible=True),279                    gr.update(value="", visible=False),280                    gr.update(value="", visible=False),281                    gr.update(visible=False),282                    gr.update(visible=False),283                    gr.update(choices=accesspoint_choices, visible=False)284                )285        286            def select_row(row_id, df):287                if not row_id or df is None or df.empty:288                    return "", "", "", "", gr.update(visible=False), gr.update(interactive=False), gr.update(interactive=True), gr.update(interactive=False), gr.update(visible=False), gr.update(visible=False)289                df["Display"] = df["IntercomName"] + " (" + df["IPUnitName"] + ")"290                match = df[df["Display"] == row_id]291                if not match.empty:292                    row = match.iloc[0]293                    return row["IntercomName"], str(row["IntercomID"]), row["IPUnitName"], str(row["IPUnitNameMobileNo"]), gr.update(visible=True), gr.update(interactive=True), gr.update(interactive=False), gr.update(interactive=False), gr.update(visible=True), gr.update(visible=True)294                return "", "", "", "", gr.update(visible=False), gr.update(interactive=False), gr.update(interactive=True), gr.update(interactive=False), gr.update(visible=False), gr.update(visible=False)295        296            def apply_edit(selected_row, df, new_name, new_intercom_id, new_unit, new_mobile, prop_id):297                if not selected_row or df is None or df.empty:298                    return df, gr.update(), gr.update(), "", "", "", "", gr.update(visible=False), gr.update(interactive=False), gr.update(interactive=True), gr.update(interactive=False), "", gr.update(visible=False)299        300                try:301                    intercom_id = int(new_intercom_id)302                except ValueError:303                    return df, gr.update(), gr.update(), new_name, new_intercom_id, new_unit, new_mobile, gr.update(visible=True, value="Intercom ID must be a valid integer"), gr.update(interactive=True), gr.update(interactive=False), gr.update(interactive=False), "", gr.update(visible=True)304                df["Display"] = df["IntercomName"] + " (" + df["IPUnitName"] + ")"305                match = df[df["Display"] == selected_row]306                if match.empty:307                    return df, ...308        309                row_id = int(match.iloc[0]["ID"])310                conn = get_connection()311                cursor = conn.cursor()312                cursor.execute(313                    'UPDATE intercom SET "IntercomName"=%s, "IntercomID"=%s, "IPUnitName"=%s, "IPUnitNameMobileNo"=%s WHERE "ID"=%s',314                    (new_name, intercom_id, new_unit, new_mobile, row_id)315                )316                conn.commit()317                conn.close()318                df_updated, df_display = sync_intercoms(prop_id)319                options = get_row_options(df_updated)320                return (321                    df_display,322                    gr.update(choices=options, value=None),  # row_to_edit323                    gr.update(choices=options, value=None),  # row_to_delete324                    "", "", "", "",325                    gr.update(visible=False),                # error_msg326                    gr.update(interactive=False),            # apply_edit_btn327                    gr.update(interactive=True),             # add_btn328                    gr.update(interactive=False),            # delete_btn329                    gr.update(visible=False)                 # cancel_edit_btn330                )331        332        333            def cancel_edit():334                return (335                    "", "", "", "", 336                    gr.update(visible=False),                     # edit_group337                    gr.update(interactive=False),                 # apply_edit_btn338                    gr.update(interactive=True),                  # add_btn339                    gr.update(interactive=False),                 # delete_btn340                    gr.update(visible=False, value="Cancel Edit"),# cancel_edit_btn (restore label)341                    gr.update(choices=[], value=None),            # accesspoint_dropdown342                    gr.update(value=None)                         # clear row_to_edit343                )344        345        346            def cancel_delete():347                return gr.update(value=None), gr.update(interactive=False), gr.update(interactive=True), gr.update(visible=False)348        349            def add_row(prop_id):350                if not prop_id:351                    return pd.DataFrame(), gr.update(), gr.update(), gr.update(), None352                new_id = generate_id()353                name = "<select intercom's access point>"354                conn = get_connection()355                cursor = conn.cursor()356                cursor.execute(357                    'INSERT INTO intercom ("ID", "PropID", "IntercomName", "IntercomID", "IPUnitName", "IPUnitNameMobileNo") VALUES (%s, %s, %s, %s, %s, %s)',358                    (new_id, int(prop_id), name, 0, "<enter intercom's Unit No>", 0)359                )360                conn.commit()361                conn.close()362                df_updated, df_display = sync_intercoms(prop_id)363                options = get_row_options(df_updated)364                return df_display, gr.update(choices=options, value=None), gr.update(choices=options, value=None), gr.update(choices=options, value=None), df_updated365        366            def enable_delete_button(row_id):367                return gr.update(interactive=bool(row_id)), gr.update(visible=bool(row_id))368        369            def delete_row(row_id, df, prop_id):370                if not row_id or df is None or df.empty:371                    return df, gr.update(), gr.update(), gr.update(interactive=False), gr.update(visible=False), df372                df["Display"] = df["IntercomName"] + " (" + df["IPUnitName"] + ")"373                match = df[df["Display"] == row_id]374                if match.empty:375                    return df, gr.update(), gr.update(), gr.update(interactive=False), gr.update(visible=False), df376                row_id_val = int(match.iloc[0]["ID"])377                conn = get_connection()378                cursor = conn.cursor()379                cursor.execute('DELETE FROM intercom WHERE "ID" = %s', (row_id_val,))380                conn.commit()381                conn.close()382                df_updated, df_display = sync_intercoms(prop_id)383                options = get_row_options(df_updated)384                return (385                    df_display,386                    gr.update(choices=options, value=None),  # row_to_edit387                    gr.update(choices=options, value=None),  # row_to_delete388                    gr.update(interactive=False),            # delete_btn389                    gr.update(visible=False),                # cancel_delete_btn390                    df_updated391                )392            def _prime_uid_then_fill_properties():393                # give callback/env time to become available (HF cold start can be slow)394                deadline = time.time() + 10.0395                while resolve_and_cache_uid(get_user_id) is None and time.time() < deadline:396                    time.sleep(0.1)397            398                uid = _uid()399                print("DEBUG (prime): starting with UID =", uid)400            401                # If still no UID, keep the dropdown visible but disabled402                if uid is None:403                    return gr.update(choices=[], value=None, interactive=False, visible=True)404            405                # Now UID is present → fetch and populate406                options = load_property_options()            # [(label, value), ...]407                # Keep choices as (label, value) so select_property receives "prop_id|name|owner"408                return gr.update(choices=options, value=None, interactive=bool(options), visible=True)409 410        411        412            # ========== UI ==========413            prop_dropdown = gr.Dropdown(choices=[""] + load_property_options(), label="Select Property", value="")414 415            btn_reload_props = gr.Button("Reload Properties", variant="secondary")416            btn_reload_props.click(_prime_uid_then_fill_properties, inputs=[], outputs=[prop_dropdown])417 418        419            intercom_table = gr.Dataframe(420                visible=False,421                headers=[422                    "Intercom's Access Point",423                    "Access Point ID",424                    "Unit connected to intercom",425                    "Mobile No for Unit"426                ]427            )428        429 430            state_df = gr.State()431            state_prop_id = gr.State()432 433            # Populate on load434            demo.load(_prime_uid_then_fill_properties, inputs=[], outputs=[prop_dropdown])            435            436 437        438            with gr.Row():439                apply_edit_btn = gr.Button("Apply Edit", interactive=False)440                add_btn = gr.Button("Add Row", interactive=False)441                delete_btn = gr.Button("Apply Delete", interactive=False)442                cancel_delete_btn = gr.Button("Cancel Delete", visible=False)443        444            with gr.Row():445                row_to_edit = gr.Dropdown(label="Select Row to Edit", choices=[""], value="")446                row_to_delete = gr.Dropdown(label="Select Row to Delete", choices=[""], value="")447        448            with gr.Group(visible=False) as edit_group:449                gr.Markdown(" Intercom details:") 450                with gr.Row():451                    accesspoint_dropdown = gr.Dropdown(label="Select Access Point at property where Intecom is located", choices=[], visible=False)452                with gr.Row():453                    edit_name = gr.Textbox(label="Intercom's Access Point [auto]", interactive=False)454                    edit_intercom_id = gr.Textbox(label="Access Point ID [auton]", interactive=False)455                with gr.Row():456                    gr.Markdown(" Unit Connected to ths Intercom:") 457                    edit_unit = gr.Textbox(label="Name of Unit Connected to this Intercom")458                    edit_mobile = gr.Textbox(label="Mobile No to CCll to Open Access Point ")459                error_msg = gr.Markdown(visible=False)460                cancel_edit_btn = gr.Button("Cancel Edit", visible=False)461        462            # ========== HOOKS ==========463            prop_dropdown.change(select_property, inputs=prop_dropdown, outputs=[464                intercom_table, state_df, state_prop_id,465                row_to_edit, row_to_delete,466                apply_edit_btn, delete_btn, add_btn,467                edit_group, error_msg,468                cancel_edit_btn, cancel_delete_btn,469                accesspoint_dropdown470            ])471        472            row_to_edit.change(select_row, inputs=[row_to_edit, state_df], outputs=[473                edit_name, edit_intercom_id, edit_unit, edit_mobile,474                edit_group, apply_edit_btn, add_btn, delete_btn, cancel_edit_btn,475                accesspoint_dropdown476            ])477        478            accesspoint_dropdown.change(set_from_accesspoint, inputs=accesspoint_dropdown, outputs=[479                edit_name, edit_intercom_id480            ])481        482            cancel_edit_btn.click(cancel_edit, outputs=[483                edit_name, edit_intercom_id, edit_unit, edit_mobile,484                edit_group, apply_edit_btn, add_btn, delete_btn, cancel_edit_btn,485                accesspoint_dropdown, row_to_edit486            ])487        488        489            row_to_delete.change(enable_delete_button, inputs=row_to_delete, outputs=[490                delete_btn, cancel_delete_btn491            ])492        493            cancel_delete_btn.click(cancel_delete, outputs=[494                row_to_delete, delete_btn, add_btn, cancel_delete_btn495            ])496        497            apply_edit_btn.click(apply_edit, inputs=[498                row_to_edit, state_df, edit_name, edit_intercom_id, edit_unit, edit_mobile, state_prop_id499            ], outputs=[500                intercom_table, row_to_edit, row_to_delete,501                edit_name, edit_intercom_id, edit_unit, edit_mobile,502                error_msg, apply_edit_btn, add_btn, delete_btn, cancel_edit_btn503            ])504        505            add_btn.click(add_row, inputs=state_prop_id, outputs=[506                intercom_table, row_to_edit, row_to_delete, row_to_edit, state_df507            ])508        509            delete_btn.click(delete_row, inputs=[row_to_delete, state_df, state_prop_id], outputs=[510                intercom_table, row_to_edit, row_to_delete, delete_btn, cancel_delete_btn, state_df511            ])512        513    514   return demo515 516#app.launch(share=True)517