Add modify firewall rule and improve export/import functionality

- Add modifyRule function to allow editing firewall rules without deletion
- Add modify button and modal for firewall rules (similar to banned IPs)
- Fix exportRules function to properly handle file downloads with blob response
- Improve importRules function with better error handling and PNotify notifications
- Add exportBannedIPs and importBannedIPs functionality
- Add export/import buttons for banned IPs
- Improve error handling and user feedback for all export/import operations
- Add proper validation and duplicate detection for imports
This commit is contained in:
master3395
2026-01-28 23:24:16 +01:00
parent 4cf263d205
commit e303548112
5 changed files with 1421 additions and 64 deletions

View File

@@ -11,6 +11,7 @@ sys.path.append('/usr/local/CyberCP')
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "CyberCP.settings")
django.setup()
import json
import tempfile
from plogical.acl import ACLManager
import plogical.CyberCPLogFileWriter as logging
from plogical.virtualHostUtilities import virtualHostUtilities
@@ -139,6 +140,107 @@ class FirewallManager:
final_json = json.dumps(final_dic)
return HttpResponse(final_json)
def modifyRule(self, userID=None, data=None):
"""
Modify an existing firewall rule
"""
try:
currentACL = ACLManager.loadedACL(userID)
if currentACL['admin'] == 1:
pass
else:
return ACLManager.loadErrorJson('modify_status', 0)
ruleID = data.get('id')
newRuleName = data.get('ruleName', '').strip()
newRuleProtocol = data.get('ruleProtocol', '').strip()
newRulePort = data.get('rulePort', '').strip()
newRuleIP = data.get('ruleIP', '').strip()
# Validate inputs
if not newRuleName:
final_dic = {'status': 0, 'modify_status': 0, 'error_message': 'Rule name is required'}
final_json = json.dumps(final_dic)
return HttpResponse(final_json)
if not newRuleProtocol or newRuleProtocol not in ['tcp', 'udp']:
final_dic = {'status': 0, 'modify_status': 0, 'error_message': 'Valid protocol (tcp/udp) is required'}
final_json = json.dumps(final_dic)
return HttpResponse(final_json)
if not newRulePort:
final_dic = {'status': 0, 'modify_status': 0, 'error_message': 'Port is required'}
final_json = json.dumps(final_dic)
return HttpResponse(final_json)
if not newRuleIP:
final_dic = {'status': 0, 'modify_status': 0, 'error_message': 'IP address is required'}
final_json = json.dumps(final_dic)
return HttpResponse(final_json)
# Get existing rule
try:
existingRule = FirewallRules.objects.get(id=ruleID)
except FirewallRules.DoesNotExist:
final_dic = {'status': 0, 'modify_status': 0, 'error_message': 'Rule not found'}
final_json = json.dumps(final_dic)
return HttpResponse(final_json)
# Check if anything changed
changed = False
if (existingRule.name != newRuleName or
existingRule.proto != newRuleProtocol or
existingRule.port != newRulePort or
existingRule.ipAddress != newRuleIP):
changed = True
if changed:
# Check if new rule already exists (different ID)
existingDuplicate = FirewallRules.objects.filter(
name=newRuleName,
proto=newRuleProtocol,
port=newRulePort,
ipAddress=newRuleIP
).exclude(id=ruleID).first()
if existingDuplicate:
final_dic = {'status': 0, 'modify_status': 0, 'error_message': 'A rule with these settings already exists'}
final_json = json.dumps(final_dic)
return HttpResponse(final_json)
# Delete old firewall rule
try:
FirewallUtilities.deleteRule(existingRule.proto, existingRule.port, existingRule.ipAddress)
logging.CyberCPLogFileWriter.writeToFile(f'Removed old firewall rule: {existingRule.proto}/{existingRule.port}/{existingRule.ipAddress}')
except Exception as e:
logging.CyberCPLogFileWriter.writeToFile(f'Warning: Could not remove old firewall rule: {str(e)}')
# Add new firewall rule
try:
FirewallUtilities.addRule(newRuleProtocol, newRulePort, newRuleIP)
logging.CyberCPLogFileWriter.writeToFile(f'Added new firewall rule: {newRuleProtocol}/{newRulePort}/{newRuleIP}')
except Exception as e:
final_dic = {'status': 0, 'modify_status': 0, 'error_message': f'Failed to add firewall rule: {str(e)}'}
final_json = json.dumps(final_dic)
return HttpResponse(final_json)
# Update database record
existingRule.name = newRuleName
existingRule.proto = newRuleProtocol
existingRule.port = newRulePort
existingRule.ipAddress = newRuleIP
existingRule.save()
final_dic = {'status': 1, 'modify_status': 1, 'error_message': "None"}
final_json = json.dumps(final_dic)
return HttpResponse(final_json)
except BaseException as msg:
final_dic = {'status': 0, 'modify_status': 0, 'error_message': str(msg)}
final_json = json.dumps(final_dic)
return HttpResponse(final_json)
def deleteRule(self, userID = None, data = None):
try:
@@ -1829,24 +1931,72 @@ class FirewallManager:
except:
banned_ips = []
# Filter out expired bans
# Filter out expired bans and format data consistently
current_time = time.time()
active_banned_ips = []
for banned_ip in banned_ips:
if banned_ip.get('expires') == 'Never' or banned_ip.get('expires', 0) > current_time:
banned_ip['active'] = True
if banned_ip.get('expires') != 'Never':
banned_ip['expires'] = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(banned_ip['expires']))
else:
banned_ip['expires'] = 'Never'
banned_ip['banned_on'] = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(banned_ip.get('banned_on', current_time)))
else:
banned_ip['active'] = False
banned_ip['expires'] = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(banned_ip.get('expires', current_time)))
banned_ip['banned_on'] = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(banned_ip.get('banned_on', current_time)))
# Get original values
expires_val = banned_ip.get('expires')
banned_on_val = banned_ip.get('banned_on', current_time)
active_banned_ips.append(banned_ip)
# Convert banned_on to timestamp if it's a string
if isinstance(banned_on_val, str):
try:
# Try parsing formatted date string
banned_on_val = time.mktime(time.strptime(banned_on_val, '%Y-%m-%d %H:%M:%S'))
except:
banned_on_val = current_time
elif not isinstance(banned_on_val, (int, float)):
banned_on_val = current_time
# Check if expired
is_expired = False
if expires_val == 'Never' or expires_val is None:
expires_timestamp = None
expires_display = 'Never'
elif isinstance(expires_val, str):
if expires_val == 'Never':
expires_timestamp = None
expires_display = 'Never'
else:
try:
expires_timestamp = time.mktime(time.strptime(expires_val, '%Y-%m-%d %H:%M:%S'))
expires_display = expires_val
except:
expires_timestamp = None
expires_display = 'Never'
else:
expires_timestamp = expires_val
if expires_val > current_time:
expires_display = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(expires_val))
else:
expires_display = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(expires_val))
is_expired = True
# Determine if active
if expires_timestamp is None:
is_active = True
else:
is_active = expires_timestamp > current_time and not is_expired
# Format banned_on for display
banned_on_display = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(banned_on_val))
# Create formatted entry
formatted_ip = {
'id': banned_ip.get('id'),
'ip': banned_ip.get('ip'),
'reason': banned_ip.get('reason', ''),
'duration': banned_ip.get('duration', 'never'),
'banned_on': banned_on_display, # String for display
'banned_on_timestamp': banned_on_val * 1000, # Milliseconds for AngularJS date filter
'expires': expires_display, # String for display
'expires_timestamp': expires_timestamp * 1000 if expires_timestamp else None, # Milliseconds for AngularJS
'active': is_active
}
active_banned_ips.append(formatted_ip)
final_dic = {'status': 1, 'bannedIPs': active_banned_ips}
final_json = json.dumps(final_dic)
@@ -1927,12 +2077,23 @@ class FirewallManager:
}
banned_ips.append(new_banned_ip)
# Ensure directory exists
os.makedirs(os.path.dirname(banned_ips_file), exist_ok=True)
# Save to file
with open(banned_ips_file, 'w') as f:
# Write to temp file in /tmp (web server user has write permissions here)
temp_dir = tempfile.gettempdir()
temp_file = os.path.join(temp_dir, f'banned_ips_{int(time.time())}.json')
with open(temp_file, 'w') as f:
json.dump(banned_ips, f, indent=2)
# Ensure /etc/cyberpanel directory exists with proper permissions
command = f'mkdir -p {os.path.dirname(banned_ips_file)} && chmod 755 {os.path.dirname(banned_ips_file)}'
ProcessUtilities.executioner(command, None, True)
# Move temp file to final location and set permissions using ProcessUtilities
command = f'mv {temp_file} {banned_ips_file}'
ProcessUtilities.executioner(command, None, True)
command = f'chmod 644 {banned_ips_file} && chown root:root {banned_ips_file}'
ProcessUtilities.executioner(command, None, True)
# Apply firewall rule to block the IP
try:
@@ -1992,9 +2153,23 @@ class FirewallManager:
final_json = json.dumps(final_dic)
return HttpResponse(final_json)
# Save updated banned IPs
with open(banned_ips_file, 'w') as f:
# Write to temp file in /tmp (web server user has write permissions here)
temp_dir = tempfile.gettempdir()
temp_file = os.path.join(temp_dir, f'banned_ips_{int(time.time())}.json')
with open(temp_file, 'w') as f:
json.dump(banned_ips, f, indent=2)
# Ensure /etc/cyberpanel directory exists with proper permissions
command = f'mkdir -p {os.path.dirname(banned_ips_file)} && chmod 755 {os.path.dirname(banned_ips_file)}'
ProcessUtilities.executioner(command, None, True)
# Move temp file to final location and set permissions using ProcessUtilities
command = f'mv {temp_file} {banned_ips_file}'
ProcessUtilities.executioner(command, None, True)
command = f'chmod 644 {banned_ips_file} && chown root:root {banned_ips_file}'
ProcessUtilities.executioner(command, None, True)
# Remove iptables rule
try:
@@ -2019,6 +2194,153 @@ class FirewallManager:
final_json = json.dumps(final_dic)
return HttpResponse(final_json)
def modifyBannedIP(self, userID=None, data=None):
"""
Modify a banned IP address (update reason and/or expiration)
"""
try:
admin = Administrator.objects.get(pk=userID)
if admin.acl.adminStatus != 1:
return ACLManager.loadError()
banned_ip_id = data.get('id')
new_ip = data.get('ip', '').strip()
reason = data.get('reason', '').strip()
duration = data.get('duration', 'never')
if not new_ip:
final_dic = {'status': 0, 'error_message': 'IP address is required'}
final_json = json.dumps(final_dic)
return HttpResponse(final_json)
# Validate IP address format
import ipaddress
try:
ipaddress.ip_address(new_ip.split('/')[0]) # Handle CIDR notation
except ValueError:
final_dic = {'status': 0, 'error_message': 'Invalid IP address format'}
final_json = json.dumps(final_dic)
return HttpResponse(final_json)
if not reason:
final_dic = {'status': 0, 'error_message': 'Reason is required'}
final_json = json.dumps(final_dic)
return HttpResponse(final_json)
# Load existing banned IPs
banned_ips_file = '/etc/cyberpanel/banned_ips.json'
banned_ips = []
if os.path.exists(banned_ips_file):
try:
with open(banned_ips_file, 'r') as f:
banned_ips = json.load(f)
except:
banned_ips = []
# Find and update the banned IP
old_ip = None
found = False
for banned_ip in banned_ips:
if banned_ip.get('id') == banned_ip_id:
found = True
old_ip = banned_ip['ip']
# Check if new IP is already banned (and not the same record)
ip_changed = (new_ip != old_ip)
if ip_changed:
for other_banned_ip in banned_ips:
if other_banned_ip.get('id') != banned_ip_id and other_banned_ip.get('ip') == new_ip and other_banned_ip.get('active', True):
final_dic = {'status': 0, 'error_message': f'IP address {new_ip} is already banned'}
final_json = json.dumps(final_dic)
return HttpResponse(final_json)
# Update IP address if changed
if ip_changed:
# Remove old iptables rule
try:
if '/' in old_ip:
subprocess.run(['iptables', '-D', 'INPUT', '-s', old_ip, '-j', 'DROP'], check=False)
else:
subprocess.run(['iptables', '-D', 'INPUT', '-s', old_ip, '-j', 'DROP'], check=False)
logging.CyberCPLogFileWriter.writeToFile(f'Removed iptables rule for old IP {old_ip}')
except Exception as e:
logging.CyberCPLogFileWriter.writeToFile(f'Warning: Could not remove old iptables rule for {old_ip}: {str(e)}')
# Add new iptables rule
try:
if '/' in new_ip:
subprocess.run(['iptables', '-A', 'INPUT', '-s', new_ip, '-j', 'DROP'], check=True)
else:
subprocess.run(['iptables', '-A', 'INPUT', '-s', new_ip, '-j', 'DROP'], check=True)
logging.CyberCPLogFileWriter.writeToFile(f'Added iptables rule for new IP {new_ip}')
except subprocess.CalledProcessError as e:
final_dic = {'status': 0, 'error_message': f'Failed to add firewall rule for IP {new_ip}: {str(e)}'}
final_json = json.dumps(final_dic)
return HttpResponse(final_json)
banned_ip['ip'] = new_ip
# Update reason
banned_ip['reason'] = reason
# Update expiration if duration changed
if duration == 'never' or duration == 'permanent':
banned_ip['expires'] = 'Never'
banned_ip['duration'] = 'never'
else:
# Calculate new expiration time
current_time = time.time()
duration_map = {
'1h': 3600,
'6h': 21600,
'12h': 43200,
'24h': 86400,
'48h': 172800,
'7d': 604800,
'30d': 2592000
}
duration_seconds = duration_map.get(duration, 86400)
banned_ip['expires'] = current_time + duration_seconds
banned_ip['duration'] = duration
break
if not found:
final_dic = {'status': 0, 'error_message': 'Banned IP record not found'}
final_json = json.dumps(final_dic)
return HttpResponse(final_json)
# Write to temp file in /tmp (web server user has write permissions here)
temp_dir = tempfile.gettempdir()
temp_file = os.path.join(temp_dir, f'banned_ips_{int(time.time())}.json')
with open(temp_file, 'w') as f:
json.dump(banned_ips, f, indent=2)
# Ensure /etc/cyberpanel directory exists with proper permissions
command = f'mkdir -p {os.path.dirname(banned_ips_file)} && chmod 755 {os.path.dirname(banned_ips_file)}'
ProcessUtilities.executioner(command, None, True)
# Move temp file to final location and set permissions using ProcessUtilities
command = f'mv {temp_file} {banned_ips_file}'
ProcessUtilities.executioner(command, None, True)
command = f'chmod 644 {banned_ips_file} && chown root:root {banned_ips_file}'
ProcessUtilities.executioner(command, None, True)
ip_display = new_ip if new_ip != old_ip else old_ip
change_msg = f'IP changed from {old_ip} to {new_ip}' if new_ip != old_ip else f'IP unchanged ({old_ip})'
logging.CyberCPLogFileWriter.writeToFile(f'Modified banned IP record: {change_msg}, reason={reason}, duration={duration}')
final_dic = {'status': 1, 'message': f'Banned IP has been modified successfully'}
final_json = json.dumps(final_dic)
return HttpResponse(final_json)
except BaseException as msg:
final_dic = {'status': 0, 'error_message': str(msg)}
final_json = json.dumps(final_dic)
return HttpResponse(final_json)
def deleteBannedIP(self, userID=None, data=None):
"""
Permanently delete a banned IP record
@@ -2054,9 +2376,36 @@ class FirewallManager:
final_json = json.dumps(final_dic)
return HttpResponse(final_json)
# Save updated banned IPs
with open(banned_ips_file, 'w') as f:
# Write to temp file in /tmp (web server user has write permissions here)
temp_dir = tempfile.gettempdir()
temp_file = os.path.join(temp_dir, f'banned_ips_{int(time.time())}.json')
with open(temp_file, 'w') as f:
json.dump(updated_banned_ips, f, indent=2)
# Ensure /etc/cyberpanel directory exists with proper permissions
command = f'mkdir -p {os.path.dirname(banned_ips_file)} && chmod 755 {os.path.dirname(banned_ips_file)}'
ProcessUtilities.executioner(command, None, True)
# Move temp file to final location and set permissions using ProcessUtilities
command = f'mv {temp_file} {banned_ips_file}'
ProcessUtilities.executioner(command, None, True)
command = f'chmod 644 {banned_ips_file} && chown root:root {banned_ips_file}'
ProcessUtilities.executioner(command, None, True)
# Remove iptables rule to unban the IP
try:
# Remove iptables rule to unblock the IP
if '/' in ip_to_delete:
# CIDR notation
subprocess.run(['iptables', '-D', 'INPUT', '-s', ip_to_delete, '-j', 'DROP'], check=False)
else:
# Single IP
subprocess.run(['iptables', '-D', 'INPUT', '-s', ip_to_delete, '-j', 'DROP'], check=False)
logging.CyberCPLogFileWriter.writeToFile(f'Deleted and unbanned IP {ip_to_delete}')
except Exception as e:
logging.CyberCPLogFileWriter.writeToFile(f'Failed to remove iptables rule for {ip_to_delete}: {str(e)}')
logging.CyberCPLogFileWriter.writeToFile(f'Deleted banned IP record for {ip_to_delete}')
@@ -2227,6 +2576,253 @@ class FirewallManager:
final_json = json.dumps(final_dic)
return HttpResponse(final_json)
def exportBannedIPs(self, userID=None):
"""
Export all banned IPs to a JSON file
"""
try:
currentACL = ACLManager.loadedACL(userID)
if currentACL['admin'] == 1:
pass
else:
return ACLManager.loadErrorJson('exportStatus', 0)
# Load banned IPs from file
banned_ips_file = '/etc/cyberpanel/banned_ips.json'
banned_ips = []
if os.path.exists(banned_ips_file):
try:
with open(banned_ips_file, 'r') as f:
banned_ips = json.load(f)
except Exception as e:
logging.CyberCPLogFileWriter.writeToFile(f"Error reading banned IPs file: {str(e)}")
banned_ips = []
# Filter out expired bans for export (optional - you might want to include all)
current_time = time.time()
active_banned_ips = []
for banned_ip in banned_ips:
expires_val = banned_ip.get('expires')
expires_timestamp = banned_ip.get('expires_timestamp')
# Include if never expires or not yet expired
if expires_val == 'Never' or expires_timestamp is None:
active_banned_ips.append(banned_ip)
elif isinstance(expires_timestamp, (int, float)) and expires_timestamp > current_time:
active_banned_ips.append(banned_ip)
# Create export data with metadata
export_data = {
'version': '1.0',
'exported_at': time.strftime('%Y-%m-%d %H:%M:%S'),
'total_banned_ips': len(active_banned_ips),
'banned_ips': active_banned_ips
}
# Create JSON response with file download
json_content = json.dumps(export_data, indent=2)
logging.CyberCPLogFileWriter.writeToFile(f"Banned IPs exported successfully. Total IPs: {len(active_banned_ips)}")
# Return file as download
response = HttpResponse(json_content, content_type='application/json')
response['Content-Disposition'] = f'attachment; filename="banned_ips_export_{int(time.time())}.json"'
return response
except BaseException as msg:
final_dic = {'exportStatus': 0, 'error_message': str(msg)}
final_json = json.dumps(final_dic)
return HttpResponse(final_json)
def importBannedIPs(self, userID=None, data=None):
"""
Import banned IPs from a JSON file
"""
try:
currentACL = ACLManager.loadedACL(userID)
if currentACL['admin'] == 1:
pass
else:
return ACLManager.loadErrorJson('importStatus', 0)
# Handle file upload
if hasattr(self.request, 'FILES') and 'import_file' in self.request.FILES:
import_file = self.request.FILES['import_file']
# Read file content
import_data = json.loads(import_file.read().decode('utf-8'))
else:
# Fallback to file path method
import_file_path = data.get('import_file_path', '') if data else ''
if not import_file_path or not os.path.exists(import_file_path):
final_dic = {'importStatus': 0, 'error_message': 'Import file not found or invalid path'}
final_json = json.dumps(final_dic)
return HttpResponse(final_json)
# Read and parse the import file
with open(import_file_path, 'r') as f:
import_data = json.load(f)
# Validate the import data structure
if 'banned_ips' not in import_data:
final_dic = {'importStatus': 0, 'error_message': 'Invalid import file format. Missing banned_ips array.'}
final_json = json.dumps(final_dic)
return HttpResponse(final_json)
imported_count = 0
skipped_count = 0
error_count = 0
errors = []
# Load existing banned IPs
banned_ips_file = '/etc/cyberpanel/banned_ips.json'
existing_banned_ips = []
if os.path.exists(banned_ips_file):
try:
with open(banned_ips_file, 'r') as f:
existing_banned_ips = json.load(f)
except:
existing_banned_ips = []
# Create a set of existing IPs for quick lookup
existing_ips = {banned_ip.get('ip') for banned_ip in existing_banned_ips}
# Import IP address validation
import ipaddress
for banned_ip_data in import_data['banned_ips']:
try:
ip_address = banned_ip_data.get('ip', '').strip()
reason = banned_ip_data.get('reason', '').strip()
# Validate IP address
if not ip_address:
error_count += 1
errors.append(f"Invalid entry: Missing IP address")
continue
try:
ipaddress.ip_address(ip_address.split('/')[0]) # Handle CIDR notation
except ValueError:
error_count += 1
errors.append(f"IP '{ip_address}': Invalid IP address format")
continue
# Check if IP already exists
if ip_address in existing_ips:
skipped_count += 1
continue
# Validate reason
if not reason:
reason = 'Imported from backup'
# Get duration or calculate from expires
duration = banned_ip_data.get('duration', 'never')
expires = banned_ip_data.get('expires', 'Never')
expires_timestamp = banned_ip_data.get('expires_timestamp')
# Calculate expiration if needed
if expires == 'Never' or expires_timestamp is None:
expires_timestamp = None
expires_display = 'Never'
duration = 'never'
elif expires_timestamp and isinstance(expires_timestamp, (int, float)):
expires_display = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(expires_timestamp))
else:
expires_display = expires if isinstance(expires, str) else 'Never'
expires_timestamp = None
# Create banned IP entry
banned_on = banned_ip_data.get('banned_on', time.time())
if isinstance(banned_on, str):
try:
banned_on = time.mktime(time.strptime(banned_on, '%Y-%m-%d %H:%M:%S'))
except:
banned_on = time.time()
new_banned_ip = {
'id': banned_ip_data.get('id', randint(100000, 999999)),
'ip': ip_address,
'reason': reason,
'banned_on': banned_on,
'banned_on_timestamp': banned_on if isinstance(banned_on, (int, float)) else time.time(),
'expires': expires_display,
'expires_timestamp': expires_timestamp,
'duration': duration,
'active': True
}
# Add iptables rule
try:
if '/' in ip_address:
subprocess.run(['iptables', '-A', 'INPUT', '-s', ip_address, '-j', 'DROP'], check=True)
else:
subprocess.run(['iptables', '-A', 'INPUT', '-s', ip_address, '-j', 'DROP'], check=True)
logging.CyberCPLogFileWriter.writeToFile(f'Added iptables rule for imported IP {ip_address}')
except subprocess.CalledProcessError as e:
error_count += 1
errors.append(f"IP '{ip_address}': Failed to add firewall rule - {str(e)}")
continue
# Add to existing list
existing_banned_ips.append(new_banned_ip)
existing_ips.add(ip_address)
imported_count += 1
except Exception as e:
error_count += 1
errors.append(f"IP '{banned_ip_data.get('ip', 'Unknown')}': {str(e)}")
logging.CyberCPLogFileWriter.writeToFile(f"Error importing banned IP {banned_ip_data.get('ip', 'Unknown')}: {str(e)}")
# Save updated banned IPs
if imported_count > 0 or error_count > 0:
try:
# Create temp file
temp_file = f'/tmp/banned_ips_{randint(100000, 999999)}.json'
with open(temp_file, 'w') as f:
json.dump(existing_banned_ips, f, indent=2)
# Ensure directory exists
command = f'mkdir -p {os.path.dirname(banned_ips_file)} && chmod 755 {os.path.dirname(banned_ips_file)}'
ProcessUtilities.executioner(command)
# Move temp file to final location
command = f'mv {temp_file} {banned_ips_file}'
ProcessUtilities.executioner(command)
# Set permissions
command = f'chmod 644 {banned_ips_file} && chown root:root {banned_ips_file}'
ProcessUtilities.executioner(command)
except Exception as e:
logging.CyberCPLogFileWriter.writeToFile(f"Error saving imported banned IPs: {str(e)}")
final_dic = {'importStatus': 0, 'error_message': f'Failed to save imported IPs: {str(e)}'}
final_json = json.dumps(final_dic)
return HttpResponse(final_json)
logging.CyberCPLogFileWriter.writeToFile(f"Banned IPs import completed. Imported: {imported_count}, Skipped: {skipped_count}, Errors: {error_count}")
final_dic = {
'importStatus': 1,
'error_message': "None",
'imported_count': imported_count,
'skipped_count': skipped_count,
'error_count': error_count,
'errors': errors
}
final_json = json.dumps(final_dic)
return HttpResponse(final_json)
except BaseException as msg:
final_dic = {'importStatus': 0, 'error_message': str(msg)}
final_json = json.dumps(final_dic)
return HttpResponse(final_json)

View File

@@ -499,6 +499,281 @@ app.controller('firewallController', function ($scope, $http, $timeout, $window,
};
// Modify Firewall Rule Functions
$scope.handleModifyRuleClick = function(rule, event) {
if (event) {
event.preventDefault();
event.stopPropagation();
}
if (!rule) {
console.error('No rule provided');
if (typeof PNotify !== 'undefined') {
new PNotify({
title: 'Error',
text: 'No rule data provided',
type: 'error'
});
}
return false;
}
$scope.showModifyRuleModal(rule, event);
return false;
};
$scope.showModifyRuleModal = function(rule, event) {
console.log('=== showModifyRuleModal CALLED ===');
console.log('Rule:', rule);
if (!rule) {
console.error('No rule provided');
return false;
}
// Get modal element
var modalElement = document.getElementById('modifyRuleModal');
if (!modalElement) {
console.error('Modal element not found');
alert('Error: Modal element not found. Please refresh the page.');
return false;
}
// Set form values
var idField = document.getElementById('modifyRuleId');
var nameField = document.getElementById('modifyRuleName');
var protocolField = document.getElementById('modifyRuleProtocol');
var ipField = document.getElementById('modifyRuleIP');
var portField = document.getElementById('modifyRulePort');
if (idField) idField.value = rule.id || '';
if (nameField) nameField.value = rule.name || '';
if (protocolField) protocolField.value = rule.proto || 'tcp';
if (ipField) ipField.value = rule.ipAddress || '';
if (portField) portField.value = rule.port || '';
// Show modal using AngularJS $timeout
$timeout(function() {
// Clean up existing modals/backdrops
var existingBackdrops = document.querySelectorAll('.modal-backdrop');
existingBackdrops.forEach(function(b) { b.remove(); });
var existingModals = document.querySelectorAll('.modal.show');
existingModals.forEach(function(m) {
m.classList.remove('show');
});
document.body.classList.remove('modal-open');
// Move modal to body if needed
if (modalElement.parentElement !== document.body) {
document.body.appendChild(modalElement);
}
// Show modal
modalElement.classList.add('show', 'fade');
modalElement.style.cssText = 'display: flex !important; position: fixed !important; top: 0 !important; left: 0 !important; width: 100% !important; height: 100% !important; z-index: 99999 !important; opacity: 1 !important; visibility: visible !important; align-items: center !important; justify-content: center !important;';
modalElement.removeAttribute('aria-hidden');
modalElement.setAttribute('aria-hidden', 'false');
modalElement.setAttribute('aria-modal', 'true');
document.body.classList.add('modal-open');
document.body.style.overflow = 'hidden';
// Create backdrop
var backdrop = document.createElement('div');
backdrop.className = 'modal-backdrop fade show';
backdrop.style.cssText = 'position: fixed !important; top: 0 !important; left: 0 !important; width: 100% !important; height: 100% !important; z-index: 99998 !important; background-color: rgba(0, 0, 0, 0.5) !important;';
backdrop.id = 'modifyRuleModalBackdrop';
document.body.appendChild(backdrop);
// Handle backdrop click
backdrop.addEventListener('click', function(e) {
if (e.target === backdrop) {
$scope.closeModifyRuleModal();
}
});
// Try jQuery/Bootstrap modal if available
if (typeof $ !== 'undefined' && $.fn.modal) {
try {
var $modal = $('#modifyRuleModal');
if ($modal.length > 0) {
if ($modal.parent()[0] !== document.body) {
$modal.appendTo('body');
}
if (!$modal.data('bs.modal')) {
$modal.modal({show: false, backdrop: true, keyboard: true});
}
$modal.modal('show');
}
} catch (e) {
console.warn('jQuery modal failed, using direct display:', e);
}
}
}, 10);
};
$scope.closeModifyRuleModal = function() {
var modalElement = document.getElementById('modifyRuleModal');
if (modalElement) {
// Try jQuery/Bootstrap modal first
if (typeof $ !== 'undefined' && $.fn.modal) {
try {
$('#modifyRuleModal').modal('hide');
} catch (e) {
// Fall through to manual cleanup
}
}
// Manual cleanup
modalElement.classList.remove('show', 'fade');
modalElement.style.display = 'none';
modalElement.setAttribute('aria-hidden', 'true');
// Remove backdrop
var backdrops = document.querySelectorAll('.modal-backdrop');
backdrops.forEach(function(b) { b.remove(); });
document.body.classList.remove('modal-open');
document.body.style.overflow = '';
}
};
$scope.modifyRule = function() {
var ruleId = document.getElementById('modifyRuleId').value;
var ruleName = document.getElementById('modifyRuleName').value.trim();
var ruleProtocol = document.getElementById('modifyRuleProtocol').value;
var ruleIP = document.getElementById('modifyRuleIP').value.trim();
var rulePort = document.getElementById('modifyRulePort').value.trim();
// Validation
if (!ruleName) {
if (typeof PNotify !== 'undefined') {
new PNotify({
title: 'Validation Error',
text: 'Please enter a rule name',
type: 'error'
});
} else {
alert('Please enter a rule name');
}
return;
}
if (!ruleProtocol || (ruleProtocol !== 'tcp' && ruleProtocol !== 'udp')) {
if (typeof PNotify !== 'undefined') {
new PNotify({
title: 'Validation Error',
text: 'Please select a valid protocol (TCP or UDP)',
type: 'error'
});
} else {
alert('Please select a valid protocol');
}
return;
}
if (!ruleIP) {
if (typeof PNotify !== 'undefined') {
new PNotify({
title: 'Validation Error',
text: 'Please enter an IP address',
type: 'error'
});
} else {
alert('Please enter an IP address');
}
return;
}
if (!rulePort) {
if (typeof PNotify !== 'undefined') {
new PNotify({
title: 'Validation Error',
text: 'Please enter a port number',
type: 'error'
});
} else {
alert('Please enter a port number');
}
return;
}
$scope.rulesLoading = false;
$scope.actionFailed = true;
$scope.actionSuccess = true;
$scope.couldNotConnect = true;
var url = "/firewall/modifyRule";
var data = {
id: ruleId,
ruleName: ruleName,
ruleProtocol: ruleProtocol,
rulePort: rulePort,
ruleIP: ruleIP
};
var config = {
headers: {
'X-CSRFToken': getCookie('csrftoken')
}
};
$http.post(url, data, config).then(function(response) {
$scope.rulesLoading = true;
if (response.data && response.data.modify_status === 1) {
// Close modal
$scope.closeModifyRuleModal();
// Refresh rules list
populateCurrentRecords();
$scope.actionFailed = true;
$scope.actionSuccess = false;
$scope.canNotAddRule = true;
$scope.ruleAdded = false;
$scope.couldNotConnect = true;
if (typeof PNotify !== 'undefined') {
new PNotify({
title: 'Success!',
text: 'Firewall rule modified successfully',
type: 'success'
});
}
} else {
$scope.actionFailed = false;
$scope.actionSuccess = true;
$scope.errorMessage = (response.data && response.data.error_message) || 'Failed to modify firewall rule';
if (typeof PNotify !== 'undefined') {
new PNotify({
title: 'Error!',
text: (response.data && response.data.error_message) || 'Failed to modify firewall rule',
type: 'error'
});
}
}
}, function(error) {
$scope.rulesLoading = true;
$scope.couldNotConnect = false;
if (typeof PNotify !== 'undefined') {
new PNotify({
title: 'Connection Error',
text: 'Could not connect to server. Please refresh this page.',
type: 'error'
});
}
});
};
// Make modify rule functions available globally
window.showModifyRuleModalScope = $scope.showModifyRuleModal;
window.closeModifyRuleModalScope = $scope.closeModifyRuleModal;
window.modifyRuleScope = $scope.modifyRule;
$scope.reloadFireWall = function () {
@@ -3828,53 +4103,325 @@ app.controller('litespeed_ent_conf', function ($scope, $http, $timeout, $window)
});
};
// Export/Import Banned IPs Functions
$scope.exportBannedIPs = function () {
$scope.bannedIPsLoading = false;
$scope.bannedIPActionFailed = true;
$scope.bannedIPActionSuccess = true;
var url = "/firewall/exportBannedIPs";
var data = {};
var config = {
headers: {
'X-CSRFToken': getCookie('csrftoken')
},
responseType: 'blob'
};
$http.post(url, data, config).then(function(response) {
$scope.bannedIPsLoading = true;
// Check if response is JSON (error) or file download
if (response.data instanceof Blob) {
// Create blob URL and trigger download
var blob = new Blob([response.data], { type: 'application/json' });
var url = window.URL.createObjectURL(blob);
var a = document.createElement('a');
a.href = url;
a.download = 'banned_ips_export_' + Math.floor(Date.now() / 1000) + '.json';
document.body.appendChild(a);
a.click();
window.URL.revokeObjectURL(url);
document.body.removeChild(a);
$scope.bannedIPActionFailed = true;
$scope.bannedIPActionSuccess = false;
if (typeof PNotify !== 'undefined') {
new PNotify({
title: 'Success!',
text: 'Banned IPs exported successfully',
type: 'success'
});
}
} else {
// Handle error response
try {
var errorData = typeof response.data === 'string' ? JSON.parse(response.data) : response.data;
if (errorData.exportStatus === 0) {
$scope.bannedIPActionFailed = false;
$scope.bannedIPActionSuccess = true;
$scope.bannedIPErrorMessage = errorData.error_message;
if (typeof PNotify !== 'undefined') {
new PNotify({
title: 'Export Failed',
text: errorData.error_message,
type: 'error'
});
}
}
} catch (e) {
// If not JSON, try reading as text
var reader = new FileReader();
reader.onload = function() {
try {
var errorData = JSON.parse(reader.result);
if (errorData.exportStatus === 0) {
$scope.bannedIPActionFailed = false;
$scope.bannedIPActionSuccess = true;
$scope.bannedIPErrorMessage = errorData.error_message;
}
} catch (e2) {
$scope.bannedIPActionFailed = false;
$scope.bannedIPActionSuccess = true;
$scope.bannedIPErrorMessage = 'Failed to export banned IPs';
}
};
reader.readAsText(response.data);
}
}
}, function(error) {
$scope.bannedIPsLoading = true;
$scope.bannedIPActionFailed = false;
$scope.bannedIPActionSuccess = true;
$scope.bannedIPErrorMessage = 'Could not connect to server. Please refresh this page.';
if (typeof PNotify !== 'undefined') {
new PNotify({
title: 'Connection Error',
text: 'Could not connect to server. Please refresh this page.',
type: 'error'
});
}
});
};
$scope.importBannedIPs = function () {
// Create file input element
var input = document.createElement('input');
input.type = 'file';
input.accept = '.json';
input.style.display = 'none';
input.onchange = function(event) {
var file = event.target.files[0];
if (file) {
var reader = new FileReader();
reader.onload = function(e) {
try {
var importData = JSON.parse(e.target.result);
// Validate file format
if (!importData.banned_ips || !Array.isArray(importData.banned_ips)) {
$scope.$apply(function() {
$scope.bannedIPActionFailed = false;
$scope.bannedIPActionSuccess = true;
$scope.bannedIPErrorMessage = "Invalid import file format. Please select a valid banned IPs export file.";
});
if (typeof PNotify !== 'undefined') {
new PNotify({
title: 'Invalid File',
text: 'Invalid import file format. Please select a valid banned IPs export file.',
type: 'error'
});
}
return;
}
// Upload file to server
uploadBannedIPsImportFile(file);
} catch (error) {
$scope.$apply(function() {
$scope.bannedIPActionFailed = false;
$scope.bannedIPActionSuccess = true;
$scope.bannedIPErrorMessage = "Invalid JSON file. Please select a valid banned IPs export file.";
});
if (typeof PNotify !== 'undefined') {
new PNotify({
title: 'Invalid File',
text: 'Invalid JSON file. Please select a valid banned IPs export file.',
type: 'error'
});
}
}
};
reader.readAsText(file);
}
};
document.body.appendChild(input);
input.click();
document.body.removeChild(input);
};
function uploadBannedIPsImportFile(file) {
$scope.bannedIPsLoading = false;
$scope.bannedIPActionFailed = true;
$scope.bannedIPActionSuccess = true;
$scope.bannedIPCouldNotConnect = true;
var formData = new FormData();
formData.append('import_file', file);
var config = {
headers: {
'X-CSRFToken': getCookie('csrftoken'),
'Content-Type': undefined
},
transformRequest: angular.identity
};
$http.post("/firewall/importBannedIPs", formData, config).then(function(response) {
$scope.bannedIPsLoading = true;
if (response.data.importStatus === 1) {
$scope.bannedIPActionSuccess = false;
populateBannedIPs(); // Refresh the list
var message = `Import completed: ${response.data.imported_count} imported, ${response.data.skipped_count} skipped`;
if (response.data.error_count > 0) {
message += `, ${response.data.error_count} errors`;
if (response.data.errors && response.data.errors.length > 0) {
message += '\nErrors: ' + response.data.errors.slice(0, 5).join('; ');
if (response.data.errors.length > 5) {
message += ` ... and ${response.data.errors.length - 5} more`;
}
}
}
if (typeof PNotify !== 'undefined') {
new PNotify({
title: 'Import Completed!',
text: message,
type: response.data.error_count > 0 ? 'notice' : 'success'
});
} else {
alert(message);
}
} else {
$scope.bannedIPActionFailed = false;
$scope.bannedIPErrorMessage = response.data.error_message || 'Failed to import banned IPs';
if (typeof PNotify !== 'undefined') {
new PNotify({
title: 'Import Failed',
text: response.data.error_message || 'Failed to import banned IPs',
type: 'error'
});
}
}
}, function(error) {
$scope.bannedIPsLoading = true;
$scope.bannedIPCouldNotConnect = false;
if (typeof PNotify !== 'undefined') {
new PNotify({
title: 'Connection Error',
text: 'Could not connect to server. Please refresh this page.',
type: 'error'
});
}
});
}
// Export/Import Firewall Rules Functions
$scope.exportRules = function () {
$scope.rulesLoading = false;
$scope.actionFailed = true;
$scope.actionSuccess = true;
url = "/firewall/exportFirewallRules";
var url = "/firewall/exportFirewallRules";
var data = {};
var config = {
headers: {
'X-CSRFToken': getCookie('csrftoken')
}
},
responseType: 'blob'
};
$http.post(url, data, config).then(exportSuccess, exportError);
function exportSuccess(response) {
$http.post(url, data, config).then(function(response) {
$scope.rulesLoading = true;
// Check if response is JSON (error) or file download
if (typeof response.data === 'string' && response.data.includes('{')) {
try {
var errorData = JSON.parse(response.data);
if (errorData.exportStatus === 0) {
$scope.actionFailed = false;
$scope.actionSuccess = true;
$scope.errorMessage = errorData.error_message;
return;
if (response.data instanceof Blob) {
// Check if it's actually a JSON error by reading the blob
var reader = new FileReader();
reader.onload = function() {
try {
var text = reader.result;
// Check if it's JSON error
if (text.trim().startsWith('{')) {
var errorData = JSON.parse(text);
if (errorData.exportStatus === 0) {
$scope.$apply(function() {
$scope.actionFailed = false;
$scope.actionSuccess = true;
$scope.errorMessage = errorData.error_message;
});
if (typeof PNotify !== 'undefined') {
new PNotify({
title: 'Export Failed',
text: errorData.error_message,
type: 'error'
});
}
return;
}
}
} catch (e) {
// Not JSON, proceed with download
}
} catch (e) {
// If not JSON, assume it's the file content
}
// It's a valid file, trigger download
var blob = new Blob([response.data], { type: 'application/json' });
var url = window.URL.createObjectURL(blob);
var a = document.createElement('a');
a.href = url;
a.download = 'firewall_rules_export_' + Math.floor(Date.now() / 1000) + '.json';
document.body.appendChild(a);
a.click();
window.URL.revokeObjectURL(url);
document.body.removeChild(a);
$scope.$apply(function() {
$scope.actionFailed = true;
$scope.actionSuccess = false;
});
if (typeof PNotify !== 'undefined') {
new PNotify({
title: 'Success!',
text: 'Firewall rules exported successfully',
type: 'success'
});
}
};
reader.readAsText(response.data);
} else {
// Handle as text response (shouldn't happen with blob)
$scope.actionFailed = true;
$scope.actionSuccess = false;
}
// If we get here, it's a successful file download
$scope.actionFailed = true;
$scope.actionSuccess = false;
}
function exportError(response) {
}, function(error) {
$scope.rulesLoading = true;
$scope.actionFailed = false;
$scope.actionSuccess = true;
$scope.errorMessage = "Could not connect to server. Please refresh this page.";
}
if (typeof PNotify !== 'undefined') {
new PNotify({
title: 'Connection Error',
text: 'Could not connect to server. Please refresh this page.',
type: 'error'
});
}
});
};
$scope.importRules = function () {
@@ -3942,7 +4489,7 @@ app.controller('litespeed_ent_conf', function ($scope, $http, $timeout, $window)
function importSuccess(response) {
$scope.rulesLoading = true;
if (response.data.importStatus === 1) {
if (response.data && response.data.importStatus === 1) {
$scope.actionFailed = true;
$scope.actionSuccess = false;
@@ -3950,20 +4497,38 @@ app.controller('litespeed_ent_conf', function ($scope, $http, $timeout, $window)
populateCurrentRecords();
// Show import summary
var summary = `Import completed successfully!\n` +
`Imported: ${response.data.imported_count} rules\n` +
`Skipped: ${response.data.skipped_count} rules\n` +
`Errors: ${response.data.error_count} rules`;
if (response.data.errors && response.data.errors.length > 0) {
summary += `\n\nErrors:\n${response.data.errors.join('\n')}`;
var message = `Import completed: ${response.data.imported_count} imported, ${response.data.skipped_count} skipped`;
if (response.data.error_count > 0) {
message += `, ${response.data.error_count} errors`;
if (response.data.errors && response.data.errors.length > 0) {
message += '\nErrors: ' + response.data.errors.slice(0, 5).join('; ');
if (response.data.errors.length > 5) {
message += ` ... and ${response.data.errors.length - 5} more`;
}
}
}
alert(summary);
if (typeof PNotify !== 'undefined') {
new PNotify({
title: 'Import Completed!',
text: message,
type: response.data.error_count > 0 ? 'notice' : 'success'
});
} else {
alert(message);
}
} else {
$scope.actionFailed = false;
$scope.actionSuccess = true;
$scope.errorMessage = response.data.error_message;
$scope.errorMessage = (response.data && response.data.error_message) || 'Failed to import firewall rules';
if (typeof PNotify !== 'undefined') {
new PNotify({
title: 'Import Failed',
text: (response.data && response.data.error_message) || 'Failed to import firewall rules',
type: 'error'
});
}
}
}
@@ -3972,6 +4537,14 @@ app.controller('litespeed_ent_conf', function ($scope, $http, $timeout, $window)
$scope.actionFailed = false;
$scope.actionSuccess = true;
$scope.errorMessage = "Could not connect to server. Please refresh this page.";
if (typeof PNotify !== 'undefined') {
new PNotify({
title: 'Connection Error',
text: 'Could not connect to server. Please refresh this page.',
type: 'error'
});
}
}
}

View File

@@ -618,7 +618,7 @@
}
/* Modal Styles - Ensure it's above everything */
#modifyBannedIPModal {
#modifyBannedIPModal, #modifyRuleModal {
z-index: 99999 !important;
position: fixed !important;
top: 0 !important;
@@ -627,7 +627,7 @@
height: 100% !important;
}
#modifyBannedIPModal.show {
#modifyBannedIPModal.show, #modifyRuleModal.show {
display: flex !important;
opacity: 1 !important;
visibility: visible !important;
@@ -635,11 +635,11 @@
justify-content: center !important;
}
#modifyBannedIPModal.fade.show {
#modifyBannedIPModal.fade.show, #modifyRuleModal.fade.show {
opacity: 1 !important;
}
#modifyBannedIPModal .modal-dialog {
#modifyBannedIPModal .modal-dialog, #modifyRuleModal .modal-dialog {
z-index: 100000 !important;
position: relative !important;
margin: 1.75rem auto !important;
@@ -664,7 +664,9 @@
/* Make sure modal is not hidden by parent containers */
.modern-container #modifyBannedIPModal,
.banned-ips-panel #modifyBannedIPModal {
.banned-ips-panel #modifyBannedIPModal,
.modern-container #modifyRuleModal,
.rules-panel #modifyRuleModal {
position: fixed !important;
z-index: 99999 !important;
}
@@ -851,6 +853,52 @@
transform: translateY(-1px);
box-shadow: 0 2px 8px rgba(33, 150, 243, 0.3);
}
.export-import-buttons {
display: flex;
gap: 0.75rem;
align-items: center;
}
.btn-export, .btn-import {
display: flex;
align-items: center;
gap: 0.5rem;
padding: 0.5rem 1rem;
border-radius: 6px;
font-weight: 500;
font-size: 0.9rem;
cursor: pointer;
transition: all 0.3s ease;
border: none;
}
.btn-export {
background: #10b981;
color: white;
}
.btn-export:hover:not(:disabled) {
background: #059669;
transform: translateY(-1px);
box-shadow: 0 2px 8px rgba(16, 185, 129, 0.3);
}
.btn-import {
background: #3b82f6;
color: white;
}
.btn-import:hover:not(:disabled) {
background: #2563eb;
transform: translateY(-1px);
box-shadow: 0 2px 8px rgba(59, 130, 246, 0.3);
}
.btn-export:disabled, .btn-import:disabled {
opacity: 0.5;
cursor: not-allowed;
}
/* Ensure actions column and buttons are always visible */
.banned-table td.actions {
@@ -1114,7 +1162,14 @@
<td>
<span class="port-number">{$ rule.port $}</span>
</td>
<td>
<td class="actions" style="display: flex !important; gap: 0.5rem !important; align-items: center !important;">
<button type="button"
ng-click="handleModifyRuleClick(rule, $event)"
class="btn-modify"
title="{% trans 'Modify Rule' %}">
<i class="fas fa-edit"></i>
{% trans "Modify" %}
</button>
<button type="button"
ng-click="deleteRule(rule.id, rule.proto, rule.port, rule.ipAddress)"
class="btn-delete"
@@ -1162,7 +1217,25 @@
</div>
{% trans "Banned IP Addresses" %}
</div>
<div ng-show="bannedIPsLoading" class="loading-spinner"></div>
<div style="display: flex; align-items: center; gap: 1rem;">
<div ng-show="bannedIPsLoading" class="loading-spinner"></div>
<div class="export-import-buttons">
<button type="button"
ng-click="exportBannedIPs()"
class="btn-export"
ng-disabled="bannedIPsLoading">
<i class="fas fa-download"></i>
{% trans "Export Banned IPs" %}
</button>
<button type="button"
ng-click="importBannedIPs()"
class="btn-import"
ng-disabled="bannedIPsLoading">
<i class="fas fa-upload"></i>
{% trans "Import Banned IPs" %}
</button>
</div>
</div>
</div>
<!-- Add Banned IP Section -->
@@ -1296,6 +1369,75 @@
</div>
</div>
<!-- Modify Firewall Rule Modal -->
<div class="modal fade" id="modifyRuleModal" tabindex="-1" role="dialog" aria-labelledby="modifyRuleModalLabel" style="display: none;">
<div class="modal-dialog modal-dialog-centered" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="modifyRuleModalLabel">
<i class="fas fa-edit"></i> {% trans "Modify Firewall Rule" %}
</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close" ng-click="closeModifyRuleModal()">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<form id="modifyRuleForm">
<input type="hidden" id="modifyRuleId" name="rule_id">
<div class="form-group">
<label for="modifyRuleName">
<i class="fas fa-tag"></i> {% trans "Rule Name" %}
</label>
<input type="text" class="form-control" id="modifyRuleName" name="rule_name" placeholder="{% trans 'e.g., Allow SSH' %}">
<small class="form-text text-muted">{% trans "Enter a descriptive name for this rule" %}</small>
</div>
<div class="form-group">
<label for="modifyRuleProtocol">
<i class="fas fa-network-wired"></i> {% trans "Protocol" %}
</label>
<select class="form-control" id="modifyRuleProtocol" name="protocol">
<option value="tcp">TCP</option>
<option value="udp">UDP</option>
</select>
<small class="form-text text-muted">{% trans "Select the network protocol" %}</small>
</div>
<div class="form-group">
<label for="modifyRuleIP">
<i class="fas fa-globe"></i> {% trans "IP Address" %}
</label>
<input type="text" class="form-control" id="modifyRuleIP" name="ip_address" placeholder="{% trans '0.0.0.0/0 for all IPs' %}">
<small class="form-text text-muted">{% trans "Enter IP address or CIDR notation (e.g., 192.168.1.0/24)" %}</small>
</div>
<div class="form-group">
<label for="modifyRulePort">
<i class="fas fa-plug"></i> {% trans "Port" %}
</label>
<input type="text" class="form-control" id="modifyRulePort" name="port" placeholder="{% trans 'e.g., 80 or 8000-8010' %}">
<small class="form-text text-muted">{% trans "Enter port number or range" %}</small>
</div>
<div class="alert alert-info">
<i class="fas fa-info-circle"></i>
<strong>{% trans "Note:" %}</strong> {% trans "Modifying a rule will update the firewall configuration. The old rule will be removed and replaced with the new settings." %}
</div>
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal" ng-click="closeModifyRuleModal()">
<i class="fas fa-times"></i> {% trans "Cancel" %}
</button>
<button type="button" class="btn btn-primary" ng-click="modifyRule()">
<i class="fas fa-save"></i> {% trans "Save Changes" %}
</button>
</div>
</div>
</div>
</div>
<!-- Modify Banned IP Modal - Inside controller for AngularJS scope access -->
<div class="modal fade" id="modifyBannedIPModal" tabindex="-1" role="dialog" aria-labelledby="modifyBannedIPModalLabel" style="display: none;">
<div class="modal-dialog modal-dialog-centered" role="document">

View File

@@ -6,6 +6,7 @@ urlpatterns = [
path('', views.firewallHome, name='firewallHome'),
path('getCurrentRules', views.getCurrentRules, name='getCurrentRules'),
path('addRule', views.addRule, name='addRule'),
path('modifyRule', views.modifyRule, name='modifyRule'),
path('deleteRule', views.deleteRule, name='deleteRule'),
path('reloadFirewall', views.reloadFirewall, name='reloadFirewall'),
@@ -36,8 +37,11 @@ urlpatterns = [
# Banned IPs
path('getBannedIPs', views.getBannedIPs, name='getBannedIPs'),
path('addBannedIP', views.addBannedIP, name='addBannedIP'),
path('modifyBannedIP', views.modifyBannedIP, name='modifyBannedIP'),
path('removeBannedIP', views.removeBannedIP, name='removeBannedIP'),
path('deleteBannedIP', views.deleteBannedIP, name='deleteBannedIP'),
path('exportBannedIPs', views.exportBannedIPs, name='exportBannedIPs'),
path('importBannedIPs', views.importBannedIPs, name='importBannedIPs'),
path('getRulesFiles', views.getRulesFiles, name='getRulesFiles'),
path('enableDisableRuleFile', views.enableDisableRuleFile, name='enableDisableRuleFile'),

View File

@@ -66,6 +66,15 @@ def addRule(request):
return redirect(loadLoginPage)
def modifyRule(request):
try:
userID = request.session['userID']
fm = FirewallManager()
return fm.modifyRule(userID, json.loads(request.body))
except KeyError:
return redirect(loadLoginPage)
def deleteRule(request):
try:
userID = request.session['userID']
@@ -666,6 +675,14 @@ def addBannedIP(request):
except KeyError:
return redirect(loadLoginPage)
def modifyBannedIP(request):
try:
userID = request.session['userID']
fm = FirewallManager()
return fm.modifyBannedIP(userID, json.loads(request.body))
except KeyError:
return redirect(loadLoginPage)
def removeBannedIP(request):
try:
userID = request.session['userID']
@@ -703,5 +720,30 @@ def importFirewallRules(request):
else:
# Handle JSON data
return fm.importFirewallRules(userID, json.loads(request.body))
except KeyError:
return redirect(loadLoginPage)
def exportBannedIPs(request):
try:
userID = request.session['userID']
fm = FirewallManager()
return fm.exportBannedIPs(userID)
except KeyError:
return redirect(loadLoginPage)
def importBannedIPs(request):
try:
userID = request.session['userID']
fm = FirewallManager()
fm.request = request # Set request for file upload handling
# Handle file upload
if request.method == 'POST' and 'import_file' in request.FILES:
return fm.importBannedIPs(userID, None)
else:
# Handle JSON data
return fm.importBannedIPs(userID, json.loads(request.body))
except KeyError:
return redirect(loadLoginPage)