Spaces:
Sleeping
Sleeping
Rick commited on
Commit ·
b4e9a3a
1
Parent(s): 459a547
Add DB write-lock setting with HF-friendly runtime controls
Browse files- app.py +36 -0
- db_store.py +33 -6
- static/js/settings.js +14 -2
- templates/index.html +7 -0
app.py
CHANGED
|
@@ -111,6 +111,8 @@ from db_store import (
|
|
| 111 |
set_context_payload,
|
| 112 |
update_item_verified,
|
| 113 |
upsert_inventory_item,
|
|
|
|
|
|
|
| 114 |
)
|
| 115 |
|
| 116 |
logger = logging.getLogger("uvicorn.error")
|
|
@@ -327,6 +329,8 @@ PREVIOUS_DATA_ROOT_DB = DATA_ROOT / "app.db"
|
|
| 327 |
SEED_DB_LOCAL = APP_HOME / "seed" / "app.db"
|
| 328 |
# Remote seeding disabled by default to avoid unintended downloads; set SEED_DB_URL to enable.
|
| 329 |
SEED_DB_URL = os.environ.get("SEED_DB_URL") or None
|
|
|
|
|
|
|
| 330 |
|
| 331 |
|
| 332 |
def _is_valid_sqlite(path: Path) -> bool:
|
|
@@ -418,6 +422,30 @@ def _bootstrap_db(force: bool = False):
|
|
| 418 |
_bootstrap_db()
|
| 419 |
configure_db(DB_PATH)
|
| 420 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 421 |
DEFAULT_store_LABEL = "Default"
|
| 422 |
DEFAULT_store = None
|
| 423 |
|
|
@@ -1121,6 +1149,8 @@ def get_defaults():
|
|
| 1121 |
"rep_penalty": 1.1,
|
| 1122 |
"mission_context": "Isolated Medical Station offshore.",
|
| 1123 |
"user_mode": "user",
|
|
|
|
|
|
|
| 1124 |
"last_prompt_verbatim": "",
|
| 1125 |
"vaccine_types": [
|
| 1126 |
"Diphtheria, Tetanus, and Pertussis (DTaP/Tdap)",
|
|
@@ -1260,7 +1290,9 @@ def db_op(cat, data=None, store=None):
|
|
| 1260 |
set_settings_meta(
|
| 1261 |
user_mode=data.get("user_mode"),
|
| 1262 |
offline_force_flags=data.get("offline_force_flags"),
|
|
|
|
| 1263 |
)
|
|
|
|
| 1264 |
return {**get_defaults(), **data}
|
| 1265 |
if cat == "inventory":
|
| 1266 |
if not isinstance(data, list):
|
|
@@ -2526,6 +2558,8 @@ async def db_status():
|
|
| 2526 |
"stores": 1,
|
| 2527 |
"crew_rows": crew,
|
| 2528 |
"vessel_rows": vessel,
|
|
|
|
|
|
|
| 2529 |
}
|
| 2530 |
except Exception as e:
|
| 2531 |
return JSONResponse({"error": str(e)}, status_code=status.HTTP_500_INTERNAL_SERVER_ERROR)
|
|
@@ -2550,6 +2584,7 @@ async def db_create():
|
|
| 2550 |
if DB_PATH.exists():
|
| 2551 |
DB_PATH.unlink()
|
| 2552 |
configure_db(DB_PATH)
|
|
|
|
| 2553 |
_store_dirs(DEFAULT_store_LABEL)
|
| 2554 |
return {"status": "created"}
|
| 2555 |
except Exception as e:
|
|
@@ -2580,6 +2615,7 @@ async def db_upload(file: UploadFile = File(...)):
|
|
| 2580 |
tmp.close()
|
| 2581 |
shutil.move(tmp.name, DB_PATH)
|
| 2582 |
configure_db(DB_PATH)
|
|
|
|
| 2583 |
_store_dirs(DEFAULT_store_LABEL)
|
| 2584 |
return {"status": "uploaded"}
|
| 2585 |
except Exception as e:
|
|
|
|
| 111 |
set_context_payload,
|
| 112 |
update_item_verified,
|
| 113 |
upsert_inventory_item,
|
| 114 |
+
set_db_write_lock,
|
| 115 |
+
get_db_write_lock,
|
| 116 |
)
|
| 117 |
|
| 118 |
logger = logging.getLogger("uvicorn.error")
|
|
|
|
| 329 |
SEED_DB_LOCAL = APP_HOME / "seed" / "app.db"
|
| 330 |
# Remote seeding disabled by default to avoid unintended downloads; set SEED_DB_URL to enable.
|
| 331 |
SEED_DB_URL = os.environ.get("SEED_DB_URL") or None
|
| 332 |
+
_DB_WRITE_LOCK_ENV = (os.environ.get("DB_WRITE_LOCK") or "").strip().lower()
|
| 333 |
+
DB_WRITE_LOCK_FORCED = _DB_WRITE_LOCK_ENV in {"1", "true", "yes", "on"} if _DB_WRITE_LOCK_ENV else None
|
| 334 |
|
| 335 |
|
| 336 |
def _is_valid_sqlite(path: Path) -> bool:
|
|
|
|
| 422 |
_bootstrap_db()
|
| 423 |
configure_db(DB_PATH)
|
| 424 |
|
| 425 |
+
|
| 426 |
+
def _apply_db_write_lock_setting(candidate=None):
|
| 427 |
+
"""
|
| 428 |
+
Apply DB write-lock policy.
|
| 429 |
+
|
| 430 |
+
Priority:
|
| 431 |
+
1) Environment override DB_WRITE_LOCK (if set)
|
| 432 |
+
2) Persisted settings_meta.db_write_lock
|
| 433 |
+
"""
|
| 434 |
+
if DB_WRITE_LOCK_FORCED is not None:
|
| 435 |
+
set_db_write_lock(DB_WRITE_LOCK_FORCED)
|
| 436 |
+
return DB_WRITE_LOCK_FORCED
|
| 437 |
+
if candidate is None:
|
| 438 |
+
try:
|
| 439 |
+
meta = get_settings_meta() or {}
|
| 440 |
+
candidate = bool(meta.get("db_write_lock"))
|
| 441 |
+
except Exception:
|
| 442 |
+
candidate = False
|
| 443 |
+
set_db_write_lock(bool(candidate))
|
| 444 |
+
return bool(candidate)
|
| 445 |
+
|
| 446 |
+
|
| 447 |
+
_apply_db_write_lock_setting()
|
| 448 |
+
|
| 449 |
DEFAULT_store_LABEL = "Default"
|
| 450 |
DEFAULT_store = None
|
| 451 |
|
|
|
|
| 1149 |
"rep_penalty": 1.1,
|
| 1150 |
"mission_context": "Isolated Medical Station offshore.",
|
| 1151 |
"user_mode": "user",
|
| 1152 |
+
"db_write_lock": bool(get_db_write_lock()),
|
| 1153 |
+
"db_write_lock_forced": DB_WRITE_LOCK_FORCED is not None,
|
| 1154 |
"last_prompt_verbatim": "",
|
| 1155 |
"vaccine_types": [
|
| 1156 |
"Diphtheria, Tetanus, and Pertussis (DTaP/Tdap)",
|
|
|
|
| 1290 |
set_settings_meta(
|
| 1291 |
user_mode=data.get("user_mode"),
|
| 1292 |
offline_force_flags=data.get("offline_force_flags"),
|
| 1293 |
+
db_write_lock=data.get("db_write_lock"),
|
| 1294 |
)
|
| 1295 |
+
_apply_db_write_lock_setting(data.get("db_write_lock"))
|
| 1296 |
return {**get_defaults(), **data}
|
| 1297 |
if cat == "inventory":
|
| 1298 |
if not isinstance(data, list):
|
|
|
|
| 2558 |
"stores": 1,
|
| 2559 |
"crew_rows": crew,
|
| 2560 |
"vessel_rows": vessel,
|
| 2561 |
+
"db_write_lock": bool(get_db_write_lock()),
|
| 2562 |
+
"db_write_lock_forced": DB_WRITE_LOCK_FORCED is not None,
|
| 2563 |
}
|
| 2564 |
except Exception as e:
|
| 2565 |
return JSONResponse({"error": str(e)}, status_code=status.HTTP_500_INTERNAL_SERVER_ERROR)
|
|
|
|
| 2584 |
if DB_PATH.exists():
|
| 2585 |
DB_PATH.unlink()
|
| 2586 |
configure_db(DB_PATH)
|
| 2587 |
+
_apply_db_write_lock_setting()
|
| 2588 |
_store_dirs(DEFAULT_store_LABEL)
|
| 2589 |
return {"status": "created"}
|
| 2590 |
except Exception as e:
|
|
|
|
| 2615 |
tmp.close()
|
| 2616 |
shutil.move(tmp.name, DB_PATH)
|
| 2617 |
configure_db(DB_PATH)
|
| 2618 |
+
_apply_db_write_lock_setting()
|
| 2619 |
_store_dirs(DEFAULT_store_LABEL)
|
| 2620 |
return {"status": "uploaded"}
|
| 2621 |
except Exception as e:
|
db_store.py
CHANGED
|
@@ -26,6 +26,7 @@ from typing import Optional, Any, Dict
|
|
| 26 |
logger = logging.getLogger("uvicorn.error")
|
| 27 |
|
| 28 |
DB_PATH: Path
|
|
|
|
| 29 |
TRIAGE_TREE_DEFAULT_JSON_PATH = Path(__file__).resolve().parent / "seed" / "triage_prompt_tree.default.json"
|
| 30 |
|
| 31 |
|
|
@@ -39,6 +40,17 @@ def configure_db(path: Path):
|
|
| 39 |
_upgrade_schema()
|
| 40 |
|
| 41 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 42 |
def _conn():
|
| 43 |
"""
|
| 44 |
Conn helper.
|
|
@@ -50,6 +62,10 @@ def _conn():
|
|
| 50 |
conn.execute("PRAGMA foreign_keys = ON;")
|
| 51 |
# Keep temp tables in memory to avoid filesystem issues when sorting large BLOB rows
|
| 52 |
conn.execute("PRAGMA temp_store = MEMORY;")
|
|
|
|
|
|
|
|
|
|
|
|
|
| 53 |
except Exception:
|
| 54 |
pass
|
| 55 |
return conn
|
|
@@ -242,6 +258,7 @@ def _init_db():
|
|
| 242 |
id INTEGER PRIMARY KEY CHECK (id = 1),
|
| 243 |
user_mode TEXT,
|
| 244 |
offline_force_flags INTEGER DEFAULT 0,
|
|
|
|
| 245 |
last_prompt_verbatim TEXT,
|
| 246 |
updated_at TEXT NOT NULL
|
| 247 |
);
|
|
@@ -857,21 +874,24 @@ def _maybe_migrate_settings_meta(conn, now):
|
|
| 857 |
data = {}
|
| 858 |
user_mode = data.get("user_mode")
|
| 859 |
offline_force_flags = 1 if data.get("offline_force_flags") else 0
|
|
|
|
| 860 |
last_prompt_verbatim = data.get("last_prompt_verbatim")
|
| 861 |
_ensure_settings_meta_columns(conn)
|
| 862 |
conn.execute(
|
| 863 |
"""
|
| 864 |
-
INSERT INTO settings_meta(id, user_mode, offline_force_flags, last_prompt_verbatim, updated_at)
|
| 865 |
-
VALUES(1, :user_mode, :offline_force_flags, :last_prompt_verbatim, :updated_at)
|
| 866 |
ON CONFLICT(id) DO UPDATE SET
|
| 867 |
user_mode=excluded.user_mode,
|
| 868 |
offline_force_flags=excluded.offline_force_flags,
|
|
|
|
| 869 |
last_prompt_verbatim=excluded.last_prompt_verbatim,
|
| 870 |
updated_at=excluded.updated_at;
|
| 871 |
""",
|
| 872 |
{
|
| 873 |
"user_mode": user_mode,
|
| 874 |
"offline_force_flags": offline_force_flags,
|
|
|
|
| 875 |
"last_prompt_verbatim": last_prompt_verbatim,
|
| 876 |
"updated_at": now,
|
| 877 |
},
|
|
@@ -2215,6 +2235,8 @@ def _ensure_settings_meta_columns(conn):
|
|
| 2215 |
names = {c["name"] for c in cols}
|
| 2216 |
if "last_prompt_verbatim" not in names:
|
| 2217 |
conn.execute("ALTER TABLE settings_meta ADD COLUMN last_prompt_verbatim TEXT;")
|
|
|
|
|
|
|
| 2218 |
except Exception as exc:
|
| 2219 |
logger.warning("Unable to add settings_meta columns: %s", exc)
|
| 2220 |
|
|
@@ -3781,13 +3803,14 @@ def get_settings_meta():
|
|
| 3781 |
with _conn() as conn:
|
| 3782 |
_ensure_settings_meta_columns(conn)
|
| 3783 |
row = conn.execute(
|
| 3784 |
-
"SELECT user_mode, offline_force_flags, last_prompt_verbatim FROM settings_meta WHERE id=1"
|
| 3785 |
).fetchone()
|
| 3786 |
if not row:
|
| 3787 |
return {}
|
| 3788 |
return {
|
| 3789 |
"user_mode": row["user_mode"],
|
| 3790 |
"offline_force_flags": bool(row["offline_force_flags"]),
|
|
|
|
| 3791 |
"last_prompt_verbatim": row["last_prompt_verbatim"],
|
| 3792 |
}
|
| 3793 |
|
|
@@ -3795,6 +3818,7 @@ def get_settings_meta():
|
|
| 3795 |
def set_settings_meta(
|
| 3796 |
user_mode: str = None,
|
| 3797 |
offline_force_flags: bool = None,
|
|
|
|
| 3798 |
last_prompt_verbatim: str = None,
|
| 3799 |
):
|
| 3800 |
"""
|
|
@@ -3805,17 +3829,18 @@ def set_settings_meta(
|
|
| 3805 |
with _conn() as conn:
|
| 3806 |
_ensure_settings_meta_columns(conn)
|
| 3807 |
existing = conn.execute(
|
| 3808 |
-
"SELECT user_mode, offline_force_flags, last_prompt_verbatim FROM settings_meta WHERE id=1"
|
| 3809 |
).fetchone()
|
| 3810 |
if existing is None:
|
| 3811 |
conn.execute(
|
| 3812 |
"""
|
| 3813 |
-
INSERT INTO settings_meta(id, user_mode, offline_force_flags, last_prompt_verbatim, updated_at)
|
| 3814 |
-
VALUES(1, :user_mode, :offline_force_flags, :last_prompt_verbatim, :updated_at)
|
| 3815 |
""",
|
| 3816 |
{
|
| 3817 |
"user_mode": user_mode,
|
| 3818 |
"offline_force_flags": 1 if offline_force_flags else 0,
|
|
|
|
| 3819 |
"last_prompt_verbatim": last_prompt_verbatim,
|
| 3820 |
"updated_at": now,
|
| 3821 |
},
|
|
@@ -3826,6 +3851,7 @@ def set_settings_meta(
|
|
| 3826 |
UPDATE settings_meta
|
| 3827 |
SET user_mode=COALESCE(:user_mode, user_mode),
|
| 3828 |
offline_force_flags=COALESCE(:offline_force_flags, offline_force_flags),
|
|
|
|
| 3829 |
last_prompt_verbatim=COALESCE(:last_prompt_verbatim, last_prompt_verbatim),
|
| 3830 |
updated_at=:updated_at
|
| 3831 |
WHERE id=1
|
|
@@ -3833,6 +3859,7 @@ def set_settings_meta(
|
|
| 3833 |
{
|
| 3834 |
"user_mode": user_mode,
|
| 3835 |
"offline_force_flags": None if offline_force_flags is None else (1 if offline_force_flags else 0),
|
|
|
|
| 3836 |
"last_prompt_verbatim": last_prompt_verbatim,
|
| 3837 |
"updated_at": now,
|
| 3838 |
},
|
|
|
|
| 26 |
logger = logging.getLogger("uvicorn.error")
|
| 27 |
|
| 28 |
DB_PATH: Path
|
| 29 |
+
DB_WRITE_LOCK = False
|
| 30 |
TRIAGE_TREE_DEFAULT_JSON_PATH = Path(__file__).resolve().parent / "seed" / "triage_prompt_tree.default.json"
|
| 31 |
|
| 32 |
|
|
|
|
| 40 |
_upgrade_schema()
|
| 41 |
|
| 42 |
|
| 43 |
+
def set_db_write_lock(enabled: bool):
|
| 44 |
+
"""Set database query-only mode for all future connections."""
|
| 45 |
+
global DB_WRITE_LOCK
|
| 46 |
+
DB_WRITE_LOCK = bool(enabled)
|
| 47 |
+
|
| 48 |
+
|
| 49 |
+
def get_db_write_lock() -> bool:
|
| 50 |
+
"""Return whether DB write lock (query-only mode) is enabled."""
|
| 51 |
+
return bool(DB_WRITE_LOCK)
|
| 52 |
+
|
| 53 |
+
|
| 54 |
def _conn():
|
| 55 |
"""
|
| 56 |
Conn helper.
|
|
|
|
| 62 |
conn.execute("PRAGMA foreign_keys = ON;")
|
| 63 |
# Keep temp tables in memory to avoid filesystem issues when sorting large BLOB rows
|
| 64 |
conn.execute("PRAGMA temp_store = MEMORY;")
|
| 65 |
+
if DB_WRITE_LOCK:
|
| 66 |
+
conn.execute("PRAGMA query_only = ON;")
|
| 67 |
+
else:
|
| 68 |
+
conn.execute("PRAGMA query_only = OFF;")
|
| 69 |
except Exception:
|
| 70 |
pass
|
| 71 |
return conn
|
|
|
|
| 258 |
id INTEGER PRIMARY KEY CHECK (id = 1),
|
| 259 |
user_mode TEXT,
|
| 260 |
offline_force_flags INTEGER DEFAULT 0,
|
| 261 |
+
db_write_lock INTEGER DEFAULT 0,
|
| 262 |
last_prompt_verbatim TEXT,
|
| 263 |
updated_at TEXT NOT NULL
|
| 264 |
);
|
|
|
|
| 874 |
data = {}
|
| 875 |
user_mode = data.get("user_mode")
|
| 876 |
offline_force_flags = 1 if data.get("offline_force_flags") else 0
|
| 877 |
+
db_write_lock = 1 if data.get("db_write_lock") else 0
|
| 878 |
last_prompt_verbatim = data.get("last_prompt_verbatim")
|
| 879 |
_ensure_settings_meta_columns(conn)
|
| 880 |
conn.execute(
|
| 881 |
"""
|
| 882 |
+
INSERT INTO settings_meta(id, user_mode, offline_force_flags, db_write_lock, last_prompt_verbatim, updated_at)
|
| 883 |
+
VALUES(1, :user_mode, :offline_force_flags, :db_write_lock, :last_prompt_verbatim, :updated_at)
|
| 884 |
ON CONFLICT(id) DO UPDATE SET
|
| 885 |
user_mode=excluded.user_mode,
|
| 886 |
offline_force_flags=excluded.offline_force_flags,
|
| 887 |
+
db_write_lock=excluded.db_write_lock,
|
| 888 |
last_prompt_verbatim=excluded.last_prompt_verbatim,
|
| 889 |
updated_at=excluded.updated_at;
|
| 890 |
""",
|
| 891 |
{
|
| 892 |
"user_mode": user_mode,
|
| 893 |
"offline_force_flags": offline_force_flags,
|
| 894 |
+
"db_write_lock": db_write_lock,
|
| 895 |
"last_prompt_verbatim": last_prompt_verbatim,
|
| 896 |
"updated_at": now,
|
| 897 |
},
|
|
|
|
| 2235 |
names = {c["name"] for c in cols}
|
| 2236 |
if "last_prompt_verbatim" not in names:
|
| 2237 |
conn.execute("ALTER TABLE settings_meta ADD COLUMN last_prompt_verbatim TEXT;")
|
| 2238 |
+
if "db_write_lock" not in names:
|
| 2239 |
+
conn.execute("ALTER TABLE settings_meta ADD COLUMN db_write_lock INTEGER DEFAULT 0;")
|
| 2240 |
except Exception as exc:
|
| 2241 |
logger.warning("Unable to add settings_meta columns: %s", exc)
|
| 2242 |
|
|
|
|
| 3803 |
with _conn() as conn:
|
| 3804 |
_ensure_settings_meta_columns(conn)
|
| 3805 |
row = conn.execute(
|
| 3806 |
+
"SELECT user_mode, offline_force_flags, db_write_lock, last_prompt_verbatim FROM settings_meta WHERE id=1"
|
| 3807 |
).fetchone()
|
| 3808 |
if not row:
|
| 3809 |
return {}
|
| 3810 |
return {
|
| 3811 |
"user_mode": row["user_mode"],
|
| 3812 |
"offline_force_flags": bool(row["offline_force_flags"]),
|
| 3813 |
+
"db_write_lock": bool(row["db_write_lock"]),
|
| 3814 |
"last_prompt_verbatim": row["last_prompt_verbatim"],
|
| 3815 |
}
|
| 3816 |
|
|
|
|
| 3818 |
def set_settings_meta(
|
| 3819 |
user_mode: str = None,
|
| 3820 |
offline_force_flags: bool = None,
|
| 3821 |
+
db_write_lock: bool = None,
|
| 3822 |
last_prompt_verbatim: str = None,
|
| 3823 |
):
|
| 3824 |
"""
|
|
|
|
| 3829 |
with _conn() as conn:
|
| 3830 |
_ensure_settings_meta_columns(conn)
|
| 3831 |
existing = conn.execute(
|
| 3832 |
+
"SELECT user_mode, offline_force_flags, db_write_lock, last_prompt_verbatim FROM settings_meta WHERE id=1"
|
| 3833 |
).fetchone()
|
| 3834 |
if existing is None:
|
| 3835 |
conn.execute(
|
| 3836 |
"""
|
| 3837 |
+
INSERT INTO settings_meta(id, user_mode, offline_force_flags, db_write_lock, last_prompt_verbatim, updated_at)
|
| 3838 |
+
VALUES(1, :user_mode, :offline_force_flags, :db_write_lock, :last_prompt_verbatim, :updated_at)
|
| 3839 |
""",
|
| 3840 |
{
|
| 3841 |
"user_mode": user_mode,
|
| 3842 |
"offline_force_flags": 1 if offline_force_flags else 0,
|
| 3843 |
+
"db_write_lock": 1 if db_write_lock else 0,
|
| 3844 |
"last_prompt_verbatim": last_prompt_verbatim,
|
| 3845 |
"updated_at": now,
|
| 3846 |
},
|
|
|
|
| 3851 |
UPDATE settings_meta
|
| 3852 |
SET user_mode=COALESCE(:user_mode, user_mode),
|
| 3853 |
offline_force_flags=COALESCE(:offline_force_flags, offline_force_flags),
|
| 3854 |
+
db_write_lock=COALESCE(:db_write_lock, db_write_lock),
|
| 3855 |
last_prompt_verbatim=COALESCE(:last_prompt_verbatim, last_prompt_verbatim),
|
| 3856 |
updated_at=:updated_at
|
| 3857 |
WHERE id=1
|
|
|
|
| 3859 |
{
|
| 3860 |
"user_mode": user_mode,
|
| 3861 |
"offline_force_flags": None if offline_force_flags is None else (1 if offline_force_flags else 0),
|
| 3862 |
+
"db_write_lock": None if db_write_lock is None else (1 if db_write_lock else 0),
|
| 3863 |
"last_prompt_verbatim": last_prompt_verbatim,
|
| 3864 |
"updated_at": now,
|
| 3865 |
},
|
static/js/settings.js
CHANGED
|
@@ -113,7 +113,9 @@ const DEFAULT_SETTINGS = {
|
|
| 113 |
pharmacy_labels: ["Antibiotic", "Analgesic", "Cardiac", "Respiratory", "Gastrointestinal", "Endocrine", "Emergency"],
|
| 114 |
equipment_categories: [],
|
| 115 |
consumable_categories: [],
|
| 116 |
-
offline_force_flags: false
|
|
|
|
|
|
|
| 117 |
};
|
| 118 |
|
| 119 |
// ============================================================================
|
|
@@ -349,6 +351,16 @@ function applySettingsToUI(data = {}) {
|
|
| 349 |
}
|
| 350 |
}
|
| 351 |
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 352 |
setUserMode(merged.user_mode);
|
| 353 |
try { localStorage.setItem('user_mode', merged.user_mode || 'user'); } catch (err) { /* ignore */ }
|
| 354 |
window.CACHED_SETTINGS = merged;
|
|
@@ -1575,7 +1587,7 @@ async function saveSettings(showAlert = true, reason = 'manual') {
|
|
| 1575 |
try {
|
| 1576 |
const s = {};
|
| 1577 |
const numeric = new Set(['tr_temp','tr_tok','tr_p','tr_k','in_temp','in_tok','in_p','in_k','rep_penalty']);
|
| 1578 |
-
['triage_instruction','inquiry_instruction','tr_temp','tr_tok','tr_p','tr_k','in_temp','in_tok','in_p','in_k','mission_context','rep_penalty','user_mode'].forEach(k => {
|
| 1579 |
const el = document.getElementById(k);
|
| 1580 |
if (!el) return;
|
| 1581 |
const val = el.type === 'checkbox' ? el.checked : el.value;
|
|
|
|
| 113 |
pharmacy_labels: ["Antibiotic", "Analgesic", "Cardiac", "Respiratory", "Gastrointestinal", "Endocrine", "Emergency"],
|
| 114 |
equipment_categories: [],
|
| 115 |
consumable_categories: [],
|
| 116 |
+
offline_force_flags: false,
|
| 117 |
+
db_write_lock: false,
|
| 118 |
+
db_write_lock_forced: false,
|
| 119 |
};
|
| 120 |
|
| 121 |
// ============================================================================
|
|
|
|
| 351 |
}
|
| 352 |
}
|
| 353 |
});
|
| 354 |
+
const dbWriteLockEl = document.getElementById('db_write_lock');
|
| 355 |
+
const dbWriteLockForcedNote = document.getElementById('db-write-lock-forced-note');
|
| 356 |
+
if (dbWriteLockEl) {
|
| 357 |
+
const forced = !!merged.db_write_lock_forced;
|
| 358 |
+
dbWriteLockEl.disabled = forced;
|
| 359 |
+
dbWriteLockEl.title = forced ? 'Locked by environment variable DB_WRITE_LOCK' : '';
|
| 360 |
+
if (dbWriteLockForcedNote) {
|
| 361 |
+
dbWriteLockForcedNote.style.display = forced ? 'block' : 'none';
|
| 362 |
+
}
|
| 363 |
+
}
|
| 364 |
setUserMode(merged.user_mode);
|
| 365 |
try { localStorage.setItem('user_mode', merged.user_mode || 'user'); } catch (err) { /* ignore */ }
|
| 366 |
window.CACHED_SETTINGS = merged;
|
|
|
|
| 1587 |
try {
|
| 1588 |
const s = {};
|
| 1589 |
const numeric = new Set(['tr_temp','tr_tok','tr_p','tr_k','in_temp','in_tok','in_p','in_k','rep_penalty']);
|
| 1590 |
+
['triage_instruction','inquiry_instruction','tr_temp','tr_tok','tr_p','tr_k','in_temp','in_tok','in_p','in_k','mission_context','rep_penalty','user_mode','db_write_lock'].forEach(k => {
|
| 1591 |
const el = document.getElementById(k);
|
| 1592 |
if (!el) return;
|
| 1593 |
const val = el.type === 'checkbox' ? el.checked : el.value;
|
templates/index.html
CHANGED
|
@@ -1181,6 +1181,13 @@
|
|
| 1181 |
<div style="margin-top:10px;">
|
| 1182 |
<a class="btn btn-sm" style="background:#b23b00; display:inline-block;" href="/logout?fresh=1" onclick="if (typeof forceClearCache === 'function') { forceClearCache(); return false; }"><span class="dev-tag">dev:cache-bust</span>Force Reload</a>
|
| 1183 |
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1184 |
<div style="margin-top:8px; font-size:12px; color:#555;">
|
| 1185 |
User: hides advanced controls. Advanced: shows prompt tools. Developer: also shows dev tags.
|
| 1186 |
</div>
|
|
|
|
| 1181 |
<div style="margin-top:10px;">
|
| 1182 |
<a class="btn btn-sm" style="background:#b23b00; display:inline-block;" href="/logout?fresh=1" onclick="if (typeof forceClearCache === 'function') { forceClearCache(); return false; }"><span class="dev-tag">dev:cache-bust</span>Force Reload</a>
|
| 1183 |
</div>
|
| 1184 |
+
<label style="display:flex; align-items:center; gap:8px; font-size:12px; color:#333; margin-top:10px;">
|
| 1185 |
+
<input type="checkbox" id="db_write_lock" />
|
| 1186 |
+
Database Write Lock (read-only mode)
|
| 1187 |
+
</label>
|
| 1188 |
+
<div id="db-write-lock-forced-note" style="margin-top:6px; font-size:12px; color:#b23b00; display:none;">
|
| 1189 |
+
Write lock is forced by environment variable <code>DB_WRITE_LOCK</code>.
|
| 1190 |
+
</div>
|
| 1191 |
<div style="margin-top:8px; font-size:12px; color:#555;">
|
| 1192 |
User: hides advanced controls. Advanced: shows prompt tools. Developer: also shows dev tags.
|
| 1193 |
</div>
|