Merge pull request #1755 from master3395/v2.5.5-dev

V2.5.5 dev
This commit is contained in:
Master3395
2026-04-09 23:35:02 +02:00
committed by GitHub
8 changed files with 811 additions and 109 deletions

View File

@@ -0,0 +1,128 @@
# CyberMail Email Delivery — initial schema.
# Database DDL uses int(11) FK to legacy loginSystem_administrator.id (MariaDB rejects bigint FK -> int PK).
from django.db import migrations, models
import django.db.models.deletion
CREATE_CYBERMAIL_TABLES = '''
CREATE TABLE `cybermail_accounts` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`platform_account_id` int(11) DEFAULT NULL,
`api_key` varchar(255) NOT NULL,
`email` varchar(255) NOT NULL,
`plan_name` varchar(100) NOT NULL,
`plan_slug` varchar(50) NOT NULL,
`emails_per_month` int(11) NOT NULL,
`is_connected` tinyint(1) NOT NULL,
`relay_enabled` tinyint(1) NOT NULL,
`smtp_credential_id` int(11) DEFAULT NULL,
`smtp_username` varchar(255) NOT NULL,
`smtp_host` varchar(255) NOT NULL,
`smtp_port` int(11) NOT NULL,
`created_at` datetime(6) NOT NULL,
`updated_at` datetime(6) NOT NULL,
`admin_id` int(11) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `cybermail_accounts_admin_id_uniq` (`admin_id`),
CONSTRAINT `cybermail_accounts_admin_id_fk` FOREIGN KEY (`admin_id`)
REFERENCES `loginSystem_administrator` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
CREATE TABLE `cybermail_domains` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`domain` varchar(255) NOT NULL,
`platform_domain_id` int(11) DEFAULT NULL,
`status` varchar(50) NOT NULL,
`spf_verified` tinyint(1) NOT NULL,
`dkim_verified` tinyint(1) NOT NULL,
`dmarc_verified` tinyint(1) NOT NULL,
`dns_configured` tinyint(1) NOT NULL,
`created_at` datetime(6) NOT NULL,
`account_id` int(11) NOT NULL,
PRIMARY KEY (`id`),
KEY `cybermail_domains_account_id_fk` (`account_id`),
CONSTRAINT `cybermail_domains_account_id_fk` FOREIGN KEY (`account_id`)
REFERENCES `cybermail_accounts` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
'''
DROP_CYBERMAIL_TABLES = '''
DROP TABLE IF EXISTS `cybermail_domains`;
DROP TABLE IF EXISTS `cybermail_accounts`;
'''
class Migration(migrations.Migration):
initial = True
dependencies = [
('loginSystem', '0001_initial'),
]
operations = [
migrations.SeparateDatabaseAndState(
database_operations=[
migrations.RunSQL(CREATE_CYBERMAIL_TABLES, DROP_CYBERMAIL_TABLES),
],
state_operations=[
migrations.CreateModel(
name='CyberMailAccount',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('platform_account_id', models.IntegerField(null=True)),
('api_key', models.CharField(blank=True, max_length=255)),
('email', models.CharField(max_length=255)),
('plan_name', models.CharField(default='Free', max_length=100)),
('plan_slug', models.CharField(default='free', max_length=50)),
('emails_per_month', models.IntegerField(default=15000)),
('is_connected', models.BooleanField(default=False)),
('relay_enabled', models.BooleanField(default=False)),
('smtp_credential_id', models.IntegerField(null=True)),
('smtp_username', models.CharField(blank=True, max_length=255)),
('smtp_host', models.CharField(default='mail.cyberpersons.com', max_length=255)),
('smtp_port', models.IntegerField(default=587)),
('created_at', models.DateTimeField(auto_now_add=True)),
('updated_at', models.DateTimeField(auto_now=True)),
(
'admin',
models.OneToOneField(
on_delete=django.db.models.deletion.CASCADE,
related_name='cybermail_account',
to='loginSystem.administrator',
),
),
],
options={
'db_table': 'cybermail_accounts',
},
),
migrations.CreateModel(
name='CyberMailDomain',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('domain', models.CharField(max_length=255)),
('platform_domain_id', models.IntegerField(null=True)),
('status', models.CharField(default='pending', max_length=50)),
('spf_verified', models.BooleanField(default=False)),
('dkim_verified', models.BooleanField(default=False)),
('dmarc_verified', models.BooleanField(default=False)),
('dns_configured', models.BooleanField(default=False)),
('created_at', models.DateTimeField(auto_now_add=True)),
(
'account',
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name='domains',
to='emailDelivery.cybermailaccount',
),
),
],
options={
'db_table': 'cybermail_domains',
},
),
],
),
]

View File

@@ -0,0 +1 @@
# CyberMail Email Delivery migrations

View File

@@ -3,6 +3,8 @@ from loginSystem.models import Administrator
class CyberMailAccount(models.Model):
# int(11) PK/FK to match legacy loginSystem_administrator.id (not DEFAULT_AUTO_FIELD BigAutoField)
id = models.AutoField(primary_key=True)
admin = models.OneToOneField(Administrator, on_delete=models.CASCADE, related_name='cybermail_account')
platform_account_id = models.IntegerField(null=True)
api_key = models.CharField(max_length=255, blank=True)
@@ -27,6 +29,7 @@ class CyberMailAccount(models.Model):
class CyberMailDomain(models.Model):
id = models.AutoField(primary_key=True)
account = models.ForeignKey(CyberMailAccount, on_delete=models.CASCADE, related_name='domains')
domain = models.CharField(max_length=255)
platform_domain_id = models.IntegerField(null=True)

View File

@@ -3,7 +3,7 @@ import os
import time
from django.shortcuts import redirect
from django.http import HttpResponse
from django.http import HttpResponse, JsonResponse
from loginSystem.models import Administrator
from mailServer.models import Domains, EUsers
@@ -1233,32 +1233,28 @@ def installStatusMailScanner(request):
###Rspamd
def Rspamd(request):
url = "https://platform.cyberpersons.com/CyberpanelAdOns/Adonpermission"
data = {
"name": "email-debugger",
"IP": ACLManager.GetServerIP()
}
"""Rspamd UI — allow for any logged-in admin (do not require cloud addon permission)."""
try:
userID = request.session['userID']
currentACL = ACLManager.loadedACL(userID)
if currentACL['admin'] != 1:
return ACLManager.loadError()
except KeyError:
return redirect(loadLoginPage)
import requests
response = requests.post(url, data=json.dumps(data))
Status = response.json()['status']
checkIfRspamdInstalled = 0
if (Status == 1) or ProcessUtilities.decideServer() == ProcessUtilities.ent:
checkIfRspamdInstalled = 0
ipFile = "/etc/cyberpanel/machineIP"
f = open(ipFile)
ipData = f.read()
ipAddress = ipData.split('\n', 1)[0]
ipFile = "/etc/cyberpanel/machineIP"
f = open(ipFile)
ipData = f.read()
ipAddress = ipData.split('\n', 1)[0]
if mailUtilities.checkIfRspamdInstalled() == 1:
checkIfRspamdInstalled = 1
if mailUtilities.checkIfRspamdInstalled() == 1:
checkIfRspamdInstalled = 1
proc = httpProc(request, 'emailPremium/Rspamd.html',
{'checkIfRspamdInstalled': checkIfRspamdInstalled, 'ipAddress': ipAddress}, 'admin')
return proc.render()
else:
return redirect("https://cyberpanel.net/cyberpanel-addons")
proc = httpProc(request, 'emailPremium/Rspamd.html',
{'checkIfRspamdInstalled': checkIfRspamdInstalled, 'ipAddress': ipAddress}, 'admin')
return proc.render()
def installRspamd(request):
try:
@@ -1268,35 +1264,20 @@ def installRspamd(request):
if currentACL['admin'] == 1:
pass
else:
return ACLManager.loadErrorJson()
return JsonResponse({'status': 0, 'error_message': 'Admin access required.'})
url = "https://platform.cyberpersons.com/CyberpanelAdOns/Adonpermission"
data = {
"name": "email-debugger",
"IP": ACLManager.GetServerIP()
}
import requests
response = requests.post(url, data=json.dumps(data))
Status = response.json()['status']
if (Status == 1) or ProcessUtilities.decideServer() == ProcessUtilities.ent:
try:
execPath = "/usr/local/CyberCP/bin/python " + virtualHostUtilities.cyberPanel + "/plogical/mailUtilities.py"
execPath = execPath + " installRspamd"
ProcessUtilities.popenExecutioner(execPath)
final_json = json.dumps({'status': 1, 'error_message': "None"})
return HttpResponse(final_json)
except BaseException as msg:
final_dic = {'status': 0, 'error_message': str(msg)}
final_json = json.dumps(final_dic)
return HttpResponse(final_json)
try:
execPath = "/usr/local/CyberCP/bin/python " + virtualHostUtilities.cyberPanel + "/plogical/mailUtilities.py"
execPath = execPath + " installRspamd"
ProcessUtilities.popenExecutioner(execPath)
return JsonResponse({'status': 1, 'error_message': 'None'})
except BaseException as msg:
return JsonResponse({'status': 0, 'error_message': str(msg)})
except KeyError:
final_dic = {'status': 0, 'error_message': "Not Logged In, please refresh the page or login again."}
final_json = json.dumps(final_dic)
return HttpResponse(final_json)
return JsonResponse({
'status': 0,
'error_message': 'Not Logged In, please refresh the page or login again.',
})
def installStatusRspamd(request):
try:
@@ -1310,8 +1291,15 @@ def installStatusRspamd(request):
try:
if request.method == 'POST':
command = "sudo cat " + mailUtilities.RspamdInstallLogPath
installStatus = ProcessUtilities.outputExecutioner(command)
if not os.path.isfile(mailUtilities.RspamdInstallLogPath):
installStatus = (
'Waiting for installation to start... '
'(Progress file: /var/log/cyberpanel/rspamd-install.log — if this persists, '
'click Install again or run as root: tail -f /var/log/cyberpanel/rspamd-install.log)\n'
)
else:
command = "sudo cat " + mailUtilities.RspamdInstallLogPath
installStatus = ProcessUtilities.outputExecutioner(command)
if installStatus.find("[200]") > -1:
@@ -1365,19 +1353,8 @@ def fetchRspamdSettings(request):
else:
return ACLManager.loadErrorJson('fetchStatus', 0)
url = "https://platform.cyberpersons.com/CyberpanelAdOns/Adonpermission"
data = {
"name": "email-debugger",
"IP": ACLManager.GetServerIP()
}
import requests
response = requests.post(url, data=json.dumps(data))
Status = response.json()['status']
if (Status == 1) or ProcessUtilities.decideServer() == ProcessUtilities.ent:
try:
if request.method == 'POST':
try:
if request.method == 'POST':
enabled = True
action = ''
@@ -1577,10 +1554,10 @@ def fetchRspamdSettings(request):
final_json = json.dumps(final_dic)
return HttpResponse(final_json)
except BaseException as msg:
final_dic = {'fetchStatus': 0, 'error_message': str(msg)}
final_json = json.dumps(final_dic)
return HttpResponse(final_json)
except BaseException as msg:
final_dic = {'fetchStatus': 0, 'error_message': str(msg)}
final_json = json.dumps(final_dic)
return HttpResponse(final_json)
except KeyError:
return redirect(loadLoginPage)
@@ -1747,21 +1724,17 @@ def unistallRspamd(request):
ProcessUtilities.popenExecutioner(execPath)
final_json = json.dumps({'status': 1, 'error_message': "None"})
return HttpResponse(final_json)
return JsonResponse({'status': 1, 'error_message': 'None'})
except BaseException as msg:
final_dic = {'status': 0, 'error_message': str(msg)}
final_json = json.dumps(final_dic)
return HttpResponse(final_json)
return JsonResponse({'status': 0, 'error_message': str(msg)})
except KeyError:
final_dic = {'status': 0, 'error_message': "Not Logged In, please refresh the page or login again."}
final_json = json.dumps(final_dic)
return HttpResponse(final_json)
return JsonResponse({
'status': 0,
'error_message': 'Not Logged In, please refresh the page or login again.',
})
def uninstallStatusRspamd(request):
try:
@@ -1775,8 +1748,14 @@ def uninstallStatusRspamd(request):
try:
if request.method == 'POST':
command = "sudo cat " + mailUtilities.RspamdUnInstallLogPath
installStatus = ProcessUtilities.outputExecutioner(command)
if not os.path.isfile(mailUtilities.RspamdUnInstallLogPath):
installStatus = (
'Waiting for uninstall to start... '
'(Progress file: /var/log/cyberpanel/rspamd-uninstall.log)\n'
)
else:
command = "sudo cat " + mailUtilities.RspamdUnInstallLogPath
installStatus = ProcessUtilities.outputExecutioner(command)
if installStatus.find("[200]") > -1:

View File

@@ -12,6 +12,7 @@ sys.path.append('/usr/local/CyberCP')
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "CyberCP.settings")
django.setup()
from django.http import HttpResponse
from django.db import connection
try:
from .models import Domains,EUsers
from loginSystem.views import loadLoginPage
@@ -45,6 +46,57 @@ import threading as multi
import argparse
def _ensure_email_filter_tables():
"""Create catch-all/plus/pattern email feature tables if missing."""
create_statements = [
"""CREATE TABLE IF NOT EXISTS `e_catchall` (
`domain_id` varchar(50) NOT NULL,
`destination` varchar(255) NOT NULL,
`enabled` tinyint(1) NOT NULL DEFAULT 1,
PRIMARY KEY (`domain_id`),
KEY `idx_e_catchall_domain_id` (`domain_id`)
) ENGINE=InnoDB""",
"""CREATE TABLE IF NOT EXISTS `e_server_settings` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`plus_addressing_enabled` tinyint(1) NOT NULL DEFAULT 0,
`plus_addressing_delimiter` varchar(1) NOT NULL DEFAULT '+',
PRIMARY KEY (`id`)
) ENGINE=InnoDB""",
"""CREATE TABLE IF NOT EXISTS `e_plus_override` (
`domain_id` varchar(50) NOT NULL,
`enabled` tinyint(1) NOT NULL DEFAULT 1,
PRIMARY KEY (`domain_id`),
KEY `idx_e_plus_override_domain_id` (`domain_id`)
) ENGINE=InnoDB""",
"""CREATE TABLE IF NOT EXISTS `e_pattern_forwarding` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`domain_id` varchar(50) NOT NULL,
`pattern` varchar(255) NOT NULL,
`destination` varchar(255) NOT NULL,
`pattern_type` varchar(20) NOT NULL DEFAULT 'wildcard',
`priority` int(11) NOT NULL DEFAULT 100,
`enabled` tinyint(1) NOT NULL DEFAULT 1,
PRIMARY KEY (`id`),
KEY `idx_e_pattern_forwarding_domain_id` (`domain_id`)
) ENGINE=InnoDB"""
]
try:
with connection.cursor() as cursor:
for query in create_statements:
cursor.execute(query)
cursor.execute(
"""INSERT INTO `e_server_settings` (`id`, `plus_addressing_enabled`, `plus_addressing_delimiter`)
SELECT 1, 0, '+'
WHERE NOT EXISTS (SELECT 1 FROM `e_server_settings` WHERE `id` = 1)"""
)
return True, ''
except BaseException as msg:
logging.CyberCPLogFileWriter.writeToFile(str(msg) + ' [_ensure_email_filter_tables]')
return False, 'Email feature tables are missing and could not be created automatically. Please run CyberPanel upgrade or apply DB migration.'
def _get_email_limits_controller_js():
"""Return EmailLimitsNew controller JS: from file or hardcoded fallback so it always works."""
try:
@@ -2084,6 +2136,12 @@ protocol sieve {
if ACLManager.currentContextPermission(currentACL, 'emailForwarding') == 0:
return ACLManager.loadErrorJson('fetchStatus', 0)
ok, schemaErr = _ensure_email_filter_tables()
if not ok:
data_ret = {'status': 0, 'fetchStatus': 0, 'error_message': schemaErr}
json_data = json.dumps(data_ret)
return HttpResponse(json_data)
data = json.loads(self.request.body)
domain = data['domain']
@@ -2132,6 +2190,12 @@ protocol sieve {
if ACLManager.currentContextPermission(currentACL, 'emailForwarding') == 0:
return ACLManager.loadErrorJson('saveStatus', 0)
ok, schemaErr = _ensure_email_filter_tables()
if not ok:
data_ret = {'status': 0, 'saveStatus': 0, 'error_message': schemaErr}
json_data = json.dumps(data_ret)
return HttpResponse(json_data)
data = json.loads(self.request.body)
domain = data['domain']
destination = data['destination']
@@ -2190,6 +2254,12 @@ protocol sieve {
if ACLManager.currentContextPermission(currentACL, 'emailForwarding') == 0:
return ACLManager.loadErrorJson('deleteStatus', 0)
ok, schemaErr = _ensure_email_filter_tables()
if not ok:
data_ret = {'status': 0, 'deleteStatus': 0, 'error_message': schemaErr}
json_data = json.dumps(data_ret)
return HttpResponse(json_data)
data = json.loads(self.request.body)
domain = data['domain']

View File

@@ -1,12 +1,13 @@
import json
import os,sys
import os, sys
import time
# CyberPanel Django app "dns" must win over PyPI "dns" (dnspython). append() leaves site-packages first.
_cybercp_root = '/usr/local/CyberCP'
if _cybercp_root not in sys.path:
sys.path.insert(0, _cybercp_root)
from django.http import HttpResponse
sys.path.append('/usr/local/CyberCP')
import django
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "CyberCP.settings")
try:
@@ -37,8 +38,10 @@ class mailUtilities:
installLogPath = "/home/cyberpanel/openDKIMInstallLog"
spamassassinInstallLogPath = "/home/cyberpanel/spamassassinInstallLogPath"
RspamdInstallLogPath = "/home/cyberpanel/RspamdInstallLogPath"
RspamdUnInstallLogPath = "/home/cyberpanel/RspamdUnInstallLogPath"
# Use /var/log (not /home/cyberpanel mode 0700) so lscpd workers can always write install progress.
rspamdLogDir = "/var/log/cyberpanel"
RspamdInstallLogPath = "/var/log/cyberpanel/rspamd-install.log"
RspamdUnInstallLogPath = "/var/log/cyberpanel/rspamd-uninstall.log"
cyberPanelHome = "/home/cyberpanel"
mailScannerInstallLogPath = "/home/cyberpanel/mailScannerInstallLogPath"
RSpamdLogPath = '/var/log/rspamd/rspamd.log'
@@ -822,14 +825,27 @@ return custom_keywords
@staticmethod
def installRspamd(install, rspamd):
# Progress file before ServiceManager import (import can fail; panel polls this path).
os.makedirs(mailUtilities.rspamdLogDir, mode=0o755, exist_ok=True)
if not os.path.isdir(mailUtilities.cyberPanelHome):
os.makedirs(mailUtilities.cyberPanelHome, mode=0o750, exist_ok=True)
with open(mailUtilities.RspamdInstallLogPath, 'w') as lf:
lf.write('Starting Rspamd installation (preparing Redis and packages)...\n')
lf.flush()
from manageServices.serviceManager import ServiceManager
try:
if os.path.exists(mailUtilities.RspamdInstallLogPath):
os.remove(mailUtilities.RspamdInstallLogPath)
####Frist install redis
with open(mailUtilities.RspamdInstallLogPath, 'a') as lf:
lf.write(
'Running InstallRedis() — this often takes 520 minutes (system packages). '
'Progress from that step is not streamed here; the log continues after Redis finishes.\n'
)
lf.flush()
ServiceManager.InstallRedis()
with open(mailUtilities.RspamdInstallLogPath, 'a') as lf:
lf.write('Redis step finished. Adding Rspamd repository and installing packages...\n')
lf.flush()
if ProcessUtilities.decideDistro() == ProcessUtilities.centos:
@@ -862,16 +878,18 @@ return custom_keywords
command = 'rpm --import https://rspamd.com/rpm-stable/gpg.key'
ProcessUtilities.normalExecutioner(command, True)
command = 'yum update'
command = 'dnf update -y'
ProcessUtilities.normalExecutioner(command, True)
command = 'sudo yum install rspamd clamav clamd clamav-update -y'
command = 'sudo dnf install -y rspamd clamav clamd clamav-update'
else:
command = 'DEBIAN_FRONTEND=noninteractive apt-get install rspamd clamav clamav-daemon -y'
with open(mailUtilities.RspamdInstallLogPath, 'w') as f:
res = subprocess.call(command, stdout=f, shell=True)
with open(mailUtilities.RspamdInstallLogPath, 'a') as f:
f.write('\n--- Package install ---\n')
f.flush()
res = subprocess.call(command, stdout=f, stderr=f, shell=True)
###### makefile
@@ -1106,10 +1124,11 @@ LogFile /var/log/clamav/clamav.log
return 1
except BaseException as msg:
writeToFile = open(mailUtilities.RspamdInstallLogPath, 'a')
writeToFile.writelines("Can not be installed.[404]\n")
writeToFile.close()
logging.CyberCPLogFileWriter.writeToFile(str(msg) + "[installRspamd]")
err = str(msg)
with open(mailUtilities.RspamdInstallLogPath, 'a') as writeToFile:
writeToFile.write('Install failed: %s\n' % err.replace('\r', ' ').replace('\n', ' ')[:2000])
writeToFile.write('Can not be installed.[404]\n')
logging.CyberCPLogFileWriter.writeToFile(err + "[installRspamd]")
@staticmethod
def uninstallRspamd(install, rspamd):
@@ -1119,6 +1138,16 @@ LogFile /var/log/clamav/clamav.log
if os.path.exists(mailUtilities.RspamdUnInstallLogPath):
os.remove(mailUtilities.RspamdUnInstallLogPath)
try:
os.makedirs(mailUtilities.rspamdLogDir, mode=0o755, exist_ok=True)
if not os.path.isdir(mailUtilities.cyberPanelHome):
os.makedirs(mailUtilities.cyberPanelHome, mode=0o750, exist_ok=True)
with open(mailUtilities.RspamdUnInstallLogPath, 'w') as lf:
lf.write('Starting Rspamd removal...\n')
lf.flush()
except BaseException as log_err:
logging.CyberCPLogFileWriter.writeToFile(
str(log_err) + ' [uninstallRspamd init log]')
if ProcessUtilities.decideDistro() == ProcessUtilities.centos or ProcessUtilities.decideDistro() == ProcessUtilities.cent8:
command = 'sudo yum remove rspamd clamav clamav-daemon -y'
@@ -1129,7 +1158,7 @@ LogFile /var/log/clamav/clamav.log
with open(mailUtilities.RspamdUnInstallLogPath, 'w') as f:
with open(mailUtilities.RspamdUnInstallLogPath, 'a') as f:
res = subprocess.call(cmd, stdout=f)
if res == 1:
writeToFile = open(mailUtilities.RspamdUnInstallLogPath, 'a')

View File

@@ -2878,8 +2878,8 @@ CREATE TABLE `websiteFunctions_backupsv2` (`id` integer AUTO_INCREMENT NOT NULL
`destination` varchar(255) NOT NULL,
`enabled` tinyint(1) NOT NULL DEFAULT 1,
PRIMARY KEY (`domain_id`),
CONSTRAINT `fk_catchall_domain` FOREIGN KEY (`domain_id`) REFERENCES `e_domains` (`domain`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4"""
KEY `idx_e_catchall_domain_id` (`domain_id`)
) ENGINE=InnoDB"""
try:
cursor.execute(query)
except:
@@ -2890,7 +2890,7 @@ CREATE TABLE `websiteFunctions_backupsv2` (`id` integer AUTO_INCREMENT NOT NULL
`plus_addressing_enabled` tinyint(1) NOT NULL DEFAULT 0,
`plus_addressing_delimiter` varchar(1) NOT NULL DEFAULT '+',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4"""
) ENGINE=InnoDB"""
try:
cursor.execute(query)
except:
@@ -2900,8 +2900,8 @@ CREATE TABLE `websiteFunctions_backupsv2` (`id` integer AUTO_INCREMENT NOT NULL
`domain_id` varchar(50) NOT NULL,
`enabled` tinyint(1) NOT NULL DEFAULT 1,
PRIMARY KEY (`domain_id`),
CONSTRAINT `fk_plus_override_domain` FOREIGN KEY (`domain_id`) REFERENCES `e_domains` (`domain`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4"""
KEY `idx_e_plus_override_domain_id` (`domain_id`)
) ENGINE=InnoDB"""
try:
cursor.execute(query)
except:
@@ -2916,14 +2916,21 @@ CREATE TABLE `websiteFunctions_backupsv2` (`id` integer AUTO_INCREMENT NOT NULL
`priority` int(11) NOT NULL DEFAULT 100,
`enabled` tinyint(1) NOT NULL DEFAULT 1,
PRIMARY KEY (`id`),
KEY `fk_pattern_domain` (`domain_id`),
CONSTRAINT `fk_pattern_domain` FOREIGN KEY (`domain_id`) REFERENCES `e_domains` (`domain`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4"""
KEY `idx_e_pattern_forwarding_domain_id` (`domain_id`)
) ENGINE=InnoDB"""
try:
cursor.execute(query)
except:
pass
# Seed singleton row for global email settings if missing.
try:
cursor.execute("""INSERT INTO `e_server_settings` (`id`, `plus_addressing_enabled`, `plus_addressing_delimiter`)
SELECT 1, 0, '+'
WHERE NOT EXISTS (SELECT 1 FROM `e_server_settings` WHERE `id` = 1)""")
except:
pass
try:
connection.close()
except:

View File

@@ -1345,3 +1345,488 @@ app.controller('listEmails', function ($scope, $http) {
/* Java script code for List Emails Ends here */
/* Java script code for EmailLimitsNew */
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 = false;
$scope.forwardError = true;
$scope.forwardSuccess = true;
$scope.couldNotConnect = true;
$scope.notifyBox = true;
};
$scope.fetchCurrentLimits = function () {
var url = "/email/fetchCurrentLimits";
var data = {
emailAddress: $scope.emailAddress
};
var config = {
headers: {
'X-CSRFToken': getCookie('csrftoken')
}
};
$http.post(url, data, config).then(function (response) {
if (response.data.fetchStatus === 1) {
$scope.currentEmailLimit = response.data.emailLimit || 3072;
$scope.currentEmailAllowed = response.data.allowedPCT || 100;
$scope.newEmailLimit = $scope.currentEmailLimit;
$scope.newEmailAllowed = $scope.currentEmailAllowed;
} else {
new PNotify({
title: 'Error!',
text: response.data.error_message || 'Could not fetch current limits.',
type: 'error'
});
}
}, function () {
new PNotify({
title: 'Error!',
text: 'Could not connect to server.',
type: 'error'
});
});
};
$scope.saveEmailLimits = function () {
var url = "/email/saveEmailLimits";
var data = {
emailAddress: $scope.emailAddress,
emailLimit: $scope.newEmailLimit,
allowedPCT: $scope.newEmailAllowed
};
var config = {
headers: {
'X-CSRFToken': getCookie('csrftoken')
}
};
$http.post(url, data, config).then(function (response) {
if (response.data.saveStatus === 1) {
new PNotify({
title: 'Success!',
text: response.data.message || 'Limits saved successfully.',
type: 'success'
});
$scope.showEmailDetails();
} else {
new PNotify({
title: 'Error!',
text: response.data.error_message || 'Failed to save limits.',
type: 'error'
});
}
}, function () {
new PNotify({
title: 'Error!',
text: 'Could not connect to server.',
type: 'error'
});
});
};
});
/* Java script for EmailLimitsNew */
/* Catch-All Email Controller */
app.controller('catchAllEmail', function ($scope, $http) {
$scope.configBox = true;
$scope.loading = false;
$scope.errorBox = true;
$scope.successBox = true;
$scope.couldNotConnect = true;
$scope.notifyBox = true;
$scope.currentConfigured = false;
$scope.enabled = true;
$scope.fetchConfig = function () {
if (!$scope.selectedDomain) {
$scope.configBox = true;
return;
}
$scope.loading = true;
$scope.configBox = true;
$scope.notifyBox = true;
var url = "/email/fetchCatchAllConfig";
var data = { domain: $scope.selectedDomain };
var config = { headers: { 'X-CSRFToken': getCookie('csrftoken') } };
$http.post(url, data, config).then(function (response) {
$scope.loading = false;
if (response.data.fetchStatus === 1) {
$scope.configBox = false;
if (response.data.configured === 1) {
$scope.currentConfigured = true;
$scope.currentDestination = response.data.destination;
$scope.currentEnabled = response.data.enabled;
$scope.destination = response.data.destination;
$scope.enabled = response.data.enabled;
} else {
$scope.currentConfigured = false;
$scope.destination = '';
$scope.enabled = true;
}
} else {
$scope.errorBox = false;
$scope.notifyBox = false;
$scope.errorMessage = response.data.error_message;
}
}, function () {
$scope.loading = false;
$scope.couldNotConnect = false;
$scope.notifyBox = false;
});
};
$scope.saveConfig = function () {
if (!$scope.destination) {
$scope.errorBox = false;
$scope.notifyBox = false;
$scope.errorMessage = 'Please enter a destination email address';
return;
}
$scope.loading = true;
$scope.notifyBox = true;
var url = "/email/saveCatchAllConfig";
var data = {
domain: $scope.selectedDomain,
destination: $scope.destination,
enabled: $scope.enabled
};
var config = { headers: { 'X-CSRFToken': getCookie('csrftoken') } };
$http.post(url, data, config).then(function (response) {
$scope.loading = false;
if (response.data.saveStatus === 1) {
$scope.successBox = false;
$scope.notifyBox = false;
$scope.successMessage = response.data.message;
$scope.currentConfigured = true;
$scope.currentDestination = $scope.destination;
$scope.currentEnabled = $scope.enabled;
} else {
$scope.errorBox = false;
$scope.notifyBox = false;
$scope.errorMessage = response.data.error_message;
}
}, function () {
$scope.loading = false;
$scope.couldNotConnect = false;
$scope.notifyBox = false;
});
};
$scope.deleteConfig = function () {
if (!confirm('Are you sure you want to remove the catch-all configuration?')) {
return;
}
$scope.loading = true;
$scope.notifyBox = true;
var url = "/email/deleteCatchAllConfig";
var data = { domain: $scope.selectedDomain };
var config = { headers: { 'X-CSRFToken': getCookie('csrftoken') } };
$http.post(url, data, config).then(function (response) {
$scope.loading = false;
if (response.data.deleteStatus === 1) {
$scope.successBox = false;
$scope.notifyBox = false;
$scope.successMessage = response.data.message;
$scope.currentConfigured = false;
$scope.destination = '';
$scope.enabled = true;
} else {
$scope.errorBox = false;
$scope.notifyBox = false;
$scope.errorMessage = response.data.error_message;
}
}, function () {
$scope.loading = false;
$scope.couldNotConnect = false;
$scope.notifyBox = false;
});
};
});
/* Plus-Addressing Controller */
app.controller('plusAddressing', function ($scope, $http) {
$scope.loading = true;
$scope.globalEnabled = false;
$scope.delimiter = '+';
$scope.domainEnabled = true;
$scope.globalNotifyBox = true;
$scope.globalErrorBox = true;
$scope.globalSuccessBox = true;
$scope.domainNotifyBox = true;
$scope.domainErrorBox = true;
$scope.domainSuccessBox = true;
var url = "/email/fetchPlusAddressingConfig";
var config = { headers: { 'X-CSRFToken': getCookie('csrftoken') } };
$http.post(url, {}, config).then(function (response) {
$scope.loading = false;
if (response.data.fetchStatus === 1) {
$scope.globalEnabled = response.data.globalEnabled;
$scope.delimiter = response.data.delimiter || '+';
}
}, function () {
$scope.loading = false;
});
$scope.saveGlobalSettings = function () {
$scope.loading = true;
$scope.globalNotifyBox = true;
var saveUrl = "/email/savePlusAddressingGlobal";
var data = {
enabled: $scope.globalEnabled,
delimiter: $scope.delimiter
};
var saveConfig = { headers: { 'X-CSRFToken': getCookie('csrftoken') } };
$http.post(saveUrl, data, saveConfig).then(function (response) {
$scope.loading = false;
if (response.data.saveStatus === 1) {
$scope.globalSuccessBox = false;
$scope.globalNotifyBox = false;
$scope.globalSuccessMessage = response.data.message;
} else {
$scope.globalErrorBox = false;
$scope.globalNotifyBox = false;
$scope.globalErrorMessage = response.data.error_message;
}
}, function () {
$scope.loading = false;
$scope.globalErrorBox = false;
$scope.globalNotifyBox = false;
$scope.globalErrorMessage = 'Could not connect to server';
});
};
$scope.saveDomainSettings = function () {
if (!$scope.selectedDomain) {
return;
}
$scope.domainNotifyBox = true;
var saveDomainUrl = "/email/savePlusAddressingDomain";
var data = {
domain: $scope.selectedDomain,
enabled: $scope.domainEnabled
};
var saveDomainConfig = { headers: { 'X-CSRFToken': getCookie('csrftoken') } };
$http.post(saveDomainUrl, data, saveDomainConfig).then(function (response) {
if (response.data.saveStatus === 1) {
$scope.domainSuccessBox = false;
$scope.domainNotifyBox = false;
$scope.domainSuccessMessage = response.data.message;
} else {
$scope.domainErrorBox = false;
$scope.domainNotifyBox = false;
$scope.domainErrorMessage = response.data.error_message;
}
}, function () {
$scope.domainErrorBox = false;
$scope.domainNotifyBox = false;
$scope.domainErrorMessage = 'Could not connect to server';
});
};
});
/* Pattern Forwarding Controller */
app.controller('patternForwarding', function ($scope, $http) {
$scope.configBox = true;
$scope.loading = false;
$scope.errorBox = true;
$scope.successBox = true;
$scope.couldNotConnect = true;
$scope.notifyBox = true;
$scope.rules = [];
$scope.patternType = 'wildcard';
$scope.priority = 100;
$scope.fetchRules = function () {
if (!$scope.selectedDomain) {
$scope.configBox = true;
return;
}
$scope.loading = true;
$scope.configBox = true;
$scope.notifyBox = true;
var url = "/email/fetchPatternRules";
var data = { domain: $scope.selectedDomain };
var config = { headers: { 'X-CSRFToken': getCookie('csrftoken') } };
$http.post(url, data, config).then(function (response) {
$scope.loading = false;
if (response.data.fetchStatus === 1) {
$scope.configBox = false;
$scope.rules = response.data.rules;
} else {
$scope.errorBox = false;
$scope.notifyBox = false;
$scope.errorMessage = response.data.error_message;
}
}, function () {
$scope.loading = false;
$scope.couldNotConnect = false;
$scope.notifyBox = false;
});
};
$scope.createRule = function () {
if (!$scope.pattern || !$scope.destination) {
$scope.errorBox = false;
$scope.notifyBox = false;
$scope.errorMessage = 'Please enter both pattern and destination';
return;
}
$scope.loading = true;
$scope.notifyBox = true;
var createUrl = "/email/createPatternRule";
var data = {
domain: $scope.selectedDomain,
pattern: $scope.pattern,
destination: $scope.destination,
pattern_type: $scope.patternType,
priority: $scope.priority
};
var createConfig = { headers: { 'X-CSRFToken': getCookie('csrftoken') } };
$http.post(createUrl, data, createConfig).then(function (response) {
$scope.loading = false;
if (response.data.createStatus === 1) {
$scope.successBox = false;
$scope.notifyBox = false;
$scope.successMessage = response.data.message;
$scope.pattern = '';
$scope.destination = '';
$scope.fetchRules();
} else {
$scope.errorBox = false;
$scope.notifyBox = false;
$scope.errorMessage = response.data.error_message;
}
}, function () {
$scope.loading = false;
$scope.couldNotConnect = false;
$scope.notifyBox = false;
});
};
$scope.deleteRule = function (ruleId) {
if (!confirm('Are you sure you want to delete this forwarding rule?')) {
return;
}
$scope.loading = true;
$scope.notifyBox = true;
var deleteUrl = "/email/deletePatternRule";
var data = { ruleId: ruleId };
var deleteConfig = { headers: { 'X-CSRFToken': getCookie('csrftoken') } };
$http.post(deleteUrl, data, deleteConfig).then(function (response) {
$scope.loading = false;
if (response.data.deleteStatus === 1) {
$scope.successBox = false;
$scope.notifyBox = false;
$scope.successMessage = response.data.message;
$scope.fetchRules();
} else {
$scope.errorBox = false;
$scope.notifyBox = false;
$scope.errorMessage = response.data.error_message;
}
}, function () {
$scope.loading = false;
$scope.couldNotConnect = false;
$scope.notifyBox = false;
});
};
});