Improve Home Assistant notifications

This commit is contained in:
seo
2026-06-21 07:46:28 +09:00
parent 49394a8ee5
commit 772010562a
6 changed files with 269 additions and 55 deletions
+3 -2
View File
@@ -1983,14 +1983,14 @@ function collect_snapshot(bool $applyFan = true): array
$spikeLogId = add_fan_spike_log($fanSpike, $fan, $system, $processes);
if ($spikeLogId > 0) {
$fanSpike['log_id'] = $spikeLogId;
if ($latestNoticeNotify <= 0 || time() - $latestNoticeNotify >= 600) {
if ($latestNoticeNotify <= 0 || time() - $latestNoticeNotify >= HA_NOTIFY_SYSTEM_NOTICE_COOLDOWN) {
$fanSpike['notify'] = send_fan_spike_notify($fanSpike, $fan, $system, $processes);
} else {
$fanSpike['notify'] = [
'sent' => 0,
'failed' => 0,
'skipped' => 'system_notice_cooldown',
'cooldown_seconds' => 600,
'cooldown_seconds' => HA_NOTIFY_SYSTEM_NOTICE_COOLDOWN,
];
}
}
@@ -2010,6 +2010,7 @@ function collect_snapshot(bool $applyFan = true): array
'processes' => $processes,
'fan_spike' => $fanSpike,
'fan_spike_history' => fan_spike_history(100),
'ha_notify_history' => ha_notify_log_rows(20),
];
return $snapshot;
+36
View File
@@ -33,6 +33,7 @@
statusBatterySoc: $('#statusBatterySoc'),
statusBatteryRemaining: $('#statusBatteryRemaining'),
spikeLogList: $('#spikeLogList'),
haNotifyList: $('#haNotifyList'),
noticeBaseline: $('#noticeBaseline'),
rebootBtn: $('#rebootBtn'),
translateBtn: $('#translateBtn'),
@@ -137,6 +138,10 @@
systemNotice: 'System Notice',
noticeHistory: 'Notice History',
noNoticeHistory: 'No system notice history.',
haNotifyHistory: 'HA Notify History',
noHaNotifyHistory: 'No HA notify history.',
notifySuccess: 'success',
notifyFailed: 'failed',
show: 'Show',
hide: 'Hide',
translateButton: 'Translate',
@@ -255,6 +260,10 @@
systemNotice: '시스템 알림',
noticeHistory: '알림 이력',
noNoticeHistory: '시스템 알림 이력이 없습니다.',
haNotifyHistory: 'HA 알림 이력',
noHaNotifyHistory: 'HA 알림 이력이 없습니다.',
notifySuccess: '성공',
notifyFailed: '실패',
show: '보기',
hide: '숨기기',
translateButton: '번역',
@@ -997,6 +1006,32 @@
: `<div class="spike-log-empty">${escapeHtml(t('noNoticeHistory'))}</div>`;
}
function renderHaNotifyHistory(rows = []) {
if (!els.haNotifyList) return;
els.haNotifyList.innerHTML = rows.length
? rows.map((row, index) => {
const ok = row.event === 'send_success';
const statusText = ok ? t('notifySuccess') : t('notifyFailed');
const detail = [
row.target ? `${t('hostLabel')}: ${row.target}` : '',
row.http_code ? `HTTP ${row.http_code}` : '',
row.channel || '',
row.error || '',
].filter(Boolean).join(' / ');
return `
<div class="spike-log-item ${index === 0 ? 'latest' : ''} ${ok ? '' : 'alert'}">
<strong>${escapeHtml(row.created_at || '-')} · ${escapeHtml(statusText)}</strong>
<span>${escapeHtml(row.title || '-')}</span>
<span>${escapeHtml(row.tag || '-')}</span>
${detail ? `<span>${escapeHtml(detail)}</span>` : ''}
</div>
`;
}).join('')
: `<div class="spike-log-empty">${escapeHtml(t('noHaNotifyHistory'))}</div>`;
}
function renderFanCause(data) {
const processes = data.processes || {};
const baseline = data.fan_spike || {};
@@ -1007,6 +1042,7 @@
? `${baselineTemp.toFixed(1)}°C / ${Math.round(baselineRpm).toLocaleString()} RPM · ${stateText}`
: stateText;
renderSpikeHistory(data.fan_spike_history || []);
renderHaNotifyHistory(data.ha_notify_history || []);
setText(els.noticeBaseline, baselineText);
renderProcessRows(els.processCpuTable, processes.cpu || [], 'cpu');
+8 -1
View File
@@ -105,7 +105,7 @@ html[data-theme="light"] .btn.secondary{background:#d9e2ef;color:#172033}html[da
.status-list{display:grid;gap:10px}.status-row{display:grid;grid-template-columns:112px minmax(0,1fr);gap:12px;align-items:baseline;padding:10px 12px;background:var(--card2);border-radius:14px}.status-key{color:var(--sub);font-size:13px}.status-value{overflow:visible;white-space:normal;word-break:break-word;font-variant-numeric:tabular-nums}
.table-wrap{max-width:100%;overflow-x:auto;overflow-y:hidden;-webkit-overflow-scrolling:touch;border-radius:16px;border:1px solid var(--line)}.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 .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}
.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}
.resource-card{margin-top:18px}.resource-head{display:flex;align-items:baseline;justify-content:space-between;gap:12px;margin-bottom:16px}.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-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 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}.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}
.resource-card{margin-top:18px}.resource-head{display:flex;align-items:baseline;justify-content:space-between;gap:12px;margin-bottom:16px}.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-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}.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}
.resource-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:14px}.resource-box{min-width:0}.resource-box h3{margin:0 0 9px;color:var(--sub);font-size:14px;font-weight:500}.resource-table{width:100%;min-width:620px;table-layout:fixed;border-collapse:collapse;border:1px solid var(--line);border-radius:14px;overflow:hidden}.resource-table th,.resource-table td{padding:9px 10px;border-bottom:1px solid rgba(128,145,170,.18);background:var(--input);text-align:left;font-size:13px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.resource-table th{background:var(--table-head);color:var(--sub);font-weight:500}.resource-table .pid{width:72px}.resource-table .metric{width:82px}.resource-table .service{width:150px}.resource-table .cmd{width:auto}
.notice{position:fixed;right:20px;bottom:20px;min-width:220px;max-width:420px;padding:14px 16px;border-radius:14px;font-weight:500;background:var(--table-head);color:var(--text);border:1px solid var(--line);box-shadow:var(--shadow);z-index:99}.notice:empty{display:none}.notice[data-type=success]{border-color:rgba(53,196,107,.5)}.notice[data-type=error]{border-color:rgba(255,95,87,.5)}
.dialog-layer{position:fixed;inset:0;display:none;align-items:center;justify-content:center;padding:22px;background:rgba(4,7,12,.68);backdrop-filter:blur(8px);z-index:120}.dialog-layer[data-open="1"]{display:flex}.dialog-box{width:min(430px,100%);border:1px solid rgba(84,101,128,.78);border-radius:20px;background:var(--card);box-shadow:0 24px 70px rgba(0,0,0,.42);padding:20px}.dialog-box h3{margin:0 0 8px;font-size:19px;font-weight:500}.dialog-message{margin:0 0 16px;color:var(--sub);line-height:1.5;white-space:pre-wrap}.dialog-input{width:100%;height:48px;margin-bottom:14px;border-radius:14px;border:1px solid var(--line);background:var(--input);color:var(--text);padding:0 14px;outline:none}.dialog-input:focus{outline:2px solid rgba(59,130,246,.65);outline-offset:2px}.dialog-actions{display:flex;justify-content:flex-end;gap:10px}.dialog-actions .btn{min-width:84px}.dialog-actions .btn.red{background:#c62828}
@@ -271,6 +271,13 @@ html[data-theme="light"] .btn.secondary{background:#d9e2ef;color:#172033}html[da
</div>
</div>
<div class="spike-log-box">
<h3 data-i18n="haNotifyHistory">HA Notify History</h3>
<div id="haNotifyList" class="spike-log-list">
<div class="spike-log-empty" data-i18n="noHaNotifyHistory">No HA notify history.</div>
</div>
</div>
<div class="spike-log-box">
<div class="dmesg-head">
<h3>dmesg</h3>