aboutsummaryrefslogtreecommitdiffstats
path: root/browser
diff options
context:
space:
mode:
authorJan Tuomi <jan@jantuomi.fi>2025-05-02 16:37:29 +0300
committerJan Tuomi <jan@jantuomi.fi>2025-05-02 16:37:29 +0300
commitef72b74ec34e95a38890d542cda298dd6565d5ad (patch)
tree63fe96b24a8534e8dd2d88b1ca3d69b21c502d3e /browser
parentdaca0e6b68d32d885face00d68df5b5cb00e098b (diff)
Start replacing browser with a chat demo
Diffstat (limited to 'browser')
-rw-r--r--browser/app.py242
-rw-r--r--browser/requirements.txt11
-rw-r--r--browser/static/InclusiveSans-Regular.ttfbin57124 -> 0 bytes
-rw-r--r--browser/static/htmp.js38
-rw-r--r--browser/static/styles.css140
-rw-r--r--browser/templates/base.html.j219
-rw-r--r--browser/templates/frag_aside.html.j227
-rw-r--r--browser/templates/frag_aside_link.html.j29
-rw-r--r--browser/templates/frag_error.html.j216
-rw-r--r--browser/templates/frag_form_delete.html.j27
-rw-r--r--browser/templates/frag_form_find.html.j27
-rw-r--r--browser/templates/frag_form_range.html.j221
-rw-r--r--browser/templates/frag_form_upsert.html.j25
-rw-r--r--browser/templates/frag_results.html.j218
-rw-r--r--browser/templates/page_error.html.j25
-rw-r--r--browser/templates/page_main.html.j211
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
deleted file mode 100644
index 3a30e79..0000000
--- a/browser/static/InclusiveSans-Regular.ttf
+++ /dev/null
Binary files differ
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 %}