secure-web/backend/templates/index.html

251 lines
12 KiB
HTML

{% extends "base.html" %}
{% block title %}Website Analyzer - Scan Your Website{% endblock %}
{% block content %}
<div x-data="scanApp()" x-cloak class="space-y-8">
<!-- Hero Section -->
<div class="text-center py-12">
<h1 class="text-4xl font-bold text-gray-900 mb-4">
Analyze Your Website
</h1>
<p class="text-xl text-gray-600 max-w-2xl mx-auto">
Get comprehensive insights on performance, security, accessibility, and SEO.
Powered by Lighthouse, OWASP ZAP, and advanced browser analysis.
</p>
</div>
<!-- URL Input Form -->
<div class="max-w-2xl mx-auto">
<form @submit.prevent="startScan" class="bg-white rounded-xl shadow-lg p-6">
<div class="flex flex-col sm:flex-row gap-4">
<div class="flex-1">
<label for="url" class="sr-only">Website URL</label>
<input
type="url"
id="url"
x-model="url"
placeholder="https://example.com"
required
class="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 text-lg"
:disabled="loading"
>
</div>
<button
type="submit"
:disabled="loading || !url"
class="px-8 py-3 bg-blue-600 text-white font-semibold rounded-lg hover:bg-blue-700 focus:ring-4 focus:ring-blue-300 disabled:opacity-50 disabled:cursor-not-allowed transition-colors"
>
<span x-show="!loading">Scan Now</span>
<span x-show="loading" class="flex items-center">
<svg class="animate-spin -ml-1 mr-2 h-5 w-5 text-white" fill="none" viewBox="0 0 24 24">
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
</svg>
Starting...
</span>
</button>
</div>
<!-- Error Message -->
<div x-show="error" x-transition class="mt-4 p-4 bg-red-50 border border-red-200 rounded-lg">
<div class="flex items-center">
<svg class="w-5 h-5 text-red-500 mr-2" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zM8.707 7.293a1 1 0 00-1.414 1.414L8.586 10l-1.293 1.293a1 1 0 101.414 1.414L10 11.414l1.293 1.293a1 1 0 001.414-1.414L11.414 10l1.293-1.293a1 1 0 00-1.414-1.414L10 8.586 8.707 7.293z" clip-rule="evenodd"/>
</svg>
<span class="text-red-700" x-text="error"></span>
</div>
</div>
</form>
</div>
<!-- Features Grid -->
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6 mt-12">
<!-- Performance -->
<div class="bg-white rounded-xl shadow p-6 border-t-4 border-blue-500">
<div class="flex items-center mb-4">
<div class="w-10 h-10 rounded-lg bg-blue-100 flex items-center justify-center">
<svg class="w-6 h-6 text-blue-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 10V3L4 14h7v7l9-11h-7z"/>
</svg>
</div>
<h3 class="ml-3 font-semibold text-gray-900">Performance</h3>
</div>
<p class="text-gray-600 text-sm">
Core Web Vitals, load times, bundle sizes, and render-blocking resources.
</p>
</div>
<!-- Security -->
<div class="bg-white rounded-xl shadow p-6 border-t-4 border-red-500">
<div class="flex items-center mb-4">
<div class="w-10 h-10 rounded-lg bg-red-100 flex items-center justify-center">
<svg class="w-6 h-6 text-red-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z"/>
</svg>
</div>
<h3 class="ml-3 font-semibold text-gray-900">Security</h3>
</div>
<p class="text-gray-600 text-sm">
OWASP ZAP scanning, security headers, TLS/SSL, and cookie analysis.
</p>
</div>
<!-- Accessibility -->
<div class="bg-white rounded-xl shadow p-6 border-t-4 border-green-500">
<div class="flex items-center mb-4">
<div class="w-10 h-10 rounded-lg bg-green-100 flex items-center justify-center">
<svg class="w-6 h-6 text-green-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0zm6 3a2 2 0 11-4 0 2 2 0 014 0zM7 10a2 2 0 11-4 0 2 2 0 014 0z"/>
</svg>
</div>
<h3 class="ml-3 font-semibold text-gray-900">Accessibility</h3>
</div>
<p class="text-gray-600 text-sm">
WCAG compliance, ARIA labels, color contrast, and keyboard navigation.
</p>
</div>
<!-- SEO -->
<div class="bg-white rounded-xl shadow p-6 border-t-4 border-purple-500">
<div class="flex items-center mb-4">
<div class="w-10 h-10 rounded-lg bg-purple-100 flex items-center justify-center">
<svg class="w-6 h-6 text-purple-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"/>
</svg>
</div>
<h3 class="ml-3 font-semibold text-gray-900">SEO</h3>
</div>
<p class="text-gray-600 text-sm">
Meta tags, structured data, crawlability, and search engine optimization.
</p>
</div>
</div>
<!-- Recent Scans -->
<div class="mt-12" x-show="recentScans.length > 0">
<h2 class="text-2xl font-bold text-gray-900 mb-6">Recent Scans</h2>
<div class="bg-white rounded-xl shadow overflow-hidden">
<table class="min-w-full divide-y divide-gray-200">
<thead class="bg-gray-50">
<tr>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Website</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Status</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Score</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Date</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Actions</th>
</tr>
</thead>
<tbody class="bg-white divide-y divide-gray-200">
<template x-for="scan in recentScans" :key="scan.id">
<tr class="hover:bg-gray-50">
<td class="px-6 py-4 whitespace-nowrap">
<span class="text-sm text-gray-900" x-text="scan.website_url"></span>
</td>
<td class="px-6 py-4 whitespace-nowrap">
<span
class="px-2 py-1 text-xs font-medium rounded-full"
:class="{
'bg-green-100 text-green-800': scan.status === 'done',
'bg-yellow-100 text-yellow-800': scan.status === 'running' || scan.status === 'pending',
'bg-red-100 text-red-800': scan.status === 'failed',
'bg-blue-100 text-blue-800': scan.status === 'partial'
}"
x-text="scan.status_display"
></span>
</td>
<td class="px-6 py-4 whitespace-nowrap">
<span
class="text-sm font-medium"
:class="{
'text-green-600': scan.overall_score >= 80,
'text-yellow-600': scan.overall_score >= 50 && scan.overall_score < 80,
'text-red-600': scan.overall_score < 50
}"
x-text="scan.overall_score !== null ? scan.overall_score + '/100' : '-'"
></span>
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
<span x-text="new Date(scan.created_at).toLocaleString()"></span>
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm">
<a
:href="'/scan/' + scan.id + '/'"
class="text-blue-600 hover:text-blue-800 font-medium"
>
View Details
</a>
</td>
</tr>
</template>
</tbody>
</table>
</div>
</div>
</div>
<script>
function scanApp() {
return {
url: '',
loading: false,
error: null,
recentScans: [],
async init() {
await this.loadRecentScans();
},
async loadRecentScans() {
try {
const response = await fetch('/api/scans/?page_size=10');
if (response.ok) {
const data = await response.json();
this.recentScans = data.results || [];
}
} catch (err) {
console.error('Failed to load recent scans:', err);
}
},
async startScan() {
this.loading = true;
this.error = null;
try {
const response = await fetch('/api/scans/', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRFToken': this.getCsrfToken(),
},
body: JSON.stringify({ url: this.url })
});
if (!response.ok) {
const data = await response.json();
throw new Error(data.error || data.url?.[0] || 'Failed to start scan');
}
const scan = await response.json();
// Redirect to scan detail page
window.location.href = `/scan/${scan.id}/`;
} catch (err) {
this.error = err.message;
} finally {
this.loading = false;
}
},
getCsrfToken() {
return document.cookie.split('; ')
.find(row => row.startsWith('csrftoken='))
?.split('=')[1] || '';
}
}
}
</script>
{% endblock %}