From 0bce9ab7582b054e8d8a12b4e673ebe4b3ed9217 Mon Sep 17 00:00:00 2001 From: Jason Rhinelander Date: Mon, 31 Aug 2020 16:56:21 -0300 Subject: [PATCH] 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 --- observer.py | 77 ++++++++++++++++++++++++++++++++++------ static/style.css | 32 +++++++++++++++-- templates/_basic.html | 12 +++---- templates/not_found.html | 10 +++++- 4 files changed, 108 insertions(+), 23 deletions(-) diff --git a/observer.py b/observer.py index 136d7e6..0b13970 100644 --- a/observer.py +++ b/observer.py @@ -6,6 +6,7 @@ import babel.dates import json import sys import statistics +import string from base64 import b32encode, b16decode from werkzeug.routing import BaseConverter 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) 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, 'end_height': end_height, 'get_tx_hashes': True, @@ -322,20 +323,41 @@ def sns(): 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/') @app.route('/service_node/') # For backwards compatibility with old explorer URLs +@app.route('/sn/') def show_sn(pubkey): lmq, lokid = lmq_connection() info = FutureJSON(lmq, lokid, 'rpc.get_info', 1) hfinfo = FutureJSON(lmq, lokid, 'rpc.hard_fork_info', 10) - sn = FutureJSON(lmq, lokid, 'rpc.get_service_nodes', 5, cache_key='single', args={ - "service_node_pubkeys": [pubkey]}).get() + sn = sn_req(lmq, lokid, pubkey).get() if 'service_node_states' not in sn or not sn['service_node_states']: return flask.render_template('not_found.html', info=info.get(), - hf=hfinfo.get(), type='sn', id=pubkey, ) @@ -362,12 +384,7 @@ def show_sn(pubkey): def show_tx(txid, more_details=False): lmq, lokid = lmq_connection() info = FutureJSON(lmq, lokid, 'rpc.get_info', 1) - txs = FutureJSON(lmq, lokid, 'rpc.get_transactions', cache_seconds=10, args={ - "txs_hashes": [txid], - "decode_as_json": True, - "tx_extra": True, - "prune": True, - }).get() + txs = tx_req(lmq, lokid, txid).get() if 'txs' not in txs or not txs['txs']: return flask.render_template('not_found.html', @@ -434,3 +451,41 @@ def show_tx(txid, more_details=False): block_info=block_info, **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, + ) + diff --git a/static/style.css b/static/style.css index 57a33e4..e123ca2 100644 --- a/static/style.css +++ b/static/style.css @@ -13,6 +13,32 @@ h1, h2, h3, h4, h5, h6 { 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 { font-size: 12px; margin-top: 5px; @@ -180,7 +206,7 @@ form { display: inline-block; } -.style-1 input[type="text"] { +.top-search input[type="text"] { padding: 2px; border: solid 1px rgba(255, 255, 255, 0.25); background-color: #1a1a1a; @@ -190,8 +216,8 @@ form { color: white; } -.style-1 input[type="text"]:focus, -.style-1 input[type="text"].focus { +.top-search input[type="text"]:focus, +.top-search input[type="text"].focus { border: solid 1px #008522; } h1, h2, h3, h4, h5, h6 { color: white; } diff --git a/templates/_basic.html b/templates/_basic.html index 5910089..2cccacc 100644 --- a/templates/_basic.html +++ b/templates/_basic.html @@ -15,7 +15,7 @@