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/
This commit is contained in:
master3395
2026-02-04 02:19:28 +01:00
parent fa7fde7950
commit 4d5d70be7a
9 changed files with 592 additions and 6 deletions

78
deploy-email-limits-fix.sh Executable file
View File

@@ -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)."

View File

@@ -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 </script> in JS from closing the HTML script tag
email_limits_controller_js = email_limits_controller_js.replace('</script>', '<\\/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):

View File

@@ -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;
}
};
});
})();

View File

@@ -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;
}
}
}
};

View File

@@ -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 @@
</div>
</div>
{% endblock %}
{% block footer_scripts %}
{# EmailLimitsNew controller: inline from view (preferred) or fallback to static file #}
{% if email_limits_controller_js %}
<script data-cfasync="false">{{ email_limits_controller_js|safe }}</script>
{% else %}
<script src="{% static 'mailServer/emailLimitsController.js' %}?v=2.4.4.1" data-cfasync="false"></script>
{% endif %}
{% endblock %}

View File

@@ -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;
}
};
});
})();

View File

@@ -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) {

View File

@@ -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.

View File

@@ -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 (repos `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 apps `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.