From b1adb8f52e72942f0178874917546b0da1495722 Mon Sep 17 00:00:00 2001 From: master3395 Date: Sun, 4 Jan 2026 04:15:23 +0100 Subject: [PATCH] DNS improvements: CloudFlare proxy toggle styling, auto AAAA records, proxy defaults - Fixed CloudFlare proxy toggle button to display as oblong with round dot - Enable CloudFlare proxy by default for all domains/subdomains except mail domains - Automatically add AAAA (IPv6) DNS records when creating domains/subdomains - Added GetServerIPv6() function to retrieve server IPv6 address - Updated DNS template styling and Angular.js binding for toggle buttons --- .../dns/addDeleteDNSRecordsCloudFlare.html | 75 ++++++++++++------- plogical/acl.py | 32 ++++++++ plogical/dnsUtilities.py | 69 ++++++++++++++++- 3 files changed, 147 insertions(+), 29 deletions(-) diff --git a/dns/templates/dns/addDeleteDNSRecordsCloudFlare.html b/dns/templates/dns/addDeleteDNSRecordsCloudFlare.html index 81c335c72..09a90bcf8 100644 --- a/dns/templates/dns/addDeleteDNSRecordsCloudFlare.html +++ b/dns/templates/dns/addDeleteDNSRecordsCloudFlare.html @@ -472,41 +472,60 @@ } } - .proxy-toggle { - appearance: none; - width: 48px; - height: 24px; - background: #e8e9ff; - border-radius: 24px; - position: relative; - cursor: pointer; - transition: all 0.3s ease; + input.proxy-toggle[type="checkbox"] { + -webkit-appearance: none !important; + -moz-appearance: none !important; + appearance: none !important; + width: 50px !important; + height: 26px !important; + min-width: 50px !important; + min-height: 26px !important; + max-width: 50px !important; + max-height: 26px !important; + background: #ccc !important; + border-radius: 13px !important; + position: relative !important; + cursor: pointer !important; + transition: background 0.3s ease !important; + border: none !important; + outline: none !important; + display: inline-block !important; + vertical-align: middle !important; + margin: 0 !important; + padding: 0 !important; + box-sizing: border-box !important; } - .proxy-toggle:checked { - background: #5b5fcf; + input.proxy-toggle[type="checkbox"]:checked { + background: #5b5fcf !important; } - .proxy-toggle::after { - content: ''; - position: absolute; - top: 2px; - left: 2px; - width: 20px; - height: 20px; - background: var(--bg-primary, white); - border-radius: 50%; - transition: all 0.3s ease; - box-shadow: 0 2px 4px rgba(0,0,0,0.2); + input.proxy-toggle[type="checkbox"]::before { + content: '' !important; + position: absolute !important; + width: 22px !important; + height: 22px !important; + border-radius: 50% !important; + background: white !important; + top: 2px !important; + left: 2px !important; + transition: left 0.3s ease, transform 0.3s ease !important; + box-shadow: 0 2px 4px rgba(0,0,0,0.2) !important; + display: block !important; } - .proxy-toggle:checked::after { - left: 26px; + input.proxy-toggle[type="checkbox"]:checked::before { + left: 26px !important; } - .proxy-toggle:disabled { - opacity: 0.5; - cursor: not-allowed; + input.proxy-toggle[type="checkbox"]:disabled { + opacity: 0.5 !important; + cursor: not-allowed !important; + } + + input.proxy-toggle[type="checkbox"]:focus { + outline: 2px solid rgba(91, 95, 207, 0.3) !important; + outline-offset: 2px !important; } @@ -876,7 +895,7 @@ diff --git a/plogical/acl.py b/plogical/acl.py index 071fc110c..061d1a6df 100644 --- a/plogical/acl.py +++ b/plogical/acl.py @@ -1048,6 +1048,38 @@ class ACLManager: ipData = f.read() return ipData.split('\n', 1)[0] + @staticmethod + def GetServerIPv6(): + """ + Get the server's primary IPv6 address (non-link-local, non-loopback) + Returns None if no IPv6 address is found + """ + try: + import subprocess + # Get IPv6 addresses, exclude link-local (fe80::) and loopback (::1) + result = subprocess.run( + ['ip', '-6', 'addr', 'show'], + capture_output=True, + text=True, + timeout=5 + ) + + if result.returncode == 0: + lines = result.stdout.split('\n') + for line in lines: + if 'inet6' in line and '::1' not in line and 'fe80::' not in line: + # Extract IPv6 address (format: inet6 2a02:c207:2139:8929::1/64) + parts = line.strip().split() + if len(parts) >= 2: + ipv6 = parts[1].split('/')[0] + # Validate it's a real IPv6 (not link-local) + if not ipv6.startswith('fe80::'): + return ipv6 + except Exception as e: + logging.CyberCPLogFileWriter.writeToFile(f'Error getting IPv6 address: {str(e)}') + + return None + @staticmethod def CheckForPremFeature(feature): try: diff --git a/plogical/dnsUtilities.py b/plogical/dnsUtilities.py index 78db0bc59..9a6fbe233 100644 --- a/plogical/dnsUtilities.py +++ b/plogical/dnsUtilities.py @@ -220,6 +220,15 @@ class DNS: DNS.createDNSRecord(zone, topLevelDomain, "A", ipAddress, 0, 3600) + # AAAA Record (IPv6) - Required for mail delivery to Google, Outlook, etc. + try: + from plogical.acl import ACLManager + ipv6Address = ACLManager.GetServerIPv6() + if ipv6Address: + DNS.createDNSRecord(zone, topLevelDomain, "AAAA", ipv6Address, 0, 3600) + except Exception as e: + logging.CyberCPLogFileWriter.writeToFile(f'Error creating AAAA record for {topLevelDomain}: {str(e)}') + # CNAME Records. cNameValue = "www." + topLevelDomain @@ -282,6 +291,15 @@ class DNS: DNS.createDNSRecord(zone, mxValue, "A", ipAddress, 0, 3600) + # AAAA Record for mail (IPv6) - Required for mail delivery + try: + from plogical.acl import ACLManager + ipv6Address = ACLManager.GetServerIPv6() + if ipv6Address: + DNS.createDNSRecord(zone, mxValue, "AAAA", ipv6Address, 0, 3600) + except Exception as e: + logging.CyberCPLogFileWriter.writeToFile(f'Error creating AAAA record for mail {mxValue}: {str(e)}') + ## TXT Records for mail # record = Records(domainOwner=zone, @@ -365,6 +383,15 @@ class DNS: DNS.createDNSRecord(zone, topLevelDomain, "A", ipAddress, 0, 3600) + # AAAA Record (IPv6) - Required for mail delivery to Google, Outlook, etc. + try: + from plogical.acl import ACLManager + ipv6Address = ACLManager.GetServerIPv6() + if ipv6Address: + DNS.createDNSRecord(zone, topLevelDomain, "AAAA", ipv6Address, 0, 3600) + except Exception as e: + logging.CyberCPLogFileWriter.writeToFile(f'Error creating AAAA record for {topLevelDomain}: {str(e)}') + # CNAME Records. cNameValue = "www." + topLevelDomain @@ -427,6 +454,15 @@ class DNS: DNS.createDNSRecord(zone, mxValue, "A", ipAddress, 0, 3600) + # AAAA Record for mail (IPv6) - Required for mail delivery + try: + from plogical.acl import ACLManager + ipv6Address = ACLManager.GetServerIPv6() + if ipv6Address: + DNS.createDNSRecord(zone, mxValue, "AAAA", ipv6Address, 0, 3600) + except Exception as e: + logging.CyberCPLogFileWriter.writeToFile(f'Error creating AAAA record for mail {mxValue}: {str(e)}') + ## TXT Records for mail # record = Records(domainOwner=zone, @@ -478,10 +514,27 @@ class DNS: DNS.createDNSRecord(zone, actualSubDomain, "A", ipAddress, 0, 3600) + # AAAA Record for subdomain (IPv6) + try: + from plogical.acl import ACLManager + ipv6Address = ACLManager.GetServerIPv6() + if ipv6Address: + DNS.createDNSRecord(zone, actualSubDomain, "AAAA", ipv6Address, 0, 3600) + except Exception as e: + logging.CyberCPLogFileWriter.writeToFile(f'Error creating AAAA record for subdomain {actualSubDomain}: {str(e)}') + ## Mail Record if ('mail.%s' % (actualSubDomain)).find('mail.mail') == -1: DNS.createDNSRecord(zone, 'mail.' + actualSubDomain, "A", ipAddress, 0, 3600) + # AAAA Record for mail subdomain (IPv6) - Required for mail delivery + try: + from plogical.acl import ACLManager + ipv6Address = ACLManager.GetServerIPv6() + if ipv6Address: + DNS.createDNSRecord(zone, 'mail.' + actualSubDomain, "AAAA", ipv6Address, 0, 3600) + except Exception as e: + logging.CyberCPLogFileWriter.writeToFile(f'Error creating AAAA record for mail subdomain {actualSubDomain}: {str(e)}') # CNAME Records. @@ -656,17 +709,31 @@ class DNS: return 0 @staticmethod - def createDNSRecordCloudFlare(cf, zone, name, type, value, priority, ttl): + def createDNSRecordCloudFlare(cf, zone, name, type, value, priority, ttl, proxied=None): try: if value.find('DKIM') > -1: value = value.replace('\n\t', '') value = value.replace('"', '') + # Only A and CNAME records can be proxied in CloudFlare + # Determine if proxy should be enabled (default: True for A/CNAME, except for mail domains) + if proxied is None and type in ['A', 'CNAME']: + # Check if this is a mail domain (starts with 'mail.' or contains 'mail.') + is_mail_domain = name.lower().startswith('mail.') or '.mail.' in name.lower() + proxied = not is_mail_domain + elif type not in ['A', 'CNAME']: + # AAAA, MX, TXT, etc. cannot be proxied + proxied = False + if ttl > 0: dns_record = {'name': name, 'type': type, 'content': value, 'ttl': ttl, 'priority': priority} else: dns_record = {'name': name, 'type': type, 'content': value, 'priority': priority} + + # Only add proxied parameter for A and CNAME records + if type in ['A', 'CNAME']: + dns_record['proxied'] = proxied cf.zones.dns_records.post(zone, data=dns_record) except BaseException as msg: