mirror of
https://github.com/usmannasir/cyberpanel.git
synced 2026-02-12 01:26:47 +01:00
ai scans schedule
This commit is contained in:
0
aiScanner/management/__init__.py
Normal file
0
aiScanner/management/__init__.py
Normal file
0
aiScanner/management/commands/__init__.py
Normal file
0
aiScanner/management/commands/__init__.py
Normal file
330
aiScanner/management/commands/run_scheduled_scans.py
Normal file
330
aiScanner/management/commands/run_scheduled_scans.py
Normal file
@@ -0,0 +1,330 @@
|
||||
from django.core.management.base import BaseCommand
|
||||
from django.utils import timezone
|
||||
from datetime import datetime, timedelta
|
||||
import json
|
||||
import time
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
help = 'Run scheduled AI security scans'
|
||||
|
||||
def add_arguments(self, parser):
|
||||
parser.add_argument(
|
||||
'--daemon',
|
||||
action='store_true',
|
||||
help='Run as daemon, checking for scheduled scans every minute',
|
||||
)
|
||||
parser.add_argument(
|
||||
'--scan-id',
|
||||
type=int,
|
||||
help='Run a specific scheduled scan by ID',
|
||||
)
|
||||
|
||||
def handle(self, *args, **options):
|
||||
if options['daemon']:
|
||||
self.stdout.write('Starting scheduled scan daemon...')
|
||||
self.run_daemon()
|
||||
elif options['scan_id']:
|
||||
self.stdout.write(f'Running scheduled scan ID {options["scan_id"]}...')
|
||||
self.run_scheduled_scan_by_id(options['scan_id'])
|
||||
else:
|
||||
self.stdout.write('Checking for scheduled scans to run...')
|
||||
self.check_and_run_scans()
|
||||
|
||||
def run_daemon(self):
|
||||
"""Run as daemon, checking for scans every minute"""
|
||||
while True:
|
||||
try:
|
||||
self.check_and_run_scans()
|
||||
time.sleep(60) # Check every minute
|
||||
except KeyboardInterrupt:
|
||||
self.stdout.write('Daemon stopped by user')
|
||||
break
|
||||
except Exception as e:
|
||||
self.stderr.write(f'Error in daemon: {str(e)}')
|
||||
time.sleep(60) # Continue after error
|
||||
|
||||
def check_and_run_scans(self):
|
||||
"""Check for scheduled scans that need to run"""
|
||||
from aiScanner.models import ScheduledScan
|
||||
|
||||
now = timezone.now()
|
||||
|
||||
# Find scans that are due to run
|
||||
due_scans = ScheduledScan.objects.filter(
|
||||
status='active',
|
||||
next_run__lte=now
|
||||
)
|
||||
|
||||
for scan in due_scans:
|
||||
self.stdout.write(f'Running scheduled scan: {scan.name} (ID: {scan.id})')
|
||||
self.execute_scheduled_scan(scan)
|
||||
|
||||
def run_scheduled_scan_by_id(self, scan_id):
|
||||
"""Run a specific scheduled scan by ID"""
|
||||
from aiScanner.models import ScheduledScan
|
||||
|
||||
try:
|
||||
scan = ScheduledScan.objects.get(id=scan_id)
|
||||
self.stdout.write(f'Running scheduled scan: {scan.name}')
|
||||
self.execute_scheduled_scan(scan)
|
||||
except ScheduledScan.DoesNotExist:
|
||||
self.stderr.write(f'Scheduled scan with ID {scan_id} not found')
|
||||
|
||||
def execute_scheduled_scan(self, scheduled_scan):
|
||||
"""Execute a scheduled scan"""
|
||||
from aiScanner.models import ScheduledScanExecution, ScanHistory
|
||||
from aiScanner.aiScannerManager import AIScannerManager
|
||||
from loginSystem.models import Administrator
|
||||
from websiteFunctions.models import Websites
|
||||
from plogical.CyberCPLogFileWriter import CyberCPLogFileWriter as logging
|
||||
|
||||
# Create execution record
|
||||
execution = ScheduledScanExecution.objects.create(
|
||||
scheduled_scan=scheduled_scan,
|
||||
status='running',
|
||||
started_at=timezone.now()
|
||||
)
|
||||
|
||||
try:
|
||||
# Update last run time
|
||||
scheduled_scan.last_run = timezone.now()
|
||||
scheduled_scan.next_run = scheduled_scan.calculate_next_run()
|
||||
scheduled_scan.save()
|
||||
|
||||
# Get domains to scan
|
||||
domains_to_scan = []
|
||||
admin = scheduled_scan.admin
|
||||
|
||||
# Validate domains still exist and user has access
|
||||
for domain in scheduled_scan.domain_list:
|
||||
try:
|
||||
website = Websites.objects.get(domain=domain, admin=admin)
|
||||
domains_to_scan.append(domain)
|
||||
except Websites.DoesNotExist:
|
||||
logging.writeToFile(f'[Scheduled Scan] Domain {domain} no longer accessible for user {admin.userName}')
|
||||
continue
|
||||
|
||||
if not domains_to_scan:
|
||||
execution.status = 'failed'
|
||||
execution.error_message = 'No accessible domains found for scanning'
|
||||
execution.completed_at = timezone.now()
|
||||
execution.save()
|
||||
self.stderr.write(f'No accessible domains for scheduled scan {scheduled_scan.name}')
|
||||
return
|
||||
|
||||
execution.set_scanned_domains(domains_to_scan)
|
||||
execution.total_scans = len(domains_to_scan)
|
||||
execution.save()
|
||||
|
||||
# Initialize scanner manager
|
||||
sm = AIScannerManager()
|
||||
scan_ids = []
|
||||
successful_scans = 0
|
||||
failed_scans = 0
|
||||
total_cost = 0.0
|
||||
|
||||
# Execute scans for each domain
|
||||
for domain in domains_to_scan:
|
||||
try:
|
||||
self.stdout.write(f'Starting scan for domain: {domain}')
|
||||
|
||||
# Create a fake request object for the scanner manager
|
||||
class FakeRequest:
|
||||
def __init__(self, admin_id):
|
||||
self.session = {'userID': admin_id}
|
||||
self.method = 'POST'
|
||||
self.POST = {
|
||||
'domain': domain,
|
||||
'scan_type': scheduled_scan.scan_type
|
||||
}
|
||||
|
||||
fake_request = FakeRequest(admin.pk)
|
||||
|
||||
# Start the scan
|
||||
result = sm.startScan(fake_request, admin.pk)
|
||||
|
||||
if hasattr(result, 'content'):
|
||||
# It's an HTTP response, parse the JSON
|
||||
import json
|
||||
response_data = json.loads(result.content.decode('utf-8'))
|
||||
else:
|
||||
# It's already a dict
|
||||
response_data = result
|
||||
|
||||
if response_data.get('success'):
|
||||
scan_id = response_data.get('scan_id')
|
||||
if scan_id:
|
||||
scan_ids.append(scan_id)
|
||||
successful_scans += 1
|
||||
|
||||
# Get cost estimate if available
|
||||
if 'cost_estimate' in response_data:
|
||||
total_cost += float(response_data['cost_estimate'])
|
||||
|
||||
logging.writeToFile(f'[Scheduled Scan] Successfully started scan {scan_id} for {domain}')
|
||||
else:
|
||||
failed_scans += 1
|
||||
logging.writeToFile(f'[Scheduled Scan] Failed to get scan ID for {domain}')
|
||||
else:
|
||||
failed_scans += 1
|
||||
error_msg = response_data.get('error', 'Unknown error')
|
||||
logging.writeToFile(f'[Scheduled Scan] Failed to start scan for {domain}: {error_msg}')
|
||||
|
||||
except Exception as e:
|
||||
failed_scans += 1
|
||||
error_msg = str(e)
|
||||
logging.writeToFile(f'[Scheduled Scan] Exception starting scan for {domain}: {error_msg}')
|
||||
|
||||
# Small delay between scans to avoid overwhelming the system
|
||||
time.sleep(2)
|
||||
|
||||
# Update execution record
|
||||
execution.successful_scans = successful_scans
|
||||
execution.failed_scans = failed_scans
|
||||
execution.total_cost = total_cost
|
||||
execution.set_scan_ids(scan_ids)
|
||||
execution.status = 'completed' if failed_scans == 0 else 'completed' # Always completed if we tried all
|
||||
execution.completed_at = timezone.now()
|
||||
execution.save()
|
||||
|
||||
# Send notifications if configured
|
||||
if scheduled_scan.email_notifications:
|
||||
self.send_notifications(scheduled_scan, execution)
|
||||
|
||||
self.stdout.write(
|
||||
f'Scheduled scan completed: {successful_scans} successful, {failed_scans} failed'
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
# Update execution record with error
|
||||
execution.status = 'failed'
|
||||
execution.error_message = str(e)
|
||||
execution.completed_at = timezone.now()
|
||||
execution.save()
|
||||
|
||||
logging.writeToFile(f'[Scheduled Scan] Failed to execute scheduled scan {scheduled_scan.name}: {str(e)}')
|
||||
self.stderr.write(f'Failed to execute scheduled scan {scheduled_scan.name}: {str(e)}')
|
||||
|
||||
# Send failure notification
|
||||
if scheduled_scan.email_notifications and scheduled_scan.notify_on_failure:
|
||||
self.send_failure_notification(scheduled_scan, str(e))
|
||||
|
||||
def send_notifications(self, scheduled_scan, execution):
|
||||
"""Send email notifications for completed scan"""
|
||||
try:
|
||||
# Determine if we should send notification
|
||||
should_notify = False
|
||||
|
||||
if execution.status == 'failed' and scheduled_scan.notify_on_failure:
|
||||
should_notify = True
|
||||
elif execution.status == 'completed':
|
||||
if scheduled_scan.notify_on_completion:
|
||||
should_notify = True
|
||||
elif scheduled_scan.notify_on_threats and execution.successful_scans > 0:
|
||||
# Check if any scans found threats
|
||||
# This would require checking the scan results, which might not be available immediately
|
||||
# For now, we'll just send completion notifications
|
||||
should_notify = scheduled_scan.notify_on_completion
|
||||
|
||||
if should_notify:
|
||||
self.send_execution_notification(scheduled_scan, execution)
|
||||
|
||||
except Exception as e:
|
||||
from plogical.CyberCPLogFileWriter import CyberCPLogFileWriter as logging
|
||||
logging.writeToFile(f'[Scheduled Scan] Failed to send notification: {str(e)}')
|
||||
|
||||
def send_execution_notification(self, scheduled_scan, execution):
|
||||
"""Send notification email for scan execution"""
|
||||
try:
|
||||
# Get notification emails
|
||||
notification_emails = scheduled_scan.notification_email_list
|
||||
if not notification_emails:
|
||||
# Use admin email as fallback
|
||||
notification_emails = [scheduled_scan.admin.email] if scheduled_scan.admin.email else []
|
||||
|
||||
if not notification_emails:
|
||||
return
|
||||
|
||||
# Prepare email content
|
||||
subject = f'AI Scanner: Scheduled Scan "{scheduled_scan.name}" Completed'
|
||||
|
||||
status_text = execution.status.title()
|
||||
if execution.status == 'completed':
|
||||
if execution.failed_scans == 0:
|
||||
status_text = 'Completed Successfully'
|
||||
else:
|
||||
status_text = f'Completed with {execution.failed_scans} failures'
|
||||
|
||||
message = f"""
|
||||
Scheduled AI Security Scan Report
|
||||
|
||||
Scan Name: {scheduled_scan.name}
|
||||
Status: {status_text}
|
||||
Execution Time: {execution.execution_time.strftime('%Y-%m-%d %H:%M:%S UTC')}
|
||||
|
||||
Results:
|
||||
- Total Domains: {execution.total_scans}
|
||||
- Successful Scans: {execution.successful_scans}
|
||||
- Failed Scans: {execution.failed_scans}
|
||||
- Total Cost: ${execution.total_cost:.4f}
|
||||
|
||||
Domains Scanned: {', '.join(execution.scanned_domains)}
|
||||
|
||||
{f'Error Message: {execution.error_message}' if execution.error_message else ''}
|
||||
|
||||
Scan IDs: {', '.join(execution.scan_id_list)}
|
||||
|
||||
View detailed results in your CyberPanel AI Scanner dashboard.
|
||||
"""
|
||||
|
||||
# Send email using CyberPanel's email system
|
||||
from plogical.mailUtilities import mailUtilities
|
||||
mailUtilities.sendEmail(notification_emails[0], subject, message)
|
||||
|
||||
# Log notification sent
|
||||
from plogical.CyberCPLogFileWriter import CyberCPLogFileWriter as logging
|
||||
logging.writeToFile(f'[Scheduled Scan] Notification sent for {scheduled_scan.name} to {len(notification_emails)} recipients')
|
||||
|
||||
except Exception as e:
|
||||
from plogical.CyberCPLogFileWriter import CyberCPLogFileWriter as logging
|
||||
logging.writeToFile(f'[Scheduled Scan] Failed to send notification email: {str(e)}')
|
||||
|
||||
def send_failure_notification(self, scheduled_scan, error_message):
|
||||
"""Send notification email for scan failure"""
|
||||
try:
|
||||
# Get notification emails
|
||||
notification_emails = scheduled_scan.notification_email_list
|
||||
if not notification_emails:
|
||||
# Use admin email as fallback
|
||||
notification_emails = [scheduled_scan.admin.email] if scheduled_scan.admin.email else []
|
||||
|
||||
if not notification_emails:
|
||||
return
|
||||
|
||||
# Prepare email content
|
||||
subject = f'AI Scanner: Scheduled Scan "{scheduled_scan.name}" Failed'
|
||||
|
||||
message = f"""
|
||||
Scheduled AI Security Scan Failure
|
||||
|
||||
Scan Name: {scheduled_scan.name}
|
||||
Status: Failed
|
||||
Time: {timezone.now().strftime('%Y-%m-%d %H:%M:%S UTC')}
|
||||
|
||||
Error: {error_message}
|
||||
|
||||
Please check your CyberPanel AI Scanner configuration and try again.
|
||||
"""
|
||||
|
||||
# Send email using CyberPanel's email system
|
||||
from plogical.mailUtilities import mailUtilities
|
||||
mailUtilities.sendEmail(notification_emails[0], subject, message)
|
||||
|
||||
# Log notification sent
|
||||
from plogical.CyberCPLogFileWriter import CyberCPLogFileWriter as logging
|
||||
logging.writeToFile(f'[Scheduled Scan] Failure notification sent for {scheduled_scan.name}')
|
||||
|
||||
except Exception as e:
|
||||
from plogical.CyberCPLogFileWriter import CyberCPLogFileWriter as logging
|
||||
logging.writeToFile(f'[Scheduled Scan] Failed to send failure notification email: {str(e)}')
|
||||
@@ -107,3 +107,216 @@ class FileAccessToken(models.Model):
|
||||
def is_expired(self):
|
||||
from django.utils import timezone
|
||||
return timezone.now() > self.expires_at
|
||||
|
||||
|
||||
class ScheduledScan(models.Model):
|
||||
"""Store scheduled scan configurations"""
|
||||
FREQUENCY_CHOICES = [
|
||||
('daily', 'Daily'),
|
||||
('weekly', 'Weekly'),
|
||||
('monthly', 'Monthly'),
|
||||
('quarterly', 'Quarterly'),
|
||||
]
|
||||
|
||||
SCAN_TYPE_CHOICES = [
|
||||
('full', 'Full Scan'),
|
||||
('quick', 'Quick Scan'),
|
||||
('custom', 'Custom Scan'),
|
||||
]
|
||||
|
||||
STATUS_CHOICES = [
|
||||
('active', 'Active'),
|
||||
('paused', 'Paused'),
|
||||
('disabled', 'Disabled'),
|
||||
]
|
||||
|
||||
admin = models.ForeignKey(Administrator, on_delete=models.CASCADE, related_name='scheduled_scans')
|
||||
name = models.CharField(max_length=200, help_text="Name for this scheduled scan")
|
||||
domains = models.TextField(help_text="JSON array of domains to scan")
|
||||
frequency = models.CharField(max_length=20, choices=FREQUENCY_CHOICES, default='weekly')
|
||||
scan_type = models.CharField(max_length=20, choices=SCAN_TYPE_CHOICES, default='full')
|
||||
time_of_day = models.TimeField(help_text="Time of day to run the scan (UTC)")
|
||||
day_of_week = models.IntegerField(null=True, blank=True, help_text="Day of week for weekly scans (0=Monday, 6=Sunday)")
|
||||
day_of_month = models.IntegerField(null=True, blank=True, help_text="Day of month for monthly scans (1-31)")
|
||||
status = models.CharField(max_length=20, choices=STATUS_CHOICES, default='active')
|
||||
last_run = models.DateTimeField(null=True, blank=True)
|
||||
next_run = models.DateTimeField(null=True, blank=True)
|
||||
created_at = models.DateTimeField(auto_now_add=True)
|
||||
updated_at = models.DateTimeField(auto_now=True)
|
||||
|
||||
# Notification settings
|
||||
email_notifications = models.BooleanField(default=True)
|
||||
notification_emails = models.TextField(blank=True, help_text="JSON array of email addresses")
|
||||
notify_on_threats = models.BooleanField(default=True)
|
||||
notify_on_completion = models.BooleanField(default=False)
|
||||
notify_on_failure = models.BooleanField(default=True)
|
||||
|
||||
class Meta:
|
||||
db_table = 'ai_scanner_scheduled_scans'
|
||||
ordering = ['-created_at']
|
||||
|
||||
def __str__(self):
|
||||
return f"Scheduled Scan: {self.name} ({self.frequency})"
|
||||
|
||||
@property
|
||||
def domain_list(self):
|
||||
"""Parse domains JSON"""
|
||||
if self.domains:
|
||||
try:
|
||||
return json.loads(self.domains)
|
||||
except json.JSONDecodeError:
|
||||
return []
|
||||
return []
|
||||
|
||||
@property
|
||||
def notification_email_list(self):
|
||||
"""Parse notification emails JSON"""
|
||||
if self.notification_emails:
|
||||
try:
|
||||
return json.loads(self.notification_emails)
|
||||
except json.JSONDecodeError:
|
||||
return []
|
||||
return []
|
||||
|
||||
def set_domains(self, domain_list):
|
||||
"""Set domains from list"""
|
||||
self.domains = json.dumps(domain_list)
|
||||
|
||||
def set_notification_emails(self, email_list):
|
||||
"""Set notification emails from list"""
|
||||
self.notification_emails = json.dumps(email_list)
|
||||
|
||||
def calculate_next_run(self):
|
||||
"""Calculate next run time based on frequency"""
|
||||
from django.utils import timezone
|
||||
from datetime import datetime, timedelta
|
||||
import calendar
|
||||
|
||||
now = timezone.now()
|
||||
|
||||
if self.frequency == 'daily':
|
||||
# Daily: next run is tomorrow at specified time
|
||||
next_run = now.replace(hour=self.time_of_day.hour, minute=self.time_of_day.minute, second=0, microsecond=0)
|
||||
if next_run <= now:
|
||||
next_run += timedelta(days=1)
|
||||
|
||||
elif self.frequency == 'weekly':
|
||||
# Weekly: next run is on specified day of week at specified time
|
||||
days_ahead = self.day_of_week - now.weekday()
|
||||
if days_ahead <= 0: # Target day already happened this week
|
||||
days_ahead += 7
|
||||
next_run = now + timedelta(days=days_ahead)
|
||||
next_run = next_run.replace(hour=self.time_of_day.hour, minute=self.time_of_day.minute, second=0, microsecond=0)
|
||||
|
||||
elif self.frequency == 'monthly':
|
||||
# Monthly: next run is on specified day of month at specified time
|
||||
year = now.year
|
||||
month = now.month
|
||||
day = min(self.day_of_month, calendar.monthrange(year, month)[1])
|
||||
|
||||
next_run = now.replace(day=day, hour=self.time_of_day.hour, minute=self.time_of_day.minute, second=0, microsecond=0)
|
||||
|
||||
if next_run <= now:
|
||||
# Move to next month
|
||||
if month == 12:
|
||||
year += 1
|
||||
month = 1
|
||||
else:
|
||||
month += 1
|
||||
day = min(self.day_of_month, calendar.monthrange(year, month)[1])
|
||||
next_run = next_run.replace(year=year, month=month, day=day)
|
||||
|
||||
elif self.frequency == 'quarterly':
|
||||
# Quarterly: next run is 3 months from now
|
||||
next_run = now.replace(hour=self.time_of_day.hour, minute=self.time_of_day.minute, second=0, microsecond=0)
|
||||
month = now.month
|
||||
year = now.year
|
||||
|
||||
# Add 3 months
|
||||
month += 3
|
||||
if month > 12:
|
||||
year += 1
|
||||
month -= 12
|
||||
|
||||
day = min(self.day_of_month or 1, calendar.monthrange(year, month)[1])
|
||||
next_run = next_run.replace(year=year, month=month, day=day)
|
||||
|
||||
if next_run <= now:
|
||||
# Add another 3 months
|
||||
month += 3
|
||||
if month > 12:
|
||||
year += 1
|
||||
month -= 12
|
||||
day = min(self.day_of_month or 1, calendar.monthrange(year, month)[1])
|
||||
next_run = next_run.replace(year=year, month=month, day=day)
|
||||
|
||||
else:
|
||||
# Default to weekly
|
||||
next_run = now + timedelta(weeks=1)
|
||||
|
||||
return next_run
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
"""Override save to calculate next_run"""
|
||||
if not self.next_run or self.status == 'active':
|
||||
self.next_run = self.calculate_next_run()
|
||||
super().save(*args, **kwargs)
|
||||
|
||||
|
||||
class ScheduledScanExecution(models.Model):
|
||||
"""Track individual executions of scheduled scans"""
|
||||
STATUS_CHOICES = [
|
||||
('pending', 'Pending'),
|
||||
('running', 'Running'),
|
||||
('completed', 'Completed'),
|
||||
('failed', 'Failed'),
|
||||
('cancelled', 'Cancelled'),
|
||||
]
|
||||
|
||||
scheduled_scan = models.ForeignKey(ScheduledScan, on_delete=models.CASCADE, related_name='executions')
|
||||
execution_time = models.DateTimeField(auto_now_add=True)
|
||||
status = models.CharField(max_length=20, choices=STATUS_CHOICES, default='pending')
|
||||
domains_scanned = models.TextField(blank=True, help_text="JSON array of domains that were scanned")
|
||||
total_scans = models.IntegerField(default=0)
|
||||
successful_scans = models.IntegerField(default=0)
|
||||
failed_scans = models.IntegerField(default=0)
|
||||
total_cost = models.DecimalField(max_digits=10, decimal_places=6, default=0.0)
|
||||
scan_ids = models.TextField(blank=True, help_text="JSON array of scan IDs created")
|
||||
error_message = models.TextField(blank=True, null=True)
|
||||
started_at = models.DateTimeField(null=True, blank=True)
|
||||
completed_at = models.DateTimeField(null=True, blank=True)
|
||||
|
||||
class Meta:
|
||||
db_table = 'ai_scanner_scheduled_executions'
|
||||
ordering = ['-execution_time']
|
||||
|
||||
def __str__(self):
|
||||
return f"Execution of {self.scheduled_scan.name} at {self.execution_time}"
|
||||
|
||||
@property
|
||||
def scanned_domains(self):
|
||||
"""Parse domains scanned JSON"""
|
||||
if self.domains_scanned:
|
||||
try:
|
||||
return json.loads(self.domains_scanned)
|
||||
except json.JSONDecodeError:
|
||||
return []
|
||||
return []
|
||||
|
||||
@property
|
||||
def scan_id_list(self):
|
||||
"""Parse scan IDs JSON"""
|
||||
if self.scan_ids:
|
||||
try:
|
||||
return json.loads(self.scan_ids)
|
||||
except json.JSONDecodeError:
|
||||
return []
|
||||
return []
|
||||
|
||||
def set_scanned_domains(self, domain_list):
|
||||
"""Set scanned domains from list"""
|
||||
self.domains_scanned = json.dumps(domain_list)
|
||||
|
||||
def set_scan_ids(self, scan_id_list):
|
||||
"""Set scan IDs from list"""
|
||||
self.scan_ids = json.dumps(scan_id_list)
|
||||
|
||||
270
aiScanner/scheduled_views.py
Normal file
270
aiScanner/scheduled_views.py
Normal file
@@ -0,0 +1,270 @@
|
||||
from django.shortcuts import render, redirect
|
||||
from django.http import JsonResponse
|
||||
from django.views.decorators.csrf import csrf_exempt
|
||||
from django.views.decorators.http import require_http_methods
|
||||
from loginSystem.views import loadLoginPage
|
||||
import json
|
||||
|
||||
|
||||
@require_http_methods(['GET', 'POST'])
|
||||
def scheduledScans(request):
|
||||
"""Manage scheduled scans"""
|
||||
try:
|
||||
userID = request.session['userID']
|
||||
from loginSystem.models import Administrator
|
||||
from .models import ScheduledScan
|
||||
from plogical.acl import ACLManager
|
||||
|
||||
admin = Administrator.objects.get(pk=userID)
|
||||
currentACL = ACLManager.loadedACL(userID)
|
||||
|
||||
if request.method == 'GET':
|
||||
# Get scheduled scans with ACL respect
|
||||
if currentACL['admin'] == 1:
|
||||
# Admin can see all scheduled scans
|
||||
scheduled_scans = ScheduledScan.objects.all()
|
||||
else:
|
||||
# Users can only see their own scheduled scans and their sub-users' scans
|
||||
user_admins = ACLManager.loadUserObjects(userID)
|
||||
scheduled_scans = ScheduledScan.objects.filter(admin__in=user_admins)
|
||||
|
||||
scan_data = []
|
||||
for scan in scheduled_scans:
|
||||
scan_data.append({
|
||||
'id': scan.id,
|
||||
'name': scan.name,
|
||||
'domains': scan.domain_list,
|
||||
'frequency': scan.frequency,
|
||||
'scan_type': scan.scan_type,
|
||||
'time_of_day': scan.time_of_day.strftime('%H:%M'),
|
||||
'day_of_week': scan.day_of_week,
|
||||
'day_of_month': scan.day_of_month,
|
||||
'status': scan.status,
|
||||
'last_run': scan.last_run.isoformat() if scan.last_run else None,
|
||||
'next_run': scan.next_run.isoformat() if scan.next_run else None,
|
||||
'email_notifications': scan.email_notifications,
|
||||
'notification_emails': scan.notification_email_list,
|
||||
'notify_on_threats': scan.notify_on_threats,
|
||||
'notify_on_completion': scan.notify_on_completion,
|
||||
'notify_on_failure': scan.notify_on_failure,
|
||||
'created_at': scan.created_at.isoformat()
|
||||
})
|
||||
|
||||
return JsonResponse({'success': True, 'scheduled_scans': scan_data})
|
||||
|
||||
elif request.method == 'POST':
|
||||
# Create new scheduled scan
|
||||
data = json.loads(request.body)
|
||||
|
||||
# Validate required fields
|
||||
required_fields = ['name', 'domains', 'frequency', 'scan_type', 'time_of_day']
|
||||
for field in required_fields:
|
||||
if field not in data or not data[field]:
|
||||
return JsonResponse({'success': False, 'error': f'Missing required field: {field}'})
|
||||
|
||||
# Validate domains
|
||||
if not isinstance(data['domains'], list) or len(data['domains']) == 0:
|
||||
return JsonResponse({'success': False, 'error': 'At least one domain must be selected'})
|
||||
|
||||
# Check if user has access to these domains
|
||||
if currentACL['admin'] != 1:
|
||||
from websiteFunctions.models import Websites
|
||||
user_domains = set(Websites.objects.filter(admin=admin).values_list('domain', flat=True))
|
||||
requested_domains = set(data['domains'])
|
||||
|
||||
if not requested_domains.issubset(user_domains):
|
||||
return JsonResponse({'success': False, 'error': 'You do not have access to some of the selected domains'})
|
||||
|
||||
# Parse time
|
||||
from datetime import datetime
|
||||
try:
|
||||
time_obj = datetime.strptime(data['time_of_day'], '%H:%M').time()
|
||||
except ValueError:
|
||||
return JsonResponse({'success': False, 'error': 'Invalid time format'})
|
||||
|
||||
# Create scheduled scan
|
||||
scheduled_scan = ScheduledScan(
|
||||
admin=admin,
|
||||
name=data['name'],
|
||||
frequency=data['frequency'],
|
||||
scan_type=data['scan_type'],
|
||||
time_of_day=time_obj,
|
||||
email_notifications=data.get('email_notifications', True),
|
||||
notify_on_threats=data.get('notify_on_threats', True),
|
||||
notify_on_completion=data.get('notify_on_completion', False),
|
||||
notify_on_failure=data.get('notify_on_failure', True)
|
||||
)
|
||||
|
||||
# Set domains
|
||||
scheduled_scan.set_domains(data['domains'])
|
||||
|
||||
# Set notification emails
|
||||
if data.get('notification_emails'):
|
||||
scheduled_scan.set_notification_emails(data['notification_emails'])
|
||||
|
||||
# Set frequency-specific fields
|
||||
if data['frequency'] == 'weekly' and 'day_of_week' in data:
|
||||
scheduled_scan.day_of_week = int(data['day_of_week'])
|
||||
elif data['frequency'] in ['monthly', 'quarterly'] and 'day_of_month' in data:
|
||||
scheduled_scan.day_of_month = int(data['day_of_month'])
|
||||
|
||||
scheduled_scan.save()
|
||||
|
||||
return JsonResponse({'success': True, 'id': scheduled_scan.id})
|
||||
|
||||
except KeyError:
|
||||
return JsonResponse({'success': False, 'error': 'Not authenticated'})
|
||||
except Exception as e:
|
||||
return JsonResponse({'success': False, 'error': str(e)})
|
||||
|
||||
|
||||
@require_http_methods(['GET', 'DELETE'])
|
||||
def scheduledScanDetail(request, scan_id):
|
||||
"""Get or delete a specific scheduled scan"""
|
||||
try:
|
||||
userID = request.session['userID']
|
||||
from loginSystem.models import Administrator
|
||||
from .models import ScheduledScan
|
||||
from plogical.acl import ACLManager
|
||||
|
||||
admin = Administrator.objects.get(pk=userID)
|
||||
currentACL = ACLManager.loadedACL(userID)
|
||||
|
||||
# Get scheduled scan with ACL respect
|
||||
try:
|
||||
scheduled_scan = ScheduledScan.objects.get(id=scan_id)
|
||||
|
||||
# Check if user has access to this scheduled scan
|
||||
if currentACL['admin'] != 1:
|
||||
user_admins = ACLManager.loadUserObjects(userID)
|
||||
if scheduled_scan.admin not in user_admins:
|
||||
return JsonResponse({'success': False, 'error': 'Access denied to this scheduled scan'})
|
||||
except ScheduledScan.DoesNotExist:
|
||||
return JsonResponse({'success': False, 'error': 'Scheduled scan not found'})
|
||||
|
||||
if request.method == 'GET':
|
||||
# Return scheduled scan details
|
||||
scan_data = {
|
||||
'id': scheduled_scan.id,
|
||||
'name': scheduled_scan.name,
|
||||
'domains': scheduled_scan.domain_list,
|
||||
'frequency': scheduled_scan.frequency,
|
||||
'scan_type': scheduled_scan.scan_type,
|
||||
'time_of_day': scheduled_scan.time_of_day.strftime('%H:%M'),
|
||||
'day_of_week': scheduled_scan.day_of_week,
|
||||
'day_of_month': scheduled_scan.day_of_month,
|
||||
'status': scheduled_scan.status,
|
||||
'last_run': scheduled_scan.last_run.isoformat() if scheduled_scan.last_run else None,
|
||||
'next_run': scheduled_scan.next_run.isoformat() if scheduled_scan.next_run else None,
|
||||
'email_notifications': scheduled_scan.email_notifications,
|
||||
'notification_emails': scheduled_scan.notification_email_list,
|
||||
'notify_on_threats': scheduled_scan.notify_on_threats,
|
||||
'notify_on_completion': scheduled_scan.notify_on_completion,
|
||||
'notify_on_failure': scheduled_scan.notify_on_failure,
|
||||
'created_at': scheduled_scan.created_at.isoformat()
|
||||
}
|
||||
|
||||
return JsonResponse({'success': True, 'scheduled_scan': scan_data})
|
||||
|
||||
elif request.method == 'DELETE':
|
||||
# Delete scheduled scan
|
||||
scheduled_scan.delete()
|
||||
return JsonResponse({'success': True})
|
||||
|
||||
except KeyError:
|
||||
return JsonResponse({'success': False, 'error': 'Not authenticated'})
|
||||
except Exception as e:
|
||||
return JsonResponse({'success': False, 'error': str(e)})
|
||||
|
||||
|
||||
@csrf_exempt
|
||||
@require_http_methods(['POST'])
|
||||
def toggleScheduledScan(request, scan_id):
|
||||
"""Toggle scheduled scan status (active/paused)"""
|
||||
try:
|
||||
userID = request.session['userID']
|
||||
from loginSystem.models import Administrator
|
||||
from .models import ScheduledScan
|
||||
from plogical.acl import ACLManager
|
||||
|
||||
admin = Administrator.objects.get(pk=userID)
|
||||
currentACL = ACLManager.loadedACL(userID)
|
||||
|
||||
# Get scheduled scan with ACL respect
|
||||
try:
|
||||
scheduled_scan = ScheduledScan.objects.get(id=scan_id)
|
||||
|
||||
# Check if user has access to this scheduled scan
|
||||
if currentACL['admin'] != 1:
|
||||
user_admins = ACLManager.loadUserObjects(userID)
|
||||
if scheduled_scan.admin not in user_admins:
|
||||
return JsonResponse({'success': False, 'error': 'Access denied to this scheduled scan'})
|
||||
except ScheduledScan.DoesNotExist:
|
||||
return JsonResponse({'success': False, 'error': 'Scheduled scan not found'})
|
||||
|
||||
# Toggle status
|
||||
if scheduled_scan.status == 'active':
|
||||
scheduled_scan.status = 'paused'
|
||||
else:
|
||||
scheduled_scan.status = 'active'
|
||||
|
||||
scheduled_scan.save()
|
||||
|
||||
return JsonResponse({'success': True, 'status': scheduled_scan.status})
|
||||
|
||||
except KeyError:
|
||||
return JsonResponse({'success': False, 'error': 'Not authenticated'})
|
||||
except Exception as e:
|
||||
return JsonResponse({'success': False, 'error': str(e)})
|
||||
|
||||
|
||||
@require_http_methods(['GET'])
|
||||
def scheduledScanExecutions(request, scan_id):
|
||||
"""Get execution history for a scheduled scan"""
|
||||
try:
|
||||
userID = request.session['userID']
|
||||
from loginSystem.models import Administrator
|
||||
from .models import ScheduledScan, ScheduledScanExecution
|
||||
from plogical.acl import ACLManager
|
||||
|
||||
admin = Administrator.objects.get(pk=userID)
|
||||
currentACL = ACLManager.loadedACL(userID)
|
||||
|
||||
# Get scheduled scan with ACL respect
|
||||
try:
|
||||
scheduled_scan = ScheduledScan.objects.get(id=scan_id)
|
||||
|
||||
# Check if user has access to this scheduled scan
|
||||
if currentACL['admin'] != 1:
|
||||
user_admins = ACLManager.loadUserObjects(userID)
|
||||
if scheduled_scan.admin not in user_admins:
|
||||
return JsonResponse({'success': False, 'error': 'Access denied to this scheduled scan'})
|
||||
except ScheduledScan.DoesNotExist:
|
||||
return JsonResponse({'success': False, 'error': 'Scheduled scan not found'})
|
||||
|
||||
# Get execution history
|
||||
executions = ScheduledScanExecution.objects.filter(scheduled_scan=scheduled_scan).order_by('-execution_time')[:20]
|
||||
|
||||
execution_data = []
|
||||
for execution in executions:
|
||||
execution_data.append({
|
||||
'id': execution.id,
|
||||
'execution_time': execution.execution_time.isoformat(),
|
||||
'status': execution.status,
|
||||
'domains_scanned': execution.scanned_domains,
|
||||
'total_scans': execution.total_scans,
|
||||
'successful_scans': execution.successful_scans,
|
||||
'failed_scans': execution.failed_scans,
|
||||
'total_cost': float(execution.total_cost),
|
||||
'scan_ids': execution.scan_id_list,
|
||||
'error_message': execution.error_message,
|
||||
'started_at': execution.started_at.isoformat() if execution.started_at else None,
|
||||
'completed_at': execution.completed_at.isoformat() if execution.completed_at else None
|
||||
})
|
||||
|
||||
return JsonResponse({'success': True, 'executions': execution_data})
|
||||
|
||||
except KeyError:
|
||||
return JsonResponse({'success': False, 'error': 'Not authenticated'})
|
||||
except Exception as e:
|
||||
return JsonResponse({'success': False, 'error': str(e)})
|
||||
@@ -642,6 +642,27 @@ AI Security Scanner - CyberPanel
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- Scheduled Scans -->
|
||||
{% if is_payment_configured or vps_info.is_vps|default:False %}
|
||||
<div class="scanner-section">
|
||||
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px;">
|
||||
<h3 class="scanner-title" style="margin: 0;">
|
||||
<i class="fas fa-calendar-alt" style="color: #5856d6;"></i>
|
||||
Scheduled Scans
|
||||
</h3>
|
||||
<button class="btn-primary btn-xs" onclick="showScheduleModal()">
|
||||
<i class="fas fa-plus"></i> Schedule New Scan
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div id="scheduledScansContainer">
|
||||
<!-- Scheduled scans will be loaded here -->
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<!-- Scan History -->
|
||||
@@ -1539,5 +1560,427 @@ setInterval(() => {
|
||||
refreshScanHistory();
|
||||
}
|
||||
}, 30000);
|
||||
|
||||
// Scheduled Scans Functions
|
||||
function showScheduleModal() {
|
||||
$('#scheduleModal').modal('show');
|
||||
}
|
||||
|
||||
function loadScheduledScans() {
|
||||
fetch('/aiscanner/scheduled-scans/')
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
displayScheduledScans(data.scheduled_scans);
|
||||
} else {
|
||||
console.error('Failed to load scheduled scans:', data.error);
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error loading scheduled scans:', error);
|
||||
});
|
||||
}
|
||||
|
||||
function displayScheduledScans(scans) {
|
||||
const container = document.getElementById('scheduledScansContainer');
|
||||
|
||||
if (!scans || scans.length === 0) {
|
||||
container.innerHTML = '<p style="color: #94a3b8; text-align: center; padding: 20px;">No scheduled scans configured yet.</p>';
|
||||
return;
|
||||
}
|
||||
|
||||
let html = '<div class="scheduled-scans-grid">';
|
||||
|
||||
scans.forEach(scan => {
|
||||
const statusClass = scan.status === 'active' ? 'success' : scan.status === 'paused' ? 'warning' : 'danger';
|
||||
const nextRun = scan.next_run ? new Date(scan.next_run).toLocaleString() : 'Not scheduled';
|
||||
const lastRun = scan.last_run ? new Date(scan.last_run).toLocaleString() : 'Never';
|
||||
|
||||
html += `
|
||||
<div class="scheduled-scan-card">
|
||||
<div class="scheduled-scan-header">
|
||||
<h4>${scan.name}</h4>
|
||||
<span class="status-label ${statusClass}">${scan.status}</span>
|
||||
</div>
|
||||
<div class="scheduled-scan-info">
|
||||
<p><strong>Frequency:</strong> ${scan.frequency}</p>
|
||||
<p><strong>Scan Type:</strong> ${scan.scan_type}</p>
|
||||
<p><strong>Domains:</strong> ${scan.domains.join(', ')}</p>
|
||||
<p><strong>Next Run:</strong> ${nextRun}</p>
|
||||
<p><strong>Last Run:</strong> ${lastRun}</p>
|
||||
</div>
|
||||
<div class="scheduled-scan-actions">
|
||||
<button class="btn-xs btn-default" onclick="editScheduledScan(${scan.id})">
|
||||
<i class="fas fa-edit"></i> Edit
|
||||
</button>
|
||||
<button class="btn-xs btn-${scan.status === 'active' ? 'warning' : 'success'}"
|
||||
onclick="toggleScheduledScan(${scan.id})">
|
||||
<i class="fas fa-${scan.status === 'active' ? 'pause' : 'play'}"></i>
|
||||
${scan.status === 'active' ? 'Pause' : 'Activate'}
|
||||
</button>
|
||||
<button class="btn-xs btn-danger" onclick="deleteScheduledScan(${scan.id})">
|
||||
<i class="fas fa-trash"></i> Delete
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
});
|
||||
|
||||
html += '</div>';
|
||||
container.innerHTML = html;
|
||||
}
|
||||
|
||||
function saveScheduledScan() {
|
||||
const formData = new FormData(document.getElementById('scheduleForm'));
|
||||
const data = Object.fromEntries(formData);
|
||||
|
||||
// Get selected domains
|
||||
const selectedDomains = Array.from(document.querySelectorAll('input[name="domains"]:checked'))
|
||||
.map(cb => cb.value);
|
||||
|
||||
if (selectedDomains.length === 0) {
|
||||
alert('Please select at least one domain to scan.');
|
||||
return;
|
||||
}
|
||||
|
||||
data.domains = selectedDomains;
|
||||
|
||||
// Get notification emails
|
||||
const notificationEmails = document.getElementById('notificationEmails').value.split(',')
|
||||
.map(email => email.trim())
|
||||
.filter(email => email.length > 0);
|
||||
|
||||
data.notification_emails = notificationEmails;
|
||||
|
||||
fetch('/aiscanner/scheduled-scans/', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-CSRFToken': getCookie('csrftoken')
|
||||
},
|
||||
body: JSON.stringify(data)
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
$('#scheduleModal').modal('hide');
|
||||
loadScheduledScans();
|
||||
document.getElementById('scheduleForm').reset();
|
||||
} else {
|
||||
alert('Error: ' + data.error);
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error:', error);
|
||||
alert('Failed to save scheduled scan. Please try again.');
|
||||
});
|
||||
}
|
||||
|
||||
function editScheduledScan(id) {
|
||||
// Fetch scheduled scan details and populate form
|
||||
fetch(`/aiscanner/scheduled-scans/${id}/`)
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
populateScheduleForm(data.scheduled_scan);
|
||||
$('#scheduleModal').modal('show');
|
||||
} else {
|
||||
alert('Error: ' + data.error);
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error:', error);
|
||||
alert('Failed to load scheduled scan details.');
|
||||
});
|
||||
}
|
||||
|
||||
function populateScheduleForm(scan) {
|
||||
document.getElementById('scheduleId').value = scan.id;
|
||||
document.getElementById('scheduleName').value = scan.name;
|
||||
document.getElementById('frequency').value = scan.frequency;
|
||||
document.getElementById('scanType').value = scan.scan_type;
|
||||
document.getElementById('timeOfDay').value = scan.time_of_day;
|
||||
|
||||
if (scan.day_of_week !== null) {
|
||||
document.getElementById('dayOfWeek').value = scan.day_of_week;
|
||||
}
|
||||
|
||||
if (scan.day_of_month !== null) {
|
||||
document.getElementById('dayOfMonth').value = scan.day_of_month;
|
||||
}
|
||||
|
||||
// Select domains
|
||||
scan.domains.forEach(domain => {
|
||||
const checkbox = document.querySelector(`input[name="domains"][value="${domain}"]`);
|
||||
if (checkbox) {
|
||||
checkbox.checked = true;
|
||||
}
|
||||
});
|
||||
|
||||
// Set notification settings
|
||||
document.getElementById('emailNotifications').checked = scan.email_notifications;
|
||||
document.getElementById('notifyOnThreats').checked = scan.notify_on_threats;
|
||||
document.getElementById('notifyOnCompletion').checked = scan.notify_on_completion;
|
||||
document.getElementById('notifyOnFailure').checked = scan.notify_on_failure;
|
||||
|
||||
if (scan.notification_emails && scan.notification_emails.length > 0) {
|
||||
document.getElementById('notificationEmails').value = scan.notification_emails.join(', ');
|
||||
}
|
||||
|
||||
updateFrequencyOptions();
|
||||
}
|
||||
|
||||
function toggleScheduledScan(id) {
|
||||
fetch(`/aiscanner/scheduled-scans/${id}/toggle/`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-CSRFToken': getCookie('csrftoken')
|
||||
}
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
loadScheduledScans();
|
||||
} else {
|
||||
alert('Error: ' + data.error);
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error:', error);
|
||||
alert('Failed to toggle scheduled scan.');
|
||||
});
|
||||
}
|
||||
|
||||
function deleteScheduledScan(id) {
|
||||
if (confirm('Are you sure you want to delete this scheduled scan?')) {
|
||||
fetch(`/aiscanner/scheduled-scans/${id}/`, {
|
||||
method: 'DELETE',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-CSRFToken': getCookie('csrftoken')
|
||||
}
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
loadScheduledScans();
|
||||
} else {
|
||||
alert('Error: ' + data.error);
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error:', error);
|
||||
alert('Failed to delete scheduled scan.');
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function updateFrequencyOptions() {
|
||||
const frequency = document.getElementById('frequency').value;
|
||||
const dayOfWeekGroup = document.getElementById('dayOfWeekGroup');
|
||||
const dayOfMonthGroup = document.getElementById('dayOfMonthGroup');
|
||||
|
||||
// Hide all optional fields first
|
||||
dayOfWeekGroup.style.display = 'none';
|
||||
dayOfMonthGroup.style.display = 'none';
|
||||
|
||||
// Show relevant fields based on frequency
|
||||
if (frequency === 'weekly') {
|
||||
dayOfWeekGroup.style.display = 'block';
|
||||
} else if (frequency === 'monthly' || frequency === 'quarterly') {
|
||||
dayOfMonthGroup.style.display = 'block';
|
||||
}
|
||||
}
|
||||
|
||||
// Load scheduled scans when page loads
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
if (document.getElementById('scheduledScansContainer')) {
|
||||
loadScheduledScans();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<!-- Scheduled Scan Modal -->
|
||||
<div class="modal fade" id="scheduleModal" tabindex="-1" role="dialog" aria-labelledby="scheduleModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog modal-lg" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="scheduleModalLabel">Schedule New Scan</h5>
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
<form id="scheduleForm">
|
||||
<div class="modal-body">
|
||||
<input type="hidden" id="scheduleId" name="id" value="">
|
||||
|
||||
<div class="form-group">
|
||||
<label for="scheduleName">Schedule Name</label>
|
||||
<input type="text" class="form-control" id="scheduleName" name="name" required>
|
||||
<small class="form-text text-muted">Give this scheduled scan a descriptive name</small>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="form-group">
|
||||
<label for="frequency">Frequency</label>
|
||||
<select class="form-control" id="frequency" name="frequency" onchange="updateFrequencyOptions()" required>
|
||||
<option value="daily">Daily</option>
|
||||
<option value="weekly" selected>Weekly</option>
|
||||
<option value="monthly">Monthly</option>
|
||||
<option value="quarterly">Quarterly</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="form-group">
|
||||
<label for="scanType">Scan Type</label>
|
||||
<select class="form-control" id="scanType" name="scan_type" required>
|
||||
<option value="full">Full Scan</option>
|
||||
<option value="quick">Quick Scan</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-4">
|
||||
<div class="form-group">
|
||||
<label for="timeOfDay">Time of Day (UTC)</label>
|
||||
<input type="time" class="form-control" id="timeOfDay" name="time_of_day" value="02:00" required>
|
||||
<small class="form-text text-muted">Time in UTC timezone</small>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4" id="dayOfWeekGroup" style="display: none;">
|
||||
<div class="form-group">
|
||||
<label for="dayOfWeek">Day of Week</label>
|
||||
<select class="form-control" id="dayOfWeek" name="day_of_week">
|
||||
<option value="0">Monday</option>
|
||||
<option value="1">Tuesday</option>
|
||||
<option value="2">Wednesday</option>
|
||||
<option value="3">Thursday</option>
|
||||
<option value="4">Friday</option>
|
||||
<option value="5">Saturday</option>
|
||||
<option value="6" selected>Sunday</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4" id="dayOfMonthGroup" style="display: none;">
|
||||
<div class="form-group">
|
||||
<label for="dayOfMonth">Day of Month</label>
|
||||
<input type="number" class="form-control" id="dayOfMonth" name="day_of_month" min="1" max="31" value="1">
|
||||
<small class="form-text text-muted">1-31 (will adjust for shorter months)</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>Websites to Scan</label>
|
||||
<div class="checkbox-group" style="max-height: 200px; overflow-y: auto; border: 1px solid #ddd; padding: 10px; border-radius: 4px;">
|
||||
{% for website in websites %}
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" name="domains" value="{{ website.domain }}" id="domain_{{ forloop.counter }}">
|
||||
<label class="form-check-label" for="domain_{{ forloop.counter }}">
|
||||
{{ website.domain }}
|
||||
</label>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" id="emailNotifications" name="email_notifications" checked>
|
||||
<label class="form-check-label" for="emailNotifications">
|
||||
Enable Email Notifications
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="notificationEmails">Notification Email Addresses</label>
|
||||
<input type="text" class="form-control" id="notificationEmails" name="notification_emails" placeholder="email1@example.com, email2@example.com">
|
||||
<small class="form-text text-muted">Separate multiple emails with commas. Leave empty to use your account email.</small>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>Notification Preferences</label>
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" id="notifyOnThreats" name="notify_on_threats" checked>
|
||||
<label class="form-check-label" for="notifyOnThreats">
|
||||
Notify when threats are found
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" id="notifyOnCompletion" name="notify_on_completion">
|
||||
<label class="form-check-label" for="notifyOnCompletion">
|
||||
Notify when scan completes successfully
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" id="notifyOnFailure" name="notify_on_failure" checked>
|
||||
<label class="form-check-label" for="notifyOnFailure">
|
||||
Notify when scan fails
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</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="saveScheduledScan()">Save Schedule</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.scheduled-scans-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(350px, 1fr));
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.scheduled-scan-card {
|
||||
border: 1px solid #e8e9ff;
|
||||
border-radius: 8px;
|
||||
padding: 20px;
|
||||
background: white;
|
||||
}
|
||||
|
||||
.scheduled-scan-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.scheduled-scan-header h4 {
|
||||
margin: 0;
|
||||
color: #2f3640;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.scheduled-scan-info p {
|
||||
margin: 5px 0;
|
||||
color: #64748b;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.scheduled-scan-actions {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
margin-top: 15px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.checkbox-group {
|
||||
background: #f8f9fa;
|
||||
}
|
||||
|
||||
.form-check {
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
@@ -1,5 +1,5 @@
|
||||
from django.urls import path
|
||||
from . import views, api
|
||||
from . import views, api, scheduled_views
|
||||
|
||||
urlpatterns = [
|
||||
# Main AI Scanner pages
|
||||
@@ -18,6 +18,12 @@ urlpatterns = [
|
||||
path('platform-monitor-url/<str:scan_id>/', views.getPlatformMonitorUrl, name='aiScannerPlatformMonitorUrl'),
|
||||
path('platform-status/<str:scan_id>/', views.getPlatformScanStatus, name='aiScannerPlatformStatus'),
|
||||
|
||||
# Scheduled scans management
|
||||
path('scheduled-scans/', scheduled_views.scheduledScans, name='aiScannerScheduledScans'),
|
||||
path('scheduled-scans/<int:scan_id>/', scheduled_views.scheduledScanDetail, name='aiScannerScheduledScanDetail'),
|
||||
path('scheduled-scans/<int:scan_id>/toggle/', scheduled_views.toggleScheduledScan, name='aiScannerToggleScheduledScan'),
|
||||
path('scheduled-scans/<int:scan_id>/executions/', scheduled_views.scheduledScanExecutions, name='aiScannerScheduledScanExecutions'),
|
||||
|
||||
# Note: RESTful API endpoints are in /api/urls.py for external access
|
||||
|
||||
# Legacy API endpoints (for backward compatibility)
|
||||
|
||||
@@ -894,6 +894,63 @@ $cfg['Servers'][$i]['LogoutURL'] = 'phpmyadminsignin.php?logout';
|
||||
except:
|
||||
pass
|
||||
|
||||
# AI Scanner Scheduled Scans Tables
|
||||
try:
|
||||
cursor.execute('''
|
||||
CREATE TABLE `ai_scanner_scheduled_scans` (
|
||||
`id` integer AUTO_INCREMENT NOT NULL PRIMARY KEY,
|
||||
`admin_id` integer NOT NULL,
|
||||
`name` varchar(200) NOT NULL,
|
||||
`domains` longtext NOT NULL,
|
||||
`frequency` varchar(20) NOT NULL DEFAULT 'weekly',
|
||||
`scan_type` varchar(20) NOT NULL DEFAULT 'full',
|
||||
`time_of_day` time NOT NULL,
|
||||
`day_of_week` integer DEFAULT NULL,
|
||||
`day_of_month` integer DEFAULT NULL,
|
||||
`status` varchar(20) NOT NULL DEFAULT 'active',
|
||||
`last_run` datetime(6) DEFAULT NULL,
|
||||
`next_run` datetime(6) DEFAULT NULL,
|
||||
`created_at` datetime(6) NOT NULL,
|
||||
`updated_at` datetime(6) NOT NULL,
|
||||
`email_notifications` bool NOT NULL DEFAULT 1,
|
||||
`notification_emails` longtext NOT NULL DEFAULT '',
|
||||
`notify_on_threats` bool NOT NULL DEFAULT 1,
|
||||
`notify_on_completion` bool NOT NULL DEFAULT 0,
|
||||
`notify_on_failure` bool NOT NULL DEFAULT 1,
|
||||
KEY `ai_scanner_scheduled_scans_admin_id_idx` (`admin_id`),
|
||||
KEY `ai_scanner_scheduled_scans_status_next_run_idx` (`status`, `next_run`),
|
||||
CONSTRAINT `ai_scanner_scheduled_scans_admin_id_fk` FOREIGN KEY (`admin_id`)
|
||||
REFERENCES `loginSystem_administrator` (`id`) ON DELETE CASCADE
|
||||
)
|
||||
''')
|
||||
except:
|
||||
pass
|
||||
|
||||
try:
|
||||
cursor.execute('''
|
||||
CREATE TABLE `ai_scanner_scheduled_executions` (
|
||||
`id` integer AUTO_INCREMENT NOT NULL PRIMARY KEY,
|
||||
`scheduled_scan_id` integer NOT NULL,
|
||||
`execution_time` datetime(6) NOT NULL,
|
||||
`status` varchar(20) NOT NULL DEFAULT 'pending',
|
||||
`domains_scanned` longtext NOT NULL DEFAULT '',
|
||||
`total_scans` integer NOT NULL DEFAULT 0,
|
||||
`successful_scans` integer NOT NULL DEFAULT 0,
|
||||
`failed_scans` integer NOT NULL DEFAULT 0,
|
||||
`total_cost` decimal(10,6) NOT NULL DEFAULT 0.000000,
|
||||
`scan_ids` longtext NOT NULL DEFAULT '',
|
||||
`error_message` longtext DEFAULT NULL,
|
||||
`started_at` datetime(6) DEFAULT NULL,
|
||||
`completed_at` datetime(6) DEFAULT NULL,
|
||||
KEY `ai_scanner_scheduled_executions_scheduled_scan_id_idx` (`scheduled_scan_id`),
|
||||
KEY `ai_scanner_scheduled_executions_execution_time_idx` (`execution_time` DESC),
|
||||
CONSTRAINT `ai_scanner_scheduled_executions_scheduled_scan_id_fk` FOREIGN KEY (`scheduled_scan_id`)
|
||||
REFERENCES `ai_scanner_scheduled_scans` (`id`) ON DELETE CASCADE
|
||||
)
|
||||
''')
|
||||
except:
|
||||
pass
|
||||
|
||||
try:
|
||||
cursor.execute(
|
||||
'CREATE TABLE `loginSystem_acl` (`id` integer AUTO_INCREMENT NOT NULL PRIMARY KEY, `name` varchar(50) NOT NULL UNIQUE, `adminStatus` integer NOT NULL DEFAULT 0, `versionManagement` integer NOT NULL DEFAULT 0, `createNewUser` integer NOT NULL DEFAULT 0, `deleteUser` integer NOT NULL DEFAULT 0, `resellerCenter` integer NOT NULL DEFAULT 0, `changeUserACL` integer NOT NULL DEFAULT 0, `createWebsite` integer NOT NULL DEFAULT 0, `modifyWebsite` integer NOT NULL DEFAULT 0, `suspendWebsite` integer NOT NULL DEFAULT 0, `deleteWebsite` integer NOT NULL DEFAULT 0, `createPackage` integer NOT NULL DEFAULT 0, `deletePackage` integer NOT NULL DEFAULT 0, `modifyPackage` integer NOT NULL DEFAULT 0, `createDatabase` integer NOT NULL DEFAULT 0, `deleteDatabase` integer NOT NULL DEFAULT 0, `listDatabases` integer NOT NULL DEFAULT 0, `createNameServer` integer NOT NULL DEFAULT 0, `createDNSZone` integer NOT NULL DEFAULT 0, `deleteZone` integer NOT NULL DEFAULT 0, `addDeleteRecords` integer NOT NULL DEFAULT 0, `createEmail` integer NOT NULL DEFAULT 0, `deleteEmail` integer NOT NULL DEFAULT 0, `emailForwarding` integer NOT NULL DEFAULT 0, `changeEmailPassword` integer NOT NULL DEFAULT 0, `dkimManager` integer NOT NULL DEFAULT 0, `createFTPAccount` integer NOT NULL DEFAULT 0, `deleteFTPAccount` integer NOT NULL DEFAULT 0, `listFTPAccounts` integer NOT NULL DEFAULT 0, `createBackup` integer NOT NULL DEFAULT 0, `restoreBackup` integer NOT NULL DEFAULT 0, `addDeleteDestinations` integer NOT NULL DEFAULT 0, `scheduleBackups` integer NOT NULL DEFAULT 0, `remoteBackups` integer NOT NULL DEFAULT 0, `manageSSL` integer NOT NULL DEFAULT 0, `hostnameSSL` integer NOT NULL DEFAULT 0, `mailServerSSL` integer NOT NULL DEFAULT 0)')
|
||||
@@ -3076,6 +3133,13 @@ vmail
|
||||
command = """sed -i '/CyberCP/d' /etc/crontab"""
|
||||
Upgrade.executioner(command, command, 0, True)
|
||||
|
||||
# Ensure log directory exists for scheduled scans
|
||||
if not os.path.exists('/usr/local/lscp/logs'):
|
||||
try:
|
||||
os.makedirs('/usr/local/lscp/logs', mode=0o755)
|
||||
except:
|
||||
pass
|
||||
|
||||
if os.path.exists('/usr/local/lsws/conf/httpd.conf'):
|
||||
# Setup /usr/local/lsws/conf/httpd.conf to use new Logformat standard for better stats and accesslogs
|
||||
command = """sed -i "s|^LogFormat.*|LogFormat '%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\"' combined|g" /usr/local/lsws/conf/httpd.conf"""
|
||||
@@ -3109,6 +3173,7 @@ vmail
|
||||
0 0 * * 4 /usr/local/CyberCP/bin/python /usr/local/CyberCP/plogical/renew.py >/dev/null 2>&1
|
||||
7 0 * * * "/root/.acme.sh"/acme.sh --cron --home "/root/.acme.sh" > /dev/null
|
||||
*/3 * * * * if ! find /home/*/public_html/ -maxdepth 2 -type f -newer /usr/local/lsws/cgid -name '.htaccess' -exec false {} +; then /usr/local/lsws/bin/lswsctrl restart; fi
|
||||
* * * * * /usr/local/CyberCP/bin/python /usr/local/CyberCP/manage.py run_scheduled_scans >/usr/local/lscp/logs/scheduled_scans.log 2>&1
|
||||
"""
|
||||
|
||||
writeToFile = open(cronPath, 'w')
|
||||
@@ -3133,6 +3198,15 @@ vmail
|
||||
0 1 * * * /usr/local/CyberCP/bin/python /usr/local/CyberCP/IncBackups/IncScheduler.py '1 Day'
|
||||
0 0 */3 * * /usr/local/CyberCP/bin/python /usr/local/CyberCP/IncBackups/IncScheduler.py '3 Days'
|
||||
0 0 * * 0 /usr/local/CyberCP/bin/python /usr/local/CyberCP/IncBackups/IncScheduler.py '1 Week'
|
||||
"""
|
||||
writeToFile = open(cronPath, 'a')
|
||||
writeToFile.write(content)
|
||||
writeToFile.close()
|
||||
|
||||
# Add AI Scanner scheduled scans cron job if missing
|
||||
if data.find('run_scheduled_scans') == -1:
|
||||
content = """
|
||||
* * * * * /usr/local/CyberCP/bin/python /usr/local/CyberCP/manage.py run_scheduled_scans >/usr/local/lscp/logs/scheduled_scans.log 2>&1
|
||||
"""
|
||||
writeToFile = open(cronPath, 'a')
|
||||
writeToFile.write(content)
|
||||
@@ -3149,6 +3223,7 @@ vmail
|
||||
7 0 * * * "/root/.acme.sh"/acme.sh --cron --home "/root/.acme.sh" > /dev/null
|
||||
0 0 * * * /usr/local/CyberCP/bin/python /usr/local/CyberCP/IncBackups/IncScheduler.py Daily
|
||||
0 0 * * 0 /usr/local/CyberCP/bin/python /usr/local/CyberCP/IncBackups/IncScheduler.py Weekly
|
||||
* * * * * /usr/local/CyberCP/bin/python /usr/local/CyberCP/manage.py run_scheduled_scans >/usr/local/lscp/logs/scheduled_scans.log 2>&1
|
||||
"""
|
||||
writeToFile = open(cronPath, 'w')
|
||||
writeToFile.write(content)
|
||||
|
||||
Reference in New Issue
Block a user