feat: complete Phase 2 transport layer — off-grid mode, transport icons, federation sync

- Add off-grid (mesh only) toggle to Mesh.vue with orange OFF-GRID banner
- Add per-peer transport indicator in Federation.vue (mesh/lan/tor icons)
- Add sync_with_peer_via_transport() for CBOR delta sync via transport router
- Fetch transport store on mount in both Mesh and Federation views

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Dorian
2026-03-17 00:45:37 +00:00
parent f42ff45475
commit 7867ac1931
3 changed files with 114 additions and 1 deletions

View File

@@ -1,10 +1,12 @@
<script setup lang="ts">
import { ref, computed, nextTick, onMounted, onUnmounted } from 'vue'
import { useMeshStore } from '@/stores/mesh'
import { useTransportStore } from '@/stores/transport'
import type { MeshPeer } from '@/stores/mesh'
import AnimatedLogo from '@/components/AnimatedLogo.vue'
const mesh = useMeshStore()
const transport = useTransportStore()
// Active chat: either a peer or a channel
const activeChatPeer = ref<MeshPeer | null>(null)
@@ -19,8 +21,17 @@ let pollInterval: ReturnType<typeof setInterval> | null = null
// The Public channel (always available on Meshcore)
const publicChannel = { index: 0, name: 'Public' }
const togglingOffGrid = ref(false)
async function handleToggleOffGrid() {
togglingOffGrid.value = true
try {
await transport.setMeshOnly(!transport.meshOnly)
} finally { togglingOffGrid.value = false }
}
onMounted(async () => {
await mesh.refreshAll()
await Promise.all([mesh.refreshAll(), transport.fetchStatus()])
pollInterval = setInterval(() => {
mesh.fetchStatus()
mesh.fetchPeers()
@@ -229,6 +240,15 @@ function truncatePubkey(hex: string | null): string {
</div>
</div>
<!-- Off-grid mode banner -->
<div v-if="transport.meshOnly" class="mesh-offgrid-banner">
<svg class="w-4 h-4 text-orange-400 shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M18.364 5.636a9 9 0 11-12.728 0M12 9v4m0 4h.01" />
</svg>
<span class="text-sm font-medium text-orange-300">OFF-GRID</span>
<span class="text-xs text-white/50">Tor disabled mesh only</span>
</div>
<!-- Actions row -->
<div class="mesh-actions">
<button class="glass-button mesh-action-btn" :disabled="configuring" @click="handleToggleEnabled">
@@ -237,6 +257,14 @@ function truncatePubkey(hex: string | null): string {
<button class="glass-button mesh-action-btn" :disabled="!mesh.status?.device_connected || broadcasting" @click="handleBroadcast">
{{ broadcasting ? 'Sending...' : 'Broadcast' }}
</button>
<button
class="glass-button mesh-action-btn"
:class="transport.meshOnly ? 'mesh-offgrid-active' : ''"
:disabled="togglingOffGrid"
@click="handleToggleOffGrid"
>
{{ transport.meshOnly ? 'Go Online' : 'Off-Grid' }}
</button>
<button class="glass-button mesh-action-btn" @click="mesh.refreshAll()">Refresh</button>
</div>
@@ -570,6 +598,22 @@ function truncatePubkey(hex: string | null): string {
color: rgba(255, 255, 255, 0.7);
}
/* ─── Off-grid banner ─── */
.mesh-offgrid-banner {
display: flex;
align-items: center;
gap: 8px;
padding: 8px 12px;
background: rgba(251, 146, 60, 0.1);
border: 1px solid rgba(251, 146, 60, 0.3);
border-radius: 8px;
flex-shrink: 0;
}
.mesh-offgrid-active {
border-color: rgba(251, 146, 60, 0.4) !important;
color: #fb923c !important;
}
/* ─── Actions ─── */
.mesh-actions {
display: flex;