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>
53 lines
2.8 KiB
Plaintext
53 lines
2.8 KiB
Plaintext
<table class="w-full text-left">
|
|
<thead class="bg-gray-100 dark:bg-darkbg text-gray-600 dark:text-gray-300">
|
|
<tr>
|
|
<th class="p-3 w-10"></th>
|
|
<th class="p-3">Server</th>
|
|
<th class="p-3">Node</th>
|
|
<th class="p-3">Restart Time (UTC)</th>
|
|
<th class="p-3">Status</th>
|
|
<th class="p-3">Skip</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody id="sortable-servers">
|
|
<% if (servers.length === 0) { %>
|
|
<tr>
|
|
<td colspan="6" class="p-6 text-center text-gray-500">
|
|
No servers imported yet. Click "Import Servers" to populate from Pterodactyl.
|
|
</td>
|
|
</tr>
|
|
<% } else { %>
|
|
<% servers.forEach((server, i) => { %>
|
|
<tr class="border-t border-gray-200 dark:border-gray-700 hover:bg-gray-50 dark:hover:bg-gray-800 transition" data-id="<%= server.server_id %>">
|
|
<td class="p-3 cursor-grab text-gray-400 hover:text-gray-900 dark:hover:text-white">
|
|
<span class="drag-handle text-lg">☰</span>
|
|
</td>
|
|
<td class="p-3 font-medium"><%= server.server_name %></td>
|
|
<td class="p-3">
|
|
<span class="px-2 py-1 rounded text-xs font-bold <%= server.node === 'TX1' ? 'bg-fire/20 text-fire' : 'bg-frost/20 text-frost' %>">
|
|
<%= server.node %>
|
|
</span>
|
|
</td>
|
|
<td class="p-3 font-mono text-sm"><%= server.effective_time || 'Not set' %></td>
|
|
<td class="p-3 text-sm">
|
|
<% if (server.sync_status === 'SUCCESS') { %>
|
|
<span class="text-green-500" title="Last synced: <%= server.last_synced_at %>">● Synced</span>
|
|
<% } else if (server.sync_status === 'FAILED') { %>
|
|
<span class="text-red-500" title="<%= server.last_error %>">✕ Error</span>
|
|
<% } else { %>
|
|
<span class="text-yellow-500">○ Pending</span>
|
|
<% } %>
|
|
</td>
|
|
<td class="p-3">
|
|
<button hx-post="/admin/scheduler/toggle-skip/<%= server.server_id %>"
|
|
hx-swap="none"
|
|
hx-on::after-request="htmx.ajax('GET', '/admin/scheduler/table-only', '#scheduler-table')"
|
|
class="px-2 py-1 rounded text-xs <%= server.skip_restart ? 'bg-red-600 text-white' : 'bg-gray-200 dark:bg-gray-700 text-gray-600 dark:text-gray-300' %>">
|
|
<%= server.skip_restart ? 'Skipped' : 'Active' %>
|
|
</button>
|
|
</td>
|
|
</tr>
|
|
<% }) %>
|
|
<% } %>
|
|
</tbody>
|
|
</table> |