Add federation management to the dashboard and update the federation page

Add new navigation item and page structure for federation management in dashboard.html, and update federation.html with new styling and content sections for explaining the federation concept and features.

Replit-Commit-Author: Agent
Replit-Commit-Session-Id: aed2e46d-25bb-4b73-81a1-bb9e8437c261
Replit-Commit-Checkpoint-Type: full_checkpoint
Replit-Commit-Event-Id: f96502b3-64e2-454a-a892-81ab2a2f62a3
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/3bdfff67-975a-46ad-9845-fbb6b4a4c4b5/aed2e46d-25bb-4b73-81a1-bb9e8437c261/MGpDued
Replit-Helium-Checkpoint-Created: true
This commit is contained in:
sirpiglr 2025-12-10 02:51:47 +00:00
parent 5a7168d9ac
commit 78a2a29bfc
3 changed files with 645 additions and 631 deletions

View file

@ -21,6 +21,10 @@ externalPort = 80
localPort = 8080
externalPort = 8080
[[ports]]
localPort = 34599
externalPort = 3000
[workflows]
runButton = "Project"

View file

@ -1171,6 +1171,9 @@
<div class="nav-item" data-page="admin-shop">
<span class="nav-icon">🏪</span> Manage Shop
</div>
<div class="nav-item" data-page="federation">
<span class="nav-icon">&#128737;</span> Federation
</div>
</div>
</nav>
@ -1708,6 +1711,114 @@
</form>
</div>
</div>
<div id="page-federation" class="page hidden">
<div class="page-header">
<h1 class="page-title"><span class="text-gradient">Federation</span> Management</h1>
<p class="page-subtitle">Manage your federation membership, view bans, and applications</p>
</div>
<div class="stats-grid" id="fedStatsGrid">
<div class="stat-card">
<div class="stat-label">Member Servers</div>
<div class="stat-value" id="fedTotalServers">-</div>
</div>
<div class="stat-card">
<div class="stat-label">Active Bans</div>
<div class="stat-value" id="fedActiveBans">-</div>
</div>
<div class="stat-card">
<div class="stat-label">Pending Applications</div>
<div class="stat-value" id="fedPendingApps">-</div>
</div>
</div>
<div class="tabs" style="margin-bottom:1.5rem">
<div class="tab active" data-fed-tab="fed-servers">Servers</div>
<div class="tab" data-fed-tab="fed-bans">Global Bans</div>
<div class="tab" data-fed-tab="fed-applications">Applications</div>
<div class="tab" data-fed-tab="fed-leaderboard">Leaderboard</div>
</div>
<div id="fed-servers" class="fed-section">
<div class="card">
<div class="card-header">
<h3 class="card-title">Federation Servers</h3>
</div>
<div class="card-body" id="fedServerGrid" style="display:grid;grid-template-columns:repeat(auto-fill,minmax(280px,1fr));gap:1rem">
<div class="empty-state">Loading servers...</div>
</div>
</div>
</div>
<div id="fed-bans" class="fed-section hidden">
<div class="card">
<div class="card-header">
<h3 class="card-title">Global Ban List</h3>
</div>
<div class="table-container">
<table class="table">
<thead>
<tr>
<th>User</th>
<th>Severity</th>
<th>Reason</th>
<th>Date</th>
</tr>
</thead>
<tbody id="fedBanList">
<tr><td colspan="4" class="empty-state">Loading bans...</td></tr>
</tbody>
</table>
</div>
</div>
</div>
<div id="fed-applications" class="fed-section hidden">
<div class="card">
<div class="card-header">
<h3 class="card-title">Pending Applications</h3>
</div>
<div class="table-container">
<table class="table">
<thead>
<tr>
<th>Server</th>
<th>Category</th>
<th>Members</th>
<th>Status</th>
<th>Actions</th>
</tr>
</thead>
<tbody id="fedAppList">
<tr><td colspan="5" class="empty-state">Loading applications...</td></tr>
</tbody>
</table>
</div>
</div>
</div>
<div id="fed-leaderboard" class="fed-section hidden">
<div class="card">
<div class="card-header">
<h3 class="card-title">Federation Reputation Leaders</h3>
</div>
<div class="card-body" id="fedLeaderboardList">
<div class="empty-state">Loading leaderboard...</div>
</div>
</div>
</div>
<div class="card" style="margin-top:2rem">
<div class="card-header">
<h3 class="card-title">Upgrade Protection</h3>
</div>
<div class="card-body" style="display:flex;gap:1rem;flex-wrap:wrap">
<a href="/pricing" class="btn btn-primary">View Pricing Plans</a>
<a href="/federation" class="btn btn-secondary">Learn About Federation</a>
</div>
</div>
</div>
</main>
</div>
@ -1885,6 +1996,7 @@
case 'admin-quests': await loadAdminQuests(); break;
case 'admin-achievements': await loadAdminAchievements(); break;
case 'admin-shop': await loadAdminShop(); break;
case 'federation': loadFederationData(); break;
}
}
@ -2591,6 +2703,180 @@
}
}
document.querySelectorAll('[data-fed-tab]').forEach(tab => {
tab.addEventListener('click', () => {
document.querySelectorAll('[data-fed-tab]').forEach(t => t.classList.remove('active'));
document.querySelectorAll('.fed-section').forEach(s => s.classList.add('hidden'));
tab.classList.add('active');
document.getElementById(tab.dataset.fedTab).classList.remove('hidden');
});
});
async function loadFederationStats() {
try {
const res = await fetch('/api/federation/stats');
const data = await res.json();
document.getElementById('fedTotalServers').textContent = data.totalServers || 0;
document.getElementById('fedActiveBans').textContent = data.activeBans || 0;
document.getElementById('fedPendingApps').textContent = data.pendingApplications || 0;
} catch (e) {
console.error('Failed to load federation stats:', e);
}
}
async function loadFederationServers() {
try {
const res = await fetch('/api/federation/servers');
const data = await res.json();
const grid = document.getElementById('fedServerGrid');
if (!data.servers || data.servers.length === 0) {
grid.innerHTML = '<div class="empty-state">No servers in the federation yet. Use /federation apply to join!</div>';
return;
}
const categoryEmojis = { gaming: '🎮', creative: '🎨', development: '💻', education: '📚', community: '👥', business: '🏢' };
grid.innerHTML = data.servers.map(s => `
<div class="card" style="padding:1rem">
<div style="display:flex;align-items:center;gap:0.75rem;margin-bottom:0.5rem">
<div style="width:40px;height:40px;background:var(--primary);border-radius:8px;display:flex;align-items:center;justify-content:center;font-size:1.25rem">${categoryEmojis[s.category] || '🌐'}</div>
<div>
<div style="font-weight:600">${s.guild_name}</div>
<div style="font-size:0.8rem;color:var(--muted)">${s.category || 'General'}</div>
</div>
</div>
<div style="font-size:0.85rem;color:var(--muted);margin-bottom:0.5rem">${s.description || 'No description'}</div>
<div style="font-size:0.8rem;color:var(--muted)">${(s.member_count || 0).toLocaleString()} members</div>
</div>
`).join('');
} catch (e) {
console.error('Failed to load federation servers:', e);
}
}
async function loadFederationBans() {
try {
const res = await fetch('/api/federation/bans?limit=50');
const data = await res.json();
const tbody = document.getElementById('fedBanList');
if (!data.bans || data.bans.length === 0) {
tbody.innerHTML = '<tr><td colspan="4" class="empty-state">No active bans</td></tr>';
return;
}
const severityColors = { low: 'var(--muted)', medium: 'var(--warning)', high: '#f97316', critical: 'var(--danger)' };
tbody.innerHTML = data.bans.map(b => `
<tr>
<td>${b.username || b.user_id}</td>
<td><span style="padding:0.25rem 0.5rem;border-radius:4px;font-size:0.75rem;background:${severityColors[b.severity] || 'var(--muted)'};color:white">${(b.severity || 'unknown').toUpperCase()}</span></td>
<td>${(b.reason || '').substring(0, 50)}${(b.reason || '').length > 50 ? '...' : ''}</td>
<td>${new Date(b.created_at).toLocaleDateString()}</td>
</tr>
`).join('');
} catch (e) {
console.error('Failed to load federation bans:', e);
}
}
async function loadFederationApplications() {
try {
const res = await fetch('/api/federation/applications');
const data = await res.json();
const tbody = document.getElementById('fedAppList');
if (!data.applications || data.applications.length === 0) {
tbody.innerHTML = '<tr><td colspan="5" class="empty-state">No applications</td></tr>';
return;
}
const statusColors = { pending: 'var(--warning)', approved: 'var(--success)', rejected: 'var(--danger)' };
tbody.innerHTML = data.applications.map(a => `
<tr>
<td>${a.guild_name}</td>
<td>${a.category || 'General'}</td>
<td>${(a.member_count || 0).toLocaleString()}</td>
<td><span style="padding:0.25rem 0.5rem;border-radius:4px;font-size:0.75rem;background:${statusColors[a.status] || 'var(--muted)'};color:white">${(a.status || 'pending').toUpperCase()}</span></td>
<td>
${a.status === 'pending' ? `
<button class="btn btn-primary" style="padding:0.25rem 0.5rem;font-size:0.75rem" onclick="approveFedApp(${a.id})">Approve</button>
<button class="btn btn-secondary" style="padding:0.25rem 0.5rem;font-size:0.75rem" onclick="rejectFedApp(${a.id})">Reject</button>
` : '-'}
</td>
</tr>
`).join('');
} catch (e) {
console.error('Failed to load federation applications:', e);
}
}
async function loadFederationLeaderboard() {
try {
const res = await fetch('/api/federation/leaderboard?limit=20');
const data = await res.json();
const container = document.getElementById('fedLeaderboardList');
if (!data.leaderboard || data.leaderboard.length === 0) {
container.innerHTML = '<div class="empty-state">No reputation data yet. Be active across federation servers!</div>';
return;
}
const tierEmojis = { newcomer: '🌱', member: '⭐', veteran: '🏆', elite: '💎', legend: '👑' };
container.innerHTML = data.leaderboard.map((l, i) => `
<div style="display:flex;align-items:center;gap:1rem;padding:0.75rem;border-bottom:1px solid var(--border)">
<div style="width:32px;height:32px;border-radius:50%;background:${i < 3 ? 'linear-gradient(135deg,var(--primary),#3b82f6)' : 'var(--secondary)'};display:flex;align-items:center;justify-content:center;font-weight:700;font-size:0.875rem">${i + 1}</div>
<div style="flex:1">
<div style="font-weight:500">${l.discord_id}</div>
<div style="font-size:0.8rem;color:var(--muted)">${tierEmojis[l.rank_tier] || '🌱'} ${(l.rank_tier || 'newcomer').toUpperCase()}</div>
</div>
<div style="font-weight:600;color:var(--primary)">${(l.reputation_score || 0).toLocaleString()} rep</div>
</div>
`).join('');
} catch (e) {
console.error('Failed to load federation leaderboard:', e);
}
}
async function approveFedApp(id) {
if (!confirm('Approve this application?')) return;
try {
await fetch('/api/federation/applications/' + id + '/approve', { method: 'POST' });
loadFederationApplications();
loadFederationStats();
loadFederationServers();
} catch (e) {
alert('Failed to approve');
}
}
async function rejectFedApp(id) {
const reason = prompt('Rejection reason:');
if (!reason) return;
try {
await fetch('/api/federation/applications/' + id + '/reject', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ reason })
});
loadFederationApplications();
loadFederationStats();
} catch (e) {
alert('Failed to reject');
}
}
function loadFederationData() {
loadFederationStats();
loadFederationServers();
loadFederationBans();
loadFederationApplications();
loadFederationLeaderboard();
}
init();
</script>
</body>

File diff suppressed because it is too large Load diff