diff options
| -rw-r--r-- | browser/app.py | 69 | ||||
| -rw-r--r-- | browser/requirements.txt | 11 | ||||
| -rw-r--r-- | browser/static/InclusiveSans-Regular.ttf | bin | 0 -> 57124 bytes | |||
| -rw-r--r-- | browser/static/styles.css | 130 | ||||
| -rw-r--r-- | browser/templates/aside.html.j2 | 16 | ||||
| -rw-r--r-- | browser/templates/base.html.j2 | 20 | ||||
| -rw-r--r-- | browser/templates/error.html.j2 | 6 | ||||
| -rw-r--r-- | browser/templates/form_find.html.j2 | 6 | ||||
| -rw-r--r-- | browser/templates/form_range.html.j2 | 8 | ||||
| -rw-r--r-- | browser/templates/index.html.j2 | 28 |
10 files changed, 294 insertions, 0 deletions
diff --git a/browser/app.py b/browser/app.py new file mode 100644 index 0000000..1398e93 --- /dev/null +++ b/browser/app.py @@ -0,0 +1,69 @@ +from dotenv import load_dotenv +load_dotenv() + +import os +import sys +import traceback +import tempfile +from flask import Flask, render_template +from flask_compress import Compress # type: ignore + +from log_db import DB, Bound + +app = Flask(__name__) +Compress(app) + +BASE_URL = os.environ["BASE_URL"] +DB_DIR = os.environ["DB_DIR"] if "DB_DIR" in os.environ else tempfile.TemporaryDirectory().name + +print("""\n""" + f"""BASE_URL: {BASE_URL}\n""" + f"""DB_DIR: {DB_DIR}\n""") + +db = DB \ + .configure() \ + .data_dir(DB_DIR) \ + .fields(["id", "name"]) \ + .primary_key("id") \ + .secondary_keys(["name"]) \ + .initialize() + +def error_page(message: str, code: int = 400): + return render_template("error.html.j2", error = message), code + +@app.errorhandler(404) +def not_found(e: Exception): + return error_page("Not found", 404) + +@app.errorhandler(405) +def method_not_allowed(e: Exception): + return error_page(str(e), 405) + +@app.errorhandler(Exception) +def error_handler(e: Exception): + traceback.print_exception(e, file=sys.stderr) + return error_page("Internal server error", 500) + +@app.get("/") +def index(): + return page_find() + +@app.get("/find") +def page_find(): + rows = db.range_by("id", Bound.unbounded(), Bound.unbounded(), limit=100) + + return render_template('index.html.j2', + selected_form = "form_find.html.j2", + field_names = ["id", "name"], + rows = rows, + ) + +@app.get("/range") +def page_range(): + rows = db.range_by("id", Bound.unbounded(), Bound.unbounded(), limit=100) + + return render_template('index.html.j2', + selected_form = "form_range.html.j2", + field_names = ["id", "name"], + rows = rows, + ) diff --git a/browser/requirements.txt b/browser/requirements.txt new file mode 100644 index 0000000..b6b9bff --- /dev/null +++ b/browser/requirements.txt @@ -0,0 +1,11 @@ +blinker==1.9.0 +Brotli==1.1.0 +click==8.1.8 +Flask==3.1.0 +Flask-Compress==1.17 +itsdangerous==2.2.0 +Jinja2==3.1.5 +MarkupSafe==3.0.2 +python-dotenv==1.0.1 +Werkzeug==3.1.3 +zstandard==0.23.0 diff --git a/browser/static/InclusiveSans-Regular.ttf b/browser/static/InclusiveSans-Regular.ttf Binary files differnew file mode 100644 index 0000000..3a30e79 --- /dev/null +++ b/browser/static/InclusiveSans-Regular.ttf diff --git a/browser/static/styles.css b/browser/static/styles.css new file mode 100644 index 0000000..73379e7 --- /dev/null +++ b/browser/static/styles.css @@ -0,0 +1,130 @@ +:root { + --blue: rgb(30, 99, 248); + --red: #d42f2f; + --yellow: #ff9800; + --black: rgb(0, 0, 0); + --white: rgb(255, 255, 255); + --gray: #666; + --light-gray-1: #f2f2f2; + --light-gray-2: #f8f6ff; + --gold-trans: #ffcd0455; + + --font-family: "Inclusive Sans", sans-serif; +} + +@font-face { + font-family: "Inclusive Sans"; + font-style: normal; + font-weight: 400; + font-display: swap; + src: url(/static/InclusiveSans-Regular.ttf) format("woff2"); +} + +html { + font-family: var(--font-family) !important; + font-size: 16px; +} + +body { + padding: 10px; + margin: 0 auto; + line-height: 24px; +} + +main { + display: flex; + flex-flow: row wrap; + justify-content: space-between; + align-items: flex-start; +} + +a, +.contrast { + color: var(--blue); +} + +h1 { + margin-bottom: 40px; +} + +h1 a { + color: var(--black); + text-decoration: none; +} + +h1:hover a { + border-bottom: 2px dotted var(--blue); + text-decoration: none; +} + +textarea, +input { + font-family: var(--font-family); + font-size: 16px; +} + +button, +input[type="submit"] { + cursor: pointer; + font-size: 16px; + border-radius: 4px; + text-shadow: none; + box-shadow: none; + padding: 5px 8px; + border: 0; + background-color: var(--blue); + color: var(--white); + + margin-top: 20px; +} + +button.red, +input[type="submit"].red { + background-color: var(--red); +} + +button.blue, +input[type="submit"].blue { + background-color: var(--blue); +} + +button.yellow, +input[type="submit"].yellow { + background-color: var(--yellow); +} + +input[type="text"], +input[type*="date"] { + font-size: 16px; + color: var(--black); +} + +input[type="text"]:disabled, +input[type*="date"]:disabled { + color: var(--gray); +} + +main > aside { + width: 250px; +} + +main > .results { + flex: 1; + overflow-y: scroll; + max-height: 100%; +} + +table { + width: 100%; +} + +th, +td { + min-width: 100px; + text-align: left; + padding: 7px 5px; +} + +tr:nth-of-type(odd) td { + background-color: var(--light-gray-2); +} diff --git a/browser/templates/aside.html.j2 b/browser/templates/aside.html.j2 new file mode 100644 index 0000000..eb8c9cf --- /dev/null +++ b/browser/templates/aside.html.j2 @@ -0,0 +1,16 @@ +{% block aside %} +<aside> + <h3>Read operations</h3> + <ul> + <li><a href="/find">Find</a></li> + <li><a href="/range">Range</a></li> + </ul> + <h3>Write operations</h3> + <ul> + <li><a href="/upsert">Upsert</a></li> + <li><a href="/delete">Delete</a></li> + </ul> + + {% include selected_form %} +</aside> +{% endblock %} diff --git a/browser/templates/base.html.j2 b/browser/templates/base.html.j2 new file mode 100644 index 0000000..a8a6cf7 --- /dev/null +++ b/browser/templates/base.html.j2 @@ -0,0 +1,20 @@ +<!DOCTYPE html> +<html lang="en"> +<head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <link rel="preload" href="/static/InclusiveSans-Regular.ttf" as="font" type="font/woff2" crossorigin="anonymous"> + <link rel="stylesheet" href="/static/styles.css"></link> + {% block head_meta %} + <title>Database browser</title> + <meta name="description" content="Database browser for log_db"> + {% endblock %} + + <!-- -- https://leanrada.com/htmz/ --> + <iframe hidden name=htmz onload="setTimeout(()=>document.querySelector(contentWindow.location.hash||null)?.replaceWith(...contentDocument.body.childNodes))"></iframe> +</head> +<body> + <h1><a href="/">Database browser</a></h1> + {% block content %}{% endblock %} +</body> +</html> diff --git a/browser/templates/error.html.j2 b/browser/templates/error.html.j2 new file mode 100644 index 0000000..2d0dc19 --- /dev/null +++ b/browser/templates/error.html.j2 @@ -0,0 +1,6 @@ +{% extends "base.html.j2" %} + +{% block content %} +<h2>Error</h2> +{{ error }} +{% endblock %} diff --git a/browser/templates/form_find.html.j2 b/browser/templates/form_find.html.j2 new file mode 100644 index 0000000..54608b6 --- /dev/null +++ b/browser/templates/form_find.html.j2 @@ -0,0 +1,6 @@ +<form action="/find" method="post"> + <label for="field">Field</label> + <input type="text" name="field" id="field" required> + <label for="values">Values</label> + <input type="text" name="values" id="values" required> + <button type="submit">Run query</button> diff --git a/browser/templates/form_range.html.j2 b/browser/templates/form_range.html.j2 new file mode 100644 index 0000000..76eb4d2 --- /dev/null +++ b/browser/templates/form_range.html.j2 @@ -0,0 +1,8 @@ +<form action="/range" method="post"> + <label for="field">Field</label> + <input type="text" name="field" id="field" required> + <label for="values">From...</label> + <input type="text" name="from" id="from" required> + <label for="values">...To</label> + <input type="text" name="to" id="to" required> + <button type="submit">Run query</button> diff --git a/browser/templates/index.html.j2 b/browser/templates/index.html.j2 new file mode 100644 index 0000000..9713944 --- /dev/null +++ b/browser/templates/index.html.j2 @@ -0,0 +1,28 @@ +{% extends "base.html.j2" %} + +{% block content %} +<main> + {% include "aside.html.j2" %} + + <div class="results"> + <table> + <thead> + <tr> + {% for field_name in field_names %} + <th>{{ field_name }}</th> + {% endfor %} + </tr> + </thead> + <tbody> + {% for row in rows %} + <tr> + {% for field in row %} + <td>{{ field }}</td> + {% endfor %} + </tr> + {% endfor %} + </tbody> + </table> + </div> +</main> +{% endblock %} |
