Tests design and descriptions

This commit is contained in:
nemunaire 2025-10-22 22:41:11 +07:00
commit a97729fea6
7 changed files with 230 additions and 164 deletions

View file

@ -74,14 +74,21 @@ body {
/* Custom card styling */
.card {
border: none;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
transition:
transform 0.2s ease,
box-shadow 0.2s ease;
}
.card:hover {
.card:not(.fade-in .card) {
border: none;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
.fade-in .card:not(.card .card) {
border: none;
}
.card:hover:not(.fade-in .card) {
transform: translateY(-2px);
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.15);
}

View file

@ -72,10 +72,9 @@
</span>
</h4>
</div>
<div class="card-body">
<div class="row row-cols-1">
<div class="list-group list-group-flush">
<!-- SPF (Required) -->
<div class="col mb-3">
<div class="list-group-item">
<div class="d-flex align-items-start">
{#if authentication.spf}
<i class="bi {getAuthResultIcon(authentication.spf.result)} {getAuthResultClass(authentication.spf.result)} me-2 fs-5"></i>
@ -108,7 +107,7 @@
</div>
<!-- DKIM (Required) -->
<div class="col mb-3">
<div class="list-group-item">
<div class="d-flex align-items-start">
{#if authentication.dkim && authentication.dkim.length > 0}
<i class="bi {getAuthResultIcon(authentication.dkim[0].result)} {getAuthResultClass(authentication.dkim[0].result)} me-2 fs-5"></i>
@ -147,7 +146,7 @@
</div>
<!-- DMARC (Required) -->
<div class="col mb-3">
<div class="list-group-item">
<div class="d-flex align-items-start">
{#if authentication.dmarc}
<i class="bi {getAuthResultIcon(authentication.dmarc.result)} {getAuthResultClass(authentication.dmarc.result)} me-2 fs-5"></i>
@ -200,7 +199,7 @@
</div>
<!-- BIMI (Optional) -->
<div class="col mb-3">
<div class="list-group-item">
<div class="d-flex align-items-start">
{#if authentication.bimi && authentication.bimi.result != "none"}
<i class="bi {getAuthResultIcon(authentication.bimi.result)} {getAuthResultClass(authentication.bimi.result)} me-2 fs-5"></i>
@ -240,7 +239,7 @@
<!-- ARC (Optional) -->
{#if authentication.arc}
<div class="col mb-3">
<div class="list-group-item">
<div class="d-flex align-items-start">
<i class="bi {getAuthResultIcon(authentication.arc.result)} {getAuthResultClass(authentication.arc.result)} me-2 fs-5"></i>
<div>
@ -258,6 +257,5 @@
</div>
</div>
{/if}
</div>
</div>
</div>

View file

@ -35,32 +35,24 @@
<div class="row row-cols-1 row-cols-lg-2">
{#each Object.entries(blacklists) as [ip, checks]}
<div class="col mb-3">
<h6 class="text-muted">
<h5 class="text-muted">
<i class="bi bi-hdd-network me-1"></i>
{ip}
</h6>
<div class="table-responsive">
<table class="table table-sm">
<thead>
</h5>
<table class="table table-sm table-striped table-hover mb-0">
<tbody>
{#each checks as check}
<tr>
<th>RBL</th>
<th>Status</th>
<td title={check.response || '-'}>
<span class="badge {check.listed ? 'bg-danger' : check.error ? 'bg-dark' : 'bg-success'}">
{check.error ? 'Error' : (check.listed ? 'Listed' : 'Clean')}
</span>
</td>
<td><code>{check.rbl}</code></td>
</tr>
</thead>
<tbody>
{#each checks as check}
<tr>
<td><code>{check.rbl}</code></td>
<td title={check.response || '-'}>
<span class="badge {check.listed ? 'bg-danger' : check.error ? 'bg-dark' : 'bg-success'}">
{check.error ? 'Error' : (check.listed ? 'Listed' : 'Clean')}
</span>
</td>
</tr>
{/each}
</tbody>
</table>
</div>
{/each}
</tbody>
</table>
</div>
{/each}
</div>

View file

@ -48,8 +48,10 @@
{/if}
<!-- Return-Path Domain Section -->
<div class="mb-3">
<strong>Return-Path Domain:</strong> <code>{dnsResults.rp_domain || dnsResults.from_domain}</code>
<div class="mb-3 d-flex align-items-center gap-2">
<h4 class="mb-0">
Return-Path Domain: <code>{dnsResults.rp_domain || dnsResults.from_domain}</code>
</h4>
{#if dnsResults.rp_domain && dnsResults.rp_domain !== dnsResults.from_domain}
<span class="badge bg-danger ms-2"><i class="bi bi-exclamation-triangle-fill"></i> Different from From domain</span>
<small>
@ -64,6 +66,7 @@
<!-- MX Records for Return-Path Domain -->
{#if dnsResults.rp_mx_records && dnsResults.rp_mx_records.length > 0}
<MxRecordsDisplay
class="mb-4"
mxRecords={dnsResults.rp_mx_records}
title="Mail Exchange Records for Return-Path Domain"
description="These MX records handle bounce messages and non-delivery reports."
@ -72,14 +75,27 @@
<!-- SPF Records (for Return-Path Domain) -->
{#if dnsResults.spf_records && dnsResults.spf_records.length > 0}
<div class="mb-4">
<h5 class="text-muted mb-2">
<span class="badge bg-secondary">SPF</span> Sender Policy Framework
</h5>
<p class="small text-muted mb-2">SPF validates the Return-Path (envelope sender) domain.</p>
{#each dnsResults.spf_records as spf, index}
<div class="card mb-2">
<div class="card-body">
{@const spfIsValid = dnsResults.spf_records.reduce((acc, r) => acc && r.valid, true)}
<div class="card mb-4">
<div class="card-header d-flex justify-content-between align-items-center">
<h5 class="text-muted mb-2">
<i
class="bi"
class:bi-check-circle-fill={spfIsValid}
class:text-success={spfIsValid}
class:bi-x-circle-fill={!spfIsValid}
class:text-danger={!spfIsValid}
></i>
Sender Policy Framework
</h5>
<span class="badge bg-secondary">SPF</span>
</div>
<div class="card-body pb-0">
<p class="card-text small text-muted mb-0">SPF specifies which mail servers are authorized to send emails on behalf of your domain. Receiving servers check the sender's IP address against your SPF record to prevent email spoofing.</p>
</div>
<div class="list-group list-group-flush">
{#each dnsResults.spf_records as spf, index}
<div class="list-group-item">
{#if spf.domain}
<div class="mb-2">
<strong>Domain:</strong> <code>{spf.domain}</code>
@ -109,16 +125,18 @@
</div>
{/if}
</div>
</div>
{/each}
{/each}
</div>
</div>
{/if}
<hr>
<hr class="my-4">
<!-- From Domain Section -->
<div class="mb-3">
<strong>From Domain:</strong> <code>{dnsResults.from_domain}</code>
<div class="mb-3 d-flex align-items-center gap-2">
<h4 class="mb-0">
From Domain: <code>{dnsResults.from_domain}</code>
</h4>
{#if dnsResults.rp_domain && dnsResults.rp_domain !== dnsResults.from_domain}
<span class="badge bg-danger ms-2"><i class="bi bi-exclamation-triangle-fill"></i> Different from Return-Path domain</span>
{/if}
@ -127,6 +145,7 @@
<!-- MX Records for From Domain -->
{#if dnsResults.from_mx_records && dnsResults.from_mx_records.length > 0}
<MxRecordsDisplay
class="mb-4"
mxRecords={dnsResults.from_mx_records}
title="Mail Exchange Records for From Domain"
description="These MX records handle replies to emails sent from this domain."
@ -135,13 +154,27 @@
<!-- DKIM Records -->
{#if dnsResults.dkim_records && dnsResults.dkim_records.length > 0}
<div class="mb-4">
<h5 class="text-muted mb-2">
<span class="badge bg-secondary">DKIM</span> DomainKeys Identified Mail
</h5>
{#each dnsResults.dkim_records as dkim}
<div class="card mb-2">
<div class="card-body">
{@const dkimIsValid = dnsResults.dkim_records.reduce((acc, r) => acc && r.valid, true)}
<div class="card mb-4">
<div class="card-header d-flex justify-content-between align-items-center">
<h5 class="text-muted mb-0">
<i
class="bi"
class:bi-check-circle-fill={dkimIsValid}
class:text-success={dkimIsValid}
class:bi-x-circle-fill={!dkimIsValid}
class:text-danger={!dkimIsValid}
></i>
DomainKeys Identified Mail
</h5>
<span class="badge bg-secondary">DKIM</span>
</div>
<div class="card-body pb-0">
<p class="card-text small text-muted mb-0">DKIM cryptographically signs your emails, proving they haven't been tampered with in transit. Receiving servers verify this signature against your DNS records.</p>
</div>
<div class="list-group list-group-flush">
{#each dnsResults.dkim_records as dkim}
<div class="list-group-item">
<div class="mb-2">
<strong>Selector:</strong> <code>{dkim.selector}</code>
<strong class="ms-3">Domain:</strong> <code>{dkim.domain}</code>
@ -166,59 +199,79 @@
</div>
{/if}
</div>
</div>
{/each}
{/each}
</div>
</div>
{/if}
<!-- DMARC Record -->
{#if dnsResults.dmarc_record}
<div class="mb-4">
<h5 class="text-muted mb-2">
<span class="badge bg-secondary">DMARC</span> Domain-based Message Authentication
</h5>
<div class="card">
<div class="card-body">
<div class="mb-2">
<strong>Status:</strong>
{#if dnsResults.dmarc_record.valid}
<span class="badge bg-success">Valid</span>
{:else}
<span class="badge bg-danger">Invalid</span>
{/if}
</div>
{#if dnsResults.dmarc_record.policy}
<div class="mb-2">
<strong>Policy:</strong>
<span class="badge {dnsResults.dmarc_record.policy === 'reject' ? 'bg-success' : dnsResults.dmarc_record.policy === 'quarantine' ? 'bg-warning' : 'bg-secondary'}">
{dnsResults.dmarc_record.policy}
</span>
</div>
{/if}
{#if dnsResults.dmarc_record.record}
<div class="mb-2">
<strong>Record:</strong><br>
<code class="d-block mt-1 text-break">{dnsResults.dmarc_record.record}</code>
</div>
{/if}
{#if dnsResults.dmarc_record.error}
<div class="text-danger">
<strong>Error:</strong> {dnsResults.dmarc_record.error}
</div>
<div class="card mb-4">
<div class="card-header d-flex justify-content-between align-items-center">
<h5 class="text-muted mb-0">
<i
class="bi"
class:bi-check-circle-fill={dnsResults.dmarc_record.valid}
class:text-success={dnsResults.dmarc_record.valid}
class:bi-x-circle-fill={!dnsResults.dmarc_record.valid}
class:text-danger={!dnsResults.dmarc_record.valid}
></i>
Domain-based Message Authentication
</h5>
<span class="badge bg-secondary">DMARC</span>
</div>
<div class="card-body">
<p class="card-text small text-muted mb-2">DMARC builds on SPF and DKIM by telling receiving servers what to do with emails that fail authentication checks. It also enables reporting so you can monitor your email security.</p>
<div class="mb-2">
<strong>Status:</strong>
{#if dnsResults.dmarc_record.valid}
<span class="badge bg-success">Valid</span>
{:else}
<span class="badge bg-danger">Invalid</span>
{/if}
</div>
{#if dnsResults.dmarc_record.policy}
<div class="mb-2">
<strong>Policy:</strong>
<span class="badge {dnsResults.dmarc_record.policy === 'reject' ? 'bg-success' : dnsResults.dmarc_record.policy === 'quarantine' ? 'bg-warning' : 'bg-secondary'}">
{dnsResults.dmarc_record.policy}
</span>
</div>
{/if}
{#if dnsResults.dmarc_record.record}
<div class="mb-2">
<strong>Record:</strong><br>
<code class="d-block mt-1 text-break">{dnsResults.dmarc_record.record}</code>
</div>
{/if}
{#if dnsResults.dmarc_record.error}
<div class="text-danger">
<strong>Error:</strong> {dnsResults.dmarc_record.error}
</div>
{/if}
</div>
</div>
{/if}
<!-- BIMI Record -->
{#if dnsResults.bimi_record}
<div class="mb-4">
<h5 class="text-muted mb-2">
<span class="badge bg-secondary">BIMI</span> Brand Indicators for Message Identification
</h5>
<div class="card mb-4">
<div class="card-header d-flex justify-content-between align-items-center">
<h5 class="text-muted mb-0">
<i
class="bi"
class:bi-check-circle-fill={dnsResults.bimi_record.valid}
class:text-success={dnsResults.bimi_record.valid}
class:bi-x-circle-fill={!dnsResults.bimi_record.valid}
class:text-danger={!dnsResults.bimi_record.valid}
></i>
Brand Indicators for Message Identification
</h5>
<span class="badge bg-secondary">BIMI</span>
</div>
<div class="card">
<div class="card-body">
<p class="card-text small text-muted mb-2">BIMI allows your brand logo to be displayed next to your emails in supported mail clients. Requires strong DMARC enforcement (quarantine or reject policy) and optionally a Verified Mark Certificate (VMC).</p>
<div class="mb-2">
<strong>Selector:</strong> <code>{dnsResults.bimi_record.selector}</code>
<strong class="ms-3">Domain:</strong> <code>{dnsResults.bimi_record.domain}</code>

View file

@ -34,7 +34,7 @@
<div class="card-body">
{#if headerAnalysis.issues && headerAnalysis.issues.length > 0}
<div class="mb-3">
<h6>Issues</h6>
<h5>Issues</h5>
{#each headerAnalysis.issues as issue}
<div class="alert alert-{issue.severity === 'critical' || issue.severity === 'high' ? 'danger' : issue.severity === 'medium' ? 'warning' : 'info'} py-2 px-3 mb-2">
<div class="d-flex justify-content-between align-items-start">
@ -56,50 +56,59 @@
{/if}
{#if headerAnalysis.domain_alignment}
<div class="mb-3" id="domain-alignment">
<h6>Domain Alignment</h6>
<div class="card">
<div class="card-body">
<div class="row">
<div class="col-md-4">
<small class="text-muted">Aligned</small>
<div>
<i class="bi {headerAnalysis.domain_alignment.aligned ? 'bi-check-circle text-success' : 'bi-x-circle text-danger'} me-1"></i>
<div class="card mb-3" id="domain-alignment">
<div class="card-header">
<h5 class="mb-0">
<i class="bi {headerAnalysis.domain_alignment.aligned ? 'bi-check-circle-fill text-success' : 'bi-x-circle-fill text-danger'}"></i>
Domain Alignment
</h5>
</div>
<div class="card-body">
<p class="card-text small text-muted mb-3">
Domain alignment ensures that the visible "From" domain matches the domain used for authentication (Return-Path). Proper alignment is crucial for DMARC compliance and helps prevent email spoofing by verifying that the sender domain is consistent across all authentication layers.
</p>
<div class="row">
<div class="col-md-4">
<small class="text-muted">Aligned</small>
<div>
<span class="badge" class:bg-success={headerAnalysis.domain_alignment.aligned} class:bg-danger={!headerAnalysis.domain_alignment.aligned}>
<i class="bi {headerAnalysis.domain_alignment.aligned ? 'bi-check-circle-fill' : 'bi-x-circle-fill'} me-1"></i>
<strong>{headerAnalysis.domain_alignment.aligned ? 'Yes' : 'No'}</strong>
</div>
</div>
<div class="col-md-4">
<small class="text-muted">From Domain</small>
<div><code>{headerAnalysis.domain_alignment.from_domain || '-'}</code></div>
</div>
<div class="col-md-4">
<small class="text-muted">Return-Path Domain</small>
<div><code>{headerAnalysis.domain_alignment.return_path_domain || '-'}</code></div>
</span>
</div>
</div>
{#if headerAnalysis.domain_alignment.issues && headerAnalysis.domain_alignment.issues.length > 0}
<div class="mt-2">
{#each headerAnalysis.domain_alignment.issues as issue}
<div class="text-warning small">
<i class="bi bi-exclamation-triangle me-1"></i>
{issue}
</div>
{/each}
</div>
{/if}
<div class="col-md-4">
<small class="text-muted">From Domain</small>
<div><code>{headerAnalysis.domain_alignment.from_domain || '-'}</code></div>
</div>
<div class="col-md-4">
<small class="text-muted">Return-Path Domain</small>
<div><code>{headerAnalysis.domain_alignment.return_path_domain || '-'}</code></div>
</div>
</div>
{#if headerAnalysis.domain_alignment.issues && headerAnalysis.domain_alignment.issues.length > 0}
<div class="mt-2">
{#each headerAnalysis.domain_alignment.issues as issue}
<div class="text-warning small">
<i class="bi bi-exclamation-triangle me-1"></i>
{issue}
</div>
{/each}
</div>
{/if}
</div>
</div>
{/if}
{#if headerAnalysis.headers && Object.keys(headerAnalysis.headers).length > 0}
<div class="mt-3">
<h6>Headers</h6>
<h5 style="margin-bottom: -1.3em">Headers</h5>
<div class="table-responsive">
<table class="table table-sm">
<thead>
<tr>
<th>Header</th>
<th></th>
<th>When?</th>
<th>Present</th>
<th>Valid</th>
<th>Value</th>
@ -115,10 +124,12 @@
<tr>
<td>
<code>{name}</code>
</td>
<td>
{#if check.importance}
<span class="badge bg-{check.importance === 'required' ? 'danger' : check.importance === 'recommended' ? 'warning' : 'secondary'} ms-1">
<small class="text-{check.importance === 'required' ? 'danger' : check.importance === 'recommended' ? 'warning' : 'secondary'}">
{check.importance}
</span>
</small>
{/if}
</td>
<td>
@ -132,7 +143,7 @@
{/if}
</td>
<td>
<small class="text-muted text-break">{check.value || '-'}</small>
<small class="text-muted text-truncate" title={check.value}>{check.value || '-'}</small>
{#if check.issues && check.issues.length > 0}
{#each check.issues as issue}
<div class="text-warning small">

View file

@ -1,49 +1,54 @@
<script lang="ts">
import type { ClassValue } from 'svelte/elements';
import type { MXRecord } from "$lib/api/types.gen";
interface Props {
class: ClassValue;
mxRecords: MXRecord[];
title: string;
description?: string;
}
let { mxRecords, title, description }: Props = $props();
let { class: className, mxRecords, title, description }: Props = $props();
let mxsAreValids = $derived(mxRecords.reduce((acc, r) => acc && r.valid, true));
</script>
<div class="mb-4">
<h5 class="text-muted mb-2">
<span class="badge bg-secondary">MX</span> {title}
</h5>
{#if description}
<p class="small text-muted mb-2">{description}</p>
{/if}
<div class="table-responsive">
<table class="table table-sm table-bordered">
<thead>
<tr>
<th>Priority</th>
<th>Host</th>
<th>Status</th>
</tr>
</thead>
<tbody>
{#each mxRecords as mx}
<tr>
<td>{mx.priority}</td>
<td><code>{mx.host}</code></td>
<td>
{#if mx.valid}
<span class="badge bg-success">Valid</span>
{:else}
<span class="badge bg-danger">Invalid</span>
{#if mx.error}
<br><small class="text-danger">{mx.error}</small>
{/if}
{/if}
</td>
</tr>
{/each}
</tbody>
</table>
<div class="card {className}">
<div class="card-header d-flex justify-content-between align-items-center">
<h5 class="text-muted mb-0">
<i
class="bi"
class:bi-check-circle-fill={mxsAreValids}
class:text-success={mxsAreValids}
class:bi-x-circle-fill={!mxsAreValids}
class:text-danger={!mxsAreValids}
></i>
{title}
</h5>
<span class="badge bg-secondary">MX</span>
</div>
<div class="card-body pb-0">
{#if description}
<p class="card-text small text-muted mb-0">{description}</p>
{/if}
</div>
<div class="list-group list-group-flush">
{#each mxRecords as mx}
<div class="list-group-item">
<div class="d-flex gap-2 align-items-center">
{#if mx.valid}
<span class="badge bg-success">Valid</span>
{:else}
<span class="badge bg-danger">Invalid</span>
{/if}
<div>Host: <code>{mx.host}</code></div>
<div>Priority: <strong>{mx.priority}</strong></div>
</div>
{#if mx.error}
<small class="text-danger">{mx.error}</small>
{/if}
</div>
{/each}
</div>
</div>

View file

@ -12,7 +12,7 @@
let { spamassassin, spamGrade, spamScore }: Props = $props();
</script>
<div class="card">
<div class="card shadow-sm">
<div class="card-header bg-white">
<h4 class="mb-0 d-flex justify-content-between align-items-center">
<span>