Merge branch 'v2.4.0-dev' of github.com:usmannasir/cyberpanel into v2.4.0-dev

This commit is contained in:
usmannasir
2025-04-23 03:00:19 +05:00
30 changed files with 14466 additions and 8414 deletions

View File

@@ -4,8 +4,8 @@ class CLMain():
def __init__(self): def __init__(self):
self.path = '/usr/local/CyberCP/version.txt' self.path = '/usr/local/CyberCP/version.txt'
#versionInfo = json.loads(open(self.path, 'r').read()) #versionInfo = json.loads(open(self.path, 'r').read())
self.version = '2.3' self.version = '2.4'
self.build = '9' self.build = '0'
ipFile = "/etc/cyberpanel/machineIP" ipFile = "/etc/cyberpanel/machineIP"
f = open(ipFile) f = open(ipFile)

View File

@@ -34,6 +34,7 @@ import googleapiclient.discovery
from googleapiclient.discovery import build from googleapiclient.discovery import build
from websiteFunctions.models import NormalBackupDests, NormalBackupJobs, NormalBackupSites from websiteFunctions.models import NormalBackupDests, NormalBackupJobs, NormalBackupSites
from plogical.IncScheduler import IncScheduler from plogical.IncScheduler import IncScheduler
from django.http import JsonResponse
class BackupManager: class BackupManager:
localBackupPath = '/home/cyberpanel/localBackupPath' localBackupPath = '/home/cyberpanel/localBackupPath'
@@ -2338,4 +2339,76 @@ class BackupManager:
json_data = json.dumps(data_ret) json_data = json.dumps(data_ret)
return HttpResponse(json_data) return HttpResponse(json_data)
def ReconfigureSubscription(self, request=None, userID=None, data=None):
try:
if not data:
return JsonResponse({'status': 0, 'error_message': 'No data provided'})
subscription_id = data['subscription_id']
customer_id = data['customer_id']
plan_name = data['plan_name']
amount = data['amount']
interval = data['interval']
# Call platform API to update SFTP key
import requests
import json
url = 'http://platform.cyberpersons.com/Billing/ReconfigureSubscription'
payload = {
'subscription_id': subscription_id,
'key': ProcessUtilities.outputExecutioner(f'cat /root/.ssh/cyberpanel.pub'),
'serverIP': ACLManager.fetchIP(),
'email': data['email'],
'code': data['code']
}
headers = {'Content-Type': 'application/json'}
response = requests.post(url, headers=headers, data=json.dumps(payload))
if response.status_code == 200:
response_data = response.json()
if response_data.get('status') == 1:
# Create OneClickBackups record
from IncBackups.models import OneClickBackups
backup_plan = OneClickBackups(
owner=Administrator.objects.get(pk=userID),
planName=plan_name,
months='1' if interval == 'month' else '12',
price=amount,
customer=customer_id,
subscription=subscription_id,
sftpUser=response_data.get('sftpUser'),
state=1 # Set as active since SFTP is already configured
)
backup_plan.save()
# Create SFTP destination in CyberPanel
finalDic = {
'IPAddress': response_data.get('ipAddress'),
'password': 'NOT-NEEDED',
'backupSSHPort': '22',
'userName': response_data.get('sftpUser'),
'type': 'SFTP',
'path': 'cpbackups',
'name': response_data.get('sftpUser')
}
wm = BackupManager()
response_inner = wm.submitDestinationCreation(userID, finalDic)
response_data_inner = json.loads(response_inner.content.decode('utf-8'))
if response_data_inner.get('status') == 0:
return JsonResponse({'status': 0, 'error_message': response_data_inner.get('error_message')})
return JsonResponse({'status': 1})
else:
return JsonResponse({'status': 0, 'error_message': response_data.get('error_message')})
else:
return JsonResponse({'status': 0, 'error_message': f'Platform API error: {response.text}'})
except Exception as e:
return JsonResponse({'status': 0, 'error_message': str(e)})

View File

@@ -2,6 +2,309 @@
* Created by usman on 9/17/17. * Created by usman on 9/17/17.
*/ */
// Using existing CyberCP module
app.controller('backupPlanNowOneClick', function($scope, $http) {
$scope.cyberpanelLoading = true;
$scope.showVerification = false;
$scope.verificationCodeSent = false;
$scope.showEmailVerification = function() {
console.log('showEmailVerification called');
$scope.showVerification = true;
};
$scope.cancelVerification = function() {
$scope.showVerification = false;
$scope.verificationCodeSent = false;
$scope.verificationEmail = '';
$scope.verificationCode = '';
};
$scope.sendVerificationCode = function() {
$scope.cyberpanelLoading = false;
var config = {
headers: {
'X-CSRFToken': getCookie('csrftoken')
}
};
$http.post('https://platform.cyberpersons.com/Billing/SendBackupVerificationCode', {
email: $scope.verificationEmail
}, config).then(function(response) {
$scope.cyberpanelLoading = true;
if (response.data.status == 1) {
$scope.verificationCodeSent = true;
new PNotify({
title: 'Success',
text: 'Verification code sent to your email.',
type: 'success'
});
} else {
new PNotify({
title: 'Error',
text: response.data.error_message,
type: 'error'
});
}
}, function(error) {
$scope.cyberpanelLoading = true;
new PNotify({
title: 'Error',
text: 'Could not send verification code. Please try again.',
type: 'error'
});
});
};
$scope.verifyCode = function() {
$scope.cyberpanelLoading = false;
var config = {
headers: {
'X-CSRFToken': getCookie('csrftoken')
}
};
$http.post('https://platform.cyberpersons.com/Billing/VerifyBackupCode', {
email: $scope.verificationEmail,
code: $scope.verificationCode
}, config).then(function(response) {
if (response.data.status == 1) {
// After successful verification, fetch Stripe subscriptions
$http.post('https://platform.cyberpersons.com/Billing/FetchStripeSubscriptionsByEmail', {
email: $scope.verificationEmail,
code: $scope.verificationCode
}, config).then(function(subResponse) {
$scope.cyberpanelLoading = true;
if (subResponse.data.status == 1) {
$scope.showVerification = false;
$scope.subscriptions = subResponse.data.subscriptions;
$scope.showSubscriptionsTable = true;
if ($scope.subscriptions.length == 0) {
new PNotify({
title: 'Info',
text: 'No active subscriptions found for this email.',
type: 'info'
});
}
} else {
new PNotify({
title: 'Error',
text: subResponse.data.error_message,
type: 'error'
});
}
}, function(error) {
$scope.cyberpanelLoading = true;
new PNotify({
title: 'Error',
text: 'Could not fetch subscriptions. Please try again.',
type: 'error'
});
});
} else {
$scope.cyberpanelLoading = true;
new PNotify({
title: 'Error',
text: response.data.error_message,
type: 'error'
});
}
}, function(error) {
$scope.cyberpanelLoading = true;
new PNotify({
title: 'Error',
text: 'Could not verify code. Please try again.',
type: 'error'
});
});
};
$scope.fetchBackupPlans = function() {
$scope.cyberpanelLoading = false;
var config = {
headers: {
'X-CSRFToken': getCookie('csrftoken')
}
};
$http.post('https://platform.cyberpersons.com/Billing/FetchBackupPlans', {
email: $scope.verificationEmail
}, config).then(function(response) {
$scope.cyberpanelLoading = true;
if (response.data.status == 1) {
$scope.plans = response.data.plans;
new PNotify({
title: 'Success',
text: 'Backup plans fetched successfully.',
type: 'success'
});
} else {
new PNotify({
title: 'Error',
text: response.data.error_message,
type: 'error'
});
}
}, function(error) {
$scope.cyberpanelLoading = true;
new PNotify({
title: 'Error',
text: 'Could not fetch backup plans. Please try again.',
type: 'error'
});
});
};
$scope.BuyNowBackupP = function (planName, monthlyPrice, yearlyPrice, months) {
const baseURL = 'https://platform.cyberpersons.com/Billing/CreateOrderforBackupPlans';
// Get the current URL
var currentURL = window.location.href;
// Find the position of the question mark
const queryStringIndex = currentURL.indexOf('?');
// Check if there is a query string
currentURL = queryStringIndex !== -1 ? currentURL.substring(0, queryStringIndex) : currentURL;
// Encode parameters to make them URL-safe
const params = new URLSearchParams({
planName: planName,
monthlyPrice: monthlyPrice,
yearlyPrice: yearlyPrice,
returnURL: currentURL, // Add the current URL as a query parameter
months: months
});
// Build the complete URL with query string
const fullURL = `${baseURL}?${params.toString()}`;
// Redirect to the constructed URL
window.location.href = fullURL;
};
$scope.PaypalBuyNowBackup = function (planName, monthlyPrice, yearlyPrice, months) {
const baseURL = 'https://platform.cyberpersons.com/Billing/PaypalCreateOrderforBackupPlans';
// Get the current URL
var currentURL = window.location.href;
// Find the position of the question mark
const queryStringIndex = currentURL.indexOf('?');
// Check if there is a query string
currentURL = queryStringIndex !== -1 ? currentURL.substring(0, queryStringIndex) : currentURL;
// Encode parameters to make them URL-safe
const params = new URLSearchParams({
planName: planName,
monthlyPrice: monthlyPrice,
yearlyPrice: yearlyPrice,
returnURL: currentURL, // Add the current URL as a query parameter
months: months
});
// Build the complete URL with query string
const fullURL = `${baseURL}?${params.toString()}`;
// Redirect to the constructed URL
window.location.href = fullURL;
};
$scope.DeployAccount = function (id) {
$scope.cyberpanelLoading = false;
url = "/backup/DeployAccount";
var data = {
id: id
};
var config = {
headers: {
'X-CSRFToken': getCookie('csrftoken')
}
};
$http.post(url, data, config).then(ListInitialDatas, cantLoadInitialDatas);
function ListInitialDatas(response) {
$scope.cyberpanelLoading = true;
if (response.data.status === 1) {
new PNotify({
title: 'Success',
text: 'Successfully deployed.',
type: 'success'
});
window.location.reload();
} else {
new PNotify({
title: 'Operation Failed!',
text: response.data.error_message,
type: 'error'
});
}
}
function cantLoadInitialDatas(response) {
$scope.cyberpanelLoading = true;
new PNotify({
title: 'Operation Failed!',
text: 'Could not connect to server, please refresh this page',
type: 'error'
});
}
};
$scope.ReconfigureSubscription = function(subscription) {
$scope.cyberpanelLoading = false;
var config = {
headers: {
'X-CSRFToken': getCookie('csrftoken')
}
};
var data = {
subscription_id: subscription.subscription_id,
customer_id: subscription.customer,
plan_name: subscription.plan_name,
amount: subscription.amount,
interval: subscription.interval,
email: $scope.verificationEmail,
code: $scope.verificationCode
};
$http.post('/backup/ReconfigureSubscription', data, config).then(function(response) {
$scope.cyberpanelLoading = true;
if (response.data.status === 1) {
new PNotify({
title: 'Success',
text: 'Subscription configured successfully for this server.',
type: 'success'
});
// Refresh the page to show new backup plan in the list
window.location.reload();
} else {
new PNotify({
title: 'Error',
text: response.data.error_message,
type: 'error'
});
}
}, function(error) {
$scope.cyberpanelLoading = true;
new PNotify({
title: 'Error',
text: 'Could not configure subscription. Please try again.',
type: 'error'
});
});
};
});
//*** Backup site ****// //*** Backup site ****//
app.controller('backupWebsiteControl', function ($scope, $http, $timeout) { app.controller('backupWebsiteControl', function ($scope, $http, $timeout) {
@@ -2045,307 +2348,6 @@ app.controller('scheduleBackup', function ($scope, $http, $window) {
}); });
app.controller('backupPlanNowOneClick', function ($scope, $http, $window) {
$scope.cyberpanelLoading = true;
$scope.sftpHide = true;
$scope.localHide = true;
$scope.BuyNowBackupP = function (planName, monthlyPrice, yearlyPrice, months) {
const baseURL = 'https://platform.cyberpersons.com/Billing/CreateOrderforBackupPlans';
// Get the current URL
var currentURL = window.location.href;
// Find the position of the question mark
const queryStringIndex = currentURL.indexOf('?');
// Check if there is a query string
currentURL = queryStringIndex !== -1 ? currentURL.substring(0, queryStringIndex) : currentURL;
// Encode parameters to make them URL-safe
const params = new URLSearchParams({
planName: planName,
monthlyPrice: monthlyPrice,
yearlyPrice: yearlyPrice,
returnURL: currentURL, // Add the current URL as a query parameter
months: months
});
// Build the complete URL with query string
const fullURL = `${baseURL}?${params.toString()}`;
// Redirect to the constructed URL
window.location.href = fullURL;
}
$scope.fetchDetails = function () {
if ($scope.destinationType === 'SFTP') {
$scope.sftpHide = false;
$scope.localHide = true;
$scope.populateCurrentRecords();
} else {
$scope.sftpHide = true;
$scope.localHide = false;
$scope.populateCurrentRecords();
}
};
$scope.populateCurrentRecords = function () {
$scope.cyberpanelLoading = false;
url = "/backup/getCurrentBackupDestinations";
var type = 'SFTP';
if ($scope.destinationType === 'SFTP') {
type = 'SFTP';
} else {
type = 'local';
}
var data = {
type: type
};
var config = {
headers: {
'X-CSRFToken': getCookie('csrftoken')
}
};
$http.post(url, data, config).then(ListInitialDatas, cantLoadInitialDatas);
function ListInitialDatas(response) {
$scope.cyberpanelLoading = true;
if (response.data.status === 1) {
$scope.records = JSON.parse(response.data.data);
} else {
new PNotify({
title: 'Operation Failed!',
text: response.data.error_message,
type: 'error'
});
}
}
function cantLoadInitialDatas(response) {
$scope.cyberpanelLoading = true;
new PNotify({
title: 'Operation Failed!',
text: 'Could not connect to server, please refresh this page',
type: 'error'
});
}
};
$scope.addDestination = function (type) {
$scope.cyberpanelLoading = false;
url = "/backup/submitDestinationCreation";
if (type === 'SFTP') {
var data = {
type: type,
name: $scope.name,
IPAddress: $scope.IPAddress,
userName: $scope.userName,
password: $scope.password,
backupSSHPort: $scope.backupSSHPort,
path: $scope.path
};
} else {
var data = {
type: type,
path: $scope.localPath,
name: $scope.name
};
}
var config = {
headers: {
'X-CSRFToken': getCookie('csrftoken')
}
};
$http.post(url, data, config).then(ListInitialDatas, cantLoadInitialDatas);
function ListInitialDatas(response) {
$scope.cyberpanelLoading = true;
$scope.populateCurrentRecords();
if (response.data.status === 1) {
new PNotify({
title: 'Success!',
text: 'Destination successfully added.',
type: 'success'
});
} else {
new PNotify({
title: 'Operation Failed!',
text: response.data.error_message,
type: 'error'
});
}
}
function cantLoadInitialDatas(response) {
$scope.cyberpanelLoading = true;
new PNotify({
title: 'Operation Failed!',
text: 'Could not connect to server, please refresh this page',
type: 'error'
});
}
};
$scope.removeDestination = function (type, nameOrPath) {
$scope.cyberpanelLoading = false;
url = "/backup/deleteDestination";
var data = {
type: type,
nameOrPath: nameOrPath,
};
var config = {
headers: {
'X-CSRFToken': getCookie('csrftoken')
}
};
$http.post(url, data, config).then(ListInitialDatas, cantLoadInitialDatas);
function ListInitialDatas(response) {
$scope.cyberpanelLoading = true;
$scope.populateCurrentRecords();
if (response.data.status === 1) {
new PNotify({
title: 'Success!',
text: 'Destination successfully removed.',
type: 'success'
});
} else {
new PNotify({
title: 'Operation Failed!',
text: response.data.error_message,
type: 'error'
});
}
}
function cantLoadInitialDatas(response) {
$scope.cyberpanelLoading = true;
new PNotify({
title: 'Operation Failed!',
text: 'Could not connect to server, please refresh this page',
type: 'error'
});
}
};
$scope.DeployAccount = function (id) {
$scope.cyberpanelLoading = false;
url = "/backup/DeployAccount";
var data = {
id:id
};
var config = {
headers: {
'X-CSRFToken': getCookie('csrftoken')
}
};
$http.post(url, data, config).then(ListInitialDatas, cantLoadInitialDatas);
function ListInitialDatas(response) {
$scope.cyberpanelLoading = true;
if (response.data.status === 1) {
new PNotify({
title: 'Success',
text: 'Successfully deployed.',
type: 'success'
});
$window.location.reload();
} else {
new PNotify({
title: 'Operation Failed!',
text: response.data.error_message,
type: 'error'
});
}
}
function cantLoadInitialDatas(response) {
$scope.couldNotConnect = false;
restoreBackupButton.disabled = false;
}
};
//// paypal
$scope.PaypalBuyNowBackup = function (planName, monthlyPrice, yearlyPrice, months) {
const baseURL = 'https://platform.cyberpersons.com/Billing/PaypalCreateOrderforBackupPlans';
// Get the current URL
var currentURL = window.location.href;
// Find the position of the question mark
const queryStringIndex = currentURL.indexOf('?');
// Check if there is a query string
currentURL = queryStringIndex !== -1 ? currentURL.substring(0, queryStringIndex) : currentURL;
// Encode parameters to make them URL-safe
const params = new URLSearchParams({
planName: planName,
monthlyPrice: monthlyPrice,
yearlyPrice: yearlyPrice,
returnURL: currentURL, // Add the current URL as a query parameter
months: months
});
// Build the complete URL with query string
const fullURL = `${baseURL}?${params.toString()}`;
// Redirect to the constructed URL
window.location.href = fullURL;
}
});
app.controller('OneClickrestoreWebsiteControl', function ($scope, $http, $timeout) { app.controller('OneClickrestoreWebsiteControl', function ($scope, $http, $timeout) {
$scope.restoreLoading = true; $scope.restoreLoading = true;

View File

@@ -5,177 +5,601 @@
{% load static %} {% load static %}
{% get_current_language as LANGUAGE_CODE %} {% get_current_language as LANGUAGE_CODE %}
<!-- Current language: {{ LANGUAGE_CODE }} --> <!-- Current language: {{ LANGUAGE_CODE }} -->
<style>
/* Use CyberPanel color scheme */
:root {
--primary-color: #0078ff;
--secondary-color: #2096f3;
--bg-light: #f5f7f9;
--border-color: #e0e6ed;
--text-dark: #3e4b5b;
}
.info-box {
background-color: #e8f4fd;
border: 1px solid #bfdff1;
border-radius: 4px;
padding: 15px;
margin-bottom: 20px;
}
.cp-card {
background: #fff;
border-radius: 8px;
box-shadow: 0 2px 10px rgba(0,0,0,0.08);
margin-bottom: 20px;
border: 1px solid var(--border-color);
transition: transform 0.2s, box-shadow 0.2s;
height: 100%;
display: flex;
flex-direction: column;
}
.cp-card:hover {
transform: translateY(-5px);
box-shadow: 0 5px 15px rgba(0,0,0,0.1);
}
.cp-card-header {
padding: 18px 20px;
border-bottom: 1px solid var(--border-color);
background: var(--bg-light);
font-weight: 600;
font-size: 18px;
border-top-left-radius: 8px;
border-top-right-radius: 8px;
}
.cp-card-body {
padding: 20px;
flex-grow: 1;
display: flex;
flex-direction: column;
}
.cp-btn {
display: inline-block;
font-weight: 500;
text-align: center;
white-space: nowrap;
vertical-align: middle;
user-select: none;
border: 1px solid transparent;
padding: 10px 16px;
font-size: 14px;
border-radius: 6px;
transition: all 0.2s;
cursor: pointer;
text-decoration: none;
}
.cp-btn-primary {
background-color: var(--primary-color);
color: white;
}
.cp-btn-primary:hover {
background-color: #0056b3;
transform: translateY(-2px);
box-shadow: 0 4px 8px rgba(0,0,0,0.1);
}
.cp-btn-outline {
background-color: white;
border: 1px solid var(--border-color);
color: var(--text-dark);
}
.cp-btn-outline:hover {
background-color: var(--bg-light);
transform: translateY(-2px);
box-shadow: 0 4px 8px rgba(0,0,0,0.1);
}
.cp-btn-block {
display: block;
width: 100%;
margin-top: auto;
}
.cp-table {
width: 100%;
margin-bottom: 0;
border-collapse: collapse;
}
.cp-table th {
background-color: var(--bg-light);
border-bottom: 1px solid var(--border-color);
padding: 15px;
text-align: left;
font-weight: 500;
}
.cp-table td {
padding: 15px;
border-bottom: 1px solid var(--border-color);
vertical-align: middle;
}
.cp-table tr:last-child td {
border-bottom: none;
}
.cp-badge {
display: inline-block;
padding: 5px 10px;
border-radius: 30px;
font-size: 12px;
font-weight: 500;
}
.cp-badge-primary {
background-color: var(--primary-color);
color: white;
}
.cp-badge-info {
background-color: #17a2b8;
color: white;
}
.cp-form-control {
display: block;
width: 100%;
padding: 10px 15px;
background-color: #fff;
border: 1px solid var(--border-color);
border-radius: 6px;
font-size: 14px;
}
.price-box {
background-color: var(--bg-light);
border-radius: 6px;
padding: 15px;
margin-bottom: 15px;
text-align: center;
}
.price-amount {
font-size: 28px;
font-weight: 600;
color: var(--primary-color);
}
.price-period {
font-size: 14px;
color: #6c757d;
}
.plan-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
gap: 25px;
}
.billing-cycle {
display: inline-block;
padding: 6px 12px;
background-color: var(--primary-color);
color: white;
border-radius: 20px;
font-size: 13px;
}
.action-btns {
display: flex;
gap: 10px;
}
.action-btns a {
text-decoration: none;
}
.page-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 25px;
}
.page-title {
font-size: 26px;
margin-bottom: 0;
font-weight: 500;
}
.section-title {
font-size: 20px;
margin-bottom: 20px;
padding-bottom: 12px;
position: relative;
font-weight: 500;
}
.section-title:after {
content: '';
position: absolute;
bottom: 0;
left: 0;
width: 50px;
height: 3px;
background-color: var(--primary-color);
}
.btn-space {
margin-top: 15px;
}
.plans-container {
margin-top: 30px;
}
.page-description {
color: #6c757d;
margin-bottom: 25px;
}
.verify-email-section {
margin: 30px 0;
text-align: center;
}
.verify-email-btn {
display: inline-block;
padding: 12px 24px;
background-color: var(--primary-color);
color: white;
border-radius: 6px;
text-decoration: none;
font-weight: 500;
transition: all 0.2s;
border: none;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
.verify-email-btn:hover {
background-color: #0056b3;
transform: translateY(-2px);
box-shadow: 0 4px 8px rgba(0,0,0,0.1);
}
@media (max-width: 768px) {
.plan-grid {
grid-template-columns: 1fr;
}
.action-btns {
flex-direction: column;
}
}
</style>
<div class="container"> <div class="container">
<div id="page-title">
<h2>{% trans "One-click Backups" %} - <a target="_blank" <!-- Page header -->
href="https://youtu.be/mLjMg8Anq70" <div class="page-header">
style="height: 23px;line-height: 21px;" <h1 class="page-title">One-click Backups</h1>
class="btn btn-border btn-alt border-red btn-link font-red" <a href="https://youtu.be/mLjMg8Anq70" target="_blank" class="cp-btn cp-btn-outline">
title=""><span>{% trans "One-Click Backup Docs" %}</span></a> Watch Tutorial
</h2> </a>
<p>{% trans "On this page you purchase and manage one-click backups." %}</p>
</div> </div>
<p class="page-description">On this page you purchase and manage one-click backups.</p>
<div ng-controller="backupPlanNowOneClick" class="panel"> <div ng-controller="backupPlanNowOneClick">
<div class="panel-body"> <!-- Email Verification Button -->
<h3 class="title-hero"> <div class="verify-email-section" ng-hide="showVerification || showSubscriptionsTable">
{% trans "Set up Backup Destinations." %} <img ng-hide="cyberpanelLoading" <a href="javascript:void(0)" class="verify-email-btn" ng-click="showEmailVerification()">
src="{% static 'images/loading.gif' %}"> {% trans "Fetch existing backup plans if any." %}
</h3> </a>
<div class="example-box-wrapper"> </div>
{% if status == 1 %} <!-- Email Verification Section -->
<div class="alert alert-info"> <div class="cp-card" ng-show="showVerification">
<p>You have successfully purchased a backup plan.</p> <div class="cp-card-header">
{% trans "Verify Your Email" %}
</div>
<div class="cp-card-body">
<div class="row">
<div class="col-md-8 offset-md-2">
<div class="form-group mb-3">
<label class="mb-2">{% trans "Email Address" %}</label>
<input type="email"
class="cp-form-control"
ng-model="verificationEmail"
placeholder="Enter your email address">
</div>
<div class="form-group mb-3" ng-show="verificationCodeSent">
<label class="mb-2">{% trans "Verification Code" %}</label>
<input type="text"
class="cp-form-control"
ng-model="verificationCode"
placeholder="Enter verification code">
</div>
<div class="text-center mt-4">
<button type="button"
ng-click="sendVerificationCode()"
ng-hide="verificationCodeSent"
class="cp-btn cp-btn-primary">
{% trans "Send Verification Code" %}
</button>
<button type="button"
ng-click="verifyCode()"
ng-show="verificationCodeSent"
class="cp-btn cp-btn-primary">
{% trans "Verify Code" %}
</button>
<button type="button"
ng-click="cancelVerification()"
class="cp-btn cp-btn-outline ml-2">
{% trans "Cancel" %}
</button>
</div>
</div> </div>
{% elif status == 0 %} </div>
</div>
</div>
<div class="alert alert-danger"> <!-- Active Subscriptions -->
<p>Your purchase was not successful.</p> {{ message }} <div class="cp-card" ng-show="showSubscriptionsTable">
</div> <div class="cp-card-header">
{% elif status == 4 %} Your Active Subscriptions
</div>
<div class="cp-card-body p-0">
<table class="cp-table">
<thead>
<tr>
<th>Subscription ID</th>
<th>Status</th>
<th>Amount</th>
<th>Billing Interval</th>
<th>Next Billing Date</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="sub in subscriptions">
<td><code>{$ sub.subscription_id $}</code></td>
<td>
<span class="cp-badge"
ng-class="{'cp-badge-primary': sub.status === 'active', 'cp-badge-info': sub.status !== 'active'}">
{$ sub.status $}
</span>
</td>
<td>${$ sub.amount $}</td>
<td>{$ sub.interval $}</td>
<td>{$ sub.current_period_end | date:'medium' $}</td>
<td>
<button class="cp-btn cp-btn-primary"
ng-click="ReconfigureSubscription(sub)">
Configure Server
</button>
</td>
</tr>
</tbody>
</table>
</div>
</div>
<div class="alert alert-danger"> <!-- Status Messages -->
{{ message }} {% if status == 1 %}
</div> <div class="info-box">
{% endif %} <p class="mb-0">You have successfully purchased a backup plan.</p>
</div>
{% elif status == 0 %}
<div class="info-box" style="background-color: #f8d7da; border-color: #f5c6cb;">
<p class="mb-0">Your purchase was not successful. {{ message }}</p>
</div>
{% elif status == 4 %}
<div class="info-box" style="background-color: #f8d7da; border-color: #f5c6cb;">
<p class="mb-0">{{ message }}</p>
</div>
{% endif %}
<form action="/" class="form-horizontal bordered-row"> <!-- Your Backup Plans Section -->
<div class="cp-card mb-4">
<p style="font-size: 15px;margin: 1%;">With CyberPanel's one-click backups, you can easily back <div class="cp-card-header">Your Backup Plans</div>
up your website to our secure <div class="cp-card-body p-0">
servers in just 60 seconds. It's simple, fast, and reliable.</p> <table class="cp-table">
<thead>
<tr>
<!------ List of Purchased backup plans ---------------> <th>{% trans "Account" %}</th>
<th>{% trans "Plan Name" %}</th>
<div class="form-group"> <th>{% trans "Subscription" %}</th>
<th>{% trans "Billing Cycle" %}</th>
<div class="col-sm-12"> <th>{% trans "Purchase Date" %}</th>
<th>{% trans "Actions" %}</th>
<table class="table"> </tr>
<thead> </thead>
<tr> <tbody>
<th>{% trans "Account" %}</th> {% for plan in bPlans %}
<th>{% trans "Plan Name" %}</th> <tr>
<th>{% trans "Subscription" %}</th> <td>{{ plan.sftpUser }}</td>
<th>{% trans "Billing Cycle" %}</th> <td>{{ plan.planName }}</td>
<th>{% trans "Purchase Date" %}</th> <td>{{ plan.subscription }}</td>
<th>{% trans "Actions" %}</th> <td>
</tr> <span class="billing-cycle">
</thead>
<tbody>
{% for plan in bPlans %}
<tr>
<td>{{ plan.sftpUser }}</td>
<td>{{ plan.planName }}</td>
<td>{{ plan.subscription }}</td>
{% if plan.months == '1' %} {% if plan.months == '1' %}
<td>${{ plan.price }}/month</td> ${{ plan.price }}/month
{% else %} {% else %}
<td>${{ plan.price }}/year</td> ${{ plan.price }}/year
{% endif %} {% endif %}
<td>{{ plan.date }}</td> </span>
<td> </td>
{% if plan.state == 1 %} <td>{{ plan.date }}</td>
<a <td>
href="{% url 'ManageOCBackups' %}?id={{ plan.id }}"> <div class="action-btns">
<button style="margin-bottom: 1%" type="button" {% if plan.state == 1 %}
class="btn btn-primary btn-lg btn-block">{% trans "Schedule Backups" %}</button> <a href="{% url 'ManageOCBackups' %}?id={{ plan.id }}"
</a> class="cp-btn cp-btn-primary">
<a href="{% url 'RestoreOCBackups' %}?id={{ plan.id }}"> {% trans "Schedule Backups" %}
<button type="button" </a>
class="btn btn-primary btn-lg btn-block">{% trans "Restore Backups" %}</button> <a href="{% url 'RestoreOCBackups' %}?id={{ plan.id }}"
</a> class="cp-btn cp-btn-outline">
{% else %} {% trans "Restore Backups" %}
<button type="button" </a>
ng-click="DeployAccount('{{ plan.id }}')" {% else %}
class="btn btn-primary btn-lg btn-block">{% trans "Deploy Account" %}</button>
{% endif %}
</td>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
<!------ List of Purchased backup plans --------------->
<!------ List of Backup plans --------------->
<h3 class="title-hero">
{% trans "Subscribe to one-click backup plans." %} <img ng-hide="cyberpanelLoading"
src="{% static 'images/loading.gif' %}">
</h3>
<div class="form-group">
<div class="col-sm-12">
<table class="table">
<thead>
<tr>
<th>{% trans "Plan Name" %}</th>
<th>{% trans "Monthly Price" %}</th>
<th>{% trans "Yearly Price" %}</th>
<th>{% trans "Actions" %}</th>
</tr>
</thead>
<tbody>
{% for plan in plans %}
<tr>
<td>{{ plan.name }}</td>
<td>${{ plan.monthlyPrice }}</td>
<td>${{ plan.yearlyPrice }}</td>
<td>
{% if plan.name != '100GB' %}
<button type="button"
ng-click="PaypalBuyNowBackup('{{ plan.name }}', '{{ plan.monthlyPrice }}', '{{ plan.yearlyPrice }}', 1)"
class="btn btn-primary btn-lg btn-block">{% trans "Buy Monthly (Paypal)" %}</button>
{% endif %}
<button type="button" <button type="button"
ng-click="PaypalBuyNowBackup('{{ plan.name }}', '{{ plan.monthlyPrice }}', '{{ plan.yearlyPrice }}', 12)" ng-click="DeployAccount('{{ plan.id }}')"
class="btn btn-primary btn-lg btn-block">{% trans "Buy Yearly (Paypal)" %}</button> class="cp-btn cp-btn-primary">
{% if plan.name != '100GB' %} {% trans "Deploy Account" %}
<button type="button" </button>
ng-click="BuyNowBackupP('{{ plan.name }}', '{{ plan.monthlyPrice }}', '{{ plan.yearlyPrice }}', 1)" {% endif %}
class="btn btn-primary btn-lg btn-block">{% trans "Buy Monthly via Card" %}</button> </div>
{% endif %} </td>
<button type="button" </tr>
ng-click="BuyNowBackupP('{{ plan.name }}', '{{ plan.monthlyPrice }}', '{{ plan.yearlyPrice }}', 12)" {% endfor %}
class="btn btn-primary btn-lg btn-block">{% trans "Buy Yearly via Card" %}</button> </tbody>
</td> </table>
</td> </div>
</tr> </div>
{% endfor %}
</tbody> <!-- Available Backup Plans Section -->
</table> <h2 class="section-title">Available Backup Plans</h2>
<div class="plans-container">
<div class="plan-grid">
<!-- 100GB Plan -->
<div class="cp-card">
<div class="cp-card-header">100GB</div>
<div class="cp-card-body">
<div class="price-box mb-3">
<div class="price-amount">${{ plans.0.monthlyPrice }}</div>
<div class="price-period">/month</div>
</div> </div>
<div class="price-box mb-4">
<div class="price-amount">${{ plans.0.yearlyPrice }}</div>
<div class="price-period">/year</div>
</div>
<a href="javascript:void(0)"
ng-click="BuyNowBackupP('{{ plans.0.name }}', '{{ plans.0.monthlyPrice }}', '{{ plans.0.yearlyPrice }}', 12)"
class="cp-btn cp-btn-outline cp-btn-block btn-space">
Yearly via Card
</a>
</div> </div>
</div>
<!------ List of backup plans --------------->
<!-- 500GB Plan -->
<div class="cp-card">
<!--- AWS End ---> <div class="cp-card-header">500GB</div>
<div class="cp-card-body">
<div class="price-box mb-3">
</form> <div class="price-amount">${{ plans.1.monthlyPrice }}</div>
<div class="price-period">/month</div>
</div>
<div class="price-box mb-4">
<div class="price-amount">${{ plans.1.yearlyPrice }}</div>
<div class="price-period">/year</div>
</div>
<div class="btn-space">
<a href="javascript:void(0)"
ng-click="BuyNowBackupP('{{ plans.1.name }}', '{{ plans.1.monthlyPrice }}', '{{ plans.1.yearlyPrice }}', 1)"
class="cp-btn cp-btn-outline cp-btn-block mb-2">
Monthly via Card
</a>
<a href="javascript:void(0)"
ng-click="BuyNowBackupP('{{ plans.1.name }}', '{{ plans.1.monthlyPrice }}', '{{ plans.1.yearlyPrice }}', 12)"
class="cp-btn cp-btn-outline cp-btn-block">
Yearly via Card
</a>
</div>
</div>
</div>
<!-- 1TB Plan -->
<div class="cp-card">
<div class="cp-card-header">1TB</div>
<div class="cp-card-body">
<div class="price-box mb-3">
<div class="price-amount">${{ plans.2.monthlyPrice }}</div>
<div class="price-period">/month</div>
</div>
<div class="price-box mb-4">
<div class="price-amount">${{ plans.2.yearlyPrice }}</div>
<div class="price-period">/year</div>
</div>
<div class="btn-space">
<a href="javascript:void(0)"
ng-click="BuyNowBackupP('{{ plans.2.name }}', '{{ plans.2.monthlyPrice }}', '{{ plans.2.yearlyPrice }}', 1)"
class="cp-btn cp-btn-outline cp-btn-block mb-2">
Monthly via Card
</a>
<a href="javascript:void(0)"
ng-click="BuyNowBackupP('{{ plans.2.name }}', '{{ plans.2.monthlyPrice }}', '{{ plans.2.yearlyPrice }}', 12)"
class="cp-btn cp-btn-outline cp-btn-block">
Yearly via Card
</a>
</div>
</div>
</div>
<!-- 2TB Plan -->
<div class="cp-card">
<div class="cp-card-header">2TB</div>
<div class="cp-card-body">
<div class="price-box mb-3">
<div class="price-amount">${{ plans.3.monthlyPrice }}</div>
<div class="price-period">/month</div>
</div>
<div class="price-box mb-4">
<div class="price-amount">${{ plans.3.yearlyPrice }}</div>
<div class="price-period">/year</div>
</div>
<div class="btn-space">
<a href="javascript:void(0)"
ng-click="BuyNowBackupP('{{ plans.3.name }}', '{{ plans.3.monthlyPrice }}', '{{ plans.3.yearlyPrice }}', 1)"
class="cp-btn cp-btn-outline cp-btn-block mb-2">
Monthly via Card
</a>
<a href="javascript:void(0)"
ng-click="BuyNowBackupP('{{ plans.3.name }}', '{{ plans.3.monthlyPrice }}', '{{ plans.3.yearlyPrice }}', 12)"
class="cp-btn cp-btn-outline cp-btn-block">
Yearly via Card
</a>
</div>
</div>
</div>
<!-- 3TB Plan -->
<div class="cp-card">
<div class="cp-card-header">3TB</div>
<div class="cp-card-body">
<div class="price-box mb-3">
<div class="price-amount">${{ plans.4.monthlyPrice }}</div>
<div class="price-period">/month</div>
</div>
<div class="price-box mb-4">
<div class="price-amount">${{ plans.4.yearlyPrice }}</div>
<div class="price-period">/year</div>
</div>
<div class="btn-space">
<a href="javascript:void(0)"
ng-click="BuyNowBackupP('{{ plans.4.name }}', '{{ plans.4.monthlyPrice }}', '{{ plans.4.yearlyPrice }}', 1)"
class="cp-btn cp-btn-outline cp-btn-block mb-2">
Monthly via Card
</a>
<a href="javascript:void(0)"
ng-click="BuyNowBackupP('{{ plans.4.name }}', '{{ plans.4.monthlyPrice }}', '{{ plans.4.yearlyPrice }}', 12)"
class="cp-btn cp-btn-outline cp-btn-block">
Yearly via Card
</a>
</div>
</div>
</div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
{% endblock %} {% endblock %}

View File

@@ -10,6 +10,7 @@ urlpatterns = [
re_path(r'^fetchOCSites$', views.fetchOCSites, name='fetchOCSites'), re_path(r'^fetchOCSites$', views.fetchOCSites, name='fetchOCSites'),
re_path(r'^StartOCRestore$', views.StartOCRestore, name='StartOCRestore'), re_path(r'^StartOCRestore$', views.StartOCRestore, name='StartOCRestore'),
re_path(r'^DeployAccount$', views.DeployAccount, name='DeployAccount'), re_path(r'^DeployAccount$', views.DeployAccount, name='DeployAccount'),
re_path(r'^ReconfigureSubscription$', views.ReconfigureSubscription, name='ReconfigureSubscription'),
re_path(r'^backupSite$', views.backupSite, name='backupSite'), re_path(r'^backupSite$', views.backupSite, name='backupSite'),
re_path(r'^restoreSite$', views.restoreSite, name='restoreSite'), re_path(r'^restoreSite$', views.restoreSite, name='restoreSite'),

View File

@@ -5,6 +5,7 @@
import json import json
from django.shortcuts import redirect from django.shortcuts import redirect
from django.http import HttpResponse
from backup.backupManager import BackupManager from backup.backupManager import BackupManager
from backup.pluginManager import pluginManager from backup.pluginManager import pluginManager
@@ -12,6 +13,8 @@ from loginSystem.views import loadLoginPage
import os import os
from plogical.CyberCPLogFileWriter import CyberCPLogFileWriter as logging from plogical.CyberCPLogFileWriter import CyberCPLogFileWriter as logging
from django.views.decorators.csrf import csrf_exempt from django.views.decorators.csrf import csrf_exempt
from django.contrib.auth.models import User
from loginSystem.models import Administrator
def loadBackupHome(request): def loadBackupHome(request):
try: try:
@@ -538,4 +541,15 @@ def DeployAccount(request):
bm = BackupManager() bm = BackupManager()
return bm.DeployAccount(request, userID) return bm.DeployAccount(request, userID)
except KeyError: except KeyError:
return redirect(loadLoginPage) return redirect(loadLoginPage)
def ReconfigureSubscription(request):
try:
userID = request.session['userID']
bm = BackupManager()
data = json.loads(request.body)
return bm.ReconfigureSubscription(request, userID, data)
except BaseException as msg:
data_ret = {'status': 0, 'error_message': str(msg)}
json_data = json.dumps(data_ret)
return HttpResponse(json_data)

View File

@@ -77,7 +77,7 @@
<!-- HELPERS --> <!-- HELPERS -->
{% with version="2.3.8.1.1" %} {% with version="2.4.0" %}
<link rel="stylesheet" type="text/css" href="{% static 'baseTemplate/assets/finalBase/finalBase.css' %}"> <link rel="stylesheet" type="text/css" href="{% static 'baseTemplate/assets/finalBase/finalBase.css' %}">
@@ -87,7 +87,7 @@
<link rel="stylesheet" type="text/css" <link rel="stylesheet" type="text/css"
href="/static/baseTemplate/assets/themes/admin/color-schemes/default.css"> href="/static/baseTemplate/assets/themes/admin/color-schemes/default.css">
<link rel="stylesheet" type="text/css" <link rel="stylesheet" type="text/css"
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.css"> href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css">
<link rel="stylesheet" type="text/css" href="{% static 'baseTemplate/custom-js/pnotify.custom.min.css' %}"> <link rel="stylesheet" type="text/css" href="{% static 'baseTemplate/custom-js/pnotify.custom.min.css' %}">
<link rel="stylesheet" type="text/css" href="{% static 'websiteFunctions/websiteFunctions.css' %}"> <link rel="stylesheet" type="text/css" href="{% static 'websiteFunctions/websiteFunctions.css' %}">
<link rel="icon" type="image/x-icon" href="{% static 'baseTemplate/assets/finalBase/favicon.png' %}"> <link rel="icon" type="image/x-icon" href="{% static 'baseTemplate/assets/finalBase/favicon.png' %}">
@@ -248,7 +248,7 @@
title="{% trans 'Server IP Address' %}"> title="{% trans 'Server IP Address' %}">
<i class="glyph-icon tooltip-button icon-laptop" title="{% trans 'Server IP Address' %}" <i class="glyph-icon tooltip-button icon-laptop" title="{% trans 'Server IP Address' %}"
data-original-title=".icon-laptop"></i> data-original-title=".icon-laptop"></i>
<span style="color: #488a3f;font-weight: bold;">{{ ipAddress }}</span> <span onclick="copyIPAddress(); return false;" style="color: #488a3f; font-weight: bold; cursor: pointer;" title="{% trans 'Click to copy IP' %}">{{ ipAddress }}</span>
</a> </a>
<a id="sidebar-menu-item-dashboard" href="{% url 'index' %}" <a id="sidebar-menu-item-dashboard" href="{% url 'index' %}"
title="{% trans 'Dashboard' %}"> title="{% trans 'Dashboard' %}">
@@ -376,7 +376,6 @@
<a href="#" title="{% trans 'Dockersite' %}"> <a href="#" title="{% trans 'Dockersite' %}">
<div class="glyph-icon icon-globe" title="{% trans 'Docker Apps' %}"></div> <div class="glyph-icon icon-globe" title="{% trans 'Docker Apps' %}"></div>
<span>{% trans "Docker Apps" %}</span> <span>{% trans "Docker Apps" %}</span>
<span class="bs-label badge-yellow">{% trans "Beta" %}</span>
</a> </a>
<div class="sidebar-submenu"> <div class="sidebar-submenu">
@@ -1198,6 +1197,7 @@
<script src="{% static 'baseTemplate/custom-js/pnotify.custom.min.js' %}"></script> <script src="{% static 'baseTemplate/custom-js/pnotify.custom.min.js' %}"></script>
<script src="{% static 'packages/packages.js' %}?ver={{ version }}"></script> <script src="{% static 'packages/packages.js' %}?ver={{ version }}"></script>
<script src="{% static 'websiteFunctions/websiteFunctions.js' %}?ver={{ version }}"></script> <script src="{% static 'websiteFunctions/websiteFunctions.js' %}?ver={{ version }}"></script>
<script type="text/javascript" src="{% static 'websiteFunctions/DockerContainers.js' %}?ver={{ version }}"></script>
<script src="{% static 'tuning/tuning.js' %}?ver={{ version }}"></script> <script src="{% static 'tuning/tuning.js' %}?ver={{ version }}"></script>
<script src="{% static 'serverStatus/serverStatus.js' %}?ver={{ version }}"></script> <script src="{% static 'serverStatus/serverStatus.js' %}?ver={{ version }}"></script>
<script src="{% static 'dns/dns.js' %}?ver={{ version }}"></script> <script src="{% static 'dns/dns.js' %}?ver={{ version }}"></script>
@@ -1228,5 +1228,27 @@
</div> </div>
{% block footer_scripts %} {% block footer_scripts %}
{% endblock %} {% endblock %}
<script type="text/javascript">
function copyIPAddress() {
const ipAddress = '{{ ipAddress }}';
navigator.clipboard.writeText(ipAddress).then(function() {
// Show success notification using PNotify
new PNotify({
title: 'Success',
text: 'IP Address copied to clipboard!',
type: 'success',
delay: 2000
});
}).catch(function(err) {
// Show error notification using PNotify
new PNotify({
title: 'Error',
text: 'Failed to copy IP address',
type: 'error',
delay: 2000
});
});
}
</script>
</body> </body>
</html> </html>

View File

@@ -20,8 +20,8 @@ from plogical.httpProc import httpProc
# Create your views here. # Create your views here.
VERSION = '2.3' VERSION = '2.4'
BUILD = 9 BUILD = 0
@ensure_csrf_cookie @ensure_csrf_cookie

View File

@@ -765,7 +765,7 @@ else
Check_Return Check_Return
fi fi
wget https://cyberpanel.sh/www.litespeedtech.com/packages/lsapi/wsgi-lsapi-2.1.tgz wget https://www.litespeedtech.com/packages/lsapi/wsgi-lsapi-2.1.tgz
tar xf wsgi-lsapi-2.1.tgz tar xf wsgi-lsapi-2.1.tgz
cd wsgi-lsapi-2.1 || exit cd wsgi-lsapi-2.1 || exit
/usr/local/CyberPanel/bin/python ./configure.py /usr/local/CyberPanel/bin/python ./configure.py

View File

@@ -3,6 +3,7 @@
import os.path import os.path
import sys import sys
import django import django
from datetime import datetime
from plogical.DockerSites import Docker_Sites from plogical.DockerSites import Docker_Sites
@@ -1116,27 +1117,68 @@ class ContainerManager(multi.Thread):
if admin.acl.adminStatus != 1: if admin.acl.adminStatus != 1:
return ACLManager.loadError() return ACLManager.loadError()
name = data['name'] name = data['name']
containerID = data['id'] containerID = data['id']
passdata = {} # Create a Docker client
passdata["JobID"] = None client = docker.from_env()
passdata['name'] = name container = client.containers.get(containerID)
passdata['containerID'] = containerID
da = Docker_Sites(None, passdata)
retdata = da.ContainerInfo()
# Get detailed container info
container_info = container.attrs
data_ret = {'status': 1, 'error_message': 'None', 'data':retdata} # Calculate uptime
started_at = container_info.get('State', {}).get('StartedAt', '')
if started_at:
started_time = datetime.strptime(started_at.split('.')[0], '%Y-%m-%dT%H:%M:%S')
uptime = datetime.now() - started_time
uptime_str = str(uptime).split('.')[0] # Format as HH:MM:SS
else:
uptime_str = "N/A"
# Get container details
details = {
'id': container.short_id,
'name': container.name,
'status': container.status,
'created': container_info.get('Created', ''),
'started_at': started_at,
'uptime': uptime_str,
'image': container_info.get('Config', {}).get('Image', ''),
'ports': container_info.get('NetworkSettings', {}).get('Ports', {}),
'volumes': container_info.get('Mounts', []),
'environment': self._mask_sensitive_env(container_info.get('Config', {}).get('Env', [])),
'memory_usage': container.stats(stream=False)['memory_stats'].get('usage', 0),
'cpu_usage': container.stats(stream=False)['cpu_stats']['cpu_usage'].get('total_usage', 0)
}
data_ret = {'status': 1, 'error_message': 'None', 'data': [1, details]}
json_data = json.dumps(data_ret) json_data = json.dumps(data_ret)
return HttpResponse(json_data) return HttpResponse(json_data)
except BaseException as msg: except BaseException as msg:
data_ret = {'removeImageStatus': 0, 'error_message': str(msg)} data_ret = {'status': 0, 'error_message': str(msg)}
json_data = json.dumps(data_ret) json_data = json.dumps(data_ret)
return HttpResponse(json_data) return HttpResponse(json_data)
def _mask_sensitive_env(self, env_vars):
"""Helper method to mask sensitive data in environment variables"""
masked_vars = []
sensitive_keywords = ['password', 'secret', 'key', 'token', 'auth']
for var in env_vars:
if '=' in var:
name, value = var.split('=', 1)
# Check if this is a sensitive variable
if any(keyword in name.lower() for keyword in sensitive_keywords):
masked_vars.append(f"{name}=********")
else:
masked_vars.append(var)
else:
masked_vars.append(var)
return masked_vars
def getContainerApplog(self, userID=None, data=None): def getContainerApplog(self, userID=None, data=None):
try: try:
admin = Administrator.objects.get(pk=userID) admin = Administrator.objects.get(pk=userID)

View File

@@ -1,7 +1,7 @@
from django.urls import path, re_path from django.urls import path, re_path
from . import views from . import views
from websiteFunctions.views import Dockersitehome from websiteFunctions.views import Dockersitehome, startContainer, stopContainer, restartContainer
urlpatterns = [ urlpatterns = [
re_path(r'^$', views.loadDockerHome, name='dockerHome'), re_path(r'^$', views.loadDockerHome, name='dockerHome'),
@@ -27,7 +27,7 @@ urlpatterns = [
re_path(r'^recreateContainer$', views.recreateContainer, name='recreateContainer'), re_path(r'^recreateContainer$', views.recreateContainer, name='recreateContainer'),
re_path(r'^installDocker$', views.installDocker, name='installDocker'), re_path(r'^installDocker$', views.installDocker, name='installDocker'),
re_path(r'^images$', views.images, name='containerImage'), re_path(r'^images$', views.images, name='containerImage'),
re_path(r'^view/(?P<name>.+)$', views.viewContainer, name='viewContainer'), re_path(r'^view/(?P<n>.+)$', views.viewContainer, name='viewContainer'),
path('manage/<int:dockerapp>/app', Dockersitehome, name='Dockersitehome'), path('manage/<int:dockerapp>/app', Dockersitehome, name='Dockersitehome'),
path('getDockersiteList', views.getDockersiteList, name='getDockersiteList'), path('getDockersiteList', views.getDockersiteList, name='getDockersiteList'),
@@ -36,4 +36,9 @@ urlpatterns = [
path('recreateappcontainer', views.recreateappcontainer, name='recreateappcontainer'), path('recreateappcontainer', views.recreateappcontainer, name='recreateappcontainer'),
path('RestartContainerAPP', views.RestartContainerAPP, name='RestartContainerAPP'), path('RestartContainerAPP', views.RestartContainerAPP, name='RestartContainerAPP'),
path('StopContainerAPP', views.StopContainerAPP, name='StopContainerAPP'), path('StopContainerAPP', views.StopContainerAPP, name='StopContainerAPP'),
# Docker Container Actions
path('startContainer', startContainer, name='startContainer'),
path('stopContainer', stopContainer, name='stopContainer'),
path('restartContainer', restartContainer, name='restartContainer'),
] ]

View File

@@ -14,8 +14,8 @@ from os.path import *
from stat import * from stat import *
import stat import stat
VERSION = '2.3' VERSION = '2.4'
BUILD = 9 BUILD = 0
char_set = {'small': 'abcdefghijklmnopqrstuvwxyz', 'nums': '0123456789', 'big': 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'} char_set = {'small': 'abcdefghijklmnopqrstuvwxyz', 'nums': '0123456789', 'big': 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'}

View File

@@ -16,8 +16,8 @@ from django.http import HttpResponse
from django.utils import translation from django.utils import translation
# Create your views here. # Create your views here.
VERSION = '2.3' VERSION = '2.4'
BUILD = 9 BUILD = 0
def verifyLogin(request): def verifyLogin(request):

View File

@@ -4,6 +4,9 @@ import os
import sys import sys
import time import time
from random import randint from random import randint
import socket
import shutil
import docker
sys.path.append('/usr/local/CyberCP') sys.path.append('/usr/local/CyberCP')
@@ -24,11 +27,25 @@ from plogical.CyberCPLogFileWriter import CyberCPLogFileWriter as logging
import argparse import argparse
import threading as multi import threading as multi
class DockerDeploymentError(Exception):
def __init__(self, message, error_code=None, recovery_possible=True):
self.message = message
self.error_code = error_code
self.recovery_possible = recovery_possible
super().__init__(self.message)
class Docker_Sites(multi.Thread): class Docker_Sites(multi.Thread):
Wordpress = 1 Wordpress = 1
Joomla = 2 Joomla = 2
# Error codes
ERROR_DOCKER_NOT_INSTALLED = 'DOCKER_NOT_INSTALLED'
ERROR_PORT_IN_USE = 'PORT_IN_USE'
ERROR_CONTAINER_FAILED = 'CONTAINER_FAILED'
ERROR_NETWORK_FAILED = 'NETWORK_FAILED'
ERROR_VOLUME_FAILED = 'VOLUME_FAILED'
ERROR_DB_FAILED = 'DB_FAILED'
def __init__(self, function_run, data): def __init__(self, function_run, data):
multi.Thread.__init__(self) multi.Thread.__init__(self)
self.function_run = function_run self.function_run = function_run
@@ -165,15 +182,54 @@ class Docker_Sites(multi.Thread):
return 0, ReturnCode return 0, ReturnCode
else: else:
command = 'apt install docker-compose -y' # Add Docker's official GPG key
command = 'curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg'
ReturnCode = ProcessUtilities.executioner(command) ReturnCode = ProcessUtilities.executioner(command, 'root', True)
if not ReturnCode:
if ReturnCode:
return 1, None
else:
return 0, ReturnCode return 0, ReturnCode
# Add Docker repository
command = 'echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null'
ReturnCode = ProcessUtilities.executioner(command, 'root', True)
if not ReturnCode:
return 0, ReturnCode
# Update package index
command = 'apt-get update'
ReturnCode = ProcessUtilities.executioner(command)
if not ReturnCode:
return 0, ReturnCode
# Install Docker packages
command = 'apt-get install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin'
ReturnCode = ProcessUtilities.executioner(command)
if not ReturnCode:
return 0, ReturnCode
# Enable and start Docker service
command = 'systemctl enable docker'
ReturnCode = ProcessUtilities.executioner(command)
if not ReturnCode:
return 0, ReturnCode
command = 'systemctl start docker'
ReturnCode = ProcessUtilities.executioner(command)
if not ReturnCode:
return 0, ReturnCode
# Install Docker Compose
command = 'curl -L "https://github.com/docker/compose/releases/download/v2.23.2/docker-compose-linux-$(uname -m)" -o /usr/local/bin/docker-compose'
ReturnCode = ProcessUtilities.executioner(command, 'root', True)
if not ReturnCode:
return 0, ReturnCode
command = 'chmod +x /usr/local/bin/docker-compose'
ReturnCode = ProcessUtilities.executioner(command, 'root', True)
if not ReturnCode:
return 0, ReturnCode
return 1, None
@staticmethod @staticmethod
def SetupProxy(port): def SetupProxy(port):
import xml.etree.ElementTree as ET import xml.etree.ElementTree as ET
@@ -614,8 +670,6 @@ services:
### forcefully delete containers ### forcefully delete containers
import docker
# Create a Docker client # Create a Docker client
client = docker.from_env() client = docker.from_env()
@@ -651,43 +705,73 @@ services:
## This function need site name which was passed while creating the app ## This function need site name which was passed while creating the app
def ListContainers(self): def ListContainers(self):
try: try:
import docker
# Create a Docker client # Create a Docker client
client = docker.from_env() client = docker.from_env()
FilerValue = self.DockerAppName # Debug logging
if os.path.exists(ProcessUtilities.debugPath):
logging.writeToFile(f'DockerAppName: {self.DockerAppName}')
# Define the label to filter containers # List all containers without filtering first
label_filter = {'name': FilerValue} all_containers = client.containers.list(all=True)
if os.path.exists(ProcessUtilities.debugPath):
logging.writeToFile(f'Total containers found: {len(all_containers)}')
for container in all_containers:
logging.writeToFile(f'Container name: {container.name}')
# Now filter containers - handle both CentOS and Ubuntu naming
containers = []
# Get both possible name formats
if ProcessUtilities.decideDistro() == ProcessUtilities.centos or ProcessUtilities.decideDistro() == ProcessUtilities.cent8:
search_name = self.DockerAppName # Already in hyphen format for CentOS
else:
# For Ubuntu, convert underscore to hyphen as containers use hyphens
search_name = self.DockerAppName.replace('_', '-')
if os.path.exists(ProcessUtilities.debugPath):
logging.writeToFile(f'Searching for containers with name containing: {search_name}')
# List containers matching the label filter for container in all_containers:
containers = client.containers.list(filters=label_filter) if os.path.exists(ProcessUtilities.debugPath):
logging.writeToFile(f'Checking container: {container.name} against filter: {search_name}')
if search_name.lower() in container.name.lower():
containers.append(container)
if os.path.exists(ProcessUtilities.debugPath):
logging.writeToFile(f'Filtered containers count: {len(containers)}')
json_data = "[" json_data = "["
checker = 0 checker = 0
for container in containers: for container in containers:
try:
dic = {
'id': container.short_id,
'name': container.name,
'status': container.status,
'state': container.attrs.get('State', {}),
'health': container.attrs.get('State', {}).get('Health', {}).get('Status', 'unknown'),
'volumes': container.attrs['HostConfig']['Binds'] if 'HostConfig' in container.attrs else [],
'logs_50': container.logs(tail=50).decode('utf-8'),
'ports': container.attrs['HostConfig']['PortBindings'] if 'HostConfig' in container.attrs else {}
}
dic = { if checker == 0:
'id': container.short_id, json_data = json_data + json.dumps(dic)
'name': container.name, checker = 1
'status': container.status, else:
'volumes': container.attrs['HostConfig']['Binds'] if 'HostConfig' in container.attrs else [], json_data = json_data + ',' + json.dumps(dic)
'logs_50': container.logs(tail=50).decode('utf-8'), except Exception as e:
'ports': container.attrs['HostConfig']['PortBindings'] if 'HostConfig' in container.attrs else {} logging.writeToFile(f"Error processing container {container.name}: {str(e)}")
} continue
if checker == 0:
json_data = json_data + json.dumps(dic)
checker = 1
else:
json_data = json_data + ',' + json.dumps(dic)
json_data = json_data + ']' json_data = json_data + ']'
if os.path.exists(ProcessUtilities.debugPath):
logging.writeToFile(f'Final JSON data: {json_data}')
return 1, json_data return 1, json_data
except BaseException as msg: except BaseException as msg:
@@ -697,7 +781,6 @@ services:
### pass container id and number of lines to fetch from logs ### pass container id and number of lines to fetch from logs
def ContainerLogs(self): def ContainerLogs(self):
try: try:
import docker
# Create a Docker client # Create a Docker client
client = docker.from_env() client = docker.from_env()
@@ -716,7 +799,6 @@ services:
def ContainerInfo(self): def ContainerInfo(self):
try: try:
import docker
# Create a Docker client # Create a Docker client
client = docker.from_env() client = docker.from_env()
@@ -748,7 +830,6 @@ services:
def RestartContainer(self): def RestartContainer(self):
try: try:
import docker
# Create a Docker client # Create a Docker client
client = docker.from_env() client = docker.from_env()
@@ -764,7 +845,6 @@ services:
def StopContainer(self): def StopContainer(self):
try: try:
import docker
# Create a Docker client # Create a Docker client
client = docker.from_env() client = docker.from_env()
@@ -780,172 +860,550 @@ services:
##### N8N Container ##### N8N Container
def DeployN8NContainer(self): def check_container_health(self, container_name, max_retries=3, delay=80):
"""
Check if a container is running, accepting healthy, unhealthy, and starting states
Total wait time will be 4 minutes (3 retries * 80 seconds)
"""
try: try:
# Format container name to match Docker's naming convention
formatted_name = f"{self.data['ServiceName']}-{container_name}-1"
logging.writeToFile(f'Checking container health for: {formatted_name}')
for attempt in range(max_retries):
client = docker.from_env()
container = client.containers.get(formatted_name)
if container.status == 'running':
health = container.attrs.get('State', {}).get('Health', {}).get('Status')
# Accept healthy, unhealthy, and starting states as long as container is running
if health in ['healthy', 'unhealthy', 'starting'] or health is None:
logging.writeToFile(f'Container {formatted_name} is running with status: {health}')
return True
else:
health_logs = container.attrs.get('State', {}).get('Health', {}).get('Log', [])
if health_logs:
last_log = health_logs[-1]
logging.writeToFile(f'Container health check failed: {last_log.get("Output", "")}')
logging.writeToFile(f'Container {formatted_name} status: {container.status}, health: {health}, attempt {attempt + 1}/{max_retries}')
time.sleep(delay)
return False
except docker.errors.NotFound:
logging.writeToFile(f'Container {formatted_name} not found')
return False
except Exception as e:
logging.writeToFile(f'Error checking container health: {str(e)}')
return False
logging.statusWriter(self.JobID, 'Checking if Docker is installed..,0') def verify_system_resources(self):
try:
# Check available disk space using root access
command = "df -B 1G /home/docker --output=avail | tail -1"
result, output = ProcessUtilities.outputExecutioner(command, None, None, None, 1)
if result == 0:
raise DockerDeploymentError("Failed to check disk space")
available_gb = int(output.strip())
command = 'docker --help' if available_gb < 5: # Require minimum 5GB free space
result = ProcessUtilities.outputExecutioner(command) raise DockerDeploymentError(
f"Insufficient disk space. Need at least 5GB but only {available_gb}GB available.",
self.ERROR_VOLUME_FAILED
)
if os.path.exists(ProcessUtilities.debugPath): # Check if Docker is running and accessible
logging.writeToFile(f'return code of docker install {result}') command = "systemctl is-active docker"
result, docker_status = ProcessUtilities.outputExecutioner(command, None, None, None, 1)
if result == 0:
raise DockerDeploymentError("Failed to check Docker status")
if docker_status.strip() != "active":
raise DockerDeploymentError("Docker service is not running")
if result.find("not found") > -1: # Check Docker system info for resource limits
if os.path.exists(ProcessUtilities.debugPath): command = "docker info --format '{{.MemTotal}}'"
logging.writeToFile(f'About to run docker install function...') result, total_memory = ProcessUtilities.outputExecutioner(command, None, None, None, 1)
if result == 0:
raise DockerDeploymentError("Failed to get Docker memory info")
# Convert total_memory from bytes to MB
total_memory_mb = int(total_memory.strip()) / (1024 * 1024)
# Calculate required memory from site and MySQL requirements
required_memory = int(self.data['MemoryMySQL']) + int(self.data['MemorySite'])
if total_memory_mb < required_memory:
raise DockerDeploymentError(
f"Insufficient memory. Need {required_memory}MB but only {int(total_memory_mb)}MB available",
'INSUFFICIENT_MEMORY'
)
execPath = "/usr/local/CyberCP/bin/python /usr/local/CyberCP/dockerManager/dockerInstall.py" # Verify Docker group and permissions
command = "getent group docker"
result, docker_group = ProcessUtilities.outputExecutioner(command, None, None, None, 1)
if result == 0 or not docker_group:
raise DockerDeploymentError("Docker group does not exist")
return True
except DockerDeploymentError as e:
raise e
except Exception as e:
raise DockerDeploymentError(f"Resource verification failed: {str(e)}")
def setup_docker_environment(self):
try:
# Create docker directory with root
command = f"mkdir -p /home/docker/{self.data['finalURL']}"
ProcessUtilities.outputExecutioner(command, None, None, None, 1)
# Set proper permissions
command = f"chown -R {self.data['externalApp']}:docker /home/docker/{self.data['finalURL']}"
ProcessUtilities.outputExecutioner(command, None, None, None, 1)
# Create docker network if doesn't exist
command = "docker network ls | grep cyberpanel"
network_exists = ProcessUtilities.outputExecutioner(command, None, None, None, 1)
if not network_exists:
command = "docker network create cyberpanel"
ProcessUtilities.outputExecutioner(command, None, None, None, 1)
return True
except Exception as e:
raise DockerDeploymentError(f"Environment setup failed: {str(e)}")
def deploy_containers(self):
try:
# Write docker-compose file
command = f"cat > {self.data['ComposePath']} << 'EOF'\n{self.data['ComposeContent']}\nEOF"
ProcessUtilities.outputExecutioner(command, None, None, None, 1)
# Set proper permissions on compose file
command = f"chmod 600 {self.data['ComposePath']} && chown root:root {self.data['ComposePath']}"
ProcessUtilities.outputExecutioner(command, None, None, None, 1)
# Deploy with docker-compose
command = f"cd {os.path.dirname(self.data['ComposePath'])} && docker-compose up -d"
result = ProcessUtilities.outputExecutioner(command, None, None, None, 1)
if "error" in result.lower():
raise DockerDeploymentError(f"Container deployment failed: {result}")
return True
except Exception as e:
raise DockerDeploymentError(f"Deployment failed: {str(e)}")
def cleanup_failed_deployment(self):
try:
# Stop and remove containers
command = f"cd {os.path.dirname(self.data['ComposePath'])} && docker-compose down -v"
ProcessUtilities.outputExecutioner(command, None, None, None, 1)
# Remove docker directory
command = f"rm -rf /home/docker/{self.data['finalURL']}"
ProcessUtilities.outputExecutioner(command, None, None, None, 1)
# Remove compose file
command = f"rm -f {self.data['ComposePath']}"
ProcessUtilities.outputExecutioner(command, None, None, None, 1)
return True
except Exception as e:
logging.writeToFile(f"Cleanup failed: {str(e)}")
return False
def monitor_deployment(self):
try:
# Format container names
n8n_container_name = f"{self.data['ServiceName']}-{self.data['ServiceName']}-1"
db_container_name = f"{self.data['ServiceName']}-{self.data['ServiceName']}-db-1"
logging.writeToFile(f'Monitoring containers: {n8n_container_name} and {db_container_name}')
# Check container health
command = f"docker ps -a --filter name={self.data['ServiceName']} --format '{{{{.Status}}}}'"
result, status = ProcessUtilities.outputExecutioner(command, None, None, None, 1)
# Only raise error if container is exited
if "exited" in status:
# Get container logs
command = f"docker logs {n8n_container_name}"
result, logs = ProcessUtilities.outputExecutioner(command, None, None, None, 1)
raise DockerDeploymentError(f"Container exited. Logs: {logs}")
# Wait for database to be ready
max_retries = 30
retry_count = 0
db_ready = False
while retry_count < max_retries:
# Check if database container is ready
command = f"docker exec {db_container_name} pg_isready -U postgres"
result, output = ProcessUtilities.outputExecutioner(command, None, None, None, 1)
if "accepting connections" in output:
db_ready = True
break
# Check container status
command = f"docker inspect --format='{{{{.State.Status}}}}' {db_container_name}"
result, db_status = ProcessUtilities.outputExecutioner(command, None, None, None, 1)
# Only raise error if database container is in a failed state
if db_status == 'exited':
raise DockerDeploymentError(f"Database container is in {db_status} state")
retry_count += 1
time.sleep(2)
logging.writeToFile(f'Waiting for database to be ready, attempt {retry_count}/{max_retries}')
if not db_ready:
raise DockerDeploymentError("Database failed to become ready within timeout period")
# Check n8n container status
command = f"docker inspect --format='{{{{.State.Status}}}}' {n8n_container_name}"
result, n8n_status = ProcessUtilities.outputExecutioner(command, None, None, None, 1)
# Only raise error if n8n container is in a failed state
if n8n_status == 'exited':
raise DockerDeploymentError(f"n8n container is in {n8n_status} state")
logging.writeToFile(f'Deployment monitoring completed successfully. n8n status: {n8n_status}, database ready: {db_ready}')
return True
except Exception as e:
logging.writeToFile(f'Error during monitoring: {str(e)}')
raise DockerDeploymentError(f"Monitoring failed: {str(e)}")
def handle_deployment_failure(self, error, cleanup=True):
"""
Handle deployment failures and attempt recovery
"""
try:
logging.writeToFile(f'Deployment failed: {str(error)}')
if cleanup:
self.cleanup_failed_deployment()
if isinstance(error, DockerDeploymentError):
if error.error_code == self.ERROR_DOCKER_NOT_INSTALLED:
# Attempt to install Docker
execPath = "/usr/local/CyberCP/bin/python /usr/local/CyberCP/dockerManager/dockerInstall.py"
ProcessUtilities.executioner(execPath)
return True
elif error.error_code == self.ERROR_PORT_IN_USE:
# Find next available port
new_port = int(self.data['port']) + 1
while new_port < 65535:
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
result = sock.connect_ex(('127.0.0.1', new_port))
sock.close()
if result != 0:
self.data['port'] = str(new_port)
return True
new_port += 1
elif error.error_code == self.ERROR_DB_FAILED:
# Attempt database recovery
return self.recover_database()
return False
except Exception as e:
logging.writeToFile(f'Error during failure handling: {str(e)}')
return False
def recover_database(self):
"""
Attempt to recover the database container
"""
try:
client = docker.from_env()
db_container_name = f"{self.data['ServiceName']}-db"
try:
db_container = client.containers.get(db_container_name)
if db_container.status == 'running':
exec_result = db_container.exec_run(
'pg_isready -U postgres'
)
if exec_result.exit_code != 0:
db_container.restart()
time.sleep(10)
if self.check_container_health(db_container_name):
return True
except docker.errors.NotFound:
pass
return False
except Exception as e:
logging.writeToFile(f'Database recovery failed: {str(e)}')
return False
def log_deployment_metrics(self, metrics):
"""
Log deployment metrics for analysis
"""
if metrics:
try:
log_file = f"/var/log/cyberpanel/docker/{self.data['ServiceName']}_metrics.json"
os.makedirs(os.path.dirname(log_file), exist_ok=True)
with open(log_file, 'w') as f:
json.dump(metrics, f, indent=2)
except Exception as e:
logging.writeToFile(f'Error logging metrics: {str(e)}')
def DeployN8NContainer(self):
"""
Main deployment method with error handling
"""
max_retries = 3
current_try = 0
while current_try < max_retries:
try:
logging.statusWriter(self.JobID, 'Starting deployment verification...,0')
# Check Docker installation
command = 'docker --help'
result = ProcessUtilities.outputExecutioner(command)
if result.find("not found") > -1:
if os.path.exists(ProcessUtilities.debugPath):
logging.writeToFile(f'About to run docker install function...')
# Call InstallDocker to install Docker
install_result, error = self.InstallDocker()
if not install_result:
logging.statusWriter(self.JobID, f'Failed to install Docker: {error} [404]')
return 0
logging.statusWriter(self.JobID, 'Docker installation verified...,20')
# Verify system resources
self.verify_system_resources()
logging.statusWriter(self.JobID, 'System resources verified...,10')
# Create directories
command = f"mkdir -p /home/docker/{self.data['finalURL']}"
result, message = ProcessUtilities.outputExecutioner(command, None, None, None, 1)
if result == 0:
raise DockerDeploymentError(f"Failed to create directories: {message}")
logging.statusWriter(self.JobID, 'Directories created...,30')
# Generate and write docker-compose file
self.data['ServiceName'] = self.data["SiteName"].replace(' ', '-')
compose_config = self.generate_compose_config()
TempCompose = f'/home/cyberpanel/{self.data["finalURL"]}-docker-compose.yml'
with open(TempCompose, 'w') as f:
f.write(compose_config)
command = f"mv {TempCompose} {self.data['ComposePath']}"
result, message = ProcessUtilities.outputExecutioner(command, None, None, None, 1)
if result == 0:
raise DockerDeploymentError(f"Failed to move compose file: {message}")
command = f"chmod 600 {self.data['ComposePath']} && chown root:root {self.data['ComposePath']}"
ProcessUtilities.executioner(command, 'root', True)
logging.statusWriter(self.JobID, 'Docker compose file created...,40')
# Deploy containers
if ProcessUtilities.decideDistro() == ProcessUtilities.cent8 or ProcessUtilities.decideDistro() == ProcessUtilities.centos:
dockerCommand = 'docker compose'
else:
dockerCommand = 'docker-compose'
command = f"{dockerCommand} -f {self.data['ComposePath']} -p '{self.data['SiteName']}' up -d"
result, message = ProcessUtilities.outputExecutioner(command, None, None, None, 1)
if result == 0:
raise DockerDeploymentError(f"Failed to deploy containers: {message}")
logging.statusWriter(self.JobID, 'Containers deployed...,60')
# Wait for containers to be healthy
time.sleep(25)
if not self.check_container_health(f"{self.data['ServiceName']}-db") or \
not self.check_container_health(self.data['ServiceName']):
raise DockerDeploymentError("Containers failed to reach healthy state", self.ERROR_CONTAINER_FAILED)
logging.statusWriter(self.JobID, 'Containers healthy...,70')
# Setup proxy
execPath = "/usr/local/CyberCP/bin/python /usr/local/CyberCP/plogical/DockerSites.py"
execPath = execPath + f" SetupProxy --port {self.data['port']}"
ProcessUtilities.executioner(execPath) ProcessUtilities.executioner(execPath)
logging.statusWriter(self.JobID, 'Proxy configured...,80')
logging.statusWriter(self.JobID, 'Docker is ready to use..,10') # Setup ht access
execPath = "/usr/local/CyberCP/bin/python /usr/local/CyberCP/plogical/DockerSites.py"
execPath = execPath + f" SetupHTAccess --port {self.data['port']} --htaccess {self.data['htaccessPath']}"
ProcessUtilities.executioner(execPath, self.data['externalApp'])
logging.statusWriter(self.JobID, 'HTAccess configured...,90')
self.data['ServiceName'] = self.data["SiteName"].replace(' ', '-') # Restart web server
from plogical.installUtilities import installUtilities
installUtilities.reStartLiteSpeedSocket()
WPSite = f''' # Monitor deployment
version: '3.8' metrics = self.monitor_deployment()
self.log_deployment_metrics(metrics)
logging.statusWriter(self.JobID, 'Deployment completed successfully. [200]')
return True
except DockerDeploymentError as e:
logging.writeToFile(f'Deployment error: {str(e)}')
if self.handle_deployment_failure(e):
current_try += 1
continue
else:
logging.statusWriter(self.JobID, f'Deployment failed: {str(e)} [404]')
return False
except Exception as e:
logging.writeToFile(f'Unexpected error: {str(e)}')
self.handle_deployment_failure(e)
logging.statusWriter(self.JobID, f'Deployment failed: {str(e)} [404]')
return False
logging.statusWriter(self.JobID, f'Deployment failed after {max_retries} attempts [404]')
return False
def generate_compose_config(self):
"""
Generate the docker-compose configuration with improved security and reliability
"""
postgres_config = {
'image': 'postgres:16-alpine',
'user': 'root',
'healthcheck': {
'test': ["CMD-SHELL", "pg_isready -U postgres"],
'interval': '10s',
'timeout': '5s',
'retries': 5,
'start_period': '30s'
},
'environment': {
'POSTGRES_USER': 'postgres',
'POSTGRES_PASSWORD': self.data['MySQLPassword'],
'POSTGRES_DB': self.data['MySQLDBName']
}
}
n8n_config = {
'image': 'docker.n8n.io/n8nio/n8n',
'user': 'root',
'healthcheck': {
'test': ["CMD", "wget", "--spider", "http://localhost:5678"],
'interval': '20s',
'timeout': '10s',
'retries': 3
},
'environment': {
'DB_TYPE': 'postgresdb',
'DB_POSTGRESDB_HOST': f"{self.data['ServiceName']}-db",
'DB_POSTGRESDB_PORT': '5432',
'DB_POSTGRESDB_DATABASE': self.data['MySQLDBName'],
'DB_POSTGRESDB_USER': 'postgres',
'DB_POSTGRESDB_PASSWORD': self.data['MySQLPassword'],
'N8N_HOST': self.data['finalURL'],
'NODE_ENV': 'production',
'WEBHOOK_URL': f"https://{self.data['finalURL']}",
'N8N_PUSH_BACKEND': 'sse',
'GENERIC_TIMEZONE': 'UTC',
'N8N_ENCRYPTION_KEY': 'auto',
'N8N_ENFORCE_SETTINGS_FILE_PERMISSIONS': 'true',
'DB_POSTGRESDB_SCHEMA': 'public'
}
}
return f'''version: '3.8'
volumes: volumes:
db_storage: db_storage:
driver: local
n8n_storage: n8n_storage:
driver: local
services: services:
'{self.data['ServiceName']}-db': '{self.data['ServiceName']}-db':
image: docker.io/bitnami/postgresql:16 image: {postgres_config['image']}
user: root user: {postgres_config['user']}
restart: always restart: always
healthcheck:
test: {postgres_config['healthcheck']['test']}
interval: {postgres_config['healthcheck']['interval']}
timeout: {postgres_config['healthcheck']['timeout']}
retries: {postgres_config['healthcheck']['retries']}
start_period: {postgres_config['healthcheck']['start_period']}
environment: environment:
# - POSTGRES_USER:root - POSTGRES_USER={postgres_config['environment']['POSTGRES_USER']}
- POSTGRESQL_USERNAME={self.data['MySQLDBNUser']} - POSTGRES_PASSWORD={postgres_config['environment']['POSTGRES_PASSWORD']}
- POSTGRESQL_DATABASE={self.data['MySQLDBName']} - POSTGRES_DB={postgres_config['environment']['POSTGRES_DB']}
- POSTGRESQL_POSTGRES_PASSWORD={self.data['MySQLPassword']}
- POSTGRESQL_PASSWORD={self.data['MySQLPassword']}
volumes: volumes:
# - "/home/docker/{self.data['finalURL']}/db:/var/lib/postgresql/data" - "/home/docker/{self.data['finalURL']}/db:/var/lib/postgresql/data"
- "/home/docker/{self.data['finalURL']}/db:/bitnami/postgresql" networks:
- n8n-network
deploy:
resources:
limits:
cpus: '{self.data["CPUsMySQL"]}'
memory: {self.data["MemoryMySQL"]}M
'{self.data['ServiceName']}': '{self.data['ServiceName']}':
image: docker.n8n.io/n8nio/n8n image: {n8n_config['image']}
user: root user: {n8n_config['user']}
restart: always restart: always
healthcheck:
test: {n8n_config['healthcheck']['test']}
interval: {n8n_config['healthcheck']['interval']}
timeout: {n8n_config['healthcheck']['timeout']}
retries: {n8n_config['healthcheck']['retries']}
environment: environment:
- DB_TYPE=postgresdb - DB_TYPE={n8n_config['environment']['DB_TYPE']}
- DB_POSTGRESDB_HOST={self.data['ServiceName']}-db - DB_POSTGRESDB_HOST={n8n_config['environment']['DB_POSTGRESDB_HOST']}
- DB_POSTGRESDB_PORT=5432 - DB_POSTGRESDB_PORT={n8n_config['environment']['DB_POSTGRESDB_PORT']}
- DB_POSTGRESDB_DATABASE={self.data['MySQLDBName']} - DB_POSTGRESDB_DATABASE={n8n_config['environment']['DB_POSTGRESDB_DATABASE']}
- DB_POSTGRESDB_USER={self.data['MySQLDBNUser']} - DB_POSTGRESDB_USER={n8n_config['environment']['DB_POSTGRESDB_USER']}
- DB_POSTGRESDB_PASSWORD={self.data['MySQLPassword']} - DB_POSTGRESDB_PASSWORD={n8n_config['environment']['DB_POSTGRESDB_PASSWORD']}
- N8N_HOST={self.data['finalURL']} - DB_POSTGRESDB_SCHEMA={n8n_config['environment']['DB_POSTGRESDB_SCHEMA']}
- NODE_ENV=production - N8N_HOST={n8n_config['environment']['N8N_HOST']}
- WEBHOOK_URL=https://{self.data['finalURL']} - NODE_ENV={n8n_config['environment']['NODE_ENV']}
- N8N_PUSH_BACKEND=sse # Use Server-Sent Events instead of WebSockets - WEBHOOK_URL={n8n_config['environment']['WEBHOOK_URL']}
- N8N_PUSH_BACKEND={n8n_config['environment']['N8N_PUSH_BACKEND']}
- GENERIC_TIMEZONE={n8n_config['environment']['GENERIC_TIMEZONE']}
- N8N_ENCRYPTION_KEY={n8n_config['environment']['N8N_ENCRYPTION_KEY']}
- N8N_ENFORCE_SETTINGS_FILE_PERMISSIONS={n8n_config['environment']['N8N_ENFORCE_SETTINGS_FILE_PERMISSIONS']}
ports: ports:
- "{self.data['port']}:5678" - "{self.data['port']}:5678"
links: depends_on:
- {self.data['ServiceName']}-db - {self.data['ServiceName']}-db
volumes: volumes:
- "/home/docker/{self.data['finalURL']}/data:/home/node/.n8n" - "/home/docker/{self.data['finalURL']}/data:/home/node/.n8n"
depends_on: networks:
- '{self.data['ServiceName']}-db' - n8n-network
''' deploy:
resources:
### WriteConfig to compose-file limits:
cpus: '{self.data["CPUsSite"]}'
command = f"mkdir -p /home/docker/{self.data['finalURL']}" memory: {self.data["MemorySite"]}M
result, message = ProcessUtilities.outputExecutioner(command, None, None, None, 1)
if result == 0:
logging.statusWriter(self.JobID, f'Error {str(message)} . [404]')
return 0
TempCompose = f'/home/cyberpanel/{self.data["finalURL"]}-docker-compose.yml'
WriteToFile = open(TempCompose, 'w')
WriteToFile.write(WPSite)
WriteToFile.close()
command = f"mv {TempCompose} {self.data['ComposePath']}"
result, message = ProcessUtilities.outputExecutioner(command, None, None, None, 1)
if result == 0:
logging.statusWriter(self.JobID, f'Error {str(message)} . [404]')
return 0
command = f"chmod 600 {self.data['ComposePath']} && chown root:root {self.data['ComposePath']}"
ProcessUtilities.executioner(command, 'root', True)
####
if ProcessUtilities.decideDistro() == ProcessUtilities.cent8 or ProcessUtilities.decideDistro() == ProcessUtilities.centos:
dockerCommand = 'docker compose'
else:
dockerCommand = 'docker-compose'
command = f"{dockerCommand} -f {self.data['ComposePath']} -p '{self.data['SiteName']}' up -d"
result, message = ProcessUtilities.outputExecutioner(command, None, None, None, 1)
if result == 0:
logging.statusWriter(self.JobID, f'Error {str(message)} . [404]')
return 0
logging.statusWriter(self.JobID, 'Bringing containers online..,50')
time.sleep(25)
### checking if everything ran properly
passdata = {}
passdata["JobID"] = None
passdata['name'] = self.data['ServiceName']
da = Docker_Sites(None, passdata)
retdata, containers = da.ListContainers()
containers = json.loads(containers)
if os.path.exists(ProcessUtilities.debugPath):
logging.writeToFile(str(containers))
### it means less then two containers which means something went wrong
if len(containers) < 2:
logging.writeToFile(f'Unkonwn error, containers not running. [DeployN8NContainer] . [404]')
logging.statusWriter(self.JobID, f'Unkonwn error, containers not running. [DeployN8NContainer] . [404]')
return 0
### Set up Proxy
execPath = "/usr/local/CyberCP/bin/python /usr/local/CyberCP/plogical/DockerSites.py"
execPath = execPath + f" SetupProxy --port {self.data['port']}"
ProcessUtilities.executioner(execPath)
### Set up ht access
execPath = "/usr/local/CyberCP/bin/python /usr/local/CyberCP/plogical/DockerSites.py"
execPath = execPath + f" SetupHTAccess --port {self.data['port']} --htaccess {self.data['htaccessPath']}"
ProcessUtilities.executioner(execPath, self.data['externalApp'])
# if ProcessUtilities.decideDistro() == ProcessUtilities.centos or ProcessUtilities.decideDistro() == ProcessUtilities.cent8:
# group = 'nobody'
# else:
# group = 'nogroup'
#
# command = f"chown -R nobody:{group} /home/docker/{self.data['finalURL']}/data"
# ProcessUtilities.executioner(command)
### just restart ls for htaccess
from plogical.installUtilities import installUtilities
installUtilities.reStartLiteSpeedSocket()
logging.statusWriter(self.JobID, 'Completed. [200]')
except BaseException as msg:
logging.writeToFile(f'{str(msg)}. [DeployN8NContainer]')
logging.statusWriter(self.JobID, f'Error {str(msg)} . [404]')
print(str(msg))
pass
networks:
n8n-network:
driver: bridge
name: {self.data['ServiceName']}_network'''
def Main(): def Main():
try: try:

View File

@@ -12,8 +12,8 @@ from plogical.acl import ACLManager
from packages.models import Package from packages.models import Package
from baseTemplate.models import version from baseTemplate.models import version
VERSION = '2.3' VERSION = '2.4'
BUILD = 9 BUILD = 0
if not os.geteuid() == 0: if not os.geteuid() == 0:
sys.exit("\nOnly root can run this script\n") sys.exit("\nOnly root can run this script\n")

View File

@@ -53,8 +53,8 @@ try:
except: except:
pass pass
VERSION = '2.3' VERSION = '2.4'
BUILD = 9 BUILD = 0
## I am not the monster that you think I am.. ## I am not the monster that you think I am..
@@ -739,7 +739,7 @@ class backupUtilities:
dbName = database.find('dbName').text dbName = database.find('dbName').text
if (VERSION == '2.1' or VERSION == '2.3') and int(BUILD) >= 1: if ((VERSION == '2.1' or VERSION == '2.3') and int(BUILD) >= 1) or (VERSION == '2.4' and int(BUILD) >= 0):
logging.CyberCPLogFileWriter.writeToFile('Backup version 2.1.1+ detected..') logging.CyberCPLogFileWriter.writeToFile('Backup version 2.1.1+ detected..')
databaseUsers = database.findall('databaseUsers') databaseUsers = database.findall('databaseUsers')
@@ -1073,7 +1073,7 @@ class backupUtilities:
dbName = database.find('dbName').text dbName = database.find('dbName').text
if (VERSION == '2.1' or VERSION == '2.3') and int(BUILD) >= 1: if ((VERSION == '2.1' or VERSION == '2.3') and int(BUILD) >= 1) or (VERSION == '2.4' and int(BUILD) >= 0):
logging.CyberCPLogFileWriter.writeToFile('Backup version 2.1.1+ detected..') logging.CyberCPLogFileWriter.writeToFile('Backup version 2.1.1+ detected..')

View File

@@ -17,8 +17,8 @@ from CyberCP import settings
import random import random
import string import string
VERSION = '2.3' VERSION = '2.4'
BUILD = 9 BUILD = 0
CENTOS7 = 0 CENTOS7 = 0
CENTOS8 = 1 CENTOS8 = 1

View File

@@ -25,7 +25,7 @@ from managePHP.phpManager import PHPManager
from plogical.vhostConfs import vhostConfs from plogical.vhostConfs import vhostConfs
from ApachController.ApacheVhosts import ApacheVhost from ApachController.ApacheVhosts import ApacheVhost
try: try:
from websiteFunctions.models import Websites, ChildDomains, aliasDomains from websiteFunctions.models import Websites, ChildDomains, aliasDomains, DockerSites
from databases.models import Databases from databases.models import Databases
except: except:
pass pass
@@ -404,6 +404,23 @@ class vhost:
if ACLManager.FindIfChild() == 0: if ACLManager.FindIfChild() == 0:
### Delete Docker Sites first before website deletion
if os.path.exists('/home/docker/%s' % (virtualHostName)):
try:
dockerSite = DockerSites.objects.get(admin__domain=virtualHostName)
passdata = {
"domain": virtualHostName,
"name": dockerSite.SiteName
}
from plogical.DockerSites import Docker_Sites
da = Docker_Sites(None, passdata)
da.DeleteDockerApp()
dockerSite.delete()
except:
# If anything fails in Docker cleanup, at least remove the directory
shutil.rmtree('/home/docker/%s' % (virtualHostName))
for items in databases: for items in databases:
mysqlUtilities.deleteDatabase(items.dbName, items.dbUser) mysqlUtilities.deleteDatabase(items.dbName, items.dbUser)

View File

@@ -25,8 +25,8 @@ EXPIRE = 3
### Version ### Version
VERSION = '2.3' VERSION = '2.4'
BUILD = 9 BUILD = 0
def serverStatusHome(request): def serverStatusHome(request):

View File

@@ -237,7 +237,7 @@ app.controller('createWebsite', function ($scope, $http, $timeout, $window) {
$("#listFail").hide(); $("#listFail").hide();
app.controller('listWebsites', function ($scope, $http) { app.controller('listWebsites', function ($scope, $http, $window) {
$scope.currentPage = 1; $scope.currentPage = 1;
@@ -384,6 +384,244 @@ app.controller('listWebsites', function ($scope, $http) {
}; };
$scope.getFullUrl = function(url) {
console.log('getFullUrl called with:', url);
if (!url) {
// If no URL is provided, try to use the domain
if (this.wp && this.wp.domain) {
url = this.wp.domain;
} else {
return '';
}
}
if (url.startsWith('http://') || url.startsWith('https://')) {
return url;
}
return 'https://' + url;
};
$scope.showWPSites = function(domain) {
var site = $scope.WebSitesList.find(function(site) {
return site.domain === domain;
});
if (site) {
site.showWPSites = !site.showWPSites;
if (site.showWPSites && (!site.wp_sites || !site.wp_sites.length)) {
// Fetch WordPress sites if not already loaded
var config = {
headers: {
'X-CSRFToken': getCookie('csrftoken')
}
};
var data = { domain: domain };
site.loadingWPSites = true;
$http.post('/websites/getWordPressSites', data, config).then(
function(response) {
site.loadingWPSites = false;
if (response.data.status === 1) {
site.wp_sites = response.data.sites;
site.wp_sites.forEach(function(wp) {
// Ensure each WP site has a URL
if (!wp.url) {
wp.url = wp.domain || domain;
}
fetchWPSiteData(wp);
});
} else {
new PNotify({
title: 'Error!',
text: response.data.error_message || 'Could not fetch WordPress sites',
type: 'error'
});
}
},
function(response) {
site.loadingWPSites = false;
new PNotify({
title: 'Error!',
text: 'Could not connect to server',
type: 'error'
});
}
);
}
}
};
function fetchWPSiteData(wp) {
var config = {
headers: {
'X-CSRFToken': getCookie('csrftoken')
}
};
var data = { WPid: wp.id };
// Fetch site data
$http.post('/websites/FetchWPdata', data, config).then(
function(response) {
if (response.data.status === 1) {
var data = response.data.ret_data;
wp.version = data.version;
wp.phpVersion = data.phpVersion || 'PHP 7.4';
wp.searchIndex = data.searchIndex === 1;
wp.debugging = data.debugging === 1;
wp.passwordProtection = data.passwordprotection === 1;
wp.maintenanceMode = data.maintenanceMode === 1;
fetchPluginData(wp);
fetchThemeData(wp);
}
}
);
}
function fetchPluginData(wp) {
var config = {
headers: {
'X-CSRFToken': getCookie('csrftoken')
}
};
var data = { WPid: wp.id };
$http.post('/websites/GetCurrentPlugins', data, config).then(
function(response) {
if (response.data.status === 1) {
var plugins = JSON.parse(response.data.plugins);
wp.activePlugins = plugins.filter(function(p) { return p.status === 'active'; }).length;
wp.totalPlugins = plugins.length;
}
}
);
}
function fetchThemeData(wp) {
var config = {
headers: {
'X-CSRFToken': getCookie('csrftoken')
}
};
var data = { WPid: wp.id };
$http.post('/websites/GetCurrentThemes', data, config).then(
function(response) {
if (response.data.status === 1) {
var themes = JSON.parse(response.data.themes);
wp.theme = themes.find(function(t) { return t.status === 'active'; }).name;
wp.totalThemes = themes.length;
}
}
);
}
$scope.updateSetting = function(wp, setting) {
var settingMap = {
'search-indexing': 'searchIndex',
'debugging': 'debugging',
'password-protection': 'passwordProtection',
'maintenance-mode': 'maintenanceMode'
};
var data = {
siteId: wp.id,
setting: setting,
value: wp[settingMap[setting]] ? 1 : 0
};
var config = {
headers: {
'X-CSRFToken': getCookie('csrftoken')
}
};
$http.post('/websites/UpdateWPSettings', data, config).then(
function(response) {
if (!response.data.status) {
wp[settingMap[setting]] = !wp[settingMap[setting]];
new PNotify({
title: 'Operation Failed!',
text: response.data.error_message || 'Unknown error',
type: 'error'
});
} else {
new PNotify({
title: 'Success!',
text: 'Setting updated successfully.',
type: 'success'
});
}
},
function(response) {
wp[settingMap[setting]] = !wp[settingMap[setting]];
new PNotify({
title: 'Operation Failed!',
text: 'Could not connect to server, please try again.',
type: 'error'
});
}
);
};
$scope.wpLogin = function(wpId) {
window.open('/websites/AutoLogin?id=' + wpId, '_blank');
};
$scope.manageWP = function(wpId) {
window.location.href = '/websites/WPHome?ID=' + wpId;
};
$scope.deleteWPSite = function(wp) {
if (confirm('Are you sure you want to delete this WordPress site? This action cannot be undone.')) {
var config = {
headers: {
'X-CSRFToken': getCookie('csrftoken')
}
};
var data = {
wpid: wp.id,
domain: wp.domain
};
$http.post('/websites/deleteWordPressSite', data, config).then(
function(response) {
if (response.data.status === 1) {
// Remove the WP site from the list
var site = $scope.WebSitesList.find(function(site) {
return site.domain === wp.domain;
});
if (site && site.wp_sites) {
site.wp_sites = site.wp_sites.filter(function(wpSite) {
return wpSite.id !== wp.id;
});
}
new PNotify({
title: 'Success!',
text: 'WordPress site deleted successfully.',
type: 'success'
});
} else {
new PNotify({
title: 'Error!',
text: response.data.error_message || 'Could not delete WordPress site',
type: 'error'
});
}
},
function(response) {
new PNotify({
title: 'Error!',
text: 'Could not connect to server',
type: 'error'
});
}
);
}
};
$scope.visitSite = function(wp) {
var url = wp.url || wp.domain;
if (!url) return '';
if (url.startsWith('http://') || url.startsWith('https://')) {
return url;
}
return 'https://' + url;
};
}); });
@@ -3460,7 +3698,6 @@ app.controller('manageAliasController', function ($scope, $http, $timeout, $wind
$window.location.reload(); $window.location.reload();
}, 3000); }, 3000);
} else { } else {
$scope.aliasTable = false; $scope.aliasTable = false;

View File

@@ -0,0 +1,170 @@
import json
import docker
from django.http import HttpResponse
from .models import DockerSites
from loginSystem.models import Administrator
from plogical.acl import ACLManager
from django.shortcuts import redirect
from loginSystem.views import loadLoginPage
from django.views.decorators.csrf import csrf_exempt
from plogical.CyberCPLogFileWriter import CyberCPLogFileWriter as logging
def require_login(view_func):
def wrapper(request, *args, **kwargs):
try:
userID = request.session['userID']
return view_func(request, *args, **kwargs)
except KeyError:
return redirect(loadLoginPage)
return wrapper
class DockerManager:
def __init__(self):
self.client = docker.from_env()
def get_container(self, container_id):
try:
return self.client.containers.get(container_id)
except docker.errors.NotFound:
return None
except Exception as e:
logging.writeToFile(f"Error getting container {container_id}: {str(e)}")
return None
@csrf_exempt
@require_login
def startContainer(request):
try:
if request.method == 'POST':
userID = request.session['userID']
currentACL = ACLManager.loadedACL(userID)
admin = Administrator.objects.get(pk=userID)
data = json.loads(request.body)
container_id = data.get('container_id')
site_name = data.get('name')
# Verify Docker site ownership
try:
docker_site = DockerSites.objects.get(SiteName=site_name)
if currentACL['admin'] != 1 and docker_site.admin != admin and docker_site.admin.owner != admin.pk:
return HttpResponse(json.dumps({
'status': 0,
'error_message': 'Not authorized to access this container'
}))
except DockerSites.DoesNotExist:
return HttpResponse(json.dumps({
'status': 0,
'error_message': 'Docker site not found'
}))
docker_manager = DockerManager()
container = docker_manager.get_container(container_id)
if not container:
return HttpResponse(json.dumps({
'status': 0,
'error_message': 'Container not found'
}))
container.start()
return HttpResponse(json.dumps({'status': 1}))
return HttpResponse('Not allowed')
except Exception as e:
return HttpResponse(json.dumps({
'status': 0,
'error_message': str(e)
}))
@csrf_exempt
@require_login
def stopContainer(request):
try:
if request.method == 'POST':
userID = request.session['userID']
currentACL = ACLManager.loadedACL(userID)
admin = Administrator.objects.get(pk=userID)
data = json.loads(request.body)
container_id = data.get('container_id')
site_name = data.get('name')
# Verify Docker site ownership
try:
docker_site = DockerSites.objects.get(SiteName=site_name)
if currentACL['admin'] != 1 and docker_site.admin != admin and docker_site.admin.owner != admin.pk:
return HttpResponse(json.dumps({
'status': 0,
'error_message': 'Not authorized to access this container'
}))
except DockerSites.DoesNotExist:
return HttpResponse(json.dumps({
'status': 0,
'error_message': 'Docker site not found'
}))
docker_manager = DockerManager()
container = docker_manager.get_container(container_id)
if not container:
return HttpResponse(json.dumps({
'status': 0,
'error_message': 'Container not found'
}))
container.stop()
return HttpResponse(json.dumps({'status': 1}))
return HttpResponse('Not allowed')
except Exception as e:
return HttpResponse(json.dumps({
'status': 0,
'error_message': str(e)
}))
@csrf_exempt
@require_login
def restartContainer(request):
try:
if request.method == 'POST':
userID = request.session['userID']
currentACL = ACLManager.loadedACL(userID)
admin = Administrator.objects.get(pk=userID)
data = json.loads(request.body)
container_id = data.get('container_id')
site_name = data.get('name')
# Verify Docker site ownership
try:
docker_site = DockerSites.objects.get(SiteName=site_name)
if currentACL['admin'] != 1 and docker_site.admin != admin and docker_site.admin.owner != admin.pk:
return HttpResponse(json.dumps({
'status': 0,
'error_message': 'Not authorized to access this container'
}))
except DockerSites.DoesNotExist:
return HttpResponse(json.dumps({
'status': 0,
'error_message': 'Docker site not found'
}))
docker_manager = DockerManager()
container = docker_manager.get_container(container_id)
if not container:
return HttpResponse(json.dumps({
'status': 0,
'error_message': 'Container not found'
}))
container.restart()
return HttpResponse(json.dumps({'status': 1}))
return HttpResponse('Not allowed')
except Exception as e:
return HttpResponse(json.dumps({
'status': 0,
'error_message': str(e)
}))

View File

@@ -0,0 +1,395 @@
app.controller('ListDockersitecontainer', function ($scope, $http) {
$scope.cyberPanelLoading = true;
$scope.conatinerview = true;
$('#cyberpanelLoading').hide();
// Format bytes to human readable
function formatBytes(bytes, decimals = 2) {
if (bytes === 0) return '0 Bytes';
const k = 1024;
const dm = decimals < 0 ? 0 : decimals;
const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB'];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i];
}
$scope.getcontainer = function () {
$('#cyberpanelLoading').show();
url = "/docker/getDockersiteList";
var data = {'name': $('#sitename').html()};
var config = {
headers: {
'X-CSRFToken': getCookie('csrftoken')
}
};
$http.post(url, data, config).then(ListInitialData, cantLoadInitialData);
function ListInitialData(response) {
$('#cyberpanelLoading').hide();
if (response.data.status === 1) {
$scope.cyberPanelLoading = true;
var finalData = JSON.parse(response.data.data[1]);
$scope.ContainerList = finalData;
$("#listFail").hide();
} else {
$("#listFail").fadeIn();
$scope.errorMessage = response.data.error_message;
}
}
function cantLoadInitialData(response) {
$scope.cyberPanelLoading = true;
$('#cyberpanelLoading').hide();
new PNotify({
title: 'Operation Failed!',
text: 'Connection disrupted, refresh the page.',
type: 'error'
});
}
};
$scope.Lunchcontainer = function (containerid) {
$scope.cyberpanelLoading = false;
$('#cyberpanelLoading').show();
var url = "/docker/getContainerAppinfo";
var data = {
'name': $('#sitename').html(),
'id': containerid
};
var config = {
headers: {
'X-CSRFToken': getCookie('csrftoken')
}
};
$http.post(url, data, config).then(ListInitialData, cantLoadInitialData);
function ListInitialData(response) {
$scope.cyberpanelLoading = true;
$('#cyberpanelLoading').hide();
if (response.data.status === 1) {
var containerInfo = response.data.data[1];
console.log("Full container info:", containerInfo);
// Find the container in the list and update its information
for (var i = 0; i < $scope.ContainerList.length; i++) {
if ($scope.ContainerList[i].id === containerid) {
// Basic Information
$scope.ContainerList[i].status = containerInfo.status;
$scope.ContainerList[i].created = new Date(containerInfo.created);
$scope.ContainerList[i].uptime = containerInfo.uptime;
$scope.ContainerList[i].image = containerInfo.image;
// Environment Variables - ensure it's properly set
if (containerInfo.environment) {
console.log("Setting environment:", containerInfo.environment);
$scope.ContainerList[i].environment = containerInfo.environment;
console.log("Container after env update:", $scope.ContainerList[i]);
} else {
console.log("No environment in container info");
}
// Resource Usage
var memoryBytes = containerInfo.memory_usage;
$scope.ContainerList[i].memoryUsage = formatBytes(memoryBytes);
$scope.ContainerList[i].memoryUsagePercent = (memoryBytes / (1024 * 1024 * 1024)) * 100;
$scope.ContainerList[i].cpuUsagePercent = (containerInfo.cpu_usage / 10000000000) * 100;
// Network & Ports
$scope.ContainerList[i].ports = containerInfo.ports;
// Volumes
$scope.ContainerList[i].volumes = containerInfo.volumes;
break;
}
}
// Get container logs
$scope.getcontainerlog(containerid);
} else {
new PNotify({
title: 'Operation Failed!',
text: response.data.error_message,
type: 'error'
});
}
}
function cantLoadInitialData(response) {
$scope.cyberpanelLoading = true;
$('#cyberpanelLoading').hide();
new PNotify({
title: 'Operation Failed!',
text: 'Connection disrupted, refresh the page.',
type: 'error'
});
}
};
$scope.getcontainerlog = function (containerid) {
$scope.cyberpanelLoading = false;
var url = "/docker/getContainerApplog";
var data = {
'name': $('#sitename').html(),
'id': containerid
};
var config = {
headers: {
'X-CSRFToken': getCookie('csrftoken')
}
};
$http.post(url, data, config).then(ListInitialData, cantLoadInitialData);
function ListInitialData(response) {
$scope.cyberpanelLoading = true;
$scope.conatinerview = false;
$('#cyberpanelLoading').hide();
if (response.data.status === 1) {
// Find the container in the list and update its logs
for (var i = 0; i < $scope.ContainerList.length; i++) {
if ($scope.ContainerList[i].id === containerid) {
$scope.ContainerList[i].logs = response.data.data[1];
break;
}
}
} else {
new PNotify({
title: 'Operation Failed!',
text: response.data.error_message,
type: 'error'
});
}
}
function cantLoadInitialData(response) {
$scope.cyberpanelLoading = true;
$('#cyberpanelLoading').hide();
$scope.conatinerview = false;
new PNotify({
title: 'Operation Failed!',
text: 'Connection disrupted, refresh the page.',
type: 'error'
});
}
};
// Auto-refresh container info every 30 seconds
var refreshInterval;
$scope.$watch('conatinerview', function(newValue, oldValue) {
if (newValue === false) { // When container view is shown
refreshInterval = setInterval(function() {
if ($scope.cid) {
$scope.Lunchcontainer($scope.cid);
}
}, 30000); // 30 seconds
} else { // When container view is hidden
if (refreshInterval) {
clearInterval(refreshInterval);
}
}
});
// Clean up on controller destruction
$scope.$on('$destroy', function() {
if (refreshInterval) {
clearInterval(refreshInterval);
}
});
// Initialize
$scope.getcontainer();
// Keep your existing functions
$scope.recreateappcontainer = function() { /* ... */ };
$scope.refreshStatus = function() { /* ... */ };
$scope.restarthStatus = function() { /* ... */ };
$scope.StopContainerAPP = function() { /* ... */ };
$scope.cAction = function(action) {
$scope.cyberpanelLoading = false;
$('#cyberpanelLoading').show();
var url = "/docker/";
switch(action) {
case 'start':
url += "startContainer";
break;
case 'stop':
url += "stopContainer";
break;
case 'restart':
url += "restartContainer";
break;
default:
console.error("Unknown action:", action);
return;
}
var data = {
'name': $('#sitename').html(),
'container_id': $scope.selectedContainer.id
};
var config = {
headers: {
'X-CSRFToken': getCookie('csrftoken')
}
};
$http.post(url, data, config).then(
function(response) {
$scope.cyberpanelLoading = true;
$('#cyberpanelLoading').hide();
if (response.data.status === 1) {
new PNotify({
title: 'Success!',
text: 'Container ' + action + ' successful.',
type: 'success'
});
// Update container status after action
$scope.selectedContainer.status = action === 'stop' ? 'stopped' : 'running';
// Refresh container info
$scope.Lunchcontainer($scope.selectedContainer.id);
} else {
new PNotify({
title: 'Operation Failed!',
text: response.data.error_message || 'An unknown error occurred.',
type: 'error'
});
}
},
function(error) {
$scope.cyberpanelLoading = true;
$('#cyberpanelLoading').hide();
new PNotify({
title: 'Operation Failed!',
text: 'Connection disrupted or server error occurred.',
type: 'error'
});
console.error("Error during container action:", error);
}
);
};
// Update the container selection when actions are triggered
$scope.setSelectedContainer = function(container) {
$scope.selectedContainer = container;
};
// Update the button click handlers to set selected container
$scope.handleAction = function(action, container) {
$scope.setSelectedContainer(container);
$scope.cAction(action);
};
$scope.openSettings = function(container) {
$scope.selectedContainer = container;
$('#settings').modal('show');
};
$scope.saveSettings = function() {
$scope.cyberpanelLoading = false;
$('#cyberpanelLoading').show();
var url = "/docker/updateContainerSettings";
var data = {
'name': $('#sitename').html(),
'id': $scope.selectedContainer.id,
'memoryLimit': $scope.selectedContainer.memoryLimit,
'startOnReboot': $scope.selectedContainer.startOnReboot
};
var config = {
headers: {
'X-CSRFToken': getCookie('csrftoken')
}
};
$http.post(url, data, config).then(ListInitialData, cantLoadInitialData);
function ListInitialData(response) {
$scope.cyberpanelLoading = true;
$('#cyberpanelLoading').hide();
if (response.data.status === 1) {
new PNotify({
title: 'Success!',
text: 'Container settings updated successfully.',
type: 'success'
});
$('#settings').modal('hide');
// Refresh container info after update
$scope.Lunchcontainer($scope.selectedContainer.id);
} else {
new PNotify({
title: 'Operation Failed!',
text: response.data.error_message,
type: 'error'
});
}
}
function cantLoadInitialData(response) {
$scope.cyberpanelLoading = true;
$('#cyberpanelLoading').hide();
new PNotify({
title: 'Operation Failed!',
text: 'Connection disrupted, refresh the page.',
type: 'error'
});
}
};
// Add location service to the controller for the n8n URL
$scope.location = window.location;
// Function to extract n8n version from environment variables
$scope.getN8nVersion = function(container) {
console.log('getN8nVersion called with container:', container);
if (!container || !container.environment) {
console.log('No container or environment data');
return 'unknown';
}
console.log('Container environment:', container.environment);
var version = null;
// Try to find NODE_VERSION first
version = container.environment.find(function(env) {
return env && env.startsWith('NODE_VERSION=');
});
if (version) {
console.log('Found NODE_VERSION:', version);
return version.split('=')[1];
}
// Try to find N8N_VERSION
version = container.environment.find(function(env) {
return env && env.startsWith('N8N_VERSION=');
});
if (version) {
console.log('Found N8N_VERSION:', version);
return version.split('=')[1];
}
console.log('No version found in environment');
return 'unknown';
};
});

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -241,10 +241,11 @@
<div class="col-md-3"> <div class="col-md-3">
<h6 style="font-weight: bold">Search Engine Indexing</h6> <h6 style="font-weight: bold">Search Engine Indexing</h6>
<div class="custom-control custom-switch"> <div class="custom-control custom-switch">
<input ng-click="UpdateWPSettings('searchIndex')" <input type="checkbox"
type="checkbox" class="custom-control-input"
class="custom-control-input ng-pristine ng-untouched ng-valid ng-not-empty" id="searchIndex"
id="searchIndex"> ng-click="UpdateWPSettings('searchIndex')"
ng-checked="searchIndex == 1">
<label class="custom-control-label" <label class="custom-control-label"
for="searchIndex"></label> for="searchIndex"></label>
</div> </div>

View File

@@ -23,6 +23,31 @@
$scope.wpSitesCount = $scope.debug.wp_sites_count; $scope.wpSitesCount = $scope.debug.wp_sites_count;
$scope.currentPage = 1; $scope.currentPage = 1;
$scope.recordsToShow = 10; $scope.recordsToShow = 10;
$scope.expandedSites = {}; // Track which sites are expanded
$scope.currentWP = null; // Store current WordPress site for password protection
// Function to toggle site expansion
$scope.toggleSite = function(site) {
if (!$scope.expandedSites[site.id]) {
$scope.expandedSites[site.id] = true;
site.loading = true;
site.loadingPlugins = true;
site.loadingTheme = true;
fetchSiteData(site);
} else {
$scope.expandedSites[site.id] = false;
}
};
// Function to check if site is expanded
$scope.isExpanded = function(siteId) {
return $scope.expandedSites[siteId];
};
// Function to check if site data is loaded
$scope.isDataLoaded = function(site) {
return site.version !== undefined;
};
$scope.updatePagination = function() { $scope.updatePagination = function() {
var filteredSites = $scope.wpSites; var filteredSites = $scope.wpSites;
@@ -66,12 +91,12 @@
var settingMap = { var settingMap = {
'search-indexing': 'searchIndex', 'search-indexing': 'searchIndex',
'debugging': 'debugging', 'debugging': 'debugging',
'password-protection': 'passwordprotection', 'password-protection': 'passwordProtection',
'maintenance-mode': 'maintenanceMode' 'maintenance-mode': 'maintenanceMode'
}; };
var data = { var data = {
siteId: site.id, WPid: site.id,
setting: setting, setting: setting,
value: site[settingMap[setting]] ? 1 : 0 value: site[settingMap[setting]] ? 1 : 0
}; };
@@ -110,13 +135,28 @@
GLobalAjaxCall($http, "{% url 'GetCurrentPlugins' %}", data, GLobalAjaxCall($http, "{% url 'GetCurrentPlugins' %}", data,
function(response) { function(response) {
if (response.data.status === 1) { if (response.data.status === 1) {
var plugins = JSON.parse(response.data.plugins); try {
site.activePlugins = plugins.filter(function(p) { return p.status === 'active'; }).length; var plugins = JSON.parse(response.data.plugins);
site.totalPlugins = plugins.length; // WordPress CLI returns an array of objects with 'name' and 'status' properties
site.activePlugins = plugins.filter(function(p) {
return p.status && p.status.toLowerCase() === 'active';
}).length;
site.totalPlugins = plugins.length;
} catch (e) {
console.error('Error parsing plugin data:', e);
site.activePlugins = 'Error';
site.totalPlugins = 'Error';
}
} else {
site.activePlugins = 'Error';
site.totalPlugins = 'Error';
} }
site.loadingPlugins = false;
}, },
function(response) { function(response) {
site.activePlugins = 'Error'; site.activePlugins = 'Error';
site.totalPlugins = 'Error';
site.loadingPlugins = false;
} }
); );
} }
@@ -131,9 +171,11 @@
site.activeTheme = themes.find(function(t) { return t.status === 'active'; }).name; site.activeTheme = themes.find(function(t) { return t.status === 'active'; }).name;
site.totalThemes = themes.length; site.totalThemes = themes.length;
} }
site.loadingTheme = false;
}, },
function(response) { function(response) {
site.activeTheme = 'Error'; site.activeTheme = 'Error';
site.loadingTheme = false;
} }
); );
} }
@@ -154,23 +196,135 @@
site.debugging = data.debugging === 1; site.debugging = data.debugging === 1;
site.passwordProtection = data.passwordprotection === 1; site.passwordProtection = data.passwordprotection === 1;
site.maintenanceMode = data.maintenanceMode === 1; site.maintenanceMode = data.maintenanceMode === 1;
site.loading = false;
fetchPluginData(site); fetchPluginData(site);
fetchThemeData(site); fetchThemeData(site);
} else { } else {
site.phpVersion = 'PHP 7.4'; // Default value on error site.phpVersion = 'PHP 7.4'; // Default value on error
site.loading = false;
console.log('Failed to fetch site data:', response.data.error_message); console.log('Failed to fetch site data:', response.data.error_message);
} }
}, },
function(response) { function(response) {
site.phpVersion = 'PHP 7.4'; // Default value on error site.phpVersion = 'PHP 7.4'; // Default value on error
site.loading = false;
console.log('Failed to fetch site data'); console.log('Failed to fetch site data');
} }
); );
} }
if ($scope.wpSites) { if ($scope.wpSites && $scope.wpSites.length > 0) {
$scope.wpSites.forEach(fetchSiteData); // Load data for first site by default
$scope.expandedSites[$scope.wpSites[0].id] = true;
fetchSiteData($scope.wpSites[0]);
} }
$scope.togglePasswordProtection = function(site) {
if (site.passwordProtection) {
// Show modal for credentials
site.PPUsername = "";
site.PPPassword = "";
$scope.currentWP = site;
$('#passwordProtectionModal').modal('show');
} else {
// Disable password protection
var data = {
WPid: site.id,
setting: 'password-protection',
value: 0
};
GLobalAjaxCall($http, "{% url 'UpdateWPSettings' %}", data,
function(response) {
if (!response.data.status) {
site.passwordProtection = !site.passwordProtection;
new PNotify({
title: 'Operation Failed!',
text: response.data.error_message || 'Failed to disable password protection',
type: 'error'
});
} else {
new PNotify({
title: 'Success!',
text: 'Password protection disabled successfully.',
type: 'success'
});
}
},
function(error) {
site.passwordProtection = !site.passwordProtection;
new PNotify({
title: 'Operation Failed!',
text: 'Could not connect to server.',
type: 'error'
});
}
);
}
};
$scope.submitPasswordProtection = function() {
if (!$scope.currentWP) {
new PNotify({
title: 'Error!',
text: 'No WordPress site selected.',
type: 'error'
});
return;
}
if (!$scope.currentWP.PPUsername || !$scope.currentWP.PPPassword) {
new PNotify({
title: 'Error!',
text: 'Please provide both username and password',
type: 'error'
});
return;
}
var data = {
siteId: $scope.currentWP.id,
setting: 'password-protection',
value: 1,
PPUsername: $scope.currentWP.PPUsername,
PPPassword: $scope.currentWP.PPPassword
};
$('#passwordProtectionModal').modal('hide');
GLobalAjaxCall($http, "{% url 'UpdateWPSettings' %}", data,
function(response) {
if (response.data.status === 1) {
// Update the site's password protection state
$scope.currentWP.passwordProtection = true;
new PNotify({
title: 'Success!',
text: 'Password protection enabled successfully!',
type: 'success'
});
// Refresh the site data
fetchSiteData($scope.currentWP);
} else {
// Revert the checkbox state
$scope.currentWP.passwordProtection = false;
new PNotify({
title: 'Error!',
text: response.data.error_message || 'Failed to enable password protection',
type: 'error'
});
}
},
function(error) {
// Revert the checkbox state
$scope.currentWP.passwordProtection = false;
new PNotify({
title: 'Error!',
text: 'Could not connect to server',
type: 'error'
});
}
);
};
}); });
// Add a range filter for pagination // Add a range filter for pagination
@@ -231,7 +385,16 @@
<div class="wp-site-header"> <div class="wp-site-header">
<div class="row"> <div class="row">
<div class="col-sm-8"> <div class="col-sm-8">
<h4>{$ site.title $}</h4> <h4>
<i class="fas"
ng-class="{'fa-chevron-down': isExpanded(site.id), 'fa-chevron-right': !isExpanded(site.id)}"
ng-click="toggleSite(site)"
style="cursor: pointer; margin-right: 10px;"></i>
{$ site.title $}
<span ng-if="site.loading || site.loadingPlugins || site.loadingTheme" class="loading-indicator">
<i class="fa fa-spinner fa-spin" style="color: #00749C; font-size: 14px;"></i>
</span>
</h4>
</div> </div>
<div class="col-sm-4 text-right"> <div class="col-sm-4 text-right">
<a ng-href="{% url 'WPHome' %}?ID={$ site.id $}" class="btn btn-primary btn-sm">Manage</a> <a ng-href="{% url 'WPHome' %}?ID={$ site.id $}" class="btn btn-primary btn-sm">Manage</a>
@@ -239,7 +402,7 @@
</div> </div>
</div> </div>
</div> </div>
<div class="wp-site-content"> <div class="wp-site-content" ng-if="isExpanded(site.id)">
<div class="row"> <div class="row">
<div class="col-sm-3"> <div class="col-sm-3">
<img ng-src="https://api.microlink.io/?url={$ getFullUrl(site.url) $}&screenshot=true&meta=false&embed=screenshot.url" <img ng-src="https://api.microlink.io/?url={$ getFullUrl(site.url) $}&screenshot=true&meta=false&embed=screenshot.url"
@@ -261,25 +424,30 @@
<div class="col-sm-3"> <div class="col-sm-3">
<div class="info-box"> <div class="info-box">
<label>WordPress</label> <label>WordPress</label>
<span>{$ site.version $}</span> <span>{$ site.version || 'Loading...' $}</span>
<i ng-if="site.loading" class="fa fa-spinner fa-spin" style="margin-left: 5px; font-size: 12px;"></i>
</div> </div>
</div> </div>
<div class="col-sm-3"> <div class="col-sm-3">
<div class="info-box"> <div class="info-box">
<label>PHP Version</label> <label>PHP Version</label>
<span>{$ site.phpVersion || 'Loading...' $}</span> <span>{$ site.phpVersion || 'Loading...' $}</span>
<i ng-if="site.loading" class="fa fa-spinner fa-spin" style="margin-left: 5px; font-size: 12px;"></i>
</div> </div>
</div> </div>
<div class="col-sm-3"> <div class="col-sm-3">
<div class="info-box"> <div class="info-box">
<label>Theme</label> <label>Theme</label>
<span>{$ site.activeTheme || 'twentytwentyfive' $}</span> <span>{$ site.activeTheme || 'Loading...' $}</span>
<i ng-if="site.loadingTheme" class="fa fa-spinner fa-spin" style="margin-left: 5px; font-size: 12px;"></i>
</div> </div>
</div> </div>
<div class="col-sm-3"> <div class="col-sm-3">
<div class="info-box"> <div class="info-box">
<label>Plugins</label> <label>Plugins</label>
<span>{$ site.activePlugins || '1' $} active</span> <span ng-if="site.activePlugins !== undefined">{$ site.activePlugins $} active of {$ site.totalPlugins $}</span>
<span ng-if="site.activePlugins === undefined">Loading...</span>
<i ng-if="site.loadingPlugins" class="fa fa-spinner fa-spin" style="margin-left: 5px; font-size: 12px;"></i>
</div> </div>
</div> </div>
</div> </div>
@@ -301,7 +469,9 @@
<div class="col-sm-6"> <div class="col-sm-6">
<div class="checkbox"> <div class="checkbox">
<label> <label>
<input type="checkbox" ng-model="site.passwordProtection" ng-change="updateSetting(site, 'password-protection')"> <input type="checkbox"
ng-model="site.passwordProtection"
ng-change="togglePasswordProtection(site)">
Password protection Password protection
</label> </label>
</div> </div>
@@ -339,6 +509,36 @@
</div> </div>
</div> </div>
</div> </div>
<!-- Password Protection Modal -->
<div class="modal fade" id="passwordProtectionModal" tabindex="-1" role="dialog">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Password Protection</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<form>
<div class="form-group">
<label>Username</label>
<input type="text" class="form-control" ng-model="currentWP.PPUsername" required>
</div>
<div class="form-group">
<label>Password</label>
<input type="password" class="form-control" ng-model="currentWP.PPPassword" required>
</div>
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">Cancel</button>
<button type="button" class="btn btn-primary" ng-click="submitPasswordProtection()">Enable Protection</button>
</div>
</div>
</div>
</div>
</div> </div>
<style> <style>
@@ -389,6 +589,18 @@
.text-center .btn { .text-center .btn {
min-width: 100px; min-width: 100px;
} }
.loading-indicator {
display: inline-flex;
align-items: center;
gap: 8px;
color: #00749C;
font-size: 14px;
padding: 0 8px;
}
.loading-indicator i {
font-size: 14px;
margin-left: 4px;
}
</style> </style>
{% endblock content %} {% endblock content %}

View File

@@ -7,6 +7,9 @@
{% get_current_language as LANGUAGE_CODE %} {% get_current_language as LANGUAGE_CODE %}
<!-- Current language: {{ LANGUAGE_CODE }} --> <!-- Current language: {{ LANGUAGE_CODE }} -->
<!-- Add Font Awesome -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
<script> <script>
$(document).ready(function () { $(document).ready(function () {
$('[data-toggle="tooltip"]').tooltip(); $('[data-toggle="tooltip"]').tooltip();
@@ -14,167 +17,256 @@
</script> </script>
<div ng-controller="listWebsites" class="container"> <div ng-controller="listWebsites" class="container">
<!-- Loading State -->
<div id="page-title"> <div ng-show="loading" class="text-center" style="padding: 50px;">
<h2 id="domainNamePage">{% trans "List Websites" %} <div class="spinner-border text-primary" role="status" style="width: 3rem; height: 3rem;">
<a class="pull-right btn btn-primary" href="{% url "createWebsite" %}">{% trans "Create Website" %}</a> <span class="sr-only">Loading...</span>
</h2>
<img ng-hide="cyberPanelLoading" src="{% static 'images/loading.gif' %}">
<p>{% trans "On this page you can launch, list, modify and delete websites from your server." %}</p>
</div>
<div class="col-sm-10" style="padding: 0px; box-shadow: 0px 0px 1px 0px #888888; margin-bottom: 2%">
<input ng-change="searchWebsites()" placeholder="Search..." ng-model="patternAdded" name="dom" type="text"
class="form-control" required>
</div>
<div class="col-sm-2">
<div class="form-group">
<select ng-model="recordsToShow" ng-change="getFurtherWebsitesFromDB()"
class="form-control" id="example-select">
<option>10</option>
<option>50</option>
<option>100</option>
</select>
</div> </div>
<h4 class="mt-3">{% trans "Loading websites..." %}</h4>
</div> </div>
<div ng-repeat="web in WebSitesList track by $index" class="panel col-md-12" <!-- Main Content (hidden while loading) -->
style="padding: 0px; box-shadow: 0px 0px 1px 0px #888888;"> <div ng-hide="loading">
<div class=""> <!-- Password Protection Modal -->
<div class="table-responsive no-gutter text-nowrap" style="overflow-x: hidden;"> <div class="modal fade" id="passwordProtectionModal" tabindex="-1" role="dialog">
<div class="modal-dialog" role="document">
<div style="border-bottom: 1px solid #888888" class="col-md-12"> <div class="modal-content">
<div class="col-lg-10 content-box-header" style="text-transform: none;"> <div class="modal-header">
<a href="http://{$ web.domain $}" target="_blank" title="Visit Site"> <h5 class="modal-title">Password Protection</h5>
<h2 style="display: inline; color: #414C59;" ng-bind="web.domain"></h2> <button type="button" class="close" data-dismiss="modal" aria-label="Close">
</a> <span aria-hidden="true">&times;</span>
<a target="_self" href="/filemanager/{$ web.domain $}" title="Open File Manager"> -- </button>
{% trans "File Manager" %}</a>
</div> </div>
<div class="col-md-2 content-box-header" style="text-transform: none;"> <div class="modal-body">
<a href="/websites/{$ web.domain $}" target="_self" title="Manage Website"> <form>
<i class="p fa fa-external-link btn-icon">&emsp;</i> <div class="form-group">
<span>{% trans "Manage" %}</span> <label>Username</label>
</a> <input type="text" class="form-control" ng-model="currentWP.PPUsername" required>
</div>
<div class="form-group">
<label>Password</label>
<input type="password" class="form-control" ng-model="currentWP.PPPassword" required>
</div>
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">Cancel</button>
<button type="button" class="btn btn-primary" ng-click="submitPasswordProtection()">Enable Protection</button>
</div> </div>
</div> </div>
</div>
</div>
<div class="col-md-12"> <div id="page-title">
<div class="col-md-3 content-box-header"> <h2 id="domainNamePage">{% trans "List Websites" %}
<i class="p fa fa-sticky-note btn-icon text-muted" data-toggle="tooltip" <a class="pull-right btn btn-primary" href="{% url "createWebsite" %}">{% trans "Create Website" %}</a>
data-placement="right" title="State">&emsp;</i> </h2>
<span ng-bind="web.state" style="text-transform: none"></span> <img ng-hide="cyberPanelLoading" src="{% static 'images/loading.gif' %}">
</div> <p>{% trans "On this page you can launch, list, modify and delete websites from your server." %}</p>
<div class="col-md-3 content-box-header"> </div>
<i class="p fa fa-map-marker btn-icon text-muted" data-toggle="tooltip"
data-placement="right" title="IP Address">&emsp;</i>
<span ng-bind="web.ipAddress"></span>
</div>
<div class="col-md-3 content-box-header">
<i class="p fa fa-lock btn-icon text-muted" data-toggle="tooltip" data-placement="right"
title="SSL">&emsp;</i>
<span><a ng-click="issueSSL(web.domain)" href=""
style="text-transform: none">{% trans "Issue SSL" %}</a></span>
</div>
<div class="col-md-3 content-box-header">
<span ng-bind="web.phpVersion"></span>
</div>
</div>
<div class="col-md-12"> <div class="col-sm-10" style="padding: 0px; box-shadow: 0px 0px 1px 0px #888888; margin-bottom: 2%">
<div class="col-md-3 content-box-header"> <input ng-change="searchWebsites()" placeholder="Search..." ng-model="patternAdded" name="dom" type="text"
<i class="p fa fa-hdd-o btn-icon text-muted" data-toggle="tooltip" class="form-control" required>
data-placement="right" </div>
title="Disk Usage">&emsp;</i>
<span ng-bind="web.diskUsed" style="text-transform: none"></span>
</div>
<div class="col-md-3 content-box-header">
<i class="p fa fa-cubes btn-icon text-muted" data-toggle="tooltip"
data-placement="right"
title="Packages">&emsp;</i>
<span ng-bind="web.package" style="text-transform: none"></span>
</div>
<div class="col-md-3 content-box-header">
<i class="p fa fa-user btn-icon text-muted" data-toggle="tooltip" data-placement="right"
title="Owner">&emsp;</i>
<span ng-bind="web.admin" style="text-transform: none"></span>
</div>
<div class="col-md-3 content-box-header">
<i class="p fa fa-wordpress btn-icon text-muted" ng-click="showWPSites(web.domain)"
data-toggle="tooltip" data-placement="right" title="Show WordPress Sites">&emsp;</i>
<span ng-if="web.wp_sites && web.wp_sites.length > 0" style="text-transform: none">
{$ web.wp_sites.length $} WordPress Sites
</span>
</div>
</div>
<!-- WordPress Sites Section --> <div class="col-sm-2">
<div ng-if="web.showWPSites && web.wp_sites && web.wp_sites.length > 0" class="card mt-3"> <div class="form-group">
<div class="card-header"> <select ng-model="recordsToShow" ng-change="getFurtherWebsitesFromDB()"
<h5 class="mb-0">WordPress Sites</h5> class="form-control" id="example-select">
<option>10</option>
<option>50</option>
<option>100</option>
</select>
</div>
</div>
<div ng-repeat="web in WebSitesList track by $index" class="panel col-md-12"
style="padding: 0px; box-shadow: 0px 0px 1px 0px #888888;">
<div class="">
<div class="table-responsive no-gutter text-nowrap" style="overflow-x: hidden;">
<div style="border-bottom: 1px solid #888888" class="col-md-12">
<div class="col-lg-10 content-box-header" style="text-transform: none;">
<a href="http://{$ web.domain $}" target="_blank" title="Visit Site">
<h2 style="display: inline; color: #414C59;" ng-bind="web.domain"></h2>
</a>
<a target="_self" href="/filemanager/{$ web.domain $}" title="Open File Manager"> --
{% trans "File Manager" %}</a>
</div>
<div class="col-md-2 content-box-header" style="text-transform: none;">
<a href="/websites/{$ web.domain $}" target="_self" title="Manage Website">
<i class="p fa fa-external-link btn-icon">&emsp;</i>
<span>{% trans "Manage" %}</span>
</a>
</div>
</div> </div>
<div class="card-body">
<div class="row"> <div class="col-md-12">
<div class="col-md-6 mb-4" ng-repeat="site in web.wp_sites"> <div class="col-md-3 content-box-header">
<div class="card h-100"> <i class="p fa fa-sticky-note btn-icon text-muted" data-toggle="tooltip"
<div class="card-header d-flex justify-content-between align-items-center"> data-placement="right" title="State">&emsp;</i>
<h6 class="mb-0">{{site.title}}</h6> <span ng-bind="web.state" style="text-transform: none"></span>
<div> </div>
<button class="btn btn-sm btn-primary mr-2" ng-click="visitSite(site.url)"> <div class="col-md-3 content-box-header">
<i class="fas fa-external-link-alt"></i> Visit <i class="p fa fa-map-marker btn-icon text-muted" data-toggle="tooltip"
</button> data-placement="right" title="IP Address">&emsp;</i>
<button class="btn btn-sm btn-info mr-2" ng-click="wpLogin(site.id)"> <span ng-bind="web.ipAddress"></span>
<i class="fas fa-sign-in-alt"></i> Login </div>
</button> <div class="col-md-3 content-box-header">
<button class="btn btn-sm btn-secondary" ng-click="manageWP(site.id)"> <i class="p fa fa-lock btn-icon text-muted" data-toggle="tooltip" data-placement="right"
<i class="fas fa-cog"></i> Manage title="SSL">&emsp;</i>
</button> <span><a ng-click="issueSSL(web.domain)" href=""
style="text-transform: none">{% trans "Issue SSL" %}</a></span>
</div>
<div class="col-md-3 content-box-header">
<span ng-bind="web.phpVersion"></span>
</div>
</div>
<div class="col-md-12">
<div class="col-md-3 content-box-header">
<i class="fa-solid fa-hard-drive btn-icon text-muted" data-toggle="tooltip"
data-placement="right"
title="Disk Usage">&emsp;</i>
<span ng-bind="web.diskUsed" style="text-transform: none"></span>
</div>
<div class="col-md-3 content-box-header">
<i class="fa-solid fa-cubes btn-icon text-muted" data-toggle="tooltip"
data-placement="right"
title="Packages">&emsp;</i>
<span ng-bind="web.package" style="text-transform: none"></span>
</div>
<div class="col-md-3 content-box-header">
<i class="fa-solid fa-user btn-icon text-muted" data-toggle="tooltip" data-placement="right"
title="Owner">&emsp;</i>
<span ng-bind="web.admin" style="text-transform: none"></span>
</div>
<div class="col-md-3 content-box-header">
<a href="javascript:void(0);" ng-click="showWPSites(web.domain)" class="wp-sites-link">
<i class="fa-brands fa-wordpress btn-icon text-muted" data-toggle="tooltip"
data-placement="right" title="Show WordPress Sites"></i>
<span ng-if="!web.loadingWPSites" class="wp-sites-count">
{$ (web.wp_sites && web.wp_sites.length) || 0 $} WordPress Sites
</span>
<span ng-if="web.loadingWPSites" class="loading-indicator">
Loading <i class="fa fa-spinner fa-spin"></i>
</span>
</a>
</div>
</div>
<!-- WordPress Sites Section -->
<div class="col-md-12" ng-if="web.showWPSites && web.wp_sites && web.wp_sites.length > 0" style="padding: 15px 30px;">
<div ng-repeat="wp in web.wp_sites" class="wp-site-item">
<div class="row">
<div class="col-sm-12">
<div class="wp-site-header">
<div class="row">
<div class="col-sm-8">
<h4>
<i class="fa-brands fa-wordpress" style="color: #00749C; margin-right: 8px;"></i>
{$ wp.title $}
<span ng-if="wp.loading || wp.loadingPlugins || wp.loadingTheme" class="loading-indicator">
<i class="fa fa-spinner fa-spin" style="color: #00749C; font-size: 14px;"></i>
</span>
</h4>
</div>
<div class="col-sm-4 text-right">
<button class="btn btn-outline-primary btn-sm wp-action-btn" ng-click="manageWP(wp.id)">
<i class="fa-solid fa-cog"></i> Manage
</button>
<button class="btn btn-outline-danger btn-sm wp-action-btn" ng-click="deleteWPSite(wp)">
<i class="fa-solid fa-trash"></i> Delete
</button>
</div>
</div> </div>
</div> </div>
<div class="card-body"> <div class="wp-site-content">
<div class="row"> <div class="row">
<div class="col-md-6"> <div class="col-sm-3">
<p><strong>WordPress Version:</strong> {{site.version}}</p> <img ng-src="{$ wp.screenshot $}"
<p><strong>PHP Version:</strong> {{site.phpVersion}}</p> alt="{$ wp.title $}"
<p><strong>Active Theme:</strong> {{site.theme}}</p> class="img-responsive"
<p><strong>Active Plugins:</strong> {{site.activePlugins}}</p> style="max-width: 100%; margin-bottom: 10px; min-height: 150px; background: #f5f5f5;"
onerror="this.onerror=null; this.src='https://s.wordpress.org/style/images/about/WordPress-logotype-standard.png';">
<div class="text-center wp-action-buttons">
<a href="javascript:void(0);" ng-click="visitSite(wp)" class="btn btn-outline-secondary btn-sm wp-action-btn">
<i class="fa-solid fa-external-link"></i> Visit Site
</a>
<a href="{% url 'AutoLogin' %}?id={$ wp.id $}" target="_blank" class="btn btn-outline-primary btn-sm wp-action-btn">
<i class="fa-brands fa-wordpress"></i> WP Login
</a>
</div>
</div> </div>
<div class="col-md-6"> <div class="col-sm-9">
<div class="form-group"> <div class="row">
<div class="custom-control custom-switch"> <div class="col-sm-3">
<input type="checkbox" class="custom-control-input" <div class="info-box">
id="searchIndex{{site.id}}" <label>WordPress</label>
ng-model="site.searchIndex" <span>{$ wp.version || 'Loading...' $}</span>
ng-change="updateSetting(site.id, 'search-indexing', site.searchIndex ? 'enable' : 'disable')"> <i ng-if="wp.loading" class="fa fa-spinner fa-spin" style="margin-left: 5px; font-size: 12px;"></i>
<label class="custom-control-label" for="searchIndex{{site.id}}">Search Indexing</label> </div>
</div>
<div class="col-sm-3">
<div class="info-box">
<label>PHP Version</label>
<span>{$ wp.phpVersion || 'Loading...' $}</span>
<i ng-if="wp.loading" class="fa fa-spinner fa-spin" style="margin-left: 5px; font-size: 12px;"></i>
</div>
</div>
<div class="col-sm-3">
<div class="info-box">
<label>Theme</label>
<span>{$ wp.theme || 'Loading...' $}</span>
<i ng-if="wp.loadingTheme" class="fa fa-spinner fa-spin" style="margin-left: 5px; font-size: 12px;"></i>
</div>
</div>
<div class="col-sm-3">
<div class="info-box">
<label>Plugins</label>
<span>{$ wp.activePlugins || '0' $} active</span>
<i ng-if="wp.loadingPlugins" class="fa fa-spinner fa-spin" style="margin-left: 5px; font-size: 12px;"></i>
</div>
</div> </div>
</div> </div>
<div class="form-group"> <div class="row mt-3">
<div class="custom-control custom-switch"> <div class="col-sm-6">
<input type="checkbox" class="custom-control-input" <div class="checkbox">
id="debugging{{site.id}}" <label>
ng-model="site.debugging" <input type="checkbox"
ng-change="updateSetting(site.id, 'debugging', site.debugging ? 'enable' : 'disable')"> ng-click="updateSetting(wp, 'search-indexing')"
<label class="custom-control-label" for="debugging{{site.id}}">Debugging</label> ng-checked="wp.searchIndex == 1">
Search engine indexing
</label>
</div>
<div class="checkbox">
<label>
<input type="checkbox"
ng-click="updateSetting(wp, 'debugging')"
ng-checked="wp.debugging == 1">
Debugging
</label>
</div>
</div> </div>
</div> <div class="col-sm-6">
<div class="form-group"> <div class="checkbox">
<div class="custom-control custom-switch"> <label>
<input type="checkbox" class="custom-control-input" <input type="checkbox"
id="passwordProtection{{site.id}}" ng-model="wp.passwordProtection"
ng-model="site.passwordProtection" ng-init="wp.passwordProtection = wp.passwordProtection || false"
ng-change="updateSetting(site.id, 'password-protection', site.passwordProtection ? 'enable' : 'disable')"> ng-change="togglePasswordProtection(wp)">
<label class="custom-control-label" for="passwordProtection{{site.id}}">Password Protection</label> Password protection
</div> </label>
</div> </div>
<div class="form-group"> <div class="checkbox">
<div class="custom-control custom-switch"> <label>
<input type="checkbox" class="custom-control-input" <input type="checkbox"
id="maintenanceMode{{site.id}}" ng-click="updateSetting(wp, 'maintenance-mode')"
ng-model="site.maintenanceMode" ng-checked="wp.maintenanceMode == 1">
ng-change="updateSetting(site.id, 'maintenance-mode', site.maintenanceMode ? 'enable' : 'disable')"> Maintenance mode
<label class="custom-control-label" for="maintenanceMode{{site.id}}">Maintenance Mode</label> </label>
</div>
</div> </div>
</div> </div>
</div> </div>
@@ -184,28 +276,163 @@
</div> </div>
</div> </div>
</div> </div>
</div>
<div id="listFail" class="alert alert-danger"> <style>
<p>{% trans "Cannot list websites. Error message:" %} {$ errorMessage $}</p> .wp-site-item {
border: 1px solid #e0e0e0;
margin-bottom: 20px;
border-radius: 8px;
background: white;
box-shadow: 0 2px 4px rgba(0,0,0,0.05);
}
.wp-site-header {
padding: 15px 20px;
border-bottom: 1px solid #e0e0e0;
background: #f8f9fa;
border-radius: 8px 8px 0 0;
}
.wp-site-header h4 {
margin: 0;
font-size: 18px;
line-height: 34px;
color: #2c3338;
font-weight: 500;
}
.wp-site-content {
padding: 20px;
}
.info-box {
margin-bottom: 15px;
background: #f8f9fa;
padding: 10px;
border-radius: 6px;
}
.info-box label {
display: block;
font-size: 12px;
color: #646970;
margin-bottom: 5px;
font-weight: 500;
}
.info-box span {
font-size: 14px;
font-weight: 600;
color: #2c3338;
}
.checkbox {
margin-bottom: 10px;
}
.mt-3 {
margin-top: 1rem;
}
/* Updated button styles */
.wp-action-btn {
margin: 0 4px;
padding: 6px 12px;
font-size: 13px;
font-weight: 500;
transition: all 0.2s ease;
border-width: 1.5px;
line-height: 1.5;
display: inline-flex;
align-items: center;
justify-content: center;
vertical-align: middle;
height: 32px;
}
.wp-action-btn i {
margin-right: 6px;
font-size: 14px;
display: inline-flex;
align-items: center;
}
.wp-action-buttons {
margin-top: 12px;
display: flex;
gap: 8px;
justify-content: center;
}
.wp-action-buttons .wp-action-btn {
min-width: 110px;
flex: 0 1 auto;
}
.btn-outline-primary {
color: #0073aa;
border-color: #0073aa;
}
.btn-outline-primary:hover {
background-color: #0073aa;
color: white;
}
.btn-outline-secondary {
color: #50575e;
border-color: #50575e;
}
.btn-outline-secondary:hover {
background-color: #50575e;
color: white;
}
.btn-outline-danger {
color: #dc3545;
border-color: #dc3545;
}
.btn-outline-danger:hover {
background-color: #dc3545;
color: white;
}
.wp-sites-link {
cursor: pointer;
display: inline-flex;
align-items: center;
gap: 8px;
color: inherit;
text-decoration: none;
}
.wp-sites-link:hover {
color: inherit;
text-decoration: none;
}
.wp-sites-link i.btn-icon {
margin-right: 4px;
}
.wp-sites-count {
text-transform: none;
}
.loading-indicator {
display: inline-flex;
align-items: center;
gap: 8px;
color: #00749C;
font-size: 14px;
padding: 0 8px;
}
.loading-indicator i {
font-size: 14px;
margin-left: 4px;
}
</style>
<div id="listFail" class="alert alert-danger">
<p>{% trans "Cannot list websites. Error message:" %} {$ errorMessage $}</p>
</div>
</div> </div>
</div> </div>
</div> </div>
</div> <div style="margin-top: 2%" class="row">
<div style="margin-top: 2%" class="row"> <div class="col-md-12">
<div class="col-md-12"> <div class="row">
<div class="row"> <div class="col-md-9">
<div class="col-md-9">
</div>
<div class="col-md-3">
<div class="form-group">
<select ng-model="currentPage" class="form-control"
ng-change="getFurtherWebsitesFromDB()">
<option ng-repeat="page in pagination">{$ $index + 1 $}</option>
</select>
</div> </div>
</div> <div class="col-md-3">
</div> <!-- end row --> <div class="form-group">
<select ng-model="currentPage" class="form-control"
ng-change="getFurtherWebsitesFromDB()">
<option ng-repeat="page in pagination">{$ $index + 1 $}</option>
</select>
</div>
</div>
</div> <!-- end row -->
</div>
</div> </div>
</div> </div>
</div> </div>

View File

@@ -51,6 +51,7 @@ urlpatterns = [
path('AddWPsiteforRemoteBackup', views.AddWPsiteforRemoteBackup, name='AddWPsiteforRemoteBackup'), path('AddWPsiteforRemoteBackup', views.AddWPsiteforRemoteBackup, name='AddWPsiteforRemoteBackup'),
path('UpdateRemoteschedules', views.UpdateRemoteschedules, name='UpdateRemoteschedules'), path('UpdateRemoteschedules', views.UpdateRemoteschedules, name='UpdateRemoteschedules'),
path('ScanWordpressSite', views.ScanWordpressSite, name='ScanWordpressSite'), path('ScanWordpressSite', views.ScanWordpressSite, name='ScanWordpressSite'),
path('fetchWPDetails', views.fetchWPDetails, name='fetchWPDetails'),
# AddPlugin # AddPlugin
path('ConfigurePlugins', views.ConfigurePlugins, name='ConfigurePlugins'), path('ConfigurePlugins', views.ConfigurePlugins, name='ConfigurePlugins'),
@@ -178,6 +179,11 @@ urlpatterns = [
path('ListDockerSites', views.ListDockerSites, name='ListDockerSites'), path('ListDockerSites', views.ListDockerSites, name='ListDockerSites'),
path('fetchDockersite', views.fetchDockersite, name='fetchDockersite'), path('fetchDockersite', views.fetchDockersite, name='fetchDockersite'),
# Docker Container Actions
path('docker/startContainer', views.startContainer, name='startContainer'),
path('docker/stopContainer', views.stopContainer, name='stopContainer'),
path('docker/restartContainer', views.restartContainer, name='restartContainer'),
# SSH Configs # SSH Configs
path('getSSHConfigs', views.getSSHConfigs, name='getSSHConfigs'), path('getSSHConfigs', views.getSSHConfigs, name='getSSHConfigs'),
path('deleteSSHKey', views.deleteSSHKey, name='deleteSSHKey'), path('deleteSSHKey', views.deleteSSHKey, name='deleteSSHKey'),
@@ -194,6 +200,4 @@ urlpatterns = [
# Catch all for domains # Catch all for domains
path('<domain>/<childDomain>', views.launchChild, name='launchChild'), path('<domain>/<childDomain>', views.launchChild, name='launchChild'),
path('<domain>', views.domain, name='domain'), path('<domain>', views.domain, name='domain'),
path(r'GetWPSitesByDomain', views.GetWPSitesByDomain, name='GetWPSitesByDomain'),
] ]

View File

@@ -14,6 +14,9 @@ from websiteFunctions.models import wpplugins
from websiteFunctions.website import WebsiteManager from websiteFunctions.website import WebsiteManager
from websiteFunctions.pluginManager import pluginManager from websiteFunctions.pluginManager import pluginManager
from django.views.decorators.csrf import csrf_exempt from django.views.decorators.csrf import csrf_exempt
from .dockerviews import startContainer as docker_startContainer
from .dockerviews import stopContainer as docker_stopContainer
from .dockerviews import restartContainer as docker_restartContainer
def loadWebsitesHome(request): def loadWebsitesHome(request):
val = request.session['userID'] val = request.session['userID']
@@ -1843,19 +1846,40 @@ def Dockersitehome(request, dockerapp):
except KeyError: except KeyError:
return redirect(loadLoginPage) return redirect(loadLoginPage)
def GetWPSitesByDomain(request): def fetchWPDetails(request):
try: try:
userID = request.session['userID'] userID = request.session['userID']
data = json.loads(request.body) data = {
domain = data['domain'] 'domain': request.POST.get('domain')
}
wm = WebsiteManager() wm = WebsiteManager()
response = wm.GetWPSitesByDomain(userID, data) return wm.fetchWPSitesForDomain(userID, data)
return response
except KeyError: except KeyError:
return redirect(reverse('login')) return redirect(loadLoginPage)
except BaseException as msg:
data_ret = {'status': 0, 'error_message': str(msg)} @csrf_exempt
json_data = json.dumps(data_ret) def startContainer(request):
return HttpResponse(json_data) try:
if request.method == 'POST':
return docker_startContainer(request)
return HttpResponse('Not allowed')
except KeyError:
return redirect(loadLoginPage)
@csrf_exempt
def stopContainer(request):
try:
if request.method == 'POST':
return docker_stopContainer(request)
return HttpResponse('Not allowed')
except KeyError:
return redirect(loadLoginPage)
@csrf_exempt
def restartContainer(request):
try:
if request.method == 'POST':
return docker_restartContainer(request)
return HttpResponse('Not allowed')
except KeyError:
return redirect(loadLoginPage)

File diff suppressed because it is too large Load Diff