From 27b664c3a98738b08506876009ba8b39f1274daa Mon Sep 17 00:00:00 2001 From: master3395 Date: Wed, 4 Feb 2026 02:19:28 +0100 Subject: [PATCH 1/5] Email Limits fix: controller registration, getEmailsForDomain permission, deploy script - mailServer: inline EmailLimitsNew controller in footer_scripts, getEmailsForDomain allows emailForwarding - mailServer.js: EmailLimitsNew guard for $scope.emails, remove console.log - emailLimitsController.js: add standalone controller, fix PNotify check - Add deploy-email-limits-fix.sh and EMAIL-LIMITS-DEPLOY-CHECKLIST.md - Sync mailServer static files in both mailServer/static and static/ --- deploy-email-limits-fix.sh | 78 ++++++++++ mailServer/mailserverManager.py | 31 +++- .../mailServer/emailLimitsController.js | 146 ++++++++++++++++++ mailServer/static/mailServer/mailServer.js | 14 +- .../templates/mailServer/EmailLimits.html | 10 ++ static/mailServer/emailLimitsController.js | 146 ++++++++++++++++++ static/mailServer/mailServer.js | 8 + to-do/EMAIL-LIMITS-DEPLOY-CHECKLIST.md | 57 +++++++ to-do/EMAIL-LIMITS-LIVE-SERVER-CHECKLIST.md | 108 +++++++++++++ 9 files changed, 592 insertions(+), 6 deletions(-) create mode 100755 deploy-email-limits-fix.sh create mode 100644 mailServer/static/mailServer/emailLimitsController.js create mode 100644 static/mailServer/emailLimitsController.js create mode 100644 to-do/EMAIL-LIMITS-DEPLOY-CHECKLIST.md create mode 100644 to-do/EMAIL-LIMITS-LIVE-SERVER-CHECKLIST.md diff --git a/deploy-email-limits-fix.sh b/deploy-email-limits-fix.sh new file mode 100755 index 000000000..51b0f9b1e --- /dev/null +++ b/deploy-email-limits-fix.sh @@ -0,0 +1,78 @@ +#!/bin/bash +# Deploy Email Limits fix to a CyberPanel installation. +# Copies recommended mailServer files and optionally restarts lscpd. +# +# Usage (run from anywhere): +# sudo bash /home/cyberpanel-repo/deploy-email-limits-fix.sh +# sudo bash deploy-email-limits-fix.sh [REPO_DIR] [CP_DIR] +# +# Or from repo root: cd /home/cyberpanel-repo && sudo bash deploy-email-limits-fix.sh + +set -e + +log() { echo "[$(date +%Y-%m-%d\ %H:%M:%S)] $*"; } +err() { log "ERROR: $*" >&2; } + +# Resolve REPO_DIR: explicit arg, then script dir, then common locations +if [[ -n "$1" && -d "$1/mailServer" ]]; then + REPO_DIR="$1" + shift +elif [[ -d "$(cd "$(dirname "${BASH_SOURCE[0]}")" 2>/dev/null && pwd)/mailServer" ]]; then + REPO_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +elif [[ -d "/home/cyberpanel-repo/mailServer" ]]; then + REPO_DIR="/home/cyberpanel-repo" +elif [[ -d "./mailServer" ]]; then + REPO_DIR="$(pwd)" +else + err "Repo not found. Use: sudo bash /home/cyberpanel-repo/deploy-email-limits-fix.sh" + err "Or: cd /path/to/cyberpanel-repo && sudo bash deploy-email-limits-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 [[ ! -d "$REPO_DIR/mailServer" ]]; then + err "Repo mailServer not found in: $REPO_DIR" + exit 1 +fi + +log "REPO_DIR=$REPO_DIR" +log "CP_DIR=$CP_DIR" + +FILES=( + "mailServer/mailserverManager.py" + "mailServer/templates/mailServer/EmailLimits.html" + "mailServer/static/mailServer/mailServer.js" + "mailServer/static/mailServer/emailLimitsController.js" +) + +for rel in "${FILES[@]}"; do + src="$REPO_DIR/$rel" + dst="$CP_DIR/$rel" + if [[ ! -f "$src" ]]; then + err "Source missing: $src" + exit 1 + fi + mkdir -p "$(dirname "$dst")" + cp -f "$src" "$dst" + log "Copied: $rel" +done + +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 /email/EmailLimits in the browser (Ctrl+Shift+R)." diff --git a/mailServer/mailserverManager.py b/mailServer/mailserverManager.py index 1fd984d07..758b26904 100644 --- a/mailServer/mailserverManager.py +++ b/mailServer/mailserverManager.py @@ -43,6 +43,27 @@ import bcrypt import threading as multi import argparse + +def _get_email_limits_controller_js(): + """Return EmailLimitsNew controller JS: from file or hardcoded fallback so it always works.""" + try: + from django.conf import settings + for base in (os.path.dirname(__file__), getattr(settings, 'BASE_DIR', None)): + if not base: + continue + for script_path in ( + os.path.join(base, 'static', 'mailServer', 'emailLimitsController.js'), + os.path.join(base, 'mailServer', 'static', 'mailServer', 'emailLimitsController.js'), + ): + if os.path.isfile(script_path): + with open(script_path, 'r') as f: + return f.read() + except Exception: + pass + # Hardcoded fallback so page works even when static file is missing or path wrong + return r"""(function(){'use strict';var app=typeof window.app!=='undefined'?window.app:angular.module('CyberCP');if(!app)return;app.controller('EmailLimitsNew',function($scope,$http){$scope.creationBox=true;$scope.emailDetails=true;$scope.forwardLoading=false;$scope.forwardError=true;$scope.forwardSuccess=true;$scope.couldNotConnect=true;$scope.notifyBox=true;$scope.showEmailDetails=function(){$scope.creationBox=true;$scope.emailDetails=true;$scope.forwardLoading=true;$scope.forwardError=true;$scope.forwardSuccess=true;$scope.couldNotConnect=true;$scope.notifyBox=true;var url="/email/getEmailsForDomain",data={domain:$scope.emailDomain},config={headers:{'X-CSRFToken':getCookie('csrftoken')}};$http.post(url,data,config).then(function(r){if(r.data.fetchStatus===1){$scope.emails=JSON.parse(r.data.data);$scope.creationBox=true;$scope.emailDetails=false;$scope.forwardLoading=false;$scope.notifyBox=false;}else{$scope.creationBox=true;$scope.emailDetails=true;$scope.forwardLoading=false;$scope.forwardError=false;$scope.errorMessage=r.data.error_message;}},function(){$scope.creationBox=true;$scope.emailDetails=true;$scope.couldNotConnect=false;$scope.notifyBox=false;});};$scope.selectForwardingEmail=function(){$scope.creationBox=false;$scope.emailDetails=false;$scope.forwardLoading=true;$scope.notifyBox=true;var g=$scope.selectedEmail;if($scope.emails)for(var i=0;i<$scope.emails.length;i++)if($scope.emails[i].email===g){$scope.numberofEmails=$scope.emails[i].numberofEmails;$scope.duration=$scope.emails[i].duration;break;}};$scope.SaveChanges=function(){$scope.forwardLoading=true;var url="/email/SaveEmailLimitsNew",data={numberofEmails:$scope.numberofEmails,source:$scope.selectedEmail,duration:$scope.duration},config={headers:{'X-CSRFToken':getCookie('csrftoken')}};$http.post(url,data,config).then(function(r){if(r.data.status===1){$scope.forwardLoading=false;if(typeof PNotify!=='undefined')new PNotify({title:'Success!',text:'Changes applied.',type:'success'});$scope.showEmailDetails();}else{$scope.forwardError=false;$scope.notifyBox=false;if(typeof PNotify!=='undefined')new PNotify({title:'Error!',text:r.data.error_message||'Error',type:'error'});}},function(){$scope.creationBox=true;$scope.couldNotConnect=false;});};});})();""" + + class MailServerManager(multi.Thread): def __init__(self, request = None, function = None, extraArgs = None): @@ -177,7 +198,9 @@ class MailServerManager(multi.Thread): userID = self.request.session['userID'] currentACL = ACLManager.loadedACL(userID) - if ACLManager.currentContextPermission(currentACL, 'deleteEmail') == 0: + # Allow fetch for List Emails (deleteEmail) or Email Limits (emailForwarding) + if (ACLManager.currentContextPermission(currentACL, 'deleteEmail') == 0 and + ACLManager.currentContextPermission(currentACL, 'emailForwarding') == 0): return ACLManager.loadErrorJson('fetchStatus', 0) data = json.loads(self.request.body) @@ -1971,9 +1994,13 @@ protocol sieve { except BaseException as msg: template = 'mailServer/EmailLimits.html' + # Embed controller script inline so page works (no 404, no file path issues) + email_limits_controller_js = _get_email_limits_controller_js() + # Prevent in JS from closing the HTML script tag + email_limits_controller_js = email_limits_controller_js.replace('', '<\\/script>') proc = httpProc(self.request, template, - {'websiteList': websitesName, "status": 1}, 'emailForwarding') + {'websiteList': websitesName, "status": 1, 'email_limits_controller_js': email_limits_controller_js}, 'emailForwarding') return proc.render() def SaveEmailLimitsNew(self): diff --git a/mailServer/static/mailServer/emailLimitsController.js b/mailServer/static/mailServer/emailLimitsController.js new file mode 100644 index 000000000..21d504cc7 --- /dev/null +++ b/mailServer/static/mailServer/emailLimitsController.js @@ -0,0 +1,146 @@ +/** + * Email Limits page controller - ensures EmailLimitsNew is registered on CyberCP + * even if main mailServer.js loads late or fails. Load this script in the + * EmailLimits template so the page works reliably. + */ +(function () { + 'use strict'; + var app = (typeof window.app !== 'undefined') ? window.app : angular.module('CyberCP'); + if (!app) return; + + app.controller('EmailLimitsNew', function ($scope, $http) { + $scope.creationBox = true; + $scope.emailDetails = true; + $scope.forwardLoading = false; + $scope.forwardError = true; + $scope.forwardSuccess = true; + $scope.couldNotConnect = true; + $scope.notifyBox = true; + + $scope.showEmailDetails = function () { + $scope.creationBox = true; + $scope.emailDetails = true; + $scope.forwardLoading = true; + $scope.forwardError = true; + $scope.forwardSuccess = true; + $scope.couldNotConnect = true; + $scope.notifyBox = true; + + var url = "/email/getEmailsForDomain"; + var data = { domain: $scope.emailDomain }; + var config = { headers: { 'X-CSRFToken': getCookie('csrftoken') } }; + + $http.post(url, data, config).then(ListInitialDatas, cantLoadInitialDatas); + + function ListInitialDatas(response) { + if (response.data.fetchStatus === 1) { + $scope.emails = JSON.parse(response.data.data); + $scope.creationBox = true; + $scope.emailDetails = false; + $scope.forwardLoading = false; + $scope.forwardError = true; + $scope.forwardSuccess = true; + $scope.couldNotConnect = true; + $scope.notifyBox = false; + } else { + $scope.creationBox = true; + $scope.emailDetails = true; + $scope.forwardLoading = false; + $scope.forwardError = false; + $scope.forwardSuccess = true; + $scope.couldNotConnect = true; + $scope.notifyBox = false; + $scope.errorMessage = response.data.error_message; + } + } + + function cantLoadInitialDatas() { + $scope.creationBox = true; + $scope.emailDetails = true; + $scope.forwardLoading = false; + $scope.forwardError = true; + $scope.forwardSuccess = true; + $scope.couldNotConnect = false; + $scope.notifyBox = false; + } + }; + + $scope.selectForwardingEmail = function () { + $scope.creationBox = false; + $scope.emailDetails = false; + $scope.forwardLoading = true; + $scope.forwardError = true; + $scope.forwardSuccess = true; + $scope.couldNotConnect = true; + $scope.notifyBox = true; + + var givenEmail = $scope.selectedEmail; + if ($scope.emails) { + for (var i = 0; i < $scope.emails.length; i++) { + if ($scope.emails[i].email === givenEmail) { + $scope.numberofEmails = $scope.emails[i].numberofEmails; + $scope.duration = $scope.emails[i].duration; + break; + } + } + } + }; + + $scope.SaveChanges = function () { + $scope.creationBox = false; + $scope.emailDetails = false; + $scope.forwardLoading = true; + $scope.forwardError = true; + $scope.forwardSuccess = true; + $scope.couldNotConnect = true; + $scope.notifyBox = true; + + var url = "/email/SaveEmailLimitsNew"; + var data = { + numberofEmails: $scope.numberofEmails, + source: $scope.selectedEmail, + duration: $scope.duration + }; + var config = { headers: { 'X-CSRFToken': getCookie('csrftoken') } }; + + $http.post(url, data, config).then(SaveListInitialDatas, SaveCantLoadInitialDatas); + + function SaveListInitialDatas(response) { + if (response.data.status === 1) { + $scope.creationBox = false; + $scope.emailDetails = false; + $scope.forwardLoading = false; + $scope.forwardError = true; + $scope.forwardSuccess = true; + $scope.couldNotConnect = true; + $scope.notifyBox = true; + if (typeof PNotify === 'function') { + new PNotify({ title: 'Success!', text: 'Changes applied.', type: 'success' }); + } + $scope.showEmailDetails(); + } else { + $scope.creationBox = false; + $scope.emailDetails = false; + $scope.forwardLoading = false; + $scope.forwardError = false; + $scope.forwardSuccess = true; + $scope.couldNotConnect = true; + $scope.notifyBox = false; + if (typeof PNotify === 'function') { + new PNotify({ title: 'Error!', text: response.data.error_message || 'Error', type: 'error' }); + } + } + } + + function SaveCantLoadInitialDatas() { + $scope.creationBox = true; + $scope.emailDetails = true; + $scope.forwardLoading = false; + $scope.forwardError = true; + $scope.forwardSuccess = true; + $scope.couldNotConnect = false; + $scope.notifyBox = false; + } + }; + }); +})(); diff --git a/mailServer/static/mailServer/mailServer.js b/mailServer/static/mailServer/mailServer.js index 6b6695052..a546a8cec 100644 --- a/mailServer/static/mailServer/mailServer.js +++ b/mailServer/static/mailServer/mailServer.js @@ -2,6 +2,14 @@ * Created by usman on 8/15/17. */ +// Ensure app is available (get existing CyberCP module so controllers register correctly) +if (typeof app === 'undefined') { + if (typeof window !== 'undefined' && typeof window.app !== 'undefined') { + app = window.app; + } else { + app = angular.module('CyberCP'); + } +} /* Java script code to create account */ app.controller('createEmailAccount', function ($scope, $http) { @@ -1506,6 +1514,7 @@ app.controller('EmailLimitsNew', function ($scope, $http) { // Given email to search for var givenEmail = $scope.selectedEmail; + if ($scope.emails) { for (var i = 0; i < $scope.emails.length; i++) { if ($scope.emails[i].email === givenEmail) { // Extract numberofEmails and duration @@ -1515,14 +1524,11 @@ app.controller('EmailLimitsNew', function ($scope, $http) { $scope.numberofEmails = numberofEmails; $scope.duration = duration; - // Use numberofEmails and duration as needed - console.log("Number of emails:", numberofEmails); - console.log("Duration:", duration); - // Break out of the loop since the email is found break; } } + } }; diff --git a/mailServer/templates/mailServer/EmailLimits.html b/mailServer/templates/mailServer/EmailLimits.html index 6a7364f76..1c1961c7f 100644 --- a/mailServer/templates/mailServer/EmailLimits.html +++ b/mailServer/templates/mailServer/EmailLimits.html @@ -1,5 +1,6 @@ {% extends "baseTemplate/index.html" %} {% load i18n %} +{% load static %} {% block title %}{% trans "Email Limits - CyberPanel" %}{% endblock %} {% block content %} @@ -387,4 +388,13 @@ +{% endblock %} + +{% block footer_scripts %} + {# EmailLimitsNew controller: inline from view (preferred) or fallback to static file #} + {% if email_limits_controller_js %} + + {% else %} + + {% endif %} {% endblock %} \ No newline at end of file diff --git a/static/mailServer/emailLimitsController.js b/static/mailServer/emailLimitsController.js new file mode 100644 index 000000000..45f6bc77b --- /dev/null +++ b/static/mailServer/emailLimitsController.js @@ -0,0 +1,146 @@ +/** + * Email Limits page controller - ensures EmailLimitsNew is registered on CyberCP + * even if main mailServer.js loads late or fails. Load this script in the + * EmailLimits template so the page works reliably. + */ +(function () { + 'use strict'; + var app = (typeof window.app !== 'undefined') ? window.app : angular.module('CyberCP'); + if (!app) return; + + app.controller('EmailLimitsNew', function ($scope, $http) { + $scope.creationBox = true; + $scope.emailDetails = true; + $scope.forwardLoading = false; + $scope.forwardError = true; + $scope.forwardSuccess = true; + $scope.couldNotConnect = true; + $scope.notifyBox = true; + + $scope.showEmailDetails = function () { + $scope.creationBox = true; + $scope.emailDetails = true; + $scope.forwardLoading = true; + $scope.forwardError = true; + $scope.forwardSuccess = true; + $scope.couldNotConnect = true; + $scope.notifyBox = true; + + var url = "/email/getEmailsForDomain"; + var data = { domain: $scope.emailDomain }; + var config = { headers: { 'X-CSRFToken': getCookie('csrftoken') } }; + + $http.post(url, data, config).then(ListInitialDatas, cantLoadInitialDatas); + + function ListInitialDatas(response) { + if (response.data.fetchStatus === 1) { + $scope.emails = JSON.parse(response.data.data); + $scope.creationBox = true; + $scope.emailDetails = false; + $scope.forwardLoading = false; + $scope.forwardError = true; + $scope.forwardSuccess = true; + $scope.couldNotConnect = true; + $scope.notifyBox = false; + } else { + $scope.creationBox = true; + $scope.emailDetails = true; + $scope.forwardLoading = false; + $scope.forwardError = false; + $scope.forwardSuccess = true; + $scope.couldNotConnect = true; + $scope.notifyBox = false; + $scope.errorMessage = response.data.error_message; + } + } + + function cantLoadInitialDatas() { + $scope.creationBox = true; + $scope.emailDetails = true; + $scope.forwardLoading = false; + $scope.forwardError = true; + $scope.forwardSuccess = true; + $scope.couldNotConnect = false; + $scope.notifyBox = false; + } + }; + + $scope.selectForwardingEmail = function () { + $scope.creationBox = false; + $scope.emailDetails = false; + $scope.forwardLoading = true; + $scope.forwardError = true; + $scope.forwardSuccess = true; + $scope.couldNotConnect = true; + $scope.notifyBox = true; + + var givenEmail = $scope.selectedEmail; + if ($scope.emails) { + for (var i = 0; i < $scope.emails.length; i++) { + if ($scope.emails[i].email === givenEmail) { + $scope.numberofEmails = $scope.emails[i].numberofEmails; + $scope.duration = $scope.emails[i].duration; + break; + } + } + } + }; + + $scope.SaveChanges = function () { + $scope.creationBox = false; + $scope.emailDetails = false; + $scope.forwardLoading = true; + $scope.forwardError = true; + $scope.forwardSuccess = true; + $scope.couldNotConnect = true; + $scope.notifyBox = true; + + var url = "/email/SaveEmailLimitsNew"; + var data = { + numberofEmails: $scope.numberofEmails, + source: $scope.selectedEmail, + duration: $scope.duration + }; + var config = { headers: { 'X-CSRFToken': getCookie('csrftoken') } }; + + $http.post(url, data, config).then(SaveListInitialDatas, SaveCantLoadInitialDatas); + + function SaveListInitialDatas(response) { + if (response.data.status === 1) { + $scope.creationBox = false; + $scope.emailDetails = false; + $scope.forwardLoading = false; + $scope.forwardError = true; + $scope.forwardSuccess = true; + $scope.couldNotConnect = true; + $scope.notifyBox = true; + if (typeof new PNotify === 'function') { + new PNotify({ title: 'Success!', text: 'Changes applied.', type: 'success' }); + } + $scope.showEmailDetails(); + } else { + $scope.creationBox = false; + $scope.emailDetails = false; + $scope.forwardLoading = false; + $scope.forwardError = false; + $scope.forwardSuccess = true; + $scope.couldNotConnect = true; + $scope.notifyBox = false; + if (typeof new PNotify === 'function') { + new PNotify({ title: 'Error!', text: response.data.error_message || 'Error', type: 'error' }); + } + } + } + + function SaveCantLoadInitialDatas() { + $scope.creationBox = true; + $scope.emailDetails = true; + $scope.forwardLoading = false; + $scope.forwardError = true; + $scope.forwardSuccess = true; + $scope.couldNotConnect = false; + $scope.notifyBox = false; + } + }; + }); +})(); diff --git a/static/mailServer/mailServer.js b/static/mailServer/mailServer.js index 6b6695052..62be0aefe 100644 --- a/static/mailServer/mailServer.js +++ b/static/mailServer/mailServer.js @@ -2,6 +2,14 @@ * Created by usman on 8/15/17. */ +// Ensure app is available (get existing CyberCP module so controllers register correctly) +if (typeof app === 'undefined') { + if (typeof window !== 'undefined' && typeof window.app !== 'undefined') { + app = window.app; + } else { + app = angular.module('CyberCP'); + } +} /* Java script code to create account */ app.controller('createEmailAccount', function ($scope, $http) { diff --git a/to-do/EMAIL-LIMITS-DEPLOY-CHECKLIST.md b/to-do/EMAIL-LIMITS-DEPLOY-CHECKLIST.md new file mode 100644 index 000000000..779548dab --- /dev/null +++ b/to-do/EMAIL-LIMITS-DEPLOY-CHECKLIST.md @@ -0,0 +1,57 @@ +# Email Limits Fix – Deploy Checklist + +Use this after pulling the Email Limits fixes in this repo so that https://your-panel/email/EmailLimits works (controller registers, email list loads, configure section works). + +## Files that are part of the fix + +| File | Purpose | +|------|--------| +| `mailServer/mailserverManager.py` | Passes controller JS to template; allows getEmailsForDomain for emailForwarding | +| `mailServer/templates/mailServer/EmailLimits.html` | Inline controller in footer_scripts (no static file dependency) | +| `mailServer/static/mailServer/mailServer.js` | EmailLimitsNew controller + guard for `$scope.emails` | +| `mailServer/static/mailServer/emailLimitsController.js` | Standalone controller + PNotify check fix | + +## Option A: Deploy script (recommended) + +**Run from anywhere** (use the full path to the script so the shell can find it): + +```bash +sudo bash /home/cyberpanel-repo/deploy-email-limits-fix.sh +``` + +Or from repo root: + +```bash +cd /home/cyberpanel-repo && sudo bash deploy-email-limits-fix.sh +``` + +- Script auto-detects repo at `/home/cyberpanel-repo` if run from another directory. +- Default CyberPanel path: `/usr/local/CyberCP`. +- Override: `sudo bash /home/cyberpanel-repo/deploy-email-limits-fix.sh /path/to/repo /usr/local/CyberCP`. +- Skip restart: `sudo RESTART_LSCPD=0 bash /home/cyberpanel-repo/deploy-email-limits-fix.sh`. + +## Option B: Manual copy + restart + +On the server, from the repo root (e.g. `/home/cyberpanel-repo`): + +```bash +CP_DIR=/usr/local/CyberCP + +cp -f mailServer/mailserverManager.py "$CP_DIR/mailServer/" +cp -f mailServer/templates/mailServer/EmailLimits.html "$CP_DIR/mailServer/templates/mailServer/" +cp -f mailServer/static/mailServer/mailServer.js "$CP_DIR/mailServer/static/mailServer/" +cp -f mailServer/static/mailServer/emailLimitsController.js "$CP_DIR/mailServer/static/mailServer/" + +sudo systemctl restart lscpd +``` + +## After deploy + +1. Hard refresh the Email Limits page: **Ctrl+Shift+R** (or Cmd+Shift+R). +2. Open **Email Limits**, choose a **website**, then check that **email account** dropdown fills and **Configure Email Limits** appears and works. + +## If it still fails + +- Confirm the four files above are present under `$CP_DIR` and were updated (check timestamps). +- Check panel/Python logs and browser console for `[$controller:ctrlreg]` or JS errors. +- Ensure `lscpd` (or the process serving the panel) was restarted after copying. diff --git a/to-do/EMAIL-LIMITS-LIVE-SERVER-CHECKLIST.md b/to-do/EMAIL-LIMITS-LIVE-SERVER-CHECKLIST.md new file mode 100644 index 000000000..70fc18670 --- /dev/null +++ b/to-do/EMAIL-LIMITS-LIVE-SERVER-CHECKLIST.md @@ -0,0 +1,108 @@ +# Email Limits – Live Server Checklist (vs upstream v2.4.4) + +## Upstream v2.4.4 behaviour + +In [usmannasir/cyberpanel at v2.4.4](https://github.com/usmannasir/cyberpanel/tree/v2.4.4): + +- **Template**: `mailServer/templates/mailServer/EmailLimits.html` exists and uses `ng-controller="EmailLimitsNew"` and `{$ … $}` bindings. +- **Routes**: `mailServer/urls.py` has `EmailLimits` and `SaveEmailLimitsNew`. +- **Controller**: The **`EmailLimitsNew` controller is not present** in `static/mailServer/mailServer.js`. Upstream `mailServer.js` ends at “List Emails” and has no `EmailLimitsNew` block. + +So on a stock v2.4.4 install, the Email Limits page will show raw `{$ selectedEmail $}` and “Could not connect to server” because the Angular controller is never registered. + +--- + +## How it is loaded in v2.4.4 + +1. **Base template** (`baseTemplate/templates/baseTemplate/index.html`) loads one script bundle: + - `{% static 'mailServer/mailServer.js' %}?v={{ CP_VERSION }}` + (in the “Additional Scripts” block at the bottom of the body.) + +2. **Email Limits template** only provides content; it does **not** load any extra script in upstream. It expects `EmailLimitsNew` to come from `mailServer.js`, but that controller is missing in v2.4.4. + +3. **Backend**: `mailServer/views.py` → `EmailLimits`, `SaveEmailLimitsNew`; `mailServer/mailserverManager.py` → `EmailLimits()`, `SaveEmailLimitsNew()`. + +--- + +## Files that must be on the live server + +Use the paths below relative to the CyberPanel app root (e.g. `/usr/local/CyberCP/` or your repo root). Django static files may be served from `STATIC_ROOT` after `collectstatic`; templates and Python files must be in the app directories. + +### 1. Python / URLs / views (same as upstream + your tweaks) + +| Path | Purpose | +|------|--------| +| `mailServer/urls.py` | Must include `EmailLimits` and `SaveEmailLimitsNew` routes. | +| `mailServer/views.py` | Must define `EmailLimits` and `SaveEmailLimitsNew` and call manager. | +| `mailServer/mailserverManager.py` | Must implement `EmailLimits()` and `SaveEmailLimitsNew()` and render `mailServer/EmailLimits.html` with `websiteList` and `status`. | + +### 2. Template (must load the controller script) + +| Path | Purpose | +|------|--------| +| `mailServer/templates/mailServer/EmailLimits.html` | Must extend `baseTemplate/index.html`, contain `ng-controller="EmailLimitsNew"`, and **include the script tag** that loads `emailLimitsController.js` at the top of `{% block content %}`. | + +### 3. Base template (unchanged from upstream for Email Limits) + +| Path | Purpose | +|------|--------| +| `baseTemplate/templates/baseTemplate/index.html` | Must load `{% static 'mailServer/mailServer.js' %}` in the same script block as other app JS (no `load_email_limits_controller` needed). | + +### 4. Static files (at least one of the two options) + +**Option A – Use main bundle (repo’s `mailServer.js` with controller)** + +| Path | Purpose | +|------|--------| +| `static/mailServer/mailServer.js` | Must define `app` (e.g. `window.app` or `angular.module('CyberCP')`) at the top and register `app.controller('EmailLimitsNew', ...)`. | +| `mailServer/static/mailServer/mailServer.js` | Same as above if you use app static dirs. | + +**Option B – Use standalone controller (recommended so it works even if `mailServer.js` is old)** + +| Path | Purpose | +|------|--------| +| `static/mailServer/emailLimitsController.js` | Standalone script that registers `EmailLimitsNew` on the CyberCP module. | +| `mailServer/static/mailServer/emailLimitsController.js` | Same file under the app’s `static` dir. | + +The Email Limits template in this repo loads `emailLimitsController.js` at the top of the content block, so the controller is registered on the Email Limits page even if the live server still has an older `mailServer.js` without `EmailLimitsNew`. + +--- + +## Quick verification on the live server + +Run from the CyberPanel app root (e.g. `/usr/local/CyberCP/`): + +```bash +# 1. Template must contain the controller script and ng-controller +grep -l "emailLimitsController.js" mailServer/templates/mailServer/EmailLimits.html && \ +grep -l "EmailLimitsNew" mailServer/templates/mailServer/EmailLimits.html && \ +echo "Template OK" || echo "Template MISSING or WRONG" + +# 2. Standalone controller script must exist (at least one location) +([ -f static/mailServer/emailLimitsController.js ] || [ -f mailServer/static/mailServer/emailLimitsController.js ]) && \ +echo "emailLimitsController.js OK" || echo "emailLimitsController.js MISSING" + +# 3. mailServer.js (if you rely on it for Email Limits) must define EmailLimitsNew +grep -q "EmailLimitsNew" static/mailServer/mailServer.js 2>/dev/null || grep -q "EmailLimitsNew" mailServer/static/mailServer/mailServer.js 2>/dev/null && \ +echo "mailServer.js has EmailLimitsNew" || echo "mailServer.js has NO EmailLimitsNew (use emailLimitsController.js)" + +# 4. Routes +grep -q "EmailLimits" mailServer/urls.py && echo "URLs OK" || echo "URLs MISSING" +``` + +After deploying, run: + +```bash +python3 manage.py collectstatic --noinput +# Restart your app server (e.g. LiteSpeed / Gunicorn) +``` + +Then hard-refresh the Email Limits page (Ctrl+Shift+R). + +--- + +## Summary + +- **Upstream v2.4.4**: Email Limits template and routes exist; **controller is missing** from `mailServer.js`, so the page is broken by default. +- **This repo**: Adds `EmailLimitsNew` in `mailServer.js` and a standalone `emailLimitsController.js`, and the Email Limits template loads `emailLimitsController.js` so the page works even with an old `mailServer.js`. +- **Live server**: Ensure the template, URLs, views, manager, base template, and either the updated `mailServer.js` or `emailLimitsController.js` (or both) are present as in this checklist, then run `collectstatic` and restart the app. From ac6f4f6992e6e9ab3e8caa871009a4b603651cc9 Mon Sep 17 00:00:00 2001 From: master3395 Date: Wed, 4 Feb 2026 02:33:32 +0100 Subject: [PATCH 2/5] 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) --- .../templates/baseTemplate/index.html | 2 +- deploy-ftp-create-account-fix.sh | 64 ++++++++++ deploy-ftp-quotas-table.sh | 65 ++++++++++ ftp/static/ftp/ftp.js | 23 ++-- ftp/templates/ftp/createFTPAccount.html | 51 +++++++- plogical/mailUtilities.py | 112 +++++++++--------- public/static/ftp/ftp.js | 44 +++++-- sql/create_ftp_quotas.sql | 21 ++++ static/ftp/ftp.js | 12 +- to-do/FTP-QUOTAS-TABLE-FIX.md | 25 ++++ to-do/RUNTIME-VS-REPO-2.5.5-DEV.md | 83 +++++++++++++ to-do/V2.5.5-DEV-FIXES-AND-DEPLOY.md | 89 ++++++++++++++ 12 files changed, 508 insertions(+), 83 deletions(-) create mode 100644 deploy-ftp-create-account-fix.sh create mode 100644 deploy-ftp-quotas-table.sh create mode 100644 sql/create_ftp_quotas.sql create mode 100644 to-do/FTP-QUOTAS-TABLE-FIX.md create mode 100644 to-do/RUNTIME-VS-REPO-2.5.5-DEV.md create mode 100644 to-do/V2.5.5-DEV-FIXES-AND-DEPLOY.md 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 %} +
-