사용자 서비스 로그 갱신 흐름 정리

This commit is contained in:
seo
2026-07-20 12:39:19 +09:00
parent 52b04453e7
commit ab55e7d4a6
4 changed files with 18 additions and 2 deletions
+4
View File
@@ -25,6 +25,7 @@ Control은 라즈베리파이/리눅스 호스트의 팬과 시스템 상태를
- System Notice 최근 이력 표시
- process CPU/MEM 후보는 프로세스 상세 접힘 영역에서 표시
- `/etc/systemd/system/*.service` 기준 사용자 생성 서비스의 서비스명, 현재 상태, enable 상태, PID, restart 횟수, 최근 journal 로그를 진단 접힘 영역에서 표시
- 사용자 서비스 로그는 서비스별 최근 500줄까지 조회하고, 사용자가 서비스 목록을 스크롤하는 동안에는 자동 갱신이 화면 위치를 되돌리지 않습니다.
- dmesg 로그는 진단 접힘 영역에서 필요할 때 열람
- WakeLock 버튼으로 대시보드 화면 꺼짐 방지
- Reboot 버튼으로 2단계 확인 후 시스템 재부팅 요청
@@ -230,8 +231,11 @@ control-wifi-observe.service
- 센서 수집 주기와 DB 증가량을 확인합니다.
- UPS/배터리를 다시 붙일 때는 `/home/seo/secret/control.php``battery.enabled`와 용량값을 먼저 확인합니다.
- WiFi client 표는 갱신 중에도 가로 스크롤 위치를 보존합니다.
- 사용자 서비스 목록은 갱신 중에도 스크롤 위치를 보존하고, 스크롤/터치/휠 조작 직후에는 DOM 교체를 잠시 미룹니다.
- 하드웨어 또는 OS 변경 후 fan sysfs 경로를 확인합니다.
- AP 국가는 `PA`로 두고, `router-performance.service``wlan0`, `wlan1` power save를 끄고 출력 요청값을 `3600`으로 넣습니다. 실제 출력은 무선 모듈/드라이버가 허용하는 값에서 멈추며 현재 두 AP 모두 `31.00 dBm`으로 표시됩니다.
- 2.4G 내장 WiFi는 `hostapd-24g.service` 개별 restart 직후 일부 IoT 단말이 WPA/EAPOL 재협상 루프에 들어갈 수 있으므로, 안정화된 상태에서는 개별 restart를 피하고 필요 시 전체 reboot 또는 채널 변경으로 재초기화합니다.
- `hostapd-24g.service`, `hostapd-5g.service`는 종료 후 해당 `wlan` 인터페이스를 `br0`에서 떼도록 두어, 모듈이 빠졌다가 돌아온 뒤 hostapd가 다시 붙이는 흐름으로 정리합니다.
- 5G WiFi 연결 시간은 외부 모듈이 값을 제공하지 않을 때 서버가 처음 감지한 시각 기준으로 계산합니다. 현재 5G 목록에서 MAC이 사라지면 관측 세션을 즉시 종료하므로 재연결 시 0부터 다시 누적됩니다.
- `control-wifi-observe.timer`는 10초마다 `/var/www/control/bin/wifi_observe.php`를 실행해 사용자가 대시보드에 접속하지 않아도 5G 연결 시간 카운터를 시작하고 유지합니다.
- WebSocket은 장기 실행 프로세스이므로 `public/api.php`, `config/config.php`, `bin/control_ws.php` 변경을 감지하면 15초 안에 종료되고 `control-websocket.service`가 새 프로세스로 재시작합니다.
+1 -1
View File
@@ -170,7 +170,7 @@ function setting_definitions(): array
'notice.log_interval_seconds' => ['group' => 'notice', 'label' => 'Alert 반복 기록 간격', 'type' => 'number', 'default' => 300, 'min' => 30, 'max' => 3600, 'step' => 30, 'unit' => '초'],
'notice.downward_only_process_hide' => ['group' => 'notice', 'label' => '하강 변화 원인 후보 숨김', 'type' => 'boolean', 'default' => 1],
'display.process_limit' => ['group' => 'display', 'label' => '프로세스 후보 표시 수', 'type' => 'number', 'default' => 6, 'min' => 1, 'max' => 30, 'step' => 1, 'unit' => '개'],
'display.custom_service_log_lines' => ['group' => 'display', 'label' => '사용자 서비스 로그 줄 수', 'type' => 'number', 'default' => 12, 'min' => 1, 'max' => 50, 'step' => 1, 'unit' => '줄'],
'display.custom_service_log_lines' => ['group' => 'display', 'label' => '사용자 서비스 로그 줄 수', 'type' => 'number', 'default' => 500, 'min' => 1, 'max' => 500, 'step' => 10, 'unit' => '줄'],
'display.custom_service_cache_seconds' => ['group' => 'display', 'label' => '사용자 서비스 캐시 시간', 'type' => 'number', 'default' => 5, 'min' => 1, 'max' => 60, 'step' => 1, 'unit' => '초'],
'display.fan_notice_history_limit' => ['group' => 'display', 'label' => '팬 이상 이력 표시 수', 'type' => 'number', 'default' => 100, 'min' => 10, 'max' => 500, 'step' => 10, 'unit' => '개'],
'battery.trend_hours' => ['group' => 'battery', 'label' => '단기 배터리 학습 범위', 'type' => 'number', 'default' => 24, 'min' => 1, 'max' => 48, 'step' => 1, 'unit' => '시간'],
+1 -1
View File
@@ -3065,7 +3065,7 @@ function parse_systemctl_show_records(string $out): array
function systemd_service_logs(string $unit, int $limit = 12): array
{
$limit = max(1, min(50, $limit));
$limit = max(1, min(500, $limit));
$cmd = [
'/usr/bin/journalctl',
'-u',
+12
View File
@@ -92,6 +92,7 @@
settingsPayload: null,
settingsOpen: false,
settingsDirty: false,
customServiceScrollHoldUntil: 0,
};
const storageKeys = {
@@ -1621,6 +1622,9 @@
function renderCustomServices(rows = []) {
if (!els.customServiceList) return;
if (Date.now() < state.customServiceScrollHoldUntil) return;
const scrollTop = els.customServiceList.scrollTop;
els.customServiceList.innerHTML = rows.length
? rows.map(row => {
@@ -1653,6 +1657,8 @@
`;
}).join('')
: `<div class="spike-log-empty">${escapeHtml(t('noCustomServices'))}</div>`;
els.customServiceList.scrollTop = scrollTop;
}
function renderFanCause(data) {
@@ -2214,6 +2220,12 @@
els.dmesgToggle?.addEventListener('click', () => {
setDmesgOpen(!state.dmesgOpen);
});
const holdCustomServiceScroll = () => {
state.customServiceScrollHoldUntil = Date.now() + 4000;
};
['scroll', 'wheel', 'touchstart', 'touchmove', 'pointerdown'].forEach(eventName => {
els.customServiceList?.addEventListener(eventName, holdCustomServiceScroll, { passive: true });
});
els.wifiTable?.addEventListener('click', event => {
const button = event.target?.closest?.('.wifi-host-edit');
if (!button) return;