Illustrate local, self-hosted and platform object mounts
Verify and deploy Tabularium / verify-and-deploy (push) Successful in 5m25s

This commit is contained in:
2026-10-27 23:40:18 +08:00
parent 9fcfbdb694
commit 2bfdc2d35a
7 changed files with 912 additions and 7 deletions
+5
View File
@@ -2,6 +2,7 @@
from pathlib import Path
import drawsvg as draw
from .architecture import build_architecture
from .deployment import build_deployment
def main() -> None:
@@ -36,3 +37,7 @@ def main() -> None:
topology = output.parent / "endor-architecture.svg"
build_architecture(topology)
print(f"Generated {topology.relative_to(root)}")
deployment = output.parent / "endor-deployment.svg"
build_deployment(deployment)
print(f"Generated {deployment.relative_to(root)}")
@@ -0,0 +1,189 @@
"""Deployment choices and explicit remote object-pool mounts; no replication implied."""
from pathlib import Path
import math
import drawsvg as d
from .architecture import INK, GREEN, BLUE, PURPLE, MUTED, LINE, FONT
def build_deployment(output: Path) -> None:
svg = d.Drawing(1800, 1550)
svg.append_title('Endor 部署与对象挂载:本地端、自部署与平台集群')
svg.append(d.Rectangle(0, 0, 1800, 1550, fill='#fcfdfb'))
def text(s, x, y, size=16, color=INK, weight='400', anchor='start'):
svg.append(d.Text(s, size, x, y, fill=color, font_family=FONT,
font_weight=weight, text_anchor=anchor))
def box(x, y, w, h, fill='white', dash=None):
svg.append(d.Rectangle(x, y, w, h, rx=16, fill=fill, stroke=LINE,
stroke_width=1.3, stroke_dasharray=dash))
def wire(points, color=GREEN, dash=None, arrow=False, width=1.6):
p = d.Path(fill='none', stroke=color, stroke_width=width,
stroke_dasharray=dash, stroke_linejoin='round', stroke_linecap='round')
p.M(*points[0])
for point in points[1:]:
p.L(*point)
svg.append(p)
if arrow:
x, y = points[-1]
px, py = points[-2]
angle = math.atan2(y-py, x-px)
for sign in [-1, 1]:
svg.append(d.Line(x-7*math.cos(angle+sign*.48),
y-7*math.sin(angle+sign*.48), x, y,
stroke=color, stroke_width=width))
def dot(x, y, color=GREEN):
svg.append(d.Circle(x, y, 3.5, fill='#fcfdfb', stroke=color, stroke_width=1.5))
def icon(kind, x, y, color=GREEN, scale=1):
g = d.Group(transform=f'translate({x},{y}) scale({scale})', fill='none',
stroke=color, stroke_width=1.8, stroke_linejoin='round', stroke_linecap='round')
if kind == 'runtime':
for yy in [-17, 0]:
g.append(d.Rectangle(-26, yy, 52, 12, rx=3))
g.append(d.Circle(-18, yy+6, 1.5, fill=color))
g.append(d.Line(-10, yy+6, 17, yy+6))
g.append(d.Lines(-13,20,13,20)); g.append(d.Line(0,12,0,20))
elif kind == 'vm':
g.append(d.Rectangle(-16,-12,32,24,rx=4))
for a in [-9,0,9]:
g.append(d.Line(a,-17,a,-12)); g.append(d.Line(a,12,a,17))
for a in [-6,6]:
g.append(d.Line(-21,a,-16,a)); g.append(d.Line(16,a,21,a))
g.append(d.Lines(-6,-5,0,0,-6,5)); g.append(d.Line(5,5,10,5))
elif kind == 'pool':
for dx,dy in [(-12,-10),(12,-10),(0,13)]:
g.append(d.Rectangle(dx-10,dy-10,20,20,rx=5))
g.append(d.Circle(dx,dy,2.5,fill=color))
elif kind == 'db':
g.append(d.Ellipse(0,-14,22,8))
g.append(d.Path().M(-22,-14).L(-22,18).C(-22,30,22,30,22,18).L(22,-14))
g.append(d.Path().M(-22,1).C(-22,13,22,13,22,1))
elif kind == 's3':
g.append(d.Ellipse(0,-16,23,7))
g.append(d.Lines(-23,-16,-18,23,18,23,23,-16))
g.append(d.Path().M(-17,6).Q(0,17,17,6))
elif kind == 'gateway':
g.append(d.Lines(0,-24,24,0,0,24,-24,0,0,-24))
g.append(d.Line(-34,0,-12,0))
for a in [-13,0,13]:
g.append(d.Lines(-8,0,8,a,34,a,29,a-4))
g.append(d.Line(34,a,29,a+4))
elif kind == 'laptop':
g.append(d.Rectangle(-37,-27,74,46,rx=5))
g.append(d.Lines(-37,19,-46,30,46,30,37,19))
g.append(d.Line(-8,25,8,25))
elif kind == 'phone':
g.append(d.Rectangle(-21,-35,42,72,rx=8))
g.append(d.Line(-7,-28,7,-28)); g.append(d.Line(-5,30,5,30))
svg.append(g)
def runtime(x, y, label, color=GREEN, compact=False):
icon('runtime', x, y, color, .85 if compact else 1)
text(label,x,y+43,14,color=INK,weight='600',anchor='middle')
wire([(x+26,y+5),(x+58,y+5),(x+58,y+68)],color,'2 4',arrow=True,width=1.1)
icon('vm',x+58,y+83,color,.62)
text('Aster VM',x+58,y+112,12,MUTED,anchor='middle')
text('ENDOR / DEPLOYMENT ATLAS',40,43,14,GREEN,'600')
text('本地、自部署与平台,连接到同一个对象世界',40,91,29,INK,'600')
text('部署位置可以不同;Tessera 通过本地运行时组织对象,并按授权挂载远端对象池。',40,123,16,MUTED)
# Two independent self-hosted examples, each with its own simple storage stack.
for x, name, count in [(40,'自部署 A',1),(565,'自部署 B',2)]:
cx=x+240
box(x,165,480,700)
text(name,x+24,204,23,INK,'600')
text('独立服务 / 独立数据',x+24,231,13,MUTED)
icon('gateway',cx,295)
text('Nginx / Traefik',cx,345,17,INK,'600','middle')
text('可选网关 · 云上可用托管入口替代',cx,371,13,MUTED,anchor='middle')
xs=[cx] if count==1 else [x+125,x+325]
for i,nx in enumerate(xs):
wire([(cx,385),(nx,419)],arrow=True)
runtime(nx,445,f'Nexus Runtime {i+1:02}')
wire([(nx-28,445),(nx-50,445),(nx-50,594),(nx,594),(nx,611)],width=1.2)
for nx in xs:
# Small deployments deliberately show a simple stack per Runtime.
icon('pool',nx,642,scale=.8)
text('Aerarium',nx,688,17,INK,'600','middle')
text('Object data pool',nx,711,11,MUTED,anchor='middle')
for target,kind,label in [(nx-65,'db','PostgreSQL'),(nx+65,'s3','S3')]:
wire([(nx-25 if kind=='db' else nx+25,650),(target,748)],arrow=True)
icon(kind,target,777,scale=.7)
text(label,target,823,13,INK,'500','middle')
# Every pool has an explicit route onto the platform mount lane.
wire([(nx+24,628),(nx+50,628),(nx+50,590),(x+450,590),
(x+450,925 if count==1 else 955),
(1100 if count==1 else 1080,925 if count==1 else 955),
(1100 if count==1 else 1080,610 if count==1 else 640),
(1195,610 if count==1 else 640)],PURPLE,'6 6',arrow=True)
wire([(cx,865),(x+16,865),(x+16,295),(cx-35,295)],BLUE,'2 5',arrow=True,width=1.1)
dot(cx,865,BLUE)
wire([(cx,865),(cx,901)],BLUE)
text('… / etc · 更多独立自部署',565,897,14,MUTED)
# Simplified platform: two gateways, a complete K2,3 mesh, grouped storage.
box(1120,165,640,700)
text('Endor 平台集群',1144,204,23,INK,'600')
text('平台托管 · 多区域 · 按需扩展',1144,231,13,MUTED)
for gx in [1300,1580]:
icon('gateway',gx,295,BLUE,.8)
text('区域入口',gx,337,14,INK,'500','middle')
for nx in [1220,1430,1640]:
wire([(gx,350),(nx,406)],BLUE,width=1.1)
dot(gx,350,BLUE)
for i,nx in enumerate([1220,1430,1640]):
runtime(nx,432,f'Nexus {i+1:02}',BLUE,True)
wire([(nx-25,432),(nx-48,432),(nx-48,575),(1440,575),(1440,600)],BLUE,width=1)
text('… / etc',1708,426,13,BLUE,anchor='middle')
box(1195,600,490,99,'#f5f8fb')
icon('pool',1240,642,BLUE,.8)
text('Aerarium 对象池',1282,637,20,INK,'600')
text('平台分片群 · 远端挂载接入',1282,669,13,MUTED)
for target,kind,label in [(1280,'db','PostgreSQL 分片群'),(1600,'s3','S3 分片群')]:
wire([(1440,699),(target,744)],BLUE,arrow=True)
icon(kind,target,773,BLUE,.85)
text(label,target,824,15,INK,'500','middle')
wire([(1440,865),(1740,865),(1740,295),(1608,295)],BLUE,'2 5',arrow=True,width=1.1)
dot(1440,865,BLUE)
wire([(1440,865),(1440,1015)],BLUE)
text('统一备份 / 区域 HA:沿用系统架构图,此处省略展开',1440,851,12,MUTED,anchor='middle')
# Dedicated labelled lanes keep federation separate from local remote-access mounts.
box(500,969,780,31,'#fcfdfb')
text('自部署 Aerarium → 平台:经授权挂载对象池,保留源端的数据归属',890,990,15,PURPLE,anchor='middle')
wire([(280,901),(280,1015),(1640,1015)],BLUE)
wire([(805,901),(805,1015)],BLUE)
text('远端挂载 · 可选择自部署或 Endor 平台',40,990,15,BLUE,'500')
for x in [280,805,1440]:
dot(x,1015,BLUE)
text('本地端',40,1071,23,INK,'600')
for i,(name,kind) in enumerate([('Windows','laptop'),('Linux','laptop'),('macOS','laptop'),('iOS','phone'),('Android','phone')]):
x=40+i*350
cx=x+160
box(x,1100,320,340,'#f9fbf8')
wire([(cx,1100),(cx,1015)],BLUE,arrow=True)
dot(cx,1015,BLUE)
icon(kind,x+68,1160,GREEN,.7)
text(name,x+118,1147,20,INK,'600')
text('Tessera 前端',x+118,1178,16,GREEN,'500')
wire([(x+145,1190),(x+145,1200),(x+65,1200),(x+65,1208)],arrow=True,width=1.1)
text('本地对象',x+38,1226,14,INK,'500')
wire([(x+105,1221),(x+155,1221),(x+155,1255)],arrow=True)
text('挂载',x+166,1240,11,MUTED)
runtime(x+155,1284,'Nexus Runtime',compact=True)
wire([(x+181,1284),(x+293,1284),(x+293,1115),(cx,1115),(cx,1100)],BLUE,'2 5',arrow=True,width=1.2)
text('本机运行 / 远端对象访问',cx,1420,12,MUTED,anchor='middle')
wire([(40,1482),(100,1482)],GREEN,arrow=True)
text('本地对象 / 存储依赖',114,1487,13,MUTED)
wire([(435,1482),(495,1482)],BLUE,'2 5',arrow=True)
text('本地 Nexus 挂载远端',509,1487,13,MUTED)
wire([(850,1482),(910,1482)],PURPLE,'6 6',arrow=True)
text('自部署 Aerarium 挂载到平台',924,1487,13,MUTED)
text('目标架构示意 · 挂载不等于复制;鉴权、命名与写入策略需另行定义。',40,1526,13,MUTED)
svg.save_svg(str(output))