diff options
| author | Bobby <[email protected]> | 2026-03-08 17:47:48 +0530 |
|---|---|---|
| committer | Bobby <[email protected]> | 2026-03-08 17:47:48 +0530 |
| commit | f48054e9bc5e4fb36b9aba9126c6ace9c5b1f470 (patch) | |
| tree | 0e993ddab1229ba7efccbb757987addb86effbe3 /templates | |
| parent | 2d5fb5e2078e92e7ec19582c3954409dd93f89fd (diff) | |
| download | dove-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.django | 5 | ||||
| -rw-r--r-- | templates/domains/htmx/detail.htmx.django | 223 | ||||
| -rw-r--r-- | templates/domains/htmx/domains.htmx.django | 5 | ||||
| -rw-r--r-- | templates/partials/sidebar.django | 6 |
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> |
