2025-01-26 19:25:43 +01:00
#!/bin/bash
2025-01-28 21:12:00 +01:00
# ==========================================================
2025-09-10 18:47:55 +02:00
# ProxMenux - A menu-driven script for Proxmox VE management
2025-01-28 21:12:00 +01:00
# ==========================================================
# Author : MacRimi
# Copyright : (c) 2024 MacRimi
2025-11-01 00:10:46 +01:00
# License : (CC BY-NC 4.0) (https://github.com/MacRimi/ProxMenux/blob/main/LICENSE)
2025-01-28 21:12:00 +01:00
# Version : 1.0
# Last Updated: 28/01/2025
# ==========================================================
2025-02-02 12:35:27 +01:00
# Description:
# This script provides a set of utility functions used across
# ProxMenux to facilitate Proxmox VE management.
#
# - Defines color codes for consistent output formatting.
# - Implements a spinner-based loading animation.
# - Provides standardized message functions (info, success, error, warning).
# - Handles translation with caching to reduce API requests.
# - Initializes and manages a local cache for improved performance.
# - Loads language settings from a configuration file.
#
# These utilities ensure a streamlined and uniform user experience
# across different ProxMenux scripts.
2025-03-07 19:01:44 +01:00
#
# This script incorporates elements from the
# Proxmox VE Post Install script from Proxmox VE Helper-Scripts.
#
# Copyright (c) Proxmox VE Helper-Scripts Community
# Script updates can be found at: https://github.com/community-scripts/ProxmoxVE
#
# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE
#
2025-02-02 12:35:27 +01:00
# ==========================================================
2025-01-26 19:27:03 +01:00
2025-01-26 19:25:43 +01:00
# Repository and directory structure
2025-11-02 02:55:19 +00:00
LOCAL_SCRIPTS = "/usr/local/share/proxmenux/scripts"
2025-01-26 19:25:43 +01:00
INSTALL_DIR = "/usr/local/bin"
BASE_DIR = "/usr/local/share/proxmenux"
CONFIG_FILE = " $BASE_DIR /config.json "
CACHE_FILE = " $BASE_DIR /cache.json "
LOCAL_VERSION_FILE = " $BASE_DIR /version.txt "
2025-02-25 17:44:17 +01:00
MENU_SCRIPT = "menu"
2025-01-26 19:25:43 +01:00
VENV_PATH = "/opt/googletrans-env"
2025-11-29 11:04:28 +01:00
COMPONENTS_STATUS_FILE = " $BASE_DIR /components_status.json "
2025-01-26 19:25:43 +01:00
2025-01-27 16:24:28 +01:00
2025-01-26 19:25:43 +01:00
# Translation context
TRANSLATION_CONTEXT = "Context: Technical message for Proxmox and IT. Translate:"
# Color and style definitions
2025-02-25 17:44:17 +01:00
NEON_PURPLE_BLUE = "\033[38;5;99m"
WHITE = "\033[38;5;15m"
RESET = "\033[0m"
DARK_GRAY = "\033[38;5;244m"
2025-08-08 18:26:32 +02:00
ORANGE = "\033[38;5;208m"
2025-01-27 16:24:28 +01:00
YW = "\033[33m"
YWB = "\033[1;33m"
GN = "\033[1;92m"
RD = "\033[01;31m"
CL = "\033[m"
2025-02-06 20:24:03 +01:00
BL = "\033[36m"
2025-03-17 16:22:55 +01:00
DGN = "\e[32m"
BGN = "\e[1;32m"
DEF = "\e[1;36m"
CUS = "\e[38;5;214m"
2025-02-06 20:24:03 +01:00
BOLD = "\033[1m"
2025-01-27 16:24:28 +01:00
BFR = "\\r\\033[K"
HOLD = "-"
2025-02-25 01:44:53 +01:00
BOR = " | "
2025-04-09 11:20:51 +02:00
CM = " ${ GN } ✓ ${ CL } "
2025-02-25 17:44:17 +01:00
TAB = " "
2025-01-26 19:25:43 +01:00
# Create and display spinner
spinner( ) {
local frames = ( '⠋' '⠙' '⠹' '⠸' '⠼' '⠴' '⠦' '⠧' '⠇' '⠏' )
local spin_i = 0
local interval = 0.1
printf "\e[?25l"
local color = " ${ YW } "
while true; do
printf " \r ${ color } %s ${ CL } " " ${ frames [spin_i] } "
spin_i = $(( ( spin_i + 1 ) % ${# frames [@] } ))
sleep " $interval "
done
}
2025-02-04 09:16:44 +01:00
2025-02-02 12:49:51 +01:00
# Function to simulate typing effect
type_text( ) {
local text = " $1 "
local delay = 0.05
for ( ( i = 0; i<${# text } ; i++) ) ; do
echo -n " ${ text : $i : 1 } "
sleep $delay
done
echo
}
2025-02-04 09:16:44 +01:00
# Stop the spinner if it is active
cleanup( ) {
2025-02-25 17:44:17 +01:00
if [ -n " $SPINNER_PID " ] && ps -p $SPINNER_PID > /dev/null; then
kill $SPINNER_PID > /dev/null
2025-02-04 09:16:44 +01:00
fi
2025-02-25 18:20:57 +01:00
sleep 1
2025-02-25 18:40:35 +01:00
if [ [ " $LANGUAGE " != "en" ] ] ; then
printf "\r\033[K"
2025-02-25 19:14:55 +01:00
printf "\e[?25h"
2025-02-25 18:40:35 +01:00
fi
2025-02-25 17:44:17 +01:00
}
# Display trnaslate message with spinner
msg_lang( ) {
local msg = " $1 "
echo -ne " ${ TAB } ${ YW } ${ HOLD } ${ msg } "
spinner &
SPINNER_PID = $!
2025-02-04 09:16:44 +01:00
}
2025-01-26 19:25:43 +01:00
# Display info message with spinner
msg_info( ) {
local msg = " $1 "
2025-02-02 12:15:39 +01:00
echo -ne " ${ TAB } ${ YW } ${ HOLD } ${ msg } "
2025-01-26 19:25:43 +01:00
spinner &
SPINNER_PID = $!
}
2025-02-04 09:16:44 +01:00
2025-02-08 17:36:41 +01:00
# Display info2 message
2025-02-03 22:45:07 +01:00
msg_info2( ) {
2025-02-08 17:36:41 +01:00
local msg = " $1 "
2025-11-02 02:55:19 +00:00
echo -e " ${ TAB } ${ BOLD } ${ YW } ${ HOLD } ${ msg } ${ CL } "
2025-02-08 17:36:41 +01:00
}
2025-08-10 21:43:05 +02:00
# Display info message with spinner
msg_info3( ) {
local msg = " $1 "
2025-08-10 21:56:07 +02:00
echo -ne " ${ TAB } ${ YW } ${ HOLD } ${ msg } ${ CL } "
2025-08-10 21:43:05 +02:00
}
2025-02-08 17:36:41 +01:00
# Display success message
msg_success( ) {
2025-02-08 17:41:06 +01:00
if [ -n " $SPINNER_PID " ] && ps -p $SPINNER_PID > /dev/null; then
kill $SPINNER_PID > /dev/null
fi
printf "\e[?25h"
2025-02-03 22:45:07 +01:00
local msg = " $1 "
2025-02-08 18:19:24 +01:00
echo -e " ${ TAB } ${ BOLD } ${ BL } ${ HOLD } ${ msg } ${ CL } "
2025-02-25 00:26:45 +01:00
echo -e ""
2025-02-03 22:45:07 +01:00
}
2025-02-04 09:16:44 +01:00
2025-02-25 01:44:53 +01:00
# Display title script
msg_title( ) {
local msg = " $1 "
echo -e "\n"
echo -e " ${ TAB } ${ BOLD } ${ HOLD } ${ BOR } ${ msg } ${ BOR } ${ HOLD } ${ CL } "
2025-02-25 02:04:44 +01:00
echo -e "\n"
2025-02-25 01:44:53 +01:00
}
2025-02-02 12:21:50 +01:00
# Display warning or highlighted information message
msg_warn( ) {
2025-02-03 15:39:08 +01:00
if [ -n " $SPINNER_PID " ] && ps -p $SPINNER_PID > /dev/null; then
kill $SPINNER_PID > /dev/null
fi
printf "\e[?25h"
2025-02-02 12:21:50 +01:00
local msg = " $1 "
2025-08-10 21:23:19 +02:00
echo -e " ${ BFR } ${ TAB } ${ CL } ${ YWB } ${ msg } ${ CL } "
2025-02-02 12:21:50 +01:00
}
2025-02-04 09:16:44 +01:00
2025-01-26 19:25:43 +01:00
# Display success message
msg_ok( ) {
if [ -n " $SPINNER_PID " ] && ps -p $SPINNER_PID > /dev/null; then
kill $SPINNER_PID > /dev/null
fi
printf "\e[?25h"
local msg = " $1 "
2025-01-27 16:24:28 +01:00
echo -e " ${ BFR } ${ TAB } ${ CM } ${ GN } ${ msg } ${ CL } "
2025-01-26 19:25:43 +01:00
}
2025-08-11 08:57:26 +02:00
msg_ok2( ) {
printf "\e[?25h"
local msg = " $1 "
echo -e " ${ BFR } ${ TAB } ${ CM } ${ GN } ${ msg } ${ CL } "
}
2025-02-04 09:16:44 +01:00
2025-01-26 19:25:43 +01:00
# Display error message
msg_error( ) {
2025-02-03 15:39:08 +01:00
if [ -n " $SPINNER_PID " ] && ps -p $SPINNER_PID > /dev/null; then
kill $SPINNER_PID > /dev/null
fi
printf "\e[?25h"
local msg = " $1 "
echo -e " ${ BFR } ${ TAB } ${ RD } [ERROR] ${ msg } ${ CL } "
2025-01-26 19:25:43 +01:00
}
2025-02-03 15:39:08 +01:00
2025-01-26 19:25:43 +01:00
# Initialize cache
initialize_cache( ) {
2025-07-04 19:40:38 +02:00
if [ [ " $LANGUAGE " != "en" ] ] ; then
if [ ! -f " $CACHE_FILE " ] ; then
mkdir -p " $( dirname " $CACHE_FILE " ) "
echo "{}" > " $CACHE_FILE "
fi
2025-01-26 19:25:43 +01:00
fi
}
2025-07-04 19:40:38 +02:00
# Load language
2025-01-31 22:10:53 +01:00
load_language( ) {
2025-07-04 19:40:38 +02:00
LANGUAGE = "en"
2025-01-31 22:10:53 +01:00
if [ -f " $CONFIG_FILE " ] ; then
2025-07-04 19:40:38 +02:00
lang_candidate = $( jq -r '.language // empty' " $CONFIG_FILE " 2>/dev/null)
if [ [ -n " $lang_candidate " && " $lang_candidate " != "null" ] ] ; then
LANGUAGE = " $lang_candidate "
fi
2025-01-31 22:10:53 +01:00
fi
}
2025-07-05 11:48:33 +02:00
########################################################
2025-07-05 11:55:41 +02:00
translate( ) {
2025-01-26 19:25:43 +01:00
local text = " $1 "
local dest_lang = " $LANGUAGE "
2025-07-05 11:55:41 +02:00
2025-01-26 19:25:43 +01:00
if [ " $dest_lang " = "en" ] ; then
echo " $text "
return
fi
if [ ! -s " $CACHE_FILE " ] || ! jq -e . " $CACHE_FILE " > /dev/null 2>& 1; then
echo "{}" > " $CACHE_FILE "
fi
local cached_translation = $( jq -r --arg text " $text " --arg lang " $dest_lang " '.[$text][$lang] // .[$text]["notranslate"] // empty' " $CACHE_FILE " )
if [ -n " $cached_translation " ] ; then
echo " $cached_translation "
return
fi
if [ ! -d " $VENV_PATH " ] ; then
echo " $text "
return
fi
source " $VENV_PATH /bin/activate "
local translated
translated = $( python3 -c "
from googletrans import Translator
import sys, json, re
2025-08-05 09:46:30 +02:00
def translate_text( text, dest_lang, context) :
2025-01-26 19:25:43 +01:00
translator = Translator( )
try:
full_text = context + ' ' + text
result = translator.translate( full_text, dest = dest_lang) .text
translated = re.sub( r'^.*?(Translate:|Traducir:|Traduire:|Übersetzen:|Tradurre:|Traduzir:|翻译:|翻訳:)' , '' , result, flags = re.IGNORECASE | re.DOTALL) .strip( )
translated = re.sub( r'^.*?(Context:|Contexto:|Contexte:|Kontext:|Contesto:|上下文:|コンテキスト:).*?:' , '' , translated, flags = re.IGNORECASE | re.DOTALL) .strip( )
2025-08-05 09:46:30 +02:00
print( json.dumps( { 'success' : True, 'text' : translated} ) )
2025-01-26 19:25:43 +01:00
except Exception as e:
2025-08-05 09:46:30 +02:00
print( json.dumps( { 'success' : False, 'error' : str( e) } ) )
translate_text(
json.loads( sys.argv[ 1] ) ,
sys.argv[ 2] ,
json.loads( sys.argv[ 3] )
)
" " $( jq -Rn --arg t " $text " '$t' ) " " $dest_lang " " $( jq -Rn --arg ctx " $TRANSLATION_CONTEXT " '$ctx' ) " )
2025-01-26 19:25:43 +01:00
deactivate
local translation_result = $( echo " $translated " | jq -r '.' )
local success = $( echo " $translation_result " | jq -r '.success' )
if [ " $success " = "true" ] ; then
translated = $( echo " $translation_result " | jq -r '.text' )
# Additional cleaning step
translated = $( echo " $translated " | sed -E 's/^(Context:|Contexto:|Contexte:|Kontext:|Contesto:|上下文:|コンテキスト:).*?(Translate:|Traducir:|Traduire:|Übersetzen:|Tradurre:|Traduzir:|翻译:|翻訳:)//gI' | sed 's/^ *//; s/ *$//' )
# Only cache if the language is not English
if [ " $dest_lang " != "en" ] ; then
local temp_cache = $( mktemp)
jq --arg text " $text " --arg lang " $dest_lang " --arg translated " $translated " '
if .[ $text ] = = null then .[ $text ] = { } else . end |
.[ $text ] [ $lang ] = $translated
' " $CACHE_FILE " > " $temp_cache " && mv " $temp_cache " " $CACHE_FILE "
fi
echo " $translated "
else
local error = $( echo " $translation_result " | jq -r '.error' )
echo " $text "
fi
}
2025-02-02 13:30:17 +01:00
2025-02-02 23:06:06 +01:00
2025-07-05 11:48:33 +02:00
########################################################
2025-02-25 01:44:53 +01:00
show_proxmenux_logo( ) {
clear
2025-02-25 01:02:40 +01:00
2025-02-25 01:44:53 +01:00
if [ [ -z " $SSH_TTY " && -z " $( who am i | awk '{print $NF}' | grep -E '([0-9]{1,3}\.){3}[0-9]{1,3}' ) " ] ] ; then
2025-02-25 01:02:40 +01:00
2025-02-25 01:44:53 +01:00
# Logo for terminal noVNC
2025-02-03 15:39:08 +01:00
2025-02-25 01:44:53 +01:00
LOGO = $( cat << "EOF"
\e [ 0m\e [ 38; 2; 61; 61; 61m▆\e [ 38; 2; 60; 60; 60m▄\e [ 38; 2; 54; 54; 54m▂\e [ 0m \e [ 38; 2; 0; 0; 0m \e [ 0m \e [ 38; 2; 54; 54; 54m▂\e [ 38; 2; 60; 60; 60m▄\e [ 38; 2; 61; 61; 61m▆\e [ 0m
\e [ 38; 2; 59; 59; 59; 48; 2; 62; 62; 62m▏ \e [ 38; 2; 61; 61; 61; 48; 2; 37; 37; 37m▇\e [ 0m\e [ 38; 2; 60; 60; 60m▅\e [ 38; 2; 56; 56; 56m▃\e [ 38; 2; 37; 37; 37m▁ \e [ 38; 2; 36; 36; 36m▁\e [ 38; 2; 56; 56; 56m▃\e [ 38; 2; 60; 60; 60m▅\e [ 38; 2; 61; 61; 61; 48; 2; 37; 37; 37m▇\e [ 48; 2; 62; 62; 62m \e [ 0m\e [ 7m\e [ 38; 2; 60; 60; 60m▁\e [ 0m
\e [ 38; 2; 59; 59; 59; 48; 2; 62; 62; 62m▏ \e [ 0m\e [ 7m\e [ 38; 2; 61; 61; 61m▂\e [ 0m\e [ 38; 2; 62; 62; 62; 48; 2; 61; 61; 61m┈\e [ 48; 2; 62; 62; 62m \e [ 48; 2; 61; 61; 61m┈\e [ 0m\e [ 38; 2; 60; 60; 60m▆\e [ 38; 2; 57; 57; 57m▄\e [ 38; 2; 48; 48; 48m▂\e [ 0m \e [ 38; 2; 47; 47; 47m▂\e [ 38; 2; 57; 57; 57m▄\e [ 38; 2; 60; 60; 60m▆\e [ 38; 2; 62; 62; 62; 48; 2; 61; 61; 61m┈\e [ 48; 2; 62; 62; 62m \e [ 48; 2; 61; 61; 61m┈\e [ 0m\e [ 7m\e [ 38; 2; 60; 60; 60m▂\e [ 38; 2; 57; 57; 57m▄\e [ 38; 2; 47; 47; 47m▆\e [ 0m \e [ 0m
\e [ 38; 2; 59; 59; 59; 48; 2; 62; 62; 62m▏ \e [ 0m\e [ 38; 2; 32; 32; 32m▏\e [ 7m\e [ 38; 2; 39; 39; 39m▇\e [ 38; 2; 57; 57; 57m▅\e [ 38; 2; 60; 60; 60m▃\e [ 0m\e [ 38; 2; 40; 40; 40; 48; 2; 61; 61; 61m▁\e [ 48; 2; 62; 62; 62m \e [ 38; 2; 54; 54; 54; 48; 2; 61; 61; 61m┊\e [ 48; 2; 62; 62; 62m \e [ 38; 2; 39; 39; 39; 48; 2; 61; 61; 61m▁\e [ 0m\e [ 7m\e [ 38; 2; 60; 60; 60m▃\e [ 38; 2; 57; 57; 57m▅\e [ 38; 2; 38; 38; 38m▇\e [ 0m \e [ 38; 2; 193; 60; 2m▃\e [ 38; 2; 217; 67; 2m▅\e [ 38; 2; 225; 70; 2m▇\e [ 0m
\e [ 38; 2; 59; 59; 59; 48; 2; 62; 62; 62m▏ \e [ 0m\e [ 38; 2; 32; 32; 32m▏\e [ 0m \e [ 38; 2; 203; 63; 2m▄\e [ 38; 2; 147; 45; 1m▂\e [ 0m \e [ 7m\e [ 38; 2; 55; 55; 55m▆\e [ 38; 2; 60; 60; 60m▄\e [ 38; 2; 61; 61; 61m▂\e [ 38; 2; 60; 60; 60m▄\e [ 38; 2; 55; 55; 55m▆\e [ 0m \e [ 38; 2; 144; 44; 1m▂\e [ 38; 2; 202; 62; 2m▄\e [ 38; 2; 219; 68; 2m▆\e [ 38; 2; 231; 72; 3; 48; 2; 226; 70; 2m┈\e [ 48; 2; 231; 72; 3m \e [ 48; 2; 225; 70; 2m▉\e [ 0m
\e [ 38; 2; 59; 59; 59; 48; 2; 62; 62; 62m▏ \e [ 0m\e [ 38; 2; 32; 32; 32m▏\e [ 7m\e [ 38; 2; 121; 37; 1m▉\e [ 0m\e [ 38; 2; 0; 0; 0; 48; 2; 231; 72; 3m \e [ 0m\e [ 38; 2; 221; 68; 2m▇\e [ 38; 2; 208; 64; 2m▅\e [ 38; 2; 212; 66; 2m▂\e [ 38; 2; 123; 37; 0m▁\e [ 38; 2; 211; 65; 2m▂\e [ 38; 2; 207; 64; 2m▅\e [ 38; 2; 220; 68; 2m▇\e [ 48; 2; 231; 72; 3m \e [ 38; 2; 231; 72; 3; 48; 2; 225; 70; 2m┈\e [ 0m\e [ 7m\e [ 38; 2; 221; 68; 2m▂\e [ 0m\e [ 38; 2; 44; 13; 0; 48; 2; 231; 72; 3m \e [ 38; 2; 231; 72; 3; 48; 2; 225; 70; 2m▉\e [ 0m
\e [ 38; 2; 59; 59; 59; 48; 2; 62; 62; 62m▏ \e [ 0m\e [ 38; 2; 32; 32; 32m▏\e [ 0m \e [ 7m\e [ 38; 2; 190; 59; 2m▅\e [ 38; 2; 216; 67; 2m▃\e [ 38; 2; 225; 70; 2m▁\e [ 0m\e [ 38; 2; 95; 29; 0; 48; 2; 231; 72; 3m \e [ 38; 2; 231; 72; 3; 48; 2; 230; 71; 2m┈\e [ 48; 2; 231; 72; 3m \e [ 0m\e [ 7m\e [ 38; 2; 225; 70; 2m▁\e [ 38; 2; 216; 67; 2m▃\e [ 38; 2; 191; 59; 2m▅\e [ 0m \e [ 38; 2; 0; 0; 0; 48; 2; 231; 72; 3m \e [ 38; 2; 231; 72; 3; 48; 2; 225; 70; 2m▉\e [ 0m
\e [ 38; 2; 59; 59; 59; 48; 2; 62; 62; 62m▏ \e [ 0m\e [ 38; 2; 32; 32; 32m▏ \e [ 0m \e [ 7m\e [ 38; 2; 172; 53; 1m▆\e [ 38; 2; 213; 66; 2m▄\e [ 38; 2; 219; 68; 2m▂\e [ 38; 2; 213; 66; 2m▄\e [ 38; 2; 174; 54; 2m▆\e [ 0m \e [ 38; 2; 0; 0; 0m \e [ 0m \e [ 38; 2; 0; 0; 0; 48; 2; 231; 72; 3m \e [ 38; 2; 231; 72; 3; 48; 2; 225; 70; 2m▉\e [ 0m
\e [ 38; 2; 59; 59; 59; 48; 2; 62; 62; 62m▏ \e [ 0m\e [ 38; 2; 32; 32; 32m▏ \e [ 0m \e [ 38; 2; 0; 0; 0; 48; 2; 231; 72; 3m \e [ 38; 2; 231; 72; 3; 48; 2; 225; 70; 2m▉\e [ 0m
\e [ 7m\e [ 38; 2; 52; 52; 52m▆\e [ 38; 2; 59; 59; 59m▄\e [ 38; 2; 61; 61; 61m▂\e [ 0m\e [ 38; 2; 31; 31; 31m▏ \e [ 0m \e [ 7m\e [ 38; 2; 228; 71; 2m▂\e [ 38; 2; 221; 69; 2m▄\e [ 38; 2; 196; 60; 2m▆\e [ 0m
2025-02-25 01:02:40 +01:00
EOF
2025-02-25 01:44:53 +01:00
)
2025-02-25 17:44:17 +01:00
2025-02-25 01:44:53 +01:00
TEXT = (
""
""
" ${ BOLD } ProxMenux ${ RESET } "
""
2025-03-06 23:32:53 +01:00
" ${ BOLD } ${ NEON_PURPLE_BLUE } An Interactive Menu for ${ RESET } "
2025-02-25 01:44:53 +01:00
" ${ BOLD } ${ NEON_PURPLE_BLUE } Proxmox VE management ${ RESET } "
""
""
""
""
)
2025-02-25 17:44:17 +01:00
2025-02-25 01:44:53 +01:00
mapfile -t logo_lines <<< " $LOGO "
for i in { 0..9} ; do
2025-02-25 17:44:17 +01:00
echo -e " ${ TAB } ${ logo_lines [i] } ${ WHITE } │ ${ RESET } ${ TEXT [i] } "
2025-02-25 01:44:53 +01:00
done
2025-02-25 19:34:19 +01:00
echo -e
2025-02-25 01:44:53 +01:00
else
2025-02-25 19:49:05 +01:00
# Logo for terminal SSH
2025-02-25 01:44:53 +01:00
TEXT = (
""
""
""
""
" ${ BOLD } ProxMenux ${ RESET } "
""
2025-03-06 23:32:53 +01:00
" ${ BOLD } ${ NEON_PURPLE_BLUE } An Interactive Menu for ${ RESET } "
2025-02-25 01:44:53 +01:00
" ${ BOLD } ${ NEON_PURPLE_BLUE } Proxmox VE management ${ RESET } "
""
""
""
""
""
""
)
LOGO = (
" ${ DARK_GRAY } ░░░░ ░░░░ ${ RESET } "
" ${ DARK_GRAY } ░░░░░░░ ░░░░░░ ${ RESET } "
" ${ DARK_GRAY } ░░░░░░░░░░░ ░░░░░░░ ${ RESET } "
" ${ DARK_GRAY } ░░░░ ░░░░░░ ░░░░░░ ${ ORANGE } ░░ ${ RESET } "
" ${ DARK_GRAY } ░░░░ ░░░░░░░ ${ ORANGE } ░░▒▒▒ ${ RESET } "
" ${ DARK_GRAY } ░░░░ ░░░ ${ ORANGE } ░▒▒▒▒▒▒▒ ${ RESET } "
" ${ DARK_GRAY } ░░░░ ${ ORANGE } ▒▒▒░ ░▒▒▒▒▒▒▒▒▒▒ ${ RESET } "
" ${ DARK_GRAY } ░░░░ ${ ORANGE } ░▒▒▒▒▒ ▒▒▒▒▒░░ ▒▒▒▒ ${ RESET } "
" ${ DARK_GRAY } ░░░░ ${ ORANGE } ░░▒▒▒▒▒▒▒░░ ▒▒▒▒ ${ RESET } "
" ${ DARK_GRAY } ░░░░ ${ ORANGE } ░░░ ▒▒▒▒ ${ RESET } "
" ${ DARK_GRAY } ░░░░ ${ ORANGE } ▒▒▒▒ ${ RESET } "
" ${ DARK_GRAY } ░░░░ ${ ORANGE } ▒▒▒░ ${ RESET } "
" ${ DARK_GRAY } ░░ ${ ORANGE } ░░ ${ RESET } "
)
for i in { 0..12} ; do
2025-02-25 17:44:17 +01:00
echo -e " ${ TAB } ${ LOGO [i] } │ ${ RESET } ${ TEXT [i] } "
2025-02-25 01:44:53 +01:00
done
2025-02-25 19:34:19 +01:00
echo -e
2025-02-25 01:44:53 +01:00
fi
2025-02-02 13:30:17 +01:00
}
2025-11-29 11:04:28 +01:00
2025-11-30 12:57:29 +01:00
########################################################
2025-11-29 11:04:28 +01:00
2025-11-30 15:31:19 +01:00
2025-11-29 11:04:28 +01:00
ensure_components_status_file( ) {
mkdir -p " $BASE_DIR "
if [ [ ! -f " $COMPONENTS_STATUS_FILE " ] ] || ! jq empty " $COMPONENTS_STATUS_FILE " >/dev/null 2>& 1; then
echo '{}' > " $COMPONENTS_STATUS_FILE "
fi
}
2025-11-30 12:57:29 +01:00
update_component_status( ) {
2025-11-29 11:04:28 +01:00
local comp = " $1 "
local stat = " $2 "
local ver = " $3 "
2025-11-30 15:31:19 +01:00
local category = " $4 "
local extra_json = " $5 "
if [ -z " $extra_json " ] ; then
extra_json = "{}"
fi
2025-11-29 11:04:28 +01:00
ensure_components_status_file
local ts
ts = $( date -u +"%Y-%m-%dT%H:%M:%SZ" )
local tmp_file
tmp_file = $( mktemp)
if jq --arg comp " $comp " \
--arg stat " $stat " \
--arg ver " $ver " \
2025-11-30 15:31:19 +01:00
--arg category " $category " \
2025-11-29 11:04:28 +01:00
--arg time " $ts " \
--argjson extra " $extra_json " \
2025-11-30 15:31:19 +01:00
'.[$comp] = ({status:$stat, version:$ver, category:$category, timestamp:$time} + $extra)' \
2025-11-29 11:04:28 +01:00
" $COMPONENTS_STATUS_FILE " > " $tmp_file " 2>/dev/null; then
mv " $tmp_file " " $COMPONENTS_STATUS_FILE "
else
2025-11-30 12:57:29 +01:00
rm -f " $tmp_file "
2025-11-29 11:04:28 +01:00
echo '{}' > " $COMPONENTS_STATUS_FILE "
fi
}
2025-11-30 17:12:11 +01:00
# ============================================
# Hybrid Dialog Functions (Web/Terminal)
# ============================================
# Detect if running in web mode
is_web_mode( ) {
[ [ " $EXECUTION_MODE " = = "web" ] ]
}
# Generate unique interaction ID
generate_interaction_id( ) {
echo " $( date +%s%N) _ $$ "
}
# Wait for web response with timeout
wait_for_web_response( ) {
local interaction_id = " $1 "
local response_file = " /tmp/proxmenux_response_ ${ interaction_id } "
local timeout = 300 # 5 minutes
local elapsed = 0
while [ [ ! -f " $response_file " ] ] && [ [ $elapsed -lt $timeout ] ] ; do
sleep 0.1
elapsed = $(( elapsed + 1 ))
done
if [ [ -f " $response_file " ] ] ; then
cat " $response_file "
rm -f " $response_file "
return 0
else
echo ""
return 1
fi
}
# Hybrid menu function
hybrid_menu( ) {
local title = " $1 "
local text = " $2 "
local height = " ${ 3 :- 20 } "
local width = " ${ 4 :- 70 } "
local menu_height = " ${ 5 :- 10 } "
shift 5
local items = ( " $@ " )
if is_web_mode; then
local interaction_id = $( generate_interaction_id)
local options_json = "["
for ( ( i = 0; i<${# items [@] } ; i += 2) ) ; do
if [ $i -gt 0 ] ; then options_json += "," ; fi
options_json += " {\"value\":\" ${ items [i] } \",\"label\":\" ${ items [i+1] } \"} "
done
options_json += "]"
echo " WEB_INTERACTION:menu: ${ interaction_id } : $( echo -n " $title " | base64 -w0) : $( echo -n " $text " | base64 -w0) : $options_json " >> " ${ WEB_LOG :- /tmp/proxmenux_web.log } "
wait_for_web_response " $interaction_id "
else
dialog --title " $title " --menu " $text " " $height " " $width " " $menu_height " " ${ items [@] } " 3>& 1 1>& 2 2>& 3
fi
}
# Hybrid yes/no prompt
hybrid_yesno( ) {
local title = " $1 "
local text = " $2 "
local height = " ${ 3 :- 10 } "
local width = " ${ 4 :- 60 } "
if is_web_mode; then
local interaction_id = $( generate_interaction_id)
echo " WEB_INTERACTION:yesno: ${ interaction_id } : $( echo -n " $title " | base64 -w0) : $( echo -n " $text " | base64 -w0) " >> " ${ WEB_LOG :- /tmp/proxmenux_web.log } "
local response = $( wait_for_web_response " $interaction_id " )
[ [ " $response " = = "yes" ] ] && return 0 || return 1
else
dialog --title " $title " --yesno " $text " " $height " " $width "
fi
}
# Hybrid message box
hybrid_msgbox( ) {
local title = " $1 "
local text = " $2 "
local height = " ${ 3 :- 10 } "
local width = " ${ 4 :- 60 } "
if is_web_mode; then
local interaction_id = $( generate_interaction_id)
echo " WEB_INTERACTION:msgbox: ${ interaction_id } : $( echo -n " $title " | base64 -w0) : $( echo -n " $text " | base64 -w0) " >> " ${ WEB_LOG :- /tmp/proxmenux_web.log } "
wait_for_web_response " $interaction_id " > /dev/null
else
dialog --title " $title " --msgbox " $text " " $height " " $width "
fi
}
# Hybrid input box
hybrid_inputbox( ) {
local title = " $1 "
local text = " $2 "
local height = " ${ 3 :- 10 } "
local width = " ${ 4 :- 60 } "
local default = " ${ 5 :- } "
if is_web_mode; then
local interaction_id = $( generate_interaction_id)
echo " WEB_INTERACTION:inputbox: ${ interaction_id } : $( echo -n " $title " | base64 -w0) : $( echo -n " $text " | base64 -w0) : $( echo -n " $default " | base64 -w0) " >> " ${ WEB_LOG :- /tmp/proxmenux_web.log } "
wait_for_web_response " $interaction_id "
else
dialog --title " $title " --inputbox " $text " " $height " " $width " " $default " 3>& 1 1>& 2 2>& 3
fi
2025-11-30 17:47:30 +01:00
}
# Hybrid whiptail menu (used during installation - doesn't hide terminal output)
hybrid_whiptail_menu( ) {
local title = " $1 "
local text = " $2 "
local height = " ${ 3 :- 20 } "
local width = " ${ 4 :- 70 } "
local menu_height = " ${ 5 :- 10 } "
shift 5
local items = ( " $@ " )
if is_web_mode; then
local interaction_id = $( generate_interaction_id)
local options_json = "["
for ( ( i = 0; i<${# items [@] } ; i += 2) ) ; do
if [ $i -gt 0 ] ; then options_json += "," ; fi
options_json += " {\"value\":\" ${ items [i] } \",\"label\":\" ${ items [i+1] } \"} "
done
options_json += "]"
echo " WEB_INTERACTION:menu: ${ interaction_id } : $( echo -n " $title " | base64 -w0) : $( echo -n " $text " | base64 -w0) : $options_json " >> " ${ WEB_LOG :- /tmp/proxmenux_web.log } "
wait_for_web_response " $interaction_id "
else
whiptail --title " $title " --menu " $text " " $height " " $width " " $menu_height " " ${ items [@] } " 3>& 1 1>& 2 2>& 3
fi
}
# Hybrid whiptail yes/no (used during installation)
hybrid_whiptail_yesno( ) {
local title = " $1 "
local text = " $2 "
local height = " ${ 3 :- 10 } "
local width = " ${ 4 :- 70 } "
if is_web_mode; then
local interaction_id = $( generate_interaction_id)
echo " WEB_INTERACTION:yesno: ${ interaction_id } : $( echo -n " $title " | base64 -w0) : $( echo -n " $text " | base64 -w0) " >> " ${ WEB_LOG :- /tmp/proxmenux_web.log } "
local response = $( wait_for_web_response " $interaction_id " )
[ [ " $response " = = "yes" ] ] && return 0 || return 1
else
whiptail --title " $title " --yesno " $text " " $height " " $width "
fi
}
# Hybrid whiptail message box (used during installation)
hybrid_whiptail_msgbox( ) {
local title = " $1 "
local text = " $2 "
local height = " ${ 3 :- 10 } "
local width = " ${ 4 :- 70 } "
if is_web_mode; then
local interaction_id = $( generate_interaction_id)
echo " WEB_INTERACTION:msgbox: ${ interaction_id } : $( echo -n " $title " | base64 -w0) : $( echo -n " $text " | base64 -w0) " >> " ${ WEB_LOG :- /tmp/proxmenux_web.log } "
wait_for_web_response " $interaction_id " > /dev/null
else
whiptail --title " $title " --msgbox " $text " " $height " " $width "
fi
2025-11-30 17:12:11 +01:00
}