管理页新增:允许/禁止新设备接入 + 修改设备配对码

== 功能一:允许新设备接入开关 ==

关掉之后只有已经登记过的电脑能继续上线,没见过的新设备一律拒绝。
状态存在 relay_state.json 里,重启不丢。

判断新设备用的是 deviceId,不是配对码 ——
这一点很关键:管理员给老设备改过码之后,
那台设备必须仍然能进来;按码判断的话它会被自己的开关挡在外面。
测试里专门有一条 A3b 守这个行为。

被拒时回 blocked=True,让客户端能区分被管理员挡了
和配对码冲突,而不是去匹配中文错误文案(文案一改就坏)。

== 功能二:修改任意设备的配对码 ==

管理页每台设备一个「改码」按钮,填 6 位数字即生效。
要点:
  * 新旧码冲突检查
  * 旧码映射必须删掉,否则变成孤儿条目,
    管理页配对码占用的数字会虚高
  * 改码踢掉该设备当前会话(会话绑在码上)
  * 立刻持久化

== 顺手修掉两个真 bug ==

1. 改码功能一上线就 500:Session 用了 __slots__,
   根本没有 session_id 属性(会话是以 token 为 key 存在 dict 里的),
   写 s.session_id 直接 AttributeError。
   改成遍历 sessions.items() 拿 key。

2. snapshot() 里有同样的错(清理超时设备那段):
   平时 stale 列表是空的所以从没炸过,一旦真有设备超过 6 小时
   没上报就会 500。这次一起修了。

== 管理页 UI ==
  * 设备小节加了「允许新设备接入」勾选框
  * 每行加「改码」按钮(走 prompt,不引入弹窗层)
  * 改码按钮用 data-recode 而不是 data-device ——
    现有单台删除按 button.danger[data-device] 绑,
    带上 data-device 会被当成删除按钮,点改码变删设备。
    测试 C6 专门断言这一点。

== 测试 ==
新增 _tests/test_admin_device_control.py(23 项),
已注册进 _tests/verify_all.ps1 第 3e 阶段。
This commit is contained in:
xmz7891
2026-10-07 17:01:14 +08:00
parent 25e7780272
commit d4f335b8f9
21 changed files with 1520 additions and 7 deletions
+104
View File
@@ -0,0 +1,104 @@
#!/bin/bash
# 隧道长时稳定性测量(默认 30 分钟)
#
# 为什么要长时:
# 短测(60 次请求)看到的可用率都在 98~100%,看不出区别。
# 真实的失败是"某条隧道连接掉了、重连期间打在上面的请求失败",
# 这种事几十分钟才出现一两次 —— 必须跑长才能抓到。
#
# 记录:
# * 连接数采样(每 10 秒)—— 看有没有掉到配置值以下
# * 公网可用率采样(每 30 秒,从**本机**打,所以含设备自身 4G 的影响)
# * 隧道日志里的 重连 / 掉线 / dial 失败 事件
# * 事件时间线(哪一刻掉的)
#
# 用法: bash longstab.sh [分钟数] [标签]
set -u
BASE=https://relay.xxmxxs.top
UA='LanRemote/1.8.2'
MINUTES=${1:-30}
LABEL=${2:-run}
ROUNDS=$((MINUTES * 6)) # 每 10 秒一轮
OUT=/root/longstab_${LABEL}.txt
MARK=$(wc -l < /root/lanremote-relay/tunnel.log 2>/dev/null || echo 0)
echo "════════ 隧道长时稳定性 · $LABEL · ${MINUTES} 分钟 ════════" | tee "$OUT"
echo " 参数: $(grep -oE '\-\-protocol [a-z0-9]+|--ha-connections [0-9]+' /etc/systemd/system/lanremote-tunnel.service | tr '\n' ' ')" | tee -a "$OUT"
echo " MTU: $(cat /sys/class/net/wlan0/mtu)" | tee -a "$OUT"
echo " 开始: $(date '+%H:%M:%S') 共 $ROUNDS 轮" | tee -a "$OUT"
minc=999; maxc=0; sumc=0; nz=0
okA=0; totA=0; timesA=()
declare -a timeline
for r in $(seq 1 "$ROUNDS"); do
n=$(curl -s --max-time 4 http://127.0.0.1:20241/metrics 2>/dev/null \
| grep '^cloudflared_tunnel_ha_connections' | awk '{print $2}')
n=${n:-0}
[ "$n" -lt "$minc" ] && minc="$n"
[ "$n" -gt "$maxc" ] && maxc="$n"
sumc=$((sumc + n))
# 每 3 轮(30 秒)打一次公网
if [ $((r % 3)) -eq 0 ]; then
out=$(curl -s -o /dev/null -w '%{http_code} %{time_total}' --max-time 25 \
-H "User-Agent: $UA" "$BASE/v1/health")
c=$(echo "$out" | awk '{print $1}')
ms=$(echo "$out" | awk '{printf "%d", $2*1000}')
totA=$((totA + 1))
if [ "$c" = "200" ]; then
okA=$((okA + 1)); timesA+=("$ms")
else
timeline+=("$(date '+%H:%M:%S') HTTP $c (连接数 $n)")
printf " [%s] ✗ HTTP %s 连接数 %s\n" "$(date '+%H:%M:%S')" "$c" "$n" | tee -a "$OUT"
fi
# 连接数掉到配置值以下也要记
if [ "$n" -lt 10 ]; then
printf " [%s] ! 连接数只有 %s\n" "$(date '+%H:%M:%S')" "$n" | tee -a "$OUT"
fi
fi
sleep 10
done
echo | tee -a "$OUT"
echo "════════ 结果 ════════" | tee -a "$OUT"
echo " 连接数: 最低 $minc 最高 $maxc 平均 $((sumc / ROUNDS))(配置 10)" | tee -a "$OUT"
pct=$((okA * 100 / (totA > 0 ? totA : 1)))
echo " 公网可用率: $okA/$totA = ${pct}%" | tee -a "$OUT"
if [ "${#timesA[@]}" -gt 0 ]; then
printf '%s\n' "${timesA[@]}" | sort -n | awk '{
a[NR]=$1
} END {
printf " 耗时: 最小 %d 中位 %d P90 %d P99 %d 最大 %d ms\n",
a[1], a[int(NR/2)+1], a[int(NR*0.9)+1], a[int(NR*0.99)+1], a[NR]
}' | tee -a "$OUT"
fi
echo | tee -a "$OUT"
echo " 这段时间的隧道事件:" | tee -a "$OUT"
for kw in 'Registered tunnel connection' 'Lost connection' 'Failed to dial' 'i/o timeout' 'no recent network activity'; do
c=$(tail -n +$((MARK+1)) /root/lanremote-relay/tunnel.log 2>/dev/null | grep -c "$kw" || echo 0)
printf " %-34s %s 次\n" "$kw" "$c" | tee -a "$OUT"
done
echo | tee -a "$OUT"
echo " 掉线/重连时间线(最后 12 条):" | tee -a "$OUT"
tail -n +$((MARK+1)) /root/lanremote-relay/tunnel.log 2>/dev/null \
| grep -E 'Lost connection|Registered tunnel connection' | tail -12 \
| sed 's/^/ /' | tee -a "$OUT"
echo | tee -a "$OUT"
echo " 期间有没有 HTTP 失败:" | tee -a "$OUT"
if [ "${#timeline[@]}" -gt 0 ]; then
printf ' %s\n' "${timeline[@]}" | tee -a "$OUT"
else
echo " 没有" | tee -a "$OUT"
fi
echo | tee -a "$OUT"
echo " watchdog 最近 5 行:" | tee -a "$OUT"
tail -5 /root/lanremote-relay/watchdog.log 2>/dev/null | sed 's/^/ /' | tee -a "$OUT"
echo | tee -a "$OUT"
echo " 存档: $OUT" | tee -a "$OUT"