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:
Master3395
2025-09-23 21:09:38 +02:00
parent 11991c0f80
commit 2c57ad595e
23 changed files with 2278 additions and 903 deletions

View File

@@ -1,162 +0,0 @@
name: Advanced Repository Sync
on:
push:
branches: [ main, stable, v2.5.5-dev, v2.4.0-dev ]
schedule:
# Run every 4 hours to keep repositories in sync
- cron: '0 */4 * * *'
workflow_dispatch:
jobs:
advanced-sync:
runs-on: ubuntu-latest
timeout-minutes: 45
steps:
- name: Checkout source repository
uses: actions/checkout@v4
with:
fetch-depth: 0
token: ${{ secrets.GITHUB_TOKEN }}
- name: Setup Git configuration
run: |
git config --global user.name "CyberPanel Sync Bot"
git config --global user.email "support@cyberpanel.net"
git config --global init.defaultBranch main
git config --global pull.rebase false
git config --global push.default simple
- name: Add Gitee remote
run: |
git remote add gitee https://gitee.com/${{ secrets.GITEE_USER }}/cyberpanel.git
- name: Fetch all remotes
run: |
git fetch origin --all --prune
git fetch gitee --all --prune || echo "Failed to fetch from Gitee, continuing..."
- name: Sync specific branches
run: |
# Function to sync a branch
sync_branch() {
local branch=$1
echo "=== Syncing branch: $branch ==="
# Check if branch exists locally
if git show-ref --verify --quiet refs/heads/$branch; then
echo "Branch $branch exists locally, checking out"
git checkout $branch
else
echo "Branch $branch doesn't exist locally, creating from origin"
git checkout -b $branch origin/$branch
fi
# Ensure we're on the latest from origin
git fetch origin $branch
git reset --hard origin/$branch
# Check if branch exists on Gitee
if git show-ref --verify --quiet refs/remotes/gitee/$branch; then
echo "Branch $branch exists on Gitee, checking for conflicts"
# Fetch latest from Gitee
git fetch gitee $branch
# Check if there are differences
if ! git diff --quiet HEAD gitee/$branch; then
echo "Differences found between local and Gitee $branch"
# Create a backup branch
git branch backup-$branch-$(date +%s) || true
# Try to merge Gitee changes
if git merge gitee/$branch --no-edit --no-ff; then
echo "Successfully merged Gitee changes for $branch"
else
echo "Merge conflict detected for $branch, resolving automatically"
# Reset to our version (GitHub is source of truth)
git reset --hard HEAD
git clean -fd
# Log the conflict for review
echo "Conflict resolved by keeping GitHub version for $branch" >> sync-conflicts.log
fi
else
echo "No differences found for $branch"
fi
else
echo "Branch $branch doesn't exist on Gitee, will be created"
fi
# Push to Gitee
echo "Pushing $branch to Gitee..."
if git push gitee $branch --force-with-lease; then
echo "Successfully pushed $branch to Gitee"
else
echo "Force-with-lease failed, trying force push for $branch"
git push gitee $branch --force
fi
echo "=== Completed syncing branch: $branch ==="
}
# Sync main branches
sync_branch "main"
sync_branch "stable"
sync_branch "v2.5.5-dev"
sync_branch "v2.4.0-dev"
# Also sync any other branches that exist
for branch in $(git branch -r | grep -v HEAD | sed 's/origin\///' | grep -E '^(v[0-9]+\.[0-9]+\.[0-9]+|dev|beta|alpha)'); do
if [[ "$branch" != "main" && "$branch" != "stable" && "$branch" != "v2.5.5-dev" && "$branch" != "v2.4.0-dev" ]]; then
sync_branch "$branch"
fi
done
- name: Sync all tags
run: |
echo "=== Syncing tags ==="
# Get all tags from origin
git fetch origin --tags
# Push all tags to Gitee
if git push gitee --tags --force; then
echo "Successfully pushed all tags to Gitee"
else
echo "Failed to push some tags, continuing..."
fi
- name: Verify sync
run: |
echo "=== Verifying sync ==="
# Check if main branches exist on Gitee
for branch in main stable v2.5.5-dev v2.4.0-dev; do
if git show-ref --verify --quiet refs/remotes/gitee/$branch; then
echo "✓ Branch $branch exists on Gitee"
else
echo "✗ Branch $branch missing on Gitee"
fi
done
# Show recent commits
echo "Recent commits on main:"
git log --oneline -5 origin/main || true
- name: Clean up
run: |
git remote remove gitee || true
rm -f sync-conflicts.log || true
- name: Upload sync logs
if: always()
uses: actions/upload-artifact@v4
with:
name: sync-logs
path: |
sync-conflicts.log
retention-days: 7

View File

@@ -1,120 +0,0 @@
name: Repository Sync
on:
push:
branches: [ main, stable, v2.5.5-dev, v2.4.0-dev ]
schedule:
# Run every 6 hours to keep repositories in sync
- cron: '0 */6 * * *'
workflow_dispatch:
jobs:
repo-sync:
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- name: Checkout source repository
uses: actions/checkout@v4
with:
fetch-depth: 0 # Fetch full history
token: ${{ secrets.GITHUB_TOKEN }}
- name: Configure Git
run: |
git config --global user.name "CyberPanel Bot"
git config --global user.email "support@cyberpanel.net"
git config --global init.defaultBranch main
- name: Add Gitee remote
run: |
git remote add gitee https://gitee.com/${{ secrets.GITEE_USER }}/cyberpanel.git
- name: Fetch from Gitee
run: |
git fetch gitee --all --prune || true
- name: Sync branches to Gitee
run: |
# Get current branch
CURRENT_BRANCH=$(git rev-parse --abbrev-ref HEAD)
echo "Current branch: $CURRENT_BRANCH"
# List of branches to sync
BRANCHES=("main" "stable" "v2.5.5-dev" "v2.4.0-dev")
for branch in "${BRANCHES[@]}"; do
echo "Processing branch: $branch"
# Check if branch exists locally
if git show-ref --verify --quiet refs/heads/$branch; then
echo "Branch $branch exists locally"
git checkout $branch
else
echo "Branch $branch doesn't exist locally, checking out from origin"
git checkout -b $branch origin/$branch || continue
fi
# Fetch latest changes from origin
git fetch origin $branch
git reset --hard origin/$branch
# Check if branch exists on Gitee
if git show-ref --verify --quiet refs/remotes/gitee/$branch; then
echo "Branch $branch exists on Gitee, attempting to sync"
# Try to merge or rebase with Gitee changes
git fetch gitee $branch || true
# Check if there are conflicts
if git show-ref --verify --quiet refs/remotes/gitee/$branch; then
# Try to merge Gitee changes
if ! git merge gitee/$branch --no-edit; then
echo "Merge conflict detected, resolving automatically"
# Reset to our version (GitHub is source of truth)
git reset --hard HEAD
git clean -fd
fi
fi
else
echo "Branch $branch doesn't exist on Gitee, will be created"
fi
# Push to Gitee with force to resolve conflicts
echo "Pushing $branch to Gitee..."
git push gitee $branch --force-with-lease || git push gitee $branch --force
done
- name: Sync tags to Gitee
run: |
# Push all tags to Gitee
git push gitee --tags --force || true
- name: Clean up
run: |
git remote remove gitee || true
# Alternative approach using hub-mirror-action with proper configuration
mirror-sync:
runs-on: ubuntu-latest
timeout-minutes: 30
if: false # Disabled by default, enable if needed
steps:
- name: Mirror repository to Gitee
uses: Yikun/hub-mirror-action@master
with:
src: github/usmannasir
dst: gitee/${{ secrets.GITEE_USER }}
dst_key: ${{ secrets.GITEE_PRIVATE_KEY }}
dst_token: ${{ secrets.GITEE_TOKEN }}
account_type: user
clone_style: https
cache_path: /tmp/hub-mirror-cache
force_update: true
debug: true
timeout: 30m
api_timeout: 60
lfs: false
mappings: |
cyberpanel cyberpanel

View File

@@ -1,34 +1,70 @@
name: sync2gitee name: Sync to Gitee
on: on:
push: push:
branches: branches: [ main, stable, v2.5.5-dev, v2.4.0-dev ]
- '**' schedule:
pull_request: # Run every 6 hours to keep repositories in sync
branches: - cron: '0 */6 * * *'
- '**' workflow_dispatch:
create:
branches:
- '**'
delete:
branches:
- '**'
jobs: jobs:
repo-sync: sync-to-gitee:
env:
dst_key: ${{ secrets.GITEE_PRIVATE_KEY }}
dst_token: ${{ secrets.GITEE_TOKEN }}
gitee_user: ${{ secrets.GITEE_USER }}
runs-on: ubuntu-latest runs-on: ubuntu-latest
timeout-minutes: 30
steps: steps:
- uses: actions/checkout@v2 - name: Checkout repository
with: uses: actions/checkout@v4
persist-credentials: false with:
- name: sync github -> gitee fetch-depth: 0
uses: Yikun/hub-mirror-action@master token: ${{ secrets.GITHUB_TOKEN }}
if: env.dst_key && env.dst_token && env.gitee_user
with: - name: Configure Git
src: 'github/${{ github.repository_owner }}' run: |
dst: 'gitee/${{ secrets.GITEE_USER }}' git config --global user.name "CyberPanel Bot"
dst_key: ${{ secrets.GITEE_PRIVATE_KEY }} git config --global user.email "support@cyberpanel.net"
dst_token: ${{ secrets.GITEE_TOKEN }} git config --global init.defaultBranch main
static_list: ${{ github.event.repository.name }}
- name: Add Gitee remote
run: |
git remote add gitee https://gitee.com/${{ secrets.GITEE_USER }}/cyberpanel.git
- name: Fetch from Gitee
run: |
git fetch gitee --all --prune || echo "Failed to fetch from Gitee, continuing..."
- name: Sync branches to Gitee
run: |
# List of branches to sync
BRANCHES=("main" "stable" "v2.5.5-dev" "v2.4.0-dev")
for branch in "${BRANCHES[@]}"; do
echo "Processing branch: $branch"
# Check if branch exists locally
if git show-ref --verify --quiet refs/heads/$branch; then
echo "Branch $branch exists locally"
git checkout $branch
else
echo "Branch $branch doesn't exist locally, creating from origin"
git checkout -b $branch origin/$branch || continue
fi
# Ensure we're on the latest from origin
git fetch origin $branch
git reset --hard origin/$branch
# Push to Gitee with force to resolve conflicts
echo "Pushing $branch to Gitee..."
git push gitee $branch --force || echo "Failed to push $branch"
done
- name: Sync tags to Gitee
run: |
# Push all tags to Gitee
git push gitee --tags --force || echo "Failed to push some tags"
- name: Clean up
run: |
git remote remove gitee || true

View File

@@ -1435,6 +1435,11 @@
<span>Reset FTP Configurations</span> <span>Reset FTP Configurations</span>
</a> </a>
{% endif %} {% endif %}
{% if admin %}
<a href="#" class="menu-item" onclick="loadFTPQuotaManagement(); return false;">
<span>FTP Quota Management</span>
</a>
{% endif %}
</div> </div>
<a href="#" class="menu-item" onclick="toggleSubmenu('backup-submenu', this); return false;"> <a href="#" class="menu-item" onclick="toggleSubmenu('backup-submenu', this); return false;">
@@ -1711,6 +1716,9 @@
<a href="{% url 'aiScannerHome' %}" class="menu-item"> <a href="{% url 'aiScannerHome' %}" class="menu-item">
<span>AI Scanner</span> <span>AI Scanner</span>
</a> </a>
<a href="#" class="menu-item" onclick="loadSecurityManagement(); return false;">
<span>Security Management</span>
</a>
</div> </div>
<a href="#" class="menu-item" onclick="toggleSubmenu('mail-settings-submenu', this); return false;"> <a href="#" class="menu-item" onclick="toggleSubmenu('mail-settings-submenu', this); return false;">
@@ -1764,6 +1772,11 @@
<a href="{% url 'managePureFtpd' %}" class="menu-item"> <a href="{% url 'managePureFtpd' %}" class="menu-item">
<span>Manage FTP</span> <span>Manage FTP</span>
</a> </a>
{% if admin %}
<a href="#" class="menu-item" onclick="loadBandwidthManagement(); return false;">
<span>Bandwidth Management</span>
</a>
{% endif %}
</div> </div>
<a href="#" class="menu-item" onclick="toggleSubmenu('plugins-submenu', this); return false;"> <a href="#" class="menu-item" onclick="toggleSubmenu('plugins-submenu', this); return false;">
@@ -2123,6 +2136,38 @@
})(); })();
</script> </script>
<!-- FTP Quota and Bandwidth Management Functions -->
<script>
function loadFTPQuotaManagement() {
// Load FTP quota management interface
$.get('{% url "getFTPQuotas" %}', function(data) {
if (data.status === 1) {
// Create modal or redirect to dedicated page
window.open('/websiteFunctions/ftpQuotaManagement.html', '_blank');
} else {
alert('Error loading FTP quota management: ' + data.message);
}
});
}
function loadBandwidthManagement() {
// Load bandwidth management interface
$.get('{% url "getBandwidthResetLogs" %}', function(data) {
if (data.status === 1) {
// Create modal or redirect to dedicated page
window.open('/websiteFunctions/bandwidthManagement.html', '_blank');
} else {
alert('Error loading bandwidth management: ' + data.message);
}
});
}
function loadSecurityManagement() {
// Load security management interface
window.open('/websiteFunctions/securityManagement.html', '_blank');
}
</script>
{% block footer_scripts %}{% endblock %} {% block footer_scripts %}{% endblock %}
</body> </body>
</html> </html>

70
cli/repositorySync.py Normal file
View File

@@ -0,0 +1,70 @@
#!/usr/local/CyberCP/bin/python
import os
import sys
import argparse
# Add CyberPanel to Python path
sys.path.append('/usr/local/CyberCP')
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "CyberCP.settings")
import django
django.setup()
from repositorySync.repositorySyncManager import RepositorySyncManager
from plogical.upgrade import Upgrade
def main():
parser = argparse.ArgumentParser(description='CyberPanel Repository Sync Tool')
parser.add_argument('--force', action='store_true', help='Force sync even if conflicts exist')
parser.add_argument('--status', action='store_true', help='Show sync status')
parser.add_argument('--sync', action='store_true', help='Perform repository sync')
args = parser.parse_args()
if args.status:
# Show sync status
try:
status = Upgrade.getRepositorySyncStatus()
print("Repository Sync Status:")
print(f"Last Sync: {status.get('last_sync', 'Never')}")
print(f"Branches Synced: {status.get('branches_synced', [])}")
print(f"Tags Synced: {status.get('tags_synced', False)}")
if 'errors' in status:
print(f"Errors: {status['errors']}")
except Exception as e:
print(f"Error getting status: {str(e)}")
sys.exit(1)
elif args.sync:
# Perform sync
try:
print("Starting repository synchronization...")
success, message = Upgrade.syncRepositoryToGitee(force_sync=args.force)
if success:
print(f"{message}")
sys.exit(0)
else:
print(f"{message}")
sys.exit(1)
except Exception as e:
print(f"Error during sync: {str(e)}")
sys.exit(1)
else:
# Default action - perform sync
try:
print("Starting repository synchronization...")
success, message = Upgrade.syncRepositoryToGitee(force_sync=args.force)
if success:
print(f"{message}")
sys.exit(0)
else:
print(f"{message}")
sys.exit(1)
except Exception as e:
print(f"Error during sync: {str(e)}")
sys.exit(1)
if __name__ == "__main__":
main()

View File

@@ -549,11 +549,11 @@ elif grep -q "AlmaLinux-9" /etc/os-release ; then
Server_OS="AlmaLinux" Server_OS="AlmaLinux"
elif grep -q "AlmaLinux-10" /etc/os-release ; then elif grep -q "AlmaLinux-10" /etc/os-release ; then
Server_OS="AlmaLinux" Server_OS="AlmaLinux"
elif grep -q -E "CloudLinux 7|CloudLinux 8" /etc/os-release ; then elif grep -q -E "CloudLinux 7|CloudLinux 8|CloudLinux 9" /etc/os-release ; then
Server_OS="CloudLinux" Server_OS="CloudLinux"
elif grep -q -E "Rocky Linux" /etc/os-release ; then elif grep -q -E "Rocky Linux" /etc/os-release ; then
Server_OS="RockyLinux" Server_OS="RockyLinux"
elif grep -q -E "Ubuntu 18.04|Ubuntu 20.04|Ubuntu 20.10|Ubuntu 22.04|Ubuntu 24.04" /etc/os-release ; then elif grep -q -E "Ubuntu 18.04|Ubuntu 20.04|Ubuntu 20.10|Ubuntu 22.04|Ubuntu 24.04|Ubuntu 24.04.3" /etc/os-release ; then
Server_OS="Ubuntu" Server_OS="Ubuntu"
elif grep -q -E "Debian GNU/Linux 11|Debian GNU/Linux 12|Debian GNU/Linux 13" /etc/os-release ; then elif grep -q -E "Debian GNU/Linux 11|Debian GNU/Linux 12|Debian GNU/Linux 13" /etc/os-release ; then
Server_OS="Debian" Server_OS="Debian"
@@ -561,8 +561,8 @@ elif grep -q -E "openEuler 20.03|openEuler 22.03" /etc/os-release ; then
Server_OS="openEuler" Server_OS="openEuler"
else else
echo -e "Unable to detect your system..." echo -e "Unable to detect your system..."
echo -e "\nCyberPanel is supported on x86_64 based Ubuntu 18.04, Ubuntu 20.04, Ubuntu 20.10, Ubuntu 22.04, Ubuntu 24.04, Ubuntu 24.04.3, Debian 11, Debian 12, Debian 13, CentOS 7, CentOS 8, CentOS 9, RHEL 8, RHEL 9, AlmaLinux 8, AlmaLinux 9, AlmaLinux 10, RockyLinux 8, CloudLinux 7, CloudLinux 8, openEuler 20.03, openEuler 22.03...\n" echo -e "\nCyberPanel is supported on x86_64 based Ubuntu 18.04, Ubuntu 20.04, Ubuntu 20.10, Ubuntu 22.04, Ubuntu 24.04, Ubuntu 24.04.3, Debian 11, Debian 12, Debian 13, CentOS 7, CentOS 8, CentOS 9, RHEL 8, RHEL 9, AlmaLinux 8, AlmaLinux 9, AlmaLinux 10, RockyLinux 8, RockyLinux 9, CloudLinux 7, CloudLinux 8, CloudLinux 9, openEuler 20.03, openEuler 22.03...\n"
Debug_Log2 "CyberPanel is supported on x86_64 based Ubuntu 18.04, Ubuntu 20.04, Ubuntu 20.10, Ubuntu 22.04, Ubuntu 24.04, Ubuntu 24.04.3, Debian 11, Debian 12, Debian 13, CentOS 7, CentOS 8, CentOS 9, RHEL 8, RHEL 9, AlmaLinux 8, AlmaLinux 9, AlmaLinux 10, RockyLinux 8, CloudLinux 7, CloudLinux 8, openEuler 20.03, openEuler 22.03... [404]" Debug_Log2 "CyberPanel is supported on x86_64 based Ubuntu 18.04, Ubuntu 20.04, Ubuntu 20.10, Ubuntu 22.04, Ubuntu 24.04, Ubuntu 24.04.3, Debian 11, Debian 12, Debian 13, CentOS 7, CentOS 8, CentOS 9, RHEL 8, RHEL 9, AlmaLinux 8, AlmaLinux 9, AlmaLinux 10, RockyLinux 8, RockyLinux 9, CloudLinux 7, CloudLinux 8, CloudLinux 9, openEuler 20.03, openEuler 22.03... [404]"
exit exit
fi fi

View File

@@ -151,7 +151,7 @@ elif grep -q -E "Rocky Linux" /etc/os-release ; then
Server_OS="RockyLinux" Server_OS="RockyLinux"
elif grep -q -E "AlmaLinux-8|AlmaLinux-9|AlmaLinux-10" /etc/os-release ; then elif grep -q -E "AlmaLinux-8|AlmaLinux-9|AlmaLinux-10" /etc/os-release ; then
Server_OS="AlmaLinux" Server_OS="AlmaLinux"
elif grep -q -E "Ubuntu 18.04|Ubuntu 20.04|Ubuntu 20.10|Ubuntu 22.04|Ubuntu 24.04" /etc/os-release ; then elif grep -q -E "Ubuntu 18.04|Ubuntu 20.04|Ubuntu 20.10|Ubuntu 22.04|Ubuntu 24.04|Ubuntu 24.04.3" /etc/os-release ; then
Server_OS="Ubuntu" Server_OS="Ubuntu"
elif grep -q -E "Debian GNU/Linux 11|Debian GNU/Linux 12|Debian GNU/Linux 13" /etc/os-release ; then elif grep -q -E "Debian GNU/Linux 11|Debian GNU/Linux 12|Debian GNU/Linux 13" /etc/os-release ; then
Server_OS="Ubuntu" Server_OS="Ubuntu"

View File

@@ -29,6 +29,11 @@ yum update curl wget ca-certificates -y 1> /dev/null
elif echo $OUTPUT | grep -q "CloudLinux 8" ; then elif echo $OUTPUT | grep -q "CloudLinux 8" ; then
echo "Checking and installing curl and wget" echo "Checking and installing curl and wget"
yum install curl wget -y 1> /dev/null yum install curl wget -y 1> /dev/null
yum update curl wget ca-certificates -y 1> /dev/null
SERVER_OS="CloudLinux"
elif echo $OUTPUT | grep -q "CloudLinux 9" ; then
echo "Checking and installing curl and wget"
yum install curl wget -y 1> /dev/null
yum update curl wget ca-certificates -y 1> /dev/null yum update curl wget ca-certificates -y 1> /dev/null
SERVER_OS="CloudLinux" SERVER_OS="CloudLinux"
elif echo $OUTPUT | grep -q "Ubuntu 20.04" ; then elif echo $OUTPUT | grep -q "Ubuntu 20.04" ; then
@@ -38,6 +43,9 @@ elif echo $OUTPUT | grep -q "Ubuntu 22.04" ; then
apt install -y -qq wget curl apt install -y -qq wget curl
SERVER_OS="Ubuntu" SERVER_OS="Ubuntu"
elif echo $OUTPUT | grep -q "Ubuntu 24.04" ; then elif echo $OUTPUT | grep -q "Ubuntu 24.04" ; then
apt install -y -qq wget curl
SERVER_OS="Ubuntu"
elif echo $OUTPUT | grep -q "Ubuntu 24.04.3" ; then
apt install -y -qq wget curl apt install -y -qq wget curl
SERVER_OS="Ubuntu" SERVER_OS="Ubuntu"
elif echo $OUTPUT | grep -q "Debian GNU/Linux 11" ; then elif echo $OUTPUT | grep -q "Debian GNU/Linux 11" ; then
@@ -88,13 +96,13 @@ else
echo -e "\nUnable to detect your OS...\n" echo -e "\nUnable to detect your OS...\n"
echo -e "\nCyberPanel is supported on:\n" echo -e "\nCyberPanel is supported on:\n"
echo -e "Ubuntu: 20.04, 22.04, 24.04.3\n" echo -e "Ubuntu: 20.04, 22.04, 24.04, 24.04.3\n"
echo -e "Debian: 11, 12, 13\n" echo -e "Debian: 11, 12, 13\n"
echo -e "AlmaLinux: 8, 9, 10\n" echo -e "AlmaLinux: 8, 9, 10\n"
echo -e "RockyLinux: 8, 9\n" echo -e "RockyLinux: 8, 9\n"
echo -e "RHEL: 8, 9\n" echo -e "RHEL: 8, 9\n"
echo -e "CentOS: 7, 9, Stream 9\n" echo -e "CentOS: 7, 9, Stream 9\n"
echo -e "CloudLinux: 8\n" echo -e "CloudLinux: 8, 9\n"
echo -e "openEuler: 20.03, 22.03\n" echo -e "openEuler: 20.03, 22.03\n"
exit 1 exit 1
fi fi

View File

@@ -75,10 +75,62 @@ class BandwidthReset:
BandwidthReset.cleanupBandwidthMetadata() BandwidthReset.cleanupBandwidthMetadata()
logging.CyberCPLogFileWriter.writeToFile(f"Monthly bandwidth reset completed. Reset {reset_count} domains.") logging.CyberCPLogFileWriter.writeToFile(f"Monthly bandwidth reset completed. Reset {reset_count} domains.")
return True return reset_count, total_reset_mb
except Exception as e: except Exception as e:
logging.CyberCPLogFileWriter.writeToFile(f"Error in monthly bandwidth reset: {str(e)}") logging.CyberCPLogFileWriter.writeToFile(f"Error in monthly bandwidth reset: {str(e)}")
return 0, 0
@staticmethod
def resetDomainBandwidth(domain_name):
"""
Reset bandwidth for a specific domain
"""
try:
logging.CyberCPLogFileWriter.writeToFile(f"Resetting bandwidth for domain: {domain_name}")
# Reset main website
try:
website = Websites.objects.get(domain=domain_name)
config = json.loads(website.config)
config['bwInMB'] = 0
config['bwUsage'] = 0
website.config = json.dumps(config)
website.save()
logging.CyberCPLogFileWriter.writeToFile(f"Reset bandwidth for website: {domain_name}")
except Websites.DoesNotExist:
logging.CyberCPLogFileWriter.writeToFile(f"Website {domain_name} not found")
return False
# Reset child domains for this website
child_domains = ChildDomains.objects.filter(master=website)
for child in child_domains:
try:
config = json.loads(child.config)
config['bwInMB'] = 0
config['bwUsage'] = 0
child.config = json.dumps(config)
child.save()
logging.CyberCPLogFileWriter.writeToFile(f"Reset bandwidth for child domain: {child.domain}")
except Exception as e:
logging.CyberCPLogFileWriter.writeToFile(f"Error resetting child domain {child.domain}: {str(e)}")
# Clean up bandwidth metadata file for this domain
metadata_file = f"/home/cyberpanel/{domain_name}.bwmeta"
if os.path.exists(metadata_file):
try:
with open(metadata_file, 'w') as f:
f.write("0\n0\n")
os.chmod(metadata_file, 0o600)
logging.CyberCPLogFileWriter.writeToFile(f"Reset metadata file: {metadata_file}")
except Exception as e:
logging.CyberCPLogFileWriter.writeToFile(f"Error resetting metadata file: {str(e)}")
logging.CyberCPLogFileWriter.writeToFile(f"Bandwidth reset completed for domain: {domain_name}")
return True
except Exception as e:
logging.CyberCPLogFileWriter.writeToFile(f"Error resetting bandwidth for domain {domain_name}: {str(e)}")
return False return False
@staticmethod @staticmethod

View File

@@ -103,6 +103,115 @@ class FirewallUtilities:
return 1 return 1
@staticmethod
def blockIP(ip_address, reason="Manual block"):
"""
Block an IP address using firewalld
"""
try:
# Create a drop rule for the IP address
ruleFamily = 'rule family="ipv4"'
sourceAddress = 'source address="' + ip_address + '"'
action = 'drop'
command = "firewall-cmd --permanent --zone=public --add-rich-rule='" + ruleFamily + " " + sourceAddress + " " + action + "'"
logging.CyberCPLogFileWriter.writeToFile(f"Blocking IP address: {ip_address} - Reason: {reason}")
result = ProcessUtilities.executioner(command)
if result == 0:
logging.CyberCPLogFileWriter.writeToFile(f"Successfully blocked IP: {ip_address}")
# Reload firewall to apply changes
ProcessUtilities.executioner('firewall-cmd --reload')
# Log the block in a dedicated file
block_log_path = "/usr/local/lscp/logs/blocked_ips.log"
with open(block_log_path, "a") as f:
from datetime import datetime
f.write(f"{datetime.now().strftime('%Y-%m-%d %H:%M:%S')} - {ip_address} - {reason}\n")
return True, f"IP {ip_address} blocked successfully"
else:
logging.CyberCPLogFileWriter.writeToFile(f"Failed to block IP: {ip_address}")
return False, f"Failed to block IP: {ip_address}"
except Exception as e:
logging.CyberCPLogFileWriter.writeToFile(f"Error blocking IP {ip_address}: {str(e)}")
return False, f"Error blocking IP: {str(e)}"
@staticmethod
def unblockIP(ip_address):
"""
Unblock an IP address by removing the drop rule
"""
try:
ruleFamily = 'rule family="ipv4"'
sourceAddress = 'source address="' + ip_address + '"'
action = 'drop'
command = "firewall-cmd --permanent --zone=public --remove-rich-rule='" + ruleFamily + " " + sourceAddress + " " + action + "'"
logging.CyberCPLogFileWriter.writeToFile(f"Unblocking IP address: {ip_address}")
result = ProcessUtilities.executioner(command)
if result == 0:
logging.CyberCPLogFileWriter.writeToFile(f"Successfully unblocked IP: {ip_address}")
# Reload firewall to apply changes
ProcessUtilities.executioner('firewall-cmd --reload')
return True, f"IP {ip_address} unblocked successfully"
else:
logging.CyberCPLogFileWriter.writeToFile(f"Failed to unblock IP: {ip_address}")
return False, f"Failed to unblock IP: {ip_address}"
except Exception as e:
logging.CyberCPLogFileWriter.writeToFile(f"Error unblocking IP {ip_address}: {str(e)}")
return False, f"Error unblocking IP: {str(e)}"
@staticmethod
def isIPBlocked(ip_address):
"""
Check if an IP address is currently blocked
"""
try:
command = "firewall-cmd --list-rich-rules | grep -i '" + ip_address + "'"
result = ProcessUtilities.normalExecutioner(command)
return result == 0
except Exception as e:
logging.CyberCPLogFileWriter.writeToFile(f"Error checking if IP {ip_address} is blocked: {str(e)}")
return False
@staticmethod
def getBlockedIPs():
"""
Get list of currently blocked IP addresses
"""
try:
command = "firewall-cmd --list-rich-rules | grep 'drop' | grep 'source address'"
result = ProcessUtilities.normalExecutioner(command)
if result == 0:
# Parse the output to extract IP addresses
import subprocess
try:
output = subprocess.check_output(command, shell=True, text=True)
blocked_ips = []
for line in output.split('\n'):
if 'source address=' in line:
# Extract IP from the line
import re
ip_match = re.search(r'source address="([^"]+)"', line)
if ip_match:
blocked_ips.append(ip_match.group(1))
return blocked_ips
except:
return []
return []
except Exception as e:
logging.CyberCPLogFileWriter.writeToFile(f"Error getting blocked IPs: {str(e)}")
return []
@staticmethod @staticmethod
def saveSSHConfigs(type, sshPort, rootLogin): def saveSSHConfigs(type, sshPort, rootLogin):
try: try:

44
scripts/block_ip.py Normal file
View File

@@ -0,0 +1,44 @@
#!/usr/local/CyberCP/bin/python
"""
Quick IP blocking script for CyberPanel
Usage: python block_ip.py <ip_address> [reason]
"""
import sys
import os
import django
# Add CyberPanel to Python path
sys.path.append('/usr/local/CyberCP')
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "CyberCP.settings")
try:
django.setup()
except:
pass
from plogical.firewallUtilities import FirewallUtilities
def main():
if len(sys.argv) < 2:
print("Usage: python block_ip.py <ip_address> [reason]")
print("Example: python block_ip.py 192.168.1.100 'Suspicious activity'")
sys.exit(1)
ip_address = sys.argv[1]
reason = sys.argv[2] if len(sys.argv) > 2 else "Manual block via CLI"
print(f"Blocking IP address: {ip_address}")
print(f"Reason: {reason}")
success, message = FirewallUtilities.blockIP(ip_address, reason)
if success:
print(f"{message}")
sys.exit(0)
else:
print(f"{message}")
sys.exit(1)
if __name__ == "__main__":
main()

View File

@@ -1,121 +0,0 @@
#!/bin/bash
# Enable FTP User Quota Feature
# This script applies the quota configuration and restarts Pure-FTPd
set -e
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
# Logging function
log_message() {
echo -e "[$(date +'%Y-%m-%d %H:%M:%S')] $1" | tee -a /var/log/cyberpanel_ftp_quota.log
}
log_message "${BLUE}Starting FTP Quota Feature Setup...${NC}"
# Check if running as root
if [ "$EUID" -ne 0 ]; then
log_message "${RED}Please run as root${NC}"
exit 1
fi
# Backup existing configurations
log_message "${YELLOW}Backing up existing Pure-FTPd configurations...${NC}"
if [ -f /etc/pure-ftpd/pure-ftpd.conf ]; then
cp /etc/pure-ftpd/pure-ftpd.conf /etc/pure-ftpd/pure-ftpd.conf.backup.$(date +%Y%m%d_%H%M%S)
log_message "${GREEN}Backed up pure-ftpd.conf${NC}"
fi
if [ -f /etc/pure-ftpd/pureftpd-mysql.conf ]; then
cp /etc/pure-ftpd/pureftpd-mysql.conf /etc/pure-ftpd/pureftpd-mysql.conf.backup.$(date +%Y%m%d_%H%M%S)
log_message "${GREEN}Backed up pureftpd-mysql.conf${NC}"
fi
# Apply new configurations
log_message "${YELLOW}Applying FTP quota configurations...${NC}"
# Copy the updated configurations
if [ -f /usr/local/CyberCP/install/pure-ftpd/pure-ftpd.conf ]; then
cp /usr/local/CyberCP/install/pure-ftpd/pure-ftpd.conf /etc/pure-ftpd/pure-ftpd.conf
log_message "${GREEN}Updated pure-ftpd.conf${NC}"
fi
if [ -f /usr/local/CyberCP/install/pure-ftpd/pureftpd-mysql.conf ]; then
cp /usr/local/CyberCP/install/pure-ftpd/pureftpd-mysql.conf /etc/pure-ftpd/pureftpd-mysql.conf
log_message "${GREEN}Updated pureftpd-mysql.conf${NC}"
fi
# Check if Pure-FTPd is running
if systemctl is-active --quiet pure-ftpd; then
log_message "${YELLOW}Restarting Pure-FTPd service...${NC}"
systemctl restart pure-ftpd
if systemctl is-active --quiet pure-ftpd; then
log_message "${GREEN}Pure-FTPd restarted successfully${NC}"
else
log_message "${RED}Failed to restart Pure-FTPd${NC}"
exit 1
fi
else
log_message "${YELLOW}Starting Pure-FTPd service...${NC}"
systemctl start pure-ftpd
if systemctl is-active --quiet pure-ftpd; then
log_message "${GREEN}Pure-FTPd started successfully${NC}"
else
log_message "${RED}Failed to start Pure-FTPd${NC}"
exit 1
fi
fi
# Verify quota enforcement is working
log_message "${YELLOW}Verifying quota enforcement...${NC}"
# Check if quota queries are in the configuration
if grep -q "MYSQLGetQTAFS" /etc/pure-ftpd/pureftpd-mysql.conf; then
log_message "${GREEN}Quota queries found in MySQL configuration${NC}"
else
log_message "${RED}Quota queries not found in MySQL configuration${NC}"
exit 1
fi
if grep -q "Quota.*yes" /etc/pure-ftpd/pure-ftpd.conf; then
log_message "${GREEN}Quota enforcement enabled in Pure-FTPd configuration${NC}"
else
log_message "${RED}Quota enforcement not enabled in Pure-FTPd configuration${NC}"
exit 1
fi
# Test database connection
log_message "${YELLOW}Testing database connection...${NC}"
# Get database credentials from configuration
MYSQL_USER=$(grep "MYSQLUser" /etc/pure-ftpd/pureftpd-mysql.conf | cut -d' ' -f2)
MYSQL_PASS=$(grep "MYSQLPassword" /etc/pure-ftpd/pureftpd-mysql.conf | cut -d' ' -f2)
MYSQL_DB=$(grep "MYSQLDatabase" /etc/pure-ftpd/pureftpd-mysql.conf | cut -d' ' -f2)
if mysql -u"$MYSQL_USER" -p"$MYSQL_PASS" -e "USE $MYSQL_DB; SELECT COUNT(*) FROM users;" >/dev/null 2>&1; then
log_message "${GREEN}Database connection successful${NC}"
else
log_message "${RED}Database connection failed${NC}"
exit 1
fi
log_message "${GREEN}FTP User Quota feature has been successfully enabled!${NC}"
log_message "${BLUE}Features enabled:${NC}"
log_message " - Individual FTP user quotas"
log_message " - Custom quota sizes per user"
log_message " - Package default quota fallback"
log_message " - Real-time quota enforcement by Pure-FTPd"
log_message " - Web interface for quota management"
log_message "${YELLOW}Note: Existing FTP users will need to have their quotas updated through the web interface to take effect.${NC}"
exit 0

View File

@@ -1,164 +0,0 @@
#!/bin/bash
# CyberPanel Repository Sync Fix Script
# This script resolves the current sync conflict between GitHub and Gitee
set -e
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
# Configuration
GITHUB_REPO="https://github.com/usmannasir/cyberpanel.git"
GITEE_REPO="https://gitee.com/qtwrk/cyberpanel.git"
TEMP_DIR="/tmp/cyberpanel-sync-$(date +%s)"
# Function to print colored output
print_status() {
echo -e "${BLUE}[INFO]${NC} $1"
}
print_success() {
echo -e "${GREEN}[SUCCESS]${NC} $1"
}
print_warning() {
echo -e "${YELLOW}[WARNING]${NC} $1"
}
print_error() {
echo -e "${RED}[ERROR]${NC} $1"
}
# Function to check if command exists
command_exists() {
command -v "$1" >/dev/null 2>&1
}
# Check prerequisites
check_prerequisites() {
print_status "Checking prerequisites..."
if ! command_exists git; then
print_error "Git is not installed. Please install git first."
exit 1
fi
if ! command_exists ssh; then
print_warning "SSH is not available. HTTPS will be used for authentication."
fi
print_success "Prerequisites check completed"
}
# Setup Git configuration
setup_git_config() {
print_status "Setting up Git configuration..."
git config --global user.name "CyberPanel Sync Bot"
git config --global user.email "support@cyberpanel.net"
git config --global init.defaultBranch main
git config --global pull.rebase false
git config --global push.default simple
print_success "Git configuration completed"
}
# Clone and sync repositories
sync_repositories() {
print_status "Starting repository synchronization..."
# Create temporary directory
mkdir -p "$TEMP_DIR"
cd "$TEMP_DIR"
# Clone GitHub repository
print_status "Cloning GitHub repository..."
git clone --mirror "$GITHUB_REPO" cyberpanel.git
cd cyberpanel.git
# Add Gitee remote
print_status "Adding Gitee remote..."
git remote add gitee "$GITEE_REPO"
# Fetch from Gitee to check current state
print_status "Fetching from Gitee..."
if git fetch gitee --all --prune; then
print_success "Successfully fetched from Gitee"
else
print_warning "Failed to fetch from Gitee, continuing with force push"
fi
# Push all branches and tags to Gitee
print_status "Pushing all branches to Gitee..."
if git push gitee --all --force; then
print_success "Successfully pushed all branches to Gitee"
else
print_error "Failed to push branches to Gitee"
exit 1
fi
print_status "Pushing all tags to Gitee..."
if git push gitee --tags --force; then
print_success "Successfully pushed all tags to Gitee"
else
print_warning "Failed to push some tags to Gitee"
fi
print_success "Repository synchronization completed"
}
# Verify sync
verify_sync() {
print_status "Verifying synchronization..."
cd "$TEMP_DIR/cyberpanel.git"
# Check if main branches exist
for branch in main stable v2.5.5-dev v2.4.0-dev; do
if git show-ref --verify --quiet "refs/remotes/gitee/$branch"; then
print_success "Branch $branch exists on Gitee"
else
print_error "Branch $branch missing on Gitee"
fi
done
# Show recent commits
print_status "Recent commits on main branch:"
git log --oneline -5 refs/remotes/origin/main || true
}
# Cleanup
cleanup() {
print_status "Cleaning up temporary files..."
if [ -d "$TEMP_DIR" ]; then
rm -rf "$TEMP_DIR"
print_success "Cleanup completed"
fi
}
# Main execution
main() {
print_status "Starting CyberPanel Repository Sync Fix"
print_status "========================================"
# Set up error handling
trap cleanup EXIT
# Execute steps
check_prerequisites
setup_git_config
sync_repositories
verify_sync
print_success "Repository sync fix completed successfully!"
print_status "The GitHub Actions workflow should now work properly."
}
# Run main function
main "$@"

View File

@@ -1,51 +0,0 @@
@echo off
REM CyberPanel Bandwidth Reset Script for Windows
REM This script resets bandwidth usage for all domains in CyberPanel
echo CyberPanel Bandwidth Reset Script
echo =================================
echo.
REM Check if running as administrator
net session >nul 2>&1
if %errorLevel% == 0 (
echo Running with administrator privileges...
) else (
echo Please run as administrator
pause
exit /b 1
)
REM Check if CyberPanel is installed
if not exist "C:\Program Files\CyberPanel\bin\python.exe" (
echo CyberPanel not found. Please ensure CyberPanel is installed.
pause
exit /b 1
)
echo Resetting bandwidth for all domains...
echo.
REM Run the bandwidth reset script
"C:\Program Files\CyberPanel\bin\python.exe" "C:\Program Files\CyberPanel\plogical\bandwidthReset.py" --reset-all
if %errorLevel% == 0 (
echo.
echo Bandwidth reset completed successfully!
echo.
echo To verify the reset, you can:
echo 1. Check the CyberPanel logs
echo 2. Check individual domain bandwidth in CyberPanel web interface
echo 3. Check bandwidth metadata files
) else (
echo.
echo Bandwidth reset failed. Please check the logs for details.
pause
exit /b 1
)
echo.
echo Note: This script only resets the displayed bandwidth values.
echo The actual bandwidth calculation will resume from the current access logs.
echo For a complete reset, you may also need to clear access logs if desired.
pause

View File

@@ -1,46 +0,0 @@
#!/bin/bash
# CyberPanel Bandwidth Reset Script
# This script resets bandwidth usage for all domains in CyberPanel
echo "CyberPanel Bandwidth Reset Script"
echo "================================="
echo ""
# Check if running as root
if [ "$EUID" -ne 0 ]; then
echo "Please run as root (use sudo)"
exit 1
fi
# Check if CyberPanel is installed
if [ ! -f "/usr/local/CyberCP/bin/python" ]; then
echo "CyberPanel not found. Please ensure CyberPanel is installed."
exit 1
fi
echo "Resetting bandwidth for all domains..."
echo ""
# Run the bandwidth reset script
/usr/local/CyberCP/bin/python /usr/local/CyberCP/plogical/bandwidthReset.py --reset-all
if [ $? -eq 0 ]; then
echo ""
echo "Bandwidth reset completed successfully!"
echo ""
echo "To verify the reset, you can:"
echo "1. Check the CyberPanel logs: /usr/local/lscp/logs/error.log"
echo "2. Check individual domain bandwidth in CyberPanel web interface"
echo "3. Check bandwidth metadata files: ls -la /home/cyberpanel/*.bwmeta"
else
echo ""
echo "Bandwidth reset failed. Please check the logs for details."
echo "Log file: /usr/local/lscp/logs/error.log"
exit 1
fi
echo ""
echo "Note: This script only resets the displayed bandwidth values."
echo "The actual bandwidth calculation will resume from the current access logs."
echo "For a complete reset, you may also need to clear access logs if desired."

View File

@@ -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'],
},
),
]

View File

@@ -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 class BandwidthResetLog(models.Model):
from packages.models import Package """
from loginSystem.models import Administrator Bandwidth Reset Log
from datetime import datetime """
RESET_TYPES = [
# Create your models here. ('manual', 'Manual Reset'),
('scheduled', 'Scheduled Reset'),
class Websites(models.Model): ('individual', 'Individual Domain Reset'),
admin = models.ForeignKey(Administrator, on_delete=models.PROTECT) ]
package = models.ForeignKey(Package, on_delete=models.PROTECT)
domain = models.CharField(max_length=255,unique=True) reset_type = models.CharField(max_length=20, choices=RESET_TYPES)
adminEmail = models.CharField(max_length=255) domain = models.ForeignKey(Websites, on_delete=models.CASCADE, null=True, blank=True)
phpSelection = models.CharField(max_length=10) reset_by = models.ForeignKey('loginSystem.Administrator', on_delete=models.CASCADE)
ssl = models.IntegerField() reset_at = models.DateTimeField(auto_now_add=True)
state = models.IntegerField(default=1) domains_affected = models.IntegerField(default=0)
externalApp = models.CharField(max_length=30, default=None) bandwidth_reset_mb = models.BigIntegerField(default=0)
config = models.TextField(default='') notes = models.TextField(blank=True, null=True)
BackupLock = models.IntegerField(default=0)
class Meta:
db_table = 'bandwidth_reset_logs'
class ChildDomains(models.Model): verbose_name = 'Bandwidth Reset Log'
master = models.ForeignKey(Websites,on_delete=models.CASCADE) verbose_name_plural = 'Bandwidth Reset Logs'
domain = models.CharField(max_length=50, unique=True) ordering = ['-reset_at']
path = models.CharField(max_length=200,default=None)
ssl = models.IntegerField() def __str__(self):
phpSelection = models.CharField(max_length=10,default=None) return f"{self.reset_type} - {self.domain or 'All Domains'} - {self.reset_at}"
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)

View File

@@ -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>&times;</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 %}

View File

@@ -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>&times;</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>&times;</span>
</button>
</div>
`;
$('.card-body').prepend(notification);
setTimeout(() => {
$('.alert').fadeOut();
}, 5000);
}
// Load quotas on page load
$(document).ready(function() {
refreshQuotas();
});
</script>
{% endblock %}

View File

@@ -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>&times;</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 %}

View File

@@ -211,5 +211,21 @@ urlpatterns = [
path('fixSubdomainLogs', views.fixSubdomainLogs, name='fixSubdomainLogs'), path('fixSubdomainLogs', views.fixSubdomainLogs, name='fixSubdomainLogs'),
path('fixSubdomainLogsAction', views.fixSubdomainLogsAction, name='fixSubdomainLogsAction'), 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'),
] ]

View File

@@ -2232,5 +2232,88 @@ def fixSubdomainLogsAction(request):
return coreResult 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: except KeyError:
return redirect(loadLoginPage) return redirect(loadLoginPage)

View File

@@ -17,7 +17,8 @@ from plogical.acl import ACLManager
import plogical.CyberCPLogFileWriter as logging import plogical.CyberCPLogFileWriter as logging
from plogical.CyberCPLogFileWriter import CyberCPLogFileWriter from plogical.CyberCPLogFileWriter import CyberCPLogFileWriter
from websiteFunctions.models import Websites, ChildDomains, GitLogs, wpplugins, WPSites, WPStaging, WPSitesBackup, \ 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 from plogical.virtualHostUtilities import virtualHostUtilities
import subprocess import subprocess
import shlex import shlex
@@ -8634,3 +8635,507 @@ StrictHostKeyChecking no
logging.CyberCPLogFileWriter.writeToFile(f'Error fixing subdomain logs for {domain_name}: {str(e)}') logging.CyberCPLogFileWriter.writeToFile(f'Error fixing subdomain logs for {domain_name}: {str(e)}')
return False 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)