mirror of
https://github.com/zvx-echo6/meshai.git
synced 2026-06-11 01:14:45 +02:00
feat(dashboard): add TomTom broadcast filter knobs to traffic panel
Add min_magnitude dropdown (1-4), drop_non_present and drop_zero_magnitude toggles to the TomTom Traffic adapter card. State loads from /api/adapter-config/tomtom_incidents on mount and saves changed keys on save, following the same pattern as the WFIGS and fires config panels. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
6149900917
commit
1e6d22ecfe
5 changed files with 621 additions and 547 deletions
|
|
@ -46,6 +46,13 @@ interface FiresConfig {
|
||||||
digest_timezone: string
|
digest_timezone: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TomTom adapter config shape
|
||||||
|
interface TomtomConfig {
|
||||||
|
min_magnitude: number
|
||||||
|
drop_non_present: boolean
|
||||||
|
drop_zero_magnitude: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
type FeedHealth = EnvStatus['feeds'][number]
|
type FeedHealth = EnvStatus['feeds'][number]
|
||||||
|
|
||||||
|
|
@ -228,6 +235,12 @@ export default function Environment() {
|
||||||
digest_timezone: "America/Boise",
|
digest_timezone: "America/Boise",
|
||||||
})
|
})
|
||||||
const [firesOriginal, setFiresOriginal] = useState<string>("")
|
const [firesOriginal, setFiresOriginal] = useState<string>("")
|
||||||
|
const [tomtomConfig, setTomtomConfig] = useState<TomtomConfig>({
|
||||||
|
min_magnitude: 4,
|
||||||
|
drop_non_present: true,
|
||||||
|
drop_zero_magnitude: true,
|
||||||
|
})
|
||||||
|
const [tomtomOriginal, setTomtomOriginal] = useState<string>("")
|
||||||
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
@ -271,6 +284,21 @@ export default function Environment() {
|
||||||
}
|
}
|
||||||
} catch { /* adapter-config optional */ }
|
} catch { /* adapter-config optional */ }
|
||||||
|
|
||||||
|
// Load adapter-config for tomtom_incidents
|
||||||
|
try {
|
||||||
|
const ttRes = await fetch("/api/adapter-config/tomtom_incidents")
|
||||||
|
if (ttRes.ok) {
|
||||||
|
const ttData = await ttRes.json()
|
||||||
|
const cfg: TomtomConfig = {
|
||||||
|
min_magnitude: ttData.min_magnitude?.value ?? 4,
|
||||||
|
drop_non_present: ttData.drop_non_present?.value ?? true,
|
||||||
|
drop_zero_magnitude: ttData.drop_zero_magnitude?.value ?? true,
|
||||||
|
}
|
||||||
|
setTomtomConfig(cfg)
|
||||||
|
setTomtomOriginal(JSON.stringify(cfg))
|
||||||
|
}
|
||||||
|
} catch { /* adapter-config optional */ }
|
||||||
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
setError(e instanceof Error ? e.message : 'Failed to load config')
|
setError(e instanceof Error ? e.message : 'Failed to load config')
|
||||||
} finally {
|
} finally {
|
||||||
|
|
@ -294,7 +322,8 @@ export default function Environment() {
|
||||||
const hasEnvChanges = env !== null && JSON.stringify(env) !== original
|
const hasEnvChanges = env !== null && JSON.stringify(env) !== original
|
||||||
const hasWfigsChanges = JSON.stringify(wfigsConfig) !== wfigsOriginal
|
const hasWfigsChanges = JSON.stringify(wfigsConfig) !== wfigsOriginal
|
||||||
const hasFiresChanges = JSON.stringify(firesConfig) !== firesOriginal
|
const hasFiresChanges = JSON.stringify(firesConfig) !== firesOriginal
|
||||||
const hasChanges = hasEnvChanges || hasWfigsChanges || hasFiresChanges
|
const hasTomtomChanges = JSON.stringify(tomtomConfig) !== tomtomOriginal
|
||||||
|
const hasChanges = hasEnvChanges || hasWfigsChanges || hasFiresChanges || hasTomtomChanges
|
||||||
|
|
||||||
|
|
||||||
const saveAdapterConfig = async (adapterName: string, key: string, value: unknown) => {
|
const saveAdapterConfig = async (adapterName: string, key: string, value: unknown) => {
|
||||||
|
|
@ -362,6 +391,21 @@ const save = async () => {
|
||||||
setFiresOriginal(JSON.stringify(firesConfig))
|
setFiresOriginal(JSON.stringify(firesConfig))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Save tomtom adapter config changes
|
||||||
|
if (hasTomtomChanges) {
|
||||||
|
const orig = JSON.parse(tomtomOriginal) as TomtomConfig
|
||||||
|
if (tomtomConfig.min_magnitude !== orig.min_magnitude) {
|
||||||
|
await saveAdapterConfig("tomtom_incidents", "min_magnitude", tomtomConfig.min_magnitude)
|
||||||
|
}
|
||||||
|
if (tomtomConfig.drop_non_present !== orig.drop_non_present) {
|
||||||
|
await saveAdapterConfig("tomtom_incidents", "drop_non_present", tomtomConfig.drop_non_present)
|
||||||
|
}
|
||||||
|
if (tomtomConfig.drop_zero_magnitude !== orig.drop_zero_magnitude) {
|
||||||
|
await saveAdapterConfig("tomtom_incidents", "drop_zero_magnitude", tomtomConfig.drop_zero_magnitude)
|
||||||
|
}
|
||||||
|
setTomtomOriginal(JSON.stringify(tomtomConfig))
|
||||||
|
}
|
||||||
|
|
||||||
setSuccess('Config saved')
|
setSuccess('Config saved')
|
||||||
setTimeout(() => setSuccess(null), 3000)
|
setTimeout(() => setSuccess(null), 3000)
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
|
@ -375,6 +419,7 @@ const save = async () => {
|
||||||
if (env) setEnv(JSON.parse(original))
|
if (env) setEnv(JSON.parse(original))
|
||||||
setWfigsConfig(JSON.parse(wfigsOriginal || JSON.stringify(wfigsConfig)))
|
setWfigsConfig(JSON.parse(wfigsOriginal || JSON.stringify(wfigsConfig)))
|
||||||
setFiresConfig(JSON.parse(firesOriginal || JSON.stringify(firesConfig)))
|
setFiresConfig(JSON.parse(firesOriginal || JSON.stringify(firesConfig)))
|
||||||
|
setTomtomConfig(JSON.parse(tomtomOriginal || JSON.stringify(tomtomConfig)))
|
||||||
}
|
}
|
||||||
const restart = async () => {
|
const restart = async () => {
|
||||||
try { await fetch('/api/restart', { method: 'POST' }); setRestartRequired(false); setSuccess('Restart initiated') }
|
try { await fetch('/api/restart', { method: 'POST' }); setRestartRequired(false); setSuccess('Restart initiated') }
|
||||||
|
|
@ -486,6 +531,35 @@ const save = async () => {
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
<button onClick={() => up({ traffic: { ...env.traffic, corridors: [...(env.traffic.corridors || []), { name: '', lat: 0, lon: 0 }] } })} className="text-xs text-accent hover:underline">+ Add Corridor</button>
|
<button onClick={() => up({ traffic: { ...env.traffic, corridors: [...(env.traffic.corridors || []), { name: '', lat: 0, lon: 0 }] } })} className="text-xs text-accent hover:underline">+ Add Corridor</button>
|
||||||
|
<div className="border-t border-slate-700/50 pt-4 mt-4">
|
||||||
|
<div className="text-xs font-medium text-slate-400 uppercase tracking-wider mb-3">Broadcast Filters</div>
|
||||||
|
<div className="grid grid-cols-2 gap-4">
|
||||||
|
<div>
|
||||||
|
<label className="text-xs text-slate-400 mb-1 block">Minimum Magnitude</label>
|
||||||
|
<select
|
||||||
|
value={tomtomConfig.min_magnitude}
|
||||||
|
onChange={(e) => setTomtomConfig({...tomtomConfig, min_magnitude: parseInt(e.target.value)})}
|
||||||
|
className="w-full bg-slate-900 border border-slate-700 rounded px-3 py-2 text-sm"
|
||||||
|
>
|
||||||
|
<option value={1}>1 — Minor (all)</option>
|
||||||
|
<option value={2}>2 — Moderate (yellow+)</option>
|
||||||
|
<option value={3}>3 — Major (orange+)</option>
|
||||||
|
<option value={4}>4 — Severe (red only)</option>
|
||||||
|
</select>
|
||||||
|
<p className="text-xs text-slate-500 mt-1">Drop TomTom incidents below this severity level</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="mt-3 space-y-2">
|
||||||
|
<label className="flex items-center justify-between">
|
||||||
|
<span className="text-sm text-slate-300">Drop non-present time validity</span>
|
||||||
|
<input type="checkbox" checked={tomtomConfig.drop_non_present} onChange={(e) => setTomtomConfig({...tomtomConfig, drop_non_present: e.target.checked})} className="w-4 h-4 rounded accent-blue-500" />
|
||||||
|
</label>
|
||||||
|
<label className="flex items-center justify-between">
|
||||||
|
<span className="text-sm text-slate-300">Drop zero-magnitude events</span>
|
||||||
|
<input type="checkbox" checked={tomtomConfig.drop_zero_magnitude} onChange={(e) => setTomtomConfig({...tomtomConfig, drop_zero_magnitude: e.target.checked})} className="w-4 h-4 rounded accent-blue-500" />
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</>)
|
</>)
|
||||||
case 'roads511': return (<>
|
case 'roads511': return (<>
|
||||||
<TextInput label="Base URL" value={env.roads511.base_url} onChange={(v) => up({ roads511: { ...env.roads511, base_url: v } })} placeholder="https://511.yourstate.gov/api/v2" />
|
<TextInput label="Base URL" value={env.roads511.base_url} onChange={(v) => up({ roads511: { ...env.roads511, base_url: v } })} placeholder="https://511.yourstate.gov/api/v2" />
|
||||||
|
|
|
||||||
543
meshai/dashboard/static/assets/index-CAtLxxSd.js
Normal file
543
meshai/dashboard/static/assets/index-CAtLxxSd.js
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
|
@ -8,8 +8,8 @@
|
||||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||||
<link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;600;700&display=swap" rel="stylesheet">
|
<link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;600;700&display=swap" rel="stylesheet">
|
||||||
<script type="module" crossorigin src="/assets/index-D0VjptvK.js"></script>
|
<script type="module" crossorigin src="/assets/index-CAtLxxSd.js"></script>
|
||||||
<link rel="stylesheet" crossorigin href="/assets/index-Be3tdMfU.css">
|
<link rel="stylesheet" crossorigin href="/assets/index-Dp9XCfH-.css">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="root"></div>
|
<div id="root"></div>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue