Skip to content

Commit 78695c2

Browse files
committed
Adding InfluxDB test connection
1 parent a383155 commit 78695c2

7 files changed

Lines changed: 151 additions & 28 deletions

File tree

Lines changed: 6 additions & 0 deletions
Loading
Lines changed: 6 additions & 0 deletions
Loading

flight-recorder/src/App.tsx

Lines changed: 61 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,22 @@
11
import React, { useState, useEffect } from 'react';
2-
import {
3-
Circle,
4-
Square,
5-
CloudUpload,
6-
Trash2,
7-
Database,
2+
import {
3+
Circle,
4+
Square,
5+
CloudUpload,
6+
Trash2,
7+
Database,
88
AlertCircle,
99
CheckCircle2,
1010
Activity,
11-
Settings,
1211
Globe,
1312
RefreshCw,
1413
Wifi,
15-
WifiOff
14+
WifiOff,
15+
FlaskConical
1616
} from 'lucide-react';
1717
import { loggingService } from './services/LoggingService';
1818
import { syncService } from './services/SyncService';
19+
import type { ConnectionTestResult } from './services/SyncService';
1920
import { webSocketService } from './services/WebSocketService';
2021
import type { ConnectionStatus } from './services/WebSocketService';
2122
import { loggingHandler } from './services/LoggingHandler';
@@ -26,6 +27,8 @@ const FlightDataRecorder: React.FC = () => {
2627
const [totalCount, setTotalCount] = useState(0);
2728
const [isSyncing, setIsSyncing] = useState(syncService.isSyncing());
2829
const [syncProgress, setSyncProgress] = useState({ processed: 0, total: 0 });
30+
const [connTest, setConnTest] = useState<ConnectionTestResult | null>(null);
31+
const [isTesting, setIsTesting] = useState(false);
2932

3033
// Connection state
3134
const [wsStatus, setWsStatus] = useState<ConnectionStatus>({
@@ -36,13 +39,17 @@ const FlightDataRecorder: React.FC = () => {
3639

3740
// Settings for InfluxDB
3841
const [influxSettings, setInfluxSettings] = useState({
39-
url: localStorage.getItem('influx-url') || 'http://localhost:8181',
42+
url: localStorage.getItem('influx-url') || 'https://influxdb3.westernformularacing.org',
4043
token: localStorage.getItem('influx-token') || '',
4144
org: localStorage.getItem('influx-org') || 'WFR',
42-
bucket: localStorage.getItem('influx-bucket') || 'WFR25'
45+
bucket: localStorage.getItem('influx-bucket') || 'WFR26',
4346
});
4447

4548
useEffect(() => {
49+
// Legacy cleanup: SSO-cookie mode no longer uses CF service-token fields.
50+
localStorage.removeItem('influx-cfClientId');
51+
localStorage.removeItem('influx-cfClientSecret');
52+
4653
// Initialize standard handlers
4754
loggingHandler.initialize();
4855
webSocketService.initialize();
@@ -119,6 +126,18 @@ const FlightDataRecorder: React.FC = () => {
119126
localStorage.setItem(`influx-${key}`, value);
120127
};
121128

129+
const handleTestConnection = async () => {
130+
setIsTesting(true);
131+
setConnTest(null);
132+
const result = await syncService.testConnection(
133+
influxSettings.url,
134+
influxSettings.token,
135+
influxSettings.bucket
136+
);
137+
setConnTest(result);
138+
setIsTesting(false);
139+
};
140+
122141
const saveWsUrl = () => {
123142
if (customWsUrl) {
124143
localStorage.setItem('custom-ws-url', customWsUrl);
@@ -138,7 +157,7 @@ const FlightDataRecorder: React.FC = () => {
138157
</div>
139158
WFR Flight Recorder
140159
</h1>
141-
<p className="text-slate-400 mt-1 font-medium italic">High-Fidelity Telemetry Logging</p>
160+
<p className="text-slate-400 mt-1 font-medium italic">ehh we will get our antenna figured out soon</p>
142161
</div>
143162

144163
<div className="flex items-center gap-3">
@@ -286,10 +305,23 @@ const FlightDataRecorder: React.FC = () => {
286305
className="w-full bg-slate-900 border border-slate-700 rounded-xl px-4 py-3 font-mono text-sm focus:ring-2 focus:ring-blue-500 outline-none"
287306
/>
288307
</div>
308+
<p className="text-[10px] text-slate-500 leading-relaxed border border-slate-700/60 rounded-xl px-3 py-2 bg-slate-900/60">
309+
Cloudflare Access uses your browser SSO session. Sync requests include cookies automatically.
310+
</p>
289311
</div>
290312
</div>
291313

292314
<div className="mt-auto pt-6 border-t border-slate-700/50">
315+
{connTest && (
316+
<div className={`mb-4 flex items-start gap-2 px-4 py-3 rounded-xl text-sm font-mono border ${
317+
connTest.ok
318+
? 'bg-emerald-500/10 border-emerald-500/30 text-emerald-400'
319+
: 'bg-red-500/10 border-red-500/30 text-red-400'
320+
}`}>
321+
{connTest.ok ? <CheckCircle2 size={16} className="mt-0.5 shrink-0" /> : <AlertCircle size={16} className="mt-0.5 shrink-0" />}
322+
<span>{connTest.message}</span>
323+
</div>
324+
)}
293325
{isSyncing && (
294326
<div className="mb-4">
295327
<div className="flex justify-between text-[10px] font-black text-emerald-400 uppercase tracking-widest mb-2">
@@ -305,14 +337,24 @@ const FlightDataRecorder: React.FC = () => {
305337
</div>
306338
)}
307339

308-
<button
309-
disabled={isSyncing || unsyncedCount === 0}
310-
onClick={handleSync}
311-
className="w-full bg-emerald-600 hover:bg-emerald-700 disabled:bg-slate-700 disabled:opacity-50 disabled:cursor-not-allowed text-white font-black py-5 rounded-2xl flex items-center justify-center gap-3 transition-all shadow-xl shadow-emerald-900/20 uppercase tracking-widest text-lg"
312-
>
313-
{isSyncing ? <Activity className="animate-spin" size={24} /> : <CloudUpload size={24} />}
314-
{isSyncing ? 'Synchronizing...' : 'Upload Data to InfluxDB'}
315-
</button>
340+
<div className="flex gap-3">
341+
<button
342+
disabled={isTesting || isSyncing}
343+
onClick={handleTestConnection}
344+
className="flex items-center justify-center gap-2 px-5 py-5 rounded-2xl font-black text-sm uppercase tracking-widest border-2 border-slate-600 hover:border-blue-500 text-slate-400 hover:text-blue-400 disabled:opacity-50 disabled:cursor-not-allowed transition-all"
345+
>
346+
{isTesting ? <Activity className="animate-spin" size={18} /> : <FlaskConical size={18} />}
347+
Test
348+
</button>
349+
<button
350+
disabled={isSyncing || unsyncedCount === 0}
351+
onClick={handleSync}
352+
className="flex-1 bg-emerald-600 hover:bg-emerald-700 disabled:bg-slate-700 disabled:opacity-50 disabled:cursor-not-allowed text-white font-black py-5 rounded-2xl flex items-center justify-center gap-3 transition-all shadow-xl shadow-emerald-900/20 uppercase tracking-widest text-lg"
353+
>
354+
{isSyncing ? <Activity className="animate-spin" size={24} /> : <CloudUpload size={24} />}
355+
{isSyncing ? 'Synchronizing...' : 'Upload to InfluxDB'}
356+
</button>
357+
</div>
316358
</div>
317359
</div>
318360
</div>

flight-recorder/src/services/SyncService.ts

Lines changed: 60 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
1-
import { loggingService, type CanFrame } from './LoggingService';
1+
import { loggingService } from './LoggingService';
22
import { createCanProcessor } from '../utils/canProcessor';
33

4+
export interface ConnectionTestResult {
5+
ok: boolean;
6+
message: string;
7+
}
8+
49
export class SyncService {
510
private syncing = false;
611
private processor: any = null;
@@ -57,7 +62,13 @@ export class SyncService {
5762
}
5863

5964
if (lineProtocolBatch.length > 0) {
60-
await this.uploadToInflux(url, token, org, bucket, lineProtocolBatch);
65+
await this.uploadToInflux(
66+
url,
67+
token,
68+
org,
69+
bucket,
70+
lineProtocolBatch
71+
);
6172
}
6273

6374
const ids = frames.map(f => f.id!).filter(id => id !== undefined);
@@ -78,6 +89,52 @@ export class SyncService {
7889
}
7990
}
8091

92+
public async testConnection(
93+
url: string,
94+
token: string,
95+
bucket: string
96+
): Promise<ConnectionTestResult> {
97+
try {
98+
const response = await fetch(`${url}/api/v3/query_sql`, {
99+
method: 'POST',
100+
credentials: 'include',
101+
headers: {
102+
'Authorization': `Bearer ${token}`,
103+
'Content-Type': 'application/json',
104+
},
105+
body: JSON.stringify({ db: bucket, q: 'SELECT 1' }),
106+
});
107+
108+
if (response.ok) {
109+
return { ok: true, message: `Connected to ${bucket}` };
110+
} else {
111+
const redirectedToAccess = response.redirected && /cdn-cgi\/access\/login/i.test(response.url);
112+
if (redirectedToAccess) {
113+
return {
114+
ok: false,
115+
message: `Cloudflare Access login required. Open ${url} in this browser and sign in with Google, then test again.`,
116+
};
117+
}
118+
119+
const text = await response.text();
120+
const accessHint = response.status === 401 || response.status === 403
121+
? ' Cloudflare Access session may be missing; sign in to Access in this browser.'
122+
: '';
123+
return { ok: false, message: `${response.status} ${response.statusText}: ${text.slice(0, 120)}${accessHint}` };
124+
}
125+
} catch (err) {
126+
const msg = err instanceof Error ? err.message : String(err);
127+
// "Load failed" / "Failed to fetch" means network-level failure (no server, CORS, etc.)
128+
const isNetworkErr = /load failed|failed to fetch|network/i.test(msg);
129+
return {
130+
ok: false,
131+
message: isNetworkErr
132+
? `Cannot reach ${url} — check URL/network. If this endpoint is behind Cloudflare Access, sign in to Access in this browser first.`
133+
: msg,
134+
};
135+
}
136+
}
137+
81138
private escapeTag(val: string): string {
82139
return val.replace(/ /g, '\\ ').replace(/,/g, '\\,').replace(/=/g, '\\=');
83140
}
@@ -94,6 +151,7 @@ export class SyncService {
94151

95152
const response = await fetch(writeUrl, {
96153
method: 'POST',
154+
credentials: 'include',
97155
headers: {
98156
'Authorization': `Token ${token}`,
99157
'Content-Type': 'text/plain; charset=utf-8',

flight-recorder/src/utils/canProcessor.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ export async function decodeAndIngestUsingDbc(params: {
6161
// Use local.dbc for development, example.dbc for production
6262
let dbcFile = import.meta.env.DEV ? localDbc : exampleDbc;
6363
let usingCache = false;
64+
const dbcDebugSeen = new Set<number>();
6465

6566
// Simple type definitions for our use, align with InfluxDB3 schema for consistency
6667
// InfluxDB3 Schema: id -> canId, name -> messageName, signalName, sensorReading, time
@@ -377,7 +378,10 @@ export function decodeCanMessage(
377378
}
378379

379380
if (canId === 0x18FF50E5 || canId > 0x7FF || (decoded === null && canId !== 1999)) {
380-
console.debug(`[DBC Debug] rawId=${canId} (0x${canId.toString(16)}), dbcId=${dbcId} (0x${dbcId.toString(16)}), decodedName=${decoded?.name ?? 'null'}`);
381+
if (!dbcDebugSeen.has(canId)) {
382+
dbcDebugSeen.add(canId);
383+
console.debug(`[DBC Debug] rawId=${canId} (0x${canId.toString(16)}), dbcId=${dbcId} (0x${dbcId.toString(16)}), decodedName=${decoded?.name ?? 'null'}`);
384+
}
381385
}
382386

383387
const rawDataStr = messageData

flight-recorder/vite.config.ts

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,22 +9,25 @@ export default defineConfig({
99
tailwindcss(),
1010
VitePWA({
1111
registerType: 'autoUpdate',
12-
includeAssets: ['favicon.ico', 'apple-touch-icon.png', 'mask-icon.svg'],
12+
includeAssets: ['pwa-192x192.svg', 'pwa-512x512.svg'],
1313
manifest: {
1414
name: 'WFR Flight Data Recorder',
1515
short_name: 'FDR',
1616
description: 'Standalone High-Fidelity Data Logging for WFR DAQ',
1717
theme_color: '#0f172a',
18+
background_color: '#0f172a',
19+
display: 'standalone',
1820
icons: [
1921
{
20-
src: 'pwa-192x192.png',
22+
src: 'pwa-192x192.svg',
2123
sizes: '192x192',
22-
type: 'image/png'
24+
type: 'image/svg+xml'
2325
},
2426
{
25-
src: 'pwa-512x512.png',
27+
src: 'pwa-512x512.svg',
2628
sizes: '512x512',
27-
type: 'image/png'
29+
type: 'image/svg+xml',
30+
purpose: 'any maskable'
2831
}
2932
]
3033
}

pecan/src/utils/canProcessor.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,7 @@ export async function decodeAndIngestUsingDbc(params: {
105105
// Use local.dbc for development, example.dbc for production
106106
let dbcFile = import.meta.env.DEV ? localDbc : exampleDbc;
107107
let usingCache = false;
108+
const dbcDebugSeen = new Set<number>();
108109

109110
// Simple type definitions for our use, align with InfluxDB3 schema for consistency
110111
// InfluxDB3 Schema: id -> canId, name -> messageName, signalName, sensorReading, time
@@ -421,7 +422,10 @@ export function decodeCanMessage(
421422
}
422423

423424
if (canId === 0x18FF50E5 || canId > 0x7FF || (decoded === null && canId !== 1999)) {
424-
console.debug(`[DBC Debug] rawId=${canId} (0x${canId.toString(16)}), dbcId=${dbcId} (0x${dbcId.toString(16)}), decodedName=${decoded?.name ?? 'null'}`);
425+
if (!dbcDebugSeen.has(canId)) {
426+
dbcDebugSeen.add(canId);
427+
console.debug(`[DBC Debug] rawId=${canId} (0x${canId.toString(16)}), dbcId=${dbcId} (0x${dbcId.toString(16)}), decodedName=${decoded?.name ?? 'null'}`);
428+
}
425429
}
426430

427431
const rawDataStr = messageData

0 commit comments

Comments
 (0)