panel.sh 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214
  1. # mac-ops :: _lib/panel.sh
  2. # Panel-rendering helper that wraps the shared skills/_lib/term.sh API.
  3. # Source AFTER common.sh. Provides:
  4. #
  5. # panel_init — detect TTY, source term.sh
  6. # panel_findings_open — start collecting findings (called once at top)
  7. # panel_render <name> <indicator>
  8. # — emit the state-grouped panel using collected
  9. # findings + log_pass/log_fail/log_warn counts
  10. #
  11. # Collection happens transparently: the existing log_pass / log_fail /
  12. # log_warn / log_info from common.sh ALSO push (state, section, label,
  13. # detail) tuples into MAC_PANEL_FINDINGS when panel mode is on.
  14. # Locate the project-level term.sh (4 levels up from script dir, then _lib)
  15. __MACOPS_SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
  16. __MACOPS_TERM_LIB="$(cd "$__MACOPS_SCRIPT_DIR/../../../_lib" 2>/dev/null && pwd)/term.sh"
  17. # Panel mode default: auto. Force on with FORCE_PANEL=1, off with NO_PANEL=1.
  18. PANEL_MODE="${PANEL_MODE:-auto}"
  19. MAC_PANEL_ENABLED=0
  20. MAC_PANEL_FINDINGS=() # each entry: "state|section|label|detail"
  21. panel_init() {
  22. # If JSON output requested, never enable panels — JSON is the contract
  23. if [[ "${JSON_MODE:-0}" -eq 1 ]]; then return 0; fi
  24. # NO_PANEL forces off; FORCE_PANEL forces on; otherwise TTY-detect
  25. if [[ "${NO_PANEL:-0}" -eq 1 ]]; then return 0; fi
  26. # Source term.sh if available.
  27. if [[ -f "$__MACOPS_TERM_LIB" ]]; then
  28. # shellcheck source=/dev/null
  29. . "$__MACOPS_TERM_LIB"
  30. term_init
  31. else
  32. return 0
  33. fi
  34. # Use panel mode if stdout is a TTY or forced
  35. if [[ "${FORCE_PANEL:-0}" -eq 1 ]] || [[ "$TERM_TTY" -eq 1 ]]; then
  36. MAC_PANEL_ENABLED=1
  37. fi
  38. }
  39. panel_enabled() { [[ "$MAC_PANEL_ENABLED" -eq 1 ]]; }
  40. # Override common.sh log_* functions to also collect findings.
  41. # (These are sourced AFTER common.sh and will override its versions.)
  42. __MAC_ORIGINAL_PASS=$(declare -f log_pass || true)
  43. __MAC_ORIGINAL_FAIL=$(declare -f log_fail || true)
  44. __MAC_ORIGINAL_WARN=$(declare -f log_warn || true)
  45. __MAC_ORIGINAL_INFO=$(declare -f log_info || true)
  46. # Wrap each log function so it both updates counters AND collects findings.
  47. # When panel mode is on, ALSO suppress the inline echo — we'll render at end.
  48. log_pass() {
  49. PASS_COUNT=$((PASS_COUNT + 1))
  50. MAC_PANEL_FINDINGS+=("pass|${CURRENT_SECTION}|$1|${2:-}")
  51. if [[ "$MAC_PANEL_ENABLED" -eq 1 ]]; then return 0; fi
  52. if [[ "${JSON_MODE:-0}" -eq 1 ]]; then
  53. printf '{"type":"check","section":"%s","label":"%s","status":"pass","detail":"%s"}\n' \
  54. "$(_json_escape "$CURRENT_SECTION")" "$(_json_escape "$1")" "$(_json_escape "${2:-}")"
  55. else
  56. echo "[PASS] $1${2:+ :: $2}"
  57. fi
  58. }
  59. log_fail() {
  60. FAIL_COUNT=$((FAIL_COUNT + 1))
  61. [[ -z "$FIRST_FAIL" ]] && FIRST_FAIL="[$CURRENT_SECTION] $1"
  62. MAC_PANEL_FINDINGS+=("fail|${CURRENT_SECTION}|$1|${2:-}")
  63. if [[ "$MAC_PANEL_ENABLED" -eq 1 ]]; then return 0; fi
  64. if [[ "${JSON_MODE:-0}" -eq 1 ]]; then
  65. printf '{"type":"check","section":"%s","label":"%s","status":"fail","detail":"%s"}\n' \
  66. "$(_json_escape "$CURRENT_SECTION")" "$(_json_escape "$1")" "$(_json_escape "${2:-}")"
  67. else
  68. echo "[FAIL] $1${2:+ :: $2}"
  69. fi
  70. }
  71. log_warn() {
  72. WARN_COUNT=$((WARN_COUNT + 1))
  73. MAC_PANEL_FINDINGS+=("warn|${CURRENT_SECTION}|$1|${2:-}")
  74. if [[ "$MAC_PANEL_ENABLED" -eq 1 ]]; then return 0; fi
  75. if [[ "${JSON_MODE:-0}" -eq 1 ]]; then
  76. printf '{"type":"check","section":"%s","label":"%s","status":"warn","detail":"%s"}\n' \
  77. "$(_json_escape "$CURRENT_SECTION")" "$(_json_escape "$1")" "$(_json_escape "${2:-}")"
  78. else
  79. echo "[WARN] $1${2:+ :: $2}"
  80. fi
  81. }
  82. log_info() {
  83. INFO_COUNT=$((INFO_COUNT + 1))
  84. MAC_PANEL_FINDINGS+=("info|${CURRENT_SECTION}|$1|${2:-}")
  85. if [[ "$MAC_PANEL_ENABLED" -eq 1 ]]; then return 0; fi
  86. if [[ "${JSON_MODE:-0}" -eq 1 ]]; then
  87. printf '{"type":"check","section":"%s","label":"%s","status":"info","detail":"%s"}\n' \
  88. "$(_json_escape "$CURRENT_SECTION")" "$(_json_escape "$1")" "$(_json_escape "${2:-}")"
  89. else
  90. echo "[INFO] $1${2:+ :: $2}"
  91. fi
  92. }
  93. # In panel mode, suppress `note` and `section` so the body stays clean —
  94. # rendering happens at the end via panel_render.
  95. __MAC_ORIGINAL_NOTE=$(declare -f note || true)
  96. __MAC_ORIGINAL_SECTION=$(declare -f section || true)
  97. note() {
  98. [[ "$MAC_PANEL_ENABLED" -eq 1 ]] && return 0
  99. [[ "${JSON_MODE:-0}" -eq 1 ]] && return 0
  100. [[ "${QUIET:-0}" -eq 1 ]] && return 0
  101. echo "$@"
  102. }
  103. section() {
  104. CURRENT_SECTION="$1"
  105. if [[ "$MAC_PANEL_ENABLED" -eq 1 ]]; then return 0; fi
  106. if [[ "${JSON_MODE:-0}" -eq 1 ]]; then
  107. printf '{"type":"section","name":"%s"}\n' "$(_json_escape "$1")"
  108. else
  109. [[ "${QUIET:-0}" -eq 1 ]] || { echo; echo "=== $1 ==="; }
  110. fi
  111. }
  112. # Render the collected findings as a state-grouped tree panel.
  113. # Args:
  114. # $1 = script tag (e.g. "health-audit")
  115. # $2 = right indicator (e.g. hostname)
  116. panel_render() {
  117. if [[ "$MAC_PANEL_ENABLED" -ne 1 ]]; then
  118. # Not panel mode — just emit the standard summary block
  119. emit_summary
  120. return
  121. fi
  122. local tag="${1:-mac-ops}"
  123. local indicator="${2:-}"
  124. echo ""
  125. term_panel_open mac-ops "mac-ops · $tag" "$indicator"
  126. term_panel_vert
  127. term_summary_line "$((PASS_COUNT+FAIL_COUNT+WARN_COUNT+INFO_COUNT)) checks · $FAIL_COUNT fail · $WARN_COUNT warn"
  128. term_panel_vert
  129. # Render in order: fail, warn, pass (count only), info (count only)
  130. local order=(fail warn)
  131. local labels=(failing warning)
  132. local i
  133. for i in 0 1; do
  134. local st="${order[$i]}"
  135. local label="${labels[$i]}"
  136. local count=0
  137. local entries=()
  138. local entry
  139. for entry in ${MAC_PANEL_FINDINGS[@]+"${MAC_PANEL_FINDINGS[@]}"}; do
  140. [[ "${entry%%|*}" == "$st" ]] && { entries+=("$entry"); count=$((count+1)); }
  141. done
  142. [[ "$count" -eq 0 ]] && continue
  143. term_section "$st" "$label" "$count"
  144. local n=${#entries[@]}
  145. local idx=0
  146. for entry in "${entries[@]}"; do
  147. local rest="${entry#*|}"
  148. local section_name="${rest%%|*}"; rest="${rest#*|}"
  149. local lbl="${rest%%|*}"; rest="${rest#*|}"
  150. local detail="$rest"
  151. local connector
  152. if [[ $((idx + 1)) -eq $n ]]; then connector="$TERM_TREE_LAST"; else connector="$TERM_TREE_BRANCH"; fi
  153. local sec_short="${section_name%% *}"
  154. sec_short="${sec_short%.}"
  155. local one_line
  156. if [[ -n "$detail" ]]; then
  157. one_line="$(printf '%-40s %s' "$lbl" "$detail")"
  158. else
  159. one_line="$lbl"
  160. fi
  161. # 100-char hard truncate
  162. (( ${#one_line} > 100 )) && one_line="${one_line:0:97}..."
  163. printf '%s %s [%s] %s\n' \
  164. "$(term_color dim "$TERM_TREE_VERT")" \
  165. "$connector" \
  166. "$(term_color dim "$sec_short")" \
  167. "$one_line"
  168. done
  169. term_panel_vert
  170. idx=$((idx+1))
  171. done
  172. # Pass + info counts on a single line
  173. term_section "ok" "pass" "$PASS_COUNT"
  174. if [[ "$INFO_COUNT" -gt 0 ]]; then
  175. term_section "info" "info" "$INFO_COUNT"
  176. fi
  177. term_panel_vert
  178. # Determine overall health indicator
  179. local health_state="healthy"
  180. [[ "$WARN_COUNT" -gt 0 ]] && health_state="warning"
  181. [[ "$FAIL_COUNT" -gt 0 ]] && health_state="critical"
  182. local right_health
  183. right_health="$(term_health "$health_state" "$FAIL_COUNT fail · $WARN_COUNT warn")"
  184. term_panel_close "" "$right_health"
  185. echo ""
  186. # Also emit a plain SUMMARY line for parseability
  187. echo " PASS: $PASS_COUNT FAIL: $FAIL_COUNT WARN: $WARN_COUNT INFO: $INFO_COUNT"
  188. if [[ -n "$FIRST_FAIL" ]]; then
  189. echo " First failure: $FIRST_FAIL"
  190. fi
  191. }