JonoRens/gr_test_app
0
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 