pinkycollie's picture
🐳 20/02 - 14:09 - built in full built in NODE server with all necessary tools that can be fulfilled by DENO at large, and easy backoffice for api configuration and esnrue aall are update to modern,
1902db7 verified
Raw
History Blame Contribute Delete
29.6 kB
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>DeafFounders Hub | Admin Backoffice</title>
<script src="https://cdn.tailwindcss.com"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<style>
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap');
body { font-family: 'Inter', sans-serif; }
.tab-active { border-bottom: 2px solid #4f46e5; color: #4f46e5; }
.tab-inactive { color: #6b7280; }
.fade-in { animation: fadeIn 0.3s ease-in; }
@keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } }
</style>
</head>
<body class="bg-gray-100">
<!-- Header -->
<header class="bg-white shadow-sm border-b border-gray-200">
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div class="flex justify-between h-16">
<div class="flex items-center">
<i class="fas fa-cog text-indigo-600 text-2xl mr-3"></i>
<h1 class="text-2xl font-bold text-gray-900">Backoffice</h1>
<span class="ml-3 px-3 py-1 bg-green-100 text-green-800 text-sm rounded-full">System Online</span>
</div>
<div class="flex items-center space-x-4">
<a href="https://pinkycollie.github.io/Business-Magician/" target="_blank"
class="text-indigo-600 hover:text-indigo-800 text-sm font-medium">
Business Magician <i class="fas fa-external-link-alt ml-1"></i>
</a>
<a href="/" class="text-gray-600 hover:text-gray-900 text-sm font-medium">
<i class="fas fa-home mr-1"></i> Home
</a>
<button onclick="logout()" class="text-gray-600 hover:text-red-600 text-sm font-medium">
<i class="fas fa-sign-out-alt mr-1"></i> Logout
</button>
</div>
</div>
</div>
</header>
<!-- Navigation Tabs -->
<nav class="bg-white border-b border-gray-200">
<div class="max-w-7xl mx-auto px-4">
<div class="flex space-x-8">
<button onclick="switchTab('dashboard')" id="tab-dashboard" class="py-4 px-1 tab-active font-medium text-sm">
<i class="fas fa-chart-line mr-2"></i>Dashboard
</button>
<button onclick="switchTab('config')" id="tab-config" class="py-4 px-1 tab-inactive font-medium text-sm">
<i class="fas fa-cogs mr-2"></i>API Config
</button>
<button onclick="switchTab('redirects')" id="tab-redirects" class="py-4 px-1 tab-inactive font-medium text-sm">
<i class="fas fa-route mr-2"></i>Redirects
</button>
<button onclick="switchTab('scaffold')" id="tab-scaffold" class="py-4 px-1 tab-inactive font-medium text-sm">
<i class="fas fa-hammer mr-2"></i>Scaffold
</button>
<button onclick="switchTab('modules')" id="tab-modules" class="py-4 px-1 tab-inactive font-medium text-sm">
<i class="fas fa-cubes mr-2"></i>Modules
</button>
</div>
</div>
</nav>
<!-- Main Content -->
<main class="max-w-7xl mx-auto px-4 py-8">
<!-- Dashboard Tab -->
<div id="content-dashboard" class="fade-in">
<div class="grid grid-cols-1 md:grid-cols-3 gap-6 mb-8">
<div class="bg-white p-6 rounded-lg shadow">
<div class="flex items-center justify-between">
<div>
<p class="text-sm text-gray-600">Server Status</p>
<p class="text-2xl font-bold text-green-600">Running</p>
</div>
<i class="fas fa-server text-green-500 text-3xl"></i>
</div>
</div>
<div class="bg-white p-6 rounded-lg shadow">
<div class="flex items-center justify-between">
<div>
<p class="text-sm text-gray-600">Active Modules</p>
<p class="text-2xl font-bold text-indigo-600">4</p>
</div>
<i class="fas fa-cubes text-indigo-500 text-3xl"></i>
</div>
</div>
<div class="bg-white p-6 rounded-lg shadow">
<div class="flex items-center justify-between">
<div>
<p class="text-sm text-gray-600">Uptime</p>
<p class="text-2xl font-bold text-blue-600" id="uptime">0s</p>
</div>
<i class="fas fa-clock text-blue-500 text-3xl"></i>
</div>
</div>
</div>
<div class="grid grid-cols-1 lg:grid-cols-2 gap-8">
<div class="bg-white p-6 rounded-lg shadow">
<h3 class="text-lg font-semibold mb-4">System Information</h3>
<div id="system-info" class="space-y-2 text-sm">
<div class="flex justify-between border-b py-2">
<span class="text-gray-600">Platform</span>
<span class="font-medium" id="platform">-</span>
</div>
<div class="flex justify-between border-b py-2">
<span class="text-gray-600">Node Version</span>
<span class="font-medium" id="node-version">-</span>
</div>
<div class="flex justify-between border-b py-2">
<span class="text-gray-600">Environment</span>
<span class="font-medium" id="env">-</span>
</div>
<div class="flex justify-between border-b py-2">
<span class="text-gray-600">Port</span>
<span class="font-medium" id="port">-</span>
</div>
</div>
</div>
<div class="bg-white p-6 rounded-lg shadow">
<h3 class="text-lg font-semibold mb-4">Quick Actions</h3>
<div class="space-y-3">
<button onclick="switchTab('scaffold')" class="w-full text-left px-4 py-3 bg-gray-50 hover:bg-gray-100 rounded flex items-center transition">
<i class="fas fa-plus-circle text-indigo-600 mr-3"></i>
<div>
<p class="font-medium">Generate New Page</p>
<p class="text-sm text-gray-500">Create HTML page with auto-redirects</p>
</div>
</button>
<button onclick="switchTab('redirects')" class="w-full text-left px-4 py-3 bg-gray-50 hover:bg-gray-100 rounded flex items-center transition">
<i class="fas fa-link text-indigo-600 mr-3"></i>
<div>
<p class="font-medium">Add Redirect</p>
<p class="text-sm text-gray-500">Configure URL redirects to external sites</p>
</div>
</button>
<a href="https://pinkycollie.github.io/Business-Magician/" target="_blank"
class="w-full text-left px-4 py-3 bg-gray-50 hover:bg-gray-100 rounded flex items-center transition">
<i class="fas fa-magic text-indigo-600 mr-3"></i>
<div>
<p class="font-medium">Business Magician</p>
<p class="text-sm text-gray-500">Open external tool (opens new tab)</p>
</div>
</a>
</div>
</div>
</div>
</div>
<!-- Config Tab -->
<div id="content-config" class="hidden fade-in">
<div class="bg-white rounded-lg shadow p-6">
<h2 class="text-2xl font-bold mb-6">API Configuration</h2>
<form id="config-form" class="space-y-6">
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">Port</label>
<input type="number" id="config-port" value="3000"
class="w-full border border-gray-300 rounded px-3 py-2 focus:outline-none focus:ring-2 focus:ring-indigo-500">
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">Environment</label>
<select id="config-env" class="w-full border border-gray-300 rounded px-3 py-2 focus:outline-none focus:ring-2 focus:ring-indigo-500">
<option value="development">Development</option>
<option value="production">Production</option>
<option value="testing">Testing</option>
</select>
</div>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">CORS Origins</label>
<input type="text" id="config-cors" value="*"
class="w-full border border-gray-300 rounded px-3 py-2 focus:outline-none focus:ring-2 focus:ring-indigo-500">
<p class="text-sm text-gray-500 mt-1">Use * for all origins or comma-separated domains</p>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-4">Active Modules</label>
<div class="grid grid-cols-2 md:grid-cols-4 gap-4">
<label class="flex items-center space-x-2 cursor-pointer">
<input type="checkbox" checked class="rounded text-indigo-600 focus:ring-indigo-500">
<span>API</span>
</label>
<label class="flex items-center space-x-2 cursor-pointer">
<input type="checkbox" checked class="rounded text-indigo-600 focus:ring-indigo-500">
<span>Auth</span>
</label>
<label class="flex items-center space-x-2 cursor-pointer">
<input type="checkbox" checked class="rounded text-indigo-600 focus:ring-indigo-500">
<span>Admin</span>
</label>
<label class="flex items-center space-x-2 cursor-pointer">
<input type="checkbox" checked class="rounded text-indigo-600 focus:ring-indigo-500">
<span>Static</span>
</label>
</div>
</div>
<div class="flex space-x-4">
<button type="submit" class="bg-indigo-600 text-white px-6 py-2 rounded hover:bg-indigo-700 transition">
Save Configuration
</button>
<button type="button" onclick="resetConfig()" class="bg-gray-200 text-gray-700 px-6 py-2 rounded hover:bg-gray-300 transition">
Reset to Default
</button>
</div>
</form>
<div id="config-message" class="mt-4 p-4 bg-green-50 text-green-700 rounded hidden">
Configuration saved successfully!
</div>
</div>
</div>
<!-- Redirects Tab -->
<div id="content-redirects" class="hidden fade-in">
<div class="bg-white rounded-lg shadow p-6">
<h2 class="text-2xl font-bold mb-6">URL Redirects</h2>
<p class="text-gray-600 mb-6">Configure redirects to external resources. All redirects open in new tabs when clicked.</p>
<div class="mb-6">
<h3 class="text-lg font-semibold mb-3">Current Redirects</h3>
<div id="redirects-list" class="space-y-2">
<!-- Populated by JS -->
</div>
</div>
<form id="redirect-form" class="border-t pt-6">
<h3 class="text-lg font-semibold mb-4">Add New Redirect</h3>
<div class="grid grid-cols-1 md:grid-cols-3 gap-4">
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">From Path</label>
<input type="text" id="redirect-from" placeholder="/example" required
class="w-full border border-gray-300 rounded px-3 py-2 focus:outline-none focus:ring-2 focus:ring-indigo-500">
</div>
<div class="md:col-span-2">
<label class="block text-sm font-medium text-gray-700 mb-2">To URL (opens new tab)</label>
<input type="url" id="redirect-to" placeholder="https://example.com" required
class="w-full border border-gray-300 rounded px-3 py-2 focus:outline-none focus:ring-2 focus:ring-indigo-500">
</div>
</div>
<button type="submit" class="mt-4 bg-indigo-600 text-white px-6 py-2 rounded hover:bg-indigo-700 transition">
<i class="fas fa-plus mr-2"></i>Add Redirect
</button>
</form>
</div>
</div>
<!-- Scaffold Tab -->
<div id="content-scaffold" class="hidden fade-in">
<div class="bg-white rounded-lg shadow p-6">
<h2 class="text-2xl font-bold mb-6">Scaffold Generator</h2>
<p class="text-gray-600 mb-6">Generate new pages, routes, and components instantly.</p>
<form id="scaffold-form" class="space-y-6">
<div class="grid grid-cols-1 md:grid-cols-3 gap-6">
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">Type</label>
<select id="scaffold-type" class="w-full border border-gray-300 rounded px-3 py-2 focus:outline-none focus:ring-2 focus:ring-indigo-500" onchange="updateScaffoldPreview()">
<option value="page">HTML Page (New Tab Ready)</option>
<option value="route">API Route</option>
<option value="component">JS Component</option>
</select>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">Name</label>
<input type="text" id="scaffold-name" placeholder="MyPage" required
class="w-full border border-gray-300 rounded px-3 py-2 focus:outline-none focus:ring-2 focus:ring-indigo-500" onkeyup="updateScaffoldPreview()">
</div>
<div id="scaffold-link-container">
<label class="block text-sm font-medium text-gray-700 mb-2">External Link (for pages)</label>
<input type="url" id="scaffold-link" placeholder="https://example.com"
class="w-full border border-gray-300 rounded px-3 py-2 focus:outline-none focus:ring-2 focus:ring-indigo-500">
</div>
</div>
<div class="bg-gray-50 p-4 rounded">
<h4 class="text-sm font-semibold text-gray-700 mb-2">Preview:</h4>
<pre id="scaffold-preview" class="text-sm text-gray-600 overflow-x-auto">Select type and enter name to preview...</pre>
</div>
<button type="submit" class="bg-indigo-600 text-white px-6 py-2 rounded hover:bg-indigo-700 transition">
<i class="fas fa-bolt mr-2"></i>Generate
</button>
</form>
<div id="scaffold-result" class="mt-6 hidden">
<div class="bg-green-50 border border-green-200 rounded p-4">
<h4 class="text-green-800 font-semibold mb-2">Generated Successfully!</h4>
<p class="text-green-700 text-sm mb-2">Files created:</p>
<ul id="scaffold-files" class="list-disc list-inside text-green-700 text-sm"></ul>
<a id="scaffold-view-link" href="#" target="_blank" class="mt-3 inline-block text-green-800 underline">
View Page <i class="fas fa-external-link-alt ml-1"></i>
</a>
</div>
</div>
</div>
</div>
<!-- Modules Tab -->
<div id="content-modules" class="hidden fade-in">
<div class="bg-white rounded-lg shadow p-6">
<h2 class="text-2xl font-bold mb-6">Module Management</h2>
<div id="modules-list" class="grid grid-cols-1 md:grid-cols-2 gap-4">
<!-- Populated by JS -->
</div>
</div>
</div>
</main>
<script>
// State
let currentTab = 'dashboard';
let redirects = {
'/business-magician': 'https://pinkycollie.github.io/Business-Magician/',
'/docs': 'https://docs.deaffoundershub.com'
};
// Tab switching
function switchTab(tab) {
// Hide all content
document.querySelectorAll('[id^="content-"]').forEach(el => {
el.classList.add('hidden');
});
// Show selected content
document.getElementById(`content-${tab}`).classList.remove('hidden');
// Update tab styles
document.querySelectorAll('nav button').forEach(btn => {
btn.classList.remove('tab-active');
btn.classList.add('tab-inactive');
});
document.getElementById(`tab-${tab}`).classList.remove('tab-inactive');
document.getElementById(`tab-${tab}`).classList.add('tab-active');
currentTab = tab;
// Load data if needed
if (tab === 'redirects') loadRedirects();
if (tab === 'modules') loadModules();
if (tab === 'dashboard') loadStats();
}
// Load stats
async function loadStats() {
try {
const res = await fetch('/api/admin/stats');
const data = await res.json();
if (data.success) {
document.getElementById('uptime').textContent = Math.floor(data.data.uptime) + 's';
document.getElementById('platform').textContent = data.data.platform;
document.getElementById('arch').textContent = data.data.arch;
}
const configRes = await fetch('/api/admin/config');
const configData = await configRes.json();
if (configData.success) {
document.getElementById('node-version').textContent = configData.data.server.nodeVersion;
document.getElementById('env').textContent = configData.data.server.env;
document.getElementById('port').textContent = configData.data.server.port;
}
} catch (e) {
console.error('Failed to load stats:', e);
}
}
// Load redirects
async function loadRedirects() {
try {
const res = await fetch('/api/redirects');
const data = await res.json();
if (data.success) {
redirects = data.data;
renderRedirects();
}
} catch (e) {
renderRedirects(); // Use local fallback
}
}
function renderRedirects() {
const container = document.getElementById('redirects-list');
container.innerHTML = '';
for (const [from, to] of Object.entries(redirects)) {
const div = document.createElement('div');
div.className = 'flex items-center justify-between bg-gray-50 p-3 rounded';
div.innerHTML = `
<div class="flex items-center space-x-4">
<code class="bg-gray-200 px-2 py-1 rounded text-sm">${from}</code>
<i class="fas fa-arrow-right text-gray-400"></i>
<a href="${to}" target="_blank" class="text-indigo-600 hover:underline text-sm">
${to} <i class="fas fa-external-link-alt ml-1 text-xs"></i>
</a>
</div>
<button onclick="removeRedirect('${from}')" class="text-red-600 hover:text-red-800">
<i class="fas fa-trash"></i>
</button>
`;
container.appendChild(div);
}
}
// Add redirect
document.getElementById('redirect-form').addEventListener('submit', async (e) => {
e.preventDefault();
const from = document.getElementById('redirect-from').value;
const to = document.getElementById('redirect-to').value;
try {
const res = await fetch('/api/redirects', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ from, to })
});
const data = await res.json();
if (data.success) {
redirects[from] = to;
renderRedirects();
document.getElementById('redirect-form').reset();
}
} catch (e) {
// Fallback: just update UI
redirects[from] = to;
renderRedirects();
document.getElementById('redirect-form').reset();
}
});
function removeRedirect(from) {
delete redirects[from];
renderRedirects();
}
// Scaffold preview
function updateScaffoldPreview() {
const type = document.getElementById('scaffold-type').value;
const name = document.getElementById('scaffold-name').value || 'Example';
const previews = {
page: `<!-- ${name} Page -->
<!DOCTYPE html>
<html>
<head>
<title>${name} | DeafFounders Hub</title>
</head>
<body>
<h1>${name}</h1>
<a href="https://example.com" target="_blank">
Open External Link <i class="fas fa-external-link-alt"></i>
</a>
</body>
</html>`,
route: `// ${name} API Route
export const routes = {
'/api/${name.toLowerCase()}': {
GET: (req, res) => {
res.json({ success: true, data: [] });
}
}
};`,
component: `// ${name} Component
export class ${name}Component {
constructor(selector) {
this.element = document.querySelector(selector);
}
render(data) {
this.element.innerHTML = '<h2>' + data.title + '</h2>';
}
}`
};
document.getElementById('scaffold-preview').textContent = previews[type];
// Show/hide link field
document.getElementById('scaffold-link-container').style.display =
type === 'page' ? 'block' : 'none';
}
// Scaffold form
document.getElementById('scaffold-form').addEventListener('submit', async (e) => {
e.preventDefault();
const type = document.getElementById('scaffold-type').value;
const name = document.getElementById('scaffold-name').value;
const link = document.getElementById('scaffold-link').value;
try {
const res = await fetch('/api/scaffold', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ type, name, link })
});
const data = await res.json();
if (data.success) {
showScaffoldResult(data.files, name, type);
}
} catch (e) {
// Fallback
showScaffoldResult([
type === 'page' ? `public/${name.toLowerCase()}.html` : null
].filter(Boolean), name, type);
}
});
function showScaffoldResult(files, name, type) {
const resultDiv = document.getElementById('scaffold-result');
const filesList = document.getElementById('scaffold-files');
const viewLink = document.getElementById('scaffold-view-link');
filesList.innerHTML = '';
files.forEach(file => {
const li = document.createElement('li');
li.textContent = file;
filesList.appendChild(li);
});
if (type === 'page') {
viewLink.href = `/${name.toLowerCase()}.html`;
viewLink.classList.remove('hidden');
} else {
viewLink.classList.add('hidden');
}
resultDiv.classList.remove('hidden');
}
// Config form
document.getElementById('config-form').addEventListener('submit', async (e) => {
e.preventDefault();
const config = {
port: parseInt(document.getElementById('config-port').value),
env: document.getElementById('config-env').value,
cors: document.getElementById('config-cors').value
};
try {
const res = await fetch('/api/admin/config', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(config)
});
if (res.ok) {
document.getElementById('config-message').classList.remove('hidden');
setTimeout(() => {
document.getElementById('config-message').classList.add('hidden');
}, 3000);
}
} catch (e) {
document.getElementById('config-message').classList.remove('hidden');
}
});
// Load modules
async function loadModules() {
try {
const res = await fetch('/api/modules');
const data = await res.json();
if (data.success) {
const container = document.getElementById('modules-list');
container.innerHTML = '';
data.data.forEach(mod => {
const div = document.createElement('div');
div.className = 'border rounded p-4 flex items-center justify-between';
div.innerHTML = `
<div>
<h4 class="font-semibold">${mod.name}</h4>
<p class="text-sm text-gray-500">ID: ${mod.id}</p>
</div>
<span class="px-3 py-1 bg-green-100 text-green-800 text-sm rounded-full">
${mod.status}
</span>
`;
container.appendChild(div);
});
}
} catch (e) {
console.error('Failed to load modules:', e);
}
}
function resetConfig() {
document.getElementById('config-port').value = '3000';
document.getElementById('config-env').value = 'development';
document.getElementById('config-cors').value = '*';
}
function logout() {
localStorage.removeItem('admin_token');
window.location.reload();
}
// Initialize
document.addEventListener('DOMContentLoaded', () => {
loadStats();
updateScaffoldPreview();
});
</script>
</body>
</html>