fix(web): keep Reality scanner results table inside its modal (item 16 follow-up)
真机暴露:扫描器浮层沿用 .modal.wide(820px) 且结果表借用 table-wrap visible(overflow:visible),8 列 nowrap 自然宽超弹窗, 「使用」按钮被挤到弹窗边框外;后端失败原因最长约 128 字在 nowrap 下更会单行撑爆。 - public/app.js:浮层 section 改用扫描器专属 .scan-modal; 结果包装 table-wrap visible -> .scan-wrap;目标/证书单元格 加 scan-target/scan-cert 类与 title 全文。 - public/styles.css:新增 .scan- 专属规则——.modal.scan-modal 宽 1140px、.scan-wrap overflow-x:auto 兜底、目标/证书列 max-width:230px 截断省略、原因列 white-space:normal + max-width:280px 换行。不动 .table-wrap/.modal 既有语义, 节点编辑弹窗本体与其它弹窗布局不受影响。 验证:以真实 styles.css + 同构 DOM 灌真机候选池 14 行 + 长 原因(近 120 字上限)/超长证书名压力行,playwright-core 驱动 Chromium 在 1280/1600 两视口断言 table 与全部「使用」按钮 右缘均在弹窗右缘内、wrap scrollWidth==clientWidth(无需滚动); 旧类名对照页同法测得表宽 1958px 冲出弹窗(负对照成立)。
This commit is contained in:
+5
-5
@@ -1060,7 +1060,7 @@ function openRealityScanner() {
|
|||||||
overlay.id = 'scan-overlay';
|
overlay.id = 'scan-overlay';
|
||||||
overlay.className = 'modal-overlay';
|
overlay.className = 'modal-overlay';
|
||||||
overlay.style.zIndex = '95';
|
overlay.style.zIndex = '95';
|
||||||
overlay.innerHTML = `<section class="modal panel wide" role="dialog" aria-modal="true">
|
overlay.innerHTML = `<section class="modal panel scan-modal" role="dialog" aria-modal="true">
|
||||||
<header class="modal-head"><h2>查找 Reality 目标</h2><button type="button" class="icon-btn" data-scan-close aria-label="关闭"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><path d="M18 6L6 18M6 6l12 12"/></svg></button></header>
|
<header class="modal-head"><h2>查找 Reality 目标</h2><button type="button" class="icon-btn" data-scan-close aria-label="关闭"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><path d="M18 6L6 18M6 6l12 12"/></svg></button></header>
|
||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
<p class="links-note">从主控所在网络发起探测:可达性与延迟是主控视角,节点所在网络可能不同。</p>
|
<p class="links-note">从主控所在网络发起探测:可达性与延迟是主控视角,节点所在网络可能不同。</p>
|
||||||
@@ -1092,13 +1092,13 @@ async function runRealityScan() {
|
|||||||
const input = $('#scan-input') ? $('#scan-input').value.trim() : '';
|
const input = $('#scan-input') ? $('#scan-input').value.trim() : '';
|
||||||
const data = await api('/api/reality/scan', { method: 'POST', body: JSON.stringify({ input }) });
|
const data = await api('/api/reality/scan', { method: 'POST', body: JSON.stringify({ input }) });
|
||||||
const rows = (data.results || []).map((r) => `<tr>
|
const rows = (data.results || []).map((r) => `<tr>
|
||||||
<td>${esc(r.target)}${Number(r.port) !== 443 ? `:${esc(r.port)}` : ''}</td>
|
<td class="scan-target" title="${esc(r.target)}">${esc(r.target)}${Number(r.port) !== 443 ? `:${esc(r.port)}` : ''}</td>
|
||||||
<td>${r.ok ? '<span class="badge green">可用</span>' : '<span class="badge gray">不可用</span>'}</td>
|
<td>${r.ok ? '<span class="badge green">可用</span>' : '<span class="badge gray">不可用</span>'}</td>
|
||||||
<td>${esc(r.tlsVersion || '—')}</td><td>${esc(r.alpn || '—')}</td>
|
<td>${esc(r.tlsVersion || '—')}</td><td>${esc(r.alpn || '—')}</td>
|
||||||
<td>${esc(r.keyExchange || '—')}</td><td>${esc(r.certName || '—')}</td>
|
<td>${esc(r.keyExchange || '—')}</td><td class="scan-cert" title="${esc(r.certName || '')}">${esc(r.certName || '—')}</td>
|
||||||
<td>${r.ok ? `${esc(r.latencyMs)} ms` : esc(r.reason || '')}</td>
|
<td class="scan-reason">${r.ok ? `${esc(r.latencyMs)} ms` : esc(r.reason || '')}</td>
|
||||||
<td>${r.ok ? `<button type="button" class="btn small" data-scan-use data-host="${esc(r.host)}" data-port="${esc(r.port)}">使用</button>` : ''}</td></tr>`).join('');
|
<td>${r.ok ? `<button type="button" class="btn small" data-scan-use data-host="${esc(r.host)}" data-port="${esc(r.port)}">使用</button>` : ''}</td></tr>`).join('');
|
||||||
box.innerHTML = `<div class="table-wrap visible"><table class="scan-table"><thead><tr><th>目标</th><th>状态</th><th>TLS</th><th>ALPN</th><th>密钥交换</th><th>证书域名</th><th>延迟 / 原因</th><th></th></tr></thead><tbody>${rows || '<tr><td colspan="8">无结果</td></tr>'}</tbody></table></div>`;
|
box.innerHTML = `<div class="scan-wrap"><table class="scan-table"><thead><tr><th>目标</th><th>状态</th><th>TLS</th><th>ALPN</th><th>密钥交换</th><th>证书域名</th><th>延迟 / 原因</th><th></th></tr></thead><tbody>${rows || '<tr><td colspan="8">无结果</td></tr>'}</tbody></table></div>`;
|
||||||
} catch (error) { box.innerHTML = `<div class="empty"><b>${esc(error.message)}</b></div>`; }
|
} catch (error) { box.innerHTML = `<div class="empty"><b>${esc(error.message)}</b></div>`; }
|
||||||
}
|
}
|
||||||
function nodeClientsModal(node) {
|
function nodeClientsModal(node) {
|
||||||
|
|||||||
@@ -408,3 +408,10 @@ label>input:focus,label>select:focus,label>textarea:focus{border-color:var(--pri
|
|||||||
.scan-table{width:100%;border-collapse:collapse;font-size:13px}
|
.scan-table{width:100%;border-collapse:collapse;font-size:13px}
|
||||||
.scan-table th,.scan-table td{padding:6px 8px;text-align:left;border-bottom:1px solid var(--border-soft);white-space:nowrap}
|
.scan-table th,.scan-table td{padding:6px 8px;text-align:left;border-bottom:1px solid var(--border-soft);white-space:nowrap}
|
||||||
.scan-table th{color:var(--mut);font-weight:600}
|
.scan-table th{color:var(--mut);font-weight:600}
|
||||||
|
/* 扫描器浮层专属收口(真机修复:结果表曾冲出弹窗边框):弹窗加宽、结果容器
|
||||||
|
横向滚动兜底、目标/证书列截断、失败原因列允许换行。全部 .scan- 专属,
|
||||||
|
不影响节点编辑弹窗本体与其它弹窗/主页面表格的既有布局。 */
|
||||||
|
.modal.scan-modal{width:1140px}
|
||||||
|
.scan-wrap{overflow-x:auto}
|
||||||
|
.scan-table td.scan-target,.scan-table td.scan-cert{max-width:230px;overflow:hidden;text-overflow:ellipsis}
|
||||||
|
.scan-table td.scan-reason{white-space:normal;max-width:280px;overflow-wrap:anywhere}
|
||||||
|
|||||||
Reference in New Issue
Block a user