// Miroir Admin UI - SPA (function() { 'use strict'; // Configuration const API_BASE = '/_miroir'; const SESSION_CHECK_INTERVAL = 60000; // 1 minute // State let currentRoute = '/'; let sessionValid = false; // Router const routes = { '/': renderOverview, '/topology': renderTopology, '/indexes': renderIndexes, '/aliases': renderAliases, '/documents': renderDocuments, '/tasks': renderTasks, '/settings': renderSettings }; // Initialize function init() { checkSession(); setupEventListeners(); setupRouter(); loadVersion(); setInterval(checkSession, SESSION_CHECK_INTERVAL); } // Session management async function checkSession() { try { const response = await fetch(`${API_BASE}/admin/session`, { credentials: 'include' }); if (response.ok) { const data = await response.json(); sessionValid = data.valid; if (!sessionValid) { window.location.href = '/_miroir/admin/login.html'; return false; } return true; } else { window.location.href = '/_miroir/admin/login.html'; return false; } } catch (error) { console.error('Session check failed:', error); window.location.href = '/_miroir/admin/login.html'; return false; } } // API helper async function apiFetch(endpoint, options = {}) { const url = endpoint.startsWith('/') ? `${API_BASE}${endpoint}` : endpoint; const response = await fetch(url, { ...options, credentials: 'include', headers: { 'Content-Type': 'application/json', ...options.headers } }); if (!response.ok) { throw new Error(`API error: ${response.status} ${response.statusText}`); } return response.json(); } // Router setup function setupRouter() { window.addEventListener('hashchange', handleRoute); handleRoute(); } async function handleRoute() { const hash = window.location.hash.slice(1) || '/'; const [path, queryString] = hash.split('?'); const route = routes[path] || routes['/']; if (route) { currentRoute = path; await route(); updateNav(); } } function updateNav() { document.querySelectorAll('.nav-link').forEach(link => { const href = link.getAttribute('href'); if (href === `#${currentRoute}`) { link.classList.add('active'); } else { link.classList.remove('active'); } }); } // Event listeners function setupEventListeners() { document.getElementById('logoutBtn').addEventListener('click', handleLogout); } async function handleLogout() { try { await apiFetch('/admin/logout', { method: 'POST' }); window.location.href = '/_miroir/admin/login.html'; } catch (error) { console.error('Logout failed:', error); } } async function loadVersion() { try { const data = await apiFetch('/metrics'); document.getElementById('version').textContent = data.version || ''; } catch (error) { console.error('Failed to load version:', error); } } // Render functions async function renderOverview() { const content = document.getElementById('content'); try { const [topology, ready, metrics] = await Promise.all([ apiFetch('/topology'), apiFetch('/ready'), apiFetch('/metrics') ]); const degradedCount = topology.shards.filter(s => !s.healthy).length; content.innerHTML = `
Replica Groups: ${topology.replica_groups}
Replication Factor: ${topology.replication_factor}
Ready: ${ready.ready ? 'Yes' : 'No'}
| Node ID | Address | Group | Status |
|---|
| Name | Documents | Indexing | Actions |
|---|
| Name | Target Index | Kind | Created | Updated |
|---|
Select an index from the Indexes page to view documents.
Document count: ${indexStats?.numberOfDocuments || 0}
Document browser is a placeholder - full implementation would query documents from the index.
| UID | Status | Type | Enqueued |
|---|
Shards: ${topology.shards}
Replica Groups: ${topology.replica_groups}
Replication Factor: ${topology.replication_factor}
Settings management is a placeholder - full implementation would allow editing cluster configuration.