feat: add podkop log watcher with alerts

This commit is contained in:
divocat
2025-10-18 23:02:35 +03:00
parent f6e347af78
commit 213b4603b7
4 changed files with 204 additions and 0 deletions

View File

@@ -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,
};
}

View File

@@ -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();
}

View File

@@ -0,0 +1,88 @@
import { logger } from './logger.service';
export type LogFetcher = () => Promise<string> | 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<string>();
private timer?: ReturnType<typeof setInterval>;
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<void> {
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');
}
}

View File

@@ -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