Implement Monitor API v1.1 changes
BREAKING CHANGES: - IP endpoint changed: ip-pool -> servers&for_monitor=1 - BurnVote: removed 'reason' field, 'monitor_id' extracted from token - Evidence format updated with checked_at ISO timestamp NEW FEATURES: - Heartbeat endpoint (POST gateway-sync&action=heartbeat) - Rate limiting with retry logic (429 handling) - 60-second heartbeat timer in main process DEPRECATED: - register() method - token now created via admin panel Files changed: - src/shared/types.ts - Updated PanelIpInfo, BurnVote, added HeartbeatStats - src/main/services/PanelService.ts - Full API v1.1 implementation - src/main/index.ts - Heartbeat timer, checks counter - src/main/preload.ts - panelHeartbeat IPC - src/renderer/types/electron.d.ts - Updated types
This commit is contained in:
parent
c6dbff3fdb
commit
367a1c428e
File diff suppressed because one or more lines are too long
Binary file not shown.
|
|
@ -21,7 +21,7 @@ import { NotificationService } from './services/NotificationService'
|
|||
import { PanelService } from './services/PanelService'
|
||||
import { M3u8Parser } from './services/M3u8Parser'
|
||||
import { IPC_CHANNELS } from '../shared/types'
|
||||
import type { AppSettings, Channel, ErrorLogEntry } from '../shared/types'
|
||||
import type { AppSettings, Channel, ErrorLogEntry, HeartbeatStats } from '../shared/types'
|
||||
|
||||
// App quitting flag
|
||||
let isAppQuitting = false
|
||||
|
|
@ -34,6 +34,10 @@ let notificationService: NotificationService
|
|||
let panelService: PanelService
|
||||
let m3u8Parser: M3u8Parser
|
||||
|
||||
// Heartbeat timer
|
||||
let heartbeatInterval: NodeJS.Timeout | null = null
|
||||
let checksLastMinute = 0
|
||||
|
||||
// Window and tray
|
||||
let mainWindow: BrowserWindow | null = null
|
||||
let tray: Tray | null = null
|
||||
|
|
@ -166,6 +170,9 @@ function initializeServices() {
|
|||
streamChecker = new StreamChecker(
|
||||
settings.monitoring,
|
||||
(result) => {
|
||||
// Increment checks counter for heartbeat stats
|
||||
incrementChecksCounter()
|
||||
|
||||
// Send result to renderer
|
||||
mainWindow?.webContents.send(IPC_CHANNELS.MONITORING_UPDATE, result)
|
||||
|
||||
|
|
@ -201,6 +208,67 @@ function initializeServices() {
|
|||
console.log('[App] Portable mode:', isPortableMode())
|
||||
}
|
||||
|
||||
/**
|
||||
* Start heartbeat timer - sends heartbeat every 60 seconds
|
||||
* API v1.1: New heartbeat endpoint for monitor liveness
|
||||
*/
|
||||
function startHeartbeat(): void {
|
||||
if (heartbeatInterval) {
|
||||
clearInterval(heartbeatInterval)
|
||||
}
|
||||
|
||||
const settings = getSettings()
|
||||
if (!settings.panel.enabled) {
|
||||
console.log('[Heartbeat] Panel not enabled, skipping heartbeat')
|
||||
return
|
||||
}
|
||||
|
||||
// Send heartbeat every 60 seconds
|
||||
heartbeatInterval = setInterval(async () => {
|
||||
if (!panelService.isEnabled()) return
|
||||
|
||||
const channels = getChannels()
|
||||
const monitoredChannels = channels.filter(c => c.isMonitored)
|
||||
|
||||
const stats: HeartbeatStats = {
|
||||
active_checks: monitoredChannels.length,
|
||||
checks_last_minute: checksLastMinute,
|
||||
cpu_usage_percent: process.cpuUsage().user / 1000000, // Convert to percent
|
||||
memory_usage_mb: Math.round(process.memoryUsage().heapUsed / 1024 / 1024)
|
||||
}
|
||||
|
||||
const result = await panelService.sendHeartbeat(stats)
|
||||
if (result.success) {
|
||||
console.log('[Heartbeat] Sent successfully')
|
||||
} else {
|
||||
console.warn('[Heartbeat] Failed:', result.message)
|
||||
}
|
||||
|
||||
// Reset checks counter
|
||||
checksLastMinute = 0
|
||||
}, 60000) // 60 seconds
|
||||
|
||||
console.log('[Heartbeat] Timer started (60s interval)')
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop heartbeat timer
|
||||
*/
|
||||
function stopHeartbeat(): void {
|
||||
if (heartbeatInterval) {
|
||||
clearInterval(heartbeatInterval)
|
||||
heartbeatInterval = null
|
||||
console.log('[Heartbeat] Timer stopped')
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Increment checks counter for heartbeat stats
|
||||
*/
|
||||
function incrementChecksCounter(): void {
|
||||
checksLastMinute++
|
||||
}
|
||||
|
||||
function setupIpcHandlers() {
|
||||
// Window controls
|
||||
ipcMain.on('window:minimize', () => mainWindow?.minimize())
|
||||
|
|
@ -214,7 +282,7 @@ function setupIpcHandlers() {
|
|||
ipcMain.on('window:close', () => {
|
||||
// Çarpı butonuna basınca uygulamayı tamamen kapat
|
||||
isAppQuitting = true
|
||||
mainWindow?.close()
|
||||
app.quit()
|
||||
})
|
||||
|
||||
// IP Service
|
||||
|
|
@ -296,6 +364,11 @@ function setupIpcHandlers() {
|
|||
return await panelService.sendBurnVote(vote)
|
||||
})
|
||||
|
||||
// Panel Heartbeat - API v1.1
|
||||
ipcMain.handle(IPC_CHANNELS.PANEL_HEARTBEAT, async (_, stats: HeartbeatStats) => {
|
||||
return await panelService.sendHeartbeat(stats)
|
||||
})
|
||||
|
||||
// Settings
|
||||
ipcMain.handle(IPC_CHANNELS.GET_SETTINGS, async () => {
|
||||
return getSettings()
|
||||
|
|
@ -337,6 +410,9 @@ app.whenReady().then(() => {
|
|||
createWindow()
|
||||
createTray()
|
||||
setupIpcHandlers()
|
||||
|
||||
// Start heartbeat timer - API v1.1
|
||||
startHeartbeat()
|
||||
|
||||
app.on('activate', () => {
|
||||
if (BrowserWindow.getAllWindows().length === 0) {
|
||||
|
|
@ -353,5 +429,6 @@ app.on('window-all-closed', () => {
|
|||
|
||||
app.on('before-quit', () => {
|
||||
isAppQuitting = true
|
||||
stopHeartbeat()
|
||||
streamChecker?.stop()
|
||||
})
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { contextBridge, ipcRenderer } from 'electron'
|
||||
import { IPC_CHANNELS } from '../shared/types'
|
||||
import type { Channel, AppSettings, BurnVote, NotificationType } from '../shared/types'
|
||||
import type { Channel, AppSettings, BurnVote, NotificationType, HeartbeatStats } from '../shared/types'
|
||||
|
||||
// Expose protected methods to renderer
|
||||
contextBridge.exposeInMainWorld('electron', {
|
||||
|
|
@ -42,6 +42,7 @@ contextBridge.exposeInMainWorld('electron', {
|
|||
panelRegister: (config: unknown) => ipcRenderer.invoke(IPC_CHANNELS.PANEL_REGISTER, config),
|
||||
panelGetIps: () => ipcRenderer.invoke(IPC_CHANNELS.PANEL_GET_IPS),
|
||||
panelSendVote: (vote: BurnVote) => ipcRenderer.invoke(IPC_CHANNELS.PANEL_SEND_VOTE, vote),
|
||||
panelHeartbeat: (stats: HeartbeatStats) => ipcRenderer.invoke(IPC_CHANNELS.PANEL_HEARTBEAT, stats),
|
||||
|
||||
// Settings
|
||||
getSettings: () => ipcRenderer.invoke(IPC_CHANNELS.GET_SETTINGS),
|
||||
|
|
|
|||
|
|
@ -1,9 +1,28 @@
|
|||
import axios, { AxiosInstance } from 'axios'
|
||||
import type { PanelConfig, PanelIpPool, BurnVote } from '../../shared/types'
|
||||
import axios, { AxiosInstance, AxiosError } from 'axios'
|
||||
import type {
|
||||
PanelConfig,
|
||||
PanelIpPool,
|
||||
BurnVote,
|
||||
HeartbeatStats,
|
||||
HeartbeatResponse,
|
||||
PanelIpInfo
|
||||
} from '../../shared/types'
|
||||
|
||||
// Rate limiting state
|
||||
interface RateLimitState {
|
||||
isLimited: boolean
|
||||
retryAfter: number
|
||||
lastLimitTime: number
|
||||
}
|
||||
|
||||
export class PanelService {
|
||||
private config: PanelConfig
|
||||
private client: AxiosInstance | null = null
|
||||
private rateLimitState: RateLimitState = {
|
||||
isLimited: false,
|
||||
retryAfter: 0,
|
||||
lastLimitTime: 0
|
||||
}
|
||||
|
||||
constructor(config: PanelConfig) {
|
||||
this.config = config
|
||||
|
|
@ -26,29 +45,50 @@ export class PanelService {
|
|||
timeout: 10000,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
// API v1.1: Token is now 64-char hex from admin panel
|
||||
...(this.config.authToken && {
|
||||
'Authorization': `Bearer ${this.config.authToken}`
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
// Add response interceptor for rate limiting
|
||||
this.client.interceptors.response.use(
|
||||
response => response,
|
||||
async (error: AxiosError) => {
|
||||
if (error.response?.status === 429) {
|
||||
const retryAfter = parseInt(error.response.headers['retry-after'] || '60', 10)
|
||||
this.rateLimitState = {
|
||||
isLimited: true,
|
||||
retryAfter,
|
||||
lastLimitTime: Date.now()
|
||||
}
|
||||
console.warn(`[PanelService] Rate limited. Retry after ${retryAfter}s`)
|
||||
}
|
||||
throw error
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Register monitor with panel
|
||||
* Based on MONITOR_API_GUIDE.md
|
||||
* @deprecated API v1.1: Token is now created via admin panel
|
||||
* This method is kept for backwards compatibility but will log a warning
|
||||
*/
|
||||
async register(): Promise<{ success: boolean; authToken?: string; error?: string }> {
|
||||
console.warn('[PanelService] register() is deprecated in API v1.1. Token should be created via admin panel.')
|
||||
|
||||
if (!this.client) {
|
||||
return { success: false, error: 'Panel not configured' }
|
||||
}
|
||||
|
||||
try {
|
||||
const os = await import('os')
|
||||
const response = await this.client.post('/api.php?endpoint=servers', {
|
||||
server_id: this.config.monitorId,
|
||||
server_type: 'monitor',
|
||||
hostname: require('os').hostname(),
|
||||
hostname: os.hostname(),
|
||||
ip_address: await this.getLocalIp(),
|
||||
country_code: 'TR' // Could be detected dynamically
|
||||
country_code: 'TR'
|
||||
})
|
||||
|
||||
if (response.data.success) {
|
||||
|
|
@ -66,104 +106,208 @@ export class PanelService {
|
|||
|
||||
/**
|
||||
* Get IP pool from panel
|
||||
* Based on MONITOR_API_GUIDE.md - Option A: REST API (Polling)
|
||||
* API v1.1: Changed endpoint from ip-pool to servers&for_monitor=1
|
||||
*/
|
||||
async getIpPool(): Promise<PanelIpPool> {
|
||||
if (!this.client || !this.config.enabled) {
|
||||
return { active: [], honeypot: [] }
|
||||
return { ips: [], timestamp: Date.now() }
|
||||
}
|
||||
|
||||
// Check rate limit
|
||||
if (this.isRateLimited()) {
|
||||
console.warn('[PanelService] Rate limited, skipping getIpPool')
|
||||
return { ips: [], timestamp: Date.now() }
|
||||
}
|
||||
|
||||
try {
|
||||
// API v1.1: New endpoint
|
||||
const response = await this.client.get('/api.php', {
|
||||
params: {
|
||||
endpoint: 'ip-pool',
|
||||
for_monitor: true
|
||||
endpoint: 'servers',
|
||||
for_monitor: '1'
|
||||
}
|
||||
})
|
||||
|
||||
if (response.data.success) {
|
||||
return response.data.ips || { active: [], honeypot: [] }
|
||||
// API v1.1: New response format
|
||||
if (response.data.ips) {
|
||||
return {
|
||||
ips: response.data.ips as PanelIpInfo[],
|
||||
timestamp: response.data.timestamp || Date.now()
|
||||
}
|
||||
}
|
||||
|
||||
console.error('Failed to get IP pool:', response.data.message)
|
||||
return { active: [], honeypot: [] }
|
||||
// Legacy format support
|
||||
if (response.data.success && response.data.ips) {
|
||||
const legacyIps = response.data.ips
|
||||
return {
|
||||
ips: [...(legacyIps.active || []), ...(legacyIps.honeypot || [])],
|
||||
timestamp: response.data.timestamp || Date.now(),
|
||||
active: legacyIps.active,
|
||||
honeypot: legacyIps.honeypot
|
||||
}
|
||||
}
|
||||
|
||||
console.error('[PanelService] Failed to get IP pool:', response.data.message)
|
||||
return { ips: [], timestamp: Date.now() }
|
||||
} catch (error) {
|
||||
console.error('Failed to fetch IP pool:', error)
|
||||
return { active: [], honeypot: [] }
|
||||
if ((error as AxiosError).response?.status !== 429) {
|
||||
console.error('[PanelService] Failed to fetch IP pool:', error)
|
||||
}
|
||||
return { ips: [], timestamp: Date.now() }
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Send burn vote
|
||||
* Based on MONITOR_API_GUIDE.md - Section 2: Burn Bildirimi
|
||||
* API v1.1: monitor_id removed from payload (extracted from Bearer token)
|
||||
* API v1.1: reason field removed, evidence format changed
|
||||
*/
|
||||
async sendBurnVote(vote: BurnVote): Promise<{ success: boolean; error?: string }> {
|
||||
async sendBurnVote(vote: BurnVote): Promise<{ success: boolean; error?: string; currentVotes?: object }> {
|
||||
if (!this.client || !this.config.enabled) {
|
||||
return { success: false, error: 'Panel not enabled' }
|
||||
}
|
||||
|
||||
// Check rate limit
|
||||
if (this.isRateLimited()) {
|
||||
console.warn('[PanelService] Rate limited, queuing burn vote')
|
||||
return { success: false, error: 'Rate limited, try again later' }
|
||||
}
|
||||
|
||||
try {
|
||||
// API v1.1: New payload format - no monitor_id, no reason
|
||||
const response = await this.client.post('/api.php?endpoint=burn-vote', {
|
||||
ip_address: vote.ip_address,
|
||||
vote: vote.vote,
|
||||
reason: vote.reason,
|
||||
evidence: {
|
||||
...vote.evidence,
|
||||
monitor_id: this.config.monitorId,
|
||||
timestamp: Math.floor(Date.now() / 1000)
|
||||
}
|
||||
evidence: vote.evidence
|
||||
})
|
||||
|
||||
if (response.data.success) {
|
||||
console.log('Burn vote submitted:', {
|
||||
console.log('[PanelService] Burn vote submitted:', {
|
||||
ip: vote.ip_address,
|
||||
vote: vote.vote,
|
||||
current_votes: response.data.current_votes
|
||||
})
|
||||
return { success: true }
|
||||
return {
|
||||
success: true,
|
||||
currentVotes: response.data.current_votes
|
||||
}
|
||||
}
|
||||
|
||||
return { success: false, error: response.data.message }
|
||||
} catch (error) {
|
||||
const axiosError = error as AxiosError
|
||||
|
||||
// Handle 403 - wrong server type
|
||||
if (axiosError.response?.status === 403) {
|
||||
const errorData = axiosError.response.data as { message?: string }
|
||||
console.error('[PanelService] Forbidden:', errorData.message)
|
||||
return { success: false, error: errorData.message || 'Forbidden: Server type not allowed' }
|
||||
}
|
||||
|
||||
// Retry on rate limit
|
||||
if (axiosError.response?.status === 429) {
|
||||
const retryAfter = parseInt(axiosError.response.headers['retry-after'] || '60', 10)
|
||||
console.warn(`[PanelService] Rate limited on burn vote. Waiting ${retryAfter}s...`)
|
||||
|
||||
// Wait and retry once
|
||||
await this.sleep(retryAfter * 1000)
|
||||
return this.sendBurnVote(vote)
|
||||
}
|
||||
|
||||
return { success: false, error: (error as Error).message }
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Report IP health status
|
||||
* Send heartbeat to panel
|
||||
* API v1.1: New endpoint for monitor liveness
|
||||
*/
|
||||
async sendHeartbeat(stats: HeartbeatStats): Promise<HeartbeatResponse> {
|
||||
if (!this.client || !this.config.enabled) {
|
||||
return { success: false, message: 'Panel not enabled' }
|
||||
}
|
||||
|
||||
// Check rate limit
|
||||
if (this.isRateLimited()) {
|
||||
return { success: false, message: 'Rate limited' }
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await this.client.post(
|
||||
'/api.php?endpoint=gateway-sync&action=heartbeat',
|
||||
{ data: stats }
|
||||
)
|
||||
|
||||
if (response.data.success) {
|
||||
console.log('[PanelService] Heartbeat sent successfully')
|
||||
return { success: true, message: response.data.message || 'Heartbeat received' }
|
||||
}
|
||||
|
||||
return { success: false, message: response.data.message || 'Heartbeat failed' }
|
||||
} catch (error) {
|
||||
console.error('[PanelService] Heartbeat failed:', error)
|
||||
return { success: false, message: (error as Error).message }
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Report IP health status - updated for API v1.1
|
||||
*/
|
||||
async reportHealth(
|
||||
ipAddress: string,
|
||||
isWorking: boolean,
|
||||
checkResult: {
|
||||
checkType: string
|
||||
port?: number
|
||||
checkType: 'http_health' | 'tcp_connect' | 'ssl_verify' | 'dns_lookup'
|
||||
responseCode?: number
|
||||
latencyMs?: number
|
||||
error?: string
|
||||
}
|
||||
): Promise<void> {
|
||||
// API v1.1: New evidence format with checked_at ISO timestamp
|
||||
const vote: BurnVote = {
|
||||
ip_address: ipAddress,
|
||||
vote: isWorking ? 'ok' : 'burn',
|
||||
reason: isWorking ? 'IP working normally' : checkResult.error || 'Check failed',
|
||||
evidence: {
|
||||
check_type: checkResult.checkType,
|
||||
port: checkResult.port,
|
||||
response_time_ms: checkResult.latencyMs,
|
||||
error_message: checkResult.error,
|
||||
timestamp: Math.floor(Date.now() / 1000),
|
||||
monitor_id: this.config.monitorId
|
||||
response_code: checkResult.responseCode || 0,
|
||||
latency_ms: checkResult.latencyMs || 0,
|
||||
error_message: checkResult.error || null,
|
||||
checked_at: new Date().toISOString()
|
||||
}
|
||||
}
|
||||
|
||||
await this.sendBurnVote(vote)
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if currently rate limited
|
||||
*/
|
||||
private isRateLimited(): boolean {
|
||||
if (!this.rateLimitState.isLimited) {
|
||||
return false
|
||||
}
|
||||
|
||||
const elapsed = (Date.now() - this.rateLimitState.lastLimitTime) / 1000
|
||||
if (elapsed >= this.rateLimitState.retryAfter) {
|
||||
this.rateLimitState.isLimited = false
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* Sleep utility for retry logic
|
||||
*/
|
||||
private sleep(ms: number): Promise<void> {
|
||||
return new Promise(resolve => setTimeout(resolve, ms))
|
||||
}
|
||||
|
||||
/**
|
||||
* Get local IP address
|
||||
*/
|
||||
private async getLocalIp(): Promise<string> {
|
||||
const os = require('os')
|
||||
const os = await import('os')
|
||||
const interfaces = os.networkInterfaces()
|
||||
|
||||
for (const name of Object.keys(interfaces)) {
|
||||
|
|
@ -183,5 +327,21 @@ export class PanelService {
|
|||
isEnabled(): boolean {
|
||||
return this.config.enabled && !!this.config.apiUrl && !!this.config.authToken
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get rate limit status for UI display
|
||||
*/
|
||||
getRateLimitStatus(): { isLimited: boolean; secondsRemaining: number } {
|
||||
if (!this.rateLimitState.isLimited) {
|
||||
return { isLimited: false, secondsRemaining: 0 }
|
||||
}
|
||||
|
||||
const elapsed = (Date.now() - this.rateLimitState.lastLimitTime) / 1000
|
||||
const remaining = Math.max(0, this.rateLimitState.retryAfter - elapsed)
|
||||
|
||||
return {
|
||||
isLimited: remaining > 0,
|
||||
secondsRemaining: Math.ceil(remaining)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@ export interface ElectronAPI {
|
|||
panelRegister: (config: import('@shared/types').PanelConfig) => Promise<{ success: boolean; authToken?: string }>
|
||||
panelGetIps: () => Promise<import('@shared/types').PanelIpPool>
|
||||
panelSendVote: (vote: import('@shared/types').BurnVote) => Promise<{ success: boolean }>
|
||||
panelHeartbeat: (stats: import('@shared/types').HeartbeatStats) => Promise<import('@shared/types').HeartbeatResponse>
|
||||
|
||||
// Settings
|
||||
getSettings: () => Promise<import('@shared/types').AppSettings>
|
||||
|
|
|
|||
|
|
@ -71,37 +71,62 @@ export interface NotificationSettings {
|
|||
notifyOnConnReset: boolean;
|
||||
}
|
||||
|
||||
// Panel API Types (from MONITOR_API_GUIDE)
|
||||
// Panel API Types (from MONITOR_API_GUIDE v1.1)
|
||||
export interface PanelConfig {
|
||||
apiUrl: string;
|
||||
authToken: string;
|
||||
authToken: string; // Now 64-char hex token from admin panel
|
||||
monitorId: string;
|
||||
enabled: boolean;
|
||||
}
|
||||
|
||||
// Updated for API v1.1 - new response format from servers endpoint
|
||||
export interface PanelIpInfo {
|
||||
ip_address: string;
|
||||
server_id: string;
|
||||
check_priority: 'high' | 'normal' | 'low';
|
||||
server_type: 'publisher' | 'monitor' | 'gateway';
|
||||
status: 'active' | 'inactive' | 'burned';
|
||||
active_users?: number;
|
||||
// Legacy field for backwards compatibility
|
||||
check_priority?: 'high' | 'normal' | 'low';
|
||||
}
|
||||
|
||||
// Updated response format for API v1.1
|
||||
export interface PanelIpPool {
|
||||
active: PanelIpInfo[];
|
||||
honeypot: PanelIpInfo[];
|
||||
ips: PanelIpInfo[];
|
||||
timestamp: number;
|
||||
// Legacy fields for backwards compatibility
|
||||
active?: PanelIpInfo[];
|
||||
honeypot?: PanelIpInfo[];
|
||||
}
|
||||
|
||||
// Updated for API v1.1 - monitor_id removed (extracted from Bearer token)
|
||||
export interface BurnVote {
|
||||
ip_address: string;
|
||||
vote: 'burn' | 'ok' | 'abstain';
|
||||
reason: string;
|
||||
evidence: {
|
||||
check_type: string;
|
||||
port?: number;
|
||||
response_time_ms?: number;
|
||||
error_message?: string;
|
||||
timestamp: number;
|
||||
monitor_id: string;
|
||||
};
|
||||
evidence: BurnVoteEvidence;
|
||||
}
|
||||
|
||||
// New evidence format for API v1.1
|
||||
export interface BurnVoteEvidence {
|
||||
check_type: 'http_health' | 'tcp_connect' | 'ssl_verify' | 'dns_lookup';
|
||||
response_code: number;
|
||||
latency_ms: number;
|
||||
error_message: string | null;
|
||||
checked_at: string; // ISO 8601 format
|
||||
}
|
||||
|
||||
// New for API v1.1 - Heartbeat stats
|
||||
export interface HeartbeatStats {
|
||||
active_checks: number;
|
||||
checks_last_minute: number;
|
||||
cpu_usage_percent?: number;
|
||||
memory_usage_mb?: number;
|
||||
}
|
||||
|
||||
// Heartbeat response
|
||||
export interface HeartbeatResponse {
|
||||
success: boolean;
|
||||
message: string;
|
||||
}
|
||||
|
||||
// App Settings
|
||||
|
|
@ -147,9 +172,10 @@ export const IPC_CHANNELS = {
|
|||
TEST_NOTIFICATION: 'notify:test',
|
||||
|
||||
// Panel API
|
||||
PANEL_REGISTER: 'panel:register',
|
||||
PANEL_REGISTER: 'panel:register', // Deprecated in v1.1
|
||||
PANEL_GET_IPS: 'panel:get-ips',
|
||||
PANEL_SEND_VOTE: 'panel:send-vote',
|
||||
PANEL_HEARTBEAT: 'panel:heartbeat',
|
||||
|
||||
// Settings
|
||||
GET_SETTINGS: 'settings:get',
|
||||
|
|
|
|||
Loading…
Reference in New Issue