Vue 3 + TypeScript + Vite web client for rusty-telemetry. - Dashboard with system status, live feeds, quick actions - Live telemetry view with real-time gauges (speed/RPM/gear) - Session management (create, run, complete, analyze) - Shift point analysis with Chart.js visualizations - Track map with Canvas 2D boundary rendering - Typed API client for all 19 REST endpoints - Dark theme, network-accessible via Vite 0.0.0.0 bind
89 lines
2.6 KiB
TypeScript
89 lines
2.6 KiB
TypeScript
import { defineStore } from 'pinia'
|
|
import { ref, computed } from 'vue'
|
|
import type { LiveTelemetryResponse, LiveFeedSnapshot } from '@/types'
|
|
import { getLiveTelemetry } from '@/api/client'
|
|
|
|
export const useTelemetryStore = defineStore('telemetry', () => {
|
|
const feeds = ref<LiveFeedSnapshot[]>([])
|
|
const activeRecording = ref<string | null>(null)
|
|
const connected = ref(false)
|
|
const error = ref<string | null>(null)
|
|
const lastUpdate = ref<Date | null>(null)
|
|
|
|
let pollTimer: ReturnType<typeof setInterval> | null = null
|
|
|
|
const anyFeedAlive = computed(() => feeds.value.some((f) => f.health === 'alive'))
|
|
const anyFeedStale = computed(() => feeds.value.some((f) => f.health === 'stale'))
|
|
const totalPackets = computed(() => feeds.value.reduce((sum, f) => sum + f.total_packets, 0))
|
|
const simConnected = computed(() => anyFeedAlive.value || anyFeedStale.value)
|
|
|
|
const overallStatus = computed((): 'connected' | 'sim-active' | 'sim-stale' | 'disconnected' => {
|
|
if (!connected.value) return 'disconnected'
|
|
if (anyFeedAlive.value) return 'sim-active'
|
|
if (anyFeedStale.value) return 'sim-stale'
|
|
return 'connected'
|
|
})
|
|
|
|
const overallStatusLabel = computed((): string => {
|
|
switch (overallStatus.value) {
|
|
case 'sim-active': return 'Sim Connected'
|
|
case 'sim-stale': return 'Sim Stale'
|
|
case 'connected': return 'API Connected — No Sim'
|
|
case 'disconnected': return 'API Disconnected'
|
|
}
|
|
})
|
|
|
|
const overallStatusClass = computed((): string => {
|
|
switch (overallStatus.value) {
|
|
case 'sim-active': return 'badge-success'
|
|
case 'sim-stale': return 'badge-warning'
|
|
case 'connected': return 'badge-primary'
|
|
case 'disconnected': return 'badge-error'
|
|
}
|
|
})
|
|
|
|
async function fetchLive() {
|
|
try {
|
|
const data: LiveTelemetryResponse = await getLiveTelemetry()
|
|
feeds.value = data.feeds
|
|
activeRecording.value = data.active_recording
|
|
connected.value = true
|
|
error.value = null
|
|
lastUpdate.value = new Date()
|
|
} catch (e: unknown) {
|
|
connected.value = false
|
|
error.value = e instanceof Error ? e.message : 'Connection failed'
|
|
}
|
|
}
|
|
|
|
function startPolling(intervalMs = 250) {
|
|
stopPolling()
|
|
fetchLive()
|
|
pollTimer = setInterval(fetchLive, intervalMs)
|
|
}
|
|
|
|
function stopPolling() {
|
|
if (pollTimer !== null) {
|
|
clearInterval(pollTimer)
|
|
pollTimer = null
|
|
}
|
|
}
|
|
|
|
return {
|
|
feeds,
|
|
activeRecording,
|
|
connected,
|
|
error,
|
|
lastUpdate,
|
|
anyFeedAlive,
|
|
anyFeedStale,
|
|
totalPackets,
|
|
simConnected,
|
|
overallStatus,
|
|
overallStatusLabel,
|
|
overallStatusClass,
|
|
fetchLive,
|
|
startPolling,
|
|
stopPolling,
|
|
}
|
|
})
|