Settings
AWS Route53 Settings
Configure AWS credentials and hosted zones for PTR record management.
Enter the hosted zone IDs for your forward DNS zones (A records)
User Management
Manage admin and staff users who can access this application. Users are authenticated via Cloudflare Access.
| Email |
Display Name |
Role |
Status |
Created |
Actions |
|
|
View all changes made to geofeed entries including creates, updates, and deletes.
Client Shortnames
Client shortnames currently in use across geofeed entries.
Company Logos
Manage logo images for client shortnames. Logos will appear in the entries table as 1:1 square icons.
Branding
Customize the appearance of the application with your company branding.
License Status
View and manage your ISP IP Manager license.
Usage Statistics
Current usage against your license limits.
Available Plans
Compare features across different license tiers.
Trial
14 days free
- ✓ 100 entries
- ✓ 2 users
- ✓ Basic CRUD
- ✓ CSV export
Basic
Small ISPs
- ✓ 500 entries
- ✓ 5 users
- ✓ Webhooks
- ✓ Audit log
Professional
Growing ISPs
- ✓ 2,500 entries
- ✓ 15 users
- ✓ IP enrichment
- ✓ Whitelabel
- ✓ PTR records
Enterprise
Large ISPs
- ✓ Unlimited entries
- ✓ Unlimited users
- ✓ API access
- ✓ Priority support
Contact info@purplecomputing.com for pricing and license keys.
Import Geofeed Data
Import geofeed entries from a CSV file or a remote URL.
Error Logs
Danger Zone
Irreversible actions. Proceed with caution.
// Initialize settings page
document.addEventListener("DOMContentLoaded", function() {
const currentTab = "' . $currentTab . '";
// Load data for the current tab
switch(currentTab) {
case "integrations":
loadAwsSettings();
loadIpRegistrySettings();
loadWebhookSettings();
loadWebhookQueueStatus();
break;
case "users":
loadUsers();
break;
case "audit":
loadAuditLog();
break;
case "advanced":
loadShortnamesGrid();
loadShortnames();
loadLogosGrid();
break;
case "whitelabel":
loadWhitelabelSettings();
break;
case "license":
loadLicenseStatus();
loadLicenseUsage();
break;
case "developer":
loadSystemInfo();
loadErrorLogs();
break;
}
});
// User Management Functions
async function loadUsers() {
try {
const response = await fetch("api.php?action=admin_users_list", {
headers: { "X-CSRF-Token": CSRF_TOKEN }
});
const data = await response.json();
if (!data.success) {
throw new Error(data.error || "Failed to load users");
}
const tbody = document.getElementById("usersTableBody");
if (data.users.length === 0) {
tbody.innerHTML = \'| No users found. Add a user above. |
\';
return;
}
tbody.innerHTML = data.users.map(user => `
| ${escapeHtml(user.email)} |
${user.display_name ? escapeHtml(user.display_name) : \'-\'} |
${user.role === \'admin\' ? \'Admin\' : \'Staff\'}
|
${user.active == 1 ? \'Active\' : \'Inactive\'}
|
${formatDate(user.created_at)} |
|
`).join(\'\');
} catch (error) {
console.error("Error loading users:", error);
showNotification("Failed to load users: " + error.message, "error");
}
}
async function addUser() {
const email = document.getElementById("newUserEmail").value.trim();
const displayName = document.getElementById("newUserDisplayName").value.trim();
const role = document.getElementById("newUserRole").value;
if (!email) {
showNotification("Please enter an email address", "error");
return;
}
try {
const response = await fetch("api.php?action=admin_user_save", {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-CSRF-Token": CSRF_TOKEN
},
body: JSON.stringify({
email: email,
display_name: displayName || null,
role: role
})
});
const data = await response.json();
if (!data.success) {
throw new Error(data.error || "Failed to add user");
}
showNotification("User added successfully", "success");
document.getElementById("newUserEmail").value = "";
document.getElementById("newUserDisplayName").value = "";
document.getElementById("newUserRole").value = "staff";
loadUsers();
} catch (error) {
console.error("Error adding user:", error);
showNotification("Failed to add user: " + error.message, "error");
}
}
async function toggleUser(userId) {
try {
const response = await fetch("api.php?action=admin_user_toggle", {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-CSRF-Token": CSRF_TOKEN
},
body: JSON.stringify({ id: userId })
});
const data = await response.json();
if (!data.success) {
throw new Error(data.error || "Failed to toggle user status");
}
showNotification("User status updated", "success");
loadUsers();
} catch (error) {
console.error("Error toggling user:", error);
showNotification("Failed to toggle user: " + error.message, "error");
}
}
async function deleteUser(userId, email) {
if (!confirm("Are you sure you want to delete the user \"" + email + "\"? This action cannot be undone.")) {
return;
}
try {
const response = await fetch("api.php?action=admin_user_delete", {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-CSRF-Token": CSRF_TOKEN
},
body: JSON.stringify({ id: userId })
});
const data = await response.json();
if (!data.success) {
throw new Error(data.error || "Failed to delete user");
}
showNotification("User deleted successfully", "success");
loadUsers();
} catch (error) {
console.error("Error deleting user:", error);
showNotification("Failed to delete user: " + error.message, "error");
}
}
function escapeHtml(text) {
if (!text) return "";
const div = document.createElement("div");
div.textContent = text;
return div.innerHTML;
}
function formatDate(dateStr) {
if (!dateStr) return "-";
const date = new Date(dateStr);
return date.toLocaleDateString("en-GB", {
day: "2-digit",
month: "short",
year: "numeric",
hour: "2-digit",
minute: "2-digit"
});
}
// License Management Functions
async function loadLicenseStatus() {
try {
const response = await fetch("api.php?action=license_status", {
headers: { "X-CSRF-Token": CSRF_TOKEN }
});
const data = await response.json();
if (!data.success) {
throw new Error(data.error || "Failed to load license status");
}
const container = document.getElementById("licenseStatusContainer");
const license = data.license;
let statusBadge = "";
let statusClass = "";
switch (license.status) {
case "active":
statusBadge = "Active";
statusClass = "badge-success";
break;
case "expired":
statusBadge = "Expired";
statusClass = "badge-error";
break;
case "inactive":
statusBadge = "Inactive";
statusClass = "badge-warning";
break;
case "unlicensed":
statusBadge = "Unlicensed";
statusClass = "badge-gray";
break;
default:
statusBadge = license.status;
statusClass = "badge-gray";
}
let expiryInfo = "";
if (license.expires_at) {
const expiryDate = new Date(license.expires_at);
expiryInfo = `
Expires: ${expiryDate.toLocaleDateString("en-GB", { day: "2-digit", month: "short", year: "numeric" })}
${license.days_remaining !== null ? `(${license.days_remaining} days remaining)` : ""}
`;
}
let licenseDetails = "";
if (license.license && license.license.licensee_name) {
licenseDetails = `
Licensee: ${escapeHtml(license.license.licensee_name)}
Email: ${escapeHtml(license.license.licensee_email)}
License Key: ${maskLicenseKey(license.license.license_key)}
`;
// Show deactivate button
document.getElementById("deactivateLicenseBtn").style.display = "inline-flex";
} else {
document.getElementById("deactivateLicenseBtn").style.display = "none";
}
container.innerHTML = `
${statusBadge}
${license.limits?.name || "Unknown"} License
${license.message}
${expiryInfo}
${licenseDetails}
`;
} catch (error) {
console.error("Error loading license status:", error);
document.getElementById("licenseStatusContainer").innerHTML = `
Failed to load license status: ${escapeHtml(error.message)}
`;
}
}
function maskLicenseKey(key) {
if (!key || key.length < 10) return key;
return key.substring(0, 10) + "..." + key.substring(key.length - 4);
}
async function loadLicenseUsage() {
try {
const response = await fetch("api.php?action=license_usage", {
headers: { "X-CSRF-Token": CSRF_TOKEN }
});
const data = await response.json();
if (!data.success) {
throw new Error(data.error || "Failed to load license usage");
}
const container = document.getElementById("licenseUsageContainer");
const usage = data.usage;
const entriesBar = renderUsageBar(usage.entries);
const usersBar = renderUsageBar(usage.users);
container.innerHTML = `
Geofeed Entries
${usage.entries.current} / ${usage.entries.unlimited ? "Unlimited" : usage.entries.max}
${entriesBar}
Users
${usage.users.current} / ${usage.users.unlimited ? "Unlimited" : usage.users.max}
${usersBar}
`;
} catch (error) {
console.error("Error loading license usage:", error);
document.getElementById("licenseUsageContainer").innerHTML = `
Failed to load usage data: ${escapeHtml(error.message)}
`;
}
}
function renderUsageBar(usage) {
if (usage.unlimited) {
return ``;
}
const percentage = Math.min(100, usage.percentage);
let barColor = "var(--purple-primary)";
if (percentage >= 90) {
barColor = "var(--error)";
} else if (percentage >= 75) {
barColor = "var(--warning)";
}
return `
`;
}
async function activateLicense() {
const licenseKey = document.getElementById("licenseKey").value.trim();
const licenseeName = document.getElementById("licenseeName").value.trim();
const licenseeEmail = document.getElementById("licenseeEmail").value.trim();
if (!licenseKey) {
showNotification("Please enter a license key", "error");
return;
}
if (!licenseeName) {
showNotification("Please enter the licensee name", "error");
return;
}
if (!licenseeEmail) {
showNotification("Please enter the licensee email", "error");
return;
}
try {
const response = await fetch("api.php?action=license_activate", {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-CSRF-Token": CSRF_TOKEN
},
body: JSON.stringify({
license_key: licenseKey,
licensee_name: licenseeName,
licensee_email: licenseeEmail
})
});
const data = await response.json();
if (!data.success) {
throw new Error(data.error || "Failed to activate license");
}
showNotification(data.message || "License activated successfully", "success");
// Clear the form
document.getElementById("licenseKey").value = "";
document.getElementById("licenseeName").value = "";
document.getElementById("licenseeEmail").value = "";
// Reload license status
loadLicenseStatus();
loadLicenseUsage();
} catch (error) {
console.error("Error activating license:", error);
showNotification("Failed to activate license: " + error.message, "error");
}
}
async function deactivateLicense() {
if (!confirm("Are you sure you want to deactivate your license? This will revert your account to trial mode.")) {
return;
}
try {
const response = await fetch("api.php?action=license_deactivate", {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-CSRF-Token": CSRF_TOKEN
}
});
const data = await response.json();
if (!data.success) {
throw new Error(data.error || "Failed to deactivate license");
}
showNotification(data.message || "License deactivated", "success");
// Reload license status
loadLicenseStatus();
loadLicenseUsage();
} catch (error) {
console.error("Error deactivating license:", error);
showNotification("Failed to deactivate license: " + error.message, "error");
}
}
';
// Include footer
require_once __DIR__ . '/includes/footer.php';
?>