Add admin editing for WHOIS and RDAP servers in TLD registry

Introduces controller actions and UI modals for admins to edit WHOIS and RDAP servers for TLDs. Updates redirect logic to return users to the correct page after actions. Adds new POST routes for updating WHOIS and RDAP servers. Improves clipboard copy feedback in the debug WHOIS view.
This commit is contained in:
Hosteroid
2025-11-21 14:49:41 +02:00
parent e2be1ef33c
commit a7321888c0
4 changed files with 353 additions and 44 deletions

View File

@@ -516,7 +516,15 @@ class TldRegistryController extends Controller
$status = $tld['is_active'] ? 'disabled' : 'enabled';
$_SESSION['success'] = "TLD {$tld['tld']} has been {$status}";
$this->redirect('/tld-registry');
// Redirect back to the same page (either view page or index page)
$referer = $_SERVER['HTTP_REFERER'] ?? '';
if (strpos($referer, '/tld-registry/' . $id) !== false) {
// Came from view page, go back to view page
$this->redirect('/tld-registry/' . $id);
} else {
// Came from index page or elsewhere, go to index
$this->redirect('/tld-registry');
}
}
/**
@@ -567,7 +575,15 @@ class TldRegistryController extends Controller
$_SESSION['error'] = 'Failed to refresh TLD data: ' . $e->getMessage();
}
$this->redirect('/tld-registry');
// Redirect back to the same page (either view page or index page)
$referer = $_SERVER['HTTP_REFERER'] ?? '';
if (strpos($referer, '/tld-registry/' . $id) !== false) {
// Came from view page, go back to view page
$this->redirect('/tld-registry/' . $id);
} else {
// Came from index page or elsewhere, go to index
$this->redirect('/tld-registry');
}
}
/**
@@ -667,4 +683,131 @@ class TldRegistryController extends Controller
}
return null;
}
/**
* Update WHOIS server for a TLD
*/
public function updateWhoisServer($params = [])
{
Auth::requireAdmin();
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
$this->redirect('/tld-registry');
return;
}
// CSRF Protection
$this->verifyCsrf('/tld-registry');
$id = $params['id'] ?? 0;
$tld = $this->tldModel->find($id);
if (!$tld) {
$_SESSION['error'] = 'TLD not found';
$this->redirect('/tld-registry');
return;
}
$whoisServer = trim($_POST['whois_server'] ?? '');
// Validate WHOIS server format (basic validation)
if (!empty($whoisServer) && !preg_match('/^[a-z0-9.-]+\.[a-z]{2,}$/i', $whoisServer)) {
$_SESSION['error'] = 'Invalid WHOIS server format';
$this->redirect('/tld-registry/' . $id);
return;
}
try {
$this->tldModel->update($id, [
'whois_server' => !empty($whoisServer) ? $whoisServer : null,
'updated_at' => date('Y-m-d H:i:s')
]);
$this->logger->info('WHOIS server updated', [
'tld_id' => $id,
'tld' => $tld['tld'],
'whois_server' => $whoisServer,
'user_id' => $_SESSION['user_id'] ?? 'unknown'
]);
$_SESSION['success'] = "WHOIS server updated successfully for {$tld['tld']}";
} catch (\Exception $e) {
$_SESSION['error'] = 'Failed to update WHOIS server: ' . $e->getMessage();
$this->logger->error('Failed to update WHOIS server', [
'tld_id' => $id,
'error' => $e->getMessage()
]);
}
$this->redirect('/tld-registry/' . $id);
}
/**
* Update RDAP servers for a TLD
*/
public function updateRdapServers($params = [])
{
Auth::requireAdmin();
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
$this->redirect('/tld-registry');
return;
}
// CSRF Protection
$this->verifyCsrf('/tld-registry');
$id = $params['id'] ?? 0;
$tld = $this->tldModel->find($id);
if (!$tld) {
$_SESSION['error'] = 'TLD not found';
$this->redirect('/tld-registry');
return;
}
$rdapServersInput = trim($_POST['rdap_servers'] ?? '');
$rdapServers = [];
// Parse RDAP servers (comma or newline separated)
if (!empty($rdapServersInput)) {
$servers = preg_split('/[,\n\r]+/', $rdapServersInput);
foreach ($servers as $server) {
$server = trim($server);
if (!empty($server)) {
// Basic URL validation
if (filter_var($server, FILTER_VALIDATE_URL) ||
preg_match('/^https?:\/\/[a-z0-9.-]+\.[a-z]{2,}(\/.*)?$/i', $server)) {
// Ensure URL ends with / if not already
$server = rtrim($server, '/') . '/';
$rdapServers[] = $server;
}
}
}
}
try {
$this->tldModel->update($id, [
'rdap_servers' => !empty($rdapServers) ? json_encode($rdapServers) : null,
'updated_at' => date('Y-m-d H:i:s')
]);
$this->logger->info('RDAP servers updated', [
'tld_id' => $id,
'tld' => $tld['tld'],
'rdap_servers_count' => count($rdapServers),
'user_id' => $_SESSION['user_id'] ?? 'unknown'
]);
$_SESSION['success'] = "RDAP servers updated successfully for {$tld['tld']} (" . count($rdapServers) . " server(s))";
} catch (\Exception $e) {
$_SESSION['error'] = 'Failed to update RDAP servers: ' . $e->getMessage();
$this->logger->error('Failed to update RDAP servers', [
'tld_id' => $id,
'error' => $e->getMessage()
]);
}
$this->redirect('/tld-registry/' . $id);
}
}

View File

@@ -59,7 +59,7 @@ ob_start();
<i class="fas fa-arrow-left mr-2"></i>
Check Another Domain
</a>
<button onclick="copyDebugReport()" class="inline-flex items-center px-4 py-2 bg-blue-600 text-white text-sm rounded-lg hover:bg-blue-700 transition-colors font-medium">
<button onclick="copyDebugReport(this)" class="inline-flex items-center px-4 py-2 bg-blue-600 text-white text-sm rounded-lg hover:bg-blue-700 transition-colors font-medium">
<i class="fas fa-copy mr-2"></i>
Copy Debug Report
</button>
@@ -196,7 +196,7 @@ ob_start();
</script>
<script>
function copyDebugReport() {
function copyDebugReport(button) {
const data = JSON.parse(document.getElementById('debug-data').textContent);
let report = `=== WHOIS DEBUG REPORT ===\n\n`;
@@ -232,27 +232,27 @@ ob_start();
report += `\n\n=== END OF REPORT ===`;
// Copy to clipboard with fallback
copyToClipboard(report);
copyToClipboard(report, button);
}
// Robust clipboard copy function with fallback
function copyToClipboard(text) {
function copyToClipboard(text, button) {
// Try modern clipboard API first
if (navigator.clipboard && window.isSecureContext) {
navigator.clipboard.writeText(text).then(() => {
showCopySuccess();
showCopySuccess(button);
}).catch(err => {
console.error('Modern clipboard API failed:', err);
// Fallback to legacy method
fallbackCopyTextToClipboard(text);
fallbackCopyTextToClipboard(text, button);
});
} else {
// Use fallback for non-HTTPS or older browsers
fallbackCopyTextToClipboard(text);
fallbackCopyTextToClipboard(text, button);
}
}
function fallbackCopyTextToClipboard(text) {
function fallbackCopyTextToClipboard(text, button) {
// Create a temporary textarea
const textArea = document.createElement('textarea');
textArea.value = text;
@@ -276,35 +276,34 @@ ob_start();
try {
const successful = document.execCommand('copy');
if (successful) {
showCopySuccess();
showCopySuccess(button);
} else {
showCopyError();
showCopyError(button);
}
} catch (err) {
console.error('Fallback copy failed:', err);
showCopyError();
showCopyError(button);
}
document.body.removeChild(textArea);
}
function showCopySuccess() {
const btn = event.target.closest('button');
if (!btn) return;
function showCopySuccess(button) {
if (!button) return;
const originalHTML = btn.innerHTML;
btn.innerHTML = '<i class="fas fa-check mr-2"></i>Copied!';
btn.classList.remove('bg-blue-600', 'hover:bg-blue-700');
btn.classList.add('bg-green-600', 'hover:bg-green-700');
const originalHTML = button.innerHTML;
button.innerHTML = '<i class="fas fa-check mr-2"></i>Copied!';
button.classList.remove('bg-blue-600', 'hover:bg-blue-700');
button.classList.add('bg-green-600', 'hover:bg-green-700');
setTimeout(() => {
btn.innerHTML = originalHTML;
btn.classList.remove('bg-green-600', 'hover:bg-green-700');
btn.classList.add('bg-blue-600', 'hover:bg-blue-700');
button.innerHTML = originalHTML;
button.classList.remove('bg-green-600', 'hover:bg-green-700');
button.classList.add('bg-blue-600', 'hover:bg-blue-700');
}, 2000);
}
function showCopyError() {
function showCopyError(button) {
alert('Failed to copy to clipboard.\n\nYour browser may not support this feature, or the site needs HTTPS.\n\nPlease manually select and copy the text from the Raw WHOIS Response section below.');
}
</script>

View File

@@ -82,53 +82,79 @@ ob_start();
</div>
<!-- RDAP Servers -->
<?php if ($tld['rdap_servers']): ?>
<?php
$rdapServers = json_decode($tld['rdap_servers'], true);
if (is_array($rdapServers) && !empty($rdapServers)):
?>
<div class="bg-white rounded-lg border border-gray-200 overflow-hidden">
<div class="px-4 py-2 border-b border-gray-200 bg-gray-50">
<div class="px-4 py-2 border-b border-gray-200 bg-gray-50 flex items-center justify-between">
<h3 class="text-xs font-semibold text-gray-700 uppercase tracking-wider flex items-center">
<i class="fas fa-database text-gray-400 mr-2" style="font-size: 10px;"></i>
RDAP Servers (<?= count($rdapServers) ?>)
RDAP Servers
<?php if ($tld['rdap_servers']): ?>
<?php
$rdapServers = json_decode($tld['rdap_servers'], true);
if (is_array($rdapServers) && !empty($rdapServers)):
?>
(<?= count($rdapServers) ?>)
<?php endif; ?>
<?php endif; ?>
</h3>
<?php if (isset($_SESSION['role']) && $_SESSION['role'] === 'admin'): ?>
<button onclick="openEditRdapModal()" class="inline-flex items-center px-2 py-1 bg-blue-600 text-white text-xs rounded hover:bg-blue-700 transition-colors font-medium">
<i class="fas fa-edit mr-1" style="font-size: 9px;"></i>
Edit
</button>
<?php endif; ?>
</div>
<div class="p-4">
<div class="space-y-1.5">
<?php foreach ($rdapServers as $index => $server): ?>
<div class="flex items-center p-2 bg-gray-50 rounded hover:bg-gray-100 transition-colors">
<div class="w-6 h-6 bg-indigo-500 rounded flex items-center justify-center text-white font-bold text-xs mr-2">
<?= $index + 1 ?>
<?php if ($tld['rdap_servers']): ?>
<?php
$rdapServers = json_decode($tld['rdap_servers'], true);
if (is_array($rdapServers) && !empty($rdapServers)):
?>
<div class="space-y-1.5">
<?php foreach ($rdapServers as $index => $server): ?>
<div class="flex items-center p-2 bg-gray-50 rounded hover:bg-gray-100 transition-colors">
<div class="w-6 h-6 bg-indigo-500 rounded flex items-center justify-center text-white font-bold text-xs mr-2">
<?= $index + 1 ?>
</div>
<p class="font-mono text-xs text-gray-800"><?= htmlspecialchars($server) ?></p>
</div>
<p class="font-mono text-xs text-gray-800"><?= htmlspecialchars($server) ?></p>
<?php endforeach; ?>
</div>
<?php endforeach; ?>
</div>
<?php else: ?>
<p class="text-xs text-gray-400 italic">No RDAP servers configured</p>
<?php endif; ?>
<?php else: ?>
<p class="text-xs text-gray-400 italic">No RDAP servers configured</p>
<?php endif; ?>
</div>
</div>
<?php endif; ?>
<?php endif; ?>
<!-- WHOIS Server -->
<?php if ($tld['whois_server']): ?>
<div class="bg-white rounded-lg border border-gray-200 overflow-hidden">
<div class="px-4 py-2 border-b border-gray-200 bg-gray-50">
<div class="px-4 py-2 border-b border-gray-200 bg-gray-50 flex items-center justify-between">
<h3 class="text-xs font-semibold text-gray-700 uppercase tracking-wider flex items-center">
<i class="fas fa-server text-gray-400 mr-2" style="font-size: 10px;"></i>
WHOIS Server
</h3>
<?php if (isset($_SESSION['role']) && $_SESSION['role'] === 'admin'): ?>
<button onclick="openEditWhoisModal()" class="inline-flex items-center px-2 py-1 bg-blue-600 text-white text-xs rounded hover:bg-blue-700 transition-colors font-medium">
<i class="fas fa-edit mr-1" style="font-size: 9px;"></i>
Edit
</button>
<?php endif; ?>
</div>
<div class="p-4">
<?php if ($tld['whois_server']): ?>
<div class="flex items-center p-2 bg-gray-50 rounded">
<div class="w-6 h-6 bg-orange-500 rounded flex items-center justify-center text-white font-bold text-xs mr-2">
<i class="fas fa-server"></i>
</div>
<p class="font-mono text-xs text-gray-800"><?= htmlspecialchars($tld['whois_server']) ?></p>
</div>
<?php else: ?>
<p class="text-xs text-gray-400 italic">No WHOIS server configured</p>
<?php endif; ?>
</div>
</div>
<?php endif; ?>
</div>
@@ -247,6 +273,104 @@ ob_start();
</div>
<!-- Edit WHOIS Server Modal -->
<?php if (isset($_SESSION['role']) && $_SESSION['role'] === 'admin'): ?>
<div id="editWhoisModal" class="hidden fixed inset-0 bg-black bg-opacity-50 z-50 flex items-center justify-center p-4">
<div class="bg-white rounded-lg shadow-xl max-w-md w-full max-h-[90vh] overflow-y-auto">
<div class="px-6 py-4 border-b border-gray-200 flex items-center justify-between">
<h3 class="text-lg font-semibold text-gray-900 flex items-center">
<i class="fas fa-server text-orange-600 mr-2"></i>
Edit WHOIS Server
</h3>
<button onclick="closeEditWhoisModal()" class="text-gray-400 hover:text-gray-600 transition-colors">
<i class="fas fa-times"></i>
</button>
</div>
<form method="POST" action="/tld-registry/<?= $tld['id'] ?>/update-whois-server" class="p-6">
<?= csrf_field() ?>
<div class="mb-4">
<label class="block text-sm font-medium text-gray-700 mb-2">
WHOIS Server
</label>
<input type="text"
name="whois_server"
id="whois_server_input"
value="<?= htmlspecialchars($tld['whois_server'] ?? '') ?>"
placeholder="whois.example.com"
class="w-full px-3 py-2.5 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 text-sm">
<p class="mt-1.5 text-xs text-gray-500">
Enter the WHOIS server hostname (e.g., whois.example.com). Leave empty to remove.
</p>
</div>
<div class="flex gap-3">
<button type="submit"
class="flex-1 inline-flex items-center justify-center px-4 py-2.5 bg-blue-600 hover:bg-blue-700 text-white rounded-lg font-medium transition-colors text-sm">
<i class="fas fa-save mr-2"></i>
Save Changes
</button>
<button type="button"
onclick="closeEditWhoisModal()"
class="flex-1 inline-flex items-center justify-center px-4 py-2.5 border border-gray-300 text-gray-700 rounded-lg font-medium hover:bg-gray-50 transition-colors text-sm">
<i class="fas fa-times mr-2"></i>
Cancel
</button>
</div>
</form>
</div>
</div>
<!-- Edit RDAP Servers Modal -->
<div id="editRdapModal" class="hidden fixed inset-0 bg-black bg-opacity-50 z-50 flex items-center justify-center p-4">
<div class="bg-white rounded-lg shadow-xl max-w-md w-full max-h-[90vh] overflow-y-auto">
<div class="px-6 py-4 border-b border-gray-200 flex items-center justify-between">
<h3 class="text-lg font-semibold text-gray-900 flex items-center">
<i class="fas fa-database text-indigo-600 mr-2"></i>
Edit RDAP Servers
</h3>
<button onclick="closeEditRdapModal()" class="text-gray-400 hover:text-gray-600 transition-colors">
<i class="fas fa-times"></i>
</button>
</div>
<form method="POST" action="/tld-registry/<?= $tld['id'] ?>/update-rdap-servers" class="p-6">
<?= csrf_field() ?>
<div class="mb-4">
<label class="block text-sm font-medium text-gray-700 mb-2">
RDAP Servers
</label>
<textarea name="rdap_servers"
id="rdap_servers_input"
rows="6"
placeholder="https://rdap.example.com/&#10;https://rdap2.example.com/"
class="w-full px-3 py-2.5 border border-gray-300 rounded-lg focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500 text-sm font-mono"><?php
if ($tld['rdap_servers']):
$rdapServers = json_decode($tld['rdap_servers'], true);
if (is_array($rdapServers) && !empty($rdapServers)):
echo htmlspecialchars(implode("\n", $rdapServers));
endif;
endif;
?></textarea>
<p class="mt-1.5 text-xs text-gray-500">
Enter RDAP server URLs (one per line or comma-separated). Must start with http:// or https://. Leave empty to remove all servers.
</p>
</div>
<div class="flex gap-3">
<button type="submit"
class="flex-1 inline-flex items-center justify-center px-4 py-2.5 bg-indigo-600 hover:bg-indigo-700 text-white rounded-lg font-medium transition-colors text-sm">
<i class="fas fa-save mr-2"></i>
Save Changes
</button>
<button type="button"
onclick="closeEditRdapModal()"
class="flex-1 inline-flex items-center justify-center px-4 py-2.5 border border-gray-300 text-gray-700 rounded-lg font-medium hover:bg-gray-50 transition-colors text-sm">
<i class="fas fa-times mr-2"></i>
Cancel
</button>
</div>
</form>
</div>
</div>
<?php endif; ?>
<script>
function toggleRawData() {
const dataDiv = document.getElementById('raw-data');
@@ -254,6 +378,47 @@ function toggleRawData() {
dataDiv.classList.toggle('hidden');
chevron.classList.toggle('rotate-180');
}
<?php if (isset($_SESSION['role']) && $_SESSION['role'] === 'admin'): ?>
function openEditWhoisModal() {
document.getElementById('editWhoisModal').classList.remove('hidden');
document.getElementById('whois_server_input').focus();
}
function closeEditWhoisModal() {
document.getElementById('editWhoisModal').classList.add('hidden');
}
function openEditRdapModal() {
document.getElementById('editRdapModal').classList.remove('hidden');
document.getElementById('rdap_servers_input').focus();
}
function closeEditRdapModal() {
document.getElementById('editRdapModal').classList.add('hidden');
}
// Close modals on Escape key
document.addEventListener('keydown', function(e) {
if (e.key === 'Escape') {
closeEditWhoisModal();
closeEditRdapModal();
}
});
// Close modals when clicking outside
document.getElementById('editWhoisModal')?.addEventListener('click', function(e) {
if (e.target === this) {
closeEditWhoisModal();
}
});
document.getElementById('editRdapModal')?.addEventListener('click', function(e) {
if (e.target === this) {
closeEditRdapModal();
}
});
<?php endif; ?>
</script>
<?php