Files
ykxmz/_build/add_batch_delete.py
xmz7891 b62ac865ea ykxmz 1.5.0:修复手表端 TLS 证书校验 + MTU 修复网络稳定性
被控端
- 修复 MTU 问题:夹 MSS + 路径 MTU 探测(公网可用率 48% -> 100%)
- 复用 HTTP 连接(免掉每次新建 TLS 的 30% 握手失败)
- 每分钟回头找局域网,自动切回快 400 倍的路径
- 画面心跳帧:静止时省流量,客户端仍知道流是活的
- 锁屏时给出可操作的提示

手表端
- 内置 GTS Root R4 / GlobalSign Root CA,修 SSLHandshakeException
- 连接错误翻译成人话(证书/时间/解析/超时分别提示)
- 经中转连上后用服务器报的地址刷新已配对记录
- 新增局域网连接页面、服务器延迟检测

服务器端
- 支持 chunked 请求体(修每 16 秒一次的连接错乱与大面积 502)
- 网络调优脚本 net-tune.sh(开机自动应用)
- 管理页:批量删除、网络体检、下载区
- 隧道改 HTTP/2 + 双连接

文档
- 许可协议 / 安装前必读 / 安装后须知(作者:小麦子)
2026-10-06 19:01:05 +08:00

203 lines
7.1 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# -*- coding: utf-8 -*-
"""中转管理端加"批量删除"(重写版)。
上一版的坑:
1. JS 里的换行我写成 \\n,但 relay.py 里是 f-string,
\\n 会被还原成真换行 -> JS 字符串跨行 -> 语法错误。
正确写法是补丁里用 \\\\n,这样 relay.py 源码里是 \\n,
f-string 渲染后 JS 拿到的是 反斜杠+n 两个字符。
2. rows.append 的锚点带太多上下文,容易被空格差异搞挂。
换成最小锚点。
"""
from __future__ import annotations
import ast
import sys
from pathlib import Path
RELAY = Path(r"C:\Users\shu\Desktop\deepseekagent\LanRemote\relay" r"\relay.py")
# ---------------------------------------------------------------- 后端
BACKEND_ANCHOR = ''' if path in ("/v1/admin/delete", "/v1/admin/delete_all"):'''
BACKEND_NEW = ''' if path == "/v1/admin/delete_many":
# 批量删除:勾几台删几台。
# 和"删除全部"的区别:不动没勾的,也不清孤儿绑定。
body = self._json_body()
wanted = body.get("deviceIds") or []
if not isinstance(wanted, list):
self._json(400, {"ok": False, "error": "deviceIds 要是数组"})
return
wanted = [str(item).strip() for item in wanted
if str(item).strip()]
if not wanted:
self._json(400, {"ok": False, "error": "没有勾选任何设备"})
return
results = [RELAY.delete_device(device_id=item)
for item in wanted]
done = [r for r in results if r.get("ok")]
failed = [r for r in results if not r.get("ok")]
log(f"管理操作:批量删除 {len(done)}/{len(wanted)} 台设备")
self._json(200, {
"ok": True,
"requested": len(wanted),
"deleted": len(done),
"failed": len(failed),
"devices": done,
"errors": failed[:10],
})
return
''' + BACKEND_ANCHOR
# ---------------------------------------------------------------- 行里加复选框(最小锚点)
ROW_ANCHOR = ''' rows.append(
"<tr>"
'''
ROW_NEW = ''' rows.append(
"<tr>"
# 批量删除用的复选框。value 放 deviceId。
"<td class=pickcell>"
f'<input type=checkbox class=pick value="{_esc(device["deviceId"])}">'
"</td>"
'''
# ---------------------------------------------------------------- 表头全选
HEADER_ANCHOR = "<table><tr><th>设备</th>"
HEADER_NEW = ("<table><tr><th class=pickcell>"
'<input type=checkbox id=pickAll title="全选"></th>'
"<th>设备</th>")
# ---------------------------------------------------------------- 顶部按钮
BAR_ANCHOR = " <button class=del id=delAll>删除全部设备</button>"
BAR_NEW = (" <button class=del id=delPicked>删除选中</button>\n"
" <button class=del id=delAll>删除全部设备</button>")
# ---------------------------------------------------------------- 样式
CSS_ANCHOR = (" button.del:hover{{background:#5a1c22;border-color:#a33;"
"color:#ffa198}}")
CSS_NEW = CSS_ANCHOR + """
button.del:disabled{{opacity:.45;cursor:not-allowed}}
.pickcell{{width:34px;text-align:center;padding-left:10px;padding-right:0}}
input[type=checkbox]{{width:15px;height:15px;cursor:pointer;accent-color:#2f81f7}}
tr.picked td{{background:#1f2733}}"""
# ---------------------------------------------------------------- 前端 JS
# 注意:这里写 \\\\n,落到 relay.py 里是 \\n,
# 再经 f-string 渲染,JS 拿到的才是 反斜杠+n。
JS_ANCHOR = '''document.querySelectorAll('button.del[data-device]').forEach(b => {{
b.onclick = () => del(b.dataset.device, b.dataset.name, b.dataset.code);
}});'''
JS_NEW = JS_ANCHOR + """
// ---------------- 批量删除 ----------------
const picks = () => Array.from(document.querySelectorAll('input.pick'));
const picked = () => picks().filter(c => c.checked).map(c => c.value);
function refreshPickState() {{
const chosen = picked().length;
const btn = document.getElementById('delPicked');
if (btn) {{
btn.disabled = chosen === 0;
btn.textContent = chosen ? ('删除选中 (' + chosen + ')') : '删除选中';
}}
picks().forEach(c => {{
const tr = c.closest('tr');
if (tr) tr.classList.toggle('picked', c.checked);
}});
const all = document.getElementById('pickAll');
if (all) {{
const total = picks().length;
all.checked = total > 0 && chosen === total;
all.indeterminate = chosen > 0 && chosen < total;
}}
}}
picks().forEach(c => c.addEventListener('change', refreshPickState));
const pickAll = document.getElementById('pickAll');
if (pickAll) pickAll.addEventListener('change', () => {{
picks().forEach(c => {{ c.checked = pickAll.checked; }});
refreshPickState();
}});
refreshPickState();
const delPicked = document.getElementById('delPicked');
if (delPicked) delPicked.onclick = async () => {{
const ids = picked();
if (!ids.length) return;
if (!confirm('删除选中的 ' + ids.length + ' 台设备?\\\\n\\\\n'
+ '会断开它们现在的手表连接,并清掉它们占的配对码。'))
return;
delPicked.disabled = true;
try {{
const r = await fetch('/v1/admin/delete_many', {{
method: 'POST',
headers: {{'Content-Type': 'application/json'}},
body: JSON.stringify({{deviceIds: ids}})
}});
const j = await r.json();
if (j.ok) {{
alert('已删除 ' + j.deleted + ' 台设备'
+ (j.failed ? (',' + j.failed + ' 台失败') : ''));
location.reload();
}} else {{
alert('删除失败:' + (j.error || '未知错误'));
delPicked.disabled = false;
}}
}} catch (e) {{
alert('请求出错:' + e);
delPicked.disabled = false;
}}
}};"""
PAIRS = [
(BACKEND_ANCHOR, BACKEND_NEW),
(ROW_ANCHOR, ROW_NEW),
(HEADER_ANCHOR, HEADER_NEW),
(BAR_ANCHOR, BAR_NEW),
(CSS_ANCHOR, CSS_NEW),
(JS_ANCHOR, JS_NEW),
]
def main() -> int:
text = RELAY.read_text(encoding="utf-8")
if "delete_many" in text:
print(" 已经加过了")
return 0
ok = True
for old, new in PAIRS:
if old not in text:
print(f" [失败] 锚点没找到 -> {old.strip()[:70]!r}")
ok = False
continue
if text.count(old) != 1:
print(f" [警告] 锚点出现 {text.count(old)} 次,只替换第一处")
text = text.replace(old, new, 1)
RELAY.write_text(text, encoding="utf-8", newline="\n")
if ok:
print(" 已加:/v1/admin/delete_many + 复选框 + 全选 + 删除选中")
try:
ast.parse(RELAY.read_text(encoding="utf-8"))
print(" relay.py 语法 OK")
except SyntaxError as exc:
print(f" relay.py 语法错误: 行 {exc.lineno}: {exc.msg}")
ok = False
return 0 if ok else 1
if __name__ == "__main__":
sys.exit(main())