mirror of
https://github.com/usmannasir/cyberpanel.git
synced 2026-02-05 06:09:06 +01:00
v2.5.5-dev: FTP Create Account fix, ftp_quotas table, CP_VERSION, mailUtilities indent, deploy scripts and docs
- baseTemplate: CP_VERSION from CYBERPANEL_FULL_VERSION (2.5.5.dev) for cache busting - FTP Create Account: inline script + polling + scope sync so details form shows after website select - ftp.js: showFTPDetails, select2/change handlers (ftp/static, static, public/static) - sql/create_ftp_quotas.sql + deploy-ftp-quotas-table.sh for /ftp/quotaManagement - plogical/mailUtilities.py: indentation fix in DNS query try/except block - deploy-ftp-create-account-fix.sh, to-do docs (FTP-QUOTAS-TABLE-FIX, V2.5.5-DEV-FIXES-AND-DEPLOY, RUNTIME-VS-REPO)
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
{% load i18n %}
|
||||
{% get_current_language as LANGUAGE_CODE %}
|
||||
{% with CP_VERSION="2.4.4.1" %}
|
||||
{% with CP_VERSION=CYBERPANEL_FULL_VERSION|default:"2.5.5.dev" %}
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" ng-app="CyberCP">
|
||||
<head>
|
||||
|
||||
64
deploy-ftp-create-account-fix.sh
Normal file
64
deploy-ftp-create-account-fix.sh
Normal file
@@ -0,0 +1,64 @@
|
||||
#!/bin/bash
|
||||
# Deploy FTP Create Account template fix to a CyberPanel installation.
|
||||
# Copies the updated createFTPAccount.html and optionally restarts lscpd.
|
||||
#
|
||||
# Usage (run from anywhere):
|
||||
# sudo bash /home/cyberpanel-repo/deploy-ftp-create-account-fix.sh
|
||||
# sudo bash deploy-ftp-create-account-fix.sh [REPO_DIR] [CP_DIR]
|
||||
#
|
||||
# Or from repo root: cd /home/cyberpanel-repo && sudo bash deploy-ftp-create-account-fix.sh
|
||||
|
||||
set -e
|
||||
|
||||
log() { echo "[$(date +%Y-%m-%d\ %H:%M:%S)] $*"; }
|
||||
err() { log "ERROR: $*" >&2; }
|
||||
|
||||
# Resolve REPO_DIR
|
||||
if [[ -n "$1" && -d "$1/ftp" ]]; then
|
||||
REPO_DIR="$1"
|
||||
shift
|
||||
elif [[ -d "$(cd "$(dirname "${BASH_SOURCE[0]}")" 2>/dev/null && pwd)/ftp" ]]; then
|
||||
REPO_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
elif [[ -d "/home/cyberpanel-repo/ftp" ]]; then
|
||||
REPO_DIR="/home/cyberpanel-repo"
|
||||
elif [[ -d "./ftp" ]]; then
|
||||
REPO_DIR="$(pwd)"
|
||||
else
|
||||
err "Repo not found. Use: sudo bash /home/cyberpanel-repo/deploy-ftp-create-account-fix.sh"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
CP_DIR="${1:-/usr/local/CyberCP}"
|
||||
RESTART_LSCPD="${RESTART_LSCPD:-1}"
|
||||
|
||||
if [[ ! -d "$CP_DIR" ]]; then
|
||||
err "CyberPanel directory not found: $CP_DIR"
|
||||
exit 1
|
||||
fi
|
||||
if [[ ! -f "$REPO_DIR/ftp/templates/ftp/createFTPAccount.html" ]]; then
|
||||
err "Source template not found in: $REPO_DIR"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
log "REPO_DIR=$REPO_DIR"
|
||||
log "CP_DIR=$CP_DIR"
|
||||
|
||||
SRC="$REPO_DIR/ftp/templates/ftp/createFTPAccount.html"
|
||||
DST="$CP_DIR/ftp/templates/ftp/createFTPAccount.html"
|
||||
mkdir -p "$(dirname "$DST")"
|
||||
cp -f "$SRC" "$DST"
|
||||
log "Copied: ftp/templates/ftp/createFTPAccount.html"
|
||||
|
||||
if [[ "$RESTART_LSCPD" =~ ^(1|yes|true)$ ]]; then
|
||||
if systemctl is-active --quiet lscpd 2>/dev/null; then
|
||||
log "Restarting lscpd..."
|
||||
systemctl restart lscpd || { err "lscpd restart failed"; exit 1; }
|
||||
log "lscpd restarted."
|
||||
else
|
||||
log "lscpd not running or not a systemd service; skip restart."
|
||||
fi
|
||||
else
|
||||
log "Skipping restart (set RESTART_LSCPD=1 to restart lscpd)."
|
||||
fi
|
||||
|
||||
log "Deploy complete. Hard-refresh /ftp/createFTPAccount in the browser (Ctrl+Shift+R)."
|
||||
65
deploy-ftp-quotas-table.sh
Normal file
65
deploy-ftp-quotas-table.sh
Normal file
@@ -0,0 +1,65 @@
|
||||
#!/bin/bash
|
||||
# Create the missing ftp_quotas table in the CyberPanel database.
|
||||
# Fixes: (1146, "Table 'cyberpanel.ftp_quotas' doesn't exist") on /ftp/quotaManagement
|
||||
#
|
||||
# Usage:
|
||||
# sudo bash /home/cyberpanel-repo/deploy-ftp-quotas-table.sh
|
||||
# sudo bash deploy-ftp-quotas-table.sh [REPO_DIR] [CP_DIR]
|
||||
|
||||
set -e
|
||||
|
||||
log() { echo "[$(date +%Y-%m-%d\ %H:%M:%S)] $*"; }
|
||||
err() { log "ERROR: $*" >&2; }
|
||||
|
||||
if [[ -n "$1" && -f "$1/sql/create_ftp_quotas.sql" ]]; then
|
||||
REPO_DIR="$1"
|
||||
shift
|
||||
elif [[ -f "$(cd "$(dirname "${BASH_SOURCE[0]}")" 2>/dev/null && pwd)/sql/create_ftp_quotas.sql" ]]; then
|
||||
REPO_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
elif [[ -f "/home/cyberpanel-repo/sql/create_ftp_quotas.sql" ]]; then
|
||||
REPO_DIR="/home/cyberpanel-repo"
|
||||
else
|
||||
err "sql/create_ftp_quotas.sql not found."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
CP_DIR="${1:-/usr/local/CyberCP}"
|
||||
SQL_FILE="$REPO_DIR/sql/create_ftp_quotas.sql"
|
||||
|
||||
if [[ ! -d "$CP_DIR" ]]; then
|
||||
err "CyberPanel directory not found: $CP_DIR"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
log "REPO_DIR=$REPO_DIR"
|
||||
log "CP_DIR=$CP_DIR"
|
||||
|
||||
mkdir -p "$CP_DIR/sql"
|
||||
cp -f "$SQL_FILE" "$CP_DIR/sql/create_ftp_quotas.sql"
|
||||
log "Copied create_ftp_quotas.sql to $CP_DIR/sql/"
|
||||
|
||||
# Run SQL using Django DB connection (no password on command line)
|
||||
log "Creating ftp_quotas table..."
|
||||
export CP_DIR
|
||||
python3 << 'PYEOF'
|
||||
import os
|
||||
import sys
|
||||
|
||||
cp_dir = os.environ.get('CP_DIR', '/usr/local/CyberCP')
|
||||
sys.path.insert(0, cp_dir)
|
||||
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'CyberCP.settings')
|
||||
|
||||
import django
|
||||
django.setup()
|
||||
|
||||
from django.db import connection
|
||||
|
||||
with open(os.path.join(cp_dir, 'sql', 'create_ftp_quotas.sql'), 'r') as f:
|
||||
sql = f.read()
|
||||
|
||||
with connection.cursor() as cursor:
|
||||
cursor.execute(sql)
|
||||
print('Executed CREATE TABLE IF NOT EXISTS ftp_quotas.')
|
||||
PYEOF
|
||||
|
||||
log "Done. Reload https://207.180.193.210:2087/ftp/quotaManagement"
|
||||
@@ -15,7 +15,7 @@ app.controller('createFTPAccount', function ($scope, $http) {
|
||||
$scope.generatedPasswordView = true;
|
||||
|
||||
$(document).ready(function () {
|
||||
$( ".ftpDetails" ).hide();
|
||||
$( ".ftpDetails, .account-details" ).hide();
|
||||
$( ".ftpPasswordView" ).hide();
|
||||
|
||||
// Only use select2 if it's actually a function (avoids errors when Rocket Loader defers scripts)
|
||||
@@ -26,9 +26,11 @@ app.controller('createFTPAccount', function ($scope, $http) {
|
||||
$sel.select2();
|
||||
$sel.on('select2:select', function (e) {
|
||||
var data = e.params.data;
|
||||
$scope.ftpDomain = data.text;
|
||||
$scope.$apply();
|
||||
$(".ftpDetails").show();
|
||||
$scope.$evalAsync(function () {
|
||||
$scope.ftpDomain = data.text;
|
||||
$scope.ftpDetails = false;
|
||||
});
|
||||
$(".ftpDetails, .account-details").show();
|
||||
});
|
||||
} else {
|
||||
initNativeSelect();
|
||||
@@ -41,20 +43,23 @@ app.controller('createFTPAccount', function ($scope, $http) {
|
||||
}
|
||||
function initNativeSelect() {
|
||||
$('.create-ftp-acct-select').off('select2:select').on('change', function () {
|
||||
$scope.ftpDomain = $(this).val();
|
||||
$scope.$apply();
|
||||
$(".ftpDetails").show();
|
||||
var val = $(this).val();
|
||||
$scope.$evalAsync(function () {
|
||||
$scope.ftpDomain = val;
|
||||
$scope.ftpDetails = (val && val !== '') ? false : true;
|
||||
});
|
||||
$(".ftpDetails, .account-details").show();
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
$scope.showFTPDetails = function() {
|
||||
if ($scope.ftpDomain && $scope.ftpDomain !== "") {
|
||||
$(".ftpDetails").show();
|
||||
$scope.ftpDetails = false;
|
||||
$(".ftpDetails, .account-details").show();
|
||||
} else {
|
||||
$(".ftpDetails").hide();
|
||||
$scope.ftpDetails = true;
|
||||
$(".ftpDetails, .account-details").hide();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -452,18 +452,65 @@
|
||||
<div class="col-md-8">
|
||||
<div class="form-group">
|
||||
<label class="form-label">{% trans "Select Website" %}</label>
|
||||
<select ng-model="ftpDomain" ng-change="showFTPDetails()" class="form-control create-ftp-acct-select">
|
||||
<select id="create-ftp-website-select" ng-model="ftpDomain" ng-change="showFTPDetails()" class="form-control create-ftp-acct-select">
|
||||
<option value="">{% trans "Choose a website..." %}</option>
|
||||
{% for items in websiteList %}
|
||||
<option value="{{ items }}">{{ items }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
<script data-cfasync="false">
|
||||
(function() {
|
||||
function hasWebsiteSelected() {
|
||||
var sel = document.getElementById('create-ftp-website-select');
|
||||
if (!sel) return false;
|
||||
if (sel.value && sel.value !== '') return true;
|
||||
var rendered = document.querySelector('.select2-selection__rendered');
|
||||
var text = rendered ? (rendered.title || rendered.textContent || '').trim() : '';
|
||||
return text !== '' && text.indexOf('Choose') === -1;
|
||||
}
|
||||
function showFTPFormSection() {
|
||||
var sel = document.getElementById('create-ftp-website-select');
|
||||
var section = document.querySelector('.account-details.ftpDetails');
|
||||
var container = document.querySelector('.modern-container[ng-controller="createFTPAccount"]');
|
||||
if (section && hasWebsiteSelected()) {
|
||||
section.classList.remove('ng-hide');
|
||||
section.style.setProperty('display', 'block', 'important');
|
||||
try { section.scrollIntoView({ behavior: 'smooth', block: 'nearest' }); } catch (e) {}
|
||||
if (typeof angular !== 'undefined' && container) {
|
||||
try {
|
||||
var scope = angular.element(container).scope();
|
||||
if (scope) {
|
||||
scope.ftpDetails = false;
|
||||
scope.$evalAsync(function() { scope.ftpDetails = false; });
|
||||
}
|
||||
} catch (e) {}
|
||||
}
|
||||
}
|
||||
}
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
var sel = document.getElementById('create-ftp-website-select');
|
||||
if (!sel) return;
|
||||
sel.addEventListener('change', showFTPFormSection);
|
||||
sel.addEventListener('input', showFTPFormSection);
|
||||
if (sel.value) showFTPFormSection();
|
||||
var delays = [50, 150, 350, 600, 1000, 1500, 2200, 3000];
|
||||
for (var i = 0; i < delays.length; i++) {
|
||||
setTimeout(showFTPFormSection, delays[i]);
|
||||
}
|
||||
var pollCount = 0;
|
||||
var pollId = setInterval(function() {
|
||||
showFTPFormSection();
|
||||
if (++pollCount >= 20) clearInterval(pollId);
|
||||
}, 200);
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div ng-hide="ftpDetails" class="account-details">
|
||||
<div ng-hide="ftpDetails" class="account-details ftpDetails">
|
||||
<h3><i class="fas fa-cog"></i> {% trans "FTP Account Details" %}</h3>
|
||||
|
||||
<div class="row">
|
||||
|
||||
@@ -1682,69 +1682,69 @@ LogFile /var/log/clamav/clamav.log
|
||||
|
||||
# Query each DNS server
|
||||
for url in urls:
|
||||
try:
|
||||
response = requests.get(f'{url}/index.php?ip={ip_address}', timeout=5)
|
||||
try:
|
||||
response = requests.get(f'{url}/index.php?ip={ip_address}', timeout=5)
|
||||
|
||||
if os.path.exists(ProcessUtilities.debugPath):
|
||||
logging.CyberCPLogFileWriter.writeToFile(f'url to call {ip_address} is {url}')
|
||||
if os.path.exists(ProcessUtilities.debugPath):
|
||||
logging.CyberCPLogFileWriter.writeToFile(f'url to call {ip_address} is {url}')
|
||||
|
||||
if response.status_code == 200:
|
||||
try:
|
||||
data = response.json()
|
||||
if response.status_code == 200:
|
||||
try:
|
||||
data = response.json()
|
||||
|
||||
if os.path.exists(ProcessUtilities.debugPath):
|
||||
logging.CyberCPLogFileWriter.writeToFile(f'response from dns system {str(data)}')
|
||||
if os.path.exists(ProcessUtilities.debugPath):
|
||||
logging.CyberCPLogFileWriter.writeToFile(f'response from dns system {str(data)}')
|
||||
|
||||
# Validate response structure
|
||||
if not isinstance(data, dict):
|
||||
logging.CyberCPLogFileWriter.writeToFile(f'Invalid response format from {url}: not a dictionary')
|
||||
continue
|
||||
# Validate response structure
|
||||
if not isinstance(data, dict):
|
||||
logging.CyberCPLogFileWriter.writeToFile(f'Invalid response format from {url}: not a dictionary')
|
||||
continue
|
||||
|
||||
if 'status' not in data:
|
||||
logging.CyberCPLogFileWriter.writeToFile(f'Response from {url} missing "status" key')
|
||||
continue
|
||||
if 'status' not in data:
|
||||
logging.CyberCPLogFileWriter.writeToFile(f'Response from {url} missing "status" key')
|
||||
continue
|
||||
|
||||
if data['status'] == 1:
|
||||
# Validate results structure
|
||||
if 'results' not in data or not isinstance(data['results'], dict):
|
||||
logging.CyberCPLogFileWriter.writeToFile(f'Response from {url} missing or invalid "results" key')
|
||||
if data['status'] == 1:
|
||||
# Validate results structure
|
||||
if 'results' not in data or not isinstance(data['results'], dict):
|
||||
logging.CyberCPLogFileWriter.writeToFile(f'Response from {url} missing or invalid "results" key')
|
||||
continue
|
||||
|
||||
results_dict = data['results']
|
||||
|
||||
# Safely extract results from different DNS servers
|
||||
dns_servers = ['8.8.8.8', '1.1.1.1', '9.9.9.9']
|
||||
for dns_server in dns_servers:
|
||||
if dns_server in results_dict:
|
||||
result_value = results_dict[dns_server]
|
||||
if result_value and result_value not in results:
|
||||
results.append(result_value)
|
||||
|
||||
successful_queries += 1
|
||||
else:
|
||||
if os.path.exists(ProcessUtilities.debugPath):
|
||||
logging.CyberCPLogFileWriter.writeToFile(f'DNS server {url} returned status != 1: {data.get("status", "unknown")}')
|
||||
except ValueError as e:
|
||||
logging.CyberCPLogFileWriter.writeToFile(f'Failed to parse JSON response from {url}: {str(e)}')
|
||||
continue
|
||||
except KeyError as e:
|
||||
logging.CyberCPLogFileWriter.writeToFile(f'Missing key in response from {url}: {str(e)}')
|
||||
continue
|
||||
else:
|
||||
if os.path.exists(ProcessUtilities.debugPath):
|
||||
logging.CyberCPLogFileWriter.writeToFile(f'DNS server {url} returned HTTP {response.status_code}')
|
||||
except Timeout as e:
|
||||
logging.CyberCPLogFileWriter.writeToFile(f'Timeout while querying DNS server {url}: {str(e)}')
|
||||
continue
|
||||
except ConnectionError as e:
|
||||
logging.CyberCPLogFileWriter.writeToFile(f'Connection error while querying DNS server {url}: {str(e)}')
|
||||
continue
|
||||
except RequestException as e:
|
||||
logging.CyberCPLogFileWriter.writeToFile(f'Request error while querying DNS server {url}: {str(e)}')
|
||||
continue
|
||||
except Exception as e:
|
||||
logging.CyberCPLogFileWriter.writeToFile(f'Unexpected error while querying DNS server {url}: {str(e)}')
|
||||
continue
|
||||
|
||||
results_dict = data['results']
|
||||
|
||||
# Safely extract results from different DNS servers
|
||||
dns_servers = ['8.8.8.8', '1.1.1.1', '9.9.9.9']
|
||||
for dns_server in dns_servers:
|
||||
if dns_server in results_dict:
|
||||
result_value = results_dict[dns_server]
|
||||
if result_value and result_value not in results:
|
||||
results.append(result_value)
|
||||
|
||||
successful_queries += 1
|
||||
else:
|
||||
if os.path.exists(ProcessUtilities.debugPath):
|
||||
logging.CyberCPLogFileWriter.writeToFile(f'DNS server {url} returned status != 1: {data.get("status", "unknown")}')
|
||||
except ValueError as e:
|
||||
logging.CyberCPLogFileWriter.writeToFile(f'Failed to parse JSON response from {url}: {str(e)}')
|
||||
continue
|
||||
except KeyError as e:
|
||||
logging.CyberCPLogFileWriter.writeToFile(f'Missing key in response from {url}: {str(e)}')
|
||||
continue
|
||||
else:
|
||||
if os.path.exists(ProcessUtilities.debugPath):
|
||||
logging.CyberCPLogFileWriter.writeToFile(f'DNS server {url} returned HTTP {response.status_code}')
|
||||
except Timeout as e:
|
||||
logging.CyberCPLogFileWriter.writeToFile(f'Timeout while querying DNS server {url}: {str(e)}')
|
||||
continue
|
||||
except ConnectionError as e:
|
||||
logging.CyberCPLogFileWriter.writeToFile(f'Connection error while querying DNS server {url}: {str(e)}')
|
||||
continue
|
||||
except RequestException as e:
|
||||
logging.CyberCPLogFileWriter.writeToFile(f'Request error while querying DNS server {url}: {str(e)}')
|
||||
continue
|
||||
except Exception as e:
|
||||
logging.CyberCPLogFileWriter.writeToFile(f'Unexpected error while querying DNS server {url}: {str(e)}')
|
||||
continue
|
||||
|
||||
if os.path.exists(ProcessUtilities.debugPath):
|
||||
logging.CyberCPLogFileWriter.writeToFile(f'rDNS result of {ip_address} is {str(results)} (successful queries: {successful_queries}/{len(urls)})')
|
||||
|
||||
@@ -8,20 +8,44 @@ app.controller('createFTPAccount', function ($scope, $http) {
|
||||
|
||||
|
||||
|
||||
$scope.ftpLoading = false;
|
||||
$scope.ftpDetails = true;
|
||||
|
||||
$(document).ready(function () {
|
||||
$( ".ftpDetails" ).hide();
|
||||
$( ".ftpDetails, .account-details" ).hide();
|
||||
$( ".ftpPasswordView" ).hide();
|
||||
$('.create-ftp-acct-select').select2();
|
||||
if (typeof $ !== 'undefined' && $ && typeof $.fn !== 'undefined' && typeof $.fn.select2 === 'function') {
|
||||
try {
|
||||
var $sel = $('.create-ftp-acct-select');
|
||||
if ($sel.length) {
|
||||
$sel.select2();
|
||||
$sel.on('select2:select', function (e) {
|
||||
var data = e.params.data;
|
||||
$scope.ftpDomain = data.text;
|
||||
$scope.ftpDetails = false;
|
||||
$scope.$apply();
|
||||
$(".ftpDetails, .account-details").show();
|
||||
});
|
||||
}
|
||||
} catch (err) {}
|
||||
}
|
||||
$('.create-ftp-acct-select').off('select2:select').on('change', function () {
|
||||
$scope.ftpDomain = $(this).val();
|
||||
$scope.ftpDetails = ($scope.ftpDomain && $scope.ftpDomain !== '') ? false : true;
|
||||
$scope.$apply();
|
||||
$(".ftpDetails, .account-details").show();
|
||||
});
|
||||
});
|
||||
|
||||
$('.create-ftp-acct-select').on('select2:select', function (e) {
|
||||
var data = e.params.data;
|
||||
$scope.ftpDomain = data.text;
|
||||
$( ".ftpDetails" ).show();
|
||||
|
||||
});
|
||||
|
||||
$scope.ftpLoading = true;
|
||||
$scope.showFTPDetails = function() {
|
||||
if ($scope.ftpDomain && $scope.ftpDomain !== "") {
|
||||
$scope.ftpDetails = false;
|
||||
$(".ftpDetails, .account-details").show();
|
||||
} else {
|
||||
$scope.ftpDetails = true;
|
||||
$(".ftpDetails, .account-details").hide();
|
||||
}
|
||||
};
|
||||
|
||||
$scope.createFTPAccount = function () {
|
||||
|
||||
|
||||
21
sql/create_ftp_quotas.sql
Normal file
21
sql/create_ftp_quotas.sql
Normal file
@@ -0,0 +1,21 @@
|
||||
-- Create ftp_quotas table for FTP Quota Management (websiteFunctions.models.FTPQuota)
|
||||
-- Run once per CyberPanel database. Safe to run: uses IF NOT EXISTS.
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `ftp_quotas` (
|
||||
`id` INT AUTO_INCREMENT NOT NULL PRIMARY KEY,
|
||||
`user_id` INT NOT NULL,
|
||||
`ftp_user` VARCHAR(255) NOT NULL,
|
||||
`domain_id` INT NULL,
|
||||
`quota_size_mb` INT NOT NULL DEFAULT 0,
|
||||
`quota_used_mb` INT NOT NULL DEFAULT 0,
|
||||
`quota_files` INT NOT NULL DEFAULT 0,
|
||||
`quota_files_used` INT NOT NULL DEFAULT 0,
|
||||
`is_active` TINYINT(1) NOT NULL DEFAULT 1,
|
||||
`created_at` DATETIME(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
|
||||
`updated_at` DATETIME(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6),
|
||||
UNIQUE KEY `ftp_quotas_ftp_user_unique` (`ftp_user`),
|
||||
KEY `ftp_quotas_user_id` (`user_id`),
|
||||
KEY `ftp_quotas_domain_id` (`domain_id`),
|
||||
CONSTRAINT `ftp_quotas_user_id_fk` FOREIGN KEY (`user_id`) REFERENCES `loginSystem_administrator` (`id`) ON DELETE CASCADE,
|
||||
CONSTRAINT `ftp_quotas_domain_id_fk` FOREIGN KEY (`domain_id`) REFERENCES `websiteFunctions_websites` (`id`) ON DELETE CASCADE
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
@@ -15,7 +15,7 @@ app.controller('createFTPAccount', function ($scope, $http) {
|
||||
$scope.generatedPasswordView = true;
|
||||
|
||||
$(document).ready(function () {
|
||||
$( ".ftpDetails" ).hide();
|
||||
$( ".ftpDetails, .account-details" ).hide();
|
||||
$( ".ftpPasswordView" ).hide();
|
||||
|
||||
// Only use select2 if it's actually a function (avoids errors when Rocket Loader defers scripts)
|
||||
@@ -27,8 +27,9 @@ app.controller('createFTPAccount', function ($scope, $http) {
|
||||
$sel.on('select2:select', function (e) {
|
||||
var data = e.params.data;
|
||||
$scope.ftpDomain = data.text;
|
||||
$scope.ftpDetails = false;
|
||||
$scope.$apply();
|
||||
$(".ftpDetails").show();
|
||||
$(".ftpDetails, .account-details").show();
|
||||
});
|
||||
} else {
|
||||
initNativeSelect();
|
||||
@@ -42,19 +43,20 @@ app.controller('createFTPAccount', function ($scope, $http) {
|
||||
function initNativeSelect() {
|
||||
$('.create-ftp-acct-select').off('select2:select').on('change', function () {
|
||||
$scope.ftpDomain = $(this).val();
|
||||
$scope.ftpDetails = ($scope.ftpDomain && $scope.ftpDomain !== '') ? false : true;
|
||||
$scope.$apply();
|
||||
$(".ftpDetails").show();
|
||||
$(".ftpDetails, .account-details").show();
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
$scope.showFTPDetails = function() {
|
||||
if ($scope.ftpDomain && $scope.ftpDomain !== "") {
|
||||
$(".ftpDetails").show();
|
||||
$scope.ftpDetails = false;
|
||||
$(".ftpDetails, .account-details").show();
|
||||
} else {
|
||||
$(".ftpDetails").hide();
|
||||
$scope.ftpDetails = true;
|
||||
$(".ftpDetails, .account-details").hide();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
25
to-do/FTP-QUOTAS-TABLE-FIX.md
Normal file
25
to-do/FTP-QUOTAS-TABLE-FIX.md
Normal file
@@ -0,0 +1,25 @@
|
||||
# FTP Quotas Table Fix
|
||||
|
||||
## Problem
|
||||
- **URL:** https://207.180.193.210:2087/ftp/quotaManagement
|
||||
- **Error:** `(1146, "Table 'cyberpanel.ftp_quotas' doesn't exist")`
|
||||
|
||||
The `FTPQuota` model in `websiteFunctions/models.py` uses `db_table = 'ftp_quotas'`, but the table had never been created in the database.
|
||||
|
||||
## Solution
|
||||
1. **SQL:** `sql/create_ftp_quotas.sql` – `CREATE TABLE IF NOT EXISTS ftp_quotas` with columns and FKs to `loginSystem_administrator` and `websiteFunctions_websites`.
|
||||
2. **Deploy script:** `deploy-ftp-quotas-table.sh` – Copies the SQL to `/usr/local/CyberCP/sql/` and runs it using Django’s DB connection (no password on command line).
|
||||
|
||||
## Deploy (already run)
|
||||
```bash
|
||||
sudo bash /home/cyberpanel-repo/deploy-ftp-quotas-table.sh
|
||||
```
|
||||
|
||||
## Manual run (if needed)
|
||||
From repo root:
|
||||
```bash
|
||||
sudo bash deploy-ftp-quotas-table.sh [REPO_DIR] [CP_DIR]
|
||||
```
|
||||
Default `CP_DIR` is `/usr/local/CyberCP`.
|
||||
|
||||
After deployment, reload `/ftp/quotaManagement` in the browser.
|
||||
83
to-do/RUNTIME-VS-REPO-2.5.5-DEV.md
Normal file
83
to-do/RUNTIME-VS-REPO-2.5.5-DEV.md
Normal file
@@ -0,0 +1,83 @@
|
||||
# Runtime vs Repo: What Belongs in cyberpanel-repo for 2.5.5-dev
|
||||
|
||||
## Goal
|
||||
|
||||
When users upgrade to **our** (master3395) 2.5.5-dev, the panel should look and behave the same. That means **default** look-and-feel and behavior must be defined in the repo, not only “generated” on the server.
|
||||
|
||||
---
|
||||
|
||||
## What is “runtime generated”?
|
||||
|
||||
On the live server, after install/upgrade you have:
|
||||
|
||||
1. **From the repo (clone/copy)**
|
||||
All app code, templates, static sources, migrations, `version.txt`, default `settings.py`, etc.
|
||||
→ This **should** be in the repo (and already is).
|
||||
|
||||
2. **Generated at install/upgrade**
|
||||
- Python venv under `/usr/local/CyberCP/bin`, `lib`, `lib64`
|
||||
- `collectstatic` output under `/usr/local/CyberCP/public/static`
|
||||
- `version` table and `baseTemplate_cyberpanelcosmetic` row (if created by code/migrations)
|
||||
- `lscpd` binary copy, symlinks, etc.
|
||||
→ The **sources** that produce these (e.g. static sources, migrations) **should** be in the repo.
|
||||
|
||||
3. **Per-server / preserved**
|
||||
- `CyberCP/settings.py` — upgrade **merges** only the `DATABASES` section from the old server; the rest (e.g. `INSTALLED_APPS`) comes from the **new** clone.
|
||||
- `baseTemplate/static/baseTemplate/custom/` (custom CSS files)
|
||||
- DB row `baseTemplate_cyberpanelcosmetic.MainDashboardCSS` (custom dashboard CSS)
|
||||
- `.git/`, phpMyAdmin config, SnappyMail data, etc.
|
||||
→ **Defaults** that define “how 2.5.5-dev looks” should be in the repo; **per-server overrides** stay on the server.
|
||||
|
||||
---
|
||||
|
||||
## What we need in the repo so 2.5.5-dev “looks the same”
|
||||
|
||||
- **Templates, static sources, JS/CSS**
|
||||
Already in repo (e.g. `baseTemplate/`, `static/`). No change needed for “same look” unless you change the design.
|
||||
|
||||
- **Default `settings.py`**
|
||||
Already in repo. Upgrade keeps DB credentials from the server and uses repo for everything else (e.g. `INSTALLED_APPS`).
|
||||
So 2.5.5-dev behavior is driven by the repo’s `settings.py`.
|
||||
|
||||
- **Version**
|
||||
`baseTemplate/views.py` has `VERSION = '2.5.5'`, `BUILD = 'dev'`. Repo’s `version.txt` is `{"version":"2.5.5","build":"dev"}`.
|
||||
Upgrade also writes version into the DB. So version “same as 2.5.5-dev” is already defined in the repo.
|
||||
|
||||
- **Default “look” (cosmetic)**
|
||||
- Code already creates a default `CyberPanelCosmetic` row with **empty** `MainDashboardCSS` if none exists (`baseTemplate/context_processors.py`, `plogical/httpProc.py`, `loginSystem/views.py`).
|
||||
- If **your live server** has custom dashboard CSS (in DB or in `baseTemplate/static/baseTemplate/custom/`), that is **your** customization.
|
||||
- To make “our 2.5.5-dev” ship with that same look as default, you have two options:
|
||||
|
||||
1. **Data migration**
|
||||
Add a baseTemplate data migration that does:
|
||||
- `CyberPanelCosmetic.objects.get_or_create(pk=1, defaults={'MainDashboardCSS': '<your default CSS>'})`
|
||||
so every new/upgraded install gets that default look.
|
||||
|
||||
2. **Static default**
|
||||
Put the CSS in a static file under `baseTemplate/static/` and include it in the base template so the default theme matches your live server.
|
||||
|
||||
- **Migrations**
|
||||
All schema (and optional data) migrations must be in the repo so every 2.5.5-dev install/upgrade runs the same schema and, if you add it, the same default cosmetic data.
|
||||
|
||||
---
|
||||
|
||||
## What should **not** be in the repo
|
||||
|
||||
- **Secrets**: DB password, `SECRET_KEY`, API keys.
|
||||
Keep in `settings.py` only placeholders or env reads; real values stay on the server (or in config.php / env per your rules).
|
||||
|
||||
- **User data**: sites, users, mail, backups.
|
||||
These are per-server.
|
||||
|
||||
- **Generated artifacts**: venv, `collectstatic` output, compiled binaries.
|
||||
Repo holds the **source**; install/upgrade generates these on the server.
|
||||
|
||||
---
|
||||
|
||||
## Summary
|
||||
|
||||
- **Yes:** “Runtime generated” **defaults** that define how 2.5.5-dev looks and behaves **should** be reflected in the repo (templates, static sources, migrations, default cosmetic logic or data).
|
||||
- **Already in repo:** App code, default settings structure, version, static sources, migrations. So 2.5.5-dev upgrades already get the same **code** and **default look** (empty custom CSS).
|
||||
- **Optional:** If your live server has a **specific** custom look (e.g. custom dashboard CSS), and you want that to be the **default** for everyone on 2.5.5-dev, add it to the repo via a data migration or default static CSS as above.
|
||||
|
||||
No change is **required** for “same look” unless you want to ship a non-empty default cosmetic (e.g. your current dashboard CSS) as part of 2.5.5-dev.
|
||||
89
to-do/V2.5.5-DEV-FIXES-AND-DEPLOY.md
Normal file
89
to-do/V2.5.5-DEV-FIXES-AND-DEPLOY.md
Normal file
@@ -0,0 +1,89 @@
|
||||
# v2.5.5-dev: Fixes and Deploy Guide
|
||||
|
||||
This document lists fixes included in **v2.5.5-dev** and how to deploy them on a CyberPanel server.
|
||||
|
||||
---
|
||||
|
||||
## Version and cache busting
|
||||
|
||||
- **baseTemplate:** `CP_VERSION` in `baseTemplate/templates/baseTemplate/index.html` now uses `CYBERPANEL_FULL_VERSION` from context (from `baseTemplate/views.py`: `VERSION = '2.5.5'`, `BUILD = 'dev'`), so static URLs use `?v=2.5.5.dev` and cache busting matches the branch.
|
||||
|
||||
---
|
||||
|
||||
## 1. Email Limits page
|
||||
|
||||
- **Issue:** Raw Angular bindings (`{$ selectedEmail $}`) and `EmailLimitsNew` controller not registered.
|
||||
- **Files:** `mailServer/mailserverManager.py`, `mailServer/templates/mailServer/EmailLimits.html`, `mailServer/static/mailServer/mailServer.js`, `mailServer/static/mailServer/emailLimitsController.js`, and mirrored under `static/mailServer/`.
|
||||
- **Deploy:**
|
||||
```bash
|
||||
sudo bash /home/cyberpanel-repo/deploy-email-limits-fix.sh
|
||||
```
|
||||
- **Details:** See `to-do/EMAIL-LIMITS-DEPLOY-CHECKLIST.md`.
|
||||
|
||||
---
|
||||
|
||||
## 2. FTP Create Account page
|
||||
|
||||
- **Issue:** After selecting a website, the “FTP Account Details” form (username, password, path, quota) did not appear.
|
||||
- **Files:** `ftp/templates/ftp/createFTPAccount.html` (inline script + polling + Angular scope sync), `ftp/static/ftp/ftp.js`, `static/ftp/ftp.js`, `public/static/ftp/ftp.js`.
|
||||
- **Deploy:**
|
||||
```bash
|
||||
sudo bash /home/cyberpanel-repo/deploy-ftp-create-account-fix.sh
|
||||
```
|
||||
- **After deploy:** Hard-refresh `/ftp/createFTPAccount` (Ctrl+Shift+R).
|
||||
|
||||
---
|
||||
|
||||
## 3. FTP Quota Management page
|
||||
|
||||
- **Issue:** `(1146, "Table 'cyberpanel.ftp_quotas' doesn't exist")` on `/ftp/quotaManagement`.
|
||||
- **Files:** `sql/create_ftp_quotas.sql`, `websiteFunctions/models.py` (FTPQuota model already present).
|
||||
- **Deploy:**
|
||||
```bash
|
||||
sudo bash /home/cyberpanel-repo/deploy-ftp-quotas-table.sh
|
||||
```
|
||||
- **Details:** See `to-do/FTP-QUOTAS-TABLE-FIX.md`.
|
||||
|
||||
---
|
||||
|
||||
## 4. mailUtilities indentation fix
|
||||
|
||||
- **File:** `plogical/mailUtilities.py` (indentation fix in DNS query try/except block).
|
||||
- **Deploy:** Copy to `/usr/local/CyberCP/plogical/mailUtilities.py` and restart lscpd if needed.
|
||||
|
||||
---
|
||||
|
||||
## Deploy all fixes (in order)
|
||||
|
||||
Run on the server (e.g. from repo root):
|
||||
|
||||
```bash
|
||||
sudo bash deploy-email-limits-fix.sh
|
||||
sudo bash deploy-ftp-create-account-fix.sh
|
||||
sudo bash deploy-ftp-quotas-table.sh
|
||||
```
|
||||
|
||||
Then hard-refresh the FTP Create Account page in the browser. No need to restart lscpd after the FTP quotas table script (it only runs SQL).
|
||||
|
||||
---
|
||||
|
||||
## Files changed / added in v2.5.5-dev (fixes)
|
||||
|
||||
| Path | Description |
|
||||
|------|-------------|
|
||||
| `baseTemplate/templates/baseTemplate/index.html` | CP_VERSION from CYBERPANEL_FULL_VERSION |
|
||||
| `mailServer/` (Email Limits) | Controller, template, getEmailsForDomain permission |
|
||||
| `ftp/templates/ftp/createFTPAccount.html` | Inline fallback + polling for details form |
|
||||
| `ftp/static/ftp/ftp.js`, `static/ftp/ftp.js`, `public/static/ftp/ftp.js` | showFTPDetails, select2/change handlers |
|
||||
| `websiteFunctions/models.py` | FTPQuota model (table created via sql script) |
|
||||
| `sql/create_ftp_quotas.sql` | CREATE TABLE ftp_quotas |
|
||||
| `plogical/mailUtilities.py` | DNS block indentation fix |
|
||||
| `deploy-email-limits-fix.sh` | Deploy Email Limits fix |
|
||||
| `deploy-ftp-create-account-fix.sh` | Deploy FTP Create Account template |
|
||||
| `deploy-ftp-quotas-table.sh` | Create ftp_quotas table |
|
||||
|
||||
---
|
||||
|
||||
## Optional (not committed by default)
|
||||
|
||||
- **CyberCP/urls.py:** If `emailMarketing` is commented out for local runserver, leave it uncommitted or revert before pushing so production keeps the route.
|
||||
Reference in New Issue
Block a user