From b18ab1103da578061c7556d216594bab0dae7f88 Mon Sep 17 00:00:00 2001 From: Kayos Date: Tue, 28 Apr 2026 20:08:01 -0700 Subject: [PATCH] ui: /me is a real page now, not raw JSON MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - New ME_TEMPLATE — palette-locked, shows user identity + Mealie connection status + connect/disconnect actions + sign out - /me.json kept for programmatic callers - Extracted _PALETTE_CSS shared between /me and /connect-mealie templates (forest #1f2d1f bg, panels #2d3a2a, meadow accents #6b8e5a/#88a87a, parchment text #f0e6cc/#ddd4ba, Cormorant Garamond serif headers) - /me also fetches the Mealie /api/users/self for the connected user so the page can show 'logged in as , admin: yes/no' - Connect page polished with cancel button + autocomplete=off on the token input Strict palette: no purple, no neon. As locked. --- cauldron/server.py | 142 ++++++++++++++++++++++++++++++++++++--------- 1 file changed, 114 insertions(+), 28 deletions(-) diff --git a/cauldron/server.py b/cauldron/server.py index aa48169..105b46b 100644 --- a/cauldron/server.py +++ b/cauldron/server.py @@ -160,13 +160,25 @@ def create_app() -> Flask: def me(): u = session["user"] connected = db.get_user_mealie_token_blob(u["sub"]) is not None - return jsonify( - { - "user": u, - "mealie_connected": connected, - } + mealie_user = None + if connected: + client = current_user_mealie() + if client: + try: + mealie_user = client.who_am_i() + except Exception: + mealie_user = None + return render_template_string( + ME_TEMPLATE, user=u, connected=connected, mealie_user=mealie_user, css=_PALETTE_CSS ) + @app.get("/me.json") + @require_session + def me_json(): + u = session["user"] + connected = db.get_user_mealie_token_blob(u["sub"]) is not None + return jsonify({"user": u, "mealie_connected": connected}) + # ---------- mealie connect flow -------------------------------------- @app.get("/connect-mealie") @@ -177,6 +189,7 @@ def create_app() -> Flask: CONNECT_TEMPLATE, user=u, mealie_url=cfg.mealie_base_url, + css=_PALETTE_CSS, ) @app.post("/connect-mealie") @@ -234,36 +247,109 @@ def create_app() -> Flask: return app +_PALETTE_CSS = """ +@import url('https://fonts.googleapis.com/css2?family=Cormorant+Garamond:wght@400;500;600&display=swap'); +* { box-sizing: border-box; } +body { font-family: system-ui, -apple-system, sans-serif; background: #1f2d1f; color: #f0e6cc; max-width: 720px; margin: 0 auto; padding: 3em 1.5em; line-height: 1.65; } +h1, h2 { font-family: 'Cormorant Garamond', Georgia, serif; font-weight: 500; color: #88a87a; margin: 0 0 .2em 0; letter-spacing: 0.01em; } +h1 { font-size: 2.6em; } +h2 { font-size: 1.6em; color: #6b8e5a; margin-top: 1.6em; } +.lede { color: #ddd4ba; font-size: 1.05em; } +a { color: #88a87a; text-decoration: none; border-bottom: 1px dotted #4d5d3a; } +a:hover { color: #ddd4ba; border-bottom-color: #88a87a; } +.panel { background: #2d3a2a; border: 1px solid #3a4a35; padding: 1.2em 1.4em; margin: 1.4em 0; border-radius: 3px; } +.panel-row { display: flex; justify-content: space-between; align-items: baseline; gap: 1em; } +.muted { color: #6b8e5a; font-size: .9em; } +.kv { display: grid; grid-template-columns: max-content 1fr; gap: .4em 1em; margin: .8em 0; } +.kv dt { color: #6b8e5a; font-size: .9em; } +.kv dd { margin: 0; color: #f0e6cc; font-family: ui-monospace, Menlo, monospace; font-size: .92em; word-break: break-all; } +.btn { display: inline-block; padding: .55em 1.3em; background: #6b8e5a; color: #1f2d1f !important; border: none; font-weight: 600; cursor: pointer; font-size: .95em; border-bottom: none; } +.btn:hover { background: #88a87a; color: #1f2d1f !important; } +.btn-secondary { background: transparent; color: #88a87a !important; border: 1px solid #4d5d3a; } +.btn-secondary:hover { background: #2d3a2a; color: #ddd4ba !important; border-color: #88a87a; } +.btn-row { display: flex; gap: .8em; flex-wrap: wrap; align-items: center; } +input[type=text] { width: 100%; padding: .65em; background: #2d3a2a; border: 1px solid #4d5d3a; color: #f0e6cc; font-family: ui-monospace, Menlo, monospace; font-size: .92em; } +input[type=text]:focus { outline: none; border-color: #88a87a; } +ol li, ul li { margin: .4em 0; } +code { background: #1f2d1f; padding: .1em .45em; border: 1px solid #3a4a35; font-family: ui-monospace, Menlo, monospace; font-size: .9em; } +.status-ok { color: #88a87a; } +.status-warn { color: #c9b27c; } +.brand { color: #6b8e5a; font-family: 'Cormorant Garamond', serif; font-size: .95em; letter-spacing: .15em; text-transform: uppercase; margin-bottom: .2em; } +hr { border: none; border-top: 1px solid #3a4a35; margin: 2em 0; } +""" + + +ME_TEMPLATE = """ +Cauldron + + +
Cauldron
+

{{ user.name or user.email }}

+

Welcome back.

+ +
+

Your account

+
+
email
{{ user.email }}
+
subject
{{ user.sub }}
+
+
+ +
+
+

Mealie

+ {% if connected %}connected{% else %}not connected{% endif %} +
+ {% if connected and mealie_user %} +
+
logged in as
{{ mealie_user.username or mealie_user.email }}
+
full name
{{ mealie_user.fullName or '—' }}
+
admin
{{ 'yes' if mealie_user.admin else 'no' }}
+
+
+ + Revoking on Mealie's side also works. +
+ {% else %} +

Connect your Mealie account so cauldron can act on your behalf — your recipes, your meal plan, your shopping list.

+

Connect Mealie

+ {% endif %} +
+ +
+
+ +
+ +""" + + CONNECT_TEMPLATE = """ Connect Mealie — Cauldron - + +
Cauldron

Connect your Mealie

-

Hi {{ user.name or user.email }}. Cauldron acts on your Mealie account using your own API token. One-time setup, ~30 seconds.

+

Hi {{ user.name or user.email }}. Cauldron acts on Mealie using your own API token. One-time, ~30 seconds.

-
    -
  1. Open Mealie → API Tokens
  2. -
  3. Click New API Token, name it cauldron, integration generic
  4. -
  5. Copy the long token string
  6. -
  7. Paste below and click Connect
  8. -
+
+
    +
  1. Open Mealie → API Tokens
  2. +
  3. Click New API Token, name it cauldron, integration generic
  4. +
  5. Copy the long token string
  6. +
  7. Paste below and click Connect
  8. +
-
-

-

-
+
+

+
+ + Cancel +
+
+
-

Stored encrypted at rest. Revoke anytime in Mealie's UI; cauldron will detect and re-prompt.

+

Stored encrypted at rest with a Fernet key that lives only in cauldron's env. Revoke anytime in Mealie's UI — cauldron will detect and re-prompt.

"""