로그 송수신 잔여 제거와 패널 주기 조정

This commit is contained in:
seo committed 2026-08-01 11:39:56 +09:00
1 parent c0dadcc8a9
commit 3f3198b29f
5 files changed
+67 -77

No files matched your search

+4 -4
View File
@@ -30,7 +30,7 @@ Control은 라즈베리파이/리눅스 호스트의 팬과 시스템 상태를
- `/etc/systemd/system/*.service` 기준 사용자 생성 서비스의 서비스명, 현재 상태, enable 상태, PID, restart 횟수, 최근 journal 로그를 진단 접힘 영역에서 표시 - `/etc/systemd/system/*.service` 기준 사용자 생성 서비스의 서비스명, 현재 상태, enable 상태, PID, restart 횟수, 최근 journal 로그를 진단 접힘 영역에서 표시
- 사용자 서비스 로그는 서비스별 최근 500줄까지 조회하고, 사용자가 서비스 목록을 스크롤하는 동안에는 자동 갱신이 화면 위치를 되돌리지 않습니다. - 사용자 서비스 로그는 서비스별 최근 500줄까지 조회하고, 사용자가 서비스 목록을 스크롤하는 동안에는 자동 갱신이 화면 위치를 되돌리지 않습니다.
- 영구 journal과 커널 크래시 보존 상태를 진단 접힘 영역에서 표시 - 영구 journal과 커널 크래시 보존 상태를 진단 접힘 영역에서 표시
- 시놀로지에서 라즈베리파이로 들어온 원격 로그와 dmesg 로그는 진단 접힘 영역에서 필요할 때 열람 - dmesg 로그는 진단 접힘 영역에서 필요할 때 열람
- WakeLock 버튼으로 대시보드 화면 꺼짐 방지 - WakeLock 버튼으로 대시보드 화면 꺼짐 방지
- Reboot 버튼으로 2단계 확인 후 시스템 재부팅 요청 - Reboot 버튼으로 2단계 확인 후 시스템 재부팅 요청
- 기본 브라우저 alert/confirm/prompt 대신 대시보드 디자인에 맞춘 custom dialog 사용 - 기본 브라우저 alert/confirm/prompt 대신 대시보드 디자인에 맞춘 custom dialog 사용
@@ -145,7 +145,7 @@ cd /var/www/control
/usr/bin/python3 touch_panel/panel.py /usr/bin/python3 touch_panel/panel.py
``` ```
패널은 Control PHP/API/DB snapshot을 호출하지 않는 standalone 방식입니다. Python이 `/sys/class/net`, `/proc/diskstats`, `/sys/class/thermal`, 팬 hwmon 파일을 직접 읽습니다. 상태패널, 팬패널, 오른쪽 대시보드 카드/그래프는 50ms 간격으로 갱신합니다. 왼쪽 상태패널은 `RASPBERRY`, `SYNOLOGY`, `CHECK` 3열이며, 라즈베리파이 순서는 `SSD I/O`, `LOCAL`, `UPLINK`, `ETH1`, `5G`, `2.4G`입니다. 시놀로지 열은 `PING`, 정상 디스크 `SATA1 I/O`, `SATA2 I/O`, `SATA4 I/O`, `LOCAL`, `UPLINK`를 표시합니다. `CHECK` 열은 `RPI TEMP`, `RPI FAN`, `RPI THROT`, `SYNO TEMP`, `SYNO FAN`, `SYNO GUARD`를 표시합니다. `RPI TEMP`와 `SYNO TEMP`는 80도 이상부터 빨간 표시등을 500ms 간격으로 점멸하고, 팬 카드는 RPM 0 또는 7000 초과를 빨강, 4500 초과를 주황으로 표시합니다. 시놀로지 `PING`은 장기 실행 `ping -i 0.05` 출력 파싱으로 읽고, 15ms 이하 초록, 30ms 이하 주황, 50ms 이하 빨강, 50ms 초과 빨강 500ms 점멸입니다. `RPI THROT`은 정상 초록, 부팅 후 이력 주황, 현재 언더볼트 빨강, 현재 스로틀 빨강 500ms 점멸, 현재 언더볼트와 스로틀 동시 발생 빨강 100ms 점멸로 표시합니다. `SYNO GUARD`는 `synology-fan-guard.service`가 active이고 DSM 자동제어면 초록 `AUTO`, 고온으로 PWM 최대 강제 중이면 주황 `FULL`, 서비스 실패나 팬 RPM 0이면 빨강 `FAIL`로 표시합니다. `LOCAL`은 `lo`, `UPLINK`는 기본 라우트 인터페이스 기준입니다. 라즈베리파이 `ETH1`은 컴퓨터와 연결된 물리 포트 단독 카운터이며 `br0`는 합산하지 않습니다. 시놀로지는 SSH를 유지한 채 50ms마다 디스크별 sector I/O, `LOCAL`, `UPLINK` 카운터를 스트림으로 가져오고, 각 SATA 카드 값에는 SMART 온도를 표시합니다. 라즈베리파이 CPU/SSD 온도, 시놀로지 CPU 온도/FAN RPM, 시놀로지 SATA SMART 온도는 1초 간격으로 캐시해 표시합니다. 왼쪽 표시등은 장식용 heartbeat가 아니라 직전 샘플 대비 현재 카운터 증가량이 0 초과인지에 반응합니다. 각 타일에는 글자 아래에 세 개의 작은 표시등을 두며 읽기/업로드은 초록, 쓰기/다운로드는 주황, 오류/드롭 증가는 빨강으로 표시합니다. 활동 표시등은 이벤트 감지 후 120ms 동안 유지하고, 오류 표시등은 2초 동안 유지해 너무 짧은 이벤트도 눈에 보이게 합니다. 네트워크 카드 오른쪽 값은 항상 `D n`으로 드롭 누적을 표시합니다. `SSD I/O`의 값은 `/dev/sda` Samsung T7을 `smartctl -d sntasmedia`로 읽은 디스크 자체 온도입니다. GUI 사용자 `seo`는 `/etc/sudoers.d/control-touch-panel-smartctl`에서 해당 smartctl 명령만 NOPASSWD로 허용합니다. 네트워크 상태값은 정상, 드롭 누적, 꺼짐으로 표시합니다. 상태패널 최하단의 `PC` 타일은 `192.168.50.73`에 1초마다 ping을 보내 켜짐 여부를 표시하고, 꺼져 있을 때 누르면 `74:56:3C:CD:CD:79`로 WOL magic packet을 보냅니다. PC 타일의 주황은 WOL 후 최대 1분 동안 컴퓨터 응답 대기 표시, 빨강은 magic packet 전송 실패 시 5초 또는 1분 대기 후 응답 실패 시 1분 표시입니다. 패널은 Control PHP/API/DB snapshot을 호출하지 않는 standalone 방식입니다. Python이 `/sys/class/net`, `/proc/diskstats`, `/sys/class/thermal`, 팬 hwmon 파일을 직접 읽습니다. 상태패널의 네트워크/I/O 표시는 50ms 간격으로 갱신하고, 팬 카드와 오른쪽 대시보드 카드/그래프는 500ms 간격으로 갱신합니다. 왼쪽 상태패널은 `RASPBERRY`, `SYNOLOGY`, `CHECK` 3열이며, 라즈베리파이 순서는 `SSD I/O`, `LOCAL`, `UPLINK`, `ETH1`, `5G`, `2.4G`입니다. 시놀로지 열은 `PING`, 정상 디스크 `SATA1 I/O`, `SATA2 I/O`, `SATA4 I/O`, `LOCAL`, `UPLINK`를 표시합니다. `CHECK` 열은 `RPI TEMP`, `RPI FAN`, `RPI THROT`, `SYNO TEMP`, `SYNO FAN`, `SYNO GUARD`를 표시합니다. `RPI TEMP`와 `SYNO TEMP`는 80도 이상부터 빨간 표시등을 500ms 간격으로 점멸하고, 팬 카드는 RPM 0 또는 7000 초과를 빨강, 4500 초과를 주황으로 표시합니다. 시놀로지 `PING`은 장기 실행 `ping -i 0.5` 출력 파싱으로 읽고, 15ms 이하 초록, 30ms 이하 주황, 50ms 이하 빨강, 50ms 초과 빨강 500ms 점멸입니다. `RPI THROT`은 정상 초록, 부팅 후 이력 주황, 현재 언더볼트 빨강, 현재 스로틀 빨강 500ms 점멸, 현재 언더볼트와 스로틀 동시 발생 빨강 100ms 점멸로 표시합니다. `SYNO GUARD`는 `synology-fan-guard.service`가 active이고 DSM 자동제어면 초록 `AUTO`, 고온으로 PWM 최대 강제 중이면 주황 `FULL`, 서비스 실패나 팬 RPM 0이면 빨강 `FAIL`로 표시합니다. `LOCAL`은 `lo`, `UPLINK`는 기본 라우트 인터페이스 기준입니다. 라즈베리파이 `ETH1`은 컴퓨터와 연결된 물리 포트 단독 카운터이며 `br0`는 합산하지 않습니다. 시놀로지는 SSH를 유지한 채 50ms마다 디스크별 sector I/O, `LOCAL`, `UPLINK` 카운터를 스트림으로 가져오고, 각 SATA 카드 값에는 SMART 온도를 표시합니다. 라즈베리파이 CPU/SSD 온도, 시놀로지 CPU 온도/FAN RPM, 시놀로지 SATA SMART 온도는 500ms 간격으로 캐시해 표시합니다. 왼쪽 표시등은 장식용 heartbeat가 아니라 직전 샘플 대비 현재 카운터 증가량이 0 초과인지에 반응합니다. 각 타일에는 글자 아래에 세 개의 작은 표시등을 두며 읽기/업로드은 초록, 쓰기/다운로드는 주황, 오류/드롭 증가는 빨강으로 표시합니다. 활동 표시등은 이벤트 감지 후 120ms 동안 유지하고, 오류 표시등은 2초 동안 유지해 너무 짧은 이벤트도 눈에 보이게 합니다. 네트워크 카드 오른쪽 값은 항상 `D n`으로 드롭 누적을 표시합니다. `SSD I/O`의 값은 `/dev/sda` Samsung T7을 `smartctl -d sntasmedia`로 읽은 디스크 자체 온도입니다. GUI 사용자 `seo`는 `/etc/sudoers.d/control-touch-panel-smartctl`에서 해당 smartctl 명령만 NOPASSWD로 허용합니다. 네트워크 상태값은 정상, 드롭 누적, 꺼짐으로 표시합니다. 상태패널 최하단의 `PC` 타일은 `192.168.50.73`에 1초마다 ping을 보내 켜짐 여부를 표시하고, 꺼져 있을 때 누르면 `74:56:3C:CD:CD:79`로 WOL magic packet을 보냅니다. PC 타일의 주황은 WOL 후 최대 1분 동안 컴퓨터 응답 대기 표시, 빨강은 magic packet 전송 실패 시 5초 또는 1분 대기 후 응답 실패 시 1분 표시입니다.
오른쪽 상단 카드는 팬 RPM, CPU 온도, PWM 퍼센트, 메모리 사용률을 표시합니다. 아래에는 최근 10초 기준 온도/RPM 그래프와 WAN 트래픽 그래프를 표시합니다. WAN 속도와 그래프 Y축은 byte/sec 기준으로 `B/s`, `KB/s`, `MB/s`, `GB/s` 중 자동 변환하고, 다운로드 주황/업로드 초록 두 선을 같은 축 범위에 맞춰 그립니다. 그래프 우측 상단 현재값은 고정폭으로 표시합니다. 위 그래프는 좌측 Y축에 온도, 우측 Y축에 RPM 단위를 표시하고, 아래 WAN 그래프는 기존 형태를 유지합니다. 팬 auto/manual/off 버튼은 없고 PWM 슬라이더는 현재값 위치만 보여 주며 터치 제어는 받지 않습니다. 오른쪽 상단 카드는 팬 RPM, CPU 온도, PWM 퍼센트, 메모리 사용률을 표시합니다. 아래에는 최근 10초 기준 온도/RPM 그래프와 WAN 트래픽 그래프를 표시합니다. WAN 속도와 그래프 Y축은 byte/sec 기준으로 `B/s`, `KB/s`, `MB/s`, `GB/s` 중 자동 변환하고, 다운로드 주황/업로드 초록 두 선을 같은 축 범위에 맞춰 그립니다. 그래프 우측 상단 현재값은 고정폭으로 표시합니다. 위 그래프는 좌측 Y축에 온도, 우측 Y축에 RPM 단위를 표시하고, 아래 WAN 그래프는 기존 형태를 유지합니다. 팬 auto/manual/off 버튼은 없고 PWM 슬라이더는 현재값 위치만 보여 주며 터치 제어는 받지 않습니다.
@@ -165,7 +165,7 @@ systemd 방식이 필요하면 `systemd/control-touch-panel.service`를 `/etc/sy
- WebSocket 끊김 시 HTTP fallback 상태 갱신: 2초마다 - WebSocket 끊김 시 HTTP fallback 상태 갱신: 2초마다
- 탭 복귀: 즉시 1회 상태 갱신 - 탭 복귀: 즉시 1회 상태 갱신
- UPS/배터리 잔여 시간: 현재 운영 구성에서는 표시하지 않음 - UPS/배터리 잔여 시간: 현재 운영 구성에서는 표시하지 않음
- 시놀로지 원격 로그/dmesg: 진단 패널에서 열었을 때만 1초마다 갱신, 닫으면 중지 - dmesg: 진단 패널에서 열었을 때만 1초마다 갱신, 닫으면 중지
- WiFi 관측 보정 timer: 10초마다 `bin/wifi_observe.php` 실행 - WiFi 관측 보정 timer: 10초마다 `bin/wifi_observe.php` 실행
- 외출/귀가 팬 정책: 팬 정책 적용 루프에서 기본 30초마다 서울 HA의 휴대폰 device tracker를 확인 - 외출/귀가 팬 정책: 팬 정책 적용 루프에서 기본 30초마다 서울 HA의 휴대폰 device tracker를 확인
- WebSocket 소스 변경 감지: 15초마다 확인 후 재시작 - WebSocket 소스 변경 감지: 15초마다 확인 후 재시작
@@ -226,7 +226,7 @@ control-wifi-observe.service
13. 저전압/스로틀링 상태 파일에는 최근 episode 시작/종료를 보관하고, 최근 10분 발생 횟수, 감지 누적시간, 감지 비율을 계산합니다. 13. 저전압/스로틀링 상태 파일에는 최근 episode 시작/종료를 보관하고, 최근 10분 발생 횟수, 감지 누적시간, 감지 비율을 계산합니다.
14. UPS/배터리 장치가 제거되어 배터리 센서 조회와 잔여 시간 학습 계산은 기본적으로 돌리지 않습니다. 14. UPS/배터리 장치가 제거되어 배터리 센서 조회와 잔여 시간 학습 계산은 기본적으로 돌리지 않습니다.
15. System Notice 조건이 맞으면 Control이 `system_notice_logs`에 이력을 저장합니다. 15. System Notice 조건이 맞으면 Control이 `system_notice_logs`에 이력을 저장합니다.
16. 대시보드 첫 화면에는 팬/전원/스로틀링/WiFi/핵심 차트를 우선 배치하고, 상세 차트, 프로세스 후보, 사용자 서비스, 시놀로지 원격 로그, dmesg는 접힘 영역으로 분리합니다. 16. 대시보드 첫 화면에는 팬/전원/스로틀링/WiFi/핵심 차트를 우선 배치하고, 상세 차트, 프로세스 후보, 사용자 서비스, dmesg는 접힘 영역으로 분리합니다.
## 팬 판단 온도 ## 팬 판단 온도
+2 -2
View File
@@ -188,7 +188,7 @@ function command_first_line(array $cmd, bool $root = false, int $timeout = 3): s
return (int)$result['code'] === 0 && $lines ? (string)$lines[0] : ''; return (int)$result['code'] === 0 && $lines ? (string)$lines[0] : '';
} }
function remote_log_status(): array function log_retention_status(): array
{ {
$journalDir = '/var/log/journal'; $journalDir = '/var/log/journal';
$journalDisk = command_first_line(['/usr/bin/journalctl', '--disk-usage', '--no-pager'], false, 3); $journalDisk = command_first_line(['/usr/bin/journalctl', '--disk-usage', '--no-pager'], false, 3);
@@ -4681,7 +4681,7 @@ function collect_snapshot(bool $applyFan = true): array
'history' => $history, 'history' => $history,
'processes' => $processes, 'processes' => $processes,
'custom_services' => custom_systemd_services(), 'custom_services' => custom_systemd_services(),
'remote_logs' => remote_log_status(), 'log_status' => log_retention_status(),
'touch_display' => touch_display_status(), 'touch_display' => touch_display_status(),
'fan_spike' => $fanSpike, 'fan_spike' => $fanSpike,
'fan_spike_history' => fan_spike_history((int)setting_value('display.fan_notice_history_limit')), 'fan_spike_history' => fan_spike_history((int)setting_value('display.fan_notice_history_limit')),
+39 -55
View File
@@ -37,7 +37,7 @@
statusBatteryRemaining: $('#statusBatteryRemaining'), statusBatteryRemaining: $('#statusBatteryRemaining'),
batteryRuntimeTooltip: $('#batteryRuntimeTooltip'), batteryRuntimeTooltip: $('#batteryRuntimeTooltip'),
spikeLogList: $('#spikeLogList'), spikeLogList: $('#spikeLogList'),
remoteLogList: $('#remoteLogList'), logStatusList: $('#logStatusList'),
customServiceList: $('#customServiceList'), customServiceList: $('#customServiceList'),
noticeBaseline: $('#noticeBaseline'), noticeBaseline: $('#noticeBaseline'),
rebootBtn: $('#rebootBtn'), rebootBtn: $('#rebootBtn'),
@@ -203,22 +203,14 @@
noNoticeHistory: 'No system notice history.', noNoticeHistory: 'No system notice history.',
customServices: 'Custom Services', customServices: 'Custom Services',
noCustomServices: 'No custom services.', noCustomServices: 'No custom services.',
remoteLogs: 'Remote Logs', logStatus: 'Log Status',
remoteLogsLoading: 'Remote log status loading...', logStatusLoading: 'Log status loading...',
remotePiToSynology: 'Raspberry -> Synology', logPersistentJournal: 'Persistent Journal',
remoteSynologyToPi: 'Synology -> Raspberry', logCrashLogs: 'Crash Logs',
remotePersistentJournal: 'Persistent Journal', logOk: 'OK',
remoteCrashLogs: 'Crash Logs', logWarn: 'Check',
remoteOk: 'OK', logUsage: 'Usage',
remoteWarn: 'Check', logBoots: 'Boots',
remoteTarget: 'Target',
remoteTransport: 'Transport',
remoteQueue: 'Queue',
remoteListener: 'Listener',
remoteLatest: 'Latest',
remoteFiles: 'Files',
remoteUsage: 'Usage',
remoteBoots: 'Boots',
logFilterAll: 'All', logFilterAll: 'All',
logFilterImportant: 'Important', logFilterImportant: 'Important',
logFilterError: 'Error', logFilterError: 'Error',
@@ -230,8 +222,8 @@
qualityOk: 'OK', qualityOk: 'OK',
qualityWarn: 'Increasing', qualityWarn: 'Increasing',
qualityBad: 'Attention', qualityBad: 'Attention',
remoteSaved: 'Saved', logSaved: 'Saved',
remotePending: 'Pending', logPending: 'Pending',
serviceLogs: 'Logs', serviceLogs: 'Logs',
serviceEnabled: 'enabled', serviceEnabled: 'enabled',
servicePid: 'pid', servicePid: 'pid',
@@ -435,22 +427,14 @@
noNoticeHistory: '시스템 알림 이력이 없습니다.', noNoticeHistory: '시스템 알림 이력이 없습니다.',
customServices: '사용자 서비스', customServices: '사용자 서비스',
noCustomServices: '사용자 생성 서비스가 없습니다.', noCustomServices: '사용자 생성 서비스가 없습니다.',
remoteLogs: '원격 로그', logStatus: '로그 보존',
remoteLogsLoading: '원격 로그 상태 확인 중...', logStatusLoading: '로그 보존 상태 확인 중...',
remotePiToSynology: '라즈베리파이 -> 시놀로지', logPersistentJournal: '영구 저널',
remoteSynologyToPi: '시놀로지 -> 라즈베리파이', logCrashLogs: '커널 크래시',
remotePersistentJournal: '영구 저널', logOk: '정상',
remoteCrashLogs: '커널 크래시', logWarn: '확인',
remoteOk: '정상', logUsage: '사용량',
remoteWarn: '확인', logBoots: '부팅 이력',
remoteTarget: '대상',
remoteTransport: '전송',
remoteQueue: '대기열',
remoteListener: '수신',
remoteLatest: '최근',
remoteFiles: '파일',
remoteUsage: '사용량',
remoteBoots: '부팅 이력',
logFilterAll: '전체', logFilterAll: '전체',
logFilterImportant: '중요', logFilterImportant: '중요',
logFilterError: '오류', logFilterError: '오류',
@@ -462,8 +446,8 @@
qualityOk: '정상', qualityOk: '정상',
qualityWarn: '증가', qualityWarn: '증가',
qualityBad: '주의', qualityBad: '주의',
remoteSaved: '저장', logSaved: '저장',
remotePending: '대기', logPending: '대기',
serviceLogs: '로그', serviceLogs: '로그',
serviceEnabled: '활성화', serviceEnabled: '활성화',
servicePid: 'PID', servicePid: 'PID',
@@ -2086,56 +2070,56 @@
: `<div class="spike-log-empty">${escapeHtml(t('noNoticeHistory'))}</div>`; : `<div class="spike-log-empty">${escapeHtml(t('noNoticeHistory'))}</div>`;
} }
function remoteState(ok, warn = false) { function logStatusState(ok, warn = false) {
if (ok) { if (ok) {
return { return {
className: 'active', className: 'active',
label: t('remoteOk'), label: t('logOk'),
}; };
} }
return { return {
className: warn ? 'warning' : 'failed', className: warn ? 'warning' : 'failed',
label: t('remoteWarn'), label: t('logWarn'),
}; };
} }
function remoteMeta(items) { function logStatusMeta(items) {
return items return items
.filter(item => item.value !== null && item.value !== undefined && item.value !== '') .filter(item => item.value !== null && item.value !== undefined && item.value !== '')
.map(item => `${item.label}: ${item.value}`) .map(item => `${item.label}: ${item.value}`)
.join(' · ') || '-'; .join(' · ') || '-';
} }
function renderRemoteLogs(data = {}) { function renderLogStatus(data = {}) {
if (!els.remoteLogList) return; if (!els.logStatusList) return;
const journal = data.journal || {}; const journal = data.journal || {};
const pstore = data.pstore || {}; const pstore = data.pstore || {};
const rows = [ const rows = [
{ {
title: t('remotePersistentJournal'), title: t('logPersistentJournal'),
desc: journal.path || '-', desc: journal.path || '-',
meta: remoteMeta([ meta: logStatusMeta([
{ label: t('remoteUsage'), value: journal.disk_usage || '-' }, { label: t('logUsage'), value: journal.disk_usage || '-' },
{ label: t('remoteBoots'), value: journal.boot_count === null || journal.boot_count === undefined ? '-' : Number(journal.boot_count).toLocaleString() }, { label: t('logBoots'), value: journal.boot_count === null || journal.boot_count === undefined ? '-' : Number(journal.boot_count).toLocaleString() },
]), ]),
state: remoteState(Boolean(journal.ok), true), state: logStatusState(Boolean(journal.ok), true),
}, },
{ {
title: t('remoteCrashLogs'), title: t('logCrashLogs'),
desc: pstore.saved_path || '-', desc: pstore.saved_path || '-',
meta: remoteMeta([ meta: logStatusMeta([
{ label: 'pstore', value: pstore.mounted ? 'mounted' : '-' }, { label: 'pstore', value: pstore.mounted ? 'mounted' : '-' },
{ label: 'collector', value: pstore.collector_active || '-' }, { label: 'collector', value: pstore.collector_active || '-' },
{ label: t('remoteSaved'), value: Number(pstore.saved_files || 0).toLocaleString() }, { label: t('logSaved'), value: Number(pstore.saved_files || 0).toLocaleString() },
{ label: t('remotePending'), value: Number(pstore.pending_files || 0).toLocaleString() }, { label: t('logPending'), value: Number(pstore.pending_files || 0).toLocaleString() },
]), ]),
state: remoteState(Boolean(pstore.ok), true), state: logStatusState(Boolean(pstore.ok), true),
}, },
]; ];
els.remoteLogList.innerHTML = rows.map(row => ` els.logStatusList.innerHTML = rows.map(row => `
<div class="service-item"> <div class="service-item">
<div class="service-head"> <div class="service-head">
<div class="service-title"> <div class="service-title">
@@ -2203,7 +2187,7 @@
? `${baselineTemp.toFixed(1)}°C / ${Math.round(baselineRpm).toLocaleString()} RPM · ${stateText}` ? `${baselineTemp.toFixed(1)}°C / ${Math.round(baselineRpm).toLocaleString()} RPM · ${stateText}`
: stateText; : stateText;
renderSpikeHistory(data.fan_spike_history || []); renderSpikeHistory(data.fan_spike_history || []);
renderRemoteLogs(data.remote_logs || {}); renderLogStatus(data.log_status || {});
renderCustomServices(data.custom_services || []); renderCustomServices(data.custom_services || []);
setText(els.noticeBaseline, baselineText); setText(els.noticeBaseline, baselineText);
+3 -3
View File
@@ -371,9 +371,9 @@ html[data-theme="light"] .details-panel{background:#edf3fa;border-color:#b8c7da}
<summary data-i18n="diagnostics">Diagnostics</summary> <summary data-i18n="diagnostics">Diagnostics</summary>
<div class="details-body"> <div class="details-body">
<div class="spike-log-box"> <div class="spike-log-box">
<h3 data-i18n="remoteLogs">Remote Logs</h3> <h3 data-i18n="logStatus">Log Status</h3>
<div id="remoteLogList" class="service-list remote-log-list"> <div id="logStatusList" class="service-list">
<div class="spike-log-empty" data-i18n="remoteLogsLoading">Remote log status loading...</div> <div class="spike-log-empty" data-i18n="logStatusLoading">Log status loading...</div>
</div> </div>
</div> </div>
+19 -13
View File
@@ -104,14 +104,16 @@ case "$enable" in ''|*[!0-9]*) enable=-1 ;; esac
awk -v cpu="$cpu" -v fan="$fan" -v pwm="$pwm" -v enable="$enable" -v guard="$guard" 'BEGIN {printf "SYNO_CPU=%.1f\nSYNO_FAN=%d\nSYNO_GUARD=%s\nSYNO_PWM=%d\nSYNO_PWM_ENABLE=%d\n", cpu / 1000, fan, guard, pwm, enable}' awk -v cpu="$cpu" -v fan="$fan" -v pwm="$pwm" -v enable="$enable" -v guard="$guard" 'BEGIN {printf "SYNO_CPU=%.1f\nSYNO_FAN=%d\nSYNO_GUARD=%s\nSYNO_PWM=%d\nSYNO_PWM_ENABLE=%d\n", cpu / 1000, fan, guard, pwm, enable}'
""" """
SAMPLE_INTERVAL_MS = 50 SAMPLE_INTERVAL_MS = 50
DASHBOARD_INTERVAL_MS = 500
FAN_INTERVAL_MS = 500
GRAPH_SECONDS = 10 GRAPH_SECONDS = 10
GRAPH_POINTS = GRAPH_SECONDS * 1000 // SAMPLE_INTERVAL_MS GRAPH_POINTS = GRAPH_SECONDS * 1000 // DASHBOARD_INTERVAL_MS
SYNOLOGY_RESTART_INTERVAL_MS = 1000 SYNOLOGY_RESTART_INTERVAL_MS = 1000
TEMP_INTERVAL_MS = 1000 TEMP_INTERVAL_MS = 500
DISK_TEMP_INTERVAL_MS = TEMP_INTERVAL_MS DISK_TEMP_INTERVAL_MS = TEMP_INTERVAL_MS
RPI_THROTTLE_INTERVAL_MS = SAMPLE_INTERVAL_MS RPI_THROTTLE_INTERVAL_MS = SAMPLE_INTERVAL_MS
SYNOLOGY_DISK_TEMP_INTERVAL_MS = TEMP_INTERVAL_MS SYNOLOGY_DISK_TEMP_INTERVAL_MS = TEMP_INTERVAL_MS
SYNOLOGY_HEALTH_INTERVAL_MS = TEMP_INTERVAL_MS SYNOLOGY_HEALTH_INTERVAL_MS = FAN_INTERVAL_MS
def byte_rate_unit(values): def byte_rate_unit(values):
@@ -624,7 +626,11 @@ class TouchPanel(QMainWindow):
self.dashboard_timer = QTimer(self) self.dashboard_timer = QTimer(self)
self.dashboard_timer.timeout.connect(self.update_dashboard) self.dashboard_timer.timeout.connect(self.update_dashboard)
self.dashboard_timer.start(SAMPLE_INTERVAL_MS) self.dashboard_timer.start(DASHBOARD_INTERVAL_MS)
self.fan_timer = QTimer(self)
self.fan_timer.timeout.connect(self.update_fan_status)
self.fan_timer.start(FAN_INTERVAL_MS)
self.pc_ping = QProcess(self) self.pc_ping = QProcess(self)
self.pc_ping.finished.connect(self.pc_ping_finished) self.pc_ping.finished.connect(self.pc_ping_finished)
@@ -676,6 +682,7 @@ class TouchPanel(QMainWindow):
self.pulse_timer.timeout.connect(self.tick_pulses) self.pulse_timer.timeout.connect(self.tick_pulses)
self.pulse_timer.start(25) self.pulse_timer.start(25)
self.sample() self.sample()
self.update_fan_status()
self.ping_pc() self.ping_pc()
self.update_disk_temp() self.update_disk_temp()
self.update_synology_status() self.update_synology_status()
@@ -803,11 +810,6 @@ class TouchPanel(QMainWindow):
dt = max(0.001, now - self.last_sample) if self.last_sample else 0.001 dt = max(0.001, now - self.last_sample) if self.last_sample else 0.001
self.last_sample = now self.last_sample = now
rpm = read_int(self.fan["rpm"])
pwm = read_int(self.fan["pwm"])
enable = read_int(self.fan["enable"], 2)
percent = pwm / 255 * 100 if pwm > 0 else 0
reads, writes, disk_error = disk_io() reads, writes, disk_error = disk_io()
io_read_activity = self.last_io is not None and reads != self.last_io[0] io_read_activity = self.last_io is not None and reads != self.last_io[0]
io_write_activity = self.last_io is not None and writes != self.last_io[1] io_write_activity = self.last_io is not None and writes != self.last_io[1]
@@ -843,9 +845,9 @@ class TouchPanel(QMainWindow):
self.last_net[key] = current self.last_net[key] = current
self.current_rates = rates self.current_rates = rates
self.render_status(rpm, pwm, percent, enable, groups, rates, activity, io_read_activity, io_write_activity, disk_error) self.render_status(groups, rates, activity, io_read_activity, io_write_activity, disk_error)
def render_status(self, rpm, pwm, percent, enable, groups, rates, activity, io_read_activity, io_write_activity, disk_error): def render_status(self, groups, rates, activity, io_read_activity, io_write_activity, disk_error):
disk_temp_text = f"{self.disk_temp:.1f} C" if self.disk_temp is not None else "- C" disk_temp_text = f"{self.disk_temp:.1f} C" if self.disk_temp is not None else "- C"
self.rows["io"].set_state(disk_temp_text, disk_error, io_read_activity, io_write_activity) self.rows["io"].set_state(disk_temp_text, disk_error, io_read_activity, io_write_activity)
self.rows["local"].set_state(self.iface_detail(groups["local"], "lo"), activity["local"]["error"], activity["local"]["read"], activity["local"]["write"]) self.rows["local"].set_state(self.iface_detail(groups["local"], "lo"), activity["local"]["error"], activity["local"]["read"], activity["local"]["write"])
@@ -853,12 +855,16 @@ class TouchPanel(QMainWindow):
self.rows["lan"].set_state(self.iface_detail(groups["lan"], "eth1"), activity["lan"]["error"], activity["lan"]["read"], activity["lan"]["write"]) self.rows["lan"].set_state(self.iface_detail(groups["lan"], "eth1"), activity["lan"]["error"], activity["lan"]["read"], activity["lan"]["write"])
self.rows["wifi24"].set_state(self.iface_detail(groups["wifi24"], "wlan0"), activity["wifi24"]["error"], activity["wifi24"]["read"], activity["wifi24"]["write"]) self.rows["wifi24"].set_state(self.iface_detail(groups["wifi24"], "wlan0"), activity["wifi24"]["error"], activity["wifi24"]["read"], activity["wifi24"]["write"])
self.rows["wifi5"].set_state(self.iface_detail(groups["wifi5"], "wlan1"), activity["wifi5"]["error"], activity["wifi5"]["read"], activity["wifi5"]["write"]) self.rows["wifi5"].set_state(self.iface_detail(groups["wifi5"], "wlan1"), activity["wifi5"]["error"], activity["wifi5"]["read"], activity["wifi5"]["write"])
def update_fan_status(self):
rpm = read_int(self.fan["rpm"])
pwm = read_int(self.fan["pwm"])
percent = pwm / 255 * 100 if pwm > 0 else 0
self.render_rpi_fan(rpm) self.render_rpi_fan(rpm)
self.latest_rpm = rpm self.latest_rpm = rpm
self.latest_pwm = pwm self.latest_pwm = pwm
self.latest_percent = percent self.latest_percent = percent
del enable
def update_dashboard(self): def update_dashboard(self):
rpm = getattr(self, "latest_rpm", read_int(self.fan["rpm"])) rpm = getattr(self, "latest_rpm", read_int(self.fan["rpm"]))
@@ -892,7 +898,7 @@ class TouchPanel(QMainWindow):
if self.synology_ping_proc.state() != QProcess.NotRunning: if self.synology_ping_proc.state() != QProcess.NotRunning:
return return
self.synology_ping_buffer = "" self.synology_ping_buffer = ""
self.synology_ping_proc.start("/usr/bin/ping", ["-n", "-i", "0.05", SYNOLOGY_PING_HOST]) self.synology_ping_proc.start("/usr/bin/ping", ["-n", "-i", "0.5", SYNOLOGY_PING_HOST])
def synology_ping_ready(self): def synology_ping_ready(self):
output = bytes(self.synology_ping_proc.readAllStandardOutput()).decode("utf-8", "ignore") output = bytes(self.synology_ping_proc.readAllStandardOutput()).decode("utf-8", "ignore")