Fix typescript/svelte checks
This commit is contained in:
parent
08c6e0eef2
commit
474f25007b
17 changed files with 199 additions and 155 deletions
|
|
@ -1,13 +1,13 @@
|
|||
<script lang="ts">
|
||||
import type { Authentication, DNSResults, ReportSummary } from "$lib/api/types.gen";
|
||||
import type { AuthenticationResults, DnsResults } from "$lib/api/types.gen";
|
||||
import { getScoreColorClass } from "$lib/score";
|
||||
import GradeDisplay from "./GradeDisplay.svelte";
|
||||
|
||||
interface Props {
|
||||
authentication: Authentication;
|
||||
authentication: AuthenticationResults;
|
||||
authenticationGrade?: string;
|
||||
authenticationScore?: number;
|
||||
dnsResults?: DNSResults;
|
||||
dnsResults?: DnsResults;
|
||||
}
|
||||
|
||||
let { authentication, authenticationGrade, authenticationScore, dnsResults }: Props = $props();
|
||||
|
|
@ -132,10 +132,10 @@
|
|||
{/if}
|
||||
</div>
|
||||
{:else}
|
||||
<i class="bi {getAuthResultIcon('missing')} {getAuthResultClass('missing')} me-2 fs-5"></i>
|
||||
<i class="bi {getAuthResultIcon('missing', true)} {getAuthResultClass('missing', true)} me-2 fs-5"></i>
|
||||
<div>
|
||||
<strong>SPF</strong>
|
||||
<span class="text-uppercase ms-2 {getAuthResultClass('missing')}">
|
||||
<span class="text-uppercase ms-2 {getAuthResultClass('missing', true)}">
|
||||
{getAuthResultText('missing')}
|
||||
</span>
|
||||
<div class="text-muted small">SPF record is required for proper email authentication</div>
|
||||
|
|
@ -171,10 +171,10 @@
|
|||
{/if}
|
||||
</div>
|
||||
{:else}
|
||||
<i class="bi {getAuthResultIcon('missing')} {getAuthResultClass('missing')} me-2 fs-5"></i>
|
||||
<i class="bi {getAuthResultIcon('missing', true)} {getAuthResultClass('missing', true)} me-2 fs-5"></i>
|
||||
<div>
|
||||
<strong>DKIM</strong>
|
||||
<span class="text-uppercase ms-2 {getAuthResultClass('missing')}">
|
||||
<span class="text-uppercase ms-2 {getAuthResultClass('missing', true)}">
|
||||
{getAuthResultText('missing')}
|
||||
</span>
|
||||
<div class="text-muted small">DKIM signature is required for proper email authentication</div>
|
||||
|
|
@ -214,7 +214,7 @@
|
|||
{/if}
|
||||
|
||||
<!-- X-Aligned-From (Disabled) -->
|
||||
{#if false && authentication.x_aligned_from}
|
||||
{#if authentication.x_aligned_from}
|
||||
<div class="list-group-item" id="authentication-x-aligned-from">
|
||||
<div class="d-flex align-items-start">
|
||||
<i class="bi {getAuthResultIcon(authentication.x_aligned_from.result, false)} {getAuthResultClass(authentication.x_aligned_from.result, false)} me-2 fs-5"></i>
|
||||
|
|
@ -253,7 +253,7 @@
|
|||
<span class="text-muted">{authentication.dmarc.domain}</span>
|
||||
</div>
|
||||
{/if}
|
||||
{#snippet DMARCPolicy(policy)}
|
||||
{#snippet DMARCPolicy(policy: string)}
|
||||
<div class="small">
|
||||
<strong>Policy:</strong>
|
||||
<span
|
||||
|
|
@ -268,10 +268,10 @@
|
|||
</div>
|
||||
{/snippet}
|
||||
{#if authentication.dmarc.result != "none"}
|
||||
{#if authentication.dmarc.details.indexOf("policy.published-domain-policy=") > 0}
|
||||
{#if authentication.dmarc.details && authentication.dmarc.details.indexOf("policy.published-domain-policy=") > 0}
|
||||
{@const policy = authentication.dmarc.details.replace(/^.*policy.published-domain-policy=([^\s]+).*$/, "$1")}
|
||||
{@render DMARCPolicy(policy)}
|
||||
{:else if authentication.dmarc.domain}
|
||||
{:else if authentication.dmarc.domain && dnsResults?.dmarc_record?.policy}
|
||||
{@render DMARCPolicy(dnsResults.dmarc_record.policy)}
|
||||
{/if}
|
||||
{/if}
|
||||
|
|
@ -280,10 +280,10 @@
|
|||
{/if}
|
||||
</div>
|
||||
{:else}
|
||||
<i class="bi {getAuthResultIcon('missing')} {getAuthResultClass('missing')} me-2 fs-5"></i>
|
||||
<i class="bi {getAuthResultIcon('missing', true)} {getAuthResultClass('missing', true)} me-2 fs-5"></i>
|
||||
<div>
|
||||
<strong>DMARC</strong>
|
||||
<span class="text-uppercase ms-2 {getAuthResultClass('missing')}">
|
||||
<span class="text-uppercase ms-2 {getAuthResultClass('missing', true)}">
|
||||
{getAuthResultText('missing')}
|
||||
</span>
|
||||
<div class="text-muted small">DMARC policy is required for proper email authentication</div>
|
||||
|
|
@ -296,10 +296,10 @@
|
|||
<div class="list-group-item" id="authentication-bimi">
|
||||
<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>
|
||||
<i class="bi {getAuthResultIcon(authentication.bimi.result, false)} {getAuthResultClass(authentication.bimi.result, false)} me-2 fs-5"></i>
|
||||
<div>
|
||||
<strong>BIMI</strong>
|
||||
<span class="text-uppercase ms-2 {getAuthResultClass(authentication.bimi.result)}">
|
||||
<span class="text-uppercase ms-2 {getAuthResultClass(authentication.bimi.result, false)}">
|
||||
{authentication.bimi.result}
|
||||
</span>
|
||||
{#if authentication.bimi.details}
|
||||
|
|
@ -335,10 +335,10 @@
|
|||
{#if authentication.arc}
|
||||
<div class="list-group-item" id="authentication-arc">
|
||||
<div class="d-flex align-items-start">
|
||||
<i class="bi {getAuthResultIcon(authentication.arc.result)} {getAuthResultClass(authentication.arc.result)} me-2 fs-5"></i>
|
||||
<i class="bi {getAuthResultIcon(authentication.arc.result, false)} {getAuthResultClass(authentication.arc.result, false)} me-2 fs-5"></i>
|
||||
<div>
|
||||
<strong>ARC</strong>
|
||||
<span class="text-uppercase ms-2 {getAuthResultClass(authentication.arc.result)}">
|
||||
<span class="text-uppercase ms-2 {getAuthResultClass(authentication.arc.result, false)}">
|
||||
{authentication.arc.result}
|
||||
</span>
|
||||
{#if authentication.arc.chain_length}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
<script lang="ts">
|
||||
import type { BIMIRecord } from "$lib/api/types.gen";
|
||||
import type { BimiRecord } from "$lib/api/types.gen";
|
||||
|
||||
interface Props {
|
||||
bimiRecord?: BIMIRecord;
|
||||
bimiRecord?: BimiRecord;
|
||||
}
|
||||
|
||||
let { bimiRecord }: Props = $props();
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
<script lang="ts">
|
||||
import type { RBLCheck, ReceivedHop } from "$lib/api/types.gen";
|
||||
import type { BlacklistCheck, ReceivedHop } from "$lib/api/types.gen";
|
||||
import { getScoreColorClass } from "$lib/score";
|
||||
import GradeDisplay from "./GradeDisplay.svelte";
|
||||
import EmailPathCard from "./EmailPathCard.svelte";
|
||||
|
||||
interface Props {
|
||||
blacklists: Record<string, RBLCheck[]>;
|
||||
blacklists: Record<string, BlacklistCheck[]>;
|
||||
blacklistGrade?: string;
|
||||
blacklistScore?: number;
|
||||
receivedChain?: ReceivedHop[];
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
<script lang="ts">
|
||||
import type { DKIMRecord } from "$lib/api/types.gen";
|
||||
import type { DkimRecord } from "$lib/api/types.gen";
|
||||
|
||||
interface Props {
|
||||
dkimRecords?: DKIMRecord[];
|
||||
dkimRecords?: DkimRecord[];
|
||||
}
|
||||
|
||||
let { dkimRecords }: Props = $props();
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
<script lang="ts">
|
||||
import type { DMARCRecord } from "$lib/api/types.gen";
|
||||
import type { DmarcRecord } from "$lib/api/types.gen";
|
||||
|
||||
interface Props {
|
||||
dmarcRecord?: DMARCRecord;
|
||||
dmarcRecord?: DmarcRecord;
|
||||
}
|
||||
|
||||
let { dmarcRecord }: Props = $props();
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
<script lang="ts">
|
||||
import type { DomainAlignment, DNSResults, ReceivedHop } from "$lib/api/types.gen";
|
||||
import type { DomainAlignment, DnsResults, ReceivedHop } from "$lib/api/types.gen";
|
||||
import { getScoreColorClass } from "$lib/score";
|
||||
import GradeDisplay from "./GradeDisplay.svelte";
|
||||
import MxRecordsDisplay from "./MxRecordsDisplay.svelte";
|
||||
|
|
@ -12,7 +12,7 @@
|
|||
|
||||
interface Props {
|
||||
domainAlignment?: DomainAlignment;
|
||||
dnsResults?: DNSResults;
|
||||
dnsResults?: DnsResults;
|
||||
dnsGrade?: string;
|
||||
dnsScore?: number;
|
||||
receivedChain?: ReceivedHop[];
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
<script lang="ts">
|
||||
interface Props {
|
||||
grade?: string;
|
||||
score: number;
|
||||
score?: number;
|
||||
size?: "inline" | "small" | "medium" | "large";
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
<script lang="ts">
|
||||
import type { AuthResult, DMARCRecord, HeaderAnalysis } from "$lib/api/types.gen";
|
||||
import type { AuthResult, DmarcRecord, HeaderAnalysis } from "$lib/api/types.gen";
|
||||
import { getScoreColorClass } from "$lib/score";
|
||||
import GradeDisplay from "./GradeDisplay.svelte";
|
||||
|
||||
interface Props {
|
||||
dmarcRecord: DMARCRecord;
|
||||
dmarcRecord?: DmarcRecord;
|
||||
headerAnalysis: HeaderAnalysis;
|
||||
headerGrade?: string;
|
||||
headerScore?: number;
|
||||
|
|
@ -62,7 +62,7 @@
|
|||
<div class="card-header">
|
||||
<h5 class="mb-0">
|
||||
{#if xAlignedFrom}
|
||||
<i class="bi {xAlignedFrom == "pass" ? 'bi-check-circle-fill text-success' : 'bi-x-circle-fill text-danger'}"></i>
|
||||
<i class="bi {xAlignedFrom.result == "pass" ? 'bi-check-circle-fill text-success' : 'bi-x-circle-fill text-danger'}"></i>
|
||||
{:else}
|
||||
<i class="bi {headerAnalysis.domain_alignment.aligned ? 'bi-check-circle-fill text-success' : headerAnalysis.domain_alignment.relaxed_aligned ? 'bi-check-circle text-info' : 'bi-x-circle-fill text-danger'}"></i>
|
||||
{/if}
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
<script lang="ts">
|
||||
import type { ClassValue } from 'svelte/elements';
|
||||
import type { MXRecord } from "$lib/api/types.gen";
|
||||
import type { ClassValue } from "svelte/elements";
|
||||
import type { MxRecord } from "$lib/api/types.gen";
|
||||
|
||||
interface Props {
|
||||
class: ClassValue;
|
||||
mxRecords: MXRecord[];
|
||||
mxRecords: MxRecord[];
|
||||
title: string;
|
||||
description?: string;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,6 +8,8 @@
|
|||
|
||||
interface Props {
|
||||
test: Test;
|
||||
nbfetch: number;
|
||||
nextfetch: number;
|
||||
fetching?: boolean;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -5,8 +5,8 @@
|
|||
|
||||
interface Props {
|
||||
spamassassin: SpamAssassinResult;
|
||||
spamGrade: string;
|
||||
spamScore: number;
|
||||
spamGrade?: string;
|
||||
spamScore?: number;
|
||||
}
|
||||
|
||||
let { spamassassin, spamGrade, spamScore }: Props = $props();
|
||||
|
|
|
|||
|
|
@ -1,18 +1,18 @@
|
|||
<script lang="ts">
|
||||
import type { SPFRecord } from "$lib/api/types.gen";
|
||||
import type { SpfRecord } from "$lib/api/types.gen";
|
||||
|
||||
interface Props {
|
||||
spfRecords?: SPFRecord[];
|
||||
spfRecords?: SpfRecord[];
|
||||
}
|
||||
|
||||
let { spfRecords }: Props = $props();
|
||||
|
||||
// Compute overall validity
|
||||
const spfIsValid = $derived(
|
||||
spfRecords?.reduce((acc, r) => acc && r.valid, true) ?? false
|
||||
);
|
||||
const spfIsValid = $derived(spfRecords?.reduce((acc, r) => acc && r.valid, true) ?? false);
|
||||
const spfCanBeImprove = $derived(
|
||||
spfRecords.length > 0 && spfRecords.filter((r) => !r.record.includes(" redirect="))[0]?.all_qualifier != "-"
|
||||
spfRecords &&
|
||||
spfRecords.length > 0 &&
|
||||
spfRecords.filter((r) => !r.record?.includes(" redirect="))[0]?.all_qualifier != "-",
|
||||
);
|
||||
</script>
|
||||
|
||||
|
|
@ -58,13 +58,13 @@
|
|||
{#if spf.all_qualifier}
|
||||
<div class="mb-2">
|
||||
<strong>All Mechanism Policy:</strong>
|
||||
{#if spf.all_qualifier === '-'}
|
||||
{#if spf.all_qualifier === "-"}
|
||||
<span class="badge bg-success">Strict (-all)</span>
|
||||
{:else if spf.all_qualifier === '~'}
|
||||
{:else if spf.all_qualifier === "~"}
|
||||
<span class="badge bg-warning">Softfail (~all)</span>
|
||||
{:else if spf.all_qualifier === '+'}
|
||||
{:else if spf.all_qualifier === "+"}
|
||||
<span class="badge bg-danger">Pass (+all)</span>
|
||||
{:else if spf.all_qualifier === '?'}
|
||||
{:else if spf.all_qualifier === "?"}
|
||||
<span class="badge bg-warning">Neutral (?all)</span>
|
||||
{/if}
|
||||
{#if index === 0 || (index === 1 && spfRecords[0].record?.includes('redirect='))}
|
||||
|
|
|
|||
|
|
@ -5,8 +5,10 @@
|
|||
interface TextSegment {
|
||||
text: string;
|
||||
highlight?: {
|
||||
color: "good" | "warning" | "danger";
|
||||
color?: "good" | "warning" | "danger";
|
||||
bold?: boolean;
|
||||
emphasis?: boolean;
|
||||
monospace?: boolean;
|
||||
};
|
||||
link?: string;
|
||||
}
|
||||
|
|
@ -22,19 +24,19 @@
|
|||
|
||||
// Email sender information
|
||||
const mailFrom = report.header_analysis?.headers?.from?.value || "an unknown sender";
|
||||
const hasDkim = report.authentication?.dkim && report.authentication.dkim.length > 0;
|
||||
const dkimPassed = hasDkim && report.authentication.dkim.some(d => d.result === "pass");
|
||||
const hasDkim = report.authentication?.dkim && report.authentication?.dkim.length > 0;
|
||||
const dkimPassed = hasDkim && report.authentication?.dkim?.some((d) => d.result === "pass");
|
||||
|
||||
segments.push({ text: "Received a " });
|
||||
segments.push({
|
||||
text: dkimPassed ? "DKIM-signed" : "non-DKIM-signed",
|
||||
highlight: { color: dkimPassed ? "good" : "danger", bold: true },
|
||||
link: "#authentication-dkim"
|
||||
link: "#authentication-dkim",
|
||||
});
|
||||
segments.push({ text: " email from " });
|
||||
segments.push({
|
||||
text: mailFrom,
|
||||
highlight: { emphasis: true }
|
||||
highlight: { emphasis: true },
|
||||
});
|
||||
|
||||
// Server information and hops
|
||||
|
|
@ -47,12 +49,12 @@
|
|||
segments.push({
|
||||
text: serverName,
|
||||
highlight: { monospace: true },
|
||||
link: "#header-details"
|
||||
link: "#header-details",
|
||||
});
|
||||
segments.push({ text: " after " });
|
||||
segments.push({
|
||||
text: `${hopCount-1} hop${hopCount-1 !== 1 ? "s" : ""}`,
|
||||
link: "#email-path"
|
||||
text: `${hopCount - 1} hop${hopCount - 1 !== 1 ? "s" : ""}`,
|
||||
link: "#email-path",
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -65,22 +67,25 @@
|
|||
segments.push({
|
||||
text: "authenticated",
|
||||
highlight: { color: "good", bold: true },
|
||||
link: "#authentication-details"
|
||||
link: "#authentication-details",
|
||||
});
|
||||
segments.push({ text: " to send email on behalf of " });
|
||||
segments.push({ text: report.header_analysis?.domain_alignment?.from_domain, highlight: {monospace: true} });
|
||||
segments.push({
|
||||
text: report.header_analysis?.domain_alignment?.from_domain || "unknown domain",
|
||||
highlight: { monospace: true },
|
||||
});
|
||||
} else if (spfResult && spfResult !== "none") {
|
||||
segments.push({
|
||||
text: "not authenticated",
|
||||
highlight: { color: "danger", bold: true },
|
||||
link: "#authentication-spf"
|
||||
link: "#authentication-spf",
|
||||
});
|
||||
segments.push({ text: " (failed authentication checks)" });
|
||||
} else {
|
||||
segments.push({
|
||||
text: "not authenticated",
|
||||
highlight: { color: "warning", bold: true },
|
||||
link: "#authentication-details"
|
||||
link: "#authentication-details",
|
||||
});
|
||||
segments.push({ text: " (lacks proper authentication)" });
|
||||
}
|
||||
|
|
@ -92,21 +97,23 @@
|
|||
segments.push({
|
||||
text: "failed",
|
||||
highlight: { color: "danger", bold: true },
|
||||
link: "#authentication-spf"
|
||||
link: "#authentication-spf",
|
||||
});
|
||||
segments.push({
|
||||
text: ", the sending server is not authorized to send mail for this domain",
|
||||
});
|
||||
segments.push({ text: ", the sending server is not authorized to send mail for this domain" });
|
||||
} else if (spfResult === "softfail") {
|
||||
segments.push({
|
||||
text: "soft-failed",
|
||||
highlight: { color: "warning", bold: true },
|
||||
link: "#authentication-spf"
|
||||
link: "#authentication-spf",
|
||||
});
|
||||
segments.push({ text: ", the sending server may not be authorized" });
|
||||
} else if (spfResult === "temperror" || spfResult === "permerror") {
|
||||
segments.push({
|
||||
text: "encountered an error",
|
||||
highlight: { color: "warning", bold: true },
|
||||
link: "#authentication-spf"
|
||||
link: "#authentication-spf",
|
||||
});
|
||||
segments.push({ text: ", check your SPF record configuration" });
|
||||
} else if (spfResult === "none") {
|
||||
|
|
@ -114,9 +121,11 @@
|
|||
segments.push({
|
||||
text: "no SPF record",
|
||||
highlight: { color: "danger", bold: true },
|
||||
link: "#dns-spf"
|
||||
link: "#dns-spf",
|
||||
});
|
||||
segments.push({
|
||||
text: ", you should add one to specify which servers can send email on your behalf",
|
||||
});
|
||||
segments.push({ text: ", you should add one to specify which servers can send email on your behalf" });
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -129,13 +138,13 @@
|
|||
segments.push({
|
||||
text: "good",
|
||||
highlight: { color: "good", bold: true },
|
||||
link: "#dns-ptr"
|
||||
link: "#dns-ptr",
|
||||
});
|
||||
} else if (iprevResult.result === "fail") {
|
||||
segments.push({
|
||||
text: "failed",
|
||||
highlight: { color: "danger", bold: true },
|
||||
link: "#dns-ptr"
|
||||
link: "#dns-ptr",
|
||||
});
|
||||
segments.push({ text: " to pass the test" });
|
||||
} else {
|
||||
|
|
@ -143,7 +152,7 @@
|
|||
segments.push({
|
||||
text: iprevResult.result,
|
||||
highlight: { color: "warning", bold: true },
|
||||
link: "#dns-ptr"
|
||||
link: "#dns-ptr",
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -152,20 +161,20 @@
|
|||
const blacklists = report.blacklists;
|
||||
if (blacklists && Object.keys(blacklists).length > 0) {
|
||||
const allChecks = Object.values(blacklists).flat();
|
||||
const listedCount = allChecks.filter(check => check.listed).length;
|
||||
const listedCount = allChecks.filter((check) => check.listed).length;
|
||||
|
||||
segments.push({ text: ". Your server is " });
|
||||
if (listedCount > 0) {
|
||||
segments.push({
|
||||
text: `blacklisted on ${listedCount} list${listedCount !== 1 ? "s" : ""}`,
|
||||
highlight: { color: "danger", bold: true },
|
||||
link: "#rbl-details"
|
||||
link: "#rbl-details",
|
||||
});
|
||||
} else {
|
||||
segments.push({
|
||||
text: "not blacklisted",
|
||||
highlight: { color: "good", bold: true },
|
||||
link: "#rbl-details"
|
||||
link: "#rbl-details",
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -178,7 +187,7 @@
|
|||
segments.push({
|
||||
text: "good",
|
||||
highlight: { color: "good", bold: true },
|
||||
link: "#domain-alignment"
|
||||
link: "#domain-alignment",
|
||||
});
|
||||
if (!domainAlignment.aligned) {
|
||||
segments.push({ text: " using organizational domain" });
|
||||
|
|
@ -187,17 +196,22 @@
|
|||
segments.push({
|
||||
text: "misaligned",
|
||||
highlight: { color: "danger", bold: true },
|
||||
link: "#domain-alignment"
|
||||
link: "#domain-alignment",
|
||||
});
|
||||
segments.push({ text: ": " });
|
||||
segments.push({ text: "Return-Path", highlight: { monospace: true } });
|
||||
segments.push({ text: " is set to an address of " });
|
||||
segments.push({ text: report.header_analysis?.domain_alignment?.return_path_domain, highlight: { monospace: true } });
|
||||
segments.push({
|
||||
text:
|
||||
report.header_analysis?.domain_alignment?.return_path_domain ||
|
||||
"unknown domain",
|
||||
highlight: { monospace: true },
|
||||
});
|
||||
segments.push({ text: ", you should " });
|
||||
segments.push({
|
||||
text: "update it",
|
||||
highlight: { bold: true },
|
||||
link: "#domain-alignment"
|
||||
link: "#domain-alignment",
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -210,25 +224,28 @@
|
|||
segments.push({
|
||||
text: "don't have",
|
||||
highlight: { color: "danger", bold: true },
|
||||
link: "#dns-dmarc"
|
||||
link: "#dns-dmarc",
|
||||
});
|
||||
segments.push({ text: " a DMARC record, " });
|
||||
segments.push({ text: "consider adding at least a record with the '", highlight: { bold : true } });
|
||||
segments.push({
|
||||
text: "consider adding at least a record with the '",
|
||||
highlight: { bold: true },
|
||||
});
|
||||
segments.push({ text: "none", highlight: { monospace: true, bold: true } });
|
||||
segments.push({ text: "' policy", highlight: { bold : true } });
|
||||
segments.push({ text: "' policy", highlight: { bold: true } });
|
||||
} else if (!dmarcRecord.valid) {
|
||||
segments.push({ text: ". Your DMARC record has " });
|
||||
segments.push({
|
||||
text: "issues",
|
||||
highlight: { color: "danger", bold: true },
|
||||
link: "#dns-dmarc"
|
||||
link: "#dns-dmarc",
|
||||
});
|
||||
} else if (dmarcRecord.policy === "none") {
|
||||
segments.push({ text: ". Your DMARC policy is " });
|
||||
segments.push({
|
||||
text: "set to 'none'",
|
||||
highlight: { color: "warning", bold: true },
|
||||
link: "#dns-dmarc"
|
||||
link: "#dns-dmarc",
|
||||
});
|
||||
segments.push({ text: ", which provides monitoring but no protection" });
|
||||
} else if (dmarcRecord.policy === "quarantine" || dmarcRecord.policy === "reject") {
|
||||
|
|
@ -236,7 +253,7 @@
|
|||
segments.push({
|
||||
text: dmarcRecord.policy,
|
||||
highlight: { color: "good", bold: true, monospace: true },
|
||||
link: "#dns-dmarc"
|
||||
link: "#dns-dmarc",
|
||||
});
|
||||
segments.push({ text: "'" });
|
||||
if (dmarcRecord.policy === "reject") {
|
||||
|
|
@ -247,17 +264,17 @@
|
|||
segments.push({ text: "'" });
|
||||
}
|
||||
}
|
||||
} else if (dmarcResult && dmarcResult.result === "fail") {
|
||||
} else if (dmarcResult === "fail") {
|
||||
segments.push({ text: ". DMARC check " });
|
||||
segments.push({
|
||||
text: "failed",
|
||||
highlight: { color: "danger", bold: true },
|
||||
link: "#authentication-dmarc"
|
||||
link: "#authentication-dmarc",
|
||||
});
|
||||
}
|
||||
|
||||
// BIMI
|
||||
if (dmarcRecord.valid && dmarcRecord.policy != "none") {
|
||||
if (dmarcRecord && dmarcRecord.valid && dmarcRecord.policy != "none") {
|
||||
const bimiResult = report.authentication?.bimi;
|
||||
const bimiRecord = report.dns_results?.bimi_record;
|
||||
if (bimiRecord?.valid) {
|
||||
|
|
@ -268,7 +285,7 @@
|
|||
link: "#dns-bimi"
|
||||
});
|
||||
segments.push({ text: " for brand indicator display" });
|
||||
} else if (bimiResult && bimiResult.details.indexOf("(No BIMI records found)") >= 0) {
|
||||
} else if (bimiResult && bimiResult.details && bimiResult.details.indexOf("(No BIMI records found)") >= 0) {
|
||||
segments.push({ text: ". Your domain has no " });
|
||||
segments.push({
|
||||
text: "BIMI record",
|
||||
|
|
@ -293,19 +310,21 @@
|
|||
segments.push({ text: ". " });
|
||||
segments.push({
|
||||
text: "ARC chain validation",
|
||||
link: "#authentication-arc"
|
||||
link: "#authentication-arc",
|
||||
});
|
||||
segments.push({ text: " " });
|
||||
if (arcResult.chain_valid) {
|
||||
segments.push({
|
||||
text: "passed",
|
||||
highlight: { color: "good", bold: true }
|
||||
highlight: { color: "good", bold: true },
|
||||
});
|
||||
segments.push({
|
||||
text: ` with ${arcResult.chain_length} set${arcResult.chain_length !== 1 ? "s" : ""}, indicating proper email forwarding`,
|
||||
});
|
||||
segments.push({ text: ` with ${arcResult.chain_length} set${arcResult.chain_length !== 1 ? "s" : ""}, indicating proper email forwarding` });
|
||||
} else {
|
||||
segments.push({
|
||||
text: "failed",
|
||||
highlight: { color: "danger", bold: true }
|
||||
highlight: { color: "danger", bold: true },
|
||||
});
|
||||
segments.push({ text: ", which may indicate issues with email forwarding" });
|
||||
}
|
||||
|
|
@ -316,20 +335,25 @@
|
|||
const listUnsubscribe = headers?.["list-unsubscribe"];
|
||||
const listUnsubscribePost = headers?.["list-unsubscribe-post"];
|
||||
|
||||
const hasNewsletterHeaders = (listUnsubscribe?.importance === "newsletter" && listUnsubscribe?.present) ||
|
||||
(listUnsubscribePost?.importance === "newsletter" && listUnsubscribePost?.present);
|
||||
const hasNewsletterHeaders =
|
||||
(listUnsubscribe?.importance === "newsletter" && listUnsubscribe?.present) ||
|
||||
(listUnsubscribePost?.importance === "newsletter" && listUnsubscribePost?.present);
|
||||
|
||||
if (!hasNewsletterHeaders && (listUnsubscribe?.importance === "newsletter" || listUnsubscribePost?.importance === "newsletter")) {
|
||||
if (
|
||||
!hasNewsletterHeaders &&
|
||||
(listUnsubscribe?.importance === "newsletter" ||
|
||||
listUnsubscribePost?.importance === "newsletter")
|
||||
) {
|
||||
segments.push({ text: ". This email is " });
|
||||
segments.push({
|
||||
text: "missing unsubscribe headers",
|
||||
highlight: { color: "warning", bold: true },
|
||||
link: "#header-details"
|
||||
link: "#header-details",
|
||||
});
|
||||
segments.push({ text: " and is " });
|
||||
segments.push({
|
||||
text: "not suitable for marketing campaigns",
|
||||
highlight: { bold: true }
|
||||
highlight: { bold: true },
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -344,7 +368,7 @@
|
|||
segments.push({
|
||||
text: "flagged as spam",
|
||||
highlight: { color: "danger", bold: true },
|
||||
link: "#spam-details"
|
||||
link: "#spam-details",
|
||||
});
|
||||
segments.push({ text: " and needs review" });
|
||||
} else if (contentScore < 50) {
|
||||
|
|
@ -352,49 +376,55 @@
|
|||
segments.push({
|
||||
text: "needs improvement",
|
||||
highlight: { color: "warning", bold: true },
|
||||
link: "#content-details"
|
||||
link: "#content-details",
|
||||
});
|
||||
} else if (contentScore >= 100 && spamScore >= 100) {
|
||||
segments.push({ text: "Content " });
|
||||
segments.push({
|
||||
text: "looks great",
|
||||
highlight: { color: "good", bold: true },
|
||||
link: "#content-details"
|
||||
link: "#content-details",
|
||||
});
|
||||
} else if (spamScore < 50) {
|
||||
segments.push({ text: "Your " });
|
||||
segments.push({
|
||||
text: "spam score",
|
||||
highlight: { color: "danger", bold: true },
|
||||
link: "#spam-details"
|
||||
link: "#spam-details",
|
||||
});
|
||||
segments.push({ text: " is low" });
|
||||
if (report.spamassassin.tests.includes("EMPTY_MESSAGE")) {
|
||||
segments.push({ text: " (you sent an empty message, which can cause this issue, retry with some real content)", highlight: { bold: true } });
|
||||
if (report.spamassassin?.tests?.includes("EMPTY_MESSAGE")) {
|
||||
segments.push({
|
||||
text: " (you sent an empty message, which can cause this issue, retry with some real content)",
|
||||
highlight: { bold: true },
|
||||
});
|
||||
}
|
||||
} else if (spamScore < 90) {
|
||||
segments.push({ text: "Pay attention to your " });
|
||||
segments.push({
|
||||
text: "spam score",
|
||||
highlight: { color: "warning", bold: true },
|
||||
link: "#spam-details"
|
||||
link: "#spam-details",
|
||||
});
|
||||
if (report.spamassassin.tests.includes("EMPTY_MESSAGE")) {
|
||||
segments.push({ text: " (you sent an empty message, which can cause this issue, retry with some real content)", highlight: { bold: true } });
|
||||
if (report.spamassassin?.tests?.includes("EMPTY_MESSAGE")) {
|
||||
segments.push({
|
||||
text: " (you sent an empty message, which can cause this issue, retry with some real content)",
|
||||
highlight: { bold: true },
|
||||
});
|
||||
}
|
||||
} else if (contentScore >= 80) {
|
||||
segments.push({ text: "Content " });
|
||||
segments.push({
|
||||
text: "looks good",
|
||||
highlight: { color: "good", bold: true },
|
||||
link: "#content-details"
|
||||
link: "#content-details",
|
||||
});
|
||||
} else {
|
||||
segments.push({ text: "Content " });
|
||||
segments.push({
|
||||
text: "should be reviewed",
|
||||
highlight: { color: "warning", bold: true },
|
||||
link: "#content-details"
|
||||
link: "#content-details",
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -403,7 +433,7 @@
|
|||
return segments;
|
||||
}
|
||||
|
||||
function getColorClass(color: "good" | "warning" | "danger"): string {
|
||||
function getColorClass(color?: "good" | "warning" | "danger"): string {
|
||||
switch (color) {
|
||||
case "good":
|
||||
return "text-success";
|
||||
|
|
@ -411,28 +441,14 @@
|
|||
return "text-warning";
|
||||
case "danger":
|
||||
return "text-danger";
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
const summarySegments = $derived(buildSummary());
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.summary-link {
|
||||
text-decoration: none;
|
||||
transition: opacity 0.2s ease;
|
||||
}
|
||||
|
||||
.summary-link:hover {
|
||||
opacity: 0.8;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.highlighted {
|
||||
font-weight: 600;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="card shadow-sm border-0 mb-4">
|
||||
<div class="card-body p-4">
|
||||
<h5 class="card-title mb-3">
|
||||
|
|
@ -460,3 +476,19 @@
|
|||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.summary-link {
|
||||
text-decoration: none;
|
||||
transition: opacity 0.2s ease;
|
||||
}
|
||||
|
||||
.summary-link:hover {
|
||||
opacity: 0.8;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.highlighted {
|
||||
font-weight: 600;
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -7,8 +7,8 @@ export class NotAuthorizedError extends Error {
|
|||
}
|
||||
}
|
||||
|
||||
async function customFetch(url: string, init: RequestInit): Promise<Response> {
|
||||
const response = await fetch(url, init);
|
||||
async function customFetch(input: RequestInfo | URL, init?: RequestInit): Promise<Response> {
|
||||
const response = await fetch(input, init);
|
||||
|
||||
if (response.status === 400) {
|
||||
const json = await response.json();
|
||||
|
|
|
|||
|
|
@ -38,7 +38,12 @@
|
|||
</h3>
|
||||
<ul class="footer-links">
|
||||
<li><a href="/#features">Features</a></li>
|
||||
<li><a href="#">Download</a></li>
|
||||
<li>
|
||||
<a
|
||||
href="https://github.com/happyDomain/happydeliver/releases"
|
||||
target="_blank">Download</a
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://github.com/happyDomain/happydeliver/" target="_blank">
|
||||
GitHub
|
||||
|
|
@ -73,16 +78,32 @@
|
|||
class="d-flex flex-wrap justify-content-between footer-links"
|
||||
style="gap: .5em; font-size: 2em"
|
||||
>
|
||||
<a href="https://framagit.org/happyDomain/happydeliver" target="_blank">
|
||||
<a
|
||||
href="https://framagit.org/happyDomain/happydeliver"
|
||||
target="_blank"
|
||||
aria-label="Visit our GitLab repository"
|
||||
>
|
||||
<i class="bi bi-gitlab"></i>
|
||||
</a>
|
||||
<a href="https://github.com/happyDomain/happydeliver" target="_blank">
|
||||
<a
|
||||
href="https://github.com/happyDomain/happydeliver"
|
||||
target="_blank"
|
||||
aria-label="Visit our GitHub repository"
|
||||
>
|
||||
<i class="bi bi-github"></i>
|
||||
</a>
|
||||
<a href="https://feedback.happydomain.org/" target="_blank">
|
||||
<a
|
||||
href="https://feedback.happydomain.org/"
|
||||
target="_blank"
|
||||
aria-label="Share your feedback"
|
||||
>
|
||||
<i class="bi bi-lightbulb-fill"></i>
|
||||
</a>
|
||||
<a href="https://floss.social/@happyDomain" target="_blank">
|
||||
<a
|
||||
href="https://floss.social/@happyDomain"
|
||||
target="_blank"
|
||||
aria-label="Follow us on Mastodon"
|
||||
>
|
||||
<i class="bi bi-mastodon"></i>
|
||||
</a>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -10,10 +10,11 @@ export const load: Load = async ({}) => {
|
|||
try {
|
||||
response = await apiCreateTest();
|
||||
} catch (err) {
|
||||
error(err.response.status, err.message);
|
||||
const errorObj = err as { response?: { status?: number }; message?: string };
|
||||
error(errorObj.response?.status || 500, errorObj.message || "Unknown error");
|
||||
}
|
||||
|
||||
if (response.response.ok) {
|
||||
if (response.response.ok && response.data) {
|
||||
redirect(302, `/test/${response.data.id}`);
|
||||
} else {
|
||||
error(response.response.status, response.error);
|
||||
|
|
|
|||
|
|
@ -28,6 +28,8 @@
|
|||
let fetching = $state(false);
|
||||
|
||||
async function fetchTest() {
|
||||
if (!testId) return;
|
||||
|
||||
if (nbfetch > 0) {
|
||||
nextfetch = Math.max(nextfetch, Math.floor(3 + nbfetch * 0.5));
|
||||
}
|
||||
|
|
@ -89,8 +91,8 @@
|
|||
}
|
||||
}
|
||||
|
||||
let lastTestId = null;
|
||||
function testChange(newTestId) {
|
||||
let lastTestId: string | null = null;
|
||||
function testChange(newTestId: string) {
|
||||
if (lastTestId != newTestId) {
|
||||
lastTestId = newTestId;
|
||||
test = null;
|
||||
|
|
@ -100,7 +102,10 @@
|
|||
}
|
||||
|
||||
$effect(() => {
|
||||
testChange(page.params.test);
|
||||
const newTestId = page.params.test;
|
||||
if (newTestId) {
|
||||
testChange(newTestId);
|
||||
}
|
||||
})
|
||||
|
||||
onDestroy(() => {
|
||||
|
|
@ -128,9 +133,9 @@
|
|||
|
||||
function handleExportJSON() {
|
||||
const dataStr = JSON.stringify(report, null, 2);
|
||||
const dataBlob = new Blob([dataStr], { type: 'application/json' });
|
||||
const dataBlob = new Blob([dataStr], { type: "application/json" });
|
||||
const url = URL.createObjectURL(dataBlob);
|
||||
const link = document.createElement('a');
|
||||
const link = document.createElement("a");
|
||||
link.href = url;
|
||||
link.download = `report-${testId}.json`;
|
||||
link.click();
|
||||
|
|
@ -140,7 +145,7 @@
|
|||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>{report ? `Test of ${report.dns_results.from_domain} ${report.test_id.slice(0, 7)}` : (test ? `Test ${test.id.slice(0, 7)}` : "Loading...")} - happyDeliver</title>
|
||||
<title>{report ? `Test${report.dns_results ? ` of ${report.dns_results.from_domain}` : ''} ${report.test_id?.slice(0, 7) || ''}` : (test ? `Test ${test.id.slice(0, 7)}` : "Loading...")} - happyDeliver</title>
|
||||
</svelte:head>
|
||||
|
||||
<div class="container py-5">
|
||||
|
|
@ -283,11 +288,11 @@
|
|||
<div class="row mb-4" id="header">
|
||||
<div class="col-12">
|
||||
<HeaderAnalysisCard
|
||||
dmarcRecord={report.dns_results.dmarc_record}
|
||||
dmarcRecord={report.dns_results?.dmarc_record}
|
||||
headerAnalysis={report.header_analysis}
|
||||
headerGrade={report.summary?.header_grade}
|
||||
headerScore={report.summary?.header_score}
|
||||
xAlignedFrom={report.authentication.x_aligned_from}
|
||||
xAlignedFrom={report.authentication?.x_aligned_from}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -348,23 +353,6 @@
|
|||
}
|
||||
}
|
||||
|
||||
.category-section {
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.category-title {
|
||||
font-size: 1.25rem;
|
||||
font-weight: 600;
|
||||
color: #495057;
|
||||
padding-bottom: 0.5rem;
|
||||
border-bottom: 2px solid #e9ecef;
|
||||
}
|
||||
|
||||
.category-score {
|
||||
font-size: 1rem;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.menu-container {
|
||||
position: relative;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue