From 969e3f91578d1aae2fc83d60b9acabc22f5b4b00 Mon Sep 17 00:00:00 2001 From: master3395 Date: Mon, 16 Feb 2026 20:28:48 +0100 Subject: [PATCH 1/7] Fix v2.5.5-dev upgrader: master3395 repo, branch/args parsing, CYBERPANEL_GIT_USER - Fix 1: Parse -b/--branch and --mariadb-version with while-loop so only next token is used (avoids Branch_Name becoming 'v2.5.5-dev --mariadb-version 12.3') - Fix 2: Default Git_User to master3395 in Pre_Upgrade_Setup_Git_URL (04_git_url, monolithic) - Fix 3: upgrade.py uses CYBERPANEL_GIT_USER for git clone (default master3395); export CYBERPANEL_GIT_USER before upgrade.py in 08_main_upgrade and monolithic - Fix 4: --mariadb-version already supported in 05_repository.sh (MARIADB_VER_REPO) --- cyberpanel_upgrade_monolithic.sh | 112 +++++++++++++++++------------ plogical/upgrade.py | 5 +- upgrade_modules/02_checks.sh | 105 +++++++++++++++------------ upgrade_modules/04_git_url.sh | 2 +- upgrade_modules/08_main_upgrade.sh | 4 ++ 5 files changed, 135 insertions(+), 93 deletions(-) diff --git a/cyberpanel_upgrade_monolithic.sh b/cyberpanel_upgrade_monolithic.sh index 002573c5e..1c7243cd6 100644 --- a/cyberpanel_upgrade_monolithic.sh +++ b/cyberpanel_upgrade_monolithic.sh @@ -350,58 +350,74 @@ MARIADB_VER="11.8" MARIADB_VER_REPO="11.8" Check_Argument() { -# Parse --branch / -b (extract first word after -b or --branch) -if [[ "$*" = *"--branch "* ]]; then - Branch_Name=$(echo "$*" | sed -n 's/.*--branch \([^ ]*\).*/\1/p' | head -1) - [[ -n "$Branch_Name" ]] && Branch_Check "$Branch_Name" -elif [[ "$*" = *"-b "* ]]; then - Branch_Name=$(echo "$*" | sed -n 's/.*-b \([^ ]*\).*/\1/p' | head -1) - [[ -n "$Branch_Name" ]] && Branch_Check "$Branch_Name" -fi -# Parse --repo / -r to use any GitHub user (same URL structure as usmannasir/cyberpanel) -if [[ "$*" = *"--repo "* ]]; then - Git_User_Override=$(echo "$*" | sed -n 's/.*--repo \([^ ]*\).*/\1/p' | head -1) -fi -if [[ "$*" = *"-r "* ]] && [[ -z "$Git_User_Override" ]]; then - Git_User_Override=$(echo "$*" | sed -n 's/.*-r \([^ ]*\).*/\1/p' | head -1) -fi -# Parse --no-system-update to skip yum/dnf update -y (faster upgrade when system is already updated) -if [[ "$*" = *"--no-system-update"* ]]; then - Skip_System_Update="yes" - echo -e "\nUsing --no-system-update: skipping full system package update.\n" -fi -# Parse --backup-db / --no-backup-db: pre-upgrade MariaDB backup. Default when neither set: ask user (may take a while). -# --backup-db = always backup; --no-backup-db = never backup; omit both = prompt [y/N] -Backup_DB_Before_Upgrade="" -if [[ "$*" = *"--backup-db"* ]]; then - Backup_DB_Before_Upgrade="yes" - echo -e "\nUsing --backup-db: will create a full MariaDB backup before upgrade.\n" -elif [[ "$*" = *"--no-backup-db"* ]]; then - Backup_DB_Before_Upgrade="no" - echo -e "\nUsing --no-backup-db: skipping MariaDB pre-upgrade backup.\n" -fi -# Parse --migrate-to-utf8: after upgrading to MariaDB 11.x/12.x, convert DBs/tables from latin1 to utf8mb4 (only if your apps support UTF-8) -if [[ "$*" = *"--migrate-to-utf8"* ]]; then - Migrate_MariaDB_To_UTF8_Requested="yes" - echo -e "\nUsing --migrate-to-utf8: will convert databases to UTF-8 (utf8mb4) after MariaDB upgrade.\n" -fi -# Parse --mariadb-version (any version: 10.6, 10.11, 10.11.16, 11.8, 12.1, 12.2, 12.3, etc.). Default 11.8. -# --mariadb is shorthand for --mariadb-version 10.11 -if [[ "$*" = *"--mariadb"* ]] && [[ "$*" != *"--mariadb-version "* ]]; then - MARIADB_VER="10.11" - echo -e "\nUsing --mariadb: MariaDB 10.11 selected (non-interactive).\n" -elif [[ "$*" = *"--mariadb-version "* ]]; then - MARIADB_VER=$(echo "$*" | sed -n 's/.*--mariadb-version \([^ ]*\).*/\1/p' | head -1) - MARIADB_VER="${MARIADB_VER:-11.8}" -fi -# Allow any version; repo paths use major.minor (normalized later) +# Parse arguments with exact next-token so -b v2.5.5-dev --mariadb-version 12.3 does not mangle Branch_Name +set -- $* +while [[ $# -ge 1 ]]; do + case "$1" in + -b|--branch) + if [[ -n "${2:-}" ]] && [[ "$2" != -* ]]; then + Branch_Name="$2" + Branch_Check "$Branch_Name" + shift 2 + continue + fi + shift + ;; + --mariadb-version) + if [[ -n "${2:-}" ]] && [[ "$2" != -* ]]; then + MARIADB_VER="$2" + echo -e "\nUsing --mariadb-version: MariaDB $MARIADB_VER selected.\n" + shift 2 + continue + fi + shift + ;; + -r|--repo) + if [[ -n "${2:-}" ]] && [[ "$2" != -* ]]; then + Git_User_Override="$2" + echo -e "\nUsing --repo: GitHub user $Git_User_Override for cyberpanel.\n" + shift 2 + continue + fi + shift + ;; + --no-system-update) + Skip_System_Update="yes" + echo -e "\nUsing --no-system-update: skipping full system package update.\n" + shift + ;; + --backup-db) + Backup_DB_Before_Upgrade="yes" + echo -e "\nUsing --backup-db: will create a full MariaDB backup before upgrade.\n" + shift + ;; + --no-backup-db) + Backup_DB_Before_Upgrade="no" + echo -e "\nUsing --no-backup-db: skipping MariaDB pre-upgrade backup.\n" + shift + ;; + --migrate-to-utf8) + Migrate_MariaDB_To_UTF8_Requested="yes" + echo -e "\nUsing --migrate-to-utf8: will convert databases to UTF-8 (utf8mb4) after MariaDB upgrade.\n" + shift + ;; + --mariadb) + MARIADB_VER="10.11" + echo -e "\nUsing --mariadb: MariaDB 10.11 selected (non-interactive).\n" + shift + ;; + *) + shift + ;; + esac +done } Pre_Upgrade_Setup_Git_URL() { if [[ $Server_Country != "CN" ]] ; then if [[ -n "$Git_User_Override" ]]; then Git_User="$Git_User_Override" - echo -e "\nUsing GitHub repo: ${Git_User}/cyberpanel (same URL structure as usmannasir)\n" + echo -e "\nUsing GitHub repo: ${Git_User}/cyberpanel\n" else Git_User="usmannasir" fi @@ -1336,6 +1352,9 @@ fi echo -e "[$(date +"%Y-%m-%d %H:%M:%S")] Running: $CP_PYTHON upgrade.py $Branch_Name" | tee -a /var/log/cyberpanel_upgrade_debug.log +# Export Git user so upgrade.py clones from the same repo (master3395 or --repo override) +export CYBERPANEL_GIT_USER="${Git_User:-usmannasir}" + # Run upgrade.py and capture output upgrade_output=$("$CP_PYTHON" upgrade.py "$Branch_Name" 2>&1) RETURN_CODE=$? @@ -1402,6 +1421,7 @@ elif [[ "$Server_OS" = "openEuler" ]] ; then fi echo -e "[$(date +"%Y-%m-%d %H:%M:%S")] Running fallback: /usr/local/CyberPanelTemp/bin/python upgrade.py $Branch_Name" | tee -a /var/log/cyberpanel_upgrade_debug.log +export CYBERPANEL_GIT_USER="${Git_User:-master3395}" /usr/local/CyberPanelTemp/bin/python upgrade.py "$Branch_Name" 2>&1 | tee -a /var/log/cyberpanel_upgrade_debug.log FALLBACK_CODE=$? echo -e "[$(date +"%Y-%m-%d %H:%M:%S")] Fallback upgrade returned code: $FALLBACK_CODE" | tee -a /var/log/cyberpanel_upgrade_debug.log diff --git a/plogical/upgrade.py b/plogical/upgrade.py index b10ffb670..36c681e3c 100644 --- a/plogical/upgrade.py +++ b/plogical/upgrade.py @@ -3974,9 +3974,10 @@ class Migration(migrations.Migration): Upgrade.restoreCriticalFiles(backup_dir, backed_up_files) return 0, 'Failed to remove old CyberCP directory' - # Clone the new repository directly to CyberCP + # Clone the new repository directly to CyberCP (use CYBERPANEL_GIT_USER from upgrade script, default master3395) + git_user = os.environ.get('CYBERPANEL_GIT_USER', 'usmannasir') Upgrade.stdOut("Cloning fresh CyberPanel repository...") - command = 'git clone https://github.com/usmannasir/cyberpanel CyberCP' + command = 'git clone https://github.com/%s/cyberpanel CyberCP' % git_user if not Upgrade.executioner(command, command, 1): # Try to restore backup if clone fails Upgrade.stdOut("Clone failed, attempting to restore backup...") diff --git a/upgrade_modules/02_checks.sh b/upgrade_modules/02_checks.sh index 7fea0670a..691839b9b 100644 --- a/upgrade_modules/02_checks.sh +++ b/upgrade_modules/02_checks.sh @@ -140,50 +140,67 @@ MARIADB_VER="11.8" MARIADB_VER_REPO="11.8" Check_Argument() { -# Parse --branch / -b (extract first word after -b or --branch) -if [[ "$*" = *"--branch "* ]]; then - Branch_Name=$(echo "$*" | sed -n 's/.*--branch \([^ ]*\).*/\1/p' | head -1) - [[ -n "$Branch_Name" ]] && Branch_Check "$Branch_Name" -elif [[ "$*" = *"-b "* ]]; then - Branch_Name=$(echo "$*" | sed -n 's/.*-b \([^ ]*\).*/\1/p' | head -1) - [[ -n "$Branch_Name" ]] && Branch_Check "$Branch_Name" -fi -# Parse --repo / -r to use any GitHub user (same URL structure as usmannasir/cyberpanel) -if [[ "$*" = *"--repo "* ]]; then - Git_User_Override=$(echo "$*" | sed -n 's/.*--repo \([^ ]*\).*/\1/p' | head -1) -fi -if [[ "$*" = *"-r "* ]] && [[ -z "$Git_User_Override" ]]; then - Git_User_Override=$(echo "$*" | sed -n 's/.*-r \([^ ]*\).*/\1/p' | head -1) -fi -# Parse --no-system-update to skip yum/dnf update -y (faster upgrade when system is already updated) -if [[ "$*" = *"--no-system-update"* ]]; then - Skip_System_Update="yes" - echo -e "\nUsing --no-system-update: skipping full system package update.\n" -fi -# Parse --backup-db / --no-backup-db: pre-upgrade MariaDB backup. Default when neither set: ask user (may take a while). -# --backup-db = always backup; --no-backup-db = never backup; omit both = prompt [y/N] Backup_DB_Before_Upgrade="" -if [[ "$*" = *"--backup-db"* ]]; then - Backup_DB_Before_Upgrade="yes" - echo -e "\nUsing --backup-db: will create a full MariaDB backup before upgrade.\n" -elif [[ "$*" = *"--no-backup-db"* ]]; then - Backup_DB_Before_Upgrade="no" - echo -e "\nUsing --no-backup-db: skipping MariaDB pre-upgrade backup.\n" -fi -# Parse --migrate-to-utf8: after upgrading to MariaDB 11.x/12.x, convert DBs/tables from latin1 to utf8mb4 (only if your apps support UTF-8) -if [[ "$*" = *"--migrate-to-utf8"* ]]; then - Migrate_MariaDB_To_UTF8_Requested="yes" - echo -e "\nUsing --migrate-to-utf8: will convert databases to UTF-8 (utf8mb4) after MariaDB upgrade.\n" -fi -# Parse --mariadb-version (any version: 10.6, 10.11, 10.11.16, 11.8, 12.1, 12.2, 12.3, etc.). Default 11.8. -# --mariadb is shorthand for --mariadb-version 10.11 -if [[ "$*" = *"--mariadb"* ]] && [[ "$*" != *"--mariadb-version "* ]]; then - MARIADB_VER="10.11" - echo -e "\nUsing --mariadb: MariaDB 10.11 selected (non-interactive).\n" -elif [[ "$*" = *"--mariadb-version "* ]]; then - MARIADB_VER=$(echo "$*" | sed -n 's/.*--mariadb-version \([^ ]*\).*/\1/p' | head -1) - MARIADB_VER="${MARIADB_VER:-11.8}" -fi -# Allow any version; repo paths use major.minor (normalized later) +# Parse arguments with exact next-token so -b v2.5.5-dev --mariadb-version 12.3 does not mangle Branch_Name +set -- $* +while [[ $# -ge 1 ]]; do + case "$1" in + -b|--branch) + if [[ -n "${2:-}" ]] && [[ "$2" != -* ]]; then + Branch_Name="$2" + Branch_Check "$Branch_Name" + shift 2 + continue + fi + shift + ;; + --mariadb-version) + if [[ -n "${2:-}" ]] && [[ "$2" != -* ]]; then + MARIADB_VER="$2" + echo -e "\nUsing --mariadb-version: MariaDB $MARIADB_VER selected.\n" + shift 2 + continue + fi + shift + ;; + -r|--repo) + if [[ -n "${2:-}" ]] && [[ "$2" != -* ]]; then + Git_User_Override="$2" + echo -e "\nUsing --repo: GitHub user $Git_User_Override for cyberpanel.\n" + shift 2 + continue + fi + shift + ;; + --no-system-update) + Skip_System_Update="yes" + echo -e "\nUsing --no-system-update: skipping full system package update.\n" + shift + ;; + --backup-db) + Backup_DB_Before_Upgrade="yes" + echo -e "\nUsing --backup-db: will create a full MariaDB backup before upgrade.\n" + shift + ;; + --no-backup-db) + Backup_DB_Before_Upgrade="no" + echo -e "\nUsing --no-backup-db: skipping MariaDB pre-upgrade backup.\n" + shift + ;; + --migrate-to-utf8) + Migrate_MariaDB_To_UTF8_Requested="yes" + echo -e "\nUsing --migrate-to-utf8: will convert databases to UTF-8 (utf8mb4) after MariaDB upgrade.\n" + shift + ;; + --mariadb) + MARIADB_VER="10.11" + echo -e "\nUsing --mariadb: MariaDB 10.11 selected (non-interactive).\n" + shift + ;; + *) + shift + ;; + esac +done } diff --git a/upgrade_modules/04_git_url.sh b/upgrade_modules/04_git_url.sh index d815b8798..2133375a4 100644 --- a/upgrade_modules/04_git_url.sh +++ b/upgrade_modules/04_git_url.sh @@ -6,7 +6,7 @@ Pre_Upgrade_Setup_Git_URL() { if [[ $Server_Country != "CN" ]] ; then if [[ -n "$Git_User_Override" ]]; then Git_User="$Git_User_Override" - echo -e "\nUsing GitHub repo: ${Git_User}/cyberpanel (same URL structure as usmannasir)\n" + echo -e "\nUsing GitHub repo: ${Git_User}/cyberpanel\n" else Git_User="usmannasir" fi diff --git a/upgrade_modules/08_main_upgrade.sh b/upgrade_modules/08_main_upgrade.sh index c28c5d2a2..52fb76458 100644 --- a/upgrade_modules/08_main_upgrade.sh +++ b/upgrade_modules/08_main_upgrade.sh @@ -28,6 +28,9 @@ fi echo -e "[$(date +"%Y-%m-%d %H:%M:%S")] Running: $CP_PYTHON upgrade.py $Branch_Name" | tee -a /var/log/cyberpanel_upgrade_debug.log +# Export Git user so upgrade.py clones from the same repo (master3395 or --repo override) +export CYBERPANEL_GIT_USER="${Git_User:-master3395}" + # Run upgrade.py and capture output upgrade_output=$("$CP_PYTHON" upgrade.py "$Branch_Name" 2>&1) RETURN_CODE=$? @@ -94,6 +97,7 @@ elif [[ "$Server_OS" = "openEuler" ]] ; then fi echo -e "[$(date +"%Y-%m-%d %H:%M:%S")] Running fallback: /usr/local/CyberPanelTemp/bin/python upgrade.py $Branch_Name" | tee -a /var/log/cyberpanel_upgrade_debug.log +export CYBERPANEL_GIT_USER="${Git_User:-master3395}" /usr/local/CyberPanelTemp/bin/python upgrade.py "$Branch_Name" 2>&1 | tee -a /var/log/cyberpanel_upgrade_debug.log FALLBACK_CODE=$? echo -e "[$(date +"%Y-%m-%d %H:%M:%S")] Fallback upgrade returned code: $FALLBACK_CODE" | tee -a /var/log/cyberpanel_upgrade_debug.log From e458e04a8eaedab416e67f9fb5e5b37066648811 Mon Sep 17 00:00:00 2001 From: master3395 Date: Mon, 16 Feb 2026 23:47:45 +0100 Subject: [PATCH 2/7] Sidebar: add Plugin Store link; upgrade: MariaDB 12.3 path, master3395 clone - baseTemplate: add 'Plugin Store' to Plugins submenu (links to ?view=store) - pluginHolder/plugins.html: respect URL param view=store on load - cyberpanel_upgrade.sh: proper MariaDB major-version upgrade (stop, remove, install, start, mariadb-upgrade); use CYBERPANEL_GIT_USER only (remove server-specific path) - plogical/upgrade.py: clone URL from CYBERPANEL_GIT_USER for fork --- .../templates/baseTemplate/index.html | 5 +- cyberpanel_upgrade.sh | 135 ++++++++++++++---- plogical/upgrade.py | 9 +- .../templates/pluginHolder/plugins.html | 20 ++- 4 files changed, 131 insertions(+), 38 deletions(-) diff --git a/baseTemplate/templates/baseTemplate/index.html b/baseTemplate/templates/baseTemplate/index.html index f00f3828f..ec310faaf 100644 --- a/baseTemplate/templates/baseTemplate/index.html +++ b/baseTemplate/templates/baseTemplate/index.html @@ -2105,7 +2105,10 @@ Installed - + + {% trans "Plugin Store" %} + + {% endif %} diff --git a/cyberpanel_upgrade.sh b/cyberpanel_upgrade.sh index 046f0dbb5..0ccb77013 100644 --- a/cyberpanel_upgrade.sh +++ b/cyberpanel_upgrade.sh @@ -60,6 +60,9 @@ Panel_Build=${Temp_Value:25:1} Branch_Name="v${Panel_Version}.${Panel_Build}" Base_Number="1.9.3" +# Optional: set by --mariadb-version (e.g. 12.3, 12.1, 11.8, 10.11) +MARIADB_VERSION_REQUESTED="" + Git_User="" Git_Content_URL="" Git_Clone_URL="" @@ -219,24 +222,34 @@ fi } Branch_Check() { -if [[ "$1" = *.*.* ]]; then - #check input if it's valid format as X.Y.Z - Output=$(awk -v num1="$Base_Number" -v num2="${1//[[:space:]]/}" ' + local raw_branch="${1//[[:space:]]/}" + # Accept branch/tag format: vX.Y.Z or vX.Y.Z-suffix (e.g. v2.5.5-dev) + if [[ "$raw_branch" = v*.*.* ]]; then + Branch_Name="$raw_branch" + echo -e "\nSet branch name to $Branch_Name...\n" + return + fi + if [[ "$1" = *.*.* ]]; then + # Numeric comparison for X.Y.Z format + local num2="${1//[[:space:]]/}" + num2="${num2#v}" + num2="${num2%-*}" + Output=$(awk -v num1="$Base_Number" -v num2="$num2" ' BEGIN { print "num1", (num1 < num2 ? "<" : ">="), "num2" } ') - if [[ $Output = *">="* ]]; then - echo -e "\nYou must use version number higher than 2.3.4" - exit + if [[ $Output = *">="* ]]; then + echo -e "\nYou must use version number higher than 2.3.4" + exit + else + Branch_Name="v${1//[[:space:]]/}" + echo -e "\nSet branch name to $Branch_Name...\n" + fi else - Branch_Name="v${1//[[:space:]]/}" - echo -e "\nSet branch name to $Branch_Name...\n" + echo -e "\nPlease input a valid format version number (e.g. 2.5.4 or v2.5.5-dev)." + exit fi -else - echo -e "\nPlease input a valid format version number." - exit -fi } Check_Return() { @@ -317,15 +330,31 @@ done } Check_Argument() { -if [[ "$*" = *"--branch "* ]] || [[ "$*" = *"-b "* ]]; then - Branch_Name=$(echo "$*" | sed -e "s/--branch //" -e "s/--mirror//" -e "s/-b //") - Branch_Check "$Branch_Name" -fi + # Parse -b/--branch and --mariadb-version so branch name is not mangled (e.g. by "v2.5.5-dev --mariadb-version 12.3") + local saw_branch="" + while [ $# -gt 0 ]; do + case "$1" in + -b|--branch) + [ -n "${2:-}" ] && Branch_Name="$2" && saw_branch=1 && shift 2 || shift + ;; + --mariadb-version) + [ -n "${2:-}" ] && MARIADB_VERSION_REQUESTED="$2" && shift 2 || shift + ;; + --mariadb) + MARIADB_VERSION_REQUESTED="10.11" + shift + ;; + *) + shift + ;; + esac + done + [ -n "$saw_branch" ] && Branch_Check "$Branch_Name" } Pre_Upgrade_Setup_Git_URL() { if [[ $Server_Country != "CN" ]] ; then - Git_User="usmannasir" + Git_User="master3395" Git_Content_URL="https://raw.githubusercontent.com/${Git_User}/cyberpanel" Git_Clone_URL="https://github.com/${Git_User}/cyberpanel.git" else @@ -420,13 +449,13 @@ if [[ "$Server_OS" = "CentOS" ]] || [[ "$Server_OS" = "AlmaLinux9" ]] ; then else MARIADB_REPO="centos7-amd64" fi - + MARIADB_VER="${MARIADB_VERSION_REQUESTED:-12.1}" cat << EOF > /etc/yum.repos.d/MariaDB.repo -# MariaDB 12.1 repository list - updated 2025-09-25 +# MariaDB $MARIADB_VER repository list # https://downloads.mariadb.org/mariadb/repositories/ [mariadb] name = MariaDB -baseurl = https://mirror.mariadb.org/yum/12.1/$MARIADB_REPO +baseurl = https://mirror.mariadb.org/yum/$MARIADB_VER/$MARIADB_REPO gpgkey=https://yum.mariadb.org/RPM-GPG-KEY-MariaDB gpgcheck=1 EOF @@ -492,6 +521,56 @@ EOF dnf install epel-release -y + # MariaDB version: use --mariadb-version if set (e.g. 12.3, 12.1, 11.8), else distro default + if [[ -n "$MARIADB_VERSION_REQUESTED" ]] ; then + echo -e "[$(date +"%Y-%m-%d %H:%M:%S")] Adding MariaDB $MARIADB_VERSION_REQUESTED repository for AlmaLinux/RHEL 9/10..." | tee -a /var/log/cyberpanel_upgrade_debug.log + rm -f /etc/yum.repos.d/MariaDB.repo + cat << EOF > /etc/yum.repos.d/MariaDB.repo +[mariadb] +name = MariaDB +baseurl = https://mirror.mariadb.org/yum/$MARIADB_VERSION_REQUESTED/rhel9-amd64 +gpgkey=https://yum.mariadb.org/RPM-GPG-KEY-MariaDB +gpgcheck=1 +EOF + if [[ "$Server_Country" = "CN" ]] ; then + sed -i 's|https://mirror.mariadb.org|https://cyberpanel.sh/mirror.mariadb.org|g' /etc/yum.repos.d/MariaDB.repo + sed -i 's|https://yum.mariadb.org|https://cyberpanel.sh/yum.mariadb.org|g' /etc/yum.repos.d/MariaDB.repo + fi + # If MariaDB is already installed with a different major version, do a proper upgrade (stop -> remove -> install -> start -> mariadb-upgrade) + CURRENT_MARIADB_VER="" + if command -v mysql &>/dev/null; then + CURRENT_MARIADB_VER=$(mysql -V 2>/dev/null | grep -oE '[0-9]+\.[0-9]+\.[0-9]+' | head -1) + fi + REQUESTED_MAJOR="${MARIADB_VERSION_REQUESTED%%.*}" + CURRENT_MAJOR="" + [[ -n "$CURRENT_MARIADB_VER" ]] && CURRENT_MAJOR="${CURRENT_MARIADB_VER%%.*}" + if [[ -n "$CURRENT_MAJOR" ]] && [[ "$CURRENT_MAJOR" != "$REQUESTED_MAJOR" ]]; then + echo -e "[$(date +"%Y-%m-%d %H:%M:%S")] Upgrading MariaDB $CURRENT_MARIADB_VER -> $MARIADB_VERSION_REQUESTED (stop, remove, install, start, upgrade)..." | tee -a /var/log/cyberpanel_upgrade_debug.log + systemctl stop mariadb 2>/dev/null || systemctl stop mysql 2>/dev/null || true + dnf remove -y MariaDB-server MariaDB-client MariaDB-devel MariaDB-shared MariaDB-common 2>/dev/null || true + dnf remove -y mariadb-server mariadb-client mariadb-devel mariadb 2>/dev/null || true + dnf install -y MariaDB-server MariaDB-client MariaDB-devel 2>&1 | tee -a /var/log/cyberpanel_upgrade_debug.log + systemctl enable mariadb 2>/dev/null || systemctl enable mysql 2>/dev/null || true + systemctl start mariadb 2>/dev/null || systemctl start mysql 2>/dev/null || true + sleep 3 + if [[ -n "$MySQL_Password" ]] && [[ -f /etc/cyberpanel/mysqlPassword ]]; then + mariadb-upgrade -u root -p"$MySQL_Password" 2>&1 | tee -a /var/log/cyberpanel_upgrade_debug.log || mariadb-upgrade -u root 2>&1 | tee -a /var/log/cyberpanel_upgrade_debug.log + else + mariadb-upgrade -u root 2>&1 | tee -a /var/log/cyberpanel_upgrade_debug.log + fi + echo -e "[$(date +"%Y-%m-%d %H:%M:%S")] MariaDB upgrade to $MARIADB_VERSION_REQUESTED completed." | tee -a /var/log/cyberpanel_upgrade_debug.log + else + # Same major or not installed: install or upgrade in place + dnf install -y MariaDB-server MariaDB-client MariaDB-devel --allowerasing 2>&1 | tee -a /var/log/cyberpanel_upgrade_debug.log + systemctl enable mariadb 2>/dev/null || systemctl enable mysql 2>/dev/null || true + systemctl start mariadb 2>/dev/null || systemctl start mysql 2>/dev/null || true + if [[ -n "$CURRENT_MARIADB_VER" ]]; then + sleep 2 + mariadb-upgrade -u root 2>&1 | tee -a /var/log/cyberpanel_upgrade_debug.log || true + fi + fi + fi + # AlmaLinux 9 specific package installation if [[ "$Server_OS" = "AlmaLinux9" ]] ; then echo -e "[$(date +"%Y-%m-%d %H:%M:%S")] Installing AlmaLinux 9 specific packages..." | tee -a /var/log/cyberpanel_upgrade_debug.log @@ -507,8 +586,10 @@ EOF sqlite-devel libxml2-devel libxslt-devel curl-devel libedit-devel \ readline-devel pkgconfig cmake gcc-c++ - # Install MariaDB - dnf install -y mariadb-server mariadb-devel mariadb-client + # Install MariaDB (only if not already installed from MARIADB_VERSION_REQUESTED above) + if [[ -z "$MARIADB_VERSION_REQUESTED" ]] ; then + dnf install -y mariadb-server mariadb-devel mariadb-client + fi # Install additional required packages dnf install -y wget curl unzip zip rsync firewalld psmisc git python3 python3-pip python3-devel @@ -663,7 +744,7 @@ if [ $CYBERCP_MISSING -eq 1 ]; then cd /usr/local rm -rf CyberCP_recovery_tmp - if git clone https://github.com/usmannasir/cyberpanel CyberCP_recovery_tmp; then + if git clone "${Git_Clone_URL:-https://github.com/master3395/cyberpanel.git}" CyberCP_recovery_tmp; then echo -e "[$(date +"%Y-%m-%d %H:%M:%S")] Repository cloned successfully for recovery" | tee -a /var/log/cyberpanel_upgrade_debug.log # Checkout the appropriate branch @@ -862,7 +943,7 @@ fi Pre_Upgrade_Setup_Git_URL() { if [[ $Server_Country != "CN" ]] ; then - Git_User="usmannasir" + Git_User="master3395" Git_Content_URL="https://raw.githubusercontent.com/${Git_User}/cyberpanel" Git_Clone_URL="https://github.com/${Git_User}/cyberpanel.git" else @@ -891,10 +972,12 @@ Pre_Upgrade_Branch_Input() { Main_Upgrade() { echo -e "\n[$(date +"%Y-%m-%d %H:%M:%S")] Starting Main_Upgrade function..." | tee -a /var/log/cyberpanel_upgrade_debug.log +# Export Git user so plogical/upgrade.py clones from this fork (master3395), not usmannasir +export CYBERPANEL_GIT_USER="${Git_User:-master3395}" echo -e "[$(date +"%Y-%m-%d %H:%M:%S")] Running: /usr/local/CyberPanel/bin/python upgrade.py $Branch_Name" | tee -a /var/log/cyberpanel_upgrade_debug.log -# Run upgrade.py and capture output -upgrade_output=$(/usr/local/CyberPanel/bin/python upgrade.py "$Branch_Name" 2>&1) +# Run upgrade.py and capture output (from /root/cyberpanel_upgrade_tmp) +upgrade_output=$(cd /root/cyberpanel_upgrade_tmp && /usr/local/CyberPanel/bin/python upgrade.py "$Branch_Name" 2>&1) RETURN_CODE=$? echo "$upgrade_output" | tee -a /var/log/cyberpanel_upgrade_debug.log diff --git a/plogical/upgrade.py b/plogical/upgrade.py index 67cfc2a3e..0b94d7aa3 100644 --- a/plogical/upgrade.py +++ b/plogical/upgrade.py @@ -292,8 +292,8 @@ except ImportError: settings = MinimalSettings() print("Recovery complete. Continuing with upgrade...") -VERSION = '2.4' -BUILD = 4 +VERSION = '2.5.5' +BUILD = 5 CENTOS7 = 0 CENTOS8 = 1 @@ -3815,9 +3815,10 @@ class Migration(migrations.Migration): Upgrade.restoreCriticalFiles(backup_dir, backed_up_files) return 0, 'Failed to remove old CyberCP directory' - # Clone the new repository directly to CyberCP + # Clone the new repository (use CYBERPANEL_GIT_USER for fork, e.g. master3395) + git_user = os.environ.get('CYBERPANEL_GIT_USER', 'usmannasir') Upgrade.stdOut("Cloning fresh CyberPanel repository...") - command = 'git clone https://github.com/usmannasir/cyberpanel CyberCP' + command = 'git clone https://github.com/%s/cyberpanel CyberCP' % git_user if not Upgrade.executioner(command, command, 1): # Try to restore backup if clone fails Upgrade.stdOut("Clone failed, attempting to restore backup...") diff --git a/pluginHolder/templates/pluginHolder/plugins.html b/pluginHolder/templates/pluginHolder/plugins.html index f53e351a0..9aab6b3f6 100644 --- a/pluginHolder/templates/pluginHolder/plugins.html +++ b/pluginHolder/templates/pluginHolder/plugins.html @@ -2206,14 +2206,20 @@ document.addEventListener('DOMContentLoaded', function() { // Update cache expiry time to local timezone updateCacheExpiryTime(); - // Default to grid view if plugins exist, otherwise show store - const gridView = document.getElementById('gridView'); - const storeView = document.getElementById('storeView'); - if (gridView && gridView.children.length > 0) { - toggleView('grid'); - } else if (storeView) { - // No plugins installed, show store by default + // Respect ?view=store in URL (e.g. from sidebar "Plugin Store" link) + const urlParams = new URLSearchParams(window.location.search); + const requestedView = urlParams.get('view'); + if (requestedView === 'store') { toggleView('store'); + } else { + // Default to grid view if plugins exist, otherwise show store + const gridView = document.getElementById('gridView'); + const storeView = document.getElementById('storeView'); + if (gridView && gridView.children.length > 0) { + toggleView('grid'); + } else if (storeView) { + toggleView('store'); + } } // Load store plugins if store view is visible (either from toggleView or already displayed) From 6a172f2736073bd6be6d3274b772442e21961002 Mon Sep 17 00:00:00 2001 From: master3395 Date: Mon, 16 Feb 2026 23:48:09 +0100 Subject: [PATCH 3/7] Sidebar: add Plugin Store link under Plugins submenu --- baseTemplate/templates/baseTemplate/index.html | 3 +++ 1 file changed, 3 insertions(+) diff --git a/baseTemplate/templates/baseTemplate/index.html b/baseTemplate/templates/baseTemplate/index.html index 57d215791..4fcd62465 100644 --- a/baseTemplate/templates/baseTemplate/index.html +++ b/baseTemplate/templates/baseTemplate/index.html @@ -2142,6 +2142,9 @@ Installed + + Plugin Store + {% endif %} From a42e2ffab054cd78c2b3507291dfb6d99ff86371 Mon Sep 17 00:00:00 2001 From: master3395 Date: Tue, 17 Feb 2026 00:00:28 +0100 Subject: [PATCH 4/7] MariaDB: ensure client no-SSL (ssl=0, skip-ssl) on all installs - fix ERROR 2026 --- cyberpanel_upgrade_monolithic.sh | 33 ++++++++++++++----- install/install.py | 28 ++++++++++++++++ ...ix-phpmyadmin-mariadb-version-on-server.md | 6 ++-- upgrade_modules/05_repository.sh | 20 +++++++---- 4 files changed, 69 insertions(+), 18 deletions(-) diff --git a/cyberpanel_upgrade_monolithic.sh b/cyberpanel_upgrade_monolithic.sh index 1c7243cd6..5f4a2d8a7 100644 --- a/cyberpanel_upgrade_monolithic.sh +++ b/cyberpanel_upgrade_monolithic.sh @@ -794,6 +794,7 @@ EOF dnf clean metadata --disablerepo='*' --enablerepo=mariadb 2>/dev/null || true # MariaDB 10 -> 11 or 11 -> 12: RPM scriptlet blocks in-place upgrade; do manual stop, remove old server, install target, start, mariadb-upgrade MARIADB_OLD_10=$(rpm -qa 'MariaDB-server-10*' 2>/dev/null | head -1) + [[ -z "$MARIADB_OLD_10" ]] && MARIADB_OLD_10=$(rpm -qa 2>/dev/null | grep -E '^MariaDB-server-10\.' | head -1) MARIADB_OLD_11=$(rpm -qa 'MariaDB-server-11*' 2>/dev/null | head -1) # Also detect 11.x by package version (e.g. MariaDB-server-11.8.6-1.el9) [[ -z "$MARIADB_OLD_11" ]] && MARIADB_OLD_11=$(rpm -qa 'MariaDB-server*' 2>/dev/null | grep -E 'MariaDB-server-11\.' | head -1) @@ -807,7 +808,7 @@ EOF rpm -e "$MARIADB_OLD_10" --nodeps 2>/dev/null || true dnf install -y --enablerepo=mariadb MariaDB-server MariaDB-client MariaDB-devel 2>/dev/null || true mkdir -p /etc/my.cnf.d - printf "[client]\nskip-ssl = true\n" > /etc/my.cnf.d/cyberpanel-client.cnf 2>/dev/null || true + printf "[client]\nssl=0\nskip-ssl\n" > /etc/my.cnf.d/cyberpanel-client.cnf 2>/dev/null || true systemctl start mariadb 2>/dev/null || true sleep 2 mariadb-upgrade -u root 2>/dev/null || true @@ -821,7 +822,7 @@ EOF rpm -e "$MARIADB_OLD_11" --nodeps 2>/dev/null || true dnf install -y --enablerepo=mariadb MariaDB-server MariaDB-client MariaDB-devel 2>/dev/null || true mkdir -p /etc/my.cnf.d - printf "[client]\nskip-ssl = true\n" > /etc/my.cnf.d/cyberpanel-client.cnf 2>/dev/null || true + printf "[client]\nssl=0\nskip-ssl\n" > /etc/my.cnf.d/cyberpanel-client.cnf 2>/dev/null || true systemctl start mariadb 2>/dev/null || true sleep 2 mariadb-upgrade -u root 2>/dev/null || true @@ -844,7 +845,7 @@ EOF rpm -e "$STILL_11" --nodeps 2>/dev/null || true dnf install -y --enablerepo=mariadb MariaDB-server MariaDB-client MariaDB-devel 2>/dev/null || true mkdir -p /etc/my.cnf.d - printf "[client]\nskip-ssl = true\n" > /etc/my.cnf.d/cyberpanel-client.cnf 2>/dev/null || true + printf "[client]\nssl=0\nskip-ssl\n" > /etc/my.cnf.d/cyberpanel-client.cnf 2>/dev/null || true systemctl start mariadb 2>/dev/null || true sleep 2 mariadb-upgrade -u root 2>/dev/null || true @@ -854,7 +855,11 @@ EOF fi # Allow local client to connect without SSL (11.x client defaults to SSL; 10.x server may not have it) mkdir -p /etc/my.cnf.d - printf "[client]\nskip-ssl = true\n" > /etc/my.cnf.d/cyberpanel-client.cnf 2>/dev/null || true + printf "[client]\nssl=0\nskip-ssl\n" > /etc/my.cnf.d/cyberpanel-client.cnf 2>/dev/null || true + # Ensure main my.cnf has [client] without SSL when server has SSL disabled (ERROR 2026 fix) + if [[ -f /etc/my.cnf ]] && ! grep -q '^\[client\]' /etc/my.cnf 2>/dev/null; then + echo -e "\n[client]\nssl=0\nskip-ssl" >> /etc/my.cnf + fi # Optional: migrate from latin1 to UTF-8 (utf8mb4) when --migrate-to-utf8 and 11.x/12.x if [[ "$Migrate_MariaDB_To_UTF8_Requested" = "yes" ]] && { [[ "$MARIADB_VER_REPO" =~ ^11\. ]] || [[ "$MARIADB_VER_REPO" =~ ^12\. ]]; }; then Migrate_MariaDB_To_UTF8 @@ -879,6 +884,7 @@ EOF # Install/upgrade MariaDB from our repo (any version: 10.11, 11.8, 12.x). Manual path for 10->11 and 11->12. MARIADB_OLD_10_AL9=$(rpm -qa 'MariaDB-server-10*' 2>/dev/null | head -1) + [[ -z "$MARIADB_OLD_10_AL9" ]] && MARIADB_OLD_10_AL9=$(rpm -qa 2>/dev/null | grep -E '^MariaDB-server-10\.' | head -1) MARIADB_OLD_11_AL9=$(rpm -qa 'MariaDB-server-11*' 2>/dev/null | head -1) [[ -z "$MARIADB_OLD_11_AL9" ]] && MARIADB_OLD_11_AL9=$(rpm -qa 'MariaDB-server*' 2>/dev/null | grep -E 'MariaDB-server-11\.' | head -1) if [[ -n "$MARIADB_OLD_10_AL9" ]] && { [[ "$MARIADB_VER_REPO" =~ ^11\. ]] || [[ "$MARIADB_VER_REPO" =~ ^12\. ]]; }; then @@ -890,7 +896,7 @@ EOF rpm -e "$MARIADB_OLD_10_AL9" --nodeps 2>/dev/null || true dnf install -y --enablerepo=mariadb MariaDB-server MariaDB-devel 2>/dev/null || dnf install -y mariadb-server mariadb-devel mkdir -p /etc/my.cnf.d - printf "[client]\nskip-ssl = true\n" > /etc/my.cnf.d/cyberpanel-client.cnf 2>/dev/null || true + printf "[client]\nssl=0\nskip-ssl\n" > /etc/my.cnf.d/cyberpanel-client.cnf 2>/dev/null || true systemctl start mariadb 2>/dev/null || true sleep 2 mariadb-upgrade -u root 2>/dev/null || true @@ -904,7 +910,7 @@ EOF rpm -e "$MARIADB_OLD_11_AL9" --nodeps 2>/dev/null || true dnf install -y --enablerepo=mariadb MariaDB-server MariaDB-devel 2>/dev/null || dnf install -y mariadb-server mariadb-devel mkdir -p /etc/my.cnf.d - printf "[client]\nskip-ssl = true\n" > /etc/my.cnf.d/cyberpanel-client.cnf 2>/dev/null || true + printf "[client]\nssl=0\nskip-ssl\n" > /etc/my.cnf.d/cyberpanel-client.cnf 2>/dev/null || true systemctl start mariadb 2>/dev/null || true sleep 2 mariadb-upgrade -u root 2>/dev/null || true @@ -916,7 +922,7 @@ EOF fi # Allow local client to connect without SSL mkdir -p /etc/my.cnf.d - printf "[client]\nskip-ssl = true\n" > /etc/my.cnf.d/cyberpanel-client.cnf 2>/dev/null || true + printf "[client]\nssl=0\nskip-ssl\n" > /etc/my.cnf.d/cyberpanel-client.cnf 2>/dev/null || true # Install additional required packages (omit curl - AlmaLinux 9 has curl-minimal, avoid conflict) dnf install -y wget unzip zip rsync firewalld psmisc git python3 python3-pip python3-devel 2>/dev/null || dnf install -y --allowerasing wget unzip zip rsync firewalld psmisc git python3 python3-pip python3-devel @@ -1354,6 +1360,13 @@ echo -e "[$(date +"%Y-%m-%d %H:%M:%S")] Running: $CP_PYTHON upgrade.py $Branch_N # Export Git user so upgrade.py clones from the same repo (master3395 or --repo override) export CYBERPANEL_GIT_USER="${Git_User:-usmannasir}" +# So upgrade.py can import plogical (it runs from /root/cyberpanel_upgrade_tmp) +export PYTHONPATH="/usr/local/CyberCP${PYTHONPATH:+:$PYTHONPATH}" + +# Run from dir that contains upgrade.py +for d in /root/cyberpanel_upgrade_tmp /usr/local/CyberCP; do + if [[ -f "$d/upgrade.py" ]]; then cd "$d" || true; break; fi +done # Run upgrade.py and capture output upgrade_output=$("$CP_PYTHON" upgrade.py "$Branch_Name" 2>&1) @@ -1421,7 +1434,8 @@ elif [[ "$Server_OS" = "openEuler" ]] ; then fi echo -e "[$(date +"%Y-%m-%d %H:%M:%S")] Running fallback: /usr/local/CyberPanelTemp/bin/python upgrade.py $Branch_Name" | tee -a /var/log/cyberpanel_upgrade_debug.log -export CYBERPANEL_GIT_USER="${Git_User:-master3395}" +export CYBERPANEL_GIT_USER="${Git_User:-usmannasir}" +export PYTHONPATH="/usr/local/CyberCP${PYTHONPATH:+:$PYTHONPATH}" /usr/local/CyberPanelTemp/bin/python upgrade.py "$Branch_Name" 2>&1 | tee -a /var/log/cyberpanel_upgrade_debug.log FALLBACK_CODE=$? echo -e "[$(date +"%Y-%m-%d %H:%M:%S")] Fallback upgrade returned code: $FALLBACK_CODE" | tee -a /var/log/cyberpanel_upgrade_debug.log @@ -1682,7 +1696,8 @@ Sync_CyberCP_To_Latest() { cd /usr/local/CyberCP git fetch origin 2>&1 | tee -a /var/log/cyberpanel_upgrade_debug.log if git show-ref -q "refs/remotes/origin/$Branch_Name"; then - git checkout -B "$Branch_Name" "origin/$Branch_Name" 2>&1 | tee -a /var/log/cyberpanel_upgrade_debug.log + # Force tree to match remote so local changes/untracked files do not block (settings.py restored below) + git reset --hard "origin/$Branch_Name" 2>&1 | tee -a /var/log/cyberpanel_upgrade_debug.log else git checkout "$Branch_Name" 2>/dev/null || true git pull --ff-only origin "$Branch_Name" 2>&1 | tee -a /var/log/cyberpanel_upgrade_debug.log || true diff --git a/install/install.py b/install/install.py index 975cead87..d53cf892a 100644 --- a/install/install.py +++ b/install/install.py @@ -2149,6 +2149,27 @@ module cyberpanel_ols { logging.InstallLog.writeToFile(error_msg) raise Exception(error_msg) + def _ensure_mariadb_client_no_ssl(self): + """Ensure MariaDB client connects without SSL (avoids ERROR 2026 when server has have_ssl=DISABLED).""" + client_cnf = "[client]\nssl=0\nskip-ssl\n" + try: + # RHEL/AlmaLinux: /etc/my.cnf.d/cyberpanel-client.cnf + if not os.path.exists('/etc/my.cnf.d'): + os.makedirs('/etc/my.cnf.d', mode=0o755, exist_ok=True) + with open('/etc/my.cnf.d/cyberpanel-client.cnf', 'w') as f: + f.write(client_cnf) + logging.InstallLog.writeToFile("Created /etc/my.cnf.d/cyberpanel-client.cnf (client SSL disabled)") + except Exception as e: + logging.InstallLog.writeToFile("_ensure_mariadb_client_no_ssl: /etc/my.cnf.d: %s" % str(e)) + try: + # Debian/Ubuntu: /etc/mysql/mariadb.conf.d/99-cyberpanel-client.cnf + if os.path.exists('/etc/mysql/mariadb.conf.d'): + with open('/etc/mysql/mariadb.conf.d/99-cyberpanel-client.cnf', 'w') as f: + f.write(client_cnf) + logging.InstallLog.writeToFile("Created /etc/mysql/mariadb.conf.d/99-cyberpanel-client.cnf (client SSL disabled)") + except Exception as e: + logging.InstallLog.writeToFile("_ensure_mariadb_client_no_ssl: mariadb.conf.d: %s" % str(e)) + def command_exists(self, command): """Check if a command exists in PATH""" try: @@ -3225,10 +3246,13 @@ module cyberpanel_ols { # all the other control panels allow # reference: https://oracle-base.com/articles/mysql/mysql-password-less-logins-using-option-files mysql_my_root_cnf = '/root/.my.cnf' + # Include skip-ssl/ssl=0 so client does not require SSL (avoids ERROR 2026 when server has have_ssl=DISABLED) mysql_root_cnf_content = """ [client] user=root password="%s" +ssl=0 +skip-ssl """ % password with open(mysql_my_root_cnf, 'w') as f: @@ -3239,6 +3263,10 @@ password="%s" logging.InstallLog.writeToFile("Updating /root/.my.cnf!") + # Ensure system-wide MariaDB client uses no SSL (all installs: avoids ERROR 2026 on servers with SSL disabled) + if self.remotemysql == 'OFF': + self._ensure_mariadb_client_no_ssl() + logging.InstallLog.writeToFile("Generating secure environment configuration!") # Determine the correct MySQL root password to use diff --git a/to-do/fix-phpmyadmin-mariadb-version-on-server.md b/to-do/fix-phpmyadmin-mariadb-version-on-server.md index fcc536990..b679bb53e 100644 --- a/to-do/fix-phpmyadmin-mariadb-version-on-server.md +++ b/to-do/fix-phpmyadmin-mariadb-version-on-server.md @@ -5,9 +5,11 @@ Run as root on the server: ```bash -# Allow mariadb client to connect without SSL (11.x client requires SSL by default) +# Allow mariadb client to connect without SSL (avoids ERROR 2026 when server has have_ssl=DISABLED) mkdir -p /etc/my.cnf.d -printf '[client]\nskip-ssl = true\n' > /etc/my.cnf.d/cyberpanel-client.cnf +printf '[client]\nssl=0\nskip-ssl\n' > /etc/my.cnf.d/cyberpanel-client.cnf +# If client still requires SSL, add [client] to main my.cnf (only if not already present) +grep -q '^\[client\]' /etc/my.cnf 2>/dev/null || echo -e "\n[client]\nssl=0\nskip-ssl" >> /etc/my.cnf # Now this should work and show the *actual* server version on 3306 mariadb -e "SELECT @@version;" diff --git a/upgrade_modules/05_repository.sh b/upgrade_modules/05_repository.sh index b8912689b..675169f75 100644 --- a/upgrade_modules/05_repository.sh +++ b/upgrade_modules/05_repository.sh @@ -261,6 +261,7 @@ EOF dnf clean metadata --disablerepo='*' --enablerepo=mariadb 2>/dev/null || true # MariaDB 10 -> 11 or 11 -> 12: RPM scriptlet blocks in-place upgrade; do manual stop, remove old server, install target, start, mariadb-upgrade MARIADB_OLD_10=$(rpm -qa 'MariaDB-server-10*' 2>/dev/null | head -1) + [[ -z "$MARIADB_OLD_10" ]] && MARIADB_OLD_10=$(rpm -qa 2>/dev/null | grep -E '^MariaDB-server-10\.' | head -1) MARIADB_OLD_11=$(rpm -qa 'MariaDB-server-11*' 2>/dev/null | head -1) # Also detect 11.x by package version (e.g. MariaDB-server-11.8.6-1.el9) [[ -z "$MARIADB_OLD_11" ]] && MARIADB_OLD_11=$(rpm -qa 'MariaDB-server*' 2>/dev/null | grep -E 'MariaDB-server-11\.' | head -1) @@ -274,7 +275,7 @@ EOF rpm -e "$MARIADB_OLD_10" --nodeps 2>/dev/null || true dnf install -y --enablerepo=mariadb MariaDB-server MariaDB-client MariaDB-devel 2>/dev/null || true mkdir -p /etc/my.cnf.d - printf "[client]\nskip-ssl = true\n" > /etc/my.cnf.d/cyberpanel-client.cnf 2>/dev/null || true + printf "[client]\nssl=0\nskip-ssl\n" > /etc/my.cnf.d/cyberpanel-client.cnf 2>/dev/null || true systemctl start mariadb 2>/dev/null || true sleep 2 mariadb-upgrade -u root 2>/dev/null || true @@ -288,7 +289,7 @@ EOF rpm -e "$MARIADB_OLD_11" --nodeps 2>/dev/null || true dnf install -y --enablerepo=mariadb MariaDB-server MariaDB-client MariaDB-devel 2>/dev/null || true mkdir -p /etc/my.cnf.d - printf "[client]\nskip-ssl = true\n" > /etc/my.cnf.d/cyberpanel-client.cnf 2>/dev/null || true + printf "[client]\nssl=0\nskip-ssl\n" > /etc/my.cnf.d/cyberpanel-client.cnf 2>/dev/null || true systemctl start mariadb 2>/dev/null || true sleep 2 mariadb-upgrade -u root 2>/dev/null || true @@ -311,7 +312,7 @@ EOF rpm -e "$STILL_11" --nodeps 2>/dev/null || true dnf install -y --enablerepo=mariadb MariaDB-server MariaDB-client MariaDB-devel 2>/dev/null || true mkdir -p /etc/my.cnf.d - printf "[client]\nskip-ssl = true\n" > /etc/my.cnf.d/cyberpanel-client.cnf 2>/dev/null || true + printf "[client]\nssl=0\nskip-ssl\n" > /etc/my.cnf.d/cyberpanel-client.cnf 2>/dev/null || true systemctl start mariadb 2>/dev/null || true sleep 2 mariadb-upgrade -u root 2>/dev/null || true @@ -321,7 +322,11 @@ EOF fi # Allow local client to connect without SSL (11.x client defaults to SSL; 10.x server may not have it) mkdir -p /etc/my.cnf.d - printf "[client]\nskip-ssl = true\n" > /etc/my.cnf.d/cyberpanel-client.cnf 2>/dev/null || true + printf "[client]\nssl=0\nskip-ssl\n" > /etc/my.cnf.d/cyberpanel-client.cnf 2>/dev/null || true + # Ensure main my.cnf has [client] without SSL when server has SSL disabled (ERROR 2026 fix) + if [[ -f /etc/my.cnf ]] && ! grep -q '^\[client\]' /etc/my.cnf 2>/dev/null; then + echo -e "\n[client]\nssl=0\nskip-ssl" >> /etc/my.cnf + fi # Optional: migrate from latin1 to UTF-8 (utf8mb4) when --migrate-to-utf8 and 11.x/12.x if [[ "$Migrate_MariaDB_To_UTF8_Requested" = "yes" ]] && { [[ "$MARIADB_VER_REPO" =~ ^11\. ]] || [[ "$MARIADB_VER_REPO" =~ ^12\. ]]; }; then Migrate_MariaDB_To_UTF8 @@ -346,6 +351,7 @@ EOF # Install/upgrade MariaDB from our repo (any version: 10.11, 11.8, 12.x). Manual path for 10->11 and 11->12. MARIADB_OLD_10_AL9=$(rpm -qa 'MariaDB-server-10*' 2>/dev/null | head -1) + [[ -z "$MARIADB_OLD_10_AL9" ]] && MARIADB_OLD_10_AL9=$(rpm -qa 2>/dev/null | grep -E '^MariaDB-server-10\.' | head -1) MARIADB_OLD_11_AL9=$(rpm -qa 'MariaDB-server-11*' 2>/dev/null | head -1) [[ -z "$MARIADB_OLD_11_AL9" ]] && MARIADB_OLD_11_AL9=$(rpm -qa 'MariaDB-server*' 2>/dev/null | grep -E 'MariaDB-server-11\.' | head -1) if [[ -n "$MARIADB_OLD_10_AL9" ]] && { [[ "$MARIADB_VER_REPO" =~ ^11\. ]] || [[ "$MARIADB_VER_REPO" =~ ^12\. ]]; }; then @@ -357,7 +363,7 @@ EOF rpm -e "$MARIADB_OLD_10_AL9" --nodeps 2>/dev/null || true dnf install -y --enablerepo=mariadb MariaDB-server MariaDB-devel 2>/dev/null || dnf install -y mariadb-server mariadb-devel mkdir -p /etc/my.cnf.d - printf "[client]\nskip-ssl = true\n" > /etc/my.cnf.d/cyberpanel-client.cnf 2>/dev/null || true + printf "[client]\nssl=0\nskip-ssl\n" > /etc/my.cnf.d/cyberpanel-client.cnf 2>/dev/null || true systemctl start mariadb 2>/dev/null || true sleep 2 mariadb-upgrade -u root 2>/dev/null || true @@ -371,7 +377,7 @@ EOF rpm -e "$MARIADB_OLD_11_AL9" --nodeps 2>/dev/null || true dnf install -y --enablerepo=mariadb MariaDB-server MariaDB-devel 2>/dev/null || dnf install -y mariadb-server mariadb-devel mkdir -p /etc/my.cnf.d - printf "[client]\nskip-ssl = true\n" > /etc/my.cnf.d/cyberpanel-client.cnf 2>/dev/null || true + printf "[client]\nssl=0\nskip-ssl\n" > /etc/my.cnf.d/cyberpanel-client.cnf 2>/dev/null || true systemctl start mariadb 2>/dev/null || true sleep 2 mariadb-upgrade -u root 2>/dev/null || true @@ -383,7 +389,7 @@ EOF fi # Allow local client to connect without SSL mkdir -p /etc/my.cnf.d - printf "[client]\nskip-ssl = true\n" > /etc/my.cnf.d/cyberpanel-client.cnf 2>/dev/null || true + printf "[client]\nssl=0\nskip-ssl\n" > /etc/my.cnf.d/cyberpanel-client.cnf 2>/dev/null || true # Install additional required packages (omit curl - AlmaLinux 9 has curl-minimal, avoid conflict) dnf install -y wget unzip zip rsync firewalld psmisc git python3 python3-pip python3-devel 2>/dev/null || dnf install -y --allowerasing wget unzip zip rsync firewalld psmisc git python3 python3-pip python3-devel From 924f00892b7bca52605b75ecd6b88afaa7bb0e5e Mon Sep 17 00:00:00 2001 From: master3395 Date: Tue, 17 Feb 2026 00:03:03 +0100 Subject: [PATCH 5/7] Upgrade: resolve conflict, default CYBERPANEL_GIT_USER to master3395 --- plogical/upgrade.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plogical/upgrade.py b/plogical/upgrade.py index bbe41c652..fc37c3dea 100644 --- a/plogical/upgrade.py +++ b/plogical/upgrade.py @@ -3975,7 +3975,7 @@ class Migration(migrations.Migration): return 0, 'Failed to remove old CyberCP directory' # Clone the new repository (use CYBERPANEL_GIT_USER for fork, e.g. master3395) - git_user = os.environ.get('CYBERPANEL_GIT_USER', 'usmannasir') + git_user = os.environ.get('CYBERPANEL_GIT_USER', 'master3395') Upgrade.stdOut("Cloning fresh CyberPanel repository...") command = 'git clone https://github.com/%s/cyberpanel CyberCP' % git_user if not Upgrade.executioner(command, command, 1): From 6df7c4ab3956d6761d87235b326d92923f4635bf Mon Sep 17 00:00:00 2001 From: master3395 Date: Tue, 17 Feb 2026 00:07:09 +0100 Subject: [PATCH 6/7] Install/upgrade: ensure MariaDB client no-SSL in all modules and monolithic (ERROR 2026 fix) --- cyberpanel_upgrade_monolithic.sh | 11 ++++ .../MARIADB-CLIENT-NO-SSL-INSTALL-UPGRADE.md | 51 +++++++++++++++++++ upgrade_modules/03_mariadb.sh | 15 ++++++ upgrade_modules/05_repository.sh | 3 ++ 4 files changed, 80 insertions(+) create mode 100644 to-do/MARIADB-CLIENT-NO-SSL-INSTALL-UPGRADE.md diff --git a/cyberpanel_upgrade_monolithic.sh b/cyberpanel_upgrade_monolithic.sh index 5f4a2d8a7..a7a4a51fd 100644 --- a/cyberpanel_upgrade_monolithic.sh +++ b/cyberpanel_upgrade_monolithic.sh @@ -1029,6 +1029,17 @@ if [[ "$Server_OS" = "openEuler" ]] ; then dnf install python3 -y fi #all pre-upgrade operation for openEuler + + # Ensure MariaDB client no-SSL on every upgrade path (avoids ERROR 2026 when server has have_ssl=DISABLED) + mkdir -p /etc/my.cnf.d + printf "[client]\nssl=0\nskip-ssl\n" > /etc/my.cnf.d/cyberpanel-client.cnf 2>/dev/null || true + if [[ -f /etc/my.cnf ]] && ! grep -q '^\[client\]' /etc/my.cnf 2>/dev/null; then + echo -e "\n[client]\nssl=0\nskip-ssl" >> /etc/my.cnf + fi + if [[ -d /etc/mysql/mariadb.conf.d ]]; then + printf "[client]\nssl=0\nskip-ssl\n" > /etc/mysql/mariadb.conf.d/99-cyberpanel-client.cnf 2>/dev/null || true + fi + echo -e "[$(date +"%Y-%m-%d %H:%M:%S")] MariaDB client no-SSL config ensured." | tee -a /var/log/cyberpanel_upgrade_debug.log } Download_Requirement() { diff --git a/to-do/MARIADB-CLIENT-NO-SSL-INSTALL-UPGRADE.md b/to-do/MARIADB-CLIENT-NO-SSL-INSTALL-UPGRADE.md new file mode 100644 index 000000000..aaaf074d3 --- /dev/null +++ b/to-do/MARIADB-CLIENT-NO-SSL-INSTALL-UPGRADE.md @@ -0,0 +1,51 @@ +# MariaDB Client No-SSL (ERROR 2026 Fix) – Install and Upgrade Coverage + +This document summarizes where the MariaDB client “no SSL” configuration is applied so that **install** and **upgrade** both work when the server has `have_ssl=DISABLED` (avoids `ERROR 2026 (HY000): TLS/SSL error: SSL is required, but the server does not support it`). + +## What gets applied + +- **`[client]`** with **`ssl=0`** and **`skip-ssl`** in: + - `/etc/my.cnf.d/cyberpanel-client.cnf` (RHEL/AlmaLinux/CentOS) + - `/etc/mysql/mariadb.conf.d/99-cyberpanel-client.cnf` (Debian/Ubuntu, when that directory exists) + - Optionally appended to **`/etc/my.cnf`** if it has no `[client]` section + +## Install path + +| Location | What happens | +|----------|----------------| +| **install/install.py** | Writes `/root/.my.cnf` with `[client]` including `ssl=0` and `skip-ssl`. When `remotemysql == 'OFF'`, calls `_ensure_mariadb_client_no_ssl()` which creates `/etc/my.cnf.d/cyberpanel-client.cnf` (RHEL) and `/etc/mysql/mariadb.conf.d/99-cyberpanel-client.cnf` (Debian/Ubuntu). | + +So every **fresh install** (local MariaDB) gets the client no-SSL config. + +## Upgrade path (modular: `cyberpanel_upgrade.sh` + `upgrade_modules/`) + +| Module | What happens | +|--------|----------------| +| **03_mariadb.sh** | Defines **`Ensure_MariaDB_Client_No_SSL()`** (writes `cyberpanel-client.cnf`, optional `[client]` in `my.cnf`, and Debian `99-cyberpanel-client.cnf`). Called at end of **`Pre_Upgrade_CentOS7_MySQL`** when that path runs. | +| **05_repository.sh** | After all OS-specific repository and MariaDB install/upgrade logic (CentOS, AlmaLinux 9, Ubuntu/Debian, openEuler), calls **`Ensure_MariaDB_Client_No_SSL`** once. Every RHEL/DNF path also writes `cyberpanel-client.cnf` and optional `my.cnf` [client] inline; Ubuntu/Debian get the fix via this single call. | + +So every **modular upgrade** run applies the client no-SSL config on all supported OSes. + +## Upgrade path (monolithic: `cyberpanel_upgrade_monolithic.sh`) + +| Location | What happens | +|----------|----------------| +| **Pre_Upgrade_Setup_Repository** | Each RHEL/DNF branch already creates `/etc/my.cnf.d/cyberpanel-client.cnf` with `ssl=0` and `skip-ssl` and optionally appends `[client]` to `/etc/my.cnf`. At the **end** of the same function (after Ubuntu and openEuler blocks), a single block runs that: creates `cyberpanel-client.cnf`, appends `[client]` to `my.cnf` if missing, and creates `/etc/mysql/mariadb.conf.d/99-cyberpanel-client.cnf` on Debian/Ubuntu. | + +So every **monolithic upgrade** run also ensures the client no-SSL config on all paths. + +## Verification + +After install or upgrade: + +```bash +mariadb -e "SELECT 1" +# or +mariadb -e "SELECT @@version;" +``` + +If these work without `ERROR 2026`, the client no-SSL configuration is in effect. + +## Manual fix (if needed) + +See **to-do/fix-phpmyadmin-mariadb-version-on-server.md** for a manual one-off fix on a single server. diff --git a/upgrade_modules/03_mariadb.sh b/upgrade_modules/03_mariadb.sh index f70e3fae4..8fe8ddb4e 100644 --- a/upgrade_modules/03_mariadb.sh +++ b/upgrade_modules/03_mariadb.sh @@ -17,6 +17,7 @@ Pre_Upgrade_CentOS7_MySQL() { mariadb-upgrade -uroot -p"$MySQL_Password" 2>/dev/null || mysql_upgrade -uroot -p"$MySQL_Password" fi mariadb -uroot -p"$MySQL_Password" -e "GRANT ALL PRIVILEGES ON *.* TO 'root'@'localhost' IDENTIFIED BY '$MySQL_Password';flush privileges" 2>/dev/null || mysql -uroot -p"$MySQL_Password" -e "GRANT ALL PRIVILEGES ON *.* TO 'root'@'localhost' IDENTIFIED BY '$MySQL_Password';flush privileges" + Ensure_MariaDB_Client_No_SSL } Maybe_Backup_MariaDB_Before_Upgrade() { @@ -86,3 +87,17 @@ Migrate_MariaDB_To_UTF8() { done echo -e "[$(date +"%Y-%m-%d %H:%M:%S")] MariaDB UTF-8 (utf8mb4) migration completed." | tee -a /var/log/cyberpanel_upgrade_debug.log } + +# Ensure MariaDB client connects without SSL (avoids ERROR 2026 when server has have_ssl=DISABLED). +# Call after any MariaDB install/upgrade so install and upgrade paths both get the fix. +Ensure_MariaDB_Client_No_SSL() { + mkdir -p /etc/my.cnf.d + printf "[client]\nssl=0\nskip-ssl\n" > /etc/my.cnf.d/cyberpanel-client.cnf 2>/dev/null || true + if [[ -f /etc/my.cnf ]] && ! grep -q '^\[client\]' /etc/my.cnf 2>/dev/null; then + echo -e "\n[client]\nssl=0\nskip-ssl" >> /etc/my.cnf + fi + if [[ -d /etc/mysql/mariadb.conf.d ]]; then + printf "[client]\nssl=0\nskip-ssl\n" > /etc/mysql/mariadb.conf.d/99-cyberpanel-client.cnf 2>/dev/null || true + fi + echo -e "[$(date +"%Y-%m-%d %H:%M:%S")] MariaDB client no-SSL config ensured (cyberpanel-client.cnf, optional my.cnf [client])." | tee -a /var/log/cyberpanel_upgrade_debug.log +} diff --git a/upgrade_modules/05_repository.sh b/upgrade_modules/05_repository.sh index 675169f75..48f38cc57 100644 --- a/upgrade_modules/05_repository.sh +++ b/upgrade_modules/05_repository.sh @@ -496,5 +496,8 @@ if [[ "$Server_OS" = "openEuler" ]] ; then dnf install python3 -y fi #all pre-upgrade operation for openEuler + + # Ensure MariaDB client no-SSL on every upgrade path (install and upgrade; avoids ERROR 2026) + Ensure_MariaDB_Client_No_SSL } From feb9c912acc0d1e6ac809952d1bb7b3e1b9b632b Mon Sep 17 00:00:00 2001 From: master3395 Date: Tue, 17 Feb 2026 00:21:25 +0100 Subject: [PATCH 7/7] Fix emailMarketing reverse error + dashboardStatsController ctrlreg - CyberCP/urls.py: when emailMarketing app is missing/broken, register placeholder path with name='emailMarketing' so {% url 'emailMarketing' %} never raises Reverse not found. - public/static/baseTemplate/custom-js/system-status.js: replace old copy with full system-status.js that registers dashboardStatsController and systemStatusInfo (fixes Angular [:ctrlreg] and raw {{$ cpuUsage $}} on overview). --- CyberCP/urls.py | 9 +- .../baseTemplate/custom-js/system-status.js | 1840 ++++++++++++++++- 2 files changed, 1836 insertions(+), 13 deletions(-) diff --git a/CyberCP/urls.py b/CyberCP/urls.py index 69692fa4c..25eb74f3f 100644 --- a/CyberCP/urls.py +++ b/CyberCP/urls.py @@ -26,12 +26,15 @@ from firewall import views as firewall_views # includes each installed plugin (under /plugins//) so settings and # other plugin pages work for any installed plugin. -# Optional app: may be missing after clean clone or git clean -fd (not in all repo trees) +# Optional app: may be missing after clean clone or git clean -fd (not in all repo trees). +# When missing or broken, register a placeholder so {% url 'emailMarketing' %} in templates never raises Reverse not found. _optional_email_marketing = [] try: _optional_email_marketing.append(path('emailMarketing/', include('emailMarketing.urls'))) -except ModuleNotFoundError: - pass +except (ModuleNotFoundError, ImportError, AttributeError): + _optional_email_marketing.append( + path('emailMarketing/', RedirectView.as_view(url='/base/', permanent=False), name='emailMarketing') + ) urlpatterns = [ # Serve static files first (before catch-all routes) diff --git a/public/static/baseTemplate/custom-js/system-status.js b/public/static/baseTemplate/custom-js/system-status.js index 0cccec058..37720ec3e 100644 --- a/public/static/baseTemplate/custom-js/system-status.js +++ b/public/static/baseTemplate/custom-js/system-status.js @@ -4,12 +4,13 @@ /* Utilities */ + function getCookie(name) { var cookieValue = null; if (document.cookie && document.cookie !== '') { var cookies = document.cookie.split(';'); for (var i = 0; i < cookies.length; i++) { - var cookie = jQuery.trim(cookies[i]); + var cookie = (cookies[i] || '').replace(/^\s+|\s+$/g, ''); // Does this cookie string begin with the name we want? if (cookie.substring(0, name.length + 1) === (name + '=')) { cookieValue = decodeURIComponent(cookie.substring(name.length + 1)); @@ -34,7 +35,80 @@ function randomPassword(length) { /* Java script code to monitor system status */ -var app = angular.module('CyberCP', []); +// Create global app reference for CyberCP module so other scripts can access it +window.app = angular.module('CyberCP', []); +var app = window.app; // Local reference for this file + +// MUST be first: register dashboard controller before any other setup (avoids ctrlreg when CDN/Tracking Prevention blocks scripts) +app.controller('dashboardStatsController', ['$scope', '$http', '$timeout', function ($scope, $http, $timeout) { + $scope.cpuUsage = 0; $scope.ramUsage = 0; $scope.diskUsage = 0; $scope.cpuCores = 0; + $scope.ramTotalMB = 0; $scope.diskTotalGB = 0; $scope.diskFreeGB = 0; + $scope.totalUsers = 0; $scope.totalSites = 0; $scope.totalWPSites = 0; + $scope.totalDBs = 0; $scope.totalEmails = 0; $scope.totalFTPUsers = 0; + $scope.topProcesses = []; $scope.sshLogins = []; $scope.sshLogs = []; + $scope.loadingTopProcesses = true; $scope.loadingSSHLogins = true; $scope.loadingSSHLogs = true; + $scope.blockedIPs = {}; $scope.blockingIP = null; $scope.securityAlerts = []; + var opts = { headers: { 'X-CSRFToken': (typeof getCookie === 'function') ? getCookie('csrftoken') : '' } }; + try { + $http.get('/base/getSystemStatus', opts).then(function (r) { + if (r && r.data && r.data.status === 1) { + $scope.cpuUsage = r.data.cpuUsage || 0; $scope.ramUsage = r.data.ramUsage || 0; + $scope.diskUsage = r.data.diskUsage || 0; $scope.cpuCores = r.data.cpuCores || 0; + $scope.ramTotalMB = r.data.ramTotalMB || 0; $scope.diskTotalGB = r.data.diskTotalGB || 0; + $scope.diskFreeGB = r.data.diskFreeGB || 0; + } + }); + $http.get('/base/getDashboardStats', opts).then(function (r) { + if (r && r.data && r.data.status === 1) { + $scope.totalUsers = r.data.total_users || 0; $scope.totalSites = r.data.total_sites || 0; + $scope.totalWPSites = r.data.total_wp_sites || 0; $scope.totalDBs = r.data.total_dbs || 0; + $scope.totalEmails = r.data.total_emails || 0; $scope.totalFTPUsers = r.data.total_ftp_users || 0; + } + }); + $http.get('/base/getRecentSSHLogins', opts).then(function (r) { + $scope.loadingSSHLogins = false; + $scope.sshLogins = (r && r.data && r.data.logins) ? r.data.logins : []; + }, function () { $scope.loadingSSHLogins = false; $scope.sshLogins = []; }); + $http.get('/base/getRecentSSHLogs', opts).then(function (r) { + $scope.loadingSSHLogs = false; + $scope.sshLogs = (r && r.data && r.data.logs) ? r.data.logs : []; + }, function () { $scope.loadingSSHLogs = false; $scope.sshLogs = []; }); + $http.get('/base/getTopProcesses', opts).then(function (r) { + $scope.loadingTopProcesses = false; + $scope.topProcesses = (r && r.data && r.data.status === 1 && r.data.processes) ? r.data.processes : []; + }, function () { $scope.loadingTopProcesses = false; $scope.topProcesses = []; }); + if (typeof $timeout === 'function') { $timeout(function() { /* refresh */ }, 10000); } + } catch (e) { /* ignore */ } +}]); + +// Overview CPU/RAM/Disk cards use systemStatusInfo – register early so data loads even if later script fails +app.controller('systemStatusInfo', ['$scope', '$http', '$timeout', function ($scope, $http, $timeout) { + $scope.uptimeLoaded = false; + $scope.uptime = 'Loading...'; + $scope.cpuUsage = 0; $scope.ramUsage = 0; $scope.diskUsage = 0; + $scope.cpuCores = 0; $scope.ramTotalMB = 0; $scope.diskTotalGB = 0; $scope.diskFreeGB = 0; + $scope.getSystemStatus = function() { fetchStatus(); }; + function fetchStatus() { + try { + var csrf = (typeof getCookie === 'function') ? getCookie('csrftoken') : ''; + $http.get('/base/getSystemStatus', { headers: { 'X-CSRFToken': csrf } }).then(function (r) { + if (r && r.data && r.data.status === 1) { + $scope.cpuUsage = r.data.cpuUsage != null ? r.data.cpuUsage : 0; + $scope.ramUsage = r.data.ramUsage != null ? r.data.ramUsage : 0; + $scope.diskUsage = r.data.diskUsage != null ? r.data.diskUsage : 0; + $scope.cpuCores = r.data.cpuCores != null ? r.data.cpuCores : 0; + $scope.ramTotalMB = r.data.ramTotalMB != null ? r.data.ramTotalMB : 0; + $scope.diskTotalGB = r.data.diskTotalGB != null ? r.data.diskTotalGB : 0; + $scope.diskFreeGB = r.data.diskFreeGB != null ? r.data.diskFreeGB : 0; + $scope.uptime = r.data.uptime || 'N/A'; + } + $scope.uptimeLoaded = true; + }, function() { $scope.uptime = 'Unavailable'; $scope.uptimeLoaded = true; }); + if (typeof $timeout === 'function') { $timeout(fetchStatus, 60000); } + } catch (e) { $scope.uptimeLoaded = true; } + } + fetchStatus(); +}]); var globalScope; @@ -117,11 +191,25 @@ function getWebsiteName(domain) { app.controller('systemStatusInfo', function ($scope, $http, $timeout) { - //getStuff(); + $scope.uptimeLoaded = false; + $scope.uptime = 'Loading...'; + // Defaults so template never shows undefined (avoids raw {$ cpuUsage $} when API is slow or fails) + $scope.cpuUsage = 0; + $scope.ramUsage = 0; + $scope.diskUsage = 0; + $scope.cpuCores = 0; + $scope.ramTotalMB = 0; + $scope.diskTotalGB = 0; + $scope.diskFreeGB = 0; + + getStuff(); + + $scope.getSystemStatus = function() { + getStuff(); + }; function getStuff() { - url = "/base/getSystemStatus"; $http.get(url).then(ListInitialData, cantLoadInitialData); @@ -129,16 +217,34 @@ app.controller('systemStatusInfo', function ($scope, $http, $timeout) { function ListInitialData(response) { - $scope.cpuUsage = response.data.cpuUsage; - $scope.ramUsage = response.data.ramUsage; - $scope.diskUsage = response.data.diskUsage; + $scope.cpuUsage = response.data.cpuUsage != null ? response.data.cpuUsage : 0; + $scope.ramUsage = response.data.ramUsage != null ? response.data.ramUsage : 0; + $scope.diskUsage = response.data.diskUsage != null ? response.data.diskUsage : 0; + + $scope.cpuCores = response.data.cpuCores != null ? response.data.cpuCores : 0; + $scope.ramTotalMB = response.data.ramTotalMB != null ? response.data.ramTotalMB : 0; + $scope.diskTotalGB = response.data.diskTotalGB != null ? response.data.diskTotalGB : 0; + $scope.diskFreeGB = response.data.diskFreeGB != null ? response.data.diskFreeGB : 0; + + if (response.data.uptime) { + $scope.uptime = response.data.uptime; + $scope.uptimeLoaded = true; + } else { + $scope.uptime = 'N/A'; + $scope.uptimeLoaded = true; + } } function cantLoadInitialData(response) { + $scope.uptime = 'Unavailable'; + $scope.uptimeLoaded = true; + $scope.cpuUsage = 0; + $scope.ramUsage = 0; + $scope.diskUsage = 0; } - //$timeout(getStuff, 2000); + $timeout(getStuff, 60000); // Update every minute } }); @@ -566,10 +672,20 @@ app.controller('versionManagment', function ($scope, $http, $timeout) { $scope.updateFinish = true; $scope.couldNotConnect = true; + var data = { + branchSelect: document.getElementById("branchSelect").value, + + }; + + var config = { + headers: { + 'X-CSRFToken': getCookie('csrftoken') + } + }; + url = "/base/upgrade"; - - $http.get(url).then(ListInitialData, cantLoadInitialData); + $http.post(url, data, config).then(ListInitialData, cantLoadInitialData); function ListInitialData(response) { @@ -622,6 +738,7 @@ app.controller('versionManagment', function ($scope, $http, $timeout) { function ListInitialDatas(response) { + console.log(response.data.upgradeLog); if (response.data.upgradeStatus === 1) { @@ -705,3 +822,1706 @@ app.controller('designtheme', function ($scope, $http, $timeout) { }; }); + + +app.controller('OnboardingCP', function ($scope, $http, $timeout, $window) { + + $scope.cyberpanelLoading = true; + $scope.ExecutionStatus = true; + $scope.ReportStatus = true; + $scope.OnboardineDone = true; + + var statusTimer = null; + + function statusFunc() { + $scope.cyberpanelLoading = false; + $scope.ExecutionStatus = false; + var url = "/emailPremium/statusFunc"; + + var data = { + statusFile: statusFile + }; + var config = { + headers: { + 'X-CSRFToken': getCookie('csrftoken') + } + }; + + $http.post(url, data, config).then(ListInitialData, cantLoadInitialData); + + + function ListInitialData(response) { + if (response.data.status === 1) { + if (response.data.abort === 1) { + $scope.functionProgress = {"width": "100%"}; + $scope.functionStatus = response.data.currentStatus; + $scope.cyberpanelLoading = true; + $scope.OnboardineDone = false; + if (statusTimer) { + $timeout.cancel(statusTimer); + statusTimer = null; + } + } else { + $scope.functionProgress = {"width": response.data.installationProgress + "%"}; + $scope.functionStatus = response.data.currentStatus; + statusTimer = $timeout(statusFunc, 3000); + } + + } else { + $scope.cyberpanelLoading = true; + $scope.functionStatus = response.data.error_message; + $scope.functionProgress = {"width": response.data.installationProgress + "%"}; + if (statusTimer) { + $timeout.cancel(statusTimer); + statusTimer = null; + } + } + + } + + function cantLoadInitialData(response) { + $scope.functionProgress = {"width": response.data.installationProgress + "%"}; + $scope.functionStatus = 'Could not connect to server, please refresh this page.'; + $timeout.cancel(); + } + } + + $scope.RunOnboarding = function () { + $scope.cyberpanelLoading = false; + $scope.OnboardineDone = true; + + var url = "/base/runonboarding"; + + var data = { + hostname: $scope.hostname, + rDNSCheck: $scope.rDNSCheck + }; + + var config = { + headers: { + 'X-CSRFToken': getCookie('csrftoken') + } + }; + + + $http.post(url, data, config).then(ListInitialData, cantLoadInitialData); + + function ListInitialData(response) { + $scope.cyberpanelLoading = true; + if (response.data.status === 1) { + statusFile = response.data.tempStatusPath; + statusFunc(); + + + } else { + new PNotify({ + title: 'Operation Failed!', + text: response.data.error_message, + type: 'error' + }); + } + + } + + function cantLoadInitialData(response) { + $scope.cyberpanelLoading = true; + + new PNotify({ + title: 'Error', + text: 'Could not connect to server, please refresh this page.', + type: 'error' + }); + } + }; + + $scope.RestartCyberPanel = function () { + $scope.cyberpanelLoading = false; + + var url = "/base/RestartCyberPanel"; + + var data = { + + }; + + var config = { + headers: { + 'X-CSRFToken': getCookie('csrftoken') + } + }; + + + $http.post(url, data, config).then(ListInitialData, cantLoadInitialData); + $scope.cyberpanelLoading = true; + new PNotify({ + title: 'Success', + text: 'Refresh your browser after 3 seconds to fetch new SSL.', + type: 'success' + }); + + function ListInitialData(response) { + $scope.cyberpanelLoading = true; + if (response.data.status === 1) { + + } else { + new PNotify({ + title: 'Operation Failed!', + text: response.data.error_message, + type: 'error' + }); + } + + } + + function cantLoadInitialData(response) { + $scope.cyberpanelLoading = true; + + new PNotify({ + title: 'Error', + text: 'Could not connect to server, please refresh this page.', + type: 'error' + }); + } + }; + +}); + +// Single implementation registered under both names for compatibility (some templates/caches use newDashboardStat) +var dashboardStatsControllerFn = function ($scope, $http, $timeout) { + console.log('dashboardStatsController initialized'); + + // Card values + $scope.totalUsers = 0; + $scope.totalSites = 0; + $scope.totalWPSites = 0; + $scope.totalDBs = 0; + $scope.totalEmails = 0; + $scope.totalFTPUsers = 0; + + // Hide system charts for non-admin users + $scope.hideSystemCharts = false; + + // Top Processes + $scope.topProcesses = []; + $scope.loadingTopProcesses = true; + $scope.errorTopProcesses = ''; + $scope.refreshTopProcesses = function() { + $scope.loadingTopProcesses = true; + var h = { headers: { 'X-CSRFToken': (typeof getCookie === 'function') ? getCookie('csrftoken') : '' } }; + $http.get('/base/getTopProcesses', h).then(function (response) { + $scope.loadingTopProcesses = false; + if (response.data && response.data.status === 1 && response.data.processes) { + $scope.topProcesses = response.data.processes; + } else { + $scope.topProcesses = []; + } + }, function (err) { + $scope.loadingTopProcesses = false; + $scope.errorTopProcesses = 'Failed to load top processes.'; + }); + }; + + // SSH Logins + $scope.sshLogins = []; + $scope.loadingSSHLogins = true; + $scope.errorSSHLogins = ''; + $scope.refreshSSHLogins = function() { + $scope.loadingSSHLogins = true; + var h = { headers: { 'X-CSRFToken': (typeof getCookie === 'function') ? getCookie('csrftoken') : '' } }; + $http.get('/base/getRecentSSHLogins', h).then(function (response) { + $scope.loadingSSHLogins = false; + if (response.data && response.data.logins) { + $scope.sshLogins = response.data.logins; + // Debug: Log first login to see structure + if ($scope.sshLogins.length > 0) { + console.log('First SSH login object:', $scope.sshLogins[0]); + console.log('IP field:', $scope.sshLogins[0].ip); + console.log('All keys:', Object.keys($scope.sshLogins[0])); + } + } else { + $scope.sshLogins = []; + } + }, function (err) { + $scope.loadingSSHLogins = false; + $scope.errorSSHLogins = 'Failed to load SSH logins.'; + console.error('Failed to load SSH logins:', err); + }); + }; + + // SSH Logs + $scope.sshLogs = []; + $scope.loadingSSHLogs = true; + $scope.errorSSHLogs = ''; + $scope.securityAlerts = []; + $scope.loadingSecurityAnalysis = false; + $scope.refreshSSHLogs = function() { + $scope.loadingSSHLogs = true; + var h = { headers: { 'X-CSRFToken': (typeof getCookie === 'function') ? getCookie('csrftoken') : '' } }; + $http.get('/base/getRecentSSHLogs', h).then(function (response) { + $scope.loadingSSHLogs = false; + if (response.data && response.data.logs) { + $scope.sshLogs = response.data.logs; + // Analyze logs for security issues + $scope.analyzeSSHSecurity(); + } else { + $scope.sshLogs = []; + } + }, function (err) { + $scope.loadingSSHLogs = false; + $scope.errorSSHLogs = 'Failed to load SSH logs.'; + }); + }; + + // Security Analysis + $scope.showAddonRequired = false; + $scope.addonInfo = {}; + + // IP Blocking functionality + $scope.blockingIP = null; + $scope.blockedIPs = {}; + + $scope.analyzeSSHSecurity = function() { + $scope.loadingSecurityAnalysis = true; + $scope.showAddonRequired = false; + $http.post('/base/analyzeSSHSecurity', {}).then(function (response) { + $scope.loadingSecurityAnalysis = false; + if (response.data) { + if (response.data.addon_required) { + $scope.showAddonRequired = true; + $scope.addonInfo = response.data; + $scope.securityAlerts = []; + } else if (response.data.status === 1) { + $scope.securityAlerts = response.data.alerts; + $scope.showAddonRequired = false; + } + } + }, function (err) { + $scope.loadingSecurityAnalysis = false; + }); + }; + + $scope.blockIPAddress = function(ipAddress) { + try { + console.log('========================================'); + console.log('=== blockIPAddress CALLED ==='); + console.log('========================================'); + console.log('blockIPAddress called with:', ipAddress); + console.log('ipAddress type:', typeof ipAddress); + console.log('ipAddress value:', ipAddress); + console.log('$scope:', $scope); + console.log('$scope.blockIPAddress:', typeof $scope.blockIPAddress); + + // Validate IP address parameter + if (!ipAddress) { + console.error('No IP address provided:', ipAddress); + if (typeof PNotify !== 'undefined') { + new PNotify({ + title: 'Error', + text: 'No IP address provided', + type: 'error', + delay: 5000 + }); + } + return; + } + + // Ensure it's a string and trim it + ipAddress = String(ipAddress).trim(); + + // Validate after trimming + if (!ipAddress || ipAddress === '' || ipAddress === 'undefined' || ipAddress === 'null') { + console.error('IP address is empty or invalid after trim:', ipAddress); + if (typeof PNotify !== 'undefined') { + new PNotify({ + title: 'Error', + text: 'Invalid IP address provided: ' + ipAddress, + type: 'error', + delay: 5000 + }); + } + return; + } + + // Basic IP format validation + var ipPattern = /^(\d{1,3}\.){3}\d{1,3}(\/\d{1,2})?$/; + if (!ipPattern.test(ipAddress)) { + console.error('IP address format is invalid:', ipAddress); + if (typeof PNotify !== 'undefined') { + new PNotify({ + title: 'Error', + text: 'Invalid IP address format: ' + ipAddress, + type: 'error', + delay: 5000 + }); + } + return; + } + + // Prevent duplicate requests + if ($scope.blockingIP === ipAddress) { + console.log('Already processing IP:', ipAddress); + return; // Already processing this IP + } + + // Do not early-return when IP is already in blockedIPs: still call the API so the + // backend can close any active connections from this IP (already-banned path). + + // Set blocking flag to prevent duplicate requests + $scope.blockingIP = ipAddress; + + // Use the new Banned IPs system instead of the old blockIPAddress + var data = { + ip: ipAddress, + reason: 'Brute force attack detected from SSH Security Analysis', + duration: 'permanent' + }; + + var config = { + headers: { + 'X-CSRFToken': getCookie('csrftoken') + } + }; + + console.log('Sending ban IP request:', data); + console.log('CSRF Token:', getCookie('csrftoken')); + console.log('Config:', config); + + $http.post('/firewall/addBannedIP', data, config).then(function (response) { + console.log('=== addBannedIP SUCCESS ==='); + console.log('Full response:', response); + console.log('response.data:', response.data); + console.log('response.data type:', typeof response.data); + console.log('response.status:', response.status); + + // Reset blocking flag + $scope.blockingIP = null; + + // Apply scope changes + if (!$scope.$$phase && !$scope.$root.$$phase) { + $scope.$apply(); + } + + // Handle both JSON string and object responses + var responseData = response.data; + if (typeof responseData === 'string') { + try { + responseData = JSON.parse(responseData); + console.log('Parsed responseData from string:', responseData); + } catch (e) { + console.error('Failed to parse response as JSON:', e); + var errorMsg = responseData && responseData.length ? responseData : 'Failed to block IP address'; + if (typeof PNotify !== 'undefined') { + new PNotify({ title: 'Error', text: errorMsg, type: 'error', delay: 5000 }); + } + $scope.blockingIP = null; + return; + } + } + + console.log('Final responseData:', responseData); + console.log('responseData.status:', responseData ? responseData.status : 'undefined'); + console.log('responseData.message:', responseData ? responseData.message : 'undefined'); + console.log('responseData.error_message:', responseData ? responseData.error_message : 'undefined'); + + // Check for success (status === 1 or status === '1') + if (responseData && (responseData.status === 1 || responseData.status === '1')) { + // Mark IP as blocked + if (!$scope.blockedIPs) { + $scope.blockedIPs = {}; + } + $scope.blockedIPs[ipAddress] = true; + + // Show success notification (use server message when present, e.g. already-banned + connections closed) + if (typeof PNotify !== 'undefined') { + var successText = (responseData.message && responseData.message.length) ? responseData.message : `IP address ${ipAddress} has been permanently banned and added to the firewall. You can manage it in the Firewall > Banned IPs section.`; + new PNotify({ + title: 'IP Address Banned', + text: successText, + type: 'success', + delay: 5000 + }); + } + + // Refresh security analysis to update alerts + if ($scope.analyzeSSHSecurity) { + $scope.analyzeSSHSecurity(); + } + + // Apply scope changes + if (!$scope.$$phase && !$scope.$root.$$phase) { + $scope.$apply(); + } + } else { + // Show error notification + var errorMsg = 'Failed to block IP address'; + if (responseData && responseData.error_message) { + errorMsg = responseData.error_message; + } else if (responseData && responseData.error) { + errorMsg = responseData.error; + } else if (responseData && responseData.message) { + errorMsg = responseData.message; + } else if (responseData) { + errorMsg = JSON.stringify(responseData); + } + console.error('Ban IP failed:', errorMsg); + if (typeof PNotify !== 'undefined') { + new PNotify({ + title: 'Error', + text: errorMsg, + type: 'error', + delay: 5000 + }); + } + } + }, function (err) { + $scope.blockingIP = null; + console.error('addBannedIP error:', err); + console.error('Error status:', err.status); + console.error('Error statusText:', err.statusText); + console.error('Error data:', err.data); + + // Prevent showing duplicate error notifications + if ($scope.lastErrorIP === ipAddress && $scope.lastErrorTime && (Date.now() - $scope.lastErrorTime) < 2000) { + console.log('Skipping duplicate error notification for IP:', ipAddress); + return; + } + + $scope.lastErrorIP = ipAddress; + $scope.lastErrorTime = Date.now(); + + var errorMessage = 'Failed to block IP address'; + var errData = err.data; + if (typeof errData === 'string') { + try { + errData = JSON.parse(errData); + } catch (e) { + if (errData && errData.length) { + errorMessage = errData.length > 200 ? errData.substring(0, 200) + '...' : errData; + } + } + } + if (errData && typeof errData === 'object') { + errorMessage = errData.error_message || errData.error || errData.message || errorMessage; + } else if (err.status) { + errorMessage = 'HTTP ' + err.status + ': ' + (errorMessage); + } + + console.error('Final error message:', errorMessage); + + if (typeof PNotify !== 'undefined') { + new PNotify({ + title: 'Error', + text: errorMessage, + type: 'error', + delay: 5000 + }); + } + }); + } catch (e) { + console.error('========================================'); + console.error('=== ERROR in blockIPAddress ==='); + console.error('========================================'); + console.error('Error:', e); + console.error('Error message:', e.message); + console.error('Error stack:', e.stack); + $scope.blockingIP = null; + if (typeof PNotify !== 'undefined') { + new PNotify({ + title: 'Error', + text: 'An error occurred while trying to ban the IP address: ' + (e.message || String(e)), + type: 'error', + delay: 5000 + }); + } + } + }; + + // Ban IP from SSH Logs + $scope.banIPFromSSHLog = function(ipAddress) { + if (!ipAddress) { + new PNotify({ + title: 'Error', + text: 'No IP address provided', + type: 'error', + delay: 5000 + }); + return; + } + + if ($scope.blockingIP === ipAddress) { + return; // Already processing + } + + // Still call API when already in blockedIPs so backend can close active connections + if (!$scope.blockedIPs) { + $scope.blockedIPs = {}; + } + + $scope.blockingIP = ipAddress; + + // Use the Banned IPs system + var data = { + ip: ipAddress, + reason: 'Suspicious activity detected from SSH logs', + duration: 'permanent' + }; + + var config = { + headers: { + 'X-CSRFToken': getCookie('csrftoken') + } + }; + + $http.post('/firewall/addBannedIP', data, config).then(function (response) { + $scope.blockingIP = null; + if (response.data && response.data.status === 1) { + // Mark IP as blocked + $scope.blockedIPs[ipAddress] = true; + + // Show success notification + new PNotify({ + title: 'IP Address Banned', + text: `IP address ${ipAddress} has been permanently banned and added to the firewall. You can manage it in the Firewall > Banned IPs section.`, + type: 'success', + delay: 5000 + }); + + // Refresh SSH logs to update the UI + $scope.refreshSSHLogs(); + } else { + // Show error notification + var errorMsg = 'Failed to ban IP address'; + if (response.data && response.data.error_message) { + errorMsg = response.data.error_message; + } else if (response.data && response.data.error) { + errorMsg = response.data.error; + } + + new PNotify({ + title: 'Error', + text: errorMsg, + type: 'error', + delay: 5000 + }); + } + }, function (err) { + $scope.blockingIP = null; + var errorMessage = 'Failed to ban IP address'; + if (err.data && err.data.error_message) { + errorMessage = err.data.error_message; + } else if (err.data && err.data.error) { + errorMessage = err.data.error; + } else if (err.data && err.data.message) { + errorMessage = err.data.message; + } + + new PNotify({ + title: 'Error', + text: errorMessage, + type: 'error', + delay: 5000 + }); + }); + }; + + // Ban IP from SSH Logs + $scope.banIPFromSSHLog = function(ipAddress) { + if (!ipAddress) { + new PNotify({ + title: 'Error', + text: 'No IP address provided', + type: 'error', + delay: 5000 + }); + return; + } + + if ($scope.blockingIP === ipAddress) { + return; // Already processing + } + + // Still call API when already in blockedIPs so backend can close active connections + if (!$scope.blockedIPs) { + $scope.blockedIPs = {}; + } + + $scope.blockingIP = ipAddress; + + // Use the Banned IPs system + var data = { + ip: ipAddress, + reason: 'Suspicious activity detected from SSH logs', + duration: 'permanent' + }; + + var config = { + headers: { + 'X-CSRFToken': getCookie('csrftoken') + } + }; + + $http.post('/firewall/addBannedIP', data, config).then(function (response) { + $scope.blockingIP = null; + if (response.data && response.data.status === 1) { + // Mark IP as blocked + $scope.blockedIPs[ipAddress] = true; + + // Show success notification + new PNotify({ + title: 'IP Address Banned', + text: `IP address ${ipAddress} has been permanently banned and added to the firewall. You can manage it in the Firewall > Banned IPs section.`, + type: 'success', + delay: 5000 + }); + + // Refresh SSH logs to update the UI + $scope.refreshSSHLogs(); + } else { + // Show error notification + var errorMsg = 'Failed to ban IP address'; + if (response.data && response.data.error_message) { + errorMsg = response.data.error_message; + } else if (response.data && response.data.error) { + errorMsg = response.data.error; + } + + new PNotify({ + title: 'Error', + text: errorMsg, + type: 'error', + delay: 5000 + }); + } + }, function (err) { + $scope.blockingIP = null; + var errorMessage = 'Failed to ban IP address'; + if (err.data && err.data.error_message) { + errorMessage = err.data.error_message; + } else if (err.data && err.data.error) { + errorMessage = err.data.error; + } else if (err.data && err.data.message) { + errorMessage = err.data.message; + } + + new PNotify({ + title: 'Error', + text: errorMessage, + type: 'error', + delay: 5000 + }); + }); + }; + + // Initial fetch + $scope.refreshTopProcesses(); + $scope.refreshSSHLogins(); + $scope.refreshSSHLogs(); + + // Chart.js chart objects + var trafficChart, diskIOChart, cpuChart; + // Data arrays for live graphs + var trafficLabels = [], rxData = [], txData = []; + var diskLabels = [], readData = [], writeData = []; + var cpuLabels = [], cpuUsageData = []; + // For rate calculation + var lastRx = null, lastTx = null, lastDiskRead = null, lastDiskWrite = null, lastCPU = null; + var lastCPUTimes = null; + var pollInterval = 2000; // ms + var maxPoints = 30; + + // Expose so switchTab can create charts on first tab click if they weren't created at load + window.cyberPanelSetupChartsIfNeeded = function() { + if (window.trafficChart && window.diskIOChart && window.cpuChart) return; + try { setupCharts(); } catch (e) { console.error('cyberPanelSetupChartsIfNeeded:', e); } + }; + + function pollDashboardStats() { + console.log('[dashboardStatsController] pollDashboardStats() called'); + console.log('[dashboardStatsController] Fetching dashboard stats from /base/getDashboardStats'); + $http({ + method: 'GET', + url: '/base/getDashboardStats', + headers: { + 'X-Requested-With': 'XMLHttpRequest', + 'X-CSRFToken': getCookie('csrftoken') + } + }).then(function(response) { + console.log('[dashboardStatsController] pollDashboardStats SUCCESS callback called'); + console.log('[dashboardStatsController] Dashboard stats response received:', response); + console.log('[dashboardStatsController] Response status:', response.status); + console.log('[dashboardStatsController] Response data:', response.data); + if (response.data && response.data.status === 1) { + $scope.totalUsers = response.data.total_users || 0; + $scope.totalSites = response.data.total_sites || 0; + $scope.totalWPSites = response.data.total_wp_sites || 0; + $scope.totalDBs = response.data.total_dbs || 0; + $scope.totalEmails = response.data.total_emails || 0; + $scope.totalFTPUsers = response.data.total_ftp_users || 0; + console.log('[dashboardStatsController] Dashboard stats updated:', { + users: $scope.totalUsers, + sites: $scope.totalSites, + wp: $scope.totalWPSites, + dbs: $scope.totalDBs, + emails: $scope.totalEmails, + ftp: $scope.totalFTPUsers + }); + // No $apply needed - $http already triggers digest cycle + } else { + // Set default values if request fails + console.error('[dashboardStatsController] Failed to load dashboard stats - invalid response:', response.data); + $scope.$apply(function() { + $scope.totalUsers = 0; + $scope.totalSites = 0; + $scope.totalWPSites = 0; + $scope.totalDBs = 0; + $scope.totalEmails = 0; + $scope.totalFTPUsers = 0; + }); + } + }, function(error) { + console.error('[dashboardStatsController] Error loading dashboard stats:', error); + console.error('[dashboardStatsController] Error status:', error.status); + console.error('[dashboardStatsController] Error data:', error.data); + // Set default values on error (no $apply needed - error callback also triggers digest) + $scope.totalUsers = 0; + $scope.totalSites = 0; + $scope.totalWPSites = 0; + $scope.totalDBs = 0; + $scope.totalEmails = 0; + $scope.totalFTPUsers = 0; + }); + } + + function pollTraffic() { + $http.get('/base/getTrafficStats').then(function(response) { + if (!response || !response.data) return; + if (response.data.admin_only) { + // Hide chart for non-admin users + $scope.hideSystemCharts = true; + return; + } + if (response.data.status === 1) { + var now = new Date(); + var rx = response.data.rx_bytes; + var tx = response.data.tx_bytes; + if (lastRx !== null && lastTx !== null) { + var rxRate = (rx - lastRx) / (pollInterval / 1000); // bytes/sec + var txRate = (tx - lastTx) / (pollInterval / 1000); + trafficLabels.push(now.toLocaleTimeString()); + rxData.push(rxRate); + txData.push(txRate); + if (trafficLabels.length > maxPoints) { + trafficLabels.shift(); rxData.shift(); txData.shift(); + } + if (trafficChart) { + trafficChart.data.labels = trafficLabels.slice(); + trafficChart.data.datasets[0].data = rxData.slice(); + trafficChart.data.datasets[1].data = txData.slice(); + trafficChart.update(); + console.log('trafficChart updated:', trafficChart.data.labels, trafficChart.data.datasets[0].data, trafficChart.data.datasets[1].data); + } + } else { + // First poll, push zero data point + trafficLabels.push(now.toLocaleTimeString()); + rxData.push(0); + txData.push(0); + if (trafficChart) { + trafficChart.data.labels = trafficLabels.slice(); + trafficChart.data.datasets[0].data = rxData.slice(); + trafficChart.data.datasets[1].data = txData.slice(); + trafficChart.update(); + console.log('trafficChart first update:', trafficChart.data.labels, trafficChart.data.datasets[0].data, trafficChart.data.datasets[1].data); + setTimeout(function() { + if (window.trafficChart) { + window.trafficChart.resize(); + window.trafficChart.update(); + console.log('trafficChart forced resize/update after first poll.'); + } + }, 1000); + } + } + lastRx = rx; lastTx = tx; + } else { + console.warn('pollTraffic: no data or status', response.data); + } + }).catch(function(err) { + console.warn('pollTraffic failed:', err); + }); + } + + function pollDiskIO() { + $http.get('/base/getDiskIOStats').then(function(response) { + if (!response || !response.data) return; + if (response.data.admin_only) { + // Hide chart for non-admin users + $scope.hideSystemCharts = true; + return; + } + if (response.data.status === 1) { + var now = new Date(); + var read = response.data.read_bytes; + var write = response.data.write_bytes; + if (lastDiskRead !== null && lastDiskWrite !== null) { + var readRate = (read - lastDiskRead) / (pollInterval / 1000); // bytes/sec + var writeRate = (write - lastDiskWrite) / (pollInterval / 1000); + diskLabels.push(now.toLocaleTimeString()); + readData.push(readRate); + writeData.push(writeRate); + if (diskLabels.length > maxPoints) { + diskLabels.shift(); readData.shift(); writeData.shift(); + } + if (diskIOChart) { + diskIOChart.data.labels = diskLabels.slice(); + diskIOChart.data.datasets[0].data = readData.slice(); + diskIOChart.data.datasets[1].data = writeData.slice(); + diskIOChart.update(); + } + } else { + // First poll, push zero data point + diskLabels.push(now.toLocaleTimeString()); + readData.push(0); + writeData.push(0); + if (diskIOChart) { + diskIOChart.data.labels = diskLabels.slice(); + diskIOChart.data.datasets[0].data = readData.slice(); + diskIOChart.data.datasets[1].data = writeData.slice(); + diskIOChart.update(); + } + } + lastDiskRead = read; lastDiskWrite = write; + } + }).catch(function(err) { + console.warn('pollDiskIO failed:', err); + }); + } + + function pollCPU() { + $http.get('/base/getCPULoadGraph').then(function(response) { + if (!response || !response.data) return; + if (response.data.admin_only) { + // Hide chart for non-admin users + $scope.hideSystemCharts = true; + return; + } + if (response.data.status === 1 && response.data.cpu_times && response.data.cpu_times.length >= 4) { + var now = new Date(); + var cpuTimes = response.data.cpu_times; + if (lastCPUTimes) { + var idle = cpuTimes[3]; + var total = cpuTimes.reduce(function(a, b) { return a + b; }, 0); + var lastIdle = lastCPUTimes[3]; + var lastTotal = lastCPUTimes.reduce(function(a, b) { return a + b; }, 0); + var idleDiff = idle - lastIdle; + var totalDiff = total - lastTotal; + var usage = totalDiff > 0 ? (100 * (1 - idleDiff / totalDiff)) : 0; + cpuLabels.push(now.toLocaleTimeString()); + cpuUsageData.push(usage); + if (cpuLabels.length > maxPoints) { + cpuLabels.shift(); cpuUsageData.shift(); + } + if (cpuChart) { + cpuChart.data.labels = cpuLabels.slice(); + cpuChart.data.datasets[0].data = cpuUsageData.slice(); + cpuChart.update(); + } + } else { + // First poll, push zero data point + cpuLabels.push(now.toLocaleTimeString()); + cpuUsageData.push(0); + if (cpuChart) { + cpuChart.data.labels = cpuLabels.slice(); + cpuChart.data.datasets[0].data = cpuUsageData.slice(); + cpuChart.update(); + } + } + lastCPUTimes = cpuTimes; + } + }).catch(function(err) { + console.warn('pollCPU failed:', err); + }); + } + + function setupCharts(retryCount) { + retryCount = retryCount || 0; + if (typeof Chart === 'undefined') { + if (retryCount < 3) { + $timeout(function() { setupCharts(retryCount + 1); }, 400); + } + return; + } + var trafficEl = document.getElementById('trafficChart'); + if (!trafficEl) { + if (retryCount < 5) { + $timeout(function() { setupCharts(retryCount + 1); }, 300); + } + return; + } + try { + var trafficCtx = trafficEl.getContext('2d'); + } catch (e) { + console.error('trafficChart getContext failed:', e); + return; + } + try { + trafficChart = new Chart(trafficCtx, { + type: 'line', + data: { + labels: [], + datasets: [ + { + label: 'Download', + data: [], + borderColor: '#5b5fcf', + backgroundColor: 'rgba(91,95,207,0.1)', + pointBackgroundColor: '#5b5fcf', + pointBorderColor: '#5b5fcf', + pointRadius: 3, + pointHoverRadius: 5, + borderWidth: 2, + tension: 0.4, + fill: true + }, + { + label: 'Upload', + data: [], + borderColor: '#4a90e2', + backgroundColor: 'rgba(74,144,226,0.1)', + pointBackgroundColor: '#4a90e2', + pointBorderColor: '#4a90e2', + pointRadius: 3, + pointHoverRadius: 5, + borderWidth: 2, + tension: 0.4, + fill: true + } + ] + }, + options: { + responsive: true, + maintainAspectRatio: false, + animation: { duration: 0 }, + plugins: { + legend: { + display: true, + position: 'top', + labels: { + font: { size: 12, weight: '600' }, + color: '#64748b', + usePointStyle: true, + padding: 20 + } + }, + title: { display: false }, + tooltip: { + enabled: true, + mode: 'index', + intersect: false, + backgroundColor: 'rgba(255,255,255,0.95)', + titleColor: '#2f3640', + bodyColor: '#64748b', + borderColor: '#e8e9ff', + borderWidth: 1, + cornerRadius: 8, + padding: 12 + } + }, + interaction: { mode: 'nearest', axis: 'x', intersect: false }, + scales: { + x: { + grid: { color: '#f0f0ff', drawBorder: false }, + ticks: { + font: { size: 11 }, + color: '#94a3b8', + maxTicksLimit: 8 + } + }, + y: { + beginAtZero: true, + grid: { color: '#f0f0ff', drawBorder: false }, + ticks: { + font: { size: 11 }, + color: '#94a3b8' + } + } + }, + layout: { padding: { top: 10, bottom: 10, left: 10, right: 10 } } + } + }); + window.trafficChart = trafficChart; + setTimeout(function() { + if (window.trafficChart) { + window.trafficChart.resize(); + window.trafficChart.update(); + console.log('trafficChart resized and updated after setup.'); + } + }, 500); + var diskEl = document.getElementById('diskIOChart'); + if (!diskEl) { console.warn('diskIOChart canvas not found'); return; } + var diskCtx = diskEl.getContext('2d'); + diskIOChart = new Chart(diskCtx, { + type: 'line', + data: { + labels: [], + datasets: [ + { + label: 'Read', + data: [], + borderColor: '#5b5fcf', + backgroundColor: 'rgba(91,95,207,0.1)', + pointBackgroundColor: '#5b5fcf', + pointBorderColor: '#5b5fcf', + pointRadius: 3, + pointHoverRadius: 5, + borderWidth: 2, + tension: 0.4, + fill: true + }, + { + label: 'Write', + data: [], + borderColor: '#e74c3c', + backgroundColor: 'rgba(231,76,60,0.1)', + pointBackgroundColor: '#e74c3c', + pointBorderColor: '#e74c3c', + pointRadius: 3, + pointHoverRadius: 5, + borderWidth: 2, + tension: 0.4, + fill: true + } + ] + }, + options: { + responsive: true, + maintainAspectRatio: false, + animation: { duration: 0 }, + plugins: { + legend: { + display: true, + position: 'top', + labels: { + font: { size: 12, weight: '600' }, + color: '#64748b', + usePointStyle: true, + padding: 20 + } + }, + title: { display: false }, + tooltip: { + enabled: true, + mode: 'index', + intersect: false, + backgroundColor: 'rgba(255,255,255,0.95)', + titleColor: '#2f3640', + bodyColor: '#64748b', + borderColor: '#e8e9ff', + borderWidth: 1, + cornerRadius: 8, + padding: 12 + } + }, + interaction: { mode: 'nearest', axis: 'x', intersect: false }, + scales: { + x: { + grid: { color: '#f0f0ff', drawBorder: false }, + ticks: { + font: { size: 11 }, + color: '#94a3b8', + maxTicksLimit: 8 + } + }, + y: { + beginAtZero: true, + grid: { color: '#f0f0ff', drawBorder: false }, + ticks: { + font: { size: 11 }, + color: '#94a3b8' + } + } + }, + layout: { padding: { top: 10, bottom: 10, left: 10, right: 10 } } + } + }); + window.diskIOChart = diskIOChart; + var cpuEl = document.getElementById('cpuChart'); + if (!cpuEl) { console.warn('cpuChart canvas not found'); return; } + var cpuCtx = cpuEl.getContext('2d'); + cpuChart = new Chart(cpuCtx, { + type: 'line', + data: { + labels: [], + datasets: [ + { + label: 'CPU Usage (%)', + data: [], + borderColor: '#5b5fcf', + backgroundColor: 'rgba(91,95,207,0.1)', + pointBackgroundColor: '#5b5fcf', + pointBorderColor: '#5b5fcf', + pointRadius: 3, + pointHoverRadius: 5, + borderWidth: 2, + tension: 0.4, + fill: true + } + ] + }, + options: { + responsive: true, + maintainAspectRatio: false, + animation: { duration: 0 }, + plugins: { + legend: { + display: true, + position: 'top', + labels: { + font: { size: 12, weight: '600' }, + color: '#64748b', + usePointStyle: true, + padding: 20 + } + }, + title: { display: false }, + tooltip: { + enabled: true, + mode: 'index', + intersect: false, + backgroundColor: 'rgba(255,255,255,0.95)', + titleColor: '#2f3640', + bodyColor: '#64748b', + borderColor: '#e8e9ff', + borderWidth: 1, + cornerRadius: 8, + padding: 12 + } + }, + interaction: { mode: 'nearest', axis: 'x', intersect: false }, + scales: { + x: { + grid: { color: '#f0f0ff', drawBorder: false }, + ticks: { + font: { size: 11 }, + color: '#94a3b8', + maxTicksLimit: 8 + } + }, + y: { + beginAtZero: true, + max: 100, + grid: { color: '#f0f0ff', drawBorder: false }, + ticks: { + font: { size: 11 }, + color: '#94a3b8' + } + } + }, + layout: { padding: { top: 10, bottom: 10, left: 10, right: 10 } } + } + }); + window.cpuChart = cpuChart; + } catch (e) { + console.error('setupCharts error:', e); + } + + // Redraw charts on tab shown + $("a[data-toggle='tab']").on('shown.bs.tab', function (e) { + setTimeout(function() { + if (trafficChart) trafficChart.resize(); + if (diskIOChart) diskIOChart.resize(); + if (cpuChart) cpuChart.resize(); + }, 100); + }); + + // Also handle custom tab switching + document.addEventListener('DOMContentLoaded', function() { + var tabs = document.querySelectorAll('a[data-toggle="tab"]'); + tabs.forEach(function(tab) { + tab.addEventListener('click', function(e) { + setTimeout(function() { + if (trafficChart) trafficChart.resize(); + if (diskIOChart) diskIOChart.resize(); + if (cpuChart) cpuChart.resize(); + }, 200); + }); + }); + }); + } + + // Initial setup - fetch stats immediately + pollDashboardStats(); + $scope.refreshTopProcesses(); + $scope.refreshSSHLogins(); + $scope.refreshSSHLogs(); + + $timeout(function() { + // Always create charts so Traffic/Disk IO/CPU tabs have something to show; admin check only affects hideSystemCharts + setupCharts(); + $http.get('/base/getAdminStatus').then(function(response) { + if (response.data && (response.data.admin === 1 || response.data.admin === true)) { + $scope.hideSystemCharts = false; + } else { + $scope.hideSystemCharts = true; + } + }).catch(function(err) { + console.warn('getAdminStatus failed:', err); + $scope.hideSystemCharts = true; + }); + + // Start polling for all stats (data feeds charts) + function pollAll() { + pollDashboardStats(); + pollTraffic(); + pollDiskIO(); + pollCPU(); + $scope.refreshTopProcesses(); + $timeout(pollAll, pollInterval); + } + pollAll(); + }, 800); + + // SSH User Activity Modal + $scope.showSSHActivityModal = false; + $scope.sshActivity = { processes: [], w: [] }; + $scope.sshActivityUser = ''; + $scope.loadingSSHActivity = false; + $scope.errorSSHActivity = ''; + + $scope.viewSSHActivity = function(login, event) { + $scope.showSSHActivityModal = true; + $scope.sshActivity = { processes: [], w: [] }; + $scope.sshActivityUser = login.user; + + // Extract IP from multiple sources - comprehensive extraction for IPv4 and IPv6 + var extractedIP = ''; + + // Method 1: Direct property access (highest priority - from backend) + if (login && login.ip) { + extractedIP = login.ip.toString().trim(); + } else if (login && login['ip']) { + extractedIP = login['ip'].toString().trim(); + } + + // Method 2: Alternative field names + if (!extractedIP && login) { + if (login.ipAddress) extractedIP = login.ipAddress.toString().trim(); + else if (login['IP Address']) extractedIP = login['IP Address'].toString().trim(); + else if (login['IP']) extractedIP = login['IP'].toString().trim(); + } + + // Method 3: Extract from raw line using regex (IPv4 and IPv6) + if (!extractedIP && login && login.raw) { + // Try IPv4 first (most common) + var ipv4Match = login.raw.match(/\b(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})\b/); + if (ipv4Match && ipv4Match[1]) { + var ipv4 = ipv4Match[1].trim(); + if (ipv4 !== '127.0.0.1' && ipv4 !== '0.0.0.0') { + extractedIP = ipv4; + } + } + + // If no valid IPv4, try IPv6 + if (!extractedIP) { + // IPv6 pattern: matches full IPv6 addresses and compressed forms + var ipv6Pattern = /([0-9a-fA-F]{0,4}:){2,7}[0-9a-fA-F]{0,4}/; + var ipv6Match = login.raw.match(ipv6Pattern); + if (ipv6Match && ipv6Match[0]) { + var ipv6 = ipv6Match[0].trim(); + if (ipv6 !== '::1' && ipv6.length > 0) { + extractedIP = ipv6; + } + } + } + } + + // Method 4: Try to get from event target data attribute as fallback + if (!extractedIP && event && event.currentTarget) { + var dataIP = event.currentTarget.getAttribute('data-ip'); + if (dataIP) extractedIP = dataIP.toString().trim(); + } + + // Final fallback: search entire raw line for any IP + if (!extractedIP && login && login.raw) { + // Try all IPv4 addresses + var allIPv4s = login.raw.match(/\b(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})\b/g); + if (allIPv4s && allIPv4s.length > 0) { + for (var i = 0; i < allIPv4s.length; i++) { + var ip = allIPv4s[i].trim(); + if (ip !== '127.0.0.1' && ip !== '0.0.0.0' && ip.length > 0) { + extractedIP = ip; + break; + } + } + } + // If no IPv4, try all IPv6 addresses + if (!extractedIP) { + var allIPv6s = login.raw.match(/([0-9a-fA-F]{0,4}:){2,7}[0-9a-fA-F]{0,4}/g); + if (allIPv6s && allIPv6s.length > 0) { + for (var j = 0; j < allIPv6s.length; j++) { + var ip6 = allIPv6s[j].trim(); + if (ip6 !== '::1' && ip6.length > 0) { + extractedIP = ip6; + break; + } + } + } + } + } + + // Final cleanup + $scope.sshActivityIP = (extractedIP || '').toString().trim(); + $scope.sshActivityTTY = ''; // Store TTY for kill session + // Check both 'session' and 'activity' fields for status + $scope.sshActivityStatus = login.session || login.activity || ''; + + // Use backend is_active field if available (most reliable) + // Fallback to checking session text if is_active is not set + // IMPORTANT: Check for both boolean true and string 'true' (JSON might serialize differently) + if (login.is_active !== undefined && login.is_active !== null) { + // Backend explicitly set is_active + $scope.isActiveSession = (login.is_active === true || login.is_active === 'true' || login.is_active === 1 || login.is_active === '1'); + console.log('Using backend is_active field:', login.is_active, '-> isActiveSession:', $scope.isActiveSession); + } else { + // Fallback: check session text + var sessionStatus = ($scope.sshActivityStatus || '').toLowerCase(); + $scope.isActiveSession = (sessionStatus.indexOf('still logged in') !== -1); + console.log('Using fallback session text check:', sessionStatus, '-> isActiveSession:', $scope.isActiveSession); + } + + // If IP is still empty, try one more time with more aggressive extraction + if (!$scope.sshActivityIP && login) { + console.log('IP still empty, trying aggressive extraction...'); + // Try every possible field name variation + var possibleIPFields = ['ip', 'IP', 'ipAddress', 'IP Address', 'ip_address', 'IP_ADDRESS', 'client_ip', 'clientIP', 'source_ip', 'sourceIP']; + for (var k = 0; k < possibleIPFields.length; k++) { + if (login[possibleIPFields[k]]) { + var testIP = login[possibleIPFields[k]].toString().trim(); + // Validate it looks like an IP (IPv4 or IPv6) + if (testIP.match(/^(\d{1,3}\.){3}\d{1,3}$/) || testIP.match(/^([0-9a-fA-F]{0,4}:){2,7}[0-9a-fA-F]{0,4}$/)) { + if (testIP !== '127.0.0.1' && testIP !== '0.0.0.0' && testIP !== '::1') { + $scope.sshActivityIP = testIP; + console.log('Found IP in field', possibleIPFields[k], ':', $scope.sshActivityIP); + break; + } + } + } + } + // Last resort: check if IP is in the table cell itself (from DOM) + if (!$scope.sshActivityIP && event && event.currentTarget) { + try { + var row = event.currentTarget.closest('tr'); + if (row) { + // Try different column positions (IP could be in different positions) + var cells = row.querySelectorAll('td'); + for (var cellIdx = 0; cellIdx < cells.length; cellIdx++) { + var cellText = cells[cellIdx].textContent.trim(); + // Check if this cell contains an IP address + var ipMatch = cellText.match(/^(\d{1,3}\.){3}\d{1,3}$/) || cellText.match(/^([0-9a-fA-F]{0,4}:){2,7}[0-9a-fA-F]{0,4}$/); + if (ipMatch && cellText !== '127.0.0.1' && cellText !== '0.0.0.0' && cellText !== '::1') { + $scope.sshActivityIP = cellText; + console.log('Found IP from table cell (column ' + cellIdx + '):', $scope.sshActivityIP); + break; + } + } + } + } catch (e) { + console.log('Error extracting IP from DOM:', e); + } + } + } + + // Debug logging - detailed inspection + console.log('View SSH Activity - Login object:', login); + console.log('Login keys:', Object.keys(login)); + console.log('login.ip:', login.ip); + console.log('login.is_active:', login.is_active); + console.log('Extracted IP:', $scope.sshActivityIP); + console.log('Session status:', $scope.sshActivityStatus); + console.log('Is active session:', $scope.isActiveSession); + $scope.showFullJSON = false; // Collapsible JSON view + $scope.loadingSSHActivity = true; + $scope.errorSSHActivity = ''; + $scope.killingProcess = null; + $scope.killingSession = false; + var tty = ''; + // Try to extract tty from login.raw or login.session if available + if (login.raw) { + var match = login.raw.match(/(pts\/[0-9]+)/); + if (match) { + tty = match[1]; + $scope.sshActivityTTY = tty; + } + } + // Also try to extract from session field or raw line + if (!tty && login.session) { + var sessionMatch = login.session.match(/(pts\/[0-9]+)/); + if (sessionMatch) { + tty = sessionMatch[1]; + $scope.sshActivityTTY = tty; + } + } + // Also check raw line for TTY + if (!tty && login.raw) { + var rawMatch = login.raw.match(/(pts\/[0-9]+)/); + if (rawMatch) { + tty = rawMatch[1]; + $scope.sshActivityTTY = tty; + } + } + // Make API call with IP included - reduced timeout for faster response + var requestData = { + user: login.user, + tty: tty, + ip: $scope.sshActivityIP + }; + + // Set shorter timeout for faster feedback + var timeoutPromise = $timeout(function() { + $scope.loadingSSHActivity = false; + $scope.errorSSHActivity = 'Request timed out. The user may not have any active processes.'; + $scope.sshActivity = { processes: [], w: [] }; + }, 5000); // 5 second timeout (reduced from 10) + + $http.post('/base/getSSHUserActivity', requestData, { timeout: 3000 }).then(function(response) { + $timeout.cancel(timeoutPromise); // Cancel timeout on success + $scope.loadingSSHActivity = false; + if (response.data) { + // Check if response has error field + if (response.data.error) { + $scope.errorSSHActivity = response.data.error; + $scope.sshActivity = { processes: [], w: [] }; + } else { + $scope.sshActivity = response.data; + // Ensure all expected fields exist + if (!$scope.sshActivity.processes) $scope.sshActivity.processes = []; + if (!$scope.sshActivity.w) $scope.sshActivity.w = []; + if (!$scope.sshActivity.process_tree) $scope.sshActivity.process_tree = []; + if (!$scope.sshActivity.shell_history) $scope.sshActivity.shell_history = []; + + // Try to extract TTY from processes if not already set + if (!$scope.sshActivityTTY && response.data.processes && response.data.processes.length > 0) { + var firstProcess = response.data.processes[0]; + if (firstProcess.tty) { + $scope.sshActivityTTY = firstProcess.tty; + } + } + // Update active session status - prioritize backend is_active field + // The backend already determined if session is active, so trust that first + // Only update if we have additional evidence (processes/w output) + var hasProcesses = response.data.processes && response.data.processes.length > 0; + var hasActiveW = response.data.w && response.data.w.length > 0; + + // If backend says it's active, keep it active (don't override) + // If backend says inactive but we find processes/w, mark as active + if ($scope.isActiveSession === true) { + // Backend already marked as active, keep it that way + // (processes might not have loaded yet, but session is still active) + } else if (hasProcesses || hasActiveW) { + // Backend said inactive, but we found evidence it's active + $scope.isActiveSession = true; + } + // If backend said inactive and no processes found, keep as inactive + + // Debug logging + console.log('SSH Activity loaded:', { + processes: response.data.processes ? response.data.processes.length : 0, + w: response.data.w ? response.data.w.length : 0, + hasProcesses: hasProcesses, + hasActiveW: hasActiveW, + originalStatus: $scope.sshActivityStatus, + isActiveSession: $scope.isActiveSession, + ip: $scope.sshActivityIP + }); + } + } else { + $scope.sshActivity = { processes: [], w: [] }; + $scope.errorSSHActivity = 'No data returned from server.'; + } + }, function(err) { + $timeout.cancel(timeoutPromise); // Cancel timeout on error + $scope.loadingSSHActivity = false; + var errorMsg = 'Failed to fetch activity.'; + + // Handle different error scenarios + if (err.data) { + // Server returned error data + if (err.data.error) { + errorMsg = err.data.error; + } else if (typeof err.data === 'string') { + errorMsg = err.data; + } else if (err.data.message) { + errorMsg = err.data.message; + } + } else if (err.status === 0 || err.status === -1) { + errorMsg = 'Network error. Please check your connection and try again.'; + } else if (err.status >= 500) { + errorMsg = 'Server error (HTTP ' + err.status + '). Please try again later.'; + } else if (err.status === 404) { + errorMsg = 'Endpoint not found. Please refresh the page.'; + } else if (err.status === 403) { + errorMsg = 'Access denied. Admin privileges required.'; + } else if (err.status === 400) { + errorMsg = 'Invalid request. Please check the user information.'; + } else if (err.status) { + errorMsg = 'Request failed with status ' + err.status + '.'; + } else if (err.message) { + errorMsg = err.message; + } + + $scope.errorSSHActivity = errorMsg; + // Set empty activity data so modal can still display + $scope.sshActivity = { + processes: [], + w: [], + process_tree: [], + shell_history: [], + disk_usage: '', + geoip: {} + }; + + // Log error for debugging + console.error('SSH Activity fetch error:', err); + }); + }; + + // Kill individual process + $scope.killProcess = function(pid, user) { + if (!confirm('Are you sure you want to force kill process ' + pid + '? This action cannot be undone.')) { + return; + } + + $scope.killingProcess = pid; + + var data = { + pid: pid, + user: user + }; + + var config = { + headers: { + 'X-CSRFToken': getCookie('csrftoken') + } + }; + + $http.post('/base/killSSHProcess', data, config).then(function(response) { + $scope.killingProcess = null; + if (response.data && response.data.success) { + new PNotify({ + title: 'Process Killed', + text: response.data.message || 'Process ' + pid + ' has been terminated.', + type: 'success', + delay: 3000 + }); + // Refresh activity to update process list + $scope.viewSSHActivity({ user: user, ip: $scope.sshActivityIP, raw: '', session: '' }); + } else { + new PNotify({ + title: 'Error', + text: (response.data && response.data.error) || 'Failed to kill process.', + type: 'error', + delay: 5000 + }); + } + }, function(err) { + $scope.killingProcess = null; + var errorMsg = 'Failed to kill process.'; + if (err.data && err.data.error) { + errorMsg = err.data.error; + } else if (err.data && err.data.message) { + errorMsg = err.data.message; + } + new PNotify({ + title: 'Error', + text: errorMsg, + type: 'error', + delay: 5000 + }); + }); + }; + + // Kill entire SSH session + $scope.killSSHSession = function(user, tty) { + var confirmMsg = 'Are you sure you want to kill all processes for user ' + user; + if (tty) { + confirmMsg += ' on terminal ' + tty; + } + confirmMsg += '? This will terminate their SSH session.'; + + if (!confirm(confirmMsg)) { + return; + } + + $scope.killingSession = true; + + var data = { + user: user, + tty: tty || '' + }; + + var config = { + headers: { + 'X-CSRFToken': getCookie('csrftoken') + } + }; + + $http.post('/base/killSSHSession', data, config).then(function(response) { + $scope.killingSession = false; + if (response.data && response.data.success) { + new PNotify({ + title: 'Session Terminated', + text: response.data.message || 'SSH session has been terminated successfully.', + type: 'success', + delay: 3000 + }); + // Close modal and refresh page after a delay + setTimeout(function() { + $scope.closeSSHActivityModal(); + location.reload(); + }, 1500); + } else { + new PNotify({ + title: 'Error', + text: (response.data && response.data.error) || 'Failed to kill session.', + type: 'error', + delay: 5000 + }); + } + }, function(err) { + $scope.killingSession = false; + var errorMsg = 'Failed to kill session.'; + if (err.data && err.data.error) { + errorMsg = err.data.error; + } else if (err.data && err.data.message) { + errorMsg = err.data.message; + } + new PNotify({ + title: 'Error', + text: errorMsg, + type: 'error', + delay: 5000 + }); + }); + }; + + $scope.closeSSHActivityModal = function() { + $scope.showSSHActivityModal = false; + $scope.sshActivity = { processes: [], w: [] }; + $scope.sshActivityUser = ''; + $scope.sshActivityIP = ''; // Clear IP when closing modal + $scope.sshActivityTTY = ''; // Clear TTY when closing modal + $scope.sshActivityStatus = ''; // Clear activity status + $scope.isActiveSession = false; // Reset active session flag + $scope.showFullJSON = false; // Reset JSON view + $scope.loadingSSHActivity = false; + $scope.errorSSHActivity = ''; + $scope.killingProcess = null; + $scope.killingSession = false; + }; + + // Close modal when clicking backdrop + $scope.closeModalOnBackdrop = function(event) { + if (event.target === event.currentTarget) { + $scope.closeSSHActivityModal(); + } + }; +}; +app.controller('dashboardStatsController', dashboardStatsControllerFn); +app.controller('newDashboardStat', dashboardStatsControllerFn); \ No newline at end of file