aboutsummaryrefslogtreecommitdiff
path: root/templates
diff options
context:
space:
mode:
authorBobby <[email protected]>2026-03-08 18:17:23 +0530
committerBobby <[email protected]>2026-03-08 18:17:23 +0530
commit1136af49815be77a0aca151f3b8ec7348bf4b4c8 (patch)
treeca4d94f981be59c51fa7d160e32be978a8d4b4fb /templates
parentf48054e9bc5e4fb36b9aba9126c6ace9c5b1f470 (diff)
downloaddove-1136af49815be77a0aca151f3b8ec7348bf4b4c8.tar.xz
dove-1136af49815be77a0aca151f3b8ec7348bf4b4c8.zip
feat(dns): add update functionality for DNS records (MX, SRV, TXT)
- Implemented UpdateMXRecord, UpdateSRVRecord, and UpdateTXTRecord functions in their respective repositories. - Added UpdateRecord method in dns service to handle updates for various DNS record types. - Updated router to include a new route for updating DNS records. - Enhanced error messages for record updates in messages.go. - Modified the frontend forms to support editing DNS records with improved UI components. - Refactored existing domain management code to remove unused update functionality. - Improved email handling by adding MX record validation during email delivery.
Diffstat (limited to 'templates')
-rw-r--r--templates/domains/htmx/detail.htmx.django228
-rw-r--r--templates/domains/htmx/domains.htmx.django2
-rw-r--r--templates/partials/sidebar.django6
3 files changed, 183 insertions, 53 deletions
diff --git a/templates/domains/htmx/detail.htmx.django b/templates/domains/htmx/detail.htmx.django
index 77a8615..eaa3806 100644
--- a/templates/domains/htmx/detail.htmx.django
+++ b/templates/domains/htmx/detail.htmx.django
@@ -23,36 +23,61 @@
{% 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 class="grid grid-cols-5 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 class="dropdown" data-dropdown data-record-type-dropdown>
+ <input type="hidden" name="record_type" value="A" data-dropdown-value>
+ <button type="button" data-dropdown-trigger class="input-field text-xs text-left flex items-center justify-between">
+ <span class="truncate" data-dropdown-label>A</span>
+ <svg class="w-3.5 h-3.5 text-zinc-500 shrink-0 ml-1 transition-transform duration-150" data-dropdown-chevron fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="1.5">
+ <path stroke-linecap="round" stroke-linejoin="round" d="m19.5 8.25-7.5 7.5-7.5-7.5" />
+ </svg>
+ </button>
+ <div class="dropdown-menu" data-dropdown-menu>
+ <div class="dropdown-options" data-dropdown-options>
+ <button type="button" class="dropdown-option" data-dropdown-option data-value="A" data-label="A"><p class="text-sm text-zinc-200">A</p><p class="text-xs text-zinc-500">IPv4 address</p></button>
+ <button type="button" class="dropdown-option" data-dropdown-option data-value="AAAA" data-label="AAAA"><p class="text-sm text-zinc-200">AAAA</p><p class="text-xs text-zinc-500">IPv6 address</p></button>
+ <button type="button" class="dropdown-option" data-dropdown-option data-value="CNAME" data-label="CNAME"><p class="text-sm text-zinc-200">CNAME</p><p class="text-xs text-zinc-500">Canonical name</p></button>
+ <button type="button" class="dropdown-option" data-dropdown-option data-value="MX" data-label="MX"><p class="text-sm text-zinc-200">MX</p><p class="text-xs text-zinc-500">Mail exchange</p></button>
+ <button type="button" class="dropdown-option" data-dropdown-option data-value="TXT" data-label="TXT"><p class="text-sm text-zinc-200">TXT</p><p class="text-xs text-zinc-500">Text record</p></button>
+ <button type="button" class="dropdown-option" data-dropdown-option data-value="SRV" data-label="SRV"><p class="text-sm text-zinc-200">SRV</p><p class="text-xs text-zinc-500">Service locator</p></button>
+ </div>
+ </div>
+ </div>
</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>
+ <label class="block text-[10px] font-medium text-zinc-500 mb-1 ml-0.5" data-value-label>Address (IPv4)</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 class="dropdown" data-dropdown>
+ <input type="hidden" name="ttl" value="1" data-dropdown-value>
+ <button type="button" data-dropdown-trigger class="input-field text-xs text-left flex items-center justify-between">
+ <span class="truncate" data-dropdown-label>Immediate</span>
+ <svg class="w-3.5 h-3.5 text-zinc-500 shrink-0 ml-1 transition-transform duration-150" data-dropdown-chevron fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="1.5">
+ <path stroke-linecap="round" stroke-linejoin="round" d="m19.5 8.25-7.5 7.5-7.5-7.5" />
+ </svg>
+ </button>
+ <div class="dropdown-menu" data-dropdown-menu>
+ <div class="dropdown-options" data-dropdown-options>
+ <button type="button" class="dropdown-option" data-dropdown-option data-value="1" data-label="Immediate"><p class="text-sm text-zinc-200">Immediate</p><p class="text-xs text-zinc-500">1 second</p></button>
+ <button type="button" class="dropdown-option" data-dropdown-option data-value="300" data-label="5 minutes"><p class="text-sm text-zinc-200">5 minutes</p><p class="text-xs text-zinc-500">300 seconds</p></button>
+ <button type="button" class="dropdown-option" data-dropdown-option data-value="900" data-label="15 minutes"><p class="text-sm text-zinc-200">15 minutes</p><p class="text-xs text-zinc-500">900 seconds</p></button>
+ <button type="button" class="dropdown-option" data-dropdown-option data-value="3600" data-label="1 hour"><p class="text-sm text-zinc-200">1 hour</p><p class="text-xs text-zinc-500">3600 seconds</p></button>
+ <button type="button" class="dropdown-option" data-dropdown-option data-value="14400" data-label="4 hours"><p class="text-sm text-zinc-200">4 hours</p><p class="text-xs text-zinc-500">14400 seconds</p></button>
+ <button type="button" class="dropdown-option" data-dropdown-option data-value="86400" data-label="1 day"><p class="text-sm text-zinc-200">1 day</p><p class="text-xs text-zinc-500">86400 seconds</p></button>
+ </div>
+ </div>
+ </div>
</div>
</div>
- <div class="grid grid-cols-6 gap-3" data-extra-fields>
+ <div class="grid grid-cols-5 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">
@@ -71,42 +96,41 @@
</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 class="dropdown" data-dropdown>
+ <input type="hidden" name="protocol" value="tcp" data-dropdown-value>
+ <button type="button" data-dropdown-trigger class="input-field text-xs text-left flex items-center justify-between">
+ <span class="truncate" data-dropdown-label>TCP</span>
+ <svg class="w-3.5 h-3.5 text-zinc-500 shrink-0 ml-1 transition-transform duration-150" data-dropdown-chevron fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="1.5">
+ <path stroke-linecap="round" stroke-linejoin="round" d="m19.5 8.25-7.5 7.5-7.5-7.5" />
+ </svg>
+ </button>
+ <div class="dropdown-menu" data-dropdown-menu>
+ <div class="dropdown-options" data-dropdown-options>
+ <button type="button" class="dropdown-option" data-dropdown-option data-value="tcp" data-label="TCP"><p class="text-sm text-zinc-200">TCP</p></button>
+ <button type="button" class="dropdown-option" data-dropdown-option data-value="udp" data-label="UDP"><p class="text-sm text-zinc-200">UDP</p></button>
+ </div>
+ </div>
+ </div>
</div>
</div>
+ <div class="flex items-center gap-3 pt-1">
+ <button type="submit" class="btn-small">Save Record</button>
+ <a href="javascript:void(0)" class="text-xs text-zinc-500 hover:text-zinc-300 transition-colors duration-150" data-cancel-new-record>Cancel</a>
+ </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">
+ <div class="flex items-center justify-between px-5 py-3 group" data-record-row="{{ record.Type }}-{{ record.ID }}">
+ <div class="flex items-center gap-3 min-w-0" data-record-display>
{% 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">
+ <div class="flex items-center justify-center w-6 h-6 rounded bg-amber-500/10 shrink-0">
+ <svg class="w-3.5 h-3.5 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
@@ -128,11 +152,12 @@
<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">
+ <div class="flex items-center gap-3 shrink-0 ml-4" data-record-actions>
{% 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>
+ <button type="button" class="text-xs text-zinc-600 hover:text-zinc-300 transition-colors duration-150 opacity-0 group-hover:opacity-100" data-edit-record data-record-type="{{ record.Type }}" data-record-id="{{ record.ID }}" data-record-name="{{ record.Name }}" data-record-value="{{ record.Value }}" data-record-ttl="{{ record.TTL }}" data-record-priority="{{ record.Priority }}">Edit</button>
+ <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 opacity-0 group-hover:opacity-100">Delete</button>
{% endif %}
</div>
</div>
@@ -178,7 +203,8 @@
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 typeDropdown = document.querySelector('[data-record-type-dropdown]');
+ var typeHiddenInput = typeDropdown.querySelector('[data-dropdown-value]');
var priorityField = document.querySelector('[data-priority-field]');
var portMapField = document.querySelector('[data-port-map-field]');
var srvWeight = document.querySelector('[data-srv-weight]');
@@ -205,7 +231,8 @@
};
function updateFields() {
- var config = fieldConfig[typeSelect.value];
+ var selectedType = typeHiddenInput.value;
+ var config = fieldConfig[selectedType];
if (!config) return;
valueLabel.textContent = config.label;
@@ -217,7 +244,118 @@
srvProtocol.style.display = config.srv ? '' : 'none';
}
- typeSelect.addEventListener('change', updateFields);
+ var typeOptions = typeDropdown.querySelectorAll('[data-dropdown-option]');
+ typeOptions.forEach(function(option) {
+ option.addEventListener('click', function() {
+ setTimeout(updateFields, 10);
+ });
+ });
+
updateFields();
+
+ var ttlOptions = [['1', 'Immediate'], ['300', '5 min'], ['900', '15 min'], ['3600', '1 hour'], ['14400', '4 hours'], ['86400', '1 day']];
+
+ function createEditForm(recordType, recordId, recordName, recordValue, recordTtl, recordPriority) {
+ var form = document.createElement('form');
+ form.setAttribute('hx-put', '/domains/records/' + recordType + '/' + recordId + '?domain_id={{ domain.ID }}');
+ form.setAttribute('hx-swap', 'none');
+ form.setAttribute('data-inline-edit', '');
+ form.className = 'flex items-center gap-3 w-full';
+
+ var typeBadge = document.createElement('span');
+ typeBadge.className = 'inline-flex items-center px-1.5 py-0.5 rounded text-[10px] font-medium shrink-0 bg-surface-800 text-zinc-400';
+ typeBadge.textContent = recordType;
+ form.appendChild(typeBadge);
+
+ var nameInput = document.createElement('input');
+ nameInput.type = 'text';
+ nameInput.name = 'name';
+ nameInput.value = recordName;
+ nameInput.className = 'input-field text-xs w-20';
+ nameInput.placeholder = '@';
+ form.appendChild(nameInput);
+
+ var valueInput = document.createElement('input');
+ valueInput.type = 'text';
+ valueInput.name = 'value';
+ valueInput.value = recordValue;
+ valueInput.required = true;
+ valueInput.className = 'input-field text-xs flex-1';
+ valueInput.placeholder = 'Value';
+ form.appendChild(valueInput);
+
+ var hasPriority = parseInt(recordPriority, 10) > 0;
+ if (hasPriority) {
+ var priorityInput = document.createElement('input');
+ priorityInput.type = 'number';
+ priorityInput.name = 'priority';
+ priorityInput.value = recordPriority;
+ priorityInput.min = '0';
+ priorityInput.className = 'input-field text-xs w-16';
+ priorityInput.placeholder = 'Pri';
+ form.appendChild(priorityInput);
+ }
+
+ var ttlSelect = document.createElement('select');
+ ttlSelect.name = 'ttl';
+ ttlSelect.className = 'input-field text-xs w-24';
+ for (var i = 0; i < ttlOptions.length; i++) {
+ var opt = document.createElement('option');
+ opt.value = ttlOptions[i][0];
+ opt.textContent = ttlOptions[i][1];
+ if (recordTtl === ttlOptions[i][0]) opt.selected = true;
+ ttlSelect.appendChild(opt);
+ }
+ form.appendChild(ttlSelect);
+
+ var saveButton = document.createElement('button');
+ saveButton.type = 'submit';
+ saveButton.className = 'btn-small';
+ saveButton.textContent = 'Save';
+ form.appendChild(saveButton);
+
+ var cancelLink = document.createElement('a');
+ cancelLink.href = 'javascript:void(0)';
+ cancelLink.className = 'text-xs text-zinc-500 hover:text-zinc-300 transition-colors duration-150 shrink-0';
+ cancelLink.textContent = 'Cancel';
+ cancelLink.setAttribute('data-cancel-edit', '');
+ form.appendChild(cancelLink);
+
+ return form;
+ }
+
+ function handleEditClick(event) {
+ var button = event.target.closest('[data-edit-record]');
+ if (!button) return;
+
+ var recordType = button.dataset.recordType;
+ var recordId = button.dataset.recordId;
+ var recordName = button.dataset.recordName;
+ var recordValue = button.dataset.recordValue;
+ var recordTtl = button.dataset.recordTtl;
+ var recordPriority = button.dataset.recordPriority;
+
+ var row = document.querySelector('[data-record-row="' + recordType + '-' + recordId + '"]');
+ if (!row || row.querySelector('[data-inline-edit]')) return;
+
+ var originalContent = row.innerHTML;
+
+ while (row.firstChild) row.removeChild(row.firstChild);
+
+ var form = createEditForm(recordType, recordId, recordName, recordValue, recordTtl, recordPriority);
+ row.appendChild(form);
+
+ form.querySelector('[data-cancel-edit]').addEventListener('click', function() {
+ row.innerHTML = originalContent;
+ htmx.process(row);
+ });
+
+ htmx.process(row);
+ }
+
+ var recordsList = document.querySelector('.divide-y');
+ if (recordsList) {
+ recordsList.addEventListener('click', handleEditClick);
+ }
})();
</script> \ No newline at end of file
diff --git a/templates/domains/htmx/domains.htmx.django b/templates/domains/htmx/domains.htmx.django
index 4c3e37f..86128f6 100644
--- a/templates/domains/htmx/domains.htmx.django
+++ b/templates/domains/htmx/domains.htmx.django
@@ -24,8 +24,6 @@
<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 %}
<button data-confirm-trigger data-confirm-title="Delete {{ domain.Name }}.{{ domain.TLD.Name }}" data-confirm-message="This domain and all associated data will be permanently removed. This action cannot be undone." data-confirm-action="{{ delete_domain_path }}" class="text-xs text-zinc-600 hover:text-red-400 transition-colors duration-150">Delete</button>
</div>
diff --git a/templates/partials/sidebar.django b/templates/partials/sidebar.django
index cb33ae5..01dd602 100644
--- a/templates/partials/sidebar.django
+++ b/templates/partials/sidebar.django
@@ -50,12 +50,6 @@
</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>