2025-01-26 19:25:43 +01:00
#!/bin/bash
2025-01-28 21:12:00 +01:00
# ==========================================================
# ProxMenu - A menu-driven script for Proxmox VE management
# ==========================================================
# Author : MacRimi
# Copyright : (c) 2024 MacRimi
# License : MIT (https://raw.githubusercontent.com/MacRimi/ProxMenux/main/LICENSE)
# 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-01-26 19:27:03 +01:00
2025-01-26 19:25:43 +01:00
# Repository and directory structure
REPO_URL = "https://raw.githubusercontent.com/MacRimi/ProxMenux/main"
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-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"
DARK_GRAY = "\033[38;5;244m"
DARK_GRAY = "\033[38;5;244m"
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"
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-01-27 16:24:28 +01: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-02-21 20:38:51 +01:00
echo -e " ${ TAB } ${ BOLD } ${ YW } ${ HOLD } ${ msg } ${ CL } "
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-02-25 00:26:45 +01:00
echo -e " ${ BFR } ${ TAB } ${ NV } ${ 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-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( ) {
if [ ! -f " $CACHE_FILE " ] ; then
mkdir -p " $( dirname " $CACHE_FILE " ) "
echo "{}" > " $CACHE_FILE "
fi
}
2025-01-31 22:10:53 +01:00
load_language( ) {
if [ -f " $CONFIG_FILE " ] ; then
LANGUAGE = $( jq -r '.language' " $CONFIG_FILE " )
fi
}
2025-01-26 19:25:43 +01:00
# Translation with cache and predefined terms
translate( ) {
local text = " $1 "
local dest_lang = " $LANGUAGE "
# If the language is English, return the original text without translating or caching
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
def translate_text( text, dest_lang) :
translator = Translator( )
context = '$TRANSLATION_CONTEXT'
try:
full_text = context + ' ' + text
result = translator.translate( full_text, dest = dest_lang) .text
# Remove context and any leading/trailing whitespace
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( )
return json.dumps( { 'success' : True, 'text' : translated} )
except Exception as e:
return json.dumps( { 'success' : False, 'error' : str( e) } )
print( translate_text( '$text' , '$dest_lang' ) )
" )
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-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
}