Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions core/frontend/src/menus.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,13 @@ const menus = [
advanced: true,
text: 'Visualize disk usage and delete files/folders.',
},
{
title: 'Health Monitor',
icon: 'mdi-heart-pulse',
route: '/tools/health-monitor',
advanced: false,
text: 'Monitor system and vehicle health warnings.',
},
{
title: 'Log Browser',
icon: 'mdi-math-log',
Expand Down
5 changes: 5 additions & 0 deletions core/frontend/src/router/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,11 @@ const routes: Array<RouteConfig> = [
name: 'Disk',
component: defineAsyncComponent(() => import('../views/Disk.vue')),
},
{
path: '/tools/health-monitor',
name: 'Health Monitor',
component: defineAsyncComponent(() => import('../views/HealthMonitor.vue')),
},
{
path: '/tools/web-terminal',
name: 'Terminal',
Expand Down
90 changes: 90 additions & 0 deletions core/frontend/src/store/health_monitor.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import {
Action, Module, Mutation, VuexModule, getModule,
} from 'vuex-module-decorators'

import store from '@/store'
import { HealthHistory, HealthSummary } from '@/types/health-monitor'
import back_axios, { isBackendOffline } from '@/utils/api'

@Module({ dynamic: true, store, name: 'health_monitor' })
class HealthMonitorStore extends VuexModule {
API_URL = '/health-monitor/v1.0/health'

summary: HealthSummary | null = null

history: HealthHistory | null = null

loading = false

error: string | null = null

@Mutation
setSummary(value: HealthSummary | null): void {
this.summary = value
}

@Mutation
setHistory(value: HealthHistory | null): void {
this.history = value
}

@Mutation
setLoading(value: boolean): void {
this.loading = value
}

@Mutation
setError(message: string | null): void {
this.error = message
}

@Action
async fetchSummary(): Promise<void> {
this.setLoading(true)
this.setError(null)

await back_axios({
method: 'get',
url: `${this.API_URL}/summary`,
timeout: 10000,
})
.then((response) => {
this.setSummary(response.data as HealthSummary)
})
.catch((error) => {
this.setSummary(null)
if (isBackendOffline(error)) {
return
}
this.setError(`Failed to fetch health summary: ${error.message}`)
})
.finally(() => {
this.setLoading(false)
})
}

@Action
async fetchHistory(limit = 200): Promise<void> {
await back_axios({
method: 'get',
url: `${this.API_URL}/history`,
params: { limit },
timeout: 10000,
})
.then((response) => {
this.setHistory(response.data as HealthHistory)
})
.catch((error) => {
this.setHistory(null)
if (isBackendOffline(error)) {
return
}
this.setError(`Failed to fetch health history: ${error.message}`)
})
}
}

const health_monitor = getModule(HealthMonitorStore)

export { HealthMonitorStore }
export default health_monitor
7 changes: 7 additions & 0 deletions core/frontend/src/types/frontend_services.ts
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,13 @@ export const kraken_service: Service = {
version: '0.1.0',
}

export const health_monitor_service: Service = {
name: 'Health Monitor',
description: 'Service to monitor system and vehicle health warnings.',
company: 'Blue Robotics',
version: '0.1.0',
}

export const parameters_service: Service = {
name: 'Parameters service',
description: 'Service to manage vehicle Parameters',
Expand Down
28 changes: 28 additions & 0 deletions core/frontend/src/types/health-monitor.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
export type HealthSeverity = 'info' | 'warn' | 'error' | 'critical'
export type HealthSource = 'system' | 'vehicle' | 'extension' | 'network'
export type HealthEventType = 'problem_detected' | 'problem_resolved' | 'problem_updated'

export interface HealthProblem {
id: string
severity: HealthSeverity
title: string
details: string
source: HealthSource
timestamp: number
metadata?: Record<string, unknown>
first_seen_ms?: number
last_seen_ms?: number
}

export interface HealthEvent extends HealthProblem {
type: HealthEventType
}

export interface HealthSummary {
active: HealthProblem[]
updated_at: number
}

export interface HealthHistory {
events: HealthEvent[]
}
Loading