Tests design and descriptions
This commit is contained in:
parent
4149a5de92
commit
a97729fea6
7 changed files with 230 additions and 164 deletions
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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">
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue