DNS record management, CSS grid, Regex, location.query

This commit is contained in:
Face
2025-08-20 14:37:57 +03:00
parent 99f17dc42c
commit e8508bfe33
22 changed files with 1351 additions and 123 deletions

View File

@@ -44,7 +44,7 @@ local function renderDomains()
for i, domain in ipairs(domains) do
local domainItem = gurt.create('div', {
style = 'domain-item'
style = 'domain-item cursor-pointer hover:bg-[#4b5563]'
})
local domainInfo = gurt.create('div', { style = 'w-full' })
@@ -54,51 +54,20 @@ local function renderDomains()
style = 'font-bold text-lg'
})
local domainIP = gurt.create('div', {
text = 'IP: ' .. domain.ip,
style = 'text-[#6b7280]'
})
local domainStatus = gurt.create('div', {
text = 'Status: ' .. (domain.status or 'Unknown'),
style = 'text-[#6b7280]'
})
domainInfo:append(domainName)
domainInfo:append(domainIP)
domainInfo:append(domainStatus)
local actions = gurt.create('div', {
style = 'flex gap-2'
})
-- Update IP button
local updateBtn = gurt.create('button', {
text = 'Update IP',
style = 'secondary-btn'
})
updateBtn:on('click', function()
local newIP = prompt('Enter new IP address for ' .. domain.name .. '.' .. domain.tld .. ':')
if newIP and newIP ~= '' then
updateDomainIP(domain.name, domain.tld, newIP)
end
end)
-- Delete button
local deleteBtn = gurt.create('button', { text = 'Delete', style = 'danger-btn' })
deleteBtn:on('click', function()
if confirm('Are you sure you want to delete ' .. domain.name .. '.' .. domain.tld .. '?') then
deleteDomain(domain.name, domain.tld)
end
end)
actions:append(updateBtn)
actions:append(deleteBtn)
domainItem:append(domainInfo)
domainItem:append(actions)
domainItem:on('click', function()
gurt.location.goto('/domain.html?name=' .. domain.name .. '.' .. domain.tld)
end)
domainsList:append(domainItem)
end
end

137
dns/frontend/domain.html Normal file
View File

@@ -0,0 +1,137 @@
<head>
<title>Domain Management</title>
<icon src="https://cdn-icons-png.flaticon.com/512/295/295128.png">
<meta name="theme-color" content="#0891b2">
<meta name="description" content="Manage DNS records for your domain">
<style>
body {
bg-[#171616] font-sans text-white
}
h1 {
text-[#ef4444] text-3xl font-bold text-center
}
h2 {
text-[#dc2626] text-xl font-semibold
}
h3 {
text-[#fca5a5] text-lg font-medium
}
.container {
bg-[#262626] p-6 rounded-lg shadow-lg max-w-6xl
}
.primary-btn {
p-3 rounded-lg font-medium cursor-pointer transition-colors bg-[#dc2626] text-white
}
.success-btn {
p-3 rounded-lg font-medium cursor-pointer transition-colors bg-[#ef4444] text-white
}
.danger-btn {
p-3 rounded-lg font-medium cursor-pointer transition-colors bg-[#b91c1c] text-white
}
.secondary-btn {
p-3 rounded-lg font-medium cursor-pointer transition-colors bg-[#525252] text-white
}
.form-group {
flex flex-col gap-2 mb-4 w-full
}
.form-input {
w-full p-3 border border-gray-600 rounded-md bg-[#374151] text-white active:border-red-500
}
.form-select {
w-full p-3 border border-gray-600 rounded-md bg-[#374151] text-white active:border-red-500 active:text-white
}
.card {
bg-[#262626] p-6 rounded-lg shadow border border-gray-700
}
.stats-card {
bg-[#1f1f1f] p-4 rounded-lg border border-[#dc2626]
}
.record-item {
bg-[#374151] p-4 rounded-lg border border-gray-700 mb-2 flex justify-between items-center
}
.error-text {
text-[#fca5a5] text-sm
}
.record-table {
w-full border
}
.record-table th {
p-3 text-left border-b border-gray-600 text-[#dc2626]
}
.record-table td {
p-3 border-b border-gray-700
}
</style>
<script src="domain.lua" />
</head>
<body>
<div style="container mt-6">
<div style="stats-card mb-6">
<div style="flex justify-between items-center w-full">
<div>
<h1 id="domain-title">Loading...</h1>
<p id="domain-status" style="text-[#6b7280]">Status: Loading...</p>
</div>
<div style="flex gap-2">
<button id="back-btn" style="secondary-btn">Back</button>
<button id="logout-btn" style="secondary-btn">Logout</button>
</div>
</div>
</div>
<div style="card mb-6">
<h2 style="mb-4">Records</h2>
<div id="records-list">
<p id="records-loading">Loading DNS records...</p>
</div>
</div>
<div style="card">
<h2 style="mb-4">New Record</h2>
<div style="form-group">
<p>Type:</p>
<select id="record-type" style="form-select">
<option value="A">A</option>
<option value="AAAA">AAAA</option>
<option value="CNAME">CNAME</option>
<option value="TXT">TXT</option>
</select>
</div>
<div style="form-group">
<p>Name:</p>
<input id="record-name" type="text" style="form-input" placeholder="@, www, *" pattern="^(?:\*|@|((?!-)[A-Za-z0-9-]{1,63}(?<!-)(\.(?!-)[A-Za-z0-9-]{1,63}(?<!-))*))$" />
</div>
<div style="form-group">
<p>Value:</p>
<input id="record-value" type="text" style="form-input" placeholder="192.168.1.1, example.com" />
</div>
<div style="form-group">
<p>TTL:</p>
<input id="record-ttl" type="text" style="form-input" placeholder="3600" pattern="^[0-9]+$" />
</div>
</div>
<div id="record-error" style="error-text hidden mb-2"></div>
<button id="add-record-btn" style="success-btn">Add Record</button>
</div>
</body>

300
dns/frontend/domain.lua Normal file
View File

@@ -0,0 +1,300 @@
local user = nil
local domain = nil
local records = {}
local authToken = nil
local domainName = nil
local domainTitle = gurt.select('#domain-title')
local domainStatus = gurt.select('#domain-status')
local recordsList = gurt.select('#records-list')
local loadingElement = gurt.select('#records-loading')
local function showError(elementId, message)
local element = gurt.select('#' .. elementId)
element.text = message
element.classList:remove('hidden')
end
local function hideError(elementId)
local element = gurt.select('#' .. elementId)
element.classList:add('hidden')
end
-- Forward declarations
local loadRecords
local renderRecords
local function deleteRecord(recordId)
print('Deleting DNS record: ' .. recordId)
local response = fetch('gurt://localhost:8877/domain/' .. domainName .. '/records/' .. recordId, {
method = 'DELETE',
headers = {
Authorization = 'Bearer ' .. authToken
}
})
if response:ok() then
print('DNS record deleted successfully')
-- Remove the record from local records array
for i = #records, 1, -1 do
if records[i].id == recordId then
table.remove(records, i)
break
end
end
-- Re-render the entire list from scratch
renderRecords()
else
print('Failed to delete DNS record: ' .. response:text())
end
end
-- Actual implementation
loadRecords = function()
print('Loading DNS records for: ' .. domainName)
local response = fetch('gurt://localhost:8877/domain/' .. domainName .. '/records', {
headers = {
Authorization = 'Bearer ' .. authToken
}
})
if response:ok() then
records = response:json()
print('Loaded ' .. #records .. ' DNS records')
renderRecords()
else
print('Failed to load DNS records: ' .. response:text())
records = {}
renderRecords()
end
end
renderRecords = function(appendOnly)
if loadingElement then
loadingElement:remove()
loadingElement = nil
end
-- Clear everything if not appending
if not appendOnly then
local children = recordsList.children
while #children > 0 do
children[1]:remove()
children = recordsList.children
end
end
if #records == 0 then
local emptyMessage = gurt.create('div', {
text = 'No DNS records found. Add your first record below!',
style = 'text-center text-[#6b7280] py-8'
})
recordsList:append(emptyMessage)
return
end
-- Create header only if not appending or if list is empty
if not appendOnly or #recordsList.children == 0 then
local header = gurt.create('div', { style = 'w-full flex justify-between gap-4 p-4 bg-gray-600 font-bold border-b rounded-xl' })
local typeHeader = gurt.create('div', { text = 'Type' })
local nameHeader = gurt.create('div', { text = 'Name' })
local valueHeader = gurt.create('div', { text = 'Value' })
local ttlHeader = gurt.create('div', { text = 'TTL' })
local actionsHeader = gurt.create('div', { text = 'Actions' })
header:append(typeHeader)
header:append(nameHeader)
header:append(valueHeader)
header:append(ttlHeader)
header:append(actionsHeader)
recordsList:append(header)
end
-- Create records list - when appending, only render the last record; otherwise render all
local startIndex = appendOnly and #records or 1
for i = startIndex, #records do
local record = records[i]
local row = gurt.create('div', { style = 'w-full flex justify-between gap-4 p-4 border-b border-gray-600 hover:bg-[rgba(244, 67, 54, 0.2)]' })
local typeCell = gurt.create('div', { text = record.type, style = 'font-bold' })
local nameCell = gurt.create('div', { text = record.name or '@' })
local valueCell = gurt.create('div', { text = record.value, style = 'font-mono text-sm break-all' })
local ttlCell = gurt.create('div', { text = record.ttl or '3600' })
local actionsCell = gurt.create('div')
local deleteBtn = gurt.create('button', {
text = 'Delete',
style = 'danger-btn text-xs px-2 py-1'
})
deleteBtn:on('click', function()
if deleteBtn.text == 'Delete' then
deleteBtn.text = 'Confirm Delete'
else
deleteRecord(record.id)
end
end)
actionsCell:append(deleteBtn)
row:append(typeCell)
row:append(nameCell)
row:append(valueCell)
row:append(ttlCell)
row:append(actionsCell)
recordsList:append(row)
end
end
local function getDomainNameFromURL()
local nameParam = gurt.location.query.get('name')
if nameParam then
return nameParam:gsub('%%%.', '.')
end
return nil
end
local function updateDomainInfo()
if domain then
domainTitle.text = domain.name .. '.' .. domain.tld
domainStatus.text = 'Status: ' .. (domain.status or 'Unknown')
end
end
local function loadDomain()
print('Loading domain details for: ' .. domainName)
local response = fetch('gurt://localhost:8877/domain/' .. domainName, {
headers = {
Authorization = 'Bearer ' .. authToken
}
})
if response:ok() then
domain = response:json()
print('Loaded domain details')
updateDomainInfo()
loadRecords()
else
print('Failed to load domain: ' .. response:text())
--gurt.location.goto('/dashboard.html')
end
end
local function checkAuth()
authToken = gurt.crumbs.get("auth_token")
if authToken then
print('Found auth token, checking validity...')
local response = fetch('gurt://localhost:8877/auth/me', {
headers = {
Authorization = 'Bearer ' .. authToken
}
})
if response:ok() then
user = response:json()
print('Authentication successful for user: ' .. user.username)
domainName = getDomainNameFromURL()
if domainName then
loadDomain()
else
print('No domain name in URL, redirecting to dashboard')
--gurt.location.goto('/dashboard.html')
end
else
print('Token invalid, redirecting to login...')
gurt.crumbs.delete('auth_token')
--gurt.location.goto('../')
end
else
print('No auth token found, redirecting to login...')
--gurt.location.goto('../')
end
end
local function addRecord(type, name, value, ttl)
hideError('record-error')
print('Adding DNS record: ' .. type .. ' ' .. name .. ' ' .. value)
local response = fetch('gurt://localhost:8877/domain/' .. domainName .. '/records', {
method = 'POST',
headers = {
['Content-Type'] = 'application/json',
Authorization = 'Bearer ' .. authToken
},
body = JSON.stringify({
type = type,
name = name,
value = value,
ttl = ttl
})
})
if response:ok() then
print('DNS record added successfully')
-- Clear form
gurt.select('#record-name').value = ''
gurt.select('#record-value').value = ''
gurt.select('#record-ttl').value = '3600'
-- Add the new record to existing records array
local newRecord = response:json()
if newRecord and newRecord.id then
-- Server returned the created record, add it to our local array
table.insert(records, newRecord)
-- Render only the new record
renderRecords(true)
else
-- Server didn't return record details, reload to get the actual data
loadRecords()
end
else
local error = response:text()
showError('record-error', 'Failed to add record: ' .. error)
print('Failed to add DNS record: ' .. error)
end
end
local function logout()
gurt.crumbs.delete('auth_token')
print('Logged out successfully')
--gurt.location.goto("../")
end
local function goBack()
--gurt.location.goto("/dashboard.html")
end
-- Event handlers
gurt.select('#logout-btn'):on('click', logout)
gurt.select('#back-btn'):on('click', goBack)
gurt.select('#add-record-btn'):on('click', function()
local recordType = gurt.select('#record-type').value
local recordName = gurt.select('#record-name').value
local recordValue = gurt.select('#record-value').value
local recordTTL = tonumber(gurt.select('#record-ttl').value) or 3600
if not recordValue or recordValue == '' then
showError('record-error', 'Record value is required')
return
end
if not recordName or recordName == '' then
recordName = '@'
end
addRecord(recordType, recordName, recordValue, recordTTL)
end)
-- Initialize
print('Domain management page initialized')
checkAuth()