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 / {
|
||||
index index.html;
|
||||
# try_files $uri $uri/ /index.html;
|
||||
}
|
||||
|
||||
location ~ /?(.*)$ {
|
||||
index index.html
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
"node-emoji": "1.11.0",
|
||||
"node-polyfill-webpack-plugin": "2.0.1",
|
||||
"nostr": "0.2.5",
|
||||
"nostr-relay-inspector": "0.0.7",
|
||||
"nostr-relay-inspector": "0.0.9",
|
||||
"nostr-tools": "0.24.1",
|
||||
"onion-regex": "2.0.8",
|
||||
"requests": "0.3.0",
|
||||
@@ -45,6 +45,8 @@
|
||||
"vue-router": "4.1.6",
|
||||
"vue-simple-maps": "1.1.3",
|
||||
"vue3-popper": "1.5.0",
|
||||
"vue3-storage": "0.1.11",
|
||||
"vue3-tabs-component": "1.1.2",
|
||||
"yaml-loader": "^0.6.0",
|
||||
"yaml2json": "1.0.2"
|
||||
},
|
||||
|
||||
@@ -45,7 +45,7 @@ const query = async function(){
|
||||
// console.log(dns, ip)
|
||||
geo = await getGeo(ip)
|
||||
|
||||
console.log(geo, ip, dns)
|
||||
// console.log(geo, ip, dns)
|
||||
|
||||
if(dns)
|
||||
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>
|
||||
.leaflet-container {
|
||||
margin-top:37px;
|
||||
margin:0;
|
||||
padding:0;
|
||||
height:250px !important;
|
||||
width:100%;
|
||||
}
|
||||
.leaflet-control-zoom {
|
||||
display: none !important;
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
:minZoom="zoom"
|
||||
:maxZoom="zoom"
|
||||
:zoomControl="false"
|
||||
style="height:50vh"
|
||||
>
|
||||
|
||||
<l-tile-layer
|
||||
@@ -102,8 +101,7 @@ export default {
|
||||
.leaflet-container {
|
||||
margin:0;
|
||||
height:250px !important;
|
||||
width:1000%;
|
||||
|
||||
width:100%;
|
||||
}
|
||||
.leaflet-control-zoom {
|
||||
display: none !important;
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
<template>
|
||||
<nav class="menu">
|
||||
<ul>
|
||||
<li><a href="#">add relay</a></li>
|
||||
<li><a href="#">github</a></li>
|
||||
<router-link :to="`/`" active-class="active">Home</router-link>
|
||||
<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>
|
||||
</nav>
|
||||
</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>
|
||||
<tr :class="getHeadingClass()">
|
||||
<tr>
|
||||
<vue-final-modal v-model="showModal" classes="modal-container" content-class="modal-content">
|
||||
<div class="modal__content">
|
||||
<span>
|
||||
{{ queryJson(section) }}
|
||||
{{ queryJson() }}
|
||||
</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>
|
||||
<h2><span class="indicator badge">{{ this.relays.length }}</span>Relays <a @click="showModal=true" class="section-json" v-if="showJson">{...}</a></h2>
|
||||
</td>
|
||||
</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 relay"></th>
|
||||
@@ -43,12 +43,11 @@
|
||||
<span>NIP-11</span>
|
||||
</th> -->
|
||||
</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
|
||||
:relay="relay"
|
||||
:result="result[relay]"
|
||||
:geo="geo[relay]"
|
||||
:showColumns="showColumns"
|
||||
:connection="connections[relay]"
|
||||
/>
|
||||
</tr>
|
||||
@@ -74,11 +73,6 @@ export default defineComponent({
|
||||
return true
|
||||
}
|
||||
},
|
||||
section: {
|
||||
type: String,
|
||||
required: true,
|
||||
default: "public"
|
||||
},
|
||||
relays:{
|
||||
type: Object,
|
||||
default(){
|
||||
@@ -115,17 +109,6 @@ export default defineComponent({
|
||||
return {}
|
||||
}
|
||||
},
|
||||
showColumns: {
|
||||
type: Object,
|
||||
default() {
|
||||
return {
|
||||
connectionStatuses: false,
|
||||
nips: false,
|
||||
geo: false,
|
||||
additionalInfo: false
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
@@ -137,61 +120,91 @@ export default defineComponent({
|
||||
},
|
||||
computed: {},
|
||||
methods: {
|
||||
getHeadingClass(){
|
||||
return {
|
||||
online: this.section != "offline",
|
||||
public: this.section == "public",
|
||||
offline: this.section == "offline",
|
||||
restricted: this.section == "restricted"
|
||||
}
|
||||
},
|
||||
// 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"
|
||||
loaded: this.result?.[relay]?.state == 'complete'
|
||||
}
|
||||
},
|
||||
query (aggregate) {
|
||||
let unordered,
|
||||
filterFn
|
||||
|
||||
filterFn = (relay) => this.result?.[relay]?.aggregate == aggregate
|
||||
|
||||
unordered = this.relays.filter(filterFn);
|
||||
|
||||
if(!this.isDone()) {
|
||||
return unordered
|
||||
sort_by_latency(ascending) {
|
||||
const self = this
|
||||
return function (a, b) {
|
||||
// equal items sort equally
|
||||
if (self.result?.[a]?.latency.final === self.result?.[b]?.latency.final) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (unordered.length) {
|
||||
return unordered.sort((relay1, relay2) => {
|
||||
return this.result?.[relay1]?.latency.final - this.result?.[relay2]?.latency.final
|
||||
})
|
||||
// nulls sort after anything else
|
||||
if (self.result?.[a]?.latency.final === null) {
|
||||
return 1;
|
||||
}
|
||||
if (self.result?.[b]?.latency.final === null) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
// otherwise, if we're ascending, lowest sorts first
|
||||
if (ascending) {
|
||||
return self.result?.[a]?.latency.final - self.result?.[b]?.latency.final;
|
||||
}
|
||||
|
||||
// if descending, highest sorts first
|
||||
return self.result?.[b]?.latency.final-self.result?.[a]?.latency.final;
|
||||
};
|
||||
},
|
||||
sortByLatency () {
|
||||
let unsorted
|
||||
|
||||
unsorted = this.relays;
|
||||
|
||||
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 []
|
||||
},
|
||||
queryJson(aggregate){
|
||||
const relays = this.query(aggregate)
|
||||
const result = {}
|
||||
result.relays = relays.map( relay => relay )
|
||||
queryJson(){
|
||||
const result = { relays: this.relays }
|
||||
return JSON.stringify(result,null,'\t')
|
||||
},
|
||||
relaysTotal () {
|
||||
return this.relays.length
|
||||
return this.relays.length //TODO: Figure out WHY?
|
||||
},
|
||||
|
||||
relaysConnected () {
|
||||
return Object.keys(this.result).length
|
||||
return Object.entries(this.result).length
|
||||
},
|
||||
relaysCompleted () {
|
||||
let value = Object.entries(this.result).length
|
||||
return value
|
||||
|
||||
relaysComplete () {
|
||||
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(){
|
||||
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>
|
||||
|
||||
@@ -1,24 +1,23 @@
|
||||
<template>
|
||||
<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>
|
||||
</td>
|
||||
|
||||
<td class="relay left-align relay-url">
|
||||
<router-link :to="`/${relayClean(relay)}`" active-class="active">{{ relay }}</router-link>
|
||||
<!-- <span @click="copy(relay)" v-tooltip:top.tooltip="'Click to copy'">{{ relay }}</span> -->
|
||||
<router-link :to="`/relay/${relayClean(relay)}`" active-class="active">{{ relay }}</router-link>
|
||||
</td>
|
||||
|
||||
<td class="verified">
|
||||
<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 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>
|
||||
</td>
|
||||
|
||||
<td class="location">{{ getFlag() }}</td>
|
||||
|
||||
<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 class="connect" :key="generateKey(relay, 'check.connect')">
|
||||
@@ -34,8 +33,8 @@
|
||||
</td>
|
||||
|
||||
<td class="info">
|
||||
<ul v-if="result.observations && result.observations.length">
|
||||
<li class="observation" v-for="(alert) in result.observations" :key="generateKey(relay, alert.description)">
|
||||
<ul v-if="result?.observations && result?.observations.length">
|
||||
<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 == 'caution'">⚠️</span>
|
||||
</li>
|
||||
@@ -43,18 +42,18 @@
|
||||
</td>
|
||||
|
||||
<!-- <td class="nip nip-11">
|
||||
<a v-if="result.info" @click="showModal=true">✅ </a>
|
||||
<a v-if="result?.info" @click="showModal=true">✅ </a>
|
||||
</td> -->
|
||||
|
||||
<vue-final-modal v-model="showModal" classes="modal-container" content-class="modal-content">
|
||||
<div class="modal__title">
|
||||
<span>{{ result.info?.name }}</span>
|
||||
<span>{{ result?.info?.name }}</span>
|
||||
</div>
|
||||
<div class="modal__content">
|
||||
<div v-if="result.info?.description">
|
||||
{{ result.info?.description }} <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" />
|
||||
<div v-if="result?.info?.description">
|
||||
{{ result?.info?.description }} <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" />
|
||||
</div>
|
||||
<div>
|
||||
<h4>Status</h4>
|
||||
@@ -66,12 +65,12 @@
|
||||
</div>
|
||||
<h4>Relay Info</h4>
|
||||
<ul>
|
||||
<li><strong>Software:</strong> {{ result.info?.software }} </li>
|
||||
<li><strong>Version</strong>: {{ result.info?.version }} </li>
|
||||
<li><strong>Software:</strong> {{ result?.info?.software }} </li>
|
||||
<li><strong>Version</strong>: {{ result?.info?.version }} </li>
|
||||
</ul>
|
||||
<h4>NIP Support</h4>
|
||||
<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>
|
||||
</li>
|
||||
</ul>
|
||||
@@ -168,14 +167,16 @@ export default defineComponent({
|
||||
identityList () {
|
||||
let string = '',
|
||||
extraString = '',
|
||||
users = Object.entries(this.result.identities),
|
||||
users = Object.entries(this.result?.identities),
|
||||
count = 0
|
||||
|
||||
console.log(this.result.uri, 'admin', this.result.identities.serverAdmin)
|
||||
// if(!this.result?.identities) return
|
||||
|
||||
if(this.result.identities) {
|
||||
if(this.result.identities.serverAdmin) {
|
||||
string = `Relay has registered an administrator pubkey: ${this.result.identities.serverAdmin}. `
|
||||
console.log(this.result?.uri, 'admin', this.result?.identities.serverAdmin, this.result.info)
|
||||
|
||||
if(this.result?.identities) {
|
||||
if(this.result?.identities.serverAdmin) {
|
||||
string = `Relay has registered an administrator pubkey: ${this.result?.identities.serverAdmin}. `
|
||||
extraString = "Additionally, "
|
||||
}
|
||||
|
||||
@@ -277,4 +278,9 @@ td.verified span {
|
||||
border-color: #2d3748;
|
||||
background-color: #1a202c;
|
||||
}
|
||||
|
||||
.restricted.aggregate.indicator {
|
||||
position:relative;
|
||||
left:-7px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -213,6 +213,11 @@ export default defineComponent({
|
||||
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) {
|
||||
const hash = crypto.createHash('sha1').update(JSON.stringify(message)).digest('hex')
|
||||
// //console.log(message, ':', hash)
|
||||
@@ -220,7 +225,7 @@ export default defineComponent({
|
||||
},
|
||||
|
||||
isDone(){
|
||||
return this.relaysTotal()-this.relaysConnected() == 0
|
||||
return this.relaysTotal()-this.relaysComplete() == 0
|
||||
},
|
||||
|
||||
loadingComplete(){
|
||||
|
||||
@@ -1,13 +1,18 @@
|
||||
import { createApp } from 'vue'
|
||||
import App from './App.vue'
|
||||
import Vue3Storage from "vue3-storage";
|
||||
|
||||
import router from './router'
|
||||
import "./styles/main.scss"
|
||||
import directives from "./directives/"
|
||||
import titleMixin from './mixins/titleMixin'
|
||||
import {Tabs, Tab} from 'vue3-tabs-component';
|
||||
|
||||
const app = createApp(App)
|
||||
app
|
||||
.use(router)
|
||||
.use(Vue3Storage, { namespace: "nostrwatch_" })
|
||||
.component('tabs', Tabs)
|
||||
.component('tab', Tab)
|
||||
.mixin(titleMixin)
|
||||
|
||||
directives(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,5 +1,10 @@
|
||||
<template>
|
||||
<!-- <NavComponent /> -->
|
||||
<LeafletComponent
|
||||
:geo="geo"
|
||||
:result="result"
|
||||
/>
|
||||
|
||||
<div id="wrapper" :class="loadingComplete()">
|
||||
|
||||
<row container :gutter="12">
|
||||
@@ -9,21 +14,15 @@
|
||||
</row>
|
||||
|
||||
<row container :gutter="12">
|
||||
<column :xs="12" :md="12" :lg="12">
|
||||
<LeafletComponent
|
||||
:geo="geo"
|
||||
:result="result"
|
||||
/>
|
||||
<column :xs="12" :md="12" :lg="12" class="title-card">
|
||||
<NavComponent />
|
||||
</column>
|
||||
</row>
|
||||
|
||||
<row container :gutter="12">
|
||||
<column :xs="12" :md="12" :lg="12">
|
||||
<div class="block">
|
||||
<column :xs="12" :md="12" :lg="12" class="title-card">
|
||||
<table>
|
||||
|
||||
<RelayListComponent
|
||||
section="public"
|
||||
:relays="relays"
|
||||
:result="result"
|
||||
:geo="geo"
|
||||
@@ -31,71 +30,75 @@
|
||||
:alerts="alerts"
|
||||
: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>
|
||||
</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">
|
||||
<span v-if="(relaysTotal()-relaysConnected()>0)">Processing {{ relaysConnected() }}/{{ relaysTotal() }}</span>
|
||||
<span>Processing {{ relaysConnected() }}/{{ relaysTotal() }}</span>
|
||||
</column>
|
||||
</row>
|
||||
|
||||
<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>
|
||||
|
||||
|
||||
</template>
|
||||
|
||||
<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 { 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 { relays } from '../../relays.yaml'
|
||||
import { geo } from '../../geo.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 { timeSince } from '../utils'
|
||||
|
||||
import crypto from "crypto"
|
||||
reactive
|
||||
|
||||
export default defineComponent({
|
||||
title: "nostr.watch registry & network status",
|
||||
@@ -104,59 +107,241 @@ export default defineComponent({
|
||||
Row,
|
||||
Column,
|
||||
RelayListComponent,
|
||||
LeafletComponent
|
||||
// NavComponent
|
||||
// RelayGroupedListComponent,
|
||||
LeafletComponent,
|
||||
NavComponent
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
relays,
|
||||
relays: relays,
|
||||
result: {},
|
||||
messages: {},
|
||||
connections: {},
|
||||
nips: {},
|
||||
alerts: {},
|
||||
timeouts: {},
|
||||
intervals: {},
|
||||
lastPing: Date.now(),
|
||||
nextPing: Date.now() + (60*1000),
|
||||
count: 0,
|
||||
storage: null,
|
||||
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
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
|
||||
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)
|
||||
},
|
||||
|
||||
|
||||
|
||||
async mounted() {
|
||||
this.relays.forEach(relay => {
|
||||
this.check(relay)
|
||||
|
||||
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: {
|
||||
|
||||
},
|
||||
|
||||
computed: {},
|
||||
|
||||
methods: {
|
||||
|
||||
check(relay){
|
||||
timeUntilRefresh(){
|
||||
return timeSince(Date.now()-(this.lastUpdate+this.cacheExpiration-Date.now()))
|
||||
},
|
||||
timeSinceRefresh(){
|
||||
return timeSince(this.lastUpdate)
|
||||
},
|
||||
|
||||
invalidate(force){
|
||||
if(!this.isExpired() && !force)
|
||||
return
|
||||
|
||||
this.relays.forEach(async relay => {
|
||||
await this.check(relay)
|
||||
this.relays[relay] = this.getState(relay)
|
||||
this.messages[relay] = this.getState(`${relay}_inbox`)
|
||||
})
|
||||
|
||||
// if(this.preferences.refresh)
|
||||
// this.timeouts.invalidate = setTimeout(()=> this.invalidate(), 1000)
|
||||
|
||||
},
|
||||
|
||||
nextRefresh(){
|
||||
return timeSince(Date.now()-(this.lastUpdate+this.cacheExpiration-Date.now()))
|
||||
},
|
||||
|
||||
isExpired(){
|
||||
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('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.messages[relay] = instance.inbox
|
||||
|
||||
// this.setFlag(relay)
|
||||
this.setAggregateResult(relay)
|
||||
this.adjustResult(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)
|
||||
@@ -166,15 +351,21 @@ export default defineComponent({
|
||||
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) {
|
||||
@@ -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
|
||||
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'
|
||||
return 'public'
|
||||
}
|
||||
else if (aggregateTally == 0) {
|
||||
this.result[relay].aggregate = 'offline'
|
||||
return 'offline'
|
||||
}
|
||||
else {
|
||||
this.result[relay].aggregate = 'restricted'
|
||||
return 'restricted'
|
||||
}
|
||||
},
|
||||
|
||||
@@ -210,18 +402,48 @@ export default defineComponent({
|
||||
return Object.entries(this.result).length
|
||||
},
|
||||
|
||||
relaysComplete () {
|
||||
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(){
|
||||
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(){
|
||||
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 /> -->
|
||||
<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">
|
||||
<column :xs="12" :md="12" :lg="12" class="title-card">
|
||||
<div style="display: none">{{result}}</div> <!-- ? -->
|
||||
|
||||
<h1>{{ relayUrl() }}</h1>
|
||||
<!-- <h2>nostr.watch<sup>{{version}}</sup></h2> -->
|
||||
<br >
|
||||
|
||||
<span class="badges">
|
||||
@@ -92,32 +101,6 @@
|
||||
|
||||
<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>
|
||||
</row>
|
||||
|
||||
@@ -127,26 +110,29 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
import { defineComponent} from 'vue'
|
||||
import { useStorage } from "vue3-storage";
|
||||
|
||||
import LeafletSingleComponent from '../components/LeafletSingleComponent.vue'
|
||||
// import NavComponent from './NavComponent.vue'\
|
||||
|
||||
import SafeMail from "@2alheure/vue-safe-mail";
|
||||
|
||||
|
||||
import { countryCodeEmoji } from 'country-code-emoji';
|
||||
import emoji from 'node-emoji';
|
||||
import NavComponent from '../components/NavComponent.vue'
|
||||
|
||||
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 { relays } from '../../relays.yaml'
|
||||
import { geo } from '../../geo.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"
|
||||
|
||||
@@ -157,8 +143,8 @@ export default defineComponent({
|
||||
Row,
|
||||
Column,
|
||||
LeafletSingleComponent,
|
||||
// NavComponent
|
||||
SafeMail
|
||||
NavComponent,
|
||||
SafeMail,
|
||||
},
|
||||
|
||||
data() {
|
||||
@@ -169,28 +155,72 @@ export default defineComponent({
|
||||
nips: {},
|
||||
alerts: {},
|
||||
timeouts: {},
|
||||
intervals: {},
|
||||
lastPing: Date.now(),
|
||||
nextPing: Date.now() + (60*1000),
|
||||
count: 0,
|
||||
geo,
|
||||
relay: "",
|
||||
version: version
|
||||
version: version,
|
||||
storage: null,
|
||||
lastUpdate: null,
|
||||
cacheExpiration: 10*60*1000 //10 minutes
|
||||
}
|
||||
},
|
||||
|
||||
async mounted() {
|
||||
console.log('mounted')
|
||||
this.relay = this.relayUrl()
|
||||
console.log('relay', this.relay)
|
||||
|
||||
this.storage = useStorage()
|
||||
this.lastUpdate = this.storage.getStorageSync('lastUpdate')
|
||||
this.result = this.storage.getStorageSync(this.relay)
|
||||
|
||||
if(this.isExpired())
|
||||
this.check(this.relay)
|
||||
console.log('relay is compete', this.relay, this.result.check)
|
||||
|
||||
// console.log('zing ', (Date.now() - this.lastUpdate) /1000)
|
||||
},
|
||||
|
||||
computed: {
|
||||
|
||||
},
|
||||
|
||||
updated() {
|
||||
Object.keys(this.timeouts).forEach(timeout => clearTimeout(this.timeouts[timeout]))
|
||||
Object.keys(this.intervals).forEach(interval => clearInterval(this.intervals[interval]))
|
||||
},
|
||||
|
||||
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() {
|
||||
// We will see what `params` is shortly
|
||||
return `wss://${this.$route.params.relayUrl}`
|
||||
@@ -229,6 +259,7 @@ export default defineComponent({
|
||||
/* this.adjustResult(relay) */
|
||||
this.setResultClass('read')
|
||||
this.setResultClass('write')
|
||||
this.saveState(relay)
|
||||
/* console.log(this.result)
|
||||
console.log(this.result.info.supported_nips) */
|
||||
/* resolve(this.result) */
|
||||
|
||||
@@ -1,27 +1,34 @@
|
||||
// /router/index.js
|
||||
import { createRouter, createWebHistory } from 'vue-router'
|
||||
import HomePage from '../pages/HomePage.vue'
|
||||
import ByStatus from '../pages/ByStatus.vue'
|
||||
import SingleRelay from '../pages/SingleRelay.vue'
|
||||
|
||||
const routes = [
|
||||
{
|
||||
path: '/',
|
||||
name: 'nostr.watch',
|
||||
component: HomePage
|
||||
path: '/relay/:relayUrl(.*)',
|
||||
// name: 'nostr.watch - :relayUrl',
|
||||
component: SingleRelay
|
||||
},
|
||||
{
|
||||
path: '/status',
|
||||
// name: 'nostr.watch',
|
||||
component: ByStatus
|
||||
},
|
||||
// Added our new route file named profile.vue
|
||||
{
|
||||
path: '/:relayUrl',
|
||||
name: 'nostr.watch - :relayUrl',
|
||||
component: SingleRelay
|
||||
}
|
||||
path: '/',
|
||||
// name: 'nostr.watch',
|
||||
component: HomePage
|
||||
},
|
||||
|
||||
]
|
||||
|
||||
// Create Vue Router Object
|
||||
const router = createRouter({
|
||||
history: createWebHistory(),
|
||||
base: process.env.BASE_URL,
|
||||
routes
|
||||
// base: process.env.BASE_URL,
|
||||
routes: routes
|
||||
})
|
||||
|
||||
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