# -*- coding: utf-8 -*-
"""中转管理器加「删除设备(连同配对码)」。
用户要求:
"这个中转管理器,首先删除设备,就是删除设备并删除对应的配对码"
现状:
/v1/admin/kick 只踢会话,设备还在,码还占着
/v1/admin/forget 只抹配对码绑定,设备还挂在列表上
两个都不是用户要的"删干净"。
新增:
Relay.delete_device(device_id) —— 一次做完:
1. 踢掉它的所有会话
2. 删掉设备记录
3. 删掉它的配对码绑定(这样那台设备下次可以重新设码)
POST /v1/admin/delete {"deviceId": "..."} 或 {"code": "..."}
POST /v1/admin/delete_all
管理页:
每行加一个「删除」按钮(带二次确认)
表格上方加一个「删除全部设备」按钮
"""
from __future__ import annotations
import sys
from pathlib import Path
RELAY = Path(r"C:\Users\shu\Desktop\deepseekagent\LanRemote\relay\relay.py")
# ---------------- 1. Relay.delete_device ----------------
DELETE_ANCHOR = ''' def forget(self, code: str) -> bool:'''
DELETE_NEW = ''' def delete_device(self, *, device_id: str = "", code: str = "") -> dict:
"""删掉一台设备,**连同它的配对码绑定和所有会话**。
这就是管理页上「删除设备」按钮做的事。
和 kick/forget 的区别:
kick 只踢会话,设备还在,码还占着
forget 只抹码绑定,设备还挂在列表上
delete 三样一起清 —— 那台设备下次可以重新设任意配对码
返回做了什么,便于界面显示。
"""
target = None
with self.lock:
if device_id:
target = self.devices.get(device_id)
if target is None and code:
owner = self.codes.get(code)
if owner:
target = self.devices.get(owner)
device_id = owner
if target is None:
return {"ok": False, "error": "找不到这台设备"}
removed_sessions = self.kick(target.device_id)
with self.lock:
self.devices.pop(target.device_id, None)
# 把它的码绑定一起删掉
gone_codes = [c for c, owner in list(self.codes.items())
if owner == target.device_id]
for item in gone_codes:
self.codes.pop(item, None)
# 清掉可能残留的会话记录
for token in list(getattr(self, "sessions", {}).keys()):
self.sessions.pop(token, None)
self.save_state()
log(f"管理操作:删除设备 {target.name}(码 {target.code}),"
f"踢掉 {removed_sessions} 个会话,清掉 {len(gone_codes)} 条码绑定")
return {
"ok": True,
"deleted": target.device_id,
"name": target.name,
"code": target.code,
"kicked": removed_sessions,
"codesCleared": gone_codes,
}
def forget(self, code: str) -> bool:'''
# ---------------- 2. 管理接口 ----------------
API_ANCHOR = ''' if path == "/v1/admin/forget":'''
API_NEW = ''' if path in ("/v1/admin/delete", "/v1/admin/delete_all"):
body = self._json_body()
if path == "/v1/admin/delete_all":
# 一次删光(清场用)
with RELAY.lock:
all_ids = list(RELAY.devices.keys())
results = [RELAY.delete_device(device_id=item)
for item in all_ids]
done = [r for r in results if r.get("ok")]
log(f"管理操作:删除全部设备,共 {len(done)} 台")
self._json(200, {"ok": True, "deleted": len(done),
"devices": done})
return
device = str(body.get("deviceId") or "").strip()
code = str(body.get("code") or "").strip()
if not device and not code:
self._json(400, {"ok": False,
"error": "要给 deviceId 或 code"})
return
result = RELAY.delete_device(device_id=device, code=code)
self._json(200 if result.get("ok") else 404, result)
return
if path == "/v1/admin/forget":'''
# ---------------- 3. 管理页:设备行加删除按钮 ----------------
ROW_OLD = ''' f'
{_kb(device["bytesIn"])} / {_kb(device["bytesOut"])} | '
"")'''
ROW_NEW = ''' f'{_kb(device["bytesIn"])} / {_kb(device["bytesOut"])} | '
# 删除按钮:连同配对码一起删掉。
# 用 data-* 传值,避免设备名里有引号把 HTML 弄坏。
' | '
"")'''
TABLE_OLD = '''| 设备 | 配对码 | 会话 | 空闲 |
待发(表/机) | 流量(入/出) |
{''.join(rows) or '| 还没有设备上线 |
'}
'''
TABLE_NEW = '''
删除会连配对码一起清掉,那台设备下次可以重新设码
| 设备 | 配对码 | 会话 | 空闲 |
待发(表/机) | 流量(入/出) | 操作 |
{''.join(rows) or '| 还没有设备上线 |
'}
'''
# 样式
CSS_OLD = ''' .foot{{color:#6e7681;font-size:12px;margin-top:16px}}'''
CSS_NEW = ''' .foot{{color:#6e7681;font-size:12px;margin-top:16px}}
.bar{{margin-bottom:10px;display:flex;align-items:center;gap:10px;flex-wrap:wrap}}
.tip{{color:#6e7681;font-size:12px}}
button.del{{background:#3d1418;color:#ff7b72;border:1px solid #6e2b2b;
border-radius:6px;padding:4px 12px;font-size:12px;cursor:pointer;
font-family:inherit}}
button.del:hover{{background:#5a1c22;border-color:#a33;color:#ffa198}}'''
def patch(pairs, label: str) -> bool:
text = RELAY.read_text(encoding="utf-8")
ok = True
for old, new, tag in pairs:
if old not in text:
print(f" [{label}] [跳过] {tag} —— 锚点没找到")
ok = False
continue
text = text.replace(old, new, 1)
print(f" [{label}] {tag}")
RELAY.write_text(text, encoding="utf-8", newline="\n")
return ok
def main() -> int:
text = RELAY.read_text(encoding="utf-8")
if "def delete_device" in text:
print(" 已经加过删除设备")
return 0
ok = patch([
(DELETE_ANCHOR, DELETE_NEW, "Relay.delete_device"),
(API_ANCHOR, API_NEW, "管理接口 /delete 和 /delete_all"),
(CSS_OLD, CSS_NEW, "删除按钮样式"),
(ROW_OLD, ROW_NEW, "设备行加删除按钮"),
(TABLE_OLD, TABLE_NEW, "工具栏 + 脚本"),
], "中转")
import ast
try:
ast.parse(RELAY.read_text(encoding="utf-8"))
print(" 语法 OK")
except SyntaxError as exc:
print(f" 语法错误: 行 {exc.lineno}: {exc.msg}")
ok = False
return 0 if ok else 1
if __name__ == "__main__":
sys.exit(main())