Control 알림과 notice 설정 세분화
This commit is contained in:
@@ -111,10 +111,11 @@ WebSocket 장기 실행 프로세스에서는 1분 단위 단기 집계 결과
|
||||
설정 가능한 항목은 다음 범위입니다.
|
||||
|
||||
- 팬 자동 제어: 팬 시작 온도, 최대 온도, 즉시 최대 PWM 온도, 자동 상승/하강 PWM step
|
||||
- 보안 정책: 자동 로그인 유지 기간, remember 쿠키 Secure/SameSite, CSRF 길이, remember 토큰 길이, User-Agent/IP 접두사 검증, 로그인 실패 잠금, 재부팅 확인 문구, 명령 timeout, WiFi 조작 허용 여부
|
||||
- 보안 정책: 자동 로그인 유지 기간, remember 쿠키 Secure/SameSite, CSRF 길이, remember 토큰 길이, User-Agent/IP 접두사 검증, 로그인 실패 잠금, 재부팅 허용 여부, 재부팅 확인 문구, 명령 timeout, WiFi 조작 허용 여부
|
||||
- 알림 정책: 배터리 긴급/위험/경고/복구 기준, 배터리 낮음/복구 알림 쿨다운, 시스템 유의사항 알림 쿨다운
|
||||
- 저전압/스로틀링: 최근 판정창, 복구 중 유지 시간, 반복 발생 기준 횟수
|
||||
- 팬 이상감지: 기준 표본 수, 최신 표본 제외 수, RPM/온도 이상 감지 차이, RPM/온도 복구 차이
|
||||
- 배터리 예측: 단기/장기 학습 범위, 후보 필터 비율, 부하 보정 강도, 전압 하한, 용량 모델 가중치
|
||||
- 팬 이상감지: 기준 표본 수, 최신 표본 제외 수, RPM/온도 이상 감지 차이, RPM/온도 복구 차이, RPM 감시 시작 PWM, alert 유지 중 반복 기록, 반복 기록 간격, 하강 변화 원인 후보 표시 여부
|
||||
- 배터리 예측: 단기/장기 학습 범위, 후보 필터 비율, 부하 보정 강도, 전압 하한, 용량 모델 가중치, 상태/잔여시간 차트 표본 수
|
||||
|
||||
## 갱신 주기
|
||||
|
||||
@@ -139,9 +140,9 @@ Control은 서버에서 Home Assistant webhook으로 알림 payload를 보내고
|
||||
- 사용 속성: `tag`, `group`, `channel`, `importance`, `priority`, `ttl`, `sticky`, `persistent`, `renotify`, `color`, `notification_icon`, `vibrationPattern`, `ledColor`, `visibility`, `timeout`, `clickAction`, `url`, `actions`
|
||||
- 발송 결과는 `ha_notify_logs`에 저장
|
||||
|
||||
배터리 SOC 20% 이하 경고는 `control-battery-low` tag를 사용합니다. SOC 값이 조금씩 달라져도 Android 알림은 같은 tag로 갱신되어 알림 목록에 여러 장이 계속 쌓이지 않습니다. 20% 이하에서는 `주의`, 15% 이하에서는 `경고`, 10% 이하에서는 `위험`, 5% 이하에서는 `긴급` 단계로 채널, 중요도, 아이콘, 진동 패턴을 조정합니다. SOC가 20%를 초과하면 `clear_notification`으로 해당 tag 알림을 제거합니다.
|
||||
배터리 SOC 낮음 경고는 `control-battery-low` tag를 사용합니다. SOC 값이 조금씩 달라져도 Android 알림은 같은 tag로 갱신되어 알림 목록에 여러 장이 계속 쌓이지 않습니다. 주의/경고/위험/긴급/복구 기준과 알림 쿨다운은 설정 모달의 알림 정책에서 조정합니다.
|
||||
|
||||
System Notice는 `control-system-notice` tag를 사용하며, 기본 10분 쿨다운으로 급격한 팬/온도 변화 알림을 제한합니다. 쿨다운은 `/home/seo/secret/control.php`의 `ha_notify.cooldowns`에서 조정할 수 있습니다.
|
||||
System Notice는 `control-system-notice` tag를 사용하며, 설정된 쿨다운으로 급격한 팬/온도 변화 알림을 제한합니다. alert 상태는 신규 진입과 유지 상태를 분리해 관리하며, alert 유지 중 반복 기록 여부와 기록 간격도 설정 모달에서 조정합니다.
|
||||
|
||||
Android 알림 채널은 생성 후 휴대폰 설정에 의해 중요도와 진동 패턴이 고정될 수 있습니다. 채널을 다시 만들 필요가 있을 때는 운영 CLI로 제거 명령을 보낸 뒤 다음 알림에서 재생성합니다.
|
||||
|
||||
@@ -209,7 +210,7 @@ control-wifi-observe.service
|
||||
- `ha_notify_due()`: 동일 tag 기준 쿨다운 판정
|
||||
- `clear_ha_notification()`: 같은 tag의 Android 알림 제거 명령 발송
|
||||
- `battery_low_notify_profile()`: 배터리 SOC 단계별 채널, 중요도, 아이콘, 진동 정책 선택
|
||||
- `send_battery_low_notify_if_needed()`: 배터리 SOC 20% 이하 알림 발송
|
||||
- `send_battery_low_notify_if_needed()`: 설정된 배터리 SOC 기준에 따라 낮음/복구 알림 발송
|
||||
- `ha_notify_log_rows()`: 대시보드 HA 알림 이력 조회
|
||||
- `battery_trend_history()`: 최근 24시간 배터리 기록을 1분 단위로 집계
|
||||
- `battery_profile_history()`: 최대 45일 배터리 기록을 약 5분 간격으로 샘플링하고 파일 캐시로 보관
|
||||
|
||||
+33
-7
@@ -147,6 +147,7 @@ function setting_definitions(): array
|
||||
'security.login_window_seconds' => ['group' => 'security', 'label' => '로그인 실패 집계 시간', 'type' => 'number', 'default' => 600, 'min' => 60, 'max' => 86400, 'step' => 60, 'unit' => '초'],
|
||||
'security.login_lock_seconds' => ['group' => 'security', 'label' => '로그인 잠금 시간', 'type' => 'number', 'default' => 300, 'min' => 10, 'max' => 86400, 'step' => 10, 'unit' => '초'],
|
||||
'security.reboot_phrase' => ['group' => 'security', 'label' => '재부팅 확인 문구', 'type' => 'text', 'default' => '재부팅', 'min_length' => 2, 'max_length' => 32],
|
||||
'security.allow_reboot' => ['group' => 'security', 'label' => '재부팅 실행 허용', 'type' => 'boolean', 'default' => 1],
|
||||
'security.reboot_timeout_seconds' => ['group' => 'security', 'label' => '재부팅 명령 timeout', 'type' => 'number', 'default' => 5, 'min' => 2, 'max' => 30, 'step' => 1, 'unit' => '초'],
|
||||
'security.wifi_command_timeout_seconds' => ['group' => 'security', 'label' => 'WiFi 명령 timeout', 'type' => 'number', 'default' => 25, 'min' => 5, 'max' => 90, 'step' => 1, 'unit' => '초'],
|
||||
'security.shell_default_timeout_seconds' => ['group' => 'security', 'label' => '기본 명령 timeout', 'type' => 'number', 'default' => 8, 'min' => 2, 'max' => 60, 'step' => 1, 'unit' => '초'],
|
||||
@@ -167,6 +168,17 @@ function setting_definitions(): array
|
||||
'notice.temp_delta_threshold' => ['group' => 'notice', 'label' => '온도 이상 감지 차이', 'type' => 'number', 'default' => 3.0, 'min' => 0.5, 'max' => 15, 'step' => 0.1, 'unit' => 'C'],
|
||||
'notice.recover_rpm_delta' => ['group' => 'notice', 'label' => '팬 RPM 복구 차이', 'type' => 'number', 'default' => 500, 'min' => 50, 'max' => 3000, 'step' => 50, 'unit' => 'RPM'],
|
||||
'notice.recover_temp_delta' => ['group' => 'notice', 'label' => '온도 복구 차이', 'type' => 'number', 'default' => 1.5, 'min' => 0.2, 'max' => 10, 'step' => 0.1, 'unit' => 'C'],
|
||||
'notice.rpm_expected_pwm' => ['group' => 'notice', 'label' => 'RPM 감시 시작 PWM', 'type' => 'number', 'default' => 20, 'min' => 0, 'max' => 255, 'step' => 1, 'unit' => 'PWM'],
|
||||
'notice.log_during_alert' => ['group' => 'notice', 'label' => 'Alert 유지 중 반복 기록', 'type' => 'boolean', 'default' => 0],
|
||||
'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],
|
||||
'notify.battery_emergency_percent' => ['group' => 'notify', 'label' => '배터리 긴급 기준', 'type' => 'number', 'default' => 5, 'min' => 1, 'max' => 30, 'step' => 0.5, 'unit' => '%'],
|
||||
'notify.battery_critical_percent' => ['group' => 'notify', 'label' => '배터리 위험 기준', 'type' => 'number', 'default' => 10, 'min' => 1, 'max' => 40, 'step' => 0.5, 'unit' => '%'],
|
||||
'notify.battery_warning_percent' => ['group' => 'notify', 'label' => '배터리 경고 기준', 'type' => 'number', 'default' => 15, 'min' => 1, 'max' => 50, 'step' => 0.5, 'unit' => '%'],
|
||||
'notify.battery_clear_percent' => ['group' => 'notify', 'label' => '배터리 복구 기준', 'type' => 'number', 'default' => 20, 'min' => 2, 'max' => 80, 'step' => 0.5, 'unit' => '%'],
|
||||
'notify.battery_low_cooldown_seconds' => ['group' => 'notify', 'label' => '배터리 낮음 알림 쿨다운', 'type' => 'number', 'default' => HA_NOTIFY_BATTERY_LOW_COOLDOWN, 'min' => 1, 'max' => 86400, 'step' => 1, 'unit' => '초'],
|
||||
'notify.battery_clear_cooldown_seconds' => ['group' => 'notify', 'label' => '배터리 복구 알림 쿨다운', 'type' => 'number', 'default' => HA_NOTIFY_BATTERY_CLEAR_COOLDOWN, 'min' => 1, 'max' => 86400, 'step' => 1, 'unit' => '초'],
|
||||
'notify.system_notice_cooldown_seconds' => ['group' => 'notify', 'label' => '시스템 유의사항 알림 쿨다운', 'type' => 'number', 'default' => HA_NOTIFY_SYSTEM_NOTICE_COOLDOWN, 'min' => 10, 'max' => 86400, 'step' => 10, 'unit' => '초'],
|
||||
'battery.trend_hours' => ['group' => 'battery', 'label' => '단기 배터리 학습 범위', 'type' => 'number', 'default' => 24, 'min' => 1, 'max' => 48, 'step' => 1, 'unit' => '시간'],
|
||||
'battery.profile_days' => ['group' => 'battery', 'label' => '장기 배터리 학습 범위', 'type' => 'number', 'default' => 45, 'min' => 3, 'max' => 90, 'step' => 1, 'unit' => '일'],
|
||||
'battery.load_adjust_recent' => ['group' => 'battery', 'label' => '최근 추세 부하 보정 강도', 'type' => 'number', 'default' => 0.45, 'min' => 0, 'max' => 1, 'step' => 0.01, 'unit' => 'x'],
|
||||
@@ -178,6 +190,8 @@ function setting_definitions(): array
|
||||
'battery.candidate_min_ratio' => ['group' => 'battery', 'label' => '후보 최소 속도 비율', 'type' => 'number', 'default' => 0.38, 'min' => 0.05, 'max' => 1, 'step' => 0.01, 'unit' => 'x'],
|
||||
'battery.candidate_max_ratio' => ['group' => 'battery', 'label' => '후보 최대 속도 비율', 'type' => 'number', 'default' => 2.65, 'min' => 1, 'max' => 8, 'step' => 0.05, 'unit' => 'x'],
|
||||
'battery.capacity_model_weight' => ['group' => 'battery', 'label' => '용량 모델 가중치', 'type' => 'number', 'default' => 0.18, 'min' => 0, 'max' => 1, 'step' => 0.01, 'unit' => 'x'],
|
||||
'battery.history_chart_limit' => ['group' => 'battery', 'label' => '상태 차트 표본 수', 'type' => 'number', 'default' => 240, 'min' => 60, 'max' => 1500, 'step' => 10, 'unit' => '개'],
|
||||
'battery.history_sync_limit' => ['group' => 'battery', 'label' => '잔여시간 차트 표본 수', 'type' => 'number', 'default' => 240, 'min' => 60, 'max' => 1500, 'step' => 10, 'unit' => '개'],
|
||||
];
|
||||
}
|
||||
|
||||
@@ -246,6 +260,7 @@ function settings_payload(): array
|
||||
$values = setting_rows();
|
||||
$groups = [
|
||||
'security' => '보안 정책',
|
||||
'notify' => '알림 정책',
|
||||
'fan' => '팬 자동 제어',
|
||||
'battery' => '배터리 예측',
|
||||
'event' => '저전압/스로틀링',
|
||||
@@ -501,7 +516,7 @@ function bootstrap_db(): void
|
||||
$pdo->exec("
|
||||
CREATE TABLE IF NOT EXISTS remember_tokens (
|
||||
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
|
||||
selector CHAR(24) NOT NULL,
|
||||
selector VARCHAR(64) NOT NULL,
|
||||
validator_hash CHAR(64) NOT NULL,
|
||||
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
expires_at DATETIME NOT NULL,
|
||||
@@ -583,6 +598,7 @@ function bootstrap_db(): void
|
||||
"ALTER TABLE sensor_logs DROP COLUMN created_ip",
|
||||
"ALTER TABLE fan_spike_logs ADD COLUMN spike_key VARCHAR(32) NULL AFTER created_at",
|
||||
"ALTER TABLE fan_spike_logs ADD UNIQUE KEY uniq_spike_key (spike_key)",
|
||||
"ALTER TABLE remember_tokens MODIFY selector VARCHAR(64) NOT NULL",
|
||||
"ALTER TABLE system_notice_state ADD COLUMN active_temp_delta DECIMAL(8,2) NULL AFTER active_reason",
|
||||
"ALTER TABLE system_notice_state ADD COLUMN active_rpm_delta DECIMAL(12,2) NULL AFTER active_temp_delta",
|
||||
"ALTER TABLE system_notice_state ADD COLUMN process_signature VARCHAR(255) NULL AFTER active_rpm_delta",
|
||||
@@ -949,7 +965,11 @@ function clear_ha_notification(string $tag, string $title = 'Control 알림 해
|
||||
|
||||
function battery_low_notify_profile(float $percent): array
|
||||
{
|
||||
if ($percent <= 5.0) {
|
||||
$emergency = (float)setting_value('notify.battery_emergency_percent');
|
||||
$critical = max($emergency, (float)setting_value('notify.battery_critical_percent'));
|
||||
$warning = max($critical, (float)setting_value('notify.battery_warning_percent'));
|
||||
|
||||
if ($percent <= $emergency) {
|
||||
return [
|
||||
'label' => '긴급',
|
||||
'level' => 'critical',
|
||||
@@ -964,7 +984,7 @@ function battery_low_notify_profile(float $percent): array
|
||||
];
|
||||
}
|
||||
|
||||
if ($percent <= 10.0) {
|
||||
if ($percent <= $critical) {
|
||||
return [
|
||||
'label' => '위험',
|
||||
'level' => 'critical',
|
||||
@@ -979,7 +999,7 @@ function battery_low_notify_profile(float $percent): array
|
||||
];
|
||||
}
|
||||
|
||||
if ($percent <= 15.0) {
|
||||
if ($percent <= $warning) {
|
||||
return [
|
||||
'label' => '경고',
|
||||
'level' => 'warning',
|
||||
@@ -1025,13 +1045,19 @@ function send_battery_low_notify_if_needed(array $battery): array
|
||||
: null;
|
||||
|
||||
$tag = 'control-battery-low';
|
||||
if ($percent > 20.0) {
|
||||
$warning = max(
|
||||
(float)setting_value('notify.battery_emergency_percent'),
|
||||
(float)setting_value('notify.battery_critical_percent'),
|
||||
(float)setting_value('notify.battery_warning_percent')
|
||||
);
|
||||
$clearPercent = max($warning + 0.5, (float)setting_value('notify.battery_clear_percent'));
|
||||
if ($percent > $clearPercent) {
|
||||
$latestLow = latest_ha_notify_epoch_by_tag($tag);
|
||||
$latestClear = latest_ha_notify_epoch_by_tag($tag . '-clear');
|
||||
if (
|
||||
$latestLow > 0
|
||||
&& $latestLow > $latestClear
|
||||
&& ha_notify_due($tag . '-clear', HA_NOTIFY_BATTERY_CLEAR_COOLDOWN)
|
||||
&& ha_notify_due($tag . '-clear', (int)setting_value('notify.battery_clear_cooldown_seconds'))
|
||||
) {
|
||||
return clear_ha_notification($tag, '배터리 SOC 복구');
|
||||
}
|
||||
@@ -1043,7 +1069,7 @@ function send_battery_low_notify_if_needed(array $battery): array
|
||||
];
|
||||
}
|
||||
|
||||
if (!ha_notify_due($tag, HA_NOTIFY_BATTERY_LOW_COOLDOWN)) {
|
||||
if (!ha_notify_due($tag, (int)setting_value('notify.battery_low_cooldown_seconds'))) {
|
||||
return [
|
||||
'sent' => 0,
|
||||
'failed' => 0,
|
||||
|
||||
+33
-15
@@ -1720,7 +1720,7 @@ function sync_current_battery_remaining_history(array $history, array $currentRo
|
||||
}
|
||||
|
||||
$history[] = $currentRow;
|
||||
return array_slice($history, -240);
|
||||
return array_slice($history, -(int)setting_value('battery.history_sync_limit'));
|
||||
}
|
||||
|
||||
function process_service_name(int $pid): string
|
||||
@@ -1973,7 +1973,8 @@ function fan_spike_analysis(array $history, array $fan, array $system, array $pr
|
||||
];
|
||||
}
|
||||
|
||||
$rpmExpected = $mode !== 'off' && ($pwm >= 20 || $pwmAvg >= 20);
|
||||
$rpmExpectedPwm = (float)setting_value('notice.rpm_expected_pwm');
|
||||
$rpmExpected = $mode !== 'off' && ($pwm >= $rpmExpectedPwm || $pwmAvg >= $rpmExpectedPwm);
|
||||
|
||||
$reasons = [];
|
||||
$rpmThreshold = (float)setting_value('notice.rpm_delta_threshold');
|
||||
@@ -1984,11 +1985,12 @@ function fan_spike_analysis(array $history, array $fan, array $system, array $pr
|
||||
if ($rpmExpected && abs($rpmDelta) >= $rpmThreshold) $reasons[] = 'RPM ' . signed_delta_text($rpmDelta);
|
||||
if (abs($tempDelta) >= $tempThreshold) $reasons[] = 'TEMP ' . signed_delta_text($tempDelta, 'C');
|
||||
|
||||
$spiking = false;
|
||||
$alertStarted = false;
|
||||
|
||||
if ($currentState === 'normal') {
|
||||
$spiking = $reasons !== [];
|
||||
if ($spiking) {
|
||||
$alertStarted = $reasons !== [];
|
||||
if ($alertStarted) {
|
||||
$currentState = 'alert';
|
||||
save_system_notice_state(
|
||||
'alert',
|
||||
$tempAvg,
|
||||
@@ -2031,14 +2033,17 @@ function fan_spike_analysis(array $history, array $fan, array $system, array $pr
|
||||
}
|
||||
|
||||
$summary = 'No system notice detected in recent samples.';
|
||||
if ($spiking) {
|
||||
if ($alertStarted) {
|
||||
$summary = 'System notice: ' . implode(', ', $reasons);
|
||||
} elseif ($currentState === 'alert' && $reasons !== []) {
|
||||
$summary = 'System notice active: ' . implode(', ', $reasons);
|
||||
}
|
||||
|
||||
$alertActive = $currentState === 'alert';
|
||||
|
||||
return [
|
||||
'active' => $spiking,
|
||||
'active' => $alertActive,
|
||||
'alert_started' => $alertStarted,
|
||||
'summary' => $summary,
|
||||
'rpm_delta' => round($rpmDelta, 1),
|
||||
'pwm_delta' => round($pwmDelta, 1),
|
||||
@@ -2046,7 +2051,7 @@ function fan_spike_analysis(array $history, array $fan, array $system, array $pr
|
||||
'rpm_avg' => round($rpmAvg, 1),
|
||||
'pwm_avg' => round($pwmAvg, 1),
|
||||
'temp_avg' => round($tempAvg, 1),
|
||||
'notice_state' => $spiking ? 'alert' : $currentState,
|
||||
'notice_state' => $currentState,
|
||||
'baseline_source' => $currentState === 'alert' ? 'frozen_alert' : 'rolling',
|
||||
'baseline_samples' => (int)$rollingBaseline['samples'],
|
||||
];
|
||||
@@ -2148,8 +2153,9 @@ function add_fan_spike_log(array $spike, array $fan, array $system, array $proce
|
||||
$rpmDelta = round((float)($spike['rpm_delta'] ?? 0), 1);
|
||||
$pwmDelta = round((float)($spike['pwm_delta'] ?? 0), 1);
|
||||
$tempDelta = round((float)($spike['temp_delta'] ?? 0), 1);
|
||||
$spikeKey = 'fan:' . date('YmdHi');
|
||||
$loggedProcesses = notice_downward_only($tempDelta, $rpmDelta)
|
||||
$interval = max(60, (int)setting_value('notice.log_interval_seconds'));
|
||||
$spikeKey = 'fan:' . (int)floor(time() / $interval);
|
||||
$loggedProcesses = ((bool)setting_value('notice.downward_only_process_hide') && notice_downward_only($tempDelta, $rpmDelta))
|
||||
? ['cpu' => [], 'memory' => []]
|
||||
: $processes;
|
||||
|
||||
@@ -2412,7 +2418,7 @@ function send_fan_spike_notify(array $spike, array $fan, array $system, array $p
|
||||
. number_format((float)($fan['rpm'] ?? 0), 0)
|
||||
. ' RPM / PWM '
|
||||
. number_format((float)($fan['pwm'] ?? 0), 0)
|
||||
. (notice_downward_only($tempDelta, $rpmDelta) ? '' : "\n원인 후보: " . expected_process_detail_text($processes));
|
||||
. (((bool)setting_value('notice.downward_only_process_hide') && notice_downward_only($tempDelta, $rpmDelta)) ? '' : "\n원인 후보: " . expected_process_detail_text($processes));
|
||||
|
||||
return send_ha_notify([
|
||||
'title' => '시스템 유의사항',
|
||||
@@ -3224,7 +3230,7 @@ function collect_snapshot(bool $applyFan = true): array
|
||||
'throttling' => $throttledStatuses['throttling'],
|
||||
]);
|
||||
|
||||
$history = add_battery_remaining_history(sensor_history(240));
|
||||
$history = add_battery_remaining_history(sensor_history((int)setting_value('battery.history_chart_limit')));
|
||||
$batteryTrend = battery_trend_history((int)setting_value('battery.trend_hours'));
|
||||
$batteryProfile = battery_profile_history((int)setting_value('battery.profile_days'));
|
||||
$battery['remaining'] = battery_remaining_estimate($battery, $history, $batteryTrend, $batteryProfile);
|
||||
@@ -3251,23 +3257,27 @@ function collect_snapshot(bool $applyFan = true): array
|
||||
'mem_used_mb' => $mem['used_mb'],
|
||||
'mem_percent' => $mem['total_mb'] > 0 ? round($mem['used_mb'] / $mem['total_mb'] * 100, 1) : 0,
|
||||
], $battery['remaining']['seconds'] ?? null);
|
||||
$history = array_slice($history, -(int)setting_value('battery.history_sync_limit'));
|
||||
|
||||
$processes = process_resource_data(6);
|
||||
$fanSpike = fan_spike_analysis($history, $fan, $system, $processes);
|
||||
|
||||
if (!empty($fanSpike['active'])) {
|
||||
$shouldLogNotice = !empty($fanSpike['alert_started'])
|
||||
|| (!empty($fanSpike['active']) && (bool)setting_value('notice.log_during_alert'));
|
||||
if ($shouldLogNotice) {
|
||||
$latestNoticeNotify = latest_system_notice_notify_epoch();
|
||||
$spikeLogId = add_fan_spike_log($fanSpike, $fan, $system, $processes);
|
||||
if ($spikeLogId > 0) {
|
||||
$fanSpike['log_id'] = $spikeLogId;
|
||||
if ($latestNoticeNotify <= 0 || time() - $latestNoticeNotify >= HA_NOTIFY_SYSTEM_NOTICE_COOLDOWN) {
|
||||
$noticeCooldown = (int)setting_value('notify.system_notice_cooldown_seconds');
|
||||
if ($latestNoticeNotify <= 0 || time() - $latestNoticeNotify >= $noticeCooldown) {
|
||||
$fanSpike['notify'] = send_fan_spike_notify($fanSpike, $fan, $system, $processes);
|
||||
} else {
|
||||
$fanSpike['notify'] = [
|
||||
'sent' => 0,
|
||||
'failed' => 0,
|
||||
'skipped' => 'system_notice_cooldown',
|
||||
'cooldown_seconds' => HA_NOTIFY_SYSTEM_NOTICE_COOLDOWN,
|
||||
'cooldown_seconds' => $noticeCooldown,
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -3383,6 +3393,14 @@ function control_api_dispatch(): void
|
||||
$password = (string)($_POST['password'] ?? '');
|
||||
$expectedPhrase = (string)setting_value('security.reboot_phrase');
|
||||
|
||||
if (!(bool)setting_value('security.allow_reboot')) {
|
||||
json_out([
|
||||
'ok' => false,
|
||||
'error' => 'reboot_disabled',
|
||||
'message' => '재부팅 실행이 설정에서 비활성화되어 있습니다.',
|
||||
], 403);
|
||||
}
|
||||
|
||||
if ($phrase !== $expectedPhrase) {
|
||||
json_out([
|
||||
'ok' => false,
|
||||
|
||||
+11
-4
@@ -625,7 +625,7 @@
|
||||
return `
|
||||
<span class="settings-toggle">
|
||||
<input data-setting-key="${key}" type="checkbox" value="1" ${checked ? 'checked' : ''}>
|
||||
<span>${checked ? 'ON' : 'OFF'}</span>
|
||||
<span>${checked ? '켜기' : '끄기'}</span>
|
||||
</span>
|
||||
`;
|
||||
}
|
||||
@@ -662,6 +662,11 @@
|
||||
if (item.type === 'select' && item.options && item.options[item.default]) {
|
||||
return item.options[item.default];
|
||||
}
|
||||
if (item.type === 'boolean') {
|
||||
return item.default === true || item.default === 1 || item.default === '1' || item.default === 'true'
|
||||
? '켜기'
|
||||
: '끄기';
|
||||
}
|
||||
return item.default ?? '';
|
||||
};
|
||||
|
||||
@@ -687,7 +692,7 @@
|
||||
els.settingsBody.querySelectorAll('.settings-toggle input').forEach(input => {
|
||||
input.addEventListener('change', () => {
|
||||
const label = input.closest('.settings-toggle')?.querySelector('span');
|
||||
if (label) label.textContent = input.checked ? 'ON' : 'OFF';
|
||||
if (label) label.textContent = input.checked ? '켜기' : '끄기';
|
||||
});
|
||||
input.closest('.settings-toggle')?.addEventListener('click', () => {
|
||||
input.checked = !input.checked;
|
||||
@@ -1382,8 +1387,10 @@
|
||||
const m = noticeMetrics(row);
|
||||
const reasons = [];
|
||||
|
||||
if (Math.abs(m.tempDelta) >= 3) reasons.push(t(m.tempDelta >= 0 ? 'tempHigher' : 'tempLower'));
|
||||
if (Math.abs(m.rpmDelta) >= 1000) reasons.push(t(m.rpmDelta >= 0 ? 'rpmHigher' : 'rpmLower'));
|
||||
const tempThreshold = Number(settingByKey('notice.temp_delta_threshold', 3));
|
||||
const rpmThreshold = Number(settingByKey('notice.rpm_delta_threshold', 1000));
|
||||
if (Math.abs(m.tempDelta) >= tempThreshold) reasons.push(t(m.tempDelta >= 0 ? 'tempHigher' : 'tempLower'));
|
||||
if (Math.abs(m.rpmDelta) >= rpmThreshold) reasons.push(t(m.rpmDelta >= 0 ? 'rpmHigher' : 'rpmLower'));
|
||||
|
||||
return `${t('recordedReason')}: ${reasons.length ? reasons.join(', ') : t('instantChange')}`;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user