File: //proc/self/root/usr/local/CyberCP/aiScanner/templates/aiScanner/scanner.html
{% extends "baseTemplate/index.html" %}
{% load i18n %}
{% block title %}
AI Security Scanner - CyberPanel
{% endblock %}
{% block header_scripts %}
<style>
/* AI Scanner Specific Styles */
.scanner-wrapper {
background: transparent;
padding: 20px;
}
.scanner-container {
max-width: 1400px;
margin: 0 auto;
}
/* Section Headers */
.scanner-section {
background: var(--bg-primary, white);
border-radius: 12px;
padding: 25px;
margin-bottom: 25px;
box-shadow: 0 2px 8px rgba(0,0,0,0.08);
border: 1px solid var(--border-primary, #e8e9ff);
}
.scanner-title {
font-size: 16px;
font-weight: 700;
color: var(--text-primary, #2f3640);
margin-bottom: 20px;
display: flex;
align-items: center;
gap: 10px;
text-transform: uppercase;
letter-spacing: 0.5px;
}
.scanner-title::before {
content: '';
width: 4px;
height: 24px;
background: #5b5fcf;
border-radius: 2px;
}
/* Alert Styles */
.scanner-alert {
border-radius: 12px;
padding: 20px;
margin-bottom: 20px;
border: 1px solid;
display: flex;
align-items: flex-start;
gap: 15px;
}
.scanner-alert-info {
background: var(--alert-info-bg, #e0f2fe);
border-color: var(--alert-info-border, #0ea5e9);
color: var(--alert-info-text, #0c4a6e);
}
.scanner-alert-success {
background: var(--alert-success-bg, #d1fae5);
border-color: var(--alert-success-border, #10b981);
color: var(--alert-success-text, #065f46);
}
.scanner-alert-warning {
background: var(--alert-warning-bg, #fef3c7);
border-color: var(--alert-warning-border, #f59e0b);
color: var(--alert-warning-text, #92400e);
}
.scanner-alert-danger {
background: var(--alert-danger-bg, #fee2e2);
border-color: var(--alert-danger-border, #ef4444);
color: var(--alert-danger-text, #991b1b);
}
.scanner-alert i {
font-size: 20px;
flex-shrink: 0;
}
.scanner-alert-content {
flex: 1;
}
.scanner-alert h4 {
margin: 0 0 8px 0;
font-size: 16px;
font-weight: 600;
}
.scanner-alert p {
margin: 0;
line-height: 1.6;
}
/* Stats Cards */
.stats-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 20px;
margin-bottom: 25px;
}
.stat-card {
background: var(--bg-primary, white);
border: 1px solid var(--border-primary, #e8e9ff);
border-radius: 12px;
padding: 25px;
text-align: center;
transition: all 0.3s ease;
}
.stat-card:hover {
transform: translateY(-2px);
box-shadow: 0 5px 15px rgba(88,86,214,0.1);
}
.stat-card-icon {
width: 56px;
height: 56px;
margin: 0 auto 15px;
border-radius: 12px;
display: flex;
align-items: center;
justify-content: center;
font-size: 24px;
color: white;
}
.stat-card-icon.success {
background: #10b981;
}
.stat-card-icon.info {
background: #3b82f6;
}
.stat-card-icon.warning {
background: #f59e0b;
}
.stat-card-icon.danger {
background: #ef4444;
}
.stat-card h2 {
font-size: 32px;
font-weight: 700;
margin: 0 0 8px 0;
color: var(--text-primary, #2f3640);
}
.stat-card p {
margin: 0;
color: var(--text-secondary, #64748b);
font-size: 14px;
font-weight: 500;
}
.stat-card small {
display: block;
margin-top: 8px;
color: var(--text-muted, #94a3b8);
font-size: 12px;
}
/* Button Styles */
.btn-primary {
background: #5856d6;
border: none;
padding: 12px 24px;
border-radius: 8px;
font-weight: 600;
color: white;
transition: all 0.3s ease;
cursor: pointer;
display: inline-flex;
align-items: center;
gap: 8px;
}
.btn-primary:hover {
background: #4644c0;
transform: translateY(-1px);
box-shadow: 0 4px 12px rgba(88,86,214,0.3);
}
.btn-primary:disabled {
background: #94a3b8;
cursor: not-allowed;
transform: none;
box-shadow: none;
}
.btn-default {
background: var(--bg-secondary, #f8f9ff);
border: 1px solid var(--border-primary, #e8e9ff);
padding: 10px 20px;
border-radius: 8px;
font-weight: 600;
color: #5856d6;
transition: all 0.3s ease;
cursor: pointer;
display: inline-flex;
align-items: center;
gap: 8px;
}
.btn-default:hover {
background: var(--border-primary, #e8e9ff);
color: #4644c0;
}
.btn-xs {
padding: 6px 12px;
font-size: 12px;
}
.btn-lg {
padding: 16px 32px;
font-size: 16px;
}
/* Form Styles */
.form-group {
margin-bottom: 20px;
}
.form-group label {
display: block;
margin-bottom: 8px;
font-weight: 600;
color: var(--text-primary, #2f3640);
font-size: 14px;
}
.form-control {
width: 100%;
padding: 10px 15px;
border: 1px solid var(--border-primary, #e8e9ff);
border-radius: 8px;
font-size: 14px;
transition: all 0.3s ease;
background: var(--bg-primary, white);
color: var(--text-primary, #2f3640);
}
.form-control:focus {
outline: none;
border-color: #5856d6;
box-shadow: 0 0 0 3px rgba(88,86,214,0.1);
}
/* Table Styles */
.table-container {
background: var(--bg-primary, white);
border-radius: 12px;
overflow: hidden;
box-shadow: 0 2px 8px rgba(0,0,0,0.08);
}
.scanner-table {
width: 100%;
border-collapse: collapse;
}
.scanner-table th {
background: var(--bg-secondary, #f8f9ff);
padding: 15px;
text-align: left;
font-size: 12px;
font-weight: 700;
color: var(--text-secondary, #64748b);
text-transform: uppercase;
letter-spacing: 0.8px;
border-bottom: 2px solid var(--border-primary, #e8e9ff);
}
.scanner-table td {
padding: 15px;
border-bottom: 1px solid var(--border-light, #f0f0ff);
color: var(--text-primary, #2f3640);
font-size: 14px;
}
.scanner-table tr:hover {
background: var(--bg-secondary, #f8f9ff);
}
/* Progress Bar */
.progress {
background: var(--border-primary, #e8e9ff);
height: 8px;
border-radius: 4px;
overflow: hidden;
margin: 0;
}
.progress-bar {
background: #5856d6;
height: 100%;
transition: width 0.3s ease;
position: relative;
}
.progress-bar-animated {
background-image: linear-gradient(45deg, rgba(255,255,255,.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,.15) 50%, rgba(255,255,255,.15) 75%, transparent 75%, transparent);
background-size: 1rem 1rem;
animation: progress-bar-stripes 1s linear infinite;
}
@keyframes progress-bar-stripes {
0% { background-position: 1rem 0; }
100% { background-position: 0 0; }
}
/* Status Labels */
.status-label {
display: inline-block;
padding: 4px 12px;
border-radius: 6px;
font-size: 12px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.5px;
}
.status-label.success {
background: var(--alert-success-bg, #d1fae5);
color: var(--alert-success-border, #10b981);
}
.status-label.warning {
background: var(--alert-warning-bg, #fef3c7);
color: var(--alert-warning-border, #f59e0b);
}
.status-label.danger {
background: var(--alert-danger-bg, #fee2e2);
color: var(--alert-danger-border, #ef4444);
}
.status-label.info {
background: var(--alert-info-bg, #e0f2fe);
color: var(--alert-info-border, #3b82f6);
}
/* Modal Styles */
.modal-backdrop {
background: rgba(0,0,0,0.5);
backdrop-filter: blur(2px);
}
.modal-content {
border: none;
border-radius: 16px;
box-shadow: 0 8px 40px rgba(0,0,0,0.18);
}
.modal-header {
background: var(--bg-secondary, #f8f9ff);
border-bottom: 1px solid var(--border-primary, #e8e9ff);
border-radius: 16px 16px 0 0;
padding: 20px 25px;
}
.modal-title {
font-size: 18px;
font-weight: 700;
color: var(--text-primary, #2f3640);
display: flex;
align-items: center;
gap: 10px;
}
.modal-body {
padding: 25px;
background: var(--bg-primary, white);
color: var(--text-primary, #2f3640);
}
.modal-footer {
background: var(--bg-secondary, #f8f9ff);
border-top: 1px solid var(--border-primary, #e8e9ff);
border-radius: 0 0 16px 16px;
padding: 15px 25px;
}
/* Responsive */
@media (max-width: 768px) {
.stats-grid {
grid-template-columns: 1fr;
}
.scanner-wrapper {
padding: 15px;
}
.scanner-section {
padding: 20px;
}
.scanner-table {
font-size: 12px;
}
.scanner-table th,
.scanner-table td {
padding: 10px;
}
}
</style>
{% endblock %}
{% block content %}
<div class="scanner-wrapper">
<div class="scanner-container">
<!-- Page Header -->
<div class="scanner-section">
<h2 class="scanner-title">
<i class="fas fa-shield-alt" style="color: #5856d6;"></i>
AI Security Scanner
</h2>
<!-- Error Display -->
{% if error %}
<div class="scanner-alert scanner-alert-danger">
<i class="fas fa-exclamation-triangle"></i>
<div class="scanner-alert-content">
<p>{{ error }}</p>
</div>
</div>
{% endif %}
<!-- Messages -->
{% if messages %}
{% for message in messages %}
<div class="scanner-alert scanner-alert-{{ message.tags }}">
<i class="fas fa-info-circle"></i>
<div class="scanner-alert-content">
<p>{{ message }}</p>
</div>
</div>
{% endfor %}
{% endif %}
</div>
<!-- Payment Setup Section -->
{% if not is_payment_configured and not vps_info.is_vps|default:False|default:False %}
<div class="scanner-section">
<div class="scanner-alert scanner-alert-info">
<i class="fas fa-credit-card"></i>
<div class="scanner-alert-content">
<h4>Setup Required</h4>
<p>Configure your payment method to start scanning WordPress sites for malware and security vulnerabilities. Your card will be charged <strong>$10 initially</strong> to fund your scanning account.</p>
{% if pricing_data.success and pricing_data.plan %}
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 30px; margin: 20px 0;">
<div>
<h6 style="font-weight: 600; margin-bottom: 10px;">Current Pricing:</h6>
<ul style="list-style: none; padding: 0; margin: 0;">
<li style="margin-bottom: 5px;"><strong>{{ pricing_data.plan.name }}</strong></li>
<li style="margin-bottom: 5px; color: var(--text-secondary, #64748b);">Max Files: {{ pricing_data.plan.limits.max_files_per_scan|floatformat:0 }}</li>
<li style="color: var(--text-secondary, #64748b);">Max File Size: {{ pricing_data.plan.limits.max_file_size_mb }}MB</li>
</ul>
</div>
<div>
<h6 style="font-weight: 600; margin-bottom: 10px;">Example Costs:</h6>
<ul style="list-style: none; padding: 0; margin: 0;">
<li style="margin-bottom: 5px; color: var(--text-secondary, #64748b);">Small Scan: ${{ pricing_data.plan.example_costs.small_scan.estimated_cost_usd }}</li>
<li style="margin-bottom: 5px; color: var(--text-secondary, #64748b);">Medium Scan: ${{ pricing_data.plan.example_costs.medium_scan.estimated_cost_usd }}</li>
<li style="color: var(--text-secondary, #64748b);">Large Scan: ${{ pricing_data.plan.example_costs.large_scan.estimated_cost_usd }}</li>
</ul>
</div>
</div>
{% endif %}
<button type="button" class="btn-primary btn-lg" onclick="setupPayment()">
<i class="fas fa-credit-card"></i> Setup Payment ($10 Initial Charge)
</button>
</div>
</div>
</div>
{% endif %}
<!-- VPS Free Scans Notice -->
{% if vps_info.is_vps|default:False|default:False and vps_info.has_free_scans|default:False|default:False %}
<div class="scanner-section">
<div class="scanner-alert scanner-alert-success">
<i class="fas fa-gift"></i>
<div class="scanner-alert-content">
<h4>VPS Free Scans Available</h4>
<p>Great news! Your CyberPanel VPS hosting includes <strong>{{ vps_info.free_scans_available|default:0 }} free AI security scans</strong> this month ({{ vps_info.scans_used_this_month|default:0 }}/{{ vps_info.free_scans_per_month|default:0 }} used).</p>
{% if not is_payment_configured %}
<p style="margin-top: 10px;"><small style="color: var(--text-secondary, #64748b);">You can still add a payment method below for additional scans beyond your free allowance.</small></p>
{% endif %}
</div>
</div>
</div>
{% elif vps_info.is_vps|default:False|default:False and not vps_info.has_free_scans|default:False|default:True %}
<div class="scanner-section">
<div class="scanner-alert scanner-alert-warning">
<i class="fas fa-exclamation-triangle"></i>
<div class="scanner-alert-content">
<h4>Free Scans Exhausted</h4>
<p>You've used all {{ vps_info.free_scans_per_month|default:0 }} free AI security scans for this month. Setup payment to continue scanning.</p>
</div>
</div>
</div>
{% endif %}
<!-- Scanner Dashboard -->
{% if is_payment_configured or vps_info.is_vps|default:False|default:False %}
<div class="stats-grid">
<div class="stat-card">
<div class="stat-card-icon success">
<i class="fas fa-wallet"></i>
</div>
{% if vps_info.is_vps|default:False|default:False and vps_info.has_free_scans|default:False|default:False %}
<h2>{{ vps_info.free_scans_available|default:0 }}</h2>
<p>Free Scans Available</p>
{% if is_payment_configured %}
<small>Plus ${{ current_balance|floatformat:4 }} credit</small>
{% endif %}
{% else %}
<h2>${{ current_balance|floatformat:4 }}</h2>
<p>Available Credit</p>
{% endif %}
{% if is_payment_configured %}
<button class="btn-default btn-xs" style="margin-top: 10px;" onclick="refreshBalance()">
<i class="fas fa-sync"></i> Refresh
</button>
{% endif %}
</div>
<div class="stat-card">
<div class="stat-card-icon info">
<i class="fas fa-chart-line"></i>
</div>
<h2>{{ recent_scans|length }}</h2>
<p>Recent Scans</p>
<small>Last 30 Days</small>
</div>
<div class="stat-card">
<div class="stat-card-icon warning">
<i class="fas fa-exclamation-triangle"></i>
</div>
<h2>
{% for scan in recent_scans %}{% if scan.status == 'completed' %}{{ scan.issues_found|add:0 }}{% endif %}{% endfor %}
</h2>
<p>Issues Found</p>
<small>Total Issues</small>
</div>
</div>
<!-- Payment Methods Section -->
<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-credit-card" style="color: #5856d6;"></i>
Payment Methods
</h3>
{% if not vps_info.is_vps|default:False or is_payment_configured %}
<button class="btn-primary btn-xs" onclick="addPaymentMethod()">
<i class="fas fa-plus"></i> Add Payment Method
</button>
{% else %}
<button class="btn-default btn-xs" onclick="addPaymentMethod()">
<i class="fas fa-plus"></i> Add Payment (Optional)
</button>
{% endif %}
</div>
{% if vps_info.is_vps|default:False and not is_payment_configured %}
<p style="color: var(--text-secondary, #64748b); margin-bottom: 15px;">
<i class="fas fa-info-circle"></i>
Payment setup is optional for VPS users. You have {{ vps_info.free_scans_available|default:0 }} free scans available.
</p>
{% else %}
<p style="color: var(--text-secondary, #64748b); margin-bottom: 15px;">
<i class="fas fa-info-circle"></i>
Your account uses automatic billing. Add multiple payment methods for backup security.
</p>
{% endif %}
<div id="paymentMethodsList">
{% if is_payment_configured %}
<p style="color: var(--text-muted, #94a3b8);">Payment methods will be managed through the platform.</p>
{% else %}
<p style="color: var(--text-muted, #94a3b8);">No payment methods configured yet.</p>
{% endif %}
</div>
</div>
<!-- Scan Form -->
<div class="scanner-section">
<h3 class="scanner-title">
<i class="fas fa-search" style="color: #5856d6;"></i>
Start New Scan
</h3>
<form id="scanForm">
<div style="display: grid; grid-template-columns: 2fr 1fr auto; gap: 20px; align-items: end;">
<div class="form-group" style="margin: 0;">
<label for="scanDomain">Select Website:</label>
<select class="form-control" id="scanDomain" name="domain" required>
<option value="">Choose a website...</option>
{% for website in websites %}
<option value="{{ website.domain }}">{{ website.domain }}</option>
{% endfor %}
</select>
</div>
<div class="form-group" style="margin: 0;">
<label for="scanType">Scan Type:</label>
<select class="form-control" id="scanType" name="scan_type">
<option value="full">Full Scan (Recommended)</option>
<option value="quick">Quick Scan</option>
</select>
</div>
<div>
{% if vps_info.is_vps|default:False and not vps_info.has_free_scans|default:False and not is_payment_configured %}
<button type="button" class="btn-primary" disabled style="width: 100%;">
<i class="fas fa-lock"></i> Free Scans Exhausted
</button>
{% else %}
<button type="submit" class="btn-primary" id="scanButton" style="width: 100%;">
{% if vps_info.is_vps|default:False|default:False and vps_info.has_free_scans|default:False|default:False %}
<i class="fas fa-search"></i> Start Free Scan
{% else %}
<i class="fas fa-search"></i> Start Scan
{% endif %}
</button>
{% endif %}
</div>
</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 -->
{% if recent_scans and is_payment_configured or recent_scans and 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-history" style="color: #5856d6;"></i>
Recent Scans
</h3>
<button class="btn-default btn-xs" onclick="refreshScanHistory()">
<i class="fas fa-sync"></i> Refresh
</button>
</div>
<div class="table-container">
<table class="scanner-table" id="scanHistoryTable">
<thead>
<tr>
<th>Date</th>
<th>Domain</th>
<th>Type</th>
<th>Status</th>
<th>Progress</th>
<th>Files</th>
<th>Issues</th>
<th>Cost</th>
<th>Actions</th>
</tr>
</thead>
<tbody id="scanHistoryBody">
{% for scan in recent_scans %}
<tr data-scan-id="{{ scan.scan_id }}">
<td>{{ scan.started_at|date:"M d, Y H:i" }}</td>
<td>{{ scan.domain }}</td>
<td>
<span class="status-label info">{{ scan.get_scan_type_display }}</span>
</td>
<td class="scan-status">
{% if scan.status == 'completed' %}
<span class="status-label success">Completed</span>
{% elif scan.status == 'running' %}
<span class="status-label warning">Running</span>
{% elif scan.status == 'failed' %}
<span class="status-label danger">Failed</span>
{% elif scan.status == 'pending' %}
<span class="status-label info">Pending</span>
{% endif %}
</td>
<td class="scan-progress">
{% if scan.status == 'running' or scan.status == 'pending' %}
<div class="progress" style="height: 20px;">
<div class="progress-bar progress-bar-animated"
role="progressbar" style="width: 0%" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100">
<span class="progress-text" style="position: absolute; width: 100%; text-align: center; line-height: 20px; color: var(--text-primary, #2f3640); font-size: 12px;">0%</span>
</div>
</div>
{% else %}
<span style="color: #10b981; font-weight: 600;">100%</span>
{% endif %}
</td>
<td class="scan-files">{{ scan.files_scanned|default:"-" }}</td>
<td class="scan-issues">
{% if scan.issues_found > 0 %}
<span style="color: #ef4444; font-weight: 700;">{{ scan.issues_found }}</span>
{% else %}
<span style="color: #10b981;">{{ scan.issues_found }}</span>
{% endif %}
</td>
<td>
{% if scan.cost_usd %}
${{ scan.cost_usd|floatformat:4 }}
{% else %}
-
{% endif %}
</td>
<td>
<div style="display: flex; gap: 5px;">
<button class="btn-default btn-xs" onclick="viewScanDetails('{{ scan.scan_id }}')">
<i class="fas fa-eye"></i> View
</button>
{% if scan.status == 'completed' %}
<button class="btn-primary btn-xs" onclick="viewOnPlatform('{{ scan.scan_id }}')" title="View detailed AI analysis on platform">
<i class="fas fa-external-link-alt"></i> AI Analysis
</button>
{% endif %}
</div>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
{% endif %}
</div>
</div>
<!-- Scan Details Modal -->
<div class="modal fade" id="scanDetailsModal" tabindex="-1" role="dialog">
<div class="modal-dialog modal-lg" role="document">
<div class="modal-content">
<div class="modal-header">
<h4 class="modal-title">
<i class="fas fa-search"></i> Scan Results
</h4>
<button type="button" class="close" data-dismiss="modal" style="font-size: 24px;">×</button>
</div>
<div class="modal-body" id="scanDetailsContent">
<!-- Real-time Progress Section -->
<div id="progressSection" style="display: none;">
<div class="row">
<div class="col-md-12">
<h5>
<span id="scanPhase" class="status-label info">Loading...</span>
<button class="btn-default btn-xs pull-right" onclick="refreshScanProgress()">
<i class="fas fa-sync-alt"></i> Refresh
</button>
</h5>
</div>
</div>
<!-- Progress Bar -->
<div class="row" style="margin-bottom: 15px;">
<div class="col-md-12">
<div class="progress" style="height: 25px;">
<div id="scanProgressBar" class="progress-bar progress-bar-animated"
role="progressbar" style="width: 0%" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100">
<span id="scanProgressText" style="position: absolute; width: 100%; text-align: center; line-height: 25px; color: var(--text-primary, #2f3640); font-weight: 600;">0%</span>
</div>
</div>
</div>
</div>
<!-- Current Activity -->
<div class="row" style="margin-bottom: 15px;">
<div class="col-md-6">
<strong>Current Activity:</strong>
<br><span id="currentActivity" style="color: var(--text-secondary, #64748b);">Initializing...</span>
</div>
<div class="col-md-6">
<strong>Current File:</strong>
<br><code id="currentFile" style="color: #5856d6;">N/A</code>
</div>
</div>
<!-- Statistics -->
<div class="row">
<div class="col-md-3">
<div style="background: var(--alert-info-bg, #e0f2fe); border-radius: 8px; padding: 15px; text-align: center;">
<h4 id="filesDiscovered" style="margin: 0 0 5px 0; color: var(--alert-info-border, #3b82f6);">0</h4>
<small style="color: var(--text-secondary, #64748b);">Files Discovered</small>
</div>
</div>
<div class="col-md-3">
<div style="background: var(--bg-secondary, #e8e6ff); border-radius: 8px; padding: 15px; text-align: center;">
<h4 id="filesScanned" style="margin: 0 0 5px 0; color: #5856d6;">0</h4>
<small style="color: var(--text-secondary, #64748b);">Files Scanned</small>
</div>
</div>
<div class="col-md-3">
<div style="background: var(--alert-warning-bg, #fef3c7); border-radius: 8px; padding: 15px; text-align: center;">
<h4 id="filesRemaining" style="margin: 0 0 5px 0; color: var(--alert-warning-border, #f59e0b);">0</h4>
<small style="color: var(--text-secondary, #64748b);">Files Remaining</small>
</div>
</div>
<div class="col-md-3">
<div style="background: var(--alert-danger-bg, #fee2e2); border-radius: 8px; padding: 15px; text-align: center;">
<h4 id="threatsFound" style="margin: 0 0 5px 0; color: var(--alert-danger-border, #ef4444);">0</h4>
<small style="color: var(--text-secondary, #64748b);">Threats Found</small>
</div>
</div>
</div>
<!-- Threat Breakdown -->
<div class="row">
<div class="col-md-12">
<strong>Threat Breakdown:</strong>
<span class="status-label danger" style="margin-left: 10px;">Critical: <span id="criticalThreats">0</span></span>
<span class="status-label warning" style="margin-left: 10px;">High: <span id="highThreats">0</span></span>
</div>
</div>
<!-- Last Updated -->
<div class="row" style="margin-top: 15px;">
<div class="col-md-12">
<small style="color: var(--text-muted, #94a3b8);">Last updated: <span id="lastUpdated">Never</span></small>
</div>
</div>
</div>
<!-- Loading Section -->
<div id="loadingSection" class="text-center">
<i class="fas fa-spinner fa-spin fa-2x"></i>
<p>Loading scan details...</p>
</div>
<!-- Completed Scan Results Section -->
<div id="completedSection" style="display: none;">
<!-- This will be populated with completed scan results -->
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn-default" data-dismiss="modal">Close</button>
</div>
</div>
</div>
</div>
<script>
function setupPayment() {
// Show loading
const button = event.target;
const originalText = button.innerHTML;
button.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Setting up...';
button.disabled = true;
fetch('/aiscanner/setup-payment/', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRFToken': getCookie('csrftoken')
}
})
.then(response => response.json())
.then(data => {
if (data.success) {
// Redirect to payment setup
window.location.href = data.payment_url;
} else {
alert('Error: ' + data.error);
button.innerHTML = originalText;
button.disabled = false;
}
})
.catch(error => {
console.error('Error:', error);
alert('Failed to setup payment. Please try again.');
button.innerHTML = originalText;
button.disabled = false;
});
}
const scanForm = document.getElementById('scanForm');
if (scanForm) {
scanForm.addEventListener('submit', function(e) {
e.preventDefault();
const domain = document.getElementById('scanDomain').value;
const scanType = document.getElementById('scanType').value;
const button = document.getElementById('scanButton');
if (!domain) {
alert('Please select a website to scan.');
return;
}
// Show loading
const originalText = button.innerHTML;
button.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Starting...';
button.disabled = true;
fetch('/aiscanner/start-scan/', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRFToken': getCookie('csrftoken')
},
body: JSON.stringify({
domain: domain,
scan_type: scanType
})
})
.then(response => response.json())
.then(data => {
if (data.success) {
alert('Scan started successfully! You will be notified when it completes.');
// Reset form and refresh page
document.getElementById('scanForm').reset();
setTimeout(() => location.reload(), 2000);
} else {
alert('Error: ' + data.error);
}
button.innerHTML = originalText;
button.disabled = false;
})
.catch(error => {
console.error('Error:', error);
alert('Failed to start scan. Please try again.');
button.innerHTML = originalText;
button.disabled = false;
});
});
}
function refreshScanHistory() {
fetch('/aiscanner/scan-history/')
.then(response => response.json())
.then(data => {
if (data.success) {
updateScanHistoryTable(data.scans);
}
})
.catch(error => console.error('Error refreshing scan history:', error));
}
function updateScanHistoryTable(scans) {
const tbody = document.getElementById('scanHistoryBody');
if (!tbody) return;
tbody.innerHTML = '';
scans.forEach(scan => {
const statusBadge = getStatusBadge(scan.status);
const issuesText = scan.issues_found > 0 ?
`<span style="color: #ef4444; font-weight: 700;">${scan.issues_found}</span>` :
`<span style="color: #10b981;">${scan.issues_found}</span>`;
const actionsHtml = `<div style="display: flex; gap: 5px;">
<button class="btn-default btn-xs" onclick="viewScanDetails('${scan.scan_id}')">
<i class="fas fa-eye"></i> View
</button>
${scan.status === 'completed' ? `
<button class="btn-primary btn-xs" onclick="viewOnPlatform('${scan.scan_id}')" title="View detailed AI analysis on platform">
<i class="fas fa-external-link-alt"></i> AI Analysis
</button>
` : ''}
</div>`;
const progressHtml = (scan.status === 'running' || scan.status === 'pending') ?
`<div class="progress" style="height: 20px;">
<div class="progress-bar progress-bar-animated"
role="progressbar" style="width: 0%" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100">
<span class="progress-text" style="position: absolute; width: 100%; text-align: center; line-height: 20px; color: #2f3640; font-size: 12px;">0%</span>
</div>
</div>` : '<span style="color: #10b981; font-weight: 600;">100%</span>';
const row = `
<tr data-scan-id="${scan.scan_id}">
<td>${new Date(scan.started_at).toLocaleDateString()}</td>
<td>${scan.domain}</td>
<td><span class="status-label info">${scan.scan_type}</span></td>
<td class="scan-status">${statusBadge}</td>
<td class="scan-progress">${progressHtml}</td>
<td class="scan-files">${scan.files_scanned || '-'}</td>
<td class="scan-issues">${issuesText}</td>
<td>${scan.cost_usd ? '$' + scan.cost_usd.toFixed(4) : '-'}</td>
<td>${actionsHtml}</td>
</tr>
`;
tbody.innerHTML += row;
});
}
function getStatusBadge(status) {
const badges = {
'completed': '<span class="status-label success">Completed</span>',
'running': '<span class="status-label warning">Running</span>',
'failed': '<span class="status-label danger">Failed</span>',
'pending': '<span class="status-label info">Pending</span>'
};
return badges[status] || '<span class="status-label info">Unknown</span>';
}
// Global scan monitor variables
let currentScanId = null;
let scanMonitorInterval = null;
function viewScanDetails(scanId) {
currentScanId = scanId;
const modal = document.getElementById('scanDetailsModal');
// Reset sections
document.getElementById('loadingSection').style.display = 'block';
document.getElementById('progressSection').style.display = 'none';
document.getElementById('completedSection').style.display = 'none';
$('#scanDetailsModal').modal('show');
// Check if scan has real-time status updates
fetchLiveProgress(scanId);
}
function viewOnPlatform(scanId) {
// Fetch the platform monitor URL
fetch(`/aiscanner/platform-monitor-url/${scanId}/`)
.then(response => response.json())
.then(data => {
if (data.success && data.monitor_url) {
// Open in new tab
window.open(data.monitor_url, '_blank');
} else {
alert('Error: ' + (data.error || 'Could not get platform URL'));
}
})
.catch(error => {
console.error('Error:', error);
alert('Failed to get platform URL. Please try again.');
});
}
function fetchLiveProgress(scanId) {
console.log(`[AI Scanner] Fetching live progress for scan: ${scanId}`);
fetch(`/api/ai-scanner/scan/${scanId}/live-progress`)
.then(response => response.json())
.then(data => {
console.log(`[AI Scanner] Live progress response:`, data);
if (data.success) {
if (data.is_active) {
console.log(`[AI Scanner] Scan is active (${data.phase}), showing progress section`);
// Show real-time progress section
showProgressSection(data);
startProgressMonitoring(scanId);
} else {
console.log(`[AI Scanner] Scan is completed, fetching full results`);
// Scan is completed, show full results
fetchCompletedScanDetails(scanId);
}
} else {
// Check if scan exists but no status yet
if (data.scan_exists && data.scan_status === 'running') {
console.log(`[AI Scanner] Scan is running but no status updates yet, retrying...`);
// Show loading state and retry in a few seconds
setTimeout(() => {
fetchLiveProgress(scanId);
}, 3000);
} else {
console.log(`[AI Scanner] No live status found, falling back to completed results`);
// No live status found, try fetching completed scan details
fetchCompletedScanDetails(scanId);
}
}
})
.catch(error => {
console.error('[AI Scanner] Error fetching live progress:', error);
fetchCompletedScanDetails(scanId);
});
}
function showProgressSection(data) {
document.getElementById('loadingSection').style.display = 'none';
document.getElementById('progressSection').style.display = 'block';
document.getElementById('completedSection').style.display = 'none';
updateProgressUI(data);
}
function updateProgressUI(data) {
// Update progress bar
const progressBar = document.getElementById('scanProgressBar');
const progressText = document.getElementById('scanProgressText');
progressBar.style.width = data.progress + '%';
progressBar.setAttribute('aria-valuenow', data.progress);
progressText.textContent = data.progress + '%';
// Update phase
const phaseElement = document.getElementById('scanPhase');
phaseElement.textContent = formatPhase(data.phase);
phaseElement.className = `status-label ${getPhaseColor(data.phase)}`;
// Update activity and file info
document.getElementById('currentActivity').textContent = data.activity_description || 'Processing...';
document.getElementById('currentFile').textContent = data.current_file || 'N/A';
// Update counters
document.getElementById('filesDiscovered').textContent = data.files_discovered;
document.getElementById('filesScanned').textContent = data.files_scanned;
document.getElementById('filesRemaining').textContent = data.files_remaining;
document.getElementById('threatsFound').textContent = data.threats_found;
document.getElementById('criticalThreats').textContent = data.critical_threats;
document.getElementById('highThreats').textContent = data.high_threats;
// Update timestamp
document.getElementById('lastUpdated').textContent = new Date(data.last_updated).toLocaleTimeString();
}
function startProgressMonitoring(scanId) {
console.log(`[AI Scanner] Starting progress monitoring for scan: ${scanId}`);
// Clear any existing interval
if (scanMonitorInterval) {
clearInterval(scanMonitorInterval);
}
// Poll every 3 seconds
scanMonitorInterval = setInterval(() => {
fetch(`/api/ai-scanner/scan/${scanId}/live-progress`)
.then(response => response.json())
.then(data => {
if (data.success) {
if (data.is_active) {
console.log(`[AI Scanner] Progress update: Phase="${data.phase}" Progress=${data.progress}% Activity="${data.activity_description || 'N/A'}" Files=${data.files_scanned}/${data.files_discovered}`);
updateProgressUI(data);
} else {
console.log(`[AI Scanner] Scan completed, stopping monitoring`);
// Scan completed, stop monitoring and show results
stopProgressMonitoring();
fetchCompletedScanDetails(scanId);
}
}
})
.catch(error => {
console.error('[AI Scanner] Error in progress monitoring:', error);
});
}, 3000);
}
function stopProgressMonitoring() {
if (scanMonitorInterval) {
clearInterval(scanMonitorInterval);
scanMonitorInterval = null;
}
}
function refreshScanProgress() {
if (currentScanId) {
fetchLiveProgress(currentScanId);
}
}
function fetchCompletedScanDetails(scanId) {
console.log(`[AI Scanner] Fetching completed scan details for: ${scanId}`);
fetch(`/aiscanner/scan-details/${scanId}/`)
.then(response => {
console.log(`[AI Scanner] Scan details response status: ${response.status}`);
return response.json();
})
.then(data => {
console.log(`[AI Scanner] Scan details data:`, data);
if (data.success) {
displayCompletedScanDetails(data.scan);
} else {
console.error(`[AI Scanner] Scan details error: ${data.error}`);
document.getElementById('loadingSection').style.display = 'none';
document.getElementById('progressSection').style.display = 'none';
document.getElementById('completedSection').style.display = 'block';
document.getElementById('completedSection').innerHTML =
`<div class="scanner-alert scanner-alert-danger">
<i class="fas fa-exclamation-triangle"></i>
<div class="scanner-alert-content">
<p>Error: ${data.error}</p>
</div>
</div>`;
}
})
.catch(error => {
console.error('[AI Scanner] Fetch error:', error);
document.getElementById('loadingSection').style.display = 'none';
document.getElementById('progressSection').style.display = 'none';
document.getElementById('completedSection').style.display = 'block';
document.getElementById('completedSection').innerHTML =
`<div class="scanner-alert scanner-alert-danger">
<i class="fas fa-exclamation-triangle"></i>
<div class="scanner-alert-content">
<p>Failed to load scan details. Please try again.</p>
</div>
</div>`;
});
}
function displayCompletedScanDetails(scan) {
console.log(`[AI Scanner] Displaying completed scan details:`, scan);
document.getElementById('loadingSection').style.display = 'none';
document.getElementById('progressSection').style.display = 'none';
document.getElementById('completedSection').style.display = 'block';
let findingsHtml = '';
if (scan.findings && scan.findings.length > 0) {
findingsHtml = `
<h5 style="margin: 20px 0 15px; font-weight: 600;">Security Issues Found (${scan.findings.length})</h5>
<div style="display: flex; flex-direction: column; gap: 10px;">
`;
scan.findings.forEach(finding => {
const severityClass = finding.severity === 'high' ? 'danger' :
finding.severity === 'medium' ? 'warning' : 'info';
findingsHtml += `
<div style="background: var(--bg-primary, white); border: 1px solid var(--border-primary, #e8e9ff); border-radius: 8px; padding: 15px;">
<h6 style="margin: 0 0 10px 0; display: flex; align-items: center; gap: 10px; color: var(--text-primary, #2f3640);">
<span class="status-label ${severityClass}" style="text-transform: uppercase;">${finding.severity}</span>
${finding.title}
</h6>
<p style="margin: 0 0 10px 0; color: var(--text-secondary, #64748b);">${finding.description}</p>
${finding.file ? `<small style="color: var(--text-muted, #94a3b8);">File: ${finding.file}</small>` : ''}
</div>
`;
});
findingsHtml += '</div>';
} else {
findingsHtml = `<div class="scanner-alert scanner-alert-success">
<i class="fas fa-check-circle"></i>
<div class="scanner-alert-content">
<p>No security issues found!</p>
</div>
</div>`;
}
// Use enhanced scan data from ScanStatusUpdate if available
const filesScanned = scan.files_scanned || 0;
const filesDiscovered = scan.files_discovered || scan.files_scanned || 0;
const threatsFound = scan.threats_found !== undefined ? scan.threats_found : (scan.issues_found || 0);
const criticalThreats = scan.critical_threats || 0;
const highThreats = scan.high_threats || 0;
document.getElementById('completedSection').innerHTML = `
<div style="display: grid; grid-template-columns: repeat(4, 1fr); gap: 15px; margin-bottom: 20px;">
<div class="stat-card" style="padding: 20px;">
<div class="stat-card-icon info" style="width: 40px; height: 40px; font-size: 18px; margin-bottom: 10px;">
<i class="fas fa-file"></i>
</div>
<h4 style="margin: 0; font-size: 24px; font-weight: 700;">${filesScanned}${filesDiscovered > filesScanned ? `/${filesDiscovered}` : ''}</h4>
<p style="margin: 5px 0 0 0; font-size: 13px;">Files Scanned</p>
</div>
<div class="stat-card" style="padding: 20px;">
<div class="stat-card-icon ${threatsFound > 0 ? 'danger' : 'success'}" style="width: 40px; height: 40px; font-size: 18px; margin-bottom: 10px;">
<i class="fas ${threatsFound > 0 ? 'fa-exclamation-triangle' : 'fa-check'}"></i>
</div>
<h4 style="margin: 0; font-size: 24px; font-weight: 700;">${threatsFound}</h4>
<p style="margin: 5px 0 0 0; font-size: 13px;">Threats Found</p>
${(criticalThreats > 0 || highThreats > 0) ? `<small style="color: var(--alert-danger-border, #ef4444); font-size: 11px;">${criticalThreats > 0 ? `${criticalThreats} Critical` : ''}${criticalThreats > 0 && highThreats > 0 ? ', ' : ''}${highThreats > 0 ? `${highThreats} High` : ''}</small>` : ''}
</div>
<div class="stat-card" style="padding: 20px;">
<div class="stat-card-icon info" style="width: 40px; height: 40px; font-size: 18px; margin-bottom: 10px;">
<i class="fas fa-dollar-sign"></i>
</div>
<h4 style="margin: 0; font-size: 24px; font-weight: 700;">${scan.cost_usd ? '$' + scan.cost_usd.toFixed(4) : 'Free'}</h4>
<p style="margin: 5px 0 0 0; font-size: 13px;">Cost</p>
</div>
<div class="stat-card" style="padding: 20px;">
<div class="stat-card-icon info" style="width: 40px; height: 40px; font-size: 18px; margin-bottom: 10px;">
<i class="fas fa-calendar"></i>
</div>
<h4 style="margin: 0; font-size: 24px; font-weight: 700;">${new Date(scan.started_at).toLocaleDateString()}</h4>
<p style="margin: 5px 0 0 0; font-size: 13px;">Scan Date</p>
</div>
</div>
${findingsHtml}
<div style="margin-top: 20px; text-align: center;">
<button class="btn-primary" onclick="viewOnPlatform('${scan.scan_id}')" style="padding: 0.75rem 2rem;">
<i class="fas fa-external-link-alt"></i> View Full AI Analysis on Platform
</button>
</div>
`;
}
function formatPhase(phase) {
const phases = {
'starting': 'Starting Scan',
'discovering_files': 'Discovering Files',
'scanning_files': 'Scanning Files',
'ai_analysis': 'AI Analysis',
'completing': 'Finalizing',
'completed': 'Completed',
'failed': 'Failed',
'cancelled': 'Cancelled'
};
return phases[phase] || phase;
}
function getPhaseColor(phase) {
const colors = {
'starting': 'info',
'discovering_files': 'warning',
'scanning_files': 'primary',
'ai_analysis': 'warning',
'completing': 'success',
'completed': 'success',
'failed': 'danger',
'cancelled': 'default'
};
return colors[phase] || 'default';
}
// Clean up monitoring when modal is closed
$('#scanDetailsModal').on('hidden.bs.modal', function () {
stopProgressMonitoring();
currentScanId = null;
});
// Global table monitoring variables
let tableMonitorInterval = null;
// Start monitoring active scans in the table
function startTableMonitoring() {
// Clear any existing interval
if (tableMonitorInterval) {
clearInterval(tableMonitorInterval);
}
// Check for active scans every 5 seconds
tableMonitorInterval = setInterval(() => {
updateActiveScansInTable();
}, 5000);
// Also run once immediately
updateActiveScansInTable();
}
function updateActiveScansInTable() {
const scanRows = document.querySelectorAll('tr[data-scan-id]');
scanRows.forEach(row => {
const scanId = row.getAttribute('data-scan-id');
const statusCell = row.querySelector('.scan-status span');
const progressCell = row.querySelector('.scan-progress');
const filesCell = row.querySelector('.scan-files');
const issuesCell = row.querySelector('.scan-issues');
// Only update running/pending scans
if (statusCell && (statusCell.textContent.includes('Running') || statusCell.textContent.includes('Pending'))) {
// Fetch live progress for this scan
fetch(`/api/ai-scanner/scan/${scanId}/live-progress`)
.then(response => response.json())
.then(data => {
if (data.success) {
updateTableRowProgress(row, data);
}
})
.catch(error => {
// Silently fail for individual scan updates
console.log(`No live data for scan ${scanId}`);
});
}
});
}
function updateTableRowProgress(row, data) {
const statusCell = row.querySelector('.scan-status span');
const progressCell = row.querySelector('.scan-progress');
const filesCell = row.querySelector('.scan-files');
const issuesCell = row.querySelector('.scan-issues');
const costCell = row.querySelector('td:nth-child(8)'); // Cost column
// Update status
if (statusCell) {
const phaseText = formatPhase(data.phase);
const statusClass = getStatusClass(data.phase);
statusCell.className = `status-label ${statusClass}`;
statusCell.textContent = phaseText;
}
// Update progress
if (progressCell) {
if (data.is_active) {
progressCell.innerHTML = `
<div class="progress" style="height: 20px;">
<div class="progress-bar progress-bar-animated"
role="progressbar" style="width: ${data.progress}%"
aria-valuenow="${data.progress}" aria-valuemin="0" aria-valuemax="100">
<span class="progress-text" style="position: absolute; width: 100%; text-align: center; line-height: 20px; color: var(--text-primary, #2f3640); font-size: 12px;">${data.progress}%</span>
</div>
</div>
`;
} else {
progressCell.innerHTML = '<span style="color: #10b981; font-weight: 600;">100%</span>';
}
}
// Update files count
if (filesCell && data.files_scanned > 0) {
filesCell.textContent = `${data.files_scanned}/${data.files_discovered}`;
}
// Update issues count
if (issuesCell && data.threats_found > 0) {
issuesCell.innerHTML = `<span style="color: #ef4444; font-weight: 700;">${data.threats_found}</span>`;
}
// Update cost (real-time cost from platform)
if (costCell && data.cost) {
costCell.textContent = data.cost;
}
}
function getStatusClass(phase) {
const statusClasses = {
'starting': 'info',
'discovering_files': 'warning',
'scanning_files': 'warning',
'ai_analysis': 'info',
'completing': 'warning',
'completed': 'success',
'failed': 'danger',
'cancelled': 'default'
};
return statusClasses[phase] || 'default';
}
// Auto-start table monitoring when page loads
document.addEventListener('DOMContentLoaded', function() {
// Only start monitoring if we're on the scanner page with a scan history table
if (document.getElementById('scanHistoryTable')) {
startTableMonitoring();
console.log('Started real-time scan monitoring');
}
});
// Stop monitoring when page unloads
window.addEventListener('beforeunload', function() {
if (tableMonitorInterval) {
clearInterval(tableMonitorInterval);
}
});
function getCookie(name) {
let cookieValue = null;
if (document.cookie && document.cookie !== '') {
const cookies = document.cookie.split(';');
for (let i = 0; i < cookies.length; i++) {
const cookie = cookies[i].trim();
if (cookie.substring(0, name.length + 1) === (name + '=')) {
cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
break;
}
}
}
return cookieValue;
}
function refreshBalance() {
const button = event.target.closest('button');
const originalHtml = button.innerHTML;
button.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Refreshing...';
button.disabled = true;
fetch('/aiscanner/refresh-balance/', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRFToken': getCookie('csrftoken')
}
})
.then(response => response.json())
.then(data => {
if (data.success) {
// Update the balance display
const balanceElement = button.closest('.stat-card').querySelector('h2');
balanceElement.textContent = '$' + parseFloat(data.balance).toFixed(4);
// Show success message
if (data.message) {
alert(data.message);
}
} else {
alert('Error: ' + data.error);
}
button.innerHTML = originalHtml;
button.disabled = false;
})
.catch(error => {
console.error('Error:', error);
alert('Failed to refresh balance. Please try again.');
button.innerHTML = originalHtml;
button.disabled = false;
});
}
function addPaymentMethod() {
const button = event.target.closest('button');
const originalHtml = button.innerHTML;
button.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Setting up...';
button.disabled = true;
fetch('/aiscanner/add-payment-method/', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRFToken': getCookie('csrftoken')
}
})
.then(response => response.json())
.then(data => {
if (data.success) {
// Redirect to payment method setup
window.location.href = data.setup_url;
} else {
alert('Error: ' + data.error);
button.innerHTML = originalHtml;
button.disabled = false;
}
})
.catch(error => {
console.error('Error:', error);
alert('Failed to setup payment method. Please try again.');
button.innerHTML = originalHtml;
button.disabled = false;
});
}
// Auto-refresh scan history every 30 seconds if there are running scans
setInterval(() => {
if (document.getElementById('scanHistoryTable')) {
refreshScanHistory();
}
}, 30000);
// Scheduled Scans Functions
function showScheduleModal() {
// Reset form for new schedule
document.getElementById('scheduleForm').reset();
document.getElementById('scheduleId').value = '';
// Uncheck all domain checkboxes
document.querySelectorAll('input[name="domains"]').forEach(cb => cb.checked = false);
// Reset checkboxes to their defaults
document.getElementById('emailNotifications').checked = true;
document.getElementById('notifyOnThreats').checked = true;
document.getElementById('notifyOnCompletion').checked = false;
document.getElementById('notifyOnFailure').checked = true;
$('#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: var(--text-muted, #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 form = document.getElementById('scheduleForm');
const formData = new FormData(form);
const data = {};
// Process form data, excluding checkboxes and multi-select fields
for (let [key, value] of formData.entries()) {
if (!['email_notifications', 'notify_on_threats', 'notify_on_completion', 'notify_on_failure', 'domains'].includes(key)) {
data[key] = value;
}
}
// 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;
// Convert checkbox values to booleans explicitly
data.email_notifications = document.getElementById('emailNotifications').checked;
data.notify_on_threats = document.getElementById('notifyOnThreats').checked;
data.notify_on_completion = document.getElementById('notifyOnCompletion').checked;
data.notify_on_failure = document.getElementById('notifyOnFailure').checked;
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 var(--border-primary, #e8e9ff);
border-radius: 8px;
padding: 20px;
background: var(--bg-primary, white);
}
.scheduled-scan-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 15px;
}
.scheduled-scan-header h4 {
margin: 0;
color: var(--text-primary, #2f3640);
font-size: 16px;
}
.scheduled-scan-info p {
margin: 5px 0;
color: var(--text-secondary, #64748b);
font-size: 14px;
}
.scheduled-scan-actions {
display: flex;
gap: 10px;
margin-top: 15px;
flex-wrap: wrap;
}
.checkbox-group {
background: var(--bg-secondary, #f8f9fa);
border-color: var(--border-primary, #ddd);
}
.form-check {
margin-bottom: 5px;
}
</style>
{% endblock %}