feat: auto-discovery

This commit is contained in:
2025-08-25 12:05:51 +02:00
parent f72e4ba220
commit 5977a37d6c
8 changed files with 552 additions and 3 deletions

View File

@@ -23,6 +23,48 @@ class FrontendApiClient {
}
}
async getDiscoveryInfo() {
try {
const response = await fetch('/api/discovery/nodes', {
method: 'GET',
headers: {
'Accept': 'application/json'
}
});
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
return await response.json();
} catch (error) {
throw new Error(`Request failed: ${error.message}`);
}
}
async selectRandomPrimaryNode() {
try {
const response = await fetch('/api/discovery/random-primary', {
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
body: JSON.stringify({
timestamp: new Date().toISOString()
})
});
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
return await response.json();
} catch (error) {
throw new Error(`Request failed: ${error.message}`);
}
}
async getNodeStatus(ip) {
try {
// Create a proxy endpoint that forwards the request to the specific node
@@ -66,6 +108,83 @@ class FrontendApiClient {
// Global client instance
const client = new FrontendApiClient();
// Function to update primary node display
async function updatePrimaryNodeDisplay() {
const primaryNodeElement = document.getElementById('primary-node-ip');
if (!primaryNodeElement) return;
try {
// Set discovering state
primaryNodeElement.textContent = 'Discovering...';
primaryNodeElement.className = 'primary-node-ip discovering';
const discoveryInfo = await client.getDiscoveryInfo();
if (discoveryInfo.primaryNode) {
const status = discoveryInfo.clientInitialized ? '✅' : '⚠️';
const nodeCount = discoveryInfo.totalNodes > 1 ? ` (${discoveryInfo.totalNodes} nodes)` : '';
primaryNodeElement.textContent = `${status} ${discoveryInfo.primaryNode}${nodeCount}`;
primaryNodeElement.className = 'primary-node-ip';
} else if (discoveryInfo.totalNodes > 0) {
// If we have nodes but no primary, show the first one
const firstNode = discoveryInfo.nodes[0];
primaryNodeElement.textContent = `⚠️ ${firstNode.ip} (No Primary)`;
primaryNodeElement.className = 'primary-node-ip error';
} else {
primaryNodeElement.textContent = '🔍 No Nodes Found';
primaryNodeElement.className = 'primary-node-ip error';
}
} catch (error) {
console.error('Failed to fetch discovery info:', error);
primaryNodeElement.textContent = '❌ Discovery Failed';
primaryNodeElement.className = 'primary-node-ip error';
}
}
// Function to randomly select a new primary node
async function selectRandomPrimaryNode() {
const primaryNodeElement = document.getElementById('primary-node-ip');
if (!primaryNodeElement) return;
try {
// Store current primary node for reference
const currentText = primaryNodeElement.textContent;
const currentPrimary = currentText.includes('✅') || currentText.includes('⚠️') ?
currentText.split(' ')[1] : 'unknown';
// Set selecting state
primaryNodeElement.textContent = '🎲 Selecting...';
primaryNodeElement.className = 'primary-node-ip selecting';
// Call the random selection API
const result = await client.selectRandomPrimaryNode();
if (result.success) {
// Show success message briefly
primaryNodeElement.textContent = `🎯 ${result.primaryNode}`;
primaryNodeElement.className = 'primary-node-ip';
// Update the display after a short delay
setTimeout(() => {
updatePrimaryNodeDisplay();
}, 1500);
console.log(`Randomly selected new primary node: ${result.primaryNode}`);
} else {
throw new Error(result.message || 'Random selection failed');
}
} catch (error) {
console.error('Failed to select random primary node:', error);
primaryNodeElement.textContent = '❌ Selection Failed';
primaryNodeElement.className = 'primary-node-ip error';
// Revert to normal display after error
setTimeout(() => {
updatePrimaryNodeDisplay();
}, 2000);
}
}
// Function to refresh cluster members
async function refreshClusterMembers() {
const container = document.getElementById('cluster-members-container');
@@ -97,6 +216,9 @@ async function refreshClusterMembers() {
const response = await client.getClusterMembers();
console.log(response);
displayClusterMembers(response.members, expandedCards);
// Update primary node display after cluster refresh
await updatePrimaryNodeDisplay();
} catch (error) {
console.error('Failed to fetch cluster members:', error);
container.innerHTML = `
@@ -105,6 +227,9 @@ async function refreshClusterMembers() {
${error.message}
</div>
`;
// Still try to update primary node display even if cluster fails
await updatePrimaryNodeDisplay();
}
}
@@ -533,8 +658,12 @@ function displayClusterMembers(members, expandedCards = new Map()) {
// Load cluster members when page loads
document.addEventListener('DOMContentLoaded', function() {
refreshClusterMembers();
updatePrimaryNodeDisplay(); // Also update primary node display
setupNavigation();
setupFirmwareView();
// Set up periodic primary node updates (every 10 seconds)
setInterval(updatePrimaryNodeDisplay, 10000);
});
// Auto-refresh every 30 seconds