diff --git a/neode-ui/mock-backend.js b/neode-ui/mock-backend.js index 75eaf731..eeb59e21 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 || [] @@ -3574,13 +3591,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 7d4c7887..c0306844 100644 --- a/neode-ui/src/views/Dashboard.vue +++ b/neode-ui/src/views/Dashboard.vue @@ -52,9 +52,11 @@ aria-hidden="true" /> - +
@@ -194,6 +196,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

+
-
- + +
+ -
diff --git a/neode-ui/src/views/web5/Web5NetworkingProfitsSettings.vue b/neode-ui/src/views/web5/Web5NetworkingProfitsSettings.vue index e2dff309..6976cf37 100644 --- a/neode-ui/src/views/web5/Web5NetworkingProfitsSettings.vue +++ b/neode-ui/src/views/web5/Web5NetworkingProfitsSettings.vue @@ -1,9 +1,11 @@