aboutsummaryrefslogtreecommitdiff
path: root/templates
diff options
context:
space:
mode:
authorBobby <[email protected]>2026-03-08 17:47:48 +0530
committerBobby <[email protected]>2026-03-08 17:47:48 +0530
commitf48054e9bc5e4fb36b9aba9126c6ace9c5b1f470 (patch)
tree0e993ddab1229ba7efccbb757987addb86effbe3 /templates
parent2d5fb5e2078e92e7ec19582c3954409dd93f89fd (diff)
downloaddove-f48054e9bc5e4fb36b9aba9126c6ace9c5b1f470.tar.xz
dove-f48054e9bc5e4fb36b9aba9126c6ace9c5b1f470.zip
feat(domains): enhance domain management UI with DNS records functionality
- Updated domain detail view to include DNS records management links. - Added new DNS records creation form with dynamic fields based on record type. - Implemented backend logic for creating and deleting DNS records. - Introduced new services and messages for DNS operations. - Refactored SMTP server initialization to streamline TLS configuration. - Added email composition and submission utilities for better email handling.
Diffstat (limited to 'templates')
-rw-r--r--templates/domains/detail.django5
-rw-r--r--templates/domains/htmx/detail.htmx.django223
-rw-r--r--templates/domains/htmx/domains.htmx.django5
-rw-r--r--templates/partials/sidebar.django6
4 files changed, 238 insertions, 1 deletions
diff --git a/templates/domains/detail.django b/templates/domains/detail.django
new file mode 100644
index 0000000..263e0f1
--- /dev/null
+++ b/templates/domains/detail.django
@@ -0,0 +1,5 @@
+{% extends "layouts/dashboard.django" %}
+
+{% block dashboard %}
+{% include "domains/htmx/detail.htmx.django" %}
+{% endblock %} \ No newline at end of file
diff --git a/templates/domains/htmx/detail.htmx.django b/templates/domains/htmx/detail.htmx.django
new file mode 100644
index 0000000..77a8615
--- /dev/null
+++ b/templates/domains/htmx/detail.htmx.django
@@ -0,0 +1,223 @@
+<div class="slide-up space-y-6">
+ <div class="glass rounded-xl glow-border">
+ <div class="flex items-center justify-between px-5 py-4 border-b border-white/[0.04]">
+ <div class="flex items-center gap-3">
+ {% url "domains.manage" as domains_path %}
+ <a href="{{ domains_path }}" hx-get="{{ domains_path }}" hx-target="#content" hx-swap="innerHTML" hx-push-url="true" class="flex items-center justify-center w-8 h-8 rounded-lg bg-surface-800 hover:bg-surface-700 border border-white/[0.06] transition-colors duration-150">
+ <svg class="w-4 h-4 text-zinc-400" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="1.5">
+ <path stroke-linecap="round" stroke-linejoin="round" d="M10.5 19.5 3 12m0 0 7.5-7.5M3 12h18" />
+ </svg>
+ </a>
+ <div>
+ <h2 class="text-sm font-medium text-zinc-200">{{ domain.Name }}.{{ domain.TLD.Name }}</h2>
+ <p class="text-xs text-zinc-600">DNS Records</p>
+ </div>
+ </div>
+ <div class="flex items-center gap-3">
+ <span class="text-xs text-zinc-600">{{ records|length }} total</span>
+ <button type="button" class="btn-small" data-toggle-new-record>Add Record</button>
+ </div>
+ </div>
+
+ <div class="hidden border-b border-white/[0.04]" data-new-record-form>
+ {% url "domains.records.create" as create_record_path %}
+ <form hx-post="{{ create_record_path }}" hx-swap="none" class="px-5 py-4 space-y-3">
+ <input type="hidden" name="domain_id" value="{{ domain.ID }}">
+ <div class="grid grid-cols-6 gap-3">
+ <div>
+ <label class="block text-[10px] font-medium text-zinc-500 mb-1 ml-0.5">Type</label>
+ <select name="record_type" class="input-field text-xs" data-record-type-select>
+ <option value="A">A</option>
+ <option value="AAAA">AAAA</option>
+ <option value="CNAME">CNAME</option>
+ <option value="MX">MX</option>
+ <option value="TXT">TXT</option>
+ <option value="SRV">SRV</option>
+ </select>
+ </div>
+ <div>
+ <label class="block text-[10px] font-medium text-zinc-500 mb-1 ml-0.5">Name</label>
+ <input type="text" name="name" autocomplete="off" placeholder="@" class="input-field text-xs">
+ </div>
+ <div class="col-span-2">
+ <label class="block text-[10px] font-medium text-zinc-500 mb-1 ml-0.5" data-value-label>Address</label>
+ <input type="text" name="value" required autocomplete="off" placeholder="127.0.0.1" class="input-field text-xs" data-value-input>
+ </div>
+ <div>
+ <label class="block text-[10px] font-medium text-zinc-500 mb-1 ml-0.5">TTL</label>
+ <input type="number" name="ttl" value="300" min="1" class="input-field text-xs">
+ </div>
+ <div class="flex items-end gap-2">
+ <button type="submit" class="btn-small bg-accent-500/20 text-accent-400 border-accent-500/20 hover:bg-accent-500/30">Save</button>
+ <button type="button" class="btn-small" data-cancel-new-record>Cancel</button>
+ </div>
+ </div>
+ <div class="grid grid-cols-6 gap-3" data-extra-fields>
+ <div data-priority-field style="display: none;">
+ <label class="block text-[10px] font-medium text-zinc-500 mb-1 ml-0.5">Priority</label>
+ <input type="number" name="priority" value="10" min="0" class="input-field text-xs">
+ </div>
+ <div data-port-map-field>
+ <label class="block text-[10px] font-medium text-zinc-500 mb-1 ml-0.5">Port Map</label>
+ <input type="number" name="port_map" value="0" min="0" class="input-field text-xs">
+ </div>
+ <div data-srv-weight style="display: none;">
+ <label class="block text-[10px] font-medium text-zinc-500 mb-1 ml-0.5">Weight</label>
+ <input type="number" name="weight" value="0" min="0" class="input-field text-xs">
+ </div>
+ <div data-srv-port style="display: none;">
+ <label class="block text-[10px] font-medium text-zinc-500 mb-1 ml-0.5">Port</label>
+ <input type="number" name="port" value="0" min="0" class="input-field text-xs">
+ </div>
+ <div data-srv-protocol style="display: none;">
+ <label class="block text-[10px] font-medium text-zinc-500 mb-1 ml-0.5">Protocol</label>
+ <select name="protocol" class="input-field text-xs">
+ <option value="tcp">TCP</option>
+ <option value="udp">UDP</option>
+ </select>
+ </div>
+ </div>
+ </form>
+ </div>
+
+ {% if records %}
+ <div class="divide-y divide-white/[0.04]">
+ {% for record in records %}
+ <div class="flex items-center justify-between px-5 py-3">
+ <div class="flex items-center gap-3 min-w-0">
+ {% if record.IsManaged %}
+ <div class="flex items-center justify-center w-8 h-8 rounded-lg bg-amber-500/10 shrink-0">
+ <svg class="w-4 h-4 text-amber-400" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="1.5">
+ <path stroke-linecap="round" stroke-linejoin="round" d="M16.5 10.5V6.75a4.5 4.5 0 1 0-9 0v3.75m-.75 11.25h10.5a2.25 2.25 0 0 0 2.25-2.25v-6.75a2.25 2.25 0 0 0-2.25-2.25H6.75a2.25 2.25 0 0 0-2.25 2.25v6.75a2.25 2.25 0 0 0 2.25 2.25Z" />
+ </svg>
+ </div>
+ {% else %}
+ <div class="flex items-center justify-center w-8 h-8 rounded-lg bg-surface-800 shrink-0">
+ {% if record.Type == "A" %}
+ <span class="text-xs font-bold text-blue-400">A</span>
+ {% elif record.Type == "AAAA" %}
+ <span class="text-[10px] font-bold text-blue-300">AAAA</span>
+ {% elif record.Type == "CNAME" %}
+ <span class="text-[10px] font-bold text-purple-400">CN</span>
+ {% elif record.Type == "MX" %}
+ <span class="text-xs font-bold text-orange-400">MX</span>
+ {% elif record.Type == "TXT" %}
+ <span class="text-[10px] font-bold text-green-400">TXT</span>
+ {% elif record.Type == "SRV" %}
+ <span class="text-[10px] font-bold text-pink-400">SRV</span>
+ {% endif %}
+ </div>
+ {% endif %}
+ <div class="flex items-center gap-2 min-w-0">
+ <span class="inline-flex items-center px-1.5 py-0.5 rounded text-[10px] font-medium shrink-0
+ {% if record.Type == "A" %}bg-blue-500/10 text-blue-400
+ {% elif record.Type == "AAAA" %}bg-blue-500/10 text-blue-300
+ {% elif record.Type == "CNAME" %}bg-purple-500/10 text-purple-400
+ {% elif record.Type == "MX" %}bg-orange-500/10 text-orange-400
+ {% elif record.Type == "TXT" %}bg-green-500/10 text-green-400
+ {% elif record.Type == "SRV" %}bg-pink-500/10 text-pink-400
+ {% endif %}">{{ record.Type }}</span>
+ <p class="text-sm text-zinc-200 shrink-0">{{ record.Name }}</p>
+ <svg class="w-3 h-3 text-zinc-700 shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="1.5">
+ <path stroke-linecap="round" stroke-linejoin="round" d="M13.5 4.5 21 12m0 0-7.5 7.5M21 12H3" />
+ </svg>
+ <p class="text-sm text-zinc-400 truncate">{{ record.Value }}</p>
+ {% if record.Priority > 0 %}
+ <span class="text-xs text-zinc-600 shrink-0">pri {{ record.Priority }}</span>
+ {% endif %}
+ <span class="text-xs text-zinc-700 shrink-0">{{ record.TTL }}s</span>
+ </div>
+ </div>
+ <div class="flex items-center gap-3 shrink-0 ml-4">
+ {% if record.IsManaged %}
+ <span class="text-xs text-amber-500/60">System managed</span>
+ {% else %}
+ <button data-confirm-trigger data-confirm-title="Delete {{ record.Type }} record" data-confirm-message="This DNS record will be permanently removed. This action cannot be undone." data-confirm-action="/domains/records/{{ record.Type }}/{{ record.ID }}?domain_id={{ domain.ID }}" class="text-xs text-zinc-600 hover:text-red-400 transition-colors duration-150">Delete</button>
+ {% endif %}
+ </div>
+ </div>
+ {% endfor %}
+ </div>
+ {% else %}
+ <div class="flex flex-col items-center justify-center py-16 text-center">
+ <div class="flex items-center justify-center w-12 h-12 rounded-2xl bg-surface-800 mb-4">
+ <svg class="w-6 h-6 text-zinc-600" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="1.5">
+ <path stroke-linecap="round" stroke-linejoin="round" d="M19.5 14.25v-2.625a3.375 3.375 0 0 0-3.375-3.375h-1.5A1.125 1.125 0 0 1 13.5 7.125v-1.5a3.375 3.375 0 0 0-3.375-3.375H8.25m0 12.75h7.5m-7.5 3H12M10.5 2.25H5.625c-.621 0-1.125.504-1.125 1.125v17.25c0 .621.504 1.125 1.125 1.125h12.75c.621 0 1.125-.504 1.125-1.125V11.25a9 9 0 0 0-9-9Z" />
+ </svg>
+ </div>
+ <p class="text-sm text-zinc-400">No DNS records</p>
+ <p class="mt-1 text-xs text-zinc-600">Click "Add Record" to get started</p>
+ </div>
+ {% endif %}
+ </div>
+
+ <div id="confirm-modal" class="fixed inset-0 z-50 hidden">
+ <div class="absolute inset-0 bg-black/60 backdrop-blur-sm" data-confirm-backdrop></div>
+ <div class="flex items-center justify-center min-h-screen p-4">
+ <div class="relative glass rounded-xl glow-border w-full max-w-sm p-6 space-y-4">
+ <div class="flex items-center gap-3">
+ <div class="flex items-center justify-center w-10 h-10 rounded-xl bg-red-500/10">
+ <svg class="w-5 h-5 text-red-400" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="1.5">
+ <path stroke-linecap="round" stroke-linejoin="round" d="M12 9v3.75m-9.303 3.376c-.866 1.5.217 3.374 1.948 3.374h14.71c1.73 0 2.813-1.874 1.948-3.374L13.949 3.378c-.866-1.5-3.032-1.5-3.898 0L2.697 16.126ZM12 15.75h.007v.008H12v-.008Z" />
+ </svg>
+ </div>
+ <h3 id="confirm-title" class="text-sm font-medium text-zinc-100"></h3>
+ </div>
+ <p id="confirm-message" class="text-xs text-zinc-400 leading-relaxed"></p>
+ <div class="flex items-center justify-end gap-3 pt-2">
+ <button data-confirm-cancel class="px-4 py-2 text-xs text-zinc-400 hover:text-zinc-200 rounded-lg bg-surface-800 border border-white/[0.06] hover:border-white/[0.1] transition-all duration-150">Cancel</button>
+ <button id="confirm-action" class="px-4 py-2 text-xs text-white rounded-lg bg-red-500/80 hover:bg-red-500 border border-red-400/20 transition-all duration-150">Delete</button>
+ </div>
+ </div>
+ </div>
+ </div>
+</div>
+
+<script>
+(function() {
+ var toggleButton = document.querySelector('[data-toggle-new-record]');
+ var cancelButton = document.querySelector('[data-cancel-new-record]');
+ var formContainer = document.querySelector('[data-new-record-form]');
+ var typeSelect = document.querySelector('[data-record-type-select]');
+ var priorityField = document.querySelector('[data-priority-field]');
+ var portMapField = document.querySelector('[data-port-map-field]');
+ var srvWeight = document.querySelector('[data-srv-weight]');
+ var srvPort = document.querySelector('[data-srv-port]');
+ var srvProtocol = document.querySelector('[data-srv-protocol]');
+ var valueLabel = document.querySelector('[data-value-label]');
+ var valueInput = document.querySelector('[data-value-input]');
+
+ toggleButton.addEventListener('click', function() {
+ formContainer.classList.toggle('hidden');
+ });
+
+ cancelButton.addEventListener('click', function() {
+ formContainer.classList.add('hidden');
+ });
+
+ var fieldConfig = {
+ 'A': { label: 'Address (IPv4)', placeholder: '127.0.0.1', priority: false, portMap: true, srv: false },
+ 'AAAA': { label: 'Address (IPv6)', placeholder: '::1', priority: false, portMap: false, srv: false },
+ 'CNAME': { label: 'Target', placeholder: 'other.example.dove.', priority: false, portMap: false, srv: false },
+ 'MX': { label: 'Mail Server', placeholder: 'mail.example.dove.', priority: true, portMap: false, srv: false },
+ 'TXT': { label: 'Content', placeholder: 'v=spf1 include:...', priority: false, portMap: false, srv: false },
+ 'SRV': { label: 'Target Host', placeholder: 'service.example.dove.', priority: true, portMap: false, srv: true }
+ };
+
+ function updateFields() {
+ var config = fieldConfig[typeSelect.value];
+ if (!config) return;
+
+ valueLabel.textContent = config.label;
+ valueInput.placeholder = config.placeholder;
+ priorityField.style.display = config.priority ? '' : 'none';
+ portMapField.style.display = config.portMap ? '' : 'none';
+ srvWeight.style.display = config.srv ? '' : 'none';
+ srvPort.style.display = config.srv ? '' : 'none';
+ srvProtocol.style.display = config.srv ? '' : 'none';
+ }
+
+ typeSelect.addEventListener('change', updateFields);
+ updateFields();
+})();
+</script> \ No newline at end of file
diff --git a/templates/domains/htmx/domains.htmx.django b/templates/domains/htmx/domains.htmx.django
index 4e09799..4c3e37f 100644
--- a/templates/domains/htmx/domains.htmx.django
+++ b/templates/domains/htmx/domains.htmx.django
@@ -18,9 +18,12 @@
<path stroke-linecap="round" stroke-linejoin="round" d="M5.25 14.25h13.5m-13.5 0a3 3 0 0 1-3-3m3 3a3 3 0 1 0 0 6h13.5a3 3 0 1 0 0-6m-16.5-3a3 3 0 0 1 3-3h13.5a3 3 0 0 1 3 3m-19.5 0a4.5 4.5 0 0 1 .9-2.7L5.737 5.1a3.375 3.375 0 0 1 2.7-1.35h7.126c1.062 0 2.062.5 2.7 1.35l2.587 3.45a4.5 4.5 0 0 1 .9 2.7m0 0a3 3 0 0 1-3 3m0 3h.008v.008h-.008v-.008Zm0-6h.008v.008h-.008v-.008Zm-3 6h.008v.008h-.008v-.008Zm0-6h.008v.008h-.008v-.008Z" />
</svg>
</div>
- <p class="text-sm text-zinc-200">{{ domain.Name }}.{{ domain.TLD.Name }}</p>
+ {% url "domains.manage.detail" id=domain.ID as detail_domain_path %}
+ <a href="{{ detail_domain_path }}" hx-get="{{ detail_domain_path }}" hx-target="#content" hx-swap="innerHTML" hx-push-url="true" class="text-sm text-zinc-200 hover:text-white transition-colors duration-150">{{ domain.Name }}.{{ domain.TLD.Name }}</a>
</div>
<div class="flex items-center gap-4">
+ {% url "domains.manage.detail" id=domain.ID as detail_domain_path %}
+ <a href="{{ detail_domain_path }}" hx-get="{{ detail_domain_path }}" hx-target="#content" hx-swap="innerHTML" hx-push-url="true" class="text-xs text-zinc-600 hover:text-zinc-300 transition-colors duration-150">DNS Records</a>
{% url "domains.manage.edit" id=domain.ID as edit_domain_path %}
<a href="{{ edit_domain_path }}" hx-get="{{ edit_domain_path }}" hx-target="#content" hx-swap="innerHTML" hx-push-url="true" class="text-xs text-zinc-600 hover:text-zinc-300 transition-colors duration-150">Edit</a>
{% url "domains.manage.delete" id=domain.ID as delete_domain_path %}
diff --git a/templates/partials/sidebar.django b/templates/partials/sidebar.django
index 01dd602..cb33ae5 100644
--- a/templates/partials/sidebar.django
+++ b/templates/partials/sidebar.django
@@ -50,6 +50,12 @@
</svg>
Domains
</a>
+ <a href="{{ manage_path }}" hx-get="{{ manage_path }}" class="nav-link flex items-center gap-3 px-3 py-2 rounded-lg text-sm transition-colors duration-150">
+ <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="1.5">
+ <path stroke-linecap="round" stroke-linejoin="round" d="M19.5 14.25v-2.625a3.375 3.375 0 0 0-3.375-3.375h-1.5A1.125 1.125 0 0 1 13.5 7.125v-1.5a3.375 3.375 0 0 0-3.375-3.375H8.25m0 12.75h7.5m-7.5 3H12M10.5 2.25H5.625c-.621 0-1.125.504-1.125 1.125v17.25c0 .621.504 1.125 1.125 1.125h12.75c.621 0 1.125-.504 1.125-1.125V11.25a9 9 0 0 0-9-9Z" />
+ </svg>
+ DNS Records
+ </a>
</div>
</div>