Files
gh-christianlouis-dmarq/backend/app/templates/domains.html
T
2026-05-23 11:37:10 +02:00

217 lines
10 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/table.html" import table, thead, tbody, tr, th, td %}
{% block title %}DMARQ - Domains{% endblock %}
{% block content %}
<div class="container mx-auto py-4" x-data="domainsApp()">
<h1 class="text-2xl font-bold mb-6">Domain Management</h1>
{% if error %}
<div class="bg-red-100 border-l-4 border-red-500 text-red-700 p-4 mb-6" role="alert">
<p>{{ error }}</p>
</div>
{% endif %}
<!-- Domain List -->
<div class="mb-8">
{% call card() %}
{% call card_header() %}
<div class="flex items-center justify-between">
{% call card_title() %}Monitored Domains{% endcall %}
<button type="button" class="btn btn-outline btn-sm" @click="openCreate = true">
<span class="mr-1">+</span> Add Domain
</button>
</div>
{% call card_description() %}
Domains currently being monitored for DMARC compliance
{% endcall %}
{% endcall %}
{% call card_content() %}
{% call table() %}
{% call thead() %}
{% call tr() %}
{% call th() %}Domain{% endcall %}
{% call th() %}DMARC Status{% endcall %}
{% call th() %}SPF Status{% endcall %}
{% call th() %}DKIM Status{% endcall %}
{% call th("text-right") %}Actions{% endcall %}
{% endcall %}
{% endcall %}
{% call tbody() %}
<template x-if="domains.length === 0">
{% call tr() %}
{% call td(colspan="5", class="text-center") %}
<p class="py-4 text-muted-foreground">No domains found. Add a domain to get started.</p>
{% endcall %}
{% endcall %}
</template>
<template x-for="(domain, index) in domains" :key="index">
{% call tr() %}
{% call td() %}
<div class="font-medium" x-text="domain.name"></div>
{% endcall %}
{% call td() %}
<div class="flex items-center">
<span class="w-2 h-2 rounded-full"
:class="domain.dmarc_status ? 'bg-green-500' : 'bg-red-500'"></span>
<span class="ml-2" x-text="domain.dmarc_policy || 'Not configured'"></span>
</div>
{% endcall %}
{% call td() %}
<div class="flex items-center">
<span class="w-2 h-2 rounded-full"
:class="domain.spf_status ? 'bg-green-500' : 'bg-red-500'"></span>
<span class="ml-2" x-text="domain.spf_status ? 'Configured' : 'Missing'"></span>
</div>
{% endcall %}
{% call td() %}
<div class="flex items-center">
<span class="w-2 h-2 rounded-full"
:class="domain.dkim_status ? 'bg-green-500' : 'bg-red-500'"></span>
<span class="ml-2" x-text="domain.dkim_status ? 'Configured' : 'Missing'"></span>
</div>
{% endcall %}
{% call td("text-right") %}
<div class="flex justify-end space-x-2">
<a :href="'/domains/' + domain.name" class="btn btn-sm btn-outline">
Details
</a>
{% call button(variant="outline", size="sm") %}
Edit
{% endcall %}
</div>
{% endcall %}
{% endcall %}
</template>
{% endcall %}
{% endcall %}
{% endcall %}
{% endcall %}
</div>
<dialog class="modal" :open="openCreate">
<div class="modal-box max-w-2xl">
<h2 class="text-xl font-semibold">Add monitored domain</h2>
<p class="mt-1 text-sm text-base-content/70">Track DNS health and future DMARC reports before the first report arrives.</p>
<form class="mt-5 space-y-4" @submit.prevent="createDomain">
<label class="form-control">
<span class="label-text font-medium">Domain</span>
<input x-model.trim="newDomain.name" type="text" required placeholder="example.com" class="input input-bordered">
</label>
<label class="form-control">
<span class="label-text font-medium">Description</span>
<textarea x-model.trim="newDomain.description" rows="3" class="textarea textarea-bordered" placeholder="Production mail domain"></textarea>
</label>
<label class="form-control">
<span class="label-text font-medium">DKIM selectors</span>
<input x-model.trim="newDomain.dkim_selectors" type="text" placeholder="default, google, selector1" class="input input-bordered">
</label>
<div x-show="createError" role="alert" class="alert alert-error py-3">
<span x-text="createError"></span>
</div>
<div class="modal-action">
<button type="button" class="btn btn-ghost" @click="closeCreate" :disabled="saving">Cancel</button>
<button type="submit" class="btn btn-primary" :disabled="saving">
<span x-show="saving" class="loading loading-spinner loading-xs"></span>
Add domain
</button>
</div>
</form>
</div>
<form method="dialog" class="modal-backdrop">
<button type="button" @click="closeCreate">close</button>
</form>
</dialog>
</div>
{% endblock %}
{% block scripts %}
<script>
function domainsApp() {
return {
domains: [],
openCreate: false,
saving: false,
createError: '',
newDomain: {
name: '',
description: '',
dkim_selectors: '',
},
init() {
// Fetch domains from server
this.fetchDomains();
},
async fetchDomains() {
try {
const response = await fetch('/api/v1/domains/summary');
if (response.ok) {
const data = await response.json();
// Map API fields — DNS status comes directly from live lookups
this.domains = data.domains.map(domain => ({
name: domain.domain_name,
dmarc_status: domain.dmarc_status ?? false,
dmarc_policy: domain.dmarc_policy || 'Not configured',
spf_status: domain.spf_status ?? false,
dkim_status: domain.dkim_status ?? false,
reports_count: domain.report_count,
emails_count: domain.total_emails,
compliance_rate: domain.pass_rate
}));
} else {
console.error('Error fetching domains:', response.status);
}
} catch (error) {
console.error('Error fetching domains:', error);
}
},
closeCreate() {
this.openCreate = false;
this.createError = '';
this.newDomain = { name: '', description: '', dkim_selectors: '' };
},
async createDomain() {
this.saving = true;
this.createError = '';
try {
const selectors = this.newDomain.dkim_selectors
.split(',')
.map((selector) => selector.trim())
.filter(Boolean);
const response = await fetch('/api/v1/domains/domains', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
name: this.newDomain.name,
description: this.newDomain.description || null,
dkim_selectors: selectors,
}),
});
if (!response.ok) {
const data = await response.json().catch(() => ({}));
const detail = typeof data.detail === 'string'
? data.detail
: Object.values(data.detail || {}).join(', ');
throw new Error(detail || 'Domain could not be added.');
}
this.closeCreate();
await this.fetchDomains();
} catch (error) {
this.createError = error.message || 'Domain could not be added.';
} finally {
this.saving = false;
}
},
}
}
</script>
{% endblock %}