feat(cli): add height-aware compact outage schedule
All checks were successful
Test blackout notifier / test (push) Successful in 5s
Hourly blackout check / check (push) Successful in 5s

This commit is contained in:
2026-07-27 18:03:18 +03:30
parent 0b24d544d3
commit bf09d4d99c
3 changed files with 153 additions and 26 deletions

155
main.sh
View File

@@ -74,6 +74,7 @@ Options:
Environment:
NO_COLOR Disable ANSI colors when set
COLUMNS Override the detected output width
LINES Override the detected output height
Required commands: Bash 4.3+, curl, jq, and GNU date
EOF
@@ -415,6 +416,38 @@ terminal_columns() {
printf '%s\n' "$columns"
}
terminal_lines() {
local lines="${LINES:-}"
local terminal_size="" detected_lines=""
if [[ -z "$lines" ]] && command -v stty >/dev/null 2>&1; then
if [[ -r /dev/tty ]]; then
terminal_size="$(stty size 2>/dev/null </dev/tty || true)"
fi
if [[ -z "$terminal_size" && -t 2 ]]; then
terminal_size="$(stty size <&2 2>/dev/null || true)"
fi
if [[ -z "$terminal_size" && -t 0 ]]; then
terminal_size="$(stty size <&0 2>/dev/null || true)"
fi
read -r detected_lines _ <<<"$terminal_size"
if [[ "$detected_lines" =~ ^[0-9]+$ ]]; then
lines="$detected_lines"
fi
fi
if [[ -z "$lines" ]] && command -v tput >/dev/null 2>&1; then
lines="$(tput lines 2>/dev/null || true)"
fi
if [[ ! "$lines" =~ ^[0-9]+$ ]]; then
lines=24
elif ((lines < 10)); then
lines=10
fi
printf '%s\n' "$lines"
}
color_setup() {
RESET=""
BOLD=""
@@ -714,10 +747,29 @@ human_duration() {
printf '%s\n' "$output"
}
compact_reference() {
local bill_id="$1"
local outage_number="$2"
local width="$3"
local reference="$bill_id/$outage_number"
local bill_width
if ((${#reference} <= width)); then
printf '%s' "$reference"
elif ((width > ${#outage_number} + 2)); then
bill_width=$((width - ${#outage_number} - 2))
printf '~%s/%s' "${bill_id: -bill_width}" "$outage_number"
elif ((width > 1)); then
printf '%s~' "${reference:0:width-1}"
else
printf '~'
fi
}
render_event() {
local line="$1"
local now_epoch="$2"
local ignored phase start_epoch stop_epoch record type_label timing
local columns="$2"
local ignored phase start_epoch stop_epoch record type_label reference reference_width
local -a fields
IFS=$'\t' read -r ignored phase start_epoch stop_epoch record <<<"$line"
@@ -725,18 +777,24 @@ render_event() {
type_label="EMERGENCY"
[[ "${fields[8]}" == "true" ]] && type_label="PLANNED"
if [[ "$phase" == "ONGOING" ]]; then
timing="ends in $(human_duration "$((stop_epoch - now_epoch))")"
if ((columns >= 60)); then
reference_width=$((columns - 47))
reference="$(compact_reference "${fields[0]}" "${fields[1]}" "$reference_width")"
printf '%s%-9s%s %s%-9s%s %-10s %-11s %s\n' \
"$([[ "$phase" == "ONGOING" ]] && printf '%s' "$RED" || printf '%s' "$CYAN")" \
"$phase" "$RESET" \
"$([[ "$type_label" == "PLANNED" ]] && printf '%s' "$GREEN" || printf '%s' "$YELLOW")" \
"$type_label" "$RESET" "${fields[3]}" "${fields[4]}-${fields[5]}" "$reference"
else
timing="starts in $(human_duration "$((start_epoch - now_epoch))")"
reference_width=$((columns - 34))
((reference_width < 1)) && reference_width=1
reference="$(compact_reference "${fields[0]}" "${fields[1]}" "$reference_width")"
printf '%s%-4s%s %-5s %-10s %-11s %s\n' \
"$([[ "$phase" == "ONGOING" ]] && printf '%s' "$RED" || printf '%s' "$CYAN")" \
"$([[ "$phase" == "ONGOING" ]] && printf 'ON' || printf 'UP')" "$RESET" \
"$([[ "$type_label" == "PLANNED" ]] && printf 'PLAN' || printf 'EMERG')" \
"${fields[3]}" "${fields[4]}-${fields[5]}" "$reference"
fi
printf '%s%s%-10s%s %s%-9s%s %s\n' \
"$BOLD" "$([[ "$phase" == "ONGOING" ]] && printf '%s' "$RED" || printf '%s' "$CYAN")" \
"$phase" "$RESET" "$([[ "$type_label" == "PLANNED" ]] && printf '%s' "$GREEN" || printf '%s' "$YELLOW")" \
"$type_label" "$RESET" "$timing"
printf ' Date %s %s - %s\n' "${fields[3]}" "${fields[4]}" "${fields[5]}"
printf ' Reference bill %s / outage %s\n' "${fields[0]}" "${fields[1]}"
}
render_empty() {
@@ -780,7 +838,7 @@ draw_frame() {
if ((TERMINAL_ACTIVE)); then
cleared_frame="${frame//$'\n'/$'\033[K\n'}"
printf '\033[H%s\033[K\n\033[J' "$cleared_frame"
printf '\033[H%s\033[K\033[J' "$cleared_frame"
else
printf '%s\n' "$frame"
fi
@@ -806,13 +864,17 @@ render_dashboard() {
local now_epoch="$1"
local columns="${2:-}"
local countdown_bill_id="${3:-}"
local rule phase start_epoch stop_epoch record remaining banner
local schedule_line ignored
local lines="${4:-}"
local rule phase start_epoch stop_epoch record remaining banner banner_art
local banner_rows fixed_rows available_rows visible_count hidden_count index show_bill_heading=0
color_setup
if [[ -z "$columns" ]]; then
columns="$(terminal_columns)"
fi
if [[ -z "$lines" ]]; then
lines="$(terminal_lines)"
fi
if ((${#SCHEDULE[@]} == 0)); then
render_empty "$columns"
@@ -834,29 +896,69 @@ render_dashboard() {
remaining=$((start_epoch - now_epoch))
fi
banner="$(banner_duration "$remaining")"
if [[ -n "$countdown_bill_id" ]]; then
banner_art="$(print_large_countdown "$banner" "$columns")"
banner_rows="$(awk 'END { print NR }' <<<"$banner_art")"
[[ -n "$countdown_bill_id" ]] && show_bill_heading=1
fixed_rows=$((8 + banner_rows))
((show_bill_heading)) && fixed_rows=$((fixed_rows + 2))
if ((fixed_rows + 2 > lines)); then
banner_art="$banner"
banner_rows=1
fixed_rows=$((8 + banner_rows))
((show_bill_heading)) && fixed_rows=$((fixed_rows + 2))
if ((fixed_rows + 1 > lines)); then
show_bill_heading=0
fi
fi
if ((show_bill_heading)); then
center_line "COUNTDOWN FOR BILL $countdown_bill_id" "$columns"
printf '\n'
fi
printf '%s%s' "$BOLD" "$YELLOW"
print_large_countdown "$banner" "$columns"
printf '%s\n' "$banner_art"
printf '%s' "$RESET"
fi
printf '\n%s%s%s\n' "$CYAN" "$rule" "$RESET"
printf '\n%sOUTAGE SCHEDULE (%d)%s\n\n' "$BOLD" "${#SCHEDULE[@]}" "$RESET"
printf '\n%sOUTAGE SCHEDULE (%d)%s\n' "$BOLD" "${#SCHEDULE[@]}" "$RESET"
if ((columns >= 60)); then
printf '%s%-9s %-9s %-10s %-11s %s%s\n' \
"$BOLD" "STATUS" "TYPE" "DATE" "TIME" "BILL/OUTAGE" "$RESET"
else
printf '%s%-4s %-5s %-10s %-11s %s%s\n' \
"$BOLD" "ST" "TYPE" "DATE" "TIME" "REF" "$RESET"
fi
printf '%s%s%s\n' "$DIM" "$(repeat_character '-' "$columns")" "$RESET"
for schedule_line in "${SCHEDULE[@]}"; do
IFS=$'\t' read -r ignored phase start_epoch stop_epoch record <<<"$schedule_line"
render_event "$schedule_line" "$now_epoch"
printf '%s\n' "$DIM$(repeat_character '-' "$columns")$RESET"
fixed_rows=$((8 + ${banner_rows:-1}))
((show_bill_heading)) && fixed_rows=$((fixed_rows + 2))
available_rows=$((lines - fixed_rows - 1))
((available_rows < 0)) && available_rows=0
visible_count="${#SCHEDULE[@]}"
hidden_count=0
if ((visible_count > available_rows)); then
if ((available_rows > 0)); then
visible_count=$((available_rows - 1))
else
visible_count=0
fi
hidden_count=$((${#SCHEDULE[@]} - visible_count))
fi
for ((index = 0; index < visible_count; index++)); do
render_event "${SCHEDULE[index]}" "$columns"
done
if ((hidden_count && available_rows > 0)); then
printf '%s... %d more outage%s%s\n' \
"$DIM" "$hidden_count" "$([[ "$hidden_count" == 1 ]] || printf 's')" "$RESET"
fi
printf '%s%s%s\n' "$CYAN" "$rule" "$RESET"
}
main() {
local now_epoch next_refresh=0 refresh_minutes=10 refresh_seconds state_ready=0
local last_rendered=-1 next_transition=0 rebuild_schedule=0 frame key input_fd=0
local display_columns countdown_bill_id=""
local display_columns display_lines render_lines countdown_bill_id=""
while (($#)); do
case "$1" in
@@ -962,9 +1064,14 @@ main() {
fi
display_columns="$(terminal_columns)"
display_lines="$(terminal_lines)"
render_lines="$display_lines"
if [[ -n "$REFRESH_ERROR" ]] && ((render_lines > 12)); then
render_lines=$((render_lines - 2))
fi
frame="$(
if ((state_ready)); then
render_dashboard "$now_epoch" "$display_columns" "$countdown_bill_id"
render_dashboard "$now_epoch" "$display_columns" "$countdown_bill_id" "$render_lines"
else
render_unavailable "$display_columns"
fi