driveAtlas: redraw large1 as a Rosewill RSV-L4500U cutaway
Lint / Shell (shellcheck) (push) Successful in 9s
Lint / Shell (shellcheck) (push) Successful in 9s
large1 lives in a Rosewill RSV-L4500U 4U case, not a generic "3x5 grid". The old render was a plain placeholder that said little about the machine. This chassis is awkward to draw honestly: the drives are NOT visible from the front. The full-width perforated door drops down to reveal a wall of 120mm fans, and the 15 bays sit behind it in three removable 5-drive modules serviced by taking the TOP COVER off. So a literal front elevation would show zero drives. Draw a front elevation with the fan wall and dust door CUT AWAY instead: the chassis silhouette stays recognisable - rack ears carrying the big vertical D-handles, the perforated drop-down door with its centred barrel lock, the slim right-hand I/O strip - while the cutaway (dashed tear edges, ghosted arcs of the outer two 120mm fans) exposes all 15 bays. Geometry per Rosewill's manual: 3 modules side by side, 5 drives stacked FLAT in each, long axis front-to-back with connectors facing the rear. So from the front every bay is a WIDE, SHORT slot and the grid reads 5 rows x 3 columns - the inverse of the Sliger's tall on-edge slots. The chassis documents no bay numbering and has no backplane and no per-bay LEDs, so numbering is stated in the output as a driveAtlas LOCAL convention (1-5 left module top->bottom, 6-10 middle, 11-15 right) with a reminder to identify a drive by serial, plus a note that service is top-cover-off. Fixed 96-column canvas, identical display width on every line in monochrome and colour, colour runs coalesced (max 17 escapes per line, never changing inside a repeated glyph run), SAFE width-1 glyphs only. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+167
-32
@@ -828,44 +828,179 @@ generate_nuc_layout() {
|
|||||||
#------------------------------------------------------------------------------
|
#------------------------------------------------------------------------------
|
||||||
# generate_large1_layout
|
# generate_large1_layout
|
||||||
#
|
#
|
||||||
# Generates ASCII art representation of a large1 chassis (Rosewill RSV-L4500U).
|
# Rosewill RSV-L4500U (4U, 427x178x635 mm) -- FRONT ELEVATION with the 3x 120 mm
|
||||||
# Shows storage controllers, 2 M.2 NVMe slots, and 15 front bays in 3x5 grid.
|
# fan wall and the perforated drop-down door CUT AWAY, exposing all 15 internal
|
||||||
|
# 3.5" bays: 3 removable modules side by side, 5 drives stacked FLAT in each, so
|
||||||
|
# from the front every bay reads as a WIDE, SHORT slot -> a 5-row x 3-column grid.
|
||||||
|
# Rack ears with D-handles at both ends; slim I/O strip on the right.
|
||||||
|
#
|
||||||
|
# Bay numbering is a driveAtlas LOCAL convention (the chassis documents none):
|
||||||
|
# 1-5 = LEFT module top->bottom, 6-10 = MIDDLE, 11-15 = RIGHT.
|
||||||
#
|
#
|
||||||
# Args:
|
# Args:
|
||||||
# $1 - Hostname to display in the layout header
|
# $1 - Hostname to display in the layout header
|
||||||
#
|
#
|
||||||
# Side effects: Calls build_drive_map() to populate DRIVE_MAP
|
# Side effects: calls build_drive_map() (populates DRIVE_MAP) then da_color_init.
|
||||||
#------------------------------------------------------------------------------
|
# Every emitted line is exactly 96 display columns wide, in colour or mono.
|
||||||
|
#
|
||||||
|
# The art printfs embed our own ANSI colour vars (DA_*) and literal box-drawing;
|
||||||
|
# device names are always passed as %s args and pre-truncated, so SC2059 does
|
||||||
|
# not apply here.
|
||||||
|
# shellcheck disable=SC2059
|
||||||
generate_large1_layout() {
|
generate_large1_layout() {
|
||||||
local hostname="$1"
|
local host="${1:-unknown}"
|
||||||
build_drive_map
|
|
||||||
|
|
||||||
# large1 has 3 stacks of 5 bays at front (15 total) + 2 M.2 slots
|
if declare -F build_drive_map >/dev/null 2>&1; then
|
||||||
# Physical bay mapping TBD - current mapping is by controller order
|
build_drive_map || true # log_* goes to stderr; stdout stays clean
|
||||||
printf "┌─────────────────────────────────────────────────────────────────────────┐\n"
|
fi
|
||||||
printf "│ %-69s │\n" "$hostname - Rosewill RSV-L4500U (15x 3.5\" Bays)"
|
da_color_init
|
||||||
printf "│ │\n"
|
|
||||||
printf "│ Storage Controllers: │\n"
|
# ---- local helpers ---------------------------------------------------
|
||||||
while IFS= read -r ctrl; do
|
_l1_rep() { # _l1_rep GLYPH N
|
||||||
[[ -n "$ctrl" ]] && printf "│ %-69.69s│\n" "$ctrl"
|
if declare -F da_repeat >/dev/null 2>&1; then
|
||||||
done < <(get_storage_controllers)
|
da_repeat "$1" "$2"
|
||||||
printf "│ │\n"
|
else
|
||||||
printf "│ M.2 NVMe: M1: %-10s M2: %-10s │\n" "${DRIVE_MAP[m2-1]:-EMPTY}" "${DRIVE_MAP[m2-2]:-EMPTY}"
|
local g="$1" n="$2" o=''
|
||||||
printf "│ │\n"
|
while [ "$n" -gt 0 ]; do o+="$g"; n=$((n - 1)); done
|
||||||
printf "│ Front Bays (3 stacks x 5 rows): [slot 5 of each stack MUST stay empty] │\n"
|
printf '%s' "$o"
|
||||||
printf "│ Stack A Stack B Stack C │\n"
|
fi
|
||||||
printf "│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │\n"
|
}
|
||||||
printf "│ │1:%-8s│ │2:%-8s│ │3:%-8s│ │\n" "${DRIVE_MAP[1]:-EMPTY}" "${DRIVE_MAP[2]:-EMPTY}" "${DRIVE_MAP[3]:-EMPTY}"
|
# DRIVE_MAP may be undeclared, empty, or (defensively) not associative
|
||||||
printf "│ ├──────────┤ ├──────────┤ ├──────────┤ │\n"
|
local has_map=0
|
||||||
printf "│ │4:%-8s│ │5:%-8s│ │6:%-8s│ │\n" "${DRIVE_MAP[4]:-EMPTY}" "${DRIVE_MAP[5]:-EMPTY}" "${DRIVE_MAP[6]:-EMPTY}"
|
case "$(declare -p DRIVE_MAP 2>/dev/null)" in *'declare -A'*) has_map=1 ;; esac
|
||||||
printf "│ ├──────────┤ ├──────────┤ ├──────────┤ │\n"
|
_l1_map() { # _l1_map KEY -> value or ''
|
||||||
printf "│ │7:%-8s│ │8:%-8s│ │9:%-8s│ │\n" "${DRIVE_MAP[7]:-EMPTY}" "${DRIVE_MAP[8]:-EMPTY}" "${DRIVE_MAP[9]:-EMPTY}"
|
[ "$has_map" = 1 ] || return 0
|
||||||
printf "│ ├──────────┤ ├──────────┤ ├──────────┤ │\n"
|
printf '%s' "${DRIVE_MAP[$1]:-}"
|
||||||
printf "│ │10:%-7s│ │11:%-7s│ │12:%-7s│ │\n" "${DRIVE_MAP[10]:-EMPTY}" "${DRIVE_MAP[11]:-EMPTY}" "${DRIVE_MAP[12]:-EMPTY}"
|
}
|
||||||
printf "│ ├──────────┤ ├──────────┤ ├──────────┤ │\n"
|
_l1_txt() { # _l1_txt TEXT [COLOUR] -- plain 96-col line
|
||||||
printf "│ │13:%-7s│ │14:%-7s│ │15:%-7s│ │\n" "${DRIVE_MAP[13]:-EMPTY}" "${DRIVE_MAP[14]:-EMPTY}" "${DRIVE_MAP[15]:-EMPTY}"
|
printf ' %s%-92.92s%s \n' "${2:-}" "$1" "${DA_RST}"
|
||||||
printf "│ └──────────┘ └──────────┘ └──────────┘ │\n"
|
}
|
||||||
printf "└─────────────────────────────────────────────────────────────────────────┘\n"
|
_l1_band() { # _l1_band TEXT WIDTH -- text centred in ▒
|
||||||
|
local t="$1" w="$2" l r
|
||||||
|
[ "${#t}" -gt "$w" ] && t="${t:0:w}"
|
||||||
|
l=$(( (w - ${#t}) / 2 )); r=$(( w - ${#t} - l ))
|
||||||
|
printf '%s%s%s' "$(_l1_rep '▒' "$l")" "$t" "$(_l1_rep '▒' "$r")"
|
||||||
|
}
|
||||||
|
|
||||||
|
# ---- geometry --------------------------------------------------------
|
||||||
|
# 96 cols = 2 pad + 5 ear + 1 + 80 body + 1 + 5 ear + 2 pad
|
||||||
|
# interior = 78 = 6 fan sliver + 1 tear + 56 grid + 1 tear + 6 fan + 8 I/O
|
||||||
|
# grid 56 = 3 modules of 18 (16-col bay cell + 2 borders) + 2 gaps
|
||||||
|
local IW=78
|
||||||
|
local i n p v rule16
|
||||||
|
rule16="$(_l1_rep '─' 16)"
|
||||||
|
|
||||||
|
# ---- bay cells: ONE colour run each, no churn inside a glyph run ------
|
||||||
|
local -a cells=()
|
||||||
|
for ((i = 1; i <= 15; i++)); do
|
||||||
|
v="$(_l1_map "$i")"
|
||||||
|
printf -v n '%2d' "$i"
|
||||||
|
if [ -n "$v" ]; then
|
||||||
|
printf -v p '%-12.12s' "$v"
|
||||||
|
cells[i]="${DA_OK}▌${n} ${p}${DA_ST}"
|
||||||
|
else
|
||||||
|
printf -v p '%-12.12s' 'empty'
|
||||||
|
cells[i]="${DA_DIM}░${n} ${p}${DA_ST}"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
# ---- module box pieces (18 wide) -------------------------------------
|
||||||
|
local mtop="┌${rule16}┐" mmid="├${rule16}┤" mbot="└${rule16}┘"
|
||||||
|
local gtop="${mtop} ${mtop} ${mtop}"
|
||||||
|
local gmid="${mmid} ${mmid} ${mmid}"
|
||||||
|
local gbot="${mbot} ${mbot} ${mbot}"
|
||||||
|
|
||||||
|
# ---- ghosted 120 mm fan rings, sliced open by the cutaway -------------
|
||||||
|
local -a fanL=(
|
||||||
|
' ╭─' ' ╭─╯ ' ' ╭╯ ' ' │ ' ' │ ' ' │ '
|
||||||
|
' │ ' ' │ ' ' ╰╮ ' ' ╰─╮ ' ' ╰─'
|
||||||
|
)
|
||||||
|
local -a fanR=(
|
||||||
|
'─╮ ' ' ╰─╮ ' ' ╰╮ ' ' │ ' ' │ ' ' │ '
|
||||||
|
' │ ' ' │ ' ' ╭╯ ' ' ╭─╯ ' '─╯ '
|
||||||
|
)
|
||||||
|
# ---- slim front I/O strip (right-hand edge of the front face) --------
|
||||||
|
local -a io=(
|
||||||
|
' ╭────╮ ' ' │I/O │ ' ' │▄pwr│ ' ' │▄rst│ ' ' │▀led│ ' ' ├────┤ '
|
||||||
|
' │▄▄▄▄│ ' ' │USB3│ ' ' │▄▄▄▄│ ' ' │USB3│ ' ' ╰────╯ '
|
||||||
|
)
|
||||||
|
|
||||||
|
# ---- banner, door band, top-cover caption ----------------------------
|
||||||
|
local ban=' FAN WALL + DUST DOOR CUT AWAY - 15 BAYS EXPOSED ' banner lp rp
|
||||||
|
lp=$(( (IW - ${#ban}) / 2 )); rp=$(( IW - ${#ban} - lp ))
|
||||||
|
banner="$(_l1_rep '╌' "$lp")${ban}$(_l1_rep '╌' "$rp")"
|
||||||
|
|
||||||
|
# perforated door band: two captions in a field of punched holes, keyed
|
||||||
|
# barrel lock dead centre on the door's lower edge
|
||||||
|
local door dlw=$(( (IW - 3) / 2 ))
|
||||||
|
door="$(_l1_band ' perforated drop-down door ' "$dlw")"
|
||||||
|
door="${door}${DA_HL}▐█▌${DA_DIM}$(_l1_band ' washable dust filter ' $(( IW - dlw - 3 )))"
|
||||||
|
|
||||||
|
local tlbl='[ TOP COVER - modules release from above ]' ttop
|
||||||
|
ttop="┌──${tlbl}$(_l1_rep '─' $(( IW - 2 - ${#tlbl} )))┐"
|
||||||
|
|
||||||
|
# ---- header ----------------------------------------------------------
|
||||||
|
local hp mp
|
||||||
|
printf -v hp '%-38.38s' "$host"
|
||||||
|
printf -v mp '%40.40s' 'Rosewill RSV-L4500U 4U 15x 3.5" bays'
|
||||||
|
printf '%s\n' " ${DA_CYN}driveAtlas${DA_ST} :: ${DA_HL}${hp}${DA_SR}${mp}${DA_RST} "
|
||||||
|
_l1_txt 'FRONT ELEVATION, fan wall cut away - 5 bays tall x 3 modules wide, drives lie FLAT' "${DA_DIM}"
|
||||||
|
printf '%96s\n' ''
|
||||||
|
|
||||||
|
# ---- chassis: rack ears carry the big vertical D-handles -------------
|
||||||
|
local eartop='┌───┐' earhole='│ ▄ │' earcapT='│╭─╮│' earbody='││ ││'
|
||||||
|
local earcapB='│╰─╯│' earbot='└───┘'
|
||||||
|
local labs l1 l2 l3
|
||||||
|
printf -v l1 '%*s%-*s' 5 '' 13 'LEFT 1-5'
|
||||||
|
printf -v l2 '%*s%-*s' 3 '' 15 'MIDDLE 6-10'
|
||||||
|
printf -v l3 '%*s%-*s' 3 '' 15 'RIGHT 11-15'
|
||||||
|
labs="${l1} ${l2} ${l3}"
|
||||||
|
local d grid E
|
||||||
|
|
||||||
|
printf '%s\n' " ${DA_HL}${eartop}${DA_SR} ${ttop} ${DA_HL}${eartop}${DA_RST} "
|
||||||
|
printf '%s\n' " ${DA_HL}${earhole}${DA_SR} │${DA_DIM}${banner}${DA_ST}│ ${DA_HL}${earhole}${DA_RST} "
|
||||||
|
printf '%s\n' " ${DA_HL}${earcapT}${DA_SR} │${DA_DIM} ╎${DA_HL}${labs}${DA_DIM}╎ ${DA_ST}│ ${DA_HL}${earcapT}${DA_RST} "
|
||||||
|
|
||||||
|
for ((d = 0; d < 11; d++)); do
|
||||||
|
case "$d" in
|
||||||
|
0) grid="$gtop" ;;
|
||||||
|
10) grid="$gbot" ;;
|
||||||
|
1|3|5|7|9)
|
||||||
|
i=$(( (d + 1) / 2 ))
|
||||||
|
grid="│${cells[$i]}│ │${cells[$(( i + 5 ))]}│ │${cells[$(( i + 10 ))]}│" ;;
|
||||||
|
*) grid="$gmid" ;;
|
||||||
|
esac
|
||||||
|
E="${DA_HL}${earbody}${DA_SR}"
|
||||||
|
printf '%s\n' " ${E} │${DA_DIM}${fanL[$d]}╎${DA_ST}${grid}${DA_DIM}╎${fanR[$d]}${io[$d]}${DA_ST}│ ${E}${DA_RST} "
|
||||||
|
done
|
||||||
|
|
||||||
|
printf '%s\n' " ${DA_HL}${earcapB}${DA_SR} │${DA_DIM}$(_l1_rep '╌' "$IW")${DA_ST}│ ${DA_HL}${earcapB}${DA_RST} "
|
||||||
|
printf '%s\n' " ${DA_HL}${earhole}${DA_SR} │${DA_DIM}${door}${DA_ST}│ ${DA_HL}${earhole}${DA_RST} "
|
||||||
|
printf '%s\n' " ${DA_HL}${earbot}${DA_SR} └$(_l1_rep '─' "$IW")┘ ${DA_HL}${earbot}${DA_RST} "
|
||||||
|
printf '%96s\n' ''
|
||||||
|
|
||||||
|
# ---- legend + the three things you must know -------------------------
|
||||||
|
local m1 m2 extra m1p m2p exp
|
||||||
|
m1="$(_l1_map m2-1)"; [ -n "$m1" ] || m1='-'
|
||||||
|
m2="$(_l1_map m2-2)"; [ -n "$m2" ] || m2='-'
|
||||||
|
extra=''
|
||||||
|
for ((i = 1; i <= 6; i++)); do
|
||||||
|
v="$(_l1_map "int-$i")"
|
||||||
|
[ -n "$v" ] && extra="${extra}int-${i} ${v} "
|
||||||
|
done
|
||||||
|
[ -n "$extra" ] || extra='-'
|
||||||
|
printf -v m1p '%-12.12s' "$m1"
|
||||||
|
printf -v m2p '%-12.12s' "$m2"
|
||||||
|
printf -v exp '%-29.29s' "$extra"
|
||||||
|
|
||||||
|
printf '%s\n' " ${DA_ST}LEGEND ${DA_OK}▌${DA_ST} drive present ${DA_DIM}░${DA_ST} bay empty$(_l1_rep ' ' 50)${DA_RST} "
|
||||||
|
printf '%s\n' " ${DA_ST}NOT IN GRID m2-1 ${DA_HL}${m1p}${DA_SR} m2-2 ${DA_HL}${m2p}${DA_SR} internal ${DA_HL}${exp}${DA_RST} "
|
||||||
|
_l1_txt 'BAYS numbering is a driveAtlas LOCAL convention (the chassis documents none): 1-5' "${DA_ST}"
|
||||||
|
_l1_txt ' LEFT module top->bottom, 6-10 MIDDLE, 11-15 RIGHT -- identify a drive by SERIAL.' "${DA_ST}"
|
||||||
|
_l1_txt 'NOTE no backplane, no per-bay LEDs, not hot-swap: every drive is cabled direct to the' "${DA_DIM}"
|
||||||
|
_l1_txt ' board/HBA, and is serviced by removing the TOP COVER, not through the front door.' "${DA_DIM}"
|
||||||
|
|
||||||
|
unset -f _l1_rep _l1_map _l1_txt _l1_band
|
||||||
}
|
}
|
||||||
|
|
||||||
#------------------------------------------------------------------------------
|
#------------------------------------------------------------------------------
|
||||||
|
|||||||
Reference in New Issue
Block a user