mirror of
https://github.com/aljazceru/nostr-watch.git
synced 2025-12-17 05:24:19 +01:00
tie it all together, needs cleanup
This commit is contained in:
@@ -4,5 +4,10 @@ server {
|
|||||||
|
|
||||||
location / {
|
location / {
|
||||||
index index.html;
|
index index.html;
|
||||||
|
# try_files $uri $uri/ /index.html;
|
||||||
|
}
|
||||||
|
|
||||||
|
location ~ /?(.*)$ {
|
||||||
|
index index.html
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,7 +30,7 @@
|
|||||||
"node-emoji": "1.11.0",
|
"node-emoji": "1.11.0",
|
||||||
"node-polyfill-webpack-plugin": "2.0.1",
|
"node-polyfill-webpack-plugin": "2.0.1",
|
||||||
"nostr": "0.2.5",
|
"nostr": "0.2.5",
|
||||||
"nostr-relay-inspector": "0.0.7",
|
"nostr-relay-inspector": "0.0.9",
|
||||||
"nostr-tools": "0.24.1",
|
"nostr-tools": "0.24.1",
|
||||||
"onion-regex": "2.0.8",
|
"onion-regex": "2.0.8",
|
||||||
"requests": "0.3.0",
|
"requests": "0.3.0",
|
||||||
@@ -45,6 +45,8 @@
|
|||||||
"vue-router": "4.1.6",
|
"vue-router": "4.1.6",
|
||||||
"vue-simple-maps": "1.1.3",
|
"vue-simple-maps": "1.1.3",
|
||||||
"vue3-popper": "1.5.0",
|
"vue3-popper": "1.5.0",
|
||||||
|
"vue3-storage": "0.1.11",
|
||||||
|
"vue3-tabs-component": "1.1.2",
|
||||||
"yaml-loader": "^0.6.0",
|
"yaml-loader": "^0.6.0",
|
||||||
"yaml2json": "1.0.2"
|
"yaml2json": "1.0.2"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ const query = async function(){
|
|||||||
// console.log(dns, ip)
|
// console.log(dns, ip)
|
||||||
geo = await getGeo(ip)
|
geo = await getGeo(ip)
|
||||||
|
|
||||||
console.log(geo, ip, dns)
|
// console.log(geo, ip, dns)
|
||||||
|
|
||||||
if(dns)
|
if(dns)
|
||||||
geo.dns = dns[dns.length-1]
|
geo.dns = dns[dns.length-1]
|
||||||
|
|||||||
42
src/components/FooterComponent.vue
Normal file
42
src/components/FooterComponent.vue
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
<template>
|
||||||
|
<section id="footer">
|
||||||
|
<span>Updated {{ refreshData.sinceLast }} ago</span>
|
||||||
|
<span><button @click="invalidate(true)">Update Now</button></span>
|
||||||
|
<span><input type="checkbox" id="checkbox" v-model="preferences.refresh" /><label for="">Refresh Automatically</label></span>
|
||||||
|
<span v-if="preferences.refresh"> Next refresh in: {{ refreshData.untilNext }}</span>
|
||||||
|
<span v-if="preferences.refresh">
|
||||||
|
Refresh Every
|
||||||
|
|
||||||
|
<input type="radio" id="1w" :value="1000*60*60*24*7" v-model="cacheExpiration" />
|
||||||
|
<label for="1w">1 Week</label>
|
||||||
|
|
||||||
|
<input type="radio" id="1d" :value="1000*60*60*24" v-model="cacheExpiration" />
|
||||||
|
<label for="1d">1 day</label>
|
||||||
|
|
||||||
|
<input type="radio" id="30m" :value="1000*60*30" v-model="cacheExpiration" />
|
||||||
|
<label for="30m">30 minutes</label>
|
||||||
|
|
||||||
|
<input type="radio" id="10m" :value="1000*60*10" v-model="cacheExpiration" />
|
||||||
|
<label for="10m">10 minutes</label>
|
||||||
|
|
||||||
|
<input type="radio" id="1m" :value="1000*60" v-model="cacheExpiration" />
|
||||||
|
<label for="1m">1 Minute</label>
|
||||||
|
</span>
|
||||||
|
</section>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default defineComponent({
|
||||||
|
name: 'FooterComponent',
|
||||||
|
components: {},
|
||||||
|
props : {
|
||||||
|
},
|
||||||
|
updated : {
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
preferences,
|
||||||
|
refreshData
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
@@ -92,8 +92,10 @@ export default {
|
|||||||
|
|
||||||
<style>
|
<style>
|
||||||
.leaflet-container {
|
.leaflet-container {
|
||||||
margin-top:37px;
|
margin:0;
|
||||||
|
padding:0;
|
||||||
height:250px !important;
|
height:250px !important;
|
||||||
|
width:100%;
|
||||||
}
|
}
|
||||||
.leaflet-control-zoom {
|
.leaflet-control-zoom {
|
||||||
display: none !important;
|
display: none !important;
|
||||||
|
|||||||
@@ -7,7 +7,6 @@
|
|||||||
:minZoom="zoom"
|
:minZoom="zoom"
|
||||||
:maxZoom="zoom"
|
:maxZoom="zoom"
|
||||||
:zoomControl="false"
|
:zoomControl="false"
|
||||||
style="height:50vh"
|
|
||||||
>
|
>
|
||||||
|
|
||||||
<l-tile-layer
|
<l-tile-layer
|
||||||
@@ -102,8 +101,7 @@ export default {
|
|||||||
.leaflet-container {
|
.leaflet-container {
|
||||||
margin:0;
|
margin:0;
|
||||||
height:250px !important;
|
height:250px !important;
|
||||||
width:1000%;
|
width:100%;
|
||||||
|
|
||||||
}
|
}
|
||||||
.leaflet-control-zoom {
|
.leaflet-control-zoom {
|
||||||
display: none !important;
|
display: none !important;
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
<template>
|
<template>
|
||||||
<nav class="menu">
|
<nav class="menu">
|
||||||
<ul>
|
<ul>
|
||||||
<li><a href="#">add relay</a></li>
|
<router-link :to="`/`" active-class="active">Home</router-link>
|
||||||
<li><a href="#">github</a></li>
|
<router-link :to="`/status`" active-class="active">Grouped</router-link>
|
||||||
|
<a href="https://github.com/dskvr/nostr-watch/edit/main/relays.yaml" target="_blank">Submit</a>
|
||||||
</ul>
|
</ul>
|
||||||
</nav>
|
</nav>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
274
src/components/RelayGroupedListComponent.vue
Normal file
274
src/components/RelayGroupedListComponent.vue
Normal file
@@ -0,0 +1,274 @@
|
|||||||
|
<template>
|
||||||
|
<tr :class="getHeadingClass()">
|
||||||
|
<vue-final-modal v-model="showModal" classes="modal-container" content-class="modal-content">
|
||||||
|
<div class="modal__content">
|
||||||
|
<span>
|
||||||
|
{{ queryJson(section) }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</vue-final-modal>
|
||||||
|
<td colspan="11">
|
||||||
|
<h2><span class="indicator badge">{{ query(section).length }}</span>{{ section }} <a @click="showModal=true" class="section-json" v-if="showJson">{...}</a></h2>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr :class="getHeadingClass()" v-if="query(section).length > 0">
|
||||||
|
<th class="table-column status-indicator"></th>
|
||||||
|
|
||||||
|
<th class="table-column relay"></th>
|
||||||
|
|
||||||
|
<th class="table-column verified">
|
||||||
|
<span class="verified-shape-wrapper">
|
||||||
|
<span class="shape verified"></span>
|
||||||
|
</span>
|
||||||
|
</th>
|
||||||
|
<th class="table-column location" v-tooltip:top.tooltip="Ping">
|
||||||
|
🌎
|
||||||
|
</th>
|
||||||
|
<th class="table-column latency" v-tooltip:top.tooltip="'Relay Latency on Read'">
|
||||||
|
⌛️
|
||||||
|
</th>
|
||||||
|
<th class="table-column connect" v-tooltip:top.tooltip="'Relay connection status'">
|
||||||
|
🔌
|
||||||
|
</th>
|
||||||
|
<th class="table-column read" v-tooltip:top.tooltip="'Relay read status'">
|
||||||
|
👁️🗨️
|
||||||
|
</th>
|
||||||
|
<th class="table-column write" v-tooltip:top.tooltip="'Relay write status'">
|
||||||
|
✏️
|
||||||
|
</th>
|
||||||
|
<th class="table-column info" v-tooltip:top.tooltip="'Additional information detected regarding the relay during processing'">
|
||||||
|
ℹ️
|
||||||
|
</th>
|
||||||
|
<!-- <th class="table-column nip nip-20" v-tooltip:top.tooltip="'Does the relay support NIP-20'">
|
||||||
|
<span>NIP-11</span>
|
||||||
|
</th> -->
|
||||||
|
</tr>
|
||||||
|
<tr v-for="relay in query(section)" :key="{relay}" :class="getResultClass(relay)" class="relay">
|
||||||
|
<RelaySingleComponent
|
||||||
|
:relay="relay"
|
||||||
|
:result="result[relay]"
|
||||||
|
:geo="geo[relay]"
|
||||||
|
:showColumns="showColumns"
|
||||||
|
:connection="connections[relay]"
|
||||||
|
/>
|
||||||
|
</tr>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
|
||||||
|
import { defineComponent} from 'vue'
|
||||||
|
import RelaySingleComponent from './RelaySingleComponent.vue'
|
||||||
|
import { VueFinalModal } from 'vue-final-modal'
|
||||||
|
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
name: 'RelayListComponent',
|
||||||
|
components: {
|
||||||
|
RelaySingleComponent,
|
||||||
|
VueFinalModal
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
showJson: {
|
||||||
|
type: Boolean,
|
||||||
|
default(){
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
section: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
default: ""
|
||||||
|
},
|
||||||
|
relays:{
|
||||||
|
type: Object,
|
||||||
|
default(){
|
||||||
|
return {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
result: {
|
||||||
|
type: Object,
|
||||||
|
default(){
|
||||||
|
return {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
geo: {
|
||||||
|
type: Object,
|
||||||
|
default(){
|
||||||
|
return {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
messages: {
|
||||||
|
type: Object,
|
||||||
|
default(){
|
||||||
|
return {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
alerts: {
|
||||||
|
type: Object,
|
||||||
|
default(){
|
||||||
|
return {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
connections: {
|
||||||
|
type: Object,
|
||||||
|
default(){
|
||||||
|
return {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
showColumns: {
|
||||||
|
type: Object,
|
||||||
|
default() {
|
||||||
|
return {
|
||||||
|
connectionStatuses: false,
|
||||||
|
nips: false,
|
||||||
|
geo: false,
|
||||||
|
additionalInfo: false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
grouping: {
|
||||||
|
type: Boolean,
|
||||||
|
default(){
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
showModal: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted(){
|
||||||
|
console.log('')
|
||||||
|
},
|
||||||
|
computed: {},
|
||||||
|
methods: {
|
||||||
|
getHeadingClass(){
|
||||||
|
return {
|
||||||
|
online: this.section != "offline",
|
||||||
|
public: this.section == "public",
|
||||||
|
offline: this.section == "offline",
|
||||||
|
restricted: this.section == "restricted"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
getResultClass (relay) {
|
||||||
|
return {
|
||||||
|
loaded: this.result?.[relay]?.state == 'complete',
|
||||||
|
online: this.section != "offline",
|
||||||
|
offline: this.section == "offline",
|
||||||
|
public: this.section == "public"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
query (aggregate) {
|
||||||
|
let unsorted,
|
||||||
|
sorted,
|
||||||
|
filterFn
|
||||||
|
|
||||||
|
filterFn = (relay) => this.grouping ? this.result?.[relay]?.aggregate == aggregate : true
|
||||||
|
|
||||||
|
unsorted = this.relays.filter(filterFn);
|
||||||
|
|
||||||
|
console.log('unsorted', unsorted)
|
||||||
|
|
||||||
|
console.log('isDone', this.isDone())
|
||||||
|
|
||||||
|
if(!this.isDone()) {
|
||||||
|
return unsorted
|
||||||
|
}
|
||||||
|
|
||||||
|
if (unsorted.length) {
|
||||||
|
sorted = unsorted.sort((relay1, relay2) => {
|
||||||
|
return this.result?.[relay1]?.latency.final - this.result?.[relay2]?.latency.final
|
||||||
|
})
|
||||||
|
console.log('sorted', sorted)
|
||||||
|
return sorted
|
||||||
|
}
|
||||||
|
|
||||||
|
return []
|
||||||
|
},
|
||||||
|
queryJson(aggregate){
|
||||||
|
const relays = this.query(aggregate)
|
||||||
|
const result = {}
|
||||||
|
result.relays = relays.map( relay => relay )
|
||||||
|
return JSON.stringify(result,null,'\t')
|
||||||
|
},
|
||||||
|
relaysTotal () {
|
||||||
|
return this.relays.length
|
||||||
|
},
|
||||||
|
relaysConnected () {
|
||||||
|
return Object.keys(this.result).length
|
||||||
|
},
|
||||||
|
relaysCompleted () {
|
||||||
|
let value = Object.entries(this.result).map((value) => { return value.state == 'complete' }).length
|
||||||
|
console.log('relaysCompleted', value)
|
||||||
|
return value
|
||||||
|
},
|
||||||
|
isDone(){
|
||||||
|
console.log('isDone()', this.relaysTotal(), '-', this.relaysCompleted(), '=', this.relaysTotal()-this.relaysCompleted() )
|
||||||
|
return this.relaysTotal()-this.relaysCompleted() == 0
|
||||||
|
},
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang='css' scoped>
|
||||||
|
.nip span {
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing:-1px;
|
||||||
|
font-size:12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-json {
|
||||||
|
font-size:13px;
|
||||||
|
color: #555;
|
||||||
|
cursor:pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
::v-deep(.modal-container) {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
::v-deep(.modal-content) {
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
max-height: 90%;
|
||||||
|
max-width:400px;
|
||||||
|
margin: 0 1rem;
|
||||||
|
padding: 1rem;
|
||||||
|
border: 1px solid #e2e8f0;
|
||||||
|
border-radius: 0.25rem;
|
||||||
|
background: #fff;
|
||||||
|
}
|
||||||
|
.modal__title {
|
||||||
|
margin: 0 2rem 0 0;
|
||||||
|
font-size: 1.5rem;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
.modal__content {
|
||||||
|
flex-grow: 1;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
.modal__action {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
flex-shrink: 0;
|
||||||
|
padding: 1rem 0 0;
|
||||||
|
}
|
||||||
|
.modal__close {
|
||||||
|
position: absolute;
|
||||||
|
top: 0.5rem;
|
||||||
|
right: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nip-11 a { cursor: pointer }
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.dark-mode div ::v-deep(.modal-content) {
|
||||||
|
border-color: #2d3748;
|
||||||
|
background-color: #1a202c;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -1,17 +1,17 @@
|
|||||||
<template>
|
<template>
|
||||||
<tr :class="getHeadingClass()">
|
<tr>
|
||||||
<vue-final-modal v-model="showModal" classes="modal-container" content-class="modal-content">
|
<vue-final-modal v-model="showModal" classes="modal-container" content-class="modal-content">
|
||||||
<div class="modal__content">
|
<div class="modal__content">
|
||||||
<span>
|
<span>
|
||||||
{{ queryJson(section) }}
|
{{ queryJson() }}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</vue-final-modal>
|
</vue-final-modal>
|
||||||
<td colspan="11">
|
<td colspan="11">
|
||||||
<h2><span class="indicator badge">{{ query(section).length }}</span>{{ section }} <a @click="showModal=true" class="section-json" v-if="showJson">{...}</a></h2>
|
<h2><span class="indicator badge">{{ this.relays.length }}</span>Relays <a @click="showModal=true" class="section-json" v-if="showJson">{...}</a></h2>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr :class="getHeadingClass()" v-if="query(section).length > 0">
|
<tr v-if="this.relays.length > 0">
|
||||||
<th class="table-column status-indicator"></th>
|
<th class="table-column status-indicator"></th>
|
||||||
|
|
||||||
<th class="table-column relay"></th>
|
<th class="table-column relay"></th>
|
||||||
@@ -43,12 +43,11 @@
|
|||||||
<span>NIP-11</span>
|
<span>NIP-11</span>
|
||||||
</th> -->
|
</th> -->
|
||||||
</tr>
|
</tr>
|
||||||
<tr v-for="relay in query(section)" :key="{relay}" :class="getResultClass(relay)" class="relay">
|
<tr v-for="relay in sortByLatency()" :key="{relay}" class="relay" :class="getResultClass(relay)">
|
||||||
<RelaySingleComponent
|
<RelaySingleComponent
|
||||||
:relay="relay"
|
:relay="relay"
|
||||||
:result="result[relay]"
|
:result="result[relay]"
|
||||||
:geo="geo[relay]"
|
:geo="geo[relay]"
|
||||||
:showColumns="showColumns"
|
|
||||||
:connection="connections[relay]"
|
:connection="connections[relay]"
|
||||||
/>
|
/>
|
||||||
</tr>
|
</tr>
|
||||||
@@ -74,11 +73,6 @@ export default defineComponent({
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
section: {
|
|
||||||
type: String,
|
|
||||||
required: true,
|
|
||||||
default: "public"
|
|
||||||
},
|
|
||||||
relays:{
|
relays:{
|
||||||
type: Object,
|
type: Object,
|
||||||
default(){
|
default(){
|
||||||
@@ -115,17 +109,6 @@ export default defineComponent({
|
|||||||
return {}
|
return {}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
showColumns: {
|
|
||||||
type: Object,
|
|
||||||
default() {
|
|
||||||
return {
|
|
||||||
connectionStatuses: false,
|
|
||||||
nips: false,
|
|
||||||
geo: false,
|
|
||||||
additionalInfo: false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
@@ -137,61 +120,91 @@ export default defineComponent({
|
|||||||
},
|
},
|
||||||
computed: {},
|
computed: {},
|
||||||
methods: {
|
methods: {
|
||||||
getHeadingClass(){
|
// getHeadingClass(){
|
||||||
return {
|
// return {
|
||||||
online: this.section != "offline",
|
// online: this.section != "offline",
|
||||||
public: this.section == "public",
|
// public: this.section == "public",
|
||||||
offline: this.section == "offline",
|
// offline: this.section == "offline",
|
||||||
restricted: this.section == "restricted"
|
// restricted: this.section == "restricted"
|
||||||
}
|
// }
|
||||||
},
|
// },
|
||||||
getResultClass (relay) {
|
getResultClass (relay) {
|
||||||
return {
|
return {
|
||||||
loaded: this.result?.[relay]?.state == 'complete',
|
loaded: this.result?.[relay]?.state == 'complete'
|
||||||
online: this.section != "offline",
|
|
||||||
offline: this.section == "offline",
|
|
||||||
public: this.section == "public"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
query (aggregate) {
|
sort_by_latency(ascending) {
|
||||||
let unordered,
|
const self = this
|
||||||
filterFn
|
return function (a, b) {
|
||||||
|
// equal items sort equally
|
||||||
|
if (self.result?.[a]?.latency.final === self.result?.[b]?.latency.final) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
filterFn = (relay) => this.result?.[relay]?.aggregate == aggregate
|
// nulls sort after anything else
|
||||||
|
if (self.result?.[a]?.latency.final === null) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
if (self.result?.[b]?.latency.final === null) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
unordered = this.relays.filter(filterFn);
|
// otherwise, if we're ascending, lowest sorts first
|
||||||
|
if (ascending) {
|
||||||
|
return self.result?.[a]?.latency.final - self.result?.[b]?.latency.final;
|
||||||
|
}
|
||||||
|
|
||||||
if(!this.isDone()) {
|
// if descending, highest sorts first
|
||||||
return unordered
|
return self.result?.[b]?.latency.final-self.result?.[a]?.latency.final;
|
||||||
}
|
};
|
||||||
|
},
|
||||||
|
sortByLatency () {
|
||||||
|
let unsorted
|
||||||
|
|
||||||
if (unordered.length) {
|
unsorted = this.relays;
|
||||||
return unordered.sort((relay1, relay2) => {
|
|
||||||
return this.result?.[relay1]?.latency.final - this.result?.[relay2]?.latency.final
|
console.log('unsorted', unsorted)
|
||||||
})
|
|
||||||
}
|
// console.log('isDone', this.isDone())
|
||||||
|
|
||||||
|
// if(!this.isDone())
|
||||||
|
// return unsorted
|
||||||
|
|
||||||
|
if (unsorted.length)
|
||||||
|
return unsorted.sort(this.sort_by_latency(true))
|
||||||
|
|
||||||
return []
|
return []
|
||||||
},
|
},
|
||||||
queryJson(aggregate){
|
queryJson(){
|
||||||
const relays = this.query(aggregate)
|
const result = { relays: this.relays }
|
||||||
const result = {}
|
|
||||||
result.relays = relays.map( relay => relay )
|
|
||||||
return JSON.stringify(result,null,'\t')
|
return JSON.stringify(result,null,'\t')
|
||||||
},
|
},
|
||||||
relaysTotal () {
|
relaysTotal () {
|
||||||
return this.relays.length
|
return this.relays.length //TODO: Figure out WHY?
|
||||||
},
|
},
|
||||||
|
|
||||||
relaysConnected () {
|
relaysConnected () {
|
||||||
return Object.keys(this.result).length
|
return Object.entries(this.result).length
|
||||||
},
|
},
|
||||||
relaysCompleted () {
|
|
||||||
let value = Object.entries(this.result).length
|
relaysComplete () {
|
||||||
return value
|
if(!Object.keys(this.results).length) return 0
|
||||||
|
return this.relays.filter(relay => this.results?.[relay]?.state == 'complete').length
|
||||||
},
|
},
|
||||||
|
|
||||||
|
sha1 (message) {
|
||||||
|
const hash = crypto.createHash('sha1').update(JSON.stringify(message)).digest('hex')
|
||||||
|
return hash
|
||||||
|
},
|
||||||
|
|
||||||
isDone(){
|
isDone(){
|
||||||
return this.relaysTotal()-this.relaysCompleted() == 0
|
console.log('is done', this.relaysTotal(), '-', this.relaysComplete(), '<=', 0, this.relaysTotal()-this.relaysComplete() <= 0)
|
||||||
|
return this.relaysTotal()-this.relaysComplete() <= 0
|
||||||
},
|
},
|
||||||
|
|
||||||
|
loadingComplete(){
|
||||||
|
return this.isDone() ? 'loaded' : ''
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -1,24 +1,23 @@
|
|||||||
<template>
|
<template>
|
||||||
<td class="status-indicator" :key="generateKey(relay, 'aggregate')">
|
<td class="status-indicator" :key="generateKey(relay, 'aggregate')">
|
||||||
<span :class="result.aggregate" class="aggregate indicator">
|
<span :class="result?.aggregate" class="aggregate indicator">
|
||||||
<span></span>
|
<span></span>
|
||||||
<span></span>
|
<span></span>
|
||||||
</span>
|
</span>
|
||||||
</td>
|
</td>
|
||||||
|
|
||||||
<td class="relay left-align relay-url">
|
<td class="relay left-align relay-url">
|
||||||
<router-link :to="`/${relayClean(relay)}`" active-class="active">{{ relay }}</router-link>
|
<router-link :to="`/relay/${relayClean(relay)}`" active-class="active">{{ relay }}</router-link>
|
||||||
<!-- <span @click="copy(relay)" v-tooltip:top.tooltip="'Click to copy'">{{ relay }}</span> -->
|
|
||||||
</td>
|
</td>
|
||||||
|
|
||||||
<td class="verified">
|
<td class="verified" v-if="result?.identities">
|
||||||
<span v-tooltip:top.tooltip="identityList()"> <span class="verified-shape-wrapper" v-if="Object.entries(result.identities).length"><span class="shape verified"></span></span></span>
|
<span v-tooltip:top.tooltip="identityList()"> <span class="verified-shape-wrapper" v-if="Object.entries(result?.identities).length"><span class="shape verified"></span></span></span>
|
||||||
</td>
|
</td>
|
||||||
|
|
||||||
<td class="location">{{ getFlag() }}</td>
|
<td class="location">{{ getFlag() }}</td>
|
||||||
|
|
||||||
<td class="latency">
|
<td class="latency">
|
||||||
<span>{{ result.latency.final }}<span v-if="result.check.latency">ms</span></span>
|
<span>{{ result?.latency.final }}<span v-if="result?.check.latency">ms</span></span>
|
||||||
</td>
|
</td>
|
||||||
|
|
||||||
<td class="connect" :key="generateKey(relay, 'check.connect')">
|
<td class="connect" :key="generateKey(relay, 'check.connect')">
|
||||||
@@ -34,8 +33,8 @@
|
|||||||
</td>
|
</td>
|
||||||
|
|
||||||
<td class="info">
|
<td class="info">
|
||||||
<ul v-if="result.observations && result.observations.length">
|
<ul v-if="result?.observations && result?.observations.length">
|
||||||
<li class="observation" v-for="(alert) in result.observations" :key="generateKey(relay, alert.description)">
|
<li class="observation" v-for="(alert) in result?.observations" :key="generateKey(relay, alert.description)">
|
||||||
<span v-tooltip:top.tooltip="alert.description" :class="alert.type" v-if="alert.type == 'notice'">✉️</span>
|
<span v-tooltip:top.tooltip="alert.description" :class="alert.type" v-if="alert.type == 'notice'">✉️</span>
|
||||||
<span v-tooltip:top.tooltip="alert.description" :class="alert.type" v-if="alert.type == 'caution'">⚠️</span>
|
<span v-tooltip:top.tooltip="alert.description" :class="alert.type" v-if="alert.type == 'caution'">⚠️</span>
|
||||||
</li>
|
</li>
|
||||||
@@ -43,18 +42,18 @@
|
|||||||
</td>
|
</td>
|
||||||
|
|
||||||
<!-- <td class="nip nip-11">
|
<!-- <td class="nip nip-11">
|
||||||
<a v-if="result.info" @click="showModal=true">✅ </a>
|
<a v-if="result?.info" @click="showModal=true">✅ </a>
|
||||||
</td> -->
|
</td> -->
|
||||||
|
|
||||||
<vue-final-modal v-model="showModal" classes="modal-container" content-class="modal-content">
|
<vue-final-modal v-model="showModal" classes="modal-container" content-class="modal-content">
|
||||||
<div class="modal__title">
|
<div class="modal__title">
|
||||||
<span>{{ result.info?.name }}</span>
|
<span>{{ result?.info?.name }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal__content">
|
<div class="modal__content">
|
||||||
<div v-if="result.info?.description">
|
<div v-if="result?.info?.description">
|
||||||
{{ result.info?.description }} <br/>
|
{{ result?.info?.description }} <br/>
|
||||||
<strong v-if="result.info?.pubkey">Public Key:</strong> {{ result.info?.pubkey }} <br/>
|
<strong v-if="result?.info?.pubkey">Public Key:</strong> {{ result?.info?.pubkey }} <br/>
|
||||||
<strong v-if="result.info?.contact">Contact:</strong> <SafeMail :email="result.info?.contact" v-if="result.info?.contact" />
|
<strong v-if="result?.info?.contact">Contact:</strong> <SafeMail :email="result?.info?.contact" v-if="result?.info?.contact" />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<h4>Status</h4>
|
<h4>Status</h4>
|
||||||
@@ -66,12 +65,12 @@
|
|||||||
</div>
|
</div>
|
||||||
<h4>Relay Info</h4>
|
<h4>Relay Info</h4>
|
||||||
<ul>
|
<ul>
|
||||||
<li><strong>Software:</strong> {{ result.info?.software }} </li>
|
<li><strong>Software:</strong> {{ result?.info?.software }} </li>
|
||||||
<li><strong>Version</strong>: {{ result.info?.version }} </li>
|
<li><strong>Version</strong>: {{ result?.info?.version }} </li>
|
||||||
</ul>
|
</ul>
|
||||||
<h4>NIP Support</h4>
|
<h4>NIP Support</h4>
|
||||||
<ul>
|
<ul>
|
||||||
<li v-for="(nip) in result.info?.supported_nips" :key="`${relay}_${nip}`">
|
<li v-for="(nip) in result?.info?.supported_nips" :key="`${relay}_${nip}`">
|
||||||
<a :href="nipLink(nip)" target="_blank">{{ nipFormatted(nip) }}</a>
|
<a :href="nipLink(nip)" target="_blank">{{ nipFormatted(nip) }}</a>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
@@ -168,14 +167,16 @@ export default defineComponent({
|
|||||||
identityList () {
|
identityList () {
|
||||||
let string = '',
|
let string = '',
|
||||||
extraString = '',
|
extraString = '',
|
||||||
users = Object.entries(this.result.identities),
|
users = Object.entries(this.result?.identities),
|
||||||
count = 0
|
count = 0
|
||||||
|
|
||||||
console.log(this.result.uri, 'admin', this.result.identities.serverAdmin)
|
// if(!this.result?.identities) return
|
||||||
|
|
||||||
if(this.result.identities) {
|
console.log(this.result?.uri, 'admin', this.result?.identities.serverAdmin, this.result.info)
|
||||||
if(this.result.identities.serverAdmin) {
|
|
||||||
string = `Relay has registered an administrator pubkey: ${this.result.identities.serverAdmin}. `
|
if(this.result?.identities) {
|
||||||
|
if(this.result?.identities.serverAdmin) {
|
||||||
|
string = `Relay has registered an administrator pubkey: ${this.result?.identities.serverAdmin}. `
|
||||||
extraString = "Additionally, "
|
extraString = "Additionally, "
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -277,4 +278,9 @@ td.verified span {
|
|||||||
border-color: #2d3748;
|
border-color: #2d3748;
|
||||||
background-color: #1a202c;
|
background-color: #1a202c;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.restricted.aggregate.indicator {
|
||||||
|
position:relative;
|
||||||
|
left:-7px;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -213,6 +213,11 @@ export default defineComponent({
|
|||||||
return Object.entries(this.result).length
|
return Object.entries(this.result).length
|
||||||
},
|
},
|
||||||
|
|
||||||
|
relaysComplete () {
|
||||||
|
if(!Object.keys(this.results).length) return 0
|
||||||
|
return this.relays.filter(relay => this.results?.[relay]?.state == 'complete').length
|
||||||
|
},
|
||||||
|
|
||||||
sha1 (message) {
|
sha1 (message) {
|
||||||
const hash = crypto.createHash('sha1').update(JSON.stringify(message)).digest('hex')
|
const hash = crypto.createHash('sha1').update(JSON.stringify(message)).digest('hex')
|
||||||
// //console.log(message, ':', hash)
|
// //console.log(message, ':', hash)
|
||||||
@@ -220,7 +225,7 @@ export default defineComponent({
|
|||||||
},
|
},
|
||||||
|
|
||||||
isDone(){
|
isDone(){
|
||||||
return this.relaysTotal()-this.relaysConnected() == 0
|
return this.relaysTotal()-this.relaysComplete() == 0
|
||||||
},
|
},
|
||||||
|
|
||||||
loadingComplete(){
|
loadingComplete(){
|
||||||
|
|||||||
@@ -1,15 +1,20 @@
|
|||||||
import { createApp } from 'vue'
|
import { createApp } from 'vue'
|
||||||
import App from './App.vue'
|
import App from './App.vue'
|
||||||
|
import Vue3Storage from "vue3-storage";
|
||||||
|
|
||||||
import router from './router'
|
import router from './router'
|
||||||
import "./styles/main.scss"
|
import "./styles/main.scss"
|
||||||
import directives from "./directives/"
|
import directives from "./directives/"
|
||||||
import titleMixin from './mixins/titleMixin'
|
import titleMixin from './mixins/titleMixin'
|
||||||
|
import {Tabs, Tab} from 'vue3-tabs-component';
|
||||||
|
|
||||||
const app = createApp(App)
|
const app = createApp(App)
|
||||||
app
|
|
||||||
.use(router)
|
.use(router)
|
||||||
|
.use(Vue3Storage, { namespace: "nostrwatch_" })
|
||||||
|
.component('tabs', Tabs)
|
||||||
|
.component('tab', Tab)
|
||||||
.mixin(titleMixin)
|
.mixin(titleMixin)
|
||||||
|
|
||||||
directives(app);
|
directives(app);
|
||||||
|
|
||||||
app.mount('#app')
|
app.mount('#app')
|
||||||
343
src/pages/ByStatus.vue
Normal file
343
src/pages/ByStatus.vue
Normal file
@@ -0,0 +1,343 @@
|
|||||||
|
<template>
|
||||||
|
<!-- <NavComponent /> -->
|
||||||
|
<LeafletComponent
|
||||||
|
:geo="geo"
|
||||||
|
:result="result"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div id="wrapper" :class="loadingComplete()">
|
||||||
|
<row container :gutter="12">
|
||||||
|
<column :xs="12" :md="12" :lg="12" class="title-card">
|
||||||
|
<h1>nostr.watch<sup>{{version}}</sup></h1>
|
||||||
|
</column>
|
||||||
|
</row>
|
||||||
|
<row container :gutter="12">
|
||||||
|
<column :xs="12" :md="12" :lg="12" class="title-card">
|
||||||
|
<NavComponent />
|
||||||
|
</column>
|
||||||
|
</row>
|
||||||
|
<div>
|
||||||
|
<table>
|
||||||
|
<RelayGroupedListComponent
|
||||||
|
section="public"
|
||||||
|
:relays="relays"
|
||||||
|
:result="result"
|
||||||
|
:geo="geo"
|
||||||
|
:messages="messages"
|
||||||
|
:alerts="alerts"
|
||||||
|
:connections="connections"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<RelayGroupedListComponent
|
||||||
|
section="restricted"
|
||||||
|
:relays="relays"
|
||||||
|
:result="result"
|
||||||
|
:geo="geo"
|
||||||
|
:messages="messages"
|
||||||
|
:alerts="alerts"
|
||||||
|
:connections="connections"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<RelayGroupedListComponent
|
||||||
|
section="offline"
|
||||||
|
:relays="relays"
|
||||||
|
:result="result"
|
||||||
|
:geo="geo"
|
||||||
|
:messages="messages"
|
||||||
|
:alerts="alerts"
|
||||||
|
:connections="connections"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<RelayGroupedListComponent
|
||||||
|
section="processing"
|
||||||
|
:relays="relays"
|
||||||
|
:result="result"
|
||||||
|
:messages="messages"
|
||||||
|
:alerts="alerts"
|
||||||
|
:connections="connections"
|
||||||
|
:showJson="false"
|
||||||
|
/>
|
||||||
|
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<!-- <row container :gutter="12">
|
||||||
|
<column :xs="12" :md="12" :lg="12">
|
||||||
|
|
||||||
|
</column>
|
||||||
|
</row> -->
|
||||||
|
|
||||||
|
<row container :gutter="12">
|
||||||
|
<column :xs="12" :md="12" :lg="12">
|
||||||
|
<div class="block">
|
||||||
|
<table>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</column>
|
||||||
|
</row>
|
||||||
|
|
||||||
|
<!-- <row container :gutter="12">
|
||||||
|
<column :xs="12" :md="12" :lg="12" class="processing-card loading">
|
||||||
|
<span v-if="(relaysTotal()-relaysConnected()>0)">Processing {{ relaysConnected() }}/{{ relaysTotal() }}</span>
|
||||||
|
</column>
|
||||||
|
</row> -->
|
||||||
|
|
||||||
|
<span class="credit"><a href="http://sandwich.farm">Another 🥪 by sandwich.farm</a>, built with <a href="https://github.com/jb55/nostr-js">nostr-js</a> and <a href="https://github.com/dskvr/nostr-relay-inspector">nostr-relay-inspector</a>, inspired by <a href="https://github.com/fiatjaf/nostr-relay-registry">nostr-relay-registry</a></span>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
|
||||||
|
|
||||||
|
import { defineComponent} from 'vue'
|
||||||
|
import { useStorage } from "vue3-storage";
|
||||||
|
// import { CallbackResult } from "vue3-storage/dist/lib/types";
|
||||||
|
|
||||||
|
import crypto from "crypto"
|
||||||
|
import { Row, Column } from 'vue-grid-responsive';
|
||||||
|
import { Inspector, InspectorObservation, InspectorResult } from 'nostr-relay-inspector'
|
||||||
|
|
||||||
|
// import RelayListComponent from '../components/RelayListComponent.vue'
|
||||||
|
import RelayGroupedListComponent from '../components/RelayGroupedListComponent.vue'
|
||||||
|
import LeafletComponent from '../components/LeafletComponent.vue'
|
||||||
|
import NavComponent from '../components/NavComponent.vue'
|
||||||
|
|
||||||
|
import { version } from '../../package.json'
|
||||||
|
import { relays } from '../../relays.yaml'
|
||||||
|
import { geo } from '../../geo.yaml'
|
||||||
|
import { messages as RELAY_MESSAGES, codes as RELAY_CODES } from '../../codes.yaml'
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
title: "nostr.watch registry & network status",
|
||||||
|
name: 'RelayTableComponent',
|
||||||
|
components: {
|
||||||
|
Row,
|
||||||
|
Column,
|
||||||
|
// RelayListComponent,
|
||||||
|
RelayGroupedListComponent,
|
||||||
|
LeafletComponent,
|
||||||
|
NavComponent
|
||||||
|
},
|
||||||
|
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
relays,
|
||||||
|
result: {},
|
||||||
|
messages: {},
|
||||||
|
connections: {},
|
||||||
|
nips: {},
|
||||||
|
alerts: {},
|
||||||
|
timeouts: {},
|
||||||
|
intervals: {},
|
||||||
|
lastPing: Date.now(),
|
||||||
|
nextPing: Date.now() + (30*60*1000),
|
||||||
|
count: 0,
|
||||||
|
storage: null,
|
||||||
|
geo,
|
||||||
|
version: version,
|
||||||
|
hasStorage: false,
|
||||||
|
lastUpdate: null,
|
||||||
|
cacheExpiration: (30*60*1000),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
updated() {
|
||||||
|
Object.keys(this.timeouts).forEach(timeout => clearTimeout(this.timeouts[timeout]))
|
||||||
|
Object.keys(this.intervals).forEach(interval => clearInterval(this.intervals[interval]))
|
||||||
|
},
|
||||||
|
|
||||||
|
async mounted() {
|
||||||
|
this.relays.forEach(relay => {
|
||||||
|
this.result[relay] = structuredClone(InspectorResult)
|
||||||
|
})
|
||||||
|
|
||||||
|
this.storage = useStorage()
|
||||||
|
this.storage.setStorageSync('relays', relays)
|
||||||
|
this.lastUpdate = this.storage.getStorageSync('lastUpdate')
|
||||||
|
|
||||||
|
this.relays.forEach(async relay => {
|
||||||
|
this.result[relay] = this.storage.getStorageSync(relay)
|
||||||
|
})
|
||||||
|
|
||||||
|
console.log('meow', this.result)
|
||||||
|
|
||||||
|
if(Object.keys(this.result).length)
|
||||||
|
this.hasStorage = true
|
||||||
|
|
||||||
|
if(this.isExpired())
|
||||||
|
this.relays.forEach(async relay => await this.check(relay) )
|
||||||
|
|
||||||
|
console.log('zzz', this.result)
|
||||||
|
|
||||||
|
// this.relays.forEach( relay => {
|
||||||
|
// // this.result[relay].state = 'complete'
|
||||||
|
// // this.setAggregateResult(relay)
|
||||||
|
// // this.adjustResult(relay)
|
||||||
|
// console.log('boom', relay, this.result[relay])
|
||||||
|
// })
|
||||||
|
// }
|
||||||
|
// console.log(`zing ${Date.now()} - ${this.lastUpdate} = ${Date.now()-this.lastUpdate} > ${60*1000}`)
|
||||||
|
return true
|
||||||
|
},
|
||||||
|
|
||||||
|
// head: {
|
||||||
|
// // creates a title tag in header.
|
||||||
|
// base () {
|
||||||
|
// return {
|
||||||
|
// href: "/"
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// },
|
||||||
|
|
||||||
|
computed: {},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
|
||||||
|
isExpired(){
|
||||||
|
return typeof this.lastUpdate === 'undefined' || Date.now() - this.lastUpdate > this.cacheExpiration
|
||||||
|
},
|
||||||
|
|
||||||
|
saveState(relay){
|
||||||
|
this.storage
|
||||||
|
.setStorage({
|
||||||
|
key: relay,
|
||||||
|
data: this.result[relay]
|
||||||
|
})
|
||||||
|
.then(successCallback => {
|
||||||
|
console.log(successCallback.errMsg);
|
||||||
|
})
|
||||||
|
.catch(failCallback => {
|
||||||
|
console.log(failCallback.errMsg);
|
||||||
|
})
|
||||||
|
|
||||||
|
this.storage
|
||||||
|
.setStorage({
|
||||||
|
key: "lastUpdate",
|
||||||
|
data: Date.now()
|
||||||
|
})
|
||||||
|
.then(successCallback => {
|
||||||
|
console.log(successCallback.errMsg);
|
||||||
|
this.lastUpdate = Date.now()
|
||||||
|
})
|
||||||
|
.catch(failCallback => {
|
||||||
|
console.log(failCallback.errMsg);
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
resetState(){
|
||||||
|
this.relays.forEach(relay=>{
|
||||||
|
this.storage.removeStorage(relay)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
async check(relay){
|
||||||
|
return new Promise( (resolve, reject) => {
|
||||||
|
const opts = {
|
||||||
|
checkLatency: true,
|
||||||
|
setIP: false,
|
||||||
|
setGeo: false,
|
||||||
|
debug: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
let inspect = new Inspector(relay, opts)
|
||||||
|
.on('run', (result) => {
|
||||||
|
result
|
||||||
|
// result.aggregate = 'processing'
|
||||||
|
})
|
||||||
|
.on('open', (e, result) => {
|
||||||
|
this.result[relay] = result
|
||||||
|
})
|
||||||
|
.on('complete', (instance) => {
|
||||||
|
this.result[relay] = Object.assign(this.result[relay], instance.result)
|
||||||
|
this.messages[relay] = instance.inbox
|
||||||
|
// this.setFlag(relay)
|
||||||
|
this.setAggregateResult(relay)
|
||||||
|
// this.adjustResult(relay)
|
||||||
|
this.saveState(relay)
|
||||||
|
resolve(this.result[relay])
|
||||||
|
|
||||||
|
})
|
||||||
|
.on('notice', (notice) => {
|
||||||
|
const hash = this.sha1(notice)
|
||||||
|
let message_obj = RELAY_MESSAGES[hash]
|
||||||
|
let code_obj = RELAY_CODES[message_obj.code]
|
||||||
|
|
||||||
|
let response_obj = {...message_obj, ...code_obj}
|
||||||
|
|
||||||
|
this.result[relay].observations.push( new InspectorObservation('notice', response_obj.code, response_obj.description, response_obj.relates_to) )
|
||||||
|
})
|
||||||
|
.on('close', () => {})
|
||||||
|
.on('error', () => {
|
||||||
|
reject(this.result[relay])
|
||||||
|
})
|
||||||
|
.run()
|
||||||
|
|
||||||
|
this.connections[relay] = inspect
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
recheck(relay){
|
||||||
|
const inspect = this.connections[relay]
|
||||||
|
inspect.checkLatency()
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
// adjustResult (relay) {
|
||||||
|
// this.result[relay].observations.forEach( observation => {
|
||||||
|
// if (observation.code == "BLOCKS_WRITE_STATUS_CHECK") {
|
||||||
|
// this.result[relay].check.write = false
|
||||||
|
// this.result[relay].aggregate = 'public'
|
||||||
|
// }
|
||||||
|
// })
|
||||||
|
// },
|
||||||
|
|
||||||
|
setAggregateResult (relay) {
|
||||||
|
let aggregateTally = 0
|
||||||
|
aggregateTally += this.result?.[relay]?.check.connect ? 1 : 0
|
||||||
|
aggregateTally += this.result?.[relay]?.check.read ? 1 : 0
|
||||||
|
aggregateTally += this.result?.[relay]?.check.write ? 1 : 0
|
||||||
|
if (aggregateTally == 3) {
|
||||||
|
this.result[relay].aggregate = 'public'
|
||||||
|
}
|
||||||
|
else if (aggregateTally == 0) {
|
||||||
|
this.result[relay].aggregate = 'offline'
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this.result[relay].aggregate = 'restricted'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
relaysTotal () {
|
||||||
|
return this.relays.length //TODO: Figure out WHY?
|
||||||
|
},
|
||||||
|
|
||||||
|
relaysConnected () {
|
||||||
|
return Object.entries(this.result).filter(result => result.state == 'complete').length
|
||||||
|
},
|
||||||
|
|
||||||
|
sha1 (message) {
|
||||||
|
const hash = crypto.createHash('sha1').update(JSON.stringify(message)).digest('hex')
|
||||||
|
return hash
|
||||||
|
},
|
||||||
|
|
||||||
|
isDone(){
|
||||||
|
return this.relaysTotal()-this.relaysConnected() <= 0
|
||||||
|
},
|
||||||
|
|
||||||
|
loadingComplete(){
|
||||||
|
return this.isDone() ? 'loaded' : ''
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
})
|
||||||
|
</script>
|
||||||
@@ -1,101 +1,104 @@
|
|||||||
<template>
|
<template>
|
||||||
<!-- <NavComponent /> -->
|
<!-- <NavComponent /> -->
|
||||||
<div id="wrapper" :class="loadingComplete()">
|
<LeafletComponent
|
||||||
|
:geo="geo"
|
||||||
|
:result="result"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div id="wrapper" :class="loadingComplete()">
|
||||||
|
|
||||||
<row container :gutter="12">
|
<row container :gutter="12">
|
||||||
<column :xs="12" :md="12" :lg="12" class="title-card">
|
<column :xs="12" :md="12" :lg="12" class="title-card">
|
||||||
<h1>nostr.watch<sup>{{version}}</sup></h1>
|
<h1>nostr.watch<sup>{{version}}</sup></h1>
|
||||||
</column>
|
</column>
|
||||||
</row>
|
</row>
|
||||||
|
|
||||||
<row container :gutter="12">
|
<row container :gutter="12">
|
||||||
<column :xs="12" :md="12" :lg="12">
|
<column :xs="12" :md="12" :lg="12" class="title-card">
|
||||||
<LeafletComponent
|
<NavComponent />
|
||||||
:geo="geo"
|
|
||||||
:result="result"
|
|
||||||
/>
|
|
||||||
</column>
|
</column>
|
||||||
</row>
|
</row>
|
||||||
|
|
||||||
<row container :gutter="12">
|
<row container :gutter="12">
|
||||||
<column :xs="12" :md="12" :lg="12">
|
<column :xs="12" :md="12" :lg="12" class="title-card">
|
||||||
<div class="block">
|
<table>
|
||||||
<table>
|
<RelayListComponent
|
||||||
|
:relays="relays"
|
||||||
<RelayListComponent
|
:result="result"
|
||||||
section="public"
|
:geo="geo"
|
||||||
:relays="relays"
|
:messages="messages"
|
||||||
:result="result"
|
:alerts="alerts"
|
||||||
:geo="geo"
|
:connections="connections"
|
||||||
:messages="messages"
|
/>
|
||||||
:alerts="alerts"
|
</table>
|
||||||
:connections="connections"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<RelayListComponent
|
|
||||||
section="restricted"
|
|
||||||
:relays="relays"
|
|
||||||
:result="result"
|
|
||||||
:geo="geo"
|
|
||||||
:messages="messages"
|
|
||||||
:alerts="alerts"
|
|
||||||
:connections="connections"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<RelayListComponent
|
|
||||||
section="offline"
|
|
||||||
:relays="relays"
|
|
||||||
:result="result"
|
|
||||||
:geo="geo"
|
|
||||||
:messages="messages"
|
|
||||||
:alerts="alerts"
|
|
||||||
:connections="connections"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<RelayListComponent
|
|
||||||
section="processing"
|
|
||||||
:relays="relays"
|
|
||||||
:result="result"
|
|
||||||
:messages="messages"
|
|
||||||
:alerts="alerts"
|
|
||||||
:connections="connections"
|
|
||||||
:showJson="false"
|
|
||||||
/>
|
|
||||||
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</column>
|
</column>
|
||||||
</row>
|
</row>
|
||||||
|
|
||||||
<row container :gutter="12">
|
<row container :gutter="12" v-if="(relaysTotal()-relaysConnected()>0)">
|
||||||
<column :xs="12" :md="12" :lg="12" class="processing-card loading">
|
<column :xs="12" :md="12" :lg="12" class="processing-card loading">
|
||||||
<span v-if="(relaysTotal()-relaysConnected()>0)">Processing {{ relaysConnected() }}/{{ relaysTotal() }}</span>
|
<span>Processing {{ relaysConnected() }}/{{ relaysTotal() }}</span>
|
||||||
</column>
|
</column>
|
||||||
</row>
|
</row>
|
||||||
|
|
||||||
<span class="credit"><a href="http://sandwich.farm">Another 🥪 by sandwich.farm</a>, built with <a href="https://github.com/jb55/nostr-js">nostr-js</a> and <a href="https://github.com/dskvr/nostr-relay-inspector">nostr-relay-inspector</a>, inspired by <a href="https://github.com/fiatjaf/nostr-relay-registry">nostr-relay-registry</a></span>
|
<section id="footer">
|
||||||
|
<span>Updated {{ refreshData.sinceLast }} ago</span>
|
||||||
|
<span><button @click="invalidate(true)">Update Now</button></span>
|
||||||
|
<span><input type="checkbox" id="checkbox" v-model="preferences.refresh" /><label for="">Refresh Automatically</label></span>
|
||||||
|
<span v-if="preferences.refresh"> Next refresh in: {{ refreshData.untilNext }}</span>
|
||||||
|
<span v-if="preferences.refresh">
|
||||||
|
Refresh Every
|
||||||
|
|
||||||
|
<input type="radio" id="1w" :value="1000*60*60*24*7" v-model="cacheExpiration" />
|
||||||
|
<label for="1w">1 Week</label>
|
||||||
|
|
||||||
|
<input type="radio" id="1d" :value="1000*60*60*24" v-model="cacheExpiration" />
|
||||||
|
<label for="1d">1 day</label>
|
||||||
|
|
||||||
|
<input type="radio" id="30m" :value="1000*60*30" v-model="cacheExpiration" />
|
||||||
|
<label for="30m">30 minutes</label>
|
||||||
|
|
||||||
|
<input type="radio" id="10m" :value="1000*60*10" v-model="cacheExpiration" />
|
||||||
|
<label for="10m">10 minutes</label>
|
||||||
|
|
||||||
|
<input type="radio" id="1m" :value="1000*60" v-model="cacheExpiration" />
|
||||||
|
<label for="1m">1 Minute</label>
|
||||||
|
</span>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<span class="credit"><a href="http://sandwich.farm">Another 🥪 by sandwich.farm</a>, built with <a href="https://github.com/jb55/nostr-js">nostr-js</a> and <a href="https://github.com/dskvr/nostr-relay-inspector">nostr-relay-inspector</a>, inspired by <a href="https://github.com/fiatjaf/nostr-relay-registry">nostr-relay-registry</a></span>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { defineComponent} from 'vue'
|
|
||||||
import RelayListComponent from '../components/RelayListComponent.vue'
|
|
||||||
import LeafletComponent from '../components/LeafletComponent.vue'
|
|
||||||
// import NavComponent from './NavComponent.vue'
|
|
||||||
|
|
||||||
|
|
||||||
|
import { defineComponent, reactive} from 'vue'
|
||||||
|
import { useStorage } from "vue3-storage";
|
||||||
|
// import { CallbackResult } from "vue3-storage/dist/lib/types";
|
||||||
|
|
||||||
|
import crypto from "crypto"
|
||||||
import { Row, Column } from 'vue-grid-responsive';
|
import { Row, Column } from 'vue-grid-responsive';
|
||||||
|
import { Inspector, InspectorObservation } from 'nostr-relay-inspector'
|
||||||
|
// import { Inspector, InspectorObservation } from '../../lib/nostr-relay-inspector'
|
||||||
|
|
||||||
|
import RelayListComponent from '../components/RelayListComponent.vue'
|
||||||
|
// import RelayGroupedListComponent from '../components/RelayGroupedListComponent.vue'
|
||||||
|
import LeafletComponent from '../components/LeafletComponent.vue'
|
||||||
|
import NavComponent from '../components/NavComponent.vue'
|
||||||
|
|
||||||
import { version } from '../../package.json'
|
import { version } from '../../package.json'
|
||||||
|
|
||||||
import { relays } from '../../relays.yaml'
|
import { relays } from '../../relays.yaml'
|
||||||
import { geo } from '../../geo.yaml'
|
import { geo } from '../../geo.yaml'
|
||||||
import { messages as RELAY_MESSAGES, codes as RELAY_CODES } from '../../codes.yaml'
|
import { messages as RELAY_MESSAGES, codes as RELAY_CODES } from '../../codes.yaml'
|
||||||
|
|
||||||
import { Inspector, InspectorObservation } from 'nostr-relay-inspector'
|
import { timeSince } from '../utils'
|
||||||
// import { Inspector, InspectorObservation } from '../../lib/nostr-relay-inspector'
|
|
||||||
|
|
||||||
import crypto from "crypto"
|
reactive
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
title: "nostr.watch registry & network status",
|
title: "nostr.watch registry & network status",
|
||||||
@@ -104,77 +107,265 @@ export default defineComponent({
|
|||||||
Row,
|
Row,
|
||||||
Column,
|
Column,
|
||||||
RelayListComponent,
|
RelayListComponent,
|
||||||
LeafletComponent
|
// RelayGroupedListComponent,
|
||||||
// NavComponent
|
LeafletComponent,
|
||||||
|
NavComponent
|
||||||
},
|
},
|
||||||
|
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
relays,
|
relays: relays,
|
||||||
result: {},
|
result: {},
|
||||||
messages: {},
|
messages: {},
|
||||||
connections: {},
|
connections: {},
|
||||||
nips: {},
|
nips: {},
|
||||||
alerts: {},
|
alerts: {},
|
||||||
timeouts: {},
|
timeouts: {},
|
||||||
|
intervals: {},
|
||||||
lastPing: Date.now(),
|
lastPing: Date.now(),
|
||||||
nextPing: Date.now() + (60*1000),
|
nextPing: Date.now() + (60*1000),
|
||||||
count: 0,
|
count: 0,
|
||||||
|
storage: null,
|
||||||
geo,
|
geo,
|
||||||
version: version
|
version: version,
|
||||||
|
hasStorage: false,
|
||||||
|
lastUpdate: null,
|
||||||
|
refresh: true,
|
||||||
|
preferences: {
|
||||||
|
refresh: true
|
||||||
|
},
|
||||||
|
refreshData: {},
|
||||||
|
// cacheExpiration: 10*60*1000, //10 minutes
|
||||||
|
cacheExpiration: 30*60*1000, //30 minutes
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
async mounted() {
|
|
||||||
this.relays.forEach(relay => {
|
|
||||||
this.check(relay)
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
|
updated(){
|
||||||
|
console.log(`zing - refresh? ${this.preferences.refresh} ${Date.now()} - ${this.lastUpdate} = ${Date.now()-this.lastUpdate} > ${this.cacheExpiration}`)
|
||||||
|
|
||||||
|
this.saveState('preferences')
|
||||||
|
|
||||||
|
Object.keys(this.timeouts).forEach(timeout => clearTimeout(this.timeouts[timeout]))
|
||||||
|
Object.keys(this.intervals).forEach(interval => clearInterval(this.intervals[interval]))
|
||||||
|
|
||||||
|
if(this.isDone()) {
|
||||||
|
this.saveState('lastUpdate')
|
||||||
|
console.log('isDone()', this.getState('lastUpdate') )
|
||||||
|
}
|
||||||
|
|
||||||
|
this.refreshData.untilNext = this.timeUntilRefresh()
|
||||||
|
this.refreshData.sinceLast = this.timeSinceRefresh()
|
||||||
|
|
||||||
|
// if(this.preferences.refresh)
|
||||||
|
// this.timeouts.invalidate = setTimeout(()=> this.invalidate(), 1000)
|
||||||
},
|
},
|
||||||
|
|
||||||
computed: {},
|
|
||||||
|
|
||||||
|
async mounted() {
|
||||||
|
|
||||||
|
this.storage = useStorage()
|
||||||
|
this.lastUpdate = this.getState('lastUpdate')|| this.lastUpdate
|
||||||
|
this.preferences = this.getState('preferences') || this.preferences
|
||||||
|
|
||||||
|
this.relays.forEach(async relay => {
|
||||||
|
this.result[relay] = this.getState(relay)
|
||||||
|
this.messages[relay] = this.getState(`${relay}_inbox`)
|
||||||
|
})
|
||||||
|
|
||||||
|
this.invalidate()
|
||||||
|
|
||||||
|
console.log('last update',-1*(Date.now()-(this.lastUpdate+this.cacheExpiration)))
|
||||||
|
|
||||||
|
this.refreshData = reactive({
|
||||||
|
untilNext: this.timeUntilRefresh(),
|
||||||
|
sinceLast: this.timeSinceRefresh()
|
||||||
|
})
|
||||||
|
|
||||||
|
setInterval(() => {
|
||||||
|
|
||||||
|
this.refreshData.untilNext = this.timeUntilRefresh()
|
||||||
|
this.refreshData.sinceLast = this.timeSinceRefresh()
|
||||||
|
|
||||||
|
console.log('timesince22222', this.refreshData.untilNext, this.refreshData.sinceLast)
|
||||||
|
|
||||||
|
if(this.isExpired())
|
||||||
|
this.invalidate()
|
||||||
|
|
||||||
|
}, 1000)
|
||||||
|
},
|
||||||
|
|
||||||
|
computed: {
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
|
|
||||||
check(relay){
|
timeUntilRefresh(){
|
||||||
|
return timeSince(Date.now()-(this.lastUpdate+this.cacheExpiration-Date.now()))
|
||||||
|
},
|
||||||
|
timeSinceRefresh(){
|
||||||
|
return timeSince(this.lastUpdate)
|
||||||
|
},
|
||||||
|
|
||||||
const opts = {
|
invalidate(force){
|
||||||
checkLatency: true,
|
if(!this.isExpired() && !force)
|
||||||
setIP: false,
|
return
|
||||||
setGeo: false,
|
|
||||||
}
|
|
||||||
|
|
||||||
let inspect = new Inspector(relay, opts)
|
this.relays.forEach(async relay => {
|
||||||
.on('run', (result) => {
|
await this.check(relay)
|
||||||
result.aggregate = 'processing'
|
this.relays[relay] = this.getState(relay)
|
||||||
})
|
this.messages[relay] = this.getState(`${relay}_inbox`)
|
||||||
.on('open', (e, result) => {
|
})
|
||||||
this.result[relay] = result
|
|
||||||
})
|
|
||||||
.on('complete', (instance) => {
|
|
||||||
this.result[relay] = instance.result
|
|
||||||
this.messages[relay] = instance.inbox
|
|
||||||
// this.setFlag(relay)
|
|
||||||
this.setAggregateResult(relay)
|
|
||||||
this.adjustResult(relay)
|
|
||||||
})
|
|
||||||
.on('notice', (notice) => {
|
|
||||||
const hash = this.sha1(notice)
|
|
||||||
let message_obj = RELAY_MESSAGES[hash]
|
|
||||||
let code_obj = RELAY_CODES[message_obj.code]
|
|
||||||
|
|
||||||
let response_obj = {...message_obj, ...code_obj}
|
// if(this.preferences.refresh)
|
||||||
|
// this.timeouts.invalidate = setTimeout(()=> this.invalidate(), 1000)
|
||||||
|
|
||||||
this.result[relay].observations.push( new InspectorObservation('notice', response_obj.code, response_obj.description, response_obj.relates_to) )
|
},
|
||||||
|
|
||||||
})
|
nextRefresh(){
|
||||||
.on('close', () => {})
|
return timeSince(Date.now()-(this.lastUpdate+this.cacheExpiration-Date.now()))
|
||||||
.on('error', () => {
|
},
|
||||||
|
|
||||||
})
|
isExpired(){
|
||||||
.run()
|
return typeof this.lastUpdate === 'undefined' || Date.now() - this.lastUpdate > this.cacheExpiration
|
||||||
|
},
|
||||||
|
|
||||||
|
getState(key){
|
||||||
|
return this.storage.getStorageSync(key)
|
||||||
|
},
|
||||||
|
|
||||||
|
saveState(type, key, data){
|
||||||
|
|
||||||
|
const now = Date.now()
|
||||||
|
|
||||||
|
let store, success, error, instance
|
||||||
|
|
||||||
|
switch(type){
|
||||||
|
case 'relay':
|
||||||
|
console.log('savestate', 'relay', data || this.result[data])
|
||||||
|
if(data)
|
||||||
|
data.aggregate = this.getAggregate(key)
|
||||||
|
store = {
|
||||||
|
key: key,
|
||||||
|
data: data || this.result[key],
|
||||||
|
// expire: Date.now()+1000*60*60*24*180,
|
||||||
|
}
|
||||||
|
success = () => {
|
||||||
|
if(data)
|
||||||
|
this.result[key] = data
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'messages':
|
||||||
|
console.log('savestate', 'messages', this.messages[data])
|
||||||
|
store = {
|
||||||
|
key: `${key}_inbox`,
|
||||||
|
data: data || this.messages[key],
|
||||||
|
// expire: Date.now()+1000*60*60*24*180,
|
||||||
|
}
|
||||||
|
success = () => {
|
||||||
|
if(data)
|
||||||
|
this.messages[key] = data
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'lastUpdate':
|
||||||
|
console.log('savestate', 'lastUpdate', now)
|
||||||
|
store = {
|
||||||
|
key: "lastUpdate",
|
||||||
|
data: now
|
||||||
|
}
|
||||||
|
success = () => {
|
||||||
|
// console.log('lastupdate success', successCallback.msg)
|
||||||
|
this.lastUpdate = now
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'preferences':
|
||||||
|
console.log('savestate', 'preferences', this.preferences)
|
||||||
|
store = {
|
||||||
|
key: "preferences",
|
||||||
|
data: this.preferences
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(store)
|
||||||
|
instance = this.storage.setStorage(store)
|
||||||
|
|
||||||
|
if(success && store)
|
||||||
|
instance.then(success)
|
||||||
|
|
||||||
|
if(error && store)
|
||||||
|
instance.catch(error)
|
||||||
|
},
|
||||||
|
|
||||||
|
resetState(){
|
||||||
|
this.relays.forEach(relay=>{
|
||||||
|
this.storage.removeStorage(relay)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
async check(relay){
|
||||||
|
return new Promise( (resolve, reject) => {
|
||||||
|
// if(!this.isExpired())
|
||||||
|
// return reject(relay)
|
||||||
|
|
||||||
|
const opts = {
|
||||||
|
checkLatency: true,
|
||||||
|
setIP: false,
|
||||||
|
setGeo: false,
|
||||||
|
getInfo: true,
|
||||||
|
debug: true,
|
||||||
|
// data: { result: this.result[relay] }
|
||||||
|
}
|
||||||
|
|
||||||
|
let inspect = new Inspector(relay, opts)
|
||||||
|
// .on('run', (result) => {
|
||||||
|
// result.aggregate = 'processing'
|
||||||
|
// })
|
||||||
|
// .on('open', (e, result) => {
|
||||||
|
// this.result[relay] = result
|
||||||
|
// })
|
||||||
|
.on('complete', (instance) => {
|
||||||
|
// console.log('getinfo()', instance.result.info)
|
||||||
|
|
||||||
|
this.result[relay] = instance.result
|
||||||
|
|
||||||
|
// this.setFlag(relay)
|
||||||
|
// this.adjustResult(relay)
|
||||||
|
|
||||||
|
this.result[relay].aggregate = this.getAggregate(relay)
|
||||||
|
|
||||||
|
this.saveState('relay', relay)
|
||||||
|
this.saveState('messages', relay, instance.inbox)
|
||||||
|
this.saveState('lastUpdate')
|
||||||
|
|
||||||
|
resolve(this.result[relay])
|
||||||
|
})
|
||||||
|
.on('notice', (notice) => {
|
||||||
|
const hash = this.sha1(notice)
|
||||||
|
let message_obj = RELAY_MESSAGES[hash]
|
||||||
|
let code_obj = RELAY_CODES[message_obj.code]
|
||||||
|
|
||||||
|
let response_obj = {...message_obj, ...code_obj}
|
||||||
|
|
||||||
|
this.result[relay].observations.push( new InspectorObservation('notice', response_obj.code, response_obj.description, response_obj.relates_to) )
|
||||||
|
})
|
||||||
|
.on('close', () => {})
|
||||||
|
.on('error', () => {
|
||||||
|
reject(this.result[relay])
|
||||||
|
})
|
||||||
|
.run()
|
||||||
|
|
||||||
|
this.connections[relay] = inspect
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
recheck(relay){
|
||||||
|
const inspect = this.connections[relay]
|
||||||
|
inspect.checkLatency()
|
||||||
|
|
||||||
this.connections[relay] = inspect
|
|
||||||
},
|
},
|
||||||
|
|
||||||
adjustResult (relay) {
|
adjustResult (relay) {
|
||||||
@@ -186,19 +377,20 @@ export default defineComponent({
|
|||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
setAggregateResult (relay) {
|
getAggregate (relay) {
|
||||||
|
console.log('getAggregate()', this.result?.[relay]?.check.connect, this.result?.[relay]?.check.read, this.result?.[relay]?.check.write)
|
||||||
let aggregateTally = 0
|
let aggregateTally = 0
|
||||||
aggregateTally += this.result?.[relay]?.check.connect ? 1 : 0
|
aggregateTally += this.result?.[relay]?.check.connect ? 1 : 0
|
||||||
aggregateTally += this.result?.[relay]?.check.read ? 1 : 0
|
aggregateTally += this.result?.[relay]?.check.read ? 1 : 0
|
||||||
aggregateTally += this.result?.[relay]?.check.write ? 1 : 0
|
aggregateTally += this.result?.[relay]?.check.write ? 1 : 0
|
||||||
if (aggregateTally == 3) {
|
if (aggregateTally == 3) {
|
||||||
this.result[relay].aggregate = 'public'
|
return 'public'
|
||||||
}
|
}
|
||||||
else if (aggregateTally == 0) {
|
else if (aggregateTally == 0) {
|
||||||
this.result[relay].aggregate = 'offline'
|
return 'offline'
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
this.result[relay].aggregate = 'restricted'
|
return 'restricted'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -210,18 +402,48 @@ export default defineComponent({
|
|||||||
return Object.entries(this.result).length
|
return Object.entries(this.result).length
|
||||||
},
|
},
|
||||||
|
|
||||||
|
relaysComplete () {
|
||||||
|
return this.relays.filter(relay => this.results?.[relay]?.state == 'complete').length
|
||||||
|
},
|
||||||
|
|
||||||
sha1 (message) {
|
sha1 (message) {
|
||||||
const hash = crypto.createHash('sha1').update(JSON.stringify(message)).digest('hex')
|
const hash = crypto.createHash('sha1').update(JSON.stringify(message)).digest('hex')
|
||||||
return hash
|
return hash
|
||||||
},
|
},
|
||||||
|
|
||||||
isDone(){
|
isDone(){
|
||||||
return this.relaysTotal()-this.relaysConnected() == 0
|
console.log('is done', this.relaysTotal(), '-', this.relaysConnected(), '<=', 0, this.relaysTotal()-this.relaysConnected() <= 0)
|
||||||
|
return this.relaysTotal()-this.relaysComplete() <= 0
|
||||||
},
|
},
|
||||||
|
|
||||||
loadingComplete(){
|
loadingComplete(){
|
||||||
return this.isDone() ? 'loaded' : ''
|
return this.isDone() ? 'loaded' : ''
|
||||||
},
|
},
|
||||||
|
|
||||||
|
timeSince(date) {
|
||||||
|
var seconds = Math.floor((new Date() - date) / 1000);
|
||||||
|
var interval = seconds / 31536000;
|
||||||
|
if (interval > 1) {
|
||||||
|
return Math.floor(interval) + " years";
|
||||||
|
}
|
||||||
|
interval = seconds / 2592000;
|
||||||
|
if (interval > 1) {
|
||||||
|
return Math.floor(interval) + " months";
|
||||||
|
}
|
||||||
|
interval = seconds / 86400;
|
||||||
|
if (interval > 1) {
|
||||||
|
return Math.floor(interval) + " days";
|
||||||
|
}
|
||||||
|
interval = seconds / 3600;
|
||||||
|
if (interval > 1) {
|
||||||
|
return Math.floor(interval) + " hours";
|
||||||
|
}
|
||||||
|
interval = seconds / 60;
|
||||||
|
if (interval > 1) {
|
||||||
|
return Math.floor(interval) + " minutes";
|
||||||
|
}
|
||||||
|
return Math.floor(seconds) + " seconds";
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -7,13 +7,22 @@
|
|||||||
<!-- <NavComponent /> -->
|
<!-- <NavComponent /> -->
|
||||||
<div id="wrapper">
|
<div id="wrapper">
|
||||||
|
|
||||||
|
<row container :gutter="12">
|
||||||
|
<column :xs="12" :md="12" :lg="12" class="title-card">
|
||||||
|
<h1>{{ relayUrl() }}</h1>
|
||||||
|
</column>
|
||||||
|
</row>
|
||||||
|
|
||||||
|
<row container :gutter="12">
|
||||||
|
<column :xs="12" :md="12" :lg="12" class="title-card">
|
||||||
|
<NavComponent />
|
||||||
|
</column>
|
||||||
|
</row>
|
||||||
|
|
||||||
|
|
||||||
<row container :gutter="12">
|
<row container :gutter="12">
|
||||||
<column :xs="12" :md="12" :lg="12" class="title-card">
|
<column :xs="12" :md="12" :lg="12" class="title-card">
|
||||||
<div style="display: none">{{result}}</div> <!-- ? -->
|
<div style="display: none">{{result}}</div> <!-- ? -->
|
||||||
|
|
||||||
<h1>{{ relayUrl() }}</h1>
|
|
||||||
<!-- <h2>nostr.watch<sup>{{version}}</sup></h2> -->
|
|
||||||
<br >
|
<br >
|
||||||
|
|
||||||
<span class="badges">
|
<span class="badges">
|
||||||
@@ -92,32 +101,6 @@
|
|||||||
|
|
||||||
<div style="display: none">{{result}}</div> <!-- ? -->
|
<div style="display: none">{{result}}</div> <!-- ? -->
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
</column>
|
|
||||||
|
|
||||||
|
|
||||||
</row>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<row container :gutter="12">
|
|
||||||
<column :xs="12" :md="6" :lg="6">
|
|
||||||
<div style="display: none">{{result}}</div> <!-- ? -->
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
</column>
|
|
||||||
<column :xs="12" :md="6" :lg="6">
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<!-- <h4 v-if="result.info?.supported_nips">NIP Support</h4> -->
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
</column>
|
</column>
|
||||||
</row>
|
</row>
|
||||||
|
|
||||||
@@ -127,26 +110,29 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
|
||||||
import { defineComponent} from 'vue'
|
import { defineComponent} from 'vue'
|
||||||
|
import { useStorage } from "vue3-storage";
|
||||||
|
|
||||||
import LeafletSingleComponent from '../components/LeafletSingleComponent.vue'
|
import LeafletSingleComponent from '../components/LeafletSingleComponent.vue'
|
||||||
// import NavComponent from './NavComponent.vue'\
|
import NavComponent from '../components/NavComponent.vue'
|
||||||
|
|
||||||
import SafeMail from "@2alheure/vue-safe-mail";
|
|
||||||
|
|
||||||
|
|
||||||
import { countryCodeEmoji } from 'country-code-emoji';
|
|
||||||
import emoji from 'node-emoji';
|
|
||||||
|
|
||||||
import { Row, Column } from 'vue-grid-responsive';
|
import { Row, Column } from 'vue-grid-responsive';
|
||||||
|
import SafeMail from "@2alheure/vue-safe-mail";
|
||||||
|
import emoji from 'node-emoji';
|
||||||
|
import { countryCodeEmoji } from 'country-code-emoji';
|
||||||
|
|
||||||
|
// import { Inspector, InspectorObservation } from 'nostr-relay-inspector'
|
||||||
|
// import { Inspector, InspectorObservation } from '../../lib/nostr-relay-inspector'
|
||||||
|
import { Inspector, InspectorObservation } from '../../lib/nostr-relay-inspector'
|
||||||
|
|
||||||
import { version } from '../../package.json'
|
import { version } from '../../package.json'
|
||||||
|
|
||||||
import { relays } from '../../relays.yaml'
|
import { relays } from '../../relays.yaml'
|
||||||
import { geo } from '../../geo.yaml'
|
import { geo } from '../../geo.yaml'
|
||||||
import { messages as RELAY_MESSAGES, codes as RELAY_CODES } from '../../codes.yaml'
|
import { messages as RELAY_MESSAGES, codes as RELAY_CODES } from '../../codes.yaml'
|
||||||
|
|
||||||
import { Inspector, InspectorObservation } from 'nostr-relay-inspector'
|
|
||||||
/* import { Inspector, InspectorObservation } from '../../lib/nostr-relay-inspector' */
|
|
||||||
|
|
||||||
import crypto from "crypto"
|
import crypto from "crypto"
|
||||||
|
|
||||||
@@ -157,8 +143,8 @@ export default defineComponent({
|
|||||||
Row,
|
Row,
|
||||||
Column,
|
Column,
|
||||||
LeafletSingleComponent,
|
LeafletSingleComponent,
|
||||||
// NavComponent
|
NavComponent,
|
||||||
SafeMail
|
SafeMail,
|
||||||
},
|
},
|
||||||
|
|
||||||
data() {
|
data() {
|
||||||
@@ -169,28 +155,72 @@ export default defineComponent({
|
|||||||
nips: {},
|
nips: {},
|
||||||
alerts: {},
|
alerts: {},
|
||||||
timeouts: {},
|
timeouts: {},
|
||||||
|
intervals: {},
|
||||||
lastPing: Date.now(),
|
lastPing: Date.now(),
|
||||||
nextPing: Date.now() + (60*1000),
|
nextPing: Date.now() + (60*1000),
|
||||||
count: 0,
|
count: 0,
|
||||||
geo,
|
geo,
|
||||||
relay: "",
|
relay: "",
|
||||||
version: version
|
version: version,
|
||||||
|
storage: null,
|
||||||
|
lastUpdate: null,
|
||||||
|
cacheExpiration: 10*60*1000 //10 minutes
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
async mounted() {
|
async mounted() {
|
||||||
console.log('mounted')
|
|
||||||
this.relay = this.relayUrl()
|
this.relay = this.relayUrl()
|
||||||
console.log('relay', this.relay)
|
|
||||||
this.check(this.relay)
|
this.storage = useStorage()
|
||||||
console.log('relay is compete', this.relay, this.result.check)
|
this.lastUpdate = this.storage.getStorageSync('lastUpdate')
|
||||||
|
this.result = this.storage.getStorageSync(this.relay)
|
||||||
|
|
||||||
|
if(this.isExpired())
|
||||||
|
this.check(this.relay)
|
||||||
|
|
||||||
|
// console.log('zing ', (Date.now() - this.lastUpdate) /1000)
|
||||||
},
|
},
|
||||||
|
|
||||||
computed: {
|
computed: {
|
||||||
|
|
||||||
},
|
},
|
||||||
|
|
||||||
|
updated() {
|
||||||
|
Object.keys(this.timeouts).forEach(timeout => clearTimeout(this.timeouts[timeout]))
|
||||||
|
Object.keys(this.intervals).forEach(interval => clearInterval(this.intervals[interval]))
|
||||||
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
|
isExpired(){
|
||||||
|
return typeof this.lastUpdate === 'undefined' || Date.now() - this.lastUpdate > this.cacheExpiration
|
||||||
|
},
|
||||||
|
|
||||||
|
saveState(relay){
|
||||||
|
this.storage
|
||||||
|
.setStorage({
|
||||||
|
key: relay,
|
||||||
|
data: this.result
|
||||||
|
})
|
||||||
|
.then(successCallback => {
|
||||||
|
console.log(successCallback.errMsg);
|
||||||
|
})
|
||||||
|
.catch(failCallback => {
|
||||||
|
console.log(failCallback.errMsg);
|
||||||
|
})
|
||||||
|
|
||||||
|
this.storage
|
||||||
|
.setStorage({
|
||||||
|
key: "lastUpdate",
|
||||||
|
data: Date.now()
|
||||||
|
})
|
||||||
|
.then(successCallback => {
|
||||||
|
console.log(successCallback.errMsg);
|
||||||
|
this.lastUpdate = Date.now()
|
||||||
|
})
|
||||||
|
.catch(failCallback => {
|
||||||
|
console.log(failCallback.errMsg);
|
||||||
|
})
|
||||||
|
},
|
||||||
relayUrl() {
|
relayUrl() {
|
||||||
// We will see what `params` is shortly
|
// We will see what `params` is shortly
|
||||||
return `wss://${this.$route.params.relayUrl}`
|
return `wss://${this.$route.params.relayUrl}`
|
||||||
@@ -229,6 +259,7 @@ export default defineComponent({
|
|||||||
/* this.adjustResult(relay) */
|
/* this.adjustResult(relay) */
|
||||||
this.setResultClass('read')
|
this.setResultClass('read')
|
||||||
this.setResultClass('write')
|
this.setResultClass('write')
|
||||||
|
this.saveState(relay)
|
||||||
/* console.log(this.result)
|
/* console.log(this.result)
|
||||||
console.log(this.result.info.supported_nips) */
|
console.log(this.result.info.supported_nips) */
|
||||||
/* resolve(this.result) */
|
/* resolve(this.result) */
|
||||||
|
|||||||
@@ -1,27 +1,34 @@
|
|||||||
// /router/index.js
|
// /router/index.js
|
||||||
import { createRouter, createWebHistory } from 'vue-router'
|
import { createRouter, createWebHistory } from 'vue-router'
|
||||||
import HomePage from '../pages/HomePage.vue'
|
import HomePage from '../pages/HomePage.vue'
|
||||||
|
import ByStatus from '../pages/ByStatus.vue'
|
||||||
import SingleRelay from '../pages/SingleRelay.vue'
|
import SingleRelay from '../pages/SingleRelay.vue'
|
||||||
|
|
||||||
const routes = [
|
const routes = [
|
||||||
{
|
{
|
||||||
path: '/',
|
path: '/relay/:relayUrl(.*)',
|
||||||
name: 'nostr.watch',
|
// name: 'nostr.watch - :relayUrl',
|
||||||
component: HomePage
|
component: SingleRelay
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/status',
|
||||||
|
// name: 'nostr.watch',
|
||||||
|
component: ByStatus
|
||||||
},
|
},
|
||||||
// Added our new route file named profile.vue
|
// Added our new route file named profile.vue
|
||||||
{
|
{
|
||||||
path: '/:relayUrl',
|
path: '/',
|
||||||
name: 'nostr.watch - :relayUrl',
|
// name: 'nostr.watch',
|
||||||
component: SingleRelay
|
component: HomePage
|
||||||
}
|
},
|
||||||
|
|
||||||
]
|
]
|
||||||
|
|
||||||
// Create Vue Router Object
|
// Create Vue Router Object
|
||||||
const router = createRouter({
|
const router = createRouter({
|
||||||
history: createWebHistory(),
|
history: createWebHistory(),
|
||||||
base: process.env.BASE_URL,
|
// base: process.env.BASE_URL,
|
||||||
routes
|
routes: routes
|
||||||
})
|
})
|
||||||
|
|
||||||
export default router
|
export default router
|
||||||
|
|||||||
@@ -328,3 +328,23 @@ tr.offline .location {
|
|||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
nav.menu a {
|
||||||
|
text-decoration: none;
|
||||||
|
display: inline-block;
|
||||||
|
margin: 0 22px 0 0;
|
||||||
|
padding:5px 10px;
|
||||||
|
color:#000;
|
||||||
|
border-bottom: 1px dotted #999;
|
||||||
|
}
|
||||||
|
|
||||||
|
nav.menu a.active {
|
||||||
|
background:#000;
|
||||||
|
color: #fff;
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
nav.menu a:hover {
|
||||||
|
background: #f0f0f0;
|
||||||
|
}
|
||||||
24
src/utils/index.js
Normal file
24
src/utils/index.js
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
export const timeSince = function(date) {
|
||||||
|
var seconds = Math.floor((new Date() - date) / 1000);
|
||||||
|
var interval = seconds / 31536000;
|
||||||
|
if (interval > 1) {
|
||||||
|
return Math.floor(interval) + " years";
|
||||||
|
}
|
||||||
|
interval = seconds / 2592000;
|
||||||
|
if (interval > 1) {
|
||||||
|
return Math.floor(interval) + " months";
|
||||||
|
}
|
||||||
|
interval = seconds / 86400;
|
||||||
|
if (interval > 1) {
|
||||||
|
return Math.floor(interval) + " days";
|
||||||
|
}
|
||||||
|
interval = seconds / 3600;
|
||||||
|
if (interval > 1) {
|
||||||
|
return Math.floor(interval) + " hours";
|
||||||
|
}
|
||||||
|
interval = seconds / 60;
|
||||||
|
if (interval > 1) {
|
||||||
|
return Math.floor(interval) + " minutes";
|
||||||
|
}
|
||||||
|
return Math.floor(seconds) + " seconds";
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user