Complete implementation of staggered restart scheduler for Trinity Console. Database: - global_restart_config: Node-wide settings (TX1 @ 04:00 UTC, NC1 @ 04:30 UTC) - server_restart_schedules: Per-server state with sort order - sync_logs: Audit trail for all sync operations Backend: - src/utils/scheduler.js: Stagger calculation with date-fns - src/lib/ptero-sync.js: Pterodactyl API integration (create/update/delete/audit) - src/routes/admin/scheduler.js: All CRUD + import + sync + audit routes Frontend: - Drag-and-drop server ordering (SortableJS) - Per-node config cards with base time + interval - Audit modal to detect and nuke rogue schedules - Skip toggle for maintenance mode - Visual sync status indicators Features: - Import servers from Pterodactyl discovery - Recalculate effective times on reorder - Rate-limited API calls (200ms delay) - [Trinity] Daily Restart naming convention Signed-off-by: Claude (Chronicler #61) <claude@firefrostgaming.com>
128 lines
6.4 KiB
Plaintext
128 lines
6.4 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>
|
|
<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;
|
|
}
|
|
}
|
|
</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">
|
|
<h1 class="text-2xl font-bold bg-gradient-to-r from-fire via-universal to-frost text-transparent bg-clip-text">Trinity Console</h1>
|
|
<!-- 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-2">
|
|
<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/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>
|
|
<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/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/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' %>">
|
|
⏰ Restart Scheduler
|
|
</a>
|
|
</nav>
|
|
<div class="p-4 border-t border-gray-200 dark:border-gray-700">
|
|
<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>
|
|
</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-4">
|
|
<button onclick="document.documentElement.classList.toggle('dark')" class="p-2 rounded-md hover:bg-gray-100 dark:hover:bg-gray-700">
|
|
🌙/☀️
|
|
</button>
|
|
<span class="relative">
|
|
🔔 <span class="absolute -top-1 -right-1 bg-fire text-white text-xs rounded-full h-4 w-4 flex items-center justify-center">0</span>
|
|
</span>
|
|
</div>
|
|
</header>
|
|
|
|
<div class="flex-1 overflow-auto p-6">
|
|
<%- body %>
|
|
</div>
|
|
</main>
|
|
</div>
|
|
</body>
|
|
</html>
|