displays: single display type with IP-driven role (dashboard/lobby/kiosk)

One 'display' image resolves what it shows from its own IP, like the existing
visitor-location BU mapping. Extend DashboardDefault with displayrole
(dashboard|lobby|partskiosk; migration 7d28, businessunitid now nullable since
only the dashboard role needs one) + a role->path map. New unauthenticated
GET /api/dashboarddefaults/display-role returns {role, path, businessunitid}
for the caller IP. Settings UI gains a Display selector, showing the business
unit only for the dashboard role.
This commit is contained in:
cproudlock
2026-07-21 09:49:14 -04:00
parent b05fa33278
commit 60e2947fc7
5 changed files with 218 additions and 21 deletions

View File

@@ -97,3 +97,48 @@ def test_non_admin_cannot_update_or_delete(client, db, auth_headers,
del_resp = client.delete(f'/api/dashboarddefaults/{default_id}',
headers=member_headers)
assert del_resp.status_code == 403
def test_lobby_role_needs_no_businessunit_and_resolves_path(client, db, auth_headers):
"""A lobby-role mapping needs no business unit; display-role returns its path."""
created = client.post('/api/dashboarddefaults', json={
'ipaddress': '10.20.30.99', 'displayrole': 'lobby',
'description': 'Front lobby TV',
}, headers=auth_headers)
assert created.status_code == 201, created.get_json()
assert created.get_json()['data']['businessunitid'] is None
resolved = client.get('/api/dashboarddefaults/display-role?ipaddress=10.20.30.99')
data = resolved.get_json()['data']
assert data['role'] == 'lobby'
assert data['path'] == '/tv'
assert data['businessunitid'] is None
def test_partskiosk_role_resolves_path(client, db, auth_headers):
client.post('/api/dashboarddefaults', json={
'ipaddress': '10.20.30.77', 'displayrole': 'partskiosk',
}, headers=auth_headers)
data = client.get('/api/dashboarddefaults/display-role?ipaddress=10.20.30.77').get_json()['data']
assert data['role'] == 'partskiosk'
assert data['path'] == '/parts-kiosk'
def test_dashboard_role_requires_businessunit(client, db, auth_headers):
resp = client.post('/api/dashboarddefaults', json={
'ipaddress': '10.20.30.55', 'displayrole': 'dashboard',
}, headers=auth_headers)
assert resp.status_code == 400
assert 'businessunitid' in resp.get_json()['data']['error']['message'].lower()
def test_invalid_role_rejected(client, db, auth_headers):
resp = client.post('/api/dashboarddefaults', json={
'ipaddress': '10.20.30.44', 'displayrole': 'bogus',
}, headers=auth_headers)
assert resp.status_code == 400
def test_unmapped_ip_display_role_null(client, db):
data = client.get('/api/dashboarddefaults/display-role?ipaddress=9.9.9.9').get_json()['data']
assert data['role'] is None and data['path'] is None