mirror of
https://gitee.com/xmz7891/ykxmz.git
synced 2026-10-18 14:50:45 +00:00
管理页新增:允许/禁止新设备接入 + 修改设备配对码
== 功能一:允许新设备接入开关 ==
关掉之后只有已经登记过的电脑能继续上线,没见过的新设备一律拒绝。
状态存在 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:
+71
-1
@@ -300,7 +300,13 @@ def _device_rows(devices: list) -> str:
|
||||
'<td data-l="空闲"><span class="mono">%ss</span></td>'
|
||||
'<td data-l="待发 表/机"><span class="mono">%d / %d</span></td>'
|
||||
'<td data-l="流量 入/出"><span class="mono">%s / %s</span></td>'
|
||||
'<td class="ops"><button class="danger sm" '
|
||||
'<td class="ops">'
|
||||
# ⚠ 这个按钮用 data-recode 而不是 data-device ——
|
||||
# 现有"单台删除"是按 button.danger[data-device] 绑的,
|
||||
# 这里要是也带 data-device 就会被当成删除按钮。
|
||||
'<button class="ghost sm" data-recode="%s" '
|
||||
'data-name="%s" data-code="%s">改码</button>'
|
||||
'<button class="danger sm" '
|
||||
'data-device="%s" data-name="%s" data-code="%s">删除</button></td>'
|
||||
'</tr>' % (
|
||||
did, name,
|
||||
@@ -310,6 +316,7 @@ def _device_rows(devices: list) -> str:
|
||||
device["idleSeconds"],
|
||||
device["queuedToClient"], device["queuedToAgent"],
|
||||
_kb(device["bytesIn"]), _kb(device["bytesOut"]),
|
||||
did, name, code,
|
||||
did, name, code)
|
||||
)
|
||||
return "".join(out)
|
||||
@@ -333,6 +340,61 @@ const post = async (path, payload) => {
|
||||
return r.json();
|
||||
};
|
||||
|
||||
// ---------------- 允许/禁止新设备接入 ----------------
|
||||
// 关掉之后,只有**已经登记过**的电脑能继续上线,
|
||||
// 没见过的新设备一律拒绝。
|
||||
const allowNew = document.getElementById('allowNew');
|
||||
if (allowNew) allowNew.onchange = async () => {
|
||||
const want = allowNew.checked;
|
||||
allowNew.disabled = true;
|
||||
try {
|
||||
const j = await post('/v1/admin/allow_new', {allow: want});
|
||||
if (j.ok) {
|
||||
toast(j.allowNew ? '已允许新设备接入' : '已禁止新设备接入', 'ok');
|
||||
} else {
|
||||
toast('设置失败:' + (j.error || '未知错误'), 'err');
|
||||
allowNew.checked = !want;
|
||||
}
|
||||
} catch (e) {
|
||||
toast('请求出错:' + e, 'err');
|
||||
allowNew.checked = !want;
|
||||
}
|
||||
allowNew.disabled = false;
|
||||
};
|
||||
|
||||
// ---------------- 改配对码 ----------------
|
||||
document.querySelectorAll('button[data-recode]').forEach(b => {
|
||||
b.onclick = async () => {
|
||||
const name = b.dataset.name, old = b.dataset.code;
|
||||
const typed = prompt('把「' + name + '」的配对码 ' + old
|
||||
+ ' 改成:\n\n(必须是 6 位数字)', '');
|
||||
if (typed === null) return;
|
||||
const code = (typed || '').trim();
|
||||
if (!/^[0-9]{6}$/.test(code)) {
|
||||
toast('配对码必须是 6 位数字', 'err');
|
||||
return;
|
||||
}
|
||||
b.disabled = true;
|
||||
try {
|
||||
const j = await post('/v1/admin/set_code',
|
||||
{deviceId: b.dataset.recode, code: code});
|
||||
if (j.ok) {
|
||||
toast(j.unchanged ? '配对码没有变化'
|
||||
: ('配对码已改成 ' + j.code
|
||||
+ (j.kicked ? ',踢掉 ' + j.kicked + ' 个会话' : '')),
|
||||
'ok');
|
||||
setTimeout(() => location.reload(), 900);
|
||||
} else {
|
||||
toast('改码失败:' + (j.error || '未知错误'), 'err');
|
||||
b.disabled = false;
|
||||
}
|
||||
} catch (e) {
|
||||
toast('请求出错:' + e, 'err');
|
||||
b.disabled = false;
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
// ---------------- 单台删除 ----------------
|
||||
document.querySelectorAll('button.danger[data-device]').forEach(b => {
|
||||
b.onclick = async () => {
|
||||
@@ -538,6 +600,8 @@ def render_admin(snap: dict, public: str, admin_path: str, codes: int,
|
||||
url_class = "url empty" if not url_text else "url"
|
||||
url_display = _esc(url_text) if url_text else "还没有地址 —— 隧道可能没起来"
|
||||
sessions = snap.get("sessions", 0)
|
||||
# 开关状态:允许新设备接入时勾上
|
||||
allow_new = bool(snap.get("allowNew", True))
|
||||
|
||||
# 下载列表
|
||||
try:
|
||||
@@ -597,6 +661,11 @@ def render_admin(snap: dict, public: str, admin_path: str, codes: int,
|
||||
<div class="sec">
|
||||
<h3>设备</h3>
|
||||
<div class="right">
|
||||
<label class="hint" id="allowWrap"
|
||||
style="cursor:pointer;display:flex;align-items:center;gap:7px;margin:0 18px 0 0">
|
||||
<input type="checkbox" id="allowNew"%(allownew)s>
|
||||
<span>允许新设备接入</span>
|
||||
</label>
|
||||
<label class="hint" style="cursor:pointer;display:flex;align-items:center;gap:6px;margin:0">
|
||||
<input type="checkbox" id="autoRef" checked> 自动刷新
|
||||
</label>
|
||||
@@ -654,5 +723,6 @@ def render_admin(snap: dict, public: str, admin_path: str, codes: int,
|
||||
"oncls": "live" if online_count else "",
|
||||
"sescls": "busy" if sessions else "",
|
||||
"rows": _device_rows(devices),
|
||||
"allownew": " checked" if allow_new else "",
|
||||
"dl": dl_rows,
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user