Files
ucvl-home-vision/inventory.py
T

95 lines
5.2 KiB
Python
Raw Permalink Normal View History

"""Common device responsibility fields and non-video device records."""
import ipaddress
import re
class Inventory:
def __init__(self, app):
self.a = app
def candidates(self, user):
if not self.a.HOUSEHOLDS.manager(user):
return []
a = self.a
users = a.HOUSEHOLDS.objects(user, 'users')
if user['role'] == 'admin' and not any(u['id'] == user['id'] for u in users):
users.append(user)
result = [dict(ref='user:'+u['id'], name=u['name'], label=u['name']+' / '+u['username'], kind='账号成员')
for u in users if not u.get('disabled')]
linked = {u.get('personId') for u in users if not u.get('disabled')}
result += [dict(ref='person:'+p['id'], name=p['name'], label=p['name']+' · '+p['id'][-4:], kind='家谱人物')
for p in a.HOUSEHOLDS.objects(user, 'people') if not p.get('archived') and p['id'] not in linked]
return result
def display(self, item):
result = dict(item)
for prefix in ('owner', 'manager'):
ref = item.get(prefix+'Ref', '')
kind, _, key = ref.partition(':')
target = self.a.get_object({'user':'users','person':'people'}.get(kind, 'users'), key) if key else None
if target and (self.a.HOUSEHOLDS.family_of(target) == self.a.HOUSEHOLDS.family_of(item) or
kind == 'user' and target.get('role') == 'admin'):
result[prefix+'Name'] = target['name']
return result
def metadata(self, data, old=None, user=None):
old = old or {}
result = {k: self.a.clean_text(data.get(k, old.get(k, '')), 80)
for k in ('ownerName', 'managerName')}
candidates = {p['ref']:p for p in self.candidates(user)}
for prefix in ('owner', 'manager'):
field = prefix+'Ref'
ref = self.a.clean_text(data.get(field, old.get(field, '')), 64)
if ref:
if ref in candidates:
result[prefix+'Name'] = candidates[ref]['name']
elif ref == old.get(field):
result[prefix+'Name'] = self.display(old).get(prefix+'Name', '')
else:
raise self.a.Problem('请选择当前家庭/场所的人员,或选择手动填写')
result[field] = ref
for field in ('managementHost','tailscaleHost'):
value = self.a.clean_text(data.get(field,old.get(field,'')),253)
if value:
try: ipaddress.ip_address(value)
except ValueError:
if re.fullmatch(r'[0-9.]+',value) or not all(re.fullmatch(r'[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?',part) for part in value.split('.')):
raise self.a.Problem('设备地址请填写 IP 或域名,不含账号、路径及端口')
result[field]=value
for field,choices in [('networkAccess',('unknown','lan','tailscale','public')),('connectionType',('unknown','wired','wifi'))]:
result[field]=data.get(field,old.get(field,'unknown'))
if result[field] not in choices:raise self.a.Problem('网络接入类型不正确')
cidr=self.a.clean_text(data.get('networkCidr',old.get('networkCidr','')),64)
if cidr:
try:cidr=str(ipaddress.ip_network(cidr,strict=True))
except ValueError:raise self.a.Problem('网段请使用 CIDR 格式,例如 192.168.8.0/24')
result['networkCidr']=cidr
uplink=self.a.clean_text(data.get('uplinkId',old.get('uplinkId','')),32)
if uplink:
records={x['id']:x for table in ('assets','recorders','devices') for x in self.a.HOUSEHOLDS.objects(user,table)}
if uplink not in records:raise self.a.Problem('上级设备不属于当前家庭/场所')
seen={data.get('id')};node=uplink
while node:
if node in seen:raise self.a.Problem('上级设备不能指向自己或形成循环')
seen.add(node);node=records.get(node,{}).get('uplinkId')
result['uplinkId']=uplink
tags = data.get('tags', old.get('tags', []))
if not isinstance(tags, list) or len(tags) > 12:
raise self.a.Problem('自定义标签最多 12 个')
result['tags'] = sorted({self.a.clean_text(tag, 40, True) for tag in tags})
return result
def save(self, data, user):
a = self.a
with a.LOCK:
family = a.HOUSEHOLDS.prepare(user, 'devices', data, (('sites', 'siteId'),))
key = a.object_key('devices', data)
old = a.get_object('devices', key)
item = {k: a.clean_text(data.get(k, ''), 300 if k == 'note' else 120, k in ('name', 'deviceType'))
for k in ('name', 'deviceType', 'brand', 'model', 'serial', 'note')}
if item['deviceType'] in ('摄像头', '摄像机', '录像机'):
raise a.Problem('视频设备请使用摄像机或录像机档案,以保留取流与通道配置')
item.update(id=key, familyId=family, siteId=a.clean_text(data.get('siteId', ''), 32),
**self.metadata(data, old, user))
a.save_object('devices', item)
a.audit('保存设备档案', item['name'])
return item