mirror of
https://github.com/usmannasir/cyberpanel.git
synced 2026-05-07 05:07:53 +02:00
Merge branch 'v2.4.0-dev' of github.com:usmannasir/cyberpanel into v2.4.0-dev
This commit is contained in:
170
websiteFunctions/dockerviews.py
Normal file
170
websiteFunctions/dockerviews.py
Normal 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)
|
||||
}))
|
||||
395
websiteFunctions/static/websiteFunctions/DockerContainers.js
Normal file
395
websiteFunctions/static/websiteFunctions/DockerContainers.js
Normal 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
@@ -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>
|
||||
|
||||
@@ -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">×</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 %}
|
||||
|
||||
|
||||
@@ -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">×</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"> </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"> </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"> </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"> </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"> </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"> </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"> </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"> </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"> </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"> </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"> </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"> </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"> </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"> </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"> </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>
|
||||
|
||||
@@ -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'),
|
||||
]
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user