diff --git a/work/dashboard-frontend/src/App.tsx b/work/dashboard-frontend/src/App.tsx index 6de0848..c749f65 100644 --- a/work/dashboard-frontend/src/App.tsx +++ b/work/dashboard-frontend/src/App.tsx @@ -1,4 +1,4 @@ -import { Routes, Route } from 'react-router-dom' +import { Routes, Route, Navigate } from 'react-router-dom' import Layout from './components/Layout' import Dashboard from './pages/Dashboard' import Mesh from './pages/Mesh' @@ -22,7 +22,6 @@ import ScheduledBroadcasts from './pages/ScheduledBroadcasts' import MeshtasticDangerZones from './pages/MeshtasticDangerZones' import MeshCoreDangerZones from './pages/MeshCoreDangerZones' import Coverage from './pages/Coverage' -import GenericSources from './pages/GenericSources' import { ToastProvider } from './components/ToastProvider' import { DirtyProvider } from './context/DirtyContext' @@ -45,7 +44,8 @@ function App() { {/* New aggregated pages */} } /> } /> - } /> + {/* Custom sources folded into Data Feeds; keep old bookmark working */} + } /> {/* De-navved routes still work */} } /> diff --git a/work/dashboard-frontend/src/pages/GenericSources.tsx b/work/dashboard-frontend/src/components/GenericSourcesEditor.tsx similarity index 95% rename from work/dashboard-frontend/src/pages/GenericSources.tsx rename to work/dashboard-frontend/src/components/GenericSourcesEditor.tsx index 1a4298d..a59a0a4 100644 --- a/work/dashboard-frontend/src/pages/GenericSources.tsx +++ b/work/dashboard-frontend/src/components/GenericSourcesEditor.tsx @@ -74,7 +74,15 @@ function SectionLabel({ children }: { children: React.ReactNode }) { ) } -export default function GenericSources() { +/** + * Self-contained editor for generic (no-code) custom data sources. + * + * Owns its own data lifecycle: fetches generic_sources on mount, tracks a + * dirty flag, and saves via saveGenericSources (raising a restart banner when + * the backend reports one). Rendered as the "Custom Sources" section of the + * Data Feeds page — a custom source is just another data feed. + */ +export default function GenericSourcesEditor() { const [sources, setSources] = useState(null) const [original, setOriginal] = useState('') const [loading, setLoading] = useState(true) @@ -88,7 +96,6 @@ export default function GenericSources() { const [previewing, setPreviewing] = useState>({}) useEffect(() => { - document.title = 'Data Sources — MeshAI' fetchGenericSources() .then((data) => { const list = Array.isArray(data) ? data : [] @@ -201,7 +208,7 @@ export default function GenericSources() { try { const result = await saveGenericSources(sources) setOriginal(JSON.stringify(sources)) - setSuccess('Data sources saved') + setSuccess('Custom sources saved') setTimeout(() => setSuccess(null), 3000) if (result.restart_required) { notifyRestartRequired(Array.isArray(result.changed_keys) ? result.changed_keys : []) @@ -215,14 +222,14 @@ export default function GenericSources() { if (loading) { return ( -
- Loading data sources… +
+ Loading custom sources…
) } if (!sources) { return ( -
+
{error || 'No config'}
) @@ -248,13 +255,18 @@ export default function GenericSources() { return (
- {/* Page description + action bar */} + {/* Section description + action bar */}

Point MeshAI at any public REST or GeoJSON feed — no code. Each source polls a URL, maps its JSON fields to an event via dotted paths, and broadcasts through the normal coverage-gated pipeline. Use Preview to fetch a - URL and read its structure before filling in the paths. + URL and read its structure before filling in the paths. Once saved, a custom source + becomes a routable family —{' '} + + enable its family in Notifications + {' '} + to route it to a channel.

{hasChanges && actionButtons}
@@ -264,7 +276,7 @@ export default function GenericSources() { {sources.length === 0 && (
- No data sources yet. Click “Add source” to wire up a public feed. + No custom sources yet. Click “Add source” to wire up a public feed.
)} diff --git a/work/dashboard-frontend/src/components/Layout.tsx b/work/dashboard-frontend/src/components/Layout.tsx index 40740ff..cdd9885 100644 --- a/work/dashboard-frontend/src/components/Layout.tsx +++ b/work/dashboard-frontend/src/components/Layout.tsx @@ -50,7 +50,6 @@ const navGroups: NavGroup[] = [ { path: '/', label: 'Dashboard', icon: LayoutDashboard }, { path: '/config', label: 'Settings', icon: Settings }, { path: '/environment', label: 'Data Feeds', icon: Cloud }, - { path: '/data-sources', label: 'Data Sources', icon: Layers }, { path: '/activity', label: 'Activity Log', icon: Activity }, { path: '/places', label: 'Places', icon: MapPin }, { path: '/coverage', label: 'Coverage', icon: Map }, diff --git a/work/dashboard-frontend/src/pages/Environment.tsx b/work/dashboard-frontend/src/pages/Environment.tsx index 689b482..4c69a7d 100644 --- a/work/dashboard-frontend/src/pages/Environment.tsx +++ b/work/dashboard-frontend/src/pages/Environment.tsx @@ -15,6 +15,7 @@ import { import { TOGGLE_FAMILY_META, type NotificationToggle, type NotificationsConfig } from './Notifications' import AdapterConfig, { CURATED_KEYS } from './AdapterConfig' import { ManagedSecret } from '@/components/ManagedSecret' +import GenericSourcesEditor from '@/components/GenericSourcesEditor' type FeedSource = 'native' | 'central' @@ -1893,6 +1894,20 @@ const save = async () => { )} + {/* ── Custom Sources — generic no-code feeds, first-class data feeds ───── */} +
+
+

+ Custom Sources +

+

+ Point MeshAI at any public REST/GeoJSON feed; it becomes a routable family in + Notifications — sitting alongside the built-in feeds above. +

+
+ +
+ {/* ── Advanced: Geocoder — infra, not a feed ──────────────────────────── */}