mirror of
https://git.wownero.com/qvqc/monero.fail.git
synced 2024-08-15 03:26:23 +00:00
adding app so far
This commit is contained in:
parent
142b570503
commit
dc06c87808
18 changed files with 3689 additions and 0 deletions
4
.gitignore
vendored
4
.gitignore
vendored
|
@ -127,3 +127,7 @@ dmypy.json
|
|||
|
||||
# Pyre type checker
|
||||
.pyre/
|
||||
|
||||
# nodes
|
||||
config.py
|
||||
data
|
||||
|
|
7
bin/cmd
Executable file
7
bin/cmd
Executable 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
7
bin/dev
Executable 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
21
bin/prod
Executable 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
4
requirements.txt
Normal file
|
@ -0,0 +1,4 @@
|
|||
requests
|
||||
flask
|
||||
peewee
|
||||
gunicorn
|
0
xmrnodes/__init__.py
Normal file
0
xmrnodes/__init__.py
Normal file
91
xmrnodes/app.py
Normal file
91
xmrnodes/app.py
Normal 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()
|
1
xmrnodes/config.example.py
Normal file
1
xmrnodes/config.example.py
Normal file
|
@ -0,0 +1 @@
|
|||
SECRET_KEY = 'xxxx'
|
25
xmrnodes/models.py
Normal file
25
xmrnodes/models.py
Normal 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])
|
46
xmrnodes/static/css/noty-relax.css
Normal file
46
xmrnodes/static/css/noty-relax.css
Normal 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; }
|
222
xmrnodes/static/css/noty.css
Normal file
222
xmrnodes/static/css/noty.css
Normal 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;
|
||||
}
|
1
xmrnodes/static/css/noty.css.map
Normal file
1
xmrnodes/static/css/noty.css.map
Normal file
|
@ -0,0 +1 @@
|
|||
{"version":3,"sources":[],"names":[],"mappings":"","file":"noty.css","sourceRoot":""}
|
41
xmrnodes/static/js/app.js
Normal file
41
xmrnodes/static/js/app.js
Normal 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
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
3124
xmrnodes/static/js/noty.js
Normal file
File diff suppressed because it is too large
Load diff
1
xmrnodes/templates/about.html
Normal file
1
xmrnodes/templates/about.html
Normal file
|
@ -0,0 +1 @@
|
|||
about
|
60
xmrnodes/templates/base.html
Normal file
60
xmrnodes/templates/base.html
Normal 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>
|
32
xmrnodes/templates/index.html
Normal file
32
xmrnodes/templates/index.html
Normal 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 %}
|
Loading…
Reference in a new issue