mirror of
https://github.com/zvx-echo6/meshai.git
synced 2026-08-26 17:31:34 +00:00
fix(gui): make 'API key required' banner reflect live /api/secrets status
The banner used a static META.hasKey flag (false for firms+roads511), so those always showed 'API key not configured' even when keys were set — contradicting the ManagedSecret SET badge. Now fetch /api/secrets and show the banner only when a keyed adapter's secret is genuinely unset; copy softened to 'API key required — set it in the field below'. wzdx kept keyless (no banner). Keyless adapters never banner. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
adfe9ff4f2
commit
3f70f2604a
1 changed files with 44 additions and 2 deletions
|
|
@ -243,7 +243,7 @@ function AdapterPanel({ title, subtitle, enabled, onEnabled, feedSource, onFeedS
|
|||
</div>
|
||||
{!hasKey && (
|
||||
<div className="text-xs text-accent bg-accent/10 p-2">
|
||||
API key not configured — contact admin
|
||||
API key required — set it in the field below
|
||||
</div>
|
||||
)}
|
||||
{nativeOnly && (
|
||||
|
|
@ -287,6 +287,17 @@ const META: Record<AdapterKey, AdapterMeta> = {
|
|||
satpass: { label: 'Satellite Passes', subtitle: 'Observer pass alerts via Central', health: 'satpass', hasCentral: true, nativeOnly: false, hasKey: true },
|
||||
}
|
||||
|
||||
// Keyed adapters → their secret env var (matches secrets_store.SECRET_LABELS).
|
||||
// Adapters NOT listed here are keyless and never show the "API key required"
|
||||
// banner. The banner for a keyed adapter is driven by the LIVE secret status
|
||||
// from GET /api/secrets, not the static META.hasKey hint below.
|
||||
const ADAPTER_SECRET_ENV: Partial<Record<AdapterKey, string>> = {
|
||||
firms: 'FIRMS_MAP_KEY',
|
||||
roads511: 'ROADS511_API_KEY',
|
||||
traffic: 'TOMTOM_API_KEY',
|
||||
// wzdx is keyless (FHWA registry) — no banner
|
||||
}
|
||||
|
||||
const FAMILIES: { key: string; label: string; icon: typeof Cloud; adapters: AdapterKey[] }[] = [
|
||||
{ key: 'central', label: 'Central', icon: Server, adapters: [] },
|
||||
{ key: 'weather', label: 'Weather', icon: Cloud, adapters: ['nws'] },
|
||||
|
|
@ -319,6 +330,11 @@ export default function Environment() {
|
|||
// include_in_llm_context per backend adapter name — fetched from /api/adapter-meta
|
||||
const [llmMeta, setLlmMeta] = useState<Record<string, boolean>>({})
|
||||
|
||||
// Live secret set/unset status per env var — fetched from /api/secrets (same
|
||||
// source the ManagedSecret widget uses). Drives the truthful "API key required"
|
||||
// banner so it only shows when a keyed adapter's secret is genuinely unset.
|
||||
const [secretStatus, setSecretStatus] = useState<Record<string, boolean>>({})
|
||||
|
||||
// WFIGS/fires adapter config state
|
||||
const [wfigsConfig, setWfigsConfig] = useState<WfigsConfig>({
|
||||
allowed_incident_types: ['WF'],
|
||||
|
|
@ -592,6 +608,21 @@ export default function Environment() {
|
|||
})()
|
||||
}, [])
|
||||
|
||||
// Fetch live secret status once on mount (best-effort) for the key banner.
|
||||
useEffect(() => {
|
||||
;(async () => {
|
||||
try {
|
||||
const res = await fetch('/api/secrets')
|
||||
if (res.ok) {
|
||||
const data = await res.json() as { env_var: string; is_set: boolean }[]
|
||||
const map: Record<string, boolean> = {}
|
||||
for (const s of data) map[s.env_var] = s.is_set
|
||||
setSecretStatus(map)
|
||||
}
|
||||
} catch { /* best-effort */ }
|
||||
})()
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
const load = async () => {
|
||||
try {
|
||||
|
|
@ -917,6 +948,17 @@ const save = async () => {
|
|||
const eventsFor = (key: AdapterKey): EnvEvent[] =>
|
||||
events.filter((e) => e.source === META[key].health)
|
||||
|
||||
// Truthful key-banner logic: keyless adapters never show the banner; keyed
|
||||
// adapters show it ONLY when their real secret (GET /api/secrets) is unset.
|
||||
// While secret status is still loading (undefined), default to true so we
|
||||
// never flash a false "API key required" banner for an already-set key.
|
||||
const effectiveHasKey = (key: AdapterKey): boolean => {
|
||||
const envVar = ADAPTER_SECRET_ENV[key]
|
||||
if (!envVar) return true // keyless adapter — no key needed
|
||||
const isSet = secretStatus[envVar]
|
||||
return isSet === undefined ? true : isSet
|
||||
}
|
||||
|
||||
const fam = FAMILIES.find((f) => f.key === family)!
|
||||
const activeAdapter: AdapterKey | null =
|
||||
fam.adapters.length === 0 ? null : (adapter && fam.adapters.includes(adapter) ? adapter : fam.adapters[0])
|
||||
|
|
@ -1735,7 +1777,7 @@ const save = async () => {
|
|||
onFeedSource={(v) => setAdapterField(activeAdapter, { feed_source: v })}
|
||||
hasCentral={META[activeAdapter].hasCentral}
|
||||
nativeOnly={META[activeAdapter].nativeOnly}
|
||||
hasKey={META[activeAdapter].hasKey}
|
||||
hasKey={effectiveHasKey(activeAdapter)}
|
||||
health={healthFor(activeAdapter)}
|
||||
events={eventsFor(activeAdapter)}
|
||||
llmContext={PANEL_META_KEY[activeAdapter] !== undefined ? (llmMeta[PANEL_META_KEY[activeAdapter]!] ?? true) : undefined}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue