diff --git a/baseTemplate/templates/baseTemplate/index.html b/baseTemplate/templates/baseTemplate/index.html index ab433a6e5..0802be080 100644 --- a/baseTemplate/templates/baseTemplate/index.html +++ b/baseTemplate/templates/baseTemplate/index.html @@ -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" %} diff --git a/deploy-ftp-create-account-fix.sh b/deploy-ftp-create-account-fix.sh new file mode 100644 index 000000000..09ddab347 --- /dev/null +++ b/deploy-ftp-create-account-fix.sh @@ -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)." diff --git a/deploy-ftp-quotas-table.sh b/deploy-ftp-quotas-table.sh new file mode 100644 index 000000000..390acfe6d --- /dev/null +++ b/deploy-ftp-quotas-table.sh @@ -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" diff --git a/ftp/static/ftp/ftp.js b/ftp/static/ftp/ftp.js index 6a7cb75da..ef6cd4a4e 100644 --- a/ftp/static/ftp/ftp.js +++ b/ftp/static/ftp/ftp.js @@ -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(); } }; diff --git a/ftp/templates/ftp/createFTPAccount.html b/ftp/templates/ftp/createFTPAccount.html index 35e796608..6fc8e8a1c 100644 --- a/ftp/templates/ftp/createFTPAccount.html +++ b/ftp/templates/ftp/createFTPAccount.html @@ -452,18 +452,65 @@
- {% for items in websiteList %} {% endfor %} +
-
+

{% trans "FTP Account Details" %}

diff --git a/plogical/mailUtilities.py b/plogical/mailUtilities.py index 59701151a..67ad2790b 100644 --- a/plogical/mailUtilities.py +++ b/plogical/mailUtilities.py @@ -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)})') diff --git a/public/static/ftp/ftp.js b/public/static/ftp/ftp.js index 113845c8f..875bf72cd 100644 --- a/public/static/ftp/ftp.js +++ b/public/static/ftp/ftp.js @@ -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 () { diff --git a/sql/create_ftp_quotas.sql b/sql/create_ftp_quotas.sql new file mode 100644 index 000000000..65af044a8 --- /dev/null +++ b/sql/create_ftp_quotas.sql @@ -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; diff --git a/static/ftp/ftp.js b/static/ftp/ftp.js index 6a7cb75da..3035a8c7c 100644 --- a/static/ftp/ftp.js +++ b/static/ftp/ftp.js @@ -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(); } }; diff --git a/to-do/FTP-QUOTAS-TABLE-FIX.md b/to-do/FTP-QUOTAS-TABLE-FIX.md new file mode 100644 index 000000000..99f3160ea --- /dev/null +++ b/to-do/FTP-QUOTAS-TABLE-FIX.md @@ -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. diff --git a/to-do/RUNTIME-VS-REPO-2.5.5-DEV.md b/to-do/RUNTIME-VS-REPO-2.5.5-DEV.md new file mode 100644 index 000000000..d272848f7 --- /dev/null +++ b/to-do/RUNTIME-VS-REPO-2.5.5-DEV.md @@ -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': ''})` + 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. diff --git a/to-do/V2.5.5-DEV-FIXES-AND-DEPLOY.md b/to-do/V2.5.5-DEV-FIXES-AND-DEPLOY.md new file mode 100644 index 000000000..b58f4721a --- /dev/null +++ b/to-do/V2.5.5-DEV-FIXES-AND-DEPLOY.md @@ -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.