mirror of
https://github.com/usmannasir/cyberpanel.git
synced 2026-07-05 08:07:38 +02:00
Enhance CyberPanel functionality with FTP Quota and Bandwidth Management features: Added models, views, and templates for managing FTP quotas and bandwidth resets. Implemented IP blocking functionality with associated views and templates. Updated system scripts for improved repository synchronization and OS detection. Removed outdated workflow files.
This commit is contained in:
@@ -0,0 +1,55 @@
|
||||
# Generated migration for FTP Quota and Bandwidth Reset Log models
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('websiteFunctions', '0001_initial'),
|
||||
('loginSystem', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='FTPQuota',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('ftp_user', models.CharField(max_length=255, unique=True)),
|
||||
('quota_size_mb', models.IntegerField(default=0)),
|
||||
('quota_used_mb', models.IntegerField(default=0)),
|
||||
('quota_files', models.IntegerField(default=0)),
|
||||
('quota_files_used', models.IntegerField(default=0)),
|
||||
('is_active', models.BooleanField(default=True)),
|
||||
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||
('updated_at', models.DateTimeField(auto_now=True)),
|
||||
('domain', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='websiteFunctions.websites')),
|
||||
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='loginSystem.administrator')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'FTP Quota',
|
||||
'verbose_name_plural': 'FTP Quotas',
|
||||
'db_table': 'ftp_quotas',
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='BandwidthResetLog',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('reset_type', models.CharField(choices=[('manual', 'Manual Reset'), ('scheduled', 'Scheduled Reset'), ('individual', 'Individual Domain Reset')], max_length=20)),
|
||||
('domains_affected', models.IntegerField(default=0)),
|
||||
('bandwidth_reset_mb', models.BigIntegerField(default=0)),
|
||||
('notes', models.TextField(blank=True, null=True)),
|
||||
('reset_at', models.DateTimeField(auto_now_add=True)),
|
||||
('domain', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='websiteFunctions.websites')),
|
||||
('reset_by', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='loginSystem.administrator')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Bandwidth Reset Log',
|
||||
'verbose_name_plural': 'Bandwidth Reset Logs',
|
||||
'db_table': 'bandwidth_reset_logs',
|
||||
'ordering': ['-reset_at'],
|
||||
},
|
||||
),
|
||||
]
|
||||
@@ -1,203 +1,64 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Add FTP quota models to existing models.py
|
||||
|
||||
# Add these models to the existing file
|
||||
class FTPQuota(models.Model):
|
||||
"""
|
||||
FTP User Quota Management
|
||||
"""
|
||||
user = models.ForeignKey('loginSystem.Administrator', on_delete=models.CASCADE)
|
||||
ftp_user = models.CharField(max_length=255, unique=True)
|
||||
domain = models.ForeignKey(Websites, on_delete=models.CASCADE, null=True, blank=True)
|
||||
quota_size_mb = models.IntegerField(default=0) # 0 = unlimited
|
||||
quota_used_mb = models.IntegerField(default=0)
|
||||
quota_files = models.IntegerField(default=0) # 0 = unlimited
|
||||
quota_files_used = models.IntegerField(default=0)
|
||||
is_active = models.BooleanField(default=True)
|
||||
created_at = models.DateTimeField(auto_now_add=True)
|
||||
updated_at = models.DateTimeField(auto_now=True)
|
||||
|
||||
class Meta:
|
||||
db_table = 'ftp_quotas'
|
||||
verbose_name = 'FTP Quota'
|
||||
verbose_name_plural = 'FTP Quotas'
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.ftp_user} - {self.quota_size_mb}MB"
|
||||
|
||||
def get_quota_percentage(self):
|
||||
"""Calculate quota usage percentage"""
|
||||
if self.quota_size_mb == 0:
|
||||
return 0
|
||||
return (self.quota_used_mb / self.quota_size_mb) * 100
|
||||
|
||||
def is_quota_exceeded(self):
|
||||
"""Check if quota is exceeded"""
|
||||
if self.quota_size_mb == 0:
|
||||
return False
|
||||
return self.quota_used_mb >= self.quota_size_mb
|
||||
|
||||
from django.db import models
|
||||
from packages.models import Package
|
||||
from loginSystem.models import Administrator
|
||||
from datetime import datetime
|
||||
|
||||
# Create your models here.
|
||||
|
||||
class Websites(models.Model):
|
||||
admin = models.ForeignKey(Administrator, on_delete=models.PROTECT)
|
||||
package = models.ForeignKey(Package, on_delete=models.PROTECT)
|
||||
domain = models.CharField(max_length=255,unique=True)
|
||||
adminEmail = models.CharField(max_length=255)
|
||||
phpSelection = models.CharField(max_length=10)
|
||||
ssl = models.IntegerField()
|
||||
state = models.IntegerField(default=1)
|
||||
externalApp = models.CharField(max_length=30, default=None)
|
||||
config = models.TextField(default='')
|
||||
BackupLock = models.IntegerField(default=0)
|
||||
|
||||
|
||||
class ChildDomains(models.Model):
|
||||
master = models.ForeignKey(Websites,on_delete=models.CASCADE)
|
||||
domain = models.CharField(max_length=50, unique=True)
|
||||
path = models.CharField(max_length=200,default=None)
|
||||
ssl = models.IntegerField()
|
||||
phpSelection = models.CharField(max_length=10,default=None)
|
||||
alais = models.IntegerField(default=0)
|
||||
|
||||
|
||||
class Backups(models.Model):
|
||||
website = models.ForeignKey(Websites,on_delete=models.CASCADE)
|
||||
fileName = models.CharField(max_length=200)
|
||||
date = models.CharField(max_length=50)
|
||||
size = models.CharField(max_length=50)
|
||||
status = models.IntegerField(default=0)
|
||||
|
||||
class dest(models.Model):
|
||||
destLoc = models.CharField(unique=True,max_length=18)
|
||||
|
||||
class backupSchedules(models.Model):
|
||||
dest = models.ForeignKey(dest, on_delete=models.CASCADE)
|
||||
frequency = models.CharField(max_length=15)
|
||||
|
||||
class aliasDomains(models.Model):
|
||||
master = models.ForeignKey(Websites, on_delete=models.CASCADE)
|
||||
aliasDomain = models.CharField(max_length=75)
|
||||
|
||||
class GitLogs(models.Model):
|
||||
owner = models.ForeignKey(Websites, on_delete=models.CASCADE)
|
||||
date = models.DateTimeField(default=datetime.now, blank=True)
|
||||
type = models.CharField(max_length=5)
|
||||
message = models.TextField(max_length=65532)
|
||||
|
||||
class BackupJob(models.Model):
|
||||
logFile = models.CharField(max_length=1000)
|
||||
ipAddress = models.CharField(max_length=50)
|
||||
port = models.CharField(max_length=15)
|
||||
jobSuccessSites = models.IntegerField()
|
||||
jobFailedSites = models.IntegerField()
|
||||
location = models.IntegerField()
|
||||
|
||||
class BackupJobLogs(models.Model):
|
||||
owner = models.ForeignKey(BackupJob, on_delete=models.CASCADE)
|
||||
status = models.IntegerField()
|
||||
message = models.TextField()
|
||||
|
||||
class GDrive(models.Model):
|
||||
owner = models.ForeignKey(Administrator, on_delete=models.CASCADE)
|
||||
name = models.CharField(max_length=50, unique=True)
|
||||
auth = models.TextField(max_length=65532, default='Inactive')
|
||||
runTime = models.CharField(max_length=20, default='NEVER')
|
||||
|
||||
class GDriveSites(models.Model):
|
||||
owner = models.ForeignKey(GDrive, on_delete=models.CASCADE)
|
||||
domain = models.CharField(max_length=200)
|
||||
|
||||
class GDriveJobLogs(models.Model):
|
||||
owner = models.ForeignKey(GDrive, on_delete=models.CASCADE)
|
||||
status = models.IntegerField()
|
||||
message = models.TextField()
|
||||
|
||||
|
||||
### Normal backup models
|
||||
|
||||
class NormalBackupDests(models.Model):
|
||||
name = models.CharField(max_length=25)
|
||||
config = models.TextField()
|
||||
|
||||
class NormalBackupJobs(models.Model):
|
||||
owner = models.ForeignKey(NormalBackupDests, on_delete=models.CASCADE)
|
||||
name = models.CharField(max_length=25)
|
||||
config = models.TextField()
|
||||
|
||||
class NormalBackupSites(models.Model):
|
||||
owner = models.ForeignKey(NormalBackupJobs, on_delete=models.CASCADE)
|
||||
domain = models.ForeignKey(Websites, on_delete=models.CASCADE)
|
||||
|
||||
class NormalBackupJobLogs(models.Model):
|
||||
owner = models.ForeignKey(NormalBackupJobs, on_delete=models.CASCADE)
|
||||
status = models.IntegerField()
|
||||
message = models.TextField()
|
||||
|
||||
class wpplugins(models.Model):
|
||||
owner = models.ForeignKey(Administrator, on_delete=models.CASCADE)
|
||||
Name = models.CharField(max_length=255, default='')
|
||||
config = models.TextField()
|
||||
|
||||
class WPSites(models.Model):
|
||||
owner = models.ForeignKey(Websites, on_delete=models.CASCADE)
|
||||
title = models.CharField(max_length=255, default='')
|
||||
path = models.CharField(max_length=255, default='')
|
||||
FinalURL = models.CharField(max_length=255, default='')
|
||||
AutoUpdates = models.CharField(max_length=100, default='Disabled')
|
||||
PluginUpdates = models.CharField(max_length=15, default='Disabled')
|
||||
ThemeUpdates = models.CharField(max_length=15, default='Disabled')
|
||||
date = models.DateTimeField(default=datetime.now)
|
||||
WPLockState = models.IntegerField(default=1)
|
||||
|
||||
class WPStaging(models.Model):
|
||||
owner = models.ForeignKey(WPSites, on_delete=models.CASCADE)
|
||||
wpsite = models.ForeignKey(WPSites, on_delete=models.CASCADE, related_name='actual_wpsite')
|
||||
|
||||
class WPSitesBackup(models.Model):
|
||||
owner = models.ForeignKey(Administrator, on_delete=models.CASCADE)
|
||||
WPSiteID = models.IntegerField(default=-1)
|
||||
WebsiteID = models.IntegerField(default=-1)
|
||||
config = models.TextField()
|
||||
|
||||
class RemoteBackupConfig(models.Model):
|
||||
owner = models.ForeignKey(Administrator, on_delete=models.CASCADE)
|
||||
configtype = models.CharField(max_length=255, default='')
|
||||
config = models.TextField()
|
||||
|
||||
class RemoteBackupSchedule(models.Model):
|
||||
RemoteBackupConfig = models.ForeignKey(RemoteBackupConfig, on_delete=models.CASCADE)
|
||||
Name = models.CharField(max_length=255, default='')
|
||||
timeintervel = models.CharField(max_length=200)
|
||||
fileretention = models.CharField(max_length=200)
|
||||
lastrun = models.CharField(max_length=200)
|
||||
config = models.TextField()
|
||||
|
||||
class RemoteBackupsites(models.Model):
|
||||
owner = models.ForeignKey(RemoteBackupSchedule, on_delete=models.CASCADE)
|
||||
WPsites = models.IntegerField(null=True)
|
||||
database = models.IntegerField(null=True)
|
||||
|
||||
import time
|
||||
|
||||
class Backupsv2(models.Model):
|
||||
website = models.ForeignKey(Websites, on_delete=models.CASCADE)
|
||||
fileName = models.CharField(max_length=255)
|
||||
status = models.IntegerField(default=0)
|
||||
timeStamp = models.CharField(max_length=255, default=str(time.time()))
|
||||
BasePath = models.TextField(default='')
|
||||
|
||||
class BackupsLogsv2(models.Model):
|
||||
owner = models.ForeignKey(Backupsv2, on_delete=models.CASCADE)
|
||||
timeStamp = models.CharField(max_length=255, default=str(time.time()))
|
||||
message = models.TextField(default='')
|
||||
|
||||
|
||||
# Takes
|
||||
# ComposePath, MySQLPath, MySQLRootPass, MySQLDBName, MySQLDBNUser, MySQLPassword, CPUsMySQL, MemoryMySQL,
|
||||
# port, SitePath, CPUsSite, MemorySite, SiteName
|
||||
# finalURL, blogTitle, adminUser, adminPassword, adminEmail
|
||||
|
||||
### Site Type 0=wp, further tbd later
|
||||
|
||||
class DockerSites(models.Model):
|
||||
admin = models.ForeignKey(Websites, on_delete=models.CASCADE)
|
||||
ComposePath = models.TextField()
|
||||
SitePath = models.TextField()
|
||||
MySQLPath = models.TextField()
|
||||
state = models.IntegerField(default=1)
|
||||
SiteType = models.IntegerField(default=0) ## WP, Joomla etc
|
||||
MySQLDBName = models.CharField(max_length=100)
|
||||
MySQLDBNUser = models.CharField(max_length=100)
|
||||
CPUsMySQL = models.CharField(max_length=100)
|
||||
MemoryMySQL = models.CharField(max_length=100)
|
||||
port = models.CharField(max_length=100)
|
||||
CPUsSite = models.CharField(max_length=100)
|
||||
MemorySite = models.CharField(max_length=100)
|
||||
SiteName = models.CharField(unique=True, max_length=255)
|
||||
finalURL = models.TextField()
|
||||
blogTitle = models.TextField()
|
||||
adminUser = models.CharField(max_length=100)
|
||||
adminEmail = models.CharField(max_length=100)
|
||||
|
||||
class DockerPackages(models.Model):
|
||||
Name = models.CharField(max_length=100, default='')
|
||||
CPUs = models.IntegerField()
|
||||
Ram = models.IntegerField()
|
||||
Bandwidth = models.TextField()
|
||||
DiskSpace = models.TextField()
|
||||
config = models.TextField()
|
||||
|
||||
|
||||
class PackageAssignment(models.Model):
|
||||
user = models.ForeignKey(Administrator, on_delete=models.CASCADE)
|
||||
package = models.ForeignKey(DockerPackages, on_delete=models.CASCADE)
|
||||
class BandwidthResetLog(models.Model):
|
||||
"""
|
||||
Bandwidth Reset Log
|
||||
"""
|
||||
RESET_TYPES = [
|
||||
('manual', 'Manual Reset'),
|
||||
('scheduled', 'Scheduled Reset'),
|
||||
('individual', 'Individual Domain Reset'),
|
||||
]
|
||||
|
||||
reset_type = models.CharField(max_length=20, choices=RESET_TYPES)
|
||||
domain = models.ForeignKey(Websites, on_delete=models.CASCADE, null=True, blank=True)
|
||||
reset_by = models.ForeignKey('loginSystem.Administrator', on_delete=models.CASCADE)
|
||||
reset_at = models.DateTimeField(auto_now_add=True)
|
||||
domains_affected = models.IntegerField(default=0)
|
||||
bandwidth_reset_mb = models.BigIntegerField(default=0)
|
||||
notes = models.TextField(blank=True, null=True)
|
||||
|
||||
class Meta:
|
||||
db_table = 'bandwidth_reset_logs'
|
||||
verbose_name = 'Bandwidth Reset Log'
|
||||
verbose_name_plural = 'Bandwidth Reset Logs'
|
||||
ordering = ['-reset_at']
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.reset_type} - {self.domain or 'All Domains'} - {self.reset_at}"
|
||||
@@ -0,0 +1,606 @@
|
||||
{% extends "baseTemplate/index.html" %}
|
||||
{% load static %}
|
||||
|
||||
{% block title %}
|
||||
Bandwidth Management - CyberPanel
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<style>
|
||||
/* Bandwidth Management Page Specific Styles */
|
||||
.cyberpanel-bandwidth-page {
|
||||
background: transparent;
|
||||
padding: 20px;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
|
||||
}
|
||||
|
||||
.cyberpanel-bandwidth-page .cyber-card {
|
||||
background: var(--bg-secondary, white);
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 2px 8px var(--shadow-color, rgba(0,0,0,0.08));
|
||||
border: 1px solid var(--border-color, #e8e9ff);
|
||||
margin-bottom: 25px;
|
||||
padding: 25px;
|
||||
transition: box-shadow 0.2s;
|
||||
}
|
||||
|
||||
.cyberpanel-bandwidth-page .cyber-card:hover {
|
||||
box-shadow: 0 4px 16px var(--shadow-color-hover, rgba(0,0,0,0.12));
|
||||
}
|
||||
|
||||
.cyberpanel-bandwidth-page .cyber-section-title {
|
||||
font-size: 16px;
|
||||
font-weight: 700;
|
||||
color: var(--text-primary, #2f3640);
|
||||
margin-bottom: 20px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
|
||||
.cyberpanel-bandwidth-page .cyber-section-title::before {
|
||||
content: '';
|
||||
width: 4px;
|
||||
height: 24px;
|
||||
background: var(--accent-color, #5b5fcf);
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
.cyberpanel-bandwidth-page .cyber-btn {
|
||||
background: var(--bg-hover, #f8f9ff);
|
||||
border: 1px solid var(--border-color, #e8e9ff);
|
||||
color: var(--accent-color, #5b5fcf);
|
||||
padding: 8px 16px;
|
||||
border-radius: 8px;
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
text-decoration: none;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
margin-right: 12px;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.cyberpanel-bandwidth-page .cyber-btn:hover {
|
||||
background: var(--accent-color, #5b5fcf);
|
||||
color: var(--text-on-accent, white);
|
||||
border-color: var(--accent-color, #5b5fcf);
|
||||
box-shadow: 0 2px 4px var(--accent-shadow, rgba(91,95,207,0.3));
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.cyberpanel-bandwidth-page .cyber-btn.warning {
|
||||
background: var(--warning-bg, #fff7ed);
|
||||
color: var(--warning-color, #ea580c);
|
||||
border-color: var(--warning-border, #fed7aa);
|
||||
}
|
||||
|
||||
.cyberpanel-bandwidth-page .cyber-btn.info {
|
||||
background: var(--info-bg, #f0f9ff);
|
||||
color: var(--info-color, #0ea5e9);
|
||||
border-color: var(--info-border, #bae6fd);
|
||||
}
|
||||
|
||||
.cyberpanel-bandwidth-page .cyber-btn.success {
|
||||
background: var(--success-bg, #f0fdf4);
|
||||
color: var(--success-color, #16a34a);
|
||||
border-color: var(--success-border, #bbf7d0);
|
||||
}
|
||||
|
||||
.cyberpanel-bandwidth-page .cyber-table {
|
||||
width: 100%;
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
border: 1px solid var(--border-color, #e8e9ff);
|
||||
}
|
||||
|
||||
.cyberpanel-bandwidth-page .cyber-table th,
|
||||
.cyberpanel-bandwidth-page .cyber-table td {
|
||||
padding: 12px 16px;
|
||||
border-bottom: 1px solid var(--border-light, #f0f0ff);
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.cyberpanel-bandwidth-page .cyber-table th {
|
||||
color: var(--text-secondary, #64748b);
|
||||
font-weight: 700;
|
||||
background: var(--bg-hover, #f8f9ff);
|
||||
border-top: none;
|
||||
font-size: 11px;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.8px;
|
||||
}
|
||||
|
||||
.cyberpanel-bandwidth-page .cyber-table tr:last-child td {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.cyberpanel-bandwidth-page .cyber-table tr:hover {
|
||||
background: var(--bg-hover, #f8f9ff);
|
||||
}
|
||||
|
||||
.cyberpanel-bandwidth-page .form-control {
|
||||
padding: 8px 12px;
|
||||
border: 1px solid var(--border-color, #e8e9ff);
|
||||
border-radius: 6px;
|
||||
font-size: 14px;
|
||||
background: var(--bg-secondary, white);
|
||||
color: var(--text-primary, #2f3640);
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.cyberpanel-bandwidth-page .form-control:focus {
|
||||
outline: none;
|
||||
border-color: var(--accent-color, #5b5fcf);
|
||||
box-shadow: 0 0 0 3px var(--accent-focus, rgba(91,95,207,0.1));
|
||||
}
|
||||
|
||||
.cyberpanel-bandwidth-page .form-group {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.cyberpanel-bandwidth-page .form-group label {
|
||||
font-weight: 600;
|
||||
color: var(--text-primary, #2f3640);
|
||||
font-size: 14px;
|
||||
margin-bottom: 8px;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.cyberpanel-bandwidth-page .alert {
|
||||
padding: 15px;
|
||||
border-radius: 8px;
|
||||
margin-bottom: 20px;
|
||||
font-size: 14px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.cyberpanel-bandwidth-page .alert-success {
|
||||
background: var(--success-bg, #f0f9ff);
|
||||
border: 1px solid var(--success-border, #bae6fd);
|
||||
color: var(--success-text, #0c4a6e);
|
||||
}
|
||||
|
||||
.cyberpanel-bandwidth-page .alert-danger {
|
||||
background: var(--danger-bg, #fef2f2);
|
||||
border: 1px solid var(--danger-border, #fecaca);
|
||||
color: var(--danger-text, #991b1b);
|
||||
}
|
||||
|
||||
.cyberpanel-bandwidth-page .alert-warning {
|
||||
background: var(--warning-bg, #fffbeb);
|
||||
border: 1px solid var(--warning-border, #fed7aa);
|
||||
color: var(--warning-text, #92400e);
|
||||
}
|
||||
|
||||
.cyberpanel-bandwidth-page .alert-info {
|
||||
background: var(--info-bg, #f0f9ff);
|
||||
border: 1px solid var(--info-border, #bae6fd);
|
||||
color: var(--info-text, #0c4a6e);
|
||||
}
|
||||
|
||||
/* Creative Hero Section Design */
|
||||
.bandwidth-hero {
|
||||
background: linear-gradient(135deg, #1e3c72 0%, #2a5298 100%);
|
||||
border-radius: 20px;
|
||||
padding: 40px;
|
||||
margin-bottom: 30px;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.bandwidth-hero::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: -100px;
|
||||
right: -100px;
|
||||
width: 300px;
|
||||
height: 300px;
|
||||
background: radial-gradient(circle, rgba(255,255,255,0.1) 0%, transparent 70%);
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.bandwidth-hero::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
bottom: -50px;
|
||||
left: -50px;
|
||||
width: 200px;
|
||||
height: 200px;
|
||||
background: radial-gradient(circle, rgba(255,255,255,0.05) 0%, transparent 70%);
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.bandwidth-info {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.bandwidth-title {
|
||||
font-size: 36px;
|
||||
font-weight: 800;
|
||||
margin-bottom: 10px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.bandwidth-description {
|
||||
font-size: 18px;
|
||||
opacity: 0.9;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
/* Action Cards */
|
||||
.action-cards {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
|
||||
gap: 20px;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
.action-card {
|
||||
background: var(--bg-secondary, white);
|
||||
border-radius: 16px;
|
||||
padding: 25px;
|
||||
box-shadow: 0 4px 20px var(--shadow-color, rgba(0,0,0,0.08));
|
||||
transition: all 0.3s ease;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.action-card::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 4px;
|
||||
background: linear-gradient(90deg, #667eea 0%, #764ba2 100%);
|
||||
}
|
||||
|
||||
.action-card:hover {
|
||||
transform: translateY(-5px);
|
||||
box-shadow: 0 8px 30px var(--shadow-color-hover, rgba(0,0,0,0.12));
|
||||
}
|
||||
|
||||
.action-card-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.action-card-icon {
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
background: var(--bg-hover, #f8f9ff);
|
||||
border-radius: 12px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 24px;
|
||||
color: var(--accent-color, #5b5fcf);
|
||||
}
|
||||
|
||||
.action-card-title {
|
||||
font-size: 18px;
|
||||
font-weight: 700;
|
||||
color: var(--text-primary, #2f3640);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.action-card-description {
|
||||
color: var(--text-secondary, #64748b);
|
||||
font-size: 14px;
|
||||
margin-bottom: 20px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
/* Badge styling */
|
||||
.badge {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
padding: 4px 12px;
|
||||
border-radius: 20px;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
|
||||
.badge-primary {
|
||||
background: var(--accent-bg, #f0f4ff);
|
||||
color: var(--accent-color, #5b5fcf);
|
||||
}
|
||||
|
||||
.badge-info {
|
||||
background: var(--info-bg, #f0f9ff);
|
||||
color: var(--info-color, #0ea5e9);
|
||||
}
|
||||
|
||||
.badge-warning {
|
||||
background: var(--warning-bg, #fff7ed);
|
||||
color: var(--warning-color, #ea580c);
|
||||
}
|
||||
|
||||
/* Mobile responsiveness */
|
||||
@media (max-width: 768px) {
|
||||
.cyberpanel-bandwidth-page {
|
||||
padding: 15px;
|
||||
}
|
||||
|
||||
.bandwidth-hero {
|
||||
padding: 30px 20px;
|
||||
}
|
||||
|
||||
.bandwidth-title {
|
||||
font-size: 28px;
|
||||
}
|
||||
|
||||
.action-cards {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.cyberpanel-bandwidth-page .cyber-table {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.cyberpanel-bandwidth-page .cyber-table th,
|
||||
.cyberpanel-bandwidth-page .cyber-table td {
|
||||
padding: 8px 12px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="cyberpanel-bandwidth-page">
|
||||
<!-- Creative Hero Section -->
|
||||
<div class="bandwidth-hero">
|
||||
<div class="bandwidth-info">
|
||||
<div class="bandwidth-title">
|
||||
<i class="fas fa-tachometer-alt"></i>
|
||||
Bandwidth Management
|
||||
</div>
|
||||
<p class="bandwidth-description">
|
||||
Monitor and manage bandwidth usage across all your domains with powerful reset tools and scheduling options.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Action Cards -->
|
||||
<div class="action-cards">
|
||||
<div class="action-card">
|
||||
<div class="action-card-header">
|
||||
<div class="action-card-icon">
|
||||
<i class="fas fa-sync"></i>
|
||||
</div>
|
||||
<h3 class="action-card-title">Reset Bandwidth</h3>
|
||||
</div>
|
||||
<p class="action-card-description">Reset bandwidth usage for all domains or a specific domain.</p>
|
||||
<div class="form-group">
|
||||
<label for="resetDomain">Domain (leave empty for all domains)</label>
|
||||
<select class="form-control" id="resetDomain">
|
||||
<option value="">All Domains</option>
|
||||
</select>
|
||||
</div>
|
||||
<button class="cyber-btn warning" onclick="resetBandwidth()">
|
||||
<i class="fas fa-sync"></i> Reset Bandwidth
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="action-card">
|
||||
<div class="action-card-header">
|
||||
<div class="action-card-icon">
|
||||
<i class="fas fa-clock"></i>
|
||||
</div>
|
||||
<h3 class="action-card-title">Schedule Reset</h3>
|
||||
</div>
|
||||
<p class="action-card-description">Schedule automatic bandwidth reset.</p>
|
||||
<div class="form-group">
|
||||
<label for="scheduleType">Schedule Type</label>
|
||||
<select class="form-control" id="scheduleType">
|
||||
<option value="monthly">Monthly</option>
|
||||
<option value="weekly">Weekly</option>
|
||||
<option value="daily">Daily</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group" id="dayOfMonthGroup">
|
||||
<label for="dayOfMonth">Day of Month</label>
|
||||
<select class="form-control" id="dayOfMonth">
|
||||
{% for i in "1234567890123456789012345678901"|make_list %}
|
||||
<option value="{{ forloop.counter }}" {% if forloop.counter == 1 %}selected{% endif %}>{{ forloop.counter }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="resetHour">Hour (24h format)</label>
|
||||
<select class="form-control" id="resetHour">
|
||||
{% for i in "012345678901234567890123"|make_list %}
|
||||
<option value="{{ forloop.counter0 }}" {% if forloop.counter0 == 2 %}selected{% endif %}>{{ forloop.counter0|stringformat:"02d" }}:00</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
<button class="cyber-btn info" onclick="scheduleReset()">
|
||||
<i class="fas fa-clock"></i> Schedule Reset
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Reset Logs -->
|
||||
<div class="cyber-card">
|
||||
<div class="cyber-section-title">
|
||||
<i class="fas fa-history"></i>
|
||||
Reset History
|
||||
<button class="cyber-btn success" onclick="refreshLogs()" style="margin-left: auto;">
|
||||
<i class="fas fa-sync"></i> Refresh
|
||||
</button>
|
||||
</div>
|
||||
<div class="table-responsive">
|
||||
<table class="cyber-table" id="logsTable">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Reset Type</th>
|
||||
<th>Domain</th>
|
||||
<th>Reset By</th>
|
||||
<th>Reset At</th>
|
||||
<th>Domains Affected</th>
|
||||
<th>Bandwidth Reset (MB)</th>
|
||||
<th>Notes</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="logsTableBody">
|
||||
<tr>
|
||||
<td colspan="7" class="text-center">
|
||||
<i class="fas fa-spinner fa-spin"></i> Loading...
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
function resetBandwidth() {
|
||||
const domain = document.getElementById('resetDomain').value;
|
||||
|
||||
if (domain && !confirm(`Are you sure you want to reset bandwidth for ${domain}?`)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!domain && !confirm('Are you sure you want to reset bandwidth for ALL domains?')) {
|
||||
return;
|
||||
}
|
||||
|
||||
const formData = {
|
||||
'csrfmiddlewaretoken': '{{ csrf_token }}',
|
||||
'reset_type': 'manual',
|
||||
'domain': domain
|
||||
};
|
||||
|
||||
$.post('{% url "resetBandwidth" %}', formData, function(data) {
|
||||
if (data.status === 1) {
|
||||
showNotification('success', data.message);
|
||||
refreshLogs();
|
||||
} else {
|
||||
showNotification('error', data.message);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function scheduleReset() {
|
||||
const scheduleType = document.getElementById('scheduleType').value;
|
||||
const dayOfMonth = document.getElementById('dayOfMonth').value;
|
||||
const hour = document.getElementById('resetHour').value;
|
||||
|
||||
const formData = {
|
||||
'csrfmiddlewaretoken': '{{ csrf_token }}',
|
||||
'schedule_type': scheduleType,
|
||||
'day_of_month': dayOfMonth,
|
||||
'hour': hour,
|
||||
'minute': '0'
|
||||
};
|
||||
|
||||
$.post('{% url "scheduleBandwidthReset" %}', formData, function(data) {
|
||||
if (data.status === 1) {
|
||||
showNotification('success', data.message);
|
||||
} else {
|
||||
showNotification('error', data.message);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function refreshLogs() {
|
||||
$.post('{% url "getBandwidthResetLogs" %}', {
|
||||
'csrfmiddlewaretoken': '{{ csrf_token }}'
|
||||
}, function(data) {
|
||||
if (data.status === 1) {
|
||||
displayLogs(data.logs);
|
||||
} else {
|
||||
showNotification('error', data.message);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function displayLogs(logs) {
|
||||
const tbody = document.getElementById('logsTableBody');
|
||||
|
||||
if (logs.length === 0) {
|
||||
tbody.innerHTML = '<tr><td colspan="7" class="text-center">No reset logs found</td></tr>';
|
||||
return;
|
||||
}
|
||||
|
||||
let html = '';
|
||||
logs.forEach(log => {
|
||||
const resetTypeClass = log.reset_type === 'Manual Reset' ? 'primary' :
|
||||
log.reset_type === 'Scheduled Reset' ? 'info' : 'warning';
|
||||
|
||||
html += `
|
||||
<tr>
|
||||
<td><span class="badge badge-${resetTypeClass}">${log.reset_type}</span></td>
|
||||
<td>${log.domain}</td>
|
||||
<td>${log.reset_by}</td>
|
||||
<td>${log.reset_at}</td>
|
||||
<td>${log.domains_affected}</td>
|
||||
<td>${log.bandwidth_reset_mb.toLocaleString()}</td>
|
||||
<td>${log.notes || '-'}</td>
|
||||
</tr>
|
||||
`;
|
||||
});
|
||||
|
||||
tbody.innerHTML = html;
|
||||
}
|
||||
|
||||
function showNotification(type, message) {
|
||||
const alertClass = type === 'success' ? 'alert-success' : 'alert-danger';
|
||||
const icon = type === 'success' ? 'fa-check-circle' : 'fa-exclamation-circle';
|
||||
|
||||
const notification = `
|
||||
<div class="alert ${alertClass} alert-dismissible fade show" role="alert" style="margin-bottom: 20px;">
|
||||
<i class="fas ${icon}"></i> ${message}
|
||||
<button type="button" class="close" data-dismiss="alert">
|
||||
<span>×</span>
|
||||
</button>
|
||||
</div>
|
||||
`;
|
||||
|
||||
$('.cyberpanel-bandwidth-page').prepend(notification);
|
||||
|
||||
setTimeout(() => {
|
||||
$('.alert').fadeOut();
|
||||
}, 5000);
|
||||
}
|
||||
|
||||
// Toggle day of month field based on schedule type
|
||||
function toggleDayOfMonth() {
|
||||
const scheduleType = document.getElementById('scheduleType').value;
|
||||
const dayGroup = document.getElementById('dayOfMonthGroup');
|
||||
|
||||
if (scheduleType === 'monthly') {
|
||||
dayGroup.style.display = 'block';
|
||||
} else {
|
||||
dayGroup.style.display = 'none';
|
||||
}
|
||||
}
|
||||
|
||||
// Load domains for reset dropdown
|
||||
function loadDomains() {
|
||||
// This would typically load from an API endpoint
|
||||
// For now, we'll leave it empty
|
||||
}
|
||||
|
||||
// Load logs on page load
|
||||
$(document).ready(function() {
|
||||
refreshLogs();
|
||||
loadDomains();
|
||||
|
||||
// Add event listener for schedule type change
|
||||
document.getElementById('scheduleType').addEventListener('change', toggleDayOfMonth);
|
||||
toggleDayOfMonth(); // Initial call
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,227 @@
|
||||
{% extends "baseTemplate/index.html" %}
|
||||
{% load static %}
|
||||
|
||||
{% block title %}
|
||||
FTP Quota Management - CyberPanel
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container-fluid">
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h3 class="card-title">
|
||||
<i class="fas fa-folder"></i>
|
||||
FTP Quota Management
|
||||
</h3>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<!-- Enable FTP Quota Section -->
|
||||
<div class="row mb-4">
|
||||
<div class="col-12">
|
||||
<div class="alert alert-info">
|
||||
<h5><i class="fas fa-info-circle"></i> FTP Quota System</h5>
|
||||
<p>Enable and manage individual FTP user quotas. This allows you to set storage limits for each FTP user.</p>
|
||||
<button class="btn btn-primary" onclick="enableFTPQuota()">
|
||||
<i class="fas fa-play"></i> Enable FTP Quota System
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- FTP Quotas List -->
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h4>FTP User Quotas</h4>
|
||||
<button class="btn btn-success btn-sm" onclick="refreshQuotas()">
|
||||
<i class="fas fa-sync"></i> Refresh
|
||||
</button>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-striped" id="quotasTable">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>FTP User</th>
|
||||
<th>Domain</th>
|
||||
<th>Quota Size (MB)</th>
|
||||
<th>Used (MB)</th>
|
||||
<th>Usage %</th>
|
||||
<th>File Limit</th>
|
||||
<th>Files Used</th>
|
||||
<th>Status</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="quotasTableBody">
|
||||
<tr>
|
||||
<td colspan="9" class="text-center">
|
||||
<i class="fas fa-spinner fa-spin"></i> Loading...
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Edit Quota Modal -->
|
||||
<div class="modal fade" id="editQuotaModal" tabindex="-1">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">Edit FTP Quota</h5>
|
||||
<button type="button" class="close" data-dismiss="modal">
|
||||
<span>×</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<form id="editQuotaForm">
|
||||
<input type="hidden" id="quotaId" name="quota_id">
|
||||
<div class="form-group">
|
||||
<label for="quotaSize">Quota Size (MB)</label>
|
||||
<input type="number" class="form-control" id="quotaSize" name="quota_size_mb" min="0" placeholder="0 = Unlimited">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="quotaFiles">File Limit</label>
|
||||
<input type="number" class="form-control" id="quotaFiles" name="quota_files" min="0" placeholder="0 = Unlimited">
|
||||
</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" onclick="saveQuota()">Save Changes</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
function enableFTPQuota() {
|
||||
$.post('{% url "enableFTPQuota" %}', {
|
||||
'csrfmiddlewaretoken': '{{ csrf_token }}'
|
||||
}, function(data) {
|
||||
if (data.status === 1) {
|
||||
showNotification('success', data.message);
|
||||
refreshQuotas();
|
||||
} else {
|
||||
showNotification('error', data.message);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function refreshQuotas() {
|
||||
$.post('{% url "getFTPQuotas" %}', {
|
||||
'csrfmiddlewaretoken': '{{ csrf_token }}'
|
||||
}, function(data) {
|
||||
if (data.status === 1) {
|
||||
displayQuotas(data.quotas);
|
||||
} else {
|
||||
showNotification('error', data.message);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function displayQuotas(quotas) {
|
||||
const tbody = document.getElementById('quotasTableBody');
|
||||
|
||||
if (quotas.length === 0) {
|
||||
tbody.innerHTML = '<tr><td colspan="9" class="text-center">No FTP quotas found</td></tr>';
|
||||
return;
|
||||
}
|
||||
|
||||
let html = '';
|
||||
quotas.forEach(quota => {
|
||||
const percentage = quota.quota_percentage.toFixed(1);
|
||||
const statusClass = quota.is_active ? 'success' : 'danger';
|
||||
const statusText = quota.is_active ? 'Active' : 'Inactive';
|
||||
|
||||
html += `
|
||||
<tr>
|
||||
<td>${quota.ftp_user}</td>
|
||||
<td>${quota.domain}</td>
|
||||
<td>${quota.quota_size_mb === 0 ? 'Unlimited' : quota.quota_size_mb}</td>
|
||||
<td>${quota.quota_used_mb}</td>
|
||||
<td>
|
||||
<div class="progress">
|
||||
<div class="progress-bar" style="width: ${percentage}%"></div>
|
||||
</div>
|
||||
${percentage}%
|
||||
</td>
|
||||
<td>${quota.quota_files === 0 ? 'Unlimited' : quota.quota_files}</td>
|
||||
<td>${quota.quota_files_used}</td>
|
||||
<td><span class="badge badge-${statusClass}">${statusText}</span></td>
|
||||
<td>
|
||||
<button class="btn btn-sm btn-primary" onclick="editQuota(${quota.id}, ${quota.quota_size_mb}, ${quota.quota_files})">
|
||||
<i class="fas fa-edit"></i>
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
`;
|
||||
});
|
||||
|
||||
tbody.innerHTML = html;
|
||||
}
|
||||
|
||||
function editQuota(id, size, files) {
|
||||
document.getElementById('quotaId').value = id;
|
||||
document.getElementById('quotaSize').value = size;
|
||||
document.getElementById('quotaFiles').value = files;
|
||||
$('#editQuotaModal').modal('show');
|
||||
}
|
||||
|
||||
function saveQuota() {
|
||||
const formData = {
|
||||
'csrfmiddlewaretoken': '{{ csrf_token }}',
|
||||
'quota_id': document.getElementById('quotaId').value,
|
||||
'quota_size_mb': document.getElementById('quotaSize').value,
|
||||
'quota_files': document.getElementById('quotaFiles').value
|
||||
};
|
||||
|
||||
$.post('{% url "updateFTPQuota" %}', formData, function(data) {
|
||||
if (data.status === 1) {
|
||||
showNotification('success', data.message);
|
||||
$('#editQuotaModal').modal('hide');
|
||||
refreshQuotas();
|
||||
} else {
|
||||
showNotification('error', data.message);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function showNotification(type, message) {
|
||||
const alertClass = type === 'success' ? 'alert-success' : 'alert-danger';
|
||||
const icon = type === 'success' ? 'fa-check-circle' : 'fa-exclamation-circle';
|
||||
|
||||
const notification = `
|
||||
<div class="alert ${alertClass} alert-dismissible fade show" role="alert">
|
||||
<i class="fas ${icon}"></i> ${message}
|
||||
<button type="button" class="close" data-dismiss="alert">
|
||||
<span>×</span>
|
||||
</button>
|
||||
</div>
|
||||
`;
|
||||
|
||||
$('.card-body').prepend(notification);
|
||||
|
||||
setTimeout(() => {
|
||||
$('.alert').fadeOut();
|
||||
}, 5000);
|
||||
}
|
||||
|
||||
// Load quotas on page load
|
||||
$(document).ready(function() {
|
||||
refreshQuotas();
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,322 @@
|
||||
{% extends "baseTemplate/index.html" %}
|
||||
{% load static %}
|
||||
|
||||
{% block title %}
|
||||
Security Management - CyberPanel
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container-fluid">
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h3 class="card-title">
|
||||
<i class="fas fa-shield-alt"></i>
|
||||
Security Management
|
||||
</h3>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<!-- Security Alerts Section -->
|
||||
<div class="row mb-4">
|
||||
<div class="col-12">
|
||||
<div class="alert alert-warning">
|
||||
<h5><i class="fas fa-exclamation-triangle"></i> Security Alerts</h5>
|
||||
<p>Monitor and manage security threats detected by the system.</p>
|
||||
<button class="btn btn-warning" onclick="refreshSecurityAlerts()">
|
||||
<i class="fas fa-sync"></i> Refresh Alerts
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Security Alerts List -->
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h4>Recent Security Alerts</h4>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div id="securityAlertsContainer">
|
||||
<!-- Alerts will be loaded here -->
|
||||
<div class="text-center">
|
||||
<i class="fas fa-spinner fa-spin"></i> Loading security alerts...
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Blocked IPs Management -->
|
||||
<div class="row mt-4">
|
||||
<div class="col-12">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h4>Blocked IP Addresses</h4>
|
||||
<button class="btn btn-success btn-sm" onclick="refreshBlockedIPs()">
|
||||
<i class="fas fa-sync"></i> Refresh
|
||||
</button>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-striped" id="blockedIPsTable">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>IP Address</th>
|
||||
<th>Blocked At</th>
|
||||
<th>Reason</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="blockedIPsTableBody">
|
||||
<tr>
|
||||
<td colspan="4" class="text-center">
|
||||
<i class="fas fa-spinner fa-spin"></i> Loading...
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Manual IP Blocking -->
|
||||
<div class="row mt-4">
|
||||
<div class="col-12">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h4>Manual IP Blocking</h4>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<form id="blockIPForm">
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="form-group">
|
||||
<label for="ipAddress">IP Address</label>
|
||||
<input type="text" class="form-control" id="ipAddress" name="ip_address" placeholder="192.168.1.100" required>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="form-group">
|
||||
<label for="blockReason">Reason</label>
|
||||
<input type="text" class="form-control" id="blockReason" name="reason" placeholder="Suspicious activity" value="Manual block via CyberPanel">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-danger">
|
||||
<i class="fas fa-ban"></i> Block IP Address
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// Sample security alerts data (in a real implementation, this would come from the backend)
|
||||
const sampleAlerts = [
|
||||
{
|
||||
id: 1,
|
||||
type: 'brute_force',
|
||||
ip: '129.212.176.254',
|
||||
attempts: 85,
|
||||
severity: 'HIGH',
|
||||
timestamp: '2024-01-15 14:30:25',
|
||||
description: 'IP address 129.212.176.254 has made 85 failed password attempts. This indicates a potential brute force attack.'
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
type: 'brute_force',
|
||||
ip: '177.10.47.186',
|
||||
attempts: 10,
|
||||
severity: 'HIGH',
|
||||
timestamp: '2024-01-15 14:25:10',
|
||||
description: 'IP address 177.10.47.186 has made 10 failed password attempts. This indicates a potential brute force attack.'
|
||||
}
|
||||
];
|
||||
|
||||
function refreshSecurityAlerts() {
|
||||
const container = document.getElementById('securityAlertsContainer');
|
||||
|
||||
if (sampleAlerts.length === 0) {
|
||||
container.innerHTML = '<div class="alert alert-info">No security alerts found.</div>';
|
||||
return;
|
||||
}
|
||||
|
||||
let html = '';
|
||||
sampleAlerts.forEach(alert => {
|
||||
const severityClass = alert.severity === 'HIGH' ? 'danger' : alert.severity === 'MEDIUM' ? 'warning' : 'info';
|
||||
|
||||
html += `
|
||||
<div class="alert alert-${severityClass} mb-3">
|
||||
<div class="d-flex justify-content-between align-items-start">
|
||||
<div>
|
||||
<h6 class="alert-heading">
|
||||
<i class="fas fa-exclamation-triangle"></i>
|
||||
${alert.type.replace('_', ' ').toUpperCase()} Attack Detected
|
||||
</h6>
|
||||
<p class="mb-2">${alert.description}</p>
|
||||
<div class="row">
|
||||
<div class="col-md-3">
|
||||
<strong>IP Address:</strong> ${alert.ip}
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<strong>Failed Attempts:</strong> ${alert.attempts}
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<strong>Attack Type:</strong> Brute Force
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<strong>Time:</strong> ${alert.timestamp}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ml-3">
|
||||
<span class="badge badge-${severityClass}">${alert.severity}</span>
|
||||
<div class="mt-2">
|
||||
<button class="btn btn-sm btn-danger" onclick="blockIP('${alert.ip}', 'Brute force attack - ${alert.attempts} attempts')">
|
||||
<i class="fas fa-ban"></i> Block IP
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
});
|
||||
|
||||
container.innerHTML = html;
|
||||
}
|
||||
|
||||
function blockIP(ipAddress, reason) {
|
||||
if (!confirm(`Are you sure you want to block IP address ${ipAddress}?`)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const formData = {
|
||||
'csrfmiddlewaretoken': '{{ csrf_token }}',
|
||||
'ip_address': ipAddress,
|
||||
'reason': reason
|
||||
};
|
||||
|
||||
$.post('{% url "blockIPAddress" %}', formData, function(data) {
|
||||
if (data.status === 1) {
|
||||
showNotification('success', data.message);
|
||||
refreshBlockedIPs();
|
||||
refreshSecurityAlerts(); // Refresh to remove the alert or update its status
|
||||
} else {
|
||||
showNotification('error', data.message);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function unblockIP(ipAddress) {
|
||||
if (!confirm(`Are you sure you want to unblock IP address ${ipAddress}?`)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const formData = {
|
||||
'csrfmiddlewaretoken': '{{ csrf_token }}',
|
||||
'ip_address': ipAddress
|
||||
};
|
||||
|
||||
$.post('{% url "unblockIPAddress" %}', formData, function(data) {
|
||||
if (data.status === 1) {
|
||||
showNotification('success', data.message);
|
||||
refreshBlockedIPs();
|
||||
} else {
|
||||
showNotification('error', data.message);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function refreshBlockedIPs() {
|
||||
$.post('{% url "getBlockedIPs" %}', {
|
||||
'csrfmiddlewaretoken': '{{ csrf_token }}'
|
||||
}, function(data) {
|
||||
if (data.status === 1) {
|
||||
displayBlockedIPs(data.blocked_ips);
|
||||
} else {
|
||||
showNotification('error', data.message);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function displayBlockedIPs(blockedIPs) {
|
||||
const tbody = document.getElementById('blockedIPsTableBody');
|
||||
|
||||
if (blockedIPs.length === 0) {
|
||||
tbody.innerHTML = '<tr><td colspan="4" class="text-center">No blocked IP addresses found</td></tr>';
|
||||
return;
|
||||
}
|
||||
|
||||
let html = '';
|
||||
blockedIPs.forEach(ip => {
|
||||
html += `
|
||||
<tr>
|
||||
<td>${ip}</td>
|
||||
<td>N/A</td>
|
||||
<td>Blocked via CyberPanel</td>
|
||||
<td>
|
||||
<button class="btn btn-sm btn-warning" onclick="unblockIP('${ip}')">
|
||||
<i class="fas fa-unlock"></i> Unblock
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
`;
|
||||
});
|
||||
|
||||
tbody.innerHTML = html;
|
||||
}
|
||||
|
||||
function showNotification(type, message) {
|
||||
const alertClass = type === 'success' ? 'alert-success' : 'alert-danger';
|
||||
const icon = type === 'success' ? 'fa-check-circle' : 'fa-exclamation-circle';
|
||||
|
||||
const notification = `
|
||||
<div class="alert ${alertClass} alert-dismissible fade show" role="alert">
|
||||
<i class="fas ${icon}"></i> ${message}
|
||||
<button type="button" class="close" data-dismiss="alert">
|
||||
<span>×</span>
|
||||
</button>
|
||||
</div>
|
||||
`;
|
||||
|
||||
$('.card-body').prepend(notification);
|
||||
|
||||
setTimeout(() => {
|
||||
$('.alert').fadeOut();
|
||||
}, 5000);
|
||||
}
|
||||
|
||||
// Handle manual IP blocking form
|
||||
$(document).ready(function() {
|
||||
$('#blockIPForm').on('submit', function(e) {
|
||||
e.preventDefault();
|
||||
|
||||
const ipAddress = $('#ipAddress').val();
|
||||
const reason = $('#blockReason').val();
|
||||
|
||||
if (!ipAddress) {
|
||||
showNotification('error', 'Please enter an IP address');
|
||||
return;
|
||||
}
|
||||
|
||||
blockIP(ipAddress, reason);
|
||||
$('#blockIPForm')[0].reset();
|
||||
});
|
||||
|
||||
// Load initial data
|
||||
refreshSecurityAlerts();
|
||||
refreshBlockedIPs();
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
@@ -211,5 +211,21 @@ urlpatterns = [
|
||||
path('fixSubdomainLogs', views.fixSubdomainLogs, name='fixSubdomainLogs'),
|
||||
path('fixSubdomainLogsAction', views.fixSubdomainLogsAction, name='fixSubdomainLogsAction'),
|
||||
|
||||
# FTP Quota Management
|
||||
path('enableFTPQuota', views.enableFTPQuota, name='enableFTPQuota'),
|
||||
path('getFTPQuotas', views.getFTPQuotas, name='getFTPQuotas'),
|
||||
path('updateFTPQuota', views.updateFTPQuota, name='updateFTPQuota'),
|
||||
|
||||
# Bandwidth Management
|
||||
path('resetBandwidth', views.resetBandwidth, name='resetBandwidth'),
|
||||
path('getBandwidthResetLogs', views.getBandwidthResetLogs, name='getBandwidthResetLogs'),
|
||||
path('scheduleBandwidthReset', views.scheduleBandwidthReset, name='scheduleBandwidthReset'),
|
||||
|
||||
# IP Blocking
|
||||
path('blockIPAddress', views.blockIPAddress, name='blockIPAddress'),
|
||||
path('unblockIPAddress', views.unblockIPAddress, name='unblockIPAddress'),
|
||||
path('getBlockedIPs', views.getBlockedIPs, name='getBlockedIPs'),
|
||||
path('checkIPStatus', views.checkIPStatus, name='checkIPStatus'),
|
||||
|
||||
|
||||
]
|
||||
|
||||
@@ -2232,5 +2232,88 @@ def fixSubdomainLogsAction(request):
|
||||
|
||||
return coreResult
|
||||
|
||||
except KeyError:
|
||||
return redirect(loadLoginPage)
|
||||
|
||||
# FTP Quota Management Views
|
||||
def enableFTPQuota(request):
|
||||
try:
|
||||
userID = request.session['userID']
|
||||
wm = WebsiteManager()
|
||||
return wm.enableFTPQuota(userID, request.POST)
|
||||
except KeyError:
|
||||
return redirect(loadLoginPage)
|
||||
|
||||
def getFTPQuotas(request):
|
||||
try:
|
||||
userID = request.session['userID']
|
||||
wm = WebsiteManager()
|
||||
return wm.getFTPQuotas(userID, request.POST)
|
||||
except KeyError:
|
||||
return redirect(loadLoginPage)
|
||||
|
||||
def updateFTPQuota(request):
|
||||
try:
|
||||
userID = request.session['userID']
|
||||
wm = WebsiteManager()
|
||||
return wm.updateFTPQuota(userID, request.POST)
|
||||
except KeyError:
|
||||
return redirect(loadLoginPage)
|
||||
|
||||
# Bandwidth Management Views
|
||||
def resetBandwidth(request):
|
||||
try:
|
||||
userID = request.session['userID']
|
||||
wm = WebsiteManager()
|
||||
return wm.resetBandwidth(userID, request.POST)
|
||||
except KeyError:
|
||||
return redirect(loadLoginPage)
|
||||
|
||||
def getBandwidthResetLogs(request):
|
||||
try:
|
||||
userID = request.session['userID']
|
||||
wm = WebsiteManager()
|
||||
return wm.getBandwidthResetLogs(userID, request.POST)
|
||||
except KeyError:
|
||||
return redirect(loadLoginPage)
|
||||
|
||||
def scheduleBandwidthReset(request):
|
||||
try:
|
||||
userID = request.session['userID']
|
||||
wm = WebsiteManager()
|
||||
return wm.scheduleBandwidthReset(userID, request.POST)
|
||||
except KeyError:
|
||||
return redirect(loadLoginPage)
|
||||
|
||||
# IP Blocking Views
|
||||
def blockIPAddress(request):
|
||||
try:
|
||||
userID = request.session['userID']
|
||||
wm = WebsiteManager()
|
||||
return wm.blockIPAddress(userID, request.POST)
|
||||
except KeyError:
|
||||
return redirect(loadLoginPage)
|
||||
|
||||
def unblockIPAddress(request):
|
||||
try:
|
||||
userID = request.session['userID']
|
||||
wm = WebsiteManager()
|
||||
return wm.unblockIPAddress(userID, request.POST)
|
||||
except KeyError:
|
||||
return redirect(loadLoginPage)
|
||||
|
||||
def getBlockedIPs(request):
|
||||
try:
|
||||
userID = request.session['userID']
|
||||
wm = WebsiteManager()
|
||||
return wm.getBlockedIPs(userID, request.POST)
|
||||
except KeyError:
|
||||
return redirect(loadLoginPage)
|
||||
|
||||
def checkIPStatus(request):
|
||||
try:
|
||||
userID = request.session['userID']
|
||||
wm = WebsiteManager()
|
||||
return wm.checkIPStatus(userID, request.POST)
|
||||
except KeyError:
|
||||
return redirect(loadLoginPage)
|
||||
@@ -17,7 +17,8 @@ from plogical.acl import ACLManager
|
||||
import plogical.CyberCPLogFileWriter as logging
|
||||
from plogical.CyberCPLogFileWriter import CyberCPLogFileWriter
|
||||
from websiteFunctions.models import Websites, ChildDomains, GitLogs, wpplugins, WPSites, WPStaging, WPSitesBackup, \
|
||||
RemoteBackupConfig, RemoteBackupSchedule, RemoteBackupsites, DockerPackages, PackageAssignment, DockerSites
|
||||
RemoteBackupConfig, RemoteBackupSchedule, RemoteBackupsites, DockerPackages, PackageAssignment, DockerSites, \
|
||||
FTPQuota, BandwidthResetLog
|
||||
from plogical.virtualHostUtilities import virtualHostUtilities
|
||||
import subprocess
|
||||
import shlex
|
||||
@@ -8634,3 +8635,507 @@ StrictHostKeyChecking no
|
||||
logging.CyberCPLogFileWriter.writeToFile(f'Error fixing subdomain logs for {domain_name}: {str(e)}')
|
||||
return False
|
||||
|
||||
def enableFTPQuota(self, userID=None, data=None):
|
||||
"""
|
||||
Enable FTP quota system
|
||||
"""
|
||||
try:
|
||||
currentACL = ACLManager.loadedACL(userID)
|
||||
admin = Administrator.objects.get(pk=userID)
|
||||
|
||||
# Check if user has permission
|
||||
if not ACLManager.checkIfUserIsAdmin(currentACL):
|
||||
return ACLManager.loadErrorJson('status', 0)
|
||||
|
||||
# Backup existing configurations
|
||||
logging.CyberCPLogFileWriter.writeToFile("Backing up existing Pure-FTPd configurations...")
|
||||
|
||||
import shutil
|
||||
from datetime import datetime
|
||||
|
||||
timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
|
||||
|
||||
# Backup pure-ftpd.conf
|
||||
if os.path.exists('/etc/pure-ftpd/pure-ftpd.conf'):
|
||||
shutil.copy('/etc/pure-ftpd/pure-ftpd.conf', f'/etc/pure-ftpd/pure-ftpd.conf.backup.{timestamp}')
|
||||
|
||||
# Backup pureftpd-mysql.conf
|
||||
if os.path.exists('/etc/pure-ftpd/pureftpd-mysql.conf'):
|
||||
shutil.copy('/etc/pure-ftpd/pureftpd-mysql.conf', f'/etc/pure-ftpd/pureftpd-mysql.conf.backup.{timestamp}')
|
||||
|
||||
# Apply new configurations
|
||||
logging.CyberCPLogFileWriter.writeToFile("Applying FTP quota configurations...")
|
||||
|
||||
# Copy updated configurations
|
||||
if os.path.exists('/usr/local/CyberCP/install/pure-ftpd/pure-ftpd.conf'):
|
||||
shutil.copy('/usr/local/CyberCP/install/pure-ftpd/pure-ftpd.conf', '/etc/pure-ftpd/pure-ftpd.conf')
|
||||
|
||||
if os.path.exists('/usr/local/CyberCP/install/pure-ftpd/pureftpd-mysql.conf'):
|
||||
shutil.copy('/usr/local/CyberCP/install/pure-ftpd/pureftpd-mysql.conf', '/etc/pure-ftpd/pureftpd-mysql.conf')
|
||||
|
||||
# Restart Pure-FTPd
|
||||
logging.CyberCPLogFileWriter.writeToFile("Restarting Pure-FTPd service...")
|
||||
ProcessUtilities.executioner('systemctl restart pure-ftpd')
|
||||
|
||||
# Verify configuration
|
||||
if ProcessUtilities.executioner('systemctl is-active --quiet pure-ftpd'):
|
||||
logging.CyberCPLogFileWriter.writeToFile("FTP quota system enabled successfully")
|
||||
|
||||
data_ret = {
|
||||
'status': 1,
|
||||
'message': 'FTP quota system enabled successfully'
|
||||
}
|
||||
else:
|
||||
data_ret = {
|
||||
'status': 0,
|
||||
'message': 'Failed to restart Pure-FTPd service'
|
||||
}
|
||||
|
||||
json_data = json.dumps(data_ret)
|
||||
return HttpResponse(json_data)
|
||||
|
||||
except Exception as e:
|
||||
data_ret = {
|
||||
'status': 0,
|
||||
'message': f'Error enabling FTP quota: {str(e)}'
|
||||
}
|
||||
json_data = json.dumps(data_ret)
|
||||
return HttpResponse(json_data)
|
||||
|
||||
def getFTPQuotas(self, userID=None, data=None):
|
||||
"""
|
||||
Get FTP quota list
|
||||
"""
|
||||
try:
|
||||
currentACL = ACLManager.loadedACL(userID)
|
||||
admin = Administrator.objects.get(pk=userID)
|
||||
|
||||
# Check if user has permission
|
||||
if not ACLManager.checkIfUserIsAdmin(currentACL):
|
||||
return ACLManager.loadErrorJson('status', 0)
|
||||
|
||||
quotas = FTPQuota.objects.all().order_by('-created_at')
|
||||
|
||||
quota_list = []
|
||||
for quota in quotas:
|
||||
quota_list.append({
|
||||
'id': quota.id,
|
||||
'ftp_user': quota.ftp_user,
|
||||
'domain': quota.domain.domain if quota.domain else 'N/A',
|
||||
'quota_size_mb': quota.quota_size_mb,
|
||||
'quota_used_mb': quota.quota_used_mb,
|
||||
'quota_percentage': quota.get_quota_percentage(),
|
||||
'quota_files': quota.quota_files,
|
||||
'quota_files_used': quota.quota_files_used,
|
||||
'is_active': quota.is_active,
|
||||
'created_at': quota.created_at.strftime('%Y-%m-%d %H:%M:%S')
|
||||
})
|
||||
|
||||
data_ret = {
|
||||
'status': 1,
|
||||
'quotas': quota_list
|
||||
}
|
||||
|
||||
json_data = json.dumps(data_ret)
|
||||
return HttpResponse(json_data)
|
||||
|
||||
except Exception as e:
|
||||
data_ret = {
|
||||
'status': 0,
|
||||
'message': f'Error getting FTP quotas: {str(e)}'
|
||||
}
|
||||
json_data = json.dumps(data_ret)
|
||||
return HttpResponse(json_data)
|
||||
|
||||
def updateFTPQuota(self, userID=None, data=None):
|
||||
"""
|
||||
Update FTP quota
|
||||
"""
|
||||
try:
|
||||
currentACL = ACLManager.loadedACL(userID)
|
||||
admin = Administrator.objects.get(pk=userID)
|
||||
|
||||
# Check if user has permission
|
||||
if not ACLManager.checkIfUserIsAdmin(currentACL):
|
||||
return ACLManager.loadErrorJson('status', 0)
|
||||
|
||||
quota_id = data.get('quota_id')
|
||||
quota_size_mb = int(data.get('quota_size_mb', 0))
|
||||
quota_files = int(data.get('quota_files', 0))
|
||||
|
||||
try:
|
||||
quota = FTPQuota.objects.get(id=quota_id)
|
||||
quota.quota_size_mb = quota_size_mb
|
||||
quota.quota_files = quota_files
|
||||
quota.save()
|
||||
|
||||
data_ret = {
|
||||
'status': 1,
|
||||
'message': 'FTP quota updated successfully'
|
||||
}
|
||||
except FTPQuota.DoesNotExist:
|
||||
data_ret = {
|
||||
'status': 0,
|
||||
'message': 'FTP quota not found'
|
||||
}
|
||||
|
||||
json_data = json.dumps(data_ret)
|
||||
return HttpResponse(json_data)
|
||||
|
||||
except Exception as e:
|
||||
data_ret = {
|
||||
'status': 0,
|
||||
'message': f'Error updating FTP quota: {str(e)}'
|
||||
}
|
||||
json_data = json.dumps(data_ret)
|
||||
return HttpResponse(json_data)
|
||||
|
||||
def resetBandwidth(self, userID=None, data=None):
|
||||
"""
|
||||
Reset bandwidth usage
|
||||
"""
|
||||
try:
|
||||
currentACL = ACLManager.loadedACL(userID)
|
||||
admin = Administrator.objects.get(pk=userID)
|
||||
|
||||
# Check if user has permission
|
||||
if not ACLManager.checkIfUserIsAdmin(currentACL):
|
||||
return ACLManager.loadErrorJson('status', 0)
|
||||
|
||||
reset_type = data.get('reset_type', 'manual')
|
||||
domain_name = data.get('domain', None)
|
||||
|
||||
# Import bandwidth reset functionality
|
||||
from plogical.bandwidthReset import BandwidthReset
|
||||
|
||||
if domain_name:
|
||||
# Reset individual domain
|
||||
try:
|
||||
website = Websites.objects.get(domain=domain_name)
|
||||
BandwidthReset.resetDomainBandwidth(domain_name)
|
||||
|
||||
# Log the reset
|
||||
BandwidthResetLog.objects.create(
|
||||
reset_type=reset_type,
|
||||
domain=website,
|
||||
reset_by=admin,
|
||||
domains_affected=1,
|
||||
notes=f"Reset bandwidth for domain {domain_name}"
|
||||
)
|
||||
|
||||
data_ret = {
|
||||
'status': 1,
|
||||
'message': f'Bandwidth reset for {domain_name} completed successfully'
|
||||
}
|
||||
except Websites.DoesNotExist:
|
||||
data_ret = {
|
||||
'status': 0,
|
||||
'message': 'Domain not found'
|
||||
}
|
||||
else:
|
||||
# Reset all domains
|
||||
reset_count, total_reset_mb = BandwidthReset.resetWebsiteBandwidth()
|
||||
|
||||
# Log the reset
|
||||
BandwidthResetLog.objects.create(
|
||||
reset_type=reset_type,
|
||||
reset_by=admin,
|
||||
domains_affected=reset_count,
|
||||
bandwidth_reset_mb=total_reset_mb,
|
||||
notes="Reset bandwidth for all domains"
|
||||
)
|
||||
|
||||
data_ret = {
|
||||
'status': 1,
|
||||
'message': f'Bandwidth reset completed successfully. {reset_count} domains affected.'
|
||||
}
|
||||
|
||||
json_data = json.dumps(data_ret)
|
||||
return HttpResponse(json_data)
|
||||
|
||||
except Exception as e:
|
||||
data_ret = {
|
||||
'status': 0,
|
||||
'message': f'Error resetting bandwidth: {str(e)}'
|
||||
}
|
||||
json_data = json.dumps(data_ret)
|
||||
return HttpResponse(json_data)
|
||||
|
||||
def getBandwidthResetLogs(self, userID=None, data=None):
|
||||
"""
|
||||
Get bandwidth reset logs
|
||||
"""
|
||||
try:
|
||||
currentACL = ACLManager.loadedACL(userID)
|
||||
admin = Administrator.objects.get(pk=userID)
|
||||
|
||||
# Check if user has permission
|
||||
if not ACLManager.checkIfUserIsAdmin(currentACL):
|
||||
return ACLManager.loadErrorJson('status', 0)
|
||||
|
||||
logs = BandwidthResetLog.objects.all().order_by('-reset_at')[:50] # Last 50 entries
|
||||
|
||||
log_list = []
|
||||
for log in logs:
|
||||
log_list.append({
|
||||
'id': log.id,
|
||||
'reset_type': log.get_reset_type_display(),
|
||||
'domain': log.domain.domain if log.domain else 'All Domains',
|
||||
'reset_by': log.reset_by.userName,
|
||||
'reset_at': log.reset_at.strftime('%Y-%m-%d %H:%M:%S'),
|
||||
'domains_affected': log.domains_affected,
|
||||
'bandwidth_reset_mb': log.bandwidth_reset_mb,
|
||||
'notes': log.notes or ''
|
||||
})
|
||||
|
||||
data_ret = {
|
||||
'status': 1,
|
||||
'logs': log_list
|
||||
}
|
||||
|
||||
json_data = json.dumps(data_ret)
|
||||
return HttpResponse(json_data)
|
||||
|
||||
except Exception as e:
|
||||
data_ret = {
|
||||
'status': 0,
|
||||
'message': f'Error getting bandwidth reset logs: {str(e)}'
|
||||
}
|
||||
json_data = json.dumps(data_ret)
|
||||
return HttpResponse(json_data)
|
||||
|
||||
def scheduleBandwidthReset(self, userID=None, data=None):
|
||||
"""
|
||||
Schedule automatic bandwidth reset
|
||||
"""
|
||||
try:
|
||||
currentACL = ACLManager.loadedACL(userID)
|
||||
admin = Administrator.objects.get(pk=userID)
|
||||
|
||||
# Check if user has permission
|
||||
if not ACLManager.checkIfUserIsAdmin(currentACL):
|
||||
return ACLManager.loadErrorJson('status', 0)
|
||||
|
||||
schedule_type = data.get('schedule_type', 'monthly') # monthly, weekly, daily
|
||||
day_of_month = int(data.get('day_of_month', 1)) # 1-31 for monthly
|
||||
hour = int(data.get('hour', 2)) # 0-23
|
||||
minute = int(data.get('minute', 0)) # 0-59
|
||||
|
||||
# Create cron job for bandwidth reset
|
||||
from plogical.cronUtil import CronUtil
|
||||
|
||||
if schedule_type == 'monthly':
|
||||
cron_expression = f"{minute} {hour} {day_of_month} * *"
|
||||
job_name = "cyberpanel_bandwidth_reset_monthly"
|
||||
elif schedule_type == 'weekly':
|
||||
cron_expression = f"{minute} {hour} * * 0" # Sunday
|
||||
job_name = "cyberpanel_bandwidth_reset_weekly"
|
||||
else: # daily
|
||||
cron_expression = f"{minute} {hour} * * *"
|
||||
job_name = "cyberpanel_bandwidth_reset_daily"
|
||||
|
||||
# Create the cron job
|
||||
command = f"/usr/local/CyberCP/bin/python /usr/local/CyberCP/plogical/bandwidthReset.py --reset-all"
|
||||
|
||||
# Remove existing bandwidth reset cron jobs
|
||||
CronUtil.removeCronByCommand(command)
|
||||
|
||||
# Add new cron job
|
||||
CronUtil.addCronByCommand(command, cron_expression, job_name)
|
||||
|
||||
data_ret = {
|
||||
'status': 1,
|
||||
'message': f'Bandwidth reset scheduled for {schedule_type} at {hour:02d}:{minute:02d}'
|
||||
}
|
||||
|
||||
json_data = json.dumps(data_ret)
|
||||
return HttpResponse(json_data)
|
||||
|
||||
except Exception as e:
|
||||
data_ret = {
|
||||
'status': 0,
|
||||
'message': f'Error scheduling bandwidth reset: {str(e)}'
|
||||
}
|
||||
json_data = json.dumps(data_ret)
|
||||
return HttpResponse(json_data)
|
||||
|
||||
def blockIPAddress(self, userID=None, data=None):
|
||||
"""
|
||||
Block an IP address
|
||||
"""
|
||||
try:
|
||||
currentACL = ACLManager.loadedACL(userID)
|
||||
admin = Administrator.objects.get(pk=userID)
|
||||
|
||||
# Check if user has permission
|
||||
if not ACLManager.checkIfUserIsAdmin(currentACL):
|
||||
return ACLManager.loadErrorJson('status', 0)
|
||||
|
||||
ip_address = data.get('ip_address')
|
||||
reason = data.get('reason', 'Manual block via CyberPanel')
|
||||
|
||||
if not ip_address:
|
||||
data_ret = {
|
||||
'status': 0,
|
||||
'message': 'IP address is required'
|
||||
}
|
||||
json_data = json.dumps(data_ret)
|
||||
return HttpResponse(json_data)
|
||||
|
||||
# Import firewall utilities
|
||||
from plogical.firewallUtilities import FirewallUtilities
|
||||
|
||||
# Block the IP
|
||||
success, message = FirewallUtilities.blockIP(ip_address, reason)
|
||||
|
||||
if success:
|
||||
data_ret = {
|
||||
'status': 1,
|
||||
'message': message
|
||||
}
|
||||
else:
|
||||
data_ret = {
|
||||
'status': 0,
|
||||
'message': message
|
||||
}
|
||||
|
||||
json_data = json.dumps(data_ret)
|
||||
return HttpResponse(json_data)
|
||||
|
||||
except Exception as e:
|
||||
data_ret = {
|
||||
'status': 0,
|
||||
'message': f'Error blocking IP: {str(e)}'
|
||||
}
|
||||
json_data = json.dumps(data_ret)
|
||||
return HttpResponse(json_data)
|
||||
|
||||
def unblockIPAddress(self, userID=None, data=None):
|
||||
"""
|
||||
Unblock an IP address
|
||||
"""
|
||||
try:
|
||||
currentACL = ACLManager.loadedACL(userID)
|
||||
admin = Administrator.objects.get(pk=userID)
|
||||
|
||||
# Check if user has permission
|
||||
if not ACLManager.checkIfUserIsAdmin(currentACL):
|
||||
return ACLManager.loadErrorJson('status', 0)
|
||||
|
||||
ip_address = data.get('ip_address')
|
||||
|
||||
if not ip_address:
|
||||
data_ret = {
|
||||
'status': 0,
|
||||
'message': 'IP address is required'
|
||||
}
|
||||
json_data = json.dumps(data_ret)
|
||||
return HttpResponse(json_data)
|
||||
|
||||
# Import firewall utilities
|
||||
from plogical.firewallUtilities import FirewallUtilities
|
||||
|
||||
# Unblock the IP
|
||||
success, message = FirewallUtilities.unblockIP(ip_address)
|
||||
|
||||
if success:
|
||||
data_ret = {
|
||||
'status': 1,
|
||||
'message': message
|
||||
}
|
||||
else:
|
||||
data_ret = {
|
||||
'status': 0,
|
||||
'message': message
|
||||
}
|
||||
|
||||
json_data = json.dumps(data_ret)
|
||||
return HttpResponse(json_data)
|
||||
|
||||
except Exception as e:
|
||||
data_ret = {
|
||||
'status': 0,
|
||||
'message': f'Error unblocking IP: {str(e)}'
|
||||
}
|
||||
json_data = json.dumps(data_ret)
|
||||
return HttpResponse(json_data)
|
||||
|
||||
def getBlockedIPs(self, userID=None, data=None):
|
||||
"""
|
||||
Get list of blocked IP addresses
|
||||
"""
|
||||
try:
|
||||
currentACL = ACLManager.loadedACL(userID)
|
||||
admin = Administrator.objects.get(pk=userID)
|
||||
|
||||
# Check if user has permission
|
||||
if not ACLManager.checkIfUserIsAdmin(currentACL):
|
||||
return ACLManager.loadErrorJson('status', 0)
|
||||
|
||||
# Import firewall utilities
|
||||
from plogical.firewallUtilities import FirewallUtilities
|
||||
|
||||
# Get blocked IPs
|
||||
blocked_ips = FirewallUtilities.getBlockedIPs()
|
||||
|
||||
data_ret = {
|
||||
'status': 1,
|
||||
'blocked_ips': blocked_ips
|
||||
}
|
||||
|
||||
json_data = json.dumps(data_ret)
|
||||
return HttpResponse(json_data)
|
||||
|
||||
except Exception as e:
|
||||
data_ret = {
|
||||
'status': 0,
|
||||
'message': f'Error getting blocked IPs: {str(e)}'
|
||||
}
|
||||
json_data = json.dumps(data_ret)
|
||||
return HttpResponse(json_data)
|
||||
|
||||
def checkIPStatus(self, userID=None, data=None):
|
||||
"""
|
||||
Check if an IP is blocked
|
||||
"""
|
||||
try:
|
||||
currentACL = ACLManager.loadedACL(userID)
|
||||
admin = Administrator.objects.get(pk=userID)
|
||||
|
||||
# Check if user has permission
|
||||
if not ACLManager.checkIfUserIsAdmin(currentACL):
|
||||
return ACLManager.loadErrorJson('status', 0)
|
||||
|
||||
ip_address = data.get('ip_address')
|
||||
|
||||
if not ip_address:
|
||||
data_ret = {
|
||||
'status': 0,
|
||||
'message': 'IP address is required'
|
||||
}
|
||||
json_data = json.dumps(data_ret)
|
||||
return HttpResponse(json_data)
|
||||
|
||||
# Import firewall utilities
|
||||
from plogical.firewallUtilities import FirewallUtilities
|
||||
|
||||
# Check if IP is blocked
|
||||
is_blocked = FirewallUtilities.isIPBlocked(ip_address)
|
||||
|
||||
data_ret = {
|
||||
'status': 1,
|
||||
'ip_address': ip_address,
|
||||
'is_blocked': is_blocked
|
||||
}
|
||||
|
||||
json_data = json.dumps(data_ret)
|
||||
return HttpResponse(json_data)
|
||||
|
||||
except Exception as e:
|
||||
data_ret = {
|
||||
'status': 0,
|
||||
'message': f'Error checking IP status: {str(e)}'
|
||||
}
|
||||
json_data = json.dumps(data_ret)
|
||||
return HttpResponse(json_data)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user