diff --git a/driveAtlas.sh b/driveAtlas.sh index e937812..753039b 100644 --- a/driveAtlas.sh +++ b/driveAtlas.sh @@ -446,74 +446,149 @@ run_diagnose() { #------------------------------------------------------------------------------ # generate_10bay_layout # -# Generates ASCII art representation of a 10-bay hot-swap chassis (Sliger CX4712). -# Shows storage controllers, M.2 NVMe slot, and 10 front hot-swap bays. +# FRONT ELEVATION of a Sliger CX4712 4U rackmount chassis (faceplate removed). +# - 2x 5.25" filler blanks side by side, spanning the bay region +# - 10x 3.5" hot-swap bays in ONE row; drives sit on edge, so each bay reads +# as a tall narrow slot -- the signature of this chassis +# - narrow front I/O strip (power ring, 2x USB-A, USB-C, key lock) +# - rack ears with vertical handles on both sides +# Occupied bays are solid, empty bays are dim ghosts. M.2 / internal drives are +# not on the front face, so they are listed as a legend under the drawing. +# +# Chassis has NO backplane and NO per-bay LEDs: each bay is an individually +# hand-cabled SFF-8482 to SATA adapter, so bay numbering is a LOCAL +# left-to-right convention, not a vendor one, and a drive can only be +# identified physically by its SERIAL (there is no bay LED to blink). # # Args: -# $1 - Hostname to display in the layout header +# $1 - Hostname to display in the header # # Side effects: Calls build_drive_map() to populate DRIVE_MAP +# +# The art printfs embed our own ANSI colour vars (DA_*) and literal box-drawing +# in the format string by design; device names are passed as %s args. #------------------------------------------------------------------------------ +# shellcheck disable=SC2059 generate_10bay_layout() { - local hostname="$1" - build_drive_map + local host="${1:-unknown}" + local i j k n st run cell c s lp - # Box interior width = 136 (determined by 10 bay boxes: 4 + 10*13 + 2) - # Total box width = 138 (136 interior + 2 for │ borders) - - # Main chassis section - printf "┌────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐\n" - printf "│ %-132s │\n" "$hostname - Sliger CX4712 (10x 3.5\" Hot-swap)" - printf "│%-136s│\n" "" - - # Show storage controllers - printf "│ %-134s│\n" "Storage Controllers:" - while IFS= read -r ctrl; do - [[ -n "$ctrl" ]] && printf "│ %-134.134s│\n" "$ctrl" - done < <(get_storage_controllers) - printf "│%-136s│\n" "" - - # M.2 NVMe slot if present - if [[ -n "${DRIVE_MAP[m2-1]}" ]]; then - printf "│ %-134s│\n" " M.2 NVMe: ${DRIVE_MAP[m2-1]}" - printf "│%-136s│\n" "" + if declare -F build_drive_map >/dev/null 2>&1; then + build_drive_map 2>/dev/null || true fi - - # Internal (non-bay) drives if present - local int_keys - int_keys="$(printf '%s\n' "${!DRIVE_MAP[@]}" | grep -E '^int-' | sort)" - if [[ -n "$int_keys" ]]; then - while IFS= read -r int_key; do - printf "│ %-134s│\n" " Internal (non-bay): ${DRIVE_MAP[$int_key]}" - done <<< "$int_keys" - printf "│%-136s│\n" "" + if ! declare -p DRIVE_MAP >/dev/null 2>&1; then + declare -g -A DRIVE_MAP=() fi - - printf "│ %-134s│\n" " Front Hot-swap Bays:" - printf "│%-136s│\n" "" - - # Bay top borders - printf "│ " - for bay in {1..10}; do - printf "┌──────────┐ " + if declare -F da_color_init >/dev/null 2>&1; then da_color_init; fi + # make every palette global exist -- safe under `set -u`, no-op when set + for s in DA_ST DA_HL DA_DIM DA_OK DA_YEL DA_HOT DA_BLU DA_CYN DA_RST DA_SR; do + printf -v "$s" '%s' "${!s-}" done - printf " │\n" - # Bay contents - printf "│ " - for bay in {1..10}; do - printf "│%-2d:%-7s│ " "$bay" "${DRIVE_MAP[$bay]:-EMPTY}" + # ---- geometry ----------------------------------------------------------- + # display width 88 = |1| 1 ear 3 | 2 | bays 61 | 5 | I/O 8 | 2 | ear 3 | 1 |1| + # bay strip 61 = wall + 10 slots of 5 cols separated by 1 col + wall + local -a state=() namec=() numc=() + for ((i = 1; i <= 10; i++)); do + s="${DRIVE_MAP[$i]:-}" + if [ -n "$s" ]; then state[i]=on; else state[i]=off; s='--'; fi + printf -v c '%-5.5s' "$s" # hard truncate: borders can never move + s="${c%% *}" + lp=$(( (5 - ${#s}) / 2 )) + printf -v c '%*s%s%*s' "$lp" '' "$s" "$((5 - lp - ${#s}))" '' + namec[i]="$c" + s="$i" + lp=$(( (5 - ${#s}) / 2 )) + printf -v c '%*s%s%*s' "$lp" '' "$s" "$((5 - lp - ${#s}))" '' + numc[i]="$c" done - printf " │\n" - # Bay bottom borders - printf "│ " - for bay in {1..10}; do - printf "└──────────┘ " + # ---- coalesced colour runs: one escape pair per RUN of like bays -------- + local fillrow='' namerow='' numrow='' + i=1 + while [ "$i" -le 10 ]; do + st="${state[i]}" + j="$i" + while [ "$j" -le 10 ] && [ "${state[j]}" = "$st" ]; do j=$((j + 1)); done + n=$((j - i)) + if [ "$st" = on ]; then cell='█████'; c="$DA_OK"; else cell='░░░░░'; c="$DA_DIM"; fi + run='' + for ((k = 0; k < n; k++)); do [ "$k" -gt 0 ] && run+=' '; run+="$cell"; done + fillrow+="${c}${run}${DA_SR}" + run='' + for ((k = i; k < j; k++)); do [ "$k" -gt "$i" ] && run+=' '; run+="${namec[k]}"; done + namerow+="${c}${run}${DA_SR}" + if [ "$j" -le 10 ]; then fillrow+=' '; namerow+=' '; fi + i="$j" + done + for ((i = 1; i <= 10; i++)); do + [ "$i" -gt 1 ] && numrow+=' ' + numrow+="${numc[i]}" done - printf " │\n" - printf "└────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘\n" + # ---- header ------------------------------------------------------------- + local hostp tail + printf -v hostp '%-24.24s' "$host" + printf -v tail '%-52.52s' 'Sliger CX4712 - 4U - 431.8 x 176.8 mm' + printf '\n' + printf "${DA_CYN}driveAtlas${DA_SR} ${DA_HL}%s${DA_SR} ${DA_ST}%s${DA_RST}\n" "$hostp" "$tail" + printf -v tail '%-88.88s' 'FRONT ELEVATION - mesh faceplate removed - drives mounted on edge' + printf "${DA_DIM}%s${DA_RST}\n" "$tail" + printf '\n' + + # ---- chassis ------------------------------------------------------------ + local T525='┌─────────────────────────────┬─────────────────────────────┐' + local M525='│ 5.25" FILLER │ 5.25" FILLER │' + local B525='└─────────────────────────────┴─────────────────────────────┘' + local TBAY='┌─────┬─────┬─────┬─────┬─────┬─────┬─────┬─────┬─────┬─────┐' + local BBAY='└─────┴─────┴─────┴─────┴─────┴─────┴─────┴─────┴─────┴─────┘' + local GAP61=' ' + local GAP74=' ' + + printf "${DA_ST}┌──────────────────────────────────────────────────────────────────────────────────────┐${DA_RST}\n" + printf "${DA_ST}│ ${DA_HL}╭─╮${DA_SR} %s ${DA_HL}╭─╮${DA_SR} │${DA_RST}\n" "$GAP74" + printf "${DA_ST}│ ${DA_HL}│ │${DA_SR} ${DA_DIM}%s${DA_SR} ┌──────┐ ${DA_HL}│ │${DA_SR} │${DA_RST}\n" "$T525" + printf "${DA_ST}│ ${DA_HL}│ │${DA_SR} ${DA_DIM}%s${DA_SR} │ ${DA_HL}(o)${DA_SR} │ ${DA_HL}│ │${DA_SR} │${DA_RST}\n" "$M525" + printf "${DA_ST}│ ${DA_HL}│ │${DA_SR} ${DA_DIM}%s${DA_SR} │ │ ${DA_HL}│ │${DA_SR} │${DA_RST}\n" "$B525" + printf "${DA_ST}│ ${DA_HL}│ │${DA_SR} %s │ ${DA_BLU}[A]${DA_SR} │ ${DA_HL}│ │${DA_SR} │${DA_RST}\n" "$GAP61" + printf "${DA_ST}│ ${DA_HL}│ │${DA_SR} ${DA_HL}%s${DA_SR} │ ${DA_BLU}[A]${DA_SR} │ ${DA_HL}│ │${DA_SR} │${DA_RST}\n" "$TBAY" + printf "${DA_ST}│ ${DA_HL}│ │${DA_SR} ${DA_HL}│${DA_SR}%s${DA_HL}│${DA_SR} │ ${DA_BLU}[C]${DA_SR} │ ${DA_HL}│ │${DA_SR} │${DA_RST}\n" "$fillrow" + printf "${DA_ST}│ ${DA_HL}│ │${DA_SR} ${DA_HL}│${DA_SR}%s${DA_HL}│${DA_SR} │ │ ${DA_HL}│ │${DA_SR} │${DA_RST}\n" "$fillrow" + printf "${DA_ST}│ ${DA_HL}│ │${DA_SR} ${DA_HL}│${DA_SR}%s${DA_HL}│${DA_SR} │ ${DA_DIM}(-)${DA_SR} │ ${DA_HL}│ │${DA_SR} │${DA_RST}\n" "$fillrow" + printf "${DA_ST}│ ${DA_HL}│ │${DA_SR} ${DA_HL}│${DA_SR}%s${DA_HL}│${DA_SR} │ │ ${DA_HL}│ │${DA_SR} │${DA_RST}\n" "$fillrow" + printf "${DA_ST}│ ${DA_HL}│ │${DA_SR} ${DA_HL}│${DA_SR}%s${DA_HL}│${DA_SR} │ │ ${DA_HL}│ │${DA_SR} │${DA_RST}\n" "$fillrow" + printf "${DA_ST}│ ${DA_HL}│ │${DA_SR} ${DA_HL}│${DA_SR}%s${DA_HL}│${DA_SR} │ │ ${DA_HL}│ │${DA_SR} │${DA_RST}\n" "$fillrow" + printf "${DA_ST}│ ${DA_HL}│ │${DA_SR} ${DA_HL}%s${DA_SR} └──────┘ ${DA_HL}│ │${DA_SR} │${DA_RST}\n" "$BBAY" + printf "${DA_ST}│ ${DA_HL}╰─╯${DA_SR} %s ${DA_HL}╰─╯${DA_SR} │${DA_RST}\n" "$GAP74" + printf "${DA_ST}└──────────────────────────────────────────────────────────────────────────────────────┘${DA_RST}\n" + + # ---- captions, aligned under the ten slots ------------------------------ + printf "${DA_ST} BAY %s${DA_ST} ${DA_RST}\n" "$numrow" + printf "${DA_ST} DEV %s${DA_ST} ${DA_RST}\n" "$namerow" + printf '\n' + + # ---- off-panel drives + the one thing you must know --------------------- + local off='' v + [ -n "${DRIVE_MAP[m2-1]:-}" ] && off="M.2 ${DRIVE_MAP[m2-1]}" + for i in 1 2 3 4 5 6 7 8; do + v="${DRIVE_MAP[int-$i]:-}" + [ -n "$v" ] || continue + [ -n "$off" ] && off+=" " + off+="internal $v" + done + [ -n "$off" ] || off='none' + printf -v off '%-66.66s' "$off" + printf "${DA_ST} not on front face ${DA_CYN}%s${DA_RST}\n" "$off" + printf -v tail '%-88.88s' ' No backplane and no per-bay LEDs: every bay is a hand-cabled SFF-8482 to SATA lead.' + printf "${DA_DIM}%s${DA_RST}\n" "$tail" + printf -v tail '%-88.88s' ' Bay numbers are a LOCAL left-to-right convention - identify a drive by SERIAL.' + printf "${DA_DIM}%s${DA_RST}\n" "$tail" + + if declare -F get_storage_controllers >/dev/null 2>&1; then + printf '\n' + printf " ${DA_HL}STORAGE CONTROLLERS${DA_RST}\n" + get_storage_controllers 2>/dev/null | sed 's/^/ /' + fi } #------------------------------------------------------------------------------