mirror of
https://github.com/zvx-echo6/meshai.git
synced 2026-08-26 17:31:34 +00:00
fix(channels): dedupe Meshtastic channel list, un-bury MeshCore channels card (#105)
The Meshtastic connection page listed channels twice (the pre-existing Observe-Channels selector plus a redundant read-only Index/Name/Role table); remove the redundant table and its now-unused getChannels() / MeshtasticChannel API helpers. On the MeshCore companion page, move the Channels (name/hash/key) card up to sit right after the Status card, above the Advertising settings, so it is no longer buried at the bottom. No behavior change; layout/UX only. 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
6e1831b606
commit
fed63b2344
3 changed files with 41 additions and 92 deletions
|
|
@ -606,18 +606,6 @@ export async function getMeshcoreChannelsDetail(): Promise<MeshcoreChannelsDetai
|
|||
return fetchJson<MeshcoreChannelsDetail>('/api/meshcore/channels/detail')
|
||||
}
|
||||
|
||||
// Meshtastic radio channels (routes by channel index).
|
||||
export interface MeshtasticChannel {
|
||||
index: number
|
||||
name: string
|
||||
role: string
|
||||
enabled: boolean
|
||||
}
|
||||
|
||||
export async function getChannels(): Promise<MeshtasticChannel[]> {
|
||||
return fetchJson<MeshtasticChannel[]>('/api/channels')
|
||||
}
|
||||
|
||||
export interface MeshcoreContact {
|
||||
name: string | null
|
||||
pubkey: string
|
||||
|
|
|
|||
|
|
@ -240,45 +240,6 @@ export default function MeshCoreCompanion() {
|
|||
)}
|
||||
</div>
|
||||
|
||||
{/* Advertising settings */}
|
||||
<div className="bg-bg-card border border-border">
|
||||
<div className="px-4 py-3 border-b border-border">
|
||||
<h3 className="text-sm font-medium text-slate-200">Advertising</h3>
|
||||
</div>
|
||||
<div className="px-4 py-4 space-y-4">
|
||||
<div className="space-y-1">
|
||||
<label className="text-xs font-medium text-[#777] uppercase tracking-wide">
|
||||
Auto-advert interval
|
||||
</label>
|
||||
<div className="flex items-center gap-3">
|
||||
<select
|
||||
value={advertIntervalHours}
|
||||
onChange={(e) => setAdvertIntervalHours(Number(e.target.value))}
|
||||
className="bg-[#0a0e17] border border-[#1e2a3a] text-slate-200 text-sm rounded px-2 py-1.5 focus:outline-none focus:border-accent"
|
||||
>
|
||||
<option value={0}>Disabled</option>
|
||||
<option value={1}>Every 1 hour</option>
|
||||
<option value={3}>Every 3 hours (default)</option>
|
||||
<option value={6}>Every 6 hours</option>
|
||||
<option value={12}>Every 12 hours</option>
|
||||
<option value={24}>Every 24 hours</option>
|
||||
</select>
|
||||
<button
|
||||
onClick={handleSaveAdvertInterval}
|
||||
disabled={advertIntervalSaving}
|
||||
className="px-3 py-1.5 text-sm bg-accent/10 hover:bg-accent/20 text-accent border border-accent/30 rounded disabled:opacity-50 transition-colors"
|
||||
>
|
||||
{advertIntervalSaving ? 'Saving…' : advertIntervalSaved ? 'Saved' : 'Save'}
|
||||
</button>
|
||||
</div>
|
||||
<p className="text-xs text-[#555]">
|
||||
AIDA sends a flood advertisement at this interval so it stays discoverable.
|
||||
Stored in <code className="text-accent/80">connection.meshcore_advert_interval_seconds</code>.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Channel list — Name · Hash · Key (read-only) */}
|
||||
<div className="bg-bg-card border border-border">
|
||||
<div className="px-4 py-3 border-b border-border">
|
||||
|
|
@ -329,6 +290,46 @@ export default function MeshCoreCompanion() {
|
|||
<div className="px-4 py-3 text-sm text-[#777]">No channels</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Advertising settings */}
|
||||
<div className="bg-bg-card border border-border">
|
||||
<div className="px-4 py-3 border-b border-border">
|
||||
<h3 className="text-sm font-medium text-slate-200">Advertising</h3>
|
||||
</div>
|
||||
<div className="px-4 py-4 space-y-4">
|
||||
<div className="space-y-1">
|
||||
<label className="text-xs font-medium text-[#777] uppercase tracking-wide">
|
||||
Auto-advert interval
|
||||
</label>
|
||||
<div className="flex items-center gap-3">
|
||||
<select
|
||||
value={advertIntervalHours}
|
||||
onChange={(e) => setAdvertIntervalHours(Number(e.target.value))}
|
||||
className="bg-[#0a0e17] border border-[#1e2a3a] text-slate-200 text-sm rounded px-2 py-1.5 focus:outline-none focus:border-accent"
|
||||
>
|
||||
<option value={0}>Disabled</option>
|
||||
<option value={1}>Every 1 hour</option>
|
||||
<option value={3}>Every 3 hours (default)</option>
|
||||
<option value={6}>Every 6 hours</option>
|
||||
<option value={12}>Every 12 hours</option>
|
||||
<option value={24}>Every 24 hours</option>
|
||||
</select>
|
||||
<button
|
||||
onClick={handleSaveAdvertInterval}
|
||||
disabled={advertIntervalSaving}
|
||||
className="px-3 py-1.5 text-sm bg-accent/10 hover:bg-accent/20 text-accent border border-accent/30 rounded disabled:opacity-50 transition-colors"
|
||||
>
|
||||
{advertIntervalSaving ? 'Saving…' : advertIntervalSaved ? 'Saved' : 'Save'}
|
||||
</button>
|
||||
</div>
|
||||
<p className="text-xs text-[#555]">
|
||||
AIDA sends a flood advertisement at this interval so it stays discoverable.
|
||||
Stored in <code className="text-accent/80">connection.meshcore_advert_interval_seconds</code>.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import { ConnectionSection, TextInput, NumberInput, Toggle, type ConnectionConfi
|
|||
import ChannelPicker from '@/components/ChannelPicker'
|
||||
import NodePicker from '@/components/NodePicker'
|
||||
import { notifyRestartRequired } from '@/components/RestartBanner'
|
||||
import { fetchConfig as apiFetchConfig, updateConfig as apiUpdateConfig, sendTestMessage, getChannels, type MeshtasticChannel } from '@/lib/api'
|
||||
import { fetchConfig as apiFetchConfig, updateConfig as apiUpdateConfig, sendTestMessage } from '@/lib/api'
|
||||
import { useDirty } from '@/context/DirtyContext'
|
||||
|
||||
// Only the fields the "Bot behavior" section edits are typed explicitly; the
|
||||
|
|
@ -37,9 +37,6 @@ export default function MeshtasticConnection() {
|
|||
const [success, setSuccess] = useState<string | null>(null)
|
||||
const [hasChanges, setHasChanges] = useState(false)
|
||||
|
||||
// Read-only channel listing (index ↔ name ↔ role), fetched from the radio.
|
||||
const [channels, setChannels] = useState<MeshtasticChannel[]>([])
|
||||
|
||||
// Test send state
|
||||
const [testChannel, setTestChannel] = useState(0)
|
||||
const [testText, setTestText] = useState('')
|
||||
|
|
@ -89,9 +86,6 @@ export default function MeshtasticConnection() {
|
|||
useEffect(() => {
|
||||
document.title = 'Meshtastic Connection - MeshAI'
|
||||
fetchConfig()
|
||||
getChannels()
|
||||
.then(setChannels)
|
||||
.catch(() => setChannels([]))
|
||||
}, [fetchConfig])
|
||||
|
||||
useEffect(() => {
|
||||
|
|
@ -296,40 +290,6 @@ export default function MeshtasticConnection() {
|
|||
</div>
|
||||
)}
|
||||
|
||||
{/* Channels card — read-only Index · Name · Role */}
|
||||
<div className="bg-bg-card border border-border">
|
||||
<div className="px-4 py-3 border-b border-border">
|
||||
<h3 className="text-sm font-medium text-slate-200">Channels</h3>
|
||||
<p className="text-xs text-[#555] mt-1">
|
||||
Channels configured on the radio. Meshtastic routes by channel index. Read-only.
|
||||
</p>
|
||||
</div>
|
||||
{channels.length > 0 ? (
|
||||
<div className="overflow-x-auto">
|
||||
<table className="w-full text-sm text-slate-200">
|
||||
<thead className="bg-[#161616] border-b border-border">
|
||||
<tr>
|
||||
<th className="px-3 py-2 font-sans text-[9px] uppercase tracking-widest text-[#666] text-left">Index</th>
|
||||
<th className="px-3 py-2 font-sans text-[9px] uppercase tracking-widest text-[#666] text-left">Name</th>
|
||||
<th className="px-3 py-2 font-sans text-[9px] uppercase tracking-widest text-[#666] text-left">Role</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="divide-y divide-border">
|
||||
{channels.map((ch) => (
|
||||
<tr key={ch.index} className="hover:bg-bg-hover">
|
||||
<td className="px-3 py-2 font-mono text-xs">{ch.index}</td>
|
||||
<td className="px-3 py-2">{ch.name}</td>
|
||||
<td className="px-3 py-2 text-xs text-[#999]">{ch.role}</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
) : (
|
||||
<div className="px-4 py-3 text-sm text-[#777]">Node offline — channels unavailable</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Send test message card */}
|
||||
<div className="bg-bg-card border border-border p-6 space-y-4">
|
||||
<div className="text-xs text-slate-500 uppercase tracking-wide">Send Test Message</div>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue