diff options
| author | Jan Tuomi <jan@jantuomi.fi> | 2025-05-02 16:37:29 +0300 |
|---|---|---|
| committer | Jan Tuomi <jan@jantuomi.fi> | 2025-05-02 16:37:29 +0300 |
| commit | ef72b74ec34e95a38890d542cda298dd6565d5ad (patch) | |
| tree | 63fe96b24a8534e8dd2d88b1ca3d69b21c502d3e /browser | |
| parent | daca0e6b68d32d885face00d68df5b5cb00e098b (diff) | |
Start replacing browser with a chat demo
Diffstat (limited to 'browser')
| -rw-r--r-- | browser/app.py | 242 | ||||
| -rw-r--r-- | browser/requirements.txt | 11 | ||||
| -rw-r--r-- | browser/static/InclusiveSans-Regular.ttf | bin | 57124 -> 0 bytes | |||
| -rw-r--r-- | browser/static/htmp.js | 38 | ||||
| -rw-r--r-- | browser/static/styles.css | 140 | ||||
| -rw-r--r-- | browser/templates/base.html.j2 | 19 | ||||
| -rw-r--r-- | browser/templates/frag_aside.html.j2 | 27 | ||||
| -rw-r--r-- | browser/templates/frag_aside_link.html.j2 | 9 | ||||
| -rw-r--r-- | browser/templates/frag_error.html.j2 | 16 | ||||
| -rw-r--r-- | browser/templates/frag_form_delete.html.j2 | 7 | ||||
| -rw-r--r-- | browser/templates/frag_form_find.html.j2 | 7 | ||||
| -rw-r--r-- | browser/templates/frag_form_range.html.j2 | 21 | ||||
| -rw-r--r-- | browser/templates/frag_form_upsert.html.j2 | 5 | ||||
| -rw-r--r-- | browser/templates/frag_results.html.j2 | 18 | ||||
| -rw-r--r-- | browser/templates/page_error.html.j2 | 5 | ||||
| -rw-r--r-- | browser/templates/page_main.html.j2 | 11 |
16 files changed, 0 insertions, 576 deletions
diff --git a/browser/app.py b/browser/app.py deleted file mode 100644 index be79432..0000000 --- a/browser/app.py +++ /dev/null @@ -1,242 +0,0 @@ -from dotenv import load_dotenv -load_dotenv() - -import os -import sys -import traceback -import tempfile -from typing import cast -from flask import Flask, render_template, request -from flask_compress import Compress # type: ignore - -import log_db -from log_db import DB, Bound, Value - -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_fields = ["id", "name"] -db_types = ["int", "string"] - -db = DB \ - .configure() \ - .data_dir(DB_DIR) \ - .fields(db_fields) \ - .primary_key("id") \ - .secondary_keys(["name"]) \ - .initialize() - -def error(e: str, code: int): - error_text = f"HTTP {code}: {e}" - #return render_template("page_error.html.j2", error = error_text) - - htmp_target = request.form.get("htmp") or request.args.get("htmp") - if htmp_target: - return render_template("frag_error.html.j2", error = error_text, container = htmp_target) - else: - return render_template("page_error.html.j2", error = error_text) - -@app.errorhandler(Exception) -def error_handler(e: Exception): - if hasattr(e, "code") and 400 >= getattr(e, "code") < 500: - return error(str(e), getattr(e, "code")) - else: - traceback.print_exception(e, file=sys.stderr) - return error("Internal Server Error", 500) - -@app.get("/") -def index_default(): - return index("find") - -@app.get("/<op>") -def index(op: str): - if op not in ["find", "range", "upsert", "delete"]: - return error("Invalid operation", 400) - - rows = db.range_by("id", Bound.unbounded(), Bound.unbounded(), limit=100) - rows = [[value_to_str(v) for v in row] for row in rows] - - htmp_target = request.form.get("htmp") or request.args.get("htmp") - if htmp_target: - return render_template(f"frag_form_{op}.html.j2") - else: - return render_template('page_main.html.j2', - selected_form = f"frag_form_{op}.html.j2", - field_names = ["id", "name"], - rows = rows, - ) - -@app.post("/find") -def query_find(): - field = request.form.get("field") - if not field: raise ValueError("Field is required") - - values = request.form.get("values") - if not values: raise ValueError("Values are required") - - field_index = db_fields.index(field) - if field_index == -1: raise ValueError(f"Field '{field}' not found") - - try: - values = [cast_to_value(field, v) for v in values.split("\n")] - values = [v for v in values if v is not None] - except ValueError as e: - return error(str(e), 400) - - tagged_rows = db.batch_find_by(field, values, limit=100) - rows = [[value_to_str(v) for v in row] for (_, row) in tagged_rows] - - htmp_target = request.form.get("htmp") or request.args.get("htmp") - if htmp_target: - return render_template("frag_results.html.j2", - field_names = ["id", "name"], - rows = rows, - ) - else: - return render_template('page_main.html.j2', - selected_form = "frag_form_find.html.j2", - field_names = ["id", "name"], - rows = rows, - ) - -@app.post("/range") -def query_range(): - field = request.form.get("field") - if not field: raise ValueError("Field is required") - - from_type = request.form.get("from_type") - if not from_type: raise ValueError("From type is required") - - to_type = request.form.get("to_type") - if not to_type: raise ValueError("To type is required") - - field_index = db_fields.index(field) - if field_index == -1: raise ValueError(f"Field '{field}' not found") - - match from_type: - case "unbounded": - bound_lower = Bound.unbounded() - case "included": - from_value = request.form.get("from_value") - try: - if not from_value: raise ValueError("From value is required") - from_value = cast_to_value(field, from_value) - except ValueError as e: - return error(str(e), 400) - bound_lower = Bound.included(cast(Value, from_value)) - case "excluded": - from_value = request.form.get("from_value") - try: - if not from_value: raise ValueError("From value is required") - from_value = cast_to_value(field, from_value) - except ValueError as e: - return error(str(e), 400) - bound_lower = Bound.excluded(cast(Value, from_value)) - case _: - return error("Invalid from type", 400) - - match to_type: - case "unbounded": - bound_upper = Bound.unbounded() - case "included": - to_value = request.form.get("to_value") - try: - if not to_value: raise ValueError("To value is required") - to_value = cast_to_value(field, to_value) - except ValueError as e: - return error(str(e), 400) - bound_upper = Bound.included(cast(Value, to_value)) - case "excluded": - to_value = request.form.get("to_value") - try: - if not to_value: raise ValueError("To value is required") - to_value = cast_to_value(field, to_value) - except ValueError as e: - return error(str(e), 400) - bound_upper = Bound.excluded(cast(Value, to_value)) - case _: - return error("Invalid to type", 400) - - tagged_rows = db.range_by(field, bound_lower, bound_upper, limit=100) - rows = [[value_to_str(v) for v in row] for row in tagged_rows] - - htmp_target = request.form.get("htmp") or request.args.get("htmp") - if htmp_target: - return render_template("frag_results.html.j2", - field_names = ["id", "name"], - rows = rows, - ) - else: - return render_template('page_main.html.j2', - selected_form = "frag_form_range.html.j2", - field_names = ["id", "name"], - rows = rows, - ) - -@app.post("/delete") -def query_delete(): - field = request.form.get("field") - if not field: raise ValueError("Field is required") - - value = request.form.get("value") - if not value: raise ValueError("Value is required") - - field_index = db_fields.index(field) - if field_index == -1: raise ValueError(f"Field '{field}' not found") - - try: - value = cast_to_value(field, value) - except ValueError as e: - return error(str(e), 400) - - rows = db.delete_by(field, cast(Value, value)) - rows = [[value_to_str(v) for v in row] for row in rows] - - htmp_target = request.form.get("htmp") or request.args.get("htmp") - if htmp_target: - return render_template("frag_results.html.j2", - field_names = ["id", "name"], - rows = rows, - ) - else: - return render_template('page_main.html.j2', - selected_form = "frag_form_delete.html.j2", - field_names = ["id", "name"], - rows = rows, - ) - - -# Utils - -def cast_to_value(field: str, str_value: str) -> Value | None: - str_value = str_value.strip() - if str_value == "": return None - if str_value[0] == "\"": - if str_value[-1] != "\"": raise ValueError(f"Invalid string: {str_value}") - str_value = str_value[1:-1] - - field_index = db_fields.index(field) - if field_index == -1: raise ValueError(f"Field '{field}' not found") - - type = db_types[field_index] - - match type: - case "int": return Value.int(int(str_value)) - case "string": return Value.string(str_value) - case _: raise ValueError(f"Unsupported type: {type}") - -def value_to_str(value: Value) -> str: - match value.kind(): - case log_db.VALUE_INT: return f"{str(value.as_int())} (int)" - case log_db.VALUE_STRING: return f"\"{value.as_string()}\" (string)" - case log_db.VALUE_DECIMAL: return f"{value.as_decimal()} (decimal)" - case log_db.VALUE_BYTES: return f"{value.as_bytes()} (bytes)" - case log_db.VALUE_NULL: return "null" - case _: raise ValueError(f"Unsupported value kind: {value.kind()}") diff --git a/browser/requirements.txt b/browser/requirements.txt deleted file mode 100644 index b6b9bff..0000000 --- a/browser/requirements.txt +++ /dev/null @@ -1,11 +0,0 @@ -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 differdeleted file mode 100644 index 3a30e79..0000000 --- a/browser/static/InclusiveSans-Regular.ttf +++ /dev/null diff --git a/browser/static/htmp.js b/browser/static/htmp.js deleted file mode 100644 index 27d963b..0000000 --- a/browser/static/htmp.js +++ /dev/null @@ -1,38 +0,0 @@ -const iframe = document.createElement("iframe"); -iframe.name = "htmp"; -iframe.hidden = true; -iframe.addEventListener("load", function () { - setTimeout(() => { - const cd = this.contentDocument; - const cw = this.contentWindow; - // If the server responds with an entire HTML document, replace the current document with it. - // To check whether the response is an entire page we check the presence of this very snippet. - if (cd.querySelector("iframe[name='htmp']")) { - document.documentElement.replaceWith(cd.documentElement); - // If the server responds with a fragment, replace the target element with it. - } else { - document - .querySelector(cw.location.hash || null) - ?.replaceWith(...cd.body.childNodes); - const url = new URL(cw.location.href); - url.searchParams.delete("htmp"); - url.hash = ""; - history.replaceState({}, "", url.toString()); - } - }); -}); -document.body.appendChild(iframe); - -document.querySelectorAll("form[htmp]").forEach(function (el) { - const r = el.attributes.replace.value; - const input = document.createElement("input"); - input.type = "hidden"; - input.name = "htmp"; - input.value = r; - el.appendChild(input); - - const action = new URL(el.action); - action.hash = r; - el.action = action.toString(); - el.target = "htmp"; -}); diff --git a/browser/static/styles.css b/browser/static/styles.css deleted file mode 100644 index 82bed74..0000000 --- a/browser/static/styles.css +++ /dev/null @@ -1,140 +0,0 @@ -: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; -} - -#container { - 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.link, -input[type="submit"].link { - background: none; - color: var(--blue); - padding: 0; - margin: 0; - text-decoration: underline; - font-family: "Inclusive Sans"; -} - -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); -} - -aside { - width: 250px; -} - -main { - 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/base.html.j2 b/browser/templates/base.html.j2 deleted file mode 100644 index 47b3f6c..0000000 --- a/browser/templates/base.html.j2 +++ /dev/null @@ -1,19 +0,0 @@ -<!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 %} -</head> -<body> - <h1><a href="/">Database browser</a></h1> - {% block content %}{% endblock %} - - <script src="/static/htmp.js"></script> -</body> -</html> diff --git a/browser/templates/frag_aside.html.j2 b/browser/templates/frag_aside.html.j2 deleted file mode 100644 index 6c5658f..0000000 --- a/browser/templates/frag_aside.html.j2 +++ /dev/null @@ -1,27 +0,0 @@ -{# -Params - selected_form Form fragment to include -#} - -<aside id="aside"> - <h3>Read operations</h3> - <ul> - {% with href="/find", text="Find" %} - <li>{% include "frag_aside_link.html.j2" %}</li> - {% endwith %} - {% with href="/range", text="Range" %} - <li>{% include "frag_aside_link.html.j2" %}</li> - {% endwith %} - </ul> - <h3>Write operations</h3> - <ul> - {% with href="/upsert", text="Upsert" %} - <li>{% include "frag_aside_link.html.j2" %}</li> - {% endwith %} - {% with href="/delete", text="Delete" %} - <li>{% include "frag_aside_link.html.j2" %}</li> - {% endwith %} - </ul> - - {% include selected_form %} -</aside> diff --git a/browser/templates/frag_aside_link.html.j2 b/browser/templates/frag_aside_link.html.j2 deleted file mode 100644 index 9cd6041..0000000 --- a/browser/templates/frag_aside_link.html.j2 +++ /dev/null @@ -1,9 +0,0 @@ -{# -Params - text Text to render in the link - href URL to link to -#} - -<form method="GET" action="{{ href }}" htmp replace=form_query> - <button class="link" type="submit">{{ text }}</button> -</form> diff --git a/browser/templates/frag_error.html.j2 b/browser/templates/frag_error.html.j2 deleted file mode 100644 index c5034cd..0000000 --- a/browser/templates/frag_error.html.j2 +++ /dev/null @@ -1,16 +0,0 @@ -{# -Params - error Error message to display - container (optional) id for a div to wrap the error message in -#} - -{% if container %} -<div id="{{ container }}"> -{% endif %} - -<h2>Error</h2> -{{ error }} - -{% if container %} -</div> -{% endif %} diff --git a/browser/templates/frag_form_delete.html.j2 b/browser/templates/frag_form_delete.html.j2 deleted file mode 100644 index 22306c1..0000000 --- a/browser/templates/frag_form_delete.html.j2 +++ /dev/null @@ -1,7 +0,0 @@ -<form id="form_query" action="/delete" method="post" htmp replace=results> - <label for="field">Field</label> - <input type="text" name="field" id="field" required> - <label for="value">Value</label> - <input type="text" name="value" id="value" required> - <button type="submit">Run query</button> -</form> diff --git a/browser/templates/frag_form_find.html.j2 b/browser/templates/frag_form_find.html.j2 deleted file mode 100644 index cadd44b..0000000 --- a/browser/templates/frag_form_find.html.j2 +++ /dev/null @@ -1,7 +0,0 @@ -<form id="form_query" action="/find" method="post" htmp replace=results> - <label for="field">Field</label> - <input type="text" name="field" id="field" required> - <label for="values">Values (one per line)</label> - <textarea name="values" id="values" required></textarea> - <button type="submit">Run query</button> -</form> diff --git a/browser/templates/frag_form_range.html.j2 b/browser/templates/frag_form_range.html.j2 deleted file mode 100644 index 84b8024..0000000 --- a/browser/templates/frag_form_range.html.j2 +++ /dev/null @@ -1,21 +0,0 @@ -<form id="form_query" action="/range" method="post" htmp replace=results> - <label for="field">Field</label> - <input type="text" name="field" id="field" required> - <label for="from_type">Lower bound type</label> - <select id="from_type" name="from_type" required> - <option value="unbounded">Unbounded</option> - <option value="included">Inclusive</option> - <option value="excluded">Exclusive</option> - </select> - <label for="values">Lower bound value</label> - <input type="text" name="from_value" id="from_value"> - <label for="to_type">Upper bound type</label> - <select id="to_type" name="to_type" required> - <option value="unbounded">Unbounded</option> - <option value="included">Inclusive</option> - <option value="excluded">Exclusive</option> - </select> - <label for="values">Upper bound value</label> - <input type="text" name="to_value" id="to_value"> - <button type="submit">Run query</button> -</form> diff --git a/browser/templates/frag_form_upsert.html.j2 b/browser/templates/frag_form_upsert.html.j2 deleted file mode 100644 index 68820f5..0000000 --- a/browser/templates/frag_form_upsert.html.j2 +++ /dev/null @@ -1,5 +0,0 @@ -<form id="form_query" action="/upsert" method="post" htmp replace=results> - <label for="values">Values (one per line)</label> - <textarea name="values" id="values" required></textarea> - <button type="submit">Run query</button> -</form> diff --git a/browser/templates/frag_results.html.j2 b/browser/templates/frag_results.html.j2 deleted file mode 100644 index bfb54e9..0000000 --- a/browser/templates/frag_results.html.j2 +++ /dev/null @@ -1,18 +0,0 @@ -<table id="results"> -<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> diff --git a/browser/templates/page_error.html.j2 b/browser/templates/page_error.html.j2 deleted file mode 100644 index 3233283..0000000 --- a/browser/templates/page_error.html.j2 +++ /dev/null @@ -1,5 +0,0 @@ -{% extends "base.html.j2" %} - -{% block content %} -{% include "frag_error.html.j2" %} -{% endblock %} diff --git a/browser/templates/page_main.html.j2 b/browser/templates/page_main.html.j2 deleted file mode 100644 index 907275f..0000000 --- a/browser/templates/page_main.html.j2 +++ /dev/null @@ -1,11 +0,0 @@ -{% extends "base.html.j2" %} - -{% block content %} -<div id="container"> - {% include "frag_aside.html.j2" %} - - <main id="main"> - {% include "frag_results.html.j2" %} - </main> -</div> -{% endblock %} |
