domain dashboard, hidden css prop, fix bugs

This commit is contained in:
Face
2025-08-16 19:26:52 +03:00
parent d379836405
commit 3ed49fae0d
12 changed files with 561 additions and 19 deletions

View File

@@ -1,10 +1,114 @@
<head>
<title>Login</title>
<title>Domain Dashboard</title>
<icon src="https://cdn-icons-png.flaticon.com/512/295/295128.png">
<meta name="theme-color" content="#1b1b1b">
<meta name="description" content="Login to your account">
<meta name="theme-color" content="#0891b2">
<meta name="description" content="Manage your domains and registrations">
<style>
body { bg-[#f8fafc] p-6 font-sans }
h1 { text-[#0891b2] text-3xl font-bold text-center }
h2 { text-[#0f766e] text-xl font-semibold }
h3 { text-[#374151] text-lg font-medium }
.container { bg-[#ffffff] p-6 rounded-lg shadow-lg max-w-6xl mx-auto }
.primary-btn { px-4 py-2 rounded-lg font-medium cursor-pointer transition-colors bg-[#0891b2] text-white hover:bg-[#0e7490] }
.success-btn { px-4 py-2 rounded-lg font-medium cursor-pointer transition-colors bg-[#059669] text-white hover:bg-[#047857] }
.danger-btn { px-4 py-2 rounded-lg font-medium cursor-pointer transition-colors bg-[#dc2626] text-white hover:bg-[#b91c1c] }
.secondary-btn { px-4 py-2 rounded-lg font-medium cursor-pointer transition-colors bg-[#6b7280] text-white hover:bg-[#4b5563] }
.warning-btn { px-4 py-2 rounded-lg font-medium cursor-pointer transition-colors bg-[#f59e0b] text-white hover:bg-[#d97706] }
.form-group { flex flex-col gap-2 mb-4 }
.form-input { w-full p-3 border border-gray-300 rounded-md }
.card { bg-[#ffffff] p-4 rounded-lg shadow border }
.stats-card { bg-[#f0f9ff] p-4 rounded-lg border border-[#0891b2] }
.domain-item { bg-[#f8fafc] p-4 rounded-lg border mb-2 flex justify-between items-center }
.log-area { bg-[#1f2937] text-white p-4 rounded-lg font-mono text-sm max-h-64 overflow-auto }
.error-text { text-[#dc2626] text-sm }
.modal { w-full h-full bg-[rgba(0,0,0,0.5)] flex items-center justify-center z-50 }
.modal-content { bg-white p-6 rounded-lg max-w-md w-full mx-4 }
.tld-selector { flex flex-wrap gap-2 }
.tld-option { px-3 py-1 rounded border cursor-pointer hover:bg-[#f3f4f6] }
.tld-selected { bg-[#0891b2] text-white hover:bg-[#0e7490] }
</style>
<script src="dashboard.lua" />
</head>
<body>
<h1>todo</h1>
<h1>🌐 Domain Management Dashboard</h1>
<div style="container mt-6">
<div style="stats-card mb-6">
<div style="flex justify-between items-center">
<div id="user-info" style="text-lg font-semibold">Loading...</div>
<button id="logout-btn" style="secondary-btn">Logout</button>
</div>
</div>
<div style="card mb-6">
<h2>Register New Domain</h2>
<div style="form-group">
<label>Domain Name:</label>
<input id="domain-name" type="text" style="form-input" placeholder="myawesome" />
</div>
<div style="form-group">
<label>Select TLD:</label>
<div id="tld-selector" style="tld-selector">
Loading TLDs...
</div>
</div>
<div style="form-group">
<label>IP Address:</label>
<input id="domain-ip" type="text" style="form-input" placeholder="192.168.1.100" />
</div>
<div id="domain-error" style="error-text hidden mb-2"></div>
<button id="submit-domain-btn" style="success-btn">Submit for Approval</button>
</div>
<div style="card mb-6">
<h2>Invite System</h2>
<p style="text-[#6b7280] mb-4">Create invite codes to share with friends, or redeem codes to get more domain registrations.</p>
<div style="flex flex-row gap-4">
<div style="flex-1">
<h3>Create Invite</h3>
<button id="create-invite-btn" style="warning-btn">Generate Invite Code</button>
</div>
<div style="flex-1">
<h3>Redeem Invite</h3>
<div style="flex gap-2">
<input id="invite-code-input" type="text" style="form-input" placeholder="Enter invite code" />
<button id="redeem-invite-btn" style="primary-btn">Redeem</button>
</div>
<div id="redeem-error" style="error-text hidden mt-2"></div>
</div>
</div>
</div>
<div style="card mb-6">
<h2>My Domains</h2>
<div id="domains-list">
Loading domains...
</div>
</div>
<div style="card">
<h2>Activity Log</h2>
<div style="log-area">
<pre id="log-area">Initializing...</pre>
</div>
</div>
</div>
<div id="invite-modal" style="modal hidden">
<div style="modal-content">
<h3>Invite Code Generated</h3>
<p>Share this code with friends to give them 3 additional domain registrations:</p>
<div style="bg-[#f3f4f6] p-3 rounded font-mono text-center mb-4">
<span id="invite-code-display">Loading...</span>
</div>
<div style="flex gap-2 justify-center">
<button id="copy-invite-code" style="primary-btn">Copy Code</button>
<button id="close-invite-modal" style="secondary-btn">Close</button>
</div>
</div>
</div>
</body>

364
dns/frontend/dashboard.lua Normal file
View File

@@ -0,0 +1,364 @@
local user = nil
local domains = {}
local tlds = {}
local authToken = nil
local userInfo = gurt.select('#user-info')
local domainsList = gurt.select('#domains-list')
local logArea = gurt.select('#log-area')
local inviteModal = gurt.select('#invite-modal')
local tldSelector = gurt.select('#tld-selector')
local logMessages = {}
local function addLog(message)
table.insert(logMessages, Time.format(Time.now(), '%H:%M:%S') .. ' - ' .. message)
if #logMessages > 50 then
table.remove(logMessages, 1)
end
logArea.text = table.concat(logMessages, '\n')
end
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
local function showModal(modalId)
local modal = gurt.select('#' .. modalId)
modal.classList:remove('hidden')
end
local function hideModal(modalId)
local modal = gurt.select('#' .. modalId)
modal.classList:add('hidden')
end
local function makeRequest(url, options)
options = options or {}
if authToken then
options.headers = options.headers or {}
options.headers.Authorization = 'Bearer ' .. authToken
end
return fetch(url, options)
end
local function checkAuth()
authToken = gurt.crumbs.get("auth_token")
if authToken then
addLog('Found auth token, checking validity...')
local response = makeRequest('gurt://localhost:4878/auth/me')
print(table.tostring(response))
if response:ok() then
user = response:json()
addLog('Authentication successful for user: ' .. user.username)
updateUserInfo()
loadDomains()
loadTLDs()
else
addLog('Token invalid, redirecting to login...')
--gurt.crumbs.delete('auth_token')
--gurt.location.goto('../')
end
else
addLog('No auth token found, redirecting to login...')
gurt.location.goto('../')
end
end
local function logout()
gurt.crumbs.delete('auth_token')
addLog('Logged out successfully')
gurt.location.goto("../")
end
local function loadDomains()
addLog('Loading domains...')
local response = makeRequest('gurt://localhost:4878/domains?page=1&size=100')
if response:ok() then
local data = response:json()
domains = data.domains or {}
addLog('Loaded ' .. #domains .. ' domains')
renderDomains()
else
addLog('Failed to load domains: ' .. response:text())
end
end
local function loadTLDs()
addLog('Loading available TLDs...')
local response = fetch('gurt://localhost:4878/tlds')
if response:ok() then
tlds = response:json()
addLog('Loaded ' .. #tlds .. ' TLDs')
renderTLDSelector()
else
addLog('Failed to load TLDs: ' .. response:text())
end
end
local function submitDomain(name, tld, ip)
hideError('domain-error')
addLog('Submitting domain: ' .. name .. '.' .. tld)
local response = makeRequest('gurt://localhost:4878/domain', {
method = 'POST',
headers = { ['Content-Type'] = 'application/json' },
body = JSON.stringify({ name = name, tld = tld, ip = ip })
})
if response:ok() then
local data = response:json()
addLog('Domain submitted successfully: ' .. data.domain)
-- Update user registrations remaining
user.registrations_remaining = user.registrations_remaining - 1
updateUserInfo()
-- Clear form
gurt.select('#domain-name').text = ''
gurt.select('#domain-ip').text = ''
-- Refresh domains list
loadDomains()
else
local error = response:text()
showError('domain-error', 'Domain submission failed: ' .. error)
addLog('Domain submission failed: ' .. error)
end
end
local function createInvite()
addLog('Creating invite code...')
local response = makeRequest('gurt://localhost:4878/auth/invite', { method = 'POST' })
if response:ok() then
local data = response:json()
local inviteCode = data.invite_code
gurt.select('#invite-code-display').text = inviteCode
addLog('Invite code created: ' .. inviteCode)
showModal('invite-modal')
else
addLog('Failed to create invite: ' .. response:text())
end
end
local function redeemInvite(code)
hideError('redeem-error')
addLog('Redeeming invite code: ' .. code)
local response = makeRequest('gurt://localhost:4878/auth/redeem-invite', {
method = 'POST',
headers = { ['Content-Type'] = 'application/json' },
body = JSON.stringify({ invite_code = code })
})
if response:ok() then
local data = response:json()
addLog('Invite redeemed: +' .. data.registrations_added .. ' registrations')
-- Update user info
user.registrations_remaining = user.registrations_remaining + data.registrations_added
updateUserInfo()
-- Clear form
gurt.select('#invite-code-input').text = ''
else
local error = response:text()
showError('redeem-error', 'Failed to redeem invite: ' .. error)
addLog('Failed to redeem invite: ' .. error)
end
end
-- UI rendering functions
local function updateUserInfo()
if user then
userInfo.text = 'Welcome, ' .. user.username .. ' | Registrations remaining: ' .. user.registrations_remaining
end
end
local function renderTLDSelector()
tldSelector.text = ''
for i, tld in ipairs(tlds) do
local option = gurt.create('div', {
text = '.' .. tld,
style = 'tld-option',
['data-tld'] = tld
})
option:on('click', function()
-- Clear previous selection
local options = gurt.selectAll('.tld-option')
for j = 1, #options do
options[j].classList:remove('tld-selected')
end
-- Select this option
option.classList:add('tld-selected')
end)
tldSelector:append(option)
end
end
local function renderDomains()
domainsList.text = ''
if #domains == 0 then
local emptyMessage = gurt.create('div', {
text = 'No domains registered yet. Submit your first domain below!',
style = 'text-center text-[#6b7280] py-8'
})
domainsList:append(emptyMessage)
return
end
for i, domain in ipairs(domains) do
local domainItem = gurt.create('div', {
style = 'domain-item'
})
local domainInfo = gurt.create('div', {})
local domainName = gurt.create('div', {
text = domain.name .. '.' .. domain.tld,
style = 'font-bold text-lg'
})
local domainIP = gurt.create('div', {
text = 'IP: ' .. domain.ip,
style = 'text-[#6b7280]'
})
domainInfo:append(domainName)
domainInfo:append(domainIP)
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)
domainsList:append(domainItem)
end
end
local function updateDomainIP(name, tld, ip)
addLog('Updating IP for ' .. name .. '.' .. tld .. ' to ' .. ip)
local response = makeRequest('gurt://localhost:4878/domain/' .. name .. '/' .. tld, {
method = 'PUT',
headers = { ['Content-Type'] = 'application/json' },
body = JSON.stringify({ ip = ip })
})
if response:ok() then
addLog('Domain IP updated successfully')
loadDomains()
else
addLog('Failed to update domain IP: ' .. response:text())
end
end
local function deleteDomain(name, tld)
addLog('Deleting domain: ' .. name .. '.' .. tld)
local response = makeRequest('gurt://localhost:4878/domain/' .. name .. '/' .. tld, {
method = 'DELETE'
})
if response:ok() then
addLog('Domain deleted successfully')
loadDomains()
else
addLog('Failed to delete domain: ' .. response:text())
end
end
-- Event handlers
gurt.select('#logout-btn'):on('click', logout)
gurt.select('#submit-domain-btn'):on('click', function()
local name = gurt.select('#domain-name').text
local ip = gurt.select('#domain-ip').text
local selectedTLD = gurt.select('.tld-selected')
if not name or name == '' then
showError('domain-error', 'Domain name is required')
return
end
if not ip or ip == '' then
showError('domain-error', 'IP address is required')
return
end
if not selectedTLD then
showError('domain-error', 'Please select a TLD')
return
end
local tld = selectedTLD:getAttribute('data-tld')
submitDomain(name, tld, ip)
end)
gurt.select('#create-invite-btn'):on('click', createInvite)
gurt.select('#redeem-invite-btn'):on('click', function()
local code = gurt.select('#invite-code-input').text
if code and code ~= '' then
redeemInvite(code)
end
end)
gurt.select('#close-invite-modal'):on('click', function()
hideModal('invite-modal')
end)
gurt.select('#copy-invite-code'):on('click', function()
local inviteCode = gurt.select('#invite-code-display').text
Clipboard.write(inviteCode)
addLog('Invite code copied to clipboard')
end)
-- Initialize
addLog('Dashboard initialized')
checkAuth()

View File

@@ -18,7 +18,7 @@ submitBtn:on('submit', function(event)
password = password
})
print(request_body)
local url = 'http://localhost:8080/auth/login'
local url = 'gurt://localhost:8080/auth/login'
local headers = {
['Content-Type'] = 'application/json'
}
@@ -39,9 +39,13 @@ submitBtn:on('submit', function(event)
local jsonData = response:json()
if jsonData then
addLog('Logged in as user: ' .. jsonData.user.username)
addLog('Token: ' .. jsonData.token:sub(1, 20) .. '...')
-- TODO: store as cookie
gurt.crumbs.set({
name = "auth_token",
value = jsonData.token,
lifespan = 604800
})
gurt.location.goto("/dashboard.html")
end
else

View File

@@ -913,6 +913,10 @@ static func parse_utility_class_internal(rule: CSSRule, utility_name: String) ->
rule.properties[utility_name] = "200ms"
return
if utility_name == "hidden":
rule.properties["display"] = "none"
return
# Handle more utility classes as needed
# Add more cases here for other utilities

View File

@@ -112,15 +112,15 @@ func handle_style_element(style_element: HTMLElement) -> void:
parse_result.external_css.append(src)
return
# Handle inline CSS - we'll get the text content when parsing is complete
# For now, create a parser that will be populated later
# Handle inline CSS
if not parse_result.css_parser:
parse_result.css_parser = CSSParser.new()
parse_result.css_parser.init()
func process_styles() -> void:
if not parse_result.css_parser:
return
parse_result.css_parser = CSSParser.new()
parse_result.css_parser.init()
var css_content = Constants.DEFAULT_CSS
var style_elements = find_all("style")

View File

@@ -109,6 +109,12 @@ static func apply_element_styles(node: Control, element: HTMLParser.HTMLElement,
if styles.has("opacity"):
node.modulate.a = styles["opacity"]
if styles.has("display"):
if styles["display"] == "none":
node.visible = false
else:
node.visible = true
# Apply cursor
if styles.has("cursor"):
var cursor_shape = get_cursor_shape_from_type(styles["cursor"])

View File

@@ -5,6 +5,27 @@ static func setup_network_api(vm: LuauVM):
vm.lua_pushcallable(_lua_fetch_handler, "fetch")
vm.lua_setglobal("fetch")
static func resolve_fetch_url(url: String) -> String:
# If URL is already absolute, return as-is
if url.begins_with("http://") or url.begins_with("https://") or url.begins_with("gurt://"):
return url
# Get current domain from main scene
var main_node = Engine.get_main_loop().current_scene
var current_domain = ""
if main_node and main_node.has_method("get_current_url"):
current_domain = main_node.get_current_url()
# If no current domain, default to gurt:// protocol for relative URLs
if current_domain.is_empty():
if url.begins_with("/"):
return "gurt://" + url.substr(1)
else:
return "gurt://" + url
# Use URLUtils to resolve relative URL against current domain
return URLUtils.resolve_url(current_domain, url)
static func _lua_fetch_handler(vm: LuauVM) -> int:
var url: String = vm.luaL_checkstring(1)
var options: Dictionary = {}
@@ -12,6 +33,9 @@ static func _lua_fetch_handler(vm: LuauVM) -> int:
if vm.lua_gettop() >= 2 and vm.lua_istable(2):
options = vm.lua_todictionary(2)
# Resolve relative URLs and default to gurt:// protocol
url = resolve_fetch_url(url)
# Default options
var method = options.get("method", "GET").to_upper()
var headers = options.get("headers", {})

View File

@@ -45,11 +45,16 @@ static func resolve_url(base_url: String, relative_url: String) -> String:
# Relative path
final_path_parts = current_path_parts.duplicate()
if final_path_parts.size() > 0:
var last_part = final_path_parts[-1]
if "." in last_part and not last_part.ends_with("/"):
final_path_parts.resize(final_path_parts.size() - 1)
var href_parts = relative_url.split("/")
for part in href_parts:
if part == "..":
if final_path_parts.size() > 0:
final_path_parts.pop_back()
final_path_parts.resize(final_path_parts.size() - 1)
elif part == "." or part == "":
continue
else:

View File

@@ -86,6 +86,8 @@ func handle_link_click(meta: Variant) -> void:
func _on_search_submitted(url: String) -> void:
print("Search submitted: ", url)
search_bar.release_focus()
if GurtProtocol.is_gurt_domain(url):
print("Processing as GURT domain")

View File

@@ -105,6 +105,9 @@ impl GurtClient {
let mut temp_buffer = [0u8; 8192];
let start_time = std::time::Instant::now();
let mut headers_parsed = false;
let mut expected_body_length: Option<usize> = None;
let mut headers_end_pos: Option<usize> = None;
loop {
if start_time.elapsed() > self.config.request_timeout {
@@ -118,12 +121,38 @@ impl GurtClient {
buffer.extend_from_slice(&temp_buffer[..bytes_read]);
// Check for complete message without converting to string
// Check for complete message
let body_separator = BODY_SEPARATOR.as_bytes();
let has_complete_response = buffer.windows(body_separator.len()).any(|w| w == body_separator) ||
(buffer.starts_with(b"{") && buffer.ends_with(b"}"));
if has_complete_response {
if !headers_parsed {
if let Some(pos) = buffer.windows(body_separator.len()).position(|w| w == body_separator) {
headers_end_pos = Some(pos + body_separator.len());
headers_parsed = true;
// Parse headers to get Content-Length
let headers_section = &buffer[..pos];
if let Ok(headers_str) = std::str::from_utf8(headers_section) {
for line in headers_str.lines() {
if line.to_lowercase().starts_with("content-length:") {
if let Some(length_str) = line.split(':').nth(1) {
if let Ok(length) = length_str.trim().parse::<usize>() {
expected_body_length = Some(length);
}
}
break;
}
}
}
}
}
if let (Some(headers_end), Some(expected_length)) = (headers_end_pos, expected_body_length) {
let current_body_length = buffer.len() - headers_end;
if current_body_length >= expected_length {
return Ok(buffer);
}
} else if headers_parsed && expected_body_length.is_none() {
// No Content-Length header, return what we have after headers
return Ok(buffer);
}
}

View File

@@ -25,21 +25,21 @@
-- Insert Before
gurt.select("#btn-insert-before"):on("click", function()
local newDiv = gurt.create("div", { class = "test-item highlight", text = "Inserted Before Child 2" })
local newDiv = gurt.create("div", { style = "test-item highlight", text = "Inserted Before Child 2" })
parent:insertBefore(newDiv, child2)
log_msg("Inserted before child2: " .. newDiv._element_id)
end)
-- Insert After
gurt.select("#btn-insert-after"):on("click", function()
local newDiv = gurt.create("div", { class = "test-item highlight", text = "Inserted After Child 2" })
local newDiv = gurt.create("div", { style = "test-item highlight", text = "Inserted After Child 2" })
parent:insertAfter(newDiv, child2)
log_msg("Inserted after child2: " .. newDiv._element_id)
end)
-- Replace
gurt.select("#btn-replace"):on("click", function()
local newDiv = gurt.create("div", { class = "test-item highlight", text = "Replacement for Child 2" })
local newDiv = gurt.create("div", { style = "test-item highlight", text = "Replacement for Child 2" })
parent:replace(newDiv, child2)
log_msg("Replaced child2 with: " .. newDiv._element_id)
end)