网页内docker版drawio内嵌和xml生成

This commit is contained in:
ljz committed 2026-07-07 16:30:09 +08:00
1 parent f73806cfb0
commit 6b7be41e02
2036 files changed
+373597 -63

No files matched your search

+141 -4
View File
@@ -3,10 +3,12 @@ from __future__ import annotations
import base64
import json
import os
import urllib.error
import urllib.request
import zlib
from pathlib import Path
from typing import Iterable
from urllib.parse import unquote
from urllib.parse import unquote, urljoin
from xml.etree import ElementTree as ET
from fastapi import FastAPI, HTTPException, Request, Response
@@ -16,6 +18,54 @@ from pydantic import BaseModel
app = FastAPI(title="System Simulation App")
INDEX_TEMPLATE = Path(__file__).parent / "static" / "index.html"
DRAWIO_UPSTREAM_URL = (
os.getenv("DRAWIO_UPSTREAM_URL")
or os.getenv("DRAWIO_BASE_URL")
or "http://127.0.0.1:8081/"
)
COMPONENT_LIBRARY = [
{
"title": "\u8d2e\u7bb1",
"w": 80,
"h": 80,
"xml": (
'<mxGraphModel><root><mxCell id="0"/><mxCell id="1" parent="0"/>'
'<object id="tank" label="\u8d2e\u7bb1" componentType="tank" '
'medium="N2" volume="1.0"><mxCell style="shape=cylinder;'
"whiteSpace=wrap;html=1;boundedLbl=1;backgroundOutline=1;"
'size=15;fillColor=#dae8fc;strokeColor=#6c8ebf;" vertex="1" '
'parent="1"><mxGeometry width="80" height="80" as="geometry"/>'
"</mxCell></object></root></mxGraphModel>"
),
},
{
"title": "\u9600\u95e8",
"w": 72,
"h": 56,
"xml": (
'<mxGraphModel><root><mxCell id="0"/><mxCell id="1" parent="0"/>'
'<object id="valve" label="\u9600\u95e8" componentType="valve" '
'valveType="manual" nominalDiameter="10"><mxCell style="rhombus;'
'whiteSpace=wrap;html=1;fillColor=#ffe6cc;strokeColor=#d79b00;" '
'vertex="1" parent="1"><mxGeometry width="72" height="56" '
'as="geometry"/></mxCell></object></root></mxGraphModel>'
),
},
{
"title": "\u7ba1\u6bb5",
"w": 96,
"h": 36,
"xml": (
'<mxGraphModel><root><mxCell id="0"/><mxCell id="1" parent="0"/>'
'<object id="pipe" label="\u7ba1\u6bb5" componentType="pipe" '
'length="1.0" diameter="0.01"><mxCell style="rounded=1;'
"whiteSpace=wrap;html=1;arcSize=12;fillColor=#d5e8d4;"
'strokeColor=#82b366;" vertex="1" parent="1"><mxGeometry '
'width="96" height="36" as="geometry"/></mxCell></object>'
"</root></mxGraphModel>"
),
},
]
class DiagramPayload(BaseModel):
@@ -25,9 +75,68 @@ class DiagramPayload(BaseModel):
@app.get("/")
def index(request: Request) -> HTMLResponse:
html = INDEX_TEMPLATE.read_text(encoding="utf-8")
drawio_base_url = os.getenv("DRAWIO_BASE_URL") or default_drawio_base_url(request)
drawio_base_url = default_drawio_base_url(request)
html = html.replace("__DRAWIO_BASE_URL__", json.dumps(drawio_base_url))
return HTMLResponse(html)
return HTMLResponse(html, headers={"Cache-Control": "no-store"})
@app.get("/api/component-library.drawiolib")
def component_library() -> Response:
library = ET.Element("mxlibrary", {"title": "System Components"})
library.text = json.dumps(COMPONENT_LIBRARY, ensure_ascii=False)
return Response(
content=ET.tostring(library, encoding="utf-8"),
media_type="application/xml",
headers={
"Access-Control-Allow-Origin": "*",
"Cache-Control": "no-store",
},
)
@app.api_route("/drawio", methods=["GET", "POST", "HEAD", "OPTIONS"])
@app.api_route("/drawio/{path:path}", methods=["GET", "POST", "HEAD", "OPTIONS"])
async def drawio_proxy(request: Request, path: str = "") -> Response:
upstream_url = build_drawio_upstream_url(path, request.url.query)
body = await request.body()
headers = {
key: value
for key, value in request.headers.items()
if key.lower()
not in {
"host",
"connection",
"content-length",
"accept-encoding",
}
}
upstream_request = urllib.request.Request(
upstream_url,
data=body if body else None,
headers=headers,
method=request.method,
)
try:
with urllib.request.urlopen(upstream_request, timeout=30) as upstream_response:
content = upstream_response.read()
return Response(
content=content,
status_code=upstream_response.status,
headers=filter_proxy_headers(upstream_response.headers),
)
except urllib.error.HTTPError as exc:
return Response(
content=exc.read(),
status_code=exc.code,
headers=filter_proxy_headers(exc.headers),
)
except urllib.error.URLError as exc:
raise HTTPException(
status_code=502,
detail=f"Unable to reach draw.io upstream: {exc.reason}",
) from exc
@app.post("/api/system-xml")
@@ -46,7 +155,35 @@ def default_drawio_base_url(request: Request) -> str:
if ":" in hostname and not hostname.startswith("["):
hostname = f"[{hostname}]"
return f"{request.url.scheme}://{hostname}:8080/"
port = f":{request.url.port}" if request.url.port else ""
return f"{request.url.scheme}://{hostname}{port}/drawio/"
def build_drawio_upstream_url(path: str, query: str) -> str:
target = urljoin(DRAWIO_UPSTREAM_URL, path)
if query:
target = f"{target}?{query}"
return target
def filter_proxy_headers(headers) -> dict[str, str]:
excluded = {
"connection",
"content-encoding",
"content-length",
"keep-alive",
"proxy-authenticate",
"proxy-authorization",
"te",
"trailer",
"transfer-encoding",
"upgrade",
}
return {
key: value
for key, value in headers.items()
if key.lower() not in excluded
}
def read_graph_model(xml_text: str) -> ET.Element: