New route to check blacklist only
Some checks are pending
continuous-integration/drone/push Build is running

This commit is contained in:
nemunaire 2025-10-31 11:01:58 +07:00
commit 6f22d340d2
9 changed files with 586 additions and 2 deletions

View file

@ -29,6 +29,7 @@ interface AppConfig {
const defaultConfig: AppConfig = {
report_retention: 0,
survey_url: "",
rbls: [],
};
function getConfigFromScriptTag(): AppConfig | null {

View file

@ -235,9 +235,13 @@
</div>
<div class="text-center mt-4">
<a href="/domain" class="btn btn-secondary btn-lg">
<a href="/domain" class="btn btn-secondary btn-lg me-2">
<i class="bi bi-globe me-2"></i>
Or Test Domain Only
Test Domain Only
</a>
<a href="/blacklist" class="btn btn-secondary btn-lg">
<i class="bi bi-shield-exclamation me-2"></i>
Check IP Blacklist
</a>
</div>
</div>

View file

@ -0,0 +1,186 @@
<script lang="ts">
import { goto } from "$app/navigation";
import { appConfig } from "$lib/stores/config";
let ip = $state("");
let error = $state<string | null>(null);
function handleSubmit() {
error = null;
if (!ip.trim()) {
error = "Please enter an IP address";
return;
}
// Basic IPv4/IPv6 validation
const ipv4Pattern = /^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/;
const ipv6Pattern = /^(([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))$/;
if (!ipv4Pattern.test(ip.trim()) && !ipv6Pattern.test(ip.trim())) {
error = "Please enter a valid IPv4 or IPv6 address (e.g., 192.0.2.1)";
return;
}
// Navigate to the blacklist check page
goto(`/blacklist/${encodeURIComponent(ip.trim())}`);
}
function handleKeyPress(event: KeyboardEvent) {
if (event.key === "Enter") {
handleSubmit();
}
}
</script>
<svelte:head>
<title>Blacklist Check - happyDeliver</title>
</svelte:head>
<div class="container py-5">
<div class="row">
<div class="col-lg-8 mx-auto">
<!-- Header -->
<div class="text-center mb-5">
<h1 class="display-4 fw-bold mb-3">
<i class="bi bi-shield-exclamation me-2"></i>
Check IP Blacklist Status
</h1>
<p class="lead text-muted">
Test an IP address against multiple DNS-based blacklists (RBLs) to check its reputation.
</p>
</div>
<!-- Input Form -->
<div class="card shadow-lg border-0 mb-5">
<div class="card-body p-5">
<h2 class="h5 mb-4">Enter IP Address</h2>
<div class="input-group input-group-lg mb-3">
<span class="input-group-text bg-light">
<i class="bi bi-hdd-network"></i>
</span>
<input
type="text"
class="form-control"
placeholder="192.0.2.1 or 2001:db8::1"
bind:value={ip}
onkeypress={handleKeyPress}
autofocus
/>
<button
class="btn btn-primary px-5"
onclick={handleSubmit}
disabled={!ip.trim()}
>
<i class="bi bi-search me-2"></i>
Check
</button>
</div>
{#if error}
<div class="alert alert-danger" role="alert">
<i class="bi bi-exclamation-triangle me-2"></i>
{error}
</div>
{/if}
<small class="text-muted">
<i class="bi bi-info-circle me-1"></i>
Enter an IPv4 address (e.g., 192.0.2.1) or IPv6 address (e.g., 2001:db8::1)
</small>
</div>
</div>
<!-- Info Section -->
<div class="row g-4 mb-4">
<div class="col-md-6">
<div class="card h-100 border-0 bg-light">
<div class="card-body">
<h3 class="h6 mb-3">
<i class="bi bi-check-circle-fill text-success me-2"></i>
What's Checked
</h3>
<ul class="list-unstyled mb-0 small">
{#each $appConfig.rbls as rbl}
<li class="mb-2"><i class="bi bi-arrow-right me-2"></i>{rbl}</li>
{/each}
</ul>
</div>
</div>
</div>
<div class="col-md-6">
<div class="card h-100 border-0 bg-light">
<div class="card-body">
<h3 class="h6 mb-3">
<i class="bi bi-info-circle-fill text-primary me-2"></i>
Why Check Blacklists?
</h3>
<p class="small mb-2">
DNS-based blacklists (RBLs) are used by email servers to identify and block spam sources. Being listed can severely impact email deliverability.
</p>
<p class="small mb-3">
This tool checks your IP against multiple popular RBLs to help you:
</p>
<ul class="list-unstyled mb-3 small">
<li class="mb-1">
<i class="bi bi-arrow-right me-2"></i>Monitor IP reputation
</li>
<li class="mb-1">
<i class="bi bi-arrow-right me-2"></i>Identify deliverability issues
</li>
<li class="mb-1">
<i class="bi bi-arrow-right me-2"></i>Take corrective action
</li>
</ul>
</div>
</div>
</div>
</div>
<!-- Additional Info -->
<div class="alert alert-info border-0">
<h3 class="h6 mb-2">
<i class="bi bi-lightbulb me-2"></i>
Need Complete Email Analysis?
</h3>
<p class="small mb-2">
For comprehensive deliverability testing including DKIM verification, content analysis, spam scoring, and more:
</p>
<a href="/" class="btn btn-sm btn-outline-primary">
<i class="bi bi-envelope-plus me-1"></i>
Send Test Email
</a>
</div>
</div>
</div>
</div>
<style>
.card {
transition: transform 0.2s ease, box-shadow 0.2s ease;
}
.card:hover {
transform: translateY(-2px);
box-shadow: 0 0.5rem 1.5rem rgba(0, 0, 0, 0.1) !important;
}
.input-group-lg .form-control {
font-size: 1.1rem;
}
.input-group-text {
border-right: none;
}
.input-group .form-control {
border-left: none;
border-right: none;
}
.input-group .form-control:focus {
box-shadow: none;
}
</style>

View file

@ -0,0 +1,230 @@
<script lang="ts">
import { page } from "$app/stores";
import { onMount } from "svelte";
import { checkBlacklist } from "$lib/api";
import type { BlacklistCheckResponse } from "$lib/api/types.gen";
import { GradeDisplay, BlacklistCard } from "$lib/components";
import { theme } from "$lib/stores/theme";
let ip = $derived($page.params.ip);
let loading = $state(true);
let error = $state<string | null>(null);
let result = $state<BlacklistCheckResponse | null>(null);
async function analyzeIP() {
loading = true;
error = null;
result = null;
if (!ip) {
error = "IP parameter is missing";
loading = false;
return;
}
try {
const response = await checkBlacklist({
body: { ip: ip },
});
if (response.response.ok) {
result = response.data;
} else if (response.error) {
error = response.error.message || "Failed to check IP address";
}
} catch (err) {
error = err instanceof Error ? err.message : "Failed to check IP address";
} finally {
loading = false;
}
}
onMount(() => {
analyzeIP();
});
</script>
<svelte:head>
<title>{ip} - Blacklist Check - happyDeliver</title>
</svelte:head>
<div class="container py-5">
<div class="row">
<div class="col-lg-10 mx-auto">
<!-- Header -->
<div class="mb-4">
<div class="d-flex align-items-center justify-content-between">
<h1 class="h2 mb-0">
<i class="bi bi-shield-exclamation me-2"></i>
Blacklist Analysis
</h1>
<a href="/blacklist" class="btn btn-outline-secondary">
<i class="bi bi-arrow-left me-2"></i>
Check Another IP
</a>
</div>
</div>
{#if loading}
<!-- Loading State -->
<div class="card shadow-sm">
<div class="card-body text-center py-5">
<div class="spinner-border text-primary mb-3" role="status">
<span class="visually-hidden">Loading...</span>
</div>
<h3 class="h5">Checking {ip}...</h3>
<p class="text-muted mb-0">Querying DNS-based blacklists</p>
</div>
</div>
{:else if error}
<!-- Error State -->
<div class="card shadow-sm">
<div class="card-body text-center py-5">
<i class="bi bi-exclamation-triangle text-danger" style="font-size: 4rem;"></i>
<h3 class="h4 mt-4">Check Failed</h3>
<p class="text-muted mb-4">{error}</p>
<button class="btn btn-primary" onclick={analyzeIP}>
<i class="bi bi-arrow-clockwise me-2"></i>
Try Again
</button>
</div>
</div>
{:else if result}
<!-- Results -->
<div class="fade-in">
<!-- Score Summary Card -->
<div class="card shadow-sm mb-4">
<div class="card-body p-4">
<div class="row align-items-center">
<div class="col-md-6 text-center text-md-start mb-3 mb-md-0">
<h2 class="h2 mb-2">
<span class="font-monospace text-truncate">{result.ip}</span>
</h2>
{#if result.listed_count === 0}
<div class="alert alert-success mb-0 d-inline-block">
<i class="bi bi-check-circle me-2"></i>
<strong>Not Listed</strong>
<p class="d-inline mb-0 mt-1 small">
This IP address is not listed on any checked blacklists.
</p>
</div>
{:else}
<div class="alert alert-danger mb-0 d-inline-block">
<i class="bi bi-exclamation-triangle me-2"></i>
<strong>Listed on {result.listed_count} blacklist{result.listed_count > 1 ? "s" : ""}</strong>
<p class="mb-0 mt-1 small">
This IP address is listed on {result.listed_count} of {result.checks.length} checked blacklist{result.checks.length > 1 ? "s" : ""}.
</p>
</div>
{/if}
</div>
<div class="offset-md-3 col-md-3 text-center">
<div
class="p-2 rounded text-center summary-card"
class:bg-light={$theme === 'light'}
class:bg-secondary={$theme !== 'light'}
>
<GradeDisplay score={result.score} grade={result.grade} />
<small class="text-muted d-block">Blacklist Score</small>
</div>
</div>
</div>
</div>
</div>
<!-- Blacklist Results Card -->
<BlacklistCard
blacklists={{ [result.ip]: result.checks }}
blacklistScore={result.score}
blacklistGrade={result.grade}
/>
<!-- Information Card -->
<div class="card shadow-sm mt-4">
<div class="card-body">
<h3 class="h5 mb-3">
<i class="bi bi-info-circle me-2"></i>
What This Means
</h3>
{#if result.listed_count === 0}
<p class="mb-3">
<strong>Good news!</strong> This IP address is not currently listed on any of the
checked DNS-based blacklists (RBLs). This indicates a good sender reputation
and should not negatively impact email deliverability.
</p>
{:else}
<p class="mb-3">
<strong>Warning:</strong> This IP address is listed on {result.listed_count} blacklist{result.listed_count > 1 ? "s" : ""}.
Being listed can significantly impact email deliverability as many mail servers
use these blacklists to filter incoming mail.
</p>
<div class="alert alert-warning">
<h4 class="h6 mb-2">Recommended Actions:</h4>
<ul class="mb-0 small">
<li>Investigate the cause of the listing (compromised system, spam complaints, etc.)</li>
<li>Fix any security issues or stop sending practices that led to the listing</li>
<li>Request delisting from each RBL (check their websites for removal procedures)</li>
<li>Monitor your IP reputation regularly to prevent future listings</li>
</ul>
</div>
{/if}
</div>
</div>
<!-- Next Steps -->
<div class="card shadow-sm border-primary mt-4">
<div class="card-body">
<h3 class="h5 mb-3">
<i class="bi bi-lightbulb me-2"></i>
Want Complete Email Analysis?
</h3>
<p class="mb-3">
This blacklist check tests IP reputation only. For comprehensive
deliverability testing including DKIM verification, content analysis,
spam scoring, and DNS configuration:
</p>
<a href="/" class="btn btn-primary">
<i class="bi bi-envelope-plus me-2"></i>
Send Test Email
</a>
</div>
</div>
</div>
{/if}
</div>
</div>
</div>
<style>
.fade-in {
animation: fadeIn 0.5s ease-in;
}
@keyframes fadeIn {
from {
opacity: 0;
transform: translateY(10px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.summary-card {
transition: transform 0.2s ease;
}
.summary-card:hover {
transform: scale(1.05);
}
.table td {
vertical-align: middle;
}
.badge {
font-size: 0.75rem;
padding: 0.35rem 0.65rem;
}
</style>