mirror of
https://github.com/usmannasir/cyberpanel.git
synced 2026-02-05 06:09:06 +01:00
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:
BIN
emailMarketing/.DS_Store
vendored
BIN
emailMarketing/.DS_Store
vendored
Binary file not shown.
@@ -1 +0,0 @@
|
||||
default_app_config = 'emailMarketing.apps.EmailmarketingConfig'
|
||||
@@ -1,6 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
|
||||
from django.contrib import admin
|
||||
|
||||
# Register your models here.
|
||||
@@ -1,10 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class EmailmarketingConfig(AppConfig):
|
||||
name = 'emailMarketing'
|
||||
def ready(self):
|
||||
from . import signals
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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.')
|
||||
@@ -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>
|
||||
@@ -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 |
@@ -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 %}
|
||||
@@ -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 %}
|
||||
@@ -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 %}
|
||||
|
||||
@@ -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 %}
|
||||
@@ -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">×
|
||||
</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">×
|
||||
</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 %}
|
||||
@@ -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 %}
|
||||
@@ -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">×</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
@@ -1,6 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
|
||||
from django.test import TestCase
|
||||
|
||||
# Create your tests here.
|
||||
@@ -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'),
|
||||
]
|
||||
@@ -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)
|
||||
@@ -1 +0,0 @@
|
||||
default_app_config = 'examplePlugin.apps.ExamplepluginConfig'
|
||||
@@ -1,3 +0,0 @@
|
||||
from django.contrib import admin
|
||||
|
||||
# Register your models here.
|
||||
@@ -1,8 +0,0 @@
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class ExamplepluginConfig(AppConfig):
|
||||
name = 'examplePlugin'
|
||||
|
||||
def ready(self):
|
||||
from . import signals
|
||||
@@ -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>
|
||||
@@ -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
|
||||
@@ -1,4 +0,0 @@
|
||||
#!/usr/local/CyberCP/bin/python
|
||||
RESET = '\033[0;0m'
|
||||
BLUE = "\033[0;34m"
|
||||
print(BLUE + "Running Post-Install Script..." + RESET)
|
||||
@@ -1,4 +0,0 @@
|
||||
#!/usr/local/CyberCP/bin/python
|
||||
RESET = '\033[0;0m'
|
||||
GREEN = '\033[0;32m'
|
||||
print(GREEN + "Running Pre-Install Script..." + RESET)
|
||||
@@ -1,4 +0,0 @@
|
||||
#!/usr/local/CyberCP/bin/python
|
||||
RESET = '\033[0;0m'
|
||||
GREEN = '\033[0;32m'
|
||||
print(GREEN + "Running Pre-Remove Script..." + RESET)
|
||||
@@ -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.')
|
||||
@@ -1,3 +0,0 @@
|
||||
$(document).ready(function () {
|
||||
console.log("using JS in static 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 %}
|
||||
@@ -1,3 +0,0 @@
|
||||
from django.test import TestCase
|
||||
|
||||
# Create your tests here.
|
||||
@@ -1,7 +0,0 @@
|
||||
from django.urls import path
|
||||
from . import views
|
||||
|
||||
urlpatterns = [
|
||||
path('', views.examplePlugin, name='examplePlugin'),
|
||||
]
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
from django.shortcuts import render, HttpResponse
|
||||
|
||||
|
||||
# Create your views here.
|
||||
|
||||
def examplePlugin(request):
|
||||
return render(request, 'examplePlugin/examplePlugin.html')
|
||||
@@ -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'
|
||||
|
||||
@@ -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
|
||||
@@ -1,4 +0,0 @@
|
||||
# PayPal Premium Plugin Example
|
||||
# This is a paid plugin that requires PayPal payment
|
||||
|
||||
default_app_config = 'paypalPremiumPlugin.apps.PaypalpremiumpluginConfig'
|
||||
@@ -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 {}
|
||||
@@ -1,5 +0,0 @@
|
||||
from django.apps import AppConfig
|
||||
|
||||
class PaypalpremiumpluginConfig(AppConfig):
|
||||
name = 'paypalPremiumPlugin'
|
||||
verbose_name = 'PayPal Premium Plugin Example'
|
||||
@@ -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&country.x=NO</paypal_me_url>
|
||||
<paypal_payment_link></paypal_payment_link>
|
||||
<url>/plugins/paypalPremiumPlugin/</url>
|
||||
<settings_url>/plugins/paypalPremiumPlugin/settings/</settings_url>
|
||||
</plugin>
|
||||
@@ -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',
|
||||
},
|
||||
),
|
||||
]
|
||||
@@ -1 +0,0 @@
|
||||
# PayPal Premium Plugin migrations
|
||||
@@ -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)
|
||||
@@ -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 %}
|
||||
@@ -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 %}
|
||||
@@ -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 %}
|
||||
@@ -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'),
|
||||
]
|
||||
@@ -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'
|
||||
})
|
||||
6
premiumPlugin/.gitignore
vendored
6
premiumPlugin/.gitignore
vendored
@@ -1,6 +0,0 @@
|
||||
# Security - Never commit secrets
|
||||
*.secret
|
||||
*_secret*
|
||||
patreon_config.py
|
||||
.env.patreon
|
||||
patreon_secrets.env
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -1,4 +0,0 @@
|
||||
# Premium Plugin Example
|
||||
# This is a paid plugin that requires Patreon subscription
|
||||
|
||||
default_app_config = 'premiumPlugin.apps.PremiumPluginConfig'
|
||||
@@ -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 {}
|
||||
@@ -1,5 +0,0 @@
|
||||
from django.apps import AppConfig
|
||||
|
||||
class PremiumPluginConfig(AppConfig):
|
||||
name = 'premiumPlugin'
|
||||
verbose_name = 'Premium Plugin Example'
|
||||
@@ -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&country.x=NO</paypal_me_url>
|
||||
<paypal_payment_link></paypal_payment_link>
|
||||
<url>/plugins/premiumPlugin/</url>
|
||||
<settings_url>/plugins/premiumPlugin/settings/</settings_url>
|
||||
</plugin>
|
||||
@@ -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',
|
||||
},
|
||||
),
|
||||
]
|
||||
@@ -1 +0,0 @@
|
||||
# Premium Plugin migrations
|
||||
@@ -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)
|
||||
@@ -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 %}
|
||||
@@ -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 %}
|
||||
@@ -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 %}
|
||||
@@ -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'),
|
||||
]
|
||||
@@ -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'
|
||||
})
|
||||
@@ -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'
|
||||
})
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -1,2 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
default_app_config = 'testPlugin.apps.TestPluginConfig'
|
||||
@@ -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'
|
||||
@@ -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
|
||||
@@ -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 "$@"
|
||||
@@ -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>
|
||||
@@ -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=()"
|
||||
)
|
||||
@@ -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}"
|
||||
@@ -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
|
||||
@@ -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', '<script')
|
||||
data = data.replace('</script>', '</script>')
|
||||
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
|
||||
@@ -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
|
||||
)
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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()">×</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;
|
||||
@@ -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
@@ -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()">×</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 %}
|
||||
@@ -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 %}
|
||||
@@ -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 %}
|
||||
@@ -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 %}
|
||||
@@ -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 %}
|
||||
@@ -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()
|
||||
@@ -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'),
|
||||
]
|
||||
@@ -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)
|
||||
Reference in New Issue
Block a user