feat: add nostrclient status indicator and connection button
- 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 <noreply@anthropic.com>
This commit is contained in:
parent
17d13dbe6b
commit
9911a03575
4 changed files with 186 additions and 30 deletions
|
|
@ -16,7 +16,29 @@ window.app = Vue.createApp({
|
||||||
privateKey: null
|
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: {
|
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 () {
|
restartNostrConnection: async function () {
|
||||||
LNbits.utils
|
LNbits.utils
|
||||||
.confirmDialog(
|
.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 () => {
|
.onOk(async () => {
|
||||||
try {
|
try {
|
||||||
|
|
@ -208,6 +258,8 @@ window.app = Vue.createApp({
|
||||||
'/nostrmarket/api/v1/restart',
|
'/nostrmarket/api/v1/restart',
|
||||||
this.g.user.wallets[0].adminkey
|
this.g.user.wallets[0].adminkey
|
||||||
)
|
)
|
||||||
|
// Check status after restart
|
||||||
|
setTimeout(() => this.checkNostrStatus(), 2000)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
LNbits.utils.notifyApiError(error)
|
LNbits.utils.notifyApiError(error)
|
||||||
}
|
}
|
||||||
|
|
@ -216,6 +268,7 @@ window.app = Vue.createApp({
|
||||||
},
|
},
|
||||||
created: async function () {
|
created: async function () {
|
||||||
await this.getMerchant()
|
await this.getMerchant()
|
||||||
|
await this.checkNostrStatus()
|
||||||
setInterval(async () => {
|
setInterval(async () => {
|
||||||
if (
|
if (
|
||||||
!this.wsConnection ||
|
!this.wsConnection ||
|
||||||
|
|
|
||||||
|
|
@ -1,44 +1,61 @@
|
||||||
<q-card>
|
<q-card>
|
||||||
<q-card-section>
|
<q-card-section>
|
||||||
<p>
|
<p class="text-body1">
|
||||||
Nostr Market<br />
|
Create, edit and publish products to your Nostr relays. Customers can
|
||||||
|
browse your stalls and pay with Lightning.
|
||||||
|
</p>
|
||||||
|
<p class="q-mb-none">
|
||||||
<small>
|
<small>
|
||||||
Created by,
|
Created by
|
||||||
<a
|
<a
|
||||||
class="text-secondary"
|
class="text-secondary"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
style="color: unset"
|
style="color: unset"
|
||||||
href="https://github.com/talvasconcelos"
|
href="https://github.com/talvasconcelos"
|
||||||
>Tal Vasconcelos</a
|
>Tal Vasconcelos</a
|
||||||
>
|
>,
|
||||||
<a
|
<a
|
||||||
class="text-secondary"
|
class="text-secondary"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
style="color: unset"
|
style="color: unset"
|
||||||
href="https://github.com/benarc"
|
href="https://github.com/benarc"
|
||||||
>Ben Arc</a
|
>Ben Arc</a
|
||||||
>
|
>,
|
||||||
<a
|
<a
|
||||||
class="text-secondary"
|
class="text-secondary"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
style="color: unset"
|
style="color: unset"
|
||||||
href="https://github.com/motorina0"
|
href="https://github.com/motorina0"
|
||||||
>motorina0</a
|
>motorina0</a
|
||||||
></small
|
>
|
||||||
>
|
</small>
|
||||||
</p>
|
</p>
|
||||||
<a
|
|
||||||
class="text-secondary"
|
|
||||||
target="_blank"
|
|
||||||
href="/docs#/nostrmarket"
|
|
||||||
class="text-white"
|
|
||||||
>Swagger REST API Documentation</a
|
|
||||||
>
|
|
||||||
</q-card-section>
|
</q-card-section>
|
||||||
<q-card-section>
|
<q-card-section class="q-pt-none">
|
||||||
<a class="text-secondary" target="_blank" href="/nostrmarket/market"
|
<a class="text-secondary" target="_blank" href="/nostrmarket/market">
|
||||||
><q-tooltip>Visit the market client</q-tooltip
|
<q-icon name="storefront" class="q-mr-sm"></q-icon>Market client
|
||||||
><q-icon name="storefront" class="q-mr-sm"></q-icon>Market client</a
|
<q-tooltip>Visit the market client</q-tooltip>
|
||||||
>
|
</a>
|
||||||
|
<br />
|
||||||
|
<a class="text-secondary" target="_blank" href="/docs#/nostrmarket">
|
||||||
|
<q-icon name="code" class="q-mr-sm"></q-icon>API Documentation
|
||||||
|
<q-tooltip>Swagger REST API Documentation</q-tooltip>
|
||||||
|
</a>
|
||||||
|
</q-card-section>
|
||||||
|
<q-card-section class="q-pt-none">
|
||||||
|
<q-banner dense class="bg-orange-8 text-white" rounded>
|
||||||
|
<template v-slot:avatar>
|
||||||
|
<q-icon name="info" color="white"></q-icon>
|
||||||
|
</template>
|
||||||
|
<strong>Requires:</strong>
|
||||||
|
<a
|
||||||
|
href="https://github.com/lnbits/nostrclient"
|
||||||
|
target="_blank"
|
||||||
|
class="text-white"
|
||||||
|
style="text-decoration: underline"
|
||||||
|
>nostrclient</a
|
||||||
|
>
|
||||||
|
extension to be installed and configured with relays.
|
||||||
|
</q-banner>
|
||||||
</q-card-section>
|
</q-card-section>
|
||||||
</q-card>
|
</q-card>
|
||||||
|
|
|
||||||
|
|
@ -151,16 +151,66 @@
|
||||||
<div v-if="g.user.admin" class="col-12 q-mb-lg">
|
<div v-if="g.user.admin" class="col-12 q-mb-lg">
|
||||||
<q-card>
|
<q-card>
|
||||||
<q-card-section class="q-pa-md">
|
<q-card-section class="q-pa-md">
|
||||||
<q-btn
|
<q-btn-dropdown
|
||||||
label="Restart Nostr Connection"
|
:color="nostrStatusColor"
|
||||||
color="grey"
|
:label="nostrStatusLabel"
|
||||||
outline
|
icon="sync"
|
||||||
|
split
|
||||||
@click="restartNostrConnection"
|
@click="restartNostrConnection"
|
||||||
>
|
>
|
||||||
<q-tooltip>
|
<q-list>
|
||||||
Restart the connection to the nostrclient extension
|
<q-item clickable v-close-popup @click="restartNostrConnection">
|
||||||
</q-tooltip>
|
<q-item-section avatar>
|
||||||
</q-btn>
|
<q-icon name="refresh" color="primary"></q-icon>
|
||||||
|
</q-item-section>
|
||||||
|
<q-item-section>
|
||||||
|
<q-item-label>Restart Connection</q-item-label>
|
||||||
|
<q-item-label caption>
|
||||||
|
Reconnect to the nostrclient extension
|
||||||
|
</q-item-label>
|
||||||
|
</q-item-section>
|
||||||
|
</q-item>
|
||||||
|
<q-item clickable v-close-popup @click="checkNostrStatus">
|
||||||
|
<q-item-section avatar>
|
||||||
|
<q-icon name="wifi_find" color="primary"></q-icon>
|
||||||
|
</q-item-section>
|
||||||
|
<q-item-section>
|
||||||
|
<q-item-label>Check Status</q-item-label>
|
||||||
|
<q-item-label caption>
|
||||||
|
Check connection to nostrclient
|
||||||
|
</q-item-label>
|
||||||
|
</q-item-section>
|
||||||
|
</q-item>
|
||||||
|
<q-separator></q-separator>
|
||||||
|
<q-item>
|
||||||
|
<q-item-section>
|
||||||
|
<q-item-label caption>
|
||||||
|
<strong>Nostrclient:</strong>
|
||||||
|
<q-badge
|
||||||
|
:color="nostrStatus.nostrclient_available ? 'green' : 'red'"
|
||||||
|
class="q-ml-xs"
|
||||||
|
v-text="nostrStatus.nostrclient_available ? 'Available' : 'Not Available'"
|
||||||
|
></q-badge>
|
||||||
|
<br />
|
||||||
|
<strong>Relays:</strong>
|
||||||
|
<span v-text="(nostrStatus.nostrclient_relays || []).length"></span> configured<br />
|
||||||
|
<strong>WebSocket:</strong>
|
||||||
|
<q-badge
|
||||||
|
:color="nostrStatus.websocket_connected ? 'green' : 'orange'"
|
||||||
|
class="q-ml-xs"
|
||||||
|
v-text="nostrStatus.websocket_connected ? 'Connected' : 'Disconnected'"
|
||||||
|
></q-badge>
|
||||||
|
</q-item-label>
|
||||||
|
<q-item-label
|
||||||
|
v-if="nostrStatus.nostrclient_error"
|
||||||
|
caption
|
||||||
|
class="text-negative q-mt-xs"
|
||||||
|
v-text="nostrStatus.nostrclient_error"
|
||||||
|
></q-item-label>
|
||||||
|
</q-item-section>
|
||||||
|
</q-item>
|
||||||
|
</q-list>
|
||||||
|
</q-btn-dropdown>
|
||||||
</q-card-section>
|
</q-card-section>
|
||||||
</q-card>
|
</q-card>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -168,7 +218,7 @@
|
||||||
<q-card>
|
<q-card>
|
||||||
<q-card-section>
|
<q-card-section>
|
||||||
<h6 class="text-subtitle1 q-my-none">
|
<h6 class="text-subtitle1 q-my-none">
|
||||||
{{SITE_TITLE}} Nostr Market Extension
|
Nostr Market
|
||||||
</h6>
|
</h6>
|
||||||
</q-card-section>
|
</q-card-section>
|
||||||
<q-card-section class="q-pa-none">
|
<q-card-section class="q-pa-none">
|
||||||
|
|
|
||||||
36
views_api.py
36
views_api.py
|
|
@ -1,10 +1,12 @@
|
||||||
import json
|
import json
|
||||||
from http import HTTPStatus
|
from http import HTTPStatus
|
||||||
|
|
||||||
|
import httpx
|
||||||
from fastapi import Depends
|
from fastapi import Depends
|
||||||
from fastapi.exceptions import HTTPException
|
from fastapi.exceptions import HTTPException
|
||||||
from lnbits.core.models import WalletTypeInfo
|
from lnbits.core.models import WalletTypeInfo
|
||||||
from lnbits.core.services import websocket_updater
|
from lnbits.core.services import websocket_updater
|
||||||
|
from lnbits.settings import settings
|
||||||
from lnbits.decorators import (
|
from lnbits.decorators import (
|
||||||
require_admin_key,
|
require_admin_key,
|
||||||
require_invoice_key,
|
require_invoice_key,
|
||||||
|
|
@ -1105,6 +1107,40 @@ async def api_list_currencies_available():
|
||||||
return list(currencies.keys())
|
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")
|
@nostrmarket_ext.put("/api/v1/restart")
|
||||||
async def restart_nostr_client(wallet: WalletTypeInfo = Depends(require_admin_key)):
|
async def restart_nostr_client(wallet: WalletTypeInfo = Depends(require_admin_key)):
|
||||||
try:
|
try:
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue