feat(rack): add elevation legend, both-gutter U-numbers, column frames
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
8d39fbcdf5
commit
08862fde51
4 changed files with 215 additions and 11 deletions
|
|
@ -1,5 +1,5 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="616" height="1012" viewBox="0 0 616 1012" style="max-width:100%;height:auto" font-family="sans-serif" font-size="11">
|
||||
<rect width="616" height="1012" fill="#ffffff"/>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="646" height="1068" viewBox="0 0 646 1068" style="max-width:100%;height:auto" font-family="sans-serif" font-size="11">
|
||||
<rect width="646" height="1068" fill="#ffffff"/>
|
||||
<text x="12" y="28" font-size="16" font-weight="bold">Rack rack01</text>
|
||||
<text x="178" y="34" text-anchor="middle" font-weight="bold">front</text>
|
||||
<rect x="58" y="40" width="240" height="20" fill="#f5f5f5" stroke="#e0e0e0"/>
|
||||
|
|
@ -147,6 +147,56 @@
|
|||
<text x="54" y="954" text-anchor="end" fill="#999">46</text>
|
||||
<text x="54" y="974" text-anchor="end" fill="#999">47</text>
|
||||
<text x="54" y="994" text-anchor="end" fill="#999">48</text>
|
||||
<text x="592" y="54" text-anchor="start" fill="#999">1</text>
|
||||
<text x="592" y="74" text-anchor="start" fill="#999">2</text>
|
||||
<text x="592" y="94" text-anchor="start" fill="#999">3</text>
|
||||
<text x="592" y="114" text-anchor="start" fill="#999">4</text>
|
||||
<text x="592" y="134" text-anchor="start" fill="#999">5</text>
|
||||
<text x="592" y="154" text-anchor="start" fill="#999">6</text>
|
||||
<text x="592" y="174" text-anchor="start" fill="#999">7</text>
|
||||
<text x="592" y="194" text-anchor="start" fill="#999">8</text>
|
||||
<text x="592" y="214" text-anchor="start" fill="#999">9</text>
|
||||
<text x="592" y="234" text-anchor="start" fill="#999">10</text>
|
||||
<text x="592" y="254" text-anchor="start" fill="#999">11</text>
|
||||
<text x="592" y="274" text-anchor="start" fill="#999">12</text>
|
||||
<text x="592" y="294" text-anchor="start" fill="#999">13</text>
|
||||
<text x="592" y="314" text-anchor="start" fill="#999">14</text>
|
||||
<text x="592" y="334" text-anchor="start" fill="#999">15</text>
|
||||
<text x="592" y="354" text-anchor="start" fill="#999">16</text>
|
||||
<text x="592" y="374" text-anchor="start" fill="#999">17</text>
|
||||
<text x="592" y="394" text-anchor="start" fill="#999">18</text>
|
||||
<text x="592" y="414" text-anchor="start" fill="#999">19</text>
|
||||
<text x="592" y="434" text-anchor="start" fill="#999">20</text>
|
||||
<text x="592" y="454" text-anchor="start" fill="#999">21</text>
|
||||
<text x="592" y="474" text-anchor="start" fill="#999">22</text>
|
||||
<text x="592" y="494" text-anchor="start" fill="#999">23</text>
|
||||
<text x="592" y="514" text-anchor="start" fill="#999">24</text>
|
||||
<text x="592" y="534" text-anchor="start" fill="#999">25</text>
|
||||
<text x="592" y="554" text-anchor="start" fill="#999">26</text>
|
||||
<text x="592" y="574" text-anchor="start" fill="#999">27</text>
|
||||
<text x="592" y="594" text-anchor="start" fill="#999">28</text>
|
||||
<text x="592" y="614" text-anchor="start" fill="#999">29</text>
|
||||
<text x="592" y="634" text-anchor="start" fill="#999">30</text>
|
||||
<text x="592" y="654" text-anchor="start" fill="#999">31</text>
|
||||
<text x="592" y="674" text-anchor="start" fill="#999">32</text>
|
||||
<text x="592" y="694" text-anchor="start" fill="#999">33</text>
|
||||
<text x="592" y="714" text-anchor="start" fill="#999">34</text>
|
||||
<text x="592" y="734" text-anchor="start" fill="#999">35</text>
|
||||
<text x="592" y="754" text-anchor="start" fill="#999">36</text>
|
||||
<text x="592" y="774" text-anchor="start" fill="#999">37</text>
|
||||
<text x="592" y="794" text-anchor="start" fill="#999">38</text>
|
||||
<text x="592" y="814" text-anchor="start" fill="#999">39</text>
|
||||
<text x="592" y="834" text-anchor="start" fill="#999">40</text>
|
||||
<text x="592" y="854" text-anchor="start" fill="#999">41</text>
|
||||
<text x="592" y="874" text-anchor="start" fill="#999">42</text>
|
||||
<text x="592" y="894" text-anchor="start" fill="#999">43</text>
|
||||
<text x="592" y="914" text-anchor="start" fill="#999">44</text>
|
||||
<text x="592" y="934" text-anchor="start" fill="#999">45</text>
|
||||
<text x="592" y="954" text-anchor="start" fill="#999">46</text>
|
||||
<text x="592" y="974" text-anchor="start" fill="#999">47</text>
|
||||
<text x="592" y="994" text-anchor="start" fill="#999">48</text>
|
||||
<rect x="58" y="40" width="240" height="960" fill="none" stroke="#999" stroke-width="1.5"/>
|
||||
<rect x="348" y="40" width="240" height="960" fill="none" stroke="#999" stroke-width="1.5"/>
|
||||
<a href="/hardware/srv04/">
|
||||
<title>srv04 · server · staging · cluster: — · U5–U6</title>
|
||||
<rect x="59" y="121" width="238" height="38" rx="3" fill="#4c78a8" stroke="#333333" stroke-width="1.5" stroke-dasharray="4 2"/>
|
||||
|
|
@ -174,8 +224,8 @@
|
|||
</a>
|
||||
<a href="/hardware/pdu02/">
|
||||
<title>pdu02 · pdu · in-use · cluster: — · 0U right</title>
|
||||
<rect x="588" y="40" width="16" height="960" fill="#e15759" stroke="#333333" stroke-width="1.5"/>
|
||||
<text x="596" y="520" text-anchor="middle" fill="#ffffff" transform="rotate(-90 596 520)">pdu02</text>
|
||||
<rect x="618" y="40" width="16" height="960" fill="#e15759" stroke="#333333" stroke-width="1.5"/>
|
||||
<text x="626" y="520" text-anchor="middle" fill="#ffffff" transform="rotate(-90 626 520)">pdu02</text>
|
||||
</a>
|
||||
<a href="/hardware/srv01/">
|
||||
<title>srv01 · server · staging · cluster: tappaas · shf01/front/slot 1</title>
|
||||
|
|
@ -198,4 +248,23 @@
|
|||
<rect x="348" y="954" width="240" height="6" fill="#bab0ac" stroke="#333"/>
|
||||
<text x="178" y="959" text-anchor="middle" fill="#333" font-size="9">shf01</text>
|
||||
</a>
|
||||
<text x="58" y="1020" font-weight="bold">Legend</text>
|
||||
<rect x="58" y="1028" width="12" height="12" fill="#9c755f" stroke="#333"/>
|
||||
<text x="74" y="1038">patch-panel</text>
|
||||
<rect x="163" y="1028" width="12" height="12" fill="#e15759" stroke="#333"/>
|
||||
<text x="179" y="1038">pdu</text>
|
||||
<rect x="212" y="1028" width="12" height="12" fill="#4c78a8" stroke="#333"/>
|
||||
<text x="228" y="1038">server</text>
|
||||
<rect x="282" y="1028" width="12" height="12" fill="#bab0ac" stroke="#333"/>
|
||||
<text x="298" y="1038">shelf</text>
|
||||
<rect x="345" y="1028" width="12" height="12" fill="#59a14f" stroke="#333"/>
|
||||
<text x="361" y="1038">switch</text>
|
||||
<rect x="58" y="1046" width="12" height="12" fill="#ffffff" stroke="#333333" stroke-width="1.5"/>
|
||||
<text x="74" y="1056">in-use</text>
|
||||
<rect x="128" y="1046" width="12" height="12" fill="#ffffff" stroke="#333333" stroke-width="1.5" stroke-dasharray="4 2"/>
|
||||
<text x="144" y="1056">staging</text>
|
||||
<rect x="205" y="1046" width="12" height="12" fill="#ffffff" stroke="#e15759" stroke-width="3"/>
|
||||
<text x="221" y="1056">broken</text>
|
||||
<rect x="275" y="1046" width="12" height="12" fill="#ffffff" stroke="#bbbbbb" stroke-width="1.5"/>
|
||||
<text x="291" y="1056">spare</text>
|
||||
</svg>
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 18 KiB |
|
|
@ -5,8 +5,8 @@ _Auto-generated from `docs/hardware/*.md` (items with `rack: rack01`) — do not
|
|||
## Elevation
|
||||
|
||||
<div class="rack-elevation">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="616" height="1012" viewBox="0 0 616 1012" style="max-width:100%;height:auto" font-family="sans-serif" font-size="11">
|
||||
<rect width="616" height="1012" fill="#ffffff"/>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="646" height="1068" viewBox="0 0 646 1068" style="max-width:100%;height:auto" font-family="sans-serif" font-size="11">
|
||||
<rect width="646" height="1068" fill="#ffffff"/>
|
||||
<text x="12" y="28" font-size="16" font-weight="bold">Rack rack01</text>
|
||||
<text x="178" y="34" text-anchor="middle" font-weight="bold">front</text>
|
||||
<rect x="58" y="40" width="240" height="20" fill="#f5f5f5" stroke="#e0e0e0"/>
|
||||
|
|
@ -154,6 +154,56 @@ _Auto-generated from `docs/hardware/*.md` (items with `rack: rack01`) — do not
|
|||
<text x="54" y="954" text-anchor="end" fill="#999">46</text>
|
||||
<text x="54" y="974" text-anchor="end" fill="#999">47</text>
|
||||
<text x="54" y="994" text-anchor="end" fill="#999">48</text>
|
||||
<text x="592" y="54" text-anchor="start" fill="#999">1</text>
|
||||
<text x="592" y="74" text-anchor="start" fill="#999">2</text>
|
||||
<text x="592" y="94" text-anchor="start" fill="#999">3</text>
|
||||
<text x="592" y="114" text-anchor="start" fill="#999">4</text>
|
||||
<text x="592" y="134" text-anchor="start" fill="#999">5</text>
|
||||
<text x="592" y="154" text-anchor="start" fill="#999">6</text>
|
||||
<text x="592" y="174" text-anchor="start" fill="#999">7</text>
|
||||
<text x="592" y="194" text-anchor="start" fill="#999">8</text>
|
||||
<text x="592" y="214" text-anchor="start" fill="#999">9</text>
|
||||
<text x="592" y="234" text-anchor="start" fill="#999">10</text>
|
||||
<text x="592" y="254" text-anchor="start" fill="#999">11</text>
|
||||
<text x="592" y="274" text-anchor="start" fill="#999">12</text>
|
||||
<text x="592" y="294" text-anchor="start" fill="#999">13</text>
|
||||
<text x="592" y="314" text-anchor="start" fill="#999">14</text>
|
||||
<text x="592" y="334" text-anchor="start" fill="#999">15</text>
|
||||
<text x="592" y="354" text-anchor="start" fill="#999">16</text>
|
||||
<text x="592" y="374" text-anchor="start" fill="#999">17</text>
|
||||
<text x="592" y="394" text-anchor="start" fill="#999">18</text>
|
||||
<text x="592" y="414" text-anchor="start" fill="#999">19</text>
|
||||
<text x="592" y="434" text-anchor="start" fill="#999">20</text>
|
||||
<text x="592" y="454" text-anchor="start" fill="#999">21</text>
|
||||
<text x="592" y="474" text-anchor="start" fill="#999">22</text>
|
||||
<text x="592" y="494" text-anchor="start" fill="#999">23</text>
|
||||
<text x="592" y="514" text-anchor="start" fill="#999">24</text>
|
||||
<text x="592" y="534" text-anchor="start" fill="#999">25</text>
|
||||
<text x="592" y="554" text-anchor="start" fill="#999">26</text>
|
||||
<text x="592" y="574" text-anchor="start" fill="#999">27</text>
|
||||
<text x="592" y="594" text-anchor="start" fill="#999">28</text>
|
||||
<text x="592" y="614" text-anchor="start" fill="#999">29</text>
|
||||
<text x="592" y="634" text-anchor="start" fill="#999">30</text>
|
||||
<text x="592" y="654" text-anchor="start" fill="#999">31</text>
|
||||
<text x="592" y="674" text-anchor="start" fill="#999">32</text>
|
||||
<text x="592" y="694" text-anchor="start" fill="#999">33</text>
|
||||
<text x="592" y="714" text-anchor="start" fill="#999">34</text>
|
||||
<text x="592" y="734" text-anchor="start" fill="#999">35</text>
|
||||
<text x="592" y="754" text-anchor="start" fill="#999">36</text>
|
||||
<text x="592" y="774" text-anchor="start" fill="#999">37</text>
|
||||
<text x="592" y="794" text-anchor="start" fill="#999">38</text>
|
||||
<text x="592" y="814" text-anchor="start" fill="#999">39</text>
|
||||
<text x="592" y="834" text-anchor="start" fill="#999">40</text>
|
||||
<text x="592" y="854" text-anchor="start" fill="#999">41</text>
|
||||
<text x="592" y="874" text-anchor="start" fill="#999">42</text>
|
||||
<text x="592" y="894" text-anchor="start" fill="#999">43</text>
|
||||
<text x="592" y="914" text-anchor="start" fill="#999">44</text>
|
||||
<text x="592" y="934" text-anchor="start" fill="#999">45</text>
|
||||
<text x="592" y="954" text-anchor="start" fill="#999">46</text>
|
||||
<text x="592" y="974" text-anchor="start" fill="#999">47</text>
|
||||
<text x="592" y="994" text-anchor="start" fill="#999">48</text>
|
||||
<rect x="58" y="40" width="240" height="960" fill="none" stroke="#999" stroke-width="1.5"/>
|
||||
<rect x="348" y="40" width="240" height="960" fill="none" stroke="#999" stroke-width="1.5"/>
|
||||
<a href="/hardware/srv04/">
|
||||
<title>srv04 · server · staging · cluster: — · U5–U6</title>
|
||||
<rect x="59" y="121" width="238" height="38" rx="3" fill="#4c78a8" stroke="#333333" stroke-width="1.5" stroke-dasharray="4 2"/>
|
||||
|
|
@ -181,8 +231,8 @@ _Auto-generated from `docs/hardware/*.md` (items with `rack: rack01`) — do not
|
|||
</a>
|
||||
<a href="/hardware/pdu02/">
|
||||
<title>pdu02 · pdu · in-use · cluster: — · 0U right</title>
|
||||
<rect x="588" y="40" width="16" height="960" fill="#e15759" stroke="#333333" stroke-width="1.5"/>
|
||||
<text x="596" y="520" text-anchor="middle" fill="#ffffff" transform="rotate(-90 596 520)">pdu02</text>
|
||||
<rect x="618" y="40" width="16" height="960" fill="#e15759" stroke="#333333" stroke-width="1.5"/>
|
||||
<text x="626" y="520" text-anchor="middle" fill="#ffffff" transform="rotate(-90 626 520)">pdu02</text>
|
||||
</a>
|
||||
<a href="/hardware/srv01/">
|
||||
<title>srv01 · server · staging · cluster: tappaas · shf01/front/slot 1</title>
|
||||
|
|
@ -205,6 +255,25 @@ _Auto-generated from `docs/hardware/*.md` (items with `rack: rack01`) — do not
|
|||
<rect x="348" y="954" width="240" height="6" fill="#bab0ac" stroke="#333"/>
|
||||
<text x="178" y="959" text-anchor="middle" fill="#333" font-size="9">shf01</text>
|
||||
</a>
|
||||
<text x="58" y="1020" font-weight="bold">Legend</text>
|
||||
<rect x="58" y="1028" width="12" height="12" fill="#9c755f" stroke="#333"/>
|
||||
<text x="74" y="1038">patch-panel</text>
|
||||
<rect x="163" y="1028" width="12" height="12" fill="#e15759" stroke="#333"/>
|
||||
<text x="179" y="1038">pdu</text>
|
||||
<rect x="212" y="1028" width="12" height="12" fill="#4c78a8" stroke="#333"/>
|
||||
<text x="228" y="1038">server</text>
|
||||
<rect x="282" y="1028" width="12" height="12" fill="#bab0ac" stroke="#333"/>
|
||||
<text x="298" y="1038">shelf</text>
|
||||
<rect x="345" y="1028" width="12" height="12" fill="#59a14f" stroke="#333"/>
|
||||
<text x="361" y="1038">switch</text>
|
||||
<rect x="58" y="1046" width="12" height="12" fill="#ffffff" stroke="#333333" stroke-width="1.5"/>
|
||||
<text x="74" y="1056">in-use</text>
|
||||
<rect x="128" y="1046" width="12" height="12" fill="#ffffff" stroke="#333333" stroke-width="1.5" stroke-dasharray="4 2"/>
|
||||
<text x="144" y="1056">staging</text>
|
||||
<rect x="205" y="1046" width="12" height="12" fill="#ffffff" stroke="#e15759" stroke-width="3"/>
|
||||
<text x="221" y="1056">broken</text>
|
||||
<rect x="275" y="1046" width="12" height="12" fill="#ffffff" stroke="#bbbbbb" stroke-width="1.5"/>
|
||||
<text x="291" y="1056">spare</text>
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
|
|
|
|||
|
|
@ -357,16 +357,18 @@ def render_svg(rack: str, items: list[dict]) -> str:
|
|||
PAD = 12
|
||||
GAP = 50
|
||||
TITLE_H = 28
|
||||
LEGEND_H = 56
|
||||
|
||||
items = _sorted_items(items)
|
||||
left_items = [i for i in items if i.get("rack_face") == "left"]
|
||||
right_items = [i for i in items if i.get("rack_face") == "right"]
|
||||
|
||||
body_h = RACK_UNITS * U_H
|
||||
height = PAD + TITLE_H + body_h + PAD
|
||||
height = PAD + TITLE_H + body_h + PAD + LEGEND_H
|
||||
front_x = PAD + len(left_items) * RAIL_W + LABEL_W
|
||||
rear_x = front_x + COL_W + GAP
|
||||
width = rear_x + COL_W + len(right_items) * RAIL_W + PAD
|
||||
right_gutter_x = rear_x + COL_W
|
||||
width = right_gutter_x + LABEL_W + len(right_items) * RAIL_W + PAD
|
||||
top = PAD + TITLE_H
|
||||
|
||||
def u_y(u: int) -> int:
|
||||
|
|
@ -405,6 +407,17 @@ def render_svg(rack: str, items: list[dict]) -> str:
|
|||
f'<text x="{front_x - 4}" y="{y + 14}" text-anchor="end" '
|
||||
f'fill="#999">{u}</text>'
|
||||
)
|
||||
for u in range(1, RACK_UNITS + 1):
|
||||
y = u_y(u)
|
||||
p.append(
|
||||
f'<text x="{right_gutter_x + 4}" y="{y + 14}" text-anchor="start" '
|
||||
f'fill="#999">{u}</text>'
|
||||
)
|
||||
for col_x in (front_x, rear_x):
|
||||
p.append(
|
||||
f'<rect x="{col_x}" y="{top}" width="{COL_W}" height="{body_h}" '
|
||||
f'fill="none" stroke="#999" stroke-width="1.5"/>'
|
||||
)
|
||||
|
||||
def draw_device(fm: dict, col_x: int) -> None:
|
||||
u = fm["rack_u"]
|
||||
|
|
@ -457,7 +470,7 @@ def render_svg(rack: str, items: list[dict]) -> str:
|
|||
for idx, fm in enumerate(left_items):
|
||||
draw_rail(fm, PAD + idx * RAIL_W)
|
||||
for idx, fm in enumerate(right_items):
|
||||
draw_rail(fm, rear_x + COL_W + idx * RAIL_W)
|
||||
draw_rail(fm, right_gutter_x + LABEL_W + idx * RAIL_W)
|
||||
|
||||
SHELF_STRIP_H = 6
|
||||
shelves = [i for i in items if i.get("kind") == "shelf"]
|
||||
|
|
@ -514,6 +527,33 @@ def render_svg(rack: str, items: list[dict]) -> str:
|
|||
for fm in sorted(shelves, key=lambda s: s.get("hostname", "")):
|
||||
draw_shelf(fm)
|
||||
|
||||
legend_y = top + body_h + PAD + 8
|
||||
p.append(
|
||||
f'<text x="{front_x}" y="{legend_y}" font-weight="bold">Legend</text>'
|
||||
)
|
||||
present_kinds = sorted({i.get("kind", "") for i in items if i.get("kind")})
|
||||
kx = front_x
|
||||
ky = legend_y + 18
|
||||
for kind in present_kinds:
|
||||
color = KIND_COLORS.get(kind, DEFAULT_COLOR)
|
||||
p.append(
|
||||
f'<rect x="{kx}" y="{ky - 10}" width="12" height="12" '
|
||||
f'fill="{color}" stroke="#333"/>'
|
||||
)
|
||||
p.append(f'<text x="{kx + 16}" y="{ky}">{_esc(kind)}</text>')
|
||||
kx += 28 + 7 * len(kind)
|
||||
sx = front_x
|
||||
sy = ky + 18
|
||||
for label in ("in-use", "staging", "broken", "spare"):
|
||||
stroke, sw, dash = _status_stroke(label)
|
||||
dash_attr = f' stroke-dasharray="{dash}"' if dash else ""
|
||||
p.append(
|
||||
f'<rect x="{sx}" y="{sy - 10}" width="12" height="12" '
|
||||
f'fill="#ffffff" stroke="{stroke}" stroke-width="{sw}"{dash_attr}/>'
|
||||
)
|
||||
p.append(f'<text x="{sx + 16}" y="{sy}">{_esc(label)}</text>')
|
||||
sx += 28 + 7 * len(label)
|
||||
|
||||
p.append("</svg>")
|
||||
return "\n".join(p) + "\n"
|
||||
|
||||
|
|
|
|||
|
|
@ -674,3 +674,29 @@ def test_render_page_inlines_svg_with_download_link():
|
|||
assert "<svg" in page
|
||||
assert "[Download SVG](rack01-elevation.svg)" in page
|
||||
assert "![Rack rack01 elevation]" not in page
|
||||
|
||||
|
||||
def test_svg_legend_shows_present_kinds():
|
||||
items = [item(hostname="sw01", kind="switch", rack_u=10, u_height=1,
|
||||
rack_face="front")]
|
||||
svg = gen_rack.render_svg("rack01", items)
|
||||
assert ">Legend<" in svg
|
||||
assert ">switch<" in svg
|
||||
|
||||
|
||||
def test_svg_legend_omits_absent_kinds():
|
||||
items = [item(hostname="sw01", kind="switch", rack_u=10, u_height=1,
|
||||
rack_face="front")]
|
||||
svg = gen_rack.render_svg("rack01", items)
|
||||
assert ">ups<" not in svg
|
||||
|
||||
|
||||
def test_svg_u_numbers_in_both_gutters():
|
||||
svg = gen_rack.render_svg("rack01", [])
|
||||
assert 'text-anchor="end"' in svg # left gutter
|
||||
assert 'text-anchor="start"' in svg # right gutter
|
||||
|
||||
|
||||
def test_svg_has_column_frames():
|
||||
svg = gen_rack.render_svg("rack01", [])
|
||||
assert svg.count('fill="none"') >= 2 # one frame per column
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue