mirror of
https://github.com/oxen-io/oxen-observer.git
synced 2023-12-14 09:22:54 +01:00
Implement search
This works and displays a little differently than before: - Only one search box for block/tx/SN instead of one for block/tx and one for SN - Put the search box at the top right when the page is wide enough. - When a search result is found we redirect to its proper URL rather than displaying on the search URL
This commit is contained in:
parent
81d9dfdef7
commit
0bce9ab758
77
observer.py
77
observer.py
|
@ -6,6 +6,7 @@ import babel.dates
|
||||||
import json
|
import json
|
||||||
import sys
|
import sys
|
||||||
import statistics
|
import statistics
|
||||||
|
import string
|
||||||
from base64 import b32encode, b16decode
|
from base64 import b32encode, b16decode
|
||||||
from werkzeug.routing import BaseConverter
|
from werkzeug.routing import BaseConverter
|
||||||
from pygments import highlight
|
from pygments import highlight
|
||||||
|
@ -237,7 +238,7 @@ def main(refresh=None, page=0, per_page=None, first=None, last=None):
|
||||||
end_height = max(0, height - per_page*page - 1)
|
end_height = max(0, height - per_page*page - 1)
|
||||||
start_height = max(0, end_height - per_page + 1)
|
start_height = max(0, end_height - per_page + 1)
|
||||||
|
|
||||||
blocks = FutureJSON(lmq, lokid, 'rpc.get_block_headers_range', args={
|
blocks = FutureJSON(lmq, lokid, 'rpc.get_block_headers_range', cache_key='main', args={
|
||||||
'start_height': start_height,
|
'start_height': start_height,
|
||||||
'end_height': end_height,
|
'end_height': end_height,
|
||||||
'get_tx_hashes': True,
|
'get_tx_hashes': True,
|
||||||
|
@ -322,20 +323,41 @@ def sns():
|
||||||
inactive_sns=inactive,
|
inactive_sns=inactive,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def tx_req(lmq, lokid, txid, **kwargs):
|
||||||
|
return FutureJSON(lmq, lokid, 'rpc.get_transactions', cache_seconds=10, cache_key='single',
|
||||||
|
args={
|
||||||
|
"txs_hashes": [txid],
|
||||||
|
"decode_as_json": True,
|
||||||
|
"tx_extra": True,
|
||||||
|
"prune": True,
|
||||||
|
},
|
||||||
|
**kwargs)
|
||||||
|
|
||||||
|
def sn_req(lmq, lokid, pubkey, **kwargs):
|
||||||
|
return FutureJSON(lmq, lokid, 'rpc.get_service_nodes', 5, cache_key='single',
|
||||||
|
args={"service_node_pubkeys": [pubkey]}, **kwargs
|
||||||
|
)
|
||||||
|
|
||||||
|
def block_req(lmq, lokid, hash_or_height, **kwargs):
|
||||||
|
if len(hash_or_height) <= 10 and hash_or_height.isdigit():
|
||||||
|
return FutureJSON(lmq, lokid, 'rpc.get_block_header_by_height', cache_key='single',
|
||||||
|
args={ "height": int(hash_or_height) }, **kwargs)
|
||||||
|
else:
|
||||||
|
return FutureJSON(lmq, lokid, 'rpc.get_block_header_by_hash', cache_key='single',
|
||||||
|
args={ "hash": hash_or_height }, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
@app.route('/sn/<hex64:pubkey>')
|
|
||||||
@app.route('/service_node/<hex64:pubkey>') # For backwards compatibility with old explorer URLs
|
@app.route('/service_node/<hex64:pubkey>') # For backwards compatibility with old explorer URLs
|
||||||
|
@app.route('/sn/<hex64:pubkey>')
|
||||||
def show_sn(pubkey):
|
def show_sn(pubkey):
|
||||||
lmq, lokid = lmq_connection()
|
lmq, lokid = lmq_connection()
|
||||||
info = FutureJSON(lmq, lokid, 'rpc.get_info', 1)
|
info = FutureJSON(lmq, lokid, 'rpc.get_info', 1)
|
||||||
hfinfo = FutureJSON(lmq, lokid, 'rpc.hard_fork_info', 10)
|
hfinfo = FutureJSON(lmq, lokid, 'rpc.hard_fork_info', 10)
|
||||||
sn = FutureJSON(lmq, lokid, 'rpc.get_service_nodes', 5, cache_key='single', args={
|
sn = sn_req(lmq, lokid, pubkey).get()
|
||||||
"service_node_pubkeys": [pubkey]}).get()
|
|
||||||
|
|
||||||
if 'service_node_states' not in sn or not sn['service_node_states']:
|
if 'service_node_states' not in sn or not sn['service_node_states']:
|
||||||
return flask.render_template('not_found.html',
|
return flask.render_template('not_found.html',
|
||||||
info=info.get(),
|
info=info.get(),
|
||||||
hf=hfinfo.get(),
|
|
||||||
type='sn',
|
type='sn',
|
||||||
id=pubkey,
|
id=pubkey,
|
||||||
)
|
)
|
||||||
|
@ -362,12 +384,7 @@ def show_sn(pubkey):
|
||||||
def show_tx(txid, more_details=False):
|
def show_tx(txid, more_details=False):
|
||||||
lmq, lokid = lmq_connection()
|
lmq, lokid = lmq_connection()
|
||||||
info = FutureJSON(lmq, lokid, 'rpc.get_info', 1)
|
info = FutureJSON(lmq, lokid, 'rpc.get_info', 1)
|
||||||
txs = FutureJSON(lmq, lokid, 'rpc.get_transactions', cache_seconds=10, args={
|
txs = tx_req(lmq, lokid, txid).get()
|
||||||
"txs_hashes": [txid],
|
|
||||||
"decode_as_json": True,
|
|
||||||
"tx_extra": True,
|
|
||||||
"prune": True,
|
|
||||||
}).get()
|
|
||||||
|
|
||||||
if 'txs' not in txs or not txs['txs']:
|
if 'txs' not in txs or not txs['txs']:
|
||||||
return flask.render_template('not_found.html',
|
return flask.render_template('not_found.html',
|
||||||
|
@ -434,3 +451,41 @@ def show_tx(txid, more_details=False):
|
||||||
block_info=block_info,
|
block_info=block_info,
|
||||||
**more_details,
|
**more_details,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@app.route('/search')
|
||||||
|
def search():
|
||||||
|
lmq, lokid = lmq_connection()
|
||||||
|
info = FutureJSON(lmq, lokid, 'rpc.get_info', 1)
|
||||||
|
val = flask.request.args.get('value')
|
||||||
|
|
||||||
|
if val and len(val) < 10 and val.isdigit(): # Block height
|
||||||
|
return show_block(val)
|
||||||
|
if not val or len(val) != 64 or any(c not in string.hexdigits for c in val):
|
||||||
|
return flask.render_template('not_found.html',
|
||||||
|
info=info.get(),
|
||||||
|
type='bad_search',
|
||||||
|
id=val,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Initiate all the lookups at once, then redirect to whichever one responds affirmatively
|
||||||
|
snreq = sn_req(lmq, lokid, val)
|
||||||
|
blreq = block_req(lmq, lokid, val, fail_okay=True)
|
||||||
|
txreq = tx_req(lmq, lokid, val)
|
||||||
|
|
||||||
|
sn = snreq.get()
|
||||||
|
if 'service_node_states' in sn and sn['service_node_states']:
|
||||||
|
return flask.redirect(flask.url_for('show_sn', pubkey=val), code=301)
|
||||||
|
bl = blreq.get()
|
||||||
|
if bl and 'block_header' in bl and bl['block_header']:
|
||||||
|
return flask.redirect(flask.url_for('show_block', hash=val), code=301)
|
||||||
|
tx = txreq.get()
|
||||||
|
if tx and 'txs' in tx and tx['txs']:
|
||||||
|
return flask.redirect(flask.url_for('show_tx', txid=val), code=301)
|
||||||
|
|
||||||
|
return flask.render_template('not_found.html',
|
||||||
|
info=info.get(),
|
||||||
|
type='search',
|
||||||
|
id=val,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
|
@ -13,6 +13,32 @@ h1, h2, h3, h4, h5, h6 {
|
||||||
margin-bottom: 0.2em;
|
margin-bottom: 0.2em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#header>#header-content {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
padding-top: 12px;
|
||||||
|
padding-bottom: 12px;
|
||||||
|
}
|
||||||
|
#header h1 {
|
||||||
|
flex: 50%;
|
||||||
|
min-width: 500px;
|
||||||
|
}
|
||||||
|
#header form.top-search {
|
||||||
|
flex: 50%;
|
||||||
|
min-width: 500px;
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
#header form.top-search input[type="text"] {
|
||||||
|
flex-grow: 1;
|
||||||
|
flex-shrink: 1;
|
||||||
|
}
|
||||||
|
#header form.top-search input[type="submit"] {
|
||||||
|
flex: 0 0 12ex;
|
||||||
|
margin-right: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
.general_info {
|
.general_info {
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
margin-top: 5px;
|
margin-top: 5px;
|
||||||
|
@ -180,7 +206,7 @@ form {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
}
|
}
|
||||||
|
|
||||||
.style-1 input[type="text"] {
|
.top-search input[type="text"] {
|
||||||
padding: 2px;
|
padding: 2px;
|
||||||
border: solid 1px rgba(255, 255, 255, 0.25);
|
border: solid 1px rgba(255, 255, 255, 0.25);
|
||||||
background-color: #1a1a1a;
|
background-color: #1a1a1a;
|
||||||
|
@ -190,8 +216,8 @@ form {
|
||||||
color: white;
|
color: white;
|
||||||
}
|
}
|
||||||
|
|
||||||
.style-1 input[type="text"]:focus,
|
.top-search input[type="text"]:focus,
|
||||||
.style-1 input[type="text"].focus {
|
.top-search input[type="text"].focus {
|
||||||
border: solid 1px #008522;
|
border: solid 1px #008522;
|
||||||
}
|
}
|
||||||
h1, h2, h3, h4, h5, h6 { color: white; }
|
h1, h2, h3, h4, h5, h6 { color: white; }
|
||||||
|
|
|
@ -15,7 +15,7 @@
|
||||||
<body>
|
<body>
|
||||||
<div id="header" class="Wrapper">
|
<div id="header" class="Wrapper">
|
||||||
{% block header %}
|
{% block header %}
|
||||||
<div>
|
<div id="header-content">
|
||||||
<h1 class="Header"><a href="/">Loki
|
<h1 class="Header"><a href="/">Loki
|
||||||
{%if info and info.testnet%}
|
{%if info and info.testnet%}
|
||||||
<span style="color:#ff6b62">Testnet</span>
|
<span style="color:#ff6b62">Testnet</span>
|
||||||
|
@ -23,16 +23,12 @@
|
||||||
<span style="color:#af5bd2">Devnet</span>
|
<span style="color:#af5bd2">Devnet</span>
|
||||||
{%endif%}
|
{%endif%}
|
||||||
Blockchain Explorer</a></h1>
|
Blockchain Explorer</a></h1>
|
||||||
<form action="/search" method="get" style="width: 100%; margin-top:15px;" class="style-1">
|
<form action="/search" method="get" class="top-search">
|
||||||
<input type="text" name="value" size="120" placeholder="Block Height, Block Hash, Transaction Hash">
|
<input type="text" name="value" size="64" placeholder="Block height, Block hash, Transaction ID, or Service Node pubkey">
|
||||||
<input type="submit" class="PageButton" value="Search">
|
<input type="submit" class="PageButton" value="Search">
|
||||||
</form>
|
</form>
|
||||||
<form action="/search_service_node" method="get" style="width: 100%; margin-top:15px; margin-bottom: 1em;" class="style-1">
|
|
||||||
<input type="text" name="value" size="120" placeholder="Service Node Public Key">
|
|
||||||
<input type="submit" class="PageButton" value="Search">
|
|
||||||
</form>
|
|
||||||
<div class="TitleUnderliner" style="margin-bottom: 1em"></div>
|
|
||||||
</div>
|
</div>
|
||||||
|
<div class="TitleUnderliner" style="margin-bottom: 1em"></div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
@ -7,8 +7,16 @@
|
||||||
|
|
||||||
{%if type == 'tx'%}
|
{%if type == 'tx'%}
|
||||||
<h2>The transaction with id <code>{{id}}</code> was not found on the blockchain.</h2>
|
<h2>The transaction with id <code>{{id}}</code> was not found on the blockchain.</h2>
|
||||||
|
{%elif type == 'sn'%}
|
||||||
|
<h2>The service node with pubkey <code>{{id}}</code> is not currently registered.</h2>
|
||||||
|
{%elif type == 'bad_search'%}
|
||||||
|
<h2>Invalid search request: <code>{{id}}</code></h2>
|
||||||
|
<h3>Please enter a block hash, height, transaction id, or service node public key.</h3>
|
||||||
|
{%elif type == 'search'%}
|
||||||
|
<h2>Not found: <code>{{id}}</code></h2>
|
||||||
|
<h3>Couldn't find a block, transaction, or service node matching your query.</h3>
|
||||||
{%else%}
|
{%else%}
|
||||||
<h3>Whoops! Couldn't find what you were looking for.</h3>
|
<h3>Whoops! Couldn't find what you were looking for.</h3>
|
||||||
{%endif%}
|
{%endif%}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue