주변 스캔 기능을 정리
This commit is contained in:
@@ -15,8 +15,6 @@
|
||||
wifi24: $('#wifi24'),
|
||||
wifi5: $('#wifi5'),
|
||||
wifiTable: $('#wifiTable'),
|
||||
wifiScan: $('#wifiScan'),
|
||||
bluetoothScan: $('#bluetoothScan'),
|
||||
signalTooltip: $('#signalTooltip'),
|
||||
statusHost: $('#statusHost'),
|
||||
statusLoad: $('#statusLoad'),
|
||||
@@ -153,20 +151,6 @@
|
||||
remaining: 'Remaining',
|
||||
batteryVoltage: 'Battery Voltage',
|
||||
wifiClients: 'WiFi Clients',
|
||||
wifiScan: 'Nearby WiFi Scan',
|
||||
wifiScanDisabled: 'Nearby WiFi scan is disabled.',
|
||||
wifiScanEmpty: 'No nearby WiFi scan results.',
|
||||
wifiScanCached: 'cached',
|
||||
wifiScanFresh: 'fresh',
|
||||
wifiScanDisabledWithHistory: 'scan disabled, showing last observed networks',
|
||||
wifiScanObservedAll: 'all observed BSSID history',
|
||||
bluetoothScan: 'Nearby Bluetooth Scan',
|
||||
bluetoothScanDisabled: 'Nearby Bluetooth scan is disabled.',
|
||||
bluetoothScanEmpty: 'No nearby Bluetooth scan results.',
|
||||
bluetoothScanCached: 'cached',
|
||||
bluetoothScanFresh: 'fresh',
|
||||
bluetoothScanDisabledWithHistory: 'scan disabled, showing last observed devices',
|
||||
bluetoothScanObservedAll: 'all observed devices',
|
||||
deviceName: 'Device',
|
||||
address: 'Address',
|
||||
paired: 'Paired',
|
||||
@@ -318,20 +302,6 @@
|
||||
remaining: '잔여 시간',
|
||||
batteryVoltage: '배터리 전압',
|
||||
wifiClients: 'WiFi 클라이언트',
|
||||
wifiScan: '주변 WiFi 스캔',
|
||||
wifiScanDisabled: '주변 WiFi 스캔이 꺼져 있습니다.',
|
||||
wifiScanEmpty: '주변 WiFi 스캔 결과가 없습니다.',
|
||||
wifiScanCached: '캐시',
|
||||
wifiScanFresh: '실시간',
|
||||
wifiScanDisabledWithHistory: '스캔 꺼짐, 마지막 관측 목록 표시',
|
||||
wifiScanObservedAll: '전체 BSSID 누적 관측',
|
||||
bluetoothScan: '주변 Bluetooth 스캔',
|
||||
bluetoothScanDisabled: '주변 Bluetooth 스캔이 꺼져 있습니다.',
|
||||
bluetoothScanEmpty: '주변 Bluetooth 스캔 결과가 없습니다.',
|
||||
bluetoothScanCached: '캐시',
|
||||
bluetoothScanFresh: '실시간',
|
||||
bluetoothScanDisabledWithHistory: '스캔 꺼짐, 마지막 관측 장치 표시',
|
||||
bluetoothScanObservedAll: '전체 장치 누적 관측',
|
||||
deviceName: '장치',
|
||||
address: '주소',
|
||||
paired: '페어링',
|
||||
@@ -1197,14 +1167,6 @@
|
||||
els.signalTooltip.setAttribute('aria-hidden', 'true');
|
||||
}
|
||||
|
||||
function holdScanRender(ms = 900) {
|
||||
state.scanScrollHoldUntil = Math.max(state.scanScrollHoldUntil, Date.now() + ms);
|
||||
}
|
||||
|
||||
function scanRenderHeld() {
|
||||
return Date.now() < state.scanScrollHoldUntil;
|
||||
}
|
||||
|
||||
function cssEscape(value) {
|
||||
if (window.CSS?.escape) {
|
||||
return window.CSS.escape(value);
|
||||
@@ -1465,191 +1427,6 @@
|
||||
if (tableWrap) {
|
||||
tableWrap.scrollLeft = scrollLeft;
|
||||
}
|
||||
|
||||
if (data.wifi && Object.prototype.hasOwnProperty.call(data.wifi, 'scan')) {
|
||||
renderWifiScan(data.wifi.scan || null);
|
||||
}
|
||||
if (data.bluetooth && Object.prototype.hasOwnProperty.call(data.bluetooth, 'scan')) {
|
||||
renderBluetoothScan(data.bluetooth.scan || null);
|
||||
}
|
||||
}
|
||||
|
||||
function renderWifiScan(scan) {
|
||||
if (!els.wifiScan) return;
|
||||
if (scanRenderHeld()) return;
|
||||
const previousScroll = {};
|
||||
els.wifiScan.querySelectorAll('.wifi-scan-band').forEach(section => {
|
||||
const band = section.dataset.band || '';
|
||||
const wrap = section.querySelector('.table-wrap');
|
||||
if (band && wrap) previousScroll[band] = wrap.scrollLeft;
|
||||
});
|
||||
|
||||
const networks = Array.isArray(scan?.networks) ? scan.networks : [];
|
||||
if (!scan || (scan.enabled === false && networks.length === 0)) {
|
||||
els.wifiScan.innerHTML = `<div class="scan-empty">${escapeHtml(t('wifiScanDisabled'))}</div>`;
|
||||
return;
|
||||
}
|
||||
|
||||
const observedMode = scan.observed_mode === 'all' ? t('wifiScanObservedAll') : `${scan.observed_hours || '-'}h`;
|
||||
const status = scan.enabled === false
|
||||
? `${t('wifiScanDisabledWithHistory')} · ${observedMode}`
|
||||
: `${scan.cached ? t('wifiScanCached') : t('wifiScanFresh')} · ${escapeHtml(scan.generated_at || '-')} · ${observedMode}`;
|
||||
const bandOrder = ['2.4G', '5G', '6G', '기타'];
|
||||
const extraBands = networks
|
||||
.map(row => row.band || '기타')
|
||||
.filter(band => !bandOrder.includes(band));
|
||||
const bands = [...bandOrder, ...Array.from(new Set(extraBands))]
|
||||
.filter((band, index) => index < 2 || networks.some(row => (row.band || '기타') === band));
|
||||
const groups = bands.map(band => {
|
||||
const bandRows = networks.filter(row => row.band === band);
|
||||
return `
|
||||
<section class="wifi-scan-band" data-band="${attr(band)}">
|
||||
<h3>${escapeHtml(band)} <span>${bandRows.length}${state.lang === 'ko' ? '개' : ''}</span></h3>
|
||||
<div class="table-wrap compact">
|
||||
<table class="wifi-scan-table">
|
||||
<thead><tr><th>${escapeHtml(t('ssid'))}</th><th>BSSID</th><th>${escapeHtml(t('channel'))}</th><th>${escapeHtml(t('signal'))}</th><th>${escapeHtml(t('security'))}</th><th>${escapeHtml(t('observed'))}</th><th>WiFi</th><th>폭</th><th>속도</th><th>기능</th></tr></thead>
|
||||
<tbody>
|
||||
${bandRows.length ? bandRows.map(row => {
|
||||
const dbm = wifiSignalDbm(row.signal);
|
||||
return `<tr>
|
||||
<td>${escapeHtml(row.ssid || '-')}</td>
|
||||
<td>${escapeHtml(row.bssid || '-')}</td>
|
||||
<td>${escapeHtml(row.channel ?? '-')} ${row.freq ? `(${escapeHtml(row.freq)}MHz)` : ''}</td>
|
||||
<td>${signalBadgeHtml(dbm, row.signal || '', `scan:${row.bssid || row.ssid || ''}`)}</td>
|
||||
<td>${escapeHtml(row.security || '-')}</td>
|
||||
<td>${escapeHtml(wifiObservedText(row))}</td>
|
||||
<td>${escapeHtml(wifiStandardText(row))}</td>
|
||||
<td>${escapeHtml(row.channel_width || '-')}</td>
|
||||
<td>${escapeHtml(row.rates || '-')}</td>
|
||||
<td>${escapeHtml(row.capability || '-')}</td>
|
||||
</tr>`;
|
||||
}).join('') : `<tr><td colspan="10">${escapeHtml(t('wifiScanEmpty'))}</td></tr>`}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
`;
|
||||
}).join('');
|
||||
|
||||
els.wifiScan.innerHTML = `
|
||||
<div class="wifi-scan-head">
|
||||
<span>${status}</span>
|
||||
</div>
|
||||
<div class="wifi-scan-grid">${groups}</div>
|
||||
`;
|
||||
requestAnimationFrame(() => {
|
||||
els.wifiScan.querySelectorAll('.wifi-scan-band').forEach(section => {
|
||||
const band = section.dataset.band || '';
|
||||
const wrap = section.querySelector('.table-wrap');
|
||||
if (band && wrap && previousScroll[band] !== undefined) {
|
||||
wrap.scrollLeft = previousScroll[band];
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function renderBluetoothScan(scan) {
|
||||
if (!els.bluetoothScan) return;
|
||||
if (scanRenderHeld()) return;
|
||||
const wrap = els.bluetoothScan.querySelector('.table-wrap');
|
||||
const scrollLeft = wrap ? wrap.scrollLeft : 0;
|
||||
const devices = Array.isArray(scan?.devices) ? scan.devices : [];
|
||||
|
||||
if (!scan || (scan.enabled === false && devices.length === 0)) {
|
||||
els.bluetoothScan.innerHTML = `<div class="scan-empty">${escapeHtml(t('bluetoothScanDisabled'))}</div>`;
|
||||
return;
|
||||
}
|
||||
|
||||
const observedMode = scan.observed_mode === 'all' ? t('bluetoothScanObservedAll') : '-';
|
||||
const status = scan.enabled === false
|
||||
? `${t('bluetoothScanDisabledWithHistory')} · ${observedMode}`
|
||||
: `${scan.cached ? t('bluetoothScanCached') : t('bluetoothScanFresh')} · ${escapeHtml(scan.generated_at || '-')} · ${observedMode}`;
|
||||
const rows = devices.length ? devices.map(device => `
|
||||
<tr>
|
||||
<td>${escapeHtml(device.display_name || device.alias || device.name || '-')}</td>
|
||||
<td>${escapeHtml(device.address || '-')}</td>
|
||||
<td>${escapeHtml(device.address_type || '-')}</td>
|
||||
<td>${signalBadgeHtml(bluetoothSignalDbm(device.rssi), bluetoothRssiText(device.rssi), `bt:${device.address || device.name || ''}`)}</td>
|
||||
<td>${escapeHtml(device.tx_power ?? '-')}</td>
|
||||
<td>${escapeHtml(booleanStateText(device.connected))}</td>
|
||||
<td>${escapeHtml(booleanStateText(device.paired))}</td>
|
||||
<td>${escapeHtml(booleanStateText(device.trusted))}</td>
|
||||
<td>${escapeHtml(booleanStateText(device.blocked))}</td>
|
||||
<td>${escapeHtml(device.icon || '-')}</td>
|
||||
<td>${escapeHtml(device.class || '-')}</td>
|
||||
<td>${escapeHtml(joinDetailLines(device.uuids))}</td>
|
||||
<td>${escapeHtml(joinDetailLines(device.manufacturer_data))}</td>
|
||||
<td>${escapeHtml(joinDetailLines(device.service_data))}</td>
|
||||
<td>${escapeHtml(device.modalias || '-')}</td>
|
||||
<td>${escapeHtml(device.appearance || '-')}</td>
|
||||
<td>${escapeHtml(wifiObservedText(device))}</td>
|
||||
</tr>
|
||||
`).join('') : `<tr><td colspan="17">${escapeHtml(t('bluetoothScanEmpty'))}</td></tr>`;
|
||||
|
||||
els.bluetoothScan.innerHTML = `
|
||||
<div class="wifi-scan-head">
|
||||
<span>${status}</span>
|
||||
</div>
|
||||
<div class="table-wrap compact">
|
||||
<table class="wifi-scan-table">
|
||||
<thead><tr><th>${escapeHtml(t('deviceName'))}</th><th>${escapeHtml(t('address'))}</th><th>주소 종류</th><th>RSSI</th><th>TxPower</th><th>${escapeHtml(t('connected'))}</th><th>${escapeHtml(t('paired'))}</th><th>${escapeHtml(t('trusted'))}</th><th>${escapeHtml(t('blocked'))}</th><th>Icon</th><th>Class</th><th>UUID</th><th>제조사 데이터</th><th>서비스 데이터</th><th>Modalias</th><th>Appearance</th><th>${escapeHtml(t('observed'))}</th></tr></thead>
|
||||
<tbody>${rows}</tbody>
|
||||
</table>
|
||||
</div>
|
||||
`;
|
||||
requestAnimationFrame(() => {
|
||||
const nextWrap = els.bluetoothScan.querySelector('.table-wrap');
|
||||
if (nextWrap) nextWrap.scrollLeft = scrollLeft;
|
||||
});
|
||||
}
|
||||
|
||||
function bluetoothRssiText(value) {
|
||||
return Number.isFinite(Number(value)) ? `${Number(value)} dBm` : '-';
|
||||
}
|
||||
|
||||
function bluetoothSignalDbm(value) {
|
||||
const numeric = Number(value);
|
||||
return Number.isFinite(numeric) ? numeric : null;
|
||||
}
|
||||
|
||||
function booleanStateText(value) {
|
||||
if (value === true) return state.lang === 'ko' ? '예' : 'yes';
|
||||
if (value === false) return state.lang === 'ko' ? '아니오' : 'no';
|
||||
return '-';
|
||||
}
|
||||
|
||||
function joinDetailLines(value) {
|
||||
if (!Array.isArray(value) || value.length === 0) return '-';
|
||||
return value.join(' / ');
|
||||
}
|
||||
|
||||
function wifiObservedText(row) {
|
||||
if (row?.observed_source === 'live') {
|
||||
return row?.last_seen_at ? `${timeAgo(secondsSinceLocalTime(row.last_seen_at))} · ${row.last_seen_at}` : '방금';
|
||||
}
|
||||
if (row?.last_seen_at) {
|
||||
return `${timeAgo(secondsSinceLocalTime(row.last_seen_at))} · ${row.last_seen_at}`;
|
||||
}
|
||||
if (row?.observed_age_seconds !== null && row?.observed_age_seconds !== undefined) {
|
||||
return `${timeAgo(row.observed_age_seconds)} · -`;
|
||||
}
|
||||
return '-';
|
||||
}
|
||||
|
||||
function secondsSinceLocalTime(value) {
|
||||
const text = String(value || '').trim();
|
||||
if (!text) return 0;
|
||||
const parsed = new Date(text.includes('T') ? text : text.replace(' ', 'T'));
|
||||
if (Number.isNaN(parsed.getTime())) return 0;
|
||||
return Math.max(0, Math.floor((Date.now() - parsed.getTime()) / 1000));
|
||||
}
|
||||
|
||||
function wifiStandardText(row) {
|
||||
const parts = [];
|
||||
if (row?.he) parts.push('WiFi 6');
|
||||
if (row?.vht) parts.push('WiFi 5');
|
||||
if (row?.ht) parts.push('WiFi 4');
|
||||
return parts.join(' / ') || '-';
|
||||
}
|
||||
|
||||
function renderSystemStatus(data) {
|
||||
@@ -2503,27 +2280,6 @@
|
||||
});
|
||||
window.addEventListener('resize', positionSignalTooltip);
|
||||
window.addEventListener('scroll', positionSignalTooltip, true);
|
||||
document.addEventListener('pointerdown', event => {
|
||||
if (event.target?.closest?.('.nearby-scan .table-wrap')) {
|
||||
holdScanRender(1200);
|
||||
}
|
||||
}, true);
|
||||
document.addEventListener('pointermove', event => {
|
||||
if (event.target?.closest?.('.nearby-scan .table-wrap')) {
|
||||
holdScanRender(900);
|
||||
}
|
||||
}, true);
|
||||
document.addEventListener('pointerup', () => {
|
||||
holdScanRender(450);
|
||||
}, true);
|
||||
document.addEventListener('pointercancel', () => {
|
||||
holdScanRender(450);
|
||||
}, true);
|
||||
document.addEventListener('scroll', event => {
|
||||
if (event.target?.closest?.('.nearby-scan .table-wrap')) {
|
||||
holdScanRender(900);
|
||||
}
|
||||
}, true);
|
||||
[els.secondaryChartDetails, els.processDetails, els.diagnosticDetails].forEach(node => {
|
||||
node?.addEventListener('toggle', resizeChartsSoon);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user