You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
161 lines
4.7 KiB
Python
161 lines
4.7 KiB
Python
import re
|
|
from random import shuffle
|
|
from math import ceil
|
|
|
|
import arrow
|
|
from flask import request, redirect, Blueprint
|
|
from flask import render_template, flash, Response
|
|
from urllib.parse import urlparse
|
|
|
|
from xmrnodes.helpers import get_highest_block
|
|
from xmrnodes.forms import SubmitNode
|
|
from xmrnodes.models import Node, Peer
|
|
from xmrnodes import config
|
|
|
|
bp = Blueprint("meta", "meta")
|
|
|
|
|
|
@bp.route("/", methods=["GET", "POST"])
|
|
def index():
|
|
form = SubmitNode()
|
|
nettype = request.args.get("network", "mainnet")
|
|
crypto = request.args.get("chain", "monero")
|
|
onion = request.args.get("onion", False)
|
|
i2p = request.args.get("i2p", False)
|
|
show_all = "true" == request.args.get("all", "false")
|
|
web_compatible = request.args.get("cors", False)
|
|
highest_block = get_highest_block(nettype, crypto)
|
|
healthy_block = highest_block - config.HEALTHY_BLOCK_DIFF
|
|
|
|
nodes = Node.select().where(
|
|
Node.validated == True, Node.nettype == nettype, Node.crypto == crypto
|
|
)
|
|
|
|
if web_compatible:
|
|
nodes = nodes.where(Node.web_compatible == True)
|
|
|
|
nodes_all = nodes.count()
|
|
nodes_unhealthy = nodes.where(
|
|
(Node.available == False) | (Node.last_height < healthy_block)
|
|
).count()
|
|
|
|
if not show_all:
|
|
nodes = nodes.where(Node.available == True, Node.last_height > healthy_block)
|
|
|
|
nodes = nodes.order_by(Node.datetime_entered.desc())
|
|
if onion:
|
|
nodes = nodes.where(Node.is_tor == True)
|
|
if i2p:
|
|
nodes = nodes.where(Node.is_i2p == True)
|
|
|
|
nodes = [n for n in nodes]
|
|
shuffle(nodes)
|
|
|
|
return render_template(
|
|
"index.html",
|
|
nodes=nodes,
|
|
nodes_all=nodes_all,
|
|
nodes_unhealthy=nodes_unhealthy,
|
|
nettype=nettype,
|
|
crypto=crypto,
|
|
form=form,
|
|
web_compatible=web_compatible,
|
|
)
|
|
|
|
|
|
@bp.route("/map")
|
|
def map():
|
|
fetch = request.args.get("fetch")
|
|
if fetch:
|
|
_peers = {}
|
|
next = None
|
|
limit = 500
|
|
rgb = "238,111,45"
|
|
offset = request.args.get("offset", 0)
|
|
offset = int(offset)
|
|
peers = Peer.select().order_by(Peer.datetime.asc())
|
|
paginated_peers = peers.paginate(offset, limit)
|
|
total = ceil(peers.count() / limit)
|
|
if offset < total:
|
|
next = offset + 1
|
|
for peer in paginated_peers:
|
|
opacity = 1 - (peer.hours_elapsed() / 100)
|
|
_peers[peer.url] = {
|
|
"rgba": f"rgba({rgb},{opacity})",
|
|
"lat": peer.lat,
|
|
"lon": peer.lon,
|
|
"last_seen": arrow.get(peer.datetime).humanize()
|
|
}
|
|
return {
|
|
"offset": offset,
|
|
"next": next,
|
|
"total": total,
|
|
"peers": _peers
|
|
}
|
|
return render_template(
|
|
"map.html",
|
|
recent_peers=Peer.select().count(),
|
|
source_node=config.NODE_HOST
|
|
)
|
|
|
|
|
|
@bp.route("/about")
|
|
def about():
|
|
return render_template("about.html")
|
|
|
|
|
|
@bp.route("/opsec")
|
|
def opsec():
|
|
return render_template("opsec.html")
|
|
|
|
|
|
@bp.route("/add", methods=["GET", "POST"])
|
|
def add():
|
|
if request.method == "POST":
|
|
url = request.form.get("node_url")
|
|
regex = re.compile(
|
|
r"^(?:http)s?://" # http:// or https://
|
|
r"(?:(?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\.)+(?:[A-Z]{2,6}\.?|[A-Z0-9-]{2,}\.?)|" # domain...
|
|
r"localhost|" # localhost...
|
|
r"\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})" # ...or ip
|
|
r"(?::\d+)?" # optional port
|
|
r"(?:/?|[/?]\S+)$",
|
|
re.IGNORECASE,
|
|
)
|
|
re_match = re.match(regex, url)
|
|
if re_match is None:
|
|
flash("This doesn't look like a valid URL")
|
|
else:
|
|
_url = urlparse(url)
|
|
url = f"{_url.scheme}://{_url.netloc}".lower()
|
|
if Node.select().where(Node.url == url).exists():
|
|
flash("This node is already in the database.")
|
|
else:
|
|
flash(
|
|
"Seems like a valid node URL. Added to the database and will check soon."
|
|
)
|
|
node = Node(url=url)
|
|
node.save()
|
|
return redirect("/")
|
|
|
|
|
|
@bp.route("/haproxy.cfg")
|
|
def haproxy():
|
|
crypto = request.args.get("chain") or "monero"
|
|
nettype = request.args.get("network") or "mainnet"
|
|
cors = request.args.get("cors") or False
|
|
tor = request.args.get("onion") or False
|
|
nodes = Node.select().where(
|
|
Node.validated == True,
|
|
Node.nettype == nettype,
|
|
Node.crypto == crypto,
|
|
Node.is_tor == tor,
|
|
Node.web_compatible == cors,
|
|
)
|
|
tpl = render_template("haproxy.html", nodes=nodes)
|
|
res = Response(tpl)
|
|
res.headers[
|
|
"Content-Disposition"
|
|
] = f'attachment; filename="haproxy-{crypto}-{nettype}-cors_{cors}-tor_{tor}.cfg"'
|
|
return res
|