feat: initial Racecraft web client v0.1.0
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
This commit is contained in:
commit
cf9af73281
27 changed files with 5123 additions and 0 deletions
89
src/stores/telemetry.ts
Normal file
89
src/stores/telemetry.ts
Normal file
|
|
@ -0,0 +1,89 @@
|
|||
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,
|
||||
}
|
||||
})
|
||||
Loading…
Add table
Add a link
Reference in a new issue