mirror of
https://github.com/zvx-echo6/meshai.git
synced 2026-08-26 17:31:34 +00:00
feat(gui): fold custom sources into Data Feeds; retire orphan page (#82)
Move the generic-source editor into a reusable GenericSourcesEditor and render it as a "Custom Sources" section on the Data Feeds (Environment) page, so a custom source sits with the built-in feeds instead of an orphan page. Remove the standalone /data-sources route + nav. Custom sources are now managed as first-class data feeds; enable their family in Notifications to route them. Co-authored-by: Matt Johnson <mj@k7zvx.com> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
a60000485b
commit
c229b1e574
4 changed files with 39 additions and 13 deletions
|
|
@ -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 */}
|
||||
<Route path="/places" element={<Places />} />
|
||||
<Route path="/coverage" element={<Coverage />} />
|
||||
<Route path="/data-sources" element={<GenericSources />} />
|
||||
{/* Custom sources folded into Data Feeds; keep old bookmark working */}
|
||||
<Route path="/data-sources" element={<Navigate to="/environment" replace />} />
|
||||
|
||||
{/* De-navved routes still work */}
|
||||
<Route path="/gauge-sites" element={<GaugeSites />} />
|
||||
|
|
|
|||
|
|
@ -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<GenericSource[] | null>(null)
|
||||
const [original, setOriginal] = useState<string>('')
|
||||
const [loading, setLoading] = useState(true)
|
||||
|
|
@ -88,7 +96,6 @@ export default function GenericSources() {
|
|||
const [previewing, setPreviewing] = useState<Record<number, boolean>>({})
|
||||
|
||||
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 (
|
||||
<div className="flex items-center justify-center h-64 text-[#777]">
|
||||
Loading data sources…
|
||||
<div className="flex items-center justify-center h-32 text-[#777]">
|
||||
Loading custom sources…
|
||||
</div>
|
||||
)
|
||||
}
|
||||
if (!sources) {
|
||||
return (
|
||||
<div className="flex items-center justify-center h-64 text-red-400">
|
||||
<div className="flex items-center justify-center h-32 text-red-400">
|
||||
{error || 'No config'}
|
||||
</div>
|
||||
)
|
||||
|
|
@ -248,13 +255,18 @@ export default function GenericSources() {
|
|||
|
||||
return (
|
||||
<div className="space-y-6 max-w-4xl">
|
||||
{/* Page description + action bar */}
|
||||
{/* Section description + action bar */}
|
||||
<div className="flex items-start justify-between gap-4">
|
||||
<p className="text-sm text-[#777]">
|
||||
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 <span className="text-accent">Preview</span> 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 —{' '}
|
||||
<a href="/notifications" className="text-accent hover:underline">
|
||||
enable its family in Notifications
|
||||
</a>{' '}
|
||||
to route it to a channel.
|
||||
</p>
|
||||
{hasChanges && actionButtons}
|
||||
</div>
|
||||
|
|
@ -264,7 +276,7 @@ export default function GenericSources() {
|
|||
|
||||
{sources.length === 0 && (
|
||||
<div className="border border-border p-6 text-center text-sm text-[#666]">
|
||||
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.
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
|
@ -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 },
|
||||
|
|
|
|||
|
|
@ -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 ───── */}
|
||||
<div className="pt-4 mt-2 border-t border-border space-y-4">
|
||||
<div>
|
||||
<h2 className="text-base font-semibold text-white flex items-center gap-2">
|
||||
<Sliders size={16} /> Custom Sources
|
||||
</h2>
|
||||
<p className="text-xs text-[#666] mt-1">
|
||||
Point MeshAI at any public REST/GeoJSON feed; it becomes a routable family in
|
||||
Notifications — sitting alongside the built-in feeds above.
|
||||
</p>
|
||||
</div>
|
||||
<GenericSourcesEditor />
|
||||
</div>
|
||||
|
||||
{/* ── Advanced: Geocoder — infra, not a feed ──────────────────────────── */}
|
||||
<details className="group border border-border p-4">
|
||||
<summary className="flex items-center gap-2 cursor-pointer text-sm font-medium text-[#e0e0e0] hover:text-white">
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue