로그 송수신 잔여 제거와 패널 주기 조정
This commit is contained in:
1 parent
c0dadcc8a9
commit
3f3198b29f
5 files changed
+67
-77
No files matched your search
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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")
|
||||||
|
|||||||
Reference in new issue
Block a user