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

@@ -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">
<h6 style="font-weight: bold">Search Engine Indexing</h6>
<div class="custom-control custom-switch">
<input ng-click="UpdateWPSettings('searchIndex')"
type="checkbox"
class="custom-control-input ng-pristine ng-untouched ng-valid ng-not-empty"
id="searchIndex">
<input type="checkbox"
class="custom-control-input"
id="searchIndex"
ng-click="UpdateWPSettings('searchIndex')"
ng-checked="searchIndex == 1">
<label class="custom-control-label"
for="searchIndex"></label>
</div>

View File

@@ -23,6 +23,31 @@
$scope.wpSitesCount = $scope.debug.wp_sites_count;
$scope.currentPage = 1;
$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() {
var filteredSites = $scope.wpSites;
@@ -66,12 +91,12 @@
var settingMap = {
'search-indexing': 'searchIndex',
'debugging': 'debugging',
'password-protection': 'passwordprotection',
'password-protection': 'passwordProtection',
'maintenance-mode': 'maintenanceMode'
};
var data = {
siteId: site.id,
WPid: site.id,
setting: setting,
value: site[settingMap[setting]] ? 1 : 0
};
@@ -110,13 +135,28 @@
GLobalAjaxCall($http, "{% url 'GetCurrentPlugins' %}", data,
function(response) {
if (response.data.status === 1) {
var plugins = JSON.parse(response.data.plugins);
site.activePlugins = plugins.filter(function(p) { return p.status === 'active'; }).length;
site.totalPlugins = plugins.length;
try {
var plugins = JSON.parse(response.data.plugins);
// 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) {
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.totalThemes = themes.length;
}
site.loadingTheme = false;
},
function(response) {
site.activeTheme = 'Error';
site.loadingTheme = false;
}
);
}
@@ -154,23 +196,135 @@
site.debugging = data.debugging === 1;
site.passwordProtection = data.passwordprotection === 1;
site.maintenanceMode = data.maintenanceMode === 1;
site.loading = false;
fetchPluginData(site);
fetchThemeData(site);
} else {
site.phpVersion = 'PHP 7.4'; // Default value on error
site.loading = false;
console.log('Failed to fetch site data:', response.data.error_message);
}
},
function(response) {
site.phpVersion = 'PHP 7.4'; // Default value on error
site.loading = false;
console.log('Failed to fetch site data');
}
);
}
if ($scope.wpSites) {
$scope.wpSites.forEach(fetchSiteData);
if ($scope.wpSites && $scope.wpSites.length > 0) {
// 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
@@ -231,7 +385,16 @@
<div class="wp-site-header">
<div class="row">
<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 class="col-sm-4 text-right">
<a ng-href="{% url 'WPHome' %}?ID={$ site.id $}" class="btn btn-primary btn-sm">Manage</a>
@@ -239,7 +402,7 @@
</div>
</div>
</div>
<div class="wp-site-content">
<div class="wp-site-content" ng-if="isExpanded(site.id)">
<div class="row">
<div class="col-sm-3">
<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="info-box">
<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 class="col-sm-3">
<div class="info-box">
<label>PHP Version</label>
<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 class="col-sm-3">
<div class="info-box">
<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 class="col-sm-3">
<div class="info-box">
<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>
@@ -301,7 +469,9 @@
<div class="col-sm-6">
<div class="checkbox">
<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
</label>
</div>
@@ -339,6 +509,36 @@
</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>
<style>
@@ -389,6 +589,18 @@
.text-center .btn {
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>
{% endblock content %}

View File

@@ -7,6 +7,9 @@
{% get_current_language as 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>
$(document).ready(function () {
$('[data-toggle="tooltip"]').tooltip();
@@ -14,167 +17,256 @@
</script>
<div ng-controller="listWebsites" class="container">
<div id="page-title">
<h2 id="domainNamePage">{% trans "List Websites" %}
<a class="pull-right btn btn-primary" href="{% url "createWebsite" %}">{% trans "Create Website" %}</a>
</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>
<!-- Loading State -->
<div ng-show="loading" class="text-center" style="padding: 50px;">
<div class="spinner-border text-primary" role="status" style="width: 3rem; height: 3rem;">
<span class="sr-only">Loading...</span>
</div>
<h4 class="mt-3">{% trans "Loading websites..." %}</h4>
</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>
<!-- Main Content (hidden while loading) -->
<div ng-hide="loading">
<!-- 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="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 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 class="col-md-12">
<div class="col-md-3 content-box-header">
<i class="p fa fa-sticky-note btn-icon text-muted" data-toggle="tooltip"
data-placement="right" title="State">&emsp;</i>
<span ng-bind="web.state" style="text-transform: none"></span>
</div>
<div class="col-md-3 content-box-header">
<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 id="page-title">
<h2 id="domainNamePage">{% trans "List Websites" %}
<a class="pull-right btn btn-primary" href="{% url "createWebsite" %}">{% trans "Create Website" %}</a>
</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-md-12">
<div class="col-md-3 content-box-header">
<i class="p fa fa-hdd-o 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="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>
<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>
<!-- WordPress Sites Section -->
<div ng-if="web.showWPSites && web.wp_sites && web.wp_sites.length > 0" class="card mt-3">
<div class="card-header">
<h5 class="mb-0">WordPress Sites</h5>
<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>
<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 class="card-body">
<div class="row">
<div class="col-md-6 mb-4" ng-repeat="site in web.wp_sites">
<div class="card h-100">
<div class="card-header d-flex justify-content-between align-items-center">
<h6 class="mb-0">{{site.title}}</h6>
<div>
<button class="btn btn-sm btn-primary mr-2" ng-click="visitSite(site.url)">
<i class="fas fa-external-link-alt"></i> Visit
</button>
<button class="btn btn-sm btn-info mr-2" ng-click="wpLogin(site.id)">
<i class="fas fa-sign-in-alt"></i> Login
</button>
<button class="btn btn-sm btn-secondary" ng-click="manageWP(site.id)">
<i class="fas fa-cog"></i> Manage
</button>
<div class="col-md-12">
<div class="col-md-3 content-box-header">
<i class="p fa fa-sticky-note btn-icon text-muted" data-toggle="tooltip"
data-placement="right" title="State">&emsp;</i>
<span ng-bind="web.state" style="text-transform: none"></span>
</div>
<div class="col-md-3 content-box-header">
<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-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 class="card-body">
<div class="wp-site-content">
<div class="row">
<div class="col-md-6">
<p><strong>WordPress Version:</strong> {{site.version}}</p>
<p><strong>PHP Version:</strong> {{site.phpVersion}}</p>
<p><strong>Active Theme:</strong> {{site.theme}}</p>
<p><strong>Active Plugins:</strong> {{site.activePlugins}}</p>
<div class="col-sm-3">
<img ng-src="{$ wp.screenshot $}"
alt="{$ wp.title $}"
class="img-responsive"
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 class="col-md-6">
<div class="form-group">
<div class="custom-control custom-switch">
<input type="checkbox" class="custom-control-input"
id="searchIndex{{site.id}}"
ng-model="site.searchIndex"
ng-change="updateSetting(site.id, 'search-indexing', site.searchIndex ? 'enable' : 'disable')">
<label class="custom-control-label" for="searchIndex{{site.id}}">Search Indexing</label>
<div class="col-sm-9">
<div class="row">
<div class="col-sm-3">
<div class="info-box">
<label>WordPress</label>
<span>{$ wp.version || '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>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 class="form-group">
<div class="custom-control custom-switch">
<input type="checkbox" class="custom-control-input"
id="debugging{{site.id}}"
ng-model="site.debugging"
ng-change="updateSetting(site.id, 'debugging', site.debugging ? 'enable' : 'disable')">
<label class="custom-control-label" for="debugging{{site.id}}">Debugging</label>
<div class="row mt-3">
<div class="col-sm-6">
<div class="checkbox">
<label>
<input type="checkbox"
ng-click="updateSetting(wp, 'search-indexing')"
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 class="form-group">
<div class="custom-control custom-switch">
<input type="checkbox" class="custom-control-input"
id="passwordProtection{{site.id}}"
ng-model="site.passwordProtection"
ng-change="updateSetting(site.id, 'password-protection', site.passwordProtection ? 'enable' : 'disable')">
<label class="custom-control-label" for="passwordProtection{{site.id}}">Password Protection</label>
</div>
</div>
<div class="form-group">
<div class="custom-control custom-switch">
<input type="checkbox" class="custom-control-input"
id="maintenanceMode{{site.id}}"
ng-model="site.maintenanceMode"
ng-change="updateSetting(site.id, 'maintenance-mode', site.maintenanceMode ? 'enable' : 'disable')">
<label class="custom-control-label" for="maintenanceMode{{site.id}}">Maintenance Mode</label>
<div class="col-sm-6">
<div class="checkbox">
<label>
<input type="checkbox"
ng-model="wp.passwordProtection"
ng-init="wp.passwordProtection = wp.passwordProtection || false"
ng-change="togglePasswordProtection(wp)">
Password protection
</label>
</div>
<div class="checkbox">
<label>
<input type="checkbox"
ng-click="updateSetting(wp, 'maintenance-mode')"
ng-checked="wp.maintenanceMode == 1">
Maintenance mode
</label>
</div>
</div>
</div>
</div>
@@ -184,28 +276,163 @@
</div>
</div>
</div>
</div>
<div id="listFail" class="alert alert-danger">
<p>{% trans "Cannot list websites. Error message:" %} {$ errorMessage $}</p>
<style>
.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 style="margin-top: 2%" class="row">
<div class="col-md-12">
<div class="row">
<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 style="margin-top: 2%" class="row">
<div class="col-md-12">
<div class="row">
<div class="col-md-9">
</div>
</div>
</div> <!-- end row -->
<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> <!-- end row -->
</div>
</div>
</div>
</div>

View File

@@ -51,6 +51,7 @@ urlpatterns = [
path('AddWPsiteforRemoteBackup', views.AddWPsiteforRemoteBackup, name='AddWPsiteforRemoteBackup'),
path('UpdateRemoteschedules', views.UpdateRemoteschedules, name='UpdateRemoteschedules'),
path('ScanWordpressSite', views.ScanWordpressSite, name='ScanWordpressSite'),
path('fetchWPDetails', views.fetchWPDetails, name='fetchWPDetails'),
# AddPlugin
path('ConfigurePlugins', views.ConfigurePlugins, name='ConfigurePlugins'),
@@ -178,6 +179,11 @@ urlpatterns = [
path('ListDockerSites', views.ListDockerSites, name='ListDockerSites'),
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
path('getSSHConfigs', views.getSSHConfigs, name='getSSHConfigs'),
path('deleteSSHKey', views.deleteSSHKey, name='deleteSSHKey'),
@@ -194,6 +200,4 @@ urlpatterns = [
# Catch all for domains
path('<domain>/<childDomain>', views.launchChild, name='launchChild'),
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.pluginManager import pluginManager
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):
val = request.session['userID']
@@ -1843,19 +1846,40 @@ def Dockersitehome(request, dockerapp):
except KeyError:
return redirect(loadLoginPage)
def GetWPSitesByDomain(request):
def fetchWPDetails(request):
try:
userID = request.session['userID']
data = json.loads(request.body)
domain = data['domain']
data = {
'domain': request.POST.get('domain')
}
wm = WebsiteManager()
response = wm.GetWPSitesByDomain(userID, data)
return response
return wm.fetchWPSitesForDomain(userID, data)
except KeyError:
return redirect(reverse('login'))
except BaseException as msg:
data_ret = {'status': 0, 'error_message': str(msg)}
json_data = json.dumps(data_ret)
return HttpResponse(json_data)
return redirect(loadLoginPage)
@csrf_exempt
def startContainer(request):
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