adding app so far
parent
142b570503
commit
dc06c87808
@ -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
|
@ -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
|
@ -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)"
|
@ -0,0 +1,4 @@
|
|||||||
|
requests
|
||||||
|
flask
|
||||||
|
peewee
|
||||||
|
gunicorn
|
@ -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()
|
@ -0,0 +1 @@
|
|||||||
|
SECRET_KEY = 'xxxx'
|
@ -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])
|
@ -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; }
|
@ -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;
|
||||||
|
}
|
@ -0,0 +1 @@
|
|||||||
|
{"version":3,"sources":[],"names":[],"mappings":"","file":"noty.css","sourceRoot":""}
|
@ -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();
|
||||||
|
}
|
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
@ -0,0 +1 @@
|
|||||||
|
about
|
@ -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>
|
@ -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 %}
|
Loading…
Reference in New Issue