From 9911a035759c26a740dbc348e0fb0c0b68993bef Mon Sep 17 00:00:00 2001 From: Ben Weeks Date: Mon, 22 Dec 2025 10:15:11 +0000 Subject: [PATCH 1/4] feat: add nostrclient status indicator and connection button MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add /api/v1/status endpoint to check nostrclient availability - Add color-coded "Connect" button (red/orange/green) based on status - Show dropdown with connection details (relays count, websocket status) - Add warning banner when nostrclient extension is not available - Update info card with description of extension functionality Closes #132 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- static/js/index.js | 57 ++++++++++++++++++++++- templates/nostrmarket/_api_docs.html | 55 ++++++++++++++-------- templates/nostrmarket/index.html | 68 ++++++++++++++++++++++++---- views_api.py | 36 +++++++++++++++ 4 files changed, 186 insertions(+), 30 deletions(-) diff --git a/static/js/index.js b/static/js/index.js index 3d89779..21f67f6 100644 --- a/static/js/index.js +++ b/static/js/index.js @@ -16,7 +16,29 @@ window.app = Vue.createApp({ privateKey: null } }, - wsConnection: null + wsConnection: null, + nostrStatus: { + nostrclient_available: false, + nostrclient_relays: [], + nostrclient_error: null, + nostrmarket_running: false, + websocket_connected: false + } + } + }, + computed: { + nostrStatusColor: function () { + if (!this.nostrStatus.nostrclient_available) { + return 'red' + } else if (this.nostrStatus.websocket_connected) { + return 'green' + } else if (this.nostrStatus.nostrmarket_running) { + return 'orange' + } + return 'red' + }, + nostrStatusLabel: function () { + return 'Connect' } }, methods: { @@ -196,10 +218,38 @@ window.app = Vue.createApp({ }) } }, + checkNostrStatus: async function () { + try { + const {data} = await LNbits.api.request( + 'GET', + '/nostrmarket/api/v1/status', + this.g.user.wallets[0].inkey + ) + this.nostrStatus = data + if (!data.nostrclient_available) { + this.$q.notify({ + timeout: 5000, + type: 'warning', + message: 'Nostrclient extension not available', + caption: + data.nostrclient_error || + 'Please install and configure the nostrclient extension' + }) + } + } catch (error) { + this.nostrStatus = { + nostrclient_available: false, + nostrclient_relays: [], + nostrclient_error: 'Failed to check status', + nostrmarket_running: false, + websocket_connected: false + } + } + }, restartNostrConnection: async function () { LNbits.utils .confirmDialog( - 'Are you sure you want to reconnect to the nostrcient extension?' + 'Are you sure you want to reconnect to the nostrclient extension?' ) .onOk(async () => { try { @@ -208,6 +258,8 @@ window.app = Vue.createApp({ '/nostrmarket/api/v1/restart', this.g.user.wallets[0].adminkey ) + // Check status after restart + setTimeout(() => this.checkNostrStatus(), 2000) } catch (error) { LNbits.utils.notifyApiError(error) } @@ -216,6 +268,7 @@ window.app = Vue.createApp({ }, created: async function () { await this.getMerchant() + await this.checkNostrStatus() setInterval(async () => { if ( !this.wsConnection || diff --git a/templates/nostrmarket/_api_docs.html b/templates/nostrmarket/_api_docs.html index 6bce480..f4f7cab 100644 --- a/templates/nostrmarket/_api_docs.html +++ b/templates/nostrmarket/_api_docs.html @@ -1,44 +1,61 @@ -

- Nostr Market
+

+ Create, edit and publish products to your Nostr relays. Customers can + browse your stalls and pay with Lightning. +

+

- Created by, + Created by Tal Vasconcelos + >, Ben Arc + >, motorina0 + > +

- Swagger REST API Documentation
- - Visit the market clientMarket client + + + Market client + Visit the market client + +
+ + API Documentation + Swagger REST API Documentation + +
+ + + + Requires:  + nostrclient + extension to be installed and configured with relays. +
diff --git a/templates/nostrmarket/index.html b/templates/nostrmarket/index.html index d95d965..319fbdb 100644 --- a/templates/nostrmarket/index.html +++ b/templates/nostrmarket/index.html @@ -151,16 +151,66 @@
- - - Restart the connection to the nostrclient extension - - + + + + + + + Restart Connection + + Reconnect to the nostrclient extension + + + + + + + + + Check Status + + Check connection to nostrclient + + + + + + + + Nostrclient: + +
+ Relays: + configured
+ WebSocket: + +
+ +
+
+
+
@@ -168,7 +218,7 @@
- {{SITE_TITLE}} Nostr Market Extension + Nostr Market
diff --git a/views_api.py b/views_api.py index af675cd..ba10674 100644 --- a/views_api.py +++ b/views_api.py @@ -1,10 +1,12 @@ import json from http import HTTPStatus +import httpx from fastapi import Depends from fastapi.exceptions import HTTPException from lnbits.core.models import WalletTypeInfo from lnbits.core.services import websocket_updater +from lnbits.settings import settings from lnbits.decorators import ( require_admin_key, require_invoice_key, @@ -1105,6 +1107,40 @@ async def api_list_currencies_available(): return list(currencies.keys()) +@nostrmarket_ext.get("/api/v1/status") +async def api_get_nostr_status( + wallet: WalletTypeInfo = Depends(require_invoice_key), +) -> dict: + """Get the status of the nostrclient extension.""" + nostrclient_available = False + nostrclient_relays = [] + nostrclient_error = None + + try: + async with httpx.AsyncClient() as client: + response = await client.get( + f"http://localhost:{settings.port}/nostrclient/api/v1/relays", + timeout=5.0, + ) + if response.status_code == 200: + nostrclient_available = True + nostrclient_relays = response.json() + except httpx.ConnectError: + nostrclient_error = "Cannot connect to nostrclient extension" + except httpx.TimeoutException: + nostrclient_error = "Timeout connecting to nostrclient" + except Exception as ex: + nostrclient_error = str(ex) + + return { + "nostrclient_available": nostrclient_available, + "nostrclient_relays": nostrclient_relays, + "nostrclient_error": nostrclient_error, + "nostrmarket_running": nostr_client.running, + "websocket_connected": nostr_client.is_websocket_connected, + } + + @nostrmarket_ext.put("/api/v1/restart") async def restart_nostr_client(wallet: WalletTypeInfo = Depends(require_admin_key)): try: From d2755d7232a0967463b8e997f1d5af4a09dd7d0a Mon Sep 17 00:00:00 2001 From: Ben Weeks Date: Mon, 22 Dec 2025 12:49:33 +0000 Subject: [PATCH 2/4] fix: improve nostrclient status detection and display MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Call nostrclient /relays API directly from frontend for accurate status - Show correct error messages from API response (body.detail) - Add orange warning state for no relays configured - Show relay count when connected (X of Y connected) - Simplify status logic: 200 = green, no relays = orange, error = red 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- static/js/index.js | 92 +++++++++++++++++++--------- templates/nostrmarket/_api_docs.html | 2 +- templates/nostrmarket/index.html | 32 +++++----- views_api.py | 52 +++++++++++----- 4 files changed, 118 insertions(+), 60 deletions(-) diff --git a/static/js/index.js b/static/js/index.js index 21f67f6..6cd50ad 100644 --- a/static/js/index.js +++ b/static/js/index.js @@ -18,21 +18,18 @@ window.app = Vue.createApp({ }, wsConnection: null, nostrStatus: { - nostrclient_available: false, - nostrclient_relays: [], - nostrclient_error: null, - nostrmarket_running: false, - websocket_connected: false + connected: false, + error: null, + relays_connected: 0, + relays_total: 0 } } }, computed: { nostrStatusColor: function () { - if (!this.nostrStatus.nostrclient_available) { - return 'red' - } else if (this.nostrStatus.websocket_connected) { + if (this.nostrStatus.connected) { return 'green' - } else if (this.nostrStatus.nostrmarket_running) { + } else if (this.nostrStatus.warning) { return 'orange' } return 'red' @@ -218,31 +215,61 @@ window.app = Vue.createApp({ }) } }, - checkNostrStatus: async function () { + checkNostrStatus: async function (showNotification = false) { try { - const {data} = await LNbits.api.request( - 'GET', - '/nostrmarket/api/v1/status', - this.g.user.wallets[0].inkey - ) - this.nostrStatus = data - if (!data.nostrclient_available) { + const response = await fetch('/nostrclient/api/v1/relays') + const body = await response.json() + console.log('Nostrclient /relays:', response.status, body) + + if (response.status === 200) { + const relaysConnected = body.filter(r => r.connected).length + if (body.length === 0) { + this.nostrStatus = { + connected: false, + error: 'No relays configured in Nostr Client', + relays_connected: 0, + relays_total: 0, + warning: true + } + } else { + this.nostrStatus = { + connected: true, + error: null, + relays_connected: relaysConnected, + relays_total: body.length + } + } + } else { + this.nostrStatus = { + connected: false, + error: body.detail, + relays_connected: 0, + relays_total: 0 + } + } + + if (showNotification) { this.$q.notify({ - timeout: 5000, - type: 'warning', - message: 'Nostrclient extension not available', - caption: - data.nostrclient_error || - 'Please install and configure the nostrclient extension' + timeout: 3000, + type: this.nostrStatus.connected ? 'positive' : 'warning', + message: this.nostrStatus.connected ? 'Connected' : 'Disconnected', + caption: this.nostrStatus.error || undefined }) } } catch (error) { + console.error('Failed to check nostr status:', error) this.nostrStatus = { - nostrclient_available: false, - nostrclient_relays: [], - nostrclient_error: 'Failed to check status', - nostrmarket_running: false, - websocket_connected: false + connected: false, + error: error.message, + relays_connected: 0, + relays_total: 0 + } + if (showNotification) { + this.$q.notify({ + timeout: 5000, + type: 'negative', + message: this.nostrStatus.error + }) } } }, @@ -253,13 +280,18 @@ window.app = Vue.createApp({ ) .onOk(async () => { try { + this.$q.notify({ + timeout: 2000, + type: 'info', + message: 'Reconnecting...' + }) await LNbits.api.request( 'PUT', '/nostrmarket/api/v1/restart', this.g.user.wallets[0].adminkey ) - // Check status after restart - setTimeout(() => this.checkNostrStatus(), 2000) + // Check status after restart (give time for websocket to reconnect) + setTimeout(() => this.checkNostrStatus(true), 3000) } catch (error) { LNbits.utils.notifyApiError(error) } diff --git a/templates/nostrmarket/_api_docs.html b/templates/nostrmarket/_api_docs.html index f4f7cab..d801649 100644 --- a/templates/nostrmarket/_api_docs.html +++ b/templates/nostrmarket/_api_docs.html @@ -1,6 +1,6 @@ -

+

Create, edit and publish products to your Nostr relays. Customers can browse your stalls and pay with Lightning.

diff --git a/templates/nostrmarket/index.html b/templates/nostrmarket/index.html index 319fbdb..0036428 100644 --- a/templates/nostrmarket/index.html +++ b/templates/nostrmarket/index.html @@ -170,7 +170,7 @@ - + @@ -185,27 +185,29 @@ - Nostrclient: + Status: -
- Relays: - configured
- WebSocket: -
+ Relays:  + + of + + connected + +
diff --git a/views_api.py b/views_api.py index ba10674..9dd8730 100644 --- a/views_api.py +++ b/views_api.py @@ -1113,31 +1113,55 @@ async def api_get_nostr_status( ) -> dict: """Get the status of the nostrclient extension.""" nostrclient_available = False - nostrclient_relays = [] - nostrclient_error = None + relays = [] + error = None try: async with httpx.AsyncClient() as client: - response = await client.get( - f"http://localhost:{settings.port}/nostrclient/api/v1/relays", - timeout=5.0, + url = f"http://127.0.0.1:{settings.port}/nostrclient/api/v1/relays" + logger.info(f"Calling nostrclient API: {url}") + response = await client.get(url, timeout=5.0) + logger.info( + f"Nostrclient response: status={response.status_code}, " + f"body={response.text[:500]}" ) + if response.status_code == 200: nostrclient_available = True - nostrclient_relays = response.json() + relays = response.json() + else: + # Any non-200 response means we can't verify nostrclient is working + try: + error = response.json().get("detail", f"HTTP {response.status_code}") + except Exception: + error = f"HTTP {response.status_code}" except httpx.ConnectError: - nostrclient_error = "Cannot connect to nostrclient extension" + error = "Cannot connect to nostrclient extension" except httpx.TimeoutException: - nostrclient_error = "Timeout connecting to nostrclient" + error = "Timeout connecting to nostrclient" except Exception as ex: - nostrclient_error = str(ex) + error = str(ex) + + # Only show connected if no errors and websocket is connected + connected = ( + nostrclient_available + and nostr_client.is_websocket_connected + and error is None + ) + + # If nostrclient exists but websocket not connected, explain why + if nostrclient_available and not nostr_client.is_websocket_connected and not error: + error = "Websocket not connected" + + # Count connected relays + relays_connected = sum(1 for r in relays if r.get("connected", False)) + relays_total = len(relays) return { - "nostrclient_available": nostrclient_available, - "nostrclient_relays": nostrclient_relays, - "nostrclient_error": nostrclient_error, - "nostrmarket_running": nostr_client.running, - "websocket_connected": nostr_client.is_websocket_connected, + "connected": connected, + "error": error, + "relays_connected": relays_connected, + "relays_total": relays_total, } From b8c2c9917526111e7feddd0ae34925ecf4bfdcbf Mon Sep 17 00:00:00 2001 From: Ben Weeks Date: Mon, 22 Dec 2025 14:46:30 +0000 Subject: [PATCH 3/4] chore: remove unused status endpoint and debug logging MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Remove api_get_nostr_status() - frontend calls nostrclient directly - Remove unused httpx and settings imports - Remove console.log debug statement 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- static/js/index.js | 1 - views_api.py | 60 ---------------------------------------------- 2 files changed, 61 deletions(-) diff --git a/static/js/index.js b/static/js/index.js index 6cd50ad..dd41d41 100644 --- a/static/js/index.js +++ b/static/js/index.js @@ -219,7 +219,6 @@ window.app = Vue.createApp({ try { const response = await fetch('/nostrclient/api/v1/relays') const body = await response.json() - console.log('Nostrclient /relays:', response.status, body) if (response.status === 200) { const relaysConnected = body.filter(r => r.connected).length diff --git a/views_api.py b/views_api.py index 9dd8730..af675cd 100644 --- a/views_api.py +++ b/views_api.py @@ -1,12 +1,10 @@ import json from http import HTTPStatus -import httpx from fastapi import Depends from fastapi.exceptions import HTTPException from lnbits.core.models import WalletTypeInfo from lnbits.core.services import websocket_updater -from lnbits.settings import settings from lnbits.decorators import ( require_admin_key, require_invoice_key, @@ -1107,64 +1105,6 @@ async def api_list_currencies_available(): return list(currencies.keys()) -@nostrmarket_ext.get("/api/v1/status") -async def api_get_nostr_status( - wallet: WalletTypeInfo = Depends(require_invoice_key), -) -> dict: - """Get the status of the nostrclient extension.""" - nostrclient_available = False - relays = [] - error = None - - try: - async with httpx.AsyncClient() as client: - url = f"http://127.0.0.1:{settings.port}/nostrclient/api/v1/relays" - logger.info(f"Calling nostrclient API: {url}") - response = await client.get(url, timeout=5.0) - logger.info( - f"Nostrclient response: status={response.status_code}, " - f"body={response.text[:500]}" - ) - - if response.status_code == 200: - nostrclient_available = True - relays = response.json() - else: - # Any non-200 response means we can't verify nostrclient is working - try: - error = response.json().get("detail", f"HTTP {response.status_code}") - except Exception: - error = f"HTTP {response.status_code}" - except httpx.ConnectError: - error = "Cannot connect to nostrclient extension" - except httpx.TimeoutException: - error = "Timeout connecting to nostrclient" - except Exception as ex: - error = str(ex) - - # Only show connected if no errors and websocket is connected - connected = ( - nostrclient_available - and nostr_client.is_websocket_connected - and error is None - ) - - # If nostrclient exists but websocket not connected, explain why - if nostrclient_available and not nostr_client.is_websocket_connected and not error: - error = "Websocket not connected" - - # Count connected relays - relays_connected = sum(1 for r in relays if r.get("connected", False)) - relays_total = len(relays) - - return { - "connected": connected, - "error": error, - "relays_connected": relays_connected, - "relays_total": relays_total, - } - - @nostrmarket_ext.put("/api/v1/restart") async def restart_nostr_client(wallet: WalletTypeInfo = Depends(require_admin_key)): try: From 1f708fff66a741501aa53ecff9f37784605c1e05 Mon Sep 17 00:00:00 2001 From: Ben Weeks Date: Tue, 23 Dec 2025 12:24:35 +0000 Subject: [PATCH 4/4] style: fix prettier formatting in index.html MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- templates/nostrmarket/index.html | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/templates/nostrmarket/index.html b/templates/nostrmarket/index.html index 0036428..8d53467 100644 --- a/templates/nostrmarket/index.html +++ b/templates/nostrmarket/index.html @@ -219,9 +219,7 @@
-
- Nostr Market -
+
Nostr Market