add routing

This commit is contained in:
dskvr
2022-12-11 00:53:48 +01:00
parent 51f351e6fa
commit 8750177ca3
10 changed files with 3734 additions and 93 deletions

View File

@@ -42,6 +42,7 @@
"vue-final-modal": "3", "vue-final-modal": "3",
"vue-grid-responsive": "1.3.0", "vue-grid-responsive": "1.3.0",
"vue-nav-tabs": "0.5.7", "vue-nav-tabs": "0.5.7",
"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",
"yaml-loader": "^0.6.0", "yaml-loader": "^0.6.0",

View File

@@ -1,15 +1,12 @@
<template> <template>
<RelayTableComponent /> <router-view></router-view>
</template> </template>
<script> <script>
import RelayTableComponent from './components/RelayTableComponent.vue'
export default { export default {
name: 'App', name: 'App',
components: { components: {}
RelayTableComponent,
}
} }
</script> </script>

View File

@@ -51,29 +51,23 @@ export default {
}, },
methods: { methods: {
getLatLng(geo){ getLatLng(geo){
console.log('meow', [geo.lat, geo.lon])
return [geo.lat, geo.lon] return [geo.lat, geo.lon]
}, },
getCircleColor(relay){ getCircleColor(relay){
if(this.result[relay]?.aggregate == 'public') { if(this.result[relay]?.aggregate == 'public') {
console.log('woof', relay, this.result[relay]?.aggregate)
return '#00AA00' return '#00AA00'
} }
else if(this.result[relay]?.aggregate == 'restricted') { else if(this.result[relay]?.aggregate == 'restricted') {
console.log('woof', relay, this.result[relay]?.aggregate)
return '#FFA500' return '#FFA500'
} }
else if(this.result[relay]?.aggregate == 'offline') { else if(this.result[relay]?.aggregate == 'offline') {
console.log('woof', relay, this.result[relay]?.aggregate)
return '#FF0000' return '#FF0000'
} }
return 'transparent' return 'transparent'
} }
}, },
async mounted() { async mounted() {},
console.log('GEO', Object.entries(this.geo))
},
props: { props: {
geo: { geo: {
type: Object, type: Object,

View File

@@ -0,0 +1,101 @@
<template>
<l-map
ref="map"
v-model:zoom="zoom"
:center="[47.41322, -1.219482]"
:minZoom="zoom"
:maxZoom="zoom"
:zoomControl="false"
style="height:50vh"
>
<l-tile-layer
url="http://{s}.tile.osm.org/{z}/{x}/{y}.png"
layer-type="base"
name="OpenStreetMap"
/>
<!-- <l-marker v-for="([relay, result]) in Object.entries(geo)" :lat-lng="getLatLng(result)" :key="relay">
<l-popup>
{{ relay }}
</l-popup>
</l-marker> -->
<l-circle-marker
:lat-lng="getLatLng(entry)"
:radius="3"
:weight="6"
:color="getCircleColor(relay)"
:fillOpacity="1"
:class="relay"
>
<l-popup>
{{ relay }}
meopw
</l-popup>
</l-circle-marker>
</l-map>
</template>
<script>
import "leaflet/dist/leaflet.css"
import { LMap, LTileLayer, LCircleMarker } from "@vue-leaflet/vue-leaflet";
export default {
components: {
LMap,
LTileLayer,
LCircleMarker,
},
methods: {
getLatLng(geo){
return [geo.lat, geo.lon]
},
getCircleColor(relay){
if(this.result[relay]?.aggregate == 'public') {
return '#00AA00'
}
else if(this.result[relay]?.aggregate == 'restricted') {
return '#FFA500'
}
else if(this.result[relay]?.aggregate == 'offline') {
return '#FF0000'
}
return 'transparent'
}
},
async mounted() {
console.log('GEO', Object.entries(this.geo))
},
props: {
geo: {
type: Object,
default(){
return {}
}
},
result: {
type: Object,
default(){
return {}
}
},
},
data() {
return {
zoom: 2
};
},
};
</script>
<style>
.leaflet-container {
margin-top:37px;
height:250px !important;
}
.leaflet-control-zoom {
display: none !important;
}
</style>

View File

@@ -55,14 +55,14 @@
<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>
<ul> <ul>
<li><strong>Connected</strong> <span :class="getResultClass(relay, 'connect')" class="connect indicator"></span></li> <li><strong>Connected</strong> <span :class="getResultClass(relay, 'connect')" class="connect indicator"></span></li>
<li><strong>Read</strong> <span :class="getResultClass(relay, 'read')" class="read indicator"></span></li> <li><strong>Read</strong> <span :class="getResultClass(relay, 'read')" class="read indicator"></span></li>
<li><strong>Write</strong> <span :class="getResultClass(relay, 'write')" class="write indicator"></span></li> <li><strong>Write</strong> <span :class="getResultClass(relay, 'write')" class="write indicator"></span></li>
</ul> </ul>
</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>

View File

@@ -1,14 +1,15 @@
import { createApp } from 'vue' import { createApp } from 'vue'
import App from './App.vue' import App from './App.vue'
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'
const app = createApp(App) const app = createApp(App)
app.mixin(titleMixin) app
.use(router)
.mixin(titleMixin)
directives(app); directives(app);
app.mount('#app') app.mount('#app')

228
src/pages/HomePage.vue Normal file
View File

@@ -0,0 +1,228 @@
<template>
<!-- <NavComponent /> -->
<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">
<LeafletComponent
:geo="geo"
:result="result"
/>
</column>
</row>
<row container :gutter="12">
<column :xs="12" :md="12" :lg="12">
<div class="block">
<table>
<RelayListComponent
section="public"
:relays="relays"
:result="result"
:geo="geo"
:messages="messages"
: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">
<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 RelayListComponent from '../components/RelayListComponent.vue'
import LeafletComponent from '../components/LeafletComponent.vue'
// import NavComponent from './NavComponent.vue'
import { Row, Column } from 'vue-grid-responsive';
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"
export default defineComponent({
title: "nostr.watch registry & network status",
name: 'RelayTableComponent',
components: {
Row,
Column,
RelayListComponent,
LeafletComponent
// NavComponent
},
data() {
return {
relays,
result: {},
messages: {},
connections: {},
nips: {},
alerts: {},
timeouts: {},
lastPing: Date.now(),
nextPing: Date.now() + (60*1000),
count: 0,
geo,
version: version
}
},
async mounted() {
this.relays.forEach(relay => {
this.check(relay)
})
},
computed: {},
methods: {
check(relay){
const opts = {
checkLatency: true,
setIP: false,
setGeo: false,
}
let inspect = new Inspector(relay, opts)
.on('run', (result) => {
result.aggregate = 'processing'
})
.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}
this.result[relay].observations.push( new InspectorObservation('notice', response_obj.code, response_obj.description, response_obj.relates_to) )
})
.on('close', () => {})
.on('error', () => {
})
.run()
this.connections[relay] = inspect
},
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-1 //TODO: Figure out WHY?
},
relaysConnected () {
return Object.entries(this.result).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>

294
src/pages/SingleRelay.vue Normal file
View File

@@ -0,0 +1,294 @@
<template>
<!-- <NavComponent /> -->
<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>
<h2>{{relayUrl()}}</h2>
</column>
</row>
<row container :gutter="12">
<column :xs="12" :md="12" :lg="12">
<LeafletSingleComponent
:geo="geo"
:relay="relay"
:result="result"
/>
</column>
</row>
<row container :gutter="12">
<column :xs="12" :md="12" :lg="12">
<div class="block">
<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>
<ul>
<li><strong>Connected</strong> <span :class="getResultClass(relay, 'connect')" class="connect indicator"></span></li>
<li><strong>Read</strong> <span :class="getResultClass(relay, 'read')" class="read indicator"></span></li>
<li><strong>Write</strong> <span :class="getResultClass(relay, 'write')" class="write indicator"></span></li>
</ul>
</div>
<h4>Relay Info</h4>
<ul>
<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}`">
<a :href="nipLink(nip)" target="_blank">{{ nipFormatted(nip) }}</a>
</li>
</ul>
</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 LeafletSingleComponent from '../components/LeafletSingleComponent.vue'
// import NavComponent from './NavComponent.vue'\
import { countryCodeEmoji } from 'country-code-emoji';
import emoji from 'node-emoji';
import { Row, Column } from 'vue-grid-responsive';
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"
export default defineComponent({
title: "nostr.watch registry & network status",
name: 'SingleRelay',
components: {
Row,
Column,
LeafletSingleComponent
// NavComponent
},
data() {
return {
relays,
result: {},
messages: {},
connections: {},
nips: {},
alerts: {},
timeouts: {},
lastPing: Date.now(),
nextPing: Date.now() + (60*1000),
count: 0,
geo,
relay: "",
version: version
}
},
async mounted() {
console.log('mounted')
this.relay = this.relayUrl()
this.check(this.relay)
},
computed: {
},
methods: {
relayUrl() {
// We will see what `params` is shortly
return `wss://${this.$route.params.relayUrl}`
},
check(relay){
const opts = {
checkLatency: true,
setIP: false,
setGeo: false,
}
let inspect = new Inspector(relay, opts)
.on('run', (result) => {
result.aggregate = 'processing'
})
.on('open', (e, result) => {
this.result = result
})
.on('complete', (instance) => {
console.log('on_complete', instance.result.aggregate)
this.result = 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}
this.result.observations.push( new InspectorObservation('notice', response_obj.code, response_obj.description, response_obj.relates_to) )
console.log(this.result.observations)
})
.on('close', () => {})
.on('error', () => {
})
.run()
this.connections[relay] = inspect
},
getResultClass (url, key) {
let result = this.result?.check?.[key] === true
? 'success'
: this.result?.check?.[key] === false
? 'failure'
: 'pending'
return `indicator ${result}`
},
getLoadingClass () {
return this.result?.state == 'complete' ? "relay loaded" : "relay"
},
generateKey (url, key) {
return `${url}_${key}`
},
getFlag () {
return this.geo?.countryCode ? countryCodeEmoji(this.geo.countryCode) : emoji.get('shrug');
},
setCheck (bool) {
return bool ? '✅ ' : ''
},
setCross (bool) {
return !bool ? '❌' : ''
},
setCaution (bool) {
return !bool ? '⚠️' : ''
},
identityList () {
let string = '',
extraString = '',
users = Object.entries(this.result.identities),
count = 0
console.log(this.result.uri, 'admin', 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, "
}
const total = users.filter(([key]) => key!='serverAdmin').length,
isOne = total==1
if(total) {
string = `${string}${extraString}Relay domain contains NIP-05 verification data for:`
users.forEach( ([key]) => {
if(key == "serverAdmin") return
count++
string = `${string} ${(count==total && !isOne) ? 'and' : ''} @${key}${(count!=total && !isOne) ? ', ' : ''}`
})
}
}
return string
},
nipSignature(key){
return key.toString().length == 1 ? `0${key}` : key
},
nipFormatted(key){
return `NIP-${this.nipSignature(key)}`
},
nipLink(key){
return `https://github.com/nostr-protocol/nips/blob/master/${this.nipSignature(key)}.md`
},
async copy(text) {
console.log('copy', text)
try {
await navigator.clipboard.writeText(text);
} catch($e) {
//console.log('Cannot copy');
}
},
// adjustResult (relay) {
// this.result.observations.forEach( observation => {
// if (observation.code == "BLOCKS_WRITE_STATUS_CHECK") {
// this.result.check.write = false
// this.result.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.aggregate = 'public'
}
else if (aggregateTally == 0) {
this.result.aggregate = 'offline'
}
else {
this.result.aggregate = 'restricted'
}
},
relaysTotal () {
return this.relays.length-1 //TODO: Figure out WHY?
},
relaysConnected () {
return Object.entries(this.result).length
},
sha1 (message) {
const hash = crypto.createHash('sha1').update(JSON.stringify(message)).digest('hex')
// //console.log(message, ':', hash)
return hash
},
isDone(){
return this.relaysTotal()-this.relaysConnected() == 0
},
loadingComplete(){
return this.isDone() ? 'loaded' : ''
},
},
})
</script>

27
src/router/index.js Normal file
View File

@@ -0,0 +1,27 @@
// /router/index.js
import { createRouter, createWebHistory } from 'vue-router'
import HomePage from '../pages/HomePage.vue'
import SingleRelay from '../pages/SingleRelay.vue'
const routes = [
{
path: '/',
name: 'nostr.watch',
component: HomePage
},
// Added our new route file named profile.vue
{
path: '/:relayUrl',
name: 'nostr.watch - :relayUrl',
component: SingleRelay
}
]
// Create Vue Router Object
const router = createRouter({
history: createWebHistory(),
base: process.env.BASE_URL,
routes
})
export default router

File diff suppressed because it is too large Load Diff