네트워크 이력 인터페이스별 섹션 분리

This commit is contained in:
seo
2026-07-25 22:39:04 +09:00
parent 9cd5160f03
commit 824538c85c
4 changed files with 74 additions and 26 deletions
+2 -2
View File
@@ -16,7 +16,7 @@ Control은 라즈베리파이/리눅스 호스트의 팬과 시스템 상태를
- 오버레이 설정 모달에서 보안 정책, 팬 자동곡선, 이벤트 판정, 팬 이상감지 파라미터 조정
- 설정 모달에서 라즈베리파이 정품 7인치 DSI 터치 디스플레이 상태 확인, 밝기/백라이트 즉시 적용, 콘솔 회전, 터치 좌표 보정, LCD/터치 비활성화 부팅 설정 조정
- CPU 온도, RP1 온도, CPU 전력, 팬 RPM 센서 이력 차트
- DHCP client 목록 아래에서 WAN 브릿지 `br0` 기준 다운로드/업로드 속도와 RX/TX 패킷 이력 차트 표시
- DHCP client 목록 아래에서 WAN, 브릿지 `br0`, LAN, 2.4G WiFi, 5G WiFi 기준 다운로드/업로드 속도와 RX/TX 패킷 이력 차트 표시
- UPS/배터리 잔여 시간 기능은 장치 제거 후 비활성 상태로 두고, 배터리 센서 조회와 장기 학습 계산은 돌리지 않음
- 라즈베리파이 저전압/스로틀링 현재 상태, 복구 중/반복 발생 판정, 부팅 후 이력, 최근 감지 시각, 지속시간, 최근 10분 통계 표시
- WiFi/LAN client 목록과 2.4G/5G/LAN client 수 표시
@@ -76,7 +76,7 @@ Control은 라즈베리파이/리눅스 호스트의 팬과 시스템 상태를
- `control_state`: 팬 모드와 PWM 상태
- `presence_fan_policy_state`: 외출/귀가 기반 팬 정책의 마지막 확인/적용 상태
- `sensor_logs`: 온도, RPM, PWM, load, memory, disk, uptime. 과거 배터리 컬럼은 호환을 위해 유지하지만 UPS 제거 상태에서는 새 배터리값을 기록하지 않습니다.
- `network_usage_logs`: WAN 브릿지 `br0` 기준 누적 byte/packet 카운터와 초당 다운로드/업로드 속도, RX/TX 패킷 이력
- `network_usage_logs`: WAN, 브릿지 `br0`, LAN, 2.4G WiFi, 5G WiFi 기준 누적 byte/packet 카운터와 초당 다운로드/업로드 속도, RX/TX 패킷 이력
- `power_event_states`: 저전압/스로틀링 episode 상태, 최근 지속시간, 최근 이벤트 이력
- `battery_profile_cache`: 배터리 잔여시간 계산용 장기 프로파일 캐시
- `systemd_service_cache`: 사용자 생성 systemd 서비스 목록과 최근 journal 로그의 짧은 TTL 캐시
+6 -3
View File
@@ -1031,8 +1031,9 @@ function network_log_interfaces(array $network): array
}
foreach ((array)($network['interfaces'] ?? []) as $row) {
if (($row['name'] ?? '') === 'br0') {
$targets['br0'] = $row;
$name = (string)($row['name'] ?? '');
if (in_array($name, ['br0', 'eth1', 'wlan0', 'wlan1'], true)) {
$targets[$name] = $row;
}
}
@@ -1148,6 +1149,8 @@ function network_usage_history(int $limit = 240): array
iface,
rx_mbps,
tx_mbps,
ROUND(rx_mbps / 8, 4) AS rx_mbytes,
ROUND(tx_mbps / 8, 4) AS tx_mbytes,
rx_pps,
tx_pps
FROM network_usage_logs
@@ -4550,7 +4553,7 @@ function collect_snapshot(bool $applyFan = true): array
$fanSpike = fan_spike_analysis($history, $fan, $system, $processes);
$network = network_info();
add_network_usage_log($network);
$networkHistory = network_usage_history(480);
$networkHistory = network_usage_history(1000);
$shouldLogNotice = !empty($fanSpike['alert_started'])
|| (!empty($fanSpike['active']) && (bool)setting_value('notice.log_during_alert'));
+29 -14
View File
@@ -161,10 +161,13 @@
rp1Temp: 'RP1 Temp',
cpuWatt: 'CPU Watt',
networkHistory: 'Network History',
wanDownloadPackets: 'WAN Download / RX Packets',
wanUploadPackets: 'WAN Upload / TX Packets',
bridgeDownloadPackets: 'Bridge Download / RX Packets',
bridgeUploadPackets: 'Bridge Upload / TX Packets',
wanHistory: 'WAN History',
bridgeHistory: 'Bridge History',
lanHistory: 'LAN History',
wifi24History: '2.4G WiFi History',
wifi5History: '5G WiFi History',
downloadRxPackets: 'Download / RX Packets',
uploadTxPackets: 'Upload / TX Packets',
downloadLine: 'Download',
uploadLine: 'Upload',
rxPacketsLine: 'RX Packets',
@@ -365,10 +368,13 @@
rp1Temp: 'RP1 온도',
cpuWatt: 'CPU 전력',
networkHistory: '네트워크 이력',
wanDownloadPackets: 'WAN 다운로드 / RX 패킷',
wanUploadPackets: 'WAN 업로드 / TX 패킷',
bridgeDownloadPackets: '브릿지 다운로드 / RX 패킷',
bridgeUploadPackets: '브릿지 업로드 / TX 패킷',
wanHistory: 'WAN 이력',
bridgeHistory: '브릿지 이력',
lanHistory: 'LAN 이력',
wifi24History: '2.4G WiFi 이력',
wifi5History: '5G WiFi 이력',
downloadRxPackets: '다운로드 / RX 패킷',
uploadTxPackets: '업로드 / TX 패킷',
downloadLine: '다운로드',
uploadLine: '업로드',
rxPacketsLine: 'RX 패킷',
@@ -2559,7 +2565,7 @@
padding: 8,
callbacks: {
label: item => {
const suffix = item.dataset.yAxisID === 'speed' ? ' Mbit/s' : ' pps';
const suffix = item.dataset.yAxisID === 'speed' ? ' MB/s' : ' pps';
return `${item.dataset.label}: ${formatChartNumber(item.parsed.y, suffix)}`;
},
},
@@ -2586,7 +2592,7 @@
ticks: {
color: speedColor,
maxTicksLimit: 4,
callback: value => formatChartNumber(value, ' Mbit/s'),
callback: value => formatChartNumber(value, ' MB/s'),
},
grid: { color: 'rgba(255,255,255,.045)', drawTicks: false },
border: { display: false },
@@ -2682,10 +2688,19 @@
const wanIface = String(data.network?.default_interface || 'eth0');
const wanRows = networkRowsForIface(networkRows, wanIface);
const bridgeRows = networkRowsForIface(networkRows, 'br0');
networkChart('netWanRxChart', wanRows, 'rx_mbps', t('downloadLine'), 'rx_pps', t('rxPacketsLine'), '#0ea5e9');
networkChart('netWanTxChart', wanRows, 'tx_mbps', t('uploadLine'), 'tx_pps', t('txPacketsLine'), '#22c55e');
networkChart('netBridgeRxChart', bridgeRows, 'rx_mbps', t('downloadLine'), 'rx_pps', t('rxPacketsLine'), '#0ea5e9');
networkChart('netBridgeTxChart', bridgeRows, 'tx_mbps', t('uploadLine'), 'tx_pps', t('txPacketsLine'), '#22c55e');
const lanRows = networkRowsForIface(networkRows, 'eth1');
const wifi24Rows = networkRowsForIface(networkRows, 'wlan0');
const wifi5Rows = networkRowsForIface(networkRows, 'wlan1');
networkChart('netWanRxChart', wanRows, 'rx_mbytes', t('downloadLine'), 'rx_pps', t('rxPacketsLine'), '#0ea5e9');
networkChart('netWanTxChart', wanRows, 'tx_mbytes', t('uploadLine'), 'tx_pps', t('txPacketsLine'), '#22c55e');
networkChart('netBridgeRxChart', bridgeRows, 'rx_mbytes', t('downloadLine'), 'rx_pps', t('rxPacketsLine'), '#0ea5e9');
networkChart('netBridgeTxChart', bridgeRows, 'tx_mbytes', t('uploadLine'), 'tx_pps', t('txPacketsLine'), '#22c55e');
networkChart('netLanRxChart', lanRows, 'rx_mbytes', t('downloadLine'), 'rx_pps', t('rxPacketsLine'), '#0ea5e9');
networkChart('netLanTxChart', lanRows, 'tx_mbytes', t('uploadLine'), 'tx_pps', t('txPacketsLine'), '#22c55e');
networkChart('netWifi24RxChart', wifi24Rows, 'rx_mbytes', t('downloadLine'), 'rx_pps', t('rxPacketsLine'), '#0ea5e9');
networkChart('netWifi24TxChart', wifi24Rows, 'tx_mbytes', t('uploadLine'), 'tx_pps', t('txPacketsLine'), '#22c55e');
networkChart('netWifi5RxChart', wifi5Rows, 'rx_mbytes', t('downloadLine'), 'rx_pps', t('rxPacketsLine'), '#0ea5e9');
networkChart('netWifi5TxChart', wifi5Rows, 'tx_mbytes', t('uploadLine'), 'tx_pps', t('txPacketsLine'), '#22c55e');
}
function resizeChartsSoon() {
+37 -7
View File
@@ -150,7 +150,7 @@ html[data-theme="light"] .btn.secondary{background:#d9e2ef;color:#172033}html[da
.table-wrap{max-width:100%;overflow-x:auto;overflow-y:hidden;-webkit-overflow-scrolling:touch;border-radius:16px;border:1px solid var(--line)}.table-wrap.compact{border-radius:12px}.wifi-table{width:100%;min-width:1080px;table-layout:fixed;border-collapse:collapse}.wifi-table th,.wifi-table td{padding:11px 13px;border-bottom:1px solid rgba(128,145,170,.18);text-align:left;font-size:14px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.wifi-table th{position:sticky;top:0;background:var(--table-head);z-index:1;font-weight:500}.wifi-table tr:hover{background:rgba(128,145,170,.09)}.wifi-table .lan-link-speed{text-align:center}.wifi-table .col-band{width:72px}.wifi-table .col-host{width:240px}.wifi-table .col-ip{width:132px}.wifi-table .col-mac{width:170px}.wifi-table .col-signal{width:100px}.wifi-table .col-rate{width:120px}.wifi-table .col-time{width:130px}
.wifi-host-edit,.lan-wake-btn{appearance:none;border:0;background:transparent;color:inherit;font:inherit;max-width:100%;padding:0;margin:0;cursor:pointer;text-align:left;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;text-decoration:underline;text-decoration-style:dotted;text-underline-offset:3px}.wifi-host-edit:hover,.lan-wake-btn:hover{color:var(--blue)}.lan-wake-btn{font-weight:700}
.wifi-signal{font-weight:700;font-variant-numeric:tabular-nums;cursor:help;text-decoration:underline;text-decoration-style:dotted;text-decoration-thickness:1px;text-underline-offset:3px}.wifi-signal.best{color:#16a34a}.wifi-signal.excellent{color:#22c55e}.wifi-signal.good{color:#38bdf8}.wifi-signal.fine{color:#0ea5e9}.wifi-signal.normal{color:#f59e0b}.wifi-signal.weak{color:#fb7185}.wifi-signal.bad{color:#ef4444}.wifi-signal.unknown{color:var(--sub);text-decoration:none;cursor:default}
.chart-grid{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:14px}.chart-box{height:280px;background:var(--input);border:1px solid rgba(84,101,128,.58);border-radius:16px;padding:12px 12px 18px}.chart-box h3{margin:0 0 8px;color:var(--sub);font-size:13px}.chart-canvas{position:relative;height:calc(100% - 27px);min-height:0}.chart-canvas canvas{width:100%!important;height:100%!important}.chart-secondary{margin-top:14px}
.chart-grid{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:14px}.chart-box{height:280px;background:var(--input);border:1px solid rgba(84,101,128,.58);border-radius:16px;padding:12px 12px 18px}.chart-box.wide{grid-column:span 2}.chart-box h3{margin:0 0 8px;color:var(--sub);font-size:13px}.chart-canvas{position:relative;height:calc(100% - 27px);min-height:0}.chart-canvas canvas{width:100%!important;height:100%!important}.chart-secondary{margin-top:14px}
.details-panel{border:1px solid rgba(84,101,128,.52);border-radius:16px;background:rgba(128,145,170,.06);padding:0;margin-top:14px;overflow:hidden}.details-panel>summary{cursor:pointer;list-style:none;display:flex;align-items:center;justify-content:space-between;gap:12px;min-height:48px;padding:0 14px;color:var(--text);font-weight:500}.details-panel>summary::-webkit-details-marker{display:none}.details-panel>summary::after{content:"+";display:grid;place-items:center;width:24px;height:24px;border:1px solid var(--line);border-radius:999px;color:var(--sub);font-weight:700}.details-panel[open]>summary::after{content:"-"}.details-body{padding:0 14px 14px}.section-note{margin:0;color:var(--sub);font-size:12px}
.resource-card{margin-top:18px}.resource-head{display:flex;align-items:baseline;justify-content:space-between;gap:12px;margin-bottom:12px}.resource-head h2{margin:0}.baseline-pill{color:var(--sub);font-size:12px;font-variant-numeric:tabular-nums;text-align:right;white-space:nowrap}.spike-log-box{margin-top:16px}.spike-log-box h3{margin:0 0 9px;color:var(--text);font-size:14px;font-weight:500}.spike-log-list{display:grid;gap:8px;max-height:260px;overflow:auto;padding-right:4px}.spike-log-list.compact{max-height:180px}.spike-log-item{display:grid;gap:4px;padding:10px 12px;border:1px solid var(--notice-border);background:var(--notice-bg);border-radius:12px}.spike-log-item.latest{border:1px solid var(--notice-border-strong);background:var(--notice-bg-strong);box-shadow:0 0 0 1px var(--notice-border) inset;}.spike-log-item.alert{border-color:rgba(255,95,87,.58);background:rgba(150,38,38,.18)}.spike-log-item.alert strong{color:#ffb4ad}.spike-log-item strong{font-size:13px;font-weight:500;color:var(--notice-title)}.spike-log-item span{font-size:12px;color:var(--notice-copy);font-variant-numeric:tabular-nums}.spike-log-empty{padding:10px 12px;border-radius:12px;background:var(--card2);color:var(--sub);font-size:13px}.service-list{display:grid;gap:10px;max-height:520px;overflow:auto;padding-right:4px}.service-list.remote-log-list{grid-template-columns:repeat(auto-fit,minmax(230px,1fr));max-height:none}.service-item{border:1px solid var(--line);border-radius:12px;background:var(--card2);padding:12px;display:grid;gap:9px}.service-head{display:flex;justify-content:space-between;gap:10px;align-items:flex-start}.service-title{display:grid;gap:3px;min-width:0}.service-name{font-size:14px;color:var(--text);font-weight:600;word-break:break-word}.service-desc,.service-meta{font-size:12px;color:var(--sub);font-variant-numeric:tabular-nums;word-break:break-word}.service-state{border:1px solid var(--line);border-radius:999px;padding:4px 9px;font-size:12px;color:var(--sub);white-space:nowrap}.service-state.active{color:#bbf7d0;border-color:rgba(34,197,94,.35);background:rgba(22,163,74,.14)}.service-state.warning{color:#fde68a;border-color:rgba(245,158,11,.42);background:rgba(180,83,9,.16)}.service-state.failed{color:#fecaca;border-color:rgba(239,68,68,.4);background:rgba(185,28,28,.16)}.service-log{margin:0;max-height:160px;overflow:auto;border:1px solid var(--line);border-radius:10px;background:var(--code-bg);color:var(--mono-text);padding:10px;font:11px/1.45 ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;white-space:pre-wrap;word-break:break-word}.dmesg-head{display:flex;align-items:center;justify-content:space-between;gap:10px}.dmesg-meta{font-size:12px;color:var(--sub);font-variant-numeric:tabular-nums}.dmesg-log{margin:10px 0 0;max-height:420px;overflow:auto;border:1px solid var(--line);border-radius:12px;background:var(--code-bg);color:var(--mono-text);padding:12px;font:12px/1.45 ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;white-space:pre-wrap;word-break:break-word}.dmesg-log[hidden]{display:none}
html[data-theme="light"] .details-panel{background:#edf3fa;border-color:#b8c7da}html[data-theme="light"] .service-item{background:#e8f0f8;border-color:#b7c8dd}html[data-theme="light"] .service-name{color:#0f1f35}html[data-theme="light"] .service-desc,html[data-theme="light"] .service-meta{color:#42526a}html[data-theme="light"] .service-state{background:#f8fbff;border-color:#aebed2;color:#304057}html[data-theme="light"] .service-state.active{background:#d8f3e3;border-color:#7fc99c;color:#146c39}html[data-theme="light"] .service-state.warning{background:#fff2c7;border-color:#dca642;color:#8a4d08}html[data-theme="light"] .service-state.failed{background:#ffe2e2;border-color:#e38b8b;color:#9f1d1d}html[data-theme="light"] .service-log,html[data-theme="light"] .dmesg-log{background:#f7fafc;border-color:#b9c8da;color:#0f2742}
@@ -163,7 +163,7 @@ html[data-theme="light"] .details-panel{background:#edf3fa;border-color:#b8c7da}
.touch-runtime-box{margin-top:14px;padding-top:14px;border-top:1px solid rgba(128,145,170,.24)}.touch-runtime-box h4{margin:0 0 12px;font-size:15px;color:var(--text)}
@media(max-width:900px){.chart-grid{grid-template-columns:repeat(2,minmax(0,1fr))}}
@media(max-width:1100px){.layout{grid-template-columns:1fr}.resource-grid,.wifi-scan-grid{grid-template-columns:1fr}.topbar{align-items:flex-start;flex-direction:column}.topbar-right{width:100%}.topbar-right .btn{flex:1}.stat-grid{grid-template-columns:1fr}.resource-head{align-items:flex-start;flex-direction:column}.baseline-pill{text-align:left;white-space:normal}}
@media(max-width:720px){.chart-grid{grid-template-columns:1fr}.settings-grid,.touch-display-status{grid-template-columns:1fr}.settings-head{align-items:stretch;flex-direction:column}.settings-actions{display:grid;grid-template-columns:1fr 1fr}.settings-box{max-height:calc(100vh - 24px)}}
@media(max-width:720px){.chart-grid{grid-template-columns:1fr}.chart-box.wide{grid-column:auto}.settings-grid,.touch-display-status{grid-template-columns:1fr}.settings-head{align-items:stretch;flex-direction:column}.settings-actions{display:grid;grid-template-columns:1fr 1fr}.settings-box{max-height:calc(100vh - 24px)}}
</style>
</head>
<body>
@@ -285,12 +285,42 @@ html[data-theme="light"] .details-panel{background:#edf3fa;border-color:#b8c7da}
</div>
<div class="card">
<h2 data-i18n="networkHistory">Network History</h2>
<h2 data-i18n="wanHistory">WAN History</h2>
<div class="chart-grid">
<div class="chart-box"><h3 data-i18n="wanDownloadPackets">WAN Download / RX Packets</h3><div class="chart-canvas"><canvas id="netWanRxChart"></canvas></div></div>
<div class="chart-box"><h3 data-i18n="wanUploadPackets">WAN Upload / TX Packets</h3><div class="chart-canvas"><canvas id="netWanTxChart"></canvas></div></div>
<div class="chart-box"><h3 data-i18n="bridgeDownloadPackets">Bridge Download / RX Packets</h3><div class="chart-canvas"><canvas id="netBridgeRxChart"></canvas></div></div>
<div class="chart-box"><h3 data-i18n="bridgeUploadPackets">Bridge Upload / TX Packets</h3><div class="chart-canvas"><canvas id="netBridgeTxChart"></canvas></div></div>
<div class="chart-box wide"><h3 data-i18n="downloadRxPackets">Download / RX Packets</h3><div class="chart-canvas"><canvas id="netWanRxChart"></canvas></div></div>
<div class="chart-box wide"><h3 data-i18n="uploadTxPackets">Upload / TX Packets</h3><div class="chart-canvas"><canvas id="netWanTxChart"></canvas></div></div>
</div>
</div>
<div class="card">
<h2 data-i18n="bridgeHistory">Bridge History</h2>
<div class="chart-grid">
<div class="chart-box wide"><h3 data-i18n="downloadRxPackets">Download / RX Packets</h3><div class="chart-canvas"><canvas id="netBridgeRxChart"></canvas></div></div>
<div class="chart-box wide"><h3 data-i18n="uploadTxPackets">Upload / TX Packets</h3><div class="chart-canvas"><canvas id="netBridgeTxChart"></canvas></div></div>
</div>
</div>
<div class="card">
<h2 data-i18n="lanHistory">LAN History</h2>
<div class="chart-grid">
<div class="chart-box wide"><h3 data-i18n="downloadRxPackets">Download / RX Packets</h3><div class="chart-canvas"><canvas id="netLanRxChart"></canvas></div></div>
<div class="chart-box wide"><h3 data-i18n="uploadTxPackets">Upload / TX Packets</h3><div class="chart-canvas"><canvas id="netLanTxChart"></canvas></div></div>
</div>
</div>
<div class="card">
<h2 data-i18n="wifi24History">2.4G WiFi History</h2>
<div class="chart-grid">
<div class="chart-box wide"><h3 data-i18n="downloadRxPackets">Download / RX Packets</h3><div class="chart-canvas"><canvas id="netWifi24RxChart"></canvas></div></div>
<div class="chart-box wide"><h3 data-i18n="uploadTxPackets">Upload / TX Packets</h3><div class="chart-canvas"><canvas id="netWifi24TxChart"></canvas></div></div>
</div>
</div>
<div class="card">
<h2 data-i18n="wifi5History">5G WiFi History</h2>
<div class="chart-grid">
<div class="chart-box wide"><h3 data-i18n="downloadRxPackets">Download / RX Packets</h3><div class="chart-canvas"><canvas id="netWifi5RxChart"></canvas></div></div>
<div class="chart-box wide"><h3 data-i18n="uploadTxPackets">Upload / TX Packets</h3><div class="chart-canvas"><canvas id="netWifi5TxChart"></canvas></div></div>
</div>
</div>