원격 syslog 기능 제거

This commit is contained in:
seo committed 2026-08-01 11:32:15 +09:00
1 parent 95a4750137
commit c0dadcc8a9
5 files changed
+4 -482

No files matched your search

+3 -8
View File
@@ -29,7 +29,7 @@ Control은 라즈베리파이/리눅스 호스트의 팬과 시스템 상태를
- process CPU/MEM 후보는 프로세스 상세 접힘 영역에서 표시
- `/etc/systemd/system/*.service` 기준 사용자 생성 서비스의 서비스명, 현재 상태, enable 상태, PID, restart 횟수, 최근 journal 로그를 진단 접힘 영역에서 표시
- 사용자 서비스 로그는 서비스별 최근 500줄까지 조회하고, 사용자가 서비스 목록을 스크롤하는 동안에는 자동 갱신이 화면 위치를 되돌리지 않습니다.
- 라즈베리파이와 시놀로지 사이 원격 syslog 송수신 상태, 영구 journal, 커널 크래시 보존 상태를 진단 접힘 영역에서 표시
- 영구 journal과 커널 크래시 보존 상태를 진단 접힘 영역에서 표시
- 시놀로지에서 라즈베리파이로 들어온 원격 로그와 dmesg 로그는 진단 접힘 영역에서 필요할 때 열람
- WakeLock 버튼으로 대시보드 화면 꺼짐 방지
- Reboot 버튼으로 2단계 확인 후 시스템 재부팅 요청
@@ -52,7 +52,6 @@ Control은 라즈베리파이/리눅스 호스트의 팬과 시스템 상태를
- `public/api.php?action=settings_reset`: 설정 모달 값을 기본값으로 초기화
- `public/api.php?action=touch_display_save`: `/boot/firmware/config.txt`, `/boot/firmware/cmdline.txt`에 DSI 터치 디스플레이 부팅 설정 저장
- `public/api.php?action=touch_display_runtime_save`: DSI 터치 디스플레이 백라이트 밝기와 백라이트 전원 즉시 적용
- `public/api.php?action=synology_log`: 라즈베리파이에 저장된 시놀로지 원격 로그를 표시용 필터로 걸러 최근 500줄 조회
- `public/api.php?action=dmesg`: dmesg 로그 조회
- `public/api.php?action=reboot`: 확인 단어와 관리자 암호 재검증 후 sudo reboot 실행
@@ -94,11 +93,7 @@ Control은 라즈베리파이/리눅스 호스트의 팬과 시스템 상태를
- `/home/seo/secret/control.php`: 앱 비밀번호, DB 설정, 선택적 배터리 설정, WOL 전용 API 토큰
- UPS/배터리 기능은 `/home/seo/secret/control.php`의 `battery.enabled`가 명시적으로 true일 때만 동작합니다. 현재 운영 기준은 UPS 제거 상태이므로 기본값은 false입니다.
## 원격 로그
라즈베리파이는 `rsyslog`와 `rsyslog-gnutls`로 로컬 journal/syslog를 시놀로지 Log Center에 TCP/TLS 6514로 보냅니다. 전송 대상은 `chaegeon.com:6514`이며, 네트워크가 끊기면 `/var/spool/rsyslog/control_synology_fwd*` 큐에 남았다가 재시도합니다.
시놀로지는 DSM `syslog-ng`에서 라즈베리파이 `121.157.202.229:6514`로 TCP/TLS syslog를 보냅니다. 라즈베리파이 수신 로그는 `/var/log/remote/synology/*.log`에 저장하고 `/etc/logrotate.d/control-remote-syslog`가 매일 회전합니다. Control 진단 패널의 시놀로지 로그 뷰어는 `/var/log/remote/synology/chaegeon.log` 최근 500줄을 최신순으로 보여주며, WebSocket 또는 HTTP fallback으로 1초마다 갱신합니다. 사용자가 로그 영역을 스크롤하는 동안에는 자동 갱신을 잠깐 멈춰 화면 위치가 튀지 않게 합니다.
## 로그 보존
커널 패닉이나 강제 재부팅 분석을 위해 `systemd-journald`는 `/var/log/journal`에 영구 저장합니다. `dtoverlay=ramoops` 기반 pstore는 부팅 직후 `control-pstore-collect.service`가 `/var/log/pstore`로 복사합니다. 현재 pstore에 남은 크래시 조각이 없으면 이 서비스는 조건 불충족으로 `inactive`일 수 있으며 정상입니다.
@@ -313,4 +308,4 @@ control-wifi-observe.service
- 저전압/스로틀링이 `N/A`로 보이면 웹 서버 실행 계정의 `/dev/vcio` 접근 권한과 `/usr/bin/vcgencmd get_throttled` sudoers 허용 여부를 확인합니다.
- 언어/테마 표시가 예상과 다르면 브라우저 localStorage의 `controlLang`, `controlTheme` 값을 확인합니다.
- 시놀로지 운영 상태 확인은 NFS 경로가 아니라 SSH와 HTTPS API를 기준으로 합니다. Control 런타임에서 필요한 공용 HA 설정은 `/home/seo/secret/control.php`의 `common` 항목에 둡니다.
- smartban 판단 로그는 로컬 `logger -t smartban`으로 남기고 rsyslog/TLS가 시놀로지 Log Center로 전달합니다.
- smartban 판단 로그는 로컬 `logger -t smartban`으로 남깁니다.
+1 -63
View File
@@ -16,12 +16,11 @@ require_once __DIR__ . '/../public/api.php';
final class ControlWebSocket implements MessageComponentInterface
{
/** @var SplObjectStorage<ConnectionInterface, array{dmesg: bool, synology_log: bool, ip: string}> */
/** @var SplObjectStorage<ConnectionInterface, array{dmesg: bool, ip: string}> */
private SplObjectStorage $clients;
private bool $statusBusy = false;
private bool $dmesgBusy = false;
private bool $synologyLogBusy = false;
public function __construct()
{
@@ -48,7 +47,6 @@ final class ControlWebSocket implements MessageComponentInterface
$this->clients[$conn] = [
'dmesg' => false,
'synology_log' => false,
'ip' => $ip,
];
$this->sendStatus($conn);
@@ -63,7 +61,6 @@ final class ControlWebSocket implements MessageComponentInterface
$state = $this->clients[$from] ?? [
'dmesg' => false,
'synology_log' => false,
'ip' => '127.0.0.1',
];
@@ -76,15 +73,6 @@ final class ControlWebSocket implements MessageComponentInterface
return;
}
if (($data['type'] ?? '') === 'synology_log') {
$state['synology_log'] = !empty($data['open']);
$this->clients[$from] = $state;
if ($state['synology_log']) {
$this->sendSynologyLog($from);
}
return;
}
if (($data['type'] ?? '') === 'status_refresh') {
$this->sendStatus($from);
}
@@ -159,40 +147,6 @@ final class ControlWebSocket implements MessageComponentInterface
}
}
public function broadcastSynologyLog(): void
{
if ($this->clients->count() === 0 || $this->synologyLogBusy) {
return;
}
$targets = [];
foreach ($this->clients as $client) {
$state = $this->clients[$client];
if (!empty($state['synology_log'])) {
$targets[] = $client;
}
}
if ($targets === []) {
return;
}
$this->synologyLogBusy = true;
try {
$payload = $this->json([
'type' => 'synology_log',
'data' => synology_remote_log(),
]);
foreach ($targets as $client) {
$client->send($payload);
}
} catch (Throwable $e) {
$this->broadcastError($e);
} finally {
$this->synologyLogBusy = false;
}
}
private function sendStatus(ConnectionInterface $conn): void
{
try {
@@ -220,21 +174,6 @@ final class ControlWebSocket implements MessageComponentInterface
}
}
private function sendSynologyLog(ConnectionInterface $conn): void
{
try {
$conn->send($this->json([
'type' => 'synology_log',
'data' => synology_remote_log(),
]));
} catch (Throwable $e) {
$conn->send($this->json([
'type' => 'error',
'message' => $e->getMessage(),
]));
}
}
private function statusPayload(): string
{
try {
@@ -341,7 +280,6 @@ $startedSignature = $watchSignature($watchFiles);
$loop->addPeriodicTimer(1.0, static fn() => $app->broadcastStatus());
$loop->addPeriodicTimer(1.0, static fn() => $app->broadcastDmesg());
$loop->addPeriodicTimer(1.0, static fn() => $app->broadcastSynologyLog());
$loop->addPeriodicTimer(15.0, static function () use ($watchFiles, $watchSignature, $startedSignature): void {
if ($watchSignature($watchFiles) === $startedSignature) {
return;
-173
View File
@@ -177,76 +177,6 @@ function text_log_tail(string $path, string $label, int $limit = 500, ?callable
];
}
function synology_remote_log_visible(string $line): bool
{
if (str_contains($line, 'Last message ') && str_contains($line, 'suppressed by syslog-ng')) {
return false;
}
if (preg_match('/\ssynotifyd\[\d+\]\s/', $line)) {
return false;
}
if (preg_match('/\ssynoelasticd\[\d+\]\s/', $line)) {
$noise = [
'(UpsertByID)',
'(SetByID)',
'(DelByID)',
'(DelByQuery)',
'(Commit) Commit: fileindex_',
];
foreach ($noise as $needle) {
if (str_contains($line, $needle)) {
return false;
}
}
}
if (preg_match('/\sfileindexd\[\d+\]\s/', $line)) {
$noise = [
'ProcessOP BEGIN: IndexUpsert',
'ProcessOP DONE!: IndexUpsert',
'ProcessOP SKIPPED: IndexUpsert',
'ProcessOP BEGIN: IndexReindex',
'ProcessOP DONE!: IndexReindex',
'ProcessOP SKIPPED: IndexReindex',
'(Process) Worker<',
'PrepareTmpOPtree',
'CheckClearOldTmpOPtree',
'BuildTree',
'Commit [fileindex_',
];
foreach ($noise as $needle) {
if (str_contains($line, $needle)) {
return false;
}
}
}
if (preg_match('/\sscemd\[\d+\]\s/', $line)) {
$noise = [
'Could not open file `/dev/i2c-1',
'Could not open file `/dev/i2c/1',
'Fail to initLedBrightnessConfig',
'No brightness setting',
'Fail to init config list',
'Fail to set led brightness.',
];
foreach ($noise as $needle) {
if (str_contains($line, $needle)) {
return false;
}
}
}
return true;
}
function synology_remote_log(): array
{
return text_log_tail('/var/log/remote/synology/chaegeon.log', 'synology remote', 5000, 'synology_remote_log_visible');
}
function command_first_line(array $cmd, bool $root = false, int $timeout = 3): string
{
$result = sh($cmd, $root, $timeout);
@@ -258,112 +188,16 @@ function command_first_line(array $cmd, bool $root = false, int $timeout = 3): s
return (int)$result['code'] === 0 && $lines ? (string)$lines[0] : '';
}
function remote_log_file_status(string $dir): array
{
$files = [];
$totalBytes = 0;
$newest = null;
foreach (glob(rtrim($dir, '/') . '/*.log') ?: [] as $path) {
$size = filesize($path) ?: 0;
$mtime = filemtime($path) ?: 0;
$totalBytes += $size;
$row = [
'name' => basename($path),
'path' => $path,
'size_bytes' => $size,
'size' => bytes_human($size),
'updated_at' => $mtime > 0 ? date('Y-m-d H:i:s', $mtime) : null,
'age_seconds' => $mtime > 0 ? max(0, time() - $mtime) : null,
];
$files[] = $row;
if ($newest === null || (int)$row['age_seconds'] < (int)($newest['age_seconds'] ?? PHP_INT_MAX)) {
$newest = $row;
}
}
usort($files, static fn($a, $b): int => (int)($a['age_seconds'] ?? PHP_INT_MAX) <=> (int)($b['age_seconds'] ?? PHP_INT_MAX));
return [
'path' => $dir,
'available' => is_dir($dir),
'file_count' => count($files),
'total_bytes' => $totalBytes,
'total_size' => bytes_human($totalBytes),
'newest' => $newest,
'files' => array_slice($files, 0, 5),
];
}
function remote_log_queue_status(): array
{
$result = sh([
'/usr/bin/find',
'/var/spool/rsyslog',
'-maxdepth',
'1',
'-name',
'control_synology_fwd*',
'-printf',
'%s\n',
], true, 3);
if ((int)$result['code'] !== 0) {
return [
'available' => false,
'file_count' => null,
'total_bytes' => null,
'total_size' => '-',
];
}
$sizes = array_values(array_filter(
array_map('trim', preg_split('/\R/', trim((string)$result['out'])) ?: []),
static fn($line): bool => $line !== ''
));
$total = array_sum(array_map('intval', $sizes));
return [
'available' => true,
'file_count' => count($sizes),
'total_bytes' => $total,
'total_size' => bytes_human($total),
];
}
function remote_log_status(): array
{
$journalDir = '/var/log/journal';
$journalDisk = command_first_line(['/usr/bin/journalctl', '--disk-usage', '--no-pager'], false, 3);
$journalBoots = command_first_line(['/usr/bin/bash', '-lc', 'journalctl --list-boots --no-pager 2>/dev/null | wc -l'], false, 3);
$rsyslogActive = command_first_line(['/usr/bin/systemctl', 'is-active', 'rsyslog'], false, 3) ?: 'unknown';
$rsyslogEnabled = command_first_line(['/usr/bin/systemctl', 'is-enabled', 'rsyslog'], false, 3) ?: 'unknown';
$pstoreActive = command_first_line(['/usr/bin/systemctl', 'is-active', 'control-pstore-collect.service'], false, 3) ?: 'inactive';
$listener = sh(['/usr/bin/ss', '-lnt', 'sport', '=', ':6514'], false, 3);
$listenerOk = (int)$listener['code'] === 0 && str_contains((string)$listener['out'], ':6514');
$remoteFiles = remote_log_file_status('/var/log/remote/synology');
$queue = remote_log_queue_status();
$savedCrashFiles = glob('/var/log/pstore/*') ?: [];
$pstoreFiles = glob('/sys/fs/pstore/*') ?: [];
return [
'raspberry_to_synology' => [
'target' => 'chaegeon.com:6514',
'transport' => 'TCP/TLS',
'rsyslog_active' => $rsyslogActive,
'rsyslog_enabled' => $rsyslogEnabled,
'queue' => $queue,
'ok' => $rsyslogActive === 'active',
],
'synology_to_raspberry' => [
'listen_port' => 6514,
'transport' => 'TCP/TLS',
'listener' => $listenerOk,
'logs' => $remoteFiles,
'ok' => $listenerOk && $remoteFiles['file_count'] > 0,
],
'journal' => [
'path' => $journalDir,
'persistent' => is_dir($journalDir),
@@ -4877,13 +4711,6 @@ function control_api_dispatch(): void
]);
}
if ($action === 'synology_log') {
json_out([
'ok' => true,
'data' => synology_remote_log(),
]);
}
if ($action === 'status') {
json_out([
'ok' => true,
-220
View File
@@ -60,10 +60,6 @@
dmesgToggle: $('#dmesgToggle'),
dmesgOutput: $('#dmesgOutput'),
dmesgMeta: $('#dmesgMeta'),
synologyLogToggle: $('#synologyLogToggle'),
synologyLogFilters: $('#synologyLogFilters'),
synologyLogOutput: $('#synologyLogOutput'),
synologyLogMeta: $('#synologyLogMeta'),
processDetails: $('#processDetails'),
diagnosticDetails: $('#diagnosticDetails'),
};
@@ -83,12 +79,6 @@
dmesgOpen: false,
dmesgTimer: null,
dmesgLatestKey: null,
synologyLogOpen: false,
synologyLogTimer: null,
synologyLogLatestKey: null,
synologyLogScrollHoldUntil: 0,
synologyLogFilter: 'all',
synologyLogData: null,
ws: null,
wsConnected: false,
wsFallbackTimer: null,
@@ -331,9 +321,6 @@
fanUpdateFailed: 'fan update failed',
dmesgUnavailable: 'dmesg log is not available yet.',
dmesgRefreshFailed: 'dmesg refresh failed',
synologyLog: 'Synology Log',
synologyLogUnavailable: 'Synology log is not available yet.',
synologyLogRefreshFailed: 'Synology log refresh failed',
rebootPrompt: 'Type reboot below to continue.',
rebootPhrase: 'reboot',
rebootTitle: 'System Reboot',
@@ -566,9 +553,6 @@
fanUpdateFailed: '팬 갱신 실패',
dmesgUnavailable: 'dmesg 로그를 아직 사용할 수 없습니다.',
dmesgRefreshFailed: 'dmesg 갱신 실패',
synologyLog: '시놀로지 로그',
synologyLogUnavailable: '시놀로지 로그를 아직 사용할 수 없습니다.',
synologyLogRefreshFailed: '시놀로지 로그 갱신 실패',
rebootPrompt: '재부팅을 계속하려면 아래에 재부팅을 입력하세요.',
rebootPhrase: '재부팅',
rebootTitle: '시스템 재부팅',
@@ -623,10 +607,6 @@
if (els.dmesgToggle) {
els.dmesgToggle.textContent = state.dmesgOpen ? t('hide') : t('show');
}
if (els.synologyLogToggle) {
els.synologyLogToggle.textContent = state.synologyLogOpen ? t('hide') : t('show');
}
updateSynologyLogFilters();
}
function initPreferences() {
@@ -1263,10 +1243,6 @@
stopDmesgFallback();
sendWs({ type: 'dmesg', open: true });
}
if (state.synologyLogOpen) {
stopSynologyLogFallback();
sendWs({ type: 'synology_log', open: true });
}
});
state.ws.addEventListener('message', event => {
@@ -1288,11 +1264,6 @@
return;
}
if (message?.type === 'synology_log') {
renderSynologyLog(message.data || {});
return;
}
if (message?.type === 'error') {
console.error(message.message || 'websocket error');
}
@@ -1304,9 +1275,6 @@
if (state.dmesgOpen) {
startDmesgFallback();
}
if (state.synologyLogOpen) {
startSynologyLogFallback();
}
const delay = state.wsReconnectDelay;
state.wsReconnectDelay = Math.min(15000, state.wsReconnectDelay * 1.7);
state.wsReconnectTimer = setTimeout(connectControlSocket, delay);
@@ -2139,49 +2107,12 @@
.join(' · ') || '-';
}
function remoteAge(row) {
if (!row || row.age_seconds === null || row.age_seconds === undefined) {
return '-';
}
const formatted = formatDhms(Number(row.age_seconds));
return formatted ? `${formatted} ${t('ago')}` : '-';
}
function renderRemoteLogs(data = {}) {
if (!els.remoteLogList) return;
const piToSynology = data.raspberry_to_synology || {};
const synologyToPi = data.synology_to_raspberry || {};
const journal = data.journal || {};
const pstore = data.pstore || {};
const newest = synologyToPi.logs?.newest || null;
const queue = piToSynology.queue || {};
const rows = [
{
title: t('remotePiToSynology'),
desc: remoteMeta([
{ label: t('remoteTarget'), value: piToSynology.target || '-' },
{ label: t('remoteTransport'), value: piToSynology.transport || '-' },
]),
meta: remoteMeta([
{ label: 'rsyslog', value: `${piToSynology.rsyslog_active || '-'} / ${piToSynology.rsyslog_enabled || '-'}` },
{ label: t('remoteQueue'), value: queue.available === false ? '-' : `${Number(queue.file_count || 0).toLocaleString()} / ${queue.total_size || '-'}` },
]),
state: remoteState(Boolean(piToSynology.ok), true),
},
{
title: t('remoteSynologyToPi'),
desc: remoteMeta([
{ label: t('remoteListener'), value: synologyToPi.listener ? `:${synologyToPi.listen_port || 6514}` : '-' },
{ label: t('remoteTransport'), value: synologyToPi.transport || '-' },
]),
meta: remoteMeta([
{ label: t('remoteLatest'), value: newest ? `${newest.name || '-'} / ${remoteAge(newest)}` : '-' },
{ label: t('remoteFiles'), value: `${Number(synologyToPi.logs?.file_count || 0).toLocaleString()} / ${synologyToPi.logs?.total_size || '-'}` },
]),
state: remoteState(Boolean(synologyToPi.ok), true),
},
{
title: t('remotePersistentJournal'),
desc: journal.path || '-',
@@ -2961,134 +2892,6 @@
}
}
function scrollSynologyLogToTop() {
if (!els.synologyLogOutput) return;
if (els.synologyLogOutput.hidden) return;
if (Date.now() < state.synologyLogScrollHoldUntil) return;
els.synologyLogOutput.scrollTop = 0;
}
function synologyLogFilterLabel(filter = state.synologyLogFilter) {
const keys = {
all: 'logFilterAll',
important: 'logFilterImportant',
error: 'logFilterError',
failed: 'logFilterFailed',
blocked: 'logFilterBlocked',
connection: 'logFilterConnection',
system: 'logFilterSystem',
};
return t(keys[filter] || keys.important);
}
function updateSynologyLogFilters() {
if (!els.synologyLogFilters) return;
els.synologyLogFilters.querySelectorAll('[data-log-filter]').forEach(button => {
const key = button.dataset.logFilter || 'all';
button.textContent = synologyLogFilterLabel(key);
button.dataset.active = key === state.synologyLogFilter ? '1' : '0';
});
}
function synologyLogLineMatches(line, filter) {
if (filter === 'all') return true;
const value = String(line || '').toLowerCase();
const patterns = {
important: /(error|err|fail|failed|failure|denied|blocked|drop|reject|disconnect|timeout|oom|panic|critical|warn|warning|throttl|undervoltage)/i,
error: /(error|err|exception|critical|panic|oom)/i,
failed: /(fail|failed|failure|timeout|unreachable|cannot|could not)/i,
blocked: /(blocked|denied|reject|drop|ban|iptables|nft|firewall)/i,
connection: /(connect|disconnect|connection|ssh|tls|handshake|listen|socket)/i,
system: /(systemd|kernel|docker|nginx|mariadb|rsyslog|syslog|journal)/i,
};
return (patterns[filter] || patterns.important).test(value);
}
function filteredSynologyLogLines(lines) {
return (lines || []).filter(line => synologyLogLineMatches(line, state.synologyLogFilter));
}
function renderSynologyLog(data) {
if (!els.synologyLogOutput || !els.synologyLogMeta) return;
if (Date.now() < state.synologyLogScrollHoldUntil) return;
const scrollTop = els.synologyLogOutput.scrollTop;
state.synologyLogData = data || null;
if (!data?.available) {
els.synologyLogOutput.textContent = data?.message || t('synologyLogUnavailable');
els.synologyLogMeta.textContent = '/var/log/remote/synology/chaegeon.log unavailable';
return;
}
const rawLines = data.lines || [];
const lines = filteredSynologyLogLines(rawLines);
const latestKey = `${data.line_count || 0}\n${lines[0] || ''}`;
const hasNewLine = state.synologyLogLatestKey !== null && latestKey !== state.synologyLogLatestKey;
els.synologyLogOutput.textContent = lines.length ? lines.join('\n') : t('logFilterNoLines');
els.synologyLogMeta.textContent = `${data.path || '/var/log/remote/synology/chaegeon.log'} · ${lines.length} / ${data.line_count || 0} lines · ${synologyLogFilterLabel()} · updated ${data.updated_at || '-'}`;
state.synologyLogLatestKey = latestKey;
els.synologyLogOutput.scrollTop = Date.now() < state.synologyLogScrollHoldUntil ? scrollTop : 0;
if (hasNewLine) {
requestAnimationFrame(scrollSynologyLogToTop);
}
}
async function refreshSynologyLog() {
if (!state.synologyLogOpen) return;
try {
renderSynologyLog(await api('synology_log'));
} catch (e) {
console.error(e);
if (els.synologyLogOutput) {
els.synologyLogOutput.textContent = e.message || t('synologyLogRefreshFailed');
}
}
}
function stopSynologyLogFallback() {
clearInterval(state.synologyLogTimer);
state.synologyLogTimer = null;
}
function startSynologyLogFallback() {
if (state.synologyLogTimer) return;
refreshSynologyLog();
state.synologyLogTimer = setInterval(refreshSynologyLog, 1000);
}
function setSynologyLogOpen(open) {
state.synologyLogOpen = open;
if (els.synologyLogOutput) {
els.synologyLogOutput.hidden = !open;
}
if (els.synologyLogFilters) {
els.synologyLogFilters.hidden = !open;
}
if (els.synologyLogToggle) {
els.synologyLogToggle.textContent = open ? t('hide') : t('show');
}
updateSynologyLogFilters();
stopSynologyLogFallback();
if (open) {
if (!sendWs({ type: 'synology_log', open: true })) {
startSynologyLogFallback();
}
} else {
sendWs({ type: 'synology_log', open: false });
}
}
async function refreshStatus() {
if (state.loading) return;
state.loading = true;
@@ -3232,26 +3035,6 @@
els.dmesgToggle?.addEventListener('click', () => {
setDmesgOpen(!state.dmesgOpen);
});
els.synologyLogToggle?.addEventListener('click', () => {
setSynologyLogOpen(!state.synologyLogOpen);
});
els.synologyLogFilters?.addEventListener('click', event => {
const button = event.target?.closest?.('[data-log-filter]');
if (!button) return;
state.synologyLogFilter = button.dataset.logFilter || 'all';
state.synologyLogScrollHoldUntil = 0;
updateSynologyLogFilters();
if (state.synologyLogData) {
renderSynologyLog(state.synologyLogData);
}
});
const holdSynologyLogScroll = () => {
state.synologyLogScrollHoldUntil = Date.now() + 4000;
};
['scroll', 'wheel', 'touchstart', 'touchmove', 'pointerdown'].forEach(eventName => {
els.synologyLogOutput?.addEventListener(eventName, holdSynologyLogScroll, { passive: true });
});
const holdCustomServiceScroll = () => {
state.customServiceScrollHoldUntil = Date.now() + 4000;
};
@@ -3331,9 +3114,6 @@
if (state.dmesgOpen && !sendWs({ type: 'dmesg', open: true })) {
refreshDmesg();
}
if (state.synologyLogOpen && !sendWs({ type: 'synology_log', open: true })) {
refreshSynologyLog();
}
}
});
document.addEventListener('keydown', event => {
-18
View File
@@ -384,24 +384,6 @@ html[data-theme="light"] .details-panel{background:#edf3fa;border-color:#b8c7da}
</div>
</div>
<div class="spike-log-box">
<div class="dmesg-head">
<h3 data-i18n="synologyLog">Synology Log</h3>
<button class="btn secondary" id="synologyLogToggle" type="button" data-i18n="show">Show</button>
</div>
<div class="log-filter-bar" id="synologyLogFilters" hidden>
<button class="btn secondary log-filter" type="button" data-log-filter="all" data-active="1">All</button>
<button class="btn secondary log-filter" type="button" data-log-filter="important">Important</button>
<button class="btn secondary log-filter" type="button" data-log-filter="error">Error</button>
<button class="btn secondary log-filter" type="button" data-log-filter="failed">Failed</button>
<button class="btn secondary log-filter" type="button" data-log-filter="blocked">Blocked</button>
<button class="btn secondary log-filter" type="button" data-log-filter="connection">Connection</button>
<button class="btn secondary log-filter" type="button" data-log-filter="system">System</button>
</div>
<div class="dmesg-meta" id="synologyLogMeta">/var/log/remote/synology/chaegeon.log</div>
<pre class="dmesg-log" id="synologyLogOutput" hidden></pre>
</div>
<div class="spike-log-box">
<div class="dmesg-head">
<h3>dmesg</h3>