feat: 自动认领开关 + 全站按设备名称显示
一、指纹匹配自动认领(可选,默认关)
- 发现设置新增「指纹匹配自动认领」勾选(app_meta: discovery_auto_claim,默认 0)
- 打开后:扫描发现某设备指纹与池中已有记录一致(同一台换了 IP)→ 自动迁移记录到新地址
并同步分组/任务引用,零点击;关闭时维持"识别自动 + 人工点一次确认"
- 默认关的原因:认领会改写分组/任务引用(数据结构变动),交人工确认更稳妥
- 扫描结果与状态行会显示本轮自动认领了几台
二、设备名称在界面上呈现(凡选择/展示设备处都显示名称)
- 新增前端 helper `devText(name, serial)`(base.js):有名称→「名称 · serial」
- 监控页设备表:名称加粗为主、地址作副行(未命名显示橙色提醒);任务概况的覆盖设备
chip 也优先显示名称(tooltip 保留完整地址)
- AI 控制台:目标设备下拉、实时画面设备下拉、目标/运行中提示都带名称(serial→name 映射)
- 任务编辑器「指定设备」下拉、分组编辑的设备勾选列表:带名称
- 后端 `/api/devices` 新增 `items`([{serial,name,model}],`devices` 保持兼容);
`/api/agent/devices` 增加 `name` 字段
- MCP `de_list_devices` 返回 `name`,并在工具说明与 Agent 系统提示里要求"汇报用名称、
调工具用 serial"
文档:API.md(items/name/auto_claim + §6.2.1 名称呈现表)、MCP.md(工具返回)
自测(全通过):自动认领端到端(开开关→扫描→自动迁址 + 名称保留 + 分组/任务引用同步 +
待连接池清理 + 开关默认关且可持久化);名称显示浏览器验证(监控页/覆盖设备 chip/AI 目标与
观看下拉/任务编辑器/分组弹窗/发现设置开关);设备指纹与人工认领回归
This commit is contained in:
@@ -31,6 +31,8 @@ _K_ENABLED = "discovery_enabled"
|
||||
_K_SUBNETS = "discovery_subnets"
|
||||
_K_INTERVAL = "discovery_interval"
|
||||
_K_PORT = "discovery_port"
|
||||
# 指纹匹配时是否自动认领(默认关:认领会改写分组/任务引用,默认交人工确认)
|
||||
_K_AUTO_CLAIM = "discovery_auto_claim"
|
||||
|
||||
# 单网段主机数上限(防误配 /8 之类),超过截断并告警
|
||||
_MAX_HOSTS_PER_SUBNET = 1024
|
||||
@@ -72,10 +74,11 @@ def get_settings():
|
||||
"subnets": subnets,
|
||||
"interval": int(_get(_K_INTERVAL, str(DISCOVERY_INTERVAL)) or DISCOVERY_INTERVAL),
|
||||
"port": int(_get(_K_PORT, str(DISCOVERY_PORT)) or DISCOVERY_PORT),
|
||||
"auto_claim": _get(_K_AUTO_CLAIM, "0") == "1", # 默认关
|
||||
}
|
||||
|
||||
|
||||
def save_settings(enabled=None, subnets=None, interval=None, port=None):
|
||||
def save_settings(enabled=None, subnets=None, interval=None, port=None, auto_claim=None):
|
||||
"""写发现配置(部分字段更新)。subnets 逐项校验 CIDR,非法返回 (False, 原因)。"""
|
||||
if subnets is not None:
|
||||
clean = []
|
||||
@@ -109,6 +112,8 @@ def save_settings(enabled=None, subnets=None, interval=None, port=None):
|
||||
_put(_K_INTERVAL, str(int(interval)))
|
||||
if port is not None:
|
||||
_put(_K_PORT, str(int(port)))
|
||||
if auto_claim is not None:
|
||||
_put(_K_AUTO_CLAIM, "1" if auto_claim else "0")
|
||||
db.session.commit()
|
||||
return True, "已保存"
|
||||
|
||||
@@ -196,6 +201,35 @@ def _parse_adb_devices(out):
|
||||
return devices
|
||||
|
||||
|
||||
# ================== 自动认领(可选) ==================
|
||||
def _auto_claim(fps):
|
||||
"""指纹命中池中已有设备 → 自动认领:迁移记录到新地址并同步分组/任务引用。
|
||||
|
||||
仅在设置 discovery_auto_claim 打开时由扫描线程调用(默认关)。
|
||||
返回 [(old_serial, new_serial, name), ...]。
|
||||
"""
|
||||
from core import device_pool
|
||||
done = []
|
||||
for serial, fp in (fps or {}).items():
|
||||
if not fp:
|
||||
continue
|
||||
try:
|
||||
info = device_pool.find_by_fingerprint(fp)
|
||||
if not info or info.get("serial") == serial:
|
||||
continue # 没匹配到,或本来就是这条(无需迁移)
|
||||
old, name = device_pool.claim_device(serial, fp)
|
||||
if not old:
|
||||
continue
|
||||
PendingDevice.query.filter_by(serial=serial).delete()
|
||||
db.session.commit()
|
||||
done.append((old, serial, name or info.get("name") or ""))
|
||||
_log.info(f"自动认领: 『{name or info.get('name') or old}』{old} → {serial}")
|
||||
except Exception as e:
|
||||
db.session.rollback()
|
||||
_log.warning(f"自动认领 {serial} 失败: {e}")
|
||||
return done
|
||||
|
||||
|
||||
# ================== 扫描 ==================
|
||||
def scan_once(manual=False):
|
||||
"""执行一轮扫描。返回 (ok, result_dict);后台线程/API 调用。"""
|
||||
@@ -234,12 +268,14 @@ def scan_once(manual=False):
|
||||
existing = {p.serial for p in PendingDevice.query.all()}
|
||||
added = 0
|
||||
from core import device_pool
|
||||
fps = {}
|
||||
for serial in verified:
|
||||
source = "tailscale" if serial.split(":")[0].startswith("100.") else "lan"
|
||||
try:
|
||||
fp = device_pool.read_fingerprint(serial, timeout=4)
|
||||
except Exception:
|
||||
fp = ""
|
||||
fps[serial] = fp
|
||||
if serial in existing:
|
||||
PendingDevice.query.filter_by(serial=serial).update(
|
||||
{"last_seen": now, "fingerprint": fp})
|
||||
@@ -249,6 +285,10 @@ def scan_once(manual=False):
|
||||
fingerprint=fp))
|
||||
added += 1
|
||||
db.session.commit()
|
||||
# 5.5 自动认领(可选,默认关):指纹命中池中已有设备 → 直接把记录迁到新地址。
|
||||
# 默认关是因为认领会改写分组/任务引用(数据结构变动),交人工点一下更稳妥;
|
||||
# 打开后零点击完成,见 doc/API.md §6。
|
||||
claimed = _auto_claim(fps) if settings.get("auto_claim") else []
|
||||
# 6. 正式池断联设备自动重连:adb connect 会因 WiFi 波动/设备重启/
|
||||
# adb 服务重启而断开——扫描线程每轮顺带重试(幂等轻量,内部
|
||||
# 全局锁串行),连上即恢复在线,无需人工干预。pending 池是给
|
||||
@@ -256,10 +296,13 @@ def scan_once(manual=False):
|
||||
back = _reconnect_offline(configured)
|
||||
_log.info(f"发现: 探测开放 {len(open_ips)} 台,可连 {len(verified)} 台,"
|
||||
f"新增待连接 {added} 台"
|
||||
+ (f",自动重连恢复 {len(back)} 台 {back}" if back else ""))
|
||||
+ (f",自动重连恢复 {len(back)} 台 {back}" if back else "")
|
||||
+ (f",自动认领 {len(claimed)} 台 {[c[0] + '→' + c[1] for c in claimed]}"
|
||||
if claimed else ""))
|
||||
_last_scan = (now, len(open_ips), len(verified), added)
|
||||
_last_error = ""
|
||||
return True, {"found": len(open_ips), "verified": len(verified), "new": added}
|
||||
return True, {"found": len(open_ips), "verified": len(verified),
|
||||
"new": added, "claimed": len(claimed)}
|
||||
except Exception as e:
|
||||
_log.warning(f"发现扫描异常: {e}")
|
||||
_last_error = str(e)[:200]
|
||||
@@ -359,6 +402,7 @@ def get_status():
|
||||
"subnets": settings["subnets"],
|
||||
"interval": settings["interval"],
|
||||
"port": settings["port"],
|
||||
"auto_claim": settings["auto_claim"],
|
||||
"scanning": _scanning,
|
||||
"last_scan": _last_scan[0] if _last_scan else "",
|
||||
"last_result": ({"found": _last_scan[1], "verified": _last_scan[2],
|
||||
|
||||
Reference in New Issue
Block a user