mirror of
https://gitee.com/xmz7891/ykxmz.git
synced 2026-10-18 14:50:45 +00:00
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 + 双连接 文档 - 许可协议 / 安装前必读 / 安装后须知(作者:小麦子)
This commit is contained in:
@@ -0,0 +1,202 @@
|
||||
# -*- 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())
|
||||
Reference in New Issue
Block a user