1. Click-to-open slide-out detail panel with full task info 2. Client-side sorting (number, priority, status, updated) with localStorage 3. Toggleable filter chips for status and priority 4. Saved filter presets (Launch Fires, Code Queue, Post-Launch, All Open) 5. Kanban board view with 4 columns (Open, In Progress, Blocked, Done) 6. Session summary badge showing tasks completed today 7. Code queue badge in sidebar nav (cyan count from tags) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
245 lines
14 KiB
Plaintext
245 lines
14 KiB
Plaintext
<!DOCTYPE html>
|
||
<html lang="en" class="dark">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<title><%= title %> | Trinity Console</title>
|
||
<!-- PWA -->
|
||
<link rel="manifest" href="/manifest.json">
|
||
<meta name="theme-color" content="#06b6d4">
|
||
<meta name="apple-mobile-web-app-capable" content="yes">
|
||
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
|
||
<meta name="apple-mobile-web-app-title" content="Trinity">
|
||
<link rel="apple-touch-icon" href="/images/trinity-icon-192.png">
|
||
<!-- Service Worker Registration -->
|
||
<script>
|
||
if ('serviceWorker' in navigator) {
|
||
window.addEventListener('load', () => {
|
||
navigator.serviceWorker.register('/sw.js').catch(err => console.log('SW registration failed:', err));
|
||
});
|
||
}
|
||
</script>
|
||
<script src="https://cdn.tailwindcss.com"></script>
|
||
<script src="https://unpkg.com/htmx.org@1.9.11"></script>
|
||
<script>
|
||
// Configure htmx to include CSRF token in all requests
|
||
document.addEventListener('DOMContentLoaded', function() {
|
||
document.body.addEventListener('htmx:configRequest', function(evt) {
|
||
evt.detail.headers['CSRF-Token'] = '<%= csrfToken %>';
|
||
});
|
||
});
|
||
</script>
|
||
<script>
|
||
tailwind.config = {
|
||
darkMode: 'class',
|
||
theme: {
|
||
extend: {
|
||
colors: {
|
||
fire: '#FF6B35',
|
||
frost: '#4ECDC4',
|
||
universal: '#A855F7',
|
||
darkbg: '#1a1a1a',
|
||
darkcard: '#2d2d2d'
|
||
}
|
||
}
|
||
}
|
||
}
|
||
</script>
|
||
<style>
|
||
/* Mobile: Hide sidebar by default, show on toggle */
|
||
@media (max-width: 768px) {
|
||
#sidebar {
|
||
position: fixed;
|
||
left: -100%;
|
||
transition: left 0.3s ease;
|
||
z-index: 50;
|
||
height: 100vh;
|
||
}
|
||
#sidebar.open {
|
||
left: 0;
|
||
}
|
||
#sidebar-overlay {
|
||
display: none;
|
||
position: fixed;
|
||
inset: 0;
|
||
background: rgba(0,0,0,0.5);
|
||
z-index: 40;
|
||
}
|
||
#sidebar-overlay.open {
|
||
display: block;
|
||
}
|
||
}
|
||
.nav-group { transition: all 0.2s ease; overflow: hidden; }
|
||
.nav-collapsed { max-height: 0; opacity: 0; margin: 0; padding: 0; }
|
||
</style>
|
||
</head>
|
||
<body class="bg-gray-100 dark:bg-darkbg text-gray-900 dark:text-gray-100 font-sans antialiased transition-colors duration-200">
|
||
<!-- Mobile menu overlay -->
|
||
<div id="sidebar-overlay" onclick="document.getElementById('sidebar').classList.remove('open'); this.classList.remove('open');"></div>
|
||
|
||
<div class="flex h-screen overflow-hidden">
|
||
<aside id="sidebar" class="w-64 bg-white dark:bg-darkcard border-r border-gray-200 dark:border-gray-700 flex flex-col">
|
||
<div class="p-6 flex justify-between items-center">
|
||
<div>
|
||
<h1 class="text-2xl font-bold bg-gradient-to-r from-fire via-universal to-frost text-transparent bg-clip-text">Trinity Console</h1>
|
||
<span class="text-xs text-gray-500 dark:text-gray-400">v1.0</span>
|
||
</div>
|
||
<!-- Mobile close button -->
|
||
<button onclick="document.getElementById('sidebar').classList.remove('open'); document.getElementById('sidebar-overlay').classList.remove('open');" class="md:hidden text-2xl">✕</button>
|
||
</div>
|
||
<nav class="flex-1 px-4 space-y-1 overflow-y-auto">
|
||
<!-- The Forge — proud and loud at the top -->
|
||
<a href="/admin/forge" class="block px-4 py-3 rounded-lg mb-2 bg-gradient-to-r from-fire/10 via-universal/10 to-frost/10 border border-universal/30 <%= currentPath.startsWith('/forge') ? 'ring-2 ring-universal' : 'hover:border-universal/60' %> transition">
|
||
<span class="text-base font-bold bg-gradient-to-r from-fire via-universal to-frost text-transparent bg-clip-text">🔥 The Forge</span>
|
||
</a>
|
||
|
||
<!-- Core -->
|
||
<button onclick="toggleNav('nav-core')" class="w-full flex items-center justify-between text-[10px] uppercase tracking-wider text-gray-500 dark:text-gray-400 font-semibold px-4 pt-3 pb-1 hover:text-gray-700 dark:hover:text-gray-300 transition">
|
||
<span>Core</span>
|
||
<span id="nav-core-arrow" class="text-xs transition-transform">▼</span>
|
||
</button>
|
||
<div id="nav-core" class="nav-group">
|
||
<a href="/admin/dashboard" class="block px-4 py-2 rounded-md <%= currentPath === '/dashboard' ? 'bg-gray-200 dark:bg-gray-700' : 'hover:bg-gray-100 dark:hover:bg-gray-800' %>">
|
||
📊 Dashboard
|
||
</a>
|
||
<a href="/admin/tasks" class="flex items-center justify-between px-4 py-2 rounded-md <%= currentPath.startsWith('/tasks') ? 'bg-gray-200 dark:bg-gray-700' : 'hover:bg-gray-100 dark:hover:bg-gray-800' %>">
|
||
<span>📋 Tasks</span>
|
||
<% if (typeof codeQueueCount !== 'undefined' && codeQueueCount > 0) { %>
|
||
<span class="inline-flex items-center justify-center w-5 h-5 text-[10px] font-bold text-white bg-cyan-500 rounded-full"><%= codeQueueCount %></span>
|
||
<% } %>
|
||
</a>
|
||
<a href="/admin/servers" class="block px-4 py-2 rounded-md <%= currentPath.startsWith('/servers') ? 'bg-gray-200 dark:bg-gray-700' : 'hover:bg-gray-100 dark:hover:bg-gray-800' %>">
|
||
🖥️ Servers
|
||
</a>
|
||
<a href="/admin/players" class="block px-4 py-2 rounded-md <%= currentPath.startsWith('/players') ? 'bg-gray-200 dark:bg-gray-700' : 'hover:bg-gray-100 dark:hover:bg-gray-800' %>">
|
||
👥 Players
|
||
</a>
|
||
</div>
|
||
|
||
<!-- Revenue -->
|
||
<button onclick="toggleNav('nav-revenue')" class="w-full flex items-center justify-between text-[10px] uppercase tracking-wider text-gray-500 dark:text-gray-400 font-semibold px-4 pt-3 pb-1 hover:text-gray-700 dark:hover:text-gray-300 transition">
|
||
<span>Revenue</span>
|
||
<span id="nav-revenue-arrow" class="text-xs transition-transform">▼</span>
|
||
</button>
|
||
<div id="nav-revenue" class="nav-group">
|
||
<a href="/admin/financials" class="block px-4 py-2 rounded-md <%= currentPath.startsWith('/financials') ? 'bg-gray-200 dark:bg-gray-700' : 'hover:bg-gray-100 dark:hover:bg-gray-800' %>">
|
||
💰 Financials
|
||
</a>
|
||
<a href="/admin/grace" class="block px-4 py-2 rounded-md <%= currentPath.startsWith('/grace') ? 'bg-gray-200 dark:bg-gray-700' : 'hover:bg-gray-100 dark:hover:bg-gray-800' %>">
|
||
⏳ Grace Period
|
||
</a>
|
||
<a href="/admin/appeals" class="block px-4 py-2 rounded-md <%= currentPath.startsWith('/appeals') ? 'bg-gray-200 dark:bg-gray-700' : 'hover:bg-gray-100 dark:hover:bg-gray-800' %>">
|
||
⚖️ Appeals
|
||
</a>
|
||
</div>
|
||
|
||
<!-- Community -->
|
||
<button onclick="toggleNav('nav-community')" class="w-full flex items-center justify-between text-[10px] uppercase tracking-wider text-gray-500 dark:text-gray-400 font-semibold px-4 pt-3 pb-1 hover:text-gray-700 dark:hover:text-gray-300 transition">
|
||
<span>Community</span>
|
||
<span id="nav-community-arrow" class="text-xs transition-transform">▼</span>
|
||
</button>
|
||
<div id="nav-community" class="nav-group">
|
||
<a href="/admin/discord" class="block px-4 py-2 rounded-md <%= currentPath.startsWith('/discord') ? 'bg-gray-200 dark:bg-gray-700' : 'hover:bg-gray-100 dark:hover:bg-gray-800' %>">
|
||
💬 Discord
|
||
</a>
|
||
<a href="/admin/social" class="block px-4 py-2 rounded-md <%= (currentPath === '/social' || (currentPath.startsWith('/social') && !currentPath.startsWith('/social-calendar'))) ? 'bg-gray-200 dark:bg-gray-700' : 'hover:bg-gray-100 dark:hover:bg-gray-800' %>">
|
||
📈 Social Analytics
|
||
</a>
|
||
<a href="/admin/social-calendar" class="block px-4 py-2 rounded-md <%= currentPath.startsWith('/social-calendar') ? 'bg-gray-200 dark:bg-gray-700' : 'hover:bg-gray-100 dark:hover:bg-gray-800' %>">
|
||
📅 Social Calendar
|
||
</a>
|
||
</div>
|
||
|
||
<!-- Operations -->
|
||
<button onclick="toggleNav('nav-ops')" class="w-full flex items-center justify-between text-[10px] uppercase tracking-wider text-gray-500 dark:text-gray-400 font-semibold px-4 pt-3 pb-1 hover:text-gray-700 dark:hover:text-gray-300 transition">
|
||
<span>Operations</span>
|
||
<span id="nav-ops-arrow" class="text-xs transition-transform">▼</span>
|
||
</button>
|
||
<div id="nav-ops" class="nav-group">
|
||
<a href="/admin/infrastructure" class="block px-4 py-2 rounded-md <%= currentPath.startsWith('/infrastructure') ? 'bg-gray-200 dark:bg-gray-700' : 'hover:bg-gray-100 dark:hover:bg-gray-800' %>">
|
||
🌐 Infrastructure
|
||
</a>
|
||
<a href="/admin/node-health" class="block px-4 py-2 rounded-md <%= currentPath.startsWith('/node-health') ? 'bg-gray-200 dark:bg-gray-700' : 'hover:bg-gray-100 dark:hover:bg-gray-800' %>">
|
||
🌡️ Node Health
|
||
</a>
|
||
<a href="/admin/scheduler" class="block px-4 py-2 rounded-md <%= currentPath.startsWith('/scheduler') ? 'bg-gray-200 dark:bg-gray-700' : 'hover:bg-gray-100 dark:hover:bg-gray-800' %>">
|
||
⏰ Scheduler
|
||
</a>
|
||
<a href="/admin/audit" class="block px-4 py-2 rounded-md <%= currentPath.startsWith('/audit') ? 'bg-gray-200 dark:bg-gray-700' : 'hover:bg-gray-100 dark:hover:bg-gray-800' %>">
|
||
📋 Audit Log
|
||
</a>
|
||
<a href="/admin/roles" class="block px-4 py-2 rounded-md <%= currentPath.startsWith('/roles') ? 'bg-gray-200 dark:bg-gray-700' : 'hover:bg-gray-100 dark:hover:bg-gray-800' %>">
|
||
🔍 Role Audit
|
||
</a>
|
||
<a href="/admin/mcp-logs" class="block px-4 py-2 rounded-md <%= currentPath.startsWith('/mcp-logs') ? 'bg-gray-200 dark:bg-gray-700' : 'hover:bg-gray-100 dark:hover:bg-gray-800' %>">
|
||
🖥️ MCP Logs
|
||
</a>
|
||
</div>
|
||
</nav>
|
||
<div class="p-4 border-t border-gray-200 dark:border-gray-700">
|
||
<!-- User Info -->
|
||
<div class="flex items-center justify-between">
|
||
<div class="flex items-center gap-3">
|
||
<img src="https://cdn.discordapp.com/avatars/<%= adminUser.id %>/<%= adminUser.avatar %>.png" class="w-10 h-10 rounded-full">
|
||
<span class="font-medium"><%= adminUser.username %></span>
|
||
</div>
|
||
<a href="/auth/logout" class="text-gray-400 hover:text-red-500 transition" title="Logout">
|
||
🚪
|
||
</a>
|
||
</div>
|
||
</div>
|
||
|
||
</aside>
|
||
|
||
<main class="flex-1 flex flex-col overflow-hidden">
|
||
<header class="bg-white dark:bg-darkcard border-b border-gray-200 dark:border-gray-700 h-16 flex items-center justify-between px-6">
|
||
<div class="flex items-center gap-4">
|
||
<!-- Mobile hamburger menu -->
|
||
<button onclick="document.getElementById('sidebar').classList.add('open'); document.getElementById('sidebar-overlay').classList.add('open');" class="md:hidden text-2xl">☰</button>
|
||
<h2 class="text-xl font-semibold"><%= title %></h2>
|
||
</div>
|
||
<div class="flex items-center gap-2">
|
||
<a href="/admin/about" class="p-2 rounded-md hover:bg-gray-100 dark:hover:bg-gray-700 transition <%= currentPath === '/about' ? 'bg-gray-200 dark:bg-gray-700' : '' %>" title="About Trinity Console">
|
||
ℹ️
|
||
</a>
|
||
<button onclick="document.documentElement.classList.toggle('dark')" class="p-2 rounded-md hover:bg-gray-100 dark:hover:bg-gray-700">
|
||
🌙/☀️
|
||
</button>
|
||
</div>
|
||
</header>
|
||
|
||
<div class="flex-1 overflow-auto p-6">
|
||
<%- body %>
|
||
</div>
|
||
</main>
|
||
</div>
|
||
<script>
|
||
function toggleNav(id) {
|
||
const el = document.getElementById(id);
|
||
const arrow = document.getElementById(id + '-arrow');
|
||
const isHidden = el.classList.contains('nav-collapsed');
|
||
if (isHidden) {
|
||
el.classList.remove('nav-collapsed');
|
||
arrow.style.transform = 'rotate(0deg)';
|
||
localStorage.setItem(id, 'open');
|
||
} else {
|
||
el.classList.add('nav-collapsed');
|
||
arrow.style.transform = 'rotate(-90deg)';
|
||
localStorage.setItem(id, 'closed');
|
||
}
|
||
}
|
||
document.addEventListener('DOMContentLoaded', function() {
|
||
['nav-core', 'nav-revenue', 'nav-community', 'nav-ops'].forEach(function(id) {
|
||
var state = localStorage.getItem(id);
|
||
if (state === 'closed') {
|
||
var el = document.getElementById(id);
|
||
var arrow = document.getElementById(id + '-arrow');
|
||
if (el) { el.classList.add('nav-collapsed'); }
|
||
if (arrow) { arrow.style.transform = 'rotate(-90deg)'; }
|
||
}
|
||
});
|
||
});
|
||
</script>
|
||
</body>
|
||
</html>
|