diff --git a/neode-ui/mock-backend.js b/neode-ui/mock-backend.js index 36b476c0..9cb03527 100755 --- a/neode-ui/mock-backend.js +++ b/neode-ui/mock-backend.js @@ -2382,6 +2382,23 @@ app.post('/rpc/v1', (req, res) => { case 'streaming.list-services': { return res.json({ result: { services: mockState.streamingServices || [] } }) } + case 'streaming.list-sessions': { + const mkTime = (minsAgo) => new Date(Date.now() - minsAgo * 60_000).toISOString() + return res.json({ result: { + sessions: [ + { id: 'sess-demo-1', peer_id: 'npub1walker…k3u9', service_id: 'content-download', metric: 'bytes', allotment: 524_288_000, used: 231_211_008, paid_sats: 500, created_at: mkTime(134), last_topup_at: mkTime(22), expires_at: '', active: true }, + { id: 'sess-demo-2', peer_id: 'npub1sailor…m2xq', service_id: 'nostr-relay', metric: 'milliseconds', allotment: 10_800_000, used: 4_920_000, paid_sats: 30, created_at: mkTime(82), last_topup_at: mkTime(82), expires_at: mkTime(-98), active: true }, + { id: 'sess-demo-3', peer_id: 'npub1nomad…t7rd', service_id: 'content-download', metric: 'bytes', allotment: 104_857_600, used: 88_080_384, paid_sats: 100, created_at: mkTime(9), last_topup_at: mkTime(9), expires_at: '', active: true }, + ], + total_active: 3, + total_revenue_sats: 770_000, + revenue_by_service: { + 'content-download': 512_400, + 'nostr-relay': 201_600, + 'api-access': 56_000, + }, + } }) + } case 'streaming.configure-service': { const p = params || {} const list = mockState.streamingServices || [] @@ -3566,13 +3583,32 @@ app.post('/rpc/v1', (req, res) => { } case 'wallet.networking-profits': { + // Deterministic-but-varied week of profit events for the dashboard + // chart (source/timestamp/sats mirror wallet::profits::ProfitEntry). + const profitSources = ['streaming_revenue', 'content_sale', 'routing_fee'] + const profitNotes = ['Paid streaming session', 'Ecash content sale', 'Lightning routing fees'] + const recent = [] + const nowMs = Date.now() + for (let i = 0; i < 42; i++) { + const daysAgo = (i * 3) % 7 + const hour = (i * 5) % 24 + recent.push({ + source: profitSources[i % 3], + amount_sats: 800 + ((i * 7919) % 14000), + timestamp: new Date(nowMs - daysAgo * 86_400_000 - hour * 3_600_000).toISOString(), + description: profitNotes[i % 3], + }) + } + recent.sort((a, b) => b.timestamp.localeCompare(a.timestamp)) return res.json({ result: { total_sats: 5_231_978, content_sales_sats: 3_180_000, routing_fees_sats: 1_281_978, - relay_sats: 770_000, + streaming_revenue_sats: 770_000, + recent, // legacy aliases kept for older UI builds + relay_sats: 770_000, total_earned_sats: 5_231_978, total_forwarded_sats: 1_281_978, forward_count: 1284, diff --git a/neode-ui/src/components/mesh/MeshDeviceSetupModal.vue b/neode-ui/src/components/mesh/MeshDeviceSetupModal.vue index f21790f7..fdfbabfd 100644 --- a/neode-ui/src/components/mesh/MeshDeviceSetupModal.vue +++ b/neode-ui/src/components/mesh/MeshDeviceSetupModal.vue @@ -216,7 +216,8 @@ async function connect() { device_kind: form.value.deviceKind, }) mesh.dismissDetectedDevice(path) - void router.push('/mesh') + // The Mesh view lives under the dashboard shell — a bare /mesh 404s. + void router.push('/dashboard/mesh') } catch (e) { error.value = e instanceof Error ? e.message : 'Failed to configure the mesh radio' } finally { diff --git a/neode-ui/src/style.css b/neode-ui/src/style.css index 493c6338..839f5e2f 100644 --- a/neode-ui/src/style.css +++ b/neode-ui/src/style.css @@ -79,7 +79,9 @@ select:focus-visible { border-color: rgba(251, 146, 60, 0.4); } -/* Card action placement: keep compact header buttons for genuinely wide layouts. */ +/* Card action placement: actions always live at the bottom of the card as + full-width buttons — same layout on every screen size. The compact header + variants are permanently retired for consistency (desktop == mobile). */ .responsive-card-actions-top, .web5-card-actions-top { display: none; @@ -109,20 +111,6 @@ select:focus-visible { white-space: nowrap; } -@media (min-width: 1800px) { - .responsive-card-actions-top, - .web5-card-actions-top { - display: flex; - } - - .responsive-card-actions-bottom, - .responsive-card-actions-bottom-grid, - .web5-card-actions-bottom, - .web5-card-actions-bottom-grid { - display: none; - } -} - /* Mobile touch targets — ensure tappable elements meet 44px minimum */ @media (max-width: 767px) { button:not(.mode-switcher-btn):not(.sidebar-nav-item):not([class*="w-9"]):not([class*="w-8"]):not([class*="w-7"]):not([class*="w-10"]):not([class*="w-11"]):not([class*="w-12"]) { diff --git a/neode-ui/src/views/Dashboard.vue b/neode-ui/src/views/Dashboard.vue index 77ba6832..1a02d722 100644 --- a/neode-ui/src/views/Dashboard.vue +++ b/neode-ui/src/views/Dashboard.vue @@ -52,9 +52,11 @@ aria-hidden="true" /> - +
@@ -190,6 +192,10 @@ const backgroundImage = computed(() => { return 'bg-home.webp' }) +// bg-web5.jpg (web5 + server sections) is bright — the scrim overlay deepens +// while it's showing so light text keeps its contrast. +const isWeb5Bg = computed(() => backgroundImage.value === 'bg-web5.jpg') + const isDarkRoute = computed(() => { const p = route.path return p.includes('/dashboard/web5') || diff --git a/neode-ui/src/views/server/TorServicesCard.vue b/neode-ui/src/views/server/TorServicesCard.vue index b2c58f48..05350ab9 100644 --- a/neode-ui/src/views/server/TorServicesCard.vue +++ b/neode-ui/src/views/server/TorServicesCard.vue @@ -30,40 +30,47 @@ Refreshing Tor services... -{{ svc.name }}
- :{{ svc.local_port }} - protocol - auth - open +{{ svc.name }}
+ :{{ svc.local_port }} + protocol + auth + open +{{ svc.onion_address }}
+Waiting for .onion address...
+Disabled
{{ svc.onion_address }}
-Waiting for .onion address...
-Disabled
+- Control what your node charges other peers for. By default everything is shared for - free — turn a service on to start earning sats (ecash) for it. Payments are collected - as Cashu tokens through your node's wallet. -
+- Everything is free. Your node isn't charging for anything — enable a service below to - start earning. -
-{{ svc.description }}
+ +Total earned
+{{ formatSats(profits?.total_sats) }}
+sats, all time
Streaming
+{{ formatSats(profits?.streaming_revenue_sats) }}
+sats from paid services
+Content sales
+{{ formatSats(profits?.content_sales_sats) }}
+sats from ecash sales
+Routing fees
+{{ formatSats(profits?.routing_fees_sats) }}
+sats from Lightning
{{ minimumNote(svc) }}
Active sessions
+{{ sessionsSummary?.total_active ?? 0 }}
+peers currently paying for services
++ Session revenue: + {{ formatSats(sessionsSummary?.total_revenue_sats) }} sats +
++ Control what your node charges other peers for. By default everything is shared for + free — turn a service on to start earning sats (ecash) for it. Payments are collected + as Cashu tokens through your node's wallet. +
++ Everything is free. Your node isn't charging for anything — enable a service below to + start earning. +
+{{ svc.description }}
+{{ minimumNote(svc) }}
++ Sell WiFi access on your OpenWrt gateway — visitors pay per {{ tollgateUnit }} in ecash. +
++ TollGate isn't set up yet — it needs an OpenWrt gateway paired with this node. +
+ +