adding app so far

This commit is contained in:
lza_menace 2020-10-12 21:32:21 -07:00
parent 142b570503
commit dc06c87808
18 changed files with 3689 additions and 0 deletions

4
.gitignore vendored
View File

@ -127,3 +127,7 @@ dmypy.json
# Pyre type checker
.pyre/
# nodes
config.py
data

7
bin/cmd Executable file
View File

@ -0,0 +1,7 @@
#!/bin/bash
source .venv/bin/activate
export FLASK_APP=nodes/app.py
export FLASK_SECRETS=config.py
export FLASK_DEBUG=1
flask $1

7
bin/dev Executable file
View File

@ -0,0 +1,7 @@
#!/bin/bash
source .venv/bin/activate
export FLASK_APP=xmrnodes/app.py
export FLASK_SECRETS=config.py
export FLASK_DEBUG=1
flask run

21
bin/prod Executable file
View File

@ -0,0 +1,21 @@
#!/bin/bash
BASE=data/gunicorn
source .venv/bin/activate
export FLASK_APP=nodes/app.py
export FLASK_SECRETS=config.py
export FLASK_DEBUG=0
export FLASK_ENV=production
mkdir -p $BASE
gunicorn \
--bind 0.0.0.0:4000 "nodes.app:app" \
--daemon \
--log-file $BASE/gunicorn.log \
--pid $BASE/gunicorn.pid \
--access-logfile $BASE/access.log \
--reload
echo "Starting gunicorn with pid $(cat $BASE/gunicorn.pid)"

4
requirements.txt Normal file
View File

@ -0,0 +1,4 @@
requests
flask
peewee
gunicorn

0
xmrnodes/__init__.py Normal file
View File

91
xmrnodes/app.py Normal file
View File

@ -0,0 +1,91 @@
import json
import requests
import re
from os import makedirs
from flask import Flask, request, redirect
from flask import render_template, flash, url_for
from urllib.parse import urlparse
from xmrnodes.models import Node
app = Flask(__name__)
app.config.from_envvar("FLASK_SECRETS")
app.secret_key = app.config["SECRET_KEY"]
@app.route("/", methods=["GET", "POST"])
def index():
itp = 20
page = request.args.get("page", 1)
try:
page = int(page)
except:
flash("Wow, wtf hackerman. Cool it.")
page = 1
nodes = Node.select().where(Node.available==True).order_by(Node.datetime_entered.desc()).paginate(page, itp)
total_pages = Node.select().count() / itp
return render_template("index.html", nodes=nodes, page=page, total_pages=total_pages)
@app.route("/add", methods=["GET", "POST"])
def add():
if request.method == "POST":
url = request.form.get("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 not None:
_url = urlparse(url)
try:
endpoint = f"{_url.scheme}://{_url.netloc}"
r = requests.get(endpoint + "/get_info", timeout=3)
r.raise_for_status()
# print(r.json())
return {"status": "success"}
except requests.exceptions.ConnectTimeout:
flash("connection timed out. double-check the port")
return {"status": "fail", "reason": "timeout"}
except requests.exceptions.SSLError:
flash("invalid certificate")
return {"status": "fail", "reason": "invalid cert"}
except Exception as e:
flash("failed to send req", str(e))
print(e)
return {"status": "fail"}
else:
flash("invalid url provided")
return {"status": "fail"}
return "ok"
node = Node(
scheme=proto,
address=addr,
port=port,
version=r.json()["version"],
tor=addr.endswith(".onion"),
available=r.json()["status"] == "OK",
mainnet=r.json()["mainnet"],
)
node.save()
return {"status": "success"}
return redirect("/")
@app.route("/about")
def about():
return render_template("about.html")
@app.errorhandler(404)
def not_found(error):
flash("nothing there, brah")
return redirect("/")
if __name__ == "__main__":
app.run()

View File

@ -0,0 +1 @@
SECRET_KEY = 'xxxx'

25
xmrnodes/models.py Normal file
View File

@ -0,0 +1,25 @@
from peewee import *
from datetime import datetime
from xmrnodes import config
data_dir = getattr(config, 'DATA_FOLDER', './data')
db = SqliteDatabase(f"{data_dir}/db/sqlite.db")
class Node(Model):
id = AutoField()
scheme = CharField()
address = CharField()
port = IntegerField()
version = CharField(null=True)
tor = BooleanField(default=False)
available = BooleanField(default=False)
mainnet = BooleanField(default=False)
datetime_entered = DateTimeField(default=datetime.now)
datetime_checked = DateTimeField(default=datetime.now)
datetime_failed = DateTimeField(default=None, null=True)
class Meta:
database = db
db.create_tables([Node])

View File

@ -0,0 +1,46 @@
.noty_theme__relax.noty_bar {
margin: 4px 0;
overflow: hidden;
border-radius: 2px;
position: relative; }
.noty_theme__relax.noty_bar .noty_body {
padding: 10px; }
.noty_theme__relax.noty_bar .noty_buttons {
border-top: 1px solid #e7e7e7;
padding: 5px 10px; }
.noty_theme__relax.noty_type__alert,
.noty_theme__relax.noty_type__notification {
background-color: #fff;
border: 1px solid #dedede;
color: #444; }
.noty_theme__relax.noty_type__warning {
background-color: #FFEAA8;
border: 1px solid #FFC237;
color: #826200; }
.noty_theme__relax.noty_type__warning .noty_buttons {
border-color: #dfaa30; }
.noty_theme__relax.noty_type__error {
background-color: #FF8181;
border: 1px solid #e25353;
color: #FFF; }
.noty_theme__relax.noty_type__error .noty_buttons {
border-color: darkred; }
.noty_theme__relax.noty_type__info,
.noty_theme__relax.noty_type__information {
background-color: #78C5E7;
border: 1px solid #3badd6;
color: #FFF; }
.noty_theme__relax.noty_type__info .noty_buttons,
.noty_theme__relax.noty_type__information .noty_buttons {
border-color: #0B90C4; }
.noty_theme__relax.noty_type__success {
background-color: #BCF5BC;
border: 1px solid #7cdd77;
color: darkgreen; }
.noty_theme__relax.noty_type__success .noty_buttons {
border-color: #50C24E; }

View File

@ -0,0 +1,222 @@
.noty_layout_mixin, #noty_layout__top, #noty_layout__topLeft, #noty_layout__topCenter, #noty_layout__topRight, #noty_layout__bottom, #noty_layout__bottomLeft, #noty_layout__bottomCenter, #noty_layout__bottomRight, #noty_layout__center, #noty_layout__centerLeft, #noty_layout__centerRight {
position: fixed;
margin: 0;
padding: 0;
z-index: 9999999;
-webkit-transform: translateZ(0) scale(1, 1);
transform: translateZ(0) scale(1, 1);
-webkit-backface-visibility: hidden;
backface-visibility: hidden;
-webkit-font-smoothing: subpixel-antialiased;
filter: blur(0);
-webkit-filter: blur(0);
max-width: 90%; }
#noty_layout__top {
top: 0;
left: 5%;
width: 90%; }
#noty_layout__topLeft {
top: 20px;
left: 20px;
width: 325px; }
#noty_layout__topCenter {
top: 5%;
left: 50%;
width: 325px;
-webkit-transform: translate(-webkit-calc(-50% - .5px)) translateZ(0) scale(1, 1);
transform: translate(calc(-50% - .5px)) translateZ(0) scale(1, 1); }
#noty_layout__topRight {
top: 20px;
right: 20px;
width: 325px; }
#noty_layout__bottom {
bottom: 0;
left: 5%;
width: 90%; }
#noty_layout__bottomLeft {
bottom: 20px;
left: 20px;
width: 325px; }
#noty_layout__bottomCenter {
bottom: 5%;
left: 50%;
width: 325px;
-webkit-transform: translate(-webkit-calc(-50% - .5px)) translateZ(0) scale(1, 1);
transform: translate(calc(-50% - .5px)) translateZ(0) scale(1, 1); }
#noty_layout__bottomRight {
bottom: 20px;
right: 20px;
width: 325px; }
#noty_layout__center {
top: 50%;
left: 50%;
width: 325px;
-webkit-transform: translate(-webkit-calc(-50% - .5px), -webkit-calc(-50% - .5px)) translateZ(0) scale(1, 1);
transform: translate(calc(-50% - .5px), calc(-50% - .5px)) translateZ(0) scale(1, 1); }
#noty_layout__centerLeft {
top: 50%;
left: 20px;
width: 325px;
-webkit-transform: translate(0, -webkit-calc(-50% - .5px)) translateZ(0) scale(1, 1);
transform: translate(0, calc(-50% - .5px)) translateZ(0) scale(1, 1); }
#noty_layout__centerRight {
top: 50%;
right: 20px;
width: 325px;
-webkit-transform: translate(0, -webkit-calc(-50% - .5px)) translateZ(0) scale(1, 1);
transform: translate(0, calc(-50% - .5px)) translateZ(0) scale(1, 1); }
.noty_progressbar {
display: none; }
.noty_has_timeout.noty_has_progressbar .noty_progressbar {
display: block;
position: absolute;
left: 0;
bottom: 0;
height: 3px;
width: 100%;
background-color: #646464;
opacity: 0.2;
filter: alpha(opacity=10); }
.noty_bar {
-webkit-backface-visibility: hidden;
-webkit-transform: translate(0, 0) translateZ(0) scale(1, 1);
-ms-transform: translate(0, 0) scale(1, 1);
transform: translate(0, 0) scale(1, 1);
-webkit-font-smoothing: subpixel-antialiased;
overflow: hidden; }
.noty_effects_open {
opacity: 0;
-webkit-transform: translate(50%);
-ms-transform: translate(50%);
transform: translate(50%);
-webkit-animation: noty_anim_in 0.5s cubic-bezier(0.68, -0.55, 0.265, 1.55);
animation: noty_anim_in 0.5s cubic-bezier(0.68, -0.55, 0.265, 1.55);
-webkit-animation-fill-mode: forwards;
animation-fill-mode: forwards; }
.noty_effects_close {
-webkit-animation: noty_anim_out 0.5s cubic-bezier(0.68, -0.55, 0.265, 1.55);
animation: noty_anim_out 0.5s cubic-bezier(0.68, -0.55, 0.265, 1.55);
-webkit-animation-fill-mode: forwards;
animation-fill-mode: forwards; }
.noty_fix_effects_height {
-webkit-animation: noty_anim_height 75ms ease-out;
animation: noty_anim_height 75ms ease-out; }
.noty_close_with_click {
cursor: pointer; }
.noty_close_button {
position: absolute;
top: 2px;
right: 2px;
font-weight: bold;
width: 20px;
height: 20px;
text-align: center;
line-height: 20px;
background-color: rgba(0, 0, 0, 0.05);
border-radius: 2px;
cursor: pointer;
-webkit-transition: all .2s ease-out;
transition: all .2s ease-out; }
.noty_close_button:hover {
background-color: rgba(0, 0, 0, 0.1); }
.noty_modal {
position: fixed;
width: 100%;
height: 100%;
background-color: #000;
z-index: 10000;
opacity: .3;
left: 0;
top: 0; }
.noty_modal.noty_modal_open {
opacity: 0;
-webkit-animation: noty_modal_in .3s ease-out;
animation: noty_modal_in .3s ease-out; }
.noty_modal.noty_modal_close {
-webkit-animation: noty_modal_out .3s ease-out;
animation: noty_modal_out .3s ease-out;
-webkit-animation-fill-mode: forwards;
animation-fill-mode: forwards; }
@-webkit-keyframes noty_modal_in {
100% {
opacity: .3; } }
@keyframes noty_modal_in {
100% {
opacity: .3; } }
@-webkit-keyframes noty_modal_out {
100% {
opacity: 0; } }
@keyframes noty_modal_out {
100% {
opacity: 0; } }
@keyframes noty_modal_out {
100% {
opacity: 0; } }
@-webkit-keyframes noty_anim_in {
100% {
-webkit-transform: translate(0);
transform: translate(0);
opacity: 1; } }
@keyframes noty_anim_in {
100% {
-webkit-transform: translate(0);
transform: translate(0);
opacity: 1; } }
@-webkit-keyframes noty_anim_out {
100% {
-webkit-transform: translate(50%);
transform: translate(50%);
opacity: 0; } }
@keyframes noty_anim_out {
100% {
-webkit-transform: translate(50%);
transform: translate(50%);
opacity: 0; } }
@-webkit-keyframes noty_anim_height {
100% {
height: 0; } }
@keyframes noty_anim_height {
100% {
height: 0; } }
/*# sourceMappingURL=noty.css.map*/
/* Custom */
.noty_body {
text-align: center;
}

View File

@ -0,0 +1 @@
{"version":3,"sources":[],"names":[],"mappings":"","file":"noty.css","sourceRoot":""}

41
xmrnodes/static/js/app.js Normal file
View File

@ -0,0 +1,41 @@
$(function() {
$('#addnode').on('submit', function(e) {
e.preventDefault();
var data = $("#addnode :input").serializeArray();
addnode(data[0].value);
});
});
function addnode(url) {
let payload = {'url': url};
$.ajax({
type: 'POST',
url: '/add',
data: payload,
}).done(function(data, status) {
notify('info', 'Trying to connect to node...');
if (data.status == 'success') {
notify('success', 'Successful!')
} else {
notify('error', 'Failed')
}
}).fail(function(data) {
notify('error', 'Failed to add node; unable to fetch info.');
});
// $.post('/add', payload, function(result) {
// $('#addnode')[0].reset();
// notify('success', 'it worked!');
// }).fail(function(data) {
// notify('error', 'Failed to add node; unable to fetch info.');
// });
}
function notify(level, msg) {
new Noty({
type: level,
theme: 'relax',
layout: 'topCenter',
text: msg,
timeout: 5000
}).show();
}

2
xmrnodes/static/js/jquery.min.js vendored Normal file

File diff suppressed because one or more lines are too long

3124
xmrnodes/static/js/noty.js Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1 @@
about

View File

@ -0,0 +1,60 @@
<!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 href="/static/css/wow.css" rel="stylesheet"> -->
<link href="/static/css/noty-relax.css" rel="stylesheet">
<link href="/static/css/noty.css" rel="stylesheet">
<title>XMR Nodes</title>
</head>
<body>
{% with messages = get_flashed_messages() %}
{% if messages %}
<ul class=flashes>
{% for message in messages %}
<li>{{ message }}</li>
{% endfor %}
</ul>
{% endif %}
{% endwith %}
<!-- Page Content -->
{% block content %} {% endblock %}
<!-- Footer -->
<footer class="bg-dark footer">
<div class="container">
<p class="m-0 text-center text-white">XMR Nodes 2020</p>
</div>
</footer>
<!-- Scripts -->
<script src="/static/js/jquery.min.js"></script>
<script src="/static/js/noty.js"></script>
<script src="/static/js/app.js"></script>
</body>
</html>

View File

@ -0,0 +1,32 @@
{% extends 'base.html' %}
{% block content %}
<div class="container" style="text-align:center;">
<div class="title">
<h3>Add Node</h3>
</div>
{% for node in nodes %}
{{ node }}<br>
{% endfor %}
<form id="addnode" method="POST">
<label>Node URL:</label>
<input type="text" name="url"/>
<input type="submit" value="Submit" name="submit" class="submit" id="submit" />
</form>
{% if page > 1 %}
<a href="/?page={{ page - 1 }}">Back</a>
{% endif %}
{% if page < total_pages and total_pages > 0 %}
<a href="/?page={{ page + 1 }}">Next</a>
{% endif %}
</div>
{% endblock %}