diff --git a/fe-app-podkop/src/luci.d.ts b/fe-app-podkop/src/luci.d.ts index 9f70c21..3600e76 100644 --- a/fe-app-podkop/src/luci.d.ts +++ b/fe-app-podkop/src/luci.d.ts @@ -39,6 +39,11 @@ declare global { const ui = { showModal: (_title: stirng, _content: HtmlElement) => undefined, hideModal: () => undefined, + addNotification: ( + _title: string, + _children: HtmlElement | HtmlElement[], + _className?: string, + ) => undefined, }; } diff --git a/fe-app-podkop/src/podkop/services/core.service.ts b/fe-app-podkop/src/podkop/services/core.service.ts index 276475d..b9a52b0 100644 --- a/fe-app-podkop/src/podkop/services/core.service.ts +++ b/fe-app-podkop/src/podkop/services/core.service.ts @@ -1,6 +1,8 @@ import { TabServiceInstance } from './tab.service'; import { store } from './store.service'; import { logger } from './logger.service'; +import { PodkopLogWatcher } from './podkopLogWatcher.service'; +import { PodkopShellMethods } from '../methods'; export function coreService() { TabServiceInstance.onChange((activeId, tabs) => { @@ -12,4 +14,28 @@ export function coreService() { }, }); }); + + const watcher = PodkopLogWatcher.getInstance(); + + watcher.init( + async () => { + const logs = await PodkopShellMethods.checkLogs(); + + if (logs.success) { + return logs.data as string; + } + + return ''; + }, + { + intervalMs: 3000, + onNewLog: (line) => { + if (line.includes('[critical]')) { + ui.addNotification('Podkop Critical', E('div', {}, line), 'error'); + } + }, + }, + ); + + watcher.start(); } diff --git a/fe-app-podkop/src/podkop/services/podkopLogWatcher.service.ts b/fe-app-podkop/src/podkop/services/podkopLogWatcher.service.ts new file mode 100644 index 0000000..9548408 --- /dev/null +++ b/fe-app-podkop/src/podkop/services/podkopLogWatcher.service.ts @@ -0,0 +1,88 @@ +import { logger } from './logger.service'; + +export type LogFetcher = () => Promise | string; + +export interface PodkopLogWatcherOptions { + intervalMs?: number; + onNewLog?: (line: string) => void; +} + +export class PodkopLogWatcher { + private static instance: PodkopLogWatcher; + private fetcher?: LogFetcher; + private onNewLog?: (line: string) => void; + private intervalMs = 5000; + private lastLines = new Set(); + private timer?: ReturnType; + private running = false; + + private constructor() {} + + static getInstance(): PodkopLogWatcher { + if (!PodkopLogWatcher.instance) { + PodkopLogWatcher.instance = new PodkopLogWatcher(); + } + return PodkopLogWatcher.instance; + } + + init(fetcher: LogFetcher, options?: PodkopLogWatcherOptions): void { + this.fetcher = fetcher; + this.onNewLog = options?.onNewLog; + this.intervalMs = options?.intervalMs ?? 5000; + } + + async checkOnce(): Promise { + if (!this.fetcher) { + logger.warn('[PodkopLogWatcher]', 'fetcher not found'); + return; + } + + try { + const raw = await this.fetcher(); + const lines = raw.split('\n').filter(Boolean); + + for (const line of lines) { + if (!this.lastLines.has(line)) { + this.lastLines.add(line); + + if (this.onNewLog) { + this.onNewLog(line); + } + } + } + + if (this.lastLines.size > 500) { + const arr = Array.from(this.lastLines); + this.lastLines = new Set(arr.slice(-500)); + } + } catch (err) { + logger.error('[PodkopLogWatcher]', 'failed to read logs:', err); + } + } + + start(): void { + if (this.running) return; + if (!this.fetcher) { + logger.warn('[PodkopLogWatcher]', 'Try to start without fetcher.'); + return; + } + + this.running = true; + this.timer = setInterval(() => this.checkOnce(), this.intervalMs); + logger.info( + `[PodkopLogWatcher]', 'Started with interval ${this.intervalMs} ms`, + ); + } + + stop(): void { + if (!this.running) return; + this.running = false; + if (this.timer) clearInterval(this.timer); + logger.info('[PodkopLogWatcher]', 'Stopped'); + } + + reset(): void { + this.lastLines.clear(); + logger.info('[PodkopLogWatcher]', 'logs history was reset'); + } +} diff --git a/luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js b/luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js index 3a9c4a1..822440a 100644 --- a/luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js +++ b/luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js @@ -1234,6 +1234,72 @@ var Logger = class { }; var logger = new Logger(); +// src/podkop/services/podkopLogWatcher.service.ts +var PodkopLogWatcher = class _PodkopLogWatcher { + constructor() { + this.intervalMs = 5e3; + this.lastLines = /* @__PURE__ */ new Set(); + this.running = false; + } + static getInstance() { + if (!_PodkopLogWatcher.instance) { + _PodkopLogWatcher.instance = new _PodkopLogWatcher(); + } + return _PodkopLogWatcher.instance; + } + init(fetcher, options) { + this.fetcher = fetcher; + this.onNewLog = options?.onNewLog; + this.intervalMs = options?.intervalMs ?? 5e3; + } + async checkOnce() { + if (!this.fetcher) { + logger.warn("[PodkopLogWatcher]", "fetcher not found"); + return; + } + try { + const raw = await this.fetcher(); + const lines = raw.split("\n").filter(Boolean); + for (const line of lines) { + if (!this.lastLines.has(line)) { + this.lastLines.add(line); + if (this.onNewLog) { + this.onNewLog(line); + } + } + } + if (this.lastLines.size > 500) { + const arr = Array.from(this.lastLines); + this.lastLines = new Set(arr.slice(-500)); + } + } catch (err) { + logger.error("[PodkopLogWatcher]", "failed to read logs:", err); + } + } + start() { + if (this.running) return; + if (!this.fetcher) { + logger.warn("[PodkopLogWatcher]", "Try to start without fetcher."); + return; + } + this.running = true; + this.timer = setInterval(() => this.checkOnce(), this.intervalMs); + logger.info( + `[PodkopLogWatcher]', 'Started with interval ${this.intervalMs} ms` + ); + } + stop() { + if (!this.running) return; + this.running = false; + if (this.timer) clearInterval(this.timer); + logger.info("[PodkopLogWatcher]", "Stopped"); + } + reset() { + this.lastLines.clear(); + logger.info("[PodkopLogWatcher]", "logs history was reset"); + } +}; + // src/podkop/services/core.service.ts function coreService() { TabServiceInstance.onChange((activeId, tabs) => { @@ -1245,6 +1311,25 @@ function coreService() { } }); }); + const watcher = PodkopLogWatcher.getInstance(); + watcher.init( + async () => { + const logs = await PodkopShellMethods.checkLogs(); + if (logs.success) { + return logs.data; + } + return ""; + }, + { + intervalMs: 3e3, + onNewLog: (line) => { + if (line.includes("[critical]")) { + ui.addNotification("Podkop Critical", E("div", {}, line), "error"); + } + } + } + ); + watcher.start(); } // src/podkop/services/socket.service.ts