Initial commit: DDNS service with NIC V2 protocol support
Features: - Token-based subdomain claiming - NIC V2 (DynDNS2) protocol implementation - Technitium DNS integration - Rate limiting (10 req/min IP, 1 req/min token) - Web UI for space claiming - Docker/Docker Compose support - Compatible with UniFi, pfSense, EdgeRouter Module: git.dws.rip/DWS/dyn
This commit is contained in:
135
web/static/js/app.js
Normal file
135
web/static/js/app.js
Normal file
@@ -0,0 +1,135 @@
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const claimForm = document.getElementById('claim-form');
|
||||
const subdomainInput = document.getElementById('subdomain');
|
||||
const availabilityStatus = document.getElementById('availability-status');
|
||||
const claimBtn = document.getElementById('claim-btn');
|
||||
const claimSection = document.getElementById('claim-section');
|
||||
const successSection = document.getElementById('success-section');
|
||||
const errorSection = document.getElementById('error-section');
|
||||
const routerConfig = document.getElementById('router-config');
|
||||
|
||||
let checkTimeout = null;
|
||||
|
||||
subdomainInput.addEventListener('input', function() {
|
||||
const value = this.value.toLowerCase().replace(/[^a-z0-9-]/g, '');
|
||||
this.value = value;
|
||||
|
||||
clearTimeout(checkTimeout);
|
||||
availabilityStatus.textContent = '';
|
||||
availabilityStatus.className = 'status';
|
||||
claimBtn.disabled = true;
|
||||
|
||||
if (value.length >= 3) {
|
||||
checkTimeout = setTimeout(() => checkAvailability(value), 300);
|
||||
}
|
||||
});
|
||||
|
||||
async function checkAvailability(subdomain) {
|
||||
try {
|
||||
const response = await fetch(`/api/check?subdomain=${encodeURIComponent(subdomain)}`);
|
||||
const data = await response.json();
|
||||
|
||||
if (data.available) {
|
||||
availabilityStatus.textContent = '✓ Available';
|
||||
availabilityStatus.className = 'status available';
|
||||
claimBtn.disabled = false;
|
||||
} else {
|
||||
availabilityStatus.textContent = '✗ Already taken';
|
||||
availabilityStatus.className = 'status taken';
|
||||
claimBtn.disabled = true;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error checking availability:', error);
|
||||
}
|
||||
}
|
||||
|
||||
claimForm.addEventListener('submit', async function(e) {
|
||||
e.preventDefault();
|
||||
const subdomain = subdomainInput.value;
|
||||
|
||||
claimBtn.disabled = true;
|
||||
claimBtn.textContent = 'Claiming...';
|
||||
|
||||
try {
|
||||
const response = await fetch('/api/claim', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({ subdomain: subdomain }),
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (response.ok) {
|
||||
showSuccess(data);
|
||||
} else {
|
||||
showError(data.error || 'Failed to claim space');
|
||||
}
|
||||
} catch (error) {
|
||||
showError('Network error. Please try again.');
|
||||
}
|
||||
|
||||
claimBtn.disabled = false;
|
||||
claimBtn.textContent = 'Claim Space';
|
||||
});
|
||||
|
||||
function showSuccess(data) {
|
||||
claimSection.classList.add('hidden');
|
||||
successSection.classList.remove('hidden');
|
||||
routerConfig.classList.remove('hidden');
|
||||
|
||||
document.getElementById('token-value').textContent = data.token;
|
||||
document.getElementById('fqdn-value').textContent = data.fqdn;
|
||||
|
||||
const updateUrl = `https://dyn.${data.fqdn.split('.').slice(-2).join('.')}/api/nic/update?hostname=%h&myip=%i`;
|
||||
document.getElementById('update-url').textContent = updateUrl;
|
||||
|
||||
document.querySelectorAll('.hostname-placeholder').forEach(el => {
|
||||
el.textContent = data.fqdn;
|
||||
});
|
||||
|
||||
document.querySelectorAll('.update-url-placeholder').forEach(el => {
|
||||
el.textContent = updateUrl;
|
||||
});
|
||||
|
||||
const curlCmd = `curl -u "none:${data.token}" "https://dyn.${data.fqdn.split('.').slice(-2).join('.')}/api/nic/update?hostname=${data.fqdn}"`;
|
||||
document.getElementById('curl-example').textContent = curlCmd;
|
||||
|
||||
document.getElementById('copy-token').addEventListener('click', function() {
|
||||
copyToClipboard(data.token);
|
||||
this.textContent = 'Copied!';
|
||||
setTimeout(() => this.textContent = 'Copy Token', 2000);
|
||||
});
|
||||
|
||||
document.getElementById('copy-curl').addEventListener('click', function() {
|
||||
copyToClipboard(curlCmd);
|
||||
this.textContent = 'Copied!';
|
||||
setTimeout(() => this.textContent = 'Copy', 2000);
|
||||
});
|
||||
}
|
||||
|
||||
function showError(message) {
|
||||
claimSection.classList.add('hidden');
|
||||
errorSection.classList.remove('hidden');
|
||||
document.getElementById('error-message').textContent = message;
|
||||
}
|
||||
|
||||
document.getElementById('try-again').addEventListener('click', function() {
|
||||
errorSection.classList.add('hidden');
|
||||
claimSection.classList.remove('hidden');
|
||||
subdomainInput.value = '';
|
||||
subdomainInput.focus();
|
||||
});
|
||||
|
||||
function copyToClipboard(text) {
|
||||
navigator.clipboard.writeText(text).catch(function() {
|
||||
const textarea = document.createElement('textarea');
|
||||
textarea.value = text;
|
||||
document.body.appendChild(textarea);
|
||||
textarea.select();
|
||||
document.execCommand('copy');
|
||||
document.body.removeChild(textarea);
|
||||
});
|
||||
}
|
||||
});
|
||||
Reference in New Issue
Block a user