Files
firefrost-services/services/arbiter-3.0/src/views/layout.ejs
Claude Chronicler #88 a01d7b9d7f feat: Node Health module — NC1 + TX1 thermal and system monitoring
- New route: /admin/node-health (30s auto-refresh)
- Temps via lm-sensors (k10temp + NVMe) displayed in both °C and °F
- RAM and disk progress bars with color thresholds
- Load averages, CPU %, uptime per node
- Nav item added under Operations
- lm-sensors installed on NC1 and TX1

Task #28 | Chronicler #88
2026-04-14 06:21:38 +00:00

227 lines
13 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>
<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="block 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' %>">
📋 Tasks
</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>