Die Diagnose auf dem betroffenen Terminal war eindeutig: Zeile A (echte
UTF-8-Rahmenzeichen) zeichnete Linien, Zeile B (VT100-Umschaltung ESC(0)
nicht. Locale war bereits C.UTF-8, dialog war installiert - und die
Oberflaeche sah trotzdem falsch aus.
Grund: ncurses benutzt die VT100-Umschaltung AUCH mit UTF-8-Locale,
solange das terminfo sie anbietet - bei TERM=xterm der Fall. Die
Locale-Korrektur allein reichte deshalb nicht, und der Wechsel von
whiptail zu dialog machte es sogar schlechter, weil whiptail (slang)
bei UTF-8-Locale von sich aus Unicode zeichnet, ncurses aber nicht.
Abhilfe ist NCURSES_NO_UTF8_ACS=1. Mit dialog unter LANG=C.UTF-8 und
TERM=xterm gemessen:
ohne Schalter : 38 ESC(0, 0 Rahmenbytes, 182 rohe q
mit Schalter : 0 ESC(0, 220 Rahmenbytes, 0 rohe q
ui_init setzt den Schalter jetzt, sobald eine UTF-8-Locale gilt.
CMK_TUI_ACS=1 stellt das alte Verhalten wieder her - fuer Terminals, die
ESC(0 beherrschen, aber kein UTF-8.
tui-check zeigt den Stand des Schalters an und erklaert bei "A Linien,
B Buchstaben" jetzt, dass genau dieser Fall abgefangen wird und die
Oberflaeche trotzdem sauber aussehen muss.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
510 lines
19 KiB
Bash
510 lines
19 KiB
Bash
#!/usr/bin/env bash
|
|
# =====================================================================
|
|
# common.sh - gemeinsame Funktionen fuer cmk-manage.sh
|
|
# Wird per "source" eingebunden, nicht direkt ausgefuehrt.
|
|
# =====================================================================
|
|
|
|
# ---------------------------------------------------------------------
|
|
# Ausgabe / Logging
|
|
# ---------------------------------------------------------------------
|
|
if [ -t 1 ] && [ -z "${NO_COLOR:-}" ]; then
|
|
C_RED=$'\033[31m'; C_GRN=$'\033[32m'; C_YEL=$'\033[33m'
|
|
C_BLU=$'\033[34m'; C_BLD=$'\033[1m'; C_RST=$'\033[0m'
|
|
else
|
|
C_RED=; C_GRN=; C_YEL=; C_BLU=; C_BLD=; C_RST=
|
|
fi
|
|
|
|
log() { printf '%s[ %s ]%s %s\n' "$C_BLU" "INFO" "$C_RST" "$*"; }
|
|
ok() { printf '%s[ OK ]%s %s\n' "$C_GRN" "$C_RST" "$*"; }
|
|
warn() { printf '%s[ WARN ]%s %s\n' "$C_YEL" "$C_RST" "$*" >&2; }
|
|
err() { printf '%s[FEHLER]%s %s\n' "$C_RED" "$C_RST" "$*" >&2; }
|
|
die() { err "$*"; exit 1; }
|
|
debug() { [ -n "${VERBOSE:-}" ] && printf ' > %s\n' "$*" >&2 || true; }
|
|
|
|
# ---------------------------------------------------------------------
|
|
# .env laden
|
|
# ---------------------------------------------------------------------
|
|
load_env() {
|
|
local envfile="${1:-$PROJECT_DIR/.env}"
|
|
if [ -f "$envfile" ]; then
|
|
set -a
|
|
# shellcheck disable=SC1090
|
|
. "$envfile"
|
|
set +a
|
|
ENV_FILE="$envfile"
|
|
else
|
|
warn "Keine .env gefunden ($envfile) - es gelten die Standardwerte."
|
|
ENV_FILE=""
|
|
fi
|
|
# Rueckfallwerte, falls ein Schluessel in der .env fehlt.
|
|
# Die .env bleibt die massgebliche Konfiguration - hier steht nur,
|
|
# was gilt, wenn dort nichts eingetragen ist.
|
|
CMK_CONTAINER_NAME="${CMK_CONTAINER_NAME:-checkmk}"
|
|
NPM_CONTAINER_NAME="${NPM_CONTAINER_NAME:-nginx-proxy-manager}"
|
|
CMK_SITE_ID="${CMK_SITE_ID:-cmk}"
|
|
CMK_IMAGE="${CMK_IMAGE:-docker.io/checkmk/check-mk-raw:2.3.0-latest}"
|
|
NPM_ENABLED="${NPM_ENABLED:-on}"
|
|
BACKUP_AUTO="${BACKUP_AUTO:-off}"
|
|
PROXY_FORWARD_HOST="${PROXY_FORWARD_HOST:-127.0.0.1}"
|
|
PROXY_FORWARD_PORT="${PROXY_FORWARD_PORT:-5000}"
|
|
NPM_ADMIN_PORT="${NPM_ADMIN_PORT:-81}"
|
|
# Leer = automatisch aus dem Admin-Port ableiten
|
|
NPM_ADMIN_URL="${NPM_ADMIN_URL:-http://127.0.0.1:$NPM_ADMIN_PORT}"
|
|
BACKUP_DIR="$(abs_path "${BACKUP_DIR:-$PROJECT_DIR/backups}")"
|
|
IMAGE_DIR="$(abs_path "${IMAGE_DIR:-$PROJECT_DIR/images}")"
|
|
}
|
|
|
|
# relativen Pfad (./foo) am Projektverzeichnis ausrichten
|
|
abs_path() {
|
|
case "$1" in
|
|
/*) printf '%s\n' "$1" ;;
|
|
*) printf '%s\n' "$PROJECT_DIR/${1#./}" ;;
|
|
esac
|
|
}
|
|
|
|
# .env-Wert dauerhaft setzen
|
|
# env_set KEY WERT [allow_empty]
|
|
env_set() {
|
|
local key="$1" val="$2" allow_empty="${3:-}" f="${ENV_FILE:-$PROJECT_DIR/.env}"
|
|
[ -f "$f" ] || die "Keine .env vorhanden - kann '$key' nicht setzen."
|
|
# mehrzeilige Werte wuerden die .env zerstoeren
|
|
case "$val" in
|
|
*$'\n'*) die "Ungueltiger Wert fuer '$key' (mehrzeilig)." ;;
|
|
esac
|
|
[ -n "$val" ] || [ -n "$allow_empty" ] || die "Leerer Wert fuer '$key' - abgebrochen."
|
|
# Werte mit Leerzeichen in Anfuehrungszeichen setzen
|
|
case "$val" in
|
|
*" "*) case "$val" in \"*\") : ;; *) val="\"$val\"" ;; esac ;;
|
|
esac
|
|
if grep -qE "^[#[:space:]]*${key}=" "$f"; then
|
|
sed -i -E "s|^[#[:space:]]*${key}=.*|${key}=${val}|" "$f"
|
|
else
|
|
printf '%s=%s\n' "$key" "$val" >>"$f"
|
|
fi
|
|
export "$key=$val"
|
|
}
|
|
|
|
# ---------------------------------------------------------------------
|
|
# Container-Runtime erkennen (docker / podman)
|
|
# ---------------------------------------------------------------------
|
|
detect_runtime() {
|
|
if [ -n "${CMK_RUNTIME:-}" ] && command -v "$CMK_RUNTIME" >/dev/null 2>&1; then
|
|
RUNTIME="$CMK_RUNTIME"
|
|
elif [ -n "${CMK_RUNTIME:-}" ]; then
|
|
# Falsch gesetzter Wert darf das Werkzeug nicht unbenutzbar machen
|
|
warn "CMK_RUNTIME='$CMK_RUNTIME' ist nicht installiert - suche selbst.
|
|
Korrigieren mit: cmk-manage.sh config set CMK_RUNTIME auto"
|
|
CMK_RUNTIME=""
|
|
detect_runtime
|
|
return
|
|
elif command -v docker >/dev/null 2>&1 && docker info >/dev/null 2>&1; then
|
|
RUNTIME=docker
|
|
elif command -v podman >/dev/null 2>&1; then
|
|
RUNTIME=podman
|
|
elif command -v docker >/dev/null 2>&1; then
|
|
RUNTIME=docker # vorhanden, aber Daemon evtl. gestoppt
|
|
else
|
|
die "Weder docker noch podman gefunden. Bitte zuerst ./install.sh ausfuehren."
|
|
fi
|
|
debug "Runtime: $RUNTIME"
|
|
}
|
|
|
|
# Compose-Kommando ermitteln (als Array COMPOSE_CMD)
|
|
detect_compose() {
|
|
COMPOSE_CMD=()
|
|
if [ "$RUNTIME" = docker ]; then
|
|
if docker compose version >/dev/null 2>&1; then
|
|
COMPOSE_CMD=(docker compose)
|
|
elif command -v docker-compose >/dev/null 2>&1; then
|
|
COMPOSE_CMD=(docker-compose)
|
|
fi
|
|
else
|
|
if command -v podman-compose >/dev/null 2>&1; then
|
|
COMPOSE_CMD=(podman-compose)
|
|
elif podman compose version >/dev/null 2>&1; then
|
|
COMPOSE_CMD=(podman compose)
|
|
fi
|
|
fi
|
|
[ ${#COMPOSE_CMD[@]} -gt 0 ] || die "Kein Compose-Kommando gefunden (docker compose / podman-compose)."
|
|
debug "Compose: ${COMPOSE_CMD[*]}"
|
|
}
|
|
|
|
compose() {
|
|
detect_compose
|
|
( cd "$PROJECT_DIR" && "${COMPOSE_CMD[@]}" "$@" )
|
|
}
|
|
|
|
# ---------------------------------------------------------------------
|
|
# Container-Helfer
|
|
# ---------------------------------------------------------------------
|
|
# Ist der NGINX Proxy Manager eingeschaltet? (on/true/1/yes = ja)
|
|
npm_enabled() {
|
|
case "$(printf '%s' "${NPM_ENABLED:-on}" | tr 'A-Z' 'a-z')" in
|
|
on|true|1|yes|ja|y) return 0 ;;
|
|
*) return 1 ;;
|
|
esac
|
|
}
|
|
|
|
container_exists() { $RUNTIME container inspect "$1" >/dev/null 2>&1; }
|
|
container_running() { [ "$($RUNTIME container inspect -f '{{.State.Running}}' "$1" 2>/dev/null)" = "true" ]; }
|
|
|
|
require_cmk_running() {
|
|
container_exists "$CMK_CONTAINER_NAME" \
|
|
|| die "Container '$CMK_CONTAINER_NAME' existiert nicht. Zuerst: $0 up"
|
|
container_running "$CMK_CONTAINER_NAME" \
|
|
|| die "Container '$CMK_CONTAINER_NAME' laeuft nicht. Zuerst: $0 up"
|
|
}
|
|
|
|
# Kommando im Checkmk-Container als root ausfuehren
|
|
ct() { $RUNTIME exec "$CMK_CONTAINER_NAME" "$@"; }
|
|
# ... mit stdin
|
|
cti() { $RUNTIME exec -i "$CMK_CONTAINER_NAME" "$@"; }
|
|
|
|
# ---------------------------------------------------------------------
|
|
# OMD-Helfer (im Container)
|
|
# ---------------------------------------------------------------------
|
|
cmk_sites() {
|
|
ct sh -c 'ls -1 /omd/sites 2>/dev/null' | sed '/^$/d' || true
|
|
}
|
|
|
|
cmk_site_exists() {
|
|
cmk_sites | grep -qxF "$1"
|
|
}
|
|
|
|
cmk_site_version() {
|
|
ct omd version -b "$1" 2>/dev/null | tr -d '\r' || true
|
|
}
|
|
|
|
# 0 = laeuft komplett, 1 = teilweise, 2 = gestoppt
|
|
cmk_site_state() {
|
|
local rc=0
|
|
ct omd status --bare "$1" >/dev/null 2>&1 || rc=$?
|
|
echo "$rc"
|
|
}
|
|
|
|
cmk_site_state_text() {
|
|
case "$(cmk_site_state "$1")" in
|
|
0) echo "laeuft" ;;
|
|
1) echo "teilweise" ;;
|
|
*) echo "gestoppt" ;;
|
|
esac
|
|
}
|
|
|
|
cmk_versions_installed() {
|
|
ct omd versions -b 2>/dev/null | tr -d '\r' || ct sh -c 'ls -1 /omd/versions | grep -v "^default$"' 2>/dev/null || true
|
|
}
|
|
|
|
# ---------------------------------------------------------------------
|
|
# Diverses
|
|
# ---------------------------------------------------------------------
|
|
human_size() {
|
|
local b="${1:-0}"
|
|
if [ "$b" -ge 1073741824 ] 2>/dev/null; then awk -v b="$b" 'BEGIN{printf "%.1f GiB", b/1073741824}'
|
|
elif [ "$b" -ge 1048576 ] 2>/dev/null; then awk -v b="$b" 'BEGIN{printf "%.1f MiB", b/1048576}'
|
|
elif [ "$b" -ge 1024 ] 2>/dev/null; then awk -v b="$b" 'BEGIN{printf "%.1f KiB", b/1024}'
|
|
else echo "${b} B"; fi
|
|
}
|
|
|
|
file_size() { stat -c %s "$1" 2>/dev/null || echo 0; }
|
|
|
|
confirm() {
|
|
[ -n "${ASSUME_YES:-}" ] && return 0
|
|
local prompt="${1:-Fortfahren?} [j/N] " a
|
|
read -r -p "$prompt" a </dev/tty || return 1
|
|
case "$a" in [jJyY]*) return 0 ;; *) return 1 ;; esac
|
|
}
|
|
|
|
primary_ip() {
|
|
ip -4 route get 1.1.1.1 2>/dev/null | awk '{for(i=1;i<=NF;i++) if($i=="src"){print $(i+1);exit}}' \
|
|
|| hostname -I 2>/dev/null | awk '{print $1}'
|
|
}
|
|
|
|
# =====================================================================
|
|
# TUI-Helfer (dialog bevorzugt, sonst whiptail) - ncurses wie im mc
|
|
# =====================================================================
|
|
UI_BACKTITLE="Checkmk Stack Manager - Container-Runtime: ${RUNTIME:-?}"
|
|
|
|
# Groesse des Terminals ermitteln (mit Rueckfallwerten)
|
|
ui_term_size() {
|
|
UI_ROWS="$(tput lines 2>/dev/null || true)"
|
|
UI_COLS="$(tput cols 2>/dev/null || true)"
|
|
case "$UI_ROWS" in ''|*[!0-9]*) UI_ROWS=24 ;; esac
|
|
case "$UI_COLS" in ''|*[!0-9]*) UI_COLS=80 ;; esac
|
|
if [ "$UI_ROWS" -lt 10 ]; then UI_ROWS=24; fi
|
|
if [ "$UI_COLS" -lt 40 ]; then UI_COLS=80; fi
|
|
return 0
|
|
}
|
|
|
|
# Fenstergroesse aus Textlaenge, Anzahl der Eintraege und Terminalgroesse
|
|
# berechnen. Vorher waren die Werte fest - bei langem Text blieb dann nur
|
|
# eine Zeile fuer die Liste uebrig.
|
|
#
|
|
# ACHTUNG: hier bewusst mit if-Bloecken statt "[ ... ] && VAR=...".
|
|
# Letzteres liefert 1, wenn die Bedingung nicht zutrifft, und beendet
|
|
# unter "set -e" die Funktion bzw. das ganze Skript.
|
|
#
|
|
# ui_calc TEXT FELDER_JE_EINTRAG EINTRAEGE...
|
|
ui_calc() {
|
|
local text="$1" per="$2"; shift 2
|
|
ui_term_size
|
|
|
|
local tl maxw
|
|
tl="$(printf '%s\n' "$text" | wc -l)"
|
|
maxw="$(printf '%s\n' "$text" | awk '{ if (length($0) > m) m = length($0) } END { print m+0 }')"
|
|
|
|
# breiteste Zeile der Liste (Kennung und Beschriftung stehen nebeneinander)
|
|
local -a args=("$@")
|
|
local i=0 w itemw=0 n=0
|
|
while [ "$i" -lt "${#args[@]}" ]; do
|
|
w=$(( ${#args[$i]} + 4 ))
|
|
if [ $((i+1)) -lt "${#args[@]}" ]; then
|
|
w=$(( w + ${#args[$((i+1))]} ))
|
|
fi
|
|
if [ "$w" -gt "$itemw" ]; then itemw="$w"; fi
|
|
i=$(( i + per )); n=$(( n + 1 ))
|
|
done
|
|
|
|
# Breite: der breitere von Text und Liste, plus Rahmen
|
|
if [ "$maxw" -gt "$itemw" ]; then UI_W=$(( maxw + 10 )); else UI_W=$(( itemw + 10 )); fi
|
|
if [ "$UI_W" -lt 60 ]; then UI_W=60; fi
|
|
if [ "$UI_W" -gt $(( UI_COLS - 4 )) ]; then UI_W=$(( UI_COLS - 4 )); fi
|
|
|
|
# Listenhoehe: moeglichst alle Eintraege zeigen, ohne aus dem Bild zu laufen
|
|
local avail=$(( UI_ROWS - tl - 9 ))
|
|
if [ "$avail" -lt 3 ]; then avail=3; fi
|
|
if [ "$n" -lt "$avail" ]; then UI_MH="$n"; else UI_MH="$avail"; fi
|
|
if [ "$UI_MH" -lt 1 ]; then UI_MH=1; fi
|
|
|
|
UI_H=$(( tl + UI_MH + 8 ))
|
|
if [ "$UI_H" -gt $(( UI_ROWS - 2 )) ]; then UI_H=$(( UI_ROWS - 2 )); fi
|
|
if [ "$UI_H" -lt 10 ]; then UI_H=10; fi
|
|
return 0
|
|
}
|
|
|
|
# Hoehe fuer Fenster ohne Liste (Meldung, Abfrage, Eingabe)
|
|
ui_calc_text() { # text zusatzzeilen
|
|
local text="$1" extra="${2:-7}"
|
|
ui_term_size
|
|
local tl maxw
|
|
tl="$(printf '%s\n' "$text" | wc -l)"
|
|
maxw="$(printf '%s\n' "$text" | awk '{ if (length($0) > m) m = length($0) } END { print m+0 }')"
|
|
UI_W=$(( maxw + 10 ))
|
|
if [ "$UI_W" -lt 60 ]; then UI_W=60; fi
|
|
if [ "$UI_W" -gt $(( UI_COLS - 4 )) ]; then UI_W=$(( UI_COLS - 4 )); fi
|
|
UI_H=$(( tl + extra ))
|
|
if [ "$UI_H" -gt $(( UI_ROWS - 2 )) ]; then UI_H=$(( UI_ROWS - 2 )); fi
|
|
if [ "$UI_H" -lt 8 ]; then UI_H=8; fi
|
|
return 0
|
|
}
|
|
|
|
# ---------------------------------------------------------------------
|
|
# Rahmenzeichen in PuTTY & Co.
|
|
#
|
|
# Ohne UTF-8-Locale zeichnen dialog/whiptail die Rahmen ueber die alte
|
|
# VT100-Umschaltung "ESC(0". Terminals, die im UTF-8-Modus laufen -
|
|
# PuTTY in der Vorgabe -, ignorieren diese Umschaltung und zeigen
|
|
# stattdessen die rohen Buchstaben q, x, l, m.
|
|
#
|
|
# Nachgemessen: bei LC_ALL=C schickt whiptail eine ESC(0-Umschaltung und
|
|
# 91 rohe "q"; mit UTF-8-Locale keine einzige. Es genuegt, LC_CTYPE zu
|
|
# setzen - Sortierung und Zahlenformat bleiben damit unberuehrt.
|
|
# Zwingt ncurses zu echten Unicode-Rahmenzeichen.
|
|
#
|
|
# Auch mit UTF-8-Locale benutzt ncurses die VT100-Umschaltung ESC(0,
|
|
# solange das terminfo sie anbietet (bei TERM=xterm der Fall). Terminals
|
|
# im UTF-8-Modus - PuTTY - machen die nicht mit und zeigen q, x, l, m.
|
|
# Gemessen mit dialog unter LANG=C.UTF-8, TERM=xterm:
|
|
# ohne Schalter : 38 ESC(0, 0 Rahmenbytes, 182 rohe q
|
|
# mit Schalter : 0 ESC(0, 220 Rahmenbytes, 0 rohe q
|
|
# whiptail (slang) braucht ihn nicht, es zeichnet bei UTF-8-Locale schon
|
|
# von sich aus Unicode.
|
|
ui_use_unicode_acs() {
|
|
# CMK_TUI_ACS=1 erzwingt umgekehrt das alte Verhalten - fuer
|
|
# Terminals, die ESC(0 koennen, aber kein UTF-8.
|
|
if [ -n "${CMK_TUI_ACS:-}" ]; then
|
|
unset NCURSES_NO_UTF8_ACS 2>/dev/null || true
|
|
return 0
|
|
fi
|
|
export NCURSES_NO_UTF8_ACS=1
|
|
return 0
|
|
}
|
|
|
|
ui_fix_locale() {
|
|
[ -n "${UI_LOCALE_DONE:-}" ] && return 0
|
|
UI_LOCALE_DONE=1
|
|
UI_LOCALE_NOTE=""
|
|
case "${LC_ALL:-${LC_CTYPE:-${LANG:-}}}" in
|
|
*UTF-8*|*utf-8*|*UTF8*|*utf8*) ui_use_unicode_acs; return 0 ;;
|
|
esac
|
|
local l
|
|
for l in C.UTF-8 C.utf8 en_US.UTF-8 en_US.utf8 de_DE.UTF-8 de_DE.utf8; do
|
|
if locale -a 2>/dev/null | grep -qix "$l"; then
|
|
# LC_ALL sticht jede einzelne Kategorie aus - solange es auf
|
|
# einem Nicht-UTF-8-Wert steht, bliebe LC_CTYPE wirkungslos.
|
|
if [ -n "${LC_ALL:-}" ]; then unset LC_ALL; fi
|
|
export LC_CTYPE="$l"
|
|
UI_LOCALE_NOTE="LC_CTYPE auf $l gesetzt (war nicht UTF-8)"
|
|
ui_use_unicode_acs
|
|
return 0
|
|
fi
|
|
done
|
|
# Keine UTF-8-Locale vorhanden - dann bleibt nur ASCII
|
|
UI_ASCII=1
|
|
UI_LOCALE_NOTE="keine UTF-8-Locale gefunden - einfache Rahmen"
|
|
return 1
|
|
}
|
|
|
|
# Zusaetzliche Argumente, wenn nur ASCII-Rahmen moeglich sind
|
|
ui_ascii_args() {
|
|
UI_EXTRA=()
|
|
case "${CMK_TUI_ASCII:-}${UI_ASCII:-}" in
|
|
'') return 0 ;;
|
|
esac
|
|
if [ "${UI_BIN:-}" = dialog ] && dialog --ascii-lines --print-version >/dev/null 2>&1; then
|
|
UI_EXTRA=(--ascii-lines)
|
|
elif [ -z "${UI_ASCII_WARNED:-}" ]; then
|
|
UI_ASCII_WARNED=1
|
|
warn "Einfache Rahmen kann nur 'dialog' zeichnen - 'whiptail' kennt
|
|
die Option nicht. Installieren mit:
|
|
dnf install -y dialog (RHEL/Rocky/Alma)
|
|
apt-get install -y dialog (Debian/Ubuntu)"
|
|
fi
|
|
return 0
|
|
}
|
|
|
|
ui_init() {
|
|
ui_fix_locale || true
|
|
|
|
# Geometrie IMMER setzen - sonst laeuft das Skript unter "set -u" auf
|
|
# eine unbound variable, wenn UI_BIN von aussen vorgegeben wurde.
|
|
UI_H="${UI_H:-22}"; UI_W="${UI_W:-78}"; UI_MH="${UI_MH:-13}"
|
|
|
|
if [ -z "${UI_BIN:-}" ]; then
|
|
if command -v dialog >/dev/null 2>&1; then UI_BIN=dialog
|
|
elif command -v whiptail >/dev/null 2>&1; then UI_BIN=whiptail
|
|
else UI_BIN=""; fi
|
|
export DIALOGRC="${DIALOGRC:-}"
|
|
fi
|
|
|
|
# Einfache Rahmen koennen nur mit dialog gezeichnet werden. Ist das
|
|
# gewuenscht und dialog vorhanden, wird es dafuer vorgezogen.
|
|
if [ -n "${CMK_TUI_ASCII:-}${UI_ASCII:-}" ] && command -v dialog >/dev/null 2>&1; then
|
|
UI_BIN=dialog
|
|
fi
|
|
ui_ascii_args
|
|
}
|
|
|
|
ui_available() { ui_init; [ -n "$UI_BIN" ]; }
|
|
|
|
ui_require() {
|
|
ui_available || die "Fuer die TUI wird 'dialog' oder 'whiptail' benoetigt.
|
|
Debian/Ubuntu : apt-get install -y dialog
|
|
RHEL/Rocky : dnf install -y dialog"
|
|
UI_BACKTITLE="Checkmk Stack Manager - Runtime: $RUNTIME"
|
|
}
|
|
|
|
# =====================================================================
|
|
# Alle Dialoge laufen ueber genau drei Engstellen.
|
|
#
|
|
# Damit stimmt die Fenstergroesse automatisch - auch fuer Dialoge, die
|
|
# spaeter dazukommen. NEUE FENSTER BITTE IMMER UEBER DIESE DREI BAUEN
|
|
# und niemals "$UI_BIN" direkt aufrufen; "cmk-manage.sh selftest"
|
|
# prueft das nach.
|
|
#
|
|
# _ui_list Listenfenster (menu, checklist, radiolist) -> ui_calc
|
|
# _ui_box Textfenster (msgbox, yesno, inputbox, ...) -> ui_calc_text
|
|
# _ui_file Dateianzeige (textbox) -> volles Bild
|
|
# =====================================================================
|
|
|
|
# _ui_list WIDGET TITEL TEXT FELDER_JE_EINTRAG EINTRAEGE...
|
|
_ui_list() {
|
|
ui_init
|
|
local widget="$1" title="$2" text="$3" per="$4"; shift 4
|
|
ui_calc "$text" "$per" "$@"
|
|
"$UI_BIN" ${UI_EXTRA[@]+"${UI_EXTRA[@]}"} --backtitle "$UI_BACKTITLE" --title "$title" \
|
|
"$widget" "$text" "$UI_H" "$UI_W" "$UI_MH" "$@" 3>&1 1>&2 2>&3
|
|
}
|
|
|
|
# _ui_box WIDGET TITEL TEXT ZUSATZZEILEN [weitere Argumente]
|
|
# Widgets ohne Rueckgabe (msgbox, yesno, infobox) liefern nur den Status.
|
|
_ui_box() {
|
|
ui_init
|
|
local widget="$1" title="$2" text="$3" extra="$4"; shift 4
|
|
ui_calc_text "$text" "$extra"
|
|
case "$widget" in
|
|
--inputbox|--passwordbox)
|
|
"$UI_BIN" ${UI_EXTRA[@]+"${UI_EXTRA[@]}"} --backtitle "$UI_BACKTITLE" --title "$title" \
|
|
"$widget" "$text" "$UI_H" "$UI_W" "$@" 3>&1 1>&2 2>&3 ;;
|
|
*)
|
|
"$UI_BIN" ${UI_EXTRA[@]+"${UI_EXTRA[@]}"} --backtitle "$UI_BACKTITLE" --title "$title" \
|
|
"$widget" "$text" "$UI_H" "$UI_W" "$@" ;;
|
|
esac
|
|
}
|
|
|
|
# _ui_file TITEL DATEI - nutzt das Terminal so weit wie moeglich aus
|
|
_ui_file() {
|
|
ui_init
|
|
ui_term_size
|
|
"$UI_BIN" ${UI_EXTRA[@]+"${UI_EXTRA[@]}"} --backtitle "$UI_BACKTITLE" --title "$1" \
|
|
--textbox "$2" $(( UI_ROWS - 2 )) $(( UI_COLS - 4 ))
|
|
}
|
|
|
|
# ---------------------------------------------------------------------
|
|
# Die eigentlichen Dialoge - alle nur noch duenne Huellen
|
|
# ---------------------------------------------------------------------
|
|
ui_menu() { local t="$1" x="$2"; shift 2; _ui_list --menu "$t" "$x" 2 "$@"; }
|
|
ui_checklist() { local t="$1" x="$2"; shift 2; _ui_list --checklist "$t" "$x" 3 "$@"; }
|
|
ui_radiolist() { local t="$1" x="$2"; shift 2; _ui_list --radiolist "$t" "$x" 3 "$@"; }
|
|
|
|
ui_input() { _ui_box --inputbox "$1" "$2" 10 "${3:-}"; }
|
|
ui_password() { _ui_box --passwordbox "$1" "$2" 10; }
|
|
ui_msg() { _ui_box --msgbox "$1" "$2" 7; }
|
|
ui_yesno() { _ui_box --yesno "$1" "$2" 8; }
|
|
ui_info() { _ui_box --infobox "$1" "$2" 5; }
|
|
ui_text() { _ui_file "$1" "$2"; }
|
|
|
|
# Kommando ausfuehren, Ausgabe live in Datei, danach in Textbox anzeigen
|
|
ui_run() { # title cmd...
|
|
ui_init
|
|
local title="$1"; shift
|
|
local logf; logf="$(mktemp)"
|
|
ui_info "$title" "Bitte warten ...
|
|
|
|
$*"
|
|
if "$@" >"$logf" 2>&1; then
|
|
ui_text "$title - erfolgreich" "$logf"
|
|
rm -f "$logf"; return 0
|
|
else
|
|
ui_text "$title - FEHLGESCHLAGEN" "$logf"
|
|
rm -f "$logf"; return 1
|
|
fi
|
|
}
|
|
|
|
# ---------------------------------------------------------------------
|
|
# Dateiauswahl im Midnight-Commander-Stil (funktioniert auch mit whiptail)
|
|
# ---------------------------------------------------------------------
|
|
ui_pick_file() { # startdir [glob] -> Dateipfad auf stdout
|
|
ui_init
|
|
local dir="${1:-$PWD}" glob="${2:-*}" sel
|
|
dir="$(cd "$dir" 2>/dev/null && pwd || echo "$PWD")"
|
|
while :; do
|
|
local -a items=()
|
|
items+=(".." "<Verzeichnis hoeher>")
|
|
local d f
|
|
while IFS= read -r d; do
|
|
[ -n "$d" ] && items+=("$d/" "<DIR>")
|
|
done < <(find "$dir" -mindepth 1 -maxdepth 1 -type d -printf '%f\n' 2>/dev/null | sort)
|
|
while IFS= read -r f; do
|
|
[ -n "$f" ] || continue
|
|
items+=("$f" "$(human_size "$(file_size "$dir/$f")")")
|
|
done < <(find "$dir" -mindepth 1 -maxdepth 1 -type f -name "$glob" -printf '%f\n' 2>/dev/null | sort)
|
|
|
|
sel="$(ui_menu "Datei waehlen" "Verzeichnis: $dir\nFilter: $glob" "${items[@]}")" || return 1
|
|
case "$sel" in
|
|
"..") dir="$(dirname "$dir")" ;;
|
|
*/) dir="$dir/${sel%/}" ;;
|
|
*) printf '%s\n' "$dir/$sel"; return 0 ;;
|
|
esac
|
|
done
|
|
}
|