sap-drawio: add 'Sketch to SAP BTP' custom-store app (FastAPI+React, single-port, seed-on-first-run SQLite, exposed via auto-SSO)
This commit is contained in:
@@ -0,0 +1,52 @@
|
|||||||
|
# sap-drawio — Build Files
|
||||||
|
|
||||||
|
Image source repo (Dockerfile lives there, NOT in this store folder):
|
||||||
|
`~/projects/sap-architecture-diagrams` (git remote `git.alexzaw.dev/alexz/sap-architecture-diagrams`).
|
||||||
|
|
||||||
|
## What the image is
|
||||||
|
|
||||||
|
A single multi-stage image, `git.alexzaw.dev/alexz/sap-drawio:latest`:
|
||||||
|
|
||||||
|
1. **Builder stage** (`node:20-bookworm-slim`) — builds the React (CRA + CRACO,
|
||||||
|
Yarn Classic 1.22.22) frontend with `REACT_APP_BACKEND_URL=""` so all API
|
||||||
|
calls are same-origin (`/api`), working from any host/domain.
|
||||||
|
2. **Runtime stage** (`python:3.12-slim`) — installs `backend/requirements.txt`,
|
||||||
|
copies `backend/` to `/app/backend` and the built UI to `/app/frontend/build`
|
||||||
|
(FastAPI/uvicorn serves both from one process on port 8011), bakes the
|
||||||
|
12 MB SQLite catalog seed to `/seed/app.db`, and runs as root (homelab —
|
||||||
|
avoids the non-root volume-permission trap).
|
||||||
|
|
||||||
|
## How to build
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd /etc/runtipi/repos/runtipi/apps/sap-drawio/build
|
||||||
|
./build.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
Builds and pushes `git.alexzaw.dev/alexz/sap-drawio:latest`. Override
|
||||||
|
`IMAGE_NAME` / `IMAGE_TAG` / `SRC` env vars if needed. To build without
|
||||||
|
pushing, run the `docker build` line directly from `$SRC`.
|
||||||
|
|
||||||
|
## Seed-on-first-run behavior
|
||||||
|
|
||||||
|
`docker/entrypoint.sh` (in the source repo) copies `/seed/app.db` →
|
||||||
|
`/data/app.db` only if `/data/app.db` is absent, then execs uvicorn. This
|
||||||
|
means:
|
||||||
|
- First install: the persistent volume (`${APP_DATA_DIR}/data` on the host)
|
||||||
|
gets the baked catalog (icons/services) as its starting point.
|
||||||
|
- Every subsequent `app update`/restart: the existing `/data/app.db` (with any
|
||||||
|
saved conversions, added logos, etc.) is left untouched — the image rebuild
|
||||||
|
never overwrites live data.
|
||||||
|
|
||||||
|
## LLM dependency
|
||||||
|
|
||||||
|
Calls an Ollama-compatible host at `OLLAMA_URL` (form field, defaults to
|
||||||
|
`http://192.168.0.32:11434`, this homeserver's local `ollama-nvidia` app).
|
||||||
|
Models (`VISION_MODEL`, `VALIDATOR_MODEL`) are also form fields so they can be
|
||||||
|
changed without a rebuild. `qwen3-vl:8b` / `gemma4:31b` must exist on the
|
||||||
|
Ollama host for the defaults to work.
|
||||||
|
|
||||||
|
## Architecture note
|
||||||
|
|
||||||
|
**amd64 only.** No multi-arch build — matches the host architecture; not
|
||||||
|
needed for this homelab deployment.
|
||||||
@@ -0,0 +1,80 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
set -euo pipefail
|
||||||
|
# Builds (and, unless SKIP_PUSH=1, pushes) the sap-drawio image from the app
|
||||||
|
# source repo. Both the floating "latest" tag and the version-pinned tag
|
||||||
|
# (kept in sync with config.json "version") are built and pushed.
|
||||||
|
#
|
||||||
|
# Before building, this script (re)generates a deterministic, catalog-only
|
||||||
|
# seed DB (backend/data/seed.db) from the developer's live app.db. This is
|
||||||
|
# REQUIRED — the live app.db is WAL-mode with uncommitted -wal data and a
|
||||||
|
# full conversion/history table, neither of which may ever ship in the image
|
||||||
|
# (see sap-drawio Phase-1 code review FIX B1 / M2). The live app.db is never
|
||||||
|
# opened directly by this script; it is only ever `cp`-copied at the OS
|
||||||
|
# level, so it cannot be mutated by an incidental SQLite WAL checkpoint.
|
||||||
|
#
|
||||||
|
# Env overrides:
|
||||||
|
# IMAGE_NAME default git.alexzaw.dev/alexz/sap-drawio
|
||||||
|
# IMAGE_TAG default latest
|
||||||
|
# VERSION_TAG default 1.0.0 (must match config.json "version")
|
||||||
|
# SRC default ~/projects/sap-architecture-diagrams
|
||||||
|
# SKIP_PUSH set to 1 to build both tags locally only (no push) — used
|
||||||
|
# for pre-push smoke-testing.
|
||||||
|
|
||||||
|
IMAGE_NAME="${IMAGE_NAME:-git.alexzaw.dev/alexz/sap-drawio}"
|
||||||
|
IMAGE_TAG="${IMAGE_TAG:-latest}"
|
||||||
|
VERSION_TAG="${VERSION_TAG:-1.0.0}"
|
||||||
|
SRC="${SRC:-$HOME/projects/sap-architecture-diagrams}"
|
||||||
|
SKIP_PUSH="${SKIP_PUSH:-0}"
|
||||||
|
|
||||||
|
cd "$SRC"
|
||||||
|
|
||||||
|
echo "== [1/3] Generating deterministic catalog-only seed DB =="
|
||||||
|
SEED_WORK="$(mktemp -d)"
|
||||||
|
trap 'rm -rf "$SEED_WORK"' EXIT
|
||||||
|
|
||||||
|
# Copy the live dev DB (main + WAL + SHM, if present) at the OS level ONLY.
|
||||||
|
# backend/data/app.db is NEVER opened directly by this script.
|
||||||
|
cp backend/data/app.db "$SEED_WORK/app.db"
|
||||||
|
[ -f backend/data/app.db-wal ] && cp backend/data/app.db-wal "$SEED_WORK/app.db-wal"
|
||||||
|
[ -f backend/data/app.db-shm ] && cp backend/data/app.db-shm "$SEED_WORK/app.db-shm"
|
||||||
|
|
||||||
|
# .backup against the COPY (never the original) merges any committed WAL
|
||||||
|
# frames into a single consistent snapshot file.
|
||||||
|
sqlite3 "$SEED_WORK/app.db" ".backup '$SEED_WORK/seed.db'"
|
||||||
|
|
||||||
|
# Clear ONLY the conversion/history table (db.py: insert_conversion() writes
|
||||||
|
# to `conversions`). Every catalog/icon/service/brand/override row is kept.
|
||||||
|
sqlite3 "$SEED_WORK/seed.db" "DELETE FROM conversions;"
|
||||||
|
sqlite3 "$SEED_WORK/seed.db" "VACUUM;"
|
||||||
|
|
||||||
|
echo "-- Catalog row counts (source vs seed) --"
|
||||||
|
for t in icon_assets sap_services label_overrides schema_meta; do
|
||||||
|
src_count=$(sqlite3 "$SEED_WORK/app.db" "SELECT COUNT(*) FROM $t;")
|
||||||
|
seed_count=$(sqlite3 "$SEED_WORK/seed.db" "SELECT COUNT(*) FROM $t;")
|
||||||
|
printf ' %-16s source=%-8s seed=%-8s\n' "$t" "$src_count" "$seed_count"
|
||||||
|
if [ "$src_count" != "$seed_count" ]; then
|
||||||
|
echo "ERROR: catalog table $t row count mismatch (source=$src_count seed=$seed_count)" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
conv_count=$(sqlite3 "$SEED_WORK/seed.db" "SELECT COUNT(*) FROM conversions;")
|
||||||
|
echo " conversions seed=$conv_count (must be 0)"
|
||||||
|
if [ "$conv_count" != "0" ]; then
|
||||||
|
echo "ERROR: seed.db still has $conv_count conversion/history rows" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
cp "$SEED_WORK/seed.db" backend/data/seed.db
|
||||||
|
echo "Seed written to backend/data/seed.db ($(du -h backend/data/seed.db | cut -f1))"
|
||||||
|
|
||||||
|
echo "== [2/3] Building image (tags: ${IMAGE_TAG}, ${VERSION_TAG}) =="
|
||||||
|
sudo docker build "$@" -t "${IMAGE_NAME}:${IMAGE_TAG}" -t "${IMAGE_NAME}:${VERSION_TAG}" .
|
||||||
|
|
||||||
|
if [ "$SKIP_PUSH" = "1" ]; then
|
||||||
|
echo "== [3/3] SKIP_PUSH=1 — not pushing. Built locally: ${IMAGE_NAME}:${IMAGE_TAG}, ${IMAGE_NAME}:${VERSION_TAG} =="
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "== [3/3] Pushing image =="
|
||||||
|
sudo docker push "${IMAGE_NAME}:${IMAGE_TAG}"
|
||||||
|
sudo docker push "${IMAGE_NAME}:${VERSION_TAG}"
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
{
|
||||||
|
"name": "Sketch to SAP BTP",
|
||||||
|
"id": "sap-drawio",
|
||||||
|
"available": true,
|
||||||
|
"short_desc": "Turn a hand-drawn or Mermaid sketch into a polished SAP BTP draw.io diagram.",
|
||||||
|
"author": "alexz",
|
||||||
|
"port": 8011,
|
||||||
|
"categories": ["development", "utilities"],
|
||||||
|
"tipi_version": 1,
|
||||||
|
"version": "1.0.0",
|
||||||
|
"source": "https://git.alexzaw.dev/alexz/sap-architecture-diagrams",
|
||||||
|
"website": "https://git.alexzaw.dev/alexz/sap-architecture-diagrams",
|
||||||
|
"exposable": true,
|
||||||
|
"dynamic_config": true,
|
||||||
|
"supported_architectures": ["amd64"],
|
||||||
|
"min_tipi_version": "4.0.0",
|
||||||
|
"form_fields": [
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"label": "Ollama URL",
|
||||||
|
"hint": "Ollama-compatible LLM host",
|
||||||
|
"required": true,
|
||||||
|
"env_variable": "OLLAMA_URL",
|
||||||
|
"default": "http://192.168.0.32:11434"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"label": "Vision model",
|
||||||
|
"required": true,
|
||||||
|
"env_variable": "VISION_MODEL",
|
||||||
|
"default": "qwen3-vl:8b"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"label": "Validator model",
|
||||||
|
"required": true,
|
||||||
|
"env_variable": "VALIDATOR_MODEL",
|
||||||
|
"default": "gemma4:31b"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "password",
|
||||||
|
"label": "Brandfetch API key (optional)",
|
||||||
|
"required": false,
|
||||||
|
"env_variable": "BRANDFETCH_API_KEY"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"created_at": 1784733792011,
|
||||||
|
"updated_at": 1784733792011
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://schemas.runtipi.io/dynamic-compose.json",
|
||||||
|
"schemaVersion": 2,
|
||||||
|
"services": [
|
||||||
|
{
|
||||||
|
"name": "sap-drawio",
|
||||||
|
"image": "git.alexzaw.dev/alexz/sap-drawio:latest",
|
||||||
|
"isMain": true,
|
||||||
|
"internalPort": 8011,
|
||||||
|
"environment": [
|
||||||
|
{ "key": "TZ", "value": "${TZ}" },
|
||||||
|
{ "key": "OLLAMA_URL", "value": "${OLLAMA_URL}" },
|
||||||
|
{ "key": "VISION_MODEL", "value": "${VISION_MODEL}" },
|
||||||
|
{ "key": "VALIDATOR_MODEL", "value": "${VALIDATOR_MODEL}" },
|
||||||
|
{ "key": "BRANDFETCH_API_KEY", "value": "${BRANDFETCH_API_KEY}" },
|
||||||
|
{ "key": "SQLITE_DB_PATH", "value": "/data/app.db" },
|
||||||
|
{ "key": "CORS_ORIGINS", "value": "https://sap-drawio.alexzaw.dev" }
|
||||||
|
],
|
||||||
|
"volumes": [
|
||||||
|
{ "hostPath": "${APP_DATA_DIR}/data", "containerPath": "/data", "readOnly": false }
|
||||||
|
],
|
||||||
|
"healthCheck": {
|
||||||
|
"test": "python -c \"import urllib.request,sys; sys.exit(0 if urllib.request.urlopen('http://127.0.0.1:8011/api/health', timeout=5).status==200 else 1)\"",
|
||||||
|
"interval": "30s",
|
||||||
|
"timeout": "10s",
|
||||||
|
"retries": 3,
|
||||||
|
"startPeriod": "40s"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
# Sketch to SAP BTP
|
||||||
|
|
||||||
|
Turn a rough architecture idea into a polished **SAP BTP architecture diagram**
|
||||||
|
in seconds. Feed it either a hand-drawn/whiteboard **sketch (image)** or a
|
||||||
|
**Mermaid diagram**, and it produces a clean draw.io XML diagram (plus an
|
||||||
|
inline SVG preview) styled in SAP's official BTP solution-diagram visual
|
||||||
|
language — complete with an LLM-generated architecture review calling out
|
||||||
|
gaps, risks, and suggestions.
|
||||||
|
|
||||||
|
## Two ways in
|
||||||
|
|
||||||
|
- **Image sketch** — upload a photo or screenshot of a whiteboard sketch; the
|
||||||
|
vision model reads the shapes, labels, and connections and maps them to the
|
||||||
|
matching SAP BTP services/icons.
|
||||||
|
- **Mermaid** — paste a Mermaid diagram and get the same SAP BTP-styled
|
||||||
|
draw.io output without needing to draw anything by hand.
|
||||||
|
|
||||||
|
Every conversion is saved to a local history so past diagrams and their
|
||||||
|
review notes can be revisited later, exported again, or refined further.
|
||||||
|
|
||||||
|
## Access note
|
||||||
|
|
||||||
|
This app has **no built-in authentication** of its own — access control is
|
||||||
|
handled at the edge (Authentik SSO via the homeserver's forward-auth gate).
|
||||||
|
Anyone who reaches the app through the gate can drive the full pipeline.
|
||||||
|
|
||||||
|
## Dependencies
|
||||||
|
|
||||||
|
Requires a reachable Ollama-compatible LLM endpoint (configured via the
|
||||||
|
"Ollama URL" field) with the configured vision and validator models
|
||||||
|
available. An optional Brandfetch API key enables looking up third-party
|
||||||
|
brand logos for non-SAP components in a diagram.
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 20 KiB |
Reference in New Issue
Block a user