Remove bundled plugins from v2.5.5-dev - plugins live in cyberpanel-plugins only

Removed: emailMarketing, examplePlugin, paypalPremiumPlugin, premiumPlugin, testPlugin

All plugins are now installed via Plugin Store from https://github.com/master3395/cyberpanel-plugins
This commit is contained in:
master3395
2026-02-03 00:00:59 +01:00
parent d8ee83e30d
commit d9329e2a21
99 changed files with 1 additions and 15209 deletions

Binary file not shown.

View File

@@ -1 +0,0 @@
default_app_config = 'emailMarketing.apps.EmailmarketingConfig'

View File

@@ -1,6 +0,0 @@
# -*- coding: utf-8 -*-
from django.contrib import admin
# Register your models here.

View File

@@ -1,10 +0,0 @@
# -*- coding: utf-8 -*-
from django.apps import AppConfig
class EmailmarketingConfig(AppConfig):
name = 'emailMarketing'
def ready(self):
from . import signals

View File

@@ -1,66 +0,0 @@
from .models import EmailMarketing, EmailTemplate, SMTPHosts, EmailLists, EmailJobs
from websiteFunctions.models import Websites
class emACL:
@staticmethod
def checkIfEMEnabled(userName):
try:
user = EmailMarketing.objects.get(userName=userName)
return 0
except:
return 1
@staticmethod
def getEmailsLists(domain):
website = Websites.objects.get(domain=domain)
emailLists = website.emaillists_set.all()
listNames = []
for items in emailLists:
listNames.append(items.listName)
return listNames
@staticmethod
def allTemplates(currentACL, admin):
if currentACL['admin'] == 1:
allTemplates = EmailTemplate.objects.all()
else:
allTemplates = admin.emailtemplate_set.all()
templateNames = []
for items in allTemplates:
templateNames.append(items.name)
return templateNames
@staticmethod
def allSMTPHosts(currentACL, admin):
if currentACL['admin'] == 1:
allHosts = SMTPHosts.objects.all()
else:
allHosts = admin.smtphosts_set.all()
hostNames = []
for items in allHosts:
hostNames.append(items.host)
return hostNames
@staticmethod
def allEmailsLists(currentACL, admin):
listNames = []
emailLists = EmailLists.objects.all()
if currentACL['admin'] == 1:
for items in emailLists:
listNames.append(items.listName)
else:
for items in emailLists:
if items.owner.admin == admin:
listNames.append(items.listName)
return listNames

View File

@@ -1,427 +0,0 @@
#!/usr/local/CyberCP/bin/python
import os
import time
import csv
import re
import plogical.CyberCPLogFileWriter as logging
from .models import EmailLists, EmailsInList, EmailTemplate, EmailJobs, SMTPHosts, ValidationLog
from plogical.backupSchedule import backupSchedule
from websiteFunctions.models import Websites
import threading as multi
import socket, smtplib
import DNS
from random import randint
from plogical.processUtilities import ProcessUtilities
class emailMarketing(multi.Thread):
def __init__(self, function, extraArgs):
multi.Thread.__init__(self)
self.function = function
self.extraArgs = extraArgs
def run(self):
try:
if self.function == 'createEmailList':
self.createEmailList()
elif self.function == 'verificationJob':
self.verificationJob()
elif self.function == 'startEmailJob':
self.startEmailJob()
except BaseException as msg:
logging.CyberCPLogFileWriter.writeToFile(str(msg) + ' [emailMarketing.run]')
def createEmailList(self):
try:
website = Websites.objects.get(domain=self.extraArgs['domain'])
try:
newList = EmailLists(owner=website, listName=self.extraArgs['listName'], dateCreated=time.strftime("%I-%M-%S-%a-%b-%Y"))
newList.save()
except:
newList = EmailLists.objects.get(listName=self.extraArgs['listName'])
counter = 0
if self.extraArgs['path'].endswith('.csv'):
with open(self.extraArgs['path'], 'r') as emailsList:
data = csv.reader(emailsList, delimiter=',')
for items in data:
try:
for value in items:
if re.match('^[_a-z0-9-]+(\.[_a-z0-9-]+)*@[a-z0-9-]+(\.[a-z0-9-]+)*(\.[a-z]{2,4})$', value) != None:
try:
getEmail = EmailsInList.objects.get(owner=newList, email=value)
except:
try:
newEmail = EmailsInList(owner=newList, email=value,
verificationStatus='NOT CHECKED',
dateCreated=time.strftime("%I-%M-%S-%a-%b-%Y"))
newEmail.save()
except:
pass
logging.CyberCPLogFileWriter.statusWriter(self.extraArgs['tempStatusPath'], str(counter) + ' emails read.')
counter = counter + 1
except BaseException as msg:
logging.CyberCPLogFileWriter.writeToFile('%s. [createEmailList]' % (str(msg)))
continue
elif self.extraArgs['path'].endswith('.txt'):
with open(self.extraArgs['path'], 'r') as emailsList:
emails = emailsList.readline()
while emails:
email = emails.strip('\n')
if re.match('^[_a-z0-9-]+(\.[_a-z0-9-]+)*@[a-z0-9-]+(\.[a-z0-9-]+)*(\.[a-z]{2,4})$', email) != None:
try:
getEmail = EmailsInList.objects.get(owner=newList, email=email)
except BaseException as msg:
newEmail = EmailsInList(owner=newList, email=email, verificationStatus='NOT CHECKED',
dateCreated=time.strftime("%I-%M-%S-%a-%b-%Y"))
newEmail.save()
logging.CyberCPLogFileWriter.statusWriter(self.extraArgs['tempStatusPath'],str(counter) + ' emails read.')
counter = counter + 1
emails = emailsList.readline()
logging.CyberCPLogFileWriter.statusWriter(self.extraArgs['tempStatusPath'], str(counter) + 'Successfully read all emails. [200]')
except BaseException as msg:
logging.CyberCPLogFileWriter.statusWriter(self.extraArgs['tempStatusPath'], str(msg) +'. [404]')
return 0
def findNextIP(self):
try:
if self.delayData['rotation'] == 'Disable':
return None
elif self.delayData['rotation'] == 'IPv4':
if self.delayData['ipv4'].find(',') == -1:
return self.delayData['ipv4']
else:
ipv4s = self.delayData['ipv4'].split(',')
if self.currentIP == '':
return ipv4s[0]
else:
returnCheck = 0
for items in ipv4s:
if returnCheck == 1:
return items
if items == self.currentIP:
returnCheck = 1
return ipv4s[0]
else:
if self.delayData['ipv6'].find(',') == -1:
return self.delayData['ipv6']
else:
ipv6 = self.delayData['ipv6'].split(',')
if self.currentIP == '':
return ipv6[0]
else:
returnCheck = 0
for items in ipv6:
if returnCheck == 1:
return items
if items == self.currentIP:
returnCheck = 1
return ipv6[0]
except BaseException as msg:
logging.CyberCPLogFileWriter.writeToFile(str(msg))
return None
def verificationJob(self):
try:
verificationList = EmailLists.objects.get(listName=self.extraArgs['listName'])
domain = verificationList.owner.domain
if not os.path.exists('/home/cyberpanel/' + domain):
os.mkdir('/home/cyberpanel/' + domain)
tempStatusPath = '/home/cyberpanel/' + domain + "/" + self.extraArgs['listName']
logging.CyberCPLogFileWriter.statusWriter(tempStatusPath, 'Starting verification job..')
counter = 1
counterGlobal = 0
allEmailsInList = verificationList.emailsinlist_set.all()
configureVerifyPath = '/home/cyberpanel/configureVerify'
finalPath = '%s/%s' % (configureVerifyPath, domain)
import json
if os.path.exists(finalPath):
self.delayData = json.loads(open(finalPath, 'r').read())
self.currentIP = ''
ValidationLog(owner=verificationList, status=backupSchedule.INFO, message='Starting email verification..').save()
for items in allEmailsInList:
if items.verificationStatus != 'Verified':
try:
email = items.email
self.currentEmail = email
domainName = email.split('@')[1]
records = DNS.dnslookup(domainName, 'MX', 15)
counterGlobal = counterGlobal + 1
for mxRecord in records:
# Get local server hostname
host = socket.gethostname()
## Only fetching smtp object
if os.path.exists(finalPath):
try:
delay = self.delayData['delay']
if delay == 'Enable':
if counterGlobal == int(self.delayData['delayAfter']):
ValidationLog(owner=verificationList, status=backupSchedule.INFO,
message='Sleeping for %s seconds...' % (self.delayData['delayTime'])).save()
time.sleep(int(self.delayData['delayTime']))
counterGlobal = 0
self.currentIP = self.findNextIP()
ValidationLog(owner=verificationList, status=backupSchedule.INFO,
message='IP being used for validation until next sleep: %s.' % (str(self.currentIP))).save()
if self.currentIP == None:
server = smtplib.SMTP(timeout=10)
else:
server = smtplib.SMTP(self.currentIP, timeout=10)
else:
if self.currentIP == '':
self.currentIP = self.findNextIP()
ValidationLog(owner=verificationList, status=backupSchedule.INFO,
message='IP being used for validation until next sleep: %s.' % (
str(self.currentIP))).save()
if self.currentIP == None:
server = smtplib.SMTP(timeout=10)
else:
server = smtplib.SMTP(self.currentIP, timeout=10)
else:
logging.CyberCPLogFileWriter.writeToFile(
'Delay not configured..')
ValidationLog(owner=verificationList, status=backupSchedule.INFO,
message='Delay not configured..').save()
server = smtplib.SMTP(timeout=10)
except BaseException as msg:
ValidationLog(owner=verificationList, status=backupSchedule.ERROR,
message='Delay not configured. Error message: %s' % (str(msg))).save()
server = smtplib.SMTP(timeout=10)
else:
server = smtplib.SMTP(timeout=10)
###
server.set_debuglevel(0)
# SMTP Conversation
server.connect(mxRecord[1])
server.helo(host)
server.mail('host' + "@" + host)
code, message = server.rcpt(str(email))
server.quit()
# Assume 250 as Success
if code == 250:
items.verificationStatus = 'Verified'
items.save()
break
else:
ValidationLog(owner=verificationList, status=backupSchedule.ERROR,
message='Failed to verify %s. Error message %s' % (email, message.decode())).save()
items.verificationStatus = 'Verification Failed'
items.save()
logging.CyberCPLogFileWriter.statusWriter(tempStatusPath, str(counter) + ' emails verified so far..')
counter = counter + 1
except BaseException as msg:
items.verificationStatus = 'Verification Failed'
items.save()
counter = counter + 1
ValidationLog(owner=verificationList, status=backupSchedule.ERROR,
message='Failed to verify %s. Error message %s' % (
self.currentEmail , str(msg))).save()
verificationList.notVerified = verificationList.emailsinlist_set.filter(verificationStatus='Verification Failed').count()
verificationList.verified = verificationList.emailsinlist_set.filter(verificationStatus='Verified').count()
verificationList.save()
ValidationLog(owner=verificationList, status=backupSchedule.ERROR, message=str(counter) + ' emails successfully verified. [200]').save()
logging.CyberCPLogFileWriter.statusWriter(tempStatusPath, str(counter) + ' emails successfully verified. [200]')
except BaseException as msg:
verificationList = EmailLists.objects.get(listName=self.extraArgs['listName'])
domain = verificationList.owner.domain
tempStatusPath = '/home/cyberpanel/' + domain + "/" + self.extraArgs['listName']
logging.CyberCPLogFileWriter.statusWriter(tempStatusPath, str(msg) +'. [404]')
logging.CyberCPLogFileWriter.writeToFile(str(msg))
return 0
def setupSMTPConnection(self):
try:
if self.extraArgs['host'] == 'localhost':
self.smtpServer = smtplib.SMTP('127.0.0.1')
return 1
else:
self.verifyHost = SMTPHosts.objects.get(host=self.extraArgs['host'])
self.smtpServer = smtplib.SMTP(str(self.verifyHost.host), int(self.verifyHost.port))
if int(self.verifyHost.port) == 587:
self.smtpServer.starttls()
self.smtpServer.login(str(self.verifyHost.userName), str(self.verifyHost.password))
return 1
except smtplib.SMTPHeloError:
logging.CyberCPLogFileWriter.statusWriter(self.extraArgs['tempStatusPath'],
'The server didnt reply properly to the HELO greeting.')
return 0
except smtplib.SMTPAuthenticationError:
logging.CyberCPLogFileWriter.statusWriter(self.extraArgs['tempStatusPath'],
'Username and password combination not accepted.')
return 0
except smtplib.SMTPException:
logging.CyberCPLogFileWriter.statusWriter(self.extraArgs['tempStatusPath'],
'No suitable authentication method was found.')
return 0
def startEmailJob(self):
try:
if self.setupSMTPConnection() == 0:
logging.CyberCPLogFileWriter.writeToFile('SMTP Connection failed. [301]')
return 0
emailList = EmailLists.objects.get(listName=self.extraArgs['listName'])
allEmails = emailList.emailsinlist_set.all()
emailMessage = EmailTemplate.objects.get(name=self.extraArgs['selectedTemplate'])
totalEmails = allEmails.count()
sent = 0
failed = 0
ipFile = "/etc/cyberpanel/machineIP"
f = open(ipFile)
ipData = f.read()
ipAddress = ipData.split('\n', 1)[0]
## Compose Message
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
import re
tempPath = "/home/cyberpanel/" + str(randint(1000, 9999))
emailJob = EmailJobs(owner=emailMessage, date=time.strftime("%I-%M-%S-%a-%b-%Y"),
host=self.extraArgs['host'], totalEmails=totalEmails,
sent=sent, failed=failed
)
emailJob.save()
for items in allEmails:
try:
message = MIMEMultipart('alternative')
message['Subject'] = emailMessage.subject
message['From'] = emailMessage.fromEmail
message['reply-to'] = emailMessage.replyTo
if (items.verificationStatus == 'Verified' or self.extraArgs[
'verificationCheck']) and not items.verificationStatus == 'REMOVED':
try:
port = ProcessUtilities.fetchCurrentPort()
removalLink = "https:\/\/" + ipAddress + ":%s\/emailMarketing\/remove\/" % (port) + self.extraArgs[
'listName'] + "\/" + items.email
messageText = emailMessage.emailMessage.encode('utf-8', 'replace')
message['To'] = items.email
if re.search(b'<html', messageText, re.IGNORECASE) and re.search(b'<body', messageText,
re.IGNORECASE):
finalMessage = messageText.decode()
self.extraArgs['unsubscribeCheck'] = 0
if self.extraArgs['unsubscribeCheck']:
messageFile = open(tempPath, 'w')
messageFile.write(finalMessage)
messageFile.close()
command = "sudo sed -i 's/{{ unsubscribeCheck }}/" + removalLink + "/g' " + tempPath
ProcessUtilities.executioner(command, 'cyberpanel')
messageFile = open(tempPath, 'r')
finalMessage = messageFile.read()
messageFile.close()
html = MIMEText(finalMessage, 'html')
message.attach(html)
else:
finalMessage = messageText
if self.extraArgs['unsubscribeCheck']:
finalMessage = finalMessage.replace('{{ unsubscribeCheck }}', removalLink)
html = MIMEText(finalMessage, 'plain')
message.attach(html)
try:
status = self.smtpServer.noop()[0]
self.smtpServer.sendmail(message['From'], items.email, message.as_string())
except: # smtplib.SMTPServerDisconnected
if self.setupSMTPConnection() == 0:
logging.CyberCPLogFileWriter.writeToFile('SMTP Connection failed. [301]')
return 0
self.smtpServer.sendmail(message['From'], items.email, message.as_string())
sent = sent + 1
emailJob.sent = sent
emailJob.save()
logging.CyberCPLogFileWriter.statusWriter(self.extraArgs['tempStatusPath'],
'Successfully sent: ' + str(
sent) + ' Failed: ' + str(
failed))
except BaseException as msg:
failed = failed + 1
emailJob.failed = failed
emailJob.save()
logging.CyberCPLogFileWriter.statusWriter(self.extraArgs['tempStatusPath'],
'Successfully sent: ' + str(
sent) + ', Failed: ' + str(failed))
if self.setupSMTPConnection() == 0:
logging.CyberCPLogFileWriter.writeToFile(
'SMTP Connection failed. Error: %s. [392]' % (str(msg)))
return 0
except BaseException as msg:
failed = failed + 1
emailJob.failed = failed
emailJob.save()
logging.CyberCPLogFileWriter.statusWriter(self.extraArgs['tempStatusPath'],
'Successfully sent: ' + str(
sent) + ', Failed: ' + str(failed))
if self.setupSMTPConnection() == 0:
logging.CyberCPLogFileWriter.writeToFile('SMTP Connection failed. Error: %s. [399]' % (str(msg)))
return 0
emailJob.sent = sent
emailJob.failed = failed
emailJob.save()
logging.CyberCPLogFileWriter.statusWriter(self.extraArgs['tempStatusPath'],
'Email job completed. [200]')
except BaseException as msg:
logging.CyberCPLogFileWriter.statusWriter(self.extraArgs['tempStatusPath'], str(msg) + '. [404]')
return 0

View File

@@ -1,899 +0,0 @@
from django.shortcuts import render, HttpResponse, redirect
from plogical.acl import ACLManager
from loginSystem.views import loadLoginPage
import json
from random import randint
import time
from plogical.httpProc import httpProc
from .models import EmailMarketing, EmailLists, EmailsInList, EmailJobs
from websiteFunctions.models import Websites
from .emailMarketing import emailMarketing as EM
from math import ceil
import smtplib
from .models import SMTPHosts, EmailTemplate
from loginSystem.models import Administrator
from .emACL import emACL
class EmailMarketingManager:
def __init__(self, request = None, domain = None):
self.request = request
self.domain = domain
def emailMarketing(self):
proc = httpProc(self.request, 'emailMarketing/emailMarketing.html', None, 'admin')
return proc.render()
def fetchUsers(self):
try:
userID = self.request.session['userID']
currentACL = ACLManager.loadedACL(userID)
if currentACL['admin'] == 1:
pass
else:
return ACLManager.loadError()
allUsers = ACLManager.findAllUsers()
disabledUsers = EmailMarketing.objects.all()
disabled = []
for items in disabledUsers:
disabled.append(items.userName)
json_data = "["
checker = 0
counter = 1
for items in allUsers:
if items in disabled:
status = 0
else:
status = 1
dic = {'id': counter, 'userName': items, 'status': status}
if checker == 0:
json_data = json_data + json.dumps(dic)
checker = 1
else:
json_data = json_data + ',' + json.dumps(dic)
counter = counter + 1
json_data = json_data + ']'
data_ret = {"status": 1, 'data': json_data}
json_data = json.dumps(data_ret)
return HttpResponse(json_data)
except BaseException as msg:
final_dic = {'status': 0, 'error_message': str(msg)}
final_json = json.dumps(final_dic)
return HttpResponse(final_json)
def enableDisableMarketing(self):
try:
userID = self.request.session['userID']
currentACL = ACLManager.loadedACL(userID)
if currentACL['admin'] == 1:
pass
else:
return ACLManager.loadErrorJson()
data = json.loads(self.request.body)
userName = data['userName']
try:
disableMarketing = EmailMarketing.objects.get(userName=userName)
disableMarketing.delete()
except:
enableMarketing = EmailMarketing(userName=userName)
enableMarketing.save()
data_ret = {"status": 1}
json_data = json.dumps(data_ret)
return HttpResponse(json_data)
except BaseException as msg:
final_dic = {'status': 0, 'error_message': str(msg)}
final_json = json.dumps(final_dic)
return HttpResponse(final_json)
def createEmailList(self):
try:
userID = self.request.session['userID']
currentACL = ACLManager.loadedACL(userID)
admin = Administrator.objects.get(pk=userID)
if ACLManager.checkOwnership(self.domain, admin, currentACL) == 1:
pass
else:
return ACLManager.loadError()
if emACL.checkIfEMEnabled(admin.userName) == 0:
return ACLManager.loadError()
proc = httpProc(self.request, 'emailMarketing/createEmailList.html', {'domain': self.domain})
return proc.render()
except KeyError as msg:
return redirect(loadLoginPage)
def submitEmailList(self):
try:
data = json.loads(self.request.body)
extraArgs = {}
extraArgs['domain'] = data['domain']
extraArgs['path'] = data['path']
extraArgs['listName'] = data['listName'].replace(' ', '')
extraArgs['tempStatusPath'] = "/home/cyberpanel/" + str(randint(1000, 9999))
userID = self.request.session['userID']
currentACL = ACLManager.loadedACL(userID)
admin = Administrator.objects.get(pk=userID)
if ACLManager.checkOwnership(data['domain'], admin, currentACL) == 1:
pass
else:
return ACLManager.loadErrorJson()
if emACL.checkIfEMEnabled(admin.userName) == 0:
return ACLManager.loadErrorJson()
# em = EM('createEmailList', extraArgs)
# em.start()
time.sleep(2)
data_ret = {"status": 1, 'tempStatusPath': extraArgs['tempStatusPath']}
json_data = json.dumps(data_ret)
return HttpResponse(json_data)
except BaseException as msg:
final_dic = {'status': 0, 'error_message': str(msg)}
final_json = json.dumps(final_dic)
return HttpResponse(final_json)
def manageLists(self):
try:
userID = self.request.session['userID']
currentACL = ACLManager.loadedACL(userID)
admin = Administrator.objects.get(pk=userID)
if ACLManager.checkOwnership(self.domain, admin, currentACL) == 1:
pass
else:
return ACLManager.loadError()
if emACL.checkIfEMEnabled(admin.userName) == 0:
return ACLManager.loadError()
listNames = emACL.getEmailsLists(self.domain)
proc = httpProc(self.request, 'emailMarketing/manageLists.html', {'listNames': listNames, 'domain': self.domain})
return proc.render()
except KeyError as msg:
return redirect(loadLoginPage)
def configureVerify(self):
try:
userID = self.request.session['userID']
currentACL = ACLManager.loadedACL(userID)
admin = Administrator.objects.get(pk=userID)
if ACLManager.checkOwnership(self.domain, admin, currentACL) == 1:
pass
else:
return ACLManager.loadError()
if emACL.checkIfEMEnabled(admin.userName) == 0:
return ACLManager.loadError()
proc = httpProc(self.request, 'emailMarketing/configureVerify.html',
{'domain': self.domain})
return proc.render()
except KeyError as msg:
return redirect(loadLoginPage)
def fetchVerifyLogs(self):
try:
userID = self.request.session['userID']
currentACL = ACLManager.loadedACL(userID)
admin = Administrator.objects.get(pk=userID)
data = json.loads(self.request.body)
self.listName = data['listName']
recordsToShow = int(data['recordsToShow'])
page = int(str(data['page']).strip('\n'))
emailList = EmailLists.objects.get(listName=self.listName)
if ACLManager.checkOwnership(emailList.owner.domain, admin, currentACL) == 1:
pass
else:
return ACLManager.loadErrorJson('status', 0)
logsLen = emailList.validationlog_set.all().count()
from s3Backups.s3Backups import S3Backups
pagination = S3Backups.getPagination(logsLen, recordsToShow)
endPageNumber, finalPageNumber = S3Backups.recordsPointer(page, recordsToShow)
finalLogs = emailList.validationlog_set.all()[finalPageNumber:endPageNumber]
json_data = "["
checker = 0
counter = 0
from plogical.backupSchedule import backupSchedule
for log in emailList.validationlog_set.all()[finalPageNumber:endPageNumber]:
if log.status == backupSchedule.INFO:
status = 'INFO'
else:
status = 'ERROR'
dic = {
'status': status, "message": log.message
}
if checker == 0:
json_data = json_data + json.dumps(dic)
checker = 1
else:
json_data = json_data + ',' + json.dumps(dic)
counter = counter + 1
json_data = json_data + ']'
totalEmail = emailList.emailsinlist_set.all().count()
verified = emailList.verified
notVerified = emailList.notVerified
data_ret = {'status': 1, 'logs': json_data, 'pagination': pagination, 'totalEmails': totalEmail, 'verified': verified, 'notVerified': notVerified}
json_data = json.dumps(data_ret)
return HttpResponse(json_data)
except BaseException as msg:
data_ret = {'status': 0, 'error_message': str(msg)}
json_data = json.dumps(data_ret)
return HttpResponse(json_data)
def saveConfigureVerify(self):
try:
userID = self.request.session['userID']
admin = Administrator.objects.get(pk=userID)
if emACL.checkIfEMEnabled(admin.userName) == 0:
return ACLManager.loadErrorJson()
data = json.loads(self.request.body)
domain = data['domain']
configureVerifyPath = '/home/cyberpanel/configureVerify'
import os
if not os.path.exists(configureVerifyPath):
os.mkdir(configureVerifyPath)
finalPath = '%s/%s' % (configureVerifyPath, domain)
writeToFile = open(finalPath, 'w')
writeToFile.write(self.request.body.decode())
writeToFile.close()
data_ret = {"status": 1}
json_data = json.dumps(data_ret)
return HttpResponse(json_data)
except BaseException as msg:
final_dic = {'status': 0, 'error_message': str(msg)}
final_json = json.dumps(final_dic)
return HttpResponse(final_json)
def fetchEmails(self):
try:
userID = self.request.session['userID']
admin = Administrator.objects.get(pk=userID)
if emACL.checkIfEMEnabled(admin.userName) == 0:
return ACLManager.loadErrorJson()
data = json.loads(self.request.body)
listName = data['listName']
recordstoShow = int(data['recordstoShow'])
page = int(data['page'])
finalPageNumber = ((page * recordstoShow)) - recordstoShow
endPageNumber = finalPageNumber + recordstoShow
emailList = EmailLists.objects.get(listName=listName)
currentACL = ACLManager.loadedACL(userID)
if currentACL['admin'] == 1:
pass
elif emailList.owner.id != userID:
return ACLManager.loadErrorJson()
emails = emailList.emailsinlist_set.all()
## Pagination value
pages = float(len(emails)) / float(recordstoShow)
pagination = []
counter = 1
if pages <= 1.0:
pages = 1
pagination.append(counter)
else:
pages = ceil(pages)
finalPages = int(pages) + 1
for i in range(1, finalPages):
pagination.append(counter)
counter = counter + 1
## Pagination value
emails = emails[finalPageNumber:endPageNumber]
json_data = "["
checker = 0
counter = 1
for items in emails:
dic = {'id': items.id, 'email': items.email, 'verificationStatus': items.verificationStatus,
'dateCreated': items.dateCreated}
if checker == 0:
json_data = json_data + json.dumps(dic)
checker = 1
else:
json_data = json_data + ',' + json.dumps(dic)
counter = counter + 1
json_data = json_data + ']'
data_ret = {"status": 1, 'data': json_data, 'pagination': pagination}
json_data = json.dumps(data_ret)
return HttpResponse(json_data)
except BaseException as msg:
final_dic = {'status': 0, 'error_message': str(msg)}
final_json = json.dumps(final_dic)
return HttpResponse(final_json)
def deleteList(self):
try:
userID = self.request.session['userID']
admin = Administrator.objects.get(pk=userID)
if emACL.checkIfEMEnabled(admin.userName) == 0:
return ACLManager.loadErrorJson()
data = json.loads(self.request.body)
listName = data['listName']
delList = EmailLists.objects.get(listName=listName)
currentACL = ACLManager.loadedACL(userID)
if currentACL['admin'] == 1:
pass
elif delList.owner.id != userID:
return ACLManager.loadErrorJson()
delList.delete()
data_ret = {"status": 1}
json_data = json.dumps(data_ret)
return HttpResponse(json_data)
except BaseException as msg:
final_dic = {'status': 0, 'error_message': str(msg)}
final_json = json.dumps(final_dic)
return HttpResponse(final_json)
def emailVerificationJob(self):
try:
userID = self.request.session['userID']
admin = Administrator.objects.get(pk=userID)
if emACL.checkIfEMEnabled(admin.userName) == 0:
return ACLManager.loadErrorJson()
data = json.loads(self.request.body)
extraArgs = {}
extraArgs['listName'] = data['listName']
delList = EmailLists.objects.get(listName=extraArgs['listName'])
currentACL = ACLManager.loadedACL(userID)
if currentACL['admin'] == 1:
pass
elif delList.owner.id != userID:
return ACLManager.loadErrorJson()
em = EM('verificationJob', extraArgs)
em.start()
time.sleep(2)
data_ret = {"status": 1}
json_data = json.dumps(data_ret)
return HttpResponse(json_data)
except BaseException as msg:
final_dic = {'status': 0, 'error_message': str(msg)}
final_json = json.dumps(final_dic)
return HttpResponse(final_json)
def deleteEmail(self):
try:
userID = self.request.session['userID']
admin = Administrator.objects.get(pk=userID)
if emACL.checkIfEMEnabled(admin.userName) == 0:
return ACLManager.loadErrorJson()
data = json.loads(self.request.body)
id = data['id']
delEmail = EmailsInList.objects.get(id=id)
currentACL = ACLManager.loadedACL(userID)
if currentACL['admin'] == 1:
pass
elif delEmail.owner.owner.id != userID:
return ACLManager.loadErrorJson()
delEmail.delete()
data_ret = {"status": 1}
json_data = json.dumps(data_ret)
return HttpResponse(json_data)
except BaseException as msg:
final_dic = {'status': 0, 'error_message': str(msg)}
final_json = json.dumps(final_dic)
return HttpResponse(final_json)
def manageSMTP(self):
try:
userID = self.request.session['userID']
currentACL = ACLManager.loadedACL(userID)
admin = Administrator.objects.get(pk=userID)
if ACLManager.checkOwnership(self.domain, admin, currentACL) == 1:
pass
else:
return ACLManager.loadError()
if emACL.checkIfEMEnabled(admin.userName) == 0:
return ACLManager.loadError()
website = Websites.objects.get(domain=self.domain)
emailLists = website.emaillists_set.all()
listNames = []
for items in emailLists:
listNames.append(items.listName)
proc = httpProc(self.request, 'emailMarketing/manageSMTPHosts.html',
{'listNames': listNames, 'domain': self.domain})
return proc.render()
except KeyError as msg:
return redirect(loadLoginPage)
def saveSMTPHost(self):
try:
userID = self.request.session['userID']
admin = Administrator.objects.get(pk=userID)
if emACL.checkIfEMEnabled(admin.userName) == 0:
return ACLManager.loadErrorJson()
data = json.loads(self.request.body)
smtpHost = data['smtpHost']
smtpPort = data['smtpPort']
smtpUserName = data['smtpUserName']
smtpPassword = data['smtpPassword']
if SMTPHosts.objects.count() == 0:
admin = Administrator.objects.get(userName='admin')
defaultHost = SMTPHosts(owner=admin, host='localhost', port=25, userName='None', password='None')
defaultHost.save()
try:
verifyLogin = smtplib.SMTP(str(smtpHost), int(smtpPort))
if int(smtpPort) == 587:
verifyLogin.starttls()
verifyLogin.login(str(smtpUserName), str(smtpPassword))
admin = Administrator.objects.get(pk=userID)
newHost = SMTPHosts(owner=admin, host=smtpHost, port=smtpPort, userName=smtpUserName,
password=smtpPassword)
newHost.save()
except smtplib.SMTPHeloError:
data_ret = {"status": 0, 'error_message': 'The server did not reply properly to the HELO greeting.'}
json_data = json.dumps(data_ret)
return HttpResponse(json_data)
except smtplib.SMTPAuthenticationError:
data_ret = {"status": 0, 'error_message': 'Username and password combination not accepted.'}
json_data = json.dumps(data_ret)
return HttpResponse(json_data)
except smtplib.SMTPException:
data_ret = {"status": 0, 'error_message': 'No suitable authentication method was found.'}
json_data = json.dumps(data_ret)
return HttpResponse(json_data)
data_ret = {"status": 1}
json_data = json.dumps(data_ret)
return HttpResponse(json_data)
except BaseException as msg:
final_dic = {'status': 0, 'error_message': str(msg)}
final_json = json.dumps(final_dic)
return HttpResponse(final_json)
def fetchSMTPHosts(self):
try:
userID = self.request.session['userID']
admin = Administrator.objects.get(pk=userID)
if emACL.checkIfEMEnabled(admin.userName) == 0:
return ACLManager.loadErrorJson()
currentACL = ACLManager.loadedACL(userID)
if currentACL['admin'] == 1:
allHosts = SMTPHosts.objects.all()
else:
admin = Administrator.objects.get(pk=userID)
allHosts = admin.smtphosts_set.all()
json_data = "["
checker = 0
counter = 1
for items in allHosts:
dic = {'id': items.id, 'owner': items.owner.userName, 'host': items.host, 'port': items.port,
'userName': items.userName}
if checker == 0:
json_data = json_data + json.dumps(dic)
checker = 1
else:
json_data = json_data + ',' + json.dumps(dic)
counter = counter + 1
json_data = json_data + ']'
data_ret = {"status": 1, 'data': json_data}
json_data = json.dumps(data_ret)
return HttpResponse(json_data)
except BaseException as msg:
final_dic = {'status': 0, 'error_message': str(msg)}
final_json = json.dumps(final_dic)
return HttpResponse(final_json)
def smtpHostOperations(self):
try:
userID = self.request.session['userID']
admin = Administrator.objects.get(pk=userID)
currentACL = ACLManager.loadedACL(userID)
if emACL.checkIfEMEnabled(admin.userName) == 0:
return ACLManager.loadErrorJson()
data = json.loads(self.request.body)
id = data['id']
operation = data['operation']
if operation == 'delete':
delHost = SMTPHosts.objects.get(id=id)
if ACLManager.VerifySMTPHost(currentACL, delHost.owner, admin) == 0:
return ACLManager.loadErrorJson()
currentACL = ACLManager.loadedACL(userID)
if currentACL['admin'] == 1:
pass
elif delHost.owner.id != userID:
return ACLManager.loadErrorJson()
delHost.delete()
data_ret = {"status": 1, 'message': 'Successfully deleted.'}
json_data = json.dumps(data_ret)
return HttpResponse(json_data)
else:
try:
verifyHost = SMTPHosts.objects.get(id=id)
if ACLManager.VerifySMTPHost(currentACL, verifyHost.owner, admin) == 0:
return ACLManager.loadErrorJson()
verifyLogin = smtplib.SMTP(str(verifyHost.host), int(verifyHost.port))
if int(verifyHost.port) == 587:
verifyLogin.starttls()
verifyLogin.login(str(verifyHost.userName), str(verifyHost.password))
data_ret = {"status": 1, 'message': 'Login successful.'}
json_data = json.dumps(data_ret)
return HttpResponse(json_data)
except smtplib.SMTPHeloError:
data_ret = {"status": 0, 'error_message': 'The server did not reply properly to the HELO greeting.'}
json_data = json.dumps(data_ret)
return HttpResponse(json_data)
except smtplib.SMTPAuthenticationError:
data_ret = {"status": 0, 'error_message': 'Username and password combination not accepted.'}
json_data = json.dumps(data_ret)
return HttpResponse(json_data)
except smtplib.SMTPException:
data_ret = {"status": 0, 'error_message': 'No suitable authentication method was found.'}
json_data = json.dumps(data_ret)
return HttpResponse(json_data)
except BaseException as msg:
final_dic = {'status': 0, 'error_message': str(msg)}
final_json = json.dumps(final_dic)
return HttpResponse(final_json)
def composeEmailMessage(self):
try:
userID = self.request.session['userID']
admin = Administrator.objects.get(pk=userID)
if emACL.checkIfEMEnabled(admin.userName) == 0:
return ACLManager.loadErrorJson()
proc = httpProc(self.request, 'emailMarketing/composeMessages.html',
None)
return proc.render()
except KeyError as msg:
return redirect(loadLoginPage)
def saveEmailTemplate(self):
try:
userID = self.request.session['userID']
admin = Administrator.objects.get(pk=userID)
if emACL.checkIfEMEnabled(admin.userName) == 0:
return ACLManager.loadErrorJson()
data = json.loads(self.request.body)
name = data['name']
subject = data['subject']
fromName = data['fromName']
fromEmail = data['fromEmail']
replyTo = data['replyTo']
emailMessage = data['emailMessage']
if ACLManager.CheckRegEx('[\w\d\s]+$', name) == 0:
return ACLManager.loadErrorJson()
admin = Administrator.objects.get(pk=userID)
newTemplate = EmailTemplate(owner=admin, name=name.replace(' ', ''), subject=subject, fromName=fromName, fromEmail=fromEmail,
replyTo=replyTo, emailMessage=emailMessage)
newTemplate.save()
data_ret = {"status": 1}
json_data = json.dumps(data_ret)
return HttpResponse(json_data)
except BaseException as msg:
final_dic = {'status': 0, 'error_message': str(msg)}
final_json = json.dumps(final_dic)
return HttpResponse(final_json)
def sendEmails(self):
try:
userID = self.request.session['userID']
admin = Administrator.objects.get(pk=userID)
if emACL.checkIfEMEnabled(admin.userName) == 0:
return ACLManager.loadErrorJson()
currentACL = ACLManager.loadedACL(userID)
templateNames = emACL.allTemplates(currentACL, admin)
hostNames = emACL.allSMTPHosts(currentACL, admin)
listNames = emACL.allEmailsLists(currentACL, admin)
Data = {}
Data['templateNames'] = templateNames
Data['hostNames'] = hostNames
Data['listNames'] = listNames
proc = httpProc(self.request, 'emailMarketing/sendEmails.html',
Data)
return proc.render()
except KeyError as msg:
return redirect(loadLoginPage)
def templatePreview(self):
try:
userID = self.request.session['userID']
admin = Administrator.objects.get(pk=userID)
template = EmailTemplate.objects.get(name=self.domain)
currentACL = ACLManager.loadedACL(userID)
if currentACL['admin'] == 1:
pass
elif template.owner != admin:
return ACLManager.loadError()
return HttpResponse(template.emailMessage)
except KeyError as msg:
return redirect(loadLoginPage)
def fetchJobs(self):
try:
userID = self.request.session['userID']
admin = Administrator.objects.get(pk=userID)
if emACL.checkIfEMEnabled(admin.userName) == 0:
return ACLManager.loadErrorJson()
data = json.loads(self.request.body)
selectedTemplate = data['selectedTemplate']
template = EmailTemplate.objects.get(name=selectedTemplate)
currentACL = ACLManager.loadedACL(userID)
if currentACL['admin'] == 1:
pass
elif template.owner != admin:
return ACLManager.loadErrorJson()
allJobs = EmailJobs.objects.filter(owner=template)
json_data = "["
checker = 0
counter = 1
for items in allJobs:
dic = {'id': items.id,
'date': items.date,
'host': items.host,
'totalEmails': items.totalEmails,
'sent': items.sent,
'failed': items.failed}
if checker == 0:
json_data = json_data + json.dumps(dic)
checker = 1
else:
json_data = json_data + ',' + json.dumps(dic)
counter = counter + 1
json_data = json_data + ']'
data_ret = {"status": 1, 'data': json_data}
json_data = json.dumps(data_ret)
return HttpResponse(json_data)
except BaseException as msg:
final_dic = {'status': 0, 'error_message': str(msg)}
final_json = json.dumps(final_dic)
return HttpResponse(final_json)
def startEmailJob(self):
try:
userID = self.request.session['userID']
admin = Administrator.objects.get(pk=userID)
data = json.loads(self.request.body)
extraArgs = {}
extraArgs['selectedTemplate'] = data['selectedTemplate']
extraArgs['listName'] = data['listName']
extraArgs['host'] = data['host']
try:
extraArgs['verificationCheck'] = data['verificationCheck']
except:
extraArgs['verificationCheck'] = False
try:
extraArgs['unsubscribeCheck'] = data['unsubscribeCheck']
except:
extraArgs['unsubscribeCheck'] = False
extraArgs['tempStatusPath'] = "/home/cyberpanel/" + data['selectedTemplate'] + '_pendingJob'
currentACL = ACLManager.loadedACL(userID)
template = EmailTemplate.objects.get(name=extraArgs['selectedTemplate'])
if currentACL['admin'] == 1:
pass
elif template.owner != admin:
return ACLManager.loadErrorJson()
em = EM('startEmailJob', extraArgs)
em.start()
time.sleep(5)
data_ret = {"status": 1, 'tempStatusPath': extraArgs['tempStatusPath']}
json_data = json.dumps(data_ret)
return HttpResponse(json_data)
except BaseException as msg:
final_dic = {'status': 0, 'error_message': str(msg)}
final_json = json.dumps(final_dic)
return HttpResponse(final_json)
def deleteTemplate(self):
try:
userID = self.request.session['userID']
admin = Administrator.objects.get(pk=userID)
data = json.loads(self.request.body)
selectedTemplate = data['selectedTemplate']
delTemplate = EmailTemplate.objects.get(name=selectedTemplate)
currentACL = ACLManager.loadedACL(userID)
if currentACL['admin'] == 1:
pass
elif delTemplate.owner != admin:
return ACLManager.loadErrorJson()
delTemplate.delete()
data_ret = {"status": 1}
json_data = json.dumps(data_ret)
return HttpResponse(json_data)
except BaseException as msg:
final_dic = {'status': 0, 'error_message': str(msg)}
final_json = json.dumps(final_dic)
return HttpResponse(final_json)
def deleteJob(self):
try:
userID = self.request.session['userID']
admin = Administrator.objects.get(pk=userID)
if emACL.checkIfEMEnabled(admin.userName) == 0:
return ACLManager.loadErrorJson()
data = json.loads(self.request.body)
id = data['id']
delJob = EmailJobs(id=id)
delJob.delete()
data_ret = {"status": 1}
json_data = json.dumps(data_ret)
return HttpResponse(json_data)
except BaseException as msg:
final_dic = {'status': 0, 'error_message': str(msg)}
final_json = json.dumps(final_dic)
return HttpResponse(final_json)
def remove(self, listName, emailAddress):
try:
eList = EmailLists.objects.get(listName=listName)
removeEmail = EmailsInList.objects.get(owner=eList, email=emailAddress)
removeEmail.verificationStatus = 'REMOVED'
removeEmail.save()
except:
pass
return HttpResponse('Email Address Successfully removed from the list.')

View File

@@ -1,7 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<cyberpanelPluginConfig>
<name>Email Marketing</name>
<type>Utility</type>
<description>Email Marketing plugin for CyberPanel.</description>
<version>1.0.1</version>
</cyberpanelPluginConfig>

View File

@@ -1,56 +0,0 @@
# -*- coding: utf-8 -*-
from django.db import models
from websiteFunctions.models import Websites
from loginSystem.models import Administrator
# Create your models here.
class EmailMarketing(models.Model):
userName = models.CharField(max_length=50, unique=True)
class EmailLists(models.Model):
owner = models.ForeignKey(Websites, on_delete=models.PROTECT)
listName = models.CharField(max_length=50, unique=True)
dateCreated = models.CharField(max_length=200)
verified = models.IntegerField(default=0)
notVerified = models.IntegerField(default=0)
class EmailsInList(models.Model):
owner = models.ForeignKey(EmailLists, on_delete=models.CASCADE)
email = models.CharField(max_length=50)
firstName = models.CharField(max_length=20, default='')
lastName = models.CharField(max_length=20, default='')
verificationStatus = models.CharField(max_length=100)
dateCreated = models.CharField(max_length=200)
class SMTPHosts(models.Model):
owner = models.ForeignKey(Administrator, on_delete=models.CASCADE)
host = models.CharField(max_length=150, unique= True)
port = models.CharField(max_length=10)
userName = models.CharField(max_length=200)
password = models.CharField(max_length=200)
class EmailTemplate(models.Model):
owner = models.ForeignKey(Administrator, on_delete=models.CASCADE)
name = models.CharField(unique=True, max_length=100)
subject = models.CharField(max_length=1000)
fromName = models.CharField(max_length=100)
fromEmail = models.CharField(max_length=150)
replyTo = models.CharField(max_length=150)
emailMessage = models.TextField(max_length=65532)
class EmailJobs(models.Model):
owner = models.ForeignKey(EmailTemplate, on_delete=models.CASCADE)
date = models.CharField(max_length=200)
host = models.CharField(max_length=1000)
totalEmails = models.IntegerField()
sent = models.IntegerField()
failed = models.IntegerField()
class ValidationLog(models.Model):
owner = models.ForeignKey(EmailLists, on_delete=models.CASCADE)
status = models.IntegerField()
message = models.TextField()

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

File diff suppressed because it is too large Load Diff

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

View File

@@ -1,90 +0,0 @@
{% extends "baseTemplate/index.html" %}
{% load i18n %}
{% block title %}{% trans "Compose Email Message - CyberPanel" %}{% endblock %}
{% block content %}
{% load static %}
{% get_current_language as LANGUAGE_CODE %}
<!-- Current language: {{ LANGUAGE_CODE }} -->
<div class="container">
<div id="page-title">
<h2>{% trans "Compose Email Message" %}</h2>
<p>{% trans "On this page you can compose email message to be sent out later." %}</p>
</div>
<div ng-controller="composeMessageCTRL" class="panel" style="background: var(--bg-primary, white); border-color: var(--border-color, #ddd);">
<div class="panel-body" style="background: var(--bg-primary, white); color: var(--text-primary, #333);">
<h3 class="title-hero">
{% trans "Compose Email Message" %} <img ng-hide="cyberPanelLoading" src="{% static 'images/loading.gif' %}">
</h3>
<div class="example-box-wrapper">
<form action="/" class="form-horizontal bordered-row">
<!---- Create Email Template --->
<div ng-hide="installationDetailsForm" class="form-group">
<label class="col-sm-3 control-label">{% trans "Template Name" %}</label>
<div class="col-sm-6">
<input type="text" class="form-control" ng-model="name" required>
</div>
</div>
<div ng-hide="installationDetailsForm" class="form-group">
<label class="col-sm-3 control-label">{% trans "Email Subject" %}</label>
<div class="col-sm-6">
<input type="text" class="form-control" ng-model="subject" required>
</div>
</div>
<div ng-hide="installationDetailsForm" class="form-group">
<label class="col-sm-3 control-label">{% trans "From Name" %}</label>
<div class="col-sm-6">
<input type="text" class="form-control" ng-model="fromName" required>
</div>
</div>
<div ng-hide="installationDetailsForm" class="form-group">
<label class="col-sm-3 control-label">{% trans "From Email" %}</label>
<div class="col-sm-6">
<input type="text" class="form-control" ng-model="fromEmail" required>
</div>
</div>
<div ng-hide="installationDetailsForm" class="form-group">
<label class="col-sm-3 control-label">{% trans "Reply Email" %}</label>
<div class="col-sm-6">
<input type="text" class="form-control" ng-model="replyTo" required>
</div>
</div>
<div ng-hide="request" class="form-group">
<div class="col-sm-12">
<textarea placeholder="Paste your email message, any format is accepted. (HTML or Plain)" ng-model="emailMessage" rows="15" class="form-control"></textarea>
</div>
</div>
<div ng-hide="installationProgress" class="form-group">
<label class="col-sm-3 control-label"></label>
<div class="col-sm-4">
<button type="button" ng-click="saveTemplate()" class="btn btn-primary btn-lg btn-block">{% trans "Save Template" %}</button>
</div>
</div>
<!---- Create Email Template --->
</form>
</div>
</div>
</div>
</div>
{% endblock %}

View File

@@ -1,99 +0,0 @@
{% extends "baseTemplate/index.html" %}
{% load i18n %}
{% block title %}{% trans "Configure Email Verification - CyberPanel" %}{% endblock %}
{% block content %}
{% load static %}
{% get_current_language as LANGUAGE_CODE %}
<!-- Current language: {{ LANGUAGE_CODE }} -->
<div class="container">
<div id="page-title">
<h2>{% trans "Configure Email Verification" %}</h2>
<p>{% trans "On this page you can configure parameters regarding how email verification is performed for " %}<span id="domainName">{{ domain }}</span></p>
</div>
<div ng-controller="configureVerify" class="panel" style="background: var(--bg-primary, white); border-color: var(--border-color, #ddd);">
<div class="panel-body" style="background: var(--bg-primary, white); color: var(--text-primary, #333);">
<h3 class="title-hero">
{% trans "Compose Email Message" %} <img ng-hide="cyberPanelLoading"
src="{% static 'images/loading.gif' %}">
</h3>
<div class="example-box-wrapper">
<form action="/" class="form-horizontal bordered-row">
<!---- Create Email Template --->
<div class="form-group">
<label class="col-sm-3 control-label">{% trans "Configure Delay" %} </label>
<div class="col-sm-6">
<select ng-change="delayInitial()" ng-model="delay" class="form-control">
<option>Disable</option>
<option>Enable</option>
</select>
</div>
</div>
<div ng-hide="delayHidden" class="form-group">
<label class="col-sm-3 control-label">{% trans "Delay After" %}</label>
<div class="col-sm-6">
<input placeholder="{% trans 'Start delay after this many verifications are done.' %}" type="number" class="form-control" ng-model="delayAfter" required>
</div>
</div>
<div ng-hide="delayHidden" class="form-group">
<label class="col-sm-3 control-label">{% trans "Delay Time" %}</label>
<div class="col-sm-6">
<input placeholder="{% trans 'Set the number of seconds to wait.' %}" type="number" class="form-control" ng-model="delayTime" required>
</div>
</div>
<div class="form-group">
<label class="col-sm-3 control-label">{% trans "IP Rotation" %} </label>
<div class="col-sm-6">
<select ng-change="rotateInitial()" ng-model="rotation" class="form-control">
<option>Disable</option>
<option>IPv4</option>
<option>IPv6</option>
</select>
</div>
</div>
<div ng-hide="ipv4Hidden" class="form-group">
<label class="col-sm-3 control-label">{% trans "IPv4" %}</label>
<div class="col-sm-6">
<input placeholder="{% trans 'Enter IPv4(s) to be used separate with commas.' %}" type="text" class="form-control" ng-model="ipv4" required>
</div>
</div>
<div ng-hide="ipv6Hidden" class="form-group">
<label class="col-sm-3 control-label">{% trans "IPv6" %}</label>
<div class="col-sm-6">
<input placeholder="{% trans 'Enter IPv6(s) to be used separate with commas.' %}" type="text" class="form-control" ng-model="ipv6" required>
</div>
</div>
<div ng-hide="installationProgress" class="form-group">
<label class="col-sm-3 control-label"></label>
<div class="col-sm-4">
<button type="button" ng-click="saveChanges()"
class="btn btn-primary btn-lg btn-block">{% trans "Save" %}</button>
</div>
</div>
<!---- Create Email Template --->
</form>
</div>
</div>
</div>
</div>
{% endblock %}

View File

@@ -1,75 +0,0 @@
{% extends "baseTemplate/index.html" %}
{% load i18n %}
{% block title %}{% trans "Create Email List - CyberPanel" %}{% endblock %}
{% block content %}
{% load static %}
{% get_current_language as LANGUAGE_CODE %}
<!-- Current language: {{ LANGUAGE_CODE }} -->
<div ng-controller="createEmailList" class="container">
<div id="page-title">
<h2>{% trans "Create Email List" %} - <span id="domainNamePage">{{ domain }}</span> </h2>
<p>{% trans "Create email list, to send out news letters and marketing emails." %}</p>
</div>
<div class="panel" style="background: var(--bg-primary, white); border-color: var(--border-color, #ddd);">
<div class="panel-body" style="background: var(--bg-primary, white); color: var(--text-primary, #333);">
<h3 class="title-hero">
{% trans "Create Email List" %} <img ng-hide="cyberPanelLoading" src="{% static 'images/loading.gif' %}">
</h3>
<div class="example-box-wrapper">
<form action="/" id="createPackages" class="form-horizontal bordered-row">
<div ng-hide="installationDetailsForm" class="form-group">
<label class="col-sm-3 control-label">{% trans "List Name" %}</label>
<div class="col-sm-6">
<input name="pname" type="text" class="form-control" ng-model="listName" required>
</div>
</div>
<div ng-hide="installationDetailsForm" class="form-group">
<label class="col-sm-3 control-label">{% trans "Path" %}</label>
<div class="col-sm-6">
<input placeholder="Path to emails file (.txt and .csv accepted)" type="text" class="form-control" ng-model="path" required>
</div>
</div>
<div ng-hide="installationDetailsForm" class="form-group">
<label class="col-sm-3 control-label"></label>
<div class="col-sm-4">
<button type="button" ng-click="createEmailList()" class="btn btn-primary btn-lg btn-block">{% trans "Create List" %}</button>
</div>
</div>
<div ng-hide="installationProgress" class="form-group">
<label class="col-sm-2 control-label"></label>
<div class="col-sm-7">
<div class="alert alert-success text-center" style="background: var(--bg-secondary, #f8f9ff); color: var(--text-primary, #333); border-color: var(--border-color, #ddd);">
<h2>{$ currentStatus $}</h2>
</div>
</div>
</div>
<div ng-hide="installationProgress" class="form-group">
<label class="col-sm-3 control-label"></label>
<div class="col-sm-4">
<button type="button" ng-disabled="goBackDisable" ng-click="goBack()" class="btn btn-primary btn-lg btn-block">{% trans "Go Back" %}</button>
</div>
</div>
</form>
</div>
</div>
</div>
</div>
{% endblock %}

View File

@@ -1,72 +0,0 @@
{% extends "baseTemplate/index.html" %}
{% load i18n %}
{% block title %}{% trans "Email Marketing - CyberPanel" %}{% endblock %}
{% block content %}
{% load static %}
{% get_current_language as LANGUAGE_CODE %}
<!-- Current language: {{ LANGUAGE_CODE }} -->
<div class="container">
<div id="page-title">
<h2 id="domainNamePage">{% trans "Email Marketing" %}</h2>
<p>{% trans "Select users to Enable/Disable Email Marketing feature!" %}</p>
</div>
<div ng-controller="emailMarketing" class="panel" style="background: var(--bg-primary, white); border-color: var(--border-color, #ddd);">
<div class="panel-body" style="background: var(--bg-primary, white); color: var(--text-primary, #333);">
<h3 class="content-box-header">
{% trans "Email Marketing" %} <img ng-hide="cyberPanelLoading" src="{% static 'images/loading.gif' %}">
</h3>
{% if installCheck == 0 %}
<div class="row">
<div class="col-md-12 text-center" style="margin-bottom: 2%;">
<h3>{% trans "Email Policy Server is not enabled " %}
<a href="{% url 'emailPolicyServer' %}">
<button class="btn btn-alt btn-hover btn-blue-alt">
<span>{% trans "Enable Now." %}</span>
<i class="glyph-icon icon-arrow-right"></i>
</button></a></h3>
</div>
</div>
{% else %}
<div class="example-box-wrapper">
<table cellpadding="0" cellspacing="0" border="0" class="table table-striped" id="datatable-example" style="background: var(--bg-primary, white); color: var(--text-primary, #333); border-color: var(--border-color, #ddd);">
<thead>
<tr>
<th>{% trans 'ID' %}</th>
<th>{% trans 'Username' %}</th>
<th>{% trans 'Status' %}</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="user in users track by $index">
<td ><code ng-bind="user.id"></code></td>
<td><code ng-bind="user.userName"></code></td>
<td>
<img style="margin-right: 4%;" ng-show="user.status==1" title="{% trans 'Email Marketing Enabled.' %}" src="{% static 'mailServer/vpsON.png' %}">
<button ng-click="enableDisableMarketing(0, user.userName)" ng-show="user.status==1" class="btn ra-100 btn-danger">{% trans 'Disable' %}</button>
<img style="margin-right: 4%;" ng-show="user.status==0" title="{% trans 'Email Marketing Disabled.' %}" src="{% static 'mailServer/vpsOff.png' %}">
<button ng-click="enableDisableMarketing(1, user.userName)" ng-show="user.status==0" class="btn ra-100 btn-success">{% trans 'Enable' %}</button>
</td>
</tr>
</tbody>
</table>
</div>
{% endif %}
</div>
</div>
{% endblock %}

View File

@@ -1,315 +0,0 @@
{% extends "baseTemplate/index.html" %}
{% load i18n %}
{% block title %}{% trans "Manage Email Lists - CyberPanel" %}{% endblock %}
{% block content %}
{% load static %}
{% get_current_language as LANGUAGE_CODE %}
<!-- Current language: {{ LANGUAGE_CODE }} -->
<div class="container">
<div id="page-title">
<h2>{% trans "Manage Email Lists" %} - <span id="domainNamePage">{{ domain }}</span></h2>
<p>{% trans "On this page you can manage your email lists (Delete, Verify, Add More Emails)." %}</p>
</div>
<div ng-controller="manageEmailLists" class="panel" style="background: var(--bg-primary, white); border-color: var(--border-color, #ddd);">
<div class="panel-body" style="background: var(--bg-primary, white); color: var(--text-primary, #333);">
<h3 class="title-hero">
{% trans "Manage Email Lists" %} <img ng-hide="cyberPanelLoading"
src="{% static 'images/loading.gif' %}">
</h3>
<div class="example-box-wrapper">
<form action="/" class="form-horizontal bordered-row">
<div class="form-group">
<label class="col-sm-3 control-label">{% trans "Select List" %} </label>
<div class="col-sm-6">
<select ng-change="fetchEmails(1)" ng-model="listName" class="form-control">
{% for items in listNames %}
<option>{{ items }}</option>
{% endfor %}
</select>
</div>
</div>
<div ng-hide="currentRecords" class="form-group">
<div class="row">
<div class="col-sm-1">
<button data-toggle="modal" data-target="#deleteList"
class="btn ra-100 btn-danger">{% trans 'Delete' %}</button>
<!--- Delete Pool --->
<div class="modal fade" id="deleteList" tabindex="-1" role="dialog"
aria-labelledby="myModalLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content" style="background: var(--bg-primary, white); color: var(--text-primary, #333); border-color: var(--border-color, #ddd);">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal"
aria-hidden="true">&times;
</button>
<h4 class="modal-title">{% trans "You are doing to delete this list.." %}
<img ng-hide="cyberPanelLoading"
src="{% static 'images/loading.gif' %}"></h4>
</div>
<div class="modal-body">
<p>{% trans 'Are you sure?' %}</p>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default"
data-dismiss="modal">{% trans 'Close' %}</button>
<button data-dismiss="modal" ng-click="deleteList()" type="button"
class="btn btn-primary">{% trans 'Confirm' %}</button>
</div>
</div>
</div>
</div>
<!--- Delete Pool --->
</div>
<div class="col-sm-1">
<button ng-disabled="verificationButton" ng-click="startVerification()"
class="btn ra-100 btn-blue-alt">{% trans 'Verify' %}</button>
</div>
<div class="col-sm-3">
<button onclick="location.href='/emailMarketing/{{ domain }}/configureVerify'"
class="btn ra-100 btn-blue-alt">{% trans 'Configure Verification' %}</button>
</div>
<div class="col-sm-3">
<button ng-click="fetchLogs()" data-toggle="modal" data-target="#verificationLogs"
class="btn ra-100 btn-blue-alt">{% trans 'Verfications Logs' %}</button>
<!--- Delete Pool --->
<div class="modal fade" id="verificationLogs" tabindex="-1" role="dialog"
aria-labelledby="myModalLabel" aria-hidden="true">
<div class="modal-dialog modal-lg">
<div class="modal-content" style="background: var(--bg-primary, white); color: var(--text-primary, #333); border-color: var(--border-color, #ddd);">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal"
aria-hidden="true">&times;
</button>
<h4 class="modal-title">{% trans "Verification Logs" %}
<img ng-hide="cyberPanelLoading"
src="{% static 'images/loading.gif' %}"></h4>
</div>
<div class="modal-body">
<!------ List of records --------------->
<div ng-hide="currentRecords" class="form-group">
<table style="margin: 0px; padding-bottom: 2%; background: var(--bg-primary, white); color: var(--text-primary, #333); border-color: var(--border-color, #ddd);" class="table">
<thead>
<tr>
<th>{% trans "Total Emails" %}</th>
<th>{% trans "Verified" %}</th>
<th>{% trans "Not-Verified" %}</th>
</tr>
</thead>
<tbody>
<tr>
<td>{$ totalEmails $}</td>
<td>{$ verified $}</td>
<td>{$ notVerified $}</td>
</tr>
</tbody>
</table>
<div class="col-sm-10">
<input placeholder="Search Logs..." name="dom" type="text"
class="form-control" ng-model="searchLogs"
required>
</div>
<div style="margin-bottom: 1%;" class="col-sm-2">
<select ng-change="fetchLogs()" ng-model="recordsToShowLogs"
class="form-control">
<option>10</option>
<option>50</option>
<option>100</option>
<option>500</option>
</select>
</div>
<div class="col-sm-12">
<table style="margin: 0px; background: var(--bg-primary, white); color: var(--text-primary, #333); border-color: var(--border-color, #ddd);" class="table">
<thead>
<tr>
<th>{% trans "Status" %}</th>
<th>{% trans "Message" %}</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="record in recordsLogs | filter:searchLogs">
<td ng-bind="record.status"></td>
<td ng-bind="record.message"></td>
</tr>
</tbody>
</table>
<div style="margin-top: 2%" class="row">
<div class="col-md-12">
<div class="row">
<div class="col-md-8">
</div>
<div class="col-md-3">
<div class="form-group">
<select ng-model="currentPageLogs"
class="form-control"
ng-change="fetchLogs()">
<option ng-repeat="page in paginationLogs">
{$ $index + 1
$}
</option>
</select>
</div>
</div>
</div> <!-- end row -->
</div>
</div>
</div>
</div>
<!------ List of records --------------->
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default"
data-dismiss="modal">{% trans 'Close' %}</button>
</div>
</div>
</div>
</div>
<!--- Delete Pool --->
</div>
<div class="col-sm-2">
<button ng-click="showAddEmails()"
class="btn ra-100 btn-blue-alt">{% trans 'Add More Emails' %}</button>
</div>
</div>
</div>
<!---- Create Email List --->
<div ng-hide="installationDetailsForm" class="form-group">
<label class="col-sm-3 control-label">{% trans "Path" %}</label>
<div class="col-sm-6">
<input placeholder="Path to emails file (.txt and .csv accepted)" type="text"
class="form-control" ng-model="path" required>
</div>
</div>
<div ng-hide="installationDetailsForm" class="form-group">
<label class="col-sm-3 control-label"></label>
<div class="col-sm-4">
<button type="button" ng-click="createEmailList()"
class="btn btn-primary btn-lg btn-block">{% trans "Load Emails" %}</button>
</div>
</div>
<div ng-hide="installationProgress" class="form-group">
<label class="col-sm-2 control-label"></label>
<div class="col-sm-7">
<div class="alert alert-success text-center" style="background: var(--bg-secondary, #f8f9ff); color: var(--text-primary, #333); border-color: var(--border-color, #ddd);">
<h2>{$ currentStatus $}</h2>
</div>
</div>
</div>
<div ng-hide="installationProgress" class="form-group">
<label class="col-sm-3 control-label"></label>
<div class="col-sm-4">
<button type="button" ng-disabled="goBackDisable" ng-click="goBack()"
class="btn btn-primary btn-lg btn-block">{% trans "Go Back" %}</button>
</div>
</div>
<!---- Create Email List --->
<!---- Email List Verification --->
<div ng-hide="verificationStatus" class="form-group">
<label class="col-sm-2 control-label"></label>
<div class="col-sm-7">
<div class="alert alert-success text-center" style="background: var(--bg-secondary, #f8f9ff); color: var(--text-primary, #333); border-color: var(--border-color, #ddd);">
<h2>{$ currentStatusVerification $}</h2>
</div>
</div>
</div>
<!---- Create Email List --->
<!------ List of records --------------->
<div ng-hide="currentRecords" class="form-group">
<div class="col-sm-10">
<input placeholder="Search Emails..." ng-model="searchEmails" name="dom" type="text"
class="form-control" ng-model="domainNameCreate" required>
</div>
<div style="margin-bottom: 1%;" class="col-sm-2">
<select ng-change="fetchRecords()" ng-model="recordstoShow" class="form-control">
<option>10</option>
<option>50</option>
<option>100</option>
</select>
</div>
<div class="col-sm-12">
<table class="table" style="background: var(--bg-primary, white); color: var(--text-primary, #333); border-color: var(--border-color, #ddd);">
<thead>
<tr>
<th>{% trans "ID" %}</th>
<th>{% trans "email" %}</th>
<th>{% trans "Verification Status" %}</th>
<th>{% trans "Date Created" %}</th>
<th>{% trans "Actions" %}</th>
<th></th>
</tr>
</thead>
<tbody>
<tr ng-repeat="record in records | filter:searchEmails">
<td ng-bind="record.id"></td>
<td ng-bind="record.email"></td>
<td ng-bind="record.verificationStatus"></td>
<td ng-bind="record.dateCreated"></td>
<td>
<button type="button" ng-click="deleteEmail(record.id)"
class="btn ra-100 btn-purple">{% trans "Delete" %}</button>
</td>
</tr>
</tbody>
</table>
<div class="row">
<div class="col-sm-4 col-sm-offset-8">
<nav aria-label="Page navigation">
<ul class="pagination">
<li ng-click="fetchEmails(page)" ng-repeat="page in pagination"><a
href="">{$ page $}</a></li>
</ul>
</nav>
</div>
</div>
</div>
</div>
<!------ List of records --------------->
</form>
</div>
</div>
</div>
</div>
{% endblock %}

View File

@@ -1,126 +0,0 @@
{% extends "baseTemplate/index.html" %}
{% load i18n %}
{% block title %}{% trans "Manage SMTP Hosts - CyberPanel" %}{% endblock %}
{% block content %}
{% load static %}
{% get_current_language as LANGUAGE_CODE %}
<!-- Current language: {{ LANGUAGE_CODE }} -->
<div class="container">
<div id="page-title">
<h2>{% trans "Manage SMTP Hosts" %}</h2>
<p>{% trans "On this page you can manage STMP Host. (SMTP hosts are used to send emails)" %}</p>
</div>
<div ng-controller="manageSMTPHostsCTRL" class="panel" style="background: var(--bg-primary, white); border-color: var(--border-color, #ddd);">
<div class="panel-body" style="background: var(--bg-primary, white); color: var(--text-primary, #333);">
<h3 class="title-hero">
{% trans "Manage SMTP Hosts" %} <img ng-hide="cyberPanelLoading" src="{% static 'images/loading.gif' %}">
</h3>
<div class="example-box-wrapper">
<form action="/" class="form-horizontal bordered-row">
<!---- Create SMTP Host --->
<div ng-hide="installationDetailsForm" class="form-group">
<label class="col-sm-3 control-label">{% trans "SMTP Host" %}</label>
<div class="col-sm-6">
<input type="text" class="form-control" ng-model="smtpHost" required>
</div>
</div>
<div ng-hide="installationDetailsForm" class="form-group">
<label class="col-sm-3 control-label">{% trans "Port" %}</label>
<div class="col-sm-6">
<input type="text" class="form-control" ng-model="smtpPort" required>
</div>
</div>
<div ng-hide="installationDetailsForm" class="form-group">
<label class="col-sm-3 control-label">{% trans "Username" %}</label>
<div class="col-sm-6">
<input type="text" class="form-control" ng-model="smtpUserName" required>
</div>
</div>
<div ng-hide="installationDetailsForm" class="form-group">
<label class="col-sm-3 control-label">{% trans "Password" %}</label>
<div class="col-sm-6">
<input type="password" class="form-control" ng-model="smtpPassword" required>
</div>
</div>
<div ng-hide="installationProgress" class="form-group">
<label class="col-sm-3 control-label"></label>
<div class="col-sm-4">
<button type="button" ng-click="saveSMTPHost()" class="btn btn-primary btn-lg btn-block">{% trans "Save Host" %}</button>
</div>
</div>
<!---- Create SMTP Host --->
<!------ List of records --------------->
<div ng-hide="currentRecords" class="form-group">
<div class="col-sm-12">
<table class="table" style="background: var(--bg-primary, white); color: var(--text-primary, #333); border-color: var(--border-color, #ddd);">
<thead>
<tr>
<th>{% trans "ID" %}</th>
<th>{% trans "Owner" %}</th>
<th>{% trans "Host" %}</th>
<th>{% trans "Port" %}</th>
<th>{% trans "Username" %}</th>
<th>{% trans "Actions" %}</th>
<th></th>
</tr>
</thead>
<tbody>
<tr ng-repeat="record in records | filter:searchEmails">
<td ng-bind="record.id"></td>
<td ng-bind="record.owner"></td>
<td ng-bind="record.host"></td>
<td ng-bind="record.port"></td>
<td ng-bind="record.userName"></td>
<td >
<button type="button" ng-click="smtpHostOperations('verify', record.id)" class="btn ra-100 btn-purple">{% trans "Verify Host" %}</button>
<button type="button" ng-click="smtpHostOperations('delete', record.id)" class="btn ra-100 btn-purple">{% trans "Delete" %}</button>
</td>
</tr>
</tbody>
</table>
<div class="row">
<div class="col-sm-4 col-sm-offset-8">
<nav aria-label="Page navigation">
<ul class="pagination">
<li ng-click="fetchEmails(page)" ng-repeat="page in pagination"><a href="">{$ page $}</a></li>
</ul>
</nav>
</div>
</div>
</div>
</div>
<!------ List of records --------------->
</form>
</div>
</div>
</div>
</div>
{% endblock %}

View File

@@ -1,204 +0,0 @@
{% extends "baseTemplate/index.html" %}
{% load i18n %}
{% block title %}{% trans "Send Emails - CyberPanel" %}{% endblock %}
{% block content %}
{% load static %}
{% get_current_language as LANGUAGE_CODE %}
<!-- Current language: {{ LANGUAGE_CODE }} -->
<div class="container">
<div id="page-title">
<h2>{% trans "Send Emails" %}</h2>
<p>{% trans "On this page you can send emails to the lists you created using SMTP Hosts." %}</p>
</div>
<div ng-controller="sendEmailsCTRL" class="panel" style="background: var(--bg-primary, white); border-color: var(--border-color, #ddd);">
<div class="panel-body" style="background: var(--bg-primary, white); color: var(--text-primary, #333);">
<h3 class="title-hero">
{% trans "Send Emails" %} <img ng-hide="cyberPanelLoading" src="{% static 'images/loading.gif' %}">
</h3>
<div class="example-box-wrapper">
<form action="/" class="form-horizontal bordered-row">
<div class="form-group">
<label class="col-sm-3 control-label">{% trans "Select Template" %} </label>
<div class="col-sm-6">
<select ng-change="templateSelected()" ng-model="selectedTemplate" class="form-control">
{% for items in templateNames %}
<option>{{ items }}</option>
{% endfor %}
</select>
</div>
</div>
<div ng-hide="availableFunctions" class="form-group">
<label class="col-sm-2 control-label"></label>
<div class="col-sm-3">
<button type="button" ng-disabled="deleteTemplateBTN" data-toggle="modal" data-target="#deleteTemplate" class="btn ra-100 btn-danger">{% trans 'Delete This Template' %}</button>
<!--- Delete Template --->
<div class="modal fade" id="deleteTemplate" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content" style="background: var(--bg-primary, white); color: var(--text-primary, #333); border-color: var(--border-color, #ddd);">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
<h4 class="modal-title">{% trans "You are doing to delete this template.." %} <img ng-hide="cyberPanelLoading" src="{% static 'images/loading.gif' %}"></h4>
</div>
<div class="modal-body">
<p>{% trans 'Are you sure?' %}</p>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">{% trans 'Close' %}</button>
<button data-dismiss="modal" ng-click="deleteTemplate()" type="button" class="btn btn-primary">{% trans 'Confirm' %}</button>
</div>
</div>
</div>
</div>
<!--- Delete Template --->
</div>
<div class="col-sm-3">
<a target="_blank" href="{$ previewLink $}"><button type="button" class="btn ra-100 btn-blue-alt">{% trans 'Preview Template' %}</button></a>
</div>
<div class="col-sm-3">
<button ng-disabled="sendEmailBTN" type="button" ng-click="sendEmails()" class="btn ra-100 btn-blue-alt">{% trans 'Send Emails' %}</button>
</div>
</div>
<div ng-hide="sendEmailsView" class="form-group">
<label class="col-sm-3 control-label">{% trans "Select List" %} </label>
<div class="col-sm-6">
<select ng-model="listName" class="form-control">
{% for items in listNames %}
<option>{{ items }}</option>
{% endfor %}
</select>
</div>
</div>
<div ng-hide="sendEmailsView" class="form-group">
<label class="col-sm-3 control-label">{% trans "Select STMP Host" %} </label>
<div class="col-sm-6">
<select ng-model="host" class="form-control">
{% for items in hostNames %}
<option>{{ items }}</option>
{% endfor %}
</select>
</div>
</div>
<div ng-hide="sendEmailsView" class="form-group">
<label class="col-sm-3 control-label">{% trans "" %}</label>
<div class="col-sm-9">
<div class="checkbox">
<label>
<input ng-model="verificationCheck" type="checkbox" value="">
{% trans 'Send to un-verified email addresses.' %}
</label>
</div>
</div>
<label class="col-sm-3 control-label"></label>
<div class="col-sm-9">
<div class="checkbox">
<label>
<input ng-model="unsubscribeCheck" type="checkbox" value="">
{% trans 'Include unsubscribe link.' %}
</label>
</div>
</div>
</div>
<div ng-hide="sendEmailsView" class="form-group">
<label class="col-sm-3 control-label"></label>
<div class="col-sm-4">
<button type="button" ng-click="startEmailJob()" class="btn btn-primary btn-lg btn-block">{% trans "Start Job" %}</button>
</div>
</div>
<!---- Email Job Status --->
<div ng-hide="jobStatus" class="form-group">
<label class="col-sm-2 control-label"></label>
<div class="col-sm-7">
<div class="alert alert-success text-center" style="background: var(--bg-secondary, #f8f9ff); color: var(--text-primary, #333); border-color: var(--border-color, #ddd);">
<h2>{$ currentStatus $}</h2>
</div>
</div>
</div>
<div ng-hide="jobStatus" class="form-group">
<label class="col-sm-3 control-label"></label>
<div class="col-sm-4">
<button type="button" ng-disabled="goBackDisable" ng-click="goBack()" class="btn btn-primary btn-lg btn-block">{% trans "Go Back" %}</button>
</div>
</div>
<!---- Email Job Status --->
<!------ List of records --------------->
<div ng-hide="sendEmailsView" class="form-group">
<div class="col-sm-12">
<table class="table" style="background: var(--bg-primary, white); color: var(--text-primary, #333); border-color: var(--border-color, #ddd);">
<thead>
<tr>
<th>{% trans "Job ID" %}</th>
<th>{% trans "Date" %}</th>
<th>{% trans "SMTP Host" %}</th>
<th>{% trans "Total Emails" %}</th>
<th>{% trans "Sent" %}</th>
<th>{% trans "Failed" %}</th>
<th>{% trans "Actions" %}</th>
<th></th>
</tr>
</thead>
<tbody>
<tr ng-repeat="record in records | filter:searchEmails">
<td ng-bind="record.id"></td>
<td ng-bind="record.date"></td>
<td ng-bind="record.host"></td>
<td ng-bind="record.totalEmails"></td>
<td ng-bind="record.sent"></td>
<td ng-bind="record.failed"></td>
<td >
<button type="button" ng-click="deleteJob(record.id)" class="btn ra-100 btn-purple">{% trans "Delete" %}</button>
</td>
</tr>
</tbody>
</table>
<div class="row">
<div class="col-sm-4 col-sm-offset-8">
<nav aria-label="Page navigation">
<ul class="pagination">
<li ng-click="fetchEmails(page)" ng-repeat="page in pagination"><a href="">{$ page $}</a></li>
</ul>
</nav>
</div>
</div>
</div>
</div>
<!------ List of records --------------->
</form>
</div>
</div>
</div>
</div>
{% endblock %}

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +0,0 @@
# -*- coding: utf-8 -*-
from django.test import TestCase
# Create your tests here.

View File

@@ -1,31 +0,0 @@
from django.urls import path, re_path
from . import views
urlpatterns = [
path('', views.emailMarketing, name='emailMarketing'),
path('fetchUsers', views.fetchUsers, name='fetchUsers'),
path('enableDisableMarketing', views.enableDisableMarketing, name='enableDisableMarketing'),
path('saveConfigureVerify', views.saveConfigureVerify, name='saveConfigureVerify'),
path('fetchVerifyLogs', views.fetchVerifyLogs, name='fetchVerifyLogs'),
re_path(r'^(?P<domain>.+)/emailLists$', views.createEmailList, name='createEmailList'),
path('submitEmailList', views.submitEmailList, name='submitEmailList'),
re_path(r'^(?P<domain>.+)/manageLists$', views.manageLists, name='manageLists'),
re_path(r'^(?P<domain>.+)/manageSMTP$', views.manageSMTP, name='manageSMTP'),
re_path(r'^(?P<domain>.+)/configureVerify$', views.configureVerify, name='configureVerify'),
path('fetchEmails', views.fetchEmails, name='fetchEmails'),
path('deleteList', views.deleteList, name='deleteList'),
path('emailVerificationJob', views.emailVerificationJob, name='emailVerificationJob'),
path('deleteEmail', views.deleteEmail, name='deleteEmail'),
path('saveSMTPHost', views.saveSMTPHost, name='saveSMTPHost'),
path('fetchSMTPHosts', views.fetchSMTPHosts, name='fetchSMTPHosts'),
path('smtpHostOperations', views.smtpHostOperations, name='smtpHostOperations'),
path('composeEmailMessage', views.composeEmailMessage, name='composeEmailMessage'),
path('saveEmailTemplate', views.saveEmailTemplate, name='saveEmailTemplate'),
path('sendEmails', views.sendEmails, name='sendEmails'),
re_path(r'^preview/(?P<templateName>[-\w]+)/$', views.templatePreview, name='templatePreview'),
path('fetchJobs', views.fetchJobs, name='fetchJobs'),
path('startEmailJob', views.startEmailJob, name='startEmailJob'),
path('deleteTemplate', views.deleteTemplate, name='deleteTemplate'),
path('deleteJob', views.deleteJob, name='deleteJob'),
re_path(r'^remove/(?P<listName>[-\w]+)/(?P<emailAddress>\w+@.+)$', views.remove, name='remove'),
]

View File

@@ -1,215 +0,0 @@
# -*- coding: utf-8 -*-
from django.shortcuts import redirect
from loginSystem.views import loadLoginPage
from .emailMarketingManager import EmailMarketingManager
# Create your views here.
def emailMarketing(request):
try:
userID = request.session['userID']
emm = EmailMarketingManager(request)
return emm.emailMarketing()
except KeyError:
return redirect(loadLoginPage)
def fetchUsers(request):
try:
userID = request.session['userID']
emm = EmailMarketingManager(request)
return emm.fetchUsers()
except KeyError:
return redirect(loadLoginPage)
def enableDisableMarketing(request):
try:
userID = request.session['userID']
emm = EmailMarketingManager(request)
return emm.enableDisableMarketing()
except KeyError:
return redirect(loadLoginPage)
def createEmailList(request, domain):
try:
userID = request.session['userID']
emm = EmailMarketingManager(request, domain)
return emm.createEmailList()
except KeyError:
return redirect(loadLoginPage)
def submitEmailList(request):
try:
userID = request.session['userID']
emm = EmailMarketingManager(request)
return emm.submitEmailList()
except KeyError:
return redirect(loadLoginPage)
def manageLists(request, domain):
try:
userID = request.session['userID']
emm = EmailMarketingManager(request, domain)
return emm.manageLists()
except KeyError:
return redirect(loadLoginPage)
def configureVerify(request, domain):
try:
userID = request.session['userID']
emm = EmailMarketingManager(request, domain)
return emm.configureVerify()
except KeyError:
return redirect(loadLoginPage)
def saveConfigureVerify(request):
try:
userID = request.session['userID']
emm = EmailMarketingManager(request)
return emm.saveConfigureVerify()
except KeyError:
return redirect(loadLoginPage)
def fetchVerifyLogs(request):
try:
userID = request.session['userID']
emm = EmailMarketingManager(request)
return emm.fetchVerifyLogs()
except KeyError:
return redirect(loadLoginPage)
def fetchEmails(request):
try:
userID = request.session['userID']
emm = EmailMarketingManager(request)
return emm.fetchEmails()
except KeyError:
return redirect(loadLoginPage)
def deleteList(request):
try:
userID = request.session['userID']
emm = EmailMarketingManager(request)
return emm.deleteList()
except KeyError:
return redirect(loadLoginPage)
def emailVerificationJob(request):
try:
userID = request.session['userID']
emm = EmailMarketingManager(request)
return emm.emailVerificationJob()
except KeyError:
return redirect(loadLoginPage)
def deleteEmail(request):
try:
userID = request.session['userID']
emm = EmailMarketingManager(request)
return emm.deleteEmail()
except KeyError:
return redirect(loadLoginPage)
def manageSMTP(request, domain):
try:
userID = request.session['userID']
emm = EmailMarketingManager(request, domain)
return emm.manageSMTP()
except KeyError:
return redirect(loadLoginPage)
def saveSMTPHost(request):
try:
userID = request.session['userID']
emm = EmailMarketingManager(request)
return emm.saveSMTPHost()
except KeyError:
return redirect(loadLoginPage)
def fetchSMTPHosts(request):
try:
userID = request.session['userID']
emm = EmailMarketingManager(request)
return emm.fetchSMTPHosts()
except KeyError:
return redirect(loadLoginPage)
def smtpHostOperations(request):
try:
userID = request.session['userID']
emm = EmailMarketingManager(request)
return emm.smtpHostOperations()
except KeyError:
return redirect(loadLoginPage)
def composeEmailMessage(request):
try:
userID = request.session['userID']
emm = EmailMarketingManager(request)
return emm.composeEmailMessage()
except KeyError:
return redirect(loadLoginPage)
def saveEmailTemplate(request):
try:
userID = request.session['userID']
emm = EmailMarketingManager(request)
return emm.saveEmailTemplate()
except KeyError:
return redirect(loadLoginPage)
def sendEmails(request):
try:
userID = request.session['userID']
emm = EmailMarketingManager(request)
return emm.sendEmails()
except KeyError:
return redirect(loadLoginPage)
def templatePreview(request, templateName):
try:
userID = request.session['userID']
emm = EmailMarketingManager(request, templateName)
return emm.templatePreview()
except KeyError:
return redirect(loadLoginPage)
def fetchJobs(request):
try:
userID = request.session['userID']
emm = EmailMarketingManager(request)
return emm.fetchJobs()
except KeyError:
return redirect(loadLoginPage)
def startEmailJob(request):
try:
userID = request.session['userID']
emm = EmailMarketingManager(request)
return emm.startEmailJob()
except KeyError:
return redirect(loadLoginPage)
def deleteTemplate(request):
try:
userID = request.session['userID']
emm = EmailMarketingManager(request)
return emm.deleteTemplate()
except KeyError:
return redirect(loadLoginPage)
def deleteJob(request):
try:
userID = request.session['userID']
emm = EmailMarketingManager(request)
return emm.deleteJob()
except KeyError:
return redirect(loadLoginPage)
def remove(request, listName, emailAddress):
try:
emm = EmailMarketingManager(request)
return emm.remove(listName, emailAddress)
except KeyError:
return redirect(loadLoginPage)

View File

@@ -1 +0,0 @@
default_app_config = 'examplePlugin.apps.ExamplepluginConfig'

View File

@@ -1,3 +0,0 @@
from django.contrib import admin
# Register your models here.

View File

@@ -1,8 +0,0 @@
from django.apps import AppConfig
class ExamplepluginConfig(AppConfig):
name = 'examplePlugin'
def ready(self):
from . import signals

View File

@@ -1,8 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<cyberpanelPluginConfig>
<name>examplePlugin</name>
<type>Utility</type>
<description>This is an example plugin</description>
<version>1.0.1</version>
<author>usmannasir</author>
</cyberpanelPluginConfig>

View File

@@ -1,9 +0,0 @@
from django.db import models
class ExamplePlugin(models.Model):
name = models.CharField(unique=True, max_length=255)
class Meta:
# db_table = "ExamplePlugin"
pass

View File

@@ -1,4 +0,0 @@
#!/usr/local/CyberCP/bin/python
RESET = '\033[0;0m'
BLUE = "\033[0;34m"
print(BLUE + "Running Post-Install Script..." + RESET)

View File

@@ -1,4 +0,0 @@
#!/usr/local/CyberCP/bin/python
RESET = '\033[0;0m'
GREEN = '\033[0;32m'
print(GREEN + "Running Pre-Install Script..." + RESET)

View File

@@ -1,4 +0,0 @@
#!/usr/local/CyberCP/bin/python
RESET = '\033[0;0m'
GREEN = '\033[0;32m'
print(GREEN + "Running Pre-Remove Script..." + RESET)

View File

@@ -1,17 +0,0 @@
from django.dispatch import receiver
from django.http import HttpResponse
from websiteFunctions.signals import postWebsiteDeletion
from plogical.CyberCPLogFileWriter import CyberCPLogFileWriter as logging
# This plugin respond to an event after CyberPanel core finished deleting a website.
# Original request object is passed, body can be accessed with request.body.
# If any Event handler returns a response object, CyberPanel will stop further processing and returns your response to browser.
# To continue processing just return 200 from your events handlers.
@receiver(postWebsiteDeletion)
def rcvr(sender, **kwargs):
request = kwargs['request']
logging.writeToFile('Hello World from Example Plugin.')
return HttpResponse('Hello World from Example Plugin.')

View File

@@ -1,3 +0,0 @@
$(document).ready(function () {
console.log("using JS in static file...!");
});

View File

@@ -1,52 +0,0 @@
{% extends "baseTemplate/index.html" %}
{% load i18n %}
{% block styles %}
<style>
.exampleBody {
font-weight: bold;
}
</style>
{% endblock %}
{% block title %}Example plugin - CyberPanel{% endblock %}
{% block content %}
{% load static %}
{% get_current_language as LANGUAGE_CODE %}
<!-- Current language: {{ LANGUAGE_CODE }} -->
<div class="container" id="examplePluginApp">
<div id="page-title">
<h2 id="domainNamePage">{% trans "Example Plugin Page" %}</h2>
<p>{% trans "Example Plugin Info" %}</p>
</div>
<div class="panel">
<div class="panel-heading container-fluid">
<div class="col-xs-4"><h3 class="panel-title">{% trans "examplePlugin" %}</h3></div>
</div>
<div class="panel-body">
<div class="example-box-wrapper">
<p class="exampleBody">[[ pluginBody ]]</p>
</div>
</div>
</div>
</div>
{% endblock %}
{% block footer_scripts %}
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
{# <script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>#}
<script src="{% static 'examplePlugin/examplePlugin.js' %}"></script>
<script>
let examplePluginApp = new Vue({
delimiters: ['[[', ']]'],
el: '#examplePluginApp',
data: function () {
return {
pluginBody: "Example Plugin Body leveraging templated imported Vue.js",
}
},
methods: {},
});
</script>
{% endblock %}

View File

@@ -1,3 +0,0 @@
from django.test import TestCase
# Create your tests here.

View File

@@ -1,7 +0,0 @@
from django.urls import path
from . import views
urlpatterns = [
path('', views.examplePlugin, name='examplePlugin'),
]

View File

@@ -1,7 +0,0 @@
from django.shortcuts import render, HttpResponse
# Create your views here.
def examplePlugin(request):
return render(request, 'examplePlugin/examplePlugin.html')

View File

@@ -3235,7 +3235,7 @@ password="%s"
apps_with_migrations = [
'loginSystem', 'packages', 'websiteFunctions', 'baseTemplate', 'userManagment',
'dns', 'databases', 'ftp', 'filemanager', 'mailServer', 'emailPremium',
'emailMarketing', 'cloudAPI', 'containerization', 'IncBackups', 'CLManager',
'cloudAPI', 'containerization', 'IncBackups', 'CLManager',
's3Backups', 'dockerManager', 'aiScanner', 'firewall', 'tuning', 'serverStatus',
'serverLogs', 'backup', 'managePHP', 'manageSSL', 'api', 'manageServices',
'pluginHolder', 'highAvailability', 'WebTerminal'

View File

@@ -1,58 +0,0 @@
# Premium Plugin Example
An example paid plugin for CyberPanel that demonstrates how to implement Patreon subscription-based plugin access.
## Features
- Requires Patreon subscription to "CyberPanel Paid Plugin" tier
- Users can install the plugin without subscription
- Plugin functionality is locked until subscription is verified
- Shows subscription required page when accessed without subscription
## Installation
1. Upload the plugin ZIP file to CyberPanel
2. Install the plugin from the plugin manager
3. The plugin will appear in the installed plugins list
## Usage
### For Users Without Subscription
- Plugin can be installed
- When accessing the plugin, a subscription required page is shown
- Link to Patreon subscription page is provided
### For Users With Subscription
- Plugin works normally
- All features are accessible
- Settings page is available
## Configuration
The plugin checks for Patreon membership via the Patreon API. Make sure to configure:
1. Patreon Client ID
2. Patreon Client Secret
3. Patreon Creator ID
These should be set in CyberPanel environment variables or settings.
## Meta.xml Structure
The plugin uses the following meta.xml structure for paid plugins:
```xml
<paid>true</paid>
<patreon_tier>CyberPanel Paid Plugin</patreon_tier>
<patreon_url>https://www.patreon.com/c/newstargeted/membership</patreon_url>
```
## Author
master3395
## License
MIT

View File

@@ -1,4 +0,0 @@
# PayPal Premium Plugin Example
# This is a paid plugin that requires PayPal payment
default_app_config = 'paypalPremiumPlugin.apps.PaypalpremiumpluginConfig'

View File

@@ -1,80 +0,0 @@
# -*- coding: utf-8 -*-
"""
AES-256-CBC encryption for plugin <-> api.newstargeted.com communication.
Key must match PLUGIN_VERIFICATION_CIPHER_KEY in config.php on the API server.
"""
import json
import base64
import os
CIPHER_KEY_B64 = '1VLPEKTmLGUbIxHUFEtsuVM2MPN1tl8HPFtyJc4dr58='
ENCRYPTION_ENABLED = True
_ENCRYPTION_CIPHER_KEY = None
def _get_key():
global _ENCRYPTION_CIPHER_KEY
if _ENCRYPTION_CIPHER_KEY is not None:
return _ENCRYPTION_CIPHER_KEY
try:
key = base64.b64decode(CIPHER_KEY_B64)
if len(key) == 32:
_ENCRYPTION_CIPHER_KEY = key
return key
except Exception:
pass
return None
def encrypt_payload(data):
if not ENCRYPTION_ENABLED or not _get_key():
body = json.dumps(data, separators=(',', ':')).encode('utf-8')
return body, {'Content-Type': 'application/json'}
try:
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
from cryptography.hazmat.primitives import padding
from cryptography.hazmat.backends import default_backend
key = _get_key()
plain = json.dumps(data, separators=(',', ':')).encode('utf-8')
padder = padding.PKCS7(128).padder()
padded = padder.update(plain) + padder.finalize()
iv = os.urandom(16)
cipher = Cipher(algorithms.AES(key), modes.CBC(iv), backend=default_backend())
encryptor = cipher.encryptor()
ciphertext = encryptor.update(padded) + encryptor.finalize()
payload = base64.b64encode(iv).decode('ascii') + '.' + base64.b64encode(ciphertext).decode('ascii')
return payload.encode('utf-8'), {'Content-Type': 'text/plain', 'X-Encrypted': '1'}
except Exception:
body = json.dumps(data, separators=(',', ':')).encode('utf-8')
return body, {'Content-Type': 'application/json'}
def decrypt_response(body_bytes, content_type='', expect_encrypted=False):
try:
body_str = body_bytes.decode('utf-8') if isinstance(body_bytes, bytes) else str(body_bytes)
is_encrypted = (
expect_encrypted or
('text/plain' in content_type and '.' in body_str) or
('.' in body_str and body_str.strip() and body_str.strip()[0] not in '{[')
)
parts = body_str.strip().split('.', 1)
if is_encrypted and len(parts) == 2 and ENCRYPTION_ENABLED and _get_key():
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
from cryptography.hazmat.primitives import padding
from cryptography.hazmat.backends import default_backend
iv = base64.b64decode(parts[0])
ciphertext = base64.b64decode(parts[1])
key = _get_key()
cipher = Cipher(algorithms.AES(key), modes.CBC(iv), backend=default_backend())
decryptor = cipher.decryptor()
padded = decryptor.update(ciphertext) + decryptor.finalize()
unpadder = padding.PKCS7(128).unpadder()
plain = unpadder.update(padded) + unpadder.finalize()
return json.loads(plain.decode('utf-8'))
return json.loads(body_str)
except Exception:
try:
return json.loads(body_bytes.decode('utf-8') if isinstance(body_bytes, bytes) else body_bytes)
except Exception:
return {}

View File

@@ -1,5 +0,0 @@
from django.apps import AppConfig
class PaypalpremiumpluginConfig(AppConfig):
name = 'paypalPremiumPlugin'
verbose_name = 'PayPal Premium Plugin Example'

View File

@@ -1,30 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<plugin>
<name>PayPal Premium Plugin Example</name>
<type>Utility</type>
<version>1.0.2</version>
<description>An example paid plugin that requires PayPal payment. Users can install it but cannot run it without payment. Supports PayPal.me links and PayPal Payment Links/Buttons.</description>
<author>master3395</author>
<website>https://github.com/master3395/cyberpanel-plugins</website>
<license>MIT</license>
<dependencies>
<python>3.6+</python>
<django>2.2+</django>
<cyberpanel>2.5.5+</cyberpanel>
</dependencies>
<compatibility>
<min_version>2.5.5</min_version>
<max_version>3.0.0</max_version>
</compatibility>
<permissions>
<admin>true</admin>
<user>false</user>
</permissions>
<paid>true</paid>
<patreon_tier>CyberPanel Paid Plugin</patreon_tier>
<patreon_url>https://www.patreon.com/membership/27789984</patreon_url>
<paypal_me_url>https://paypal.me/KimBS?locale.x=en_US&amp;country.x=NO</paypal_me_url>
<paypal_payment_link></paypal_payment_link>
<url>/plugins/paypalPremiumPlugin/</url>
<settings_url>/plugins/paypalPremiumPlugin/settings/</settings_url>
</plugin>

View File

@@ -1,27 +0,0 @@
# Generated migration for PaypalPremiumPluginConfig
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = []
operations = [
migrations.CreateModel(
name='PaypalPremiumPluginConfig',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('payment_method', models.CharField(choices=[('patreon', 'Patreon Subscription'), ('paypal', 'PayPal Payment'), ('both', 'Check Both (Patreon or PayPal)')], default='both', help_text='Choose which payment method to use for verification.', max_length=10)),
('activation_key', models.CharField(blank=True, default='', help_text='Validated activation key - grants access without re-entering.', max_length=64)),
('created_at', models.DateTimeField(auto_now_add=True)),
('updated_at', models.DateTimeField(auto_now=True)),
],
options={
'verbose_name': 'PayPal Premium Plugin Configuration',
'verbose_name_plural': 'PayPal Premium Plugin Configurations',
},
),
]

View File

@@ -1 +0,0 @@
# PayPal Premium Plugin migrations

View File

@@ -1,40 +0,0 @@
# -*- coding: utf-8 -*-
from django.db import models
class PaypalPremiumPluginConfig(models.Model):
PAYMENT_METHOD_CHOICES = [
('patreon', 'Patreon Subscription'),
('paypal', 'PayPal Payment'),
('both', 'Check Both (Patreon or PayPal)'),
]
payment_method = models.CharField(
max_length=10,
choices=PAYMENT_METHOD_CHOICES,
default='both',
help_text="Choose which payment method to use for verification."
)
activation_key = models.CharField(
max_length=64,
blank=True,
default='',
help_text="Validated activation key - grants access without re-entering."
)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
class Meta:
verbose_name = "PayPal Premium Plugin Configuration"
verbose_name_plural = "PayPal Premium Plugin Configurations"
def __str__(self):
return "PayPal Premium Plugin Configuration"
@classmethod
def get_config(cls):
config, _ = cls.objects.get_or_create(pk=1)
return config
def save(self, *args, **kwargs):
self.pk = 1
super().save(*args, **kwargs)

View File

@@ -1,96 +0,0 @@
{% extends "baseTemplate/index.html" %}
{% load i18n %}
{% block title %}{% trans "PayPal Premium Plugin Example - CyberPanel" %}{% endblock %}
{% block header_scripts %}
<style>
.premium-plugin-container {
padding: 20px;
max-width: 1200px;
margin: 0 auto;
}
.premium-badge {
display: inline-block;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
padding: 8px 16px;
border-radius: 20px;
font-size: 12px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.5px;
margin-bottom: 20px;
}
.plugin-header {
background: white;
border-radius: 12px;
padding: 30px;
margin-bottom: 25px;
box-shadow: 0 2px 8px rgba(0,0,0,0.08);
}
.plugin-header h1 {
margin: 0 0 10px 0;
color: #2f3640;
}
.features-list {
background: white;
border-radius: 12px;
padding: 30px;
box-shadow: 0 2px 8px rgba(0,0,0,0.08);
}
.features-list h2 {
margin-top: 0;
color: #2f3640;
}
.features-list ul {
list-style: none;
padding: 0;
}
.features-list li {
padding: 12px 0;
border-bottom: 1px solid #e8e9ff;
display: flex;
align-items: center;
gap: 12px;
}
.features-list li:last-child {
border-bottom: none;
}
.features-list li::before {
content: "✓";
color: #28a745;
font-weight: bold;
font-size: 18px;
}
</style>
{% endblock %}
{% block content %}
<div class="premium-plugin-container">
<div class="plugin-header">
<span class="premium-badge">{% trans "Premium Plugin" %}</span>
<h1>{{ plugin_name }}</h1>
<p>{{ description }}</p>
<p><strong>{% trans "Version:" %}</strong> {{ version }}</p>
</div>
<div class="features-list">
<h2>{% trans "Premium Features" %}</h2>
<ul>
{% for feature in features %}
<li>{{ feature }}</li>
{% endfor %}
</ul>
</div>
</div>
{% endblock %}

View File

@@ -1,331 +0,0 @@
{% extends "baseTemplate/index.html" %}
{% load i18n %}
{% block title %}{% trans "PayPal Premium Plugin Settings - CyberPanel" %}{% endblock %}
{% block header_scripts %}
<style>
.premium-plugin-container {
padding: 20px;
max-width: 1200px;
margin: 0 auto;
}
.premium-badge {
display: inline-block;
background: linear-gradient(135deg, #0070ba 0%, #003087 100%);
color: white;
padding: 8px 16px;
border-radius: 20px;
font-size: 12px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.5px;
margin-bottom: 20px;
}
.settings-form {
background: white;
border-radius: 12px;
padding: 30px;
box-shadow: 0 2px 8px rgba(0,0,0,0.08);
}
.payment-warning {
background: #fff3cd;
border: 1px solid #ffc107;
border-radius: 8px;
padding: 15px 20px;
margin-bottom: 25px;
display: flex;
align-items: center;
justify-content: space-between;
flex-wrap: wrap;
gap: 15px;
}
.payment-warning-content {
flex: 1;
min-width: 250px;
}
.payment-warning-title {
font-weight: 600;
color: #856404;
margin-bottom: 5px;
font-size: 16px;
}
.payment-warning-text {
color: #856404;
font-size: 14px;
margin: 0;
}
.paypal-button {
background: linear-gradient(135deg, #0070ba 0%, #003087 100%);
color: white;
border: none;
padding: 10px 20px;
border-radius: 6px;
font-weight: 600;
text-decoration: none;
display: inline-flex;
align-items: center;
gap: 8px;
transition: transform 0.2s, box-shadow 0.2s;
white-space: nowrap;
margin: 5px;
}
.paypal-button:hover {
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(0, 112, 186, 0.4);
color: white;
text-decoration: none;
}
.paypal-me-button {
background: linear-gradient(135deg, #0070ba 0%, #003087 100%);
}
.paypal-payment-link-button {
background: linear-gradient(135deg, #009cde 0%, #0070ba 100%);
}
.settings-disabled {
opacity: 0.6;
pointer-events: none;
position: relative;
}
.settings-disabled::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(255, 255, 255, 0.7);
z-index: 1;
border-radius: 8px;
}
.form-group {
margin-bottom: 20px;
}
.form-group label {
display: block;
margin-bottom: 8px;
font-weight: 600;
color: #2f3640;
}
.form-control {
width: 100%;
padding: 10px 15px;
border: 1px solid #e0e0e0;
border-radius: 6px;
font-size: 14px;
transition: border-color 0.3s, box-shadow 0.3s;
}
.form-control:focus {
outline: none;
border-color: #5856d6;
box-shadow: 0 0 0 3px rgba(88, 86, 214, 0.1);
}
.form-control:disabled {
background-color: #f5f5f5;
cursor: not-allowed;
}
.btn {
padding: 10px 20px;
border: none;
border-radius: 6px;
font-weight: 600;
cursor: pointer;
transition: all 0.3s;
}
.btn-primary {
background: linear-gradient(135deg, #5856d6 0%, #4a90e2 100%);
color: white;
}
.btn-primary:hover:not(:disabled) {
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(88, 86, 214, 0.4);
}
.btn-primary:disabled {
background: #ccc;
cursor: not-allowed;
opacity: 0.6;
}
/* Plugin Information Section - Ensure visibility */
.plugin-info-section {
background: #d1ecf1 !important;
border: 1px solid #bee5eb !important;
border-radius: 8px !important;
padding: 15px 20px !important;
margin-bottom: 25px !important;
display: block !important;
visibility: visible !important;
opacity: 1 !important;
position: relative !important;
z-index: 10 !important;
}
.plugin-info-section ul {
list-style: none !important;
padding-left: 0 !important;
margin-top: 10px !important;
margin-bottom: 0 !important;
}
.plugin-info-section li {
margin-bottom: 8px;
}
.plugin-info-section li:last-child {
margin-bottom: 0;
}
.payment-buttons-container {
display: flex;
flex-wrap: wrap;
gap: 10px;
margin-top: 10px;
}
</style>
{% endblock %}
{% block content %}
<div class="premium-plugin-container">
<div class="settings-form">
<span class="premium-badge">{% trans "Premium Plugin" %}</span>
<h1>{% trans "PayPal Premium Plugin Settings" %}</h1>
{% if show_payment_ui %}
<!-- Activate Premium Access -->
<div style="background: #e6f7ff; border: 2px solid #17a2b8; border-radius: 12px; padding: 20px; margin-bottom: 25px;">
<h3 style="color: #17a2b8; margin-top: 0;"><i class="fas fa-key"></i> {% trans "Activate Premium Access" %}</h3>
<p style="color: #495057;">{% trans "If you have an activation key, enter it below." %}</p>
<div id="activation-msg" style="display: none; margin-bottom: 10px; padding: 10px; border-radius: 6px;"></div>
<form id="activation-form" style="display: flex; gap: 10px; align-items: flex-end;">
{% csrf_token %}
<input type="text" id="activation-key" name="activation_key" placeholder="PAYPALPREMIUMPLUGIN-XXXX-XXXX-XXXX" style="flex: 1; padding: 10px; border: 2px solid #17a2b8; border-radius: 6px; font-family: monospace; text-transform: uppercase;" />
<button type="submit" class="btn btn-primary" id="activate-btn"><i class="fas fa-check-circle"></i> {% trans "Activate" %}</button>
</form>
</div>
<!-- Payment Method Preference -->
<div style="margin-bottom: 25px;">
<label style="font-weight: 600;">{% trans "Payment Method Preference" %}</label>
<form id="payment-method-form" method="post" action="{% url 'paypalPremiumPlugin:save_payment_method' %}" style="display: inline-block; margin-left: 10px;">
{% csrf_token %}
<select name="payment_method" onchange="this.form.submit()" style="padding: 8px 12px; border-radius: 6px;">
<option value="both" {% if config.payment_method == 'both' %}selected{% endif %}>{% trans "Check Both (Patreon or PayPal)" %}</option>
<option value="patreon" {% if config.payment_method == 'patreon' %}selected{% endif %}>{% trans "Patreon Only" %}</option>
<option value="paypal" {% if config.payment_method == 'paypal' %}selected{% endif %}>{% trans "PayPal Only" %}</option>
</select>
</form>
</div>
{% else %}
<div style="background: #d4edda; border: 2px solid #28a745; border-radius: 12px; padding: 20px; margin-bottom: 25px;">
<i class="fas fa-check-circle" style="color: #28a745;"></i> <strong>{% trans "Premium Access Active" %}</strong> — {% trans "Access granted via Plugin Grants or activation key." %}
</div>
{% endif %}
<!-- Plugin Information Section - Always Visible -->
<div class="plugin-info-section" style="background: #d1ecf1 !important; border: 1px solid #bee5eb !important; border-radius: 8px !important; padding: 15px 20px !important; margin-bottom: 25px !important; display: block !important; visibility: visible !important; opacity: 1 !important;">
<i class="fas fa-info-circle" style="color: #0c5460; margin-right: 8px;"></i>
<strong style="color: #0c5460; font-weight: 600;">{% trans "Plugin Information" %}</strong>
<ul style="list-style: none !important; padding-left: 0 !important; margin-top: 10px !important; margin-bottom: 0 !important;">
<li style="margin-bottom: 8px;"><strong>{% trans "Name" %}:</strong> {{ plugin_name|default:"PayPal Premium Plugin Example" }}</li>
<li style="margin-bottom: 8px;"><strong>{% trans "Version" %}:</strong> {{ version|default:"1.0.0" }}</li>
<li style="margin-bottom: 0;">
<strong>{% trans "Status" %}:</strong>
<span class="badge" style="background: {% if has_access %}#28a745{% else %}#6c757d{% endif %} !important; color: white !important; padding: 4px 10px !important; border-radius: 4px !important; font-size: 12px !important; font-weight: 600 !important; display: inline-block !important;">
{{ plugin_status|default:status|default:"Active" }}
</span>
</li>
</ul>
</div>
<p>{{ description }}</p>
<form method="post" id="settings-form">
{% csrf_token %}
<div class="form-group">
<label>{% trans "Setting 1" %}</label>
<input type="text" class="form-control" name="setting1" value="" placeholder="{% trans 'Enter setting value' %}">
</div>
<div class="form-group">
<label>{% trans "Setting 2" %}</label>
<input type="text" class="form-control" name="setting2" value="" placeholder="{% trans 'Enter setting value' %}">
</div>
<div class="form-group">
<label>{% trans "Setting 3 (Advanced)" %}</label>
<textarea class="form-control" name="setting3" rows="4" placeholder="{% trans 'Enter advanced configuration' %}"></textarea>
</div>
<div class="form-group">
<label>
<input type="checkbox" name="enable_feature">
{% trans "Enable Premium Feature" %}
</label>
</div>
<button type="submit" class="btn btn-primary">
<i class="fas fa-save"></i> {% trans "Save Settings" %}
</button>
</form>
{% if show_payment_ui %}
<script>
document.addEventListener('DOMContentLoaded', function() {
const form = document.getElementById('activation-form');
if (form) {
form.addEventListener('submit', async function(e) {
e.preventDefault();
const key = document.getElementById('activation-key').value.trim().toUpperCase();
if (!key) return;
const btn = document.getElementById('activate-btn');
const msg = document.getElementById('activation-msg');
btn.disabled = true;
btn.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Activating...';
try {
const res = await fetch('/plugins/paypalPremiumPlugin/activate-key/', {
method: 'POST',
headers: { 'Content-Type': 'application/json', 'X-CSRFToken': form.querySelector('[name=csrfmiddlewaretoken]').value },
body: JSON.stringify({ activation_key: key })
});
const data = await res.json();
msg.textContent = data.success ? '✅ ' + data.message : '❌ ' + (data.message || 'Invalid key');
msg.style.display = 'block';
msg.style.background = data.success ? '#d4edda' : '#f8d7da';
msg.style.color = data.success ? '#155724' : '#721c24';
if (data.success) { document.getElementById('activation-key').value = ''; setTimeout(function() { location.reload(); }, 2000); }
} catch (err) {
msg.textContent = '❌ ' + err.message;
msg.style.display = 'block';
msg.style.background = '#f8d7da';
msg.style.color = '#721c24';
}
btn.disabled = false;
btn.innerHTML = '<i class="fas fa-check-circle"></i> Activate';
});
}
});
</script>
{% endif %}
</div>
</div>
{% endblock %}

View File

@@ -1,139 +0,0 @@
{% extends "baseTemplate/index.html" %}
{% load i18n %}
{% block title %}{% trans "Payment Required - PayPal Premium Plugin" %}{% endblock %}
{% block header_scripts %}
<style>
.subscription-required-container { padding: 40px 20px; max-width: 900px; margin: 0 auto; text-align: center; }
.subscription-card { background: white; border-radius: 12px; padding: 40px; box-shadow: 0 4px 16px rgba(0,0,0,0.1); }
.premium-icon { font-size: 64px; color: #0070ba; margin-bottom: 20px; }
.subscription-card h1 { color: #2f3640; margin-bottom: 15px; }
.subscription-card p { color: #64748b; font-size: 16px; line-height: 1.6; margin-bottom: 30px; }
.payment-methods { display: flex; gap: 20px; justify-content: center; flex-wrap: wrap; margin: 30px 0; }
.payment-method-card { flex: 1; min-width: 250px; max-width: 350px; background: #f8f9ff; border: 2px solid #e2e8f0; border-radius: 12px; padding: 25px; text-align: center; }
.payment-method-card.patreon { border-color: #FF424D; }
.payment-method-card.paypal { border-color: #0070ba; }
.payment-method-card h3 { margin-top: 0; color: #2f3640; font-size: 1.3rem; }
.patreon-button, .paypal-button { display: inline-block; color: white; padding: 15px 30px; border-radius: 8px; text-decoration: none; font-weight: 600; font-size: 16px; transition: all 0.3s ease; width: 100%; box-sizing: border-box; margin-bottom: 10px; }
.patreon-button { background: #FF424D; }
.patreon-button:hover { background: #E62E37; color: white; text-decoration: none; }
.paypal-button { background: #0070ba; }
.paypal-button:hover { background: #003087; color: white; text-decoration: none; }
.info-box { background: #f8f9ff; border-left: 4px solid #0070ba; padding: 20px; border-radius: 8px; margin-top: 30px; text-align: left; }
.payment-method-selector { background: #fff3cd; border: 1px solid #ffc107; border-radius: 8px; padding: 15px; margin-bottom: 30px; text-align: left; }
</style>
{% endblock %}
{% block content %}
<div class="subscription-required-container">
<div class="subscription-card">
<div class="premium-icon"><i class="fab fa-paypal"></i></div>
<h1>{% trans "Premium Plugin Access Required" %}</h1>
<p>{% trans "This plugin requires payment or subscription to access premium features." %}</p>
<!-- Activation Key Section -->
<div style="background: linear-gradient(135deg, #e6f7ff 0%, #f0f9ff 100%); border: 2px solid #17a2b8; border-radius: 12px; padding: 25px; margin: 30px 0; text-align: left;">
<h3 style="color: #17a2b8; margin-top: 0;"><i class="fas fa-key"></i> {% trans "Activate Premium Access" %}</h3>
<p style="color: #495057; margin-bottom: 20px;">{% trans "If you received an activation key, enter it below." %}</p>
<div id="activation-message" style="display: none; margin-bottom: 15px; padding: 12px; border-radius: 6px;"></div>
<form id="activation-key-form" style="display: flex; gap: 10px; align-items: flex-end; max-width: 600px;">
{% csrf_token %}
<div style="flex: 1;">
<label for="activation-key-input" style="font-weight: 600; display: block; margin-bottom: 8px;">{% trans "Activation Key" %}</label>
<input type="text" id="activation-key-input" name="activation_key" placeholder="PAYPALPREMIUMPLUGIN-XXXX-XXXX-XXXX" style="width: 100%; padding: 12px; border: 2px solid #17a2b8; border-radius: 6px; font-family: monospace; text-transform: uppercase;" required />
</div>
<button type="submit" style="background: #17a2b8; color: white; border: none; padding: 12px 24px; border-radius: 6px; font-weight: 600; cursor: pointer;" id="activate-btn">
<i class="fas fa-check-circle"></i> {% trans "Activate" %}
</button>
</form>
</div>
{% if payment_method == 'both' %}
<div class="payment-method-selector">
<strong>{% trans "Current Payment Method:" %}</strong> {% trans "Check Both (Patreon or PayPal)" %}
</div>
{% elif payment_method == 'patreon' %}
<div class="payment-method-selector">
<strong>{% trans "Current Payment Method:" %}</strong> {% trans "Patreon Subscription Only" %}
</div>
{% elif payment_method == 'paypal' %}
<div class="payment-method-selector">
<strong>{% trans "Current Payment Method:" %}</strong> {% trans "PayPal Payment Only" %}
</div>
{% endif %}
<div class="payment-methods">
<div class="payment-method-card patreon">
<h3><i class="fab fa-patreon"></i> {% trans "Patreon Subscription" %}</h3>
<p>{% trans "Subscribe to" %} <strong>"{{ patreon_tier }}"</strong></p>
<a href="{{ patreon_url }}" target="_blank" rel="noopener noreferrer" class="patreon-button">
<i class="fab fa-patreon"></i> {% trans "Subscribe on Patreon" %}
</a>
</div>
<div class="payment-method-card paypal">
<h3><i class="fab fa-paypal"></i> {% trans "PayPal Payment" %}</h3>
<p>{% trans "Complete one-time payment via PayPal" %}</p>
{% if paypal_me_url %}
<a href="{{ paypal_me_url }}" target="_blank" rel="noopener noreferrer" class="paypal-button">
<i class="fab fa-paypal"></i> {% trans "Pay with PayPal.me" %}
</a>
{% endif %}
</div>
</div>
<div class="info-box">
<h3>{% trans "How it works:" %}</h3>
<ul>
<li>{% trans "Install the plugin (already done)" %}</li>
<li>{% trans "Enter activation key, subscribe on Patreon, or pay via PayPal" %}</li>
<li>{% trans "The plugin will automatically unlock" %}</li>
</ul>
</div>
{% if error %}
<div style="background: #fee; border: 1px solid #fcc; border-radius: 8px; padding: 15px; margin-top: 20px; color: #c33;">
<strong>{% trans "Error:" %}</strong> {{ error }}
</div>
{% endif %}
</div>
</div>
<script>
document.addEventListener('DOMContentLoaded', function() {
const form = document.getElementById('activation-key-form');
const input = document.getElementById('activation-key-input');
const btn = document.getElementById('activate-btn');
const msg = document.getElementById('activation-message');
if (form) {
form.addEventListener('submit', async function(e) {
e.preventDefault();
const key = input.value.trim().toUpperCase();
if (!key) return;
btn.disabled = true;
btn.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Activating...';
try {
const res = await fetch('/plugins/paypalPremiumPlugin/activate-key/', {
method: 'POST',
headers: { 'Content-Type': 'application/json', 'X-CSRFToken': document.querySelector('input[name=csrfmiddlewaretoken]') ? document.querySelector('input[name=csrfmiddlewaretoken]').value : '' },
body: JSON.stringify({ activation_key: key })
});
const data = await res.json();
msg.textContent = data.success ? '✅ ' + data.message : '❌ ' + (data.message || 'Invalid key');
msg.style.display = 'block';
msg.style.background = data.success ? '#d4edda' : '#f8d7da';
msg.style.color = data.success ? '#155724' : '#721c24';
if (data.success) { input.value = ''; setTimeout(function() { location.reload(); }, 2000); }
} catch (err) {
msg.textContent = '❌ ' + err.message;
msg.style.display = 'block';
msg.style.background = '#f8d7da';
msg.style.color = '#721c24';
}
btn.disabled = false;
btn.innerHTML = '<i class="fas fa-check-circle"></i> Activate';
});
}
});
</script>
{% endblock %}

View File

@@ -1,12 +0,0 @@
from django.urls import path, re_path
from . import views
app_name = 'paypalPremiumPlugin'
urlpatterns = [
path('', views.main_view, name='main'),
path('settings/', views.settings_view, name='settings'),
re_path(r'^activate-key/$', views.activate_key, name='activate_key'),
path('save-payment-method/', views.save_payment_method, name='save_payment_method'),
path('api/status/', views.api_status_view, name='api_status'),
]

View File

@@ -1,387 +0,0 @@
# -*- coding: utf-8 -*-
"""
PayPal Premium Plugin Views - Unified Verification (same as contaboAutoSnapshot)
Supports: Plugin Grants, Activation Key, Patreon, PayPal, AES encryption
"""
from django.shortcuts import render, redirect
from django.http import JsonResponse, HttpResponse
from django.views.decorators.http import require_http_methods
from plogical.mailUtilities import mailUtilities
from plogical.httpProc import httpProc
from plogical.CyberCPLogFileWriter import CyberCPLogFileWriter as logging
from functools import wraps
import urllib.request
import urllib.error
import json
import time
from .models import PaypalPremiumPluginConfig
from . import api_encryption
PLUGIN_NAME = 'paypalPremiumPlugin'
PLUGIN_VERSION = '1.0.2'
REMOTE_VERIFICATION_PATREON_URL = 'https://api.newstargeted.com/api/verify-patreon-membership.php'
REMOTE_VERIFICATION_PAYPAL_URL = 'https://api.newstargeted.com/api/verify-paypal-payment.php'
REMOTE_VERIFICATION_PLUGIN_GRANT_URL = 'https://api.newstargeted.com/api/verify-plugin-grant.php'
REMOTE_ACTIVATION_KEY_URL = 'https://api.newstargeted.com/api/activate-plugin-key.php'
PATREON_TIER = 'CyberPanel Paid Plugin'
PATREON_URL = 'https://www.patreon.com/membership/27789984'
PAYPAL_ME_URL = 'https://paypal.me/KimBS?locale.x=en_US&country.x=NO'
PAYPAL_PAYMENT_LINK = ''
def cyberpanel_login_required(view_func):
@wraps(view_func)
def _wrapped_view(request, *args, **kwargs):
try:
if not request.session.get('userID'):
from loginSystem.views import loadLoginPage
return redirect(loadLoginPage)
return view_func(request, *args, **kwargs)
except KeyError:
from loginSystem.views import loadLoginPage
return redirect(loadLoginPage)
return _wrapped_view
def _api_request(url, data, timeout=10):
try:
body, extra_headers = api_encryption.encrypt_payload(data)
headers = {
'User-Agent': f'CyberPanel-Plugin/{PLUGIN_VERSION}',
'X-Plugin-Name': PLUGIN_NAME
}
headers.update(extra_headers)
req = urllib.request.Request(url, data=body, headers=headers)
with urllib.request.urlopen(req, timeout=timeout) as response:
raw = response.read()
ct = response.headers.get('Content-Type', '')
expect_enc = extra_headers.get('X-Encrypted') == '1'
return api_encryption.decrypt_response(raw, ct, expect_encrypted=expect_enc)
except Exception as e:
logging.writeToFile(f"PayPal Premium Plugin: API request error to {url}: {str(e)}")
return {}
def check_plugin_grant(user_email, user_ip='', domain=''):
try:
request_data = {
'user_email': user_email or '',
'plugin_name': PLUGIN_NAME,
'user_ip': user_ip,
'domain': domain,
}
data = _api_request(REMOTE_VERIFICATION_PLUGIN_GRANT_URL, request_data)
if data.get('success') and data.get('has_access'):
return {'has_access': True, 'message': data.get('message', 'Access granted via Plugin Grants')}
return {'has_access': False, 'message': data.get('message', '')}
except Exception as e:
logging.writeToFile(f"PayPal Premium Plugin: Plugin grant check error: {str(e)}")
return {'has_access': False, 'message': ''}
def check_patreon_membership(user_email, user_ip='', domain=''):
try:
request_data = {
'user_email': user_email,
'plugin_name': PLUGIN_NAME,
'plugin_version': PLUGIN_VERSION,
'user_ip': user_ip,
'domain': domain,
'tier_id': '27789984'
}
response_data = _api_request(REMOTE_VERIFICATION_PATREON_URL, request_data)
if response_data.get('success', False):
return {
'has_access': response_data.get('has_access', False),
'patreon_tier': response_data.get('patreon_tier', PATREON_TIER),
'patreon_url': response_data.get('patreon_url', PATREON_URL),
'message': response_data.get('message', 'Access granted'),
'error': None
}
return {
'has_access': False,
'patreon_tier': PATREON_TIER,
'patreon_url': PATREON_URL,
'message': response_data.get('message', 'Patreon subscription required'),
'error': response_data.get('error')
}
except Exception as e:
logging.writeToFile(f"PayPal Premium Plugin: Patreon check error: {str(e)}")
return {'has_access': False, 'patreon_tier': PATREON_TIER, 'patreon_url': PATREON_URL, 'message': 'Unable to verify Patreon.', 'error': str(e)}
def check_paypal_payment(user_email, user_ip='', domain=''):
try:
request_data = {
'user_email': user_email,
'plugin_name': PLUGIN_NAME,
'plugin_version': PLUGIN_VERSION,
'user_ip': user_ip,
'domain': domain,
'timestamp': int(time.time()),
}
response_data = _api_request(REMOTE_VERIFICATION_PAYPAL_URL, request_data)
if response_data.get('success', False):
return {
'has_access': response_data.get('has_access', False),
'paypal_me_url': response_data.get('paypal_me_url', PAYPAL_ME_URL),
'paypal_payment_link': response_data.get('paypal_payment_link', PAYPAL_PAYMENT_LINK),
'message': response_data.get('message', 'Access granted'),
'error': None
}
return {
'has_access': False,
'paypal_me_url': PAYPAL_ME_URL,
'paypal_payment_link': PAYPAL_PAYMENT_LINK,
'message': response_data.get('message', 'PayPal payment required'),
'error': response_data.get('error')
}
except Exception as e:
logging.writeToFile(f"PayPal Premium Plugin: PayPal check error: {str(e)}")
return {'has_access': False, 'paypal_me_url': PAYPAL_ME_URL, 'paypal_payment_link': PAYPAL_PAYMENT_LINK, 'message': 'Unable to verify PayPal.', 'error': str(e)}
def unified_verification_required(view_func):
@wraps(view_func)
def _wrapped_view(request, *args, **kwargs):
try:
if not request.session.get('userID'):
from loginSystem.views import loadLoginPage
return redirect(loadLoginPage)
user_email = request.session.get('email', '') or (getattr(request.user, 'email', '') if hasattr(request, 'user') and request.user else '') or getattr(request.user, 'username', '')
try:
config = PaypalPremiumPluginConfig.get_config()
payment_method = config.payment_method
except Exception:
payment_method = 'both'
has_access = False
verification_result = {}
activation_key = request.GET.get('activation_key') or request.POST.get('activation_key')
if not activation_key:
try:
config = PaypalPremiumPluginConfig.get_config()
activation_key = getattr(config, 'activation_key', '') or ''
except Exception:
activation_key = ''
if activation_key:
try:
request_data = {'activation_key': activation_key.strip(), 'plugin_name': PLUGIN_NAME, 'user_email': user_email}
response_data = _api_request(REMOTE_ACTIVATION_KEY_URL, request_data)
if response_data.get('success', False) and response_data.get('has_access', False):
has_access = True
verification_result = {'method': 'activation_key', 'has_access': True, 'message': response_data.get('message', 'Access activated via key')}
try:
config = PaypalPremiumPluginConfig.get_config()
config.activation_key = activation_key.strip()
config.save(update_fields=['activation_key', 'updated_at'])
except Exception as e:
logging.writeToFile(f"PayPal Premium Plugin: Could not persist activation key: {str(e)}")
elif not response_data.get('success') and activation_key:
try:
config = PaypalPremiumPluginConfig.get_config()
if getattr(config, 'activation_key', '') == activation_key.strip():
config.activation_key = ''
config.save(update_fields=['activation_key', 'updated_at'])
except Exception:
pass
except Exception as e:
logging.writeToFile(f"PayPal Premium Plugin: Activation key check error: {str(e)}")
if not has_access:
grant_result = check_plugin_grant(user_email, request.META.get('REMOTE_ADDR', ''), request.get_host())
if grant_result.get('has_access'):
has_access = True
verification_result = {'method': 'plugin_grant', 'has_access': True, 'message': grant_result.get('message', 'Access granted via Plugin Grants')}
if not has_access:
try:
if payment_method == 'patreon':
result = check_patreon_membership(user_email, request.META.get('REMOTE_ADDR', ''), request.get_host())
has_access = result.get('has_access', False)
verification_result = {
'method': 'patreon', 'has_access': has_access,
'patreon_tier': result.get('patreon_tier', PATREON_TIER),
'patreon_url': result.get('patreon_url', PATREON_URL),
'paypal_me_url': PAYPAL_ME_URL, 'paypal_payment_link': PAYPAL_PAYMENT_LINK,
'message': result.get('message', 'Patreon subscription required'),
'error': result.get('error')
}
elif payment_method == 'paypal':
result = check_paypal_payment(user_email, request.META.get('REMOTE_ADDR', ''), request.get_host())
has_access = result.get('has_access', False)
verification_result = {
'method': 'paypal', 'has_access': has_access,
'patreon_tier': PATREON_TIER, 'patreon_url': PATREON_URL,
'paypal_me_url': result.get('paypal_me_url', PAYPAL_ME_URL),
'paypal_payment_link': result.get('paypal_payment_link', PAYPAL_PAYMENT_LINK),
'message': result.get('message', 'PayPal payment required'),
'error': result.get('error')
}
else:
patreon_result = check_patreon_membership(user_email, request.META.get('REMOTE_ADDR', ''), request.get_host())
paypal_result = check_paypal_payment(user_email, request.META.get('REMOTE_ADDR', ''), request.get_host())
has_access = patreon_result.get('has_access', False) or paypal_result.get('has_access', False)
verification_result = {
'method': 'both', 'has_access': has_access,
'patreon_tier': patreon_result.get('patreon_tier', PATREON_TIER),
'patreon_url': patreon_result.get('patreon_url', PATREON_URL),
'paypal_me_url': paypal_result.get('paypal_me_url', PAYPAL_ME_URL),
'paypal_payment_link': paypal_result.get('paypal_payment_link', PAYPAL_PAYMENT_LINK),
'message': 'Payment or subscription required' if not has_access else 'Access granted'
}
except Exception as e:
logging.writeToFile(f"PayPal Premium Plugin: Verification error: {str(e)}")
has_access = False
verification_result = {
'method': payment_method, 'has_access': False,
'patreon_tier': PATREON_TIER, 'patreon_url': PATREON_URL,
'paypal_me_url': PAYPAL_ME_URL, 'paypal_payment_link': PAYPAL_PAYMENT_LINK,
'message': 'Unable to verify access.', 'error': str(e)
}
if not has_access:
context = {
'plugin_name': 'PayPal Premium Plugin Example',
'is_paid': True,
'payment_method': payment_method,
'verification_result': verification_result,
'patreon_tier': verification_result.get('patreon_tier', PATREON_TIER),
'patreon_url': verification_result.get('patreon_url', PATREON_URL),
'paypal_me_url': verification_result.get('paypal_me_url', PAYPAL_ME_URL),
'paypal_payment_link': verification_result.get('paypal_payment_link', PAYPAL_PAYMENT_LINK),
'message': verification_result.get('message', 'Payment or subscription required'),
'error': verification_result.get('error')
}
proc = httpProc(request, 'paypalPremiumPlugin/subscription_required.html', context, 'admin')
return proc.render()
if has_access and verification_result:
request.session['paypal_premium_access_via'] = verification_result.get('method', '')
return view_func(request, *args, **kwargs)
except Exception as e:
logging.writeToFile(f"PayPal Premium Plugin: Decorator error: {str(e)}")
return HttpResponse(f"<div style='padding: 20px;'><h2>Plugin Error</h2><p>{str(e)}</p></div>")
return _wrapped_view
@cyberpanel_login_required
def main_view(request):
mailUtilities.checkHome()
return redirect('paypalPremiumPlugin:settings')
@cyberpanel_login_required
@unified_verification_required
def settings_view(request):
mailUtilities.checkHome()
try:
config = PaypalPremiumPluginConfig.get_config()
except Exception:
from django.core.management import call_command
try:
call_command('migrate', 'paypalPremiumPlugin', verbosity=0, interactive=False)
config = PaypalPremiumPluginConfig.get_config()
except Exception as e:
return HttpResponse(f"<div style='padding: 20px;'><h2>Database Error</h2><p>{str(e)}</p></div>")
access_via = request.session.get('paypal_premium_access_via', '')
show_payment_ui = access_via not in ('plugin_grant', 'activation_key')
context = {
'plugin_name': 'PayPal Premium Plugin Example',
'version': PLUGIN_VERSION,
'status': 'Active',
'config': config,
'has_access': True,
'show_payment_ui': show_payment_ui,
'access_via_grant_or_key': not show_payment_ui,
'patreon_tier': PATREON_TIER,
'patreon_url': PATREON_URL,
'paypal_me_url': PAYPAL_ME_URL,
'paypal_payment_link': PAYPAL_PAYMENT_LINK,
'description': 'Configure your PayPal premium plugin settings.',
}
proc = httpProc(request, 'paypalPremiumPlugin/settings.html', context, 'admin')
return proc.render()
@cyberpanel_login_required
@require_http_methods(["POST"])
def activate_key(request):
try:
if request.content_type == 'application/json':
data = json.loads(request.body)
else:
data = request.POST
activation_key = data.get('activation_key', '').strip()
user_email = data.get('user_email', '').strip()
if not user_email:
user_email = request.session.get('email', '') or (getattr(request.user, 'email', '') if hasattr(request, 'user') and request.user else '')
if not activation_key:
return JsonResponse({'success': False, 'message': 'Activation key is required'}, status=400)
request_data = {'activation_key': activation_key, 'plugin_name': PLUGIN_NAME, 'user_email': user_email}
response_data = _api_request(REMOTE_ACTIVATION_KEY_URL, request_data)
if response_data.get('success', False) and response_data.get('has_access', False):
try:
config = PaypalPremiumPluginConfig.get_config()
config.activation_key = activation_key
config.save(update_fields=['activation_key', 'updated_at'])
except Exception as e:
logging.writeToFile(f"PayPal Premium Plugin: Could not persist activation key: {str(e)}")
return JsonResponse({
'success': True,
'has_access': True,
'message': response_data.get('message', 'Access activated successfully')
})
return JsonResponse({
'success': False,
'has_access': False,
'message': response_data.get('message', 'Invalid activation key')
})
except Exception as e:
logging.writeToFile(f"PayPal Premium Plugin: activate_key error: {str(e)}")
return JsonResponse({'success': False, 'message': str(e)}, status=500)
@cyberpanel_login_required
@require_http_methods(["POST"])
def save_payment_method(request):
try:
payment_method = request.POST.get('payment_method', 'both')
if payment_method not in ('patreon', 'paypal', 'both'):
payment_method = 'both'
config = PaypalPremiumPluginConfig.get_config()
config.payment_method = payment_method
config.save(update_fields=['payment_method', 'updated_at'])
return JsonResponse({'success': True, 'message': 'Payment method saved'})
except Exception as e:
return JsonResponse({'success': False, 'message': str(e)}, status=500)
@cyberpanel_login_required
@unified_verification_required
def api_status_view(request):
return JsonResponse({
'plugin_name': 'PayPal Premium Plugin Example',
'version': PLUGIN_VERSION,
'status': 'active',
'payment': 'verified',
'verification_method': 'unified'
})

View File

@@ -1,6 +0,0 @@
# Security - Never commit secrets
*.secret
*_secret*
patreon_config.py
.env.patreon
patreon_secrets.env

View File

@@ -1,81 +0,0 @@
# Remote Verification Setup
## Overview
This version of the plugin uses **remote verification** - all Patreon API calls happen on YOUR server, not the user's server.
## Benefits
**No secrets in plugin** - Users can see all plugin code, but no credentials
**Secure** - All Patreon API credentials stay on your server
**Centralized** - You control access, can revoke, update logic, etc.
**Public code** - Plugin code can be open source
## Architecture
```
User's Server Your Server Patreon API
| | |
|-- Verify Request ------------> | |
| |-- Check Membership --> |
| |<-- Membership Status - |
|<-- Access Granted/Denied ----- | |
```
## Setup
### 1. Deploy Verification API
Deploy the verification endpoint to your server:
- File: `/home/newstargeted.com/api.newstargeted.com/modules/patreon/verify-membership.php`
- URL: `https://api.newstargeted.com/api/verify-patreon-membership`
### 2. Configure Your Server
Add Patreon credentials to your server's `config.php`:
```php
define('PATREON_CLIENT_ID', 'your_client_id');
define('PATREON_CLIENT_SECRET', 'your_client_secret');
define('PATREON_CREATOR_ACCESS_TOKEN', 'your_access_token');
```
### 3. Update Plugin
Replace `views.py` with `views_remote.py`:
```bash
mv views.py views_local.py # Backup local version
mv views_remote.py views.py # Use remote version
```
### 4. Configure Plugin URL
Update `REMOTE_VERIFICATION_URL` in `views.py` to point to your server:
```python
REMOTE_VERIFICATION_URL = 'https://api.newstargeted.com/api/verify-patreon-membership'
```
## Security Features
- **Rate limiting** - Prevents abuse (60 requests/hour per IP)
- **HTTPS only** - All communication encrypted
- **No secrets** - Plugin only makes API calls
- **Caching** - Reduces Patreon API calls (5 min cache)
## Testing
1. Install plugin on user's server
2. Try accessing plugin (should show subscription required)
3. Subscribe to Patreon tier
4. Access plugin again (should work)
## Migration from Local Verification
If you were using local verification:
1. Keep `views_local.py` as backup
2. Use `views_remote.py` as `views.py`
3. Deploy verification API to your server
4. Update plugin URL in code

View File

@@ -1,58 +0,0 @@
# Premium Plugin Example
An example paid plugin for CyberPanel that demonstrates how to implement Patreon subscription-based plugin access.
## Features
- Requires Patreon subscription to "CyberPanel Paid Plugin" tier
- Users can install the plugin without subscription
- Plugin functionality is locked until subscription is verified
- Shows subscription required page when accessed without subscription
## Installation
1. Upload the plugin ZIP file to CyberPanel
2. Install the plugin from the plugin manager
3. The plugin will appear in the installed plugins list
## Usage
### For Users Without Subscription
- Plugin can be installed
- When accessing the plugin, a subscription required page is shown
- Link to Patreon subscription page is provided
### For Users With Subscription
- Plugin works normally
- All features are accessible
- Settings page is available
## Configuration
The plugin checks for Patreon membership via the Patreon API. Make sure to configure:
1. Patreon Client ID
2. Patreon Client Secret
3. Patreon Creator ID
These should be set in CyberPanel environment variables or settings.
## Meta.xml Structure
The plugin uses the following meta.xml structure for paid plugins:
```xml
<paid>true</paid>
<patreon_tier>CyberPanel Paid Plugin</patreon_tier>
<patreon_url>https://www.patreon.com/c/newstargeted/membership</patreon_url>
```
## Author
master3395
## License
MIT

View File

@@ -1,57 +0,0 @@
# Security Guidelines for Premium Plugin
## ⚠️ IMPORTANT: Never Expose Secrets
This plugin is designed to be **publicly shareable**. It contains **NO secrets** and is safe to publish.
## What's Safe to Share
**Safe to commit:**
- Plugin code (views.py, urls.py, etc.)
- Templates (HTML files)
- meta.xml (no secrets, only tier name and URL)
- README.md
- Documentation
**Never commit:**
- Patreon Client Secret
- Patreon Access Tokens
- Patreon Refresh Tokens
- Any hardcoded credentials
## Configuration
All Patreon credentials are configured on the **server side** via:
- Environment variables
- Django settings (from environment)
- Secure config files (not in repository)
## For Your Own Setup
When setting up this plugin on your server:
1. **Do NOT** modify plugin files with your secrets
2. **Do** configure environment variables on the server
3. **Do** use Django settings.py (with environment variable fallbacks)
4. **Do** add any secret config files to .gitignore
## Example Secure Configuration
```python
# In settings.py (safe to commit)
PATREON_CLIENT_ID = os.environ.get('PATREON_CLIENT_ID', '')
PATREON_CLIENT_SECRET = os.environ.get('PATREON_CLIENT_SECRET', '')
# On server (NOT in repo)
export PATREON_CLIENT_ID="your_actual_secret"
export PATREON_CLIENT_SECRET="your_actual_secret"
```
## Verification
Before publishing, verify:
- [ ] No secrets in plugin files
- [ ] No secrets in meta.xml
- [ ] No secrets in README
- [ ] All credentials use environment variables
- [ ] .gitignore excludes secret files

View File

@@ -1,4 +0,0 @@
# Premium Plugin Example
# This is a paid plugin that requires Patreon subscription
default_app_config = 'premiumPlugin.apps.PremiumPluginConfig'

View File

@@ -1,83 +0,0 @@
# -*- coding: utf-8 -*-
"""
AES-256-CBC encryption for plugin <-> api.newstargeted.com communication.
Key must match PLUGIN_VERIFICATION_CIPHER_KEY in config.php on the API server.
"""
import json
import base64
import os
CIPHER_KEY_B64 = '1VLPEKTmLGUbIxHUFEtsuVM2MPN1tl8HPFtyJc4dr58='
ENCRYPTION_ENABLED = True
_ENCRYPTION_CIPHER_KEY = None
def _get_key():
"""Get 32-byte AES key from base64."""
global _ENCRYPTION_CIPHER_KEY
if _ENCRYPTION_CIPHER_KEY is not None:
return _ENCRYPTION_CIPHER_KEY
try:
key = base64.b64decode(CIPHER_KEY_B64)
if len(key) == 32:
_ENCRYPTION_CIPHER_KEY = key
return key
except Exception:
pass
return None
def encrypt_payload(data):
"""Encrypt JSON payload for API request. Returns (body_bytes, headers_dict)."""
if not ENCRYPTION_ENABLED or not _get_key():
body = json.dumps(data, separators=(',', ':')).encode('utf-8')
return body, {'Content-Type': 'application/json'}
try:
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
from cryptography.hazmat.primitives import padding
from cryptography.hazmat.backends import default_backend
key = _get_key()
plain = json.dumps(data, separators=(',', ':')).encode('utf-8')
padder = padding.PKCS7(128).padder()
padded = padder.update(plain) + padder.finalize()
iv = os.urandom(16)
cipher = Cipher(algorithms.AES(key), modes.CBC(iv), backend=default_backend())
encryptor = cipher.encryptor()
ciphertext = encryptor.update(padded) + encryptor.finalize()
payload = base64.b64encode(iv).decode('ascii') + '.' + base64.b64encode(ciphertext).decode('ascii')
return payload.encode('utf-8'), {'Content-Type': 'text/plain', 'X-Encrypted': '1'}
except Exception:
body = json.dumps(data, separators=(',', ':')).encode('utf-8')
return body, {'Content-Type': 'application/json'}
def decrypt_response(body_bytes, content_type='', expect_encrypted=False):
"""Decrypt API response. Handles both encrypted and plain JSON."""
try:
body_str = body_bytes.decode('utf-8') if isinstance(body_bytes, bytes) else str(body_bytes)
is_encrypted = (
expect_encrypted or
('text/plain' in content_type and '.' in body_str) or
('.' in body_str and body_str.strip() and body_str.strip()[0] not in '{[')
)
parts = body_str.strip().split('.', 1)
if is_encrypted and len(parts) == 2 and ENCRYPTION_ENABLED and _get_key():
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
from cryptography.hazmat.primitives import padding
from cryptography.hazmat.backends import default_backend
iv = base64.b64decode(parts[0])
ciphertext = base64.b64decode(parts[1])
key = _get_key()
cipher = Cipher(algorithms.AES(key), modes.CBC(iv), backend=default_backend())
decryptor = cipher.decryptor()
padded = decryptor.update(ciphertext) + decryptor.finalize()
unpadder = padding.PKCS7(128).unpadder()
plain = unpadder.update(padded) + unpadder.finalize()
return json.loads(plain.decode('utf-8'))
return json.loads(body_str)
except Exception:
try:
return json.loads(body_bytes.decode('utf-8') if isinstance(body_bytes, bytes) else body_bytes)
except Exception:
return {}

View File

@@ -1,5 +0,0 @@
from django.apps import AppConfig
class PremiumPluginConfig(AppConfig):
name = 'premiumPlugin'
verbose_name = 'Premium Plugin Example'

View File

@@ -1,30 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<plugin>
<name>Premium Plugin Example</name>
<type>Utility</type>
<version>1.0.2</version>
<description>An example paid plugin that requires Patreon subscription to "CyberPanel Paid Plugin" tier. Users can install it but cannot run it without subscription.</description>
<author>master3395</author>
<website>https://github.com/master3395/cyberpanel-plugins</website>
<license>MIT</license>
<dependencies>
<python>3.6+</python>
<django>2.2+</django>
<cyberpanel>2.5.5+</cyberpanel>
</dependencies>
<compatibility>
<min_version>2.5.5</min_version>
<max_version>3.0.0</max_version>
</compatibility>
<permissions>
<admin>true</admin>
<user>false</user>
</permissions>
<paid>true</paid>
<patreon_tier>CyberPanel Paid Plugin</patreon_tier>
<patreon_url>https://www.patreon.com/membership/27789984</patreon_url>
<paypal_me_url>https://paypal.me/KimBS?locale.x=en_US&amp;country.x=NO</paypal_me_url>
<paypal_payment_link></paypal_payment_link>
<url>/plugins/premiumPlugin/</url>
<settings_url>/plugins/premiumPlugin/settings/</settings_url>
</plugin>

View File

@@ -1,27 +0,0 @@
# Generated migration for PremiumPluginConfig
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = []
operations = [
migrations.CreateModel(
name='PremiumPluginConfig',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('payment_method', models.CharField(choices=[('patreon', 'Patreon Subscription'), ('paypal', 'PayPal Payment'), ('both', 'Check Both (Patreon or PayPal)')], default='both', help_text='Choose which payment method to use for verification.', max_length=10)),
('activation_key', models.CharField(blank=True, default='', help_text='Validated activation key - grants access without re-entering.', max_length=64)),
('created_at', models.DateTimeField(auto_now_add=True)),
('updated_at', models.DateTimeField(auto_now=True)),
],
options={
'verbose_name': 'Premium Plugin Configuration',
'verbose_name_plural': 'Premium Plugin Configurations',
},
),
]

View File

@@ -1 +0,0 @@
# Premium Plugin migrations

View File

@@ -1,42 +0,0 @@
# -*- coding: utf-8 -*-
from django.db import models
class PremiumPluginConfig(models.Model):
"""Config for Premium Plugin - activation key and payment preference."""
PAYMENT_METHOD_CHOICES = [
('patreon', 'Patreon Subscription'),
('paypal', 'PayPal Payment'),
('both', 'Check Both (Patreon or PayPal)'),
]
payment_method = models.CharField(
max_length=10,
choices=PAYMENT_METHOD_CHOICES,
default='both',
help_text="Choose which payment method to use for verification."
)
activation_key = models.CharField(
max_length=64,
blank=True,
default='',
help_text="Validated activation key - grants access without re-entering."
)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
class Meta:
verbose_name = "Premium Plugin Configuration"
verbose_name_plural = "Premium Plugin Configurations"
def __str__(self):
return "Premium Plugin Configuration"
@classmethod
def get_config(cls):
"""Get or create the singleton config instance."""
config, _ = cls.objects.get_or_create(pk=1)
return config
def save(self, *args, **kwargs):
self.pk = 1
super().save(*args, **kwargs)

View File

@@ -1,96 +0,0 @@
{% extends "baseTemplate/index.html" %}
{% load i18n %}
{% block title %}{% trans "Premium Plugin Example - CyberPanel" %}{% endblock %}
{% block header_scripts %}
<style>
.premium-plugin-container {
padding: 20px;
max-width: 1200px;
margin: 0 auto;
}
.premium-badge {
display: inline-block;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
padding: 8px 16px;
border-radius: 20px;
font-size: 12px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.5px;
margin-bottom: 20px;
}
.plugin-header {
background: white;
border-radius: 12px;
padding: 30px;
margin-bottom: 25px;
box-shadow: 0 2px 8px rgba(0,0,0,0.08);
}
.plugin-header h1 {
margin: 0 0 10px 0;
color: #2f3640;
}
.features-list {
background: white;
border-radius: 12px;
padding: 30px;
box-shadow: 0 2px 8px rgba(0,0,0,0.08);
}
.features-list h2 {
margin-top: 0;
color: #2f3640;
}
.features-list ul {
list-style: none;
padding: 0;
}
.features-list li {
padding: 12px 0;
border-bottom: 1px solid #e8e9ff;
display: flex;
align-items: center;
gap: 12px;
}
.features-list li:last-child {
border-bottom: none;
}
.features-list li::before {
content: "✓";
color: #28a745;
font-weight: bold;
font-size: 18px;
}
</style>
{% endblock %}
{% block content %}
<div class="premium-plugin-container">
<div class="plugin-header">
<span class="premium-badge">{% trans "Premium Plugin" %}</span>
<h1>{{ plugin_name }}</h1>
<p>{{ description }}</p>
<p><strong>{% trans "Version:" %}</strong> {{ version }}</p>
</div>
<div class="features-list">
<h2>{% trans "Premium Features" %}</h2>
<ul>
{% for feature in features %}
<li>{{ feature }}</li>
{% endfor %}
</ul>
</div>
</div>
{% endblock %}

View File

@@ -1,315 +0,0 @@
{% extends "baseTemplate/index.html" %}
{% load i18n %}
{% block title %}{% trans "Premium Plugin Settings - CyberPanel" %}{% endblock %}
{% block header_scripts %}
<style>
.premium-plugin-container {
padding: 20px;
max-width: 1200px;
margin: 0 auto;
}
.premium-badge {
display: inline-block;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
padding: 8px 16px;
border-radius: 20px;
font-size: 12px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.5px;
margin-bottom: 20px;
}
.settings-form {
background: white;
border-radius: 12px;
padding: 30px;
box-shadow: 0 2px 8px rgba(0,0,0,0.08);
}
.subscription-warning {
background: #fff3cd;
border: 1px solid #ffc107;
border-radius: 8px;
padding: 15px 20px;
margin-bottom: 25px;
display: flex;
align-items: center;
justify-content: space-between;
flex-wrap: wrap;
gap: 15px;
}
.subscription-warning-content {
flex: 1;
min-width: 250px;
}
.subscription-warning-title {
font-weight: 600;
color: #856404;
margin-bottom: 5px;
font-size: 16px;
}
.subscription-warning-text {
color: #856404;
font-size: 14px;
margin: 0;
}
.subscription-warning-button {
background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
color: white;
border: none;
padding: 10px 20px;
border-radius: 6px;
font-weight: 600;
text-decoration: none;
display: inline-flex;
align-items: center;
gap: 8px;
transition: transform 0.2s, box-shadow 0.2s;
white-space: nowrap;
}
.subscription-warning-button:hover {
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(245, 87, 108, 0.4);
color: white;
text-decoration: none;
}
.settings-disabled {
opacity: 0.6;
pointer-events: none;
position: relative;
}
.settings-disabled::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(255, 255, 255, 0.7);
z-index: 1;
border-radius: 8px;
}
.form-group {
margin-bottom: 20px;
}
.form-group label {
display: block;
margin-bottom: 8px;
font-weight: 600;
color: #2f3640;
}
.form-control {
width: 100%;
padding: 10px 15px;
border: 1px solid #e0e0e0;
border-radius: 6px;
font-size: 14px;
transition: border-color 0.3s, box-shadow 0.3s;
}
.form-control:focus {
outline: none;
border-color: #5856d6;
box-shadow: 0 0 0 3px rgba(88, 86, 214, 0.1);
}
.form-control:disabled {
background-color: #f5f5f5;
cursor: not-allowed;
}
.btn {
padding: 10px 20px;
border: none;
border-radius: 6px;
font-weight: 600;
cursor: pointer;
transition: all 0.3s;
}
.btn-primary {
background: linear-gradient(135deg, #5856d6 0%, #4a90e2 100%);
color: white;
}
.btn-primary:hover:not(:disabled) {
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(88, 86, 214, 0.4);
}
.btn-primary:disabled {
background: #ccc;
cursor: not-allowed;
opacity: 0.6;
}
/* Plugin Information Section - Ensure visibility */
.plugin-info-section {
background: #d1ecf1 !important;
border: 1px solid #bee5eb !important;
border-radius: 8px !important;
padding: 15px 20px !important;
margin-bottom: 25px !important;
display: block !important;
visibility: visible !important;
opacity: 1 !important;
position: relative !important;
z-index: 10 !important;
}
.plugin-info-section ul {
list-style: none !important;
padding-left: 0 !important;
margin-top: 10px !important;
margin-bottom: 0 !important;
}
.plugin-info-section li {
margin-bottom: 8px;
}
.plugin-info-section li:last-child {
margin-bottom: 0;
}
</style>
{% endblock %}
{% block content %}
<div class="premium-plugin-container">
<div class="settings-form">
<span class="premium-badge">{% trans "Premium Plugin" %}</span>
<h1>{% trans "Premium Plugin Settings" %}</h1>
{% if show_payment_ui %}
<!-- Activate Premium Access -->
<div style="background: #e6f7ff; border: 2px solid #17a2b8; border-radius: 12px; padding: 20px; margin-bottom: 25px;">
<h3 style="color: #17a2b8; margin-top: 0;"><i class="fas fa-key"></i> {% trans "Activate Premium Access" %}</h3>
<p style="color: #495057;">{% trans "If you have an activation key, enter it below." %}</p>
<div id="activation-msg" style="display: none; margin-bottom: 10px; padding: 10px; border-radius: 6px;"></div>
<form id="activation-form" style="display: flex; gap: 10px; align-items: flex-end;">
{% csrf_token %}
<input type="text" id="activation-key" name="activation_key" placeholder="PREMIUMPLUGIN-XXXX-XXXX-XXXX" style="flex: 1; padding: 10px; border: 2px solid #17a2b8; border-radius: 6px; font-family: monospace; text-transform: uppercase;" />
<button type="submit" class="btn btn-primary" id="activate-btn"><i class="fas fa-check-circle"></i> {% trans "Activate" %}</button>
</form>
</div>
<!-- Payment Method Preference -->
<div style="margin-bottom: 25px;">
<label style="font-weight: 600;">{% trans "Payment Method Preference" %}</label>
<form id="payment-method-form" method="post" action="{% url 'premiumPlugin:save_payment_method' %}" style="display: inline-block; margin-left: 10px;">
{% csrf_token %}
<select name="payment_method" onchange="this.form.submit()" style="padding: 8px 12px; border-radius: 6px;">
<option value="both" {% if config.payment_method == 'both' %}selected{% endif %}>{% trans "Check Both (Patreon or PayPal)" %}</option>
<option value="patreon" {% if config.payment_method == 'patreon' %}selected{% endif %}>{% trans "Patreon Only" %}</option>
<option value="paypal" {% if config.payment_method == 'paypal' %}selected{% endif %}>{% trans "PayPal Only" %}</option>
</select>
</form>
</div>
{% else %}
<div style="background: #d4edda; border: 2px solid #28a745; border-radius: 12px; padding: 20px; margin-bottom: 25px;">
<i class="fas fa-check-circle" style="color: #28a745;"></i> <strong>{% trans "Premium Access Active" %}</strong> — {% trans "Access granted via Plugin Grants or activation key." %}
</div>
{% endif %}
<!-- Plugin Information Section - Always Visible -->
<div class="plugin-info-section" style="background: #d1ecf1 !important; border: 1px solid #bee5eb !important; border-radius: 8px !important; padding: 15px 20px !important; margin-bottom: 25px !important; display: block !important; visibility: visible !important; opacity: 1 !important;">
<i class="fas fa-info-circle" style="color: #0c5460; margin-right: 8px;"></i>
<strong style="color: #0c5460; font-weight: 600;">{% trans "Plugin Information" %}</strong>
<ul style="list-style: none !important; padding-left: 0 !important; margin-top: 10px !important; margin-bottom: 0 !important;">
<li style="margin-bottom: 8px;"><strong>{% trans "Name" %}:</strong> {{ plugin_name|default:"Premium Plugin Example" }}</li>
<li style="margin-bottom: 8px;"><strong>{% trans "Version" %}:</strong> {{ version|default:"1.0.0" }}</li>
<li style="margin-bottom: 0;">
<strong>{% trans "Status" %}:</strong>
<span class="badge" style="background: {% if has_access %}#28a745{% else %}#6c757d{% endif %} !important; color: white !important; padding: 4px 10px !important; border-radius: 4px !important; font-size: 12px !important; font-weight: 600 !important; display: inline-block !important;">
{{ plugin_status|default:status|default:"Active" }}
</span>
</li>
</ul>
</div>
<p>{{ description }}</p>
<form method="post" id="settings-form">
{% csrf_token %}
<div class="form-group">
<label>{% trans "Setting 1" %}</label>
<input type="text" class="form-control" name="setting1" value="" placeholder="{% trans 'Enter setting value' %}">
</div>
<div class="form-group">
<label>{% trans "Setting 2" %}</label>
<input type="text" class="form-control" name="setting2" value="" placeholder="{% trans 'Enter setting value' %}">
</div>
<div class="form-group">
<label>{% trans "Setting 3 (Advanced)" %}</label>
<textarea class="form-control" name="setting3" rows="4" placeholder="{% trans 'Enter advanced configuration' %}"></textarea>
</div>
<div class="form-group">
<label>
<input type="checkbox" name="enable_feature">
{% trans "Enable Premium Feature" %}
</label>
</div>
<button type="submit" class="btn btn-primary">
<i class="fas fa-save"></i> {% trans "Save Settings" %}
</button>
</form>
{% if show_payment_ui %}
<script>
document.addEventListener('DOMContentLoaded', function() {
const form = document.getElementById('activation-form');
if (form) {
form.addEventListener('submit', async function(e) {
e.preventDefault();
const key = document.getElementById('activation-key').value.trim().toUpperCase();
if (!key) return;
const btn = document.getElementById('activate-btn');
const msg = document.getElementById('activation-msg');
btn.disabled = true;
btn.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Activating...';
try {
const res = await fetch('/plugins/premiumPlugin/activate-key/', {
method: 'POST',
headers: { 'Content-Type': 'application/json', 'X-CSRFToken': form.querySelector('[name=csrfmiddlewaretoken]').value },
body: JSON.stringify({ activation_key: key })
});
const data = await res.json();
msg.textContent = data.success ? '✅ ' + data.message : '❌ ' + (data.message || 'Invalid key');
msg.style.display = 'block';
msg.style.background = data.success ? '#d4edda' : '#f8d7da';
msg.style.color = data.success ? '#155724' : '#721c24';
if (data.success) { document.getElementById('activation-key').value = ''; setTimeout(function() { location.reload(); }, 2000); }
} catch (err) {
msg.textContent = '❌ ' + err.message;
msg.style.display = 'block';
msg.style.background = '#f8d7da';
msg.style.color = '#721c24';
}
btn.disabled = false;
btn.innerHTML = '<i class="fas fa-check-circle"></i> Activate';
});
}
});
</script>
{% endif %}
</div>
</div>
{% endblock %}

View File

@@ -1,139 +0,0 @@
{% extends "baseTemplate/index.html" %}
{% load i18n %}
{% block title %}{% trans "Payment Required - Premium Plugin" %}{% endblock %}
{% block header_scripts %}
<style>
.subscription-required-container { padding: 40px 20px; max-width: 900px; margin: 0 auto; text-align: center; }
.subscription-card { background: white; border-radius: 12px; padding: 40px; box-shadow: 0 4px 16px rgba(0,0,0,0.1); }
.premium-icon { font-size: 64px; color: #FF6B35; margin-bottom: 20px; }
.subscription-card h1 { color: #2f3640; margin-bottom: 15px; }
.subscription-card p { color: #64748b; font-size: 16px; line-height: 1.6; margin-bottom: 30px; }
.payment-methods { display: flex; gap: 20px; justify-content: center; flex-wrap: wrap; margin: 30px 0; }
.payment-method-card { flex: 1; min-width: 250px; max-width: 350px; background: #f8f9ff; border: 2px solid #e2e8f0; border-radius: 12px; padding: 25px; text-align: center; }
.payment-method-card.patreon { border-color: #FF424D; }
.payment-method-card.paypal { border-color: #0070ba; }
.payment-method-card h3 { margin-top: 0; color: #2f3640; font-size: 1.3rem; }
.patreon-button, .paypal-button { display: inline-block; color: white; padding: 15px 30px; border-radius: 8px; text-decoration: none; font-weight: 600; font-size: 16px; transition: all 0.3s ease; width: 100%; box-sizing: border-box; margin-bottom: 10px; }
.patreon-button { background: #FF424D; }
.patreon-button:hover { background: #E62E37; color: white; text-decoration: none; }
.paypal-button { background: #0070ba; }
.paypal-button:hover { background: #003087; color: white; text-decoration: none; }
.info-box { background: #f8f9ff; border-left: 4px solid #FF6B35; padding: 20px; border-radius: 8px; margin-top: 30px; text-align: left; }
.payment-method-selector { background: #fff3cd; border: 1px solid #ffc107; border-radius: 8px; padding: 15px; margin-bottom: 30px; text-align: left; }
</style>
{% endblock %}
{% block content %}
<div class="subscription-required-container">
<div class="subscription-card">
<div class="premium-icon"><i class="fas fa-crown"></i></div>
<h1>{% trans "Premium Plugin Access Required" %}</h1>
<p>{% trans "This plugin requires payment or subscription to access premium features." %}</p>
<!-- Activation Key Section -->
<div style="background: linear-gradient(135deg, #e6f7ff 0%, #f0f9ff 100%); border: 2px solid #17a2b8; border-radius: 12px; padding: 25px; margin: 30px 0; text-align: left;">
<h3 style="color: #17a2b8; margin-top: 0;"><i class="fas fa-key"></i> {% trans "Activate Premium Access" %}</h3>
<p style="color: #495057; margin-bottom: 20px;">{% trans "If you received an activation key, enter it below." %}</p>
<div id="activation-message" style="display: none; margin-bottom: 15px; padding: 12px; border-radius: 6px;"></div>
<form id="activation-key-form" style="display: flex; gap: 10px; align-items: flex-end; max-width: 600px;">
{% csrf_token %}
<div style="flex: 1;">
<label for="activation-key-input" style="font-weight: 600; display: block; margin-bottom: 8px;">{% trans "Activation Key" %}</label>
<input type="text" id="activation-key-input" name="activation_key" placeholder="PREMIUMPLUGIN-XXXX-XXXX-XXXX" style="width: 100%; padding: 12px; border: 2px solid #17a2b8; border-radius: 6px; font-family: monospace; text-transform: uppercase;" required />
</div>
<button type="submit" style="background: #17a2b8; color: white; border: none; padding: 12px 24px; border-radius: 6px; font-weight: 600; cursor: pointer;" id="activate-btn">
<i class="fas fa-check-circle"></i> {% trans "Activate" %}
</button>
</form>
</div>
{% if payment_method == 'both' %}
<div class="payment-method-selector">
<strong>{% trans "Current Payment Method:" %}</strong> {% trans "Check Both (Patreon or PayPal)" %}
</div>
{% elif payment_method == 'patreon' %}
<div class="payment-method-selector">
<strong>{% trans "Current Payment Method:" %}</strong> {% trans "Patreon Subscription Only" %}
</div>
{% elif payment_method == 'paypal' %}
<div class="payment-method-selector">
<strong>{% trans "Current Payment Method:" %}</strong> {% trans "PayPal Payment Only" %}
</div>
{% endif %}
<div class="payment-methods">
<div class="payment-method-card patreon">
<h3><i class="fab fa-patreon"></i> {% trans "Patreon Subscription" %}</h3>
<p>{% trans "Subscribe to" %} <strong>"{{ patreon_tier }}"</strong></p>
<a href="{{ patreon_url }}" target="_blank" rel="noopener noreferrer" class="patreon-button">
<i class="fab fa-patreon"></i> {% trans "Subscribe on Patreon" %}
</a>
</div>
<div class="payment-method-card paypal">
<h3><i class="fab fa-paypal"></i> {% trans "PayPal Payment" %}</h3>
<p>{% trans "Complete one-time payment via PayPal" %}</p>
{% if paypal_me_url %}
<a href="{{ paypal_me_url }}" target="_blank" rel="noopener noreferrer" class="paypal-button">
<i class="fab fa-paypal"></i> {% trans "Pay with PayPal.me" %}
</a>
{% endif %}
</div>
</div>
<div class="info-box">
<h3>{% trans "How it works:" %}</h3>
<ul>
<li>{% trans "Install the plugin (already done)" %}</li>
<li>{% trans "Enter activation key, subscribe on Patreon, or pay via PayPal" %}</li>
<li>{% trans "The plugin will automatically unlock" %}</li>
</ul>
</div>
{% if error %}
<div style="background: #fee; border: 1px solid #fcc; border-radius: 8px; padding: 15px; margin-top: 20px; color: #c33;">
<strong>{% trans "Error:" %}</strong> {{ error }}
</div>
{% endif %}
</div>
</div>
<script>
document.addEventListener('DOMContentLoaded', function() {
const form = document.getElementById('activation-key-form');
const input = document.getElementById('activation-key-input');
const btn = document.getElementById('activate-btn');
const msg = document.getElementById('activation-message');
if (form) {
form.addEventListener('submit', async function(e) {
e.preventDefault();
const key = input.value.trim().toUpperCase();
if (!key) return;
btn.disabled = true;
btn.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Activating...';
try {
const res = await fetch('/plugins/premiumPlugin/activate-key/', {
method: 'POST',
headers: { 'Content-Type': 'application/json', 'X-CSRFToken': document.querySelector('input[name=csrfmiddlewaretoken]') ? document.querySelector('input[name=csrfmiddlewaretoken]').value : (document.cookie.match(/csrftoken=([^;]+)/) ? document.cookie.match(/csrftoken=([^;]+)/)[1].trim() : '') },
body: JSON.stringify({ activation_key: key })
});
const data = await res.json();
msg.textContent = data.success ? '✅ ' + data.message : '❌ ' + (data.message || 'Invalid key');
msg.style.display = 'block';
msg.style.background = data.success ? '#d4edda' : '#f8d7da';
msg.style.color = data.success ? '#155724' : '#721c24';
if (data.success) { input.value = ''; setTimeout(() => location.reload(), 2000); }
} catch (err) {
msg.textContent = '❌ ' + err.message;
msg.style.display = 'block';
msg.style.background = '#f8d7da';
msg.style.color = '#721c24';
}
btn.disabled = false;
btn.innerHTML = '<i class="fas fa-check-circle"></i> Activate';
});
}
});
</script>
{% endblock %}

View File

@@ -1,12 +0,0 @@
from django.urls import path, re_path
from . import views
app_name = 'premiumPlugin'
urlpatterns = [
path('', views.main_view, name='main'),
path('settings/', views.settings_view, name='settings'),
re_path(r'^activate-key/$', views.activate_key, name='activate_key'),
path('save-payment-method/', views.save_payment_method, name='save_payment_method'),
path('api/status/', views.api_status_view, name='api_status'),
]

View File

@@ -1,406 +0,0 @@
# -*- coding: utf-8 -*-
"""
Premium Plugin Views - Unified Verification (same as contaboAutoSnapshot)
Supports: Plugin Grants, Activation Key, Patreon, PayPal, AES encryption
"""
from django.shortcuts import render, redirect
from django.http import JsonResponse, HttpResponse
from django.views.decorators.http import require_http_methods
from plogical.mailUtilities import mailUtilities
from plogical.httpProc import httpProc
from plogical.CyberCPLogFileWriter import CyberCPLogFileWriter as logging
from functools import wraps
import urllib.request
import urllib.error
import json
from .models import PremiumPluginConfig
from . import api_encryption
PLUGIN_NAME = 'premiumPlugin'
PLUGIN_VERSION = '1.0.2'
REMOTE_VERIFICATION_PATREON_URL = 'https://api.newstargeted.com/api/verify-patreon-membership.php'
REMOTE_VERIFICATION_PAYPAL_URL = 'https://api.newstargeted.com/api/verify-paypal-payment.php'
REMOTE_VERIFICATION_PLUGIN_GRANT_URL = 'https://api.newstargeted.com/api/verify-plugin-grant.php'
REMOTE_ACTIVATION_KEY_URL = 'https://api.newstargeted.com/api/activate-plugin-key.php'
PATREON_TIER = 'CyberPanel Paid Plugin'
PATREON_URL = 'https://www.patreon.com/membership/27789984'
PAYPAL_ME_URL = 'https://paypal.me/KimBS?locale.x=en_US&country.x=NO'
PAYPAL_PAYMENT_LINK = ''
def cyberpanel_login_required(view_func):
@wraps(view_func)
def _wrapped_view(request, *args, **kwargs):
try:
if not request.session.get('userID'):
from loginSystem.views import loadLoginPage
return redirect(loadLoginPage)
return view_func(request, *args, **kwargs)
except KeyError:
from loginSystem.views import loadLoginPage
return redirect(loadLoginPage)
return _wrapped_view
def _api_request(url, data, timeout=10):
"""Send encrypted API request and return decoded response dict."""
try:
body, extra_headers = api_encryption.encrypt_payload(data)
headers = {
'User-Agent': f'CyberPanel-Plugin/{PLUGIN_VERSION}',
'X-Plugin-Name': PLUGIN_NAME
}
headers.update(extra_headers)
req = urllib.request.Request(url, data=body, headers=headers)
with urllib.request.urlopen(req, timeout=timeout) as response:
raw = response.read()
ct = response.headers.get('Content-Type', '')
expect_enc = extra_headers.get('X-Encrypted') == '1'
return api_encryption.decrypt_response(raw, ct, expect_encrypted=expect_enc)
except Exception as e:
logging.writeToFile(f"Premium Plugin: API request error to {url}: {str(e)}")
return {}
def check_plugin_grant(user_email, user_ip='', domain=''):
try:
request_data = {
'user_email': user_email or '',
'plugin_name': PLUGIN_NAME,
'user_ip': user_ip,
'domain': domain,
}
data = _api_request(REMOTE_VERIFICATION_PLUGIN_GRANT_URL, request_data)
if data.get('success') and data.get('has_access'):
return {'has_access': True, 'message': data.get('message', 'Access granted via Plugin Grants')}
return {'has_access': False, 'message': data.get('message', '')}
except Exception as e:
logging.writeToFile(f"Premium Plugin: Plugin grant check error: {str(e)}")
return {'has_access': False, 'message': ''}
def check_patreon_membership(user_email, user_ip='', domain=''):
try:
request_data = {
'user_email': user_email,
'plugin_name': PLUGIN_NAME,
'plugin_version': PLUGIN_VERSION,
'user_ip': user_ip,
'domain': domain,
'tier_id': '27789984'
}
response_data = _api_request(REMOTE_VERIFICATION_PATREON_URL, request_data)
if response_data.get('success', False):
return {
'has_access': response_data.get('has_access', False),
'patreon_tier': response_data.get('patreon_tier', PATREON_TIER),
'patreon_url': response_data.get('patreon_url', PATREON_URL),
'message': response_data.get('message', 'Access granted'),
'error': None
}
return {
'has_access': False,
'patreon_tier': PATREON_TIER,
'patreon_url': PATREON_URL,
'message': response_data.get('message', 'Patreon subscription required'),
'error': response_data.get('error')
}
except Exception as e:
logging.writeToFile(f"Premium Plugin: Patreon check error: {str(e)}")
return {
'has_access': False,
'patreon_tier': PATREON_TIER,
'patreon_url': PATREON_URL,
'message': 'Unable to verify Patreon membership.',
'error': str(e)
}
def check_paypal_payment(user_email, user_ip='', domain=''):
try:
request_data = {
'user_email': user_email,
'plugin_name': PLUGIN_NAME,
'plugin_version': PLUGIN_VERSION,
'user_ip': user_ip,
'domain': domain,
'timestamp': 0,
}
import time
request_data['timestamp'] = int(time.time())
response_data = _api_request(REMOTE_VERIFICATION_PAYPAL_URL, request_data)
if response_data.get('success', False):
return {
'has_access': response_data.get('has_access', False),
'paypal_me_url': response_data.get('paypal_me_url', PAYPAL_ME_URL),
'paypal_payment_link': response_data.get('paypal_payment_link', PAYPAL_PAYMENT_LINK),
'message': response_data.get('message', 'Access granted'),
'error': None
}
return {
'has_access': False,
'paypal_me_url': PAYPAL_ME_URL,
'paypal_payment_link': PAYPAL_PAYMENT_LINK,
'message': response_data.get('message', 'PayPal payment required'),
'error': response_data.get('error')
}
except Exception as e:
logging.writeToFile(f"Premium Plugin: PayPal check error: {str(e)}")
return {
'has_access': False,
'paypal_me_url': PAYPAL_ME_URL,
'paypal_payment_link': PAYPAL_PAYMENT_LINK,
'message': 'Unable to verify PayPal payment.',
'error': str(e)
}
def unified_verification_required(view_func):
@wraps(view_func)
def _wrapped_view(request, *args, **kwargs):
try:
if not request.session.get('userID'):
from loginSystem.views import loadLoginPage
return redirect(loadLoginPage)
user_email = request.session.get('email', '') or (getattr(request.user, 'email', '') if hasattr(request, 'user') and request.user else '') or getattr(request.user, 'username', '')
try:
config = PremiumPluginConfig.get_config()
payment_method = config.payment_method
except Exception:
payment_method = 'both'
has_access = False
verification_result = {}
activation_key = request.GET.get('activation_key') or request.POST.get('activation_key')
if not activation_key:
try:
config = PremiumPluginConfig.get_config()
activation_key = getattr(config, 'activation_key', '') or ''
except Exception:
activation_key = ''
if activation_key:
try:
request_data = {
'activation_key': activation_key.strip(),
'plugin_name': PLUGIN_NAME,
'user_email': user_email
}
response_data = _api_request(REMOTE_ACTIVATION_KEY_URL, request_data)
if response_data.get('success', False) and response_data.get('has_access', False):
has_access = True
verification_result = {'method': 'activation_key', 'has_access': True, 'message': response_data.get('message', 'Access activated via key')}
try:
config = PremiumPluginConfig.get_config()
config.activation_key = activation_key.strip()
config.save(update_fields=['activation_key', 'updated_at'])
except Exception as e:
logging.writeToFile(f"Premium Plugin: Could not persist activation key: {str(e)}")
elif not response_data.get('success') and activation_key:
try:
config = PremiumPluginConfig.get_config()
if getattr(config, 'activation_key', '') == activation_key.strip():
config.activation_key = ''
config.save(update_fields=['activation_key', 'updated_at'])
except Exception:
pass
except Exception as e:
logging.writeToFile(f"Premium Plugin: Activation key check error: {str(e)}")
if not has_access:
grant_result = check_plugin_grant(user_email, request.META.get('REMOTE_ADDR', ''), request.get_host())
if grant_result.get('has_access'):
has_access = True
verification_result = {'method': 'plugin_grant', 'has_access': True, 'message': grant_result.get('message', 'Access granted via Plugin Grants')}
if not has_access:
try:
if payment_method == 'patreon':
result = check_patreon_membership(user_email, request.META.get('REMOTE_ADDR', ''), request.get_host())
has_access = result.get('has_access', False)
verification_result = {
'method': 'patreon', 'has_access': has_access,
'patreon_tier': result.get('patreon_tier', PATREON_TIER),
'patreon_url': result.get('patreon_url', PATREON_URL),
'paypal_me_url': PAYPAL_ME_URL, 'paypal_payment_link': PAYPAL_PAYMENT_LINK,
'message': result.get('message', 'Patreon subscription required'),
'error': result.get('error')
}
elif payment_method == 'paypal':
result = check_paypal_payment(user_email, request.META.get('REMOTE_ADDR', ''), request.get_host())
has_access = result.get('has_access', False)
verification_result = {
'method': 'paypal', 'has_access': has_access,
'patreon_tier': PATREON_TIER, 'patreon_url': PATREON_URL,
'paypal_me_url': result.get('paypal_me_url', PAYPAL_ME_URL),
'paypal_payment_link': result.get('paypal_payment_link', PAYPAL_PAYMENT_LINK),
'message': result.get('message', 'PayPal payment required'),
'error': result.get('error')
}
else:
patreon_result = check_patreon_membership(user_email, request.META.get('REMOTE_ADDR', ''), request.get_host())
paypal_result = check_paypal_payment(user_email, request.META.get('REMOTE_ADDR', ''), request.get_host())
has_access = patreon_result.get('has_access', False) or paypal_result.get('has_access', False)
verification_result = {
'method': 'both', 'has_access': has_access,
'patreon_tier': patreon_result.get('patreon_tier', PATREON_TIER),
'patreon_url': patreon_result.get('patreon_url', PATREON_URL),
'paypal_me_url': paypal_result.get('paypal_me_url', PAYPAL_ME_URL),
'paypal_payment_link': paypal_result.get('paypal_payment_link', PAYPAL_PAYMENT_LINK),
'message': 'Payment or subscription required' if not has_access else 'Access granted'
}
except Exception as e:
logging.writeToFile(f"Premium Plugin: Verification error: {str(e)}")
has_access = False
verification_result = {
'method': payment_method, 'has_access': False,
'patreon_tier': PATREON_TIER, 'patreon_url': PATREON_URL,
'paypal_me_url': PAYPAL_ME_URL, 'paypal_payment_link': PAYPAL_PAYMENT_LINK,
'message': 'Unable to verify access.',
'error': str(e)
}
if not has_access:
context = {
'plugin_name': 'Premium Plugin Example',
'is_paid': True,
'payment_method': payment_method,
'verification_result': verification_result,
'patreon_tier': verification_result.get('patreon_tier', PATREON_TIER),
'patreon_url': verification_result.get('patreon_url', PATREON_URL),
'paypal_me_url': verification_result.get('paypal_me_url', PAYPAL_ME_URL),
'paypal_payment_link': verification_result.get('paypal_payment_link', PAYPAL_PAYMENT_LINK),
'message': verification_result.get('message', 'Payment or subscription required'),
'error': verification_result.get('error')
}
proc = httpProc(request, 'premiumPlugin/subscription_required.html', context, 'admin')
return proc.render()
if has_access and verification_result:
request.session['premium_plugin_access_via'] = verification_result.get('method', '')
return view_func(request, *args, **kwargs)
except Exception as e:
logging.writeToFile(f"Premium Plugin: Decorator error: {str(e)}")
return HttpResponse(f"<div style='padding: 20px;'><h2>Plugin Error</h2><p>{str(e)}</p></div>")
return _wrapped_view
@cyberpanel_login_required
def main_view(request):
mailUtilities.checkHome()
return redirect('premiumPlugin:settings')
@cyberpanel_login_required
@unified_verification_required
def settings_view(request):
mailUtilities.checkHome()
try:
config = PremiumPluginConfig.get_config()
except Exception:
from django.core.management import call_command
try:
call_command('migrate', 'premiumPlugin', verbosity=0, interactive=False)
config = PremiumPluginConfig.get_config()
except Exception as e:
return HttpResponse(f"<div style='padding: 20px;'><h2>Database Error</h2><p>{str(e)}</p></div>")
access_via = request.session.get('premium_plugin_access_via', '')
show_payment_ui = access_via not in ('plugin_grant', 'activation_key')
context = {
'plugin_name': 'Premium Plugin Example',
'version': PLUGIN_VERSION,
'status': 'Active',
'config': config,
'has_access': True,
'show_payment_ui': show_payment_ui,
'access_via_grant_or_key': not show_payment_ui,
'patreon_tier': PATREON_TIER,
'patreon_url': PATREON_URL,
'paypal_me_url': PAYPAL_ME_URL,
'paypal_payment_link': PAYPAL_PAYMENT_LINK,
'description': 'Configure your premium plugin settings.',
}
proc = httpProc(request, 'premiumPlugin/settings.html', context, 'admin')
return proc.render()
@cyberpanel_login_required
@require_http_methods(["POST"])
def activate_key(request):
try:
if request.content_type == 'application/json':
data = json.loads(request.body)
else:
data = request.POST
activation_key = data.get('activation_key', '').strip()
user_email = data.get('user_email', '').strip()
if not user_email:
user_email = request.session.get('email', '') or (getattr(request.user, 'email', '') if hasattr(request, 'user') and request.user else '')
if not activation_key:
return JsonResponse({'success': False, 'message': 'Activation key is required'}, status=400)
request_data = {'activation_key': activation_key, 'plugin_name': PLUGIN_NAME, 'user_email': user_email}
response_data = _api_request(REMOTE_ACTIVATION_KEY_URL, request_data)
if response_data.get('success', False) and response_data.get('has_access', False):
try:
config = PremiumPluginConfig.get_config()
config.activation_key = activation_key
config.save(update_fields=['activation_key', 'updated_at'])
except Exception as e:
logging.writeToFile(f"Premium Plugin: Could not persist activation key: {str(e)}")
return JsonResponse({
'success': True,
'has_access': True,
'message': response_data.get('message', 'Access activated successfully')
})
return JsonResponse({
'success': False,
'has_access': False,
'message': response_data.get('message', 'Invalid activation key')
})
except Exception as e:
logging.writeToFile(f"Premium Plugin: activate_key error: {str(e)}")
return JsonResponse({'success': False, 'message': str(e)}, status=500)
@cyberpanel_login_required
@require_http_methods(["POST"])
def save_payment_method(request):
try:
payment_method = request.POST.get('payment_method', 'both')
if payment_method not in ('patreon', 'paypal', 'both'):
payment_method = 'both'
config = PremiumPluginConfig.get_config()
config.payment_method = payment_method
config.save(update_fields=['payment_method', 'updated_at'])
return JsonResponse({'success': True, 'message': 'Payment method saved'})
except Exception as e:
return JsonResponse({'success': False, 'message': str(e)}, status=500)
@cyberpanel_login_required
@unified_verification_required
def api_status_view(request):
return JsonResponse({
'plugin_name': 'Premium Plugin Example',
'version': PLUGIN_VERSION,
'status': 'active',
'subscription': 'active',
'verification_method': 'unified'
})

View File

@@ -1,236 +0,0 @@
# -*- coding: utf-8 -*-
"""
Premium Plugin Views - Remote Verification Version
This version uses remote server verification (no secrets in plugin)
"""
from django.shortcuts import render, redirect
from django.http import JsonResponse
from plogical.mailUtilities import mailUtilities
from plogical.httpProc import httpProc
from functools import wraps
import sys
import os
import urllib.request
import urllib.error
import json
# Remote verification server (YOUR server, not user's server)
REMOTE_VERIFICATION_URL = 'https://api.newstargeted.com/api/verify-patreon-membership'
PLUGIN_NAME = 'premiumPlugin'
PLUGIN_VERSION = '1.0.0'
def cyberpanel_login_required(view_func):
"""
Custom decorator that checks for CyberPanel session userID
"""
@wraps(view_func)
def _wrapped_view(request, *args, **kwargs):
try:
userID = request.session['userID']
# User is authenticated via CyberPanel session
return view_func(request, *args, **kwargs)
except KeyError:
# Not logged in, redirect to login
from loginSystem.views import loadLoginPage
return redirect(loadLoginPage)
return _wrapped_view
def remote_verification_required(view_func):
"""
Decorator that checks Patreon membership via remote server
No secrets stored in plugin - all verification happens on your server
"""
@wraps(view_func)
def _wrapped_view(request, *args, **kwargs):
# First check login
try:
userID = request.session['userID']
except KeyError:
from loginSystem.views import loadLoginPage
return redirect(loadLoginPage)
# Get user email
user_email = getattr(request.user, 'email', None) if hasattr(request, 'user') and request.user else None
if not user_email:
# Try to get from session or username
user_email = request.session.get('email', '') or getattr(request.user, 'username', '')
# Check membership via remote server
verification_result = check_remote_membership(user_email, request.META.get('REMOTE_ADDR', ''))
if not verification_result.get('has_access', False):
# User doesn't have subscription - show subscription required page
context = {
'plugin_name': 'Premium Plugin Example',
'is_paid': True,
'patreon_tier': verification_result.get('patreon_tier', 'CyberPanel Paid Plugin'),
'patreon_url': verification_result.get('patreon_url', 'https://www.patreon.com/c/newstargeted/membership'),
'message': verification_result.get('message', 'Patreon subscription required'),
'error': verification_result.get('error')
}
proc = httpProc(request, 'premiumPlugin/subscription_required.html', context, 'admin')
return proc.render()
# User has access - proceed with view
return view_func(request, *args, **kwargs)
return _wrapped_view
def check_remote_membership(user_email, user_ip=''):
"""
Check Patreon membership via remote verification server
Args:
user_email: User's email address
user_ip: User's IP address (for logging/security)
Returns:
dict: {
'has_access': bool,
'patreon_tier': str,
'patreon_url': str,
'message': str,
'error': str or None
}
"""
try:
# Prepare request data
request_data = {
'user_email': user_email,
'plugin_name': PLUGIN_NAME,
'plugin_version': PLUGIN_VERSION,
'user_ip': user_ip,
'tier_id': '27789984' # CyberPanel Paid Plugin tier ID
}
# Make request to remote verification server
req = urllib.request.Request(
REMOTE_VERIFICATION_URL,
data=json.dumps(request_data).encode('utf-8'),
headers={
'Content-Type': 'application/json',
'User-Agent': f'CyberPanel-Plugin/{PLUGIN_VERSION}',
'X-Plugin-Name': PLUGIN_NAME
}
)
# Send request with timeout
try:
with urllib.request.urlopen(req, timeout=10) as response:
response_data = json.loads(response.read().decode('utf-8'))
if response_data.get('success', False):
return {
'has_access': response_data.get('has_access', False),
'patreon_tier': response_data.get('patreon_tier', 'CyberPanel Paid Plugin'),
'patreon_url': response_data.get('patreon_url', 'https://www.patreon.com/c/newstargeted/membership'),
'message': response_data.get('message', 'Access granted'),
'error': None
}
else:
return {
'has_access': False,
'patreon_tier': response_data.get('patreon_tier', 'CyberPanel Paid Plugin'),
'patreon_url': response_data.get('patreon_url', 'https://www.patreon.com/c/newstargeted/membership'),
'message': response_data.get('message', 'Patreon subscription required'),
'error': response_data.get('error')
}
except urllib.error.HTTPError as e:
# Server returned error
error_body = e.read().decode('utf-8') if e.fp else 'Unknown error'
return {
'has_access': False,
'patreon_tier': 'CyberPanel Paid Plugin',
'patreon_url': 'https://www.patreon.com/c/newstargeted/membership',
'message': 'Unable to verify subscription. Please try again later.',
'error': f'HTTP {e.code}: {error_body}'
}
except urllib.error.URLError as e:
# Network error
return {
'has_access': False,
'patreon_tier': 'CyberPanel Paid Plugin',
'patreon_url': 'https://www.patreon.com/c/newstargeted/membership',
'message': 'Unable to connect to verification server. Please check your internet connection.',
'error': str(e.reason) if hasattr(e, 'reason') else str(e)
}
except Exception as e:
# Other errors
return {
'has_access': False,
'patreon_tier': 'CyberPanel Paid Plugin',
'patreon_url': 'https://www.patreon.com/c/newstargeted/membership',
'message': 'Verification error occurred. Please try again later.',
'error': str(e)
}
except Exception as e:
import logging
logging.writeToFile(f"Error in remote membership check: {str(e)}")
return {
'has_access': False,
'patreon_tier': 'CyberPanel Paid Plugin',
'patreon_url': 'https://www.patreon.com/c/newstargeted/membership',
'message': 'Verification error occurred. Please try again later.',
'error': str(e)
}
@cyberpanel_login_required
@remote_verification_required
def main_view(request):
"""
Main view for premium plugin
Only accessible with Patreon subscription (verified remotely)
"""
mailUtilities.checkHome()
context = {
'plugin_name': 'Premium Plugin Example',
'version': PLUGIN_VERSION,
'description': 'This is an example paid plugin. You have access because you are subscribed to Patreon!',
'features': [
'Premium Feature 1',
'Premium Feature 2',
'Premium Feature 3',
'Advanced Configuration',
'Priority Support'
]
}
proc = httpProc(request, 'premiumPlugin/index.html', context, 'admin')
return proc.render()
@cyberpanel_login_required
@remote_verification_required
def settings_view(request):
"""
Settings page for premium plugin
Only accessible with Patreon subscription (verified remotely)
"""
mailUtilities.checkHome()
context = {
'plugin_name': 'Premium Plugin Example',
'version': PLUGIN_VERSION,
'description': 'Configure your premium plugin settings'
}
proc = httpProc(request, 'premiumPlugin/settings.html', context, 'admin')
return proc.render()
@cyberpanel_login_required
@remote_verification_required
def api_status_view(request):
"""
API endpoint for plugin status
Only accessible with Patreon subscription (verified remotely)
"""
return JsonResponse({
'plugin_name': 'Premium Plugin Example',
'version': PLUGIN_VERSION,
'status': 'active',
'subscription': 'active',
'description': 'Premium plugin is active and accessible',
'verification_method': 'remote'
})

View File

@@ -1,464 +0,0 @@
# OS Compatibility Guide - CyberPanel Test Plugin
## 🌐 Supported Operating Systems
The CyberPanel Test Plugin is designed to work seamlessly across all CyberPanel-supported operating systems with comprehensive multi-OS compatibility.
### ✅ Currently Supported OS
| Operating System | Version | Support Status | Python Version | Package Manager | Service Manager |
|------------------|---------|----------------|----------------|-----------------|-----------------|
| **Ubuntu** | 22.04 | ✅ Full Support | 3.10+ | apt-get | systemctl |
| **Ubuntu** | 20.04 | ✅ Full Support | 3.8+ | apt-get | systemctl |
| **Debian** | 13 | ✅ Full Support | 3.11+ | apt-get | systemctl |
| **Debian** | 12 | ✅ Full Support | 3.10+ | apt-get | systemctl |
| **Debian** | 11 | ✅ Full Support | 3.9+ | apt-get | systemctl |
| **AlmaLinux** | 10 | ✅ Full Support | 3.11+ | dnf | systemctl |
| **AlmaLinux** | 9 | ✅ Full Support | 3.9+ | dnf | systemctl |
| **AlmaLinux** | 8 | ✅ Full Support | 3.6+ | dnf/yum | systemctl |
| **RockyLinux** | 9 | ✅ Full Support | 3.9+ | dnf | systemctl |
| **RockyLinux** | 8 | ✅ Full Support | 3.6+ | dnf | systemctl |
| **RHEL** | 9 | ✅ Full Support | 3.9+ | dnf | systemctl |
| **RHEL** | 8 | ✅ Full Support | 3.6+ | dnf | systemctl |
| **CloudLinux** | 8 | ✅ Full Support | 3.6+ | yum | systemctl |
| **CentOS** | 9 | ✅ Full Support | 3.9+ | dnf | systemctl |
### 🔧 Third-Party OS Support
| Operating System | Compatibility | Notes |
|------------------|---------------|-------|
| **Fedora** | ✅ Compatible | Uses dnf package manager |
| **openEuler** | ⚠️ Limited | Community-supported, limited testing |
| **Other RHEL derivatives** | ⚠️ Limited | May work with AlmaLinux/RockyLinux packages |
## 🚀 Installation Compatibility
### Automatic OS Detection
The installation script automatically detects your operating system and configures the plugin accordingly:
```bash
# The script automatically detects:
# - OS name and version
# - Python executable path
# - Package manager (apt-get, dnf, yum)
# - Service manager (systemctl, service)
# - Web server (apache2, httpd)
```
### OS-Specific Configurations
#### Ubuntu/Debian Systems
```bash
# Package Manager: apt-get
# Python: python3
# Pip: pip3
# Service Manager: systemctl
# Web Server: apache2
# User/Group: cyberpanel:cyberpanel
```
#### RHEL-based Systems (AlmaLinux, RockyLinux, RHEL, CentOS)
```bash
# Package Manager: dnf (RHEL 8+) / yum (RHEL 7)
# Python: python3
# Pip: pip3
# Service Manager: systemctl
# Web Server: httpd
# User/Group: cyberpanel:cyberpanel
```
#### CloudLinux
```bash
# Package Manager: yum
# Python: python3
# Pip: pip3
# Service Manager: systemctl
# Web Server: httpd
# User/Group: cyberpanel:cyberpanel
```
## 🐍 Python Compatibility
### Supported Python Versions
| Python Version | Ubuntu 22.04 | Ubuntu 20.04 | AlmaLinux 9 | AlmaLinux 8 | RockyLinux 9 | RockyLinux 8 | RHEL 9 | RHEL 8 | CloudLinux 8 |
|----------------|--------------|--------------|-------------|-------------|--------------|--------------|-------|-------|--------------|
| **3.6** | ❌ | ❌ | ❌ | ✅ | ❌ | ✅ | ❌ | ✅ | ✅ |
| **3.7** | ❌ | ❌ | ❌ | ✅ | ❌ | ✅ | ❌ | ✅ | ✅ |
| **3.8** | ❌ | ✅ | ❌ | ✅ | ❌ | ✅ | ❌ | ✅ | ✅ |
| **3.9** | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
| **3.10** | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
| **3.11** | ✅ | ❌ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
| **3.12** | ✅ | ❌ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
### Python Path Detection
The plugin automatically detects the correct Python executable:
```python
# Detection order:
1. python3.12
2. python3.11
3. python3.10
4. python3.9
5. python3.8
6. python3.7
7. python3.6
8. python3
9. python (fallback)
```
## 📦 Package Manager Compatibility
### Ubuntu/Debian (apt-get)
```bash
# Required packages
apt-get update
apt-get install -y python3 python3-pip python3-venv git curl
apt-get install -y build-essential python3-dev
# Python packages
pip3 install Django>=2.2,<4.0 django-cors-headers Pillow requests psutil
```
### RHEL-based (dnf/yum)
```bash
# RHEL 8+ (dnf)
dnf install -y python3 python3-pip python3-devel git curl
dnf install -y gcc gcc-c++ make
# RHEL 7 (yum)
yum install -y python3 python3-pip python3-devel git curl
yum install -y gcc gcc-c++ make
# Python packages
pip3 install Django>=2.2,<4.0 django-cors-headers Pillow requests psutil
```
### CloudLinux (yum)
```bash
# Required packages
yum install -y python3 python3-pip python3-devel git curl
yum install -y gcc gcc-c++ make
# Python packages
pip3 install Django>=2.2,<4.0 django-cors-headers Pillow requests psutil
```
## 🔧 Service Management Compatibility
### systemd (All supported OS)
```bash
# Service management commands
systemctl start lscpd
systemctl restart lscpd
systemctl status lscpd
systemctl enable lscpd
# Web server management
systemctl start apache2 # Ubuntu/Debian
systemctl start httpd # RHEL-based
systemctl restart apache2 # Ubuntu/Debian
systemctl restart httpd # RHEL-based
```
### Legacy init.d (Fallback)
```bash
# Service management commands
service lscpd start
service lscpd restart
service lscpd status
# Web server management
service apache2 start # Ubuntu/Debian
service httpd start # RHEL-based
```
## 🌐 Web Server Compatibility
### Apache2 (Ubuntu/Debian)
```bash
# Configuration paths
/etc/apache2/apache2.conf
/etc/apache2/sites-available/
/etc/apache2/sites-enabled/
# Service management
systemctl start apache2
systemctl restart apache2
systemctl status apache2
```
### HTTPD (RHEL-based)
```bash
# Configuration paths
/etc/httpd/conf/httpd.conf
/etc/httpd/conf.d/
# Service management
systemctl start httpd
systemctl restart httpd
systemctl status httpd
```
## 🔐 Security Compatibility
### SELinux (RHEL-based systems)
```bash
# Check SELinux status
sestatus
# Set proper context for plugin files
setsebool -P httpd_can_network_connect 1
chcon -R -t httpd_exec_t /usr/local/CyberCP/testPlugin/
```
### AppArmor (Ubuntu/Debian)
```bash
# Check AppArmor status
aa-status
# Allow Apache to access plugin files
aa-complain apache2
```
### Firewall Compatibility
```bash
# Ubuntu/Debian (ufw)
ufw allow 8090/tcp
ufw allow 80/tcp
ufw allow 443/tcp
# RHEL-based (firewalld)
firewall-cmd --permanent --add-port=8090/tcp
firewall-cmd --permanent --add-port=80/tcp
firewall-cmd --permanent --add-port=443/tcp
firewall-cmd --reload
# iptables (legacy)
iptables -A INPUT -p tcp --dport 8090 -j ACCEPT
iptables -A INPUT -p tcp --dport 80 -j ACCEPT
iptables -A INPUT -p tcp --dport 443 -j ACCEPT
```
## 🧪 Testing Compatibility
### Run Compatibility Test
```bash
# Navigate to plugin directory
cd /usr/local/CyberCP/testPlugin
# Run compatibility test
python3 test_os_compatibility.py
# Or make it executable and run
chmod +x test_os_compatibility.py
./test_os_compatibility.py
```
### Test Results
The compatibility test checks:
- ✅ OS detection and version
- ✅ Python installation and version
- ✅ Package manager availability
- ✅ Service manager functionality
- ✅ Web server configuration
- ✅ File permissions and ownership
- ✅ Network connectivity
- ✅ CyberPanel integration
### Sample Output
```
🔍 Testing OS Compatibility for CyberPanel Test Plugin
============================================================
📋 Testing OS Detection...
✅ OS: ubuntu 22.04 (x86_64)
✅ Supported: True
🐍 Testing Python Detection...
✅ Python: Python 3.10.12
✅ Path: /usr/bin/python3
✅ Pip: /usr/bin/pip3
✅ Compatible: True
📦 Testing Package Manager Detection...
✅ Package Manager: apt-get
✅ Available: True
🔧 Testing Service Manager Detection...
✅ Service Manager: systemctl
✅ Web Server: apache2
✅ Available: True
🌐 Testing Web Server Detection...
✅ Web Server: apache2
✅ Installed: True
🔐 Testing File Permissions...
✅ Plugin Directory: /home/cyberpanel/plugins
✅ CyberPanel Directory: /usr/local/CyberCP
🌍 Testing Network Connectivity...
✅ GitHub: True
✅ Internet: True
⚡ Testing CyberPanel Integration...
✅ CyberPanel Installed: True
✅ Settings File: True
✅ URLs File: True
✅ LSCPD Service: True
============================================================
📊 COMPATIBILITY TEST RESULTS
============================================================
Total Tests: 8
✅ Passed: 8
⚠️ Warnings: 0
❌ Failed: 0
🎉 All tests passed! The plugin is compatible with this OS.
```
## 🚨 Troubleshooting
### Common Issues by OS
#### Ubuntu/Debian Issues
```bash
# Python not found
sudo apt-get update
sudo apt-get install -y python3 python3-pip
# Permission denied
sudo chown -R cyberpanel:cyberpanel /home/cyberpanel/plugins
sudo chown -R cyberpanel:cyberpanel /usr/local/CyberCP/testPlugin
# Service not starting
sudo systemctl daemon-reload
sudo systemctl restart lscpd
```
#### RHEL-based Issues
```bash
# Python not found
sudo dnf install -y python3 python3-pip
# or
sudo yum install -y python3 python3-pip
# SELinux issues
sudo setsebool -P httpd_can_network_connect 1
sudo chcon -R -t httpd_exec_t /usr/local/CyberCP/testPlugin/
# Permission denied
sudo chown -R cyberpanel:cyberpanel /home/cyberpanel/plugins
sudo chown -R cyberpanel:cyberpanel /usr/local/CyberCP/testPlugin
```
#### CloudLinux Issues
```bash
# Python not found
sudo yum install -y python3 python3-pip
# CageFS issues
cagefsctl --enable cyberpanel
cagefsctl --update
# Permission denied
sudo chown -R cyberpanel:cyberpanel /home/cyberpanel/plugins
sudo chown -R cyberpanel:cyberpanel /usr/local/CyberCP/testPlugin
```
### Debug Commands
```bash
# Check OS information
cat /etc/os-release
uname -a
# Check Python installation
python3 --version
which python3
which pip3
# Check services
systemctl status lscpd
systemctl status apache2 # Ubuntu/Debian
systemctl status httpd # RHEL-based
# Check file permissions
ls -la /home/cyberpanel/plugins/
ls -la /usr/local/CyberCP/testPlugin/
# Check CyberPanel logs
tail -f /home/cyberpanel/logs/cyberpanel.log
tail -f /home/cyberpanel/logs/django.log
```
## 📋 Installation Checklist
### Pre-Installation
- [ ] Verify OS is supported
- [ ] Check Python 3.6+ is installed
- [ ] Ensure CyberPanel is installed and running
- [ ] Verify internet connectivity
- [ ] Check available disk space (minimum 100MB)
### Installation
- [ ] Download installation script
- [ ] Run as root user
- [ ] Monitor installation output
- [ ] Verify plugin files are created
- [ ] Check Django settings are updated
- [ ] Confirm URL configuration is added
### Post-Installation
- [ ] Test plugin access via web interface
- [ ] Verify all features work correctly
- [ ] Check security settings
- [ ] Run compatibility test
- [ ] Review installation logs
## 🔄 Updates and Maintenance
### Updating the Plugin
```bash
# Navigate to plugin directory
cd /usr/local/CyberCP/testPlugin
# Pull latest changes
git pull origin main
# Restart services
sudo systemctl restart lscpd
sudo systemctl restart apache2 # Ubuntu/Debian
sudo systemctl restart httpd # RHEL-based
```
### Uninstalling the Plugin
```bash
# Run uninstall script
sudo ./install.sh --uninstall
# Or manually remove
sudo rm -rf /usr/local/CyberCP/testPlugin
sudo rm -f /home/cyberpanel/plugins/testPlugin
```
## 📞 Support
### OS-Specific Support
- **Ubuntu/Debian**: Check Ubuntu/Debian documentation
- **RHEL-based**: Check Red Hat documentation
- **CloudLinux**: Check CloudLinux documentation
### Plugin Support
- **GitHub Issues**: https://github.com/cyberpanel/testPlugin/issues
- **CyberPanel Forums**: https://forums.cyberpanel.net/
- **Documentation**: https://cyberpanel.net/docs/
---
**Last Updated**: September 2025
**Compatibility Version**: 1.0.0
**Next Review**: March 2026

View File

@@ -1,247 +0,0 @@
# Security Implementation - CyberPanel Test Plugin
## 🔒 Security Overview
The CyberPanel Test Plugin has been designed with **enterprise-grade security** as the top priority. This document outlines all security measures implemented to protect against common web application vulnerabilities and attacks.
## 🛡️ Security Features Implemented
### 1. Authentication & Authorization
- **Admin-only access** required for all plugin functions
- **User session validation** on every request
- **Privilege escalation protection**
- **Role-based access control** (RBAC)
### 2. Rate Limiting & Brute Force Protection
- **50 requests per 5-minute window** per user
- **10 test button clicks per minute** limit
- **Automatic lockout** after 5 failed attempts
- **15-minute lockout duration**
- **Progressive punishment system**
### 3. CSRF Protection
- **HMAC-based CSRF token validation**
- **Token expiration** after 1 hour
- **User-specific token generation**
- **Secure token verification**
### 4. Input Validation & Sanitization
- **Regex-based input validation**
- **XSS attack prevention**
- **SQL injection prevention**
- **Path traversal protection**
- **Maximum input length limits** (1000 characters)
- **Character whitelisting**
### 5. Security Monitoring & Logging
- **All security events logged** with IP and user agent
- **Failed attempt tracking** and alerting
- **Suspicious activity detection**
- **Real-time security event monitoring**
- **Comprehensive audit trail**
### 6. HTTP Security Headers
- **X-Frame-Options: DENY** (clickjacking protection)
- **X-Content-Type-Options: nosniff**
- **X-XSS-Protection: 1; mode=block**
- **Content-Security-Policy (CSP)**
- **Strict-Transport-Security (HSTS)**
- **Referrer-Policy: strict-origin-when-cross-origin**
- **Permissions-Policy**
### 7. Data Isolation & Privacy
- **User-specific data isolation**
- **Logs restricted** to user's own activities
- **Settings isolated** per user
- **No cross-user data access**
## 🔍 Security Middleware
The plugin includes a comprehensive security middleware that performs:
### Request Analysis
- **Suspicious pattern detection**
- **SQL injection attempt detection**
- **XSS attempt detection**
- **Path traversal attempt detection**
- **Malicious payload identification**
### Response Protection
- **Security headers injection**
- **Content Security Policy enforcement**
- **Clickjacking protection**
- **MIME type sniffing prevention**
## 🚨 Attack Prevention
### OWASP Top 10 Protection
1. **A01: Broken Access Control** ✅ Protected
2. **A02: Cryptographic Failures** ✅ Protected
3. **A03: Injection** ✅ Protected
4. **A04: Insecure Design** ✅ Protected
5. **A05: Security Misconfiguration** ✅ Protected
6. **A06: Vulnerable Components** ✅ Protected
7. **A07: Authentication Failures** ✅ Protected
8. **A08: Software Integrity Failures** ✅ Protected
9. **A09: Logging Failures** ✅ Protected
10. **A10: Server-Side Request Forgery** ✅ Protected
### Specific Attack Vectors Blocked
- **SQL Injection** - Regex pattern matching + parameterized queries
- **Cross-Site Scripting (XSS)** - Input sanitization + CSP headers
- **Cross-Site Request Forgery (CSRF)** - HMAC token validation
- **Brute Force Attacks** - Rate limiting + account lockout
- **Path Traversal** - Pattern detection + input validation
- **Clickjacking** - X-Frame-Options header
- **Session Hijacking** - Secure session management
- **Privilege Escalation** - Role-based access control
## 📊 Security Metrics
- **15+ Security Features** implemented
- **99% Attack Prevention** rate
- **24/7 Security Monitoring** active
- **0 Known Vulnerabilities** in current version
- **Enterprise-grade** security standards
## 🔧 Security Configuration
### Rate Limiting Settings
```python
RATE_LIMIT_WINDOW = 300 # 5 minutes
MAX_REQUESTS_PER_WINDOW = 50
MAX_FAILED_ATTEMPTS = 5
LOCKOUT_DURATION = 900 # 15 minutes
```
### Input Validation Settings
```python
SAFE_STRING_PATTERN = re.compile(r'^[a-zA-Z0-9\s\-_.,!?@#$%^&*()+=\[\]{}|\\:";\'<>?/~`]*$')
MAX_MESSAGE_LENGTH = 1000
```
### CSRF Token Settings
```python
TOKEN_EXPIRATION = 3600 # 1 hour
HMAC_ALGORITHM = 'sha256'
```
## 🚀 Security Best Practices
### For Developers
1. **Always validate input** before processing
2. **Use parameterized queries** for database operations
3. **Implement proper error handling** without information disclosure
4. **Log security events** for monitoring
5. **Keep dependencies updated**
6. **Use HTTPS** in production
7. **Implement proper session management**
### For Administrators
1. **Keep CyberPanel updated**
2. **Use strong, unique passwords**
3. **Enable 2FA** on admin accounts
4. **Regularly review security logs**
5. **Monitor failed login attempts**
6. **Use HTTPS** in production environments
7. **Regular security audits**
## 🔍 Security Monitoring
### Logged Events
- **Authentication attempts** (successful and failed)
- **Authorization failures**
- **Rate limit violations**
- **Suspicious request patterns**
- **Input validation failures**
- **Security policy violations**
- **System errors and exceptions**
### Monitoring Dashboard
Access the security information page at: `/testPlugin/security/`
## 🛠️ Security Testing
### Automated Tests
- **Unit tests** for all security functions
- **Integration tests** for security middleware
- **Penetration testing** scenarios
- **Vulnerability scanning**
### Manual Testing
- **OWASP ZAP** security testing
- **Burp Suite** penetration testing
- **Manual security review**
- **Code security audit**
## 📋 Security Checklist
- [x] Authentication implemented
- [x] Authorization implemented
- [x] CSRF protection enabled
- [x] Rate limiting configured
- [x] Input validation active
- [x] XSS protection enabled
- [x] SQL injection protection
- [x] Security headers configured
- [x] Logging implemented
- [x] Error handling secure
- [x] Session management secure
- [x] Data isolation implemented
- [x] Security monitoring active
## 🚨 Incident Response
### Security Incident Procedure
1. **Immediate Response**
- Block suspicious IP addresses
- Review security logs
- Assess impact
2. **Investigation**
- Analyze attack vectors
- Identify compromised accounts
- Document findings
3. **Recovery**
- Patch vulnerabilities
- Reset compromised accounts
- Update security measures
4. **Post-Incident**
- Review security policies
- Update monitoring rules
- Conduct security training
## 📞 Security Contact
For security-related issues or vulnerability reports:
- **Email**: security@cyberpanel.net
- **GitHub**: Create a private security issue
- **Response Time**: Within 24-48 hours
## 🔄 Security Updates
Security is an ongoing process. Regular updates include:
- **Security patches** for vulnerabilities
- **Enhanced monitoring** capabilities
- **Improved detection** algorithms
- **Updated security policies**
- **New protection mechanisms**
## 📚 Additional Resources
- [OWASP Top 10](https://owasp.org/www-project-top-ten/)
- [Django Security](https://docs.djangoproject.com/en/stable/topics/security/)
- [CyberPanel Security](https://cyberpanel.net/docs/)
- [Web Application Security](https://cheatsheetseries.owasp.org/)
---
**Security Note**: This plugin implements enterprise-grade security measures. However, security is an ongoing process. Regular updates and monitoring are essential to maintain the highest security standards.
**Last Updated**: December 2024
**Security Version**: 1.0.0
**Next Review**: March 2025

View File

@@ -1,2 +0,0 @@
# -*- coding: utf-8 -*-
default_app_config = 'testPlugin.apps.TestPluginConfig'

View File

@@ -1,20 +0,0 @@
# -*- coding: utf-8 -*-
from django.contrib import admin
from .models import TestPluginSettings, TestPluginLog
@admin.register(TestPluginSettings)
class TestPluginSettingsAdmin(admin.ModelAdmin):
list_display = ['user', 'plugin_enabled', 'test_count', 'last_test_time']
list_filter = ['plugin_enabled', 'last_test_time']
search_fields = ['user__username', 'custom_message']
readonly_fields = ['last_test_time']
@admin.register(TestPluginLog)
class TestPluginLogAdmin(admin.ModelAdmin):
list_display = ['timestamp', 'action', 'message', 'user']
list_filter = ['action', 'timestamp', 'user']
search_fields = ['action', 'message', 'user__username']
readonly_fields = ['timestamp']
date_hierarchy = 'timestamp'

View File

@@ -1,11 +0,0 @@
# -*- coding: utf-8 -*-
from django.apps import AppConfig
class TestPluginConfig(AppConfig):
name = 'testPlugin'
verbose_name = 'Test Plugin'
def ready(self):
# Import signal handlers
import testPlugin.signals

View File

@@ -1,580 +0,0 @@
#!/bin/bash
# Test Plugin Installation Script for CyberPanel
# Multi-OS Compatible Installation Script
# Supports: Ubuntu, Debian, AlmaLinux, RockyLinux, RHEL, CloudLinux, CentOS
set -e
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
# Configuration
PLUGIN_NAME="testPlugin"
PLUGIN_DIR="/home/cyberpanel/plugins"
CYBERPANEL_DIR="/usr/local/CyberCP"
GITHUB_REPO="https://github.com/cyberpanel/testPlugin.git"
TEMP_DIR="/tmp/cyberpanel_plugin_install"
# OS Detection Variables
OS_NAME=""
OS_VERSION=""
OS_ARCH=""
PYTHON_CMD=""
PIP_CMD=""
SERVICE_CMD=""
WEB_SERVER=""
# Function to print colored output
print_status() {
echo -e "${BLUE}[INFO]${NC} $1"
}
print_success() {
echo -e "${GREEN}[SUCCESS]${NC} $1"
}
print_warning() {
echo -e "${YELLOW}[WARNING]${NC} $1"
}
print_error() {
echo -e "${RED}[ERROR]${NC} $1"
}
# Function to detect operating system
detect_os() {
print_status "Detecting operating system..."
if [ -f /etc/os-release ]; then
. /etc/os-release
OS_NAME="$ID"
OS_VERSION="$VERSION_ID"
elif [ -f /etc/redhat-release ]; then
OS_NAME="rhel"
OS_VERSION=$(cat /etc/redhat-release | grep -oE '[0-9]+\.[0-9]+' | head -1)
elif [ -f /etc/debian_version ]; then
OS_NAME="debian"
OS_VERSION=$(cat /etc/debian_version)
else
print_error "Unable to detect operating system"
exit 1
fi
# Detect architecture
OS_ARCH=$(uname -m)
print_success "Detected: $OS_NAME $OS_VERSION ($OS_ARCH)"
# Set OS-specific configurations
configure_os_specific
}
# Function to configure OS-specific settings
configure_os_specific() {
case "$OS_NAME" in
"ubuntu"|"debian")
PYTHON_CMD="python3"
PIP_CMD="pip3"
SERVICE_CMD="systemctl"
WEB_SERVER="apache2"
;;
"almalinux"|"rocky"|"rhel"|"centos"|"cloudlinux")
PYTHON_CMD="python3"
PIP_CMD="pip3"
SERVICE_CMD="systemctl"
WEB_SERVER="httpd"
;;
*)
print_warning "Unknown OS: $OS_NAME. Using default configurations."
PYTHON_CMD="python3"
PIP_CMD="pip3"
SERVICE_CMD="systemctl"
WEB_SERVER="httpd"
;;
esac
print_status "Using Python: $PYTHON_CMD"
print_status "Using Pip: $PIP_CMD"
print_status "Using Service Manager: $SERVICE_CMD"
print_status "Using Web Server: $WEB_SERVER"
}
# Function to check if running as root
check_root() {
if [[ $EUID -ne 0 ]]; then
print_error "This script must be run as root"
exit 1
fi
}
# Function to check if CyberPanel is installed
check_cyberpanel() {
if [ ! -d "$CYBERPANEL_DIR" ]; then
print_error "CyberPanel is not installed at $CYBERPANEL_DIR"
print_error "Please install CyberPanel first: https://cyberpanel.net/docs/"
exit 1
fi
# Check if CyberPanel is running
if ! $SERVICE_CMD is-active --quiet lscpd; then
print_warning "CyberPanel service (lscpd) is not running. Starting it..."
$SERVICE_CMD start lscpd
fi
print_success "CyberPanel installation verified"
}
# Function to check Python installation
check_python() {
print_status "Checking Python installation..."
if ! command -v $PYTHON_CMD &> /dev/null; then
print_error "Python3 is not installed. Installing..."
install_python
fi
# Check Python version (require 3.6+)
PYTHON_VERSION=$($PYTHON_CMD -c "import sys; print(f'{sys.version_info.major}.{sys.version_info.minor}')")
PYTHON_MAJOR=$(echo $PYTHON_VERSION | cut -d. -f1)
PYTHON_MINOR=$(echo $PYTHON_VERSION | cut -d. -f2)
if [ "$PYTHON_MAJOR" -lt 3 ] || ([ "$PYTHON_MAJOR" -eq 3 ] && [ "$PYTHON_MINOR" -lt 6 ]); then
print_error "Python 3.6+ is required. Found: $PYTHON_VERSION"
exit 1
fi
print_success "Python $PYTHON_VERSION is available"
}
# Function to install Python if needed
install_python() {
case "$OS_NAME" in
"ubuntu"|"debian")
apt-get update
apt-get install -y python3 python3-pip python3-venv
;;
"almalinux"|"rocky"|"rhel"|"centos"|"cloudlinux")
if command -v dnf &> /dev/null; then
dnf install -y python3 python3-pip
elif command -v yum &> /dev/null; then
yum install -y python3 python3-pip
else
print_error "No package manager found (dnf/yum)"
exit 1
fi
;;
esac
}
# Function to check pip installation
check_pip() {
print_status "Checking pip installation..."
if ! command -v $PIP_CMD &> /dev/null; then
print_error "pip3 is not installed. Installing..."
install_pip
fi
print_success "pip3 is available"
}
# Function to install pip if needed
install_pip() {
case "$OS_NAME" in
"ubuntu"|"debian")
apt-get install -y python3-pip
;;
"almalinux"|"rocky"|"rhel"|"centos"|"cloudlinux")
if command -v dnf &> /dev/null; then
dnf install -y python3-pip
elif command -v yum &> /dev/null; then
yum install -y python3-pip
fi
;;
esac
}
# Function to check required packages
check_packages() {
print_status "Checking required packages..."
# Check for git
if ! command -v git &> /dev/null; then
print_error "git is not installed. Installing..."
install_git
fi
# Check for curl
if ! command -v curl &> /dev/null; then
print_error "curl is not installed. Installing..."
install_curl
fi
print_success "All required packages are available"
}
# Function to install git
install_git() {
case "$OS_NAME" in
"ubuntu"|"debian")
apt-get update
apt-get install -y git
;;
"almalinux"|"rocky"|"rhel"|"centos"|"cloudlinux")
if command -v dnf &> /dev/null; then
dnf install -y git
elif command -v yum &> /dev/null; then
yum install -y git
fi
;;
esac
}
# Function to install curl
install_curl() {
case "$OS_NAME" in
"ubuntu"|"debian")
apt-get update
apt-get install -y curl
;;
"almalinux"|"rocky"|"rhel"|"centos"|"cloudlinux")
if command -v dnf &> /dev/null; then
dnf install -y curl
elif command -v yum &> /dev/null; then
yum install -y curl
fi
;;
esac
}
# Function to create plugin directory
create_plugin_directory() {
print_status "Creating plugin directory structure..."
# Create main plugin directory
mkdir -p "$PLUGIN_DIR"
# Create CyberPanel plugin directory
mkdir -p "$CYBERPANEL_DIR/$PLUGIN_NAME"
# Set proper permissions
chown -R cyberpanel:cyberpanel "$PLUGIN_DIR" 2>/dev/null || chown -R root:root "$PLUGIN_DIR"
chmod -R 755 "$PLUGIN_DIR"
chown -R cyberpanel:cyberpanel "$CYBERPANEL_DIR/$PLUGIN_NAME" 2>/dev/null || chown -R root:root "$CYBERPANEL_DIR/$PLUGIN_NAME"
chmod -R 755 "$CYBERPANEL_DIR/$PLUGIN_NAME"
print_success "Plugin directory structure created"
}
# Function to download plugin
download_plugin() {
print_status "Downloading plugin from GitHub..."
# Clean up temp directory
rm -rf "$TEMP_DIR"
mkdir -p "$TEMP_DIR"
# Clone the repository
if ! git clone "$GITHUB_REPO" "$TEMP_DIR"; then
print_error "Failed to download plugin from GitHub"
print_error "Please check your internet connection and try again"
exit 1
fi
print_success "Plugin downloaded successfully"
}
# Function to install plugin files
install_plugin_files() {
print_status "Installing plugin files..."
# Copy plugin files
cp -r "$TEMP_DIR"/* "$CYBERPANEL_DIR/$PLUGIN_NAME/"
# Create symlink
ln -sf "$CYBERPANEL_DIR/$PLUGIN_NAME" "$PLUGIN_DIR/$PLUGIN_NAME"
# Set proper ownership and permissions
chown -R cyberpanel:cyberpanel "$CYBERPANEL_DIR/$PLUGIN_NAME" 2>/dev/null || chown -R root:root "$CYBERPANEL_DIR/$PLUGIN_NAME"
chmod -R 755 "$CYBERPANEL_DIR/$PLUGIN_NAME"
# Make scripts executable
chmod +x "$CYBERPANEL_DIR/$PLUGIN_NAME/install.sh" 2>/dev/null || true
print_success "Plugin files installed"
}
# Function to update Django settings
update_django_settings() {
print_status "Updating Django settings..."
SETTINGS_FILE="$CYBERPANEL_DIR/cyberpanel/settings.py"
# Check if plugin is already in INSTALLED_APPS
if ! grep -q "'$PLUGIN_NAME'" "$SETTINGS_FILE"; then
# Add plugin to INSTALLED_APPS
sed -i "/INSTALLED_APPS = \[/a\ '$PLUGIN_NAME'," "$SETTINGS_FILE"
print_success "Added $PLUGIN_NAME to INSTALLED_APPS"
else
print_warning "$PLUGIN_NAME already in INSTALLED_APPS"
fi
}
# Function to update URL configuration
update_urls() {
print_status "Updating URL configuration..."
URLS_FILE="$CYBERPANEL_DIR/cyberpanel/urls.py"
# Check if plugin URLs are already included
if ! grep -q "path(\"$PLUGIN_NAME/\"" "$URLS_FILE"; then
# Add plugin URLs
sed -i "/urlpatterns = \[/a\ path(\"$PLUGIN_NAME/\", include(\"$PLUGIN_NAME.urls\"))," "$URLS_FILE"
print_success "Added $PLUGIN_NAME URLs"
else
print_warning "$PLUGIN_NAME URLs already configured"
fi
}
# Function to run database migrations
run_migrations() {
print_status "Running database migrations..."
cd "$CYBERPANEL_DIR"
# Create migrations
if ! $PYTHON_CMD manage.py makemigrations $PLUGIN_NAME; then
print_warning "No migrations to create for $PLUGIN_NAME"
fi
# Apply migrations
if ! $PYTHON_CMD manage.py migrate $PLUGIN_NAME; then
print_warning "No migrations to apply for $PLUGIN_NAME"
fi
print_success "Database migrations completed"
}
# Function to collect static files
collect_static() {
print_status "Collecting static files..."
cd "$CYBERPANEL_DIR"
if ! $PYTHON_CMD manage.py collectstatic --noinput; then
print_warning "Static file collection failed, but continuing..."
else
print_success "Static files collected"
fi
}
# Function to restart services
restart_services() {
print_status "Restarting CyberPanel services..."
# Restart lscpd
if $SERVICE_CMD is-active --quiet lscpd; then
$SERVICE_CMD restart lscpd
print_success "lscpd service restarted"
else
print_warning "lscpd service not running"
fi
# Restart web server
if $SERVICE_CMD is-active --quiet $WEB_SERVER; then
$SERVICE_CMD restart $WEB_SERVER
print_success "$WEB_SERVER service restarted"
else
print_warning "$WEB_SERVER service not running"
fi
# Additional service restart for different OS
case "$OS_NAME" in
"ubuntu"|"debian")
if $SERVICE_CMD is-active --quiet cyberpanel; then
$SERVICE_CMD restart cyberpanel
print_success "cyberpanel service restarted"
fi
;;
"almalinux"|"rocky"|"rhel"|"centos"|"cloudlinux")
if $SERVICE_CMD is-active --quiet cyberpanel; then
$SERVICE_CMD restart cyberpanel
print_success "cyberpanel service restarted"
fi
;;
esac
}
# Function to verify installation
verify_installation() {
print_status "Verifying installation..."
# Check if plugin directory exists
if [ ! -d "$CYBERPANEL_DIR/$PLUGIN_NAME" ]; then
print_error "Plugin directory not found"
return 1
fi
# Check if symlink exists
if [ ! -L "$PLUGIN_DIR/$PLUGIN_NAME" ]; then
print_error "Plugin symlink not found"
return 1
fi
# Check if meta.xml exists
if [ ! -f "$CYBERPANEL_DIR/$PLUGIN_NAME/meta.xml" ]; then
print_error "Plugin meta.xml not found"
return 1
fi
print_success "Installation verified successfully"
return 0
}
# Function to display installation summary
display_summary() {
echo ""
echo "=========================================="
print_success "Test Plugin Installation Complete!"
echo "=========================================="
echo "Plugin Name: $PLUGIN_NAME"
echo "Installation Directory: $CYBERPANEL_DIR/$PLUGIN_NAME"
echo "Plugin Directory: $PLUGIN_DIR/$PLUGIN_NAME"
echo "Access URL: https://your-domain:8090/testPlugin/"
echo "Operating System: $OS_NAME $OS_VERSION ($OS_ARCH)"
echo "Python Version: $($PYTHON_CMD --version)"
echo ""
echo "Features Installed:"
echo "✓ Enable/Disable Toggle"
echo "✓ Test Button with Popup Messages"
echo "✓ Settings Page"
echo "✓ Activity Logs"
echo "✓ Inline Integration"
echo "✓ Complete Documentation"
echo "✓ Official CyberPanel Guide"
echo "✓ Advanced Development Guide"
echo "✓ Enterprise-Grade Security"
echo "✓ Brute Force Protection"
echo "✓ CSRF Protection"
echo "✓ XSS Prevention"
echo "✓ SQL Injection Protection"
echo "✓ Rate Limiting"
echo "✓ Security Monitoring"
echo "✓ Security Information Page"
echo "✓ Multi-OS Compatibility"
echo ""
echo "Supported Operating Systems:"
echo "✓ Ubuntu 22.04, 20.04"
echo "✓ Debian (compatible)"
echo "✓ AlmaLinux 8, 9, 10"
echo "✓ RockyLinux 8, 9"
echo "✓ RHEL 8, 9"
echo "✓ CloudLinux 8"
echo "✓ CentOS 9"
echo ""
echo "To uninstall, run: $0 --uninstall"
echo "=========================================="
}
# Function to uninstall plugin
uninstall_plugin() {
print_status "Uninstalling $PLUGIN_NAME..."
# Remove plugin files
rm -rf "$CYBERPANEL_DIR/$PLUGIN_NAME"
rm -f "$PLUGIN_DIR/$PLUGIN_NAME"
# Remove from Django settings
SETTINGS_FILE="$CYBERPANEL_DIR/cyberpanel/settings.py"
if [ -f "$SETTINGS_FILE" ]; then
sed -i "/'$PLUGIN_NAME',/d" "$SETTINGS_FILE"
print_success "Removed $PLUGIN_NAME from INSTALLED_APPS"
fi
# Remove from URLs
URLS_FILE="$CYBERPANEL_DIR/cyberpanel/urls.py"
if [ -f "$URLS_FILE" ]; then
sed -i "/path(\"$PLUGIN_NAME\/\"/d" "$URLS_FILE"
print_success "Removed $PLUGIN_NAME URLs"
fi
# Restart services
restart_services
print_success "Plugin uninstalled successfully"
}
# Main installation function
install_plugin() {
print_status "Starting Test Plugin installation..."
# Detect OS
detect_os
# Check requirements
check_root
check_cyberpanel
check_python
check_pip
check_packages
# Install plugin
create_plugin_directory
download_plugin
install_plugin_files
update_django_settings
update_urls
run_migrations
collect_static
restart_services
# Verify installation
if verify_installation; then
display_summary
else
print_error "Installation verification failed"
exit 1
fi
}
# Main script logic
main() {
case "${1:-}" in
"--uninstall")
check_root
uninstall_plugin
;;
"--help"|"-h")
echo "Usage: $0 [OPTIONS]"
echo "Options:"
echo " --uninstall Uninstall the plugin"
echo " --help, -h Show this help message"
echo ""
echo "Supported Operating Systems:"
echo " Ubuntu 22.04, 20.04"
echo " Debian (compatible)"
echo " AlmaLinux 8, 9, 10"
echo " RockyLinux 8, 9"
echo " RHEL 8, 9"
echo " CloudLinux 8"
echo " CentOS 9"
;;
"")
install_plugin
;;
*)
print_error "Unknown option: $1"
echo "Use --help for usage information"
exit 1
;;
esac
}
# Run main function
main "$@"

View File

@@ -1,26 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<plugin>
<name>Test Plugin</name>
<type>Utility</type>
<version>1.0.0</version>
<description>A comprehensive test plugin for CyberPanel with enable/disable functionality, test button, popup messages, and inline integration</description>
<author>usmannasir</author>
<website>https://github.com/cyberpanel/testPlugin</website>
<license>MIT</license>
<dependencies>
<python>3.6+</python>
<django>2.2+</django>
</dependencies>
<permissions>
<admin>true</admin>
<user>false</user>
</permissions>
<settings>
<enable_toggle>true</enable_toggle>
<test_button>true</test_button>
<popup_messages>true</popup_messages>
<inline_integration>true</inline_integration>
</settings>
<url>/plugins/testPlugin/</url>
<settings_url>/plugins/testPlugin/settings/</settings_url>
</plugin>

View File

@@ -1,208 +0,0 @@
# -*- coding: utf-8 -*-
"""
Security middleware for the Test Plugin
Provides additional security measures and monitoring
"""
import time
import hashlib
from django.http import JsonResponse
from django.core.cache import cache
from django.conf import settings
from .security import SecurityManager
class TestPluginSecurityMiddleware:
"""
Security middleware for the Test Plugin
Provides additional protection against various attacks
"""
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
# Only apply security measures to testPlugin URLs
if not request.path.startswith('/testPlugin/'):
return self.get_response(request)
# Security checks
if not self._security_checks(request):
return JsonResponse({
'status': 0,
'error_message': 'Security violation detected. Access denied.'
}, status=403)
response = self.get_response(request)
# Add security headers
self._add_security_headers(response)
return response
def _security_checks(self, request):
"""Perform security checks on the request"""
# Check for suspicious patterns
if self._is_suspicious_request(request):
SecurityManager.log_security_event(request, "Suspicious request pattern detected", "suspicious_request")
return False
# Check for SQL injection attempts
if self._has_sql_injection_patterns(request):
SecurityManager.log_security_event(request, "SQL injection attempt detected", "sql_injection")
return False
# Check for XSS attempts
if self._has_xss_patterns(request):
SecurityManager.log_security_event(request, "XSS attempt detected", "xss_attempt")
return False
# Check for path traversal attempts
if self._has_path_traversal_patterns(request):
SecurityManager.log_security_event(request, "Path traversal attempt detected", "path_traversal")
return False
return True
def _is_suspicious_request(self, request):
"""Check for suspicious request patterns"""
suspicious_patterns = [
'..', '//', '\\', 'cmd', 'exec', 'system', 'eval',
'base64', 'decode', 'encode', 'hex', 'binary',
'union', 'select', 'insert', 'update', 'delete',
'drop', 'create', 'alter', 'grant', 'revoke'
]
# Check URL
url_lower = request.path.lower()
for pattern in suspicious_patterns:
if pattern in url_lower:
return True
# Check query parameters
for key, value in request.GET.items():
if isinstance(value, str):
value_lower = value.lower()
for pattern in suspicious_patterns:
if pattern in value_lower:
return True
# Check POST data
if request.method == 'POST':
for key, value in request.POST.items():
if isinstance(value, str):
value_lower = value.lower()
for pattern in suspicious_patterns:
if pattern in value_lower:
return True
return False
def _has_sql_injection_patterns(self, request):
"""Check for SQL injection patterns"""
sql_patterns = [
"'", '"', ';', '--', '/*', '*/', 'xp_', 'sp_',
'union', 'select', 'insert', 'update', 'delete',
'drop', 'create', 'alter', 'exec', 'execute',
'waitfor', 'delay', 'benchmark', 'sleep'
]
# Check all request data
all_data = []
all_data.extend(request.GET.values())
all_data.extend(request.POST.values())
for value in all_data:
if isinstance(value, str):
value_lower = value.lower()
for pattern in sql_patterns:
if pattern in value_lower:
return True
return False
def _has_xss_patterns(self, request):
"""Check for XSS patterns"""
xss_patterns = [
'<script', '</script>', 'javascript:', 'vbscript:',
'onload=', 'onerror=', 'onclick=', 'onmouseover=',
'onfocus=', 'onblur=', 'onchange=', 'onsubmit=',
'onreset=', 'onselect=', 'onkeydown=', 'onkeyup=',
'onkeypress=', 'onmousedown=', 'onmouseup=',
'onmousemove=', 'onmouseout=', 'oncontextmenu='
]
# Check all request data
all_data = []
all_data.extend(request.GET.values())
all_data.extend(request.POST.values())
for value in all_data:
if isinstance(value, str):
value_lower = value.lower()
for pattern in xss_patterns:
if pattern in value_lower:
return True
return False
def _has_path_traversal_patterns(self, request):
"""Check for path traversal patterns"""
traversal_patterns = [
'../', '..\\', '..%2f', '..%5c', '%2e%2e%2f',
'%2e%2e%5c', '..%252f', '..%255c'
]
# Check URL and all request data
all_data = [request.path]
all_data.extend(request.GET.values())
all_data.extend(request.POST.values())
for value in all_data:
if isinstance(value, str):
for pattern in traversal_patterns:
if pattern in value.lower():
return True
return False
def _add_security_headers(self, response):
"""Add security headers to the response"""
# Prevent clickjacking
response['X-Frame-Options'] = 'DENY'
# Prevent MIME type sniffing
response['X-Content-Type-Options'] = 'nosniff'
# Enable XSS protection
response['X-XSS-Protection'] = '1; mode=block'
# Strict Transport Security (if HTTPS)
if request.is_secure():
response['Strict-Transport-Security'] = 'max-age=31536000; includeSubDomains'
# Content Security Policy
response['Content-Security-Policy'] = (
"default-src 'self'; "
"script-src 'self' 'unsafe-inline' 'unsafe-eval'; "
"style-src 'self' 'unsafe-inline'; "
"img-src 'self' data: https:; "
"font-src 'self' data:; "
"connect-src 'self'; "
"frame-ancestors 'none';"
)
# Referrer Policy
response['Referrer-Policy'] = 'strict-origin-when-cross-origin'
# Permissions Policy
response['Permissions-Policy'] = (
"geolocation=(), "
"microphone=(), "
"camera=(), "
"payment=(), "
"usb=(), "
"magnetometer=(), "
"gyroscope=(), "
"accelerometer=()"
)

View File

@@ -1,35 +0,0 @@
# -*- coding: utf-8 -*-
from django.db import models
from django.contrib.auth.models import User
class TestPluginSettings(models.Model):
"""Model to store plugin settings and enable/disable state"""
user = models.ForeignKey(User, on_delete=models.CASCADE, null=True, blank=True)
plugin_enabled = models.BooleanField(default=True, help_text="Enable or disable the plugin")
test_count = models.IntegerField(default=0, help_text="Number of times test button was clicked")
last_test_time = models.DateTimeField(auto_now=True, help_text="Last time test button was clicked")
custom_message = models.TextField(default="Test plugin is working!", help_text="Custom message for popup")
class Meta:
verbose_name = "Test Plugin Settings"
verbose_name_plural = "Test Plugin Settings"
def __str__(self):
return f"Test Plugin Settings - Enabled: {self.plugin_enabled}"
class TestPluginLog(models.Model):
"""Model to store plugin activity logs"""
timestamp = models.DateTimeField(auto_now_add=True)
action = models.CharField(max_length=100)
message = models.TextField()
user = models.ForeignKey(User, on_delete=models.CASCADE, null=True, blank=True)
class Meta:
verbose_name = "Test Plugin Log"
verbose_name_plural = "Test Plugin Logs"
ordering = ['-timestamp']
def __str__(self):
return f"{self.timestamp} - {self.action}: {self.message}"

View File

@@ -1,365 +0,0 @@
# -*- coding: utf-8 -*-
"""
Operating System Configuration for Test Plugin
Provides OS-specific configurations and compatibility checks
"""
import os
import platform
import subprocess
import sys
from pathlib import Path
class OSConfig:
"""Operating System Configuration Manager"""
def __init__(self):
self.os_name = self._detect_os_name()
self.os_version = self._detect_os_version()
self.os_arch = platform.machine()
self.python_path = self._detect_python_path()
self.pip_path = self._detect_pip_path()
self.service_manager = self._detect_service_manager()
self.web_server = self._detect_web_server()
self.package_manager = self._detect_package_manager()
def _detect_os_name(self):
"""Detect operating system name"""
try:
with open('/etc/os-release', 'r') as f:
for line in f:
if line.startswith('ID='):
return line.split('=')[1].strip().strip('"')
except FileNotFoundError:
pass
# Fallback detection
if os.path.exists('/etc/redhat-release'):
return 'rhel'
elif os.path.exists('/etc/debian_version'):
return 'debian'
else:
return platform.system().lower()
def _detect_os_version(self):
"""Detect operating system version"""
try:
with open('/etc/os-release', 'r') as f:
for line in f:
if line.startswith('VERSION_ID='):
return line.split('=')[1].strip().strip('"')
except FileNotFoundError:
pass
# Fallback detection
if os.path.exists('/etc/redhat-release'):
try:
with open('/etc/redhat-release', 'r') as f:
content = f.read()
import re
match = re.search(r'(\d+\.\d+)', content)
if match:
return match.group(1)
except:
pass
return platform.release()
def _detect_python_path(self):
"""Detect Python executable path"""
# Try different Python commands
python_commands = ['python3', 'python3.11', 'python3.10', 'python3.9', 'python3.8', 'python3.7', 'python3.6', 'python']
for cmd in python_commands:
try:
result = subprocess.run([cmd, '--version'],
capture_output=True, text=True, timeout=5)
if result.returncode == 0:
# Check if it's Python 3.6+
version = result.stdout.strip()
if 'Python 3' in version:
version_num = version.split()[1]
major, minor = map(int, version_num.split('.')[:2])
if major == 3 and minor >= 6:
return cmd
except (subprocess.TimeoutExpired, FileNotFoundError, ValueError):
continue
# Fallback to sys.executable
return sys.executable
def _detect_pip_path(self):
"""Detect pip executable path"""
# Try different pip commands
pip_commands = ['pip3', 'pip3.11', 'pip3.10', 'pip3.9', 'pip3.8', 'pip3.7', 'pip3.6', 'pip']
for cmd in pip_commands:
try:
result = subprocess.run([cmd, '--version'],
capture_output=True, text=True, timeout=5)
if result.returncode == 0:
return cmd
except (subprocess.TimeoutExpired, FileNotFoundError):
continue
# Fallback
return 'pip3'
def _detect_service_manager(self):
"""Detect service manager (systemd, init.d, etc.)"""
if os.path.exists('/bin/systemctl') or os.path.exists('/usr/bin/systemctl'):
return 'systemctl'
elif os.path.exists('/etc/init.d'):
return 'service'
else:
return 'systemctl' # Default to systemctl
def _detect_web_server(self):
"""Detect web server"""
if os.path.exists('/etc/apache2') or os.path.exists('/etc/httpd'):
if os.path.exists('/etc/apache2'):
return 'apache2'
else:
return 'httpd'
else:
return 'httpd' # Default
def _detect_package_manager(self):
"""Detect package manager"""
if os.path.exists('/usr/bin/dnf'):
return 'dnf'
elif os.path.exists('/usr/bin/yum'):
return 'yum'
elif os.path.exists('/usr/bin/apt'):
return 'apt'
elif os.path.exists('/usr/bin/apt-get'):
return 'apt-get'
else:
return 'unknown'
def get_os_info(self):
"""Get comprehensive OS information"""
return {
'name': self.os_name,
'version': self.os_version,
'architecture': self.os_arch,
'python_path': self.python_path,
'pip_path': self.pip_path,
'service_manager': self.service_manager,
'web_server': self.web_server,
'package_manager': self.package_manager,
'platform': platform.platform(),
'python_version': sys.version
}
def is_supported_os(self):
"""Check if the current OS is supported"""
supported_os = [
'ubuntu', 'debian', 'almalinux', 'rocky', 'rhel',
'centos', 'cloudlinux', 'fedora'
]
return self.os_name in supported_os
def get_os_specific_config(self):
"""Get OS-specific configuration"""
configs = {
'ubuntu': {
'python_cmd': 'python3',
'pip_cmd': 'pip3',
'service_cmd': 'systemctl',
'web_server': 'apache2',
'package_manager': 'apt-get',
'cyberpanel_user': 'cyberpanel',
'cyberpanel_group': 'cyberpanel'
},
'debian': {
'python_cmd': 'python3',
'pip_cmd': 'pip3',
'service_cmd': 'systemctl',
'web_server': 'apache2',
'package_manager': 'apt-get',
'cyberpanel_user': 'cyberpanel',
'cyberpanel_group': 'cyberpanel'
},
'almalinux': {
'python_cmd': 'python3',
'pip_cmd': 'pip3',
'service_cmd': 'systemctl',
'web_server': 'httpd',
'package_manager': 'dnf',
'cyberpanel_user': 'cyberpanel',
'cyberpanel_group': 'cyberpanel'
},
'rocky': {
'python_cmd': 'python3',
'pip_cmd': 'pip3',
'service_cmd': 'systemctl',
'web_server': 'httpd',
'package_manager': 'dnf',
'cyberpanel_user': 'cyberpanel',
'cyberpanel_group': 'cyberpanel'
},
'rhel': {
'python_cmd': 'python3',
'pip_cmd': 'pip3',
'service_cmd': 'systemctl',
'web_server': 'httpd',
'package_manager': 'dnf',
'cyberpanel_user': 'cyberpanel',
'cyberpanel_group': 'cyberpanel'
},
'centos': {
'python_cmd': 'python3',
'pip_cmd': 'pip3',
'service_cmd': 'systemctl',
'web_server': 'httpd',
'package_manager': 'dnf',
'cyberpanel_user': 'cyberpanel',
'cyberpanel_group': 'cyberpanel'
},
'cloudlinux': {
'python_cmd': 'python3',
'pip_cmd': 'pip3',
'service_cmd': 'systemctl',
'web_server': 'httpd',
'package_manager': 'yum',
'cyberpanel_user': 'cyberpanel',
'cyberpanel_group': 'cyberpanel'
}
}
return configs.get(self.os_name, configs['ubuntu']) # Default to Ubuntu config
def get_python_requirements(self):
"""Get Python requirements for the current OS"""
base_requirements = [
'Django>=2.2,<4.0',
'django-cors-headers',
'Pillow',
'requests',
'psutil'
]
# OS-specific requirements
os_requirements = {
'ubuntu': [],
'debian': [],
'almalinux': ['python3-devel', 'gcc'],
'rocky': ['python3-devel', 'gcc'],
'rhel': ['python3-devel', 'gcc'],
'centos': ['python3-devel', 'gcc'],
'cloudlinux': ['python3-devel', 'gcc']
}
return base_requirements + os_requirements.get(self.os_name, [])
def get_install_commands(self):
"""Get OS-specific installation commands"""
config = self.get_os_specific_config()
if config['package_manager'] in ['apt-get', 'apt']:
return {
'update': 'apt-get update',
'install_python': 'apt-get install -y python3 python3-pip python3-venv',
'install_git': 'apt-get install -y git',
'install_curl': 'apt-get install -y curl',
'install_dev_tools': 'apt-get install -y build-essential python3-dev'
}
elif config['package_manager'] == 'dnf':
return {
'update': 'dnf update -y',
'install_python': 'dnf install -y python3 python3-pip python3-devel',
'install_git': 'dnf install -y git',
'install_curl': 'dnf install -y curl',
'install_dev_tools': 'dnf install -y gcc gcc-c++ make python3-devel'
}
elif config['package_manager'] == 'yum':
return {
'update': 'yum update -y',
'install_python': 'yum install -y python3 python3-pip python3-devel',
'install_git': 'yum install -y git',
'install_curl': 'yum install -y curl',
'install_dev_tools': 'yum install -y gcc gcc-c++ make python3-devel'
}
else:
# Fallback to Ubuntu commands
return {
'update': 'apt-get update',
'install_python': 'apt-get install -y python3 python3-pip python3-venv',
'install_git': 'apt-get install -y git',
'install_curl': 'apt-get install -y curl',
'install_dev_tools': 'apt-get install -y build-essential python3-dev'
}
def validate_environment(self):
"""Validate the current environment"""
issues = []
# Check Python version
try:
result = subprocess.run([self.python_path, '--version'],
capture_output=True, text=True, timeout=5)
if result.returncode == 0:
version = result.stdout.strip()
if 'Python 3' in version:
version_num = version.split()[1]
major, minor = map(int, version_num.split('.')[:2])
if major < 3 or (major == 3 and minor < 6):
issues.append(f"Python 3.6+ required, found {version}")
else:
issues.append(f"Python 3 required, found {version}")
else:
issues.append("Python not found or not working")
except Exception as e:
issues.append(f"Error checking Python: {e}")
# Check pip
try:
result = subprocess.run([self.pip_path, '--version'],
capture_output=True, text=True, timeout=5)
if result.returncode != 0:
issues.append("pip not found or not working")
except Exception as e:
issues.append(f"Error checking pip: {e}")
# Check if OS is supported
if not self.is_supported_os():
issues.append(f"Unsupported operating system: {self.os_name}")
return issues
def get_compatibility_info(self):
"""Get compatibility information for the current OS"""
return {
'os_supported': self.is_supported_os(),
'python_available': self.python_path is not None,
'pip_available': self.pip_path is not None,
'service_manager': self.service_manager,
'web_server': self.web_server,
'package_manager': self.package_manager,
'validation_issues': self.validate_environment()
}
# Global instance
os_config = OSConfig()
def get_os_config():
"""Get the global OS configuration instance"""
return os_config
def is_os_supported():
"""Check if the current OS is supported"""
return os_config.is_supported_os()
def get_python_path():
"""Get the Python executable path"""
return os_config.python_path
def get_pip_path():
"""Get the pip executable path"""
return os_config.pip_path

View File

@@ -1,256 +0,0 @@
# -*- coding: utf-8 -*-
"""
Security utilities for the Test Plugin
Provides rate limiting, input validation, and security logging
Multi-OS compatible security implementation
"""
import time
import hashlib
import hmac
import json
import re
import os
import platform
from django.core.cache import cache
from django.conf import settings
from django.http import JsonResponse
from django.utils import timezone
from django.contrib.auth.models import User
from functools import wraps
from .models import TestPluginLog
from .os_config import get_os_config
class SecurityManager:
"""Centralized security management for the plugin"""
# Rate limiting settings
RATE_LIMIT_WINDOW = 300 # 5 minutes
MAX_REQUESTS_PER_WINDOW = 50
MAX_FAILED_ATTEMPTS = 5
LOCKOUT_DURATION = 900 # 15 minutes
# Input validation patterns
SAFE_STRING_PATTERN = re.compile(r'^[a-zA-Z0-9\s\-_.,!?@#$%^&*()+=\[\]{}|\\:";\'<>?/~`]*$')
MAX_MESSAGE_LENGTH = 1000
@staticmethod
def is_rate_limited(request):
"""Check if user has exceeded rate limits"""
user_id = request.user.id if request.user.is_authenticated else request.META.get('REMOTE_ADDR')
cache_key = f"rate_limit_{user_id}"
current_time = time.time()
requests = cache.get(cache_key, [])
# Remove old requests outside the window
requests = [req_time for req_time in requests if current_time - req_time < SecurityManager.RATE_LIMIT_WINDOW]
if len(requests) >= SecurityManager.MAX_REQUESTS_PER_WINDOW:
return True
# Add current request
requests.append(current_time)
cache.set(cache_key, requests, SecurityManager.RATE_LIMIT_WINDOW)
return False
@staticmethod
def is_user_locked_out(request):
"""Check if user is temporarily locked out due to failed attempts"""
user_id = request.user.id if request.user.is_authenticated else request.META.get('REMOTE_ADDR')
lockout_key = f"lockout_{user_id}"
return cache.get(lockout_key, False)
@staticmethod
def record_failed_attempt(request, reason="Invalid request"):
"""Record a failed security attempt"""
user_id = request.user.id if request.user.is_authenticated else request.META.get('REMOTE_ADDR')
failed_key = f"failed_attempts_{user_id}"
attempts = cache.get(failed_key, 0) + 1
cache.set(failed_key, attempts, SecurityManager.RATE_LIMIT_WINDOW)
# Log security event
SecurityManager.log_security_event(request, f"Failed attempt: {reason}", "security_failure")
# Lock out user if too many failed attempts
if attempts >= SecurityManager.MAX_FAILED_ATTEMPTS:
lockout_key = f"lockout_{user_id}"
cache.set(lockout_key, True, SecurityManager.LOCKOUT_DURATION)
SecurityManager.log_security_event(request, "User locked out due to excessive failed attempts", "user_locked_out")
@staticmethod
def clear_failed_attempts(request):
"""Clear failed attempts for user after successful action"""
user_id = request.user.id if request.user.is_authenticated else request.META.get('REMOTE_ADDR')
failed_key = f"failed_attempts_{user_id}"
cache.delete(failed_key)
@staticmethod
def validate_input(data, field_name, max_length=None):
"""Validate input data for security"""
if not isinstance(data, str):
return False, f"{field_name} must be a string"
if max_length and len(data) > max_length:
return False, f"{field_name} exceeds maximum length of {max_length} characters"
if not SecurityManager.SAFE_STRING_PATTERN.match(data):
return False, f"{field_name} contains invalid characters"
return True, "Valid"
@staticmethod
def sanitize_input(data):
"""Sanitize input data"""
if isinstance(data, str):
# Remove potential XSS vectors
data = data.replace('<script', '&lt;script')
data = data.replace('</script>', '&lt;/script&gt;')
data = data.replace('javascript:', '')
data = data.replace('onload=', '')
data = data.replace('onerror=', '')
data = data.replace('onclick=', '')
data = data.replace('onmouseover=', '')
# Remove null bytes
data = data.replace('\x00', '')
# Limit length
data = data[:SecurityManager.MAX_MESSAGE_LENGTH]
return data
@staticmethod
def log_security_event(request, message, event_type="security"):
"""Log security-related events"""
try:
user_id = request.user.id if request.user.is_authenticated else None
ip_address = request.META.get('REMOTE_ADDR', 'unknown')
user_agent = request.META.get('HTTP_USER_AGENT', 'unknown')
TestPluginLog.objects.create(
user_id=user_id,
action=event_type,
message=f"[SECURITY] {message} | IP: {ip_address} | UA: {user_agent[:100]}"
)
except Exception:
# Don't let logging errors break the application
pass
@staticmethod
def generate_csrf_token(request):
"""Generate a secure CSRF token"""
if hasattr(request, 'csrf_token'):
return request.csrf_token
# Fallback CSRF token generation
secret = getattr(settings, 'SECRET_KEY', 'fallback-secret')
timestamp = str(int(time.time()))
user_id = str(request.user.id) if request.user.is_authenticated else 'anonymous'
token_data = f"{user_id}:{timestamp}"
token = hmac.new(
secret.encode(),
token_data.encode(),
hashlib.sha256
).hexdigest()
return f"{token}:{timestamp}"
@staticmethod
def verify_csrf_token(request, token):
"""Verify CSRF token"""
if hasattr(request, 'csrf_token'):
return request.csrf_token == token
try:
secret = getattr(settings, 'SECRET_KEY', 'fallback-secret')
token_part, timestamp = token.split(':')
# Check if token is not too old (1 hour)
if time.time() - int(timestamp) > 3600:
return False
user_id = str(request.user.id) if request.user.is_authenticated else 'anonymous'
token_data = f"{user_id}:{timestamp}"
expected_token = hmac.new(
secret.encode(),
token_data.encode(),
hashlib.sha256
).hexdigest()
return hmac.compare_digest(token_part, expected_token)
except (ValueError, AttributeError):
return False
def secure_view(require_csrf=True, rate_limit=True, log_activity=True):
"""Decorator for secure view functions"""
def decorator(view_func):
@wraps(view_func)
def wrapper(request, *args, **kwargs):
# Check if user is locked out
if SecurityManager.is_user_locked_out(request):
SecurityManager.log_security_event(request, "Blocked request from locked out user", "blocked_request")
return JsonResponse({
'status': 0,
'error_message': 'Account temporarily locked due to security violations. Please try again later.'
}, status=423)
# Check rate limiting
if rate_limit and SecurityManager.is_rate_limited(request):
SecurityManager.record_failed_attempt(request, "Rate limit exceeded")
return JsonResponse({
'status': 0,
'error_message': 'Too many requests. Please slow down and try again later.'
}, status=429)
# CSRF protection
if require_csrf and request.method == 'POST':
csrf_token = request.META.get('HTTP_X_CSRFTOKEN') or request.POST.get('csrfmiddlewaretoken')
if not csrf_token or not SecurityManager.verify_csrf_token(request, csrf_token):
SecurityManager.record_failed_attempt(request, "Invalid CSRF token")
return JsonResponse({
'status': 0,
'error_message': 'Invalid security token. Please refresh the page and try again.'
}, status=403)
# Log activity
if log_activity:
SecurityManager.log_security_event(request, f"Accessing {view_func.__name__}", "view_access")
try:
result = view_func(request, *args, **kwargs)
# Clear failed attempts on successful request
SecurityManager.clear_failed_attempts(request)
return result
except Exception as e:
SecurityManager.log_security_event(request, f"Error in {view_func.__name__}: {str(e)}", "view_error")
return JsonResponse({
'status': 0,
'error_message': 'An internal error occurred. Please try again later.'
}, status=500)
return wrapper
return decorator
def admin_required(view_func):
"""Decorator to ensure only admin users can access the view"""
@wraps(view_func)
def wrapper(request, *args, **kwargs):
if not request.user.is_authenticated:
return JsonResponse({
'status': 0,
'error_message': 'Authentication required.'
}, status=401)
if not request.user.is_staff and not request.user.is_superuser:
SecurityManager.log_security_event(request, "Unauthorized access attempt by non-admin user", "unauthorized_access")
return JsonResponse({
'status': 0,
'error_message': 'Admin privileges required.'
}, status=403)
return view_func(request, *args, **kwargs)
return wrapper

View File

@@ -1,15 +0,0 @@
# -*- coding: utf-8 -*-
from django.db.models.signals import post_save
from django.dispatch import receiver
from django.contrib.auth.models import User
from .models import TestPluginSettings
@receiver(post_save, sender=User)
def create_user_settings(sender, instance, created, **kwargs):
"""Create default plugin settings when a new user is created"""
if created:
TestPluginSettings.objects.create(
user=instance,
plugin_enabled=True
)

View File

@@ -1,418 +0,0 @@
/* Test Plugin CSS - Additional styles for better integration */
/* Popup Message Animations */
@keyframes slideInRight {
from {
transform: translateX(100%);
opacity: 0;
}
to {
transform: translateX(0);
opacity: 1;
}
}
@keyframes slideOutRight {
from {
transform: translateX(0);
opacity: 1;
}
to {
transform: translateX(100%);
opacity: 0;
}
}
/* Enhanced Button Styles */
.btn-test {
position: relative;
overflow: hidden;
}
.btn-test::before {
content: '';
position: absolute;
top: 50%;
left: 50%;
width: 0;
height: 0;
background: rgba(255, 255, 255, 0.3);
border-radius: 50%;
transform: translate(-50%, -50%);
transition: width 0.6s, height 0.6s;
}
.btn-test:active::before {
width: 300px;
height: 300px;
}
/* Toggle Switch Enhanced */
.toggle-switch {
position: relative;
display: inline-block;
width: 60px;
height: 34px;
}
.toggle-switch input {
opacity: 0;
width: 0;
height: 0;
}
.slider {
position: absolute;
cursor: pointer;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: #ccc;
transition: .4s;
border-radius: 34px;
}
.slider:before {
position: absolute;
content: "";
height: 26px;
width: 26px;
left: 4px;
bottom: 4px;
background-color: white;
transition: .4s;
border-radius: 50%;
box-shadow: 0 2px 4px rgba(0,0,0,0.2);
}
input:checked + .slider {
background-color: #5856d6;
}
input:checked + .slider:before {
transform: translateX(26px);
}
/* Loading States */
.loading {
opacity: 0.6;
pointer-events: none;
}
.loading::after {
content: '';
position: absolute;
top: 50%;
left: 50%;
width: 20px;
height: 20px;
margin: -10px 0 0 -10px;
border: 2px solid transparent;
border-top: 2px solid currentColor;
border-radius: 50%;
animation: spin 1s linear infinite;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
/* Card Hover Effects */
.plugin-card {
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}
.plugin-card:hover {
transform: translateY(-8px);
box-shadow: 0 12px 32px rgba(0,0,0,0.15);
}
/* Status Indicators */
.status-indicator {
display: inline-flex;
align-items: center;
gap: 8px;
padding: 6px 12px;
border-radius: 20px;
font-size: 12px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.5px;
}
.status-indicator.enabled {
background: #e8f5e8;
color: #388e3c;
}
.status-indicator.disabled {
background: #ffebee;
color: #d32f2f;
}
/* Responsive Enhancements */
@media (max-width: 768px) {
.control-row {
flex-direction: column;
align-items: stretch;
}
.control-group {
justify-content: space-between;
margin-bottom: 15px;
}
.stats-grid {
grid-template-columns: 1fr;
gap: 15px;
}
.logs-table {
font-size: 12px;
}
.logs-table th,
.logs-table td {
padding: 8px 4px;
}
}
@media (max-width: 480px) {
.test-plugin-wrapper {
padding: 10px;
}
.plugin-header,
.control-panel,
.settings-form,
.logs-content {
padding: 15px;
}
.plugin-header h1 {
font-size: 24px;
flex-direction: column;
text-align: center;
}
.btn-test,
.btn-secondary {
width: 100%;
justify-content: center;
margin-bottom: 10px;
}
}
/* Dark Mode Support */
@media (prefers-color-scheme: dark) {
:root {
--bg-primary: #1a1a1a;
--bg-secondary: #2d2d2d;
--text-primary: #ffffff;
--text-secondary: #b3b3b3;
--text-tertiary: #808080;
--border-primary: #404040;
--shadow-md: 0 2px 8px rgba(0,0,0,0.3);
--shadow-lg: 0 8px 24px rgba(0,0,0,0.4);
}
}
/* Print Styles */
@media print {
.test-plugin-wrapper {
background: white !important;
color: black !important;
}
.btn-test,
.btn-secondary,
.toggle-switch {
display: none !important;
}
.popup-message {
display: none !important;
}
}
/* Additional styles for inline elements */
.popup-message {
position: fixed;
top: 20px;
right: 20px;
background: white;
border-radius: 8px;
padding: 16px 20px;
box-shadow: 0 8px 24px rgba(0,0,0,0.15);
border-left: 4px solid #10b981;
z-index: 9999;
max-width: 400px;
transform: translateX(100%);
transition: transform 0.3s ease;
}
.popup-message.show {
transform: translateX(0);
}
.popup-message.error {
border-left-color: #ef4444;
}
.popup-message.warning {
border-left-color: #f59e0b;
}
.popup-title {
font-weight: 600;
color: var(--text-primary, #2f3640);
margin-bottom: 4px;
}
.popup-content {
font-size: 14px;
color: var(--text-secondary, #64748b);
margin-bottom: 8px;
}
.popup-time {
font-size: 12px;
color: var(--text-tertiary, #9ca3af);
}
.popup-close {
position: absolute;
top: 8px;
right: 8px;
background: none;
border: none;
font-size: 18px;
cursor: pointer;
color: var(--text-tertiary, #9ca3af);
}
.notification {
position: fixed;
top: 20px;
right: 20px;
background: white;
border-radius: 8px;
padding: 16px 20px;
box-shadow: 0 8px 24px rgba(0,0,0,0.15);
border-left: 4px solid #10b981;
z-index: 9999;
max-width: 400px;
transform: translateX(100%);
transition: transform 0.3s ease;
}
.notification.show {
transform: translateX(0);
}
.notification.error {
border-left-color: #ef4444;
}
.notification-title {
font-weight: 600;
color: var(--text-primary, #2f3640);
margin-bottom: 4px;
}
.notification-content {
font-size: 14px;
color: var(--text-secondary, #64748b);
}
.popup-container {
position: fixed;
top: 0;
right: 0;
z-index: 9999;
pointer-events: none;
}
/* OS Compatibility Styles */
.compatibility-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: 20px;
margin: 20px 0;
}
.os-card {
background: var(--bg-secondary, #f8f9ff);
border: 1px solid var(--border-primary, #e8e9ff);
border-radius: 8px;
padding: 20px;
transition: all 0.3s ease;
}
.os-card:hover {
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(0,0,0,0.1);
}
.os-card h3 {
color: var(--text-primary, #2f3640);
margin-bottom: 15px;
font-size: 18px;
font-weight: 600;
}
.os-card ul {
list-style: none;
padding: 0;
margin: 0 0 15px 0;
}
.os-card li {
padding: 5px 0;
color: var(--text-secondary, #64748b);
font-size: 14px;
}
.os-card p {
margin: 5px 0;
color: var(--text-secondary, #64748b);
font-size: 13px;
}
.troubleshooting-section {
margin: 20px 0;
}
.troubleshooting-section h4 {
color: var(--text-primary, #2f3640);
margin: 15px 0 10px 0;
font-size: 16px;
font-weight: 600;
}
.code-block {
background: var(--bg-secondary, #f8f9ff);
border: 1px solid var(--border-primary, #e8e9ff);
border-radius: 6px;
padding: 15px;
margin: 10px 0;
overflow-x: auto;
}
.code-block pre {
margin: 0;
font-family: 'Courier New', monospace;
font-size: 13px;
line-height: 1.4;
color: var(--text-primary, #2f3640);
}
.code-block code {
background: none;
padding: 0;
font-family: inherit;
font-size: inherit;
color: inherit;
}

View File

@@ -1,323 +0,0 @@
/**
* Test Plugin JavaScript
* Handles all client-side functionality for the test plugin
*/
class TestPlugin {
constructor() {
this.init();
}
init() {
this.bindEvents();
this.initializeComponents();
}
bindEvents() {
// Toggle switch functionality
const toggleSwitch = document.getElementById('plugin-toggle');
if (toggleSwitch) {
toggleSwitch.addEventListener('change', (e) => this.handleToggle(e));
}
// Test button functionality
const testButton = document.getElementById('test-button');
if (testButton) {
testButton.addEventListener('click', (e) => this.handleTestClick(e));
}
// Settings form
const settingsForm = document.getElementById('settings-form');
if (settingsForm) {
settingsForm.addEventListener('submit', (e) => this.handleSettingsSubmit(e));
}
// Log filter
const actionFilter = document.getElementById('action-filter');
if (actionFilter) {
actionFilter.addEventListener('change', (e) => this.handleLogFilter(e));
}
}
initializeComponents() {
// Initialize any components that need setup
this.initializeTooltips();
this.initializeAnimations();
}
async handleToggle(event) {
const toggleSwitch = event.target;
const testButton = document.getElementById('test-button');
try {
const response = await fetch('/testPlugin/toggle/', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRFToken': this.getCSRFToken()
}
});
const data = await response.json();
if (data.status === 1) {
if (testButton) {
testButton.disabled = !data.enabled;
}
this.showNotification('success', 'Plugin Toggle', data.message);
// Update status indicator if exists
this.updateStatusIndicator(data.enabled);
// Reload page after a short delay to update UI
setTimeout(() => {
window.location.reload();
}, 1000);
} else {
this.showNotification('error', 'Error', data.error_message);
// Revert toggle state
toggleSwitch.checked = !toggleSwitch.checked;
}
} catch (error) {
this.showNotification('error', 'Error', 'Failed to toggle plugin');
// Revert toggle state
toggleSwitch.checked = !toggleSwitch.checked;
}
}
async handleTestClick(event) {
const testButton = event.target;
if (testButton.disabled) return;
// Add loading state
testButton.classList.add('loading');
testButton.disabled = true;
const originalContent = testButton.innerHTML;
testButton.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Testing...';
try {
const response = await fetch('/testPlugin/test/', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRFToken': this.getCSRFToken()
}
});
const data = await response.json();
if (data.status === 1) {
// Update test count
const testCountElement = document.getElementById('test-count');
if (testCountElement) {
testCountElement.textContent = data.test_count;
}
// Show popup message
this.showPopup(
data.popup_message.type,
data.popup_message.title,
data.popup_message.message
);
// Add success animation
testButton.style.background = 'linear-gradient(135deg, #10b981, #059669)';
setTimeout(() => {
testButton.style.background = '';
}, 2000);
} else {
this.showNotification('error', 'Error', data.error_message);
}
} catch (error) {
this.showNotification('error', 'Error', 'Failed to execute test');
} finally {
// Remove loading state
testButton.classList.remove('loading');
testButton.disabled = false;
testButton.innerHTML = originalContent;
}
}
async handleSettingsSubmit(event) {
event.preventDefault();
const form = event.target;
const formData = new FormData(form);
const data = {
custom_message: formData.get('custom_message')
};
try {
const response = await fetch('/testPlugin/update-settings/', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRFToken': this.getCSRFToken()
},
body: JSON.stringify(data)
});
const result = await response.json();
if (result.status === 1) {
this.showNotification('success', 'Settings Updated', result.message);
} else {
this.showNotification('error', 'Error', result.error_message);
}
} catch (error) {
this.showNotification('error', 'Error', 'Failed to update settings');
}
}
handleLogFilter(event) {
const selectedAction = event.target.value;
const logRows = document.querySelectorAll('.log-row');
logRows.forEach(row => {
if (selectedAction === '' || row.dataset.action === selectedAction) {
row.style.display = '';
} else {
row.style.display = 'none';
}
});
}
showPopup(type, title, message) {
const popupContainer = document.getElementById('popup-container') || this.createPopupContainer();
const popup = document.createElement('div');
popup.className = `popup-message ${type}`;
popup.innerHTML = `
<button class="popup-close" onclick="this.parentElement.remove()">&times;</button>
<div class="popup-title">${title}</div>
<div class="popup-content">${message}</div>
<div class="popup-time">${new Date().toLocaleTimeString()}</div>
`;
popupContainer.appendChild(popup);
// Show popup with animation
setTimeout(() => popup.classList.add('show'), 100);
// Auto remove after 5 seconds
setTimeout(() => {
popup.classList.remove('show');
setTimeout(() => popup.remove(), 300);
}, 5000);
}
showNotification(type, title, message) {
const notification = document.createElement('div');
notification.className = `notification ${type}`;
notification.innerHTML = `
<div class="notification-title">${title}</div>
<div class="notification-content">${message}</div>
`;
document.body.appendChild(notification);
// Show notification
setTimeout(() => notification.classList.add('show'), 100);
// Auto remove after 3 seconds
setTimeout(() => {
notification.classList.remove('show');
setTimeout(() => notification.remove(), 300);
}, 3000);
}
createPopupContainer() {
const container = document.createElement('div');
container.id = 'popup-container';
container.className = 'popup-container';
document.body.appendChild(container);
return container;
}
updateStatusIndicator(enabled) {
const statusElements = document.querySelectorAll('.status-indicator');
statusElements.forEach(element => {
element.className = `status-indicator ${enabled ? 'enabled' : 'disabled'}`;
element.innerHTML = enabled ?
'<i class="fas fa-check-circle"></i> Enabled' :
'<i class="fas fa-times-circle"></i> Disabled';
});
}
initializeTooltips() {
// Add tooltips to buttons and controls
const elements = document.querySelectorAll('[data-tooltip]');
elements.forEach(element => {
element.addEventListener('mouseenter', (e) => this.showTooltip(e));
element.addEventListener('mouseleave', (e) => this.hideTooltip(e));
});
}
showTooltip(event) {
const element = event.target;
const tooltipText = element.dataset.tooltip;
if (!tooltipText) return;
const tooltip = document.createElement('div');
tooltip.className = 'tooltip';
tooltip.textContent = tooltipText;
tooltip.style.cssText = `
position: absolute;
background: #333;
color: white;
padding: 8px 12px;
border-radius: 4px;
font-size: 12px;
z-index: 10000;
pointer-events: none;
white-space: nowrap;
`;
document.body.appendChild(tooltip);
const rect = element.getBoundingClientRect();
tooltip.style.left = rect.left + (rect.width / 2) - (tooltip.offsetWidth / 2) + 'px';
tooltip.style.top = rect.top - tooltip.offsetHeight - 8 + 'px';
element._tooltip = tooltip;
}
hideTooltip(event) {
const element = event.target;
if (element._tooltip) {
element._tooltip.remove();
delete element._tooltip;
}
}
initializeAnimations() {
// Add entrance animations to cards
const cards = document.querySelectorAll('.plugin-card, .stat-card, .log-item');
cards.forEach((card, index) => {
card.style.opacity = '0';
card.style.transform = 'translateY(20px)';
card.style.transition = 'opacity 0.6s ease, transform 0.6s ease';
setTimeout(() => {
card.style.opacity = '1';
card.style.transform = 'translateY(0)';
}, index * 100);
});
}
getCSRFToken() {
const token = document.querySelector('[name=csrfmiddlewaretoken]');
return token ? token.value : '';
}
}
// Initialize the plugin when DOM is loaded
document.addEventListener('DOMContentLoaded', function() {
new TestPlugin();
});
// Export for potential external use
window.TestPlugin = TestPlugin;

View File

@@ -1,71 +0,0 @@
{% extends "baseTemplate/base.html" %}
{% load static %}
{% load i18n %}
{% block title %}
Test Plugin - {% trans "CyberPanel" %}
{% endblock %}
{% block content %}
<div class="container-fluid">
<div class="row">
<div class="col-lg-12">
<div class="card">
<div class="card-header">
<h3 class="card-title">
<i class="fas fa-cog"></i>
{% trans "Test Plugin Dashboard" %}
</h3>
</div>
<div class="card-body">
<div class="row">
<div class="col-md-6">
<div class="info-box">
<span class="info-box-icon bg-info">
<i class="fas fa-info-circle"></i>
</span>
<div class="info-box-content">
<span class="info-box-text">{% trans "Plugin Name" %}</span>
<span class="info-box-number">{{ plugin_name }}</span>
</div>
</div>
</div>
<div class="col-md-6">
<div class="info-box">
<span class="info-box-icon bg-success">
<i class="fas fa-tag"></i>
</span>
<div class="info-box-content">
<span class="info-box-text">{% trans "Version" %}</span>
<span class="info-box-number">{{ version }}</span>
</div>
</div>
</div>
</div>
<div class="alert alert-info">
<h4><i class="icon fa fa-info"></i> {% trans "Plugin Information" %}</h4>
<p>{{ description }}</p>
</div>
<div class="card">
<div class="card-header">
<h3 class="card-title">
<i class="fas fa-check-circle"></i>
{% trans "Test Plugin Status" %}
</h3>
</div>
<div class="card-body">
<div class="alert alert-success">
<i class="fas fa-check"></i>
{% trans "Test Plugin is working correctly!" %}
</div>
<p>{% trans "This is a test plugin created for testing CyberPanel plugin functionality." %}</p>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
{% endblock %}

File diff suppressed because it is too large Load Diff

View File

@@ -1,571 +0,0 @@
{% extends "baseTemplate/index.html" %}
{% load i18n %}
{% load static %}
{% block title %}{% trans "Test Plugin - CyberPanel" %}{% endblock %}
{% block header_scripts %}
<style>
/* Test Plugin Specific Styles */
.test-plugin-wrapper {
background: transparent;
padding: 20px;
}
.test-plugin-container {
max-width: 1200px;
margin: 0 auto;
}
/* Page Header */
.plugin-header {
background: var(--bg-primary, white);
border-radius: 12px;
padding: 25px;
margin-bottom: 25px;
box-shadow: var(--shadow-md, 0 2px 8px rgba(0,0,0,0.08));
border: 1px solid var(--border-primary, #e8e9ff);
}
.plugin-header h1 {
font-size: 28px;
font-weight: 700;
color: var(--text-primary, #2f3640);
margin: 0 0 10px 0;
display: flex;
align-items: center;
gap: 15px;
}
.plugin-header .icon {
width: 48px;
height: 48px;
background: #5856d6;
border-radius: 12px;
display: flex;
align-items: center;
justify-content: center;
color: white;
font-size: 24px;
box-shadow: 0 4px 12px rgba(88,86,214,0.3);
}
.plugin-header p {
font-size: 15px;
color: var(--text-secondary, #64748b);
margin: 0;
}
/* Control Panel */
.control-panel {
background: var(--bg-primary, white);
border-radius: 12px;
padding: 25px;
margin-bottom: 25px;
box-shadow: var(--shadow-md, 0 2px 8px rgba(0,0,0,0.08));
border: 1px solid var(--border-primary, #e8e9ff);
}
.control-row {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 20px;
flex-wrap: wrap;
gap: 15px;
}
.control-group {
display: flex;
align-items: center;
gap: 15px;
}
.toggle-switch {
position: relative;
display: inline-block;
width: 60px;
height: 34px;
}
.toggle-switch input {
opacity: 0;
width: 0;
height: 0;
}
.slider {
position: absolute;
cursor: pointer;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: #ccc;
transition: .4s;
border-radius: 34px;
}
.slider:before {
position: absolute;
content: "";
height: 26px;
width: 26px;
left: 4px;
bottom: 4px;
background-color: white;
transition: .4s;
border-radius: 50%;
}
input:checked + .slider {
background-color: #5856d6;
}
input:checked + .slider:before {
transform: translateX(26px);
}
.btn-test {
background: linear-gradient(135deg, #5856d6, #4a90e2);
color: white;
border: none;
padding: 12px 24px;
border-radius: 8px;
font-weight: 600;
cursor: pointer;
transition: all 0.3s ease;
display: flex;
align-items: center;
gap: 8px;
}
.btn-test:hover {
transform: translateY(-2px);
box-shadow: 0 8px 20px rgba(88,86,214,0.3);
}
.btn-test:disabled {
opacity: 0.6;
cursor: not-allowed;
transform: none;
}
.btn-secondary {
background: #6c757d;
color: white;
border: none;
padding: 10px 20px;
border-radius: 6px;
font-weight: 500;
cursor: pointer;
transition: all 0.3s ease;
text-decoration: none;
display: inline-flex;
align-items: center;
gap: 8px;
}
.btn-secondary:hover {
background: #5a6268;
color: white;
text-decoration: none;
}
/* Stats Cards */
.stats-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 20px;
margin-bottom: 25px;
}
.stat-card {
background: var(--bg-primary, white);
border-radius: 12px;
padding: 20px;
box-shadow: var(--shadow-md, 0 2px 8px rgba(0,0,0,0.08));
border: 1px solid var(--border-primary, #e8e9ff);
text-align: center;
}
.stat-value {
font-size: 32px;
font-weight: 700;
color: #5856d6;
margin-bottom: 8px;
}
.stat-label {
font-size: 14px;
color: var(--text-secondary, #64748b);
text-transform: uppercase;
letter-spacing: 0.5px;
}
/* Recent Logs */
.logs-section {
background: var(--bg-primary, white);
border-radius: 12px;
padding: 25px;
box-shadow: var(--shadow-md, 0 2px 8px rgba(0,0,0,0.08));
border: 1px solid var(--border-primary, #e8e9ff);
}
.logs-list {
max-height: 300px;
overflow-y: auto;
}
.log-item {
display: flex;
align-items: center;
padding: 12px 0;
border-bottom: 1px solid var(--border-primary, #e8e9ff);
}
.log-item:last-child {
border-bottom: none;
}
.log-icon {
width: 32px;
height: 32px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
margin-right: 12px;
font-size: 14px;
}
.log-icon.info {
background: #e3f2fd;
color: #1976d2;
}
.log-icon.success {
background: #e8f5e8;
color: #388e3c;
}
.log-icon.warning {
background: #fff3e0;
color: #f57c00;
}
.log-content {
flex: 1;
}
.log-action {
font-weight: 600;
color: var(--text-primary, #2f3640);
margin-bottom: 4px;
}
.log-message {
font-size: 14px;
color: var(--text-secondary, #64748b);
}
.log-time {
font-size: 12px;
color: var(--text-tertiary, #9ca3af);
margin-left: 12px;
}
/* Popup Message Styles */
.popup-message {
position: fixed;
top: 20px;
right: 20px;
background: white;
border-radius: 8px;
padding: 16px 20px;
box-shadow: 0 8px 24px rgba(0,0,0,0.15);
border-left: 4px solid #10b981;
z-index: 9999;
max-width: 400px;
transform: translateX(100%);
transition: transform 0.3s ease;
}
.popup-message.show {
transform: translateX(0);
}
.popup-message.error {
border-left-color: #ef4444;
}
.popup-message.warning {
border-left-color: #f59e0b;
}
.popup-title {
font-weight: 600;
color: var(--text-primary, #2f3640);
margin-bottom: 4px;
}
.popup-content {
font-size: 14px;
color: var(--text-secondary, #64748b);
margin-bottom: 8px;
}
.popup-time {
font-size: 12px;
color: var(--text-tertiary, #9ca3af);
}
.popup-close {
position: absolute;
top: 8px;
right: 8px;
background: none;
border: none;
font-size: 18px;
cursor: pointer;
color: var(--text-tertiary, #9ca3af);
}
/* Responsive */
@media (max-width: 768px) {
.test-plugin-wrapper {
padding: 15px;
}
.control-row {
flex-direction: column;
align-items: stretch;
}
.control-group {
justify-content: space-between;
}
.stats-grid {
grid-template-columns: 1fr;
}
.popup-message {
right: 10px;
left: 10px;
max-width: none;
}
}
</style>
{% endblock %}
{% block content %}
<div class="test-plugin-wrapper">
<div class="test-plugin-container">
<!-- Plugin Header -->
<div class="plugin-header">
<h1>
<div class="icon">
<i class="fas fa-vial"></i>
</div>
{% trans "Test Plugin" %}
</h1>
<p>{% trans "A comprehensive test plugin with enable/disable functionality, test button, and popup messages" %}</p>
</div>
<!-- Control Panel -->
<div class="control-panel">
<div class="control-row">
<div class="control-group">
<label for="plugin-toggle" style="font-weight: 600; color: var(--text-primary, #2f3640);">
{% trans "Enable Plugin" %}
</label>
<label class="toggle-switch">
<input type="checkbox" id="plugin-toggle" {% if plugin_enabled %}checked{% endif %}>
<span class="slider"></span>
</label>
</div>
<div class="control-group">
<button class="btn-test" id="test-button" {% if not plugin_enabled %}disabled{% endif %}>
<i class="fas fa-play"></i>
{% trans "Test Button" %}
</button>
<a href="{% url 'testPlugin:plugin_settings' %}" class="btn-secondary">
<i class="fas fa-cog"></i>
{% trans "Settings" %}
</a>
<a href="{% url 'testPlugin:plugin_logs' %}" class="btn-secondary">
<i class="fas fa-list"></i>
{% trans "Logs" %}
</a>
<a href="{% url 'testPlugin:plugin_docs' %}" class="btn-secondary">
<i class="fas fa-book"></i>
{% trans "Documentation" %}
</a>
<a href="{% url 'testPlugin:security_info' %}" class="btn-secondary">
<i class="fas fa-shield-alt"></i>
{% trans "Security Info" %}
</a>
</div>
</div>
</div>
<!-- Stats Cards -->
<div class="stats-grid">
<div class="stat-card">
<div class="stat-value" id="test-count">{{ settings.test_count|default:0 }}</div>
<div class="stat-label">{% trans "Test Clicks" %}</div>
</div>
<div class="stat-card">
<div class="stat-value">
{% if plugin_enabled %}
<i class="fas fa-check-circle" style="color: #10b981;"></i>
{% else %}
<i class="fas fa-times-circle" style="color: #ef4444;"></i>
{% endif %}
</div>
<div class="stat-label">{% trans "Plugin Status" %}</div>
</div>
<div class="stat-card">
<div class="stat-value">{{ recent_logs|length }}</div>
<div class="stat-label">{% trans "Recent Activities" %}</div>
</div>
</div>
<!-- Recent Logs -->
<div class="logs-section">
<h3 style="margin-bottom: 20px; color: var(--text-primary, #2f3640);">
<i class="fas fa-history" style="margin-right: 8px; color: #5856d6;"></i>
{% trans "Recent Activity" %}
</h3>
<div class="logs-list">
{% for log in recent_logs %}
<div class="log-item">
<div class="log-icon {% if 'error' in log.action %}warning{% elif 'success' in log.action or 'click' in log.action %}success{% else %}info{% endif %}">
{% if 'click' in log.action %}
<i class="fas fa-mouse-pointer"></i>
{% elif 'toggle' in log.action %}
<i class="fas fa-toggle-on"></i>
{% elif 'settings' in log.action %}
<i class="fas fa-cog"></i>
{% else %}
<i class="fas fa-info-circle"></i>
{% endif %}
</div>
<div class="log-content">
<div class="log-action">{{ log.action|title }}</div>
<div class="log-message">{{ log.message }}</div>
</div>
<div class="log-time">{{ log.timestamp|date:"M d, H:i" }}</div>
</div>
{% empty %}
<div style="text-align: center; padding: 40px; color: var(--text-secondary, #64748b);">
<i class="fas fa-inbox" style="font-size: 48px; margin-bottom: 16px; opacity: 0.5;"></i>
<p>{% trans "No recent activity" %}</p>
</div>
{% endfor %}
</div>
</div>
</div>
</div>
<!-- Popup Message Container -->
<div id="popup-container"></div>
<script>
document.addEventListener('DOMContentLoaded', function() {
const toggleSwitch = document.getElementById('plugin-toggle');
const testButton = document.getElementById('test-button');
const testCountElement = document.getElementById('test-count');
// Toggle switch functionality
toggleSwitch.addEventListener('change', function() {
fetch('{% url "testPlugin:toggle_plugin" %}', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRFToken': '{{ csrf_token }}'
}
})
.then(response => response.json())
.then(data => {
if (data.status === 1) {
testButton.disabled = !data.enabled;
showPopup('success', 'Plugin Toggle', data.message);
location.reload(); // Refresh to update UI
} else {
showPopup('error', 'Error', data.error_message);
}
})
.catch(error => {
showPopup('error', 'Error', 'Failed to toggle plugin');
});
});
// Test button functionality
testButton.addEventListener('click', function() {
if (testButton.disabled) return;
testButton.disabled = true;
testButton.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Testing...';
fetch('{% url "testPlugin:test_button" %}', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRFToken': '{{ csrf_token }}'
}
})
.then(response => response.json())
.then(data => {
if (data.status === 1) {
testCountElement.textContent = data.test_count;
showPopup(data.popup_message.type, data.popup_message.title, data.popup_message.message);
} else {
showPopup('error', 'Error', data.error_message);
}
})
.catch(error => {
showPopup('error', 'Error', 'Failed to execute test');
})
.finally(() => {
testButton.disabled = false;
testButton.innerHTML = '<i class="fas fa-play"></i> Test Button';
});
});
// Popup message function
function showPopup(type, title, message) {
const popupContainer = document.getElementById('popup-container');
const popup = document.createElement('div');
popup.className = `popup-message ${type}`;
popup.innerHTML = `
<button class="popup-close" onclick="this.parentElement.remove()">&times;</button>
<div class="popup-title">${title}</div>
<div class="popup-content">${message}</div>
<div class="popup-time">${new Date().toLocaleTimeString()}</div>
`;
popupContainer.appendChild(popup);
// Show popup
setTimeout(() => popup.classList.add('show'), 100);
// Auto remove after 5 seconds
setTimeout(() => {
popup.classList.remove('show');
setTimeout(() => popup.remove(), 300);
}, 5000);
}
});
</script>
{% endblock %}

View File

@@ -1,291 +0,0 @@
{% extends "baseTemplate/index.html" %}
{% load i18n %}
{% load static %}
{% block title %}{% trans "Test Plugin Logs - CyberPanel" %}{% endblock %}
{% block header_scripts %}
<style>
.logs-wrapper {
background: transparent;
padding: 20px;
}
.logs-container {
max-width: 1200px;
margin: 0 auto;
}
.logs-header {
background: var(--bg-primary, white);
border-radius: 12px;
padding: 25px;
margin-bottom: 25px;
box-shadow: var(--shadow-md, 0 2px 8px rgba(0,0,0,0.08));
border: 1px solid var(--border-primary, #e8e9ff);
}
.logs-content {
background: var(--bg-primary, white);
border-radius: 12px;
padding: 25px;
box-shadow: var(--shadow-md, 0 2px 8px rgba(0,0,0,0.08));
border: 1px solid var(--border-primary, #e8e9ff);
}
.logs-table {
width: 100%;
border-collapse: collapse;
margin-top: 20px;
}
.logs-table th,
.logs-table td {
padding: 12px;
text-align: left;
border-bottom: 1px solid var(--border-primary, #e8e9ff);
}
.logs-table th {
background: var(--bg-secondary, #f8f9ff);
font-weight: 600;
color: var(--text-primary, #2f3640);
font-size: 14px;
text-transform: uppercase;
letter-spacing: 0.5px;
}
.logs-table td {
font-size: 14px;
color: var(--text-secondary, #64748b);
}
.log-action {
font-weight: 600;
color: var(--text-primary, #2f3640);
}
.log-timestamp {
color: var(--text-tertiary, #9ca3af);
font-size: 13px;
}
.log-icon {
display: inline-block;
width: 24px;
height: 24px;
border-radius: 50%;
text-align: center;
line-height: 24px;
font-size: 12px;
margin-right: 8px;
}
.log-icon.info {
background: #e3f2fd;
color: #1976d2;
}
.log-icon.success {
background: #e8f5e8;
color: #388e3c;
}
.log-icon.warning {
background: #fff3e0;
color: #f57c00;
}
.log-icon.error {
background: #ffebee;
color: #d32f2f;
}
.btn-secondary {
background: #6c757d;
color: white;
border: none;
padding: 10px 20px;
border-radius: 6px;
font-weight: 500;
cursor: pointer;
transition: all 0.3s ease;
text-decoration: none;
display: inline-flex;
align-items: center;
gap: 8px;
margin-right: 10px;
}
.btn-secondary:hover {
background: #5a6268;
color: white;
text-decoration: none;
}
.empty-state {
text-align: center;
padding: 60px 20px;
color: var(--text-secondary, #64748b);
}
.empty-state i {
font-size: 64px;
margin-bottom: 20px;
opacity: 0.5;
}
.empty-state h3 {
font-size: 24px;
margin-bottom: 10px;
color: var(--text-primary, #2f3640);
}
.empty-state p {
font-size: 16px;
margin: 0;
}
.filter-controls {
display: flex;
gap: 15px;
margin-bottom: 20px;
flex-wrap: wrap;
align-items: center;
}
.filter-select {
padding: 8px 12px;
border: 1px solid var(--border-primary, #e8e9ff);
border-radius: 6px;
background: white;
font-size: 14px;
}
.filter-select:focus {
outline: none;
border-color: #5856d6;
box-shadow: 0 0 0 3px rgba(88,86,214,0.1);
}
@media (max-width: 768px) {
.logs-table {
font-size: 12px;
}
.logs-table th,
.logs-table td {
padding: 8px;
}
.filter-controls {
flex-direction: column;
align-items: stretch;
}
}
</style>
{% endblock %}
{% block content %}
<div class="logs-wrapper">
<div class="logs-container">
<!-- Logs Header -->
<div class="logs-header">
<h1>
<i class="fas fa-list" style="margin-right: 12px; color: #5856d6;"></i>
{% trans "Test Plugin Logs" %}
</h1>
<p>{% trans "View detailed activity logs for the test plugin" %}</p>
</div>
<!-- Logs Content -->
<div class="logs-content">
<div class="filter-controls">
<select class="filter-select" id="action-filter" title="{% trans 'Filter logs by action type' %}">
<option value="">{% trans "All Actions" %}</option>
<option value="test_button_click">{% trans "Test Button Clicks" %}</option>
<option value="plugin_toggle">{% trans "Plugin Toggle" %}</option>
<option value="settings_update">{% trans "Settings Update" %}</option>
<option value="page_visit">{% trans "Page Visits" %}</option>
</select>
<a href="{% url 'testPlugin:plugin_home' %}" class="btn-secondary">
<i class="fas fa-arrow-left"></i>
{% trans "Back to Plugin" %}
</a>
<a href="{% url 'testPlugin:plugin_docs' %}" class="btn-secondary">
<i class="fas fa-book"></i>
{% trans "Documentation" %}
</a>
<a href="{% url 'testPlugin:security_info' %}" class="btn-secondary">
<i class="fas fa-shield-alt"></i>
{% trans "Security Info" %}
</a>
</div>
{% if logs %}
<table class="logs-table">
<thead>
<tr>
<th>{% trans "Action" %}</th>
<th>{% trans "Message" %}</th>
<th>{% trans "Timestamp" %}</th>
</tr>
</thead>
<tbody>
{% for log in logs %}
<tr class="log-row" data-action="{{ log.action }}">
<td>
<span class="log-icon {% if 'error' in log.action %}error{% elif 'success' in log.action or 'click' in log.action %}success{% elif 'warning' in log.action %}warning{% else %}info{% endif %}">
{% if 'click' in log.action %}
<i class="fas fa-mouse-pointer"></i>
{% elif 'toggle' in log.action %}
<i class="fas fa-toggle-on"></i>
{% elif 'settings' in log.action %}
<i class="fas fa-cog"></i>
{% elif 'visit' in log.action %}
<i class="fas fa-eye"></i>
{% else %}
<i class="fas fa-info-circle"></i>
{% endif %}
</span>
<span class="log-action">{{ log.action|title|replace:"_":" " }}</span>
</td>
<td>{{ log.message }}</td>
<td class="log-timestamp">{{ log.timestamp|date:"M d, Y H:i:s" }}</td>
</tr>
{% endfor %}
</tbody>
</table>
{% else %}
<div class="empty-state">
<i class="fas fa-inbox"></i>
<h3>{% trans "No Logs Found" %}</h3>
<p>{% trans "No activity logs available yet. Start using the plugin to see logs here." %}</p>
</div>
{% endif %}
</div>
</div>
</div>
<script>
document.addEventListener('DOMContentLoaded', function() {
const actionFilter = document.getElementById('action-filter');
const logRows = document.querySelectorAll('.log-row');
actionFilter.addEventListener('change', function() {
const selectedAction = this.value;
logRows.forEach(row => {
if (selectedAction === '' || row.dataset.action === selectedAction) {
row.style.display = '';
} else {
row.style.display = 'none';
}
});
});
});
</script>
{% endblock %}

View File

@@ -1,264 +0,0 @@
{% extends "baseTemplate/index.html" %}
{% load i18n %}
{% load static %}
{% block title %}{% trans "Test Plugin Settings - CyberPanel" %}{% endblock %}
{% block header_scripts %}
<style>
.settings-wrapper {
background: transparent;
padding: 20px;
}
.settings-container {
max-width: 800px;
margin: 0 auto;
}
.settings-header {
background: var(--bg-primary, white);
border-radius: 12px;
padding: 25px;
margin-bottom: 25px;
box-shadow: var(--shadow-md, 0 2px 8px rgba(0,0,0,0.08));
border: 1px solid var(--border-primary, #e8e9ff);
}
.settings-form {
background: var(--bg-primary, white);
border-radius: 12px;
padding: 25px;
box-shadow: var(--shadow-md, 0 2px 8px rgba(0,0,0,0.08));
border: 1px solid var(--border-primary, #e8e9ff);
}
.form-group {
margin-bottom: 20px;
}
.form-label {
display: block;
font-weight: 600;
color: var(--text-primary, #2f3640);
margin-bottom: 8px;
}
.form-control {
width: 100%;
padding: 12px;
border: 1px solid var(--border-primary, #e8e9ff);
border-radius: 8px;
font-size: 14px;
transition: border-color 0.3s ease;
}
.form-control:focus {
outline: none;
border-color: #5856d6;
box-shadow: 0 0 0 3px rgba(88,86,214,0.1);
}
.btn-primary {
background: linear-gradient(135deg, #5856d6, #4a90e2);
color: white;
border: none;
padding: 12px 24px;
border-radius: 8px;
font-weight: 600;
cursor: pointer;
transition: all 0.3s ease;
}
.btn-primary:hover {
transform: translateY(-2px);
box-shadow: 0 8px 20px rgba(88,86,214,0.3);
}
.btn-secondary {
background: #6c757d;
color: white;
border: none;
padding: 10px 20px;
border-radius: 6px;
font-weight: 500;
cursor: pointer;
transition: all 0.3s ease;
text-decoration: none;
display: inline-flex;
align-items: center;
gap: 8px;
margin-right: 10px;
}
.btn-secondary:hover {
background: #5a6268;
color: white;
text-decoration: none;
}
</style>
{% endblock %}
{% block content %}
<div class="settings-wrapper">
<div class="settings-container">
<!-- Settings Header -->
<div class="settings-header">
<h1>
<i class="fas fa-cog" style="margin-right: 12px; color: #5856d6;"></i>
{% trans "Test Plugin Settings" %}
</h1>
<p>{% trans "Configure your test plugin settings and preferences" %}</p>
</div>
<!-- Settings Form -->
<div class="settings-form">
<form id="settings-form">
{% csrf_token %}
<div class="form-group">
<label for="custom_message" class="form-label">
{% trans "Custom Test Message" %}
</label>
<textarea
id="custom_message"
name="custom_message"
class="form-control"
rows="3"
placeholder="Enter your custom message for the test button popup..."
>{{ settings.custom_message }}</textarea>
<small style="color: var(--text-secondary, #64748b); margin-top: 5px; display: block;">
{% trans "This message will be displayed when you click the test button" %}
</small>
</div>
<div class="form-group">
<label class="form-label">
{% trans "Plugin Status" %}
</label>
<div style="padding: 12px; background: var(--bg-secondary, #f8f9ff); border-radius: 8px; border: 1px solid var(--border-primary, #e8e9ff);">
<strong style="color: {% if settings.plugin_enabled %}#10b981{% else %}#ef4444{% endif %};">
{% if settings.plugin_enabled %}
<i class="fas fa-check-circle"></i> {% trans "Enabled" %}
{% else %}
<i class="fas fa-times-circle"></i> {% trans "Disabled" %}
{% endif %}
</strong>
<p style="margin: 8px 0 0 0; color: var(--text-secondary, #64748b); font-size: 14px;">
{% trans "Use the toggle switch on the main page to enable/disable the plugin" %}
</p>
</div>
</div>
<div class="form-group">
<label class="form-label">
{% trans "Test Statistics" %}
</label>
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 15px;">
<div style="padding: 12px; background: var(--bg-secondary, #f8f9ff); border-radius: 8px; text-align: center;">
<div style="font-size: 24px; font-weight: 700; color: #5856d6;">{{ settings.test_count }}</div>
<div style="font-size: 14px; color: var(--text-secondary, #64748b);">{% trans "Total Tests" %}</div>
</div>
<div style="padding: 12px; background: var(--bg-secondary, #f8f9ff); border-radius: 8px; text-align: center;">
<div style="font-size: 24px; font-weight: 700; color: #5856d6;">{{ settings.last_test_time|date:"M d" }}</div>
<div style="font-size: 14px; color: var(--text-secondary, #64748b);">{% trans "Last Test" %}</div>
</div>
</div>
</div>
<div style="margin-top: 30px; padding-top: 20px; border-top: 1px solid var(--border-primary, #e8e9ff);">
<button type="submit" class="btn-primary">
<i class="fas fa-save"></i>
{% trans "Save Settings" %}
</button>
<a href="{% url 'testPlugin:plugin_home' %}" class="btn-secondary">
<i class="fas fa-arrow-left"></i>
{% trans "Back to Plugin" %}
</a>
<a href="{% url 'testPlugin:plugin_docs' %}" class="btn-secondary">
<i class="fas fa-book"></i>
{% trans "Documentation" %}
</a>
<a href="{% url 'testPlugin:security_info' %}" class="btn-secondary">
<i class="fas fa-shield-alt"></i>
{% trans "Security Info" %}
</a>
</div>
</form>
</div>
</div>
</div>
<script>
document.addEventListener('DOMContentLoaded', function() {
const form = document.getElementById('settings-form');
form.addEventListener('submit', function(e) {
e.preventDefault();
const formData = new FormData(form);
const data = {
custom_message: formData.get('custom_message')
};
fetch('{% url "testPlugin:update_settings" %}', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRFToken': '{{ csrf_token }}'
},
body: JSON.stringify(data)
})
.then(response => response.json())
.then(data => {
if (data.status === 1) {
showNotification('success', 'Settings Updated', data.message);
} else {
showNotification('error', 'Error', data.error_message);
}
})
.catch(error => {
showNotification('error', 'Error', 'Failed to update settings');
});
});
function showNotification(type, title, message) {
// Create notification element
const notification = document.createElement('div');
notification.style.cssText = `
position: fixed;
top: 20px;
right: 20px;
background: white;
border-radius: 8px;
padding: 16px 20px;
box-shadow: 0 8px 24px rgba(0,0,0,0.15);
border-left: 4px solid ${type === 'success' ? '#10b981' : '#ef4444'};
z-index: 9999;
max-width: 400px;
transform: translateX(100%);
transition: transform 0.3s ease;
`;
notification.innerHTML = `
<div style="font-weight: 600; color: var(--text-primary, #2f3640); margin-bottom: 4px;">${title}</div>
<div style="font-size: 14px; color: var(--text-secondary, #64748b);">${message}</div>
`;
document.body.appendChild(notification);
// Show notification
setTimeout(() => notification.style.transform = 'translateX(0)', 100);
// Auto remove after 3 seconds
setTimeout(() => {
notification.style.transform = 'translateX(100%)';
setTimeout(() => notification.remove(), 300);
}, 3000);
}
});
</script>
{% endblock %}

View File

@@ -1,499 +0,0 @@
{% extends "baseTemplate/index.html" %}
{% load i18n %}
{% load static %}
{% block title %}{% trans "Security Information - CyberPanel" %}{% endblock %}
{% block header_scripts %}
<style>
.security-wrapper {
background: transparent;
padding: 20px;
}
.security-container {
max-width: 1200px;
margin: 0 auto;
}
.security-header {
background: var(--bg-primary, white);
border-radius: 12px;
padding: 25px;
margin-bottom: 25px;
box-shadow: var(--shadow-md, 0 2px 8px rgba(0,0,0,0.08));
border: 1px solid var(--border-primary, #e8e9ff);
}
.security-content {
background: var(--bg-primary, white);
border-radius: 12px;
padding: 25px;
box-shadow: var(--shadow-md, 0 2px 8px rgba(0,0,0,0.08));
border: 1px solid var(--border-primary, #e8e9ff);
}
.security-feature {
background: var(--bg-secondary, #f8f9ff);
padding: 20px;
border-radius: 8px;
margin-bottom: 20px;
border-left: 4px solid #10b981;
}
.security-feature.warning {
border-left-color: #f59e0b;
}
.security-feature.danger {
border-left-color: #ef4444;
}
.security-feature h3 {
color: var(--text-primary, #2f3640);
margin-bottom: 10px;
display: flex;
align-items: center;
gap: 10px;
}
.security-feature p {
color: var(--text-secondary, #64748b);
margin-bottom: 10px;
}
.security-list {
list-style: none;
padding: 0;
}
.security-list li {
padding: 8px 0;
border-bottom: 1px solid var(--border-primary, #e8e9ff);
display: flex;
align-items: center;
gap: 10px;
}
.security-list li:last-child {
border-bottom: none;
}
.security-icon {
width: 20px;
height: 20px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-size: 12px;
color: white;
}
.security-icon.success {
background: #10b981;
}
.security-icon.warning {
background: #f59e0b;
}
.security-icon.danger {
background: #ef4444;
}
.back-button {
background: #6c757d;
color: white;
border: none;
padding: 10px 20px;
border-radius: 6px;
font-weight: 500;
cursor: pointer;
transition: all 0.3s ease;
text-decoration: none;
display: inline-flex;
align-items: center;
gap: 8px;
margin-bottom: 20px;
}
.back-button:hover {
background: #5a6268;
color: white;
text-decoration: none;
}
.security-stats {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 20px;
margin-bottom: 30px;
}
.stat-card {
background: var(--bg-secondary, #f8f9ff);
padding: 20px;
border-radius: 8px;
text-align: center;
border: 1px solid var(--border-primary, #e8e9ff);
}
.stat-value {
font-size: 32px;
font-weight: 700;
color: #10b981;
margin-bottom: 8px;
}
.stat-label {
font-size: 14px;
color: var(--text-secondary, #64748b);
text-transform: uppercase;
letter-spacing: 0.5px;
}
</style>
{% endblock %}
{% block content %}
<div class="security-wrapper">
<div class="security-container">
<!-- Security Header -->
<div class="security-header">
<h1>
<i class="fas fa-shield-alt" style="margin-right: 12px; color: #10b981;"></i>
{% trans "Security Information" %}
</h1>
<p>{% trans "Comprehensive security measures implemented in the Test Plugin" %}</p>
</div>
<!-- Security Stats -->
<div class="security-stats">
<div class="stat-card">
<div class="stat-value">15+</div>
<div class="stat-label">{% trans "Security Features" %}</div>
</div>
<div class="stat-card">
<div class="stat-value">99%</div>
<div class="stat-label">{% trans "Attack Prevention" %}</div>
</div>
<div class="stat-card">
<div class="stat-value">24/7</div>
<div class="stat-label">{% trans "Monitoring" %}</div>
</div>
<div class="stat-card">
<div class="stat-value">0</div>
<div class="stat-label">{% trans "Known Vulnerabilities" %}</div>
</div>
</div>
<!-- Security Content -->
<div class="security-content">
<a href="{% url 'testPlugin:plugin_home' %}" class="back-button">
<i class="fas fa-arrow-left"></i>
{% trans "Back to Plugin" %}
</a>
<h2>{% trans "Security Features Implemented" %}</h2>
<div class="security-feature">
<h3>
<div class="security-icon success">
<i class="fas fa-lock"></i>
</div>
{% trans "Authentication & Authorization" %}
</h3>
<p>{% trans "Multi-layered authentication and authorization system" %}</p>
<ul class="security-list">
<li>
<div class="security-icon success"><i class="fas fa-check"></i></div>
{% trans "Admin-only access required for all plugin functions" %}
</li>
<li>
<div class="security-icon success"><i class="fas fa-check"></i></div>
{% trans "User session validation on every request" %}
</li>
<li>
<div class="security-icon success"><i class="fas fa-check"></i></div>
{% trans "Privilege escalation protection" %}
</li>
</ul>
</div>
<div class="security-feature">
<h3>
<div class="security-icon success">
<i class="fas fa-tachometer-alt"></i>
</div>
{% trans "Rate Limiting & Brute Force Protection" %}
</h3>
<p>{% trans "Advanced rate limiting to prevent brute force attacks" %}</p>
<ul class="security-list">
<li>
<div class="security-icon success"><i class="fas fa-check"></i></div>
{% trans "50 requests per 5-minute window per user" %}
</li>
<li>
<div class="security-icon success"><i class="fas fa-check"></i></div>
{% trans "10 test button clicks per minute limit" %}
</li>
<li>
<div class="security-icon success"><i class="fas fa-check"></i></div>
{% trans "Automatic lockout after 5 failed attempts" %}
</li>
<li>
<div class="security-icon success"><i class="fas fa-check"></i></div>
{% trans "15-minute lockout duration" %}
</li>
</ul>
</div>
<div class="security-feature">
<h3>
<div class="security-icon success">
<i class="fas fa-shield-virus"></i>
</div>
{% trans "CSRF Protection" %}
</h3>
<p>{% trans "Cross-Site Request Forgery protection on all POST requests" %}</p>
<ul class="security-list">
<li>
<div class="security-icon success"><i class="fas fa-check"></i></div>
{% trans "HMAC-based CSRF token validation" %}
</li>
<li>
<div class="security-icon success"><i class="fas fa-check"></i></div>
{% trans "Token expiration after 1 hour" %}
</li>
<li>
<div class="security-icon success"><i class="fas fa-check"></i></div>
{% trans "User-specific token generation" %}
</li>
</ul>
</div>
<div class="security-feature">
<h3>
<div class="security-icon success">
<i class="fas fa-filter"></i>
</div>
{% trans "Input Validation & Sanitization" %}
</h3>
<p>{% trans "Comprehensive input validation and sanitization" %}</p>
<ul class="security-list">
<li>
<div class="security-icon success"><i class="fas fa-check"></i></div>
{% trans "Regex-based input validation" %}
</li>
<li>
<div class="security-icon success"><i class="fas fa-check"></i></div>
{% trans "XSS attack prevention" %}
</li>
<li>
<div class="security-icon success"><i class="fas fa-check"></i></div>
{% trans "SQL injection prevention" %}
</li>
<li>
<div class="security-icon success"><i class="fas fa-check"></i></div>
{% trans "Path traversal protection" %}
</li>
<li>
<div class="security-icon success"><i class="fas fa-check"></i></div>
{% trans "Maximum input length limits" %}
</li>
</ul>
</div>
<div class="security-feature">
<h3>
<div class="security-icon success">
<i class="fas fa-eye"></i>
</div>
{% trans "Security Monitoring & Logging" %}
</h3>
<p>{% trans "Comprehensive security event monitoring and logging" %}</p>
<ul class="security-list">
<li>
<div class="security-icon success"><i class="fas fa-check"></i></div>
{% trans "All security events logged with IP and user agent" %}
</li>
<li>
<div class="security-icon success"><i class="fas fa-check"></i></div>
{% trans "Failed attempt tracking and alerting" %}
</li>
<li>
<div class="security-icon success"><i class="fas fa-check"></i></div>
{% trans "Suspicious activity detection" %}
</li>
<li>
<div class="security-icon success"><i class="fas fa-check"></i></div>
{% trans "Real-time security event monitoring" %}
</li>
</ul>
</div>
<div class="security-feature">
<h3>
<div class="security-icon success">
<i class="fas fa-server"></i>
</div>
{% trans "HTTP Security Headers" %}
</h3>
<p>{% trans "Comprehensive HTTP security headers for additional protection" %}</p>
<ul class="security-list">
<li>
<div class="security-icon success"><i class="fas fa-check"></i></div>
{% trans "X-Frame-Options: DENY (clickjacking protection)" %}
</li>
<li>
<div class="security-icon success"><i class="fas fa-check"></i></div>
{% trans "X-Content-Type-Options: nosniff" %}
</li>
<li>
<div class="security-icon success"><i class="fas fa-check"></i></div>
{% trans "X-XSS-Protection: 1; mode=block" %}
</li>
<li>
<div class="security-icon success"><i class="fas fa-check"></i></div>
{% trans "Content-Security-Policy (CSP)" %}
</li>
<li>
<div class="security-icon success"><i class="fas fa-check"></i></div>
{% trans "Strict-Transport-Security (HSTS)" %}
</li>
<li>
<div class="security-icon success"><i class="fas fa-check"></i></div>
{% trans "Referrer-Policy: strict-origin-when-cross-origin" %}
</li>
</ul>
</div>
<div class="security-feature">
<h3>
<div class="security-icon success">
<i class="fas fa-database"></i>
</div>
{% trans "Data Isolation & Privacy" %}
</h3>
<p>{% trans "User data isolation and privacy protection" %}</p>
<ul class="security-list">
<li>
<div class="security-icon success"><i class="fas fa-check"></i></div>
{% trans "User-specific data isolation" %}
</li>
<li>
<div class="security-icon success"><i class="fas fa-check"></i></div>
{% trans "Logs restricted to user's own activities" %}
</li>
<li>
<div class="security-icon success"><i class="fas fa-check"></i></div>
{% trans "Settings isolated per user" %}
</li>
<li>
<div class="security-icon success"><i class="fas fa-check"></i></div>
{% trans "No cross-user data access" %}
</li>
</ul>
</div>
<div class="security-feature warning">
<h3>
<div class="security-icon warning">
<i class="fas fa-exclamation-triangle"></i>
</div>
{% trans "Security Recommendations" %}
</h3>
<p>{% trans "Additional security measures you should implement" %}</p>
<ul class="security-list">
<li>
<div class="security-icon warning"><i class="fas fa-info"></i></div>
{% trans "Keep CyberPanel and all plugins updated" %}
</li>
<li>
<div class="security-icon warning"><i class="fas fa-info"></i></div>
{% trans "Use strong, unique passwords" %}
</li>
<li>
<div class="security-icon warning"><i class="fas fa-info"></i></div>
{% trans "Enable 2FA on your CyberPanel account" %}
</li>
<li>
<div class="security-icon warning"><i class="fas fa-info"></i></div>
{% trans "Regularly review security logs" %}
</li>
<li>
<div class="security-icon warning"><i class="fas fa-info"></i></div>
{% trans "Use HTTPS in production environments" %}
</li>
</ul>
</div>
<div class="security-feature danger">
<h3>
<div class="security-icon danger">
<i class="fas fa-bug"></i>
</div>
{% trans "Security Vulnerability Reporting" %}
</h3>
<p>{% trans "If you discover a security vulnerability, please report it responsibly" %}</p>
<ul class="security-list">
<li>
<div class="security-icon danger"><i class="fas fa-envelope"></i></div>
{% trans "Email: security@cyberpanel.net" %}
</li>
<li>
<div class="security-icon danger"><i class="fas fa-github"></i></div>
{% trans "GitHub: Create a private security issue" %}
</li>
<li>
<div class="security-icon danger"><i class="fas fa-clock"></i></div>
{% trans "Response time: Within 24-48 hours" %}
</li>
</ul>
</div>
<h2>{% trans "Security Audit Results" %}</h2>
<p>{% trans "This plugin has been designed with security as a top priority. All major security vulnerabilities have been addressed:" %}</p>
<ul class="security-list">
<li>
<div class="security-icon success"><i class="fas fa-check"></i></div>
{% trans "OWASP Top 10 vulnerabilities addressed" %}
</li>
<li>
<div class="security-icon success"><i class="fas fa-check"></i></div>
{% trans "No SQL injection vulnerabilities" %}
</li>
<li>
<div class="security-icon success"><i class="fas fa-check"></i></div>
{% trans "No XSS vulnerabilities" %}
</li>
<li>
<div class="security-icon success"><i class="fas fa-check"></i></div>
{% trans "No CSRF vulnerabilities" %}
</li>
<li>
<div class="security-icon success"><i class="fas fa-check"></i></div>
{% trans "No authentication bypass vulnerabilities" %}
</li>
<li>
<div class="security-icon success"><i class="fas fa-check"></i></div>
{% trans "No authorization bypass vulnerabilities" %}
</li>
<li>
<div class="security-icon success"><i class="fas fa-check"></i></div>
{% trans "No information disclosure vulnerabilities" %}
</li>
<li>
<div class="security-icon success"><i class="fas fa-check"></i></div>
{% trans "No path traversal vulnerabilities" %}
</li>
</ul>
<blockquote style="background: #e8f5e8; border-left: 4px solid #10b981; padding: 20px; margin: 20px 0; border-radius: 4px;">
<strong>{% trans "Security Note:" %}</strong> {% trans "This plugin implements enterprise-grade security measures. However, security is an ongoing process. Regular updates and monitoring are essential to maintain the highest security standards." %}
</blockquote>
</div>
</div>
</div>
{% endblock %}

View File

@@ -1,165 +0,0 @@
{% extends "baseTemplate/index.html" %}
{% load static %}
{% load i18n %}
{% block title %}
Test Plugin Settings - {% trans "CyberPanel" %}
{% endblock %}
{% block content %}
<div class="container-fluid">
<div class="row">
<div class="col-lg-12">
<div class="card">
<div class="card-header">
<h3 class="card-title">
<i class="fas fa-cog"></i>
{% trans "Test Plugin Settings" %}
</h3>
</div>
<div class="card-body">
<div class="alert alert-info">
<i class="fas fa-info-circle"></i>
<strong>{% trans "Plugin Information" %}</strong>
<ul class="mb-0 mt-2">
<li><strong>{% trans "Name" %}:</strong> {{ plugin_name }}</li>
<li><strong>{% trans "Version" %}:</strong> {{ version }}</li>
<li><strong>{% trans "Status" %}:</strong> <span class="badge badge-success">{% trans "Active" %}</span></li>
</ul>
</div>
<div class="card">
<div class="card-header">
<h3 class="card-title">
<i class="fas fa-sliders-h"></i>
{% trans "Configuration Options" %}
</h3>
</div>
<div class="card-body">
<form method="post" action="">
{% csrf_token %}
<div class="form-group">
<label for="test_setting_1">
<i class="fas fa-toggle-on"></i>
{% trans "Enable Test Feature" %}
</label>
<div class="custom-control custom-switch">
<input type="checkbox" class="custom-control-input" id="test_setting_1" name="test_setting_1" checked>
<label class="custom-control-label" for="test_setting_1">
{% trans "Enable this test feature" %}
</label>
</div>
<small class="form-text text-muted">
{% trans "This is a test setting for demonstration purposes." %}
</small>
</div>
<div class="form-group">
<label for="test_setting_2">
<i class="fas fa-text-width"></i>
{% trans "Test Text Input" %}
</label>
<input type="text" class="form-control" id="test_setting_2" name="test_setting_2" placeholder="{% trans 'Enter test value' %}" value="Test Value">
<small class="form-text text-muted">
{% trans "This is a test text input field." %}
</small>
</div>
<div class="form-group">
<label for="test_setting_3">
<i class="fas fa-list"></i>
{% trans "Test Select Option" %}
</label>
<select class="form-control" id="test_setting_3" name="test_setting_3">
<option value="option1">{% trans "Option 1" %}</option>
<option value="option2" selected>{% trans "Option 2" %}</option>
<option value="option3">{% trans "Option 3" %}</option>
</select>
<small class="form-text text-muted">
{% trans "Select a test option from the dropdown." %}
</small>
</div>
<div class="form-group">
<button type="submit" class="btn btn-primary">
<i class="fas fa-save"></i>
{% trans "Save Settings" %}
</button>
<button type="reset" class="btn btn-secondary">
<i class="fas fa-undo"></i>
{% trans "Reset" %}
</button>
</div>
</form>
</div>
</div>
<div class="card">
<div class="card-header">
<h3 class="card-title">
<i class="fas fa-check-circle"></i>
{% trans "Plugin Status" %}
</h3>
</div>
<div class="card-body">
<div class="alert alert-success">
<i class="fas fa-check"></i>
<strong>{% trans "Plugin is Active" %}</strong>
<p class="mb-0 mt-2">{% trans "The Test Plugin is installed and working correctly." %}</p>
</div>
<div class="row">
<div class="col-md-6">
<div class="info-box">
<span class="info-box-icon bg-info">
<i class="fas fa-info-circle"></i>
</span>
<div class="info-box-content">
<span class="info-box-text">{% trans "Plugin Name" %}</span>
<span class="info-box-number">{{ plugin_name }}</span>
</div>
</div>
</div>
<div class="col-md-6">
<div class="info-box">
<span class="info-box-icon bg-success">
<i class="fas fa-tag"></i>
</span>
<div class="info-box-content">
<span class="info-box-text">{% trans "Version" %}</span>
<span class="info-box-number">{{ version }}</span>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="card">
<div class="card-header">
<h3 class="card-title">
<i class="fas fa-question-circle"></i>
{% trans "About This Plugin" %}
</h3>
</div>
<div class="card-body">
<p>{{ description }}</p>
<p>{% trans "This is a test plugin created for testing CyberPanel plugin functionality. You can use this plugin to verify that the plugin system is working correctly." %}</p>
<h5>{% trans "Features" %}</h5>
<ul>
<li>{% trans "Enable/disable functionality" %}</li>
<li>{% trans "Test button" %}</li>
<li>{% trans "Popup messages" %}</li>
<li>{% trans "Inline integration" %}</li>
<li>{% trans "Settings page" %}</li>
</ul>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
{% endblock %}

View File

@@ -1,446 +0,0 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
OS Compatibility Test Script for Test Plugin
Tests the plugin on different operating systems
"""
import os
import sys
import subprocess
import platform
import json
from pathlib import Path
# Add the plugin directory to Python path
plugin_dir = Path(__file__).parent
sys.path.insert(0, str(plugin_dir))
from os_config import OSConfig
class OSCompatibilityTester:
"""Test OS compatibility for the Test Plugin"""
def __init__(self):
self.os_config = OSConfig()
self.test_results = {}
def run_all_tests(self):
"""Run all compatibility tests"""
print("🔍 Testing OS Compatibility for CyberPanel Test Plugin")
print("=" * 60)
# Test 1: OS Detection
self.test_os_detection()
# Test 2: Python Detection
self.test_python_detection()
# Test 3: Package Manager Detection
self.test_package_manager_detection()
# Test 4: Service Manager Detection
self.test_service_manager_detection()
# Test 5: Web Server Detection
self.test_web_server_detection()
# Test 6: File Permissions
self.test_file_permissions()
# Test 7: Network Connectivity
self.test_network_connectivity()
# Test 8: CyberPanel Integration
self.test_cyberpanel_integration()
# Display results
self.display_results()
return self.test_results
def test_os_detection(self):
"""Test OS detection functionality"""
print("\n📋 Testing OS Detection...")
try:
os_info = self.os_config.get_os_info()
is_supported = self.os_config.is_supported_os()
self.test_results['os_detection'] = {
'status': 'PASS',
'os_name': os_info['name'],
'os_version': os_info['version'],
'os_arch': os_info['architecture'],
'is_supported': is_supported,
'platform': os_info['platform']
}
print(f" ✅ OS: {os_info['name']} {os_info['version']} ({os_info['architecture']})")
print(f" ✅ Supported: {is_supported}")
except Exception as e:
self.test_results['os_detection'] = {
'status': 'FAIL',
'error': str(e)
}
print(f" ❌ Error: {e}")
def test_python_detection(self):
"""Test Python detection and version"""
print("\n🐍 Testing Python Detection...")
try:
python_path = self.os_config.python_path
pip_path = self.os_config.pip_path
# Test Python version
result = subprocess.run([python_path, '--version'],
capture_output=True, text=True, timeout=10)
if result.returncode == 0:
version = result.stdout.strip()
version_num = version.split()[1]
major, minor = map(int, version_num.split('.')[:2])
is_compatible = major == 3 and minor >= 6
self.test_results['python_detection'] = {
'status': 'PASS' if is_compatible else 'WARN',
'python_path': python_path,
'pip_path': pip_path,
'version': version,
'is_compatible': is_compatible
}
print(f" ✅ Python: {version}")
print(f" ✅ Path: {python_path}")
print(f" ✅ Pip: {pip_path}")
print(f" {'' if is_compatible else '⚠️'} Compatible: {is_compatible}")
else:
raise Exception("Python not working properly")
except Exception as e:
self.test_results['python_detection'] = {
'status': 'FAIL',
'error': str(e)
}
print(f" ❌ Error: {e}")
def test_package_manager_detection(self):
"""Test package manager detection"""
print("\n📦 Testing Package Manager Detection...")
try:
package_manager = self.os_config.package_manager
config = self.os_config.get_os_specific_config()
# Test if package manager is available
if package_manager in ['apt-get', 'apt']:
test_cmd = ['apt', '--version']
elif package_manager == 'dnf':
test_cmd = ['dnf', '--version']
elif package_manager == 'yum':
test_cmd = ['yum', '--version']
else:
test_cmd = None
is_available = True
if test_cmd:
try:
result = subprocess.run(test_cmd, capture_output=True, text=True, timeout=5)
is_available = result.returncode == 0
except:
is_available = False
self.test_results['package_manager'] = {
'status': 'PASS' if is_available else 'WARN',
'package_manager': package_manager,
'is_available': is_available,
'config': config
}
print(f" ✅ Package Manager: {package_manager}")
print(f" {'' if is_available else '⚠️'} Available: {is_available}")
except Exception as e:
self.test_results['package_manager'] = {
'status': 'FAIL',
'error': str(e)
}
print(f" ❌ Error: {e}")
def test_service_manager_detection(self):
"""Test service manager detection"""
print("\n🔧 Testing Service Manager Detection...")
try:
service_manager = self.os_config.service_manager
web_server = self.os_config.web_server
# Test if service manager is available
if service_manager == 'systemctl':
test_cmd = ['systemctl', '--version']
elif service_manager == 'service':
test_cmd = ['service', '--version']
else:
test_cmd = None
is_available = True
if test_cmd:
try:
result = subprocess.run(test_cmd, capture_output=True, text=True, timeout=5)
is_available = result.returncode == 0
except:
is_available = False
self.test_results['service_manager'] = {
'status': 'PASS' if is_available else 'WARN',
'service_manager': service_manager,
'web_server': web_server,
'is_available': is_available
}
print(f" ✅ Service Manager: {service_manager}")
print(f" ✅ Web Server: {web_server}")
print(f" {'' if is_available else '⚠️'} Available: {is_available}")
except Exception as e:
self.test_results['service_manager'] = {
'status': 'FAIL',
'error': str(e)
}
print(f" ❌ Error: {e}")
def test_web_server_detection(self):
"""Test web server detection"""
print("\n🌐 Testing Web Server Detection...")
try:
web_server = self.os_config.web_server
# Check if web server is installed
if web_server == 'apache2':
config_paths = ['/etc/apache2/apache2.conf', '/etc/apache2/httpd.conf']
else: # httpd
config_paths = ['/etc/httpd/conf/httpd.conf', '/etc/httpd/conf.d']
is_installed = any(os.path.exists(path) for path in config_paths)
self.test_results['web_server'] = {
'status': 'PASS' if is_installed else 'WARN',
'web_server': web_server,
'is_installed': is_installed,
'config_paths': config_paths
}
print(f" ✅ Web Server: {web_server}")
print(f" {'' if is_installed else '⚠️'} Installed: {is_installed}")
except Exception as e:
self.test_results['web_server'] = {
'status': 'FAIL',
'error': str(e)
}
print(f" ❌ Error: {e}")
def test_file_permissions(self):
"""Test file permissions and ownership"""
print("\n🔐 Testing File Permissions...")
try:
# Test if we can create files in plugin directory
plugin_dir = "/home/cyberpanel/plugins"
cyberpanel_dir = "/usr/local/CyberCP"
can_create_plugin_dir = True
can_create_cyberpanel_dir = True
try:
os.makedirs(plugin_dir, exist_ok=True)
except PermissionError:
can_create_plugin_dir = False
try:
os.makedirs(f"{cyberpanel_dir}/test", exist_ok=True)
os.rmdir(f"{cyberpanel_dir}/test")
except PermissionError:
can_create_cyberpanel_dir = False
self.test_results['file_permissions'] = {
'status': 'PASS' if can_create_plugin_dir and can_create_cyberpanel_dir else 'WARN',
'can_create_plugin_dir': can_create_plugin_dir,
'can_create_cyberpanel_dir': can_create_cyberpanel_dir,
'plugin_dir': plugin_dir,
'cyberpanel_dir': cyberpanel_dir
}
print(f" {'' if can_create_plugin_dir else '⚠️'} Plugin Directory: {plugin_dir}")
print(f" {'' if can_create_cyberpanel_dir else '⚠️'} CyberPanel Directory: {cyberpanel_dir}")
except Exception as e:
self.test_results['file_permissions'] = {
'status': 'FAIL',
'error': str(e)
}
print(f" ❌ Error: {e}")
def test_network_connectivity(self):
"""Test network connectivity"""
print("\n🌍 Testing Network Connectivity...")
try:
# Test GitHub connectivity
github_result = subprocess.run(['curl', '-s', '--connect-timeout', '10',
'https://github.com'],
capture_output=True, text=True, timeout=15)
github_available = github_result.returncode == 0
# Test general internet connectivity
internet_result = subprocess.run(['curl', '-s', '--connect-timeout', '10',
'https://www.google.com'],
capture_output=True, text=True, timeout=15)
internet_available = internet_result.returncode == 0
self.test_results['network_connectivity'] = {
'status': 'PASS' if github_available and internet_available else 'WARN',
'github_available': github_available,
'internet_available': internet_available
}
print(f" {'' if github_available else '⚠️'} GitHub: {github_available}")
print(f" {'' if internet_available else '⚠️'} Internet: {internet_available}")
except Exception as e:
self.test_results['network_connectivity'] = {
'status': 'FAIL',
'error': str(e)
}
print(f" ❌ Error: {e}")
def test_cyberpanel_integration(self):
"""Test CyberPanel integration"""
print("\n⚡ Testing CyberPanel Integration...")
try:
cyberpanel_dir = "/usr/local/CyberCP"
# Check if CyberPanel is installed
cyberpanel_installed = os.path.exists(cyberpanel_dir)
# Check if Django settings exist
settings_file = f"{cyberpanel_dir}/cyberpanel/settings.py"
settings_exist = os.path.exists(settings_file)
# Check if URLs file exists
urls_file = f"{cyberpanel_dir}/cyberpanel/urls.py"
urls_exist = os.path.exists(urls_file)
# Check if lscpd service exists
lscpd_exists = os.path.exists("/usr/local/lscp/bin/lscpd")
self.test_results['cyberpanel_integration'] = {
'status': 'PASS' if cyberpanel_installed and settings_exist and urls_exist else 'WARN',
'cyberpanel_installed': cyberpanel_installed,
'settings_exist': settings_exist,
'urls_exist': urls_exist,
'lscpd_exists': lscpd_exists
}
print(f" {'' if cyberpanel_installed else '⚠️'} CyberPanel Installed: {cyberpanel_installed}")
print(f" {'' if settings_exist else '⚠️'} Settings File: {settings_exist}")
print(f" {'' if urls_exist else '⚠️'} URLs File: {urls_exist}")
print(f" {'' if lscpd_exists else '⚠️'} LSCPD Service: {lscpd_exists}")
except Exception as e:
self.test_results['cyberpanel_integration'] = {
'status': 'FAIL',
'error': str(e)
}
print(f" ❌ Error: {e}")
def display_results(self):
"""Display test results summary"""
print("\n" + "=" * 60)
print("📊 COMPATIBILITY TEST RESULTS")
print("=" * 60)
total_tests = len(self.test_results)
passed_tests = sum(1 for result in self.test_results.values() if result['status'] == 'PASS')
warned_tests = sum(1 for result in self.test_results.values() if result['status'] == 'WARN')
failed_tests = sum(1 for result in self.test_results.values() if result['status'] == 'FAIL')
print(f"Total Tests: {total_tests}")
print(f"✅ Passed: {passed_tests}")
print(f"⚠️ Warnings: {warned_tests}")
print(f"❌ Failed: {failed_tests}")
if failed_tests == 0:
print("\n🎉 All tests passed! The plugin is compatible with this OS.")
elif warned_tests > 0 and failed_tests == 0:
print("\n⚠️ Some warnings detected. The plugin should work but may need attention.")
else:
print("\n❌ Some tests failed. The plugin may not work properly on this OS.")
# Show detailed results
print("\n📋 Detailed Results:")
for test_name, result in self.test_results.items():
status_icon = {'PASS': '', 'WARN': '⚠️', 'FAIL': ''}[result['status']]
print(f" {status_icon} {test_name.replace('_', ' ').title()}: {result['status']}")
if 'error' in result:
print(f" Error: {result['error']}")
# Generate compatibility report
self.generate_compatibility_report()
def generate_compatibility_report(self):
"""Generate a compatibility report file"""
try:
report = {
'timestamp': time.time(),
'os_info': self.os_config.get_os_info(),
'test_results': self.test_results,
'compatibility_score': self.calculate_compatibility_score()
}
report_file = "compatibility_report.json"
with open(report_file, 'w') as f:
json.dump(report, f, indent=2)
print(f"\n📄 Compatibility report saved to: {report_file}")
except Exception as e:
print(f"\n⚠️ Could not save compatibility report: {e}")
def calculate_compatibility_score(self):
"""Calculate overall compatibility score"""
total_tests = len(self.test_results)
if total_tests == 0:
return 0
score = 0
for result in self.test_results.values():
if result['status'] == 'PASS':
score += 1
elif result['status'] == 'WARN':
score += 0.5
return round((score / total_tests) * 100, 1)
def main():
"""Main function"""
tester = OSCompatibilityTester()
results = tester.run_all_tests()
# Exit with appropriate code
failed_tests = sum(1 for result in results.values() if result['status'] == 'FAIL')
if failed_tests > 0:
sys.exit(1)
else:
sys.exit(0)
if __name__ == "__main__":
main()

View File

@@ -1,8 +0,0 @@
from django.urls import path
from . import views
urlpatterns = [
path('', views.test_plugin_view, name='testPlugin'),
path('info/', views.plugin_info_view, name='testPluginInfo'),
path('settings/', views.settings_view, name='testPluginSettings'),
]

View File

@@ -1,54 +0,0 @@
from django.shortcuts import render, redirect
from django.http import JsonResponse
from functools import wraps
def cyberpanel_login_required(view_func):
"""
Custom decorator that checks for CyberPanel session userID
"""
@wraps(view_func)
def _wrapped_view(request, *args, **kwargs):
try:
userID = request.session['userID']
# User is authenticated via CyberPanel session
return view_func(request, *args, **kwargs)
except KeyError:
# Not logged in, redirect to login
return redirect('/')
return _wrapped_view
@cyberpanel_login_required
def test_plugin_view(request):
"""
Main view for the test plugin
"""
context = {
'plugin_name': 'Test Plugin',
'version': '1.0.0',
'description': 'A simple test plugin for CyberPanel'
}
return render(request, 'testPlugin/index.html', context)
@cyberpanel_login_required
def plugin_info_view(request):
"""
API endpoint for plugin information
"""
return JsonResponse({
'plugin_name': 'Test Plugin',
'version': '1.0.0',
'status': 'active',
'description': 'A simple test plugin for CyberPanel testing'
})
@cyberpanel_login_required
def settings_view(request):
"""
Settings page for the test plugin
"""
context = {
'plugin_name': 'Test Plugin',
'version': '1.0.0',
'description': 'A simple test plugin for CyberPanel'
}
return render(request, 'testPlugin/settings.html', context)