feat: align keyboard LocalID to GlobalID and preserve server credentials

Two operator requirements for MBeg keyboards and server connections:

1. align_keyboard_local_ids(): on every keyboard interface under
   Clients/GeViIO/GeViIO_01, set each video input/output LocalID equal to
   its GlobalID so operators can address cameras/monitors by global id.
   Virtual decoders/servers under GeViIO_Virtual keep their sequential
   local ids. Runs after prune in /api/set/export and /api/batch/build.

2. preserve_server_credentials(): server User/Password are entered by hand
   and must survive re-importing the server list from Excel. Matched by
   alias, an existing non-empty credential overrides the imported value.
   Applied to G-Core and GeViScope servers in /api/batch/build.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Docker Config Backup
2026-06-07 10:32:18 +02:00
parent 3b38a5c6d9
commit c54d3a6792
2 changed files with 62 additions and 0 deletions

View File

@@ -1120,6 +1120,64 @@ def disable_keyboard_ptz(tree):
return cleared return cleared
def align_keyboard_local_ids(tree):
"""Make LocalID equal GlobalID for every video input and output on every
MBeg keyboard interface (Clients/GeViIO/GeViIO_01).
Operators address cameras/monitors on the keyboards by their global id,
so the local id must match it. Other interfaces (virtual decoders and
servers under GeViIO_Virtual) keep their own sequential local ids.
"""
geviio = _find_folder(tree, ["Clients", "GeViIO", "GeViIO_01"])
if not geviio:
return 0
changed = 0
for interface in geviio.get("children", []):
if not isinstance(interface, dict) or interface.get("type") != "folder":
continue
for sub in ("VideoInputs", "VideoOutputs"):
container = _child_by_name(interface, sub)
if container is None:
continue
for entry in container.get("children", []):
if entry.get("type") != "folder":
continue
local_node = _child_by_name(entry, "LocalID")
global_node = _child_by_name(entry, "GlobalID")
if local_node is None or global_node is None:
continue
global_id = global_node.get("value")
if isinstance(global_id, int) and local_node.get("value") != global_id:
local_node["value"] = global_id
changed += 1
return changed
def preserve_server_credentials(new_servers, existing_servers):
"""Keep User/Password that were already entered on the matching server.
Server credentials are filled in by hand and must not be wiped when the
server list is re-imported from Excel. Servers are matched by alias; an
existing non-empty user or password overrides the imported value.
"""
by_alias = {}
for server in existing_servers or []:
alias = str(server.get("alias", "")).strip()
if alias:
by_alias[alias] = server
preserved = 0
for server in new_servers or []:
old = by_alias.get(str(server.get("alias", "")).strip())
if not old:
continue
if old.get("user"):
server["user"] = old["user"]
if old.get("password"):
server["password"] = old["password"]
preserved += 1
return preserved
def prune_video_inputs(tree, global_ids): def prune_video_inputs(tree, global_ids):
target_ids = {int(x) for x in global_ids if isinstance(x, int) and x > 0} target_ids = {int(x) for x in global_ids if isinstance(x, int) and x > 0}
if not target_ids: if not target_ids:

View File

@@ -111,6 +111,7 @@ async def export_set(payload: dict):
geviset.ensure_vx3_video_inputs(tree, camera_ids, ptz_by_id) geviset.ensure_vx3_video_inputs(tree, camera_ids, ptz_by_id)
geviset.prune_video_inputs(tree, camera_ids) geviset.prune_video_inputs(tree, camera_ids)
geviset.disable_keyboard_ptz(tree) geviset.disable_keyboard_ptz(tree)
geviset.align_keyboard_local_ids(tree)
print( print(
f"EXPORT camera_ids={len(camera_ids)} contains_101027={101027 in camera_ids}", f"EXPORT camera_ids={len(camera_ids)} contains_101027={101027 in camera_ids}",
flush=True, flush=True,
@@ -303,6 +304,7 @@ async def build_from_excel(
geviset.ensure_vx3_video_inputs(tree, camera_ids, ptz_by_id) geviset.ensure_vx3_video_inputs(tree, camera_ids, ptz_by_id)
geviset.prune_video_inputs(tree, camera_ids) geviset.prune_video_inputs(tree, camera_ids)
geviset.disable_keyboard_ptz(tree) geviset.disable_keyboard_ptz(tree)
geviset.align_keyboard_local_ids(tree)
if servers and servers.filename: if servers and servers.filename:
if not servers.filename.lower().endswith(".xlsx"): if not servers.filename.lower().endswith(".xlsx"):
@@ -316,6 +318,8 @@ async def build_from_excel(
s["id"] = str(idx) s["id"] = str(idx)
bundle_gcore = geviset.extract_gcore(tree) bundle_gcore = geviset.extract_gcore(tree)
bundle_gsc = geviset.extract_gsc(tree) bundle_gsc = geviset.extract_gsc(tree)
geviset.preserve_server_credentials(gcore_list, bundle_gcore["servers"])
geviset.preserve_server_credentials(gsc_list, bundle_gsc["servers"])
bundle_gcore["servers"] = gcore_list bundle_gcore["servers"] = gcore_list
bundle_gsc["servers"] = gsc_list bundle_gsc["servers"] = gsc_list
geviset.apply_gcore(tree, bundle_gcore) geviset.apply_gcore(tree, bundle_gcore)