69f8438a36
- Implemented domain management page with a list of monitored domains and their DMARC, SPF, and DKIM statuses. - Created reports page with filtering options for domain, report type, and date range, displaying DMARC reports. - Developed settings page for IMAP configuration and DMARC policy management, including form validation and feedback. - Added upload page for DMARC report files with drag-and-drop functionality and file type validation. - Integrated Alpine.js for interactivity and dynamic data handling across all templates.
299 lines
16 KiB
HTML
299 lines
16 KiB
HTML
{% extends "layouts/base.html" %}
|
|
{% from "components/ui/card.html" import card, card_header, card_title, card_description, card_content, card_footer %}
|
|
{% from "components/ui/button.html" import button, button_link %}
|
|
{% from "components/ui/alert.html" import alert, alert_title, alert_description %}
|
|
{% from "components/ui/table.html" import table, thead, tbody, tr, th, td %}
|
|
|
|
{% block title %}DMARQ - Dashboard{% endblock %}
|
|
|
|
{% block page_title %}Dashboard{% endblock %}
|
|
|
|
{% block content %}
|
|
<div class="grid gap-4 md:gap-8 py-4" x-data="dashboardApp()">
|
|
<!-- Overview Stats -->
|
|
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4">
|
|
<!-- Total Domains Card -->
|
|
{% call card() %}
|
|
{% call card_header() %}
|
|
<div class="flex items-center justify-between">
|
|
{% call card_title() %}Total Domains{% endcall %}
|
|
<div class="p-2 bg-primary/10 rounded-full">
|
|
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="text-primary"><circle cx="12" cy="12" r="10"></circle><line x1="2" x2="22" y1="12" y2="12"></line><path d="M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z"></path></svg>
|
|
</div>
|
|
</div>
|
|
{% endcall %}
|
|
{% call card_content() %}
|
|
<div class="stat-card">
|
|
<div id="total-domains" class="stat-value">0</div>
|
|
<p class="stat-description">Active domains being monitored</p>
|
|
</div>
|
|
{% endcall %}
|
|
{% endcall %}
|
|
|
|
<!-- Emails Analyzed Card -->
|
|
{% call card() %}
|
|
{% call card_header() %}
|
|
<div class="flex items-center justify-between">
|
|
{% call card_title() %}Emails Analyzed{% endcall %}
|
|
<div class="p-2 bg-secondary/10 rounded-full">
|
|
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="text-secondary"><rect width="20" height="16" x="2" y="4" rx="2"></rect><path d="m22 7-8.97 5.7a1.94 1.94 0 0 1-2.06 0L2 7"></path></svg>
|
|
</div>
|
|
</div>
|
|
{% endcall %}
|
|
{% call card_content() %}
|
|
<div class="stat-card">
|
|
<div id="total-emails" class="stat-value">0</div>
|
|
<p class="stat-description">Total emails processed</p>
|
|
</div>
|
|
{% endcall %}
|
|
{% endcall %}
|
|
|
|
<!-- Pass Rate Card -->
|
|
{% call card() %}
|
|
{% call card_header() %}
|
|
<div class="flex items-center justify-between">
|
|
{% call card_title() %}Pass Rate{% endcall %}
|
|
<div class="p-2 bg-green-500/10 rounded-full">
|
|
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="text-green-500"><path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"></path><polyline points="22 4 12 14.01 9 11.01"></polyline></svg>
|
|
</div>
|
|
</div>
|
|
{% endcall %}
|
|
{% call card_content() %}
|
|
<div class="stat-card">
|
|
<div id="overall-pass-rate" class="stat-value text-green-500">0%</div>
|
|
<p class="stat-description">Overall DMARC compliance</p>
|
|
</div>
|
|
{% endcall %}
|
|
{% endcall %}
|
|
|
|
<!-- Reports Card -->
|
|
{% call card() %}
|
|
{% call card_header() %}
|
|
<div class="flex items-center justify-between">
|
|
{% call card_title() %}Reports Processed{% endcall %}
|
|
<div class="p-2 bg-accent/10 rounded-full">
|
|
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="text-accent"><path d="M14.5 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7.5L14.5 2z"></path><polyline points="14 2 14 8 20 8"></polyline></svg>
|
|
</div>
|
|
</div>
|
|
{% endcall %}
|
|
{% call card_content() %}
|
|
<div class="stat-card">
|
|
<div id="reports-processed" class="stat-value">0</div>
|
|
<p class="stat-description">DMARC reports received</p>
|
|
</div>
|
|
{% endcall %}
|
|
{% endcall %}
|
|
</div>
|
|
|
|
<!-- Domain Compliance Table -->
|
|
<div x-show="hasDomainData" x-cloak>
|
|
{% call card() %}
|
|
{% call card_header() %}
|
|
<div class="flex items-center justify-between">
|
|
{% call card_title() %}Domain Compliance{% endcall %}
|
|
{% call button(variant="outline", size="sm") %}
|
|
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="mr-2"><path d="M21 12a9 9 0 1 1-9-9c2.52 0 4.93 1 6.74 2.74L21 8"></path><path d="M21 3v5h-5"></path></svg>
|
|
Refresh
|
|
{% endcall %}
|
|
</div>
|
|
{% call card_description() %}
|
|
Overview of domains and their DMARC compliance status
|
|
{% endcall %}
|
|
{% endcall %}
|
|
{% call card_content() %}
|
|
{% call table() %}
|
|
{% call thead() %}
|
|
{% call tr() %}
|
|
{% call th() %}Domain{% endcall %}
|
|
{% call th() %}Emails{% endcall %}
|
|
{% call th() %}Pass Rate{% endcall %}
|
|
{% call th() %}Failed{% endcall %}
|
|
{% call th() %}Reports{% endcall %}
|
|
{% call th("text-right") %}Actions{% endcall %}
|
|
{% endcall %}
|
|
{% endcall %}
|
|
{% call tbody() %}
|
|
<tbody id="domains-table-body">
|
|
<!-- Domain data will be populated here via JavaScript -->
|
|
</tbody>
|
|
{% endcall %}
|
|
{% endcall %}
|
|
{% endcall %}
|
|
{% endcall %}
|
|
</div>
|
|
|
|
<!-- No Data Message -->
|
|
<div x-show="!hasDomainData" x-cloak>
|
|
{% call card() %}
|
|
{% call card_content() %}
|
|
<div class="flex flex-col items-center justify-center py-12 text-center">
|
|
<div class="rounded-full p-4 bg-muted mb-4">
|
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="text-muted-foreground"><circle cx="12" cy="12" r="10"></circle><path d="m8 12 4 0"></path><path d="m16 12 0 .01"></path><path d="M12 16v-4"></path></svg>
|
|
</div>
|
|
<h3 class="text-lg font-semibold mb-2">No DMARC reports have been uploaded yet</h3>
|
|
<p class="text-muted-foreground mb-4 max-w-md">Upload a report or configure IMAP integration to see statistics and gain insights into your domain's email authentication.</p>
|
|
<div class="flex gap-2">
|
|
{% call button_link(href="/upload") %}
|
|
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="mr-2"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path><polyline points="17 8 12 3 7 8"></polyline><line x1="12" x2="12" y1="3" y2="15"></line></svg>
|
|
Upload Report
|
|
{% endcall %}
|
|
{% call button_link(href="/settings", variant="outline") %}
|
|
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="mr-2"><circle cx="12" cy="12" r="3"></circle><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z"></path></svg>
|
|
Configure IMAP
|
|
{% endcall %}
|
|
</div>
|
|
</div>
|
|
{% endcall %}
|
|
{% endcall %}
|
|
</div>
|
|
|
|
<!-- IMAP Status -->
|
|
<div>
|
|
{% call card() %}
|
|
{% call card_header() %}
|
|
<div class="flex items-center justify-between">
|
|
{% call card_title() %}IMAP Integration Status{% endcall %}
|
|
{% call button_link(href="/api/v1/admin/trigger-poll", variant="outline", size="sm") %}
|
|
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="mr-2"><path d="m3 2 2 4h8L7 14h2l-4 8 1-6H4l2-8H3z"></path></svg>
|
|
Trigger Poll Now
|
|
{% endcall %}
|
|
</div>
|
|
{% endcall %}
|
|
{% call card_content() %}
|
|
<div class="flex flex-col gap-4">
|
|
<div class="flex items-center">
|
|
<span class="w-40 text-sm text-muted-foreground">Status:</span>
|
|
<span class="inline-flex items-center gap-1.5">
|
|
<span id="imap-status-icon" class="h-2.5 w-2.5 rounded-full bg-green-500"></span>
|
|
<span id="imap-status-text">Running</span>
|
|
</span>
|
|
</div>
|
|
<div class="flex items-center">
|
|
<span class="w-40 text-sm text-muted-foreground">Last Check:</span>
|
|
<span id="imap-last-check">Never</span>
|
|
</div>
|
|
<div class="flex items-center">
|
|
<span class="w-40 text-sm text-muted-foreground">Mailbox:</span>
|
|
<span id="imap-mailbox">dmarc-reports@hosterra.net</span>
|
|
</div>
|
|
</div>
|
|
{% endcall %}
|
|
{% endcall %}
|
|
</div>
|
|
</div>
|
|
{% endblock %}
|
|
|
|
{% block scripts %}
|
|
<script>
|
|
function dashboardApp() {
|
|
return {
|
|
hasDomainData: false,
|
|
|
|
init() {
|
|
// Fetch domain summary on page load
|
|
this.fetchDomainSummary();
|
|
|
|
// Check IMAP status
|
|
this.getImapStatus();
|
|
},
|
|
|
|
async getImapStatus() {
|
|
try {
|
|
const response = await fetch('/api/v1/admin/poll-status');
|
|
const data = await response.json();
|
|
|
|
const statusIcon = document.getElementById('imap-status-icon');
|
|
const statusText = document.getElementById('imap-status-text');
|
|
const lastCheck = document.getElementById('imap-last-check');
|
|
|
|
if (data.is_running) {
|
|
statusIcon.classList.remove('bg-red-500');
|
|
statusIcon.classList.add('bg-green-500');
|
|
statusText.textContent = 'Running';
|
|
} else {
|
|
statusIcon.classList.remove('bg-green-500');
|
|
statusIcon.classList.add('bg-red-500');
|
|
statusText.textContent = 'Stopped';
|
|
}
|
|
|
|
if (data.last_check) {
|
|
lastCheck.textContent = new Date(data.last_check).toLocaleString();
|
|
} else {
|
|
lastCheck.textContent = 'Never';
|
|
}
|
|
} catch (error) {
|
|
console.error('Error checking IMAP status:', error);
|
|
}
|
|
},
|
|
|
|
async fetchDomainSummary() {
|
|
try {
|
|
const response = await fetch('/api/v1/domains/summary');
|
|
const data = await response.json();
|
|
|
|
if (data && data.domains && data.domains.length > 0) {
|
|
this.hasDomainData = true;
|
|
this.updateDashboardStats(data);
|
|
this.populateDomainsTable(data.domains);
|
|
} else {
|
|
this.hasDomainData = false;
|
|
}
|
|
} catch (error) {
|
|
console.error('Error fetching domain summary:', error);
|
|
this.hasDomainData = false;
|
|
}
|
|
},
|
|
|
|
updateDashboardStats(data) {
|
|
if (!data) return;
|
|
|
|
const totalDomains = document.getElementById('total-domains');
|
|
const totalEmails = document.getElementById('total-emails');
|
|
const passRate = document.getElementById('overall-pass-rate');
|
|
const reportsProcessed = document.getElementById('reports-processed');
|
|
|
|
if (totalDomains) totalDomains.textContent = data.total_domains || 0;
|
|
if (totalEmails) totalEmails.textContent = data.total_emails || 0;
|
|
if (passRate) passRate.textContent = `${data.overall_pass_rate || 0}%`;
|
|
if (reportsProcessed) reportsProcessed.textContent = data.reports_processed || 0;
|
|
},
|
|
|
|
populateDomainsTable(domains) {
|
|
if (!domains || !domains.length) return;
|
|
|
|
const tableBody = document.getElementById('domains-table-body');
|
|
if (!tableBody) return;
|
|
|
|
tableBody.innerHTML = '';
|
|
|
|
domains.forEach(domain => {
|
|
const row = document.createElement('tr');
|
|
row.className = 'table-row';
|
|
|
|
row.innerHTML = `
|
|
<td class="table-cell">
|
|
<div class="font-medium">${domain.domain_name}</div>
|
|
</td>
|
|
<td class="table-cell">${domain.total_emails || 0}</td>
|
|
<td class="table-cell">
|
|
<span class="inline-flex items-center rounded-md bg-green-50 dark:bg-green-900/20 px-2 py-1 text-xs font-medium text-green-700 dark:text-green-300">
|
|
${domain.pass_rate || 0}%
|
|
</span>
|
|
</td>
|
|
<td class="table-cell">${domain.failed_count || 0}</td>
|
|
<td class="table-cell">${domain.report_count || 0}</td>
|
|
<td class="table-cell text-right">
|
|
<a href="/domains/${domain.id}" class="btn btn-outline btn-sm">
|
|
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="mr-1"><path d="M3 3v18h18"></path><path d="m19 9-5 5-4-4-3 3"></path></svg>
|
|
Details
|
|
</a>
|
|
</td>
|
|
`;
|
|
|
|
tableBody.appendChild(row);
|
|
});
|
|
}
|
|
}
|
|
}
|
|
</script>
|
|
{% endblock %} |