mirror of
https://github.com/aljazceru/nostr-watch.git
synced 2025-12-17 05:24:19 +01:00
Working refactor
This commit is contained in:
11
Dockerfile
11
Dockerfile
@@ -2,15 +2,18 @@ FROM node:14-alpine as build
|
|||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
COPY public/index.html /app/public/index.html
|
COPY . /app/
|
||||||
|
|
||||||
COPY main.js package.json build.js /app/
|
|
||||||
|
|
||||||
RUN yarn \
|
RUN yarn \
|
||||||
&& yarn run build
|
&& yarn build
|
||||||
|
|
||||||
|
RUN yarn global add yaml2json
|
||||||
|
|
||||||
|
RUN yaml2json relays.yaml > dist/relays.json
|
||||||
|
|
||||||
FROM nginx:stable-alpine as nginx-nostr-relay-registry
|
FROM nginx:stable-alpine as nginx-nostr-relay-registry
|
||||||
|
|
||||||
COPY ./nginx/conf.d/default.conf /etc/nginx/conf.d/default.conf
|
COPY ./nginx/conf.d/default.conf /etc/nginx/conf.d/default.conf
|
||||||
|
|
||||||
COPY --from=build /app/public /app
|
COPY --from=build /app/public /app
|
||||||
|
COPY --from=build /app/dist /app
|
||||||
|
|||||||
33
README.md
33
README.md
@@ -1,23 +1,24 @@
|
|||||||
# nostr-relay-registry
|
# nostr-relay-registry
|
||||||
|
|
||||||
A dynamic registry of nostr relays that tests for very basic tasks in real-time.
|
## Project setup
|
||||||
|
```
|
||||||
## Docker
|
yarn install
|
||||||
|
|
||||||
Build the docker image:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
docker build -t nostr-relay-registry .
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Run the container interactively:
|
### Compiles and hot-reloads for development
|
||||||
|
```
|
||||||
```bash
|
yarn serve
|
||||||
docker run --rm -it --name=nostr-relay-registry -p 8080:80 nostr-relay-registry
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Run container in the background:
|
### Compiles and minifies for production
|
||||||
|
|
||||||
```bash
|
|
||||||
docker run -d --restart unless-stopped --name nostr-relay-registry -p 8080:80 nostr-relay-registry
|
|
||||||
```
|
```
|
||||||
|
yarn build
|
||||||
|
```
|
||||||
|
|
||||||
|
### Lints and fixes files
|
||||||
|
```
|
||||||
|
yarn lint
|
||||||
|
```
|
||||||
|
|
||||||
|
### Customize configuration
|
||||||
|
See [Configuration Reference](https://cli.vuejs.org/config/).
|
||||||
|
|||||||
5
babel.config.js
Normal file
5
babel.config.js
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
module.exports = {
|
||||||
|
presets: [
|
||||||
|
'@vue/cli-plugin-babel/preset'
|
||||||
|
]
|
||||||
|
}
|
||||||
29
build.js
29
build.js
@@ -1,29 +0,0 @@
|
|||||||
#!/usr/bin/env node
|
|
||||||
|
|
||||||
const esbuild = require('esbuild')
|
|
||||||
const alias = require('esbuild-plugin-alias')
|
|
||||||
const nodeGlobals = require('@esbuild-plugins/node-globals-polyfill').default
|
|
||||||
const vuePlugin = require("esbuild-vue");
|
|
||||||
|
|
||||||
const prod = process.argv.indexOf('prod') !== -1
|
|
||||||
|
|
||||||
esbuild
|
|
||||||
.build({
|
|
||||||
bundle: true,
|
|
||||||
entryPoints: ['main.js'],
|
|
||||||
outdir: 'public',
|
|
||||||
plugins: [
|
|
||||||
alias({
|
|
||||||
stream: require.resolve('readable-stream')
|
|
||||||
}),
|
|
||||||
nodeGlobals({buffer: true}),
|
|
||||||
vuePlugin(),
|
|
||||||
],
|
|
||||||
minify: true,
|
|
||||||
sourcemap: prod ? false : 'inline',
|
|
||||||
define: {
|
|
||||||
window: 'self',
|
|
||||||
global: 'self'
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.then(() => console.log('build success.'))
|
|
||||||
41
codes.yaml
Normal file
41
codes.yaml
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
messages:
|
||||||
|
11cb5ca38038c3eb41bd014814f6e2e18da18ff1:
|
||||||
|
text: "we don't accept any events"
|
||||||
|
code: "READ_ONLY"
|
||||||
|
|
||||||
|
1003d4ec1466033d0dcc4a1babc6c5f409784593:
|
||||||
|
text: "NIP-05 verification needed to publish events"
|
||||||
|
code: "NIP_05_REQUIRED"
|
||||||
|
|
||||||
|
2e36f6955db854ac51105aa198fdf37cec694135:
|
||||||
|
text: "[ERROR]: Pubkey is not whitelisted."
|
||||||
|
code: "WHITELIST_REQUIRED"
|
||||||
|
|
||||||
|
5d6c9cb06d52c3f0456cc08fdf883dbe19b3c782:
|
||||||
|
text: "failed to save event from 5a462fa6044b4b8da318528a6987a45e3adf832bd1c64bd6910eacfecdf07541"
|
||||||
|
code: "BLOCKS_WRITE_STATUS_CHECK"
|
||||||
|
2c791b26eda3205558c235973ca84cc68a80e5e0:
|
||||||
|
text: "pubkey is not allowed to publish to this relay"
|
||||||
|
code: "BLOCKS_WRITE_STATUS_CHECK"
|
||||||
|
203442bfdf9f7f1562f1164b6dc79fefb42790ac:
|
||||||
|
text: 'failed to save event 41ce9bc50da77dda5542f020370ecc2b056d8f2be93c1cedf1bf57efcab095b0: pq: duplicate key value violates unique constraint "ididx"'
|
||||||
|
code: "BLOCKS_WRITE_STATUS_CHECK"
|
||||||
|
af155223f0e51dea64560c4ef6dc341a2775af76:
|
||||||
|
text: 'failed to save event from 5a462fa6044b4b8da318528a6987a45e3adf832bd1c64bd6910eacfecdf07541'
|
||||||
|
code: 'BLOCKS_WRITE_STATUS_CHECK'
|
||||||
|
codes:
|
||||||
|
READ_ONLY:
|
||||||
|
type: "write_restricted"
|
||||||
|
description: "This relay only access read queries"
|
||||||
|
WRITE_ONLY:
|
||||||
|
type: "write_restricted"
|
||||||
|
description: "This relay only accepts the publishing of events"
|
||||||
|
NIP_05_REQUIRED:
|
||||||
|
type: "write_restricted"
|
||||||
|
description: "This relay only accepts the publishing of events from NIP-05 verified public keys"
|
||||||
|
WHITELIST_REQUIRED:
|
||||||
|
type: "write_restricted"
|
||||||
|
description: "This relay onoly accepts the publishing of events from whitelisted public keys"
|
||||||
|
BLOCKS_WRITE_STATUS_CHECK:
|
||||||
|
type: "maybe_public"
|
||||||
|
description: "This relay blocks the events that enable us to test writing to the relay, so there's some uncertainty"
|
||||||
9
docker-compose.yaml
Normal file
9
docker-compose.yaml
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
services:
|
||||||
|
nostr-relay-registry:
|
||||||
|
ports:
|
||||||
|
- '80:80'
|
||||||
|
restart: always
|
||||||
|
logging:
|
||||||
|
options:
|
||||||
|
max-size: 1g
|
||||||
|
image: nrr
|
||||||
19
jsconfig.json
Normal file
19
jsconfig.json
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "es5",
|
||||||
|
"module": "esnext",
|
||||||
|
"baseUrl": "./",
|
||||||
|
"moduleResolution": "node",
|
||||||
|
"paths": {
|
||||||
|
"@/*": [
|
||||||
|
"src/*"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"lib": [
|
||||||
|
"esnext",
|
||||||
|
"dom",
|
||||||
|
"dom.iterable",
|
||||||
|
"scripthost"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
125
main.js
125
main.js
@@ -1,125 +0,0 @@
|
|||||||
import {createApp, h} from 'vue'
|
|
||||||
import {relayConnect} from 'nostr-tools/relay'
|
|
||||||
import yaml from 'js-yaml'
|
|
||||||
import fs from 'fs'
|
|
||||||
|
|
||||||
const App = {
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
relays: yaml.load(fs.readFileSync('relays.yml', 'utf8')).relays,
|
|
||||||
status: {}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
mounted() {
|
|
||||||
this.connections = {}
|
|
||||||
this.relays.forEach(async url => {
|
|
||||||
this.status[url] = {}
|
|
||||||
|
|
||||||
try {
|
|
||||||
let conn = relayConnect(
|
|
||||||
url,
|
|
||||||
() => {},
|
|
||||||
() => {
|
|
||||||
this.status[url].didConnect = false
|
|
||||||
}
|
|
||||||
)
|
|
||||||
this.status[url].didConnect = true
|
|
||||||
|
|
||||||
try {
|
|
||||||
await conn.publish({
|
|
||||||
id: '41ce9bc50da77dda5542f020370ecc2b056d8f2be93c1cedf1bf57efcab095b0',
|
|
||||||
pubkey:
|
|
||||||
'5a462fa6044b4b8da318528a6987a45e3adf832bd1c64bd6910eacfecdf07541',
|
|
||||||
created_at: 1640305962,
|
|
||||||
kind: 1,
|
|
||||||
tags: [],
|
|
||||||
content: 'running branle',
|
|
||||||
sig: '08e6303565e9282f32bed41eee4136f45418f366c0ec489ef4f90d13de1b3b9fb45e14c74f926441f8155236fb2f6fef5b48a5c52b19298a0585a2c06afe39ed'
|
|
||||||
})
|
|
||||||
this.status[url].didPublish = true
|
|
||||||
} catch (err) {
|
|
||||||
this.status[url].didPublish = false
|
|
||||||
}
|
|
||||||
|
|
||||||
let {unsub} = conn.sub(
|
|
||||||
{
|
|
||||||
cb: () => {
|
|
||||||
this.status[url].didQuery = true
|
|
||||||
unsub()
|
|
||||||
clearTimeout(willUnsub)
|
|
||||||
},
|
|
||||||
filter: {
|
|
||||||
ids: [
|
|
||||||
'41ce9bc50da77dda5542f020370ecc2b056d8f2be93c1cedf1bf57efcab095b0'
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
'nostr-registry'
|
|
||||||
)
|
|
||||||
let willUnsub = setTimeout(() => {
|
|
||||||
unsub()
|
|
||||||
this.status[url].didQuery = false
|
|
||||||
}, 3000)
|
|
||||||
} catch (err) {
|
|
||||||
this.status[url].didConnect = false
|
|
||||||
}
|
|
||||||
})
|
|
||||||
},
|
|
||||||
|
|
||||||
// render() {
|
|
||||||
// return h('table', {style: {fontSize: '28px'}}, [
|
|
||||||
// h('tr', [h('th'), h('th', 'connect'), h('th', 'read'), h('th', 'write')]),
|
|
||||||
// ...this.relays.map(url =>
|
|
||||||
// h('tr', [
|
|
||||||
// h(
|
|
||||||
// 'th',
|
|
||||||
// {
|
|
||||||
// style: {
|
|
||||||
// textAlign: 'right',
|
|
||||||
// whiteSpace: 'pre-wrap',
|
|
||||||
// wordWrap: 'break-word',
|
|
||||||
// wordBreak: 'break-all'
|
|
||||||
// }
|
|
||||||
// },
|
|
||||||
// url
|
|
||||||
// ),
|
|
||||||
// ...['didConnect', 'didQuery', 'didPublish'].map(attr =>
|
|
||||||
// h(
|
|
||||||
// 'td',
|
|
||||||
// {
|
|
||||||
// style: {
|
|
||||||
// fontSize: '50px',
|
|
||||||
// textAlign: 'center',
|
|
||||||
// padding: '5px 35px',
|
|
||||||
// color:
|
|
||||||
// this.status?.[url]?.[attr] === true
|
|
||||||
// ? 'green'
|
|
||||||
// : this.status?.[url]?.[attr] === false
|
|
||||||
// ? 'red'
|
|
||||||
// : 'silver'
|
|
||||||
// }
|
|
||||||
// },
|
|
||||||
// '•'
|
|
||||||
// )
|
|
||||||
// )
|
|
||||||
// ])
|
|
||||||
// ),
|
|
||||||
// h('tr', [
|
|
||||||
// h('th', {style: {textAlign: 'right'}}, 'Your relay here:'),
|
|
||||||
// h('th', {colSpan: 3}, [
|
|
||||||
// h(
|
|
||||||
// 'a',
|
|
||||||
// {
|
|
||||||
// href: 'https://github.com/fiatjaf/nostr-relay-registry',
|
|
||||||
// style: {color: 'black'}
|
|
||||||
// },
|
|
||||||
// '________________'
|
|
||||||
// )
|
|
||||||
// ])
|
|
||||||
// ])
|
|
||||||
// ])
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
|
|
||||||
createApp(App).mount('#app')
|
|
||||||
@@ -2,6 +2,10 @@ server {
|
|||||||
listen 80;
|
listen 80;
|
||||||
root /app;
|
root /app;
|
||||||
|
|
||||||
|
location /relays/ {
|
||||||
|
rewrite ^ relays.json break;
|
||||||
|
}
|
||||||
|
|
||||||
location / {
|
location / {
|
||||||
index index.html;
|
index index.html;
|
||||||
}
|
}
|
||||||
|
|||||||
16
onion.js
Normal file
16
onion.js
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
const url = require('url');
|
||||||
|
const http = require('http');
|
||||||
|
const https = require('https');
|
||||||
|
const SocksProxyAgent = require('socks-proxy-agent');
|
||||||
|
|
||||||
|
// Use the SOCKS_PROXY env var if using a custom bind address or port for your TOR proxy:
|
||||||
|
const proxy = process.env.SOCKS_PROXY || 'socks5h://127.0.0.1:9050';
|
||||||
|
console.log('Using proxy server %j', proxy);
|
||||||
|
// The default HTTP endpoint here is DuckDuckGo's v3 onion address:
|
||||||
|
const endpoint = process.argv[2] || 'https://duckduckgogg42xjoc72x3sjasowoarfbgcmvfimaftt6twagswzczad.onion';
|
||||||
|
console.log('Attempting to GET %j', endpoint);
|
||||||
|
// Prepare options for the http/s module by parsing the endpoint URL:
|
||||||
|
let options = url.parse(endpoint);
|
||||||
|
const agent = new SocksProxyAgent(proxy);
|
||||||
|
// Here we pass the socks proxy agent to the http/s module:
|
||||||
|
options.agent = agent;
|
||||||
77
package.json
77
package.json
@@ -1,20 +1,69 @@
|
|||||||
{
|
{
|
||||||
|
"name": "nostr-relay-registry",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"private": true,
|
||||||
|
"scripts": {
|
||||||
|
"serve": "vue-cli-service serve --host localhost",
|
||||||
|
"build": "vue-cli-service build",
|
||||||
|
"watch": "vue-cli-service build --watch",
|
||||||
|
"lint": "vue-cli-service lint"
|
||||||
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@esbuild-plugins/node-globals-polyfill": "^0.1.1",
|
"core-js": "^3.8.3",
|
||||||
"buffer": "^6.0.3",
|
"country-code-emoji": "2.3.0",
|
||||||
"esbuild": "^0.14.21",
|
"doh-resolver": "1.2.8",
|
||||||
"esbuild-plugin-alias": "^0.2.1",
|
"geoip-lite": "1.4.6",
|
||||||
"events": "^3.3.0",
|
"global": "4.4.0",
|
||||||
"nostr-tools": "^0.22.1",
|
"ip-fetch": "1.0.10",
|
||||||
"readable-stream": "^3.6.0",
|
"js-yaml": "4.1.0",
|
||||||
"vue": "3"
|
"json-loader": "^0.5.7",
|
||||||
|
"json-server": "0.17.1",
|
||||||
|
"leaflet": "1.9.3",
|
||||||
|
"node-emoji": "1.11.0",
|
||||||
|
"node-polyfill-webpack-plugin": "2.0.1",
|
||||||
|
"nostr": "https://github.com/dskvr/nostr-js",
|
||||||
|
"nostr-tools": "0.24.1",
|
||||||
|
"onion-regex": "2.0.8",
|
||||||
|
"requests": "0.3.0",
|
||||||
|
"socks-proxy-agent": "7.0.0",
|
||||||
|
"stream-browserify": "3.0.0",
|
||||||
|
"vue": "^3.2.13",
|
||||||
|
"vue-grid-responsive": "1.3.0",
|
||||||
|
"vue-nav-tabs": "0.5.7",
|
||||||
|
"vue-simple-maps": "1.1.3",
|
||||||
|
"vue3-popper": "1.5.0",
|
||||||
|
"yaml-loader": "^0.6.0",
|
||||||
|
"yaml2json": "1.0.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"esbuild-vue": "1.2.2",
|
"@babel/core": "^7.12.16",
|
||||||
"json-server": "0.17.1"
|
"@babel/eslint-parser": "^7.12.16",
|
||||||
|
"@vue/cli-plugin-babel": "~5.0.0",
|
||||||
|
"@vue/cli-plugin-eslint": "~5.0.0",
|
||||||
|
"@vue/cli-service": "~5.0.0",
|
||||||
|
"eslint": "^7.32.0",
|
||||||
|
"eslint-plugin-vue": "^8.0.3",
|
||||||
|
"vue-cli-plugin-yaml-loader": "~1.0.0",
|
||||||
|
"webpack-cli": "5.0.0"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"eslintConfig": {
|
||||||
"build": "./build.js prod",
|
"root": true,
|
||||||
"watch": "ag -l --js | entr ./build.js"
|
"env": {
|
||||||
}
|
"node": true
|
||||||
|
},
|
||||||
|
"extends": [
|
||||||
|
"plugin:vue/vue3-essential",
|
||||||
|
"eslint:recommended"
|
||||||
|
],
|
||||||
|
"parserOptions": {
|
||||||
|
"parser": "@babel/eslint-parser"
|
||||||
|
},
|
||||||
|
"rules": {}
|
||||||
|
},
|
||||||
|
"browserslist": [
|
||||||
|
"> 1%",
|
||||||
|
"last 2 versions",
|
||||||
|
"not dead",
|
||||||
|
"not ie 11"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,4 @@
|
|||||||
relays:
|
relays:
|
||||||
- 'wss://nostr-pub.wellorder.net'
|
|
||||||
- 'wss://relayer.fiatjaf.com'
|
|
||||||
- 'wss://nostr.rocks'
|
|
||||||
- 'wss://rsslay.fiatjaf.com'
|
- 'wss://rsslay.fiatjaf.com'
|
||||||
- 'wss://freedom-relay.herokuapp.com/ws'
|
- 'wss://freedom-relay.herokuapp.com/ws'
|
||||||
- 'wss://nostr-relay.freeberty.net'
|
- 'wss://nostr-relay.freeberty.net'
|
||||||
@@ -11,7 +8,6 @@ relays:
|
|||||||
- 'wss://nostr-relay.untethr.me'
|
- 'wss://nostr-relay.untethr.me'
|
||||||
- 'wss://nostr.semisol.dev'
|
- 'wss://nostr.semisol.dev'
|
||||||
- 'wss://nostr-pub.semisol.dev'
|
- 'wss://nostr-pub.semisol.dev'
|
||||||
- 'ws://jgqaglhautb4k6e6i2g34jakxiemqp6z4wynlirltuukgkft2xuglmqd.onion'
|
|
||||||
- 'wss://nostr-verified.wellorder.net'
|
- 'wss://nostr-verified.wellorder.net'
|
||||||
- 'wss://nostr.drss.io'
|
- 'wss://nostr.drss.io'
|
||||||
- 'wss://nostr.unknown.place'
|
- 'wss://nostr.unknown.place'
|
||||||
@@ -25,3 +21,7 @@ relays:
|
|||||||
- 'wss://nostr.ono.re'
|
- 'wss://nostr.ono.re'
|
||||||
- 'wss://relay.grunch.dev'
|
- 'wss://relay.grunch.dev'
|
||||||
- 'wss://relay.cynsar.foundation'
|
- 'wss://relay.cynsar.foundation'
|
||||||
|
- 'wss://nostr-pub.wellorder.net'
|
||||||
|
- 'wss://relayer.fiatjaf.com'
|
||||||
|
- 'wss://nostr.rocks'
|
||||||
|
- 'wss://nostr.sandwich.farm'
|
||||||
24
src/App.vue
24
src/App.vue
@@ -1,11 +1,25 @@
|
|||||||
<template>
|
<template>
|
||||||
<router-view />
|
<BaseRelays />
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { defineComponent } from 'vue'
|
import BaseRelays from './components/BaseRelays.vue'
|
||||||
|
|
||||||
export default defineComponent({
|
export default {
|
||||||
name: 'App'
|
name: 'App',
|
||||||
})
|
components: {
|
||||||
|
BaseRelays,
|
||||||
|
}
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
#app {
|
||||||
|
font-family: Avenir, Helvetica, Arial, sans-serif;
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
-moz-osx-font-smoothing: grayscale;
|
||||||
|
text-align: center;
|
||||||
|
color: #2c3e50;
|
||||||
|
margin-top: 60px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|||||||
BIN
src/assets/logo.png
Normal file
BIN
src/assets/logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 6.7 KiB |
@@ -0,0 +1,603 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<div class="text-h5 text-bold q-py-md q-px-sm full-width flex row justify-start">
|
||||||
|
<h1>Nostr Relay Registry</h1>
|
||||||
|
<span>Next ping in {{ nextPing }} seconds</span> |
|
||||||
|
<span v-if="relays.filter((url) => status[url] && !status[url].complete).length > 0">Processing {{relays.filter((url) => status[url].complete).length}}/{{relays.length}}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<row container :gutter="12">
|
||||||
|
<column :xs="12" :md="12" :lg="6">
|
||||||
|
<div>
|
||||||
|
<h2><span class="indicator badge readwrite">{{ query('public').length }}</span>Public</h2>
|
||||||
|
<table class="online" v-if="query('public').length > 0">
|
||||||
|
<tr>
|
||||||
|
<th></th>
|
||||||
|
<th></th>
|
||||||
|
<th>🔌</th>
|
||||||
|
<th>👁️🗨️</th>
|
||||||
|
<th>✏️</th>
|
||||||
|
<th>🌎</th>
|
||||||
|
<!-- <td>wl</td>
|
||||||
|
<td>nip-05><td> -->
|
||||||
|
<th>⌛️</th>
|
||||||
|
<th>ℹ️</th>
|
||||||
|
</tr>
|
||||||
|
<tr v-for="relay in query('public')" :key="{relay}" :class="getLoadingClass(relay)">
|
||||||
|
<td :key="generateKey(relay, 'aggregate')"><span :class="getAggregateStatusClass(relay)"></span></td>
|
||||||
|
|
||||||
|
<td class="left-align relay-url" @click="copy(relay)">{{ relay }}</td>
|
||||||
|
<td :key="generateKey(relay, 'didConnect')"><span :class="getStatusClass(relay, 'didConnect')"></span></td>
|
||||||
|
<td :key="generateKey(relay, 'didRead')"><span :class="getStatusClass(relay, 'didRead')"></span></td>
|
||||||
|
<td :key="generateKey(relay, 'didWrite')"><span :class="getStatusClass(relay, 'didWrite')"></span></td>
|
||||||
|
<td>{{status[relay].flag}}</td>
|
||||||
|
<td><span v-if="status[relay].didConnect">{{ status[relay].latency }}<span v-if="status[relay].latency">ms</span></span></td>
|
||||||
|
<td>
|
||||||
|
<Popper v-if="Object.keys(status[relay].messages).length">
|
||||||
|
{{ status[relay].type }}
|
||||||
|
<button @mouseover="showPopper">log</button>
|
||||||
|
<template #content>
|
||||||
|
<ul>
|
||||||
|
<li v-for="(message, key) in status[relay].messages" :key="generateKey(relay, key)">{{key}}</li>
|
||||||
|
</ul>
|
||||||
|
</template>
|
||||||
|
</Popper>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</column>
|
||||||
|
|
||||||
|
<column :xs="12" :md="12" :lg="6">
|
||||||
|
<div>
|
||||||
|
<h2><span class="indicator badge write-only">{{ query('restricted').length }}</span>Restricted</h2>
|
||||||
|
<table class="online">
|
||||||
|
<tr>
|
||||||
|
<th></th>
|
||||||
|
<th></th>
|
||||||
|
<th>🔌</th>
|
||||||
|
<th>👁️🗨️</th>
|
||||||
|
<th>✏️</th>
|
||||||
|
<th>🌎</th>
|
||||||
|
<th>⌛️</th>
|
||||||
|
<th>ℹ️</th>
|
||||||
|
</tr>
|
||||||
|
<tr v-for="relay in query('restricted')" :key="{relay}" :class="getLoadingClass(relay)">
|
||||||
|
<td :key="generateKey(relay, 'aggregate')"><span :class="getAggregateStatusClass(relay)"><span></span><span></span></span></td>
|
||||||
|
<td class="left-align relay-url" @click="copy(relay)">{{ relay }}</td>
|
||||||
|
<td :key="generateKey(relay, 'didConnect')"><span :class="getStatusClass(relay, 'didConnect')"></span></td>
|
||||||
|
<td :key="generateKey(relay, 'didRead')"><span :class="getStatusClass(relay, 'didRead')"></span></td>
|
||||||
|
<td :key="generateKey(relay, 'didWrite')"><span :class="getStatusClass(relay, 'didWrite')"></span></td>
|
||||||
|
<td>{{status[relay].flag}}</td>
|
||||||
|
<td><span v-if="status[relay].didConnect">{{ status[relay].latency }}<span v-if="status[relay].latency">ms</span></span></td>
|
||||||
|
<td>
|
||||||
|
<Popper v-if="Object.keys(status[relay].messages).length">
|
||||||
|
<button @mouseover="showPopper">log</button>
|
||||||
|
<template #content>
|
||||||
|
<ul>
|
||||||
|
<li v-for="(message, key) in status[relay].messages" :key="generateKey(relay, key)">{{key}}</li>
|
||||||
|
</ul>
|
||||||
|
</template>
|
||||||
|
</Popper>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<h2><span class="indicator badge offline">{{ query('offline').length }}</span>Offline</h2>
|
||||||
|
<table v-if="query('offline').length > 0">
|
||||||
|
<tr>
|
||||||
|
<th></th>
|
||||||
|
<th></th>
|
||||||
|
<th>🔌</th>
|
||||||
|
<th>👁️🗨️</th>
|
||||||
|
<th>✏️</th>
|
||||||
|
<th>msg</th>
|
||||||
|
</tr>
|
||||||
|
<tr v-for="relay in query('offline')" :key="{relay}" :class="getLoadingClass(relay)">
|
||||||
|
<td :key="generateKey(relay, 'aggregate')"><span :class="getAggregateStatusClass(relay)"></span></td>
|
||||||
|
<td class="left-align relay-url">{{ relay }}</td>
|
||||||
|
<td :key="generateKey(relay, 'didConnect')"><span :class="getStatusClass(relay, 'didConnect')"></span></td>
|
||||||
|
<td :key="generateKey(relay, 'didRead')"><span :class="getStatusClass(relay, 'didRead')"></span></td>
|
||||||
|
<td :key="generateKey(relay, 'didWrite')"><span :class="getStatusClass(relay, 'didWrite')"></span></td>
|
||||||
|
<td>
|
||||||
|
<Popper v-if="Object.keys(status[relay].messages).length">
|
||||||
|
<button>log</button>
|
||||||
|
<template #content>
|
||||||
|
<ul>
|
||||||
|
<li v-for="(message, key) in status[relay].messages" :key="generateKey(relay, key)">{{key}}</li>
|
||||||
|
</ul>
|
||||||
|
</template>
|
||||||
|
</Popper>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</column>
|
||||||
|
</row>
|
||||||
|
|
||||||
|
<!-- <h2>Processing</h2>
|
||||||
|
<table v-if="relays.filter((url) => !status[url].complete).length > 0">
|
||||||
|
<tr>
|
||||||
|
<th></th>
|
||||||
|
</tr>
|
||||||
|
<tr v-for="relay in relays.filter((url) => !status[url].complete)" :key="{relay}" :class="getLoadingClass(relay)">
|
||||||
|
<td>{{ relay }}</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
<a href="./relays/">JSON API</a> -->
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { defineComponent} from 'vue'
|
||||||
|
import { relayConnect } from 'nostr-tools/relay'
|
||||||
|
|
||||||
|
import { Row, Column } from 'vue-grid-responsive';
|
||||||
|
import Popper from "vue3-popper";
|
||||||
|
import onionRegex from 'onion-regex';
|
||||||
|
import countryCodeEmoji from 'country-code-emoji'
|
||||||
|
import emoji from 'node-emoji'
|
||||||
|
|
||||||
|
import { relays } from '../../relays.yaml'
|
||||||
|
import { messages as RELAY_MESSAGES, codes as RELAY_CODES } from '../../codes.yaml'
|
||||||
|
|
||||||
|
import crypto from "crypto"
|
||||||
|
|
||||||
|
const refreshMillis = 3*60*1000
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
name: 'BaseRelays',
|
||||||
|
components: {
|
||||||
|
Row,
|
||||||
|
Column,
|
||||||
|
Popper
|
||||||
|
},
|
||||||
|
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
relays,
|
||||||
|
status: {},
|
||||||
|
lastPing: Date.now(),
|
||||||
|
nextPing: Date.now() + (60*1000),
|
||||||
|
connections: {},
|
||||||
|
latency: {},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
|
||||||
|
// query (group, filterType) {
|
||||||
|
query (group) {
|
||||||
|
let unordered,
|
||||||
|
filterFn
|
||||||
|
|
||||||
|
// if(filterByType) {
|
||||||
|
// filterFn = (relay) => this.status?.[relay]?.aggregate == group || this.status?.[relay]?.[filterType];
|
||||||
|
// } else {
|
||||||
|
filterFn = (relay) => this.status?.[relay]?.aggregate == group
|
||||||
|
// }
|
||||||
|
|
||||||
|
unordered = this.relays.filter(filterFn);
|
||||||
|
|
||||||
|
if (unordered.length) {
|
||||||
|
return unordered.sort((relay1, relay2) => {
|
||||||
|
return this.status?.[relay1]?.latency - this.status?.[relay2]?.latency
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return []
|
||||||
|
},
|
||||||
|
|
||||||
|
getAggregateStatusClass (url) {
|
||||||
|
let status = ''
|
||||||
|
if (this.status?.[url]?.aggregate == null) {
|
||||||
|
status = 'unprocessed'
|
||||||
|
}
|
||||||
|
else if (this.status?.[url]?.aggregate == 'public') {
|
||||||
|
status = 'readwrite'
|
||||||
|
}
|
||||||
|
else if (this.status?.[url]?.aggregate == 'restricted') {
|
||||||
|
if(this.status?.[url]?.didWrite) {
|
||||||
|
status = 'write-only'
|
||||||
|
} else {
|
||||||
|
status = 'read-only'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (this.status?.[url]?.aggregate == 'offline') {
|
||||||
|
status = 'offline'
|
||||||
|
}
|
||||||
|
return `aggregate indicator ${status}`
|
||||||
|
},
|
||||||
|
|
||||||
|
getStatusClass (url, key) {
|
||||||
|
let status = this.status?.[url]?.[key] === true
|
||||||
|
? 'green'
|
||||||
|
: this.status?.[url]?.[key] === false
|
||||||
|
? 'red'
|
||||||
|
: 'silver'
|
||||||
|
return `indicator ${status}`
|
||||||
|
},
|
||||||
|
|
||||||
|
getLoadingClass (url) {
|
||||||
|
return this.status?.[url]?.complete ? "relay loaded" : "relay"
|
||||||
|
},
|
||||||
|
|
||||||
|
setAggregateStatus (url) {
|
||||||
|
let aggregateTally = 0
|
||||||
|
aggregateTally += this.status?.[url]?.didConnect ? 1 : 0
|
||||||
|
aggregateTally += this.status?.[url]?.didRead ? 1 : 0
|
||||||
|
aggregateTally += this.status?.[url]?.didWrite ? 1 : 0
|
||||||
|
if (aggregateTally == 3) {
|
||||||
|
this.status[url].aggregate = 'public'
|
||||||
|
}
|
||||||
|
else if (aggregateTally == 0) {
|
||||||
|
this.status[url].aggregate = 'offline'
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this.status[url].aggregate = 'restricted'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
async copy(text) {
|
||||||
|
try {
|
||||||
|
await navigator.clipboard.writeText(text);
|
||||||
|
} catch($e) {
|
||||||
|
console.log('Cannot copy');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
setComplete (url) {
|
||||||
|
this.setAggregateStatus(url)
|
||||||
|
this.status[url].complete = true
|
||||||
|
},
|
||||||
|
generateKey (url, key) {
|
||||||
|
return `${url}_${key}`
|
||||||
|
},
|
||||||
|
|
||||||
|
testConnect (url) {
|
||||||
|
console.log(url, "CONNECT", "TEST")
|
||||||
|
this.connections[url] = relayConnect(
|
||||||
|
url,
|
||||||
|
// () => {},
|
||||||
|
(message) => {
|
||||||
|
console.log(url, "CONNECT", "SUCCESS")
|
||||||
|
const hash = this.sha1(message)
|
||||||
|
let message_obj = RELAY_MESSAGES[hash]
|
||||||
|
let code_obj = RELAY_CODES[message_obj.code]
|
||||||
|
|
||||||
|
console.log(hash)
|
||||||
|
console.dir(message_obj)
|
||||||
|
console.dir(code_obj)
|
||||||
|
|
||||||
|
message_obj.type = code_obj.type
|
||||||
|
|
||||||
|
this.status[url].messages[message] = message_obj
|
||||||
|
|
||||||
|
this.adjustStatus(url, hash)
|
||||||
|
// console.log("RECIEVED MESSAGE!")
|
||||||
|
// console.dir(this.status[url].messages)
|
||||||
|
},
|
||||||
|
() => {
|
||||||
|
console.log(url, "CONNECT", "FAILURE")
|
||||||
|
this.status[url].didConnect = false
|
||||||
|
this.status[url].didRead = false
|
||||||
|
this.status[url].didWrite = false
|
||||||
|
this.setComplete(url)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
this.status[url].didConnect = true
|
||||||
|
},
|
||||||
|
|
||||||
|
async testRead (url) {
|
||||||
|
console.dir(this.connections[url])
|
||||||
|
// console.log(this.connections[url]['get status']())
|
||||||
|
console.log(url, "READ", "TEST")
|
||||||
|
let start
|
||||||
|
start = Date.now();
|
||||||
|
let {unsub} = await this.connections[url].sub(
|
||||||
|
{
|
||||||
|
cb: () => {
|
||||||
|
console.log(url, "READ", "SUCCESS")
|
||||||
|
this.status[url].didRead = true
|
||||||
|
this.setComplete(url)
|
||||||
|
this.latency[url].read = Date.now() - start;
|
||||||
|
unsub()
|
||||||
|
clearTimeout(willUnsub)
|
||||||
|
},
|
||||||
|
filter: {
|
||||||
|
ids: [
|
||||||
|
'41ce9bc50da77dda5542f020370ecc2b056d8f2be93c1cedf1bf57efcab095b0'
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'nostr-registry'
|
||||||
|
)
|
||||||
|
let willUnsub = setTimeout(() => {
|
||||||
|
unsub()
|
||||||
|
console.log(url, "READ", "FAILURE")
|
||||||
|
if(!this.status[url].maybe_public) this.status[url].didRead = false
|
||||||
|
this.setComplete(url)
|
||||||
|
}, 10000)
|
||||||
|
},
|
||||||
|
|
||||||
|
async testWrite (url) {
|
||||||
|
console.log(url, "WRITE", "TEST")
|
||||||
|
let start
|
||||||
|
start = Date.now();
|
||||||
|
await this.connections[url].publish({
|
||||||
|
id: '41ce9bc50da77dda5542f020370ecc2b056d8f2be93c1cedf1bf57efcab095b0',
|
||||||
|
pubkey:
|
||||||
|
'5a462fa6044b4b8da318528a6987a45e3adf832bd1c64bd6910eacfecdf07541',
|
||||||
|
created_at: 1640305962,
|
||||||
|
kind: 1,
|
||||||
|
tags: [],
|
||||||
|
content: 'running branle',
|
||||||
|
sig: '08e6303565e9282f32bed41eee4136f45418f366c0ec489ef4f90d13de1b3b9fb45e14c74f926441f8155236fb2f6fef5b48a5c52b19298a0585a2c06afe39ed'
|
||||||
|
})
|
||||||
|
this.latency[url].write = Date.now() - start;
|
||||||
|
},
|
||||||
|
|
||||||
|
async testRelay (url) {
|
||||||
|
this.lastPing = Date.now()
|
||||||
|
this.latency[url] = {}
|
||||||
|
this.status[url].messages = {}
|
||||||
|
try {
|
||||||
|
|
||||||
|
//Test Connect
|
||||||
|
this.testConnect(url)
|
||||||
|
|
||||||
|
//Test Write
|
||||||
|
try {
|
||||||
|
await this.testWrite(url)
|
||||||
|
console.log(url, "WRITE", "SUCCESS")
|
||||||
|
this.status[url].didWrite = true
|
||||||
|
} catch (err) {
|
||||||
|
console.log(url, "WRITE", "FAILURE")
|
||||||
|
this.status[url].didWrite = false
|
||||||
|
this.setComplete(url)
|
||||||
|
}
|
||||||
|
|
||||||
|
//Test Read
|
||||||
|
this.testRead(url)
|
||||||
|
|
||||||
|
} catch (err) {
|
||||||
|
this.status[url].didConnect = false
|
||||||
|
this.setComplete(url)
|
||||||
|
}
|
||||||
|
|
||||||
|
if(this.status[url].didRead){
|
||||||
|
this.setLatency(url)
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.getIP(url)
|
||||||
|
await this.setGeo(url)
|
||||||
|
this.setFlag(url)
|
||||||
|
},
|
||||||
|
|
||||||
|
isOnion(url){
|
||||||
|
return onionRegex().test(url)
|
||||||
|
},
|
||||||
|
|
||||||
|
setLatency(url) {
|
||||||
|
this.status[url].latency = this.latency[url].read
|
||||||
|
},
|
||||||
|
|
||||||
|
testRelayLatency(){
|
||||||
|
console.log('testing latency')
|
||||||
|
this.relays.forEach(url => {
|
||||||
|
// this.testWrite(url, true)
|
||||||
|
this.testRead(url, true)
|
||||||
|
this.setLatency(url)
|
||||||
|
})
|
||||||
|
this.lastPing = Date.now()
|
||||||
|
},
|
||||||
|
|
||||||
|
async getIP(url){
|
||||||
|
let ip
|
||||||
|
await fetch(`https://1.1.1.1/dns-query?name=${url.replace('wss://', '')}`, { headers: { 'accept': 'application/dns-json' } })
|
||||||
|
.then(response => response.json())
|
||||||
|
.then((data) => { ip = data.Answer ? data.Answer[data.Answer.length-1].data : false });
|
||||||
|
this.status[url].ip = ip
|
||||||
|
console.log('IP:', ip)
|
||||||
|
},
|
||||||
|
|
||||||
|
async setGeo(url){
|
||||||
|
if (!this.status[url].ip) return
|
||||||
|
await fetch(`http://ip-api.com/json/${this.status[url].ip}`, { headers: { 'accept': 'application/dns-json' } })
|
||||||
|
.then(response => response.json())
|
||||||
|
.then((data) => { this.status[url].geo = data });
|
||||||
|
console.dir(this.status[url].geo)
|
||||||
|
},
|
||||||
|
|
||||||
|
setFlag (url) {
|
||||||
|
this.status[url].flag = this.status[url].geo?.countryCode ? countryCodeEmoji(this.status[url].geo.countryCode) : emoji.get('shrug');
|
||||||
|
},
|
||||||
|
|
||||||
|
adjustStatus (url, hash) {
|
||||||
|
let code = RELAY_MESSAGES[hash].code,
|
||||||
|
type = RELAY_CODES[code].type
|
||||||
|
|
||||||
|
this.status[url][type] = code
|
||||||
|
if (type == "maybe_public") {
|
||||||
|
this.status[url].didWrite = true
|
||||||
|
this.status[url].didRead = true
|
||||||
|
}
|
||||||
|
if (type == "write_restricted") {
|
||||||
|
this.status[url].didWrite = false
|
||||||
|
}
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
sha1 (message) {
|
||||||
|
const hash = crypto.createHash('sha1').update(JSON.stringify(message)).digest('hex')
|
||||||
|
// console.log(message, ':', hash)
|
||||||
|
return hash
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
mounted() {
|
||||||
|
this.relays.forEach(async url => {
|
||||||
|
this.status[url] = {} //statusInterface
|
||||||
|
if (this.isOnion(url)) {
|
||||||
|
url = `${url}.to` //add proxy
|
||||||
|
}
|
||||||
|
await this.testRelay(url)
|
||||||
|
})
|
||||||
|
|
||||||
|
// eslint-disable-next-line
|
||||||
|
let latencyTimeout = setTimeout(() => { this.testRelayLatency() }, 10000)
|
||||||
|
|
||||||
|
// eslint-disable-next-line
|
||||||
|
let latencyIntVal = setInterval(() => { this.testRelayLatency() }, refreshMillis)
|
||||||
|
// eslint-disable-next-line
|
||||||
|
let counterIntVal = setInterval(() => {
|
||||||
|
this.nextPing = Math.round((this.lastPing + refreshMillis - Date.now())/1000)
|
||||||
|
}, 1000)
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang='css' scoped>
|
||||||
|
.q-tabs {
|
||||||
|
border-bottom: 1px solid var(--q-accent)
|
||||||
|
}
|
||||||
|
|
||||||
|
table {
|
||||||
|
width:100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.left-align {
|
||||||
|
text-align:left;
|
||||||
|
}
|
||||||
|
|
||||||
|
tr.relay td {
|
||||||
|
font-style: italic;
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
tr.relay.loaded td {
|
||||||
|
font-style: normal;
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.indicator {
|
||||||
|
display:block;
|
||||||
|
margin: 0 auto;
|
||||||
|
height: 14px;
|
||||||
|
width: 14px;
|
||||||
|
border-radius: 7px;
|
||||||
|
border-width:0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.badge {
|
||||||
|
height:auto;
|
||||||
|
width: auto;
|
||||||
|
display:inline-block;
|
||||||
|
padding: 2px 5px;
|
||||||
|
font-size: 15px;
|
||||||
|
position: relative;
|
||||||
|
top: -3px;
|
||||||
|
min-width: 15px;
|
||||||
|
margin-right:5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.badge.readwrite,
|
||||||
|
.badge.offline {
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.badge.write-only,
|
||||||
|
.badge.read-only {
|
||||||
|
background-color:orange !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.aggregate.indicator {
|
||||||
|
background-color: transparent;
|
||||||
|
border-radius: 0px;
|
||||||
|
border-style: solid;
|
||||||
|
}
|
||||||
|
|
||||||
|
.indicator.silver {
|
||||||
|
background-color: #c0c0c0;
|
||||||
|
border-color: rgba(55,55,55,0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
.indicator.green {
|
||||||
|
background-color: green;
|
||||||
|
border-color: rgba(0,255,0,0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
.indicator.red {
|
||||||
|
background-color: red;
|
||||||
|
border-color: rgba(255,0,0,0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
.indicator.orange {
|
||||||
|
background-color: orange;
|
||||||
|
border-color: rgba(255, 191, 0,0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
.indicator.readwrite {
|
||||||
|
background-color: green;
|
||||||
|
border-color: rgba(0,255,0,0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
.indicator.read-only {
|
||||||
|
position:relative;
|
||||||
|
border-color: transparent;
|
||||||
|
background-color: transparent
|
||||||
|
}
|
||||||
|
|
||||||
|
.indicator.read-only span:first-child {
|
||||||
|
position:absolute;
|
||||||
|
width: 0;
|
||||||
|
height: 0;
|
||||||
|
border-top: 14px solid green;
|
||||||
|
border-right: 14px solid transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.indicator.read-only span:last-child {
|
||||||
|
position:absolute;
|
||||||
|
width: 0;
|
||||||
|
height: 0;
|
||||||
|
border-bottom: 14px solid orange;
|
||||||
|
border-left: 14px solid transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.indicator.write-only {
|
||||||
|
position:relative;
|
||||||
|
border-color: transparent;
|
||||||
|
background-color: transparent
|
||||||
|
}
|
||||||
|
|
||||||
|
.indicator.write-only span:first-child {
|
||||||
|
position:absolute;
|
||||||
|
width: 0;
|
||||||
|
height: 0;
|
||||||
|
border-bottom: 14px solid orange;
|
||||||
|
border-left: 14px solid transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.indicator.write-only span:last-child {
|
||||||
|
position:absolute;
|
||||||
|
width: 0;
|
||||||
|
height: 0;
|
||||||
|
border-top: 14px solid green;
|
||||||
|
border-right: 14px solid transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.indicator.offline {
|
||||||
|
background-color: red;
|
||||||
|
border-color: rgba(255,0,0,0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
table.online .relay-url {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
||||||
|
|||||||
39
src/components/RelayListComponent.vue
Normal file
39
src/components/RelayListComponent.vue
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
<template>
|
||||||
|
<h2><span class="indicator badge readwrite">{{ query('public').length }}</span>Public</h2>
|
||||||
|
<table class="online" v-if="query('public').length > 0">
|
||||||
|
<tr>
|
||||||
|
<th></th>
|
||||||
|
<th></th>
|
||||||
|
<th>🔌</th>
|
||||||
|
<th>👁️🗨️</th>
|
||||||
|
<th>✏️</th>
|
||||||
|
<th>🌎</th>
|
||||||
|
<!-- <td>wl</td>
|
||||||
|
<td>nip-05><td> -->
|
||||||
|
<th>⌛️</th>
|
||||||
|
<th>ℹ️</th>
|
||||||
|
</tr>
|
||||||
|
<tr v-for="relay in query('public')" :key="{relay}" :class="getLoadingClass(relay)">
|
||||||
|
<RelaySingle
|
||||||
|
:relay="{{relay}}"
|
||||||
|
:didConnect="{{didConnect}}"
|
||||||
|
:didRead="{{didRead}}"
|
||||||
|
:didWrite="{{didWrite}}"
|
||||||
|
/>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { defineComponent} from 'vue'
|
||||||
|
export default defineComponent({
|
||||||
|
name: 'RelaySingle',
|
||||||
|
components: {
|
||||||
|
Popper
|
||||||
|
},
|
||||||
|
|
||||||
|
data() {
|
||||||
|
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
||||||
38
src/components/RelaySingleComponent.vue
Normal file
38
src/components/RelaySingleComponent.vue
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
<template>
|
||||||
|
<td :key="generateKey(relay, 'aggregate')"><span :class="getAggregateStatusClass(relay)"></span></td>
|
||||||
|
|
||||||
|
<td class="left-align relay-url" @click="copy(relay)">{{ relay }}</td>
|
||||||
|
<td :key="generateKey(relay, 'didConnect')"><span :class="getStatusClass(relay, 'didConnect')"></span></td>
|
||||||
|
<td :key="generateKey(relay, 'didRead')"><span :class="getStatusClass(relay, 'didRead')"></span></td>
|
||||||
|
<td :key="generateKey(relay, 'didWrite')"><span :class="getStatusClass(relay, 'didWrite')"></span></td>
|
||||||
|
<td>{{status[relay].flag}}</td>
|
||||||
|
<td><span v-if="status[relay].didConnect">{{ status[relay].latency }}<span v-if="status[relay].latency">ms</span></span></td>
|
||||||
|
<td>
|
||||||
|
<Popper v-if="Object.keys(status[relay].messages).length">
|
||||||
|
{{ status[relay].type }}
|
||||||
|
<button @mouseover="showPopper">log</button>
|
||||||
|
<template #content>
|
||||||
|
<ul>
|
||||||
|
<li v-for="(message, key) in status[relay].messages" :key="generateKey(relay, key)">{{key}}</li>
|
||||||
|
</ul>
|
||||||
|
</template>
|
||||||
|
</Popper>
|
||||||
|
</td>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { defineComponent} from 'vue'
|
||||||
|
export default defineComponent({
|
||||||
|
name: 'RelaySingle',
|
||||||
|
components: {
|
||||||
|
Popper
|
||||||
|
},
|
||||||
|
props: [
|
||||||
|
'relay',
|
||||||
|
'status',
|
||||||
|
]
|
||||||
|
data() {
|
||||||
|
return {}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
||||||
0
src/index.js
Normal file
0
src/index.js
Normal file
4
src/main.js
Normal file
4
src/main.js
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
import { createApp } from 'vue'
|
||||||
|
import App from './App.vue'
|
||||||
|
|
||||||
|
createApp(App).mount('#app')
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
import { route } from 'quasar/wrappers'
|
|
||||||
import {
|
|
||||||
createRouter,
|
|
||||||
createWebHistory,
|
|
||||||
createWebHashHistory,
|
|
||||||
} from 'vue-router'
|
|
||||||
import routes from './routes'
|
|
||||||
|
|
||||||
export default route(() => {
|
|
||||||
const createHistory =
|
|
||||||
process.env.VUE_ROUTER_MODE === 'history'
|
|
||||||
? createWebHistory
|
|
||||||
: createWebHashHistory
|
|
||||||
|
|
||||||
const Router = createRouter({
|
|
||||||
routes,
|
|
||||||
history: createHistory(
|
|
||||||
process.env.MODE === 'ssr' ? void 0 : process.env.VUE_ROUTER_BASE
|
|
||||||
),
|
|
||||||
})
|
|
||||||
|
|
||||||
return Router
|
|
||||||
})
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
const routes = [
|
|
||||||
{
|
|
||||||
path: '/',
|
|
||||||
component: () => import('layouts/MainLayout.vue'),
|
|
||||||
children: [
|
|
||||||
{ path: '', component: () => import('pages/IndexPage.vue') }
|
|
||||||
]
|
|
||||||
|
|
||||||
export default routes
|
|
||||||
0
src/utils/test-connection-clearnet.js
Normal file
0
src/utils/test-connection-clearnet.js
Normal file
0
src/utils/test-connection-onion.js
Normal file
0
src/utils/test-connection-onion.js
Normal file
25
vue.config.js
Normal file
25
vue.config.js
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
const { defineConfig } = require('@vue/cli-service')
|
||||||
|
const NodePolyfillPlugin = require("node-polyfill-webpack-plugin");
|
||||||
|
|
||||||
|
module.exports = defineConfig({
|
||||||
|
transpileDependencies: true,
|
||||||
|
devServer: {
|
||||||
|
port: 8080
|
||||||
|
},
|
||||||
|
configureWebpack: {
|
||||||
|
// watch: true,
|
||||||
|
plugins: [new NodePolyfillPlugin()],
|
||||||
|
optimization: {
|
||||||
|
splitChunks: {
|
||||||
|
chunks: "all",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
chainWebpack: config => {
|
||||||
|
config.module
|
||||||
|
.rule('yaml')
|
||||||
|
.test(/\.ya?ml?$/)
|
||||||
|
.use('yaml-loader')
|
||||||
|
.loader('yaml-loader')
|
||||||
|
}
|
||||||
|
})
|
||||||
6179
yarn-error.log
Normal file
6179
yarn-error.log
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user