Files
firefrost-services/services/arbiter-3.0/src/views/layout.ejs
Claude (Chronicler #83 - The Compiler) 166e4c8424 Task module: 7 UX features (detail panel, sort, filters, presets, kanban, badges)
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>
2026-04-14 12:13:41 -05:00

245 lines
14 KiB
Plaintext
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!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>