- Implement a new getter method in RelayHub to return an array of subscription details, including IDs, filters, and associated relays. - Enhance the ability to track and manage subscriptions more effectively within the RelayHub component.
366 lines
7.5 KiB
Vue
366 lines
7.5 KiB
Vue
<template>
|
|
<div class="relay-hub-status">
|
|
<div class="status-header">
|
|
<h3>Nostr Relay Hub Status</h3>
|
|
<div class="connection-indicator" :class="connectionStatus">
|
|
{{ connectionStatus }}
|
|
</div>
|
|
</div>
|
|
|
|
<div class="connection-info">
|
|
<div class="info-row">
|
|
<span class="label">Status:</span>
|
|
<span class="value">{{ connectionStatus }}</span>
|
|
</div>
|
|
<div class="info-row">
|
|
<span class="label">Connected Relays:</span>
|
|
<span class="value">{{ connectedRelayCount }}/{{ totalRelayCount }}</span>
|
|
</div>
|
|
<div class="info-row">
|
|
<span class="label">Health:</span>
|
|
<span class="value">{{ connectionHealth.toFixed(1) }}%</span>
|
|
</div>
|
|
<div class="info-row" v-if="error">
|
|
<span class="label">Error:</span>
|
|
<span class="value error">{{ error.message }}</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="relay-list" v-if="relayStatuses.length > 0">
|
|
<h4>Relay Status</h4>
|
|
<div class="relay-item" v-for="relay in relayStatuses" :key="relay.url">
|
|
<div class="relay-url">{{ relay.url }}</div>
|
|
<div class="relay-status" :class="{ connected: relay.connected }">
|
|
{{ relay.connected ? 'Connected' : 'Disconnected' }}
|
|
</div>
|
|
<div class="relay-latency" v-if="relay.latency !== undefined">
|
|
{{ relay.latency }}ms
|
|
</div>
|
|
<div class="relay-error" v-if="relay.error">
|
|
{{ relay.error }}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="actions">
|
|
<button @click="connect" :disabled="isConnected || connectionStatus === 'connecting'">
|
|
Connect
|
|
</button>
|
|
<button @click="disconnect" :disabled="!isConnected">
|
|
Disconnect
|
|
</button>
|
|
<button @click="reconnect" :disabled="connectionStatus === 'connecting'">
|
|
Reconnect
|
|
</button>
|
|
</div>
|
|
|
|
<div class="subscription-info">
|
|
<h4>Active Subscriptions</h4>
|
|
<div class="subscription-count">
|
|
<div>Local: {{ activeSubscriptions.size }}</div>
|
|
<div>Global: {{ totalSubscriptionCount }}</div>
|
|
</div>
|
|
|
|
<!-- Subscription Details -->
|
|
<div v-if="subscriptionDetails.length > 0" class="subscription-details">
|
|
<h5>Subscription Details:</h5>
|
|
<div class="subscription-list">
|
|
<div
|
|
v-for="sub in subscriptionDetails"
|
|
:key="sub.id"
|
|
class="subscription-item"
|
|
>
|
|
<div class="subscription-id">
|
|
<strong>ID:</strong> {{ sub.id }}
|
|
</div>
|
|
<div class="subscription-filters">
|
|
<strong>Filters:</strong> {{ sub.filters.length }} filter(s)
|
|
<div v-if="sub.filters.length > 0" class="filter-details">
|
|
<div
|
|
v-for="(filter, index) in sub.filters"
|
|
:key="index"
|
|
class="filter-item"
|
|
>
|
|
<code>{{ JSON.stringify(filter) }}</code>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div v-if="sub.relays && sub.relays.length > 0" class="subscription-relays">
|
|
<strong>Relays:</strong> {{ sub.relays.join(', ') }}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div v-else class="no-subscriptions">
|
|
No active subscriptions found
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import { useRelayHub } from '@/composables/useRelayHub'
|
|
|
|
const {
|
|
isConnected,
|
|
connectionStatus,
|
|
relayStatuses,
|
|
error,
|
|
activeSubscriptions,
|
|
connectedRelayCount,
|
|
totalRelayCount,
|
|
totalSubscriptionCount,
|
|
subscriptionDetails,
|
|
connectionHealth,
|
|
connect,
|
|
disconnect,
|
|
reconnect
|
|
} = useRelayHub()
|
|
</script>
|
|
|
|
<style scoped>
|
|
.relay-hub-status {
|
|
padding: 1rem;
|
|
border: 1px solid #e2e8f0;
|
|
border-radius: 0.5rem;
|
|
background-color: #f8fafc;
|
|
max-width: 600px;
|
|
}
|
|
|
|
.status-header {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
margin-bottom: 1rem;
|
|
}
|
|
|
|
.status-header h3 {
|
|
margin: 0;
|
|
color: #1e293b;
|
|
}
|
|
|
|
.connection-indicator {
|
|
padding: 0.25rem 0.75rem;
|
|
border-radius: 9999px;
|
|
font-size: 0.875rem;
|
|
font-weight: 500;
|
|
text-transform: capitalize;
|
|
}
|
|
|
|
.connection-indicator.connected {
|
|
background-color: #dcfce7;
|
|
color: #166534;
|
|
}
|
|
|
|
.connection-indicator.connecting {
|
|
background-color: #fef3c7;
|
|
color: #92400e;
|
|
}
|
|
|
|
.connection-indicator.disconnected {
|
|
background-color: #fee2e2;
|
|
color: #991b1b;
|
|
}
|
|
|
|
.connection-indicator.error {
|
|
background-color: #fecaca;
|
|
color: #7f1d1d;
|
|
}
|
|
|
|
.connection-info {
|
|
margin-bottom: 1.5rem;
|
|
}
|
|
|
|
.info-row {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
padding: 0.5rem 0;
|
|
border-bottom: 1px solid #e2e8f0;
|
|
}
|
|
|
|
.info-row:last-child {
|
|
border-bottom: none;
|
|
}
|
|
|
|
.label {
|
|
font-weight: 500;
|
|
color: #475569;
|
|
}
|
|
|
|
.value {
|
|
color: #1e293b;
|
|
}
|
|
|
|
.value.error {
|
|
color: #dc2626;
|
|
}
|
|
|
|
.relay-list {
|
|
margin-bottom: 1.5rem;
|
|
}
|
|
|
|
.relay-list h4 {
|
|
margin: 0 0 0.75rem 0;
|
|
color: #1e293b;
|
|
}
|
|
|
|
.relay-item {
|
|
display: grid;
|
|
grid-template-columns: 1fr auto auto auto;
|
|
gap: 0.75rem;
|
|
align-items: center;
|
|
padding: 0.75rem;
|
|
background-color: white;
|
|
border: 1px solid #e2e8f0;
|
|
border-radius: 0.375rem;
|
|
margin-bottom: 0.5rem;
|
|
}
|
|
|
|
.relay-url {
|
|
font-family: monospace;
|
|
font-size: 0.875rem;
|
|
color: #475569;
|
|
word-break: break-all;
|
|
}
|
|
|
|
.relay-status {
|
|
padding: 0.25rem 0.5rem;
|
|
border-radius: 0.25rem;
|
|
font-size: 0.75rem;
|
|
font-weight: 500;
|
|
text-align: center;
|
|
background-color: #fee2e2;
|
|
color: #991b1b;
|
|
}
|
|
|
|
.relay-status.connected {
|
|
background-color: #dcfce7;
|
|
color: #166534;
|
|
}
|
|
|
|
.relay-latency {
|
|
font-size: 0.75rem;
|
|
color: #64748b;
|
|
text-align: center;
|
|
}
|
|
|
|
.relay-error {
|
|
font-size: 0.75rem;
|
|
color: #dc2626;
|
|
text-align: center;
|
|
}
|
|
|
|
.actions {
|
|
display: flex;
|
|
gap: 0.5rem;
|
|
margin-bottom: 1.5rem;
|
|
}
|
|
|
|
.actions button {
|
|
padding: 0.5rem 1rem;
|
|
border: 1px solid #d1d5db;
|
|
border-radius: 0.375rem;
|
|
background-color: white;
|
|
color: #374151;
|
|
font-size: 0.875rem;
|
|
cursor: pointer;
|
|
transition: all 0.2s;
|
|
}
|
|
|
|
.actions button:hover:not(:disabled) {
|
|
background-color: #f9fafb;
|
|
border-color: #9ca3af;
|
|
}
|
|
|
|
.actions button:disabled {
|
|
opacity: 0.5;
|
|
cursor: not-allowed;
|
|
}
|
|
|
|
.subscription-info {
|
|
border-top: 1px solid #e2e8f0;
|
|
padding-top: 1rem;
|
|
}
|
|
|
|
.subscription-info h4 {
|
|
margin: 0 0 0.5rem 0;
|
|
color: #1e293b;
|
|
}
|
|
|
|
.subscription-count {
|
|
font-size: 1.5rem;
|
|
font-weight: bold;
|
|
color: #3b82f6;
|
|
margin-bottom: 1rem;
|
|
}
|
|
|
|
.subscription-details {
|
|
margin-top: 1rem;
|
|
padding: 1rem;
|
|
background-color: #f1f5f9;
|
|
border-radius: 0.375rem;
|
|
}
|
|
|
|
.subscription-details h5 {
|
|
margin: 0 0 0.75rem 0;
|
|
color: #475569;
|
|
font-size: 0.875rem;
|
|
font-weight: 600;
|
|
}
|
|
|
|
.subscription-list {
|
|
/* Individual subscription items handle their own spacing */
|
|
}
|
|
|
|
.subscription-item {
|
|
padding: 0.75rem;
|
|
background-color: white;
|
|
border: 1px solid #e2e8f0;
|
|
border-radius: 0.375rem;
|
|
margin-bottom: 0.75rem;
|
|
}
|
|
|
|
.subscription-item:last-child {
|
|
margin-bottom: 0;
|
|
}
|
|
|
|
.subscription-id {
|
|
margin-bottom: 0.5rem;
|
|
font-family: monospace;
|
|
font-size: 0.875rem;
|
|
}
|
|
|
|
.subscription-filters {
|
|
margin-bottom: 0.5rem;
|
|
}
|
|
|
|
.filter-details {
|
|
margin-top: 0.25rem;
|
|
margin-left: 1rem;
|
|
}
|
|
|
|
.filter-item {
|
|
margin-bottom: 0.25rem;
|
|
}
|
|
|
|
.filter-item code {
|
|
background-color: #f8fafc;
|
|
padding: 0.125rem 0.25rem;
|
|
border-radius: 0.25rem;
|
|
font-size: 0.75rem;
|
|
color: #475569;
|
|
}
|
|
|
|
.subscription-relays {
|
|
font-size: 0.875rem;
|
|
color: #64748b;
|
|
}
|
|
|
|
.no-subscriptions {
|
|
text-align: center;
|
|
color: #94a3b8;
|
|
font-style: italic;
|
|
padding: 1rem;
|
|
}
|
|
</style>
|