mirror of
https://github.com/lalanza808/monero.fail.git
synced 2025-04-20 05:45:51 -04:00
starting frontend revamp for fun w/ new geoip on nodes
This commit is contained in:
parent
2a6d0a8e9c
commit
8e5f35b0d3
@ -30,4 +30,6 @@ Werkzeug==2.2.3
|
||||
WTForms==3.0.1
|
||||
yarl==1.8.2
|
||||
zipp==3.15.0
|
||||
dnspython==1.15.0
|
||||
ipwhois==1.2.0
|
||||
matplotlib==3.7.1
|
||||
numpy==1.24.3
|
||||
|
@ -8,7 +8,7 @@ import requests
|
||||
from flask import Blueprint
|
||||
from urllib.parse import urlparse
|
||||
|
||||
from xmrnodes.helpers import determine_crypto, is_onion, is_i2p, make_request
|
||||
from xmrnodes.helpers import determine_crypto, is_onion, is_i2p, make_request, get_whois
|
||||
from xmrnodes.helpers import retrieve_peers, rw_cache, get_highest_block, get_geoip
|
||||
from xmrnodes.models import Node, HealthCheck, Peer
|
||||
from xmrnodes import config
|
||||
@ -23,7 +23,7 @@ def init():
|
||||
|
||||
@bp.cli.command("check")
|
||||
def check_nodes():
|
||||
diff = datetime.utcnow() - timedelta(hours=72)
|
||||
diff = datetime.utcnow() - timedelta(hours=96)
|
||||
checks = HealthCheck.select().where(HealthCheck.datetime <= diff)
|
||||
for check in checks:
|
||||
print("Deleting check", check.id)
|
||||
@ -194,6 +194,13 @@ def validate():
|
||||
node.lat = geoip.location.latitude
|
||||
node.lon = geoip.location.longitude
|
||||
logging.info(f"found geo data for {node.url} - {node.country_code}, {node.country_name}, {node.city}")
|
||||
whois = get_whois(node.url)
|
||||
if whois:
|
||||
logging.info(f"found whois data for {node.url}")
|
||||
node.asn = whois['asn']
|
||||
node.asn_cidr = whois['asn_cidr']
|
||||
node.asn_country_code = whois['asn_country_code']
|
||||
node.asn_description = whois['asn_description']
|
||||
node.save()
|
||||
logging.info("success")
|
||||
else:
|
||||
|
@ -4,6 +4,7 @@ import pickle
|
||||
from os import path
|
||||
|
||||
import geoip2.database
|
||||
from ipwhois import ipwhois
|
||||
from requests import get as r_get
|
||||
from urllib.parse import urlparse
|
||||
from levin.bucket import Bucket
|
||||
@ -159,18 +160,21 @@ def get_highest_block(nettype, crypto):
|
||||
else:
|
||||
return 0
|
||||
|
||||
|
||||
def get_geoip(ip_or_dns):
|
||||
host = urlparse(ip_or_dns).netloc.split(':')[0]
|
||||
def get_host(node_url):
|
||||
host = urlparse(node_url).netloc.split(':')[0]
|
||||
resolved = socket.gethostbyname(host)
|
||||
host = host if resolved == host else resolved
|
||||
return host if resolved == host else resolved
|
||||
|
||||
|
||||
def get_geoip(node_url):
|
||||
host = get_host(node_url)
|
||||
with geoip2.database.Reader("./data/GeoLite2-City.mmdb") as reader:
|
||||
return reader.city(host)
|
||||
|
||||
def get_whois(ip_or_dns):
|
||||
pass
|
||||
|
||||
# asn
|
||||
# asn_cidr
|
||||
# asn_country_code
|
||||
# asn_description
|
||||
def get_whois(node_url):
|
||||
host = get_host(node_url)
|
||||
try:
|
||||
who = ipwhois.IPWhois(host).lookup_rdap()
|
||||
return who
|
||||
except:
|
||||
return None
|
||||
|
@ -1,4 +1,5 @@
|
||||
from urllib.parse import urlparse
|
||||
from statistics import mean
|
||||
from datetime import datetime
|
||||
|
||||
from peewee import *
|
||||
@ -28,6 +29,10 @@ class Node(Model):
|
||||
postal = IntegerField(null=True)
|
||||
lat = FloatField(null=True)
|
||||
lon = FloatField(null=True)
|
||||
asn = CharField(null=True)
|
||||
asn_cidr = CharField(null=True)
|
||||
asn_country_code = CharField(null=True)
|
||||
asn_description = CharField(null=True)
|
||||
datetime_entered = DateTimeField(default=datetime.utcnow)
|
||||
datetime_checked = DateTimeField(default=None, null=True)
|
||||
datetime_failed = DateTimeField(default=None, null=True)
|
||||
@ -46,6 +51,16 @@ class Node(Model):
|
||||
def get_all_checks(self):
|
||||
hcs = HealthCheck.select().where(HealthCheck.node == self)
|
||||
return hcs
|
||||
|
||||
@property
|
||||
def uptime(self):
|
||||
hcs = self.healthchecks
|
||||
ups = [1 for i in hcs if i.health == True]
|
||||
downs = [0 for i in hcs if i.health == False]
|
||||
if (len(ups + downs)):
|
||||
m = mean(ups + downs)
|
||||
return int(m * 100)
|
||||
return 0
|
||||
|
||||
class Meta:
|
||||
database = db
|
||||
@ -79,6 +94,10 @@ class HealthCheck(Model):
|
||||
datetime = DateTimeField(default=datetime.utcnow)
|
||||
health = BooleanField()
|
||||
|
||||
@property
|
||||
def color(self):
|
||||
return 'green' if self.health else 'red'
|
||||
|
||||
class Meta:
|
||||
database = db
|
||||
|
||||
|
@ -1,9 +1,15 @@
|
||||
import re
|
||||
from io import BytesIO
|
||||
from random import shuffle
|
||||
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
import matplotlib.pyplot as plt
|
||||
from urllib.parse import urlparse
|
||||
from flask import request, redirect, Blueprint
|
||||
from flask import render_template, flash, Response
|
||||
from urllib.parse import urlparse
|
||||
from matplotlib.backends.backend_agg import FigureCanvasAgg as FigureCanvas
|
||||
from matplotlib.figure import Figure
|
||||
|
||||
from xmrnodes.helpers import rw_cache, get_highest_block
|
||||
from xmrnodes.forms import SubmitNode
|
||||
@ -61,6 +67,33 @@ def index():
|
||||
)
|
||||
|
||||
|
||||
@bp.route('/plot/<int:node_id>.png')
|
||||
def plot_health(node_id):
|
||||
node = Node.get(node_id).healthchecks
|
||||
if not node:
|
||||
return None
|
||||
df = pd.DataFrame(node.dicts())
|
||||
df['health'] = df['health'].astype(int)
|
||||
fig = Figure(figsize=(3,3), tight_layout=True)
|
||||
output = BytesIO()
|
||||
axis = fig.add_subplot()
|
||||
dff = df.groupby(['health']).agg({'health': 'count'})
|
||||
if 0 not in dff.index:
|
||||
dff.loc[0] = 0
|
||||
print(dff.index)
|
||||
dff = dff.sort_index() # sorting by index
|
||||
axis.pie(
|
||||
dff['health'],
|
||||
colors=['#B76D68', '#78BC61'],
|
||||
radius=8,
|
||||
wedgeprops={"linewidth": 2, "edgecolor": "white"},
|
||||
frame=True
|
||||
)
|
||||
axis.axis('off')
|
||||
FigureCanvas(fig).print_png(output)
|
||||
return Response(output.getvalue(), mimetype='image/png')
|
||||
|
||||
|
||||
@bp.route("/map")
|
||||
def map():
|
||||
try:
|
||||
|
1
xmrnodes/static/css/bulma.min.css
vendored
Normal file
1
xmrnodes/static/css/bulma.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
BIN
xmrnodes/static/css/flagSprite42.png
Normal file
BIN
xmrnodes/static/css/flagSprite42.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 31 KiB |
271
xmrnodes/static/css/freakflags.css
Normal file
271
xmrnodes/static/css/freakflags.css
Normal file
@ -0,0 +1,271 @@
|
||||
/*!*****************************************************
|
||||
|
||||
Freak Flags, Copyright ©2023 Michael P. Cohen. Freak flags is licenced under the MIT licence.
|
||||
|
||||
For complete information visit: www.freakflagsprite.com
|
||||
|
||||
******************************************************/
|
||||
|
||||
.fflag {
|
||||
background-image:url(flagSprite42.png);
|
||||
background-repeat:no-repeat;
|
||||
background-size: 100% 49494%;
|
||||
display: inline-block;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
vertical-align: middle;
|
||||
box-sizing: content-box;
|
||||
}
|
||||
.fflag-CH,
|
||||
.fflag-NP {box-shadow: none!important}
|
||||
.fflag-DZ {background-position:center 0.2287%}
|
||||
.fflag-AO {background-position:center 0.4524%}
|
||||
.fflag-BJ {background-position:center 0.6721%}
|
||||
.fflag-BW {background-position:center 0.8958%}
|
||||
.fflag-BF {background-position:center 1.1162%}
|
||||
.fflag-BI {background-position:center 1.3379%}
|
||||
.fflag-CM {background-position:center 1.5589%}
|
||||
.fflag-CV {background-position:center 1.7805%}
|
||||
.fflag-CF {background-position:center 2.0047%}
|
||||
.fflag-TD {background-position:center 2.2247%}
|
||||
.fflag-CD {background-position:left 2.4467%}
|
||||
.fflag-DJ {background-position:left 2.6674%}
|
||||
.fflag-EG {background-position:center 2.8931%}
|
||||
.fflag-GQ {background-position:center 3.1125%}
|
||||
.fflag-ER {background-position:left 3.3325%}
|
||||
.fflag-ET {background-position:center 3.5542%}
|
||||
.fflag-GA {background-position:center 3.7759%}
|
||||
.fflag-GM {background-position:center 4.0015%}
|
||||
.fflag-GH {background-position:center 4.2229%}
|
||||
.fflag-GN {background-position:center 4.441%}
|
||||
.fflag-GW {background-position:left 4.66663%}
|
||||
.fflag-CI {background-position:center 4.8844%}
|
||||
.fflag-KE {background-position:center 5.1061%}
|
||||
.fflag-LS {background-position:center 5.3298%}
|
||||
.fflag-LR {background-position:left 5.5495%}
|
||||
.fflag-LY {background-position:center 5.7712%}
|
||||
.fflag-MG {background-position:center 5.994%}
|
||||
.fflag-MW {background-position:center 6.2156%}
|
||||
.fflag-ML {background-position:center 6.4363%}
|
||||
.fflag-MR {background-position:center 6.658%}
|
||||
.fflag-MU {background-position:center 6.8805%}
|
||||
.fflag-YT {background-position:center 7.1038%}
|
||||
.fflag-MA {background-position:center 7.3231%}
|
||||
.fflag-MZ {background-position:left 7.5448%}
|
||||
.fflag-NA {background-position:left 7.7661%}
|
||||
.fflag-NE {background-position:center 7.98937%}
|
||||
.fflag-NG {background-position:center 8.2099%}
|
||||
.fflag-CG {background-position:center 8.4316%}
|
||||
.fflag-RE {background-position:center 8.6533%}
|
||||
.fflag-RW {background-position:right 8.875%}
|
||||
.fflag-SH {background-position:center 9.0967%}
|
||||
.fflag-ST {background-position:center 9.32237%}
|
||||
.fflag-SN {background-position:center 9.5426%}
|
||||
.fflag-SC {background-position:left 9.7628%}
|
||||
.fflag-SL {background-position:center 9.9845%}
|
||||
.fflag-SO {background-position:center 10.2052%}
|
||||
.fflag-ZA {background-position:left 10.4269%}
|
||||
.fflag-SS {background-position:left 10.6486%}
|
||||
.fflag-SD {background-position:center 10.8703%}
|
||||
.fflag-SR {background-position:center 11.0945%}
|
||||
.fflag-SZ {background-position:center 11.3135%}
|
||||
.fflag-TG {background-position:left 11.5354%}
|
||||
.fflag-TN {background-position:center 11.7593%}
|
||||
.fflag-UG {background-position:center 11.9799%}
|
||||
.fflag-TZ {background-position:center 12.2005%}
|
||||
.fflag-EH {background-position:center 12.4222%}
|
||||
.fflag-YE {background-position:center 12.644%}
|
||||
.fflag-ZM {background-position:center 12.8664%}
|
||||
.fflag-ZW {background-position:left 13.0873%}
|
||||
.fflag-AI {background-position:center 13.309%}
|
||||
.fflag-AG {background-position:center 13.5307%}
|
||||
.fflag-AR {background-position:center 13.7524%}
|
||||
.fflag-AW {background-position:left 13.9741%}
|
||||
.fflag-BS {background-position:left 14.1958%}
|
||||
.fflag-BB {background-position:center 14.4175%}
|
||||
.fflag-BQ {background-position:center 14.6415%}
|
||||
.fflag-BZ {background-position:center 14.8609%}
|
||||
.fflag-BM {background-position:center 15.0826%}
|
||||
.fflag-BO {background-position:center 15.306%}
|
||||
.fflag-VG {background-position:center 15.528%}
|
||||
.fflag-BR {background-position:center 15.7496%}
|
||||
.fflag-CA {background-position:center 15.9694%}
|
||||
.fflag-KY {background-position:center 16.1911%}
|
||||
.fflag-CL {background-position:left 16.4128%}
|
||||
.fflag-CO {background-position:left 16.6345%}
|
||||
.fflag-KM {background-position:center 16.8562%}
|
||||
.fflag-CR {background-position:center 17.0779%}
|
||||
.fflag-CU {background-position:left 17.2996%}
|
||||
.fflag-CW {background-position:center 17.5213%}
|
||||
.fflag-DM {background-position:center 17.743%}
|
||||
.fflag-DO {background-position:center 17.968%}
|
||||
.fflag-EC {background-position:center 18.1864%}
|
||||
.fflag-SV {background-position:center 18.4081%}
|
||||
.fflag-FK {background-position:center 18.6298%}
|
||||
.fflag-GF {background-position:center 18.8515%}
|
||||
.fflag-GL {background-position:left 19.0732%}
|
||||
.fflag-GD {background-position:center 19.2987%}
|
||||
.fflag-GP {background-position:center 19.518%}
|
||||
.fflag-GT {background-position:center 19.7383%}
|
||||
.fflag-GY {background-position:center 19.96%}
|
||||
.fflag-HT {background-position:center 20.1817%}
|
||||
.fflag-HN {background-position:center 20.4034%}
|
||||
.fflag-JM {background-position:center 20.6241%}
|
||||
.fflag-MQ {background-position:center 20.8468%}
|
||||
.fflag-MX {background-position:center 21.0685%}
|
||||
.fflag-MS {background-position:center 21.2902%}
|
||||
.fflag-NI {background-position:center 21.5119%}
|
||||
.fflag-PA {background-position:center 21.7336%}
|
||||
.fflag-PY {background-position:center 21.9553%}
|
||||
.fflag-PE {background-position:center 22.177%}
|
||||
.fflag-PR {background-position:left 22.4002%}
|
||||
.fflag-BL {background-position:center 22.6204%}
|
||||
.fflag-KN {background-position:center 22.8421%}
|
||||
.fflag-LC {background-position:center 23.0638%}
|
||||
.fflag-PM {background-position:center 23.2855%}
|
||||
.fflag-VC {background-position:center 23.5072%}
|
||||
.fflag-SX {background-position:left 23.732%}
|
||||
.fflag-TT {background-position:center 23.9506%}
|
||||
.fflag-TC {background-position:center 24.1723%}
|
||||
.fflag-US {background-position:center 24.392%}
|
||||
.fflag-VI {background-position:center 24.6157%}
|
||||
.fflag-UY {background-position:left 24.8374%}
|
||||
.fflag-VE {background-position:center 25.0591%}
|
||||
.fflag-AB {background-position:center 25.279%}
|
||||
.fflag-AF {background-position:center 25.5025%}
|
||||
.fflag-AZ {background-position:center 25.7242%}
|
||||
.fflag-BD {background-position:center 25.9459%}
|
||||
.fflag-BT {background-position:center 26.1676%}
|
||||
.fflag-BN {background-position:center 26.3885%}
|
||||
.fflag-KH {background-position:center 26.611%}
|
||||
.fflag-CN {background-position:left 26.8327%}
|
||||
.fflag-GE {background-position:center 27.0544%}
|
||||
.fflag-HK {background-position:center 27.2761%}
|
||||
.fflag-IN {background-position:center 27.4978%}
|
||||
.fflag-ID {background-position:center 27.7195%}
|
||||
.fflag-JP {background-position:center 27.9412%}
|
||||
.fflag-KZ {background-position:center 28.1615%}
|
||||
.fflag-LA {background-position:center 28.3846%}
|
||||
.fflag-MO {background-position:center 28.6063%}
|
||||
.fflag-MY {background-position:center 28.829%}
|
||||
.fflag-MV {background-position:center 29.0497%}
|
||||
.fflag-MN {background-position:left 29.2714%}
|
||||
.fflag-MM {background-position:center 29.4931%}
|
||||
.fflag-NP {background-position:left 29.7148%}
|
||||
.fflag-KP {background-position:left 29.9365%}
|
||||
.fflag-MP {background-position:center 30.1582%}
|
||||
.fflag-PW {background-position:center 30.3799%}
|
||||
.fflag-PG {background-position:center 30.6016%}
|
||||
.fflag-PH {background-position:left 30.8233%}
|
||||
.fflag-SG {background-position:left 31.045%}
|
||||
.fflag-KR {background-position:center 31.2667%}
|
||||
.fflag-LK {background-position:right 31.4884%}
|
||||
.fflag-TW {background-position:left 31.7101%}
|
||||
.fflag-TJ {background-position:center 31.9318%}
|
||||
.fflag-TH {background-position:center 32.1535%}
|
||||
.fflag-TL {background-position:left 32.3752%}
|
||||
.fflag-TM {background-position:center 32.5969%}
|
||||
.fflag-VN {background-position:center 32.8186%}
|
||||
.fflag-AX {background-position:center 33.0403%}
|
||||
.fflag-AL {background-position:center 33.25975%}
|
||||
.fflag-AD {background-position:center 33.4837%}
|
||||
.fflag-AM {background-position:center 33.7054%}
|
||||
.fflag-AT {background-position:center 33.9271%}
|
||||
.fflag-BY {background-position:left 34.1488%}
|
||||
.fflag-BE {background-position:center 34.3705%}
|
||||
.fflag-BA {background-position:center 34.5922%}
|
||||
.fflag-BG {background-position:center 34.8139%}
|
||||
.fflag-HR {background-position:center 35.0356%}
|
||||
.fflag-CY {background-position:center 35.2555%}
|
||||
.fflag-CZ {background-position:left 35.479%}
|
||||
.fflag-DK {background-position:center 35.7007%}
|
||||
.fflag-EE {background-position:center 35.9224%}
|
||||
.fflag-FO {background-position:center 36.1441%}
|
||||
.fflag-FI {background-position:center 36.3658%}
|
||||
.fflag-FR {background-position:center 36.5875%}
|
||||
.fflag-DE {background-position:center 36.8092%}
|
||||
.fflag-GI {background-position:center 37.0309%}
|
||||
.fflag-GR {background-position:left 37.2526%}
|
||||
.fflag-GG {background-position:center 37.4743%}
|
||||
.fflag-HU {background-position:center 37.696%}
|
||||
.fflag-IS {background-position:center 37.9177%}
|
||||
.fflag-IE {background-position:center 38.1394%}
|
||||
.fflag-IM {background-position:center 38.3611%}
|
||||
.fflag-IT {background-position:center 38.5828%}
|
||||
.fflag-JE {background-position:center 38.8045%}
|
||||
.fflag-XK {background-position:center 39.0262%}
|
||||
.fflag-LV {background-position:center 39.2479%}
|
||||
.fflag-LI {background-position:left 39.4696%}
|
||||
.fflag-LT {background-position:center 39.6913%}
|
||||
.fflag-LU {background-position:center 39.913%}
|
||||
.fflag-MT {background-position:left 40.1347%}
|
||||
.fflag-MD {background-position:center 40.3564%}
|
||||
.fflag-MC {background-position:center 40.5781%}
|
||||
.fflag-ME {background-position:center 40.7998%}
|
||||
.fflag-NL {background-position:center 41.0215%}
|
||||
.fflag-MK {background-position:center 41.2432%}
|
||||
.fflag-NO {background-position:center 41.4649%}
|
||||
.fflag-PL {background-position:center 41.6866%}
|
||||
.fflag-PT {background-position:center 41.9083%}
|
||||
.fflag-RO {background-position:center 42.13%}
|
||||
.fflag-RU {background-position:center 42.3517%}
|
||||
.fflag-SM {background-position:center 42.5734%}
|
||||
.fflag-RS {background-position:center 42.7951%}
|
||||
.fflag-SK {background-position:center 43.0168%}
|
||||
.fflag-SI {background-position:center 43.2385%}
|
||||
.fflag-ES {background-position:left 43.4602%}
|
||||
.fflag-SE {background-position:center 43.6819%}
|
||||
.fflag-CH {background-position:center 43.9036%}
|
||||
.fflag-TR {background-position:center 44.1253%}
|
||||
.fflag-UA {background-position:center 44.347%}
|
||||
.fflag-GB {background-position:center 44.5687%}
|
||||
.fflag-VA {background-position:right 44.7904%}
|
||||
.fflag-BH {background-position:center 45.0121%}
|
||||
.fflag-IR {background-position:center 45.2338%}
|
||||
.fflag-IQ {background-position:center 45.4555%}
|
||||
.fflag-IL {background-position:center 45.6772%}
|
||||
.fflag-KW {background-position:left 45.897%}
|
||||
.fflag-JO {background-position:left 46.1206%}
|
||||
.fflag-KG {background-position:center 46.3423%}
|
||||
.fflag-LB {background-position:center 46.561%}
|
||||
.fflag-OM {background-position:left 46.7857%}
|
||||
.fflag-PK {background-position:center 47.0074%}
|
||||
.fflag-PS {background-position:center 47.2291%}
|
||||
.fflag-QA {background-position:center 47.4508%}
|
||||
.fflag-SA {background-position:center 47.6725%}
|
||||
.fflag-SY {background-position:center 47.8942%}
|
||||
.fflag-AE {background-position:center 48.1159%}
|
||||
.fflag-UZ {background-position:left 48.3376%}
|
||||
.fflag-AS {background-position:right 48.5593%}
|
||||
.fflag-AU {background-position:center 48.781%}
|
||||
.fflag-CX {background-position:center 49.002%}
|
||||
.fflag-CC {background-position:center 49.2244%}
|
||||
.fflag-CK {background-position:center 49.4445%}
|
||||
.fflag-FJ {background-position:center 49.6678%}
|
||||
.fflag-PF {background-position:center 49.8895%}
|
||||
.fflag-GU {background-position:center 50.1112%}
|
||||
.fflag-KI {background-position:center 50.3329%}
|
||||
.fflag-MH {background-position:left 50.5546%}
|
||||
.fflag-FM {background-position:center 50.7763%}
|
||||
.fflag-NC {background-position:center 50.998%}
|
||||
.fflag-NZ {background-position:center 51.2197%}
|
||||
.fflag-NR {background-position:left 51.4414%}
|
||||
.fflag-NU {background-position:center 51.6631%}
|
||||
.fflag-NF {background-position:center 51.8848%}
|
||||
.fflag-WS {background-position:left 52.1065%}
|
||||
.fflag-SB {background-position:left 52.3282%}
|
||||
.fflag-TK {background-position:center 52.5499%}
|
||||
.fflag-TO {background-position:left 52.7716%}
|
||||
.fflag-TV {background-position:center 52.9933%}
|
||||
.fflag-VU {background-position:left 53.215%}
|
||||
.fflag-WF {background-position:center 53.4385%}
|
||||
.fflag-AQ {background-position:center 53.6584%}
|
||||
.fflag-EU {background-position:center 53.875%}
|
||||
.fflag-JR {background-position:center 54.099%}
|
||||
.fflag-OLY {background-position:center 54.32%}
|
||||
.fflag-UN {background-position:center 54.54%}
|
||||
|
||||
.fflag.ff-sm {width: 18px;height: 11px}
|
||||
.fflag.ff-md {width: 27px;height: 17px}
|
||||
.fflag.ff-lg {width: 42px;height: 27px}
|
||||
.fflag.ff-xl {width: 60px;height: 37px}
|
82
xmrnodes/static/css/main.css
Normal file
82
xmrnodes/static/css/main.css
Normal file
@ -0,0 +1,82 @@
|
||||
body {
|
||||
background-color: #1B2432;
|
||||
color: #f9f9f9;
|
||||
font-family: monospace;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.media-content .title {
|
||||
color: #1B2432;
|
||||
}
|
||||
|
||||
.title {
|
||||
color: #f9f9f9;
|
||||
}
|
||||
|
||||
.card {
|
||||
background-color: #F1F2EB;
|
||||
}
|
||||
|
||||
.card .media:not(:last-child) {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.card-content {
|
||||
padding: 1em;
|
||||
margin: 1em;
|
||||
}
|
||||
|
||||
.smol {
|
||||
font-size: .75em;
|
||||
}
|
||||
|
||||
.donationAddress {
|
||||
font-size: .8em;
|
||||
user-select: all;
|
||||
color: #ff006e;
|
||||
display: block;
|
||||
padding-top: .35em;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
.asn {
|
||||
font-size: .8em;
|
||||
color: #ff006e;
|
||||
display: block;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
.nodeURL {
|
||||
user-select: all;
|
||||
font-size: 1em;
|
||||
}
|
||||
|
||||
.messages {
|
||||
width: 30em;
|
||||
padding: 1em;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
}
|
||||
|
||||
#addNodeForm {
|
||||
width: 50%;
|
||||
}
|
||||
|
||||
#webCompat {
|
||||
margin: 1em;
|
||||
}
|
||||
|
||||
.dot {
|
||||
height: 10px;
|
||||
width: 10px;
|
||||
border-radius: 50%;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.glowing-green {
|
||||
background-color: #78BC61;
|
||||
}
|
||||
|
||||
.glowing-red {
|
||||
background-color: #B76D68;
|
||||
}
|
@ -134,6 +134,7 @@ input[type="text"] {
|
||||
|
||||
.nodeURL {
|
||||
user-select: all;
|
||||
font-size: 1em;
|
||||
}
|
||||
|
||||
td img {
|
||||
@ -148,4 +149,12 @@ td img {
|
||||
display: block;
|
||||
padding-top: .35em;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
.asn {
|
||||
font-size: .8em;
|
||||
color: #ff006e;
|
||||
display: block;
|
||||
padding-top: .35em;
|
||||
word-wrap: break-word;
|
||||
}
|
18
xmrnodes/static/proofsheet.html
Normal file
18
xmrnodes/static/proofsheet.html
Normal file
File diff suppressed because one or more lines are too long
@ -19,36 +19,69 @@
|
||||
<meta name="application-name" content="XMR Nodes">
|
||||
<meta name="msapplication-TileColor" content="#da532c">
|
||||
<meta name="keywords" content="wownero, monero, xmr, bitmonero, cryptocurrency">
|
||||
<link href="/static/css/normalize.css" rel="stylesheet">
|
||||
<link href="/static/css/pure.css" rel="stylesheet">
|
||||
<link href="/static/css/style.css" rel="stylesheet">
|
||||
<link href="/static/css/freakflags.css" rel="stylesheet">
|
||||
<link href="/static/css/bulma.min.css" rel="stylesheet">
|
||||
<link href="/static/css/main.css" rel="stylesheet">
|
||||
<title>XMR Nodes</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<nav class="navbar" role="navigation" aria-label="main navigation">
|
||||
<div class="navbar-brand">
|
||||
<a class="navbar-item" href="/">
|
||||
<img src="/static/images/monero.svg" width="25" height="28">.fail
|
||||
</a>
|
||||
<a role="button" class="navbar-burger" data-target="navMenu" aria-label="menu" aria-expanded="false">
|
||||
<span aria-hidden="true"></span>
|
||||
<span aria-hidden="true"></span>
|
||||
<span aria-hidden="true"></span>
|
||||
</a>
|
||||
</div>
|
||||
<div id="navMenu" class="navbar-menu">
|
||||
<div class="navbar-start">
|
||||
<div class="navbar-item has-dropdown is-hoverable">
|
||||
<a class="navbar-link">
|
||||
Links
|
||||
</a>
|
||||
<div class="navbar-dropdown">
|
||||
<a class="navbar-item" href="{{ url_for('meta.about') }}">
|
||||
About
|
||||
</a>
|
||||
<a class="navbar-item" href="{{ url_for('meta.map') }}">
|
||||
Map
|
||||
</a>
|
||||
<a class="navbar-item" href="mailto:lza_menace@protonmail.com" target="_blank">
|
||||
Contact
|
||||
</a>
|
||||
<a class="navbar-item" href="https://github.com/lalanza808/monero.fail" target="_blank">
|
||||
Source Code
|
||||
</a>
|
||||
<hr class="navbar-divider">
|
||||
<a class="navbar-item" href="https://moneroinfodump.neocities.org/" target="_blank">
|
||||
Info Dump
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
{% with messages = get_flashed_messages() %}
|
||||
{% if messages %}
|
||||
<ul class="flashes pure-u-1 center">
|
||||
{% for message in messages %}
|
||||
<li>{{ message }}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
<div class="messages">
|
||||
{% for message in messages %}
|
||||
<article class="message is-info">
|
||||
<div class="message-body">
|
||||
{{ message }}
|
||||
</div>
|
||||
</article>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
|
||||
<div id="" class="center">
|
||||
<br>
|
||||
<a href="{{ url_for('meta.about') }}">about</a>
|
||||
-
|
||||
<a href="{{ url_for('meta.map') }}">map</a>
|
||||
-
|
||||
<a href="https://twitter.com/lza_menace" target="_blank">contact</a>
|
||||
-
|
||||
<a href="https://github.com/lalanza808/monero.fail" target="_blank">source</a>
|
||||
-
|
||||
<a href="https://moneroinfodump.neocities.org/" target="_blank">infodump</a>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- Page Content -->
|
||||
{% block content %} {% endblock %}
|
||||
@ -58,5 +91,19 @@
|
||||
<a href="#">Go to top</a>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
const $navbarBurgers = Array.prototype.slice.call(document.querySelectorAll('.navbar-burger'), 0);
|
||||
$navbarBurgers.forEach( el => {
|
||||
el.addEventListener('click', () => {
|
||||
const target = el.dataset.target;
|
||||
const $target = document.getElementById(target);
|
||||
el.classList.toggle('is-active');
|
||||
$target.classList.toggle('is-active');
|
||||
});
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
@ -5,33 +5,31 @@
|
||||
|
||||
<div id="index" class="container">
|
||||
|
||||
<div id="addnode" class="pure-g center section">
|
||||
<div class="title pure-u-1">
|
||||
<h2>Add A Node</h2>
|
||||
</div>
|
||||
<form method="POST" action="{{ url_for('meta.add') }}" class="pure-form pure-u-1">
|
||||
<div id="addnode" class="section">
|
||||
<h2 class="title is-4">Add A Node</h2>
|
||||
<form method="POST" action="{{ url_for('meta.add') }}" id="addNodeForm">
|
||||
{{ form.csrf_token }}
|
||||
{% for f in form %}
|
||||
{% if f.name != 'csrf_token' %}
|
||||
<div class="form-group">
|
||||
{{ f.label }}
|
||||
{{ f }}
|
||||
<button type="submit" class="pure-button pure-button-primary">Submit</button>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
<div class="field">
|
||||
<div class="control">
|
||||
<input class="input" type="text" placeholder="http://mynode.com:18081" id="node_url" name="node_url" required>
|
||||
</div>
|
||||
</div>
|
||||
<div class="field is-grouped">
|
||||
<div class="control">
|
||||
<button class="button is-link">Submit</button>
|
||||
</div>
|
||||
</div>
|
||||
<ul>
|
||||
{% for field, errors in form.errors.items() %}
|
||||
<li>{{ form[field].label }}: {{ ', '.join(errors) }}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div id="nodes" class="pure-u-1 section">
|
||||
<h2 class="center">Find a Node</h2>
|
||||
<div class="center" id="filters">
|
||||
<div id="nodes" class="section">
|
||||
<h2 class="title is-4">Find a Node</h2>
|
||||
<div class="" id="filters">
|
||||
<form>
|
||||
<span>
|
||||
<label for="chainSelect">Chain:</label>
|
||||
@ -40,7 +38,6 @@
|
||||
<option value="wownero" {% if request.args.get('chain') == 'wownero' %}selected{% endif %}>Wownero</option>
|
||||
</select>
|
||||
</span>
|
||||
|
||||
<span>
|
||||
<label for="networkSelect">Network:</label>
|
||||
<select name="network" id="networkSelect">
|
||||
@ -49,48 +46,42 @@
|
||||
<option value="stagenet" {% if request.args.get('network') == 'stagenet' %}selected{% endif %}>Stagenet</option>
|
||||
</select>
|
||||
</span>
|
||||
|
||||
<span>
|
||||
<label for="cors">Web (CORS):</label>
|
||||
<label for="cors">CORS:</label>
|
||||
<input type="checkbox" name="cors" id="cors" {% if request.args.get('cors') == 'on' %}checked{% endif %}>
|
||||
</span>
|
||||
|
||||
<span>
|
||||
<label for="onion">Onion:</label>
|
||||
<input type="checkbox" name="onion" id="onion" {% if request.args.get('onion') == 'on' %}checked{% endif %}>
|
||||
</span>
|
||||
|
||||
<span>
|
||||
<label for="i2p">I2P:</label>
|
||||
<input type="checkbox" name="i2p" id="i2p" {% if request.args.get('i2p') == 'on' %}checked{% endif %}>
|
||||
</span>
|
||||
|
||||
<span>
|
||||
<input type="submit" value="Filter" class="pure-button pure-pink">
|
||||
</span>
|
||||
|
||||
<span>
|
||||
<a href="/"><input type="button" value="Reset" class="pure-button pure-grey"></a>
|
||||
</span>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
{% if nodes %}
|
||||
<div class="xmrnodes">
|
||||
<p class="center">
|
||||
<p class="">
|
||||
{% if web_compatible %}
|
||||
<strong>
|
||||
Web compatible means the node is run in such a way that it allows web clients to access their APIs (CORS headers allow all and secure HTTP / TLS terminated).
|
||||
<br>
|
||||
The more nodes there are running with these settings the more robust web clients will be in the future.
|
||||
</strong>
|
||||
<br><br>
|
||||
<div class="notification is-info is-light" id="webCompat">
|
||||
Web compatible means the node is run in such a way that it allows web clients to access their APIs (CORS headers allow all and secure HTTP / TLS terminated).
|
||||
The more nodes there are running with these settings the more robust web clients will be in the future.
|
||||
</div>
|
||||
{% endif %}
|
||||
<a href="{{ url_for('meta.haproxy', chain=request.args.get('chain'), network=request.args.get('network'), cors=request.args.get('cors'), onion=request.args.get('onion')) }}">Download HAProxy config</a><br /><br />
|
||||
Tracking {{ nodes_all }} {{ nettype }} {{ crypto | capitalize }} nodes in the database.
|
||||
<br>
|
||||
Of those, {{ nodes_unhealthy }} nodes failed their last check-in (unresponsive to ping or over 500 blocks away from highest reported block).
|
||||
</p>
|
||||
<p class="center">Showing {{ nodes | length }} nodes.
|
||||
<p class="">Showing {{ nodes | length }} nodes.
|
||||
{% if 'all' not in request.args %}
|
||||
<a href="{% if request.args %}{{ request.url }}&{% else %}?{% endif %}all=true">Show All</a>
|
||||
{% else %}
|
||||
@ -98,67 +89,125 @@
|
||||
{% endif %}
|
||||
</p>
|
||||
<br>
|
||||
<table class="pure-table pure-table-horizontal pure-table-striped js-sort-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="js-sort-string">Type</th>
|
||||
<th class="js-sort-string">URL</th>
|
||||
<th class="js-sort-number">Height</th>
|
||||
<th class="js-sort-none">Up</th>
|
||||
<th class="js-sort-none">Web<br/>Compatible</th>
|
||||
<th class="js-sort-none">Network</th>
|
||||
<th class="js-sort-none">Last Checked</th>
|
||||
<th class="js-sort-none">History</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for node in nodes %}
|
||||
<tr class="js-sort-table">
|
||||
<td>
|
||||
{% if node.is_tor %}
|
||||
<img src="/static/images/tor.svg" height="20px">
|
||||
<span class="hidden">tor</span>
|
||||
{% elif node.is_i2p %}
|
||||
<img src="/static/images/i2p.svg" height="20px">
|
||||
<span class="hidden">i2p</span>
|
||||
{% else %}
|
||||
<img src="/static/images/clearnet.svg" height="20px">
|
||||
<span class="hidden">clear</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
<span class="nodeURL">{{ node.url }}</span>
|
||||
{% if node.donation_address | seems_legit %}
|
||||
<span class="donationAddress">{{ node.donation_address }}</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>{{ node.last_height }}</td>
|
||||
<td>
|
||||
{% if node.available %}
|
||||
<span class="dot glowing-green"></span>
|
||||
{% else %}
|
||||
<span class="dot glowing-red""></span>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
{% if node.web_compatible %}
|
||||
<img src="/static/images/success.svg" class="filter-green" width=16px>
|
||||
{% else %}
|
||||
<img src="/static/images/error.svg" class="filter-red" width=16px>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>{{ node.nettype }}</td>
|
||||
<td>{{ node.datetime_checked | humanize }}</td>
|
||||
<td>{% for hc in node.healthchecks %}
|
||||
{% if loop.index > loop.length - 6 %}
|
||||
<span class="dot glowing-{% if hc.health %}green{% else %}red{% endif %}"></span>
|
||||
{% endif %}
|
||||
<div>
|
||||
|
||||
<div class="columns">
|
||||
|
||||
<div class="column">
|
||||
{% for node in nodes %}
|
||||
<div class="card">
|
||||
|
||||
<div class="card-content">
|
||||
|
||||
<div class="media">
|
||||
<div class="media-left">
|
||||
<figure class="image is-32x32">
|
||||
{% if node.is_tor %}
|
||||
<img src="/static/images/tor.svg" />
|
||||
{% elif node.is_i2p %}
|
||||
<img src="/static/images/i2p.svg" />
|
||||
{% else %}
|
||||
<span class="fflag fflag-{{ node.country_code }} ff-lg mr-4" title="{{ node.asn_description }}"></span>
|
||||
{% endif %}
|
||||
</figure>
|
||||
</div>
|
||||
<div class="media-content">
|
||||
<p class="title is-6 nodeURL">{{ node.url }}</p>
|
||||
{% if node.asn %}
|
||||
<p class="subtitle asn">
|
||||
ASN: {{ node.asn }} -
|
||||
{{ node.asn_description}}
|
||||
</p>
|
||||
{% endif %}
|
||||
<div class="statusDots subtitle">
|
||||
{% for check in node.healthchecks %}
|
||||
<span class="dot glowing-{{ check.color }}"></span>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="content">
|
||||
<br>
|
||||
<p class="smol">
|
||||
last checked {{ node.datetime_checked | humanize }}<br />
|
||||
{{ node.uptime }}% uptime
|
||||
</p>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<footer class="card-footer">
|
||||
<p class="card-footer-item">
|
||||
<span>
|
||||
View on <a href="https://twitter.com/codinghorror/status/506010907021828096">Twitter</a>
|
||||
</span>
|
||||
</p>
|
||||
<p class="card-footer-item">
|
||||
<span>
|
||||
Share on <a href="#">Facebook</a>
|
||||
</span>
|
||||
</p>
|
||||
</footer>
|
||||
|
||||
</div>
|
||||
{% endfor %}
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="column">
|
||||
<article class="panel is-info">
|
||||
<p class="panel-heading">
|
||||
Info
|
||||
</p>
|
||||
<p class="panel-tabs">
|
||||
<a class="is-active">All</a>
|
||||
<a>Public</a>
|
||||
<a>Private</a>
|
||||
<a>Sources</a>
|
||||
<a>Forks</a>
|
||||
</p>
|
||||
<div class="panel-block">
|
||||
<p class="control has-icons-left">
|
||||
<input class="input is-info" type="text" placeholder="Search">
|
||||
<span class="icon is-left">
|
||||
<i class="fas fa-search" aria-hidden="true"></i>
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
<a class="panel-block is-active">
|
||||
<span class="panel-icon">
|
||||
<i class="fas fa-book" aria-hidden="true"></i>
|
||||
</span>
|
||||
bulma
|
||||
</a>
|
||||
<a class="panel-block">
|
||||
<span class="panel-icon">
|
||||
<i class="fas fa-book" aria-hidden="true"></i>
|
||||
</span>
|
||||
marksheet
|
||||
</a>
|
||||
<a class="panel-block">
|
||||
<span class="panel-icon">
|
||||
<i class="fas fa-book" aria-hidden="true"></i>
|
||||
</span>
|
||||
minireset.css
|
||||
</a>
|
||||
<a class="panel-block">
|
||||
<span class="panel-icon">
|
||||
<i class="fas fa-book" aria-hidden="true"></i>
|
||||
</span>
|
||||
jgthms.github.io
|
||||
</a>
|
||||
</article>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<!-- </tbody>
|
||||
</table> -->
|
||||
</div>
|
||||
</div>
|
||||
{% else %}
|
||||
<p class="center">No nodes in the database yet...</p>
|
||||
|
@ -1,59 +1,6 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta name="HandheldFriendly" content="True">
|
||||
<meta name="MobileOptimized" content="320">
|
||||
<link rel="shortcut icon" href="/static/favicon.ico" type="image/x-icon" />
|
||||
<meta property="fb:app_id" content="0" />
|
||||
<meta property="og:image" content="https://www.getmonero.org/press-kit/symbols/monero-symbol-on-white-480.png" />
|
||||
<meta property="og:description" content="xmrnodes" />
|
||||
<meta property="og:url" content="http://localhost" />
|
||||
<meta property="og:title" content="XMR Nodes" />
|
||||
<meta property="og:type" content="website" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1"/>
|
||||
<meta name="theme-color" content="#ffffff">
|
||||
<meta name="apple-mobile-web-app-title" content="XMR Nodes">
|
||||
<meta name="application-name" content="XMR Nodes">
|
||||
<meta name="msapplication-TileColor" content="#da532c">
|
||||
<meta name="keywords" content="wownero, monero, xmr, bitmonero, cryptocurrency">
|
||||
<link rel="stylesheet" href="//cdn.jsdelivr.net/gh/openlayers/openlayers.github.io@master/en/v6.5.0/css/ol.css" type="text/css">
|
||||
<link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/4.5.0/css/bootstrap.min.css" type="text/css">
|
||||
<style>
|
||||
.map {
|
||||
height: 600px;
|
||||
margin: 2em;
|
||||
padding: 0;
|
||||
}
|
||||
.popover-body {
|
||||
min-width: 276px;
|
||||
}
|
||||
.center.info {
|
||||
width: 50%;
|
||||
margin: auto;
|
||||
padding: 2em;
|
||||
}
|
||||
</style>
|
||||
<script src="https://cdn.polyfill.io/v3/polyfill.min.js?features=fetch,requestAnimationFrame,Element.prototype.classList,URL,TextDecoder"></script>
|
||||
<script src="//cdn.jsdelivr.net/gh/openlayers/openlayers.github.io@master/en/v6.5.0/build/ol.js"></script>
|
||||
<script src="//code.jquery.com/jquery-3.5.1.min.js"></script>
|
||||
<script src="//maxcdn.bootstrapcdn.com/bootstrap/4.5.0/js/bootstrap.bundle.min.js"></script>
|
||||
<title>XMR Nodes</title>
|
||||
</head>
|
||||
{% extends 'base.html' %}
|
||||
|
||||
<body>
|
||||
|
||||
{% with messages = get_flashed_messages() %}
|
||||
{% if messages %}
|
||||
<ul class="flashes pure-u-1 center">
|
||||
{% for message in messages %}
|
||||
<li>{{ message }}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
{% block content %}
|
||||
|
||||
<div class="center info">
|
||||
<p>Peers seen ~24 hours: {{ peers | length }}</p>
|
||||
@ -65,12 +12,31 @@
|
||||
Older peers are shown as more transparent and will be removed
|
||||
if not seen again after {{ config.PEER_LIFETIME }} hours.
|
||||
</p>
|
||||
<br>
|
||||
<a href="/">Go home</a>
|
||||
</div>
|
||||
<div id="map" class="map"></div>
|
||||
<div id="popup" class="popup" title="Welcome to OpenLayers"></div>
|
||||
|
||||
<link rel="stylesheet" href="//cdn.jsdelivr.net/gh/openlayers/openlayers.github.io@master/en/v6.5.0/css/ol.css" type="text/css">
|
||||
<link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/4.5.0/css/bootstrap.min.css" type="text/css">
|
||||
<style>
|
||||
.map {
|
||||
height: 600px;
|
||||
margin: 2em;
|
||||
padding: 0;
|
||||
}
|
||||
.popover-body {
|
||||
min-width: 276px;
|
||||
}
|
||||
.center.info {
|
||||
width: 50%;
|
||||
margin: auto;
|
||||
padding: 2em;
|
||||
}
|
||||
</style>
|
||||
<script src="https://cdn.polyfill.io/v3/polyfill.min.js?features=fetch,requestAnimationFrame,Element.prototype.classList,URL,TextDecoder"></script>
|
||||
<script src="//cdn.jsdelivr.net/gh/openlayers/openlayers.github.io@master/en/v6.5.0/build/ol.js"></script>
|
||||
<script src="//code.jquery.com/jquery-3.5.1.min.js"></script>
|
||||
<script src="//maxcdn.bootstrapcdn.com/bootstrap/4.5.0/js/bootstrap.bundle.min.js"></script>
|
||||
<script>
|
||||
// Marker layer
|
||||
markerLayer = new ol.layer.Vector({
|
||||
@ -157,6 +123,4 @@
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
{% endblock %}
|
Loading…
x
Reference in New Issue
Block a user