Add LAN clients to dashboard
This commit is contained in:
@@ -17,7 +17,7 @@ Control은 라즈베리파이/리눅스 호스트의 팬과 시스템 상태를
|
|||||||
- RP1 온도, 팬 효율, CPU 전력 상세 차트는 접힘 영역에서 확인
|
- RP1 온도, 팬 효율, CPU 전력 상세 차트는 접힘 영역에서 확인
|
||||||
- UPS/배터리 잔여 시간 기능은 장치 제거 후 비활성 상태로 두고, 배터리 센서 조회와 장기 학습 계산은 돌리지 않음
|
- UPS/배터리 잔여 시간 기능은 장치 제거 후 비활성 상태로 두고, 배터리 센서 조회와 장기 학습 계산은 돌리지 않음
|
||||||
- 라즈베리파이 저전압/스로틀링 현재 상태, 복구 중/반복 발생 판정, 부팅 후 이력, 최근 감지 시각, 지속시간, 최근 10분 통계 표시
|
- 라즈베리파이 저전압/스로틀링 현재 상태, 복구 중/반복 발생 판정, 부팅 후 이력, 최근 감지 시각, 지속시간, 최근 10분 통계 표시
|
||||||
- WiFi client 목록과 2.4G/5G client 수 표시
|
- WiFi/LAN client 목록과 2.4G/5G/LAN client 수 표시
|
||||||
- 5G 외부 WiFi 모듈이 연결 시간을 제공하지 않는 경우 서버 관측 기반 연결 시간 보정
|
- 5G 외부 WiFi 모듈이 연결 시간을 제공하지 않는 경우 서버 관측 기반 연결 시간 보정
|
||||||
- WiFi 호스트명이 비어 있으면 MAC 기반 임시명을 표시하고, MAC별 수동 호스트명을 DB에 저장해 이후에도 유지
|
- WiFi 호스트명이 비어 있으면 MAC 기반 임시명을 표시하고, MAC별 수동 호스트명을 DB에 저장해 이후에도 유지
|
||||||
- WiFi 신호/송신 속도/수신 속도가 비어 있으면 대역, dBm, 송수신 속도, 5G RTL8822BU 특성을 기준으로 결측값 보정
|
- WiFi 신호/송신 속도/수신 속도가 비어 있으면 대역, dBm, 송수신 속도, 5G RTL8822BU 특성을 기준으로 결측값 보정
|
||||||
@@ -156,7 +156,7 @@ control-wifi-observe.service
|
|||||||
3. 대시보드는 `status` snapshot을 렌더링합니다.
|
3. 대시보드는 `status` snapshot을 렌더링합니다.
|
||||||
4. WebSocket 연결이 성공하면 `status` 메시지로 갱신하고, 실패하면 HTTP fallback을 사용합니다.
|
4. WebSocket 연결이 성공하면 `status` 메시지로 갱신하고, 실패하면 HTTP fallback을 사용합니다.
|
||||||
5. 팬 조작은 상태 저장, 정책 적용, 로그 저장 순서로 처리합니다.
|
5. 팬 조작은 상태 저장, 정책 적용, 로그 저장 순서로 처리합니다.
|
||||||
6. WiFi client 목록은 `iw station dump`와 dnsmasq lease를 조합하고, 5G 연결 시간이 `N/A`이면 MAC 기준 최초 감지 시간을 DB에 저장해 경과 시간을 계산합니다.
|
6. WiFi client 목록은 `iw station dump`와 dnsmasq lease를 조합하고, LAN client는 `bridge fdb show br br0`의 USB LAN 포트 MAC을 dnsmasq lease와 매칭합니다. 5G 연결 시간이 `N/A`이면 MAC 기준 최초 감지 시간을 DB에 저장해 경과 시간을 계산합니다.
|
||||||
7. WiFi hostname이 비어 있으면 MAC 기반 임시명을 표시하고, 수동 저장한 MAC별 이름은 DB에서 우선 적용합니다.
|
7. WiFi hostname이 비어 있으면 MAC 기반 임시명을 표시하고, 수동 저장한 MAC별 이름은 DB에서 우선 적용합니다.
|
||||||
8. WiFi 신호/송신 속도/수신 속도 값이 비어 있으면 실제로 존재하는 다른 무선 지표와 대역별 기준으로 값을 채웁니다.
|
8. WiFi 신호/송신 속도/수신 속도 값이 비어 있으면 실제로 존재하는 다른 무선 지표와 대역별 기준으로 값을 채웁니다.
|
||||||
9. `control-wifi-observe.timer`는 사용자 접속과 무관하게 10초마다 5G 관측 세션을 갱신합니다.
|
9. `control-wifi-observe.timer`는 사용자 접속과 무관하게 10초마다 5G 관측 세션을 갱신합니다.
|
||||||
@@ -233,8 +233,8 @@ control-wifi-observe.service
|
|||||||
- WiFi client 표는 갱신 중에도 가로 스크롤 위치를 보존합니다.
|
- WiFi client 표는 갱신 중에도 가로 스크롤 위치를 보존합니다.
|
||||||
- 사용자 서비스 목록은 갱신 중에도 스크롤 위치를 보존하고, 스크롤/터치/휠 조작 직후에는 DOM 교체를 잠시 미룹니다.
|
- 사용자 서비스 목록은 갱신 중에도 스크롤 위치를 보존하고, 스크롤/터치/휠 조작 직후에는 DOM 교체를 잠시 미룹니다.
|
||||||
- 하드웨어 또는 OS 변경 후 fan sysfs 경로를 확인합니다.
|
- 하드웨어 또는 OS 변경 후 fan sysfs 경로를 확인합니다.
|
||||||
- AP 국가는 `PA`로 두고, `router-performance.service`가 `wlan0`, `wlan1` power save를 끄고 출력 요청값을 `3600`으로 넣습니다. 실제 출력은 무선 모듈/드라이버가 허용하는 값에서 멈추며 현재 두 AP 모두 `31.00 dBm`으로 표시됩니다.
|
- AP 국가는 `PA`로 두고, `router-performance.service`가 WiFi power save를 끄고 출력 요청값을 `3600`으로 넣습니다. 실제 출력은 무선 모듈/드라이버가 허용하는 값에서 멈춥니다.
|
||||||
- 2.4G `hostapd-24g.conf`는 `max_num_sta=16`으로 두어 hostapd station table 제한 가능성을 줄입니다. 실제 동시 접속 수는 내장 `brcmfmac` 펌웨어/드라이버 한계가 따로 걸릴 수 있습니다.
|
- 2.4G `hostapd-24g.conf`는 `max_num_sta=32`로 두어 hostapd station table 제한 가능성을 줄입니다. 실제 동시 접속 수는 내장 `brcmfmac` 펌웨어/드라이버 한계가 따로 걸릴 수 있습니다.
|
||||||
- 2.4G 내장 WiFi는 `hostapd-24g.service` 개별 restart 직후 일부 IoT 단말이 WPA/EAPOL 재협상 루프에 들어갈 수 있으므로, 안정화된 상태에서는 개별 restart를 피하고 필요 시 전체 reboot 또는 채널 변경으로 재초기화합니다.
|
- 2.4G 내장 WiFi는 `hostapd-24g.service` 개별 restart 직후 일부 IoT 단말이 WPA/EAPOL 재협상 루프에 들어갈 수 있으므로, 안정화된 상태에서는 개별 restart를 피하고 필요 시 전체 reboot 또는 채널 변경으로 재초기화합니다.
|
||||||
- `hostapd-24g.service`, `hostapd-5g.service`는 종료 후 해당 `wlan` 인터페이스를 `br0`에서 떼도록 두어, 모듈이 빠졌다가 돌아온 뒤 hostapd가 다시 붙이는 흐름으로 정리합니다.
|
- `hostapd-24g.service`, `hostapd-5g.service`는 종료 후 해당 `wlan` 인터페이스를 `br0`에서 떼도록 두어, 모듈이 빠졌다가 돌아온 뒤 hostapd가 다시 붙이는 흐름으로 정리합니다.
|
||||||
- 5G WiFi 연결 시간은 외부 모듈이 값을 제공하지 않을 때 서버가 처음 감지한 시각 기준으로 계산합니다. 현재 5G 목록에서 MAC이 사라지면 관측 세션을 즉시 종료하므로 재연결 시 0부터 다시 누적됩니다.
|
- 5G WiFi 연결 시간은 외부 모듈이 값을 제공하지 않을 때 서버가 처음 감지한 시각 기준으로 계산합니다. 현재 5G 목록에서 MAC이 사라지면 관측 세션을 즉시 종료하므로 재연결 시 0부터 다시 누적됩니다.
|
||||||
|
|||||||
+85
-1
@@ -2604,6 +2604,20 @@ function iw_station_dump(string $iface): string
|
|||||||
return sh(['/usr/sbin/iw', 'dev', $iface, 'station', 'dump'], true, 4)['out'];
|
return sh(['/usr/sbin/iw', 'dev', $iface, 'station', 'dump'], true, 4)['out'];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function wifi_iface_by_driver(string $driver): ?string
|
||||||
|
{
|
||||||
|
foreach (glob('/sys/class/net/wlan*') ?: [] as $path) {
|
||||||
|
$iface = basename($path);
|
||||||
|
$driverPath = @readlink($path . '/device/driver');
|
||||||
|
|
||||||
|
if ($driverPath !== false && basename($driverPath) === $driver) {
|
||||||
|
return $iface;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
function parse_live_wifi_rows(string $iface, string $band, string $text, array $leases, array $aliases): array
|
function parse_live_wifi_rows(string $iface, string $band, string $text, array $leases, array $aliases): array
|
||||||
{
|
{
|
||||||
$rows = [];
|
$rows = [];
|
||||||
@@ -2678,6 +2692,65 @@ function parse_live_wifi_rows(string $iface, string $band, string $text, array $
|
|||||||
return $rows;
|
return $rows;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function lan_fdb_macs(string $iface = 'eth1'): array
|
||||||
|
{
|
||||||
|
$rows = sh(['/usr/sbin/bridge', 'fdb', 'show', 'br', 'br0'], false, 4)['out'];
|
||||||
|
$macs = [];
|
||||||
|
|
||||||
|
foreach (explode("\n", $rows) as $line) {
|
||||||
|
if (!preg_match('/^([0-9a-f]{2}(?::[0-9a-f]{2}){5})\s+dev\s+' . preg_quote($iface, '/') . '\b/i', trim($line), $m)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$mac = strtolower($m[1]);
|
||||||
|
if (str_starts_with($mac, '01:') || str_starts_with($mac, '33:') || str_contains($line, 'permanent')) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$macs[$mac] = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return array_keys($macs);
|
||||||
|
}
|
||||||
|
|
||||||
|
function lan_client_rows(array $leases, array $aliases, string $iface = 'eth1'): array
|
||||||
|
{
|
||||||
|
$rows = [];
|
||||||
|
|
||||||
|
foreach (lan_fdb_macs($iface) as $mac) {
|
||||||
|
$lease = $leases[$mac] ?? [];
|
||||||
|
$leaseHostname = (string)($lease['hostname'] ?? 'N/A');
|
||||||
|
$aliasHostname = $aliases[$mac] ?? null;
|
||||||
|
$hostname = $aliasHostname ?: $leaseHostname;
|
||||||
|
$hostnameSource = $aliasHostname ? 'alias' : ($leaseHostname === 'N/A' ? 'generated' : 'lease');
|
||||||
|
|
||||||
|
$rows[] = [
|
||||||
|
'band' => 'LAN',
|
||||||
|
'iface' => $iface,
|
||||||
|
'mac' => $mac,
|
||||||
|
'ip' => $lease['ip'] ?? 'N/A',
|
||||||
|
'hostname' => $hostname === 'N/A' ? '기기-' . strtoupper(substr(str_replace(':', '', $mac), -6)) : $hostname,
|
||||||
|
'hostname_source' => $hostnameSource,
|
||||||
|
'name' => $hostname === 'N/A' ? $mac : $hostname,
|
||||||
|
'signal' => '-',
|
||||||
|
'tx_bitrate' => '-',
|
||||||
|
'rx_bitrate' => '-',
|
||||||
|
'signal_source' => 'lan',
|
||||||
|
'tx_bitrate_source' => 'lan',
|
||||||
|
'rx_bitrate_source' => 'lan',
|
||||||
|
'connected_time' => 'N/A',
|
||||||
|
'inactive_time' => 'N/A',
|
||||||
|
'rx_bytes' => 0,
|
||||||
|
'tx_bytes' => 0,
|
||||||
|
'rx_packets' => 0,
|
||||||
|
'tx_packets' => 0,
|
||||||
|
'tx_failed' => 0,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
return $rows;
|
||||||
|
}
|
||||||
|
|
||||||
function wifi_time_ms(string $value): int
|
function wifi_time_ms(string $value): int
|
||||||
{
|
{
|
||||||
if (preg_match('/(\d+)/', $value, $m)) {
|
if (preg_match('/(\d+)/', $value, $m)) {
|
||||||
@@ -2762,6 +2835,10 @@ function apply_wifi_estimates(array $clients): array
|
|||||||
{
|
{
|
||||||
foreach ($clients as &$client) {
|
foreach ($clients as &$client) {
|
||||||
$band = (string)($client['band'] ?? '2.4G');
|
$band = (string)($client['band'] ?? '2.4G');
|
||||||
|
if ($band === 'LAN') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
$signalDbm = wifi_signal_dbm((string)($client['signal'] ?? ''));
|
$signalDbm = wifi_signal_dbm((string)($client['signal'] ?? ''));
|
||||||
$txMbps = wifi_bitrate_mbps($client['tx_bitrate'] ?? null);
|
$txMbps = wifi_bitrate_mbps($client['tx_bitrate'] ?? null);
|
||||||
$rxMbps = wifi_bitrate_mbps($client['rx_bitrate'] ?? null);
|
$rxMbps = wifi_bitrate_mbps($client['rx_bitrate'] ?? null);
|
||||||
@@ -2973,12 +3050,18 @@ function wifi_data(): array
|
|||||||
$aliases = wifi_client_aliases();
|
$aliases = wifi_client_aliases();
|
||||||
$clients = [];
|
$clients = [];
|
||||||
|
|
||||||
foreach (['wlan0' => '2.4G', 'wlan1' => '5G'] as $iface => $band) {
|
$wifiIfaces = array_filter([
|
||||||
|
'wlan0' => '2.4G',
|
||||||
|
wifi_iface_by_driver('rtl88x2bu') ?: 'wlan1' => '5G',
|
||||||
|
]);
|
||||||
|
|
||||||
|
foreach ($wifiIfaces as $iface => $band) {
|
||||||
$clients = array_merge(
|
$clients = array_merge(
|
||||||
$clients,
|
$clients,
|
||||||
parse_live_wifi_rows($iface, $band, iw_station_dump($iface), $leases, $aliases)
|
parse_live_wifi_rows($iface, $band, iw_station_dump($iface), $leases, $aliases)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
$clients = array_merge($clients, lan_client_rows($leases, $aliases));
|
||||||
|
|
||||||
$clients = dedupe_wifi_clients($clients);
|
$clients = dedupe_wifi_clients($clients);
|
||||||
$clients = apply_wifi_estimates($clients);
|
$clients = apply_wifi_estimates($clients);
|
||||||
@@ -2988,6 +3071,7 @@ function wifi_data(): array
|
|||||||
'clients' => $clients,
|
'clients' => $clients,
|
||||||
'count24' => count(array_filter($clients, fn($c) => $c['band'] === '2.4G')),
|
'count24' => count(array_filter($clients, fn($c) => $c['band'] === '2.4G')),
|
||||||
'count5' => count(array_filter($clients, fn($c) => $c['band'] === '5G')),
|
'count5' => count(array_filter($clients, fn($c) => $c['band'] === '5G')),
|
||||||
|
'countLan' => count(array_filter($clients, fn($c) => $c['band'] === 'LAN')),
|
||||||
'leases' => array_values($leases),
|
'leases' => array_values($leases),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,6 +14,7 @@
|
|||||||
fanModeOptions: $('#fanModeOptions'),
|
fanModeOptions: $('#fanModeOptions'),
|
||||||
wifi24: $('#wifi24'),
|
wifi24: $('#wifi24'),
|
||||||
wifi5: $('#wifi5'),
|
wifi5: $('#wifi5'),
|
||||||
|
wifiLan: $('#wifiLan'),
|
||||||
wifiTable: $('#wifiTable'),
|
wifiTable: $('#wifiTable'),
|
||||||
signalTooltip: $('#signalTooltip'),
|
signalTooltip: $('#signalTooltip'),
|
||||||
statusHost: $('#statusHost'),
|
statusHost: $('#statusHost'),
|
||||||
@@ -110,6 +111,7 @@
|
|||||||
wifiStatus: 'WiFi Status',
|
wifiStatus: 'WiFi Status',
|
||||||
clients24: '2.4G Clients',
|
clients24: '2.4G Clients',
|
||||||
clients5: '5G Clients',
|
clients5: '5G Clients',
|
||||||
|
clientsLan: 'LAN Clients',
|
||||||
systemStatus: 'System Status',
|
systemStatus: 'System Status',
|
||||||
loadAvg: 'Load Avg',
|
loadAvg: 'Load Avg',
|
||||||
activeUsers: 'Active Users',
|
activeUsers: 'Active Users',
|
||||||
@@ -257,6 +259,7 @@
|
|||||||
wifiStatus: 'WiFi 상태',
|
wifiStatus: 'WiFi 상태',
|
||||||
clients24: '2.4G 접속',
|
clients24: '2.4G 접속',
|
||||||
clients5: '5G 접속',
|
clients5: '5G 접속',
|
||||||
|
clientsLan: 'LAN 접속',
|
||||||
systemStatus: '시스템 상태',
|
systemStatus: '시스템 상태',
|
||||||
loadAvg: '부하 평균',
|
loadAvg: '부하 평균',
|
||||||
activeUsers: '활성 사용자',
|
activeUsers: '활성 사용자',
|
||||||
@@ -1173,6 +1176,7 @@
|
|||||||
setText(els.fanPercent, Number(data.fan?.percent || 0).toFixed(1) + '%');
|
setText(els.fanPercent, Number(data.fan?.percent || 0).toFixed(1) + '%');
|
||||||
setText(els.wifi24, data.wifi?.count24 ?? 0);
|
setText(els.wifi24, data.wifi?.count24 ?? 0);
|
||||||
setText(els.wifi5, data.wifi?.count5 ?? 0);
|
setText(els.wifi5, data.wifi?.count5 ?? 0);
|
||||||
|
setText(els.wifiLan, data.wifi?.countLan ?? 0);
|
||||||
state.latestFanPwm = Math.max(0, Math.min(255, Number(data.fan?.pwm || 0)));
|
state.latestFanPwm = Math.max(0, Math.min(255, Number(data.fan?.pwm || 0)));
|
||||||
|
|
||||||
if (!isFanEditing()) {
|
if (!isFanEditing()) {
|
||||||
|
|||||||
@@ -203,6 +203,7 @@ html[data-theme="light"] .details-panel{background:#edf3fa;border-color:#b8c7da}
|
|||||||
<div class="stat-grid">
|
<div class="stat-grid">
|
||||||
<div class="stat"><div class="stat-label" data-i18n="clients24">2.4G Clients</div><div class="stat-value" id="wifi24">-</div></div>
|
<div class="stat"><div class="stat-label" data-i18n="clients24">2.4G Clients</div><div class="stat-value" id="wifi24">-</div></div>
|
||||||
<div class="stat"><div class="stat-label" data-i18n="clients5">5G Clients</div><div class="stat-value" id="wifi5">-</div></div>
|
<div class="stat"><div class="stat-label" data-i18n="clients5">5G Clients</div><div class="stat-value" id="wifi5">-</div></div>
|
||||||
|
<div class="stat"><div class="stat-label" data-i18n="clientsLan">LAN Clients</div><div class="stat-value" id="wifiLan">-</div></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user