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:
Jason Rhinelander 2020-08-31 16:56:21 -03:00
parent 81d9dfdef7
commit 0bce9ab758
4 changed files with 108 additions and 23 deletions

View file

@ -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,
)

View file

@ -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; }

View file

@ -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>

View file

@ -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>